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