@vinaes/succ 1.3.19
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/LICENSE +87 -0
- package/README.md +588 -0
- package/agents/succ-checkpoint-manager.md +51 -0
- package/agents/succ-code-reviewer.md +181 -0
- package/agents/succ-context-optimizer.md +83 -0
- package/agents/succ-debug.md +224 -0
- package/agents/succ-decision-auditor.md +74 -0
- package/agents/succ-deep-search.md +41 -0
- package/agents/succ-diff-reviewer.md +123 -0
- package/agents/succ-explore.md +83 -0
- package/agents/succ-general.md +109 -0
- package/agents/succ-knowledge-indexer.md +45 -0
- package/agents/succ-knowledge-mapper.md +61 -0
- package/agents/succ-memory-curator.md +43 -0
- package/agents/succ-memory-health-monitor.md +55 -0
- package/agents/succ-pattern-detective.md +62 -0
- package/agents/succ-plan.md +172 -0
- package/agents/succ-quality-improvement-coach.md +63 -0
- package/agents/succ-readiness-improver.md +62 -0
- package/agents/succ-session-handoff-orchestrator.md +54 -0
- package/agents/succ-session-reviewer.md +46 -0
- package/agents/succ-style-tracker.md +73 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +749 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents-md.d.ts +16 -0
- package/dist/commands/agents-md.d.ts.map +1 -0
- package/dist/commands/agents-md.js +33 -0
- package/dist/commands/agents-md.js.map +1 -0
- package/dist/commands/analyze-agents.d.ts +20 -0
- package/dist/commands/analyze-agents.d.ts.map +1 -0
- package/dist/commands/analyze-agents.js +305 -0
- package/dist/commands/analyze-agents.js.map +1 -0
- package/dist/commands/analyze-helpers.d.ts +22 -0
- package/dist/commands/analyze-helpers.d.ts.map +1 -0
- package/dist/commands/analyze-helpers.js +38 -0
- package/dist/commands/analyze-helpers.js.map +1 -0
- package/dist/commands/analyze-profile.d.ts +53 -0
- package/dist/commands/analyze-profile.d.ts.map +1 -0
- package/dist/commands/analyze-profile.js +638 -0
- package/dist/commands/analyze-profile.js.map +1 -0
- package/dist/commands/analyze-recursive.d.ts +20 -0
- package/dist/commands/analyze-recursive.d.ts.map +1 -0
- package/dist/commands/analyze-recursive.js +326 -0
- package/dist/commands/analyze-recursive.js.map +1 -0
- package/dist/commands/analyze-utils.d.ts +83 -0
- package/dist/commands/analyze-utils.d.ts.map +1 -0
- package/dist/commands/analyze-utils.js +541 -0
- package/dist/commands/analyze-utils.js.map +1 -0
- package/dist/commands/analyze.d.ts +15 -0
- package/dist/commands/analyze.d.ts.map +1 -0
- package/dist/commands/analyze.js +265 -0
- package/dist/commands/analyze.js.map +1 -0
- package/dist/commands/backfill.d.ts +22 -0
- package/dist/commands/backfill.d.ts.map +1 -0
- package/dist/commands/backfill.js +62 -0
- package/dist/commands/backfill.js.map +1 -0
- package/dist/commands/benchmark-quality.d.ts +18 -0
- package/dist/commands/benchmark-quality.d.ts.map +1 -0
- package/dist/commands/benchmark-quality.js +316 -0
- package/dist/commands/benchmark-quality.js.map +1 -0
- package/dist/commands/benchmark-sqlite-vec.d.ts +12 -0
- package/dist/commands/benchmark-sqlite-vec.d.ts.map +1 -0
- package/dist/commands/benchmark-sqlite-vec.js +281 -0
- package/dist/commands/benchmark-sqlite-vec.js.map +1 -0
- package/dist/commands/benchmark.d.ts +57 -0
- package/dist/commands/benchmark.d.ts.map +1 -0
- package/dist/commands/benchmark.js +682 -0
- package/dist/commands/benchmark.js.map +1 -0
- package/dist/commands/chat.d.ts +14 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +84 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/checkpoint.d.ts +27 -0
- package/dist/commands/checkpoint.d.ts.map +1 -0
- package/dist/commands/checkpoint.js +181 -0
- package/dist/commands/checkpoint.js.map +1 -0
- package/dist/commands/clear.d.ts +9 -0
- package/dist/commands/clear.d.ts.map +1 -0
- package/dist/commands/clear.js +31 -0
- package/dist/commands/clear.js.map +1 -0
- package/dist/commands/config.d.ts +26 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +247 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/consolidate.d.ts +21 -0
- package/dist/commands/consolidate.d.ts.map +1 -0
- package/dist/commands/consolidate.js +117 -0
- package/dist/commands/consolidate.js.map +1 -0
- package/dist/commands/daemon.d.ts +48 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +218 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/embedding.d.ts +26 -0
- package/dist/commands/embedding.d.ts.map +1 -0
- package/dist/commands/embedding.js +168 -0
- package/dist/commands/embedding.js.map +1 -0
- package/dist/commands/graph.d.ts +20 -0
- package/dist/commands/graph.d.ts.map +1 -0
- package/dist/commands/graph.js +128 -0
- package/dist/commands/graph.js.map +1 -0
- package/dist/commands/index-code.d.ts +23 -0
- package/dist/commands/index-code.d.ts.map +1 -0
- package/dist/commands/index-code.js +218 -0
- package/dist/commands/index-code.js.map +1 -0
- package/dist/commands/index.d.ts +23 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +217 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init-templates.d.ts +21 -0
- package/dist/commands/init-templates.d.ts.map +1 -0
- package/dist/commands/init-templates.js +487 -0
- package/dist/commands/init-templates.js.map +1 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +865 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/memories.d.ts +47 -0
- package/dist/commands/memories.d.ts.map +1 -0
- package/dist/commands/memories.js +597 -0
- package/dist/commands/memories.js.map +1 -0
- package/dist/commands/migrate.d.ts +19 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +154 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/prd.d.ts +46 -0
- package/dist/commands/prd.d.ts.map +1 -0
- package/dist/commands/prd.js +378 -0
- package/dist/commands/prd.js.map +1 -0
- package/dist/commands/precompute-context.d.ts +11 -0
- package/dist/commands/precompute-context.d.ts.map +1 -0
- package/dist/commands/precompute-context.js +12 -0
- package/dist/commands/precompute-context.js.map +1 -0
- package/dist/commands/progress.d.ts +16 -0
- package/dist/commands/progress.d.ts.map +1 -0
- package/dist/commands/progress.js +38 -0
- package/dist/commands/progress.js.map +1 -0
- package/dist/commands/reindex.d.ts +17 -0
- package/dist/commands/reindex.d.ts.map +1 -0
- package/dist/commands/reindex.js +83 -0
- package/dist/commands/reindex.js.map +1 -0
- package/dist/commands/retention.d.ts +19 -0
- package/dist/commands/retention.d.ts.map +1 -0
- package/dist/commands/retention.js +162 -0
- package/dist/commands/retention.js.map +1 -0
- package/dist/commands/score.d.ts +10 -0
- package/dist/commands/score.d.ts.map +1 -0
- package/dist/commands/score.js +28 -0
- package/dist/commands/score.js.map +1 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +89 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/session-summary.d.ts +15 -0
- package/dist/commands/session-summary.d.ts.map +1 -0
- package/dist/commands/session-summary.js +16 -0
- package/dist/commands/session-summary.js.map +1 -0
- package/dist/commands/setup.d.ts +6 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +222 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/soul.d.ts +9 -0
- package/dist/commands/soul.d.ts.map +1 -0
- package/dist/commands/soul.js +256 -0
- package/dist/commands/soul.js.map +1 -0
- package/dist/commands/stats.d.ts +18 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +138 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +145 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/train-bpe.d.ts +8 -0
- package/dist/commands/train-bpe.d.ts.map +1 -0
- package/dist/commands/train-bpe.js +35 -0
- package/dist/commands/train-bpe.js.map +1 -0
- package/dist/commands/watch.d.ts +22 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +171 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/daemon/analyzer.d.ts +54 -0
- package/dist/daemon/analyzer.d.ts.map +1 -0
- package/dist/daemon/analyzer.js +362 -0
- package/dist/daemon/analyzer.js.map +1 -0
- package/dist/daemon/client.d.ts +87 -0
- package/dist/daemon/client.d.ts.map +1 -0
- package/dist/daemon/client.js +356 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/index.d.ts +12 -0
- package/dist/daemon/index.d.ts.map +1 -0
- package/dist/daemon/index.js +12 -0
- package/dist/daemon/index.js.map +1 -0
- package/dist/daemon/service.d.ts +51 -0
- package/dist/daemon/service.d.ts.map +1 -0
- package/dist/daemon/service.js +1203 -0
- package/dist/daemon/service.js.map +1 -0
- package/dist/daemon/session-processor.d.ts +85 -0
- package/dist/daemon/session-processor.d.ts.map +1 -0
- package/dist/daemon/session-processor.js +571 -0
- package/dist/daemon/session-processor.js.map +1 -0
- package/dist/daemon/sessions.d.ts +62 -0
- package/dist/daemon/sessions.d.ts.map +1 -0
- package/dist/daemon/sessions.js +192 -0
- package/dist/daemon/sessions.js.map +1 -0
- package/dist/daemon/watcher.d.ts +52 -0
- package/dist/daemon/watcher.d.ts.map +1 -0
- package/dist/daemon/watcher.js +363 -0
- package/dist/daemon/watcher.js.map +1 -0
- package/dist/lib/agents-md-generator.d.ts +33 -0
- package/dist/lib/agents-md-generator.d.ts.map +1 -0
- package/dist/lib/agents-md-generator.js +156 -0
- package/dist/lib/agents-md-generator.js.map +1 -0
- package/dist/lib/ai-readiness.d.ts +132 -0
- package/dist/lib/ai-readiness.d.ts.map +1 -0
- package/dist/lib/ai-readiness.js +702 -0
- package/dist/lib/ai-readiness.js.map +1 -0
- package/dist/lib/analyze-state.d.ts +34 -0
- package/dist/lib/analyze-state.d.ts.map +1 -0
- package/dist/lib/analyze-state.js +106 -0
- package/dist/lib/analyze-state.js.map +1 -0
- package/dist/lib/benchmark.d.ts +250 -0
- package/dist/lib/benchmark.d.ts.map +1 -0
- package/dist/lib/benchmark.js +778 -0
- package/dist/lib/benchmark.js.map +1 -0
- package/dist/lib/bm25.d.ts +114 -0
- package/dist/lib/bm25.d.ts.map +1 -0
- package/dist/lib/bm25.js +727 -0
- package/dist/lib/bm25.js.map +1 -0
- package/dist/lib/bpe.d.ts +70 -0
- package/dist/lib/bpe.d.ts.map +1 -0
- package/dist/lib/bpe.js +270 -0
- package/dist/lib/bpe.js.map +1 -0
- package/dist/lib/checkpoint.d.ts +124 -0
- package/dist/lib/checkpoint.d.ts.map +1 -0
- package/dist/lib/checkpoint.js +321 -0
- package/dist/lib/checkpoint.js.map +1 -0
- package/dist/lib/chunker.d.ts +47 -0
- package/dist/lib/chunker.d.ts.map +1 -0
- package/dist/lib/chunker.js +358 -0
- package/dist/lib/chunker.js.map +1 -0
- package/dist/lib/claude-ws-transport.d.ts +76 -0
- package/dist/lib/claude-ws-transport.d.ts.map +1 -0
- package/dist/lib/claude-ws-transport.js +487 -0
- package/dist/lib/claude-ws-transport.js.map +1 -0
- package/dist/lib/compact-briefing.d.ts +22 -0
- package/dist/lib/compact-briefing.d.ts.map +1 -0
- package/dist/lib/compact-briefing.js +180 -0
- package/dist/lib/compact-briefing.js.map +1 -0
- package/dist/lib/config-defaults.d.ts +41 -0
- package/dist/lib/config-defaults.d.ts.map +1 -0
- package/dist/lib/config-defaults.js +99 -0
- package/dist/lib/config-defaults.js.map +1 -0
- package/dist/lib/config-display.d.ts +16 -0
- package/dist/lib/config-display.d.ts.map +1 -0
- package/dist/lib/config-display.js +408 -0
- package/dist/lib/config-display.js.map +1 -0
- package/dist/lib/config-types.d.ts +601 -0
- package/dist/lib/config-types.d.ts.map +1 -0
- package/dist/lib/config-types.js +7 -0
- package/dist/lib/config-types.js.map +1 -0
- package/dist/lib/config-validation.d.ts +19 -0
- package/dist/lib/config-validation.d.ts.map +1 -0
- package/dist/lib/config-validation.js +136 -0
- package/dist/lib/config-validation.js.map +1 -0
- package/dist/lib/config.d.ts +143 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +689 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/consolidate.d.ts +115 -0
- package/dist/lib/consolidate.d.ts.map +1 -0
- package/dist/lib/consolidate.js +600 -0
- package/dist/lib/consolidate.js.map +1 -0
- package/dist/lib/db/bm25-indexes.d.ts +58 -0
- package/dist/lib/db/bm25-indexes.d.ts.map +1 -0
- package/dist/lib/db/bm25-indexes.js +333 -0
- package/dist/lib/db/bm25-indexes.js.map +1 -0
- package/dist/lib/db/bpe.d.ts +18 -0
- package/dist/lib/db/bpe.d.ts.map +1 -0
- package/dist/lib/db/bpe.js +44 -0
- package/dist/lib/db/bpe.js.map +1 -0
- package/dist/lib/db/connection.d.ts +47 -0
- package/dist/lib/db/connection.d.ts.map +1 -0
- package/dist/lib/db/connection.js +114 -0
- package/dist/lib/db/connection.js.map +1 -0
- package/dist/lib/db/documents.d.ts +58 -0
- package/dist/lib/db/documents.d.ts.map +1 -0
- package/dist/lib/db/documents.js +374 -0
- package/dist/lib/db/documents.js.map +1 -0
- package/dist/lib/db/file-hash.d.ts +26 -0
- package/dist/lib/db/file-hash.d.ts.map +1 -0
- package/dist/lib/db/file-hash.js +56 -0
- package/dist/lib/db/file-hash.js.map +1 -0
- package/dist/lib/db/global-memories.d.ts +62 -0
- package/dist/lib/db/global-memories.d.ts.map +1 -0
- package/dist/lib/db/global-memories.js +173 -0
- package/dist/lib/db/global-memories.js.map +1 -0
- package/dist/lib/db/graph.d.ts +163 -0
- package/dist/lib/db/graph.d.ts.map +1 -0
- package/dist/lib/db/graph.js +488 -0
- package/dist/lib/db/graph.js.map +1 -0
- package/dist/lib/db/helpers.d.ts +13 -0
- package/dist/lib/db/helpers.d.ts.map +1 -0
- package/dist/lib/db/helpers.js +21 -0
- package/dist/lib/db/helpers.js.map +1 -0
- package/dist/lib/db/hybrid-search.d.ts +64 -0
- package/dist/lib/db/hybrid-search.d.ts.map +1 -0
- package/dist/lib/db/hybrid-search.js +549 -0
- package/dist/lib/db/hybrid-search.js.map +1 -0
- package/dist/lib/db/index.d.ts +23 -0
- package/dist/lib/db/index.d.ts.map +1 -0
- package/dist/lib/db/index.js +23 -0
- package/dist/lib/db/index.js.map +1 -0
- package/dist/lib/db/memories.d.ts +174 -0
- package/dist/lib/db/memories.d.ts.map +1 -0
- package/dist/lib/db/memories.js +866 -0
- package/dist/lib/db/memories.js.map +1 -0
- package/dist/lib/db/retention.d.ts +64 -0
- package/dist/lib/db/retention.d.ts.map +1 -0
- package/dist/lib/db/retention.js +115 -0
- package/dist/lib/db/retention.js.map +1 -0
- package/dist/lib/db/schema.d.ts +29 -0
- package/dist/lib/db/schema.d.ts.map +1 -0
- package/dist/lib/db/schema.js +832 -0
- package/dist/lib/db/schema.js.map +1 -0
- package/dist/lib/db/skills.d.ts +65 -0
- package/dist/lib/db/skills.d.ts.map +1 -0
- package/dist/lib/db/skills.js +119 -0
- package/dist/lib/db/skills.js.map +1 -0
- package/dist/lib/db/token-frequency.d.ts +34 -0
- package/dist/lib/db/token-frequency.d.ts.map +1 -0
- package/dist/lib/db/token-frequency.js +92 -0
- package/dist/lib/db/token-frequency.js.map +1 -0
- package/dist/lib/db/token-stats.d.ts +43 -0
- package/dist/lib/db/token-stats.d.ts.map +1 -0
- package/dist/lib/db/token-stats.js +59 -0
- package/dist/lib/db/token-stats.js.map +1 -0
- package/dist/lib/db/types.d.ts +57 -0
- package/dist/lib/db/types.d.ts.map +1 -0
- package/dist/lib/db/types.js +5 -0
- package/dist/lib/db/types.js.map +1 -0
- package/dist/lib/db/web-search-history.d.ts +22 -0
- package/dist/lib/db/web-search-history.d.ts.map +1 -0
- package/dist/lib/db/web-search-history.js +107 -0
- package/dist/lib/db/web-search-history.js.map +1 -0
- package/dist/lib/debug/state.d.ts +17 -0
- package/dist/lib/debug/state.d.ts.map +1 -0
- package/dist/lib/debug/state.js +131 -0
- package/dist/lib/debug/state.js.map +1 -0
- package/dist/lib/debug/types.d.ts +74 -0
- package/dist/lib/debug/types.d.ts.map +1 -0
- package/dist/lib/debug/types.js +85 -0
- package/dist/lib/debug/types.js.map +1 -0
- package/dist/lib/embedding-pool.d.ts +37 -0
- package/dist/lib/embedding-pool.d.ts.map +1 -0
- package/dist/lib/embedding-pool.js +159 -0
- package/dist/lib/embedding-pool.js.map +1 -0
- package/dist/lib/embedding-worker.d.ts +7 -0
- package/dist/lib/embedding-worker.d.ts.map +1 -0
- package/dist/lib/embedding-worker.js +49 -0
- package/dist/lib/embedding-worker.js.map +1 -0
- package/dist/lib/embeddings.d.ts +54 -0
- package/dist/lib/embeddings.d.ts.map +1 -0
- package/dist/lib/embeddings.js +437 -0
- package/dist/lib/embeddings.js.map +1 -0
- package/dist/lib/errors.d.ts +40 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +66 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/fault-logger.d.ts +35 -0
- package/dist/lib/fault-logger.d.ts.map +1 -0
- package/dist/lib/fault-logger.js +182 -0
- package/dist/lib/fault-logger.js.map +1 -0
- package/dist/lib/graph/centrality.d.ts +34 -0
- package/dist/lib/graph/centrality.d.ts.map +1 -0
- package/dist/lib/graph/centrality.js +76 -0
- package/dist/lib/graph/centrality.js.map +1 -0
- package/dist/lib/graph/cleanup.d.ts +36 -0
- package/dist/lib/graph/cleanup.d.ts.map +1 -0
- package/dist/lib/graph/cleanup.js +96 -0
- package/dist/lib/graph/cleanup.js.map +1 -0
- package/dist/lib/graph/community-detection.d.ts +61 -0
- package/dist/lib/graph/community-detection.d.ts.map +1 -0
- package/dist/lib/graph/community-detection.js +218 -0
- package/dist/lib/graph/community-detection.js.map +1 -0
- package/dist/lib/graph/contextual-proximity.d.ts +41 -0
- package/dist/lib/graph/contextual-proximity.d.ts.map +1 -0
- package/dist/lib/graph/contextual-proximity.js +125 -0
- package/dist/lib/graph/contextual-proximity.js.map +1 -0
- package/dist/lib/graph/llm-relations.d.ts +54 -0
- package/dist/lib/graph/llm-relations.d.ts.map +1 -0
- package/dist/lib/graph/llm-relations.js +265 -0
- package/dist/lib/graph/llm-relations.js.map +1 -0
- package/dist/lib/graph-export.d.ts +13 -0
- package/dist/lib/graph-export.d.ts.map +1 -0
- package/dist/lib/graph-export.js +637 -0
- package/dist/lib/graph-export.js.map +1 -0
- package/dist/lib/graph-scheduler.d.ts +7 -0
- package/dist/lib/graph-scheduler.d.ts.map +1 -0
- package/dist/lib/graph-scheduler.js +21 -0
- package/dist/lib/graph-scheduler.js.map +1 -0
- package/dist/lib/indexer.d.ts +36 -0
- package/dist/lib/indexer.d.ts.map +1 -0
- package/dist/lib/indexer.js +226 -0
- package/dist/lib/indexer.js.map +1 -0
- package/dist/lib/learning-delta.d.ts +35 -0
- package/dist/lib/learning-delta.d.ts.map +1 -0
- package/dist/lib/learning-delta.js +53 -0
- package/dist/lib/learning-delta.js.map +1 -0
- package/dist/lib/llm.d.ts +118 -0
- package/dist/lib/llm.d.ts.map +1 -0
- package/dist/lib/llm.js +439 -0
- package/dist/lib/llm.js.map +1 -0
- package/dist/lib/lock.d.ts +27 -0
- package/dist/lib/lock.d.ts.map +1 -0
- package/dist/lib/lock.js +143 -0
- package/dist/lib/lock.js.map +1 -0
- package/dist/lib/md-fetch.d.ts +61 -0
- package/dist/lib/md-fetch.d.ts.map +1 -0
- package/dist/lib/md-fetch.js +141 -0
- package/dist/lib/md-fetch.js.map +1 -0
- package/dist/lib/mmr.d.ts +29 -0
- package/dist/lib/mmr.d.ts.map +1 -0
- package/dist/lib/mmr.js +89 -0
- package/dist/lib/mmr.js.map +1 -0
- package/dist/lib/onboarding/ai-chat.d.ts +11 -0
- package/dist/lib/onboarding/ai-chat.d.ts.map +1 -0
- package/dist/lib/onboarding/ai-chat.js +85 -0
- package/dist/lib/onboarding/ai-chat.js.map +1 -0
- package/dist/lib/onboarding/index.d.ts +7 -0
- package/dist/lib/onboarding/index.d.ts.map +1 -0
- package/dist/lib/onboarding/index.js +7 -0
- package/dist/lib/onboarding/index.js.map +1 -0
- package/dist/lib/onboarding/wizard.d.ts +11 -0
- package/dist/lib/onboarding/wizard.d.ts.map +1 -0
- package/dist/lib/onboarding/wizard.js +104 -0
- package/dist/lib/onboarding/wizard.js.map +1 -0
- package/dist/lib/ort-provider.d.ts +27 -0
- package/dist/lib/ort-provider.d.ts.map +1 -0
- package/dist/lib/ort-provider.js +83 -0
- package/dist/lib/ort-provider.js.map +1 -0
- package/dist/lib/ort-session.d.ts +32 -0
- package/dist/lib/ort-session.d.ts.map +1 -0
- package/dist/lib/ort-session.js +227 -0
- package/dist/lib/ort-session.js.map +1 -0
- package/dist/lib/patterns.d.ts +43 -0
- package/dist/lib/patterns.d.ts.map +1 -0
- package/dist/lib/patterns.js +395 -0
- package/dist/lib/patterns.js.map +1 -0
- package/dist/lib/prd/codebase-context.d.ts +27 -0
- package/dist/lib/prd/codebase-context.d.ts.map +1 -0
- package/dist/lib/prd/codebase-context.js +420 -0
- package/dist/lib/prd/codebase-context.js.map +1 -0
- package/dist/lib/prd/context.d.ts +24 -0
- package/dist/lib/prd/context.d.ts.map +1 -0
- package/dist/lib/prd/context.js +68 -0
- package/dist/lib/prd/context.js.map +1 -0
- package/dist/lib/prd/executor.d.ts +52 -0
- package/dist/lib/prd/executor.d.ts.map +1 -0
- package/dist/lib/prd/executor.js +154 -0
- package/dist/lib/prd/executor.js.map +1 -0
- package/dist/lib/prd/export.d.ts +40 -0
- package/dist/lib/prd/export.d.ts.map +1 -0
- package/dist/lib/prd/export.js +511 -0
- package/dist/lib/prd/export.js.map +1 -0
- package/dist/lib/prd/gates.d.ts +30 -0
- package/dist/lib/prd/gates.d.ts.map +1 -0
- package/dist/lib/prd/gates.js +100 -0
- package/dist/lib/prd/gates.js.map +1 -0
- package/dist/lib/prd/generate.d.ts +56 -0
- package/dist/lib/prd/generate.d.ts.map +1 -0
- package/dist/lib/prd/generate.js +357 -0
- package/dist/lib/prd/generate.js.map +1 -0
- package/dist/lib/prd/parse.d.ts +21 -0
- package/dist/lib/prd/parse.d.ts.map +1 -0
- package/dist/lib/prd/parse.js +270 -0
- package/dist/lib/prd/parse.js.map +1 -0
- package/dist/lib/prd/prompt-builder.d.ts +18 -0
- package/dist/lib/prd/prompt-builder.d.ts.map +1 -0
- package/dist/lib/prd/prompt-builder.js +61 -0
- package/dist/lib/prd/prompt-builder.js.map +1 -0
- package/dist/lib/prd/runner.d.ts +54 -0
- package/dist/lib/prd/runner.d.ts.map +1 -0
- package/dist/lib/prd/runner.js +563 -0
- package/dist/lib/prd/runner.js.map +1 -0
- package/dist/lib/prd/scheduler.d.ts +27 -0
- package/dist/lib/prd/scheduler.d.ts.map +1 -0
- package/dist/lib/prd/scheduler.js +113 -0
- package/dist/lib/prd/scheduler.js.map +1 -0
- package/dist/lib/prd/state.d.ts +77 -0
- package/dist/lib/prd/state.d.ts.map +1 -0
- package/dist/lib/prd/state.js +253 -0
- package/dist/lib/prd/state.js.map +1 -0
- package/dist/lib/prd/team-runner.d.ts +48 -0
- package/dist/lib/prd/team-runner.d.ts.map +1 -0
- package/dist/lib/prd/team-runner.js +261 -0
- package/dist/lib/prd/team-runner.js.map +1 -0
- package/dist/lib/prd/types.d.ts +169 -0
- package/dist/lib/prd/types.d.ts.map +1 -0
- package/dist/lib/prd/types.js +143 -0
- package/dist/lib/prd/types.js.map +1 -0
- package/dist/lib/prd/worktree.d.ts +54 -0
- package/dist/lib/prd/worktree.d.ts.map +1 -0
- package/dist/lib/prd/worktree.js +255 -0
- package/dist/lib/prd/worktree.js.map +1 -0
- package/dist/lib/precompute-context.d.ts +48 -0
- package/dist/lib/precompute-context.d.ts.map +1 -0
- package/dist/lib/precompute-context.js +265 -0
- package/dist/lib/precompute-context.js.map +1 -0
- package/dist/lib/pricing.d.ts +60 -0
- package/dist/lib/pricing.d.ts.map +1 -0
- package/dist/lib/pricing.js +258 -0
- package/dist/lib/pricing.js.map +1 -0
- package/dist/lib/process-registry.d.ts +30 -0
- package/dist/lib/process-registry.d.ts.map +1 -0
- package/dist/lib/process-registry.js +92 -0
- package/dist/lib/process-registry.js.map +1 -0
- package/dist/lib/progress-log.d.ts +44 -0
- package/dist/lib/progress-log.d.ts.map +1 -0
- package/dist/lib/progress-log.js +58 -0
- package/dist/lib/progress-log.js.map +1 -0
- package/dist/lib/public-api.d.ts +56 -0
- package/dist/lib/public-api.d.ts.map +1 -0
- package/dist/lib/public-api.js +63 -0
- package/dist/lib/public-api.js.map +1 -0
- package/dist/lib/quality.d.ts +66 -0
- package/dist/lib/quality.d.ts.map +1 -0
- package/dist/lib/quality.js +486 -0
- package/dist/lib/quality.js.map +1 -0
- package/dist/lib/query-expansion.d.ts +16 -0
- package/dist/lib/query-expansion.d.ts.map +1 -0
- package/dist/lib/query-expansion.js +53 -0
- package/dist/lib/query-expansion.js.map +1 -0
- package/dist/lib/readiness.d.ts +35 -0
- package/dist/lib/readiness.d.ts.map +1 -0
- package/dist/lib/readiness.js +142 -0
- package/dist/lib/readiness.js.map +1 -0
- package/dist/lib/reference-embeddings.d.ts +39 -0
- package/dist/lib/reference-embeddings.d.ts.map +1 -0
- package/dist/lib/reference-embeddings.js +95 -0
- package/dist/lib/reference-embeddings.js.map +1 -0
- package/dist/lib/reflection-synthesizer.d.ts +27 -0
- package/dist/lib/reflection-synthesizer.d.ts.map +1 -0
- package/dist/lib/reflection-synthesizer.js +149 -0
- package/dist/lib/reflection-synthesizer.js.map +1 -0
- package/dist/lib/retention.d.ts +105 -0
- package/dist/lib/retention.d.ts.map +1 -0
- package/dist/lib/retention.js +246 -0
- package/dist/lib/retention.js.map +1 -0
- package/dist/lib/sensitive-filter.d.ts +34 -0
- package/dist/lib/sensitive-filter.d.ts.map +1 -0
- package/dist/lib/sensitive-filter.js +344 -0
- package/dist/lib/sensitive-filter.js.map +1 -0
- package/dist/lib/session-observations.d.ts +59 -0
- package/dist/lib/session-observations.d.ts.map +1 -0
- package/dist/lib/session-observations.js +128 -0
- package/dist/lib/session-observations.js.map +1 -0
- package/dist/lib/session-summary.d.ts +65 -0
- package/dist/lib/session-summary.d.ts.map +1 -0
- package/dist/lib/session-summary.js +344 -0
- package/dist/lib/session-summary.js.map +1 -0
- package/dist/lib/similarity-worker.d.ts +7 -0
- package/dist/lib/similarity-worker.d.ts.map +1 -0
- package/dist/lib/similarity-worker.js +36 -0
- package/dist/lib/similarity-worker.js.map +1 -0
- package/dist/lib/skills.d.ts +78 -0
- package/dist/lib/skills.d.ts.map +1 -0
- package/dist/lib/skills.js +439 -0
- package/dist/lib/skills.js.map +1 -0
- package/dist/lib/skyll-client.d.ts +59 -0
- package/dist/lib/skyll-client.d.ts.map +1 -0
- package/dist/lib/skyll-client.js +257 -0
- package/dist/lib/skyll-client.js.map +1 -0
- package/dist/lib/storage/backends/interface.d.ts +383 -0
- package/dist/lib/storage/backends/interface.d.ts.map +1 -0
- package/dist/lib/storage/backends/interface.js +12 -0
- package/dist/lib/storage/backends/interface.js.map +1 -0
- package/dist/lib/storage/backends/postgresql.d.ts +454 -0
- package/dist/lib/storage/backends/postgresql.d.ts.map +1 -0
- package/dist/lib/storage/backends/postgresql.js +2528 -0
- package/dist/lib/storage/backends/postgresql.js.map +1 -0
- package/dist/lib/storage/benchmark.d.ts +16 -0
- package/dist/lib/storage/benchmark.d.ts.map +1 -0
- package/dist/lib/storage/benchmark.js +219 -0
- package/dist/lib/storage/benchmark.js.map +1 -0
- package/dist/lib/storage/dispatcher-export.d.ts +108 -0
- package/dist/lib/storage/dispatcher-export.d.ts.map +1 -0
- package/dist/lib/storage/dispatcher-export.js +593 -0
- package/dist/lib/storage/dispatcher-export.js.map +1 -0
- package/dist/lib/storage/dispatcher.d.ts +468 -0
- package/dist/lib/storage/dispatcher.d.ts.map +1 -0
- package/dist/lib/storage/dispatcher.js +1926 -0
- package/dist/lib/storage/dispatcher.js.map +1 -0
- package/dist/lib/storage/index.d.ts +481 -0
- package/dist/lib/storage/index.d.ts.map +1 -0
- package/dist/lib/storage/index.js +727 -0
- package/dist/lib/storage/index.js.map +1 -0
- package/dist/lib/storage/migration/export-import.d.ts +133 -0
- package/dist/lib/storage/migration/export-import.d.ts.map +1 -0
- package/dist/lib/storage/migration/export-import.js +264 -0
- package/dist/lib/storage/migration/export-import.js.map +1 -0
- package/dist/lib/storage/types.d.ts +313 -0
- package/dist/lib/storage/types.d.ts.map +1 -0
- package/dist/lib/storage/types.js +30 -0
- package/dist/lib/storage/types.js.map +1 -0
- package/dist/lib/storage/vector/interface.d.ts +89 -0
- package/dist/lib/storage/vector/interface.d.ts.map +1 -0
- package/dist/lib/storage/vector/interface.js +10 -0
- package/dist/lib/storage/vector/interface.js.map +1 -0
- package/dist/lib/storage/vector/qdrant.d.ts +221 -0
- package/dist/lib/storage/vector/qdrant.d.ts.map +1 -0
- package/dist/lib/storage/vector/qdrant.js +880 -0
- package/dist/lib/storage/vector/qdrant.js.map +1 -0
- package/dist/lib/supersession.d.ts +26 -0
- package/dist/lib/supersession.d.ts.map +1 -0
- package/dist/lib/supersession.js +97 -0
- package/dist/lib/supersession.js.map +1 -0
- package/dist/lib/temporal.d.ts +140 -0
- package/dist/lib/temporal.d.ts.map +1 -0
- package/dist/lib/temporal.js +303 -0
- package/dist/lib/temporal.js.map +1 -0
- package/dist/lib/token-budget.d.ts +79 -0
- package/dist/lib/token-budget.d.ts.map +1 -0
- package/dist/lib/token-budget.js +146 -0
- package/dist/lib/token-budget.js.map +1 -0
- package/dist/lib/token-counter.d.ts +27 -0
- package/dist/lib/token-counter.d.ts.map +1 -0
- package/dist/lib/token-counter.js +52 -0
- package/dist/lib/token-counter.js.map +1 -0
- package/dist/lib/tree-sitter/chunker-ts.d.ts +24 -0
- package/dist/lib/tree-sitter/chunker-ts.d.ts.map +1 -0
- package/dist/lib/tree-sitter/chunker-ts.js +206 -0
- package/dist/lib/tree-sitter/chunker-ts.js.map +1 -0
- package/dist/lib/tree-sitter/extractor.d.ts +43 -0
- package/dist/lib/tree-sitter/extractor.d.ts.map +1 -0
- package/dist/lib/tree-sitter/extractor.js +297 -0
- package/dist/lib/tree-sitter/extractor.js.map +1 -0
- package/dist/lib/tree-sitter/index.d.ts +13 -0
- package/dist/lib/tree-sitter/index.d.ts.map +1 -0
- package/dist/lib/tree-sitter/index.js +14 -0
- package/dist/lib/tree-sitter/index.js.map +1 -0
- package/dist/lib/tree-sitter/parser.d.ts +70 -0
- package/dist/lib/tree-sitter/parser.d.ts.map +1 -0
- package/dist/lib/tree-sitter/parser.js +354 -0
- package/dist/lib/tree-sitter/parser.js.map +1 -0
- package/dist/lib/tree-sitter/public.d.ts +28 -0
- package/dist/lib/tree-sitter/public.d.ts.map +1 -0
- package/dist/lib/tree-sitter/public.js +50 -0
- package/dist/lib/tree-sitter/public.js.map +1 -0
- package/dist/lib/tree-sitter/queries.d.ts +28 -0
- package/dist/lib/tree-sitter/queries.d.ts.map +1 -0
- package/dist/lib/tree-sitter/queries.js +383 -0
- package/dist/lib/tree-sitter/queries.js.map +1 -0
- package/dist/lib/tree-sitter/types.d.ts +69 -0
- package/dist/lib/tree-sitter/types.d.ts.map +1 -0
- package/dist/lib/tree-sitter/types.js +131 -0
- package/dist/lib/tree-sitter/types.js.map +1 -0
- package/dist/lib/working-memory-pipeline.d.ts +118 -0
- package/dist/lib/working-memory-pipeline.d.ts.map +1 -0
- package/dist/lib/working-memory-pipeline.js +344 -0
- package/dist/lib/working-memory-pipeline.js.map +1 -0
- package/dist/mcp/helpers.d.ts +44 -0
- package/dist/mcp/helpers.d.ts.map +1 -0
- package/dist/mcp/helpers.js +244 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/index.d.ts +10 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +15 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/resources.d.ts +12 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +136 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +131 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/config.d.ts +10 -0
- package/dist/mcp/tools/config.d.ts.map +1 -0
- package/dist/mcp/tools/config.js +236 -0
- package/dist/mcp/tools/config.js.map +1 -0
- package/dist/mcp/tools/dead-end.d.ts +9 -0
- package/dist/mcp/tools/dead-end.d.ts.map +1 -0
- package/dist/mcp/tools/dead-end.js +137 -0
- package/dist/mcp/tools/dead-end.js.map +1 -0
- package/dist/mcp/tools/debug.d.ts +9 -0
- package/dist/mcp/tools/debug.d.ts.map +1 -0
- package/dist/mcp/tools/debug.js +387 -0
- package/dist/mcp/tools/debug.js.map +1 -0
- package/dist/mcp/tools/graph.d.ts +9 -0
- package/dist/mcp/tools/graph.d.ts.map +1 -0
- package/dist/mcp/tools/graph.js +337 -0
- package/dist/mcp/tools/graph.js.map +1 -0
- package/dist/mcp/tools/indexing.d.ts +12 -0
- package/dist/mcp/tools/indexing.d.ts.map +1 -0
- package/dist/mcp/tools/indexing.js +289 -0
- package/dist/mcp/tools/indexing.js.map +1 -0
- package/dist/mcp/tools/memory.d.ts +14 -0
- package/dist/mcp/tools/memory.d.ts.map +1 -0
- package/dist/mcp/tools/memory.js +1170 -0
- package/dist/mcp/tools/memory.js.map +1 -0
- package/dist/mcp/tools/prd.d.ts +12 -0
- package/dist/mcp/tools/prd.d.ts.map +1 -0
- package/dist/mcp/tools/prd.js +276 -0
- package/dist/mcp/tools/prd.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +9 -0
- package/dist/mcp/tools/search.d.ts.map +1 -0
- package/dist/mcp/tools/search.js +279 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/status.d.ts +10 -0
- package/dist/mcp/tools/status.d.ts.map +1 -0
- package/dist/mcp/tools/status.js +296 -0
- package/dist/mcp/tools/status.js.map +1 -0
- package/dist/mcp/tools/web-fetch.d.ts +10 -0
- package/dist/mcp/tools/web-fetch.d.ts.map +1 -0
- package/dist/mcp/tools/web-fetch.js +107 -0
- package/dist/mcp/tools/web-fetch.js.map +1 -0
- package/dist/mcp/tools/web-search.d.ts +14 -0
- package/dist/mcp/tools/web-search.d.ts.map +1 -0
- package/dist/mcp/tools/web-search.js +427 -0
- package/dist/mcp/tools/web-search.js.map +1 -0
- package/dist/mcp/types.d.ts +16 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +5 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/mcp-server.d.ts +9 -0
- package/dist/mcp-server.d.ts.map +1 -0
- package/dist/mcp-server.js +9 -0
- package/dist/mcp-server.js.map +1 -0
- package/dist/prompts/analyze.d.ts +21 -0
- package/dist/prompts/analyze.d.ts.map +1 -0
- package/dist/prompts/analyze.js +27 -0
- package/dist/prompts/analyze.js.map +1 -0
- package/dist/prompts/briefing.d.ts +27 -0
- package/dist/prompts/briefing.d.ts.map +1 -0
- package/dist/prompts/briefing.js +123 -0
- package/dist/prompts/briefing.js.map +1 -0
- package/dist/prompts/chat.d.ts +8 -0
- package/dist/prompts/chat.d.ts.map +1 -0
- package/dist/prompts/chat.js +39 -0
- package/dist/prompts/chat.js.map +1 -0
- package/dist/prompts/daemon.d.ts +16 -0
- package/dist/prompts/daemon.d.ts.map +1 -0
- package/dist/prompts/daemon.js +46 -0
- package/dist/prompts/daemon.js.map +1 -0
- package/dist/prompts/extraction.d.ts +13 -0
- package/dist/prompts/extraction.d.ts.map +1 -0
- package/dist/prompts/extraction.js +93 -0
- package/dist/prompts/extraction.js.map +1 -0
- package/dist/prompts/index.d.ts +17 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +27 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/memory.d.ts +11 -0
- package/dist/prompts/memory.d.ts.map +1 -0
- package/dist/prompts/memory.js +23 -0
- package/dist/prompts/memory.js.map +1 -0
- package/dist/prompts/onboarding.d.ts +16 -0
- package/dist/prompts/onboarding.d.ts.map +1 -0
- package/dist/prompts/onboarding.js +183 -0
- package/dist/prompts/onboarding.js.map +1 -0
- package/dist/prompts/prd.d.ts +12 -0
- package/dist/prompts/prd.d.ts.map +1 -0
- package/dist/prompts/prd.js +211 -0
- package/dist/prompts/prd.js.map +1 -0
- package/dist/prompts/quality.d.ts +11 -0
- package/dist/prompts/quality.d.ts.map +1 -0
- package/dist/prompts/quality.js +11 -0
- package/dist/prompts/quality.js.map +1 -0
- package/dist/prompts/skills.d.ts +16 -0
- package/dist/prompts/skills.d.ts.map +1 -0
- package/dist/prompts/skills.js +30 -0
- package/dist/prompts/skills.js.map +1 -0
- package/hooks/succ-post-tool.cjs +227 -0
- package/hooks/succ-pre-tool.cjs +312 -0
- package/hooks/succ-session-end.cjs +85 -0
- package/hooks/succ-session-start.cjs +618 -0
- package/hooks/succ-stop-reflection.cjs +87 -0
- package/hooks/succ-user-prompt.cjs +220 -0
- package/package.json +128 -0
|
@@ -0,0 +1,1926 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Dispatcher - Routes database operations to the configured backend.
|
|
3
|
+
*
|
|
4
|
+
* This module provides a unified interface that automatically routes database
|
|
5
|
+
* operations to either:
|
|
6
|
+
* - SQLite (default, uses db.ts functions directly)
|
|
7
|
+
* - PostgreSQL (uses backends/postgresql.ts)
|
|
8
|
+
*
|
|
9
|
+
* Additionally supports Qdrant for vector search when configured.
|
|
10
|
+
* When Qdrant is present, ALL search goes through Qdrant hybrid
|
|
11
|
+
* (BM25 + dense + RRF fusion) regardless of SQL backend.
|
|
12
|
+
*
|
|
13
|
+
* 4 supported configurations:
|
|
14
|
+
* - SQLite standalone
|
|
15
|
+
* - SQLite + Qdrant
|
|
16
|
+
* - PostgreSQL standalone
|
|
17
|
+
* - PostgreSQL + Qdrant
|
|
18
|
+
*
|
|
19
|
+
* Usage:
|
|
20
|
+
* import { getStorageDispatcher } from './storage/dispatcher.js';
|
|
21
|
+
* const storage = await getStorageDispatcher();
|
|
22
|
+
* await storage.saveMemory(...);
|
|
23
|
+
*/
|
|
24
|
+
import { getConfig, getProjectRoot } from '../config.js';
|
|
25
|
+
import { logError, logWarn } from '../fault-logger.js';
|
|
26
|
+
// Dispatcher state
|
|
27
|
+
let _backend = 'sqlite';
|
|
28
|
+
let _vectorBackend = 'builtin';
|
|
29
|
+
let _postgresBackend = null;
|
|
30
|
+
let _qdrantStore = null;
|
|
31
|
+
let _initialized = false;
|
|
32
|
+
function getDispatcherStorageConfig() {
|
|
33
|
+
const config = getConfig();
|
|
34
|
+
return {
|
|
35
|
+
backend: config.storage?.backend ?? 'sqlite',
|
|
36
|
+
vector: config.storage?.vector ?? 'builtin',
|
|
37
|
+
sqlite: config.storage?.sqlite,
|
|
38
|
+
postgresql: config.storage?.postgresql,
|
|
39
|
+
qdrant: config.storage?.qdrant,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function initStorageDispatcher() {
|
|
43
|
+
if (_initialized)
|
|
44
|
+
return;
|
|
45
|
+
const config = getDispatcherStorageConfig();
|
|
46
|
+
_backend = config.backend ?? 'sqlite';
|
|
47
|
+
_vectorBackend = config.vector ?? 'builtin';
|
|
48
|
+
const projectId = getProjectRoot().replace(/\\/g, '/').toLowerCase();
|
|
49
|
+
if (_backend === 'postgresql') {
|
|
50
|
+
const { createPostgresBackend } = await import('./backends/postgresql.js');
|
|
51
|
+
_postgresBackend = createPostgresBackend(config);
|
|
52
|
+
_postgresBackend.setProjectId(projectId);
|
|
53
|
+
await _postgresBackend.getDocumentStats();
|
|
54
|
+
}
|
|
55
|
+
if (_vectorBackend === 'qdrant') {
|
|
56
|
+
try {
|
|
57
|
+
const { createQdrantVectorStore } = await import('./vector/qdrant.js');
|
|
58
|
+
_qdrantStore = createQdrantVectorStore(config);
|
|
59
|
+
_qdrantStore.setProjectId(projectId);
|
|
60
|
+
const { getEmbeddingInfo } = await import('../embeddings.js');
|
|
61
|
+
const embDims = getEmbeddingInfo().dimensions ?? 384;
|
|
62
|
+
await _qdrantStore.init(embDims);
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
logError('storage', `Qdrant init failed, falling back to builtin: ${error.message}`, error);
|
|
66
|
+
_qdrantStore = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
_initialized = true;
|
|
70
|
+
_dispatcher = null;
|
|
71
|
+
}
|
|
72
|
+
export function getBackendType() {
|
|
73
|
+
return _backend;
|
|
74
|
+
}
|
|
75
|
+
export function getVectorBackendType() {
|
|
76
|
+
return _vectorBackend;
|
|
77
|
+
}
|
|
78
|
+
export function isPostgresBackend() {
|
|
79
|
+
return _backend === 'postgresql';
|
|
80
|
+
}
|
|
81
|
+
export function isQdrantVectors() {
|
|
82
|
+
return _vectorBackend === 'qdrant';
|
|
83
|
+
}
|
|
84
|
+
export function getPostgresBackend() {
|
|
85
|
+
return _postgresBackend;
|
|
86
|
+
}
|
|
87
|
+
export function getQdrantStore() {
|
|
88
|
+
return _qdrantStore;
|
|
89
|
+
}
|
|
90
|
+
export async function closeStorageDispatcher() {
|
|
91
|
+
if (_postgresBackend) {
|
|
92
|
+
await _postgresBackend.close();
|
|
93
|
+
_postgresBackend = null;
|
|
94
|
+
}
|
|
95
|
+
if (_qdrantStore) {
|
|
96
|
+
await _qdrantStore.close();
|
|
97
|
+
_qdrantStore = null;
|
|
98
|
+
}
|
|
99
|
+
_initialized = false;
|
|
100
|
+
_dispatcher = null;
|
|
101
|
+
}
|
|
102
|
+
// =============================================================================
|
|
103
|
+
// Storage Dispatcher
|
|
104
|
+
// =============================================================================
|
|
105
|
+
export class StorageDispatcher {
|
|
106
|
+
backend;
|
|
107
|
+
vectorBackend;
|
|
108
|
+
postgres;
|
|
109
|
+
qdrant;
|
|
110
|
+
_sqliteFns = null;
|
|
111
|
+
// Learning delta auto-tracking counters
|
|
112
|
+
_sessionCounters = {
|
|
113
|
+
memoriesCreated: 0,
|
|
114
|
+
memoriesDuplicated: 0,
|
|
115
|
+
globalMemoriesCreated: 0,
|
|
116
|
+
recallQueries: 0,
|
|
117
|
+
searchQueries: 0,
|
|
118
|
+
codeSearchQueries: 0,
|
|
119
|
+
webSearchQueries: 0,
|
|
120
|
+
webSearchCostUsd: 0,
|
|
121
|
+
qdrantSyncFailures: 0,
|
|
122
|
+
typesCreated: {},
|
|
123
|
+
startedAt: new Date().toISOString(),
|
|
124
|
+
};
|
|
125
|
+
constructor() {
|
|
126
|
+
this.backend = _backend;
|
|
127
|
+
this.vectorBackend = _vectorBackend;
|
|
128
|
+
this.postgres = _postgresBackend;
|
|
129
|
+
this.qdrant = _qdrantStore;
|
|
130
|
+
}
|
|
131
|
+
/** Log Qdrant failure and increment counter. Qdrant is optional — errors never throw. */
|
|
132
|
+
_warnQdrantFailure(operation, error) {
|
|
133
|
+
this._sessionCounters.qdrantSyncFailures++;
|
|
134
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
135
|
+
logError('storage', `Qdrant ${operation} failed: ${msg}`);
|
|
136
|
+
}
|
|
137
|
+
/** Get current session counters (non-destructive read) */
|
|
138
|
+
getSessionCounters() {
|
|
139
|
+
return { ...this._sessionCounters };
|
|
140
|
+
}
|
|
141
|
+
/** Flush session counters to learning_deltas table and reset */
|
|
142
|
+
async flushSessionCounters(source) {
|
|
143
|
+
const c = this._sessionCounters;
|
|
144
|
+
const totalCreated = c.memoriesCreated + c.globalMemoriesCreated;
|
|
145
|
+
if (totalCreated === 0 &&
|
|
146
|
+
c.recallQueries === 0 &&
|
|
147
|
+
c.searchQueries === 0 &&
|
|
148
|
+
c.codeSearchQueries === 0 &&
|
|
149
|
+
c.webSearchQueries === 0)
|
|
150
|
+
return;
|
|
151
|
+
try {
|
|
152
|
+
const stats = await this.getMemoryStats();
|
|
153
|
+
await this.appendLearningDelta({
|
|
154
|
+
timestamp: new Date().toISOString(),
|
|
155
|
+
source,
|
|
156
|
+
memoriesBefore: (stats.total_memories ?? 0) - totalCreated,
|
|
157
|
+
memoriesAfter: stats.total_memories ?? 0,
|
|
158
|
+
newMemories: totalCreated,
|
|
159
|
+
typesAdded: c.typesCreated,
|
|
160
|
+
avgQualityOfNew: null,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
// Don't let flush errors break shutdown
|
|
165
|
+
logError('storage', 'Failed to flush session counters', error instanceof Error ? error : new Error(String(error)));
|
|
166
|
+
}
|
|
167
|
+
this._sessionCounters = {
|
|
168
|
+
memoriesCreated: 0,
|
|
169
|
+
memoriesDuplicated: 0,
|
|
170
|
+
globalMemoriesCreated: 0,
|
|
171
|
+
recallQueries: 0,
|
|
172
|
+
searchQueries: 0,
|
|
173
|
+
codeSearchQueries: 0,
|
|
174
|
+
webSearchQueries: 0,
|
|
175
|
+
webSearchCostUsd: 0,
|
|
176
|
+
qdrantSyncFailures: 0,
|
|
177
|
+
typesCreated: {},
|
|
178
|
+
startedAt: new Date().toISOString(),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async getSqliteFns() {
|
|
182
|
+
if (!this._sqliteFns) {
|
|
183
|
+
this._sqliteFns = await import('../db/index.js');
|
|
184
|
+
}
|
|
185
|
+
return this._sqliteFns;
|
|
186
|
+
}
|
|
187
|
+
/** Check if Qdrant is configured and available */
|
|
188
|
+
hasQdrant() {
|
|
189
|
+
return this.vectorBackend === 'qdrant' && this.qdrant !== null;
|
|
190
|
+
}
|
|
191
|
+
// ===========================================================================
|
|
192
|
+
// Document Operations
|
|
193
|
+
// ===========================================================================
|
|
194
|
+
async upsertDocument(filePath, chunkIndex, content, startLine, endLine, embedding, symbolName, symbolType, signature) {
|
|
195
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
196
|
+
const id = await this.postgres.upsertDocument(filePath, chunkIndex, content, startLine, endLine, embedding, symbolName, symbolType, signature);
|
|
197
|
+
if (this.hasQdrant()) {
|
|
198
|
+
try {
|
|
199
|
+
await this.qdrant.upsertDocumentWithPayload(id, embedding, {
|
|
200
|
+
filePath,
|
|
201
|
+
content,
|
|
202
|
+
startLine,
|
|
203
|
+
endLine,
|
|
204
|
+
projectId: this.qdrant.getProjectId() ?? '',
|
|
205
|
+
symbolName,
|
|
206
|
+
symbolType,
|
|
207
|
+
signature,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
this._warnQdrantFailure(`Failed to sync document vector ${id}`, error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const sqlite = await this.getSqliteFns();
|
|
217
|
+
sqlite.upsertDocument(filePath, chunkIndex, content, startLine, endLine, embedding, symbolName, symbolType, signature);
|
|
218
|
+
}
|
|
219
|
+
async upsertDocumentsBatch(documents) {
|
|
220
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
221
|
+
const ids = await this.postgres.upsertDocumentsBatch(documents);
|
|
222
|
+
if (this.hasQdrant() && ids.length > 0) {
|
|
223
|
+
try {
|
|
224
|
+
const items = documents.map((doc, idx) => ({
|
|
225
|
+
id: ids[idx],
|
|
226
|
+
embedding: doc.embedding,
|
|
227
|
+
meta: {
|
|
228
|
+
filePath: doc.filePath,
|
|
229
|
+
content: doc.content,
|
|
230
|
+
startLine: doc.startLine,
|
|
231
|
+
endLine: doc.endLine,
|
|
232
|
+
projectId: this.qdrant.getProjectId() ?? '',
|
|
233
|
+
symbolName: doc.symbolName,
|
|
234
|
+
symbolType: doc.symbolType,
|
|
235
|
+
signature: doc.signature,
|
|
236
|
+
},
|
|
237
|
+
}));
|
|
238
|
+
await this.qdrant.upsertDocumentsBatchWithPayload(items);
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
this._warnQdrantFailure(`Failed to sync ${ids.length} document vectors`, error);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const sqlite = await this.getSqliteFns();
|
|
247
|
+
sqlite.upsertDocumentsBatch(documents);
|
|
248
|
+
}
|
|
249
|
+
async upsertDocumentsBatchWithHashes(documents) {
|
|
250
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
251
|
+
const ids = await this.postgres.upsertDocumentsBatchWithHashes(documents);
|
|
252
|
+
if (this.hasQdrant() && ids.length > 0) {
|
|
253
|
+
try {
|
|
254
|
+
const items = documents.map((doc, idx) => ({
|
|
255
|
+
id: ids[idx],
|
|
256
|
+
embedding: doc.embedding,
|
|
257
|
+
meta: {
|
|
258
|
+
filePath: doc.filePath,
|
|
259
|
+
content: doc.content,
|
|
260
|
+
startLine: doc.startLine,
|
|
261
|
+
endLine: doc.endLine,
|
|
262
|
+
projectId: this.qdrant.getProjectId() ?? '',
|
|
263
|
+
symbolName: doc.symbolName,
|
|
264
|
+
symbolType: doc.symbolType,
|
|
265
|
+
signature: doc.signature,
|
|
266
|
+
},
|
|
267
|
+
}));
|
|
268
|
+
await this.qdrant.upsertDocumentsBatchWithPayload(items);
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
this._warnQdrantFailure(`Failed to sync ${ids.length} document vectors`, error);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const sqlite = await this.getSqliteFns();
|
|
277
|
+
sqlite.upsertDocumentsBatchWithHashes(documents);
|
|
278
|
+
}
|
|
279
|
+
async deleteDocumentsByPath(filePath) {
|
|
280
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
281
|
+
const deletedIds = await this.postgres.deleteDocumentsByPath(filePath);
|
|
282
|
+
if (this.vectorBackend === 'qdrant' && this.qdrant && deletedIds.length > 0) {
|
|
283
|
+
try {
|
|
284
|
+
await this.qdrant.deleteDocumentVectorsByIds(deletedIds);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
this._warnQdrantFailure('Failed to delete document vectors', error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const sqlite = await this.getSqliteFns();
|
|
293
|
+
sqlite.deleteDocumentsByPath(filePath);
|
|
294
|
+
}
|
|
295
|
+
async searchDocuments(queryEmbedding, limit = 5, threshold = 0.5) {
|
|
296
|
+
if (this.hasQdrant()) {
|
|
297
|
+
try {
|
|
298
|
+
// Access Qdrant private methods via property access for backward compatibility
|
|
299
|
+
const qdrantAny = this.qdrant;
|
|
300
|
+
const client = await qdrantAny.getClient();
|
|
301
|
+
const name = qdrantAny.collectionName('documents');
|
|
302
|
+
const qResults = await client.query(name, {
|
|
303
|
+
query: queryEmbedding,
|
|
304
|
+
using: 'dense',
|
|
305
|
+
limit,
|
|
306
|
+
score_threshold: threshold,
|
|
307
|
+
params: { hnsw_ef: 128, exact: false },
|
|
308
|
+
with_payload: true,
|
|
309
|
+
});
|
|
310
|
+
const points = qResults.points ?? qResults;
|
|
311
|
+
if (points.length > 0 && points[0].payload?.content) {
|
|
312
|
+
return points.map((p) => ({
|
|
313
|
+
file_path: p.payload?.file_path ?? '',
|
|
314
|
+
content: p.payload?.content ?? '',
|
|
315
|
+
start_line: p.payload?.start_line ?? 0,
|
|
316
|
+
end_line: p.payload?.end_line ?? 0,
|
|
317
|
+
similarity: p.score,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
321
|
+
const results = await this.qdrant.searchDocuments(queryEmbedding, limit * 3, threshold);
|
|
322
|
+
if (results.length > 0) {
|
|
323
|
+
const pgRows = await this.postgres.getDocumentsByIds(results.map((r) => r.id));
|
|
324
|
+
return pgRows
|
|
325
|
+
.map((row) => {
|
|
326
|
+
const score = results.find((r) => r.id === row.id)?.similarity ?? 0;
|
|
327
|
+
return {
|
|
328
|
+
file_path: row.file_path,
|
|
329
|
+
content: row.content,
|
|
330
|
+
start_line: row.start_line,
|
|
331
|
+
end_line: row.end_line,
|
|
332
|
+
similarity: score,
|
|
333
|
+
};
|
|
334
|
+
})
|
|
335
|
+
.sort((a, b) => b.similarity - a.similarity)
|
|
336
|
+
.slice(0, limit);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
catch (error) {
|
|
341
|
+
this._warnQdrantFailure('searchDocuments failed, falling back', error);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
345
|
+
return this.postgres.searchDocuments(queryEmbedding, limit, threshold);
|
|
346
|
+
}
|
|
347
|
+
const sqlite = await this.getSqliteFns();
|
|
348
|
+
return sqlite.searchDocuments(queryEmbedding, limit, threshold);
|
|
349
|
+
}
|
|
350
|
+
async getRecentDocuments(limit = 10) {
|
|
351
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
352
|
+
return this.postgres.getRecentDocuments(limit);
|
|
353
|
+
const sqlite = await this.getSqliteFns();
|
|
354
|
+
return sqlite.getRecentDocuments(limit);
|
|
355
|
+
}
|
|
356
|
+
async getStats() {
|
|
357
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
358
|
+
return this.postgres.getDocumentStats();
|
|
359
|
+
const sqlite = await this.getSqliteFns();
|
|
360
|
+
return sqlite.getStats();
|
|
361
|
+
}
|
|
362
|
+
async clearDocuments() {
|
|
363
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
364
|
+
await this.postgres.clearDocuments();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
const sqlite = await this.getSqliteFns();
|
|
368
|
+
sqlite.clearDocuments();
|
|
369
|
+
}
|
|
370
|
+
async clearCodeDocuments() {
|
|
371
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
372
|
+
await this.postgres.clearCodeDocuments();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const sqlite = await this.getSqliteFns();
|
|
376
|
+
sqlite.clearCodeDocuments();
|
|
377
|
+
}
|
|
378
|
+
async getStoredEmbeddingDimension() {
|
|
379
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
380
|
+
return this.postgres.getStoredEmbeddingDimension();
|
|
381
|
+
const sqlite = await this.getSqliteFns();
|
|
382
|
+
return sqlite.getStoredEmbeddingDimension();
|
|
383
|
+
}
|
|
384
|
+
// ===========================================================================
|
|
385
|
+
// Memory Operations
|
|
386
|
+
// ===========================================================================
|
|
387
|
+
async saveMemory(content, embedding, tags = [], source, options) {
|
|
388
|
+
const type = options?.type ?? 'observation';
|
|
389
|
+
const deduplicate = options?.deduplicate ?? true;
|
|
390
|
+
const qualityScore = options?.qualityScore;
|
|
391
|
+
const qualityFactors = options?.qualityFactors;
|
|
392
|
+
const validFrom = options?.validFrom;
|
|
393
|
+
const validUntil = options?.validUntil;
|
|
394
|
+
// Auto-correction: detect similar (but not duplicate) memories in 0.82-0.92 range
|
|
395
|
+
if (deduplicate) {
|
|
396
|
+
try {
|
|
397
|
+
const correctionCandidate = await this.findSimilarMemory(embedding, 0.82);
|
|
398
|
+
if (correctionCandidate) {
|
|
399
|
+
if (correctionCandidate.similarity >= 0.92) {
|
|
400
|
+
// Exact/near-exact duplicate — skip saving
|
|
401
|
+
this._sessionCounters.memoriesDuplicated++;
|
|
402
|
+
return { id: correctionCandidate.id, created: false, duplicate: correctionCandidate };
|
|
403
|
+
}
|
|
404
|
+
// Similar but different = correction/refinement — increment existing
|
|
405
|
+
await this.incrementCorrectionCount(correctionCandidate.id);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// Non-fatal: correction detection failure shouldn't block save
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
let savedId;
|
|
413
|
+
let wasDuplicate = false;
|
|
414
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
415
|
+
savedId = await this.postgres.saveMemory(content, embedding, tags, source, type, qualityScore, qualityFactors, validFrom, validUntil);
|
|
416
|
+
// Sync to Qdrant with full payload
|
|
417
|
+
if (this.hasQdrant()) {
|
|
418
|
+
try {
|
|
419
|
+
await this.qdrant.upsertMemoryWithPayload(savedId, embedding, {
|
|
420
|
+
content,
|
|
421
|
+
tags,
|
|
422
|
+
source,
|
|
423
|
+
type,
|
|
424
|
+
projectId: this.qdrant.getProjectId(),
|
|
425
|
+
createdAt: new Date().toISOString(),
|
|
426
|
+
validFrom,
|
|
427
|
+
validUntil,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
catch (error) {
|
|
431
|
+
this._warnQdrantFailure(`Failed to sync memory vector ${savedId}`, error);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
else {
|
|
436
|
+
const sqlite = await this.getSqliteFns();
|
|
437
|
+
const result = sqlite.saveMemory(content, embedding, tags, source, {
|
|
438
|
+
type,
|
|
439
|
+
deduplicate: false, // dedup already handled above
|
|
440
|
+
qualityScore: qualityScore != null ? { score: qualityScore, factors: qualityFactors ?? {} } : undefined,
|
|
441
|
+
validFrom,
|
|
442
|
+
validUntil,
|
|
443
|
+
});
|
|
444
|
+
savedId = result.id;
|
|
445
|
+
wasDuplicate = result.isDuplicate;
|
|
446
|
+
// SQLite + Qdrant: sync memory
|
|
447
|
+
if (this.hasQdrant() && !wasDuplicate) {
|
|
448
|
+
try {
|
|
449
|
+
await this.qdrant.upsertMemoryWithPayload(savedId, embedding, {
|
|
450
|
+
content,
|
|
451
|
+
tags,
|
|
452
|
+
source,
|
|
453
|
+
type,
|
|
454
|
+
projectId: this.qdrant.getProjectId(),
|
|
455
|
+
createdAt: new Date().toISOString(),
|
|
456
|
+
validFrom,
|
|
457
|
+
validUntil,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
this._warnQdrantFailure(`Failed to sync memory vector ${savedId}`, error);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (!wasDuplicate) {
|
|
466
|
+
this._sessionCounters.memoriesCreated++;
|
|
467
|
+
this._sessionCounters.typesCreated[type] =
|
|
468
|
+
(this._sessionCounters.typesCreated[type] ?? 0) + 1;
|
|
469
|
+
// Auto-detect invariant content and set is_invariant + compute priority_score
|
|
470
|
+
// Hybrid: regex fast path (8 languages) + embedding similarity fallback (any language)
|
|
471
|
+
// Skip observations — they're subagent reports/facts, not rules/constraints.
|
|
472
|
+
// Invariant detection only makes sense for decision/learning/pattern/error types.
|
|
473
|
+
try {
|
|
474
|
+
if (type !== 'observation') {
|
|
475
|
+
const { detectInvariant, detectInvariantWithEmbedding } = await import('../working-memory-pipeline.js');
|
|
476
|
+
const isInvariant = detectInvariant(content) || (await detectInvariantWithEmbedding(content, embedding));
|
|
477
|
+
if (isInvariant) {
|
|
478
|
+
await this.setMemoryInvariant(savedId, true);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
await this.recomputePriorityScore(savedId);
|
|
482
|
+
}
|
|
483
|
+
catch {
|
|
484
|
+
// Non-fatal
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
else {
|
|
488
|
+
this._sessionCounters.memoriesDuplicated++;
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
id: savedId,
|
|
492
|
+
created: !wasDuplicate,
|
|
493
|
+
duplicate: wasDuplicate ? { id: savedId, content: '', similarity: 1.0 } : undefined,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
async searchMemories(queryEmbedding, limit = 5, threshold = 0.3, tags, since, options) {
|
|
497
|
+
if (this.hasQdrant()) {
|
|
498
|
+
try {
|
|
499
|
+
const results = await this.qdrant.hybridSearchMemories('', queryEmbedding, limit, threshold, {
|
|
500
|
+
projectId: this.qdrant.getProjectId(),
|
|
501
|
+
tags,
|
|
502
|
+
since,
|
|
503
|
+
asOfDate: options?.asOfDate,
|
|
504
|
+
includeExpired: options?.includeExpired,
|
|
505
|
+
});
|
|
506
|
+
if (results.length > 0)
|
|
507
|
+
return results;
|
|
508
|
+
}
|
|
509
|
+
catch (error) {
|
|
510
|
+
this._warnQdrantFailure('searchMemories hybrid failed, falling back', error);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
514
|
+
return this.postgres.searchMemories(queryEmbedding, limit, threshold, tags, since, options);
|
|
515
|
+
}
|
|
516
|
+
const sqlite = await this.getSqliteFns();
|
|
517
|
+
return sqlite.searchMemories(queryEmbedding, limit, threshold, tags, since, options);
|
|
518
|
+
}
|
|
519
|
+
async getMemoryById(id) {
|
|
520
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
521
|
+
return this.postgres.getMemoryById(id);
|
|
522
|
+
const sqlite = await this.getSqliteFns();
|
|
523
|
+
return sqlite.getMemoryById(id);
|
|
524
|
+
}
|
|
525
|
+
async deleteMemory(id) {
|
|
526
|
+
// Tier 1 immutability guard: pinned memories cannot be deleted
|
|
527
|
+
await this._guardPinned(id);
|
|
528
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
529
|
+
const deleted = await this.postgres.deleteMemory(id);
|
|
530
|
+
if (deleted && this.vectorBackend === 'qdrant' && this.qdrant)
|
|
531
|
+
await this.qdrant.deleteMemoryVector(id);
|
|
532
|
+
return deleted;
|
|
533
|
+
}
|
|
534
|
+
const sqlite = await this.getSqliteFns();
|
|
535
|
+
const deleted = sqlite.deleteMemory(id);
|
|
536
|
+
if (deleted && this.vectorBackend === 'qdrant' && this.qdrant) {
|
|
537
|
+
try {
|
|
538
|
+
await this.qdrant.deleteMemoryVector(id);
|
|
539
|
+
}
|
|
540
|
+
catch (err) {
|
|
541
|
+
logWarn('storage', `Qdrant vector delete failed for memory ${id}`, { error: String(err) });
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return deleted;
|
|
545
|
+
}
|
|
546
|
+
async getRecentMemories(limit = 10) {
|
|
547
|
+
// Two-phase fetch: pinned memories + over-fetched recent
|
|
548
|
+
const overfetchLimit = limit * 3;
|
|
549
|
+
let raw;
|
|
550
|
+
let pinned;
|
|
551
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
552
|
+
[raw, pinned] = await Promise.all([
|
|
553
|
+
this.postgres.getRecentMemories(overfetchLimit),
|
|
554
|
+
this.postgres.getPinnedMemories(),
|
|
555
|
+
]);
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
const sqlite = await this.getSqliteFns();
|
|
559
|
+
raw = sqlite.getRecentMemories(overfetchLimit);
|
|
560
|
+
pinned = sqlite.getPinnedMemories();
|
|
561
|
+
}
|
|
562
|
+
// Apply working memory pipeline: pinned first → validity filter → score → rank
|
|
563
|
+
const { applyWorkingMemoryPipeline, applyDiversityFilter } = await import('../working-memory-pipeline.js');
|
|
564
|
+
// Over-request to account for diversity filtering
|
|
565
|
+
const pipelineLimit = Math.min(limit * 2, raw.length + pinned.length);
|
|
566
|
+
const pipeline = applyWorkingMemoryPipeline(raw, pipelineLimit, new Date(), pinned);
|
|
567
|
+
// Diversity filter: remove near-duplicate embeddings
|
|
568
|
+
const diverse = await applyDiversityFilter(pipeline, (ids) => this.getMemoryEmbeddingsByIds(ids));
|
|
569
|
+
return diverse.slice(0, limit);
|
|
570
|
+
}
|
|
571
|
+
async findSimilarMemory(embedding, threshold) {
|
|
572
|
+
const thresh = threshold ?? 0.92;
|
|
573
|
+
if (this.vectorBackend === 'qdrant' && this.qdrant) {
|
|
574
|
+
try {
|
|
575
|
+
const results = await this.qdrant.findSimilarWithContent('memories', embedding, 3, thresh);
|
|
576
|
+
if (results.length > 0 && results[0].similarity >= thresh) {
|
|
577
|
+
return {
|
|
578
|
+
id: results[0].id,
|
|
579
|
+
content: results[0].content,
|
|
580
|
+
similarity: results[0].similarity,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
// v1 schema fallback: IDs only -> PG
|
|
584
|
+
if (results.length === 0 && this.backend === 'postgresql' && this.postgres) {
|
|
585
|
+
const qr = await this.qdrant.searchMemories(embedding, 3, thresh);
|
|
586
|
+
if (qr.length > 0) {
|
|
587
|
+
const pgRows = await this.postgres.getMemoriesByIds(qr.map((r) => r.id), { excludeInvalidated: false });
|
|
588
|
+
if (pgRows.length > 0) {
|
|
589
|
+
const score = qr.find((r) => r.id === pgRows[0].id)?.similarity ?? 0;
|
|
590
|
+
if (score >= thresh)
|
|
591
|
+
return { id: pgRows[0].id, content: pgRows[0].content, similarity: score };
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
catch (error) {
|
|
597
|
+
this._warnQdrantFailure('findSimilarMemory failed, falling back', error);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
601
|
+
return this.postgres.findSimilarMemory(embedding, threshold);
|
|
602
|
+
const sqlite = await this.getSqliteFns();
|
|
603
|
+
return sqlite.findSimilarMemory(embedding, threshold);
|
|
604
|
+
}
|
|
605
|
+
async saveMemoriesBatch(memories, deduplicateThreshold, options) {
|
|
606
|
+
let result;
|
|
607
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
608
|
+
result = await this.postgres.saveMemoriesBatch(memories, deduplicateThreshold, options);
|
|
609
|
+
}
|
|
610
|
+
else {
|
|
611
|
+
const sqlite = await this.getSqliteFns();
|
|
612
|
+
result = await sqlite.saveMemoriesBatch(memories, deduplicateThreshold, options);
|
|
613
|
+
}
|
|
614
|
+
// Sync newly saved memories to Qdrant
|
|
615
|
+
if (this.hasQdrant() && result?.results?.length > 0) {
|
|
616
|
+
try {
|
|
617
|
+
const saved = result.results.filter((r) => !r.isDuplicate && r.id != null);
|
|
618
|
+
if (saved.length > 0) {
|
|
619
|
+
const items = saved.map((r) => {
|
|
620
|
+
const mem = memories[r.index];
|
|
621
|
+
return {
|
|
622
|
+
id: r.id,
|
|
623
|
+
embedding: mem.embedding,
|
|
624
|
+
meta: {
|
|
625
|
+
content: mem.content,
|
|
626
|
+
tags: mem.tags ?? [],
|
|
627
|
+
source: mem.source,
|
|
628
|
+
type: mem.type,
|
|
629
|
+
projectId: this.qdrant.getProjectId(),
|
|
630
|
+
createdAt: new Date().toISOString(),
|
|
631
|
+
validFrom: mem.validFrom
|
|
632
|
+
? mem.validFrom instanceof Date
|
|
633
|
+
? mem.validFrom.toISOString()
|
|
634
|
+
: mem.validFrom
|
|
635
|
+
: null,
|
|
636
|
+
validUntil: mem.validUntil
|
|
637
|
+
? mem.validUntil instanceof Date
|
|
638
|
+
? mem.validUntil.toISOString()
|
|
639
|
+
: mem.validUntil
|
|
640
|
+
: null,
|
|
641
|
+
},
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
await this.qdrant.upsertMemoriesBatchWithPayload(items);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
this._warnQdrantFailure(`Failed to sync ${result.saved} batch memories`, error);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return result;
|
|
652
|
+
}
|
|
653
|
+
async invalidateMemory(memoryId, supersededById) {
|
|
654
|
+
// Tier 1 immutability guard: pinned memories cannot be invalidated
|
|
655
|
+
await this._guardPinned(memoryId);
|
|
656
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
657
|
+
return this.postgres.invalidateMemory(memoryId, supersededById);
|
|
658
|
+
const sqlite = await this.getSqliteFns();
|
|
659
|
+
return sqlite.invalidateMemory(memoryId, supersededById);
|
|
660
|
+
}
|
|
661
|
+
async restoreInvalidatedMemory(memoryId) {
|
|
662
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
663
|
+
return this.postgres.restoreInvalidatedMemory(memoryId);
|
|
664
|
+
const sqlite = await this.getSqliteFns();
|
|
665
|
+
return sqlite.restoreInvalidatedMemory(memoryId);
|
|
666
|
+
}
|
|
667
|
+
async getConsolidationHistory(limit) {
|
|
668
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
669
|
+
return this.postgres.getConsolidationHistory(limit);
|
|
670
|
+
const sqlite = await this.getSqliteFns();
|
|
671
|
+
return sqlite.getConsolidationHistory(limit);
|
|
672
|
+
}
|
|
673
|
+
async getMemoryStats() {
|
|
674
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
675
|
+
return this.postgres.getMemoryStats();
|
|
676
|
+
const sqlite = await this.getSqliteFns();
|
|
677
|
+
return sqlite.getMemoryStats();
|
|
678
|
+
}
|
|
679
|
+
async deleteMemoriesOlderThan(date) {
|
|
680
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
681
|
+
return this.postgres.deleteMemoriesOlderThan(date);
|
|
682
|
+
const sqlite = await this.getSqliteFns();
|
|
683
|
+
return sqlite.deleteMemoriesOlderThan(date);
|
|
684
|
+
}
|
|
685
|
+
async deleteMemoriesByTag(tag) {
|
|
686
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
687
|
+
return this.postgres.deleteMemoriesByTag(tag);
|
|
688
|
+
const sqlite = await this.getSqliteFns();
|
|
689
|
+
return sqlite.deleteMemoriesByTag(tag);
|
|
690
|
+
}
|
|
691
|
+
async deleteMemoriesByIds(ids) {
|
|
692
|
+
if (ids.length === 0)
|
|
693
|
+
return 0;
|
|
694
|
+
// Filter out pinned memories (Tier 1 immutability)
|
|
695
|
+
const safeIds = await this._filterOutPinned(ids);
|
|
696
|
+
if (safeIds.length === 0)
|
|
697
|
+
return 0;
|
|
698
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
699
|
+
return this.postgres.deleteMemoriesByIds(safeIds);
|
|
700
|
+
const sqlite = await this.getSqliteFns();
|
|
701
|
+
return sqlite.deleteMemoriesByIds(safeIds);
|
|
702
|
+
}
|
|
703
|
+
async searchMemoriesAsOf(queryEmbedding, asOfDate, limit, threshold) {
|
|
704
|
+
const lim = limit ?? 5;
|
|
705
|
+
const thresh = threshold ?? 0.3;
|
|
706
|
+
if (this.hasQdrant()) {
|
|
707
|
+
try {
|
|
708
|
+
const results = await this.qdrant.hybridSearchMemories('', queryEmbedding, lim, thresh, {
|
|
709
|
+
createdBefore: asOfDate,
|
|
710
|
+
asOfDate,
|
|
711
|
+
includeExpired: false,
|
|
712
|
+
});
|
|
713
|
+
if (results.length > 0)
|
|
714
|
+
return results;
|
|
715
|
+
}
|
|
716
|
+
catch (error) {
|
|
717
|
+
this._warnQdrantFailure('searchMemoriesAsOf failed, falling back', error);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
721
|
+
return this.postgres.searchMemoriesAsOf(queryEmbedding, asOfDate, limit, threshold);
|
|
722
|
+
const sqlite = await this.getSqliteFns();
|
|
723
|
+
return sqlite.searchMemoriesAsOf(queryEmbedding, asOfDate, limit, threshold);
|
|
724
|
+
}
|
|
725
|
+
// ===========================================================================
|
|
726
|
+
// Memory Links
|
|
727
|
+
// ===========================================================================
|
|
728
|
+
async createMemoryLink(sourceId, targetId, relation = 'related', weight = 1.0, validFrom, validUntil) {
|
|
729
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
730
|
+
return this.postgres.createMemoryLink(sourceId, targetId, relation, weight, validFrom, validUntil);
|
|
731
|
+
const sqlite = await this.getSqliteFns();
|
|
732
|
+
return sqlite.createMemoryLink(sourceId, targetId, relation, weight, { validFrom, validUntil });
|
|
733
|
+
}
|
|
734
|
+
async deleteMemoryLink(sourceId, targetId, relation) {
|
|
735
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
736
|
+
return this.postgres.deleteMemoryLink(sourceId, targetId, relation);
|
|
737
|
+
const sqlite = await this.getSqliteFns();
|
|
738
|
+
return sqlite.deleteMemoryLink(sourceId, targetId, relation);
|
|
739
|
+
}
|
|
740
|
+
async getMemoryLinks(memoryId) {
|
|
741
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
742
|
+
return this.postgres.getMemoryLinks(memoryId);
|
|
743
|
+
const sqlite = await this.getSqliteFns();
|
|
744
|
+
return sqlite.getMemoryLinks(memoryId);
|
|
745
|
+
}
|
|
746
|
+
async getMemoryWithLinks(memoryId, options) {
|
|
747
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
748
|
+
return this.postgres.getMemoryWithLinks(memoryId, options);
|
|
749
|
+
const sqlite = await this.getSqliteFns();
|
|
750
|
+
return sqlite.getMemoryWithLinks(memoryId, options);
|
|
751
|
+
}
|
|
752
|
+
async findConnectedMemories(memoryId, maxDepth) {
|
|
753
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
754
|
+
return this.postgres.findConnectedMemories(memoryId, maxDepth);
|
|
755
|
+
const sqlite = await this.getSqliteFns();
|
|
756
|
+
return sqlite.findConnectedMemories(memoryId, maxDepth);
|
|
757
|
+
}
|
|
758
|
+
async findRelatedMemoriesForLinking(memoryId, threshold, maxLinks) {
|
|
759
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
760
|
+
return this.postgres.findRelatedMemoriesForLinking(memoryId, threshold, maxLinks);
|
|
761
|
+
const sqlite = await this.getSqliteFns();
|
|
762
|
+
return sqlite.findRelatedMemoriesForLinking(memoryId, threshold, maxLinks);
|
|
763
|
+
}
|
|
764
|
+
async createAutoLinks(memoryId, threshold, maxLinks) {
|
|
765
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
766
|
+
return this.postgres.createAutoLinks(memoryId, threshold, maxLinks);
|
|
767
|
+
const sqlite = await this.getSqliteFns();
|
|
768
|
+
return sqlite.createAutoLinks(memoryId, threshold, maxLinks);
|
|
769
|
+
}
|
|
770
|
+
async autoLinkSimilarMemories(threshold, maxLinks) {
|
|
771
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
772
|
+
return this.postgres.autoLinkSimilarMemories(threshold, maxLinks);
|
|
773
|
+
const sqlite = await this.getSqliteFns();
|
|
774
|
+
return sqlite.autoLinkSimilarMemories(threshold, maxLinks);
|
|
775
|
+
}
|
|
776
|
+
async getGraphStats() {
|
|
777
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
778
|
+
return this.postgres.getGraphStats();
|
|
779
|
+
const sqlite = await this.getSqliteFns();
|
|
780
|
+
return sqlite.getGraphStats();
|
|
781
|
+
}
|
|
782
|
+
async invalidateMemoryLink(sourceId, targetId, relation) {
|
|
783
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
784
|
+
return this.postgres.invalidateMemoryLink(sourceId, targetId, relation);
|
|
785
|
+
const sqlite = await this.getSqliteFns();
|
|
786
|
+
return sqlite.invalidateMemoryLink(sourceId, targetId, relation);
|
|
787
|
+
}
|
|
788
|
+
async getMemoryLinksAsOf(memoryId, asOfDate) {
|
|
789
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
790
|
+
return this.postgres.getMemoryLinksAsOf(memoryId, asOfDate);
|
|
791
|
+
const sqlite = await this.getSqliteFns();
|
|
792
|
+
return sqlite.getMemoryLinksAsOf(memoryId, asOfDate);
|
|
793
|
+
}
|
|
794
|
+
async findConnectedMemoriesAsOf(memoryId, asOfDate, maxDepth) {
|
|
795
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
796
|
+
return this.postgres.findConnectedMemoriesAsOf(memoryId, asOfDate, maxDepth);
|
|
797
|
+
const sqlite = await this.getSqliteFns();
|
|
798
|
+
return sqlite.findConnectedMemoriesAsOf(memoryId, asOfDate, maxDepth);
|
|
799
|
+
}
|
|
800
|
+
async getGraphStatsAsOf(asOfDate) {
|
|
801
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
802
|
+
return this.postgres.getGraphStatsAsOf(asOfDate);
|
|
803
|
+
const sqlite = await this.getSqliteFns();
|
|
804
|
+
return sqlite.getGraphStatsAsOf(asOfDate);
|
|
805
|
+
}
|
|
806
|
+
// ===========================================================================
|
|
807
|
+
// Graph Enrichment
|
|
808
|
+
// ===========================================================================
|
|
809
|
+
async updateMemoryEmbedding(memoryId, embedding) {
|
|
810
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
811
|
+
return this.postgres.updateMemoryEmbedding(memoryId, embedding);
|
|
812
|
+
}
|
|
813
|
+
const sqlite = await this.getSqliteFns();
|
|
814
|
+
sqlite.updateMemoryEmbedding(memoryId, embedding);
|
|
815
|
+
}
|
|
816
|
+
async updateMemoryEmbeddingsBatch(updates) {
|
|
817
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
818
|
+
return this.postgres.updateMemoryEmbeddingsBatch(updates);
|
|
819
|
+
}
|
|
820
|
+
const sqlite = await this.getSqliteFns();
|
|
821
|
+
for (const { id, embedding } of updates) {
|
|
822
|
+
sqlite.updateMemoryEmbedding(id, embedding);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
async getMemoriesNeedingReembedding(limit = 100, afterId = 0) {
|
|
826
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
827
|
+
return this.postgres.getMemoriesNeedingReembedding(limit, afterId);
|
|
828
|
+
}
|
|
829
|
+
const sqlite = await this.getSqliteFns();
|
|
830
|
+
return sqlite.getMemoriesNeedingReembedding(limit, afterId);
|
|
831
|
+
}
|
|
832
|
+
async getMemoryCount() {
|
|
833
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
834
|
+
return this.postgres.getMemoryCount();
|
|
835
|
+
}
|
|
836
|
+
const sqlite = await this.getSqliteFns();
|
|
837
|
+
return sqlite.getMemoryCount();
|
|
838
|
+
}
|
|
839
|
+
async getMemoryEmbeddingCount() {
|
|
840
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
841
|
+
return this.postgres.getMemoryEmbeddingCount();
|
|
842
|
+
}
|
|
843
|
+
const sqlite = await this.getSqliteFns();
|
|
844
|
+
return sqlite.getMemoryEmbeddingCount();
|
|
845
|
+
}
|
|
846
|
+
async updateMemoryTags(memoryId, tags) {
|
|
847
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
848
|
+
return this.postgres.updateMemoryTags(memoryId, tags);
|
|
849
|
+
}
|
|
850
|
+
const sqlite = await this.getSqliteFns();
|
|
851
|
+
sqlite.updateMemoryTags(memoryId, tags);
|
|
852
|
+
}
|
|
853
|
+
async updateMemoryLink(linkId, updates) {
|
|
854
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
855
|
+
return this.postgres.updateMemoryLink(linkId, updates);
|
|
856
|
+
}
|
|
857
|
+
const sqlite = await this.getSqliteFns();
|
|
858
|
+
sqlite.updateMemoryLink(linkId, updates);
|
|
859
|
+
}
|
|
860
|
+
async upsertCentralityScore(memoryId, degree, normalizedDegree) {
|
|
861
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
862
|
+
return this.postgres.upsertCentralityScore(memoryId, degree, normalizedDegree);
|
|
863
|
+
}
|
|
864
|
+
const sqlite = await this.getSqliteFns();
|
|
865
|
+
sqlite.upsertCentralityScore(memoryId, degree, normalizedDegree);
|
|
866
|
+
}
|
|
867
|
+
async getCentralityScores(memoryIds) {
|
|
868
|
+
if (memoryIds.length === 0)
|
|
869
|
+
return new Map();
|
|
870
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
871
|
+
return this.postgres.getCentralityScores(memoryIds);
|
|
872
|
+
}
|
|
873
|
+
const sqlite = await this.getSqliteFns();
|
|
874
|
+
return sqlite.getCentralityScores(memoryIds);
|
|
875
|
+
}
|
|
876
|
+
async deleteMemoryLinksByIds(ids) {
|
|
877
|
+
if (ids.length === 0)
|
|
878
|
+
return 0;
|
|
879
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
880
|
+
const pool = await this.postgres.getPool();
|
|
881
|
+
const BATCH_SIZE = 999;
|
|
882
|
+
let totalDeleted = 0;
|
|
883
|
+
for (let i = 0; i < ids.length; i += BATCH_SIZE) {
|
|
884
|
+
const batch = ids.slice(i, i + BATCH_SIZE);
|
|
885
|
+
const placeholders = batch.map((_, idx) => `$${idx + 1}`).join(',');
|
|
886
|
+
const result = await pool.query(`DELETE FROM memory_links WHERE id IN (${placeholders})`, batch);
|
|
887
|
+
totalDeleted += result.rowCount ?? 0;
|
|
888
|
+
}
|
|
889
|
+
return totalDeleted;
|
|
890
|
+
}
|
|
891
|
+
const sqlite = await this.getSqliteFns();
|
|
892
|
+
return sqlite.deleteMemoryLinksByIds(ids);
|
|
893
|
+
}
|
|
894
|
+
async findIsolatedMemoryIds() {
|
|
895
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
896
|
+
const pool = await this.postgres.getPool();
|
|
897
|
+
const scopeCond = this.postgres.getProjectId()
|
|
898
|
+
? 'AND (LOWER(m.project_id) = $1 OR m.project_id IS NULL)'
|
|
899
|
+
: 'AND m.project_id IS NULL';
|
|
900
|
+
const params = this.postgres.getProjectId() ? [this.postgres.getProjectId()] : [];
|
|
901
|
+
const result = await pool.query(`SELECT m.id FROM memories m
|
|
902
|
+
WHERE NOT EXISTS (
|
|
903
|
+
SELECT 1 FROM memory_links WHERE source_id = m.id OR target_id = m.id
|
|
904
|
+
) ${scopeCond}`, params);
|
|
905
|
+
return result.rows.map((r) => r.id);
|
|
906
|
+
}
|
|
907
|
+
const sqlite = await this.getSqliteFns();
|
|
908
|
+
return sqlite.findIsolatedMemoryIds();
|
|
909
|
+
}
|
|
910
|
+
// ===========================================================================
|
|
911
|
+
// File Hashes
|
|
912
|
+
// ===========================================================================
|
|
913
|
+
async getFileHash(filePath) {
|
|
914
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
915
|
+
return this.postgres.getFileHash(filePath);
|
|
916
|
+
const sqlite = await this.getSqliteFns();
|
|
917
|
+
return sqlite.getFileHash(filePath);
|
|
918
|
+
}
|
|
919
|
+
async setFileHash(filePath, hash) {
|
|
920
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
921
|
+
return this.postgres.setFileHash(filePath, hash);
|
|
922
|
+
const sqlite = await this.getSqliteFns();
|
|
923
|
+
return sqlite.setFileHash(filePath, hash);
|
|
924
|
+
}
|
|
925
|
+
async deleteFileHash(filePath) {
|
|
926
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
927
|
+
await this.postgres.deleteFileHash(filePath);
|
|
928
|
+
return;
|
|
929
|
+
}
|
|
930
|
+
const sqlite = await this.getSqliteFns();
|
|
931
|
+
sqlite.deleteFileHash(filePath);
|
|
932
|
+
}
|
|
933
|
+
async getAllFileHashes() {
|
|
934
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
935
|
+
return this.postgres.getAllFileHashes();
|
|
936
|
+
const sqlite = await this.getSqliteFns();
|
|
937
|
+
return sqlite.getAllFileHashes();
|
|
938
|
+
}
|
|
939
|
+
async getAllFileHashesWithTimestamps() {
|
|
940
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
941
|
+
return this.postgres.getAllFileHashesWithTimestamps();
|
|
942
|
+
const sqlite = await this.getSqliteFns();
|
|
943
|
+
return sqlite.getAllFileHashesWithTimestamps();
|
|
944
|
+
}
|
|
945
|
+
// ===========================================================================
|
|
946
|
+
// Token Frequency Operations
|
|
947
|
+
// ===========================================================================
|
|
948
|
+
async updateTokenFrequencies(tokens) {
|
|
949
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
950
|
+
await this.postgres.updateTokenFrequencies(tokens);
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const sqlite = await this.getSqliteFns();
|
|
954
|
+
sqlite.updateTokenFrequencies(tokens);
|
|
955
|
+
}
|
|
956
|
+
async getTokenFrequency(token) {
|
|
957
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
958
|
+
return this.postgres.getTokenFrequency(token);
|
|
959
|
+
const sqlite = await this.getSqliteFns();
|
|
960
|
+
return sqlite.getTokenFrequency(token);
|
|
961
|
+
}
|
|
962
|
+
async getTokenFrequencies(tokens) {
|
|
963
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
964
|
+
return this.postgres.getTokenFrequencies(tokens);
|
|
965
|
+
const sqlite = await this.getSqliteFns();
|
|
966
|
+
return sqlite.getTokenFrequencies(tokens);
|
|
967
|
+
}
|
|
968
|
+
async getTotalTokenCount() {
|
|
969
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
970
|
+
return this.postgres.getTotalTokenCount();
|
|
971
|
+
const sqlite = await this.getSqliteFns();
|
|
972
|
+
return sqlite.getTotalTokenCount();
|
|
973
|
+
}
|
|
974
|
+
async getTopTokens(limit) {
|
|
975
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
976
|
+
return this.postgres.getTopTokens(limit);
|
|
977
|
+
const sqlite = await this.getSqliteFns();
|
|
978
|
+
return sqlite.getTopTokens(limit);
|
|
979
|
+
}
|
|
980
|
+
async clearTokenFrequencies() {
|
|
981
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
982
|
+
await this.postgres.clearTokenFrequencies();
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
const sqlite = await this.getSqliteFns();
|
|
986
|
+
sqlite.clearTokenFrequencies();
|
|
987
|
+
}
|
|
988
|
+
async getTokenFrequencyStats() {
|
|
989
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
990
|
+
return this.postgres.getTokenFrequencyStats();
|
|
991
|
+
const sqlite = await this.getSqliteFns();
|
|
992
|
+
return sqlite.getTokenFrequencyStats();
|
|
993
|
+
}
|
|
994
|
+
// ===========================================================================
|
|
995
|
+
// Token Stats
|
|
996
|
+
// ===========================================================================
|
|
997
|
+
async recordTokenStat(record) {
|
|
998
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
999
|
+
return this.postgres.recordTokenStat(record);
|
|
1000
|
+
const sqlite = await this.getSqliteFns();
|
|
1001
|
+
return sqlite.recordTokenStat(record);
|
|
1002
|
+
}
|
|
1003
|
+
async getTokenStatsSummary() {
|
|
1004
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1005
|
+
const summary = await this.postgres.getTokenStatsSummary();
|
|
1006
|
+
const savingsPercent = summary.total_full_source_tokens > 0
|
|
1007
|
+
? (summary.total_savings_tokens / summary.total_full_source_tokens) * 100
|
|
1008
|
+
: 0;
|
|
1009
|
+
return {
|
|
1010
|
+
total_calls: summary.total_queries,
|
|
1011
|
+
total_returned_tokens: summary.total_returned_tokens,
|
|
1012
|
+
total_full_source_tokens: summary.total_full_source_tokens,
|
|
1013
|
+
total_savings_tokens: summary.total_savings_tokens,
|
|
1014
|
+
total_estimated_cost: summary.total_estimated_cost,
|
|
1015
|
+
savings_percent: savingsPercent,
|
|
1016
|
+
by_event_type: [],
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
const sqlite = await this.getSqliteFns();
|
|
1020
|
+
return sqlite.getTokenStatsSummary();
|
|
1021
|
+
}
|
|
1022
|
+
async getTokenStatsAggregated() {
|
|
1023
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1024
|
+
return this.postgres.getTokenStatsAggregated();
|
|
1025
|
+
const sqlite = await this.getSqliteFns();
|
|
1026
|
+
return sqlite.getTokenStatsAggregated();
|
|
1027
|
+
}
|
|
1028
|
+
async clearTokenStats() {
|
|
1029
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1030
|
+
await this.postgres.clearTokenStats();
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
const sqlite = await this.getSqliteFns();
|
|
1034
|
+
sqlite.clearTokenStats();
|
|
1035
|
+
}
|
|
1036
|
+
// ===========================================================================
|
|
1037
|
+
// Web Search History
|
|
1038
|
+
// ===========================================================================
|
|
1039
|
+
async recordWebSearch(record) {
|
|
1040
|
+
this._sessionCounters.webSearchQueries++;
|
|
1041
|
+
this._sessionCounters.webSearchCostUsd += record.estimated_cost_usd;
|
|
1042
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1043
|
+
return this.postgres.recordWebSearch(record);
|
|
1044
|
+
}
|
|
1045
|
+
const sqlite = await this.getSqliteFns();
|
|
1046
|
+
return sqlite.recordWebSearch(record);
|
|
1047
|
+
}
|
|
1048
|
+
async getWebSearchHistory(filter) {
|
|
1049
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1050
|
+
return this.postgres.getWebSearchHistory(filter);
|
|
1051
|
+
const sqlite = await this.getSqliteFns();
|
|
1052
|
+
return sqlite.getWebSearchHistory(filter);
|
|
1053
|
+
}
|
|
1054
|
+
async getWebSearchSummary() {
|
|
1055
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1056
|
+
return this.postgres.getWebSearchSummary();
|
|
1057
|
+
const sqlite = await this.getSqliteFns();
|
|
1058
|
+
return sqlite.getWebSearchSummary();
|
|
1059
|
+
}
|
|
1060
|
+
async getTodayWebSearchSpend() {
|
|
1061
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1062
|
+
return this.postgres.getTodayWebSearchSpend();
|
|
1063
|
+
const sqlite = await this.getSqliteFns();
|
|
1064
|
+
return sqlite.getTodayWebSearchSpend();
|
|
1065
|
+
}
|
|
1066
|
+
async clearWebSearchHistory() {
|
|
1067
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1068
|
+
await this.postgres.clearWebSearchHistory();
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const sqlite = await this.getSqliteFns();
|
|
1072
|
+
sqlite.clearWebSearchHistory();
|
|
1073
|
+
}
|
|
1074
|
+
// ===========================================================================
|
|
1075
|
+
// Retention Operations
|
|
1076
|
+
// ===========================================================================
|
|
1077
|
+
async incrementMemoryAccess(memoryId, weight) {
|
|
1078
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1079
|
+
await this.postgres.incrementMemoryAccess(memoryId, weight);
|
|
1080
|
+
}
|
|
1081
|
+
else {
|
|
1082
|
+
const sqlite = await this.getSqliteFns();
|
|
1083
|
+
sqlite.incrementMemoryAccess(memoryId, weight);
|
|
1084
|
+
}
|
|
1085
|
+
// Recompute priority_score after access change
|
|
1086
|
+
await this.recomputePriorityScore(memoryId);
|
|
1087
|
+
}
|
|
1088
|
+
async incrementMemoryAccessBatch(accesses) {
|
|
1089
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1090
|
+
await this.postgres.incrementMemoryAccessBatch(accesses);
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
const sqlite = await this.getSqliteFns();
|
|
1094
|
+
sqlite.incrementMemoryAccessBatch(accesses);
|
|
1095
|
+
}
|
|
1096
|
+
async incrementCorrectionCount(memoryId) {
|
|
1097
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1098
|
+
await this.postgres.incrementCorrectionCount(memoryId);
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
const sqlite = await this.getSqliteFns();
|
|
1102
|
+
sqlite.incrementCorrectionCount(memoryId);
|
|
1103
|
+
}
|
|
1104
|
+
// Recompute priority_score after correction change
|
|
1105
|
+
await this.recomputePriorityScore(memoryId);
|
|
1106
|
+
}
|
|
1107
|
+
async setMemoryInvariant(memoryId, isInvariant) {
|
|
1108
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1109
|
+
await this.postgres.setMemoryInvariant(memoryId, isInvariant);
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
const sqlite = await this.getSqliteFns();
|
|
1113
|
+
sqlite.setMemoryInvariant(memoryId, isInvariant);
|
|
1114
|
+
}
|
|
1115
|
+
// Recompute priority_score after invariant change
|
|
1116
|
+
await this.recomputePriorityScore(memoryId);
|
|
1117
|
+
}
|
|
1118
|
+
async getPinnedMemories(threshold) {
|
|
1119
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1120
|
+
return this.postgres.getPinnedMemories(threshold);
|
|
1121
|
+
const sqlite = await this.getSqliteFns();
|
|
1122
|
+
return sqlite.getPinnedMemories(threshold);
|
|
1123
|
+
}
|
|
1124
|
+
async updatePriorityScore(memoryId, score) {
|
|
1125
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1126
|
+
await this.postgres.updatePriorityScore(memoryId, score);
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
const sqlite = await this.getSqliteFns();
|
|
1130
|
+
sqlite.updatePriorityScore(memoryId, score);
|
|
1131
|
+
}
|
|
1132
|
+
async recomputePriorityScore(memoryId) {
|
|
1133
|
+
try {
|
|
1134
|
+
const memory = await this.getMemoryById(memoryId);
|
|
1135
|
+
if (!memory)
|
|
1136
|
+
return;
|
|
1137
|
+
const { computePriorityScore } = await import('../working-memory-pipeline.js');
|
|
1138
|
+
const score = computePriorityScore({
|
|
1139
|
+
is_invariant: !!memory.is_invariant,
|
|
1140
|
+
quality_score: memory.quality_score,
|
|
1141
|
+
correction_count: memory.correction_count ?? 0,
|
|
1142
|
+
type: memory.type ?? null,
|
|
1143
|
+
tags: memory.tags,
|
|
1144
|
+
access_count: memory.access_count,
|
|
1145
|
+
last_accessed: memory.last_accessed
|
|
1146
|
+
? typeof memory.last_accessed === 'object'
|
|
1147
|
+
? memory.last_accessed.toISOString()
|
|
1148
|
+
: memory.last_accessed
|
|
1149
|
+
: null,
|
|
1150
|
+
created_at: typeof memory.created_at === 'object'
|
|
1151
|
+
? memory.created_at.toISOString()
|
|
1152
|
+
: memory.created_at,
|
|
1153
|
+
}, new Date());
|
|
1154
|
+
await this.updatePriorityScore(memoryId, score);
|
|
1155
|
+
}
|
|
1156
|
+
catch {
|
|
1157
|
+
// Non-fatal: priority_score recomputation failure shouldn't break writes
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
/** Filter out pinned memory IDs from a list (for bulk operations) */
|
|
1161
|
+
async _filterOutPinned(ids) {
|
|
1162
|
+
const { isPinned } = await import('../working-memory-pipeline.js');
|
|
1163
|
+
const safe = [];
|
|
1164
|
+
for (const id of ids) {
|
|
1165
|
+
const memory = await this.getMemoryById(id);
|
|
1166
|
+
if (!memory) {
|
|
1167
|
+
safe.push(id);
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (!isPinned({
|
|
1171
|
+
is_invariant: !!memory.is_invariant,
|
|
1172
|
+
correction_count: memory.correction_count ?? 0,
|
|
1173
|
+
})) {
|
|
1174
|
+
safe.push(id);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return safe;
|
|
1178
|
+
}
|
|
1179
|
+
/** Throw PinnedMemoryError if the memory is pinned (Tier 1 immutability) */
|
|
1180
|
+
async _guardPinned(memoryId) {
|
|
1181
|
+
const memory = await this.getMemoryById(memoryId);
|
|
1182
|
+
if (!memory)
|
|
1183
|
+
return;
|
|
1184
|
+
const { isPinned, PinnedMemoryError } = await import('../working-memory-pipeline.js');
|
|
1185
|
+
if (isPinned({
|
|
1186
|
+
is_invariant: !!memory.is_invariant,
|
|
1187
|
+
correction_count: memory.correction_count ?? 0,
|
|
1188
|
+
})) {
|
|
1189
|
+
throw new PinnedMemoryError(memoryId);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
async getAllMemoriesForRetention() {
|
|
1193
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1194
|
+
return this.postgres.getAllMemoriesForRetention();
|
|
1195
|
+
const sqlite = await this.getSqliteFns();
|
|
1196
|
+
return sqlite.getAllMemoriesForRetention();
|
|
1197
|
+
}
|
|
1198
|
+
// ===========================================================================
|
|
1199
|
+
// BM25 Index Management
|
|
1200
|
+
// ===========================================================================
|
|
1201
|
+
async invalidateCodeBm25Index() {
|
|
1202
|
+
const s = await this.getSqliteFns();
|
|
1203
|
+
s.invalidateCodeBm25Index();
|
|
1204
|
+
}
|
|
1205
|
+
async invalidateDocsBm25Index() {
|
|
1206
|
+
const s = await this.getSqliteFns();
|
|
1207
|
+
s.invalidateDocsBm25Index();
|
|
1208
|
+
}
|
|
1209
|
+
async invalidateMemoriesBm25Index() {
|
|
1210
|
+
const s = await this.getSqliteFns();
|
|
1211
|
+
s.invalidateMemoriesBm25Index();
|
|
1212
|
+
}
|
|
1213
|
+
async invalidateGlobalMemoriesBm25Index() {
|
|
1214
|
+
const s = await this.getSqliteFns();
|
|
1215
|
+
s.invalidateGlobalMemoriesBm25Index();
|
|
1216
|
+
}
|
|
1217
|
+
async invalidateBM25Index() {
|
|
1218
|
+
const s = await this.getSqliteFns();
|
|
1219
|
+
s.invalidateBM25Index();
|
|
1220
|
+
}
|
|
1221
|
+
async updateCodeBm25Index(docId, content, symbolName, signature) {
|
|
1222
|
+
const s = await this.getSqliteFns();
|
|
1223
|
+
s.updateCodeBm25Index(docId, content, symbolName, signature);
|
|
1224
|
+
}
|
|
1225
|
+
async updateMemoriesBm25Index(memoryId, content) {
|
|
1226
|
+
const s = await this.getSqliteFns();
|
|
1227
|
+
s.updateMemoriesBm25Index(memoryId, content);
|
|
1228
|
+
}
|
|
1229
|
+
async updateGlobalMemoriesBm25Index(memoryId, content) {
|
|
1230
|
+
const s = await this.getSqliteFns();
|
|
1231
|
+
s.updateGlobalMemoriesBm25Index(memoryId, content);
|
|
1232
|
+
}
|
|
1233
|
+
// ===========================================================================
|
|
1234
|
+
// Hybrid Search — Approach C (BM25 + dense + RRF in single Qdrant call)
|
|
1235
|
+
// ===========================================================================
|
|
1236
|
+
async hybridSearchCode(query, queryEmbedding, limit, threshold, alpha, filters) {
|
|
1237
|
+
this._sessionCounters.codeSearchQueries++;
|
|
1238
|
+
const lim = limit ?? 10;
|
|
1239
|
+
const thresh = threshold ?? 0.3;
|
|
1240
|
+
// Overfetch only when regex post-filter is needed (symbolType is filtered natively by Qdrant/PG)
|
|
1241
|
+
const hasRegex = !!filters?.regex;
|
|
1242
|
+
const fetchLimit = hasRegex ? lim * 3 : lim;
|
|
1243
|
+
const regexFilter = hasRegex ? { regex: filters.regex } : undefined;
|
|
1244
|
+
if (this.hasQdrant()) {
|
|
1245
|
+
try {
|
|
1246
|
+
const qdrantResults = await this.qdrant.hybridSearchDocuments(query, queryEmbedding, fetchLimit, thresh, { codeOnly: true, symbolType: filters?.symbolType });
|
|
1247
|
+
if (qdrantResults.length > 0)
|
|
1248
|
+
return this.applyCodeFilters(qdrantResults, regexFilter, lim);
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
this._warnQdrantFailure('hybridSearchCode failed, falling back', error);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1255
|
+
const results = (await this.postgres.searchDocuments(queryEmbedding, fetchLimit, thresh, {
|
|
1256
|
+
codeOnly: true,
|
|
1257
|
+
symbolType: filters?.symbolType,
|
|
1258
|
+
})).map((r) => ({ ...r, bm25Score: 0, vectorScore: r.similarity }));
|
|
1259
|
+
return this.applyCodeFilters(results, regexFilter, lim);
|
|
1260
|
+
}
|
|
1261
|
+
const sqlite = await this.getSqliteFns();
|
|
1262
|
+
return sqlite.hybridSearchCode(query, queryEmbedding, limit, threshold, alpha, filters);
|
|
1263
|
+
}
|
|
1264
|
+
async hybridSearchDocs(query, queryEmbedding, limit, threshold, alpha) {
|
|
1265
|
+
this._sessionCounters.searchQueries++;
|
|
1266
|
+
const lim = limit ?? 10;
|
|
1267
|
+
const thresh = threshold ?? 0.3;
|
|
1268
|
+
if (this.hasQdrant()) {
|
|
1269
|
+
try {
|
|
1270
|
+
const results = await this.qdrant.hybridSearchDocuments(query, queryEmbedding, lim, thresh, { docsOnly: true });
|
|
1271
|
+
if (results.length > 0)
|
|
1272
|
+
return results;
|
|
1273
|
+
}
|
|
1274
|
+
catch (error) {
|
|
1275
|
+
this._warnQdrantFailure('hybridSearchDocs failed, falling back', error);
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1279
|
+
return (await this.postgres.searchDocuments(queryEmbedding, lim, thresh, { docsOnly: true })).map((r) => ({ ...r, bm25Score: 0, vectorScore: r.similarity }));
|
|
1280
|
+
}
|
|
1281
|
+
const sqlite = await this.getSqliteFns();
|
|
1282
|
+
return sqlite.hybridSearchDocs(query, queryEmbedding, limit, threshold, alpha);
|
|
1283
|
+
}
|
|
1284
|
+
async hybridSearchMemories(query, queryEmbedding, limit, threshold, alpha) {
|
|
1285
|
+
this._sessionCounters.recallQueries++;
|
|
1286
|
+
const lim = limit ?? 10;
|
|
1287
|
+
const thresh = threshold ?? 0.3;
|
|
1288
|
+
if (this.hasQdrant()) {
|
|
1289
|
+
try {
|
|
1290
|
+
const results = await this.qdrant.hybridSearchMemories(query, queryEmbedding, lim, thresh, { projectId: this.qdrant.getProjectId() });
|
|
1291
|
+
if (results.length > 0)
|
|
1292
|
+
return results;
|
|
1293
|
+
}
|
|
1294
|
+
catch (error) {
|
|
1295
|
+
this._warnQdrantFailure('hybridSearchMemories failed, falling back', error);
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1299
|
+
return this.postgres.searchMemories(queryEmbedding, lim, thresh);
|
|
1300
|
+
const sqlite = await this.getSqliteFns();
|
|
1301
|
+
return sqlite.hybridSearchMemories(query, queryEmbedding, limit, threshold, alpha);
|
|
1302
|
+
}
|
|
1303
|
+
async hybridSearchGlobalMemories(query, queryEmbedding, limit, threshold, alpha, tags, since) {
|
|
1304
|
+
const lim = limit ?? 10;
|
|
1305
|
+
const thresh = threshold ?? 0.3;
|
|
1306
|
+
if (this.hasQdrant()) {
|
|
1307
|
+
try {
|
|
1308
|
+
const results = await this.qdrant.hybridSearchGlobalMemories(query, queryEmbedding, lim, thresh, { tags, since });
|
|
1309
|
+
if (results.length > 0)
|
|
1310
|
+
return results;
|
|
1311
|
+
}
|
|
1312
|
+
catch (error) {
|
|
1313
|
+
this._warnQdrantFailure('hybridSearchGlobalMemories failed, falling back', error);
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1317
|
+
return this.postgres.searchGlobalMemories(queryEmbedding, lim, thresh, tags);
|
|
1318
|
+
const sqlite = await this.getSqliteFns();
|
|
1319
|
+
return sqlite.hybridSearchGlobalMemories(query, queryEmbedding, limit, threshold, alpha, tags, since);
|
|
1320
|
+
}
|
|
1321
|
+
/** Post-filter code search results by regex and/or symbolType, then trim to limit. */
|
|
1322
|
+
applyCodeFilters(results, filters, limit) {
|
|
1323
|
+
if (!filters)
|
|
1324
|
+
return results.slice(0, limit);
|
|
1325
|
+
let regexFilter = null;
|
|
1326
|
+
if (filters.regex && filters.regex.length <= 500) {
|
|
1327
|
+
try {
|
|
1328
|
+
regexFilter = new RegExp(filters.regex, 'i');
|
|
1329
|
+
}
|
|
1330
|
+
catch {
|
|
1331
|
+
/* invalid regex — skip */
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const filtered = [];
|
|
1335
|
+
for (const r of results) {
|
|
1336
|
+
if (filters.symbolType && r.symbol_type !== filters.symbolType)
|
|
1337
|
+
continue;
|
|
1338
|
+
if (regexFilter && !regexFilter.test(r.content))
|
|
1339
|
+
continue;
|
|
1340
|
+
filtered.push(r);
|
|
1341
|
+
if (filtered.length >= limit)
|
|
1342
|
+
break;
|
|
1343
|
+
}
|
|
1344
|
+
return filtered;
|
|
1345
|
+
}
|
|
1346
|
+
// ===========================================================================
|
|
1347
|
+
// Global Memory Operations
|
|
1348
|
+
// ===========================================================================
|
|
1349
|
+
async saveGlobalMemory(content, embedding, tags = [], source, options) {
|
|
1350
|
+
const type = options?.type ?? 'observation';
|
|
1351
|
+
const deduplicate = options?.deduplicate ?? true;
|
|
1352
|
+
const qualityScore = options?.qualityScore;
|
|
1353
|
+
const qualityFactors = options?.qualityFactors;
|
|
1354
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1355
|
+
if (deduplicate) {
|
|
1356
|
+
const similar = await this.findSimilarGlobalMemory(embedding, 0.95);
|
|
1357
|
+
if (similar) {
|
|
1358
|
+
this._sessionCounters.memoriesDuplicated++;
|
|
1359
|
+
return { id: similar.id, created: false, duplicate: similar };
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const id = await this.postgres.saveGlobalMemory(content, embedding, tags, source, type, qualityScore, qualityFactors);
|
|
1363
|
+
if (this.hasQdrant()) {
|
|
1364
|
+
try {
|
|
1365
|
+
await this.qdrant.upsertGlobalMemoryWithPayload(id, embedding, {
|
|
1366
|
+
content,
|
|
1367
|
+
tags,
|
|
1368
|
+
source,
|
|
1369
|
+
type,
|
|
1370
|
+
createdAt: new Date().toISOString(),
|
|
1371
|
+
});
|
|
1372
|
+
}
|
|
1373
|
+
catch (error) {
|
|
1374
|
+
this._warnQdrantFailure(`Failed to sync global memory vector ${id}`, error);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
this._sessionCounters.globalMemoriesCreated++;
|
|
1378
|
+
return { id, created: true };
|
|
1379
|
+
}
|
|
1380
|
+
const sqlite = await this.getSqliteFns();
|
|
1381
|
+
const result = sqlite.saveGlobalMemory(content, embedding, tags, source, undefined, {
|
|
1382
|
+
type,
|
|
1383
|
+
deduplicate,
|
|
1384
|
+
});
|
|
1385
|
+
if (this.hasQdrant() && !result.isDuplicate) {
|
|
1386
|
+
try {
|
|
1387
|
+
await this.qdrant.upsertGlobalMemoryWithPayload(result.id, embedding, {
|
|
1388
|
+
content,
|
|
1389
|
+
tags,
|
|
1390
|
+
source,
|
|
1391
|
+
type,
|
|
1392
|
+
createdAt: new Date().toISOString(),
|
|
1393
|
+
});
|
|
1394
|
+
}
|
|
1395
|
+
catch (error) {
|
|
1396
|
+
this._warnQdrantFailure(`Failed to sync global memory vector ${result.id}`, error);
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
if (!result.isDuplicate) {
|
|
1400
|
+
this._sessionCounters.globalMemoriesCreated++;
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
this._sessionCounters.memoriesDuplicated++;
|
|
1404
|
+
}
|
|
1405
|
+
return {
|
|
1406
|
+
id: result.id,
|
|
1407
|
+
created: !result.isDuplicate,
|
|
1408
|
+
duplicate: result.isDuplicate && result.similarity != null
|
|
1409
|
+
? { id: result.id, content: '', similarity: result.similarity }
|
|
1410
|
+
: undefined,
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
async searchGlobalMemories(queryEmbedding, limit = 5, threshold = 0.3, tags) {
|
|
1414
|
+
if (this.hasQdrant()) {
|
|
1415
|
+
try {
|
|
1416
|
+
const results = await this.qdrant.hybridSearchGlobalMemories('', queryEmbedding, limit, threshold, { tags });
|
|
1417
|
+
if (results.length > 0)
|
|
1418
|
+
return results;
|
|
1419
|
+
}
|
|
1420
|
+
catch (error) {
|
|
1421
|
+
this._warnQdrantFailure('searchGlobalMemories failed, falling back', error);
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1425
|
+
return this.postgres.searchGlobalMemories(queryEmbedding, limit, threshold, tags);
|
|
1426
|
+
const sqlite = await this.getSqliteFns();
|
|
1427
|
+
return sqlite.searchGlobalMemories(queryEmbedding, limit, threshold, tags);
|
|
1428
|
+
}
|
|
1429
|
+
async getRecentGlobalMemories(limit = 10) {
|
|
1430
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1431
|
+
return this.postgres.getRecentGlobalMemories(limit);
|
|
1432
|
+
const sqlite = await this.getSqliteFns();
|
|
1433
|
+
return sqlite.getRecentGlobalMemories(limit);
|
|
1434
|
+
}
|
|
1435
|
+
async deleteGlobalMemory(id) {
|
|
1436
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1437
|
+
const deleted = await this.postgres.deleteGlobalMemory(id);
|
|
1438
|
+
if (deleted && this.vectorBackend === 'qdrant' && this.qdrant)
|
|
1439
|
+
await this.qdrant.deleteGlobalMemoryVector(id);
|
|
1440
|
+
return deleted;
|
|
1441
|
+
}
|
|
1442
|
+
const sqlite = await this.getSqliteFns();
|
|
1443
|
+
const deleted = sqlite.deleteGlobalMemory(id);
|
|
1444
|
+
if (deleted && this.vectorBackend === 'qdrant' && this.qdrant) {
|
|
1445
|
+
try {
|
|
1446
|
+
await this.qdrant.deleteGlobalMemoryVector(id);
|
|
1447
|
+
}
|
|
1448
|
+
catch (err) {
|
|
1449
|
+
logWarn('storage', `Qdrant global vector delete failed for memory ${id}`, {
|
|
1450
|
+
error: String(err),
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
return deleted;
|
|
1455
|
+
}
|
|
1456
|
+
async findSimilarGlobalMemory(embedding, threshold) {
|
|
1457
|
+
const thresh = threshold ?? 0.92;
|
|
1458
|
+
if (this.vectorBackend === 'qdrant' && this.qdrant) {
|
|
1459
|
+
try {
|
|
1460
|
+
const results = await this.qdrant.findSimilarWithContent('global_memories', embedding, 3, thresh);
|
|
1461
|
+
if (results.length > 0 && results[0].similarity >= thresh) {
|
|
1462
|
+
return {
|
|
1463
|
+
id: results[0].id,
|
|
1464
|
+
content: results[0].content,
|
|
1465
|
+
similarity: results[0].similarity,
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
if (results.length === 0 && this.backend === 'postgresql' && this.postgres) {
|
|
1469
|
+
const qr = await this.qdrant.searchGlobalMemories(embedding, 3, thresh);
|
|
1470
|
+
if (qr.length > 0) {
|
|
1471
|
+
const pgRows = await this.postgres.getMemoriesByIds(qr.map((r) => r.id), { excludeInvalidated: false });
|
|
1472
|
+
if (pgRows.length > 0) {
|
|
1473
|
+
const score = qr.find((r) => r.id === pgRows[0].id)?.similarity ?? 0;
|
|
1474
|
+
if (score >= thresh)
|
|
1475
|
+
return { id: pgRows[0].id, content: pgRows[0].content, similarity: score };
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
catch (error) {
|
|
1481
|
+
this._warnQdrantFailure('findSimilarGlobalMemory failed, falling back', error);
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1485
|
+
return this.postgres.findSimilarGlobalMemory(embedding, threshold);
|
|
1486
|
+
const sqlite = await this.getSqliteFns();
|
|
1487
|
+
return sqlite.findSimilarGlobalMemory(embedding, threshold);
|
|
1488
|
+
}
|
|
1489
|
+
async getGlobalMemoryStats() {
|
|
1490
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1491
|
+
return this.postgres.getGlobalMemoryStats();
|
|
1492
|
+
const sqlite = await this.getSqliteFns();
|
|
1493
|
+
return sqlite.getGlobalMemoryStats();
|
|
1494
|
+
}
|
|
1495
|
+
// ===========================================================================
|
|
1496
|
+
// Skills
|
|
1497
|
+
// ===========================================================================
|
|
1498
|
+
async upsertSkill(skill) {
|
|
1499
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1500
|
+
return this.postgres.upsertSkill({
|
|
1501
|
+
...skill,
|
|
1502
|
+
cacheExpires: skill.cacheExpires ? new Date(skill.cacheExpires) : undefined,
|
|
1503
|
+
});
|
|
1504
|
+
}
|
|
1505
|
+
const { upsertSkill } = await import('../db/skills.js');
|
|
1506
|
+
return upsertSkill(skill);
|
|
1507
|
+
}
|
|
1508
|
+
async getAllSkills() {
|
|
1509
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1510
|
+
const rows = await this.postgres.getAllSkills();
|
|
1511
|
+
return rows.map((r) => ({
|
|
1512
|
+
...r,
|
|
1513
|
+
lastUsed: r.lastUsed ? String(r.lastUsed) : undefined,
|
|
1514
|
+
usageCount: r.usageCount,
|
|
1515
|
+
}));
|
|
1516
|
+
}
|
|
1517
|
+
const { getAllSkills } = await import('../db/skills.js');
|
|
1518
|
+
const rows = getAllSkills();
|
|
1519
|
+
return rows.map((r) => ({
|
|
1520
|
+
id: r.id,
|
|
1521
|
+
name: r.name,
|
|
1522
|
+
description: r.description,
|
|
1523
|
+
source: r.source,
|
|
1524
|
+
path: r.path,
|
|
1525
|
+
content: r.content,
|
|
1526
|
+
skyllId: r.skyll_id,
|
|
1527
|
+
usageCount: r.usage_count ?? 0,
|
|
1528
|
+
lastUsed: r.last_used,
|
|
1529
|
+
}));
|
|
1530
|
+
}
|
|
1531
|
+
async searchSkills(query, limit = 10) {
|
|
1532
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1533
|
+
return this.postgres.searchSkills(query, limit);
|
|
1534
|
+
const { searchSkills } = await import('../db/skills.js');
|
|
1535
|
+
const rows = searchSkills(query, limit);
|
|
1536
|
+
return rows.map((r) => ({
|
|
1537
|
+
id: r.id,
|
|
1538
|
+
name: r.name,
|
|
1539
|
+
description: r.description,
|
|
1540
|
+
source: r.source,
|
|
1541
|
+
path: r.path,
|
|
1542
|
+
usageCount: r.usage_count ?? 0,
|
|
1543
|
+
}));
|
|
1544
|
+
}
|
|
1545
|
+
async getSkillByName(name) {
|
|
1546
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1547
|
+
return this.postgres.getSkillByName(name);
|
|
1548
|
+
const { getSkillByName } = await import('../db/skills.js');
|
|
1549
|
+
const row = getSkillByName(name);
|
|
1550
|
+
if (!row)
|
|
1551
|
+
return null;
|
|
1552
|
+
return {
|
|
1553
|
+
id: row.id,
|
|
1554
|
+
name: row.name,
|
|
1555
|
+
description: row.description,
|
|
1556
|
+
source: row.source,
|
|
1557
|
+
path: row.path,
|
|
1558
|
+
content: row.content,
|
|
1559
|
+
skyllId: row.skyll_id,
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
async trackSkillUsage(name) {
|
|
1563
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1564
|
+
return this.postgres.trackSkillUsage(name);
|
|
1565
|
+
const { trackSkillUsage } = await import('../db/skills.js');
|
|
1566
|
+
trackSkillUsage(name);
|
|
1567
|
+
}
|
|
1568
|
+
async deleteSkill(name) {
|
|
1569
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1570
|
+
return this.postgres.deleteSkill(name);
|
|
1571
|
+
const { deleteSkill } = await import('../db/skills.js');
|
|
1572
|
+
return deleteSkill(name);
|
|
1573
|
+
}
|
|
1574
|
+
async clearExpiredSkyllCache() {
|
|
1575
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1576
|
+
return this.postgres.clearExpiredSkyllCache();
|
|
1577
|
+
const { clearExpiredSkyllCache } = await import('../db/skills.js');
|
|
1578
|
+
return clearExpiredSkyllCache();
|
|
1579
|
+
}
|
|
1580
|
+
async getCachedSkyllSkill(skyllId) {
|
|
1581
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1582
|
+
return this.postgres.getCachedSkyllSkill(skyllId);
|
|
1583
|
+
const { getCachedSkyllSkill } = await import('../db/skills.js');
|
|
1584
|
+
return getCachedSkyllSkill(skyllId);
|
|
1585
|
+
}
|
|
1586
|
+
async getSkyllCacheStats() {
|
|
1587
|
+
if (this.backend === 'postgresql' && this.postgres)
|
|
1588
|
+
return this.postgres.getSkyllCacheStats();
|
|
1589
|
+
const { getSkyllCacheStats } = await import('../db/skills.js');
|
|
1590
|
+
return getSkyllCacheStats();
|
|
1591
|
+
}
|
|
1592
|
+
// ===========================================================================
|
|
1593
|
+
// Bulk Export (for checkpoint, graph-export)
|
|
1594
|
+
// ===========================================================================
|
|
1595
|
+
async getAllMemoriesForExport() {
|
|
1596
|
+
const { getAllMemoriesForExportImpl } = await import('./dispatcher-export.js');
|
|
1597
|
+
return getAllMemoriesForExportImpl(this);
|
|
1598
|
+
}
|
|
1599
|
+
async getAllDocumentsForExport() {
|
|
1600
|
+
const { getAllDocumentsForExportImpl } = await import('./dispatcher-export.js');
|
|
1601
|
+
return getAllDocumentsForExportImpl(this);
|
|
1602
|
+
}
|
|
1603
|
+
async getAllMemoryLinksForExport() {
|
|
1604
|
+
const { getAllMemoryLinksForExportImpl } = await import('./dispatcher-export.js');
|
|
1605
|
+
return getAllMemoryLinksForExportImpl(this);
|
|
1606
|
+
}
|
|
1607
|
+
async getAllCentralityForExport() {
|
|
1608
|
+
const { getAllCentralityForExportImpl } = await import('./dispatcher-export.js');
|
|
1609
|
+
return getAllCentralityForExportImpl(this);
|
|
1610
|
+
}
|
|
1611
|
+
// ===========================================================================
|
|
1612
|
+
// Learning Deltas
|
|
1613
|
+
// ===========================================================================
|
|
1614
|
+
async appendLearningDelta(delta) {
|
|
1615
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1616
|
+
const pool = await this.postgres.getPool();
|
|
1617
|
+
await pool.query(`INSERT INTO learning_deltas (project_id, timestamp, source, memories_before, memories_after, new_memories, types_added, avg_quality)
|
|
1618
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [
|
|
1619
|
+
this.postgres.getProjectId(),
|
|
1620
|
+
delta.timestamp,
|
|
1621
|
+
delta.source,
|
|
1622
|
+
delta.memoriesBefore,
|
|
1623
|
+
delta.memoriesAfter,
|
|
1624
|
+
delta.newMemories,
|
|
1625
|
+
Object.keys(delta.typesAdded).length > 0 ? JSON.stringify(delta.typesAdded) : null,
|
|
1626
|
+
delta.avgQualityOfNew ?? null,
|
|
1627
|
+
]);
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
const sqlite = await this.getSqliteFns();
|
|
1631
|
+
const db = sqlite.getDb();
|
|
1632
|
+
db.prepare(`INSERT INTO learning_deltas (timestamp, source, memories_before, memories_after, new_memories, types_added, avg_quality)
|
|
1633
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`).run(delta.timestamp, delta.source, delta.memoriesBefore, delta.memoriesAfter, delta.newMemories, Object.keys(delta.typesAdded).length > 0 ? JSON.stringify(delta.typesAdded) : null, delta.avgQualityOfNew ?? null);
|
|
1634
|
+
}
|
|
1635
|
+
async appendRawLearningDelta(text) {
|
|
1636
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1637
|
+
const pool = await this.postgres.getPool();
|
|
1638
|
+
await pool.query(`INSERT INTO learning_deltas (project_id, timestamp, source, memories_before, memories_after, new_memories)
|
|
1639
|
+
VALUES ($1, $2, $3, 0, 0, 0)`, [this.postgres.getProjectId(), new Date().toISOString(), text]);
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
const sqlite = await this.getSqliteFns();
|
|
1643
|
+
const db = sqlite.getDb();
|
|
1644
|
+
db.prepare(`INSERT INTO learning_deltas (timestamp, source, memories_before, memories_after, new_memories)
|
|
1645
|
+
VALUES (?, ?, 0, 0, 0)`).run(new Date().toISOString(), text);
|
|
1646
|
+
}
|
|
1647
|
+
async getLearningDeltas(options = {}) {
|
|
1648
|
+
const limit = options.limit && options.limit > 0 ? options.limit : 20;
|
|
1649
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1650
|
+
const pool = await this.postgres.getPool();
|
|
1651
|
+
const params = [this.postgres.getProjectId()];
|
|
1652
|
+
let sql = `SELECT id, timestamp::text as timestamp, source, memories_before, memories_after,
|
|
1653
|
+
new_memories, types_added, avg_quality, created_at::text as created_at
|
|
1654
|
+
FROM learning_deltas WHERE LOWER(project_id) = $1`;
|
|
1655
|
+
if (options.since) {
|
|
1656
|
+
params.push(options.since);
|
|
1657
|
+
sql += ` AND timestamp >= $${params.length}`;
|
|
1658
|
+
}
|
|
1659
|
+
params.push(limit);
|
|
1660
|
+
sql += ` ORDER BY timestamp DESC LIMIT $${params.length}`;
|
|
1661
|
+
const { rows } = await pool.query(sql, params);
|
|
1662
|
+
return rows;
|
|
1663
|
+
}
|
|
1664
|
+
const sqlite = await this.getSqliteFns();
|
|
1665
|
+
const db = sqlite.getDb();
|
|
1666
|
+
const params = [];
|
|
1667
|
+
let sql = 'SELECT * FROM learning_deltas';
|
|
1668
|
+
if (options.since) {
|
|
1669
|
+
sql += ' WHERE timestamp >= ?';
|
|
1670
|
+
params.push(options.since);
|
|
1671
|
+
}
|
|
1672
|
+
sql += ' ORDER BY timestamp DESC LIMIT ?';
|
|
1673
|
+
params.push(limit);
|
|
1674
|
+
return db.prepare(sql).all(...params);
|
|
1675
|
+
}
|
|
1676
|
+
// ===========================================================================
|
|
1677
|
+
// AI Readiness Stats
|
|
1678
|
+
// ===========================================================================
|
|
1679
|
+
async getCodeFileCount() {
|
|
1680
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1681
|
+
const pool = await this.postgres.getPool();
|
|
1682
|
+
const { rows } = await pool.query(`SELECT COUNT(DISTINCT file_path) as count FROM documents WHERE LOWER(project_id) = $1 AND file_path LIKE 'code:%'`, [this.postgres.getProjectId()]);
|
|
1683
|
+
return parseInt(rows[0]?.count ?? '0');
|
|
1684
|
+
}
|
|
1685
|
+
const sqlite = await this.getSqliteFns();
|
|
1686
|
+
const db = sqlite.getDb();
|
|
1687
|
+
const row = db
|
|
1688
|
+
.prepare(`SELECT COUNT(DISTINCT file_path) as count FROM documents WHERE file_path LIKE 'code:%'`)
|
|
1689
|
+
.get();
|
|
1690
|
+
return row.count;
|
|
1691
|
+
}
|
|
1692
|
+
async getDocsFileCount() {
|
|
1693
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1694
|
+
const pool = await this.postgres.getPool();
|
|
1695
|
+
const { rows } = await pool.query(`SELECT COUNT(DISTINCT file_path) as count FROM documents WHERE LOWER(project_id) = $1 AND file_path NOT LIKE 'code:%'`, [this.postgres.getProjectId()]);
|
|
1696
|
+
return parseInt(rows[0]?.count ?? '0');
|
|
1697
|
+
}
|
|
1698
|
+
const sqlite = await this.getSqliteFns();
|
|
1699
|
+
const db = sqlite.getDb();
|
|
1700
|
+
const row = db
|
|
1701
|
+
.prepare(`SELECT COUNT(DISTINCT file_path) as count FROM documents WHERE file_path NOT LIKE 'code:%'`)
|
|
1702
|
+
.get();
|
|
1703
|
+
return row.count;
|
|
1704
|
+
}
|
|
1705
|
+
async getAverageMemoryQuality() {
|
|
1706
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1707
|
+
const pool = await this.postgres.getPool();
|
|
1708
|
+
const { rows } = await pool.query(`SELECT AVG(quality_score) as avg, COUNT(*) as count FROM memories WHERE LOWER(project_id) = $1 AND quality_score IS NOT NULL`, [this.postgres.getProjectId()]);
|
|
1709
|
+
return {
|
|
1710
|
+
avg: rows[0]?.avg ? parseFloat(rows[0].avg) : null,
|
|
1711
|
+
count: parseInt(rows[0]?.count ?? '0'),
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
const sqlite = await this.getSqliteFns();
|
|
1715
|
+
const db = sqlite.getDb();
|
|
1716
|
+
const row = db
|
|
1717
|
+
.prepare(`SELECT AVG(quality_score) as avg, COUNT(*) as count FROM memories WHERE quality_score IS NOT NULL`)
|
|
1718
|
+
.get();
|
|
1719
|
+
return { avg: row.avg, count: row.count };
|
|
1720
|
+
}
|
|
1721
|
+
// ===========================================================================
|
|
1722
|
+
// Filtered Memory Export (for agents-md-generator)
|
|
1723
|
+
// ===========================================================================
|
|
1724
|
+
async getMemoriesForAgentsExport(options) {
|
|
1725
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1726
|
+
const pool = await this.postgres.getPool();
|
|
1727
|
+
const typePlaceholders = options.types.map((_, i) => `$${i + 2}`).join(', ');
|
|
1728
|
+
const { rows } = await pool.query(`SELECT id, content, type, tags, source, quality_score, created_at::text as created_at
|
|
1729
|
+
FROM memories
|
|
1730
|
+
WHERE LOWER(project_id) = $1
|
|
1731
|
+
AND invalidated_by IS NULL
|
|
1732
|
+
AND type IN (${typePlaceholders})
|
|
1733
|
+
AND (quality_score IS NULL OR quality_score >= $${options.types.length + 2})
|
|
1734
|
+
ORDER BY
|
|
1735
|
+
CASE type
|
|
1736
|
+
WHEN 'dead_end' THEN 0 WHEN 'decision' THEN 1
|
|
1737
|
+
WHEN 'pattern' THEN 2 WHEN 'learning' THEN 3 ELSE 4
|
|
1738
|
+
END,
|
|
1739
|
+
quality_score DESC NULLS LAST
|
|
1740
|
+
LIMIT $${options.types.length + 3}`, [this.postgres.getProjectId(), ...options.types, options.minQuality, options.limit]);
|
|
1741
|
+
return rows.map((r) => ({
|
|
1742
|
+
...r,
|
|
1743
|
+
tags: typeof r.tags === 'string' ? JSON.parse(r.tags) : (r.tags ?? []),
|
|
1744
|
+
}));
|
|
1745
|
+
}
|
|
1746
|
+
const sqlite = await this.getSqliteFns();
|
|
1747
|
+
const db = sqlite.getDb();
|
|
1748
|
+
const typePlaceholders = options.types.map(() => '?').join(', ');
|
|
1749
|
+
const rows = db
|
|
1750
|
+
.prepare(`SELECT id, content, type, tags, source, quality_score, created_at
|
|
1751
|
+
FROM memories
|
|
1752
|
+
WHERE invalidated_by IS NULL
|
|
1753
|
+
AND type IN (${typePlaceholders})
|
|
1754
|
+
AND (quality_score IS NULL OR quality_score >= ?)
|
|
1755
|
+
ORDER BY
|
|
1756
|
+
CASE type
|
|
1757
|
+
WHEN 'dead_end' THEN 0 WHEN 'decision' THEN 1
|
|
1758
|
+
WHEN 'pattern' THEN 2 WHEN 'learning' THEN 3 ELSE 4
|
|
1759
|
+
END,
|
|
1760
|
+
quality_score DESC
|
|
1761
|
+
LIMIT ?`)
|
|
1762
|
+
.all(...options.types, options.minQuality, options.limit);
|
|
1763
|
+
return rows.map((r) => ({
|
|
1764
|
+
...r,
|
|
1765
|
+
tags: r.tags ? JSON.parse(r.tags) : [],
|
|
1766
|
+
}));
|
|
1767
|
+
}
|
|
1768
|
+
// ===========================================================================
|
|
1769
|
+
// Consolidation Helpers
|
|
1770
|
+
// ===========================================================================
|
|
1771
|
+
async getAllMemoriesWithEmbeddings(options) {
|
|
1772
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1773
|
+
const rows = await this.postgres.getAllMemoriesWithEmbeddings();
|
|
1774
|
+
let result = rows.map((r) => ({
|
|
1775
|
+
id: r.id,
|
|
1776
|
+
content: r.content,
|
|
1777
|
+
tags: typeof r.tags === 'string' ? JSON.parse(r.tags) : (r.tags ?? []),
|
|
1778
|
+
source: r.source ?? null,
|
|
1779
|
+
embedding: r.embedding ?? null,
|
|
1780
|
+
type: r.type ?? null,
|
|
1781
|
+
quality_score: r.qualityScore ?? r.quality_score ?? null,
|
|
1782
|
+
created_at: r.createdAt ?? r.created_at ?? new Date().toISOString(),
|
|
1783
|
+
invalidated_by: r.invalidatedBy ?? r.invalidated_by ?? null,
|
|
1784
|
+
}));
|
|
1785
|
+
if (options?.excludeInvalidated)
|
|
1786
|
+
result = result.filter((r) => r.invalidated_by == null);
|
|
1787
|
+
if (options?.types?.length)
|
|
1788
|
+
result = result.filter((r) => r.type != null && options.types.includes(r.type));
|
|
1789
|
+
return result;
|
|
1790
|
+
}
|
|
1791
|
+
const sqlite = await this.getSqliteFns();
|
|
1792
|
+
const db = sqlite.getDb();
|
|
1793
|
+
let sql = `SELECT id, content, tags, source, embedding, type, quality_score, created_at, invalidated_by FROM memories`;
|
|
1794
|
+
const conditions = [];
|
|
1795
|
+
if (options?.excludeInvalidated)
|
|
1796
|
+
conditions.push('invalidated_by IS NULL');
|
|
1797
|
+
if (options?.types?.length) {
|
|
1798
|
+
const placeholders = options.types.map(() => '?').join(', ');
|
|
1799
|
+
conditions.push(`type IN (${placeholders})`);
|
|
1800
|
+
}
|
|
1801
|
+
if (conditions.length)
|
|
1802
|
+
sql += ' WHERE ' + conditions.join(' AND ');
|
|
1803
|
+
sql += ' ORDER BY id ASC';
|
|
1804
|
+
const params = options?.types?.length ? options.types : [];
|
|
1805
|
+
const rows = db.prepare(sql).all(...params);
|
|
1806
|
+
return rows.map((row) => ({
|
|
1807
|
+
id: row.id,
|
|
1808
|
+
content: row.content,
|
|
1809
|
+
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
1810
|
+
source: row.source,
|
|
1811
|
+
embedding: row.embedding
|
|
1812
|
+
? Array.from(new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4))
|
|
1813
|
+
: null,
|
|
1814
|
+
type: row.type,
|
|
1815
|
+
quality_score: row.quality_score,
|
|
1816
|
+
created_at: row.created_at,
|
|
1817
|
+
invalidated_by: row.invalidated_by ?? null,
|
|
1818
|
+
}));
|
|
1819
|
+
}
|
|
1820
|
+
async getMemoryEmbeddingsByIds(ids) {
|
|
1821
|
+
if (ids.length === 0)
|
|
1822
|
+
return new Map();
|
|
1823
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1824
|
+
const pool = await this.postgres.getPool();
|
|
1825
|
+
const placeholders = ids.map((_, i) => `$${i + 1}`).join(',');
|
|
1826
|
+
const { rows } = await pool.query(`SELECT id, embedding::text FROM memories WHERE id IN (${placeholders})`, ids);
|
|
1827
|
+
const map = new Map();
|
|
1828
|
+
for (const row of rows) {
|
|
1829
|
+
if (row.embedding) {
|
|
1830
|
+
// pgvector returns embedding as string "[0.1,0.2,...]"
|
|
1831
|
+
const vec = JSON.parse(row.embedding.replace(/^\[/, '[').replace(/\]$/, ']'));
|
|
1832
|
+
map.set(row.id, vec);
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1835
|
+
return map;
|
|
1836
|
+
}
|
|
1837
|
+
const sqlite = await this.getSqliteFns();
|
|
1838
|
+
const db = sqlite.getDb();
|
|
1839
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
1840
|
+
const rows = db
|
|
1841
|
+
.prepare(`SELECT id, embedding FROM memories WHERE id IN (${placeholders})`)
|
|
1842
|
+
.all(...ids);
|
|
1843
|
+
const map = new Map();
|
|
1844
|
+
for (const row of rows) {
|
|
1845
|
+
if (row.embedding) {
|
|
1846
|
+
map.set(row.id, Array.from(new Float32Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 4)));
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return map;
|
|
1850
|
+
}
|
|
1851
|
+
async deleteMemoryLinksForMemory(memoryId) {
|
|
1852
|
+
if (this.backend === 'postgresql' && this.postgres) {
|
|
1853
|
+
const pool = await this.postgres.getPool();
|
|
1854
|
+
const result = await pool.query('DELETE FROM memory_links WHERE (source_id = $1 OR target_id = $1) AND LOWER(project_id) = $2', [memoryId, this.postgres.getProjectId()]);
|
|
1855
|
+
return result.rowCount ?? 0;
|
|
1856
|
+
}
|
|
1857
|
+
const sqlite = await this.getSqliteFns();
|
|
1858
|
+
const db = sqlite.getDb();
|
|
1859
|
+
const result = db
|
|
1860
|
+
.prepare('DELETE FROM memory_links WHERE source_id = ? OR target_id = ?')
|
|
1861
|
+
.run(memoryId, memoryId);
|
|
1862
|
+
return result.changes;
|
|
1863
|
+
}
|
|
1864
|
+
// ===========================================================================
|
|
1865
|
+
// Bulk Restore (for checkpoint restore)
|
|
1866
|
+
// ===========================================================================
|
|
1867
|
+
/**
|
|
1868
|
+
* Bulk restore memories, links, centrality, and documents from checkpoint data.
|
|
1869
|
+
* Used by checkpoint.ts to avoid direct SQLite/PG access.
|
|
1870
|
+
*/
|
|
1871
|
+
async bulkRestore(data) {
|
|
1872
|
+
const { bulkRestoreImpl } = await import('./dispatcher-export.js');
|
|
1873
|
+
return bulkRestoreImpl(this, data);
|
|
1874
|
+
}
|
|
1875
|
+
// ===========================================================================
|
|
1876
|
+
// Backend Info
|
|
1877
|
+
// ===========================================================================
|
|
1878
|
+
getBackendInfo() {
|
|
1879
|
+
return {
|
|
1880
|
+
backend: this.backend,
|
|
1881
|
+
vector: this.vectorBackend,
|
|
1882
|
+
vectorName: this.vectorBackend === 'qdrant'
|
|
1883
|
+
? 'qdrant'
|
|
1884
|
+
: this.backend === 'postgresql'
|
|
1885
|
+
? 'pgvector'
|
|
1886
|
+
: 'sqlite-vec',
|
|
1887
|
+
};
|
|
1888
|
+
}
|
|
1889
|
+
// ===========================================================================
|
|
1890
|
+
// Qdrant Backfill — Sync existing SQL data → Qdrant
|
|
1891
|
+
// ===========================================================================
|
|
1892
|
+
/**
|
|
1893
|
+
* Backfill Qdrant collections from the SQL backend.
|
|
1894
|
+
* Use after Qdrant schema migration or when collections are empty.
|
|
1895
|
+
*/
|
|
1896
|
+
async backfillQdrant(target = 'all', options) {
|
|
1897
|
+
const { backfillQdrantImpl } = await import('./dispatcher-export.js');
|
|
1898
|
+
return backfillQdrantImpl(this, target, options);
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
/** Parse pgvector string '[1.0, 2.0, 3.0]' to number[] */
|
|
1902
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
1903
|
+
function parsePgVector(str) {
|
|
1904
|
+
const inner = str.slice(1, -1);
|
|
1905
|
+
if (!inner)
|
|
1906
|
+
return [];
|
|
1907
|
+
return inner.split(',').map((s) => parseFloat(s.trim()));
|
|
1908
|
+
}
|
|
1909
|
+
// Singleton
|
|
1910
|
+
let _dispatcher = null;
|
|
1911
|
+
export async function getStorageDispatcher() {
|
|
1912
|
+
if (!_initialized)
|
|
1913
|
+
await initStorageDispatcher();
|
|
1914
|
+
if (!_dispatcher)
|
|
1915
|
+
_dispatcher = new StorageDispatcher();
|
|
1916
|
+
return _dispatcher;
|
|
1917
|
+
}
|
|
1918
|
+
export function resetStorageDispatcher() {
|
|
1919
|
+
_dispatcher = null;
|
|
1920
|
+
_initialized = false;
|
|
1921
|
+
_postgresBackend = null;
|
|
1922
|
+
_qdrantStore = null;
|
|
1923
|
+
_backend = 'sqlite';
|
|
1924
|
+
_vectorBackend = 'builtin';
|
|
1925
|
+
}
|
|
1926
|
+
//# sourceMappingURL=dispatcher.js.map
|