@jungjaehoon/mama-core 2.4.0 → 4.0.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 (374) hide show
  1. package/README.md +76 -239
  2. package/db/migrations/030-case-first-memory-substrate.sql +13 -98
  3. package/db/migrations/034-add-connector-event-scope-columns.sql +9 -13
  4. package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +9 -95
  5. package/db/migrations/062-refresh-connector-event-sequences.sql +9 -113
  6. package/db/migrations/063-refresh-legacy-connector-event-updates.sql +9 -32
  7. package/db/migrations/067-add-connector-event-source-entity-id.sql +9 -13
  8. package/db/migrations/068-tool-trace-diagnostics.sql +13 -0
  9. package/db/migrations/069-create-registry-nodes.sql +52 -0
  10. package/db/migrations/070-record-identity.sql +35 -0
  11. package/db/migrations/071-service-operation-origins.sql +187 -0
  12. package/db/migrations/072-observation-versions.sql +29 -0
  13. package/db/migrations/073-registry-corrections.sql +45 -0
  14. package/db/migrations/074-work-graph-ref-kinds.sql +39 -0
  15. package/db/migrations/075-agent-judgments.sql +58 -0
  16. package/db/migrations/076-scoped-checkpoint-bindings.sql +15 -0
  17. package/db/migrations/077-legacy-record-kind.sql +6 -0
  18. package/db/migrations/078-source-ingest-commands.sql +18 -0
  19. package/db/migrations/079-twin-edge-relations.sql +43 -0
  20. package/db/migrations/080-restore-duration-days.sql +7 -0
  21. package/db/migrations/081-mailbox.sql +61 -0
  22. package/db/migrations/082-commitments-row-id.sql +12 -0
  23. package/db/migrations/083-observation-is-the-evidence.sql +49 -0
  24. package/db/migrations/084-scope-kind-is-not-the-cores-to-list.sql +34 -0
  25. package/db/migrations/085-a-migration-has-a-source.sql +37 -0
  26. package/db/migrations/086-the-mailbox-is-generic.sql +110 -0
  27. package/db/migrations/087-mailbox-payload.sql +5 -0
  28. package/db/migrations/088-native-input-journal.sql +14 -0
  29. package/db/migrations/089-native-turn-receipt-lookup.sql +3 -0
  30. package/db/migrations/090-model-run-native-inputs.sql +29 -0
  31. package/db/migrations/091-native-turn-results.sql +11 -0
  32. package/db/migrations/092-restore-decision-fts.sql +34 -0
  33. package/db/migrations/093-native-turn-primary-kind.sql +14 -0
  34. package/db/migrations/094-commitment-run-provenance.sql +9 -0
  35. package/db/migrations/095-model-run-native-input-identity.sql +39 -0
  36. package/db/migrations/096-drop-connector-event-index-fts.sql +9 -0
  37. package/db/migrations/097-native-input-json-validity.sql +37 -0
  38. package/db/migrations/098-workflow-memory-kind.sql +105 -0
  39. package/dist/action-contracts.d.ts +212 -0
  40. package/dist/action-contracts.js +10 -0
  41. package/dist/api/catalog.d.ts +118 -0
  42. package/dist/api/catalog.js +1756 -0
  43. package/dist/api/dispatch.d.ts +76 -0
  44. package/dist/api/dispatch.js +372 -0
  45. package/dist/canonicalize.d.ts +2 -2
  46. package/dist/canonicalize.js +3 -3
  47. package/dist/client/client.d.ts +33 -0
  48. package/dist/client/client.js +123 -0
  49. package/dist/client/ipc.d.ts +81 -0
  50. package/dist/client/ipc.js +314 -0
  51. package/dist/db-adapter/base-adapter.d.ts +6 -66
  52. package/dist/db-adapter/base-adapter.js +6 -6
  53. package/dist/db-adapter/index.d.ts +5 -5
  54. package/dist/db-adapter/index.js +2 -5
  55. package/dist/db-adapter/node-sqlite-adapter.d.ts +87 -15
  56. package/dist/db-adapter/node-sqlite-adapter.js +2045 -568
  57. package/dist/db-manager.d.ts +53 -97
  58. package/dist/db-manager.js +121 -603
  59. package/dist/embedding/embedder.d.ts +132 -0
  60. package/dist/embedding/embedder.js +299 -0
  61. package/dist/identity/principal-repository.d.ts +2 -2
  62. package/dist/index.d.ts +43 -58
  63. package/dist/index.js +99 -145
  64. package/dist/{context-compile/channel-grant.d.ts → knowledge/access.d.ts} +12 -17
  65. package/dist/knowledge/access.js +663 -0
  66. package/dist/knowledge/case-errors.d.ts +31 -0
  67. package/dist/knowledge/case-errors.js +40 -0
  68. package/dist/{cases/search-rollup.d.ts → knowledge/case-search-rollup.d.ts} +5 -4
  69. package/dist/{cases/search-rollup.js → knowledge/case-search-rollup.js} +6 -8
  70. package/dist/{cases/store.d.ts → knowledge/case-store.d.ts} +3 -6
  71. package/dist/{cases/store.js → knowledge/case-store.js} +7 -42
  72. package/dist/{cases/timeline-range.d.ts → knowledge/case-timeline-range.d.ts} +3 -3
  73. package/dist/{cases/timeline-range.js → knowledge/case-timeline-range.js} +47 -25
  74. package/dist/{cases/types.d.ts → knowledge/case-types.d.ts} +8 -2
  75. package/dist/{cases/types.js → knowledge/case-types.js} +1 -1
  76. package/dist/knowledge/commitments.d.ts +188 -0
  77. package/dist/knowledge/commitments.js +370 -0
  78. package/dist/knowledge/decision-edges.d.ts +70 -0
  79. package/dist/knowledge/decision-edges.js +123 -0
  80. package/dist/{agent-graph/types.d.ts → knowledge/graph-query.d.ts} +80 -70
  81. package/dist/knowledge/graph-query.js +1585 -0
  82. package/dist/knowledge/identity.d.ts +16 -0
  83. package/dist/knowledge/identity.js +22 -0
  84. package/dist/knowledge/index.d.ts +47 -0
  85. package/dist/knowledge/index.js +90 -0
  86. package/dist/knowledge/judgments.d.ts +118 -0
  87. package/dist/knowledge/judgments.js +808 -0
  88. package/dist/knowledge/observations.d.ts +160 -0
  89. package/dist/knowledge/observations.js +449 -0
  90. package/dist/knowledge/question-type.d.ts +34 -0
  91. package/dist/knowledge/question-type.js +70 -0
  92. package/dist/{search → knowledge}/ranker-features.js +1 -1
  93. package/dist/{search → knowledge}/search-quality.d.ts +0 -1
  94. package/dist/knowledge/search.d.ts +38 -0
  95. package/dist/knowledge/search.js +91 -0
  96. package/dist/knowledge/source-ingest.d.ts +65 -0
  97. package/dist/knowledge/source-ingest.js +174 -0
  98. package/dist/{edges/types.d.ts → knowledge/twin-edge-types.d.ts} +31 -4
  99. package/dist/{edges/types.js → knowledge/twin-edge-types.js} +13 -2
  100. package/dist/knowledge/work-dates.d.ts +49 -0
  101. package/dist/knowledge/work-dates.js +120 -0
  102. package/dist/mama-api.d.ts +116 -778
  103. package/dist/mama-api.js +313 -2457
  104. package/dist/memory/api.d.ts +264 -24
  105. package/dist/memory/api.js +1633 -843
  106. package/dist/memory/bootstrap-builder.d.ts +2 -1
  107. package/dist/memory/bootstrap-builder.js +5 -5
  108. package/dist/memory/channel-summary-state-store.d.ts +3 -2
  109. package/dist/memory/channel-summary-state-store.js +53 -51
  110. package/dist/memory/channel-summary-store.d.ts +5 -3
  111. package/dist/memory/channel-summary-store.js +3 -8
  112. package/dist/memory/dashboard-read.d.ts +46 -0
  113. package/dist/memory/dashboard-read.js +94 -0
  114. package/dist/memory/event-store.d.ts +3 -3
  115. package/dist/memory/event-store.js +3 -10
  116. package/dist/memory/finding-store.d.ts +3 -2
  117. package/dist/memory/finding-store.js +2 -7
  118. package/dist/memory/graph-read.d.ts +52 -0
  119. package/dist/memory/graph-read.js +130 -0
  120. package/dist/memory/judgment-types.d.ts +394 -0
  121. package/dist/memory/judgment-types.js +16 -0
  122. package/dist/memory/provenance-audit.d.ts +5 -2
  123. package/dist/memory/provenance-audit.js +8 -14
  124. package/dist/memory/provenance-live.d.ts +141 -0
  125. package/dist/memory/provenance-live.js +409 -0
  126. package/dist/memory/provenance-query.d.ts +5 -4
  127. package/dist/memory/provenance-query.js +21 -29
  128. package/dist/memory/provenance-resolver.d.ts +191 -0
  129. package/dist/memory/provenance-resolver.js +155 -0
  130. package/dist/memory/provenance.d.ts +1 -11
  131. package/dist/memory/provenance.js +2 -33
  132. package/dist/memory/recall-sanitize.d.ts +55 -0
  133. package/dist/memory/recall-sanitize.js +144 -0
  134. package/dist/memory/secret-filter.d.ts +32 -0
  135. package/dist/memory/secret-filter.js +126 -0
  136. package/dist/memory/truth-store.d.ts +2 -1
  137. package/dist/memory/truth-store.js +1 -4
  138. package/dist/memory/types.d.ts +74 -24
  139. package/dist/memory/types.js +75 -5
  140. package/dist/memory/write-adapters.d.ts +98 -0
  141. package/dist/memory/write-adapters.js +242 -0
  142. package/dist/operations/owner-action-effects.d.ts +53 -0
  143. package/dist/operations/owner-action-effects.js +96 -0
  144. package/dist/provenance/source-ref.d.ts +1 -1
  145. package/dist/provenance/source-ref.js +0 -2
  146. package/dist/registry/corrections.d.ts +31 -0
  147. package/dist/registry/corrections.js +332 -0
  148. package/dist/registry/record-identity.d.ts +49 -0
  149. package/dist/registry/record-identity.js +133 -0
  150. package/dist/registry/store.d.ts +101 -0
  151. package/dist/registry/store.js +395 -0
  152. package/dist/registry/types.d.ts +28 -0
  153. package/dist/registry/types.js +5 -0
  154. package/dist/relevance-scorer.js +2 -2
  155. package/dist/runtime/agent-event-bus.d.ts +50 -0
  156. package/dist/runtime/agent-event-bus.js +101 -0
  157. package/dist/runtime/drivers/claude-cli-wrapper.d.ts +155 -0
  158. package/dist/runtime/drivers/claude-cli-wrapper.js +392 -0
  159. package/dist/runtime/drivers/cli-arg-redaction.d.ts +2 -0
  160. package/dist/runtime/drivers/cli-arg-redaction.js +18 -0
  161. package/dist/runtime/drivers/cli-secret-redaction.d.ts +17 -0
  162. package/dist/runtime/drivers/cli-secret-redaction.js +126 -0
  163. package/dist/runtime/drivers/codex-app-server-process.d.ts +277 -0
  164. package/dist/runtime/drivers/codex-app-server-process.js +2342 -0
  165. package/dist/runtime/drivers/codex-auxiliary-tools.d.ts +17 -0
  166. package/dist/runtime/drivers/codex-auxiliary-tools.js +267 -0
  167. package/dist/runtime/drivers/codex-home.d.ts +20 -0
  168. package/dist/runtime/drivers/codex-home.js +674 -0
  169. package/dist/runtime/drivers/codex-thread-registry.d.ts +63 -0
  170. package/dist/runtime/drivers/codex-thread-registry.js +433 -0
  171. package/dist/runtime/drivers/persistent-cli-adapter.d.ts +160 -0
  172. package/dist/runtime/drivers/persistent-cli-adapter.js +396 -0
  173. package/dist/runtime/drivers/persistent-cli-process.d.ts +424 -0
  174. package/dist/runtime/drivers/persistent-cli-process.js +1543 -0
  175. package/dist/runtime/drivers/types.d.ts +456 -0
  176. package/dist/runtime/drivers/types.js +110 -0
  177. package/dist/runtime/mailbox.d.ts +223 -0
  178. package/dist/runtime/mailbox.js +582 -0
  179. package/dist/runtime/model-run-store.d.ts +28 -0
  180. package/dist/{model-runs/store.js → runtime/model-run-store.js} +81 -41
  181. package/dist/runtime/model-run-types.d.ts +132 -0
  182. package/dist/{model-runs/types.js → runtime/model-run-types.js} +1 -1
  183. package/dist/runtime/native-effect-observer.d.ts +22 -0
  184. package/dist/runtime/native-effect-observer.js +103 -0
  185. package/dist/runtime/native-input-journal.d.ts +46 -0
  186. package/dist/runtime/native-input-journal.js +308 -0
  187. package/dist/runtime/native-prompt.d.ts +118 -0
  188. package/dist/runtime/native-prompt.js +496 -0
  189. package/dist/runtime/native-session.d.ts +10 -0
  190. package/dist/{agent-graph/index.js → runtime/native-session.js} +9 -6
  191. package/dist/runtime/native-tool-trace-observer.d.ts +5 -0
  192. package/dist/runtime/native-tool-trace-observer.js +65 -0
  193. package/dist/runtime/native-turn.d.ts +272 -0
  194. package/dist/runtime/native-turn.js +1041 -0
  195. package/dist/runtime/operations.d.ts +236 -0
  196. package/dist/runtime/operations.js +565 -0
  197. package/dist/runtime/post-tool-handler.d.ts +74 -0
  198. package/dist/runtime/post-tool-handler.js +137 -0
  199. package/dist/runtime/prompt-layers.d.ts +126 -0
  200. package/dist/runtime/prompt-layers.js +239 -0
  201. package/dist/runtime/runtime-process.d.ts +102 -0
  202. package/dist/runtime/runtime-process.js +206 -0
  203. package/dist/runtime/runtime.d.ts +278 -0
  204. package/dist/runtime/runtime.js +563 -0
  205. package/dist/runtime/session-pool.d.ts +176 -0
  206. package/dist/runtime/session-pool.js +369 -0
  207. package/dist/runtime/stimulus-payload.d.ts +5 -0
  208. package/dist/runtime/stimulus-payload.js +50 -0
  209. package/dist/runtime/subagent-bridge.d.ts +24 -0
  210. package/dist/{agent-graph/types.js → runtime/subagent-bridge.js} +1 -1
  211. package/dist/runtime/text-completion.d.ts +22 -0
  212. package/dist/{agent-situation/types.js → runtime/text-completion.js} +1 -1
  213. package/dist/runtime/token-estimator.d.ts +25 -0
  214. package/dist/runtime/token-estimator.js +75 -0
  215. package/dist/runtime/tool-trace-store.d.ts +22 -0
  216. package/dist/runtime/tool-trace-store.js +242 -0
  217. package/dist/runtime/trace-summary.d.ts +5 -0
  218. package/dist/runtime/trace-summary.js +74 -0
  219. package/dist/runtime/turn-text.d.ts +23 -0
  220. package/dist/runtime/turn-text.js +41 -0
  221. package/dist/storage/database.d.ts +67 -0
  222. package/dist/storage/database.js +158 -0
  223. package/package.json +20 -34
  224. package/dist/agent-graph/alias-write.d.ts +0 -3
  225. package/dist/agent-graph/alias-write.js +0 -234
  226. package/dist/agent-graph/entity-resolve.d.ts +0 -3
  227. package/dist/agent-graph/entity-resolve.js +0 -271
  228. package/dist/agent-graph/errors.d.ts +0 -4
  229. package/dist/agent-graph/errors.js +0 -11
  230. package/dist/agent-graph/graph-query.d.ts +0 -5
  231. package/dist/agent-graph/graph-query.js +0 -360
  232. package/dist/agent-graph/index.d.ts +0 -6
  233. package/dist/agent-situation/builder.d.ts +0 -8
  234. package/dist/agent-situation/builder.js +0 -239
  235. package/dist/agent-situation/cache-key.d.ts +0 -4
  236. package/dist/agent-situation/cache-key.js +0 -64
  237. package/dist/agent-situation/index.d.ts +0 -7
  238. package/dist/agent-situation/index.js +0 -28
  239. package/dist/agent-situation/packet-store.d.ts +0 -27
  240. package/dist/agent-situation/packet-store.js +0 -228
  241. package/dist/agent-situation/ranking-policy.d.ts +0 -18
  242. package/dist/agent-situation/ranking-policy.js +0 -75
  243. package/dist/agent-situation/source-readers.d.ts +0 -52
  244. package/dist/agent-situation/source-readers.js +0 -300
  245. package/dist/agent-situation/types.d.ts +0 -195
  246. package/dist/cases/wiki-page-index.d.ts +0 -37
  247. package/dist/cases/wiki-page-index.js +0 -201
  248. package/dist/config-loader.d.ts +0 -68
  249. package/dist/config-loader.js +0 -239
  250. package/dist/connectors/event-index.d.ts +0 -24
  251. package/dist/connectors/event-index.js +0 -245
  252. package/dist/connectors/raw-query.d.ts +0 -40
  253. package/dist/connectors/raw-query.js +0 -412
  254. package/dist/connectors/types.d.ts +0 -117
  255. package/dist/connectors/types.js +0 -3
  256. package/dist/context-compile/boundary-defaults.d.ts +0 -12
  257. package/dist/context-compile/boundary-defaults.js +0 -105
  258. package/dist/context-compile/channel-grant.js +0 -66
  259. package/dist/context-compile/compiler-policy.d.ts +0 -39
  260. package/dist/context-compile/compiler-policy.js +0 -147
  261. package/dist/context-compile/compiler.d.ts +0 -18
  262. package/dist/context-compile/compiler.js +0 -404
  263. package/dist/context-compile/index.d.ts +0 -10
  264. package/dist/context-compile/index.js +0 -26
  265. package/dist/context-compile/packet-store.d.ts +0 -8
  266. package/dist/context-compile/packet-store.js +0 -239
  267. package/dist/context-compile/ref.d.ts +0 -7
  268. package/dist/context-compile/ref.js +0 -102
  269. package/dist/context-compile/source-readers.d.ts +0 -70
  270. package/dist/context-compile/source-readers.js +0 -840
  271. package/dist/context-compile/types.d.ts +0 -155
  272. package/dist/context-compile/types.js +0 -11
  273. package/dist/context-compile/visibility.d.ts +0 -21
  274. package/dist/context-compile/visibility.js +0 -224
  275. package/dist/decision-tracker.d.ts +0 -206
  276. package/dist/decision-tracker.js +0 -481
  277. package/dist/edges/ref-validation.d.ts +0 -7
  278. package/dist/edges/ref-validation.js +0 -321
  279. package/dist/edges/store.d.ts +0 -8
  280. package/dist/edges/store.js +0 -129
  281. package/dist/embedding-client.d.ts +0 -28
  282. package/dist/embedding-client.js +0 -53
  283. package/dist/embedding-server/index.d.ts +0 -67
  284. package/dist/embedding-server/index.js +0 -405
  285. package/dist/embedding-server/mobile/auth.d.ts +0 -44
  286. package/dist/embedding-server/mobile/auth.js +0 -150
  287. package/dist/embedding-server/mobile/daemon.d.ts +0 -129
  288. package/dist/embedding-server/mobile/daemon.js +0 -313
  289. package/dist/embedding-server/mobile/output-parser.d.ts +0 -115
  290. package/dist/embedding-server/mobile/output-parser.js +0 -241
  291. package/dist/embedding-server/mobile/session-api.d.ts +0 -57
  292. package/dist/embedding-server/mobile/session-api.js +0 -261
  293. package/dist/embedding-server/mobile/session-manager.d.ts +0 -138
  294. package/dist/embedding-server/mobile/session-manager.js +0 -378
  295. package/dist/embedding-server/mobile/websocket-handler.d.ts +0 -137
  296. package/dist/embedding-server/mobile/websocket-handler.js +0 -486
  297. package/dist/embeddings.d.ts +0 -68
  298. package/dist/embeddings.js +0 -244
  299. package/dist/entities/audit-metrics.d.ts +0 -68
  300. package/dist/entities/audit-metrics.js +0 -124
  301. package/dist/entities/candidate-generator.d.ts +0 -8
  302. package/dist/entities/candidate-generator.js +0 -24
  303. package/dist/entities/entity-linked-decision-counts.d.ts +0 -2
  304. package/dist/entities/entity-linked-decision-counts.js +0 -29
  305. package/dist/entities/entity-list.d.ts +0 -22
  306. package/dist/entities/entity-list.js +0 -3
  307. package/dist/entities/entity-orphan-list.d.ts +0 -17
  308. package/dist/entities/entity-orphan-list.js +0 -3
  309. package/dist/entities/entity-search.d.ts +0 -20
  310. package/dist/entities/entity-search.js +0 -3
  311. package/dist/entities/errors.d.ts +0 -55
  312. package/dist/entities/errors.js +0 -100
  313. package/dist/entities/lineage-store.d.ts +0 -28
  314. package/dist/entities/lineage-store.js +0 -204
  315. package/dist/entities/normalization.d.ts +0 -14
  316. package/dist/entities/normalization.js +0 -63
  317. package/dist/entities/policy-store.d.ts +0 -8
  318. package/dist/entities/policy-store.js +0 -75
  319. package/dist/entities/policy-types.d.ts +0 -72
  320. package/dist/entities/policy-types.js +0 -59
  321. package/dist/entities/projection.d.ts +0 -8
  322. package/dist/entities/projection.js +0 -82
  323. package/dist/entities/provenance-query.d.ts +0 -32
  324. package/dist/entities/provenance-query.js +0 -3
  325. package/dist/entities/read-identity.d.ts +0 -31
  326. package/dist/entities/read-identity.js +0 -264
  327. package/dist/entities/recall-bridge.d.ts +0 -5
  328. package/dist/entities/recall-bridge.js +0 -171
  329. package/dist/entities/resolution-engine.d.ts +0 -8
  330. package/dist/entities/resolution-engine.js +0 -3
  331. package/dist/entities/rollback-preview.d.ts +0 -35
  332. package/dist/entities/rollback-preview.js +0 -3
  333. package/dist/entities/source-locator.d.ts +0 -3
  334. package/dist/entities/source-locator.js +0 -32
  335. package/dist/entities/store.d.ts +0 -69
  336. package/dist/entities/store.js +0 -544
  337. package/dist/entities/types.d.ts +0 -166
  338. package/dist/entities/types.js +0 -30
  339. package/dist/memory/extraction-prompt.d.ts +0 -4
  340. package/dist/memory/extraction-prompt.js +0 -89
  341. package/dist/memory/scope-backfill.d.ts +0 -14
  342. package/dist/memory/scope-backfill.js +0 -3
  343. package/dist/memory/scope-store.d.ts +0 -4
  344. package/dist/memory/scope-store.js +0 -9
  345. package/dist/memory-store.d.ts +0 -87
  346. package/dist/memory-store.js +0 -78
  347. package/dist/model-runs/store.d.ts +0 -13
  348. package/dist/model-runs/tool-trace-store.d.ts +0 -4
  349. package/dist/model-runs/tool-trace-store.js +0 -118
  350. package/dist/model-runs/types.d.ts +0 -72
  351. package/dist/notification-manager.d.ts +0 -7
  352. package/dist/notification-manager.js +0 -12
  353. package/dist/outcome-tracker.d.ts +0 -151
  354. package/dist/outcome-tracker.js +0 -271
  355. package/dist/query-intent.d.ts +0 -23
  356. package/dist/query-intent.js +0 -114
  357. package/dist/runtime/trusted-provenance.d.ts +0 -3
  358. package/dist/runtime/trusted-provenance.js +0 -6
  359. package/dist/search/question-type.d.ts +0 -5
  360. package/dist/search/question-type.js +0 -41
  361. package/dist/test-utils.d.ts +0 -80
  362. package/dist/test-utils.js +0 -218
  363. package/dist/tier-validator.d.ts +0 -43
  364. package/dist/tier-validator.js +0 -159
  365. package/dist/time-formatter.d.ts +0 -25
  366. package/dist/time-formatter.js +0 -93
  367. /package/dist/{search → knowledge}/feedback-store.d.ts +0 -0
  368. /package/dist/{search → knowledge}/feedback-store.js +0 -0
  369. /package/dist/{search → knowledge}/ranker-features.d.ts +0 -0
  370. /package/dist/{search → knowledge}/ranker-rescore.d.ts +0 -0
  371. /package/dist/{search → knowledge}/ranker-rescore.js +0 -0
  372. /package/dist/{search → knowledge}/ranker-trainer.d.ts +0 -0
  373. /package/dist/{search → knowledge}/ranker-trainer.js +0 -0
  374. /package/dist/{search → knowledge}/search-quality.js +0 -0
package/dist/mama-api.js CHANGED
@@ -18,102 +18,59 @@
18
18
  * @version 1.3
19
19
  * @date 2025-11-26
20
20
  */
21
- var __importDefault = (this && this.__importDefault) || function (mod) {
22
- return (mod && mod.__esModule) ? mod : { "default": mod };
23
- };
24
21
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.listToolTracesForRun = exports.appendToolTrace = exports.getModelRunInAdapter = exports.getModelRun = exports.failModelRunInAdapter = exports.failModelRun = exports.commitModelRunInAdapter = exports.commitModelRun = exports.beginModelRunInAdapter = exports.beginModelRun = exports.listRecentMemoryEvents = exports.listMemoryEventsForMemory = exports.listMemoriesByModelRunId = exports.listMemoriesByGatewayCallId = exports.listMemoriesByEnvelopeHash = exports.getMemoryProvenance = exports.createAuditFinding = exports.listOpenAuditFindings = exports.getChannelSummary = exports.upsertChannelSummary = exports.recordMemoryAudit = exports.createAuditAck = exports.buildMemoryBootstrap = exports.evolveMemory = exports.ingestConversationWithTrustedProvenance = exports.ingestConversation = exports.ingestWithTrustedProvenance = exports.ingestMemory = exports.buildProfile = exports.recallMemory = exports.saveMemoryWithTrustedProvenance = exports.saveMemory = void 0;
22
+ exports.createAuditAck = exports.evolveMemory = void 0;
23
+ exports.createMamaApi = createMamaApi;
26
24
  exports.save = save;
27
- exports.saveWithTrustedProvenance = saveWithTrustedProvenance;
28
25
  exports.suggest = suggest;
29
- exports.annotateTopicCurrency = annotateTopicCurrency;
26
+ exports.saveMemory = saveMemory;
27
+ exports.recallMemory = recallMemory;
30
28
  exports.list = listDecisions;
31
29
  exports.listCheckpoints = listCheckpoints;
32
30
  exports.updateOutcome = updateOutcome;
31
+ exports.buildProfile = buildProfile;
32
+ exports.ingestMemory = ingestMemory;
33
+ exports.ingestConversation = ingestConversation;
34
+ exports.buildMemoryBootstrap = buildMemoryBootstrap;
35
+ exports.recordMemoryAudit = recordMemoryAudit;
36
+ exports.upsertChannelSummary = upsertChannelSummary;
37
+ exports.getChannelSummary = getChannelSummary;
38
+ exports.listOpenAuditFindings = listOpenAuditFindings;
39
+ exports.createAuditFinding = createAuditFinding;
40
+ exports.getMemoryProvenance = getMemoryProvenance;
41
+ exports.listMemoriesByEnvelopeHash = listMemoriesByEnvelopeHash;
42
+ exports.listMemoriesByGatewayCallId = listMemoriesByGatewayCallId;
43
+ exports.listMemoriesByModelRunId = listMemoriesByModelRunId;
44
+ exports.listMemoryEventsForMemory = listMemoryEventsForMemory;
45
+ exports.listRecentMemoryEvents = listRecentMemoryEvents;
46
+ exports.beginModelRun = beginModelRun;
47
+ exports.commitModelRun = commitModelRun;
48
+ exports.failModelRun = failModelRun;
49
+ exports.getModelRun = getModelRun;
50
+ exports.appendToolTrace = appendToolTrace;
51
+ exports.listToolTracesForRun = listToolTracesForRun;
52
+ exports.listToolTraces = listToolTraces;
53
+ exports.readToolTrace = readToolTrace;
33
54
  exports.saveCheckpoint = saveCheckpoint;
34
55
  exports.loadCheckpoint = loadCheckpoint;
35
56
  exports.recall = recall;
36
- exports.proposeLink = proposeLink;
37
- exports.approveLink = approveLink;
38
- exports.rejectLink = rejectLink;
39
- exports.getPendingLinks = getPendingLinks;
40
- exports.deprecateAutoLinks = deprecateAutoLinks;
41
- exports.calculateCoverage = calculateCoverage;
42
- exports.calculateQuality = calculateQuality;
43
- exports.generateQualityReport = generateQualityReport;
44
- exports.logRestartAttempt = logRestartAttempt;
45
- exports.calculateRestartSuccessRate = calculateRestartSuccessRate;
46
- exports.calculateRestartLatency = calculateRestartLatency;
47
- exports.getRestartMetrics = getRestartMetrics;
48
- exports.scanAutoLinks = scanAutoLinks;
49
- exports.createLinkBackup = createLinkBackup;
50
- exports.generatePreCleanupReport = generatePreCleanupReport;
51
- exports.restoreLinkBackup = restoreLinkBackup;
52
- exports.verifyBackupExists = verifyBackupExists;
53
- exports.deleteAutoLinks = deleteAutoLinks;
54
- exports.validateCleanupResult = validateCleanupResult;
55
57
  exports.expandWithGraph = expandWithGraph;
56
- // Node built-ins
57
- const fs_1 = __importDefault(require("fs"));
58
- const path_1 = __importDefault(require("path"));
59
- const os_1 = __importDefault(require("os"));
60
- const crypto_1 = __importDefault(require("crypto"));
61
58
  // Internal modules
62
- const decision_tracker_js_1 = require("./decision-tracker.js");
59
+ const api_js_1 = require("./memory/api.js");
63
60
  const db_manager_js_1 = require("./db-manager.js");
64
- const memory_store_js_1 = require("./memory-store.js");
65
- const db_manager_js_2 = require("./db-manager.js");
61
+ const graph_query_js_1 = require("./knowledge/graph-query.js");
66
62
  const decision_formatter_js_1 = require("./decision-formatter.js");
67
63
  const progress_indicator_js_1 = require("./progress-indicator.js");
68
- const embeddings_js_1 = require("./embeddings.js");
69
- const ollama_client_js_1 = require("./ollama-client.js");
70
64
  const debug_logger_js_1 = require("./debug-logger.js");
71
- const api_js_1 = require("./memory/api.js");
72
- Object.defineProperty(exports, "saveMemory", { enumerable: true, get: function () { return api_js_1.saveMemory; } });
73
- Object.defineProperty(exports, "saveMemoryWithTrustedProvenance", { enumerable: true, get: function () { return api_js_1.saveMemoryWithTrustedProvenance; } });
74
- Object.defineProperty(exports, "recallMemory", { enumerable: true, get: function () { return api_js_1.recallMemory; } });
75
- Object.defineProperty(exports, "buildProfile", { enumerable: true, get: function () { return api_js_1.buildProfile; } });
76
- Object.defineProperty(exports, "ingestMemory", { enumerable: true, get: function () { return api_js_1.ingestMemory; } });
77
- Object.defineProperty(exports, "ingestWithTrustedProvenance", { enumerable: true, get: function () { return api_js_1.ingestWithTrustedProvenance; } });
78
- Object.defineProperty(exports, "ingestConversation", { enumerable: true, get: function () { return api_js_1.ingestConversation; } });
79
- Object.defineProperty(exports, "ingestConversationWithTrustedProvenance", { enumerable: true, get: function () { return api_js_1.ingestConversationWithTrustedProvenance; } });
80
- Object.defineProperty(exports, "evolveMemory", { enumerable: true, get: function () { return api_js_1.evolveMemory; } });
81
- Object.defineProperty(exports, "buildMemoryBootstrap", { enumerable: true, get: function () { return api_js_1.buildMemoryBootstrap; } });
82
- Object.defineProperty(exports, "createAuditAck", { enumerable: true, get: function () { return api_js_1.createAuditAck; } });
83
- Object.defineProperty(exports, "recordMemoryAudit", { enumerable: true, get: function () { return api_js_1.recordMemoryAudit; } });
84
- Object.defineProperty(exports, "upsertChannelSummary", { enumerable: true, get: function () { return api_js_1.upsertChannelSummary; } });
85
- Object.defineProperty(exports, "getChannelSummary", { enumerable: true, get: function () { return api_js_1.getChannelSummary; } });
65
+ const api_js_2 = require("./memory/api.js");
66
+ Object.defineProperty(exports, "evolveMemory", { enumerable: true, get: function () { return api_js_2.evolveMemory; } });
67
+ Object.defineProperty(exports, "createAuditAck", { enumerable: true, get: function () { return api_js_2.createAuditAck; } });
86
68
  const finding_store_js_1 = require("./memory/finding-store.js");
87
- Object.defineProperty(exports, "createAuditFinding", { enumerable: true, get: function () { return finding_store_js_1.createAuditFinding; } });
88
- Object.defineProperty(exports, "listOpenAuditFindings", { enumerable: true, get: function () { return finding_store_js_1.listOpenAuditFindings; } });
89
69
  const event_store_js_1 = require("./memory/event-store.js");
90
- Object.defineProperty(exports, "listMemoryEventsForMemory", { enumerable: true, get: function () { return event_store_js_1.listMemoryEventsForMemory; } });
91
- Object.defineProperty(exports, "listRecentMemoryEvents", { enumerable: true, get: function () { return event_store_js_1.listRecentMemoryEvents; } });
92
70
  const provenance_query_js_1 = require("./memory/provenance-query.js");
93
- Object.defineProperty(exports, "getMemoryProvenance", { enumerable: true, get: function () { return provenance_query_js_1.getMemoryProvenance; } });
94
- Object.defineProperty(exports, "listMemoriesByEnvelopeHash", { enumerable: true, get: function () { return provenance_query_js_1.listMemoriesByEnvelopeHash; } });
95
- Object.defineProperty(exports, "listMemoriesByGatewayCallId", { enumerable: true, get: function () { return provenance_query_js_1.listMemoriesByGatewayCallId; } });
96
- Object.defineProperty(exports, "listMemoriesByModelRunId", { enumerable: true, get: function () { return provenance_query_js_1.listMemoriesByModelRunId; } });
97
- const store_js_1 = require("./model-runs/store.js");
98
- Object.defineProperty(exports, "beginModelRun", { enumerable: true, get: function () { return store_js_1.beginModelRun; } });
99
- Object.defineProperty(exports, "beginModelRunInAdapter", { enumerable: true, get: function () { return store_js_1.beginModelRunInAdapter; } });
100
- Object.defineProperty(exports, "commitModelRun", { enumerable: true, get: function () { return store_js_1.commitModelRun; } });
101
- Object.defineProperty(exports, "commitModelRunInAdapter", { enumerable: true, get: function () { return store_js_1.commitModelRunInAdapter; } });
102
- Object.defineProperty(exports, "failModelRun", { enumerable: true, get: function () { return store_js_1.failModelRun; } });
103
- Object.defineProperty(exports, "failModelRunInAdapter", { enumerable: true, get: function () { return store_js_1.failModelRunInAdapter; } });
104
- Object.defineProperty(exports, "getModelRun", { enumerable: true, get: function () { return store_js_1.getModelRun; } });
105
- Object.defineProperty(exports, "getModelRunInAdapter", { enumerable: true, get: function () { return store_js_1.getModelRunInAdapter; } });
106
- const tool_trace_store_js_1 = require("./model-runs/tool-trace-store.js");
107
- Object.defineProperty(exports, "appendToolTrace", { enumerable: true, get: function () { return tool_trace_store_js_1.appendToolTrace; } });
108
- Object.defineProperty(exports, "listToolTracesForRun", { enumerable: true, get: function () { return tool_trace_store_js_1.listToolTracesForRun; } });
109
- const search_rollup_js_1 = require("./cases/search-rollup.js");
110
- const ranker_rescore_js_1 = require("./search/ranker-rescore.js");
111
- const ranker_features_js_1 = require("./search/ranker-features.js");
112
- const search_quality_js_1 = require("./search/search-quality.js");
113
- // Session-level warning cooldown cache (Story 1.1, 1.2)
71
+ const model_run_store_js_1 = require("./runtime/model-run-store.js");
72
+ const tool_trace_store_js_1 = require("./runtime/tool-trace-store.js");
114
73
  // Prevents spam by tracking warned topics per session
115
- const warnedTopicsCache = new Map();
116
- const WARNING_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
117
74
  /**
118
75
  * Save a decision or insight to MAMA's memory
119
76
  *
@@ -141,7 +98,7 @@ const WARNING_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
141
98
  * outcome: 'success'
142
99
  * });
143
100
  */
144
- async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type = 'user_decision', outcome = 'pending', failure_reason = null, limitation = null, trust_context: _trust_context = null, is_static, scopes: inputScopes, event_date, timelineEvent, }, options) {
101
+ async function saveInternal(adapter, { topic, decision, reasoning, confidence = 0.5, type = 'user_decision', outcome = 'pending', failure_reason = null, limitation = null, trust_context: _trust_context = null, is_static, scopes: inputScopes, item, actors, event_date, }) {
145
102
  // Validate required fields
146
103
  if (!topic || typeof topic !== 'string') {
147
104
  throw new Error('mama.save() requires topic (string)');
@@ -190,97 +147,45 @@ async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type
190
147
  // Note: Current schema uses user_involvement ('requested', 'approved', 'rejected')
191
148
  // Future: Will use decision_type column for proper distinction
192
149
  const _userInvolvement = type === 'user_decision' ? 'approved' : null;
150
+ const outcomeMap = {
151
+ pending: null,
152
+ success: 'SUCCESS',
153
+ failure: 'FAILED',
154
+ partial: 'PARTIAL',
155
+ superseded: null,
156
+ };
157
+ const dbOutcome = outcome in outcomeMap ? outcomeMap[outcome] : outcome;
158
+ // Reasoning text is evidence, not authority: relationships are only written
159
+ // when the caller names explicit targets (saveLegacyMemory's legacy field or
160
+ // twin-edge links). Parsing IDs out of prose fabricated edges, so it is gone.
193
161
  (0, progress_indicator_js_1.logProgress)(`Saving decision: ${topic.substring(0, 30)}...`);
194
- const { id: decisionId, timeline_event_id: timelineEventId, timeline_event_ids: timelineEventIds, } = options
195
- ? await (0, api_js_1.saveMemoryWithTrustedProvenance)({
196
- topic,
197
- kind: is_static === 1 ? 'preference' : 'decision',
198
- summary: decision,
199
- details: reasoning,
200
- confidence,
201
- scopes: Array.isArray(inputScopes) && inputScopes.length > 0 ? inputScopes : [],
202
- source: {
203
- package: 'mama-core',
204
- source_type: 'legacy_save',
205
- },
206
- eventDate: event_date ?? undefined,
207
- timelineEvent,
208
- }, options)
209
- : await (0, api_js_1.saveMemory)({
210
- topic,
211
- kind: is_static === 1 ? 'preference' : 'decision',
212
- summary: decision,
213
- details: reasoning,
214
- confidence,
215
- scopes: Array.isArray(inputScopes) && inputScopes.length > 0 ? inputScopes : [],
216
- source: {
217
- package: 'mama-core',
218
- source_type: 'legacy_save',
219
- },
220
- eventDate: event_date ?? undefined,
221
- timelineEvent,
222
- });
162
+ const { id: decisionId } = await (0, api_js_2.saveLegacyMemory)(adapter, {
163
+ topic,
164
+ kind: is_static === 1 ? 'preference' : 'decision',
165
+ summary: decision,
166
+ details: reasoning,
167
+ confidence,
168
+ scopes: Array.isArray(inputScopes) && inputScopes.length > 0 ? inputScopes : [],
169
+ source: {
170
+ package: 'mama-core',
171
+ source_type: 'legacy_save',
172
+ },
173
+ eventDate: event_date ?? undefined,
174
+ itemId: item ?? undefined,
175
+ actors: actors?.map((actor) => ({ personId: actor.person, role: actor.role })),
176
+ }, {
177
+ userInvolvement: _userInvolvement,
178
+ outcome: dbOutcome,
179
+ failureReason: failure_reason ?? null,
180
+ limitation: limitation ?? null,
181
+ isStatic: is_static,
182
+ });
223
183
  (0, progress_indicator_js_1.logComplete)(`Decision saved: ${decisionId.substring(0, 20)}...`);
224
- // Update user_involvement, outcome, failure_reason, limitation
225
- // Note: learnDecision always sets 'requested', we need to override it
226
- await (0, db_manager_js_2.initDB)();
227
- const adapter = (0, memory_store_js_1.getAdapter)();
228
- // Build UPDATE query dynamically based on what fields are provided
229
- const updates = [];
230
- const values = [];
231
- // user_involvement based on type
232
- if (type === 'assistant_insight') {
233
- updates.push('user_involvement = NULL');
234
- }
235
- else if (type === 'user_decision') {
236
- updates.push('user_involvement = ?');
237
- values.push('approved');
238
- }
239
- // outcome (always set, default is 'pending')
240
- // Story M4.1 fix: Map to DB format (uppercase, pending → NULL)
241
- if (outcome) {
242
- const outcomeMap = {
243
- pending: null,
244
- success: 'SUCCESS',
245
- failure: 'FAILED',
246
- partial: 'PARTIAL',
247
- superseded: null,
248
- };
249
- const dbOutcome = outcomeMap[outcome] !== undefined ? outcomeMap[outcome] : outcome;
250
- updates.push('outcome = ?');
251
- values.push(dbOutcome);
252
- }
253
- // failure_reason (optional)
254
- if (failure_reason) {
255
- updates.push('failure_reason = ?');
256
- values.push(failure_reason);
257
- }
258
- // limitation (optional)
259
- if (limitation) {
260
- updates.push('limitation = ?');
261
- values.push(limitation);
262
- }
263
- // is_static (user profile marker)
264
- if (is_static !== undefined) {
265
- updates.push('is_static = ?');
266
- values.push(is_static);
267
- }
268
- // Execute UPDATE if we have any fields to update
269
- if (updates.length > 0) {
270
- values.push(decisionId); // WHERE id = ?
271
- const stmt = adapter.prepare(`
272
- UPDATE decisions
273
- SET ${updates.join(', ')}
274
- WHERE id = ?
275
- `);
276
- await stmt.run(...values);
277
- }
278
184
  // ════════════════════════════════════════════════════════════════════════════
279
185
  // Story 1.1: Auto-Search on Save
280
186
  // Story 1.2: Response Enhancement
281
187
  // ════════════════════════════════════════════════════════════════════════════
282
188
  let similar_decisions = [];
283
- let warning = null;
284
189
  let collaboration_hint = null;
285
190
  let reasoning_graph = null;
286
191
  // Only run auto-search for decisions (not checkpoints) with a topic
@@ -292,7 +197,7 @@ async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type
292
197
  // NOTE: suggest() searches globally and does not yet support scoped similarity search.
293
198
  // Cross-scope suggestions are possible here. Track as a follow-up.
294
199
  (0, progress_indicator_js_1.logSearching)('Searching for related decisions...');
295
- const searchResults = await suggest(topic, {
200
+ const searchResults = await (0, api_js_1.suggestInAdapter)(adapter, topic, {
296
201
  limit: 3,
297
202
  threshold: 0.7,
298
203
  disableRecency: true, // Pure semantic similarity for comparison
@@ -316,12 +221,12 @@ async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type
316
221
  if (similar_decisions.length > 0) {
317
222
  (0, progress_indicator_js_1.logComplete)(`Found ${similar_decisions.length} related decision(s)`);
318
223
  }
319
- // Story 1.2: Warning logic (similarity >= 0.85)
320
- const highSimilarity = similar_decisions.find((d) => (d.similarity ?? 0) >= 0.85);
321
- if (highSimilarity && !_isTopicInCooldown(topic)) {
322
- warning = `High similarity (${((highSimilarity.similarity ?? 0) * 100).toFixed(0)}%) with existing decision "${highSimilarity.decision.substring(0, 50)}..."`;
323
- _markTopicWarned(topic);
324
- }
224
+ // There was a "High similarity (N%)" warning here, keyed on a number
225
+ // that measured rank rather than likeness -- it fired on every save
226
+ // that had a second result at all, whatever that result said. Nothing
227
+ // in either retrieval path now produces a similarity for this query
228
+ // shape, so the warning has no measure to stand on and is gone. The
229
+ // hint below states what IS true: how many related rows came back.
325
230
  // Story 1.2: Collaboration hint
326
231
  if (similar_decisions.length > 0) {
327
232
  collaboration_hint = _generateCollaborationHint(similar_decisions);
@@ -335,34 +240,20 @@ async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type
335
240
  }
336
241
  // Story 1.2: Reasoning graph info
337
242
  try {
338
- reasoning_graph = await _getReasoningGraphInfo(topic, decisionId);
243
+ reasoning_graph = await _getReasoningGraphInfo(adapter, topic, decisionId);
339
244
  }
340
245
  catch (error) {
341
246
  const errMsg = error instanceof Error ? error.message : String(error);
342
247
  (0, debug_logger_js_1.error)('Reasoning graph query failed:', errMsg);
343
248
  }
344
249
  }
345
- // Story 2.2: Parse reasoning for relationship edges (builds_on, debates, synthesizes)
346
- if (reasoning) {
347
- try {
348
- await (0, decision_tracker_js_1.createEdgesFromReasoning)(decisionId, reasoning);
349
- }
350
- catch (error) {
351
- // Best-effort - save succeeds even if edge creation fails
352
- const errMsg = error instanceof Error ? error.message : String(error);
353
- (0, debug_logger_js_1.error)('Edge creation from reasoning failed:', errMsg);
354
- }
355
- }
356
250
  }
357
251
  // Story 1.2: Enhanced response (backward compatible)
358
252
  return {
359
253
  success: true,
360
254
  id: decisionId,
361
255
  saved_decision_id: decisionId,
362
- timeline_event_id: timelineEventId ?? null,
363
- timeline_event_ids: timelineEventIds ?? [],
364
256
  ...(similar_decisions.length > 0 && { similar_decisions }),
365
- ...(warning && { warning }),
366
257
  ...(collaboration_hint && { collaboration_hint }),
367
258
  ...(reasoning_graph && { reasoning_graph }),
368
259
  };
@@ -370,25 +261,6 @@ async function saveInternal({ topic, decision, reasoning, confidence = 0.5, type
370
261
  // ════════════════════════════════════════════════════════════════════════════
371
262
  // Story 1.2: Helper functions for Response Enhancement
372
263
  // ════════════════════════════════════════════════════════════════════════════
373
- /**
374
- * Check if a topic is in warning cooldown
375
- * @param {string} topic - Topic to check
376
- * @returns {boolean} True if topic was warned recently
377
- */
378
- function _isTopicInCooldown(topic) {
379
- const lastWarned = warnedTopicsCache.get(topic);
380
- if (!lastWarned) {
381
- return false;
382
- }
383
- return Date.now() - lastWarned < WARNING_COOLDOWN_MS;
384
- }
385
- /**
386
- * Mark a topic as warned (start cooldown)
387
- * @param {string} topic - Topic to mark
388
- */
389
- function _markTopicWarned(topic) {
390
- warnedTopicsCache.set(topic, Date.now());
391
- }
392
264
  /**
393
265
  * Generate collaboration hint message
394
266
  * @param {Array} similarDecisions - Similar decisions found
@@ -400,7 +272,7 @@ function _generateCollaborationHint(similarDecisions) {
400
272
  return null;
401
273
  }
402
274
  return `Found ${count} related decision(s). Consider:
403
- - SUPERSEDE: Same topic replaces prior (automatic)
275
+ - SUPERSEDE: Add "supersedes: <id>" in reasoning to replace a specific prior decision
404
276
  - BUILD-ON: Add "builds_on: <id>" in reasoning to extend
405
277
  - DEBATE: Add "debates: <id>" in reasoning for alternative view
406
278
  - SYNTHESIZE: Add "synthesizes: [id1, id2]" in reasoning to unify`;
@@ -411,9 +283,9 @@ function _generateCollaborationHint(similarDecisions) {
411
283
  * @param {string} currentId - Current decision ID
412
284
  * @returns {Object} Reasoning graph info
413
285
  */
414
- async function _getReasoningGraphInfo(topic, currentId) {
286
+ async function _getReasoningGraphInfo(adapter, topic, currentId) {
415
287
  try {
416
- const chain = await (0, memory_store_js_1.queryDecisionGraph)(topic);
288
+ const chain = await (0, graph_query_js_1.queryDecisionGraph)(adapter, topic);
417
289
  if (!chain || chain.length === 0) {
418
290
  return {
419
291
  topic,
@@ -435,13 +307,13 @@ async function _getReasoningGraphInfo(topic, currentId) {
435
307
  };
436
308
  }
437
309
  }
438
- async function recall(topic, options = {}) {
310
+ async function recallInAdapter(adapter, topic, options = {}) {
439
311
  if (!topic || typeof topic !== 'string') {
440
312
  throw new Error('mama.recall() requires topic (string)');
441
313
  }
442
314
  const { format = 'json' } = options;
443
315
  try {
444
- const decisions = await (0, memory_store_js_1.queryDecisionGraph)(topic);
316
+ const decisions = await (0, graph_query_js_1.queryDecisionGraph)(adapter, topic);
445
317
  if (!decisions || decisions.length === 0) {
446
318
  if (format === 'markdown') {
447
319
  return `❌ No decisions found for topic: ${topic}`;
@@ -460,7 +332,7 @@ async function recall(topic, options = {}) {
460
332
  }
461
333
  // Query semantic edges for all decisions
462
334
  const decisionIds = decisions.map((d) => d.id);
463
- const rawEdges = await (0, memory_store_js_1.querySemanticEdges)(decisionIds);
335
+ const rawEdges = await (0, graph_query_js_1.querySemanticEdges)(adapter, decisionIds);
464
336
  const semanticEdges = {
465
337
  refines: rawEdges.refines || [],
466
338
  refined_by: rawEdges.refined_by || [],
@@ -559,2216 +431,158 @@ async function recall(topic, options = {}) {
559
431
  throw new Error(`mama.recall() failed: ${error instanceof Error ? error.message : String(error)}`);
560
432
  }
561
433
  }
562
- async function updateOutcome(decisionId, { outcome, failure_reason, limitation }) {
563
- if (!decisionId || typeof decisionId !== 'string') {
564
- throw new Error('mama.updateOutcome() requires decisionId (string)');
565
- }
566
- // AX Improvement: Be forgiving with case sensitivity
567
- const normalizedOutcome = outcome ? outcome.toUpperCase() : null;
568
- if (!normalizedOutcome || !['SUCCESS', 'FAILED', 'PARTIAL'].includes(normalizedOutcome)) {
569
- throw new Error('mama.updateOutcome() outcome must be "SUCCESS", "FAILED", or "PARTIAL"');
570
- }
571
- try {
572
- const adapter = (0, memory_store_js_1.getAdapter)();
573
- // Update outcome and related fields
574
- const stmt = adapter.prepare(`
575
- UPDATE decisions
576
- SET
577
- outcome = ?,
578
- failure_reason = ?,
579
- limitation = ?,
580
- updated_at = ?
581
- WHERE id = ?
582
- `);
583
- const result = stmt.run(normalizedOutcome, failure_reason || null, limitation || null, Date.now(), decisionId);
584
- // Check if decision was found and updated
585
- if (result.changes === 0) {
586
- throw new Error(`Decision not found: ${decisionId}`);
587
- }
588
- return;
589
- }
590
- catch (error) {
591
- throw new Error(`mama.updateOutcome() failed: ${error instanceof Error ? error.message : String(error)}`);
592
- }
593
- }
594
- async function expandWithGraph(candidates) {
595
- const graphEnhanced = new Map(); // Use Map for deduplication by ID
596
- const primaryIds = new Set(candidates.map((c) => c.id)); // Track primary candidates
597
- // Process each candidate
598
- for (const candidate of candidates) {
599
- // Add primary candidate with higher rank
600
- if (!graphEnhanced.has(candidate.id)) {
601
- graphEnhanced.set(candidate.id, {
602
- ...candidate,
603
- graph_source: 'primary', // Mark as primary result
604
- graph_rank: 1.0, // Highest rank
605
- });
606
- }
607
- // 1. Add supersedes chain (evolution history)
608
- try {
609
- const chain = await (0, memory_store_js_1.queryDecisionGraph)(candidate.topic);
610
- for (const decision of chain) {
611
- if (!graphEnhanced.has(decision.id)) {
612
- graphEnhanced.set(decision.id, {
613
- ...decision,
614
- graph_source: 'supersedes_chain',
615
- graph_rank: 0.8, // Lower rank than primary
616
- similarity: (candidate.similarity ?? 0) * 0.9, // Inherit similarity, slightly reduced
617
- related_to: candidate.id, // Track relationship
618
- });
619
- }
620
- }
621
- }
622
- catch (error) {
623
- (0, debug_logger_js_1.warn)(`Failed to get supersedes chain for ${candidate.topic}: ${error instanceof Error ? error.message : String(error)}`);
624
- }
625
- // 2. Add semantic edges (refines, contradicts, builds_on, debates, synthesizes)
626
- try {
627
- const rawEdges = (await (0, memory_store_js_1.querySemanticEdges)([candidate.id])) || {};
628
- const edges = {
629
- refines: rawEdges.refines || [],
630
- refined_by: rawEdges.refined_by || [],
631
- contradicts: rawEdges.contradicts || [],
632
- contradicted_by: rawEdges.contradicted_by || [],
633
- builds_on: rawEdges.builds_on || [],
634
- built_on_by: rawEdges.built_on_by || [],
635
- debates: rawEdges.debates || [],
636
- debated_by: rawEdges.debated_by || [],
637
- synthesizes: rawEdges.synthesizes || [],
638
- synthesized_by: rawEdges.synthesized_by || [],
639
- };
640
- // Helper to add edge to graph
641
- const addEdge = (edge, idField, source, rank, simFactor) => {
642
- const id = edge[idField];
643
- if (!graphEnhanced.has(id)) {
644
- graphEnhanced.set(id, {
645
- id: id,
646
- topic: edge.topic,
647
- decision: edge.decision,
648
- confidence: edge.confidence,
649
- created_at: edge.created_at,
650
- graph_source: source,
651
- graph_rank: rank,
652
- similarity: (candidate.similarity ?? 0) * simFactor,
653
- related_to: candidate.id,
654
- edge_reason: edge.reason,
655
- });
656
- }
657
- };
658
- // Add refines edges
659
- for (const edge of edges.refines) {
660
- addEdge(edge, 'to_id', 'refines', 0.7, 0.85);
661
- }
662
- // Add refined_by edges
663
- for (const edge of edges.refined_by) {
664
- addEdge(edge, 'from_id', 'refined_by', 0.7, 0.85);
665
- }
666
- // Add contradicts edges (lower rank, but still relevant)
667
- for (const edge of edges.contradicts) {
668
- addEdge(edge, 'to_id', 'contradicts', 0.6, 0.8);
669
- }
670
- // Story 2.1: Add builds_on edges (high relevance - extending prior work)
671
- for (const edge of edges.builds_on) {
672
- addEdge(edge, 'to_id', 'builds_on', 0.75, 0.9);
673
- }
674
- // Add built_on_by edges (someone built on this decision)
675
- for (const edge of edges.built_on_by) {
676
- addEdge(edge, 'from_id', 'built_on_by', 0.75, 0.9);
677
- }
678
- // Add debates edges (alternative view)
679
- for (const edge of edges.debates) {
680
- addEdge(edge, 'to_id', 'debates', 0.65, 0.85);
681
- }
682
- // Add debated_by edges
683
- for (const edge of edges.debated_by) {
684
- addEdge(edge, 'from_id', 'debated_by', 0.65, 0.85);
685
- }
686
- // Add synthesizes edges (unified approach)
687
- for (const edge of edges.synthesizes) {
688
- addEdge(edge, 'to_id', 'synthesizes', 0.7, 0.88);
689
- }
690
- // Add synthesized_by edges
691
- for (const edge of edges.synthesized_by) {
692
- addEdge(edge, 'from_id', 'synthesized_by', 0.7, 0.88);
693
- }
694
- }
695
- catch (error) {
696
- (0, debug_logger_js_1.warn)(`Failed to get semantic edges for ${candidate.id}: ${error instanceof Error ? error.message : String(error)}`);
697
- }
698
- }
699
- // 3. Convert Map to Array
700
- const allResults = Array.from(graphEnhanced.values());
701
- // 4. Sort: Interleave expanded results after their related primary
702
- // This ensures edge-connected decisions appear near their source
703
- const primaryResults = allResults
704
- .filter((r) => primaryIds.has(r.id))
705
- .sort((a, b) => {
706
- const scoreA = a.final_score || a.similarity || 0;
707
- const scoreB = b.final_score || b.similarity || 0;
708
- return scoreB - scoreA;
709
- });
710
- const expandedResults = allResults.filter((r) => !primaryIds.has(r.id));
711
- // Build final results: each primary followed by its related expanded results
712
- const results = [];
713
- for (const primary of primaryResults) {
714
- results.push(primary);
715
- // Find expanded results related to this primary
716
- const relatedExpanded = expandedResults.filter((e) => e.related_to === primary.id);
717
- // Sort related by graph_rank (higher first)
718
- relatedExpanded.sort((a, b) => (b.graph_rank || 0) - (a.graph_rank || 0));
719
- // Add related expanded results right after their primary
720
- results.push(...relatedExpanded);
721
- }
722
- // Add any orphaned expanded results (shouldn't happen, but safety net)
723
- const includedIds = new Set(results.map((r) => r.id));
724
- const orphaned = expandedResults.filter((e) => !includedIds.has(e.id));
725
- results.push(...orphaned);
726
- return results;
727
- }
728
- function applyRecencyBoost(results, options = {}) {
729
- const { recencyWeight = 0.3, recencyScale = 7, recencyDecay = 0.5, disableRecency = false, } = options;
730
- if (disableRecency || recencyWeight === 0) {
731
- return results;
732
- }
733
- const now = Date.now(); // Current timestamp in milliseconds
734
- return results
735
- .map((r) => {
736
- // created_at is stored in milliseconds in the database
737
- const createdAt = typeof r.created_at === 'number' ? r.created_at : Date.parse(r.created_at || '0');
738
- const ageInDays = (now - createdAt) / (86400 * 1000);
739
- // Gaussian Decay: exp(-((age / scale)^2) / (2 * ln(1 / decay)))
740
- // At scale days: score = decay (e.g., 7 days = 50%)
741
- const gaussianDecay = Math.exp(-Math.pow(ageInDays / recencyScale, 2) / (2 * Math.log(1 / recencyDecay)));
742
- // Combine semantic similarity with recency
743
- const similarity = r.similarity ?? 0;
744
- const finalScore = similarity * (1 - recencyWeight) + gaussianDecay * recencyWeight;
745
- return {
746
- ...r,
747
- recency_score: gaussianDecay,
748
- recency_age_days: Math.round(ageInDays * 10) / 10,
749
- final_score: finalScore,
750
- };
751
- })
752
- .sort((a, b) => (b.final_score ?? 0) - (a.final_score ?? 0));
753
- }
754
- /** Phase 3 Task 33: learned-ranker meta attached to mama.suggest response. */
755
- function buildRankerMeta(applied, modelId, skippedReason) {
756
- const meta = {
757
- model_id: modelId,
758
- feature_set_version: ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION,
759
- applied,
760
- mode: 'offline',
761
- };
762
- if (skippedReason) {
763
- meta.skipped_reason = skippedReason;
764
- }
765
- return meta;
766
- }
767
- function resultRecord(result) {
768
- if (typeof result.record === 'object' &&
769
- result.record !== null &&
770
- !Array.isArray(result.record)) {
771
- return result.record;
772
- }
773
- return {};
774
- }
775
- function stringOrNull(value) {
776
- if (value === null || value === undefined) {
777
- return null;
778
- }
779
- return String(value);
780
- }
781
- function numberOrNull(value) {
782
- if (typeof value !== 'number' || !Number.isFinite(value)) {
783
- return null;
784
- }
785
- return value;
786
- }
787
- function confidenceValue(record, fallback) {
788
- const numeric = numberOrNull(record.confidence);
789
- if (numeric !== null) {
790
- return numeric;
791
- }
792
- switch (record.confidence) {
793
- case 'high':
794
- return 0.9;
795
- case 'medium':
796
- return 0.6;
797
- case 'low':
798
- return 0.3;
799
- default:
800
- return fallback;
801
- }
802
- }
803
- function mapRolledUpResult(result) {
804
- const record = resultRecord(result);
805
- const retrievalDiagnostics = result.retrieval_diagnostics;
806
- const topic = stringOrNull(record.topic ?? record.title) ?? result.source_id;
807
- // For wiki_page leaves, prefer the markdown body (`content`) in `decision` so
808
- // downstream consumers see the meaningful body rather than the short title.
809
- // Decision/checkpoint records use their own fields (summary/decision).
810
- const isWikiPageLeaf = result.source_type === 'wiki_page';
811
- const decision = isWikiPageLeaf
812
- ? (stringOrNull(record.content ?? record.summary ?? record.decision ?? record.title) ??
813
- result.source_id)
814
- : (stringOrNull(record.summary ?? record.decision ?? record.title ?? record.content) ??
815
- result.source_id);
816
- const reasoning = stringOrNull(record.details ?? record.reasoning ?? record.status_reason ?? record.content) ??
817
- '';
818
- return {
819
- id: result.source_id,
820
- topic,
821
- decision,
822
- reasoning,
823
- confidence: confidenceValue(record, result.score),
824
- // Back-compat with pre-rollup consumers (swarm-mama-adapter tests,
825
- // older callers): similarity mirrors the retrieval score when we don't
826
- // have a separate similarity measure. retrieval_score remains the
827
- // authoritative field for Phase 3 code paths.
828
- similarity: result.score,
829
- retrieval_score: result.score,
830
- created_at: record.created_at ?? null,
831
- event_date: record.event_date ?? null,
832
- event_datetime: record.event_datetime ?? null,
833
- graph_source: retrievalDiagnostics?.graph_source ?? 'primary',
834
- graph_rank: 1,
835
- related_to: null,
836
- edge_reason: null,
837
- case_id: result.case_id,
838
- source_type: result.source_type,
839
- contributing_leaves: result.contributing_leaves ?? null,
840
- ...(result.contributing_leaf_diagnostics
841
- ? { contributing_leaf_diagnostics: result.contributing_leaf_diagnostics }
842
- : {}),
843
- ...(retrievalDiagnostics ? { retrieval_diagnostics: retrievalDiagnostics } : {}),
844
- };
845
- }
846
434
  async function save(params) {
847
- return saveInternal(params);
435
+ await (0, db_manager_js_1.initDB)();
436
+ return saveInternal((0, db_manager_js_1.getAdapter)(), params);
848
437
  }
849
- async function saveWithTrustedProvenance(params, options) {
850
- return saveInternal(params, options);
438
+ // Facade boundary: the ambient handle is resolved only here so the public
439
+ // (input)-only signatures stay intact. Instance-owning callers use
440
+ // createMamaApi(adapter) instead.
441
+ async function suggest(userQuestion, options = {}
442
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
443
+ ) {
444
+ await (0, db_manager_js_1.initDB)();
445
+ return (0, api_js_1.suggestInAdapter)((0, db_manager_js_1.getAdapter)(), userQuestion, options);
851
446
  }
852
- /**
853
- * Annotate suggest results with topic currency. MAMA semantics treat topic
854
- * reuse as supersession, so a result row is stale when the DB holds a newer
855
- * decision row for the same topic - even when its status column still says
856
- * 'active' (historical chains predate status maintenance). Surfacing
857
- * `superseded_by_newer` lets consumers (and LLMs) tell current truth from
858
- * superseded history; delta-bench measured 80% -> 92.5% answer accuracy when
859
- * the current row is explicitly marked.
860
- */
861
- function annotateTopicCurrency(rows, scopes) {
862
- try {
863
- const topics = [
864
- ...new Set(rows.map((r) => r.topic).filter((t) => typeof t === 'string' && t.length > 0)),
865
- ];
866
- if (topics.length === 0) {
867
- return rows;
868
- }
869
- const adapter = (0, memory_store_js_1.getAdapter)();
870
- const placeholders = topics.map(() => '?').join(',');
871
- // Supersession is scope-isolated (mirrors the save-path same-topic lookup):
872
- // a newer row for the same topic in ANOTHER project/channel must not mark
873
- // this scope's current truth as stale. When the search ran scoped, the
874
- // currency comparison is restricted to the same scopes. Rows with an
875
- // excluded status (quarantined etc.) never count as "the newer truth".
876
- const statusFilter = "AND (d.status IS NULL OR d.status NOT IN ('superseded','quarantined','contradicted','stale'))";
877
- let all;
878
- if (scopes && scopes.length > 0) {
879
- const scopePairs = scopes.flatMap((s) => [s.kind, s.id]);
880
- const scopePlaceholders = scopes
881
- .map(() => '(ms.kind = ? AND ms.external_id = ?)')
882
- .join(' OR ');
883
- all = adapter
884
- .prepare(`SELECT DISTINCT d.id, d.topic, d.created_at
885
- FROM decisions d
886
- JOIN memory_scope_bindings msb ON msb.memory_id = d.id
887
- JOIN memory_scopes ms ON ms.id = msb.scope_id
888
- WHERE d.topic IN (${placeholders}) AND (${scopePlaceholders}) ${statusFilter}`)
889
- .all(...topics, ...scopePairs);
890
- }
891
- else {
892
- all = adapter
893
- .prepare(`SELECT id, topic, created_at FROM decisions d WHERE topic IN (${placeholders}) ${statusFilter}`)
894
- .all(...topics);
895
- }
896
- // created_at mixes epoch seconds, epoch ms, and TEXT datetimes in live DBs.
897
- const toMs = (v) => {
898
- if (typeof v === 'number' && Number.isFinite(v)) {
899
- return v > 1e12 ? v : v * 1000;
900
- }
901
- if (typeof v === 'string') {
902
- const trimmed = v.trim();
903
- if (/^\d+$/.test(trimmed)) {
904
- return toMs(Number(trimmed));
905
- }
906
- // Stamp UTC only when the text carries no timezone marker: appending
907
- // 'Z' to a value that already ends in 'Z' or an offset yields NaN, and
908
- // a bare T-form datetime would otherwise parse as ambiguous local time.
909
- const formatted = trimmed.includes('T') ? trimmed : trimmed.replace(' ', 'T');
910
- const hasTz = formatted.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(formatted);
911
- const parsed = Date.parse(hasTz ? formatted : formatted + 'Z');
912
- // Unparseable rows are skipped per-row by the caller (NaN), never
913
- // thrown: one legacy bad timestamp must not blank the whole annotation.
914
- return Number.isFinite(parsed) ? parsed : NaN;
915
- }
916
- return NaN;
917
- };
918
- const newestByTopic = new Map();
919
- for (const row of all) {
920
- const ms = toMs(row.created_at);
921
- if (!Number.isFinite(ms)) {
922
- continue;
923
- }
924
- const cur = newestByTopic.get(row.topic);
925
- // Deterministic tiebreak on equal timestamps: higher id wins (ids embed
926
- // their creation ms, so lexicographic order is stable and monotonic-ish).
927
- if (!cur || ms > cur.ms || (ms === cur.ms && row.id > cur.id)) {
928
- newestByTopic.set(row.topic, { ms, id: row.id });
929
- }
930
- }
931
- return rows.map((r) => {
932
- const newest = typeof r.topic === 'string' ? newestByTopic.get(r.topic) : undefined;
933
- if (!newest) {
934
- return r;
935
- }
936
- return { ...r, superseded_by_newer: newest.id !== r.id };
937
- });
938
- }
939
- catch (err) {
940
- // Annotation is additive - a failure must degrade to unannotated results,
941
- // never break the search itself. But it is logged loudly, not swallowed.
942
- (0, debug_logger_js_1.warn)(`[mama.suggest] topic-currency annotation failed: ${String(err)}`);
943
- return rows;
944
- }
447
+ async function recall(topic, options = {}) {
448
+ await (0, db_manager_js_1.initDB)();
449
+ return recallInAdapter((0, db_manager_js_1.getAdapter)(), topic, options);
945
450
  }
946
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
947
- async function suggest(userQuestion, options = {}) {
948
- if (!userQuestion || typeof userQuestion !== 'string') {
949
- throw new Error('mama.suggest() requires userQuestion (string)');
950
- }
951
- const { format = 'json', limit = 5, threshold, useReranking = false, rerankWithLearned = false,
952
- // Recency boosting parameters (Gaussian Decay - Elasticsearch style)
953
- recencyWeight = 0.3, // 0-1: How much to weight recency (0.3 = 70% semantic, 30% recency)
954
- recencyScale = 7, // Days until recency score drops to 50%
955
- recencyDecay = 0.5, // Score at scale point (0.5 = 50%)
956
- disableRecency = false, // Set true to disable recency boosting entirely
957
- strict, strictness, includeRelated, minLexicalSupport, diagnostics: includeDiagnostics, } = options;
958
- const normalizedSearchOptions = (0, search_quality_js_1.normalizeSearchQualityOptions)({
959
- threshold,
960
- strict,
961
- strictness,
962
- disableRecency,
963
- includeRelated,
964
- topicPrefix: options.topicPrefix,
965
- minLexicalSupport,
966
- diagnostics: includeDiagnostics,
967
- });
968
- const memoryV2QualityContractRequested = threshold !== undefined ||
969
- strict !== undefined ||
970
- strictness !== undefined ||
971
- includeRelated !== undefined ||
972
- minLexicalSupport !== undefined ||
973
- includeDiagnostics === true ||
974
- options.topicPrefix !== undefined ||
975
- options.scopes !== undefined;
976
- const rerankPoolLimit = rerankWithLearned ? Math.max(limit * 4, limit + 5) : limit;
977
- try {
978
- const bundle = await (0, api_js_1.recallMemory)(userQuestion, {
979
- includeProfile: false,
980
- topicPrefix: options.topicPrefix,
981
- limit: rerankPoolLimit,
982
- threshold,
983
- strict,
984
- strictness,
985
- disableRecency,
986
- includeRelated,
987
- minLexicalSupport,
988
- diagnostics: includeDiagnostics,
989
- ...(options.scopes && { scopes: options.scopes }),
990
- });
991
- const diagnosticsByMemoryId = new Map(bundle.memories
992
- .filter((memory) => memory.retrieval_diagnostics)
993
- .map((memory) => [memory.id, memory.retrieval_diagnostics]));
994
- const rawFusedHits = bundle.fused_hits ?? [];
995
- const fusedHits = rawFusedHits.map((hit) => {
996
- if (hit.source_type !== 'decision') {
997
- return hit;
998
- }
999
- const recordDiagnostics = typeof hit.record === 'object' && hit.record !== null && !Array.isArray(hit.record)
1000
- ? hit.record.retrieval_diagnostics
1001
- : undefined;
1002
- const retrievalDiagnostics = diagnosticsByMemoryId.get(hit.source_id) ?? recordDiagnostics;
1003
- if (!retrievalDiagnostics) {
1004
- return hit;
1005
- }
1006
- const record = typeof hit.record === 'object' && hit.record !== null && !Array.isArray(hit.record)
1007
- ? { ...hit.record, retrieval_diagnostics: retrievalDiagnostics }
1008
- : hit.record;
1009
- return {
1010
- ...hit,
1011
- record,
1012
- retrieval_diagnostics: retrievalDiagnostics,
1013
- };
1014
- });
1015
- const rolledUp = fusedHits.length > 0 ? (0, search_rollup_js_1.rollUpSearchHits)({ fusedHits, adapter: (0, memory_store_js_1.getAdapter)() }) : [];
1016
- const diagnosticsResponse = includeDiagnostics === true ? { diagnostics: bundle.search_meta.diagnostics ?? null } : {};
1017
- // Phase 3 Task 33: compute base ranker meta once so every return path
1018
- // (rolledUp, memories fallback, vector-search fallback) can attach it.
1019
- // rerankWithLearned-driven rescoring still only applies to result arrays
1020
- // that match the ranker's expected shape (id + source_type + case_id).
1021
- const baseRankerMeta = useReranking
1022
- ? buildRankerMeta(false, null, 'llm_reranking_requested')
1023
- : rerankWithLearned
1024
- ? null // marker: rescoring requested, actual meta set per-path after rescore
1025
- : buildRankerMeta(false, null, 'feature_disabled');
1026
- const applyLearnedRanker = (results) => {
1027
- if (baseRankerMeta !== null) {
1028
- return { results, meta: baseRankerMeta };
1029
- }
1030
- // Phase 3 Task 33: the caller opted in via rerankWithLearned, but the
1031
- // runtime `search_ranker_enabled` gate still has final say. This lets
1032
- // operators disable the learned ranker globally during rollback without
1033
- // touching any caller code.
1034
- let runtimeEnabled = true;
1035
- try {
1036
- runtimeEnabled = (0, ranker_rescore_js_1.isSearchRankerEnabled)((0, memory_store_js_1.getAdapter)());
1037
- }
1038
- catch (err) {
1039
- (0, debug_logger_js_1.warn)(`[mama.suggest] isSearchRankerEnabled check failed: ${String(err)}`);
1040
- }
1041
- if (!runtimeEnabled) {
1042
- return { results, meta: buildRankerMeta(false, null, 'feature_disabled') };
1043
- }
1044
- try {
1045
- const rescored = (0, ranker_rescore_js_1.rescoreSearchResults)((0, memory_store_js_1.getAdapter)(), {
1046
- query: userQuestion,
1047
- results,
1048
- });
1049
- return {
1050
- results: rescored.results,
1051
- meta: buildRankerMeta(rescored.skipped_reason === undefined, rescored.model_id, rescored.skipped_reason),
1052
- };
1053
- }
1054
- catch (err) {
1055
- (0, debug_logger_js_1.warn)(`[mama.suggest] learned-ranker rescore failed: ${String(err)}`);
1056
- return { results, meta: buildRankerMeta(false, null, 'rescore_error') };
1057
- }
1058
- };
1059
- const summarizeGraphExpansion = (rows) => {
1060
- const sources = {
1061
- primary: 0,
1062
- supersedes_chain: 0,
1063
- refines: 0,
1064
- refined_by: 0,
1065
- contradicts: 0,
1066
- };
1067
- let expandedCount = 0;
1068
- for (const row of rows) {
1069
- const graphSource = row.graph_source ?? 'primary';
1070
- if (graphSource === 'primary') {
1071
- sources.primary += 1;
1072
- continue;
1073
- }
1074
- expandedCount += 1;
1075
- if (graphSource in sources) {
1076
- const key = graphSource;
1077
- sources[key] += 1;
1078
- }
1079
- }
1080
- return {
1081
- total_results: rows.length,
1082
- primary_count: sources.primary,
1083
- expanded_count: expandedCount,
1084
- sources,
1085
- };
1086
- };
1087
- if (rolledUp.length > 0) {
1088
- const filteredResults = rolledUp.slice(0, rerankPoolLimit);
1089
- const { results: mappedResults, meta: rankerMeta } = applyLearnedRanker(filteredResults.map(mapRolledUpResult));
1090
- const limitedResults = mappedResults.slice(0, limit);
1091
- if (format === 'markdown') {
1092
- const context = limitedResults
1093
- .map((result, index) => `${index + 1}. [${result.topic}] ${result.decision}\n ${result.reasoning}`)
1094
- .join('\n');
1095
- return `🔍 Search method: memory_v2\n${context}`;
1096
- }
1097
- return {
1098
- query: userQuestion,
1099
- results: annotateTopicCurrency(limitedResults, options.scopes),
1100
- ...diagnosticsResponse,
1101
- meta: {
1102
- count: limitedResults.length,
1103
- search_method: 'memory_v2',
1104
- threshold: normalizedSearchOptions.threshold,
1105
- recency_boost: disableRecency
1106
- ? null
1107
- : {
1108
- weight: recencyWeight,
1109
- scale: recencyScale,
1110
- decay: recencyDecay,
1111
- },
1112
- graph_expansion: summarizeGraphExpansion(limitedResults),
1113
- ranker: rankerMeta,
1114
- },
1115
- };
1116
- }
1117
- if (bundle.memories.length > 0) {
1118
- // recallMemory uses RRF fusion — confidence is overwritten with the normalized
1119
- // retrieval score (0-1 range, where 1.0 = best match in this result set).
1120
- // The original stored confidence is lost after RRF normalization.
1121
- // We capture the retrieval score separately so `similarity` reflects search
1122
- // relevance while `confidence` is passed through as-is from the bundle.
1123
- const filteredMemories = bundle.memories.slice(0, rerankPoolLimit);
1124
- const baseRows = filteredMemories.map((memory) => ({
1125
- id: memory.id,
1126
- topic: memory.topic,
1127
- decision: memory.summary,
1128
- reasoning: memory.details,
1129
- confidence: memory.confidence,
1130
- // recallMemory currently normalizes fused retrieval rank into `confidence`.
1131
- // Keep that value visible as retrieval_score, but do not pretend it is
1132
- // semantic similarity; save-time warning logic keys off `similarity`.
1133
- similarity: null,
1134
- retrieval_score: memory.confidence ?? null,
1135
- final_score: memory.confidence ?? null,
1136
- created_at: memory.created_at,
1137
- event_date: memory.event_date ?? null,
1138
- event_datetime: memory.event_datetime ?? null,
1139
- graph_source: memory.retrieval_diagnostics?.graph_source ?? 'primary',
1140
- graph_rank: 1,
1141
- related_to: null,
1142
- edge_reason: null,
1143
- case_id: null,
1144
- source_type: memory.kind ??
1145
- memory.source?.source_type ??
1146
- memory.source_type ??
1147
- memory.type ??
1148
- 'decision',
1149
- ...(memory.retrieval_diagnostics
1150
- ? { retrieval_diagnostics: memory.retrieval_diagnostics }
1151
- : {}),
1152
- }));
1153
- const { results: rankedRows, meta: rankerMeta } = applyLearnedRanker(baseRows);
1154
- const limitedRows = rankedRows.slice(0, limit);
1155
- if (format === 'markdown') {
1156
- const context = limitedRows
1157
- .map((row, index) => `${index + 1}. [${row.topic}] ${row.decision}\n ${row.reasoning}`)
1158
- .join('\n');
1159
- return `🔍 Search method: memory_v2\n${context}`;
1160
- }
1161
- return {
1162
- query: userQuestion,
1163
- results: annotateTopicCurrency(limitedRows, options.scopes),
1164
- ...diagnosticsResponse,
1165
- meta: {
1166
- count: limitedRows.length,
1167
- search_method: 'memory_v2',
1168
- threshold: normalizedSearchOptions.threshold,
1169
- recency_boost: disableRecency
1170
- ? null
1171
- : {
1172
- weight: recencyWeight,
1173
- scale: recencyScale,
1174
- decay: recencyDecay,
1175
- },
1176
- graph_expansion: summarizeGraphExpansion(limitedRows),
1177
- ranker: rankerMeta,
1178
- },
1179
- };
1180
- }
1181
- if (memoryV2QualityContractRequested) {
1182
- const emptyRows = [];
1183
- const { meta: rankerMeta } = applyLearnedRanker(emptyRows);
1184
- if (format === 'markdown') {
1185
- return '🔍 Search method: memory_v2\n';
1186
- }
1187
- return {
1188
- query: userQuestion,
1189
- results: emptyRows,
1190
- ...diagnosticsResponse,
1191
- meta: {
1192
- count: 0,
1193
- search_method: 'memory_v2',
1194
- threshold: normalizedSearchOptions.threshold,
1195
- recency_boost: disableRecency
1196
- ? null
1197
- : {
1198
- weight: recencyWeight,
1199
- scale: recencyScale,
1200
- decay: recencyDecay,
1201
- },
1202
- graph_expansion: summarizeGraphExpansion(emptyRows),
1203
- ranker: rankerMeta,
1204
- },
1205
- };
1206
- }
1207
- // 1. Try vector search first (if sqlite-vss is available)
1208
- // eslint-disable-next-line no-unused-vars, @typescript-eslint/no-explicit-any
1209
- let results = [];
1210
- let searchMethod = 'vector';
1211
- try {
1212
- // Check if vector search is available
1213
- if (!(0, memory_store_js_1.getAdapter)().vectorSearchEnabled) {
1214
- throw new Error('Vector search not available');
1215
- }
1216
- // Generate query embedding
1217
- const queryEmbedding = await (0, embeddings_js_1.generateEmbedding)(userQuestion, 'query');
1218
- // Adaptive threshold (shorter queries need higher confidence)
1219
- const wordCount = userQuestion.split(/\s+/).length;
1220
- const adaptiveThreshold = threshold !== undefined ? threshold : wordCount < 3 ? 0.7 : 0.6;
1221
- // Vector search
1222
- results = await (0, memory_store_js_1.vectorSearch)(queryEmbedding, rerankPoolLimit * 2, 0.5); // Get more candidates
1223
- // Filter by adaptive threshold
1224
- results = results.filter((r) => r.similarity >= adaptiveThreshold);
1225
- // Stage 1.4: Temporal boost — detect time-related queries and boost matching results
1226
- {
1227
- const temporalPatterns = [
1228
- // English
1229
- /\b(yesterday|today|last\s+(?:week|month|year)|(\d+)\s+(?:days?|weeks?|months?)\s+ago)\b/i,
1230
- /\b(before|after|since|until|during)\s+\w+/i,
1231
- /\b(how\s+long|when\s+did|what\s+date|what\s+day)\b/i,
1232
- // Korean
1233
- /(?:어제|오늘|그제|지난\s*(?:주|달|해)|(\d+)\s*(?:일|주|달|개월)\s*(?:전|후|뒤))/,
1234
- /(?:언제|얼마나|며칠|몇\s*(?:일|주|달|개월))/,
1235
- ];
1236
- const isTemporalQuery = temporalPatterns.some((p) => p.test(userQuestion));
1237
- if (isTemporalQuery && results.length > 0) {
1238
- // Boost results that contain date/time references in their content
1239
- const datePatterns = [
1240
- /\d{4}[-/]\d{1,2}[-/]\d{1,2}/,
1241
- /(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\w*\s+\d/i,
1242
- /\d+\s*(?:일|월|년|주|시간|분)/,
1243
- /(?:monday|tuesday|wednesday|thursday|friday|saturday|sunday)/i,
1244
- /(?:월요일|화요일|수요일|목요일|금요일|토요일|일요일)/,
1245
- ];
1246
- for (const result of results) {
1247
- const content = `${result.decision || ''} ${result.reasoning || ''}`;
1248
- const hasDateRef = datePatterns.some((p) => p.test(content));
1249
- if (hasDateRef) {
1250
- result.similarity = Math.min(1.0, (result.similarity || 0) + 0.1);
1251
- }
1252
- }
1253
- results.sort((a, b) => (b.similarity || 0) - (a.similarity || 0));
1254
- }
1255
- }
1256
- // Stage 1.5: Apply recency boosting (Gaussian Decay)
1257
- // Allows Claude to adjust search strategy (recent vs historical)
1258
- if (results.length > 0 && !disableRecency) {
1259
- results = applyRecencyBoost(results, {
1260
- recencyWeight,
1261
- recencyScale,
1262
- recencyDecay,
1263
- disableRecency,
1264
- });
1265
- searchMethod = 'vector+recency';
1266
- }
1267
- // Stage 1.7: FTS5 hybrid merge (Haiku Memory Layer)
1268
- {
1269
- try {
1270
- const ftsResults = await (0, db_manager_js_1.fts5Search)(userQuestion, rerankPoolLimit * 2);
1271
- if (ftsResults.length > 0) {
1272
- // Normalize FTS5 ranks (BM25 returns negative values, closer to 0 = better)
1273
- const maxRank = Math.max(...ftsResults.map((r) => Math.abs(r.rank)));
1274
- const ftsMap = new Map(ftsResults.map((r) => [r.id, maxRank > 0 ? 1 - Math.abs(r.rank) / maxRank : 0.5]));
1275
- // Tunable hybrid weights (env: MAMA_VECTOR_WEIGHT, MAMA_FTS5_WEIGHT)
1276
- const vectorWeight = parseFloat(process.env.MAMA_VECTOR_WEIGHT || '0.6');
1277
- const fts5Weight = parseFloat(process.env.MAMA_FTS5_WEIGHT || '0.4');
1278
- // Merge: boost existing results that also matched FTS5
1279
- for (const result of results) {
1280
- const ftsScore = ftsMap.get(result.id);
1281
- if (ftsScore !== undefined) {
1282
- result.similarity = vectorWeight * result.similarity + fts5Weight * ftsScore;
1283
- ftsMap.delete(result.id);
1284
- }
1285
- }
1286
- // Add FTS5-only results (not in embedding results)
1287
- for (const [id, ftsScore] of ftsMap) {
1288
- const ftsResult = ftsResults.find((r) => r.id === id);
1289
- if (ftsResult) {
1290
- // Need to get full decision record
1291
- const adapter = (0, memory_store_js_1.getAdapter)();
1292
- const stmt = adapter.prepare('SELECT * FROM decisions WHERE id = ? AND superseded_by IS NULL');
1293
- const decision = stmt.get(id);
1294
- if (decision) {
1295
- results.push({
1296
- ...decision,
1297
- similarity: fts5Weight * ftsScore, // Only FTS5 score component
1298
- graph_source: 'fts5',
1299
- });
1300
- }
1301
- }
1302
- }
1303
- // Re-sort by similarity
1304
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1305
- results.sort((a, b) => (b.similarity || 0) - (a.similarity || 0));
1306
- searchMethod = disableRecency ? 'vector+fts5' : 'vector+recency+fts5';
1307
- }
1308
- }
1309
- catch {
1310
- // FTS5 not available, continue with embedding-only results
1311
- }
1312
- }
1313
- // Stage 2: Graph expansion (NEW - Phase 1)
1314
- // Expand candidates with supersedes chain and semantic edges
1315
- if (results.length > 0) {
1316
- const graphEnhanced = await expandWithGraph(results);
1317
- results = graphEnhanced;
1318
- searchMethod = disableRecency ? 'vector+graph' : 'vector+recency+graph';
1319
- }
1320
- // Stage 2.5: is_static boost (after graph expansion to preserve sort order)
1321
- for (const result of results) {
1322
- if (result.is_static === 1) {
1323
- result.final_score = Math.min(1.0, (result.final_score ?? result.similarity ?? 0) + 0.2);
1324
- }
1325
- }
1326
- // Re-sort by final_score after is_static boost
1327
- results.sort((a, b) => (b.final_score ?? b.similarity ?? 0) - (a.final_score ?? a.similarity ?? 0));
1328
- }
1329
- catch (vectorError) {
1330
- // Fallback to keyword search if vector search unavailable
1331
- (0, debug_logger_js_1.warn)(`Vector search failed: ${vectorError instanceof Error ? vectorError.message : String(vectorError)}, falling back to keyword search`);
1332
- searchMethod = 'keyword';
1333
- // Keyword search fallback
1334
- const adapter = (0, memory_store_js_1.getAdapter)();
1335
- const keywords = userQuestion
1336
- .toLowerCase()
1337
- .split(/\s+/)
1338
- .filter((w) => w.length > 2); // Filter short words
1339
- if (keywords.length === 0) {
1340
- if (format === 'markdown') {
1341
- return `💡 Hint: Please be more specific.\nExample: "Railway Volume settings" or "mesh parameter optimization"`;
1342
- }
1343
- return null; // JSON mode returns null for empty/invalid queries
1344
- }
1345
- // Build LIKE query for each keyword
1346
- const likeConditions = keywords.map(() => '(topic LIKE ? OR decision LIKE ?)').join(' OR ');
1347
- const likeParams = keywords.flatMap((k) => [`%${k}%`, `%${k}%`]);
1348
- const stmt = adapter.prepare(`
1349
- SELECT * FROM decisions
1350
- WHERE ${likeConditions}
1351
- AND superseded_by IS NULL
1352
- ORDER BY created_at DESC
1353
- LIMIT ?
1354
- `);
1355
- const rows = (await stmt.all(...likeParams, rerankPoolLimit));
1356
- results = rows.map((row) => ({
1357
- ...row,
1358
- similarity: 0.75, // Assign moderate similarity for keyword matches
1359
- }));
1360
- // Stage 2: Graph expansion for keyword results (Phase 1)
1361
- if (results.length > 0) {
1362
- const graphEnhanced = await expandWithGraph(results);
1363
- results = graphEnhanced;
1364
- searchMethod = 'keyword+graph';
1365
- }
1366
- }
1367
- if (results.length === 0) {
1368
- if (format === 'markdown') {
1369
- const wordCount = userQuestion.split(/\s+/).length;
1370
- if (wordCount < 3) {
1371
- return `💡 Hint: Please be more specific.\nExample: "Why did we choose COMPLEX mesh structure?" or "What parameters are used for large layers?"`;
1372
- }
1373
- }
1374
- return null;
1375
- }
1376
- // 5. Optional: LLM re-ranking (only if requested)
1377
- if (useReranking) {
1378
- results = await rerankWithLLM(userQuestion, results);
1379
- }
1380
- const rerankCandidateResults = results.slice(0, rerankPoolLimit);
1381
- const vectorRows = rerankCandidateResults.map((r) => ({
1382
- id: r.id,
1383
- topic: r.topic,
1384
- decision: r.decision,
1385
- reasoning: r.reasoning,
1386
- confidence: r.confidence,
1387
- similarity: r.similarity,
1388
- created_at: r.created_at,
1389
- event_date: r.event_date ?? null,
1390
- event_datetime: r.event_datetime ?? null,
1391
- // Recency metadata (NEW - Gaussian Decay)
1392
- recency_score: r.recency_score,
1393
- recency_age_days: r.recency_age_days,
1394
- final_score: r.final_score || r.similarity, // Falls back to similarity if no recency
1395
- retrieval_score: r.similarity ?? null,
1396
- // Graph metadata (NEW - Phase 1)
1397
- graph_source: r.graph_source || 'primary',
1398
- graph_rank: r.graph_rank || 1.0,
1399
- related_to: r.related_to || null,
1400
- edge_reason: r.edge_reason || null,
1401
- case_id: null,
1402
- source_type: 'decision',
1403
- }));
1404
- const { results: rankedVectorRows, meta: rankerMeta } = applyLearnedRanker(vectorRows);
1405
- const finalResults = rankedVectorRows.slice(0, limit);
1406
- // Markdown format (for human display)
1407
- if (format === 'markdown') {
1408
- const context = (0, decision_formatter_js_1.formatContext)(finalResults, { maxTokens: 500 });
1409
- // Add graph expansion summary if applicable
1410
- let graphSummary = '';
1411
- if (searchMethod.includes('graph')) {
1412
- const primaryCount = finalResults.filter((r) => r.graph_source === 'primary').length;
1413
- const expandedCount = finalResults.filter((r) => r.graph_source !== 'primary').length;
1414
- graphSummary = `\n📊 Graph expansion: ${primaryCount} primary + ${expandedCount} related (supersedes/refines/contradicts)\n`;
1415
- }
1416
- return `🔍 Search method: ${searchMethod}${graphSummary}\n${context}`;
1417
- }
1418
- // Calculate graph expansion stats
1419
- const graphStats = {
1420
- total_results: finalResults.length,
1421
- primary_count: finalResults.filter((r) => r.graph_source === 'primary').length,
1422
- expanded_count: finalResults.filter((r) => r.graph_source !== 'primary').length,
1423
- sources: {
1424
- primary: finalResults.filter((r) => r.graph_source === 'primary').length,
1425
- supersedes_chain: finalResults.filter((r) => r.graph_source === 'supersedes_chain').length,
1426
- refines: finalResults.filter((r) => r.graph_source === 'refines').length,
1427
- refined_by: finalResults.filter((r) => r.graph_source === 'refined_by').length,
1428
- contradicts: finalResults.filter((r) => r.graph_source === 'contradicts').length,
1429
- },
1430
- };
1431
- return {
1432
- query: userQuestion,
1433
- results: finalResults,
1434
- meta: {
1435
- count: finalResults.length,
1436
- search_method: searchMethod,
1437
- threshold: threshold || 'adaptive',
1438
- // Recency boosting config (NEW - Gaussian Decay)
1439
- recency_boost: disableRecency
1440
- ? null
1441
- : {
1442
- weight: recencyWeight,
1443
- scale: recencyScale,
1444
- decay: recencyDecay,
1445
- },
1446
- // Graph expansion stats (NEW - Phase 1)
1447
- graph_expansion: searchMethod.includes('graph') ? graphStats : null,
1448
- ranker: rankerMeta,
1449
- },
1450
- };
1451
- }
1452
- catch (error) {
1453
- // Graceful degradation
1454
- (0, debug_logger_js_1.warn)(`mama.suggest() failed: ${error instanceof Error ? error.message : String(error)}`);
1455
- return null;
1456
- }
451
+ async function expandWithGraph(candidates) {
452
+ await (0, db_manager_js_1.initDB)();
453
+ return (0, api_js_1.expandWithGraphInAdapter)((0, db_manager_js_1.getAdapter)(), candidates);
1457
454
  }
1458
- /**
1459
- * Re-rank search results using local LLM (optional enhancement)
1460
- *
1461
- * @param {string} userQuestion - User's question
1462
- * @param {Array} results - Vector search results
1463
- * @returns {Promise<Array>} Re-ranked results
1464
- */
1465
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1466
- async function rerankWithLLM(userQuestion, results) {
1467
- try {
1468
- const prompt = `User asked: "${userQuestion}"
1469
-
1470
- Found decisions (ranked by vector similarity):
1471
- ${results.map((r, i) => `${i + 1}. [${(r.similarity ?? 0).toFixed(3)}] ${r.topic}: ${r.decision.substring(0, 60)}...`).join('\n')}
1472
-
1473
- Re-rank these by actual relevance to the user's intent (not just keyword similarity).
1474
- Return JSON: { "ranking": [index1, index2, ...] } (0-based indices)
1475
-
1476
- Example: { "ranking": [2, 0, 4, 1, 3] } means 3rd is most relevant, then 1st, then 5th...`;
1477
- const response = await (0, ollama_client_js_1.generate)(prompt, {
1478
- format: 'json',
1479
- temperature: 0.3,
1480
- max_tokens: 100,
1481
- timeout: 3000,
1482
- });
1483
- const parsed = typeof response === 'string' ? JSON.parse(response) : response;
1484
- // Reorder results based on LLM ranking
1485
- return parsed.ranking.map((idx) => results[idx]).filter(Boolean);
1486
- }
1487
- catch (error) {
1488
- (0, debug_logger_js_1.warn)(`Re-ranking failed: ${error instanceof Error ? error.message : String(error)}, using vector ranking`);
1489
- return results; // Fallback to vector ranking
1490
- }
455
+ async function updateOutcome(decisionId, outcome) {
456
+ await (0, db_manager_js_1.initDB)();
457
+ return (0, api_js_1.updateOutcomeInAdapter)((0, db_manager_js_1.getAdapter)(), decisionId, outcome);
1491
458
  }
1492
459
  async function listDecisions(options = {}) {
1493
- const { limit = 10, format = 'json' } = options;
1494
- try {
1495
- const adapter = (0, memory_store_js_1.getAdapter)();
1496
- let decisions;
1497
- if (options.scopes && options.scopes.length > 0) {
1498
- // Scope-filtered query: JOIN memory_scope_bindings + memory_scopes
1499
- const scopeIds = await Promise.all(options.scopes.map((s) => (0, db_manager_js_1.ensureMemoryScope)(s.kind, s.id)));
1500
- const placeholders = scopeIds.map(() => '?').join(', ');
1501
- const stmt = adapter.prepare(`
1502
- SELECT DISTINCT d.* FROM decisions d
1503
- JOIN memory_scope_bindings msb ON msb.memory_id = d.id
1504
- WHERE msb.scope_id IN (${placeholders})
1505
- AND d.superseded_by IS NULL
1506
- ORDER BY COALESCE(d.event_datetime, d.created_at) DESC, d.created_at DESC
1507
- LIMIT ?
1508
- `);
1509
- decisions = await stmt.all(...scopeIds, limit);
1510
- }
1511
- else {
1512
- const stmt = adapter.prepare(`
1513
- SELECT * FROM decisions
1514
- WHERE superseded_by IS NULL
1515
- ORDER BY COALESCE(event_datetime, created_at) DESC, created_at DESC
1516
- LIMIT ?
1517
- `);
1518
- decisions = await stmt.all(limit);
1519
- }
1520
- if (format === 'markdown') {
1521
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1522
- return (0, decision_formatter_js_1.formatList)(decisions);
1523
- }
1524
- return decisions;
1525
- }
1526
- catch (error) {
1527
- throw new Error(`mama.listDecisions() failed: ${error instanceof Error ? error.message : String(error)}`);
1528
- }
460
+ await (0, db_manager_js_1.initDB)();
461
+ return (0, api_js_1.listDecisionsInAdapter)((0, db_manager_js_1.getAdapter)(), options);
1529
462
  }
1530
- /**
1531
- * Save current session checkpoint (New Feature: Session Continuity)
1532
- *
1533
- * @param {string} summary - Summary of current session state
1534
- * @param {Array<string>} openFiles - List of currently open files
1535
- * @param {string} nextSteps - Next steps to be taken
1536
- * @returns {Promise<number>} Checkpoint ID
1537
- */
1538
463
  async function saveCheckpoint(summary, openFiles = [], nextSteps = '',
1539
464
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1540
465
  recentConversation = []) {
1541
- if (!summary) {
1542
- throw new Error('Summary is required for checkpoint');
1543
- }
1544
- try {
1545
- const adapter = (0, memory_store_js_1.getAdapter)();
1546
- const stmt = adapter.prepare(`
1547
- INSERT INTO checkpoints (timestamp, summary, open_files, next_steps, recent_conversation, status)
1548
- VALUES (?, ?, ?, ?, ?, 'active')
1549
- `);
1550
- const result = stmt.run(Date.now(), summary, JSON.stringify(openFiles), nextSteps, JSON.stringify(recentConversation || []));
1551
- return result.lastInsertRowid;
1552
- }
1553
- catch (error) {
1554
- throw new Error(`Failed to save checkpoint: ${error instanceof Error ? error.message : String(error)}`);
1555
- }
466
+ await (0, db_manager_js_1.initDB)();
467
+ return (0, api_js_1.saveCheckpointInAdapter)((0, db_manager_js_1.getAdapter)(), summary, openFiles, nextSteps, recentConversation);
1556
468
  }
1557
469
  async function loadCheckpoint() {
1558
- try {
1559
- const adapter = (0, memory_store_js_1.getAdapter)();
1560
- const stmt = adapter.prepare(`
1561
- SELECT * FROM checkpoints
1562
- WHERE status = 'active'
1563
- ORDER BY timestamp DESC
1564
- LIMIT 1
1565
- `);
1566
- const checkpoint = stmt.get();
1567
- if (checkpoint) {
1568
- try {
1569
- checkpoint.open_files =
1570
- typeof checkpoint.open_files === 'string'
1571
- ? JSON.parse(checkpoint.open_files)
1572
- : checkpoint.open_files || [];
1573
- }
1574
- catch {
1575
- checkpoint.open_files = [];
1576
- }
1577
- try {
1578
- checkpoint.recent_conversation =
1579
- typeof checkpoint.recent_conversation === 'string'
1580
- ? JSON.parse(checkpoint.recent_conversation || '[]')
1581
- : checkpoint.recent_conversation || [];
1582
- }
1583
- catch {
1584
- checkpoint.recent_conversation = [];
1585
- }
1586
- }
1587
- return checkpoint || null;
1588
- }
1589
- catch (error) {
1590
- throw new Error(`Failed to load checkpoint: ${error instanceof Error ? error.message : String(error)}`);
1591
- }
470
+ await (0, db_manager_js_1.initDB)();
471
+ return (0, api_js_1.loadCheckpointInAdapter)((0, db_manager_js_1.getAdapter)());
1592
472
  }
1593
- /**
1594
- * List recent checkpoints (New Feature: Session Continuity)
1595
- *
1596
- * @param {number} limit - Max number of checkpoints to return
1597
- * @returns {Promise<Array>} Recent checkpoints
1598
- */
1599
473
  async function listCheckpoints(limit = 10) {
1600
- try {
1601
- const adapter = (0, memory_store_js_1.getAdapter)();
1602
- const stmt = adapter.prepare(`
1603
- SELECT * FROM checkpoints
1604
- ORDER BY timestamp DESC
1605
- LIMIT ?
1606
- `);
1607
- const checkpoints = stmt.all(limit);
1608
- return checkpoints.map((c) => {
1609
- try {
1610
- c.open_files =
1611
- typeof c.open_files === 'string' ? JSON.parse(c.open_files) : c.open_files || [];
1612
- }
1613
- catch {
1614
- c.open_files = [];
1615
- }
1616
- try {
1617
- c.recent_conversation =
1618
- typeof c.recent_conversation === 'string'
1619
- ? JSON.parse(c.recent_conversation)
1620
- : c.recent_conversation || [];
1621
- }
1622
- catch {
1623
- c.recent_conversation = [];
1624
- }
1625
- return c;
1626
- });
1627
- }
1628
- catch (error) {
1629
- throw new Error(`Failed to list checkpoints: ${error instanceof Error ? error.message : String(error)}`);
1630
- }
1631
- }
1632
- async function proposeLink({ from_id, to_id, relationship, reason, decision_id, evidence, }) {
1633
- if (!from_id || !to_id || !relationship || !reason) {
1634
- throw new Error('proposeLink() requires from_id, to_id, relationship, and reason');
1635
- }
1636
- if (!['refines', 'contradicts'].includes(relationship)) {
1637
- throw new Error('proposeLink() relationship must be "refines" or "contradicts"');
1638
- }
1639
- try {
1640
- const adapter = (0, memory_store_js_1.getAdapter)();
1641
- // Use transaction to ensure atomicity (link + audit log)
1642
- adapter.transaction(() => {
1643
- // Insert link with pending approval
1644
- const stmt = adapter.prepare(`
1645
- INSERT INTO decision_edges
1646
- (from_id, to_id, relationship, reason, created_by, approved_by_user, decision_id, evidence, created_at)
1647
- VALUES (?, ?, ?, ?, 'llm', 0, ?, ?, ?)
1648
- `);
1649
- stmt.run(from_id, to_id, relationship, reason, decision_id || null, evidence || null, Date.now());
1650
- // Log to audit trail
1651
- const auditStmt = adapter.prepare(`
1652
- INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
1653
- VALUES (?, ?, ?, 'proposed', 'llm', ?, ?)
1654
- `);
1655
- auditStmt.run(from_id, to_id, relationship, reason, Date.now());
1656
- });
1657
- return;
1658
- }
1659
- catch (error) {
1660
- throw new Error(`proposeLink() failed: ${error instanceof Error ? error.message : String(error)}`);
1661
- }
1662
- }
1663
- /**
1664
- * Approve a proposed link (Epic 3 - Story 3.1)
1665
- *
1666
- * User approves a pending link, making it active.
1667
- *
1668
- * @param {string} from_id - Source decision ID
1669
- * @param {string} to_id - Target decision ID
1670
- * @param {string} relationship - Link relationship type
1671
- * @returns {Promise<void>}
1672
- */
1673
- async function approveLink(from_id, to_id, relationship) {
1674
- if (!from_id || !to_id || !relationship) {
1675
- throw new Error('approveLink() requires from_id, to_id, and relationship');
1676
- }
1677
- try {
1678
- const adapter = (0, memory_store_js_1.getAdapter)();
1679
- // Update link to approved with timestamp
1680
- const stmt = adapter.prepare(`
1681
- UPDATE decision_edges
1682
- SET approved_by_user = 1, approved_at = ?
1683
- WHERE from_id = ? AND to_id = ? AND relationship = ?
1684
- `);
1685
- stmt.run(Date.now(), from_id, to_id, relationship);
1686
- // Log approval
1687
- const auditStmt = adapter.prepare(`
1688
- INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, created_at)
1689
- VALUES (?, ?, ?, 'approved', 'user', ?)
1690
- `);
1691
- auditStmt.run(from_id, to_id, relationship, Date.now());
1692
- return;
1693
- }
1694
- catch (error) {
1695
- throw new Error(`approveLink() failed: ${error instanceof Error ? error.message : String(error)}`);
1696
- }
1697
- }
1698
- /**
1699
- * Reject a proposed link (Epic 3 - Story 3.1)
1700
- *
1701
- * User rejects a pending link, removing it from the database.
1702
- *
1703
- * @param {string} from_id - Source decision ID
1704
- * @param {string} to_id - Target decision ID
1705
- * @param {string} relationship - Link relationship type
1706
- * @param {string} [reason] - Optional reason for rejection
1707
- * @returns {Promise<void>}
1708
- */
1709
- async function rejectLink(from_id, to_id, relationship, reason) {
1710
- if (!from_id || !to_id || !relationship) {
1711
- throw new Error('rejectLink() requires from_id, to_id, and relationship');
1712
- }
1713
- try {
1714
- const adapter = (0, memory_store_js_1.getAdapter)();
1715
- // Log rejection before deletion
1716
- const auditStmt = adapter.prepare(`
1717
- INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
1718
- VALUES (?, ?, ?, 'rejected', 'user', ?, ?)
1719
- `);
1720
- auditStmt.run(from_id, to_id, relationship, reason || 'User rejected', Date.now());
1721
- // Delete the link
1722
- const stmt = adapter.prepare(`
1723
- DELETE FROM decision_edges
1724
- WHERE from_id = ? AND to_id = ? AND relationship = ?
1725
- `);
1726
- stmt.run(from_id, to_id, relationship);
1727
- return;
1728
- }
1729
- catch (error) {
1730
- throw new Error(`rejectLink() failed: ${error instanceof Error ? error.message : String(error)}`);
1731
- }
1732
- }
1733
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1734
- async function getPendingLinks(options = {}) {
1735
- try {
1736
- const adapter = (0, memory_store_js_1.getAdapter)();
1737
- const { from_id, to_id } = options;
1738
- let query = `
1739
- SELECT
1740
- e.*,
1741
- d_from.topic as from_topic,
1742
- d_from.decision as from_decision,
1743
- d_to.topic as to_topic,
1744
- d_to.decision as to_decision
1745
- FROM decision_edges e
1746
- LEFT JOIN decisions d_from ON e.from_id = d_from.id
1747
- LEFT JOIN decisions d_to ON e.to_id = d_to.id
1748
- WHERE e.approved_by_user = 0
1749
- `;
1750
- const params = [];
1751
- if (from_id) {
1752
- query += ' AND e.from_id = ?';
1753
- params.push(from_id);
1754
- }
1755
- if (to_id) {
1756
- query += ' AND e.to_id = ?';
1757
- params.push(to_id);
1758
- }
1759
- query += ' ORDER BY e.created_at DESC';
1760
- const stmt = adapter.prepare(query);
1761
- const links = await stmt.all(...params);
1762
- return links;
1763
- }
1764
- catch (error) {
1765
- throw new Error(`getPendingLinks() failed: ${error instanceof Error ? error.message : String(error)}`);
1766
- }
1767
- }
1768
- async function deprecateAutoLinks(options = {}) {
1769
- const { dryRun = true } = options;
1770
- try {
1771
- const adapter = (0, memory_store_js_1.getAdapter)();
1772
- // Identify auto-generated links (v0 legacy)
1773
- // Criteria: created_by='user' (default) AND decision_id IS NULL (no proposal context)
1774
- // Protected: decision_id IS NOT NULL OR created_by='llm' (explicitly proposed)
1775
- const identifyStmt = adapter.prepare(`
1776
- SELECT * FROM decision_edges
1777
- WHERE created_by = 'user' AND decision_id IS NULL
1778
- `);
1779
- const autoLinks = (await identifyStmt.all());
1780
- // Identify protected links for comparison
1781
- const protectedStmt = adapter.prepare(`
1782
- SELECT COUNT(*) as count FROM decision_edges
1783
- WHERE decision_id IS NOT NULL OR created_by = 'llm'
1784
- `);
1785
- const protectedResult = (await protectedStmt.get());
1786
- const protectedCount = protectedResult?.count ?? 0;
1787
- const totalLinks = autoLinks.length + protectedCount;
1788
- const autoLinkRatio = totalLinks > 0 ? (autoLinks.length / totalLinks) * 100 : 0;
1789
- if (!dryRun && autoLinks.length > 0) {
1790
- // Delete auto-generated links
1791
- const deleteStmt = adapter.prepare(`
1792
- DELETE FROM decision_edges
1793
- WHERE created_by = 'user' AND decision_id IS NULL
1794
- `);
1795
- await deleteStmt.run();
1796
- // Log deprecation to audit trail
1797
- const timestamp = Date.now();
1798
- const auditStmt = adapter.prepare(`
1799
- INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
1800
- VALUES (?, ?, ?, 'deprecated', 'system', ?, ?)
1801
- `);
1802
- for (const link of autoLinks) {
1803
- await auditStmt.run(link.from_id, link.to_id, link.relationship, 'v0 auto-generated link removed during governance migration', timestamp);
1804
- }
1805
- }
1806
- return {
1807
- dryRun,
1808
- deprecated: autoLinks.length,
1809
- protected: protectedCount,
1810
- total: totalLinks,
1811
- autoLinkRatio: autoLinkRatio.toFixed(2) + '%',
1812
- links: autoLinks.map((l) => ({
1813
- from_id: l.from_id,
1814
- to_id: l.to_id,
1815
- relationship: l.relationship,
1816
- reason: l.reason,
1817
- created_at: l.created_at,
1818
- })),
1819
- };
1820
- }
1821
- catch (error) {
1822
- throw new Error(`deprecateAutoLinks() failed: ${error instanceof Error ? error.message : String(error)}`);
1823
- }
1824
- }
1825
- /**
1826
- * Scan and identify auto-generated links for cleanup (Epic 5 - Story 5.1)
1827
- *
1828
- * Identifies auto-generated links lacking proper approval metadata.
1829
- * Separates deletion targets from protected links.
1830
- *
1831
- * Identification criteria:
1832
- * - approved_by_user = 0 OR (created_by IS NULL AND decision_id IS NULL)
1833
- *
1834
- * Protected (excluded from deletion):
1835
- * - approved_by_user = 1 AND (decision_id IS NOT NULL OR evidence IS NOT NULL)
1836
- *
1837
- * @returns {Object} Scan results with counts and link details
1838
- */
1839
- function scanAutoLinks() {
1840
- const adapter = (0, memory_store_js_1.getAdapter)();
1841
- // Total links
1842
- const totalStmt = adapter.prepare(`SELECT COUNT(*) as count FROM decision_edges`);
1843
- const totalResult = totalStmt.get();
1844
- const totalLinks = totalResult?.count ?? 0;
1845
- // Auto-generated links (lacking proper metadata)
1846
- const autoStmt = adapter.prepare(`
1847
- SELECT * FROM decision_edges
1848
- WHERE approved_by_user = 0
1849
- OR (created_by IS NULL AND decision_id IS NULL)
1850
- `);
1851
- const autoLinks = autoStmt.all();
1852
- // Protected links (approved or has complete metadata)
1853
- const protectedStmt = adapter.prepare(`
1854
- SELECT COUNT(*) as count FROM decision_edges
1855
- WHERE approved_by_user = 1
1856
- OR (decision_id IS NOT NULL AND evidence IS NOT NULL)
1857
- `);
1858
- const protectedResult = protectedStmt.get();
1859
- const protectedLinks = protectedResult?.count ?? 0;
1860
- // Filter deletion targets (exclude protected links)
1861
- const deletionTargets = autoLinks.filter((link) => {
1862
- // Exclude protected links
1863
- return !(link.approved_by_user === 1 || (link.decision_id && link.evidence));
1864
- });
1865
- return {
1866
- total_links: totalLinks,
1867
- auto_links: autoLinks.length,
1868
- protected_links: protectedLinks,
1869
- deletion_targets: deletionTargets.length,
1870
- deletion_target_list: deletionTargets,
1871
- };
1872
- }
1873
- /**
1874
- * Create backup of links before cleanup (Epic 5 - Story 5.1)
1875
- *
1876
- * Backs up deletion target links with full metadata to JSON file.
1877
- * Generates SHA-256 checksum for data integrity verification.
1878
- * Creates backup manifest with timestamp and metadata.
1879
- *
1880
- * @param {Array} targetLinks - Links to back up
1881
- * @returns {Object} Backup result with file paths and checksum
1882
- */
1883
- function createLinkBackup(targetLinks) {
1884
- const backupDir = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-backups');
1885
- // Create backup directory if not exists
1886
- if (!fs_1.default.existsSync(backupDir)) {
1887
- fs_1.default.mkdirSync(backupDir, { recursive: true });
1888
- }
1889
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
1890
- const backupFile = path_1.default.join(backupDir, `links-backup-${timestamp}.json`);
1891
- // Serialize target links with full metadata
1892
- const backupData = {
1893
- timestamp: new Date().toISOString(),
1894
- link_count: targetLinks.length,
1895
- links: targetLinks,
1896
- };
1897
- const backupJson = JSON.stringify(backupData, null, 2);
1898
- // Calculate SHA-256 checksum
1899
- const checksum = crypto_1.default.createHash('sha256').update(backupJson).digest('hex');
1900
- // Save backup file
1901
- fs_1.default.writeFileSync(backupFile, backupJson, 'utf8');
1902
- // Save manifest
1903
- const manifest = {
1904
- timestamp: backupData.timestamp,
1905
- backup_file: backupFile,
1906
- checksum,
1907
- link_count: targetLinks.length,
1908
- };
1909
- const manifestFile = path_1.default.join(backupDir, `backup-manifest-${timestamp}.json`);
1910
- fs_1.default.writeFileSync(manifestFile, JSON.stringify(manifest, null, 2), 'utf8');
1911
- return {
1912
- backup_file: backupFile,
1913
- manifest_file: manifestFile,
1914
- checksum,
1915
- link_count: targetLinks.length,
1916
- };
1917
- }
1918
- /**
1919
- * Generate pre-cleanup report with risk assessment (Epic 5 - Story 5.1)
1920
- *
1921
- * Creates comprehensive report with statistics, risk level, and samples.
1922
- * Risk assessment based on deletion ratio:
1923
- * - HIGH: > 50% deletion
1924
- * - MEDIUM: 30-50% deletion
1925
- * - LOW: < 30% deletion
1926
- *
1927
- * @returns {Object} Report data with markdown output and file path
1928
- */
1929
- function generatePreCleanupReport() {
1930
- const scanResult = scanAutoLinks();
1931
- // Calculate risk level (guard against division by zero)
1932
- const deletionRatio = scanResult.total_links > 0 ? scanResult.deletion_targets / scanResult.total_links : 0;
1933
- let riskLevel;
1934
- if (deletionRatio > 0.5) {
1935
- riskLevel = 'HIGH';
1936
- }
1937
- else if (deletionRatio > 0.3) {
1938
- riskLevel = 'MEDIUM';
1939
- }
1940
- else {
1941
- riskLevel = 'LOW';
1942
- }
1943
- // Sample deletion targets (max 10)
1944
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1945
- const samples = scanResult.deletion_target_list.slice(0, 10).map((link) => ({
1946
- from_id: link.from_id,
1947
- to_id: link.to_id,
1948
- relationship: link.relationship,
1949
- reason: link.reason,
1950
- created_by: link.created_by,
1951
- approved_by_user: link.approved_by_user,
1952
- }));
1953
- const report = {
1954
- generated_at: new Date().toISOString(),
1955
- statistics: {
1956
- total_links: scanResult.total_links,
1957
- auto_links: scanResult.auto_links,
1958
- protected_links: scanResult.protected_links,
1959
- deletion_targets: scanResult.deletion_targets,
1960
- deletion_ratio: `${(deletionRatio * 100).toFixed(1)}%`,
1961
- },
1962
- risk_assessment: {
1963
- level: riskLevel,
1964
- message: riskLevel === 'HIGH'
1965
- ? '⚠️ HIGH RISK: Deletion targets exceed 50%. Create backup before proceeding.'
1966
- : riskLevel === 'MEDIUM'
1967
- ? '⚡ MEDIUM RISK: Deletion targets 30-50%. Verify backup recommended.'
1968
- : '✅ LOW RISK: Deletion targets under 30%. Safe to proceed.',
1969
- },
1970
- deletion_target_samples: samples,
1971
- };
1972
- // Generate markdown report
1973
- const markdown = `# Pre-Cleanup Report
1974
-
1975
- **Generated:** ${report.generated_at}
1976
-
1977
- ## Statistics
1978
-
1979
- - **Total Links:** ${report.statistics.total_links}
1980
- - **Auto Links:** ${report.statistics.auto_links}
1981
- - **Protected Links:** ${report.statistics.protected_links}
1982
- - **Deletion Targets:** ${report.statistics.deletion_targets} (${report.statistics.deletion_ratio})
1983
-
1984
- ## Risk Assessment
1985
-
1986
- **Level:** ${report.risk_assessment.level}
1987
-
1988
- ${report.risk_assessment.message}
1989
-
1990
- ## Sample Deletion Targets (First 10)
1991
-
1992
- ${samples
1993
- .map(
1994
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
1995
- (link, idx) => `
1996
- ### ${idx + 1}. ${link.from_id} → ${link.to_id}
1997
-
1998
- - **Relationship:** ${link.relationship}
1999
- - **Reason:** ${link.reason || 'N/A'}
2000
- - **Created By:** ${link.created_by || 'N/A'}
2001
- - **Approved:** ${link.approved_by_user ? 'Yes' : 'No'}
2002
- `)
2003
- .join('\n')}
2004
-
2005
- ---
2006
-
2007
- **Next Steps:**
2008
-
2009
- 1. Review the deletion targets above
2010
- 2. Run \`create_link_backup\` to create a backup
2011
- 3. Proceed with cleanup using Story 5.2 tools
2012
- 4. If needed, restore from backup using \`restore_link_backup\`
2013
- `;
2014
- const backupDir = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-backups');
2015
- // Ensure backup directory exists
2016
- if (!fs_1.default.existsSync(backupDir)) {
2017
- fs_1.default.mkdirSync(backupDir, { recursive: true });
2018
- }
2019
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2020
- const reportFile = path_1.default.join(backupDir, `pre-cleanup-report-${timestamp}.md`);
2021
- fs_1.default.writeFileSync(reportFile, markdown, 'utf8');
2022
- return {
2023
- report: report,
2024
- report_file: reportFile,
2025
- markdown,
2026
- };
2027
- }
2028
- /**
2029
- * Restore links from backup file (Epic 5 - Story 5.1)
2030
- *
2031
- * Restores previously backed-up links to the database.
2032
- * Verifies checksum before restoration to ensure data integrity.
2033
- * Reports number of restored and failed links.
2034
- *
2035
- * @param {string} backupFile - Path to backup file
2036
- * @returns {Object} Restoration result with counts
2037
- */
2038
- function restoreLinkBackup(backupFile) {
2039
- // Read backup file
2040
- const backupJson = fs_1.default.readFileSync(backupFile, 'utf8');
2041
- const backupData = JSON.parse(backupJson);
2042
- // Read manifest for checksum verification
2043
- const manifestFile = backupFile.replace('links-backup', 'backup-manifest');
2044
- const manifest = JSON.parse(fs_1.default.readFileSync(manifestFile, 'utf8'));
2045
- // Verify checksum
2046
- const calculatedChecksum = crypto_1.default.createHash('sha256').update(backupJson).digest('hex');
2047
- if (calculatedChecksum !== manifest.checksum) {
2048
- throw new Error('Backup file checksum mismatch. File may be corrupted.');
2049
- }
2050
- // Restore links to database
2051
- const adapter = (0, memory_store_js_1.getAdapter)();
2052
- let restored = 0;
2053
- let failed = 0;
2054
- const insertStmt = adapter.prepare(`
2055
- INSERT OR REPLACE INTO decision_edges
2056
- (from_id, to_id, relationship, reason, created_by, approved_by_user, decision_id, evidence, created_at)
2057
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2058
- `);
2059
- for (const link of backupData.links) {
2060
- try {
2061
- insertStmt.run(link.from_id, link.to_id, link.relationship, link.reason, link.created_by, link.approved_by_user, link.decision_id, link.evidence, link.created_at);
2062
- restored++;
2063
- }
2064
- catch (error) {
2065
- const msg = error instanceof Error ? error.message : String(error);
2066
- (0, debug_logger_js_1.error)(`Failed to restore link ${link.from_id} -> ${link.to_id}:`, msg);
2067
- failed++;
2068
- }
2069
- }
2070
- return {
2071
- total_links: backupData.link_count,
2072
- restored,
2073
- failed,
2074
- backup_file: backupFile,
2075
- };
2076
- }
2077
- /**
2078
- * Verify backup file exists and is recent (Epic 5 - Story 5.2)
2079
- *
2080
- * Checks for backup files in backup directory and verifies they are recent enough.
2081
- * Required as safety check before executing link deletion.
2082
- *
2083
- * @param {number} maxAgeHours - Maximum age of backup in hours (default: 24)
2084
- * @returns {Object} Backup verification result with latest backup info
2085
- */
2086
- function verifyBackupExists(maxAgeHours = 24) {
2087
- const backupDir = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-backups');
2088
- if (!fs_1.default.existsSync(backupDir)) {
2089
- throw new Error('Backup directory not found. Please create a backup first using create_link_backup.');
2090
- }
2091
- const backupFiles = fs_1.default
2092
- .readdirSync(backupDir)
2093
- .filter((f) => f.startsWith('links-backup-'))
2094
- .map((f) => ({
2095
- name: f,
2096
- path: path_1.default.join(backupDir, f),
2097
- mtime: fs_1.default.statSync(path_1.default.join(backupDir, f)).mtime,
2098
- }))
2099
- .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
2100
- if (backupFiles.length === 0) {
2101
- throw new Error('No recent backup found. Please run create_link_backup first.');
2102
- }
2103
- const latestBackup = backupFiles[0];
2104
- const backupAge = Date.now() - latestBackup.mtime.getTime();
2105
- const maxAgeMs = maxAgeHours * 60 * 60 * 1000;
2106
- if (backupAge > maxAgeMs) {
2107
- throw new Error(`Most recent backup is too old (${(backupAge / (60 * 60 * 1000)).toFixed(1)} hours). Max age: ${maxAgeHours} hours.`);
2108
- }
2109
- // Read backup metadata
2110
- const backupJson = fs_1.default.readFileSync(latestBackup.path, 'utf8');
2111
- const backupData = JSON.parse(backupJson);
2112
- return {
2113
- backup_file: latestBackup.path,
2114
- age_hours: backupAge / (60 * 60 * 1000),
2115
- link_count: backupData.link_count || 0,
2116
- };
2117
- }
2118
- /**
2119
- * Delete auto-generated links with batch processing (Epic 5 - Story 5.2)
2120
- *
2121
- * Executes batch deletion of auto-generated links with transaction support.
2122
- * Requires recent backup (within 24 hours) before execution.
2123
- * Logs all deletions to audit trail.
2124
- *
2125
- * Safety features:
2126
- * - Backup verification before deletion
2127
- * - Batch processing with transaction support
2128
- * - Dry-run mode for simulation
2129
- * - Large deletion warning (> 1000 links)
2130
- *
2131
- * @param {number} batchSize - Number of links to delete per batch (default: 100)
2132
- * @param {boolean} dryRun - If true, simulate deletion without actual changes (default: true)
2133
- * @returns {Object} Deletion result with counts and backup info
2134
- */
2135
- function deleteAutoLinks(batchSize = 100, dryRun = true) {
2136
- const adapter = (0, memory_store_js_1.getAdapter)();
2137
- // Safety check: Verify recent backup exists
2138
- const backupInfo = verifyBackupExists(24);
2139
- // Scan for deletion targets
2140
- const scanResult = scanAutoLinks();
2141
- const deletionTargets = scanResult.deletion_target_list;
2142
- if (deletionTargets.length === 0) {
2143
- return {
2144
- dry_run: dryRun,
2145
- deleted: 0,
2146
- failed: 0,
2147
- total_targets: 0,
2148
- backup_file: backupInfo.backup_file,
2149
- message: 'No auto-generated links found. Nothing to delete.',
2150
- };
2151
- }
2152
- // Large deletion warning
2153
- const largeDelection = deletionTargets.length > 1000;
2154
- if (largeDelection) {
2155
- (0, debug_logger_js_1.warn)(`⚠️ LARGE DELETION: ${deletionTargets.length} links will be deleted. Consider running in dry-run mode first.`);
2156
- }
2157
- if (dryRun) {
2158
- return {
2159
- dry_run: true,
2160
- would_delete: deletionTargets.length,
2161
- deleted: 0,
2162
- backup_file: backupInfo.backup_file,
2163
- large_deletion_warning: largeDelection,
2164
- warning_message: largeDelection
2165
- ? `Warning: More than 1000 links (${deletionTargets.length}) will be deleted.`
2166
- : null,
2167
- sample_links: deletionTargets.slice(0, 5).map((l) => ({
2168
- from_id: l.from_id,
2169
- to_id: l.to_id,
2170
- relationship: l.relationship,
2171
- })),
2172
- message: 'Dry-run mode: No links were actually deleted.',
2173
- };
2174
- }
2175
- // Execute batch deletion with transaction support
2176
- let deleted = 0;
2177
- let failed = 0;
2178
- let batchesProcessed = 0;
2179
- const errors = [];
2180
- const deleteStmt = adapter.prepare(`
2181
- DELETE FROM decision_edges
2182
- WHERE from_id = ? AND to_id = ? AND relationship = ?
2183
- `);
2184
- const auditStmt = adapter.prepare(`
2185
- INSERT INTO link_audit_log (from_id, to_id, relationship, action, actor, reason, created_at)
2186
- VALUES (?, ?, ?, 'deprecated', 'system', ?, ?)
2187
- `);
2188
- // Process in batches
2189
- for (let i = 0; i < deletionTargets.length; i += batchSize) {
2190
- const batch = deletionTargets.slice(i, i + batchSize);
2191
- try {
2192
- const processBatch = () => {
2193
- for (const link of batch) {
2194
- try {
2195
- deleteStmt.run(link.from_id, link.to_id, link.relationship);
2196
- auditStmt.run(link.from_id, link.to_id, link.relationship, 'Auto-link cleanup - v1.1 migration', Date.now());
2197
- deleted++;
2198
- }
2199
- catch (error) {
2200
- failed++;
2201
- errors.push({
2202
- link: `${link.from_id}->${link.to_id}`,
2203
- error: error instanceof Error ? error.message : String(error),
2204
- });
2205
- }
2206
- }
2207
- };
2208
- // Use transaction if available, otherwise run directly
2209
- if (adapter.transaction) {
2210
- adapter.transaction(processBatch);
2211
- }
2212
- else {
2213
- processBatch();
2214
- }
2215
- batchesProcessed++;
2216
- }
2217
- catch (error) {
2218
- (0, debug_logger_js_1.error)(`Batch deletion failed at index ${i}:`, error);
2219
- failed += batch.length;
2220
- errors.push({
2221
- batch_index: i,
2222
- batch_size: batch.length,
2223
- error: error instanceof Error ? error.message : String(error),
2224
- });
2225
- break; // Stop on batch failure
2226
- }
2227
- }
2228
- const successRate = deletionTargets.length > 0 ? (deleted / deletionTargets.length) * 100 : 0;
2229
- return {
2230
- dry_run: false,
2231
- deleted,
2232
- failed,
2233
- total_targets: deletionTargets.length,
2234
- backup_file: backupInfo.backup_file,
2235
- batches_processed: batchesProcessed,
2236
- errors: errors.slice(0, 10), // Return first 10 errors
2237
- success_rate: successRate,
2238
- };
2239
- }
2240
- /**
2241
- * Validate cleanup result and generate post-cleanup report (Epic 5 - Story 5.2)
2242
- *
2243
- * Re-scans for remaining auto-generated links and evaluates cleanup success.
2244
- * Generates comprehensive report with statistics and recommendations.
2245
- *
2246
- * Success criteria:
2247
- * - SUCCESS: Remaining auto links < 5%
2248
- * - PARTIAL: Remaining auto links 5-10%
2249
- * - FAILED: Remaining auto links > 10%
2250
- *
2251
- * @returns {Object} Validation result with report and file path
2252
- */
2253
- function validateCleanupResult() {
2254
- // Re-scan for remaining auto links
2255
- const scanResult = scanAutoLinks();
2256
- // Calculate remaining ratio
2257
- const totalLinks = scanResult.total_links;
2258
- const remainingAutoLinks = scanResult.auto_links;
2259
- const remainingRatio = totalLinks > 0 ? remainingAutoLinks / totalLinks : 0;
2260
- // Evaluate cleanup success
2261
- let status;
2262
- let message;
2263
- let recommendation;
2264
- if (remainingRatio < 0.05) {
2265
- status = 'SUCCESS';
2266
- message = '✅ SUCCESS: Remaining auto-links under 5%. Target achieved!';
2267
- recommendation = 'Cleanup completed successfully. You can proceed with migration.';
2268
- }
2269
- else if (remainingRatio < 0.1) {
2270
- status = 'PARTIAL';
2271
- message = '⚡ PARTIAL: Remaining auto-links 5-10%. Additional cleanup recommended.';
2272
- recommendation = 'Run execute_link_cleanup again to clean up more auto-links.';
2273
- }
2274
- else {
2275
- status = 'FAILED';
2276
- message = '⚠️ FAILED: Remaining auto-links exceed 10%. Rollback or re-run needed.';
2277
- recommendation = 'Significantly missed target. Consider restoring from backup and retry.';
2278
- }
2279
- // Generate post-cleanup report
2280
- const report = {
2281
- validated_at: new Date().toISOString(),
2282
- status,
2283
- message,
2284
- statistics: {
2285
- total_links: totalLinks,
2286
- remaining_auto_links: remainingAutoLinks,
2287
- remaining_ratio: `${(remainingRatio * 100).toFixed(1)}%`,
2288
- protected_links: scanResult.protected_links,
2289
- deletion_targets: scanResult.deletion_targets,
2290
- },
2291
- recommendation,
2292
- };
2293
- // Generate markdown report
2294
- const markdown = generatePostCleanupReportMarkdown(report);
2295
- // Save report to file
2296
- const backupDir = path_1.default.join(os_1.default.homedir(), '.claude', 'mama-backups');
2297
- if (!fs_1.default.existsSync(backupDir)) {
2298
- fs_1.default.mkdirSync(backupDir, { recursive: true });
2299
- }
2300
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
2301
- const reportFile = path_1.default.join(backupDir, `post-cleanup-report-${timestamp}.md`);
2302
- fs_1.default.writeFileSync(reportFile, markdown, 'utf8');
2303
- return {
2304
- status,
2305
- total_links_before: totalLinks,
2306
- auto_links_remaining: remainingAutoLinks,
2307
- remaining_ratio: remainingRatio * 100,
2308
- protected_links: scanResult.protected_links,
2309
- report,
2310
- report_file: reportFile,
2311
- markdown,
2312
- };
474
+ await (0, db_manager_js_1.initDB)();
475
+ return (0, api_js_1.listCheckpointsInAdapter)((0, db_manager_js_1.getAdapter)(), limit);
2313
476
  }
2314
477
  /**
2315
- * Generate post-cleanup report in Markdown format (Epic 5 - Story 5.2)
2316
- *
2317
- * @param {Object} report - Validation report data
2318
- * @returns {string} Markdown formatted report
478
+ * Public write boundary for audit findings. The store writer takes the adapter
479
+ * from its caller; this edge resolves the ambient adapter so the
480
+ * MAMAApiInterface signature stays (input) => Promise<string>.
2319
481
  */
2320
- function generatePostCleanupReportMarkdown(report) {
2321
- let markdown = `# Post-Cleanup Validation Report
2322
-
2323
- **Generated:** ${report.validated_at}
2324
- **Status:** ${report.status}
2325
-
2326
- ${report.message}
2327
-
2328
- ## Statistics
2329
-
2330
- - **Total Links:** ${report.statistics.total_links}
2331
- - **Remaining Auto Links:** ${report.statistics.remaining_auto_links}
2332
- - **Remaining Ratio:** ${report.statistics.remaining_ratio}
2333
- - **Protected Links:** ${report.statistics.protected_links}
2334
- - **Deletion Targets (if any):** ${report.statistics.deletion_targets}
2335
-
2336
- ## Recommendation
2337
-
2338
- ${report.recommendation}
2339
- `;
2340
- // Add rollback instructions for non-SUCCESS statuses
2341
- if (report.status !== 'SUCCESS') {
2342
- markdown += `
2343
-
2344
- ---
2345
-
2346
- ## Rollback Instructions
2347
-
2348
- If you need to restore the deleted links:
2349
-
2350
- 1. Find the latest backup file in \`~/.claude/mama-backups/\`
2351
- 2. Run: \`restore_link_backup <backup_file_path>\`
2352
- 3. Re-run validation: \`validate_cleanup_result\`
2353
-
2354
- **Next Steps:**
2355
-
2356
- - For PARTIAL status: Review remaining auto links and run cleanup again if needed
2357
- - For FAILED status: Consider rollback and investigate why many links remain
2358
- `;
2359
- }
2360
- return markdown;
2361
- }
2362
- /**
2363
- * Calculate coverage metrics (Epic 4 - Story 4.1)
2364
- *
2365
- * Measures narrative coverage (% of decisions with narrative fields)
2366
- * and link coverage (% of decisions with at least one link).
2367
- *
2368
- * @returns {Object} Coverage metrics
2369
- */
2370
- function calculateCoverage() {
2371
- const adapter = (0, memory_store_js_1.getAdapter)();
2372
- // Total decisions
2373
- const totalDecisions = adapter.prepare(`SELECT COUNT(*) as count FROM decisions`).get().count;
2374
- if (totalDecisions === 0) {
2375
- return {
2376
- narrativeCoverage: '0.0%',
2377
- linkCoverage: '0.0%',
2378
- totalDecisions: 0,
2379
- completeNarratives: 0,
2380
- decisionsWithLinks: 0,
2381
- };
2382
- }
2383
- // Narrative coverage: Decisions with evidence, alternatives, and risks filled
2384
- // Note: Using existing schema fields (evidence, alternatives, risks) instead of
2385
- // 5-layer fields (specificity, evidence, reasoning, tension, continuity) mentioned in story
2386
- const completeNarratives = adapter
2387
- .prepare(`
2388
- SELECT COUNT(*) as count FROM decisions
2389
- WHERE evidence IS NOT NULL AND evidence != ''
2390
- AND alternatives IS NOT NULL AND alternatives != ''
2391
- AND risks IS NOT NULL AND risks != ''
2392
- `)
2393
- .get().count;
2394
- const narrativeCoverage = (completeNarratives / totalDecisions) * 100;
2395
- // Link coverage: Decisions with at least one link
2396
- const decisionsWithLinks = adapter
2397
- .prepare(`
2398
- SELECT COUNT(DISTINCT d.id) as count FROM decisions d
2399
- WHERE EXISTS (
2400
- SELECT 1 FROM decision_edges e
2401
- WHERE e.from_id = d.id OR e.to_id = d.id
2402
- )
2403
- `)
2404
- .get().count;
2405
- const linkCoverage = (decisionsWithLinks / totalDecisions) * 100;
2406
- return {
2407
- narrativeCoverage: `${narrativeCoverage.toFixed(1)}%`,
2408
- linkCoverage: `${linkCoverage.toFixed(1)}%`,
2409
- totalDecisions,
2410
- completeNarratives,
2411
- decisionsWithLinks,
2412
- };
2413
- }
2414
- /**
2415
- * Log restart attempt (Epic 4 - Story 4.2)
2416
- *
2417
- * Records restart attempt with success/failure status, latency, and mode.
2418
- * Replaces in-memory restart-metrics.js with SQLite-backed storage.
2419
- *
2420
- * @param {string} sessionId - Session identifier
2421
- * @param {string} status - 'success' or 'failure'
2422
- * @param {string|null} failureReason - 'NO_CHECKPOINT', 'LOAD_ERROR', 'CONTEXT_INCOMPLETE', or null
2423
- * @param {number} latencyMs - Latency in milliseconds
2424
- * @param {string} mode - 'full' (narrative+links) or 'summary' (summary only)
2425
- * @returns {void}
2426
- */
2427
- function logRestartAttempt(sessionId, status, failureReason, latencyMs, mode = 'full') {
2428
- const adapter = (0, memory_store_js_1.getAdapter)();
2429
- const timestamp = new Date().toISOString();
2430
- adapter
2431
- .prepare(`
2432
- INSERT INTO restart_metrics (timestamp, session_id, status, failure_reason, latency_ms, mode)
2433
- VALUES (?, ?, ?, ?, ?, ?)
2434
- `)
2435
- .run(timestamp, sessionId, status, failureReason, latencyMs, mode);
2436
- // Performance warning if exceeds threshold
2437
- // Note: Using console.warn directly (not logWarn) because performance warnings
2438
- // should always be visible regardless of MAMA_LOG_LEVEL setting
2439
- const threshold = mode === 'summary' ? 1000 : 2500;
2440
- if (latencyMs > threshold) {
2441
- console.warn(JSON.stringify({
2442
- performance_warning: true,
2443
- message: `Restart latency exceeded threshold: ${latencyMs}ms > ${threshold}ms`,
2444
- session_id: sessionId,
2445
- mode,
2446
- latency_ms: latencyMs,
2447
- threshold_ms: threshold,
2448
- }));
2449
- }
2450
- }
2451
- /**
2452
- * Calculate restart success rate (Epic 4 - Story 4.2)
2453
- *
2454
- * Calculates success rate over a given period (24h, 7d, 30d).
2455
- *
2456
- * @param {string} period - '24h', '7d', or '30d'
2457
- * @returns {Object} Success rate metrics
2458
- */
2459
- function calculateRestartSuccessRate(period = '7d') {
2460
- const adapter = (0, memory_store_js_1.getAdapter)();
2461
- const periodMap = {
2462
- '24h': 1,
2463
- '7d': 7,
2464
- '30d': 30,
2465
- };
2466
- const days = periodMap[period] || 7;
2467
- const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
2468
- const stats = adapter
2469
- .prepare(`
2470
- SELECT
2471
- COUNT(*) as total,
2472
- COUNT(CASE WHEN status = 'success' THEN 1 END) as success,
2473
- COUNT(CASE WHEN status = 'failure' THEN 1 END) as failure
2474
- FROM restart_metrics
2475
- WHERE timestamp >= ?
2476
- `)
2477
- .get(since);
2478
- const successRate = stats.total > 0 ? stats.success / stats.total : 0;
2479
- return {
2480
- period,
2481
- total: stats.total,
2482
- success: stats.success,
2483
- failure: stats.failure,
2484
- successRate: `${(successRate * 100).toFixed(1)}%`,
2485
- meetsTarget: successRate >= 0.95,
2486
- };
2487
- }
2488
- /**
2489
- * Calculate restart latency percentiles (Epic 4 - Story 4.2)
2490
- *
2491
- * Calculates p50, p95, p99 latencies for successful restarts.
2492
- * Optionally filters by mode (full/summary).
2493
- *
2494
- * @param {string} period - '24h', '7d', or '30d'
2495
- * @param {string|null} mode - 'full', 'summary', or null (all modes)
2496
- * @returns {Object} Latency percentile metrics
2497
- */
2498
- function calculateRestartLatency(period = '7d', mode = null) {
2499
- const adapter = (0, memory_store_js_1.getAdapter)();
2500
- const periodMap = { '24h': 1, '7d': 7, '30d': 30 };
2501
- const days = periodMap[period] || 7;
2502
- const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
2503
- let query = `
2504
- SELECT latency_ms
2505
- FROM restart_metrics
2506
- WHERE timestamp >= ? AND status = 'success'
2507
- `;
2508
- const params = [since];
2509
- if (mode) {
2510
- query += ` AND mode = ?`;
2511
- params.push(mode);
2512
- }
2513
- query += ` ORDER BY latency_ms ASC`;
2514
- const rows = adapter.prepare(query).all(...params);
2515
- const latencies = rows.map((r) => r.latency_ms);
2516
- if (latencies.length === 0) {
2517
- return { p50: 0, p95: 0, p99: 0, count: 0, mode: mode || 'all' };
2518
- }
2519
- const percentile = (arr, p) => {
2520
- const index = Math.ceil((p / 100) * arr.length) - 1;
2521
- return arr[Math.max(0, index)];
2522
- };
2523
- return {
2524
- p50: percentile(latencies, 50),
2525
- p95: percentile(latencies, 95),
2526
- p99: percentile(latencies, 99),
2527
- count: latencies.length,
2528
- mode: mode || 'all',
2529
- };
2530
- }
2531
- /**
2532
- * Get restart metrics (Epic 4 - Story 4.2)
2533
- *
2534
- * Combines success rate and latency metrics for a given period.
2535
- *
2536
- * @param {string} period - '24h', '7d', or '30d'
2537
- * @param {boolean} includeLatency - Whether to include latency percentiles
2538
- * @returns {Object} Combined restart metrics
2539
- */
2540
- function getRestartMetrics(period = '7d', includeLatency = true) {
2541
- const successRate = calculateRestartSuccessRate(period);
2542
- const result = { successRate };
2543
- if (includeLatency) {
2544
- result.latency = {
2545
- full: calculateRestartLatency(period, 'full'),
2546
- summary: calculateRestartLatency(period, 'summary'),
2547
- };
2548
- }
2549
- return result;
2550
- }
2551
- /**
2552
- * Calculate quality metrics (Epic 4 - Story 4.1)
2553
- *
2554
- * Measures narrative quality (field completeness per layer)
2555
- * and link quality (rich reason ratio, approved link ratio).
2556
- *
2557
- * @returns {Object} Quality metrics
2558
- */
2559
- function calculateQuality() {
2560
- const adapter = (0, memory_store_js_1.getAdapter)();
2561
- // Narrative quality: Average completeness for each narrative field
2562
- const narrativeQuality = adapter
2563
- .prepare(`
2564
- SELECT
2565
- AVG(CASE WHEN evidence IS NOT NULL AND evidence != '' THEN 1 ELSE 0 END) as evidence,
2566
- AVG(CASE WHEN alternatives IS NOT NULL AND alternatives != '' THEN 1 ELSE 0 END) as alternatives,
2567
- AVG(CASE WHEN risks IS NOT NULL AND risks != '' THEN 1 ELSE 0 END) as risks
2568
- FROM decisions
2569
- `)
2570
- .get();
2571
- // Link quality: "Rich" reason ratio (reason > 50 chars) and approved link ratio
2572
- const linkStats = adapter
2573
- .prepare(`
2574
- SELECT
2575
- COUNT(*) as total,
2576
- COUNT(CASE WHEN reason IS NOT NULL AND LENGTH(reason) > 50 THEN 1 END) as rich,
2577
- COUNT(CASE WHEN approved_by_user = 1 THEN 1 END) as approved
2578
- FROM decision_edges
2579
- `)
2580
- .get();
2581
- const linkQuality = linkStats.total > 0 ? ((linkStats.rich / linkStats.total) * 100).toFixed(1) : '0.0';
2582
- const approvedRatio = linkStats.total > 0 ? ((linkStats.approved / linkStats.total) * 100).toFixed(1) : '0.0';
2583
- return {
2584
- narrativeQuality: {
2585
- evidence: `${(narrativeQuality.evidence * 100).toFixed(1)}%`,
2586
- alternatives: `${(narrativeQuality.alternatives * 100).toFixed(1)}%`,
2587
- risks: `${(narrativeQuality.risks * 100).toFixed(1)}%`,
2588
- },
2589
- linkQuality: {
2590
- richReasonRatio: `${linkQuality}%`,
2591
- approvedRatio: `${approvedRatio}%`,
2592
- totalLinks: linkStats.total,
2593
- richLinks: linkStats.rich,
2594
- approvedLinks: linkStats.approved,
2595
- },
2596
- };
2597
- }
2598
- /**
2599
- * Generate quality report with recommendations (Epic 4 - Story 4.1 + 4.2)
2600
- *
2601
- * Generates a comprehensive quality report with coverage, quality metrics,
2602
- * restart metrics, and recommendations for improvement.
2603
- *
2604
- * @param {Object} options - Report options
2605
- * @param {string} [options.format='json'] - Output format: 'json' or 'markdown'
2606
- * @param {string} [options.period='7d'] - Period for restart metrics: '24h', '7d', or '30d'
2607
- * @param {Object} [options.thresholds] - Custom thresholds
2608
- * @param {number} [options.thresholds.narrativeCoverage=0.8] - Narrative coverage threshold (0-1)
2609
- * @param {number} [options.thresholds.linkCoverage=0.7] - Link coverage threshold (0-1)
2610
- * @param {number} [options.thresholds.richReasonRatio=0.7] - Rich reason ratio threshold (0-1)
2611
- * @param {number} [options.thresholds.restartSuccessRate=0.95] - Restart success rate threshold (0-1)
2612
- * @param {number} [options.thresholds.restartLatencyP95Full=2500] - Full mode p95 latency threshold (ms)
2613
- * @param {number} [options.thresholds.restartLatencyP95Summary=1000] - Summary mode p95 latency threshold (ms)
2614
- * @returns {Object|string} Quality report as JSON or Markdown
2615
- */
2616
- function generateQualityReport(options = {}) {
2617
- const { format = 'json', period = '7d', thresholds = {} } = options;
2618
- const defaultThresholds = {
2619
- narrativeCoverage: 0.8,
2620
- linkCoverage: 0.7,
2621
- richReasonRatio: 0.7,
2622
- restartSuccessRate: 0.95,
2623
- restartLatencyP95Full: 2500,
2624
- restartLatencyP95Summary: 1000,
2625
- ...thresholds,
2626
- };
2627
- const coverage = calculateCoverage();
2628
- const quality = calculateQuality();
2629
- // Story 4.2: Add restart metrics
2630
- const validPeriod = (['24h', '7d', '30d'].includes(period || '7d') ? period : '7d');
2631
- const restartMetrics = getRestartMetrics(validPeriod, true);
2632
- const recommendations = [];
2633
- // Check narrative coverage threshold
2634
- const narrativeCoveragePct = parseFloat(coverage.narrativeCoverage);
2635
- if (narrativeCoveragePct < defaultThresholds.narrativeCoverage * 100) {
2636
- recommendations.push({
2637
- type: 'narrative_coverage',
2638
- message: `Narrative coverage below target (${defaultThresholds.narrativeCoverage * 100}%). Add narrative to decisions missing evidence, alternatives, or risks fields.`,
2639
- target: `${defaultThresholds.narrativeCoverage * 100}%`,
2640
- current: coverage.narrativeCoverage,
2641
- });
2642
- }
2643
- // Check link coverage threshold
2644
- const linkCoveragePct = parseFloat(coverage.linkCoverage);
2645
- if (linkCoveragePct < defaultThresholds.linkCoverage * 100) {
2646
- recommendations.push({
2647
- type: 'link_coverage',
2648
- message: `Link coverage below target (${defaultThresholds.linkCoverage * 100}%). Add links between related decisions.`,
2649
- target: `${defaultThresholds.linkCoverage * 100}%`,
2650
- current: coverage.linkCoverage,
2651
- });
2652
- }
2653
- // Check link quality threshold
2654
- const richReasonRatioPct = parseFloat(quality.linkQuality.richReasonRatio);
2655
- if (richReasonRatioPct < defaultThresholds.richReasonRatio * 100) {
2656
- recommendations.push({
2657
- type: 'link_quality',
2658
- message: `Link quality below target (${defaultThresholds.richReasonRatio * 100}%). Add specific causality and evidence to link reason fields.`,
2659
- target: `${defaultThresholds.richReasonRatio * 100}%`,
2660
- current: quality.linkQuality.richReasonRatio,
2661
- });
2662
- }
2663
- // Story 4.2: Check restart success rate threshold (only if there's data)
2664
- if (restartMetrics.successRate.total > 0 && !restartMetrics.successRate.meetsTarget) {
2665
- const _successRatePct = parseFloat(restartMetrics.successRate.successRate);
2666
- recommendations.push({
2667
- type: 'restart_success_rate',
2668
- message: `Restart success rate below target (95%). Analyze failure reasons and improve checkpoint quality.`,
2669
- target: '95%',
2670
- current: restartMetrics.successRate.successRate,
2671
- });
2672
- }
2673
- // Story 4.2: Check restart latency thresholds (only if there's data)
2674
- if (restartMetrics.latency) {
2675
- const fullP95 = restartMetrics.latency.full.p95;
2676
- if (restartMetrics.latency.full.count > 0 &&
2677
- fullP95 > defaultThresholds.restartLatencyP95Full) {
2678
- recommendations.push({
2679
- type: 'restart_latency_full',
2680
- message: `Narrative+link expansion p95 latency exceeds target (2.5s). Consider limiting link expansion depth or adding caching.`,
2681
- target: `${defaultThresholds.restartLatencyP95Full}ms`,
2682
- current: `${fullP95}ms`,
2683
- });
2684
- }
2685
- const summaryP95 = restartMetrics.latency.summary.p95;
2686
- if (restartMetrics.latency.summary.count > 0 &&
2687
- summaryP95 > defaultThresholds.restartLatencyP95Summary) {
2688
- recommendations.push({
2689
- type: 'restart_latency_summary',
2690
- message: `Summary mode p95 latency exceeds target (1.0s). Review query optimization or add indexes.`,
2691
- target: `${defaultThresholds.restartLatencyP95Summary}ms`,
2692
- current: `${summaryP95}ms`,
2693
- });
2694
- }
2695
- }
2696
- const report = {
2697
- generated_at: new Date().toISOString(),
2698
- period,
2699
- coverage,
2700
- quality,
2701
- restart: restartMetrics,
2702
- thresholds: defaultThresholds,
2703
- recommendations,
2704
- format,
2705
- };
2706
- if (format === 'markdown') {
2707
- return formatQualityReportMarkdown(report);
2708
- }
2709
- return report;
2710
- }
2711
- /**
2712
- * Format quality report as Markdown
2713
- *
2714
- * @param {Object} report - Quality report data
2715
- * @returns {string} Markdown-formatted report
2716
- */
2717
- function formatQualityReportMarkdown(report) {
2718
- const { generated_at, period, coverage, quality, restart, thresholds, recommendations } = report;
2719
- let markdown = `# 📊 MAMA Quality Report\n\n`;
2720
- markdown += `Generated: ${generated_at}\n`;
2721
- markdown += `Period: ${period}\n\n`;
2722
- markdown += `## Coverage Metrics\n\n`;
2723
- markdown += `- **Narrative Coverage**: ${coverage.narrativeCoverage} (${coverage.completeNarratives}/${coverage.totalDecisions} decisions)\n`;
2724
- markdown += `- **Link Coverage**: ${coverage.linkCoverage} (${coverage.decisionsWithLinks}/${coverage.totalDecisions} decisions)\n\n`;
2725
- markdown += `## Quality Metrics\n\n`;
2726
- markdown += `### Narrative Quality\n`;
2727
- markdown += `- Evidence: ${quality.narrativeQuality.evidence}\n`;
2728
- markdown += `- Alternatives: ${quality.narrativeQuality.alternatives}\n`;
2729
- markdown += `- Risks: ${quality.narrativeQuality.risks}\n\n`;
2730
- markdown += `### Link Quality\n`;
2731
- markdown += `- Rich Reason Ratio: ${quality.linkQuality.richReasonRatio} (${quality.linkQuality.richLinks}/${quality.linkQuality.totalLinks} links)\n`;
2732
- markdown += `- Approved Link Ratio: ${quality.linkQuality.approvedRatio} (${quality.linkQuality.approvedLinks}/${quality.linkQuality.totalLinks} links)\n\n`;
2733
- // Story 4.2: Add restart metrics section
2734
- if (restart) {
2735
- markdown += `## Restart Metrics\n\n`;
2736
- markdown += `### Success Rate\n`;
2737
- markdown += `- **Success Rate**: ${restart.successRate.successRate} (${restart.successRate.success}/${restart.successRate.total} attempts)\n`;
2738
- markdown += `- **Meets Target**: ${restart.successRate.meetsTarget ? '✅ Yes' : '❌ No'}\n`;
2739
- markdown += `- Failures: ${restart.successRate.failure}\n\n`;
2740
- if (restart.latency) {
2741
- markdown += `### Latency (Percentiles)\n\n`;
2742
- markdown += `**Full Mode (Narrative + Links)**\n`;
2743
- markdown += `- p50: ${restart.latency.full.p50}ms\n`;
2744
- markdown += `- p95: ${restart.latency.full.p95}ms\n`;
2745
- markdown += `- p99: ${restart.latency.full.p99}ms\n`;
2746
- markdown += `- Count: ${restart.latency.full.count}\n\n`;
2747
- markdown += `**Summary Mode**\n`;
2748
- markdown += `- p50: ${restart.latency.summary.p50}ms\n`;
2749
- markdown += `- p95: ${restart.latency.summary.p95}ms\n`;
2750
- markdown += `- p99: ${restart.latency.summary.p99}ms\n`;
2751
- markdown += `- Count: ${restart.latency.summary.count}\n\n`;
2752
- }
2753
- }
2754
- markdown += `## Thresholds\n\n`;
2755
- markdown += `- Narrative Coverage: ≥ ${thresholds.narrativeCoverage * 100}%\n`;
2756
- markdown += `- Link Coverage: ≥ ${thresholds.linkCoverage * 100}%\n`;
2757
- markdown += `- Rich Reason Ratio: ≥ ${thresholds.richReasonRatio * 100}%\n`;
2758
- markdown += `- Restart Success Rate: ≥ ${thresholds.restartSuccessRate * 100}%\n`;
2759
- markdown += `- Restart Latency p95 (Full): ≤ ${thresholds.restartLatencyP95Full}ms\n`;
2760
- markdown += `- Restart Latency p95 (Summary): ≤ ${thresholds.restartLatencyP95Summary}ms\n\n`;
2761
- if (recommendations.length > 0) {
2762
- markdown += `## ⚠️ Recommendations\n\n`;
2763
- recommendations.forEach((rec, idx) => {
2764
- markdown += `${idx + 1}. **${rec.type}**: ${rec.message}\n`;
2765
- markdown += ` - Target: ${rec.target}, Current: ${rec.current}\n\n`;
2766
- });
2767
- }
2768
- else {
2769
- markdown += `## ✅ All quality targets met!\n\n`;
2770
- }
2771
- return markdown;
482
+ async function createAuditFinding(input) {
483
+ await (0, db_manager_js_1.initDB)();
484
+ return (0, finding_store_js_1.createAuditFinding)((0, db_manager_js_1.getAdapter)(), input);
485
+ }
486
+ // Facade wrappers: the stores below take an explicit adapter as their first
487
+ // argument; the `mama` surface keeps its published (input) signatures and
488
+ // resolves the ambient adapter at this boundary instead.
489
+ async function upsertChannelSummary(input) {
490
+ await (0, db_manager_js_1.initDB)();
491
+ return (0, api_js_2.upsertChannelSummary)((0, db_manager_js_1.getAdapter)(), input);
492
+ }
493
+ async function getChannelSummary(channelKey) {
494
+ await (0, db_manager_js_1.initDB)();
495
+ return (0, api_js_2.getChannelSummary)((0, db_manager_js_1.getAdapter)(), channelKey);
496
+ }
497
+ async function listOpenAuditFindings() {
498
+ await (0, db_manager_js_1.initDB)();
499
+ return (0, finding_store_js_1.listOpenAuditFindings)((0, db_manager_js_1.getAdapter)());
500
+ }
501
+ async function listMemoryEventsForMemory(memoryId) {
502
+ await (0, db_manager_js_1.initDB)();
503
+ return (0, event_store_js_1.listMemoryEventsForMemory)((0, db_manager_js_1.getAdapter)(), memoryId);
504
+ }
505
+ async function listRecentMemoryEvents(limit) {
506
+ await (0, db_manager_js_1.initDB)();
507
+ return (0, event_store_js_1.listRecentMemoryEvents)((0, db_manager_js_1.getAdapter)(), limit);
508
+ }
509
+ async function getMemoryProvenance(memoryId, options) {
510
+ await (0, db_manager_js_1.initDB)();
511
+ return (0, provenance_query_js_1.getMemoryProvenance)((0, db_manager_js_1.getAdapter)(), memoryId, options);
512
+ }
513
+ async function listMemoriesByEnvelopeHash(envelopeHash, options) {
514
+ await (0, db_manager_js_1.initDB)();
515
+ return (0, provenance_query_js_1.listMemoriesByEnvelopeHash)((0, db_manager_js_1.getAdapter)(), envelopeHash, options);
516
+ }
517
+ async function listMemoriesByGatewayCallId(gatewayCallId, options) {
518
+ await (0, db_manager_js_1.initDB)();
519
+ return (0, provenance_query_js_1.listMemoriesByGatewayCallId)((0, db_manager_js_1.getAdapter)(), gatewayCallId, options);
520
+ }
521
+ async function listMemoriesByModelRunId(modelRunId, options) {
522
+ await (0, db_manager_js_1.initDB)();
523
+ return (0, provenance_query_js_1.listMemoriesByModelRunId)((0, db_manager_js_1.getAdapter)(), modelRunId, options);
524
+ }
525
+ // memory/api.ts is adapter-first; the `mama` surface keeps its published
526
+ // (input) signatures and resolves the ambient adapter at this boundary.
527
+ async function saveMemory(input) {
528
+ await (0, db_manager_js_1.initDB)();
529
+ return (0, api_js_2.saveMemory)((0, db_manager_js_1.getAdapter)(), input);
530
+ }
531
+ async function recallMemory(query, options) {
532
+ await (0, db_manager_js_1.initDB)();
533
+ return (0, api_js_2.recallMemory)((0, db_manager_js_1.getAdapter)(), query, options);
534
+ }
535
+ async function buildProfile(scopes) {
536
+ await (0, db_manager_js_1.initDB)();
537
+ return (0, api_js_2.buildProfile)((0, db_manager_js_1.getAdapter)(), scopes);
538
+ }
539
+ async function ingestMemory(input) {
540
+ await (0, db_manager_js_1.initDB)();
541
+ return (0, api_js_2.ingestMemory)((0, db_manager_js_1.getAdapter)(), input);
542
+ }
543
+ async function ingestConversation(input) {
544
+ await (0, db_manager_js_1.initDB)();
545
+ return (0, api_js_2.ingestConversation)((0, db_manager_js_1.getAdapter)(), input);
546
+ }
547
+ async function buildMemoryBootstrap(params) {
548
+ await (0, db_manager_js_1.initDB)();
549
+ return (0, api_js_2.buildMemoryBootstrap)((0, db_manager_js_1.getAdapter)(), params);
550
+ }
551
+ async function recordMemoryAudit(input) {
552
+ await (0, db_manager_js_1.initDB)();
553
+ return (0, api_js_2.recordMemoryAudit)((0, db_manager_js_1.getAdapter)(), input);
554
+ }
555
+ async function beginModelRun(input) {
556
+ await (0, db_manager_js_1.initDB)();
557
+ return (0, model_run_store_js_1.beginModelRun)((0, db_manager_js_1.getAdapter)(), input);
558
+ }
559
+ async function commitModelRun(modelRunId, summary, tokenCount) {
560
+ await (0, db_manager_js_1.initDB)();
561
+ return (0, model_run_store_js_1.commitModelRun)((0, db_manager_js_1.getAdapter)(), modelRunId, summary, tokenCount);
562
+ }
563
+ async function failModelRun(modelRunId, errorSummary, tokenCount) {
564
+ await (0, db_manager_js_1.initDB)();
565
+ return (0, model_run_store_js_1.failModelRun)((0, db_manager_js_1.getAdapter)(), modelRunId, errorSummary, tokenCount);
566
+ }
567
+ async function getModelRun(modelRunId) {
568
+ await (0, db_manager_js_1.initDB)();
569
+ return (0, model_run_store_js_1.getModelRun)((0, db_manager_js_1.getAdapter)(), modelRunId);
570
+ }
571
+ async function appendToolTrace(input) {
572
+ await (0, db_manager_js_1.initDB)();
573
+ return (0, tool_trace_store_js_1.appendToolTrace)((0, db_manager_js_1.getAdapter)(), input);
574
+ }
575
+ async function listToolTracesForRun(modelRunId) {
576
+ await (0, db_manager_js_1.initDB)();
577
+ return (0, tool_trace_store_js_1.listToolTracesForRun)((0, db_manager_js_1.getAdapter)(), modelRunId);
578
+ }
579
+ async function listToolTraces(input) {
580
+ await (0, db_manager_js_1.initDB)();
581
+ return (0, tool_trace_store_js_1.listToolTraces)((0, db_manager_js_1.getAdapter)(), input);
582
+ }
583
+ async function readToolTrace(traceId, scope) {
584
+ await (0, db_manager_js_1.initDB)();
585
+ return (0, tool_trace_store_js_1.readToolTrace)((0, db_manager_js_1.getAdapter)(), traceId, scope);
2772
586
  }
2773
587
  /**
2774
588
  * MAMA Public API
@@ -2791,77 +605,119 @@ function formatQualityReportMarkdown(report) {
2791
605
  // Retained internal functions for future use, but MCP exposes only:
2792
606
  // save, search, update, load_checkpoint
2793
607
  // ════════════════════════════════════════════════════════════════════════════
608
+ /**
609
+ * Instance-bound MAMA API factory.
610
+ *
611
+ * Same member surface as the ambient `mama` object, but every call reads and
612
+ * writes through the adapter the owner hands in — no module-global handle is
613
+ * consulted. Products that own their database lifetime (openDatabase) bind
614
+ * once at boot and pass this object down; the ambient `mama` export remains
615
+ * the compatibility boundary for callers without an instance.
616
+ */
617
+ function createMamaApi(adapter) {
618
+ return {
619
+ save: (params) => saveInternal(adapter, params),
620
+ suggest: (userQuestion, options) => (0, api_js_1.suggestInAdapter)(adapter, userQuestion, options),
621
+ saveMemory: (input) => (0, api_js_2.saveMemory)(adapter, input),
622
+ recallMemory: (query, options) => (0, api_js_2.recallMemory)(adapter, query, options),
623
+ list: (options) => (0, api_js_1.listDecisionsInAdapter)(adapter, options),
624
+ listDecisions: (options) => (0, api_js_1.listDecisionsInAdapter)(adapter, options),
625
+ listCheckpoints: (limit) => (0, api_js_1.listCheckpointsInAdapter)(adapter, limit),
626
+ updateOutcome: (decisionId, outcome) => (0, api_js_1.updateOutcomeInAdapter)(adapter, decisionId, outcome),
627
+ buildProfile: (scopes) => (0, api_js_2.buildProfile)(adapter, scopes),
628
+ ingestMemory: (input) => (0, api_js_2.ingestMemory)(adapter, input),
629
+ ingestConversation: (input) => (0, api_js_2.ingestConversation)(adapter, input),
630
+ evolveMemory: (input) => (0, api_js_2.evolveMemory)(input),
631
+ buildMemoryBootstrap: (params) => (0, api_js_2.buildMemoryBootstrap)(adapter, params),
632
+ createAuditAck: (input) => (0, api_js_2.createAuditAck)(input),
633
+ recordMemoryAudit: (input) => (0, api_js_2.recordMemoryAudit)(adapter, input),
634
+ upsertChannelSummary: (input) => (0, api_js_2.upsertChannelSummary)(adapter, input),
635
+ getChannelSummary: (channelKey) => (0, api_js_2.getChannelSummary)(adapter, channelKey),
636
+ listAuditFindings: () => (0, finding_store_js_1.listOpenAuditFindings)(adapter),
637
+ listOpenAuditFindings: () => (0, finding_store_js_1.listOpenAuditFindings)(adapter),
638
+ createAuditFinding: (input) => (0, finding_store_js_1.createAuditFinding)(adapter, input),
639
+ getMemoryProvenance: (memoryId, options) => (0, provenance_query_js_1.getMemoryProvenance)(adapter, memoryId, options),
640
+ listMemoriesByEnvelopeHash: (envelopeHash, options) => (0, provenance_query_js_1.listMemoriesByEnvelopeHash)(adapter, envelopeHash, options),
641
+ listMemoriesByGatewayCallId: (gatewayCallId, options) => (0, provenance_query_js_1.listMemoriesByGatewayCallId)(adapter, gatewayCallId, options),
642
+ listMemoriesByModelRunId: (modelRunId, options) => (0, provenance_query_js_1.listMemoriesByModelRunId)(adapter, modelRunId, options),
643
+ listMemoryEventsForMemory: (memoryId) => (0, event_store_js_1.listMemoryEventsForMemory)(adapter, memoryId),
644
+ listRecentMemoryEvents: (limit) => (0, event_store_js_1.listRecentMemoryEvents)(adapter, limit),
645
+ beginModelRun: (input) => (0, model_run_store_js_1.beginModelRun)(adapter, input),
646
+ commitModelRun: (modelRunId, summary, tokenCount) => (0, model_run_store_js_1.commitModelRun)(adapter, modelRunId, summary, tokenCount),
647
+ failModelRun: (modelRunId, errorSummary, tokenCount) => (0, model_run_store_js_1.failModelRun)(adapter, modelRunId, errorSummary, tokenCount),
648
+ getModelRun: (modelRunId) => (0, model_run_store_js_1.getModelRun)(adapter, modelRunId),
649
+ listModelRunNativeInputs: async (modelRunId, principalId, options) => (0, model_run_store_js_1.listModelRunNativeInputs)(adapter, modelRunId, principalId, options),
650
+ appendToolTrace: (input) => (0, tool_trace_store_js_1.appendToolTrace)(adapter, input),
651
+ listToolTracesForRun: (modelRunId) => (0, tool_trace_store_js_1.listToolTracesForRun)(adapter, modelRunId),
652
+ listToolTraces: (input) => (0, tool_trace_store_js_1.listToolTraces)(adapter, input),
653
+ readToolTrace: (traceId, scope) => (0, tool_trace_store_js_1.readToolTrace)(adapter, traceId, scope),
654
+ saveCheckpoint: (summary, openFiles, nextSteps,
655
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
656
+ recentConversation) => (0, api_js_1.saveCheckpointInAdapter)(adapter, summary, openFiles, nextSteps, recentConversation),
657
+ loadCheckpoint: () => (0, api_js_1.loadCheckpointInAdapter)(adapter),
658
+ recall: (topic, options) => recallInAdapter(adapter, topic, options),
659
+ expandWithGraph: (candidates) => (0, api_js_1.expandWithGraphInAdapter)(adapter, candidates),
660
+ };
661
+ }
2794
662
  const mama = {
2795
663
  // Core functions (used by 4 MCP tools)
2796
664
  save,
2797
- saveWithTrustedProvenance,
2798
665
  suggest,
2799
- saveMemory: api_js_1.saveMemory,
2800
- saveMemoryWithTrustedProvenance: api_js_1.saveMemoryWithTrustedProvenance,
2801
- recallMemory: api_js_1.recallMemory,
666
+ saveMemory,
667
+ recallMemory,
2802
668
  list: listDecisions,
2803
669
  listCheckpoints,
2804
670
  updateOutcome,
2805
- buildProfile: api_js_1.buildProfile,
2806
- ingestMemory: api_js_1.ingestMemory,
2807
- ingestWithTrustedProvenance: api_js_1.ingestWithTrustedProvenance,
2808
- ingestConversation: api_js_1.ingestConversation,
2809
- ingestConversationWithTrustedProvenance: api_js_1.ingestConversationWithTrustedProvenance,
2810
- evolveMemory: api_js_1.evolveMemory,
2811
- buildMemoryBootstrap: api_js_1.buildMemoryBootstrap,
2812
- createAuditAck: api_js_1.createAuditAck,
2813
- recordMemoryAudit: api_js_1.recordMemoryAudit,
2814
- upsertChannelSummary: api_js_1.upsertChannelSummary,
2815
- getChannelSummary: api_js_1.getChannelSummary,
2816
- listAuditFindings: finding_store_js_1.listOpenAuditFindings,
2817
- listOpenAuditFindings: finding_store_js_1.listOpenAuditFindings,
2818
- createAuditFinding: finding_store_js_1.createAuditFinding,
2819
- getMemoryProvenance: provenance_query_js_1.getMemoryProvenance,
2820
- listMemoriesByEnvelopeHash: provenance_query_js_1.listMemoriesByEnvelopeHash,
2821
- listMemoriesByGatewayCallId: provenance_query_js_1.listMemoriesByGatewayCallId,
2822
- listMemoriesByModelRunId: provenance_query_js_1.listMemoriesByModelRunId,
2823
- listMemoryEventsForMemory: event_store_js_1.listMemoryEventsForMemory,
2824
- listRecentMemoryEvents: event_store_js_1.listRecentMemoryEvents,
2825
- beginModelRun: store_js_1.beginModelRun,
2826
- beginModelRunInAdapter: store_js_1.beginModelRunInAdapter,
2827
- commitModelRun: store_js_1.commitModelRun,
2828
- commitModelRunInAdapter: store_js_1.commitModelRunInAdapter,
2829
- failModelRun: store_js_1.failModelRun,
2830
- failModelRunInAdapter: store_js_1.failModelRunInAdapter,
2831
- getModelRun: store_js_1.getModelRun,
2832
- getModelRunInAdapter: store_js_1.getModelRunInAdapter,
2833
- appendToolTrace: tool_trace_store_js_1.appendToolTrace,
2834
- listToolTracesForRun: tool_trace_store_js_1.listToolTracesForRun,
671
+ buildProfile,
672
+ ingestMemory,
673
+ ingestConversation,
674
+ evolveMemory: api_js_2.evolveMemory,
675
+ buildMemoryBootstrap,
676
+ createAuditAck: api_js_2.createAuditAck,
677
+ recordMemoryAudit,
678
+ upsertChannelSummary,
679
+ getChannelSummary,
680
+ listAuditFindings: listOpenAuditFindings,
681
+ listOpenAuditFindings,
682
+ createAuditFinding,
683
+ getMemoryProvenance,
684
+ listMemoriesByEnvelopeHash,
685
+ listMemoriesByGatewayCallId,
686
+ listMemoriesByModelRunId,
687
+ listMemoryEventsForMemory,
688
+ listRecentMemoryEvents,
689
+ beginModelRun,
690
+ commitModelRun,
691
+ failModelRun,
692
+ getModelRun,
693
+ appendToolTrace,
694
+ listToolTracesForRun,
695
+ listToolTraces,
696
+ readToolTrace,
2835
697
  saveCheckpoint,
2836
698
  loadCheckpoint,
2837
699
  // Legacy functions (retained for internal use, not exposed via MCP)
2838
700
  recall,
2839
- proposeLink,
2840
- approveLink,
2841
- rejectLink,
2842
- getPendingLinks,
2843
- deprecateAutoLinks,
2844
- calculateCoverage,
2845
- calculateQuality,
2846
- generateQualityReport,
2847
- logRestartAttempt,
2848
- calculateRestartSuccessRate,
2849
- calculateRestartLatency,
2850
- getRestartMetrics,
2851
- scanAutoLinks,
2852
- createLinkBackup,
2853
- generatePreCleanupReport,
2854
- restoreLinkBackup,
2855
- verifyBackupExists,
2856
- deleteAutoLinks,
2857
- validateCleanupResult,
2858
701
  expandWithGraph,
2859
702
  };
2860
703
  // Default export for backward compatibility
2861
704
  exports.default = mama;
2862
- // CommonJS compatibility - allows require('@jungjaehoon/mama-core/mama-api').save()
705
+ // CommonJS compatibility - require('@jungjaehoon/mama-core/mama-api') exposes the
706
+ // ambient `mama` facade methods at top level. Merge instead of replacing
707
+ // module.exports: the compiled `exports.X = X` named exports (suggestInAdapter,
708
+ // saveCheckpointInAdapter, ...) are how api/catalog.ts reaches them from dist.
709
+ // Getter-only named exports (evolveMemory, createAuditAck) keep their getter —
710
+ // it already returns the same function the facade carries.
2863
711
  if (typeof module !== 'undefined' && module.exports) {
2864
- module.exports = mama;
2865
- module.exports.default = mama;
712
+ const target = module.exports;
713
+ for (const [key, value] of Object.entries(mama)) {
714
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
715
+ if (!descriptor || descriptor.writable) {
716
+ target[key] = value;
717
+ }
718
+ }
719
+ target.default = mama;
720
+ target.mama = mama;
721
+ target.createMamaApi = createMamaApi;
2866
722
  }
2867
723
  //# sourceMappingURL=mama-api.js.map