@jungjaehoon/mama-core 2.4.1 → 4.1.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.
- package/README.md +76 -239
- package/db/migrations/030-case-first-memory-substrate.sql +13 -98
- package/db/migrations/034-add-connector-event-scope-columns.sql +9 -13
- package/db/migrations/039-add-connector-event-operator-ingest-seq.sql +9 -95
- package/db/migrations/062-refresh-connector-event-sequences.sql +9 -113
- package/db/migrations/063-refresh-legacy-connector-event-updates.sql +9 -32
- package/db/migrations/067-add-connector-event-source-entity-id.sql +9 -13
- package/db/migrations/069-create-registry-nodes.sql +52 -0
- package/db/migrations/070-record-identity.sql +35 -0
- package/db/migrations/071-service-operation-origins.sql +187 -0
- package/db/migrations/072-observation-versions.sql +29 -0
- package/db/migrations/073-registry-corrections.sql +45 -0
- package/db/migrations/074-work-graph-ref-kinds.sql +39 -0
- package/db/migrations/075-agent-judgments.sql +58 -0
- package/db/migrations/076-scoped-checkpoint-bindings.sql +15 -0
- package/db/migrations/077-legacy-record-kind.sql +6 -0
- package/db/migrations/078-source-ingest-commands.sql +18 -0
- package/db/migrations/079-twin-edge-relations.sql +43 -0
- package/db/migrations/080-restore-duration-days.sql +7 -0
- package/db/migrations/081-mailbox.sql +61 -0
- package/db/migrations/082-commitments-row-id.sql +12 -0
- package/db/migrations/083-observation-is-the-evidence.sql +49 -0
- package/db/migrations/084-scope-kind-is-not-the-cores-to-list.sql +34 -0
- package/db/migrations/085-a-migration-has-a-source.sql +37 -0
- package/db/migrations/086-the-mailbox-is-generic.sql +110 -0
- package/db/migrations/087-mailbox-payload.sql +5 -0
- package/db/migrations/088-native-input-journal.sql +14 -0
- package/db/migrations/089-native-turn-receipt-lookup.sql +3 -0
- package/db/migrations/090-model-run-native-inputs.sql +29 -0
- package/db/migrations/091-native-turn-results.sql +11 -0
- package/db/migrations/092-restore-decision-fts.sql +34 -0
- package/db/migrations/093-native-turn-primary-kind.sql +14 -0
- package/db/migrations/094-commitment-run-provenance.sql +9 -0
- package/db/migrations/095-model-run-native-input-identity.sql +39 -0
- package/db/migrations/096-drop-connector-event-index-fts.sql +9 -0
- package/db/migrations/097-native-input-json-validity.sql +37 -0
- package/db/migrations/098-workflow-memory-kind.sql +105 -0
- package/db/migrations/099-commitment-revision-graph.sql +2 -0
- package/dist/action-contracts.d.ts +212 -0
- package/dist/action-contracts.js +10 -0
- package/dist/api/catalog.d.ts +118 -0
- package/dist/api/catalog.js +1756 -0
- package/dist/api/dispatch.d.ts +76 -0
- package/dist/api/dispatch.js +372 -0
- package/dist/canonicalize.d.ts +2 -2
- package/dist/canonicalize.js +3 -3
- package/dist/client/client.d.ts +33 -0
- package/dist/client/client.js +123 -0
- package/dist/client/ipc.d.ts +81 -0
- package/dist/client/ipc.js +314 -0
- package/dist/db-adapter/base-adapter.d.ts +6 -66
- package/dist/db-adapter/base-adapter.js +6 -6
- package/dist/db-adapter/index.d.ts +5 -5
- package/dist/db-adapter/index.js +2 -5
- package/dist/db-adapter/node-sqlite-adapter.d.ts +85 -15
- package/dist/db-adapter/node-sqlite-adapter.js +1985 -570
- package/dist/db-manager.d.ts +53 -97
- package/dist/db-manager.js +121 -603
- package/dist/embedding/embedder.d.ts +132 -0
- package/dist/embedding/embedder.js +299 -0
- package/dist/identity/principal-repository.d.ts +2 -2
- package/dist/index.d.ts +43 -58
- package/dist/index.js +97 -145
- package/dist/{context-compile/channel-grant.d.ts → knowledge/access.d.ts} +12 -17
- package/dist/knowledge/access.js +663 -0
- package/dist/knowledge/case-errors.d.ts +31 -0
- package/dist/knowledge/case-errors.js +40 -0
- package/dist/{cases/search-rollup.d.ts → knowledge/case-search-rollup.d.ts} +5 -4
- package/dist/{cases/search-rollup.js → knowledge/case-search-rollup.js} +6 -8
- package/dist/{cases/store.d.ts → knowledge/case-store.d.ts} +3 -6
- package/dist/{cases/store.js → knowledge/case-store.js} +7 -42
- package/dist/{cases/timeline-range.d.ts → knowledge/case-timeline-range.d.ts} +3 -3
- package/dist/{cases/timeline-range.js → knowledge/case-timeline-range.js} +47 -25
- package/dist/{cases/types.d.ts → knowledge/case-types.d.ts} +8 -2
- package/dist/{cases/types.js → knowledge/case-types.js} +1 -1
- package/dist/knowledge/commitment-revision-migration.d.ts +4 -0
- package/dist/knowledge/commitment-revision-migration.js +50 -0
- package/dist/knowledge/commitments.d.ts +192 -0
- package/dist/knowledge/commitments.js +386 -0
- package/dist/knowledge/decision-edges.d.ts +70 -0
- package/dist/knowledge/decision-edges.js +123 -0
- package/dist/{agent-graph/types.d.ts → knowledge/graph-query.d.ts} +80 -70
- package/dist/knowledge/graph-query.js +1585 -0
- package/dist/knowledge/identity.d.ts +16 -0
- package/dist/knowledge/identity.js +22 -0
- package/dist/knowledge/index.d.ts +47 -0
- package/dist/knowledge/index.js +90 -0
- package/dist/knowledge/judgment-edge.d.ts +6 -0
- package/dist/knowledge/judgment-edge.js +22 -0
- package/dist/knowledge/judgments.d.ts +118 -0
- package/dist/knowledge/judgments.js +802 -0
- package/dist/knowledge/observations.d.ts +160 -0
- package/dist/knowledge/observations.js +449 -0
- package/dist/knowledge/question-type.d.ts +34 -0
- package/dist/knowledge/question-type.js +70 -0
- package/dist/{search → knowledge}/ranker-features.js +1 -1
- package/dist/{search → knowledge}/search-quality.d.ts +0 -1
- package/dist/knowledge/search.d.ts +38 -0
- package/dist/knowledge/search.js +91 -0
- package/dist/knowledge/source-ingest.d.ts +65 -0
- package/dist/knowledge/source-ingest.js +174 -0
- package/dist/{edges/types.d.ts → knowledge/twin-edge-types.d.ts} +31 -4
- package/dist/{edges/types.js → knowledge/twin-edge-types.js} +13 -2
- package/dist/knowledge/work-dates.d.ts +49 -0
- package/dist/knowledge/work-dates.js +120 -0
- package/dist/mama-api.d.ts +114 -778
- package/dist/mama-api.js +313 -2461
- package/dist/memory/api.d.ts +264 -24
- package/dist/memory/api.js +1633 -843
- package/dist/memory/bootstrap-builder.d.ts +2 -1
- package/dist/memory/bootstrap-builder.js +5 -5
- package/dist/memory/channel-summary-state-store.d.ts +3 -2
- package/dist/memory/channel-summary-state-store.js +53 -51
- package/dist/memory/channel-summary-store.d.ts +5 -3
- package/dist/memory/channel-summary-store.js +3 -8
- package/dist/memory/dashboard-read.d.ts +46 -0
- package/dist/memory/dashboard-read.js +94 -0
- package/dist/memory/event-store.d.ts +3 -3
- package/dist/memory/event-store.js +3 -10
- package/dist/memory/finding-store.d.ts +3 -2
- package/dist/memory/finding-store.js +2 -7
- package/dist/memory/graph-read.d.ts +52 -0
- package/dist/memory/graph-read.js +130 -0
- package/dist/memory/judgment-types.d.ts +394 -0
- package/dist/memory/judgment-types.js +16 -0
- package/dist/memory/provenance-audit.d.ts +5 -2
- package/dist/memory/provenance-audit.js +8 -14
- package/dist/memory/provenance-live.d.ts +141 -0
- package/dist/memory/provenance-live.js +409 -0
- package/dist/memory/provenance-query.d.ts +5 -4
- package/dist/memory/provenance-query.js +21 -29
- package/dist/memory/provenance-resolver.d.ts +191 -0
- package/dist/memory/provenance-resolver.js +155 -0
- package/dist/memory/provenance.d.ts +1 -11
- package/dist/memory/provenance.js +2 -33
- package/dist/memory/recall-sanitize.d.ts +55 -0
- package/dist/memory/recall-sanitize.js +144 -0
- package/dist/memory/secret-filter.d.ts +32 -0
- package/dist/memory/secret-filter.js +126 -0
- package/dist/memory/truth-store.d.ts +2 -1
- package/dist/memory/truth-store.js +1 -4
- package/dist/memory/types.d.ts +74 -24
- package/dist/memory/types.js +75 -5
- package/dist/memory/write-adapters.d.ts +98 -0
- package/dist/memory/write-adapters.js +242 -0
- package/dist/operations/owner-action-effects.d.ts +53 -0
- package/dist/operations/owner-action-effects.js +96 -0
- package/dist/provenance/source-ref.d.ts +1 -1
- package/dist/provenance/source-ref.js +0 -2
- package/dist/registry/corrections.d.ts +31 -0
- package/dist/registry/corrections.js +332 -0
- package/dist/registry/record-identity.d.ts +49 -0
- package/dist/registry/record-identity.js +133 -0
- package/dist/registry/store.d.ts +101 -0
- package/dist/registry/store.js +395 -0
- package/dist/registry/types.d.ts +28 -0
- package/dist/registry/types.js +5 -0
- package/dist/relevance-scorer.js +2 -2
- package/dist/runtime/agent-event-bus.d.ts +50 -0
- package/dist/runtime/agent-event-bus.js +101 -0
- package/dist/runtime/drivers/claude-cli-wrapper.d.ts +152 -0
- package/dist/runtime/drivers/claude-cli-wrapper.js +375 -0
- package/dist/runtime/drivers/claude-effort.d.ts +4 -0
- package/dist/runtime/drivers/claude-effort.js +16 -0
- package/dist/runtime/drivers/cli-arg-redaction.d.ts +2 -0
- package/dist/runtime/drivers/cli-arg-redaction.js +18 -0
- package/dist/runtime/drivers/cli-secret-redaction.d.ts +17 -0
- package/dist/runtime/drivers/cli-secret-redaction.js +126 -0
- package/dist/runtime/drivers/codex-app-server-process.d.ts +277 -0
- package/dist/runtime/drivers/codex-app-server-process.js +2342 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.d.ts +17 -0
- package/dist/runtime/drivers/codex-auxiliary-tools.js +267 -0
- package/dist/runtime/drivers/codex-home.d.ts +20 -0
- package/dist/runtime/drivers/codex-home.js +674 -0
- package/dist/runtime/drivers/codex-thread-registry.d.ts +63 -0
- package/dist/runtime/drivers/codex-thread-registry.js +433 -0
- package/dist/runtime/drivers/persistent-cli-adapter.d.ts +160 -0
- package/dist/runtime/drivers/persistent-cli-adapter.js +396 -0
- package/dist/runtime/drivers/persistent-cli-process.d.ts +425 -0
- package/dist/runtime/drivers/persistent-cli-process.js +1529 -0
- package/dist/runtime/drivers/types.d.ts +456 -0
- package/dist/runtime/drivers/types.js +110 -0
- package/dist/runtime/mailbox.d.ts +223 -0
- package/dist/runtime/mailbox.js +582 -0
- package/dist/runtime/model-run-store.d.ts +28 -0
- package/dist/{model-runs/store.js → runtime/model-run-store.js} +81 -41
- package/dist/{model-runs/types.d.ts → runtime/model-run-types.d.ts} +33 -4
- package/dist/{model-runs/types.js → runtime/model-run-types.js} +1 -1
- package/dist/runtime/native-effect-observer.d.ts +22 -0
- package/dist/runtime/native-effect-observer.js +103 -0
- package/dist/runtime/native-input-journal.d.ts +46 -0
- package/dist/runtime/native-input-journal.js +308 -0
- package/dist/runtime/native-prompt.d.ts +118 -0
- package/dist/runtime/native-prompt.js +496 -0
- package/dist/runtime/native-session.d.ts +10 -0
- package/dist/{agent-graph/index.js → runtime/native-session.js} +9 -6
- package/dist/runtime/native-tool-trace-observer.d.ts +5 -0
- package/dist/runtime/native-tool-trace-observer.js +65 -0
- package/dist/runtime/native-turn.d.ts +272 -0
- package/dist/runtime/native-turn.js +1041 -0
- package/dist/runtime/operations.d.ts +236 -0
- package/dist/runtime/operations.js +565 -0
- package/dist/runtime/post-tool-handler.d.ts +74 -0
- package/dist/runtime/post-tool-handler.js +137 -0
- package/dist/runtime/prompt-layers.d.ts +126 -0
- package/dist/runtime/prompt-layers.js +239 -0
- package/dist/runtime/runtime-process.d.ts +102 -0
- package/dist/runtime/runtime-process.js +206 -0
- package/dist/runtime/runtime.d.ts +278 -0
- package/dist/runtime/runtime.js +563 -0
- package/dist/runtime/session-pool.d.ts +176 -0
- package/dist/runtime/session-pool.js +369 -0
- package/dist/runtime/stimulus-payload.d.ts +5 -0
- package/dist/runtime/stimulus-payload.js +50 -0
- package/dist/runtime/subagent-bridge.d.ts +24 -0
- package/dist/{agent-graph/types.js → runtime/subagent-bridge.js} +1 -1
- package/dist/runtime/text-completion.d.ts +22 -0
- package/dist/{agent-situation/types.js → runtime/text-completion.js} +1 -1
- package/dist/runtime/token-estimator.d.ts +25 -0
- package/dist/runtime/token-estimator.js +75 -0
- package/dist/runtime/tool-trace-store.d.ts +22 -0
- package/dist/{model-runs → runtime}/tool-trace-store.js +46 -27
- package/dist/runtime/trace-summary.d.ts +5 -0
- package/dist/runtime/trace-summary.js +74 -0
- package/dist/runtime/turn-text.d.ts +23 -0
- package/dist/runtime/turn-text.js +41 -0
- package/dist/storage/database.d.ts +67 -0
- package/dist/storage/database.js +158 -0
- package/package.json +20 -34
- package/dist/agent-graph/alias-write.d.ts +0 -3
- package/dist/agent-graph/alias-write.js +0 -234
- package/dist/agent-graph/entity-resolve.d.ts +0 -3
- package/dist/agent-graph/entity-resolve.js +0 -271
- package/dist/agent-graph/errors.d.ts +0 -4
- package/dist/agent-graph/errors.js +0 -11
- package/dist/agent-graph/graph-query.d.ts +0 -5
- package/dist/agent-graph/graph-query.js +0 -360
- package/dist/agent-graph/index.d.ts +0 -6
- package/dist/agent-situation/builder.d.ts +0 -8
- package/dist/agent-situation/builder.js +0 -239
- package/dist/agent-situation/cache-key.d.ts +0 -4
- package/dist/agent-situation/cache-key.js +0 -64
- package/dist/agent-situation/index.d.ts +0 -7
- package/dist/agent-situation/index.js +0 -28
- package/dist/agent-situation/packet-store.d.ts +0 -27
- package/dist/agent-situation/packet-store.js +0 -228
- package/dist/agent-situation/ranking-policy.d.ts +0 -18
- package/dist/agent-situation/ranking-policy.js +0 -75
- package/dist/agent-situation/source-readers.d.ts +0 -52
- package/dist/agent-situation/source-readers.js +0 -300
- package/dist/agent-situation/types.d.ts +0 -195
- package/dist/cases/wiki-page-index.d.ts +0 -37
- package/dist/cases/wiki-page-index.js +0 -201
- package/dist/config-loader.d.ts +0 -68
- package/dist/config-loader.js +0 -239
- package/dist/connectors/event-index.d.ts +0 -24
- package/dist/connectors/event-index.js +0 -245
- package/dist/connectors/raw-query.d.ts +0 -40
- package/dist/connectors/raw-query.js +0 -412
- package/dist/connectors/types.d.ts +0 -117
- package/dist/connectors/types.js +0 -3
- package/dist/context-compile/boundary-defaults.d.ts +0 -12
- package/dist/context-compile/boundary-defaults.js +0 -105
- package/dist/context-compile/channel-grant.js +0 -66
- package/dist/context-compile/compiler-policy.d.ts +0 -39
- package/dist/context-compile/compiler-policy.js +0 -147
- package/dist/context-compile/compiler.d.ts +0 -18
- package/dist/context-compile/compiler.js +0 -404
- package/dist/context-compile/index.d.ts +0 -10
- package/dist/context-compile/index.js +0 -26
- package/dist/context-compile/packet-store.d.ts +0 -8
- package/dist/context-compile/packet-store.js +0 -239
- package/dist/context-compile/ref.d.ts +0 -7
- package/dist/context-compile/ref.js +0 -102
- package/dist/context-compile/source-readers.d.ts +0 -70
- package/dist/context-compile/source-readers.js +0 -840
- package/dist/context-compile/types.d.ts +0 -155
- package/dist/context-compile/types.js +0 -11
- package/dist/context-compile/visibility.d.ts +0 -21
- package/dist/context-compile/visibility.js +0 -224
- package/dist/decision-tracker.d.ts +0 -206
- package/dist/decision-tracker.js +0 -481
- package/dist/edges/ref-validation.d.ts +0 -7
- package/dist/edges/ref-validation.js +0 -321
- package/dist/edges/store.d.ts +0 -8
- package/dist/edges/store.js +0 -129
- package/dist/embedding-client.d.ts +0 -28
- package/dist/embedding-client.js +0 -53
- package/dist/embedding-server/index.d.ts +0 -67
- package/dist/embedding-server/index.js +0 -405
- package/dist/embedding-server/mobile/auth.d.ts +0 -44
- package/dist/embedding-server/mobile/auth.js +0 -150
- package/dist/embedding-server/mobile/daemon.d.ts +0 -129
- package/dist/embedding-server/mobile/daemon.js +0 -313
- package/dist/embedding-server/mobile/output-parser.d.ts +0 -115
- package/dist/embedding-server/mobile/output-parser.js +0 -241
- package/dist/embedding-server/mobile/session-api.d.ts +0 -57
- package/dist/embedding-server/mobile/session-api.js +0 -261
- package/dist/embedding-server/mobile/session-manager.d.ts +0 -138
- package/dist/embedding-server/mobile/session-manager.js +0 -378
- package/dist/embedding-server/mobile/websocket-handler.d.ts +0 -137
- package/dist/embedding-server/mobile/websocket-handler.js +0 -486
- package/dist/embeddings.d.ts +0 -68
- package/dist/embeddings.js +0 -244
- package/dist/entities/audit-metrics.d.ts +0 -68
- package/dist/entities/audit-metrics.js +0 -124
- package/dist/entities/candidate-generator.d.ts +0 -8
- package/dist/entities/candidate-generator.js +0 -24
- package/dist/entities/entity-linked-decision-counts.d.ts +0 -2
- package/dist/entities/entity-linked-decision-counts.js +0 -29
- package/dist/entities/entity-list.d.ts +0 -22
- package/dist/entities/entity-list.js +0 -3
- package/dist/entities/entity-orphan-list.d.ts +0 -17
- package/dist/entities/entity-orphan-list.js +0 -3
- package/dist/entities/entity-search.d.ts +0 -20
- package/dist/entities/entity-search.js +0 -3
- package/dist/entities/errors.d.ts +0 -55
- package/dist/entities/errors.js +0 -100
- package/dist/entities/lineage-store.d.ts +0 -28
- package/dist/entities/lineage-store.js +0 -204
- package/dist/entities/normalization.d.ts +0 -14
- package/dist/entities/normalization.js +0 -63
- package/dist/entities/policy-store.d.ts +0 -8
- package/dist/entities/policy-store.js +0 -75
- package/dist/entities/policy-types.d.ts +0 -72
- package/dist/entities/policy-types.js +0 -59
- package/dist/entities/projection.d.ts +0 -8
- package/dist/entities/projection.js +0 -82
- package/dist/entities/provenance-query.d.ts +0 -32
- package/dist/entities/provenance-query.js +0 -3
- package/dist/entities/read-identity.d.ts +0 -31
- package/dist/entities/read-identity.js +0 -264
- package/dist/entities/recall-bridge.d.ts +0 -5
- package/dist/entities/recall-bridge.js +0 -171
- package/dist/entities/resolution-engine.d.ts +0 -8
- package/dist/entities/resolution-engine.js +0 -3
- package/dist/entities/rollback-preview.d.ts +0 -35
- package/dist/entities/rollback-preview.js +0 -3
- package/dist/entities/source-locator.d.ts +0 -3
- package/dist/entities/source-locator.js +0 -32
- package/dist/entities/store.d.ts +0 -69
- package/dist/entities/store.js +0 -544
- package/dist/entities/types.d.ts +0 -166
- package/dist/entities/types.js +0 -30
- package/dist/memory/extraction-prompt.d.ts +0 -4
- package/dist/memory/extraction-prompt.js +0 -89
- package/dist/memory/scope-backfill.d.ts +0 -14
- package/dist/memory/scope-backfill.js +0 -3
- package/dist/memory/scope-store.d.ts +0 -4
- package/dist/memory/scope-store.js +0 -9
- package/dist/memory-store.d.ts +0 -87
- package/dist/memory-store.js +0 -78
- package/dist/model-runs/store.d.ts +0 -13
- package/dist/model-runs/tool-trace-store.d.ts +0 -8
- package/dist/notification-manager.d.ts +0 -7
- package/dist/notification-manager.js +0 -12
- package/dist/outcome-tracker.d.ts +0 -151
- package/dist/outcome-tracker.js +0 -271
- package/dist/query-intent.d.ts +0 -23
- package/dist/query-intent.js +0 -114
- package/dist/runtime/trusted-provenance.d.ts +0 -3
- package/dist/runtime/trusted-provenance.js +0 -6
- package/dist/search/question-type.d.ts +0 -5
- package/dist/search/question-type.js +0 -41
- package/dist/test-utils.d.ts +0 -80
- package/dist/test-utils.js +0 -218
- package/dist/tier-validator.d.ts +0 -43
- package/dist/tier-validator.js +0 -159
- package/dist/time-formatter.d.ts +0 -25
- package/dist/time-formatter.js +0 -93
- /package/dist/{search → knowledge}/feedback-store.d.ts +0 -0
- /package/dist/{search → knowledge}/feedback-store.js +0 -0
- /package/dist/{search → knowledge}/ranker-features.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-rescore.js +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.d.ts +0 -0
- /package/dist/{search → knowledge}/ranker-trainer.js +0 -0
- /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.
|
|
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.
|
|
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
|
|
59
|
+
const api_js_1 = require("./memory/api.js");
|
|
63
60
|
const db_manager_js_1 = require("./db-manager.js");
|
|
64
|
-
const
|
|
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
|
|
72
|
-
Object.defineProperty(exports, "
|
|
73
|
-
Object.defineProperty(exports, "
|
|
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
|
-
|
|
94
|
-
|
|
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,
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
:
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
|
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
|
-
//
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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:
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
435
|
+
await (0, db_manager_js_1.initDB)();
|
|
436
|
+
return saveInternal((0, db_manager_js_1.getAdapter)(), params);
|
|
850
437
|
}
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
856
|
-
|
|
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
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1496
|
-
|
|
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
|
-
|
|
1544
|
-
|
|
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
|
-
|
|
1561
|
-
|
|
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
|
-
|
|
1603
|
-
|
|
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
|
-
*
|
|
2318
|
-
*
|
|
2319
|
-
*
|
|
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
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
)
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
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
|
|
2802
|
-
|
|
2803
|
-
recallMemory: api_js_1.recallMemory,
|
|
666
|
+
saveMemory,
|
|
667
|
+
recallMemory,
|
|
2804
668
|
list: listDecisions,
|
|
2805
669
|
listCheckpoints,
|
|
2806
670
|
updateOutcome,
|
|
2807
|
-
buildProfile
|
|
2808
|
-
ingestMemory
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
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 -
|
|
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
|
|
2869
|
-
|
|
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
|