squish-memory 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +32 -16
- package/CHANGELOG.md +147 -0
- package/README.md +120 -78
- package/{scripts → bin}/dependency-manager.mjs +217 -217
- package/{scripts → bin}/detect-clients.mjs +78 -78
- package/bin/install-interactive.mjs +321 -0
- package/bin/squish-mcp.mjs +46 -0
- package/bin/squish.mjs +33 -0
- package/config/mcp-migration-map.json +1 -6
- package/config/mcp-mode-semantics.json +19 -23
- package/config/mcp-remote-auth.json +3 -26
- package/config/mcp-universal.schema.json +5 -35
- package/config/settings.json +107 -52
- package/config.js +5 -0
- package/config.ts +218 -0
- package/core/adapters/config/claude-code.ts +133 -0
- package/core/adapters/config/cursor.ts +90 -0
- package/core/adapters/config/opencode.ts +89 -0
- package/core/adapters/config/windsurf.ts +90 -0
- package/core/adapters/index.ts +102 -0
- package/core/adapters/timeline.ts +116 -0
- package/core/adapters/types.ts +166 -0
- package/core/agent-preferences.ts +140 -0
- package/core/algorithms/analytics/token-estimator.ts +216 -0
- package/core/algorithms/detection/hash-filters.ts +260 -0
- package/core/algorithms/detection/semantic-ranker.ts +194 -0
- package/core/algorithms/detection/two-stage-detector.ts +421 -0
- package/core/algorithms/handlers/approve-merge.ts +215 -0
- package/core/algorithms/handlers/detect-duplicates.ts +192 -0
- package/core/algorithms/handlers/get-stats.ts +132 -0
- package/core/algorithms/handlers/list-proposals.ts +130 -0
- package/core/algorithms/handlers/preview-merge.ts +139 -0
- package/core/algorithms/handlers/reject-merge.ts +93 -0
- package/core/algorithms/handlers/reverse-merge.ts +155 -0
- package/core/algorithms/index.ts +39 -0
- package/core/algorithms/operations/cache-maintenance.ts +182 -0
- package/core/algorithms/safety/safety-checks.ts +256 -0
- package/core/algorithms/strategies/merge-strategies.ts +381 -0
- package/core/algorithms/types.ts +140 -0
- package/core/algorithms/utils/response-builder.ts +61 -0
- package/core/associations.ts +363 -0
- package/core/beliefs/decay.ts +289 -0
- package/core/beliefs/extractor.ts +131 -0
- package/core/beliefs/store.ts +557 -0
- package/core/beliefs/types.ts +38 -0
- package/core/commands/mcp-server.ts +5 -0
- package/core/compression.ts +177 -0
- package/core/config.js +2 -0
- package/core/consolidation.ts +330 -0
- package/core/context/agent-context.ts +388 -0
- package/core/context/context-paging.ts +449 -0
- package/core/context/context-window.ts +234 -0
- package/core/context/context.ts +35 -0
- package/core/embeddings/embeddings.ts +616 -0
- package/core/embeddings/google-multimodal.ts +200 -0
- package/{dist/core/local-embeddings.js → core/embeddings/local-embeddings.ts} +12 -11
- package/core/embeddings/qmd-client.ts +495 -0
- package/core/embeddings/transformers-local.ts +261 -0
- package/core/embeddings.js +4 -0
- package/core/error-handling.ts +206 -0
- package/core/external +219 -0
- package/core/graph/entity-deduplicator.ts +232 -0
- package/core/graph/graph-builder.ts +257 -0
- package/core/graph/graph-traversal.ts +490 -0
- package/core/graph/index.ts +24 -0
- package/core/graph/llm-entity-extractor.ts +402 -0
- package/core/graph/multi-hop-retrieval.ts +317 -0
- package/core/graph/relationship-extractor.ts +465 -0
- package/core/hooks/agent-hooks.ts +653 -0
- package/core/hooks/auto-tagger.ts +149 -0
- package/core/hooks/capture-filter.ts +169 -0
- package/core/hot-cache.ts +388 -0
- package/core/index.ts +10 -0
- package/core/ingestion/agent-memory.ts +167 -0
- package/core/ingestion/core-memory.ts +326 -0
- package/core/ingestion/learnings.ts +260 -0
- package/core/ingestion/signal-engine.ts +266 -0
- package/core/integrations/obsidian-vault.ts +197 -0
- package/core/layers/generator.ts +115 -0
- package/core/lib/db-client.ts +168 -0
- package/core/lib/parse-embedding.ts +59 -0
- package/core/lib/schemas.ts +102 -0
- package/core/lib/types.ts +49 -0
- package/core/lib/utils.ts +151 -0
- package/core/lib/validation.ts +180 -0
- package/core/lifecycle.ts +353 -0
- package/core/logger.ts +59 -0
- package/core/memory/bridge-discovery.ts +395 -0
- package/core/memory/categorizer.ts +390 -0
- package/core/memory/conflict-detector.ts +62 -0
- package/core/memory/consolidation.ts +372 -0
- package/core/memory/context-collector.ts +75 -0
- package/core/memory/contradiction-resolver.ts +494 -0
- package/core/memory/edit-workflow.ts +174 -0
- package/core/memory/entity-extractor.ts +426 -0
- package/core/memory/entity-resolver.ts +89 -0
- package/core/memory/explain.ts +112 -0
- package/core/memory/fact-deriver.ts +300 -0
- package/core/memory/fact-extractor.ts +120 -0
- package/core/memory/feedback-tracker.ts +200 -0
- package/core/memory/hooks.ts +230 -0
- package/core/memory/hybrid-retrieval.ts +65 -0
- package/core/memory/hybrid-scorer.ts +325 -0
- package/core/memory/hybrid-search.ts +748 -0
- package/core/memory/importance.ts +319 -0
- package/core/memory/index.ts +11 -0
- package/core/memory/loader.ts +178 -0
- package/core/memory/markdown/markdown-storage.ts +318 -0
- package/core/memory/memories.ts +565 -0
- package/core/memory/memory-lifecycle.ts +51 -0
- package/core/memory/memory-manager.ts +53 -0
- package/core/memory/migrate.ts +173 -0
- package/core/memory/normalization.ts +30 -0
- package/core/memory/path-strengthener.ts +211 -0
- package/core/memory/progressive-disclosure.ts +392 -0
- package/core/memory/query-processor.ts +130 -0
- package/core/memory/query-rewriter.ts +153 -0
- package/core/memory/response-analyzer.ts +81 -0
- package/core/memory/retrieval-feedback.ts +276 -0
- package/core/memory/serialization.ts +83 -0
- package/core/memory/stale-cleaner.ts +147 -0
- package/core/memory/stats.ts +181 -0
- package/core/memory/telemetry.ts +392 -0
- package/core/memory/temporal-facts.ts +356 -0
- package/core/memory/temporal-parser.ts +477 -0
- package/core/memory/trigger-detector.ts +104 -0
- package/core/memory/write-gate.ts +288 -0
- package/core/places/index.ts +14 -0
- package/core/places/memory-places.ts +339 -0
- package/core/places/places.ts +406 -0
- package/core/places/rules.ts +308 -0
- package/core/places/walking.ts +192 -0
- package/core/projects +89 -0
- package/core/projects.ts +131 -0
- package/core/redis.ts +82 -0
- package/core/responses.ts +187 -0
- package/core/runtime/trust-report.ts +195 -0
- package/core/runtime/trust-state.ts +360 -0
- package/core/scheduler/cron-scheduler.ts +581 -0
- package/core/scheduler/heartbeat.ts +91 -0
- package/core/scheduler/index.ts +8 -0
- package/core/scheduler/job-runner.ts +197 -0
- package/core/search/conversations.ts +166 -0
- package/core/search/entities.ts +46 -0
- package/core/search/folder-context.ts +154 -0
- package/core/search/graph-boost.ts +22 -0
- package/core/search/index.ts +4 -0
- package/core/search/qmd-wrapper.ts +84 -0
- package/core/security/encrypt.ts +51 -0
- package/core/security/governance.ts +102 -0
- package/core/security/privacy.ts +108 -0
- package/core/security/secret-detector.ts +122 -0
- package/core/session/auto-load.ts +160 -0
- package/core/session/entity-tracker.ts +363 -0
- package/core/session/index.ts +7 -0
- package/core/session/reference-resolver.ts +158 -0
- package/core/session/self-iteration-job.ts +478 -0
- package/core/session/session-hooks.ts +69 -0
- package/core/session/types.ts +36 -0
- package/core/session/working-set.ts +275 -0
- package/core/snapshots/cleanup.ts +13 -0
- package/core/snapshots/comparison.ts +59 -0
- package/core/snapshots/creation.ts +139 -0
- package/core/snapshots/retrieval.ts +44 -0
- package/core/snapshots/stats.ts +63 -0
- package/core/storage/cache.ts +241 -0
- package/core/storage/database.ts +23 -0
- package/core/summarization/cleanup.ts +13 -0
- package/core/summarization/queries.ts +32 -0
- package/core/summarization/stats.ts +64 -0
- package/core/summarization/strategies.ts +52 -0
- package/core/summarization.ts +248 -0
- package/core/temporal-facts.ts +244 -0
- package/core/tracing/collector.ts +470 -0
- package/core/tracing/visualizer.ts +195 -0
- package/core/utils/cleanup-operations.ts +50 -0
- package/core/utils/content-extraction.ts +95 -0
- package/core/utils/filter-builder.ts +56 -0
- package/core/utils/history-traversal.ts +63 -0
- package/core/utils/memory-operations.ts +56 -0
- package/core/utils/query-operations.ts +83 -0
- package/core/utils/summarization-helpers.ts +45 -0
- package/core/utils/temporal-queries.ts +39 -0
- package/core/utils/vector-operations.ts +135 -0
- package/core/utils/version-management.ts +74 -0
- package/core/worker.ts +324 -0
- package/db/adapter.ts +215 -0
- package/db/bootstrap.ts +1055 -0
- package/db/drizzle/migrations/0000_needy_cerebro.sql +402 -0
- package/db/drizzle/migrations/meta/0000_snapshot.json +3451 -0
- package/db/drizzle/migrations/meta/_journal.json +13 -0
- package/db/drizzle/schema-sqlite.ts +1032 -0
- package/db/drizzle/schema.ts +1128 -0
- package/db/drizzle.config.ts +12 -0
- package/db/index.ts +83 -0
- package/db/init.sql +5 -0
- package/db/migrations/associations.ts +35 -0
- package/db/migrations/beliefs.ts +89 -0
- package/db/migrations/core-memory.ts +35 -0
- package/db/migrations/fts.ts +59 -0
- package/db/migrations/index.ts +54 -0
- package/db/migrations/indexes.ts +36 -0
- package/db/migrations/learnings.ts +34 -0
- package/db/migrations/maintenance.ts +68 -0
- package/db/migrations/memories.ts +22 -0
- package/db/migrations/memory-places.ts +35 -0
- package/db/migrations/places.ts +49 -0
- package/db/migrations/projects.ts +21 -0
- package/db/migrations/tier-conversion.ts +24 -0
- package/db/neon.ts +22 -0
- package/db/schema/beliefs.ts +50 -0
- package/db/schema/generator.ts +159 -0
- package/db/schema/index.ts +58 -0
- package/db/schema/learnings.ts +32 -0
- package/db/schema/memories.ts +83 -0
- package/db/schema/projects.ts +33 -0
- package/db/schema.ts +13 -0
- package/db/supabase.ts +27 -0
- package/dist/config.d.ts +40 -17
- package/dist/config.js +150 -198
- package/dist/core/adapters/types.d.ts +13 -33
- package/dist/core/adapters/types.js +1 -1
- package/dist/core/agent-preferences.d.ts +16 -0
- package/dist/core/agent-preferences.js +124 -0
- package/dist/core/algorithms/safety/safety-checks.d.ts +1 -5
- package/dist/core/algorithms/types.d.ts +0 -8
- package/dist/core/associations.d.ts +3 -1
- package/dist/core/associations.js +37 -1
- package/dist/core/beliefs/decay.d.ts +27 -0
- package/dist/core/beliefs/decay.js +217 -0
- package/dist/core/beliefs/extractor.d.ts +9 -0
- package/dist/core/beliefs/extractor.js +113 -0
- package/dist/core/beliefs/store.d.ts +46 -0
- package/dist/core/beliefs/store.js +466 -0
- package/dist/core/beliefs/types.d.ts +28 -0
- package/dist/core/beliefs/types.js +2 -0
- package/dist/core/commands/mcp-server.d.ts +0 -1
- package/dist/core/commands/mcp-server.js +4 -737
- package/dist/core/commands/remember.d.ts +24 -0
- package/dist/core/commands/remember.js +144 -0
- package/dist/core/{toon.d.ts → compression.d.ts} +6 -4
- package/dist/core/{toon.js → compression.js} +8 -8
- package/dist/core/context/agent-context.js +1 -1
- package/dist/core/embeddings/embeddings.d.ts +29 -0
- package/dist/core/embeddings/embeddings.js +546 -0
- package/dist/core/embeddings/google-multimodal.js +6 -2
- package/dist/core/{local-embeddings.d.ts → embeddings/local-embeddings.d.ts} +1 -1
- package/dist/core/embeddings/local-embeddings.js +11 -0
- package/dist/core/embeddings/qmd-client.js +1 -1
- package/dist/core/embeddings/transformers-local.d.ts +64 -0
- package/dist/core/embeddings/transformers-local.js +213 -0
- package/dist/core/embeddings.d.ts +1 -28
- package/dist/core/embeddings.js +2 -453
- package/dist/core/graph/entity-deduplicator.d.ts +24 -0
- package/dist/core/graph/entity-deduplicator.js +183 -0
- package/dist/core/graph/graph-builder.d.ts +46 -0
- package/dist/core/graph/graph-builder.js +174 -0
- package/dist/core/graph/graph-traversal.d.ts +80 -0
- package/dist/core/graph/graph-traversal.js +315 -0
- package/dist/core/graph/index.d.ts +19 -0
- package/dist/core/graph/index.js +13 -0
- package/dist/core/graph/llm-entity-extractor.d.ts +49 -0
- package/dist/core/graph/llm-entity-extractor.js +313 -0
- package/dist/core/graph/multi-hop-retrieval.d.ts +48 -0
- package/dist/core/graph/multi-hop-retrieval.js +215 -0
- package/dist/core/graph/relationship-extractor.d.ts +48 -0
- package/dist/core/graph/relationship-extractor.js +351 -0
- package/dist/core/hooks/agent-hooks.d.ts +10 -1
- package/dist/core/hooks/agent-hooks.js +301 -24
- package/dist/core/hot-cache.d.ts +86 -0
- package/dist/core/hot-cache.js +285 -0
- package/dist/core/index.d.ts +9 -9
- package/dist/core/index.js +9 -12
- package/dist/core/ingestion/core-memory.d.ts +2 -2
- package/dist/core/ingestion/core-memory.js +3 -3
- package/dist/core/ingestion/learnings.js +3 -0
- package/dist/core/ingestion/signal-engine.d.ts +41 -0
- package/dist/core/ingestion/signal-engine.js +201 -0
- package/dist/core/{obsidian-vault.d.ts → integrations/obsidian-vault.d.ts} +2 -1
- package/dist/core/{obsidian-vault.js → integrations/obsidian-vault.js} +69 -7
- package/dist/core/lib/parse-embedding.d.ts +9 -0
- package/dist/core/lib/parse-embedding.js +58 -0
- package/dist/core/lib/schemas.d.ts +57 -54
- package/dist/core/lib/types.d.ts +45 -0
- package/dist/core/lib/types.js +6 -0
- package/dist/core/lib/utils.d.ts +4 -0
- package/dist/core/lib/utils.js +55 -0
- package/dist/core/lifecycle.d.ts +0 -1
- package/dist/core/lifecycle.js +13 -23
- package/dist/core/logger.d.ts +1 -0
- package/dist/core/logger.js +14 -8
- package/dist/core/mcp/tools.d.ts +0 -2
- package/dist/core/mcp/tools.js +0 -87
- package/dist/core/mcp/types.d.ts +25 -253
- package/dist/core/mcp/types.js +2 -2
- package/dist/core/memory/categorizer.js +1 -0
- package/dist/core/memory/consolidation.js +2 -28
- package/dist/core/memory/entity-extractor.d.ts +4 -0
- package/dist/core/memory/entity-extractor.js +30 -16
- package/dist/core/memory/explain.d.ts +18 -0
- package/dist/core/memory/explain.js +92 -0
- package/dist/core/memory/fact-deriver.d.ts +31 -0
- package/dist/core/memory/fact-deriver.js +236 -0
- package/dist/core/memory/hybrid-retrieval.d.ts +14 -16
- package/dist/core/memory/hybrid-retrieval.js +25 -127
- package/dist/core/memory/hybrid-scorer.js +6 -23
- package/dist/core/memory/hybrid-search.d.ts +10 -7
- package/dist/core/memory/hybrid-search.js +458 -221
- package/dist/core/memory/importance.d.ts +0 -17
- package/dist/core/memory/importance.js +1 -58
- package/dist/core/memory/index.d.ts +1 -0
- package/dist/core/memory/index.js +1 -0
- package/dist/core/memory/memories.d.ts +13 -17
- package/dist/core/memory/memories.js +78 -75
- package/dist/core/memory/memory-lifecycle.d.ts +2 -2
- package/dist/core/memory/memory-lifecycle.js +10 -18
- package/dist/core/memory/normalization.d.ts +1 -16
- package/dist/core/memory/path-strengthener.d.ts +39 -0
- package/dist/core/memory/path-strengthener.js +150 -0
- package/dist/core/memory/query-processor.js +37 -3
- package/dist/core/memory/retrieval-feedback.d.ts +70 -0
- package/dist/core/memory/retrieval-feedback.js +213 -0
- package/dist/core/memory/stale-cleaner.d.ts +26 -0
- package/dist/core/memory/stale-cleaner.js +97 -0
- package/dist/core/memory/stats.d.ts +10 -0
- package/dist/core/memory/stats.js +8 -3
- package/dist/core/memory/trigger-detector.d.ts +8 -1
- package/dist/core/memory/trigger-detector.js +42 -5
- package/dist/core/places/index.d.ts +1 -1
- package/dist/core/places/index.js +1 -1
- package/dist/core/places/places.d.ts +13 -13
- package/dist/core/places/places.js +27 -27
- package/dist/core/places/rules.js +23 -23
- package/dist/core/places/walking.d.ts +3 -3
- package/dist/core/places/walking.js +7 -7
- package/dist/core/projects.js +8 -0
- package/dist/core/runtime/trust-report.d.ts +102 -0
- package/dist/core/runtime/trust-report.js +107 -0
- package/dist/core/runtime/trust-state.d.ts +12 -0
- package/dist/core/runtime/trust-state.js +309 -0
- package/dist/core/scheduler/cron-scheduler.d.ts +1 -1
- package/dist/core/scheduler/cron-scheduler.js +164 -3
- package/dist/core/scheduler/job-runner.js +1 -1
- package/dist/core/search/qmd-wrapper.d.ts +36 -0
- package/dist/core/search/qmd-wrapper.js +58 -0
- package/dist/core/session/auto-load.js +28 -3
- package/dist/core/session/entity-tracker.d.ts +62 -0
- package/dist/core/session/entity-tracker.js +287 -0
- package/dist/core/session/reference-resolver.d.ts +26 -0
- package/dist/core/session/reference-resolver.js +121 -0
- package/dist/core/session/self-iteration-job.d.ts +15 -0
- package/dist/core/session/self-iteration-job.js +163 -58
- package/dist/core/session/working-set.d.ts +50 -0
- package/dist/core/session/working-set.js +212 -0
- package/dist/core/snapshots/creation.d.ts +2 -8
- package/dist/core/snapshots/creation.js +3 -12
- package/dist/core/utils/summarization-helpers.d.ts +0 -4
- package/dist/core/utils/summarization-helpers.js +1 -6
- package/dist/db/bootstrap.d.ts +2 -0
- package/dist/db/bootstrap.js +229 -280
- package/dist/db/drizzle/schema-sqlite.d.ts +702 -1
- package/dist/db/drizzle/schema-sqlite.js +83 -4
- package/dist/db/drizzle/schema.d.ts +653 -1
- package/dist/db/drizzle/schema.js +93 -4
- package/dist/db/migrations/associations.d.ts +6 -0
- package/dist/db/migrations/associations.js +29 -0
- package/dist/db/migrations/beliefs.d.ts +10 -0
- package/dist/db/migrations/beliefs.js +76 -0
- package/dist/db/migrations/core-memory.d.ts +6 -0
- package/dist/db/migrations/core-memory.js +29 -0
- package/dist/db/migrations/fts.d.ts +6 -0
- package/dist/db/migrations/fts.js +52 -0
- package/dist/db/migrations/index.d.ts +25 -0
- package/dist/db/migrations/index.js +51 -0
- package/dist/db/migrations/indexes.d.ts +6 -0
- package/dist/db/migrations/indexes.js +30 -0
- package/dist/db/migrations/learnings.d.ts +7 -0
- package/dist/db/migrations/learnings.js +26 -0
- package/dist/db/migrations/maintenance.d.ts +6 -0
- package/dist/db/migrations/maintenance.js +61 -0
- package/dist/db/migrations/memories.d.ts +7 -0
- package/dist/db/migrations/memories.js +16 -0
- package/dist/db/migrations/memory-places.d.ts +6 -0
- package/dist/db/migrations/memory-places.js +29 -0
- package/dist/db/migrations/places.d.ts +6 -0
- package/dist/db/migrations/places.js +43 -0
- package/dist/db/migrations/projects.d.ts +3 -0
- package/dist/db/migrations/projects.js +13 -0
- package/dist/db/migrations/tier-conversion.d.ts +7 -0
- package/dist/db/migrations/tier-conversion.js +20 -0
- package/dist/db/schema/beliefs.d.ts +9 -0
- package/dist/db/schema/beliefs.js +46 -0
- package/dist/db/schema/generator.d.ts +38 -0
- package/dist/db/schema/generator.js +108 -0
- package/dist/db/schema/index.d.ts +19 -20
- package/dist/db/schema/index.js +25 -79
- package/dist/db/schema/learnings.d.ts +7 -0
- package/dist/db/schema/learnings.js +30 -0
- package/dist/db/schema/memories.d.ts +7 -0
- package/dist/db/schema/memories.js +81 -0
- package/dist/db/schema/projects.d.ts +4 -0
- package/dist/db/schema/projects.js +31 -0
- package/dist/packages/mcp/src/index.d.ts +3 -0
- package/dist/packages/mcp/src/index.js +733 -0
- package/mcp.json.example +8 -11
- package/package.json +57 -76
- package/packages/cli/package.json +22 -0
- package/packages/cli/src/commands/clean.ts +68 -0
- package/packages/cli/src/commands/context.ts +79 -0
- package/packages/cli/src/commands/doctor.ts +357 -0
- package/packages/cli/src/commands/forget.ts +72 -0
- package/packages/cli/src/commands/health.ts +36 -0
- package/packages/cli/src/commands/inspect.ts +41 -0
- package/packages/cli/src/commands/link.ts +50 -0
- package/packages/cli/src/commands/migrate.ts +93 -0
- package/packages/cli/src/commands/recall.ts +99 -0
- package/packages/cli/src/commands/recent.ts +57 -0
- package/packages/cli/src/commands/remember.ts +139 -0
- package/packages/cli/src/commands/run.ts +58 -0
- package/packages/cli/src/commands/stale.ts +43 -0
- package/packages/cli/src/commands/stats.ts +42 -0
- package/packages/cli/src/index.ts +57 -0
- package/packages/cli/tsconfig.json +24 -0
- package/packages/mcp/package.json +26 -0
- package/packages/mcp/src/index.ts +877 -0
- package/packages/mcp/tsconfig.json +20 -0
- package/skills/squish-memory/SKILL.md +38 -35
- package/skills/squish-memory/{scripts/install.sh → install.sh} +1 -1
- package/skills/squish-memory/references/claude-desktop.json +12 -0
- package/skills/squish-memory/references/openclaw.json +13 -0
- package/skills/squish-memory/references/opencode.json +14 -0
- package/config/hooks/claude-code-hooks.json +0 -39
- package/config/hooks/cursor-hooks.json +0 -30
- package/config/hooks/opencode-hooks.json +0 -30
- package/config/hooks/windsurf-hooks.json +0 -30
- package/config/mcp-cli-fallback-policy.json +0 -22
- package/config/mcp.json +0 -38
- package/config/plugin-manifest.json +0 -101
- package/config/plugin-manifest.schema.json +0 -244
- package/config/plugin.json +0 -32
- package/config/remote-memory-policy.json +0 -32
- package/core/commands/context-paging.md +0 -51
- package/core/commands/context-status.md +0 -22
- package/core/commands/context.md +0 -5
- package/core/commands/core-memory.md +0 -56
- package/core/commands/health.md +0 -5
- package/core/commands/init.md +0 -39
- package/core/commands/merge.md +0 -113
- package/core/commands/recall.md +0 -5
- package/core/commands/remember.md +0 -11
- package/core/commands/search.md +0 -10
- package/dist/core/commands/managed-sync.d.ts +0 -10
- package/dist/core/commands/managed-sync.js +0 -64
- package/dist/core/external-folder/index.d.ts +0 -102
- package/dist/core/external-folder/index.js +0 -294
- package/dist/core/namespaces/index.d.ts +0 -71
- package/dist/core/namespaces/index.js +0 -305
- package/dist/core/namespaces/uri-parser.d.ts +0 -31
- package/dist/core/namespaces/uri-parser.js +0 -74
- package/dist/core/search/qmd-search.d.ts +0 -61
- package/dist/core/search/qmd-search.js +0 -178
- package/dist/core/session-hooks/self-iteration-job.d.ts +0 -20
- package/dist/core/session-hooks/self-iteration-job.js +0 -282
- package/dist/core/session-hooks/session-hooks.d.ts +0 -18
- package/dist/core/session-hooks/session-hooks.js +0 -58
- package/dist/core/snapshots.d.ts +0 -29
- package/dist/core/snapshots.js +0 -220
- package/dist/core/sync/qmd-sync.d.ts +0 -94
- package/dist/core/sync/qmd-sync.js +0 -201
- package/dist/index.d.ts +0 -7
- package/dist/index.js +0 -1677
- package/dist/vendor/sql.js/sql-wasm.wasm +0 -0
- package/dist/webui/server.d.ts +0 -5
- package/dist/webui/server.js +0 -642
- package/generated/mcp/manifest.json +0 -23
- package/generated/mcp/mcp-servers.json +0 -25
- package/generated/mcp/mcporter.json +0 -34
- package/generated/mcp/openclaw-memory-qmd.json +0 -17
- package/generated/mcp/runtime.json +0 -12
- package/scripts/README.md +0 -60
- package/scripts/build-release.sh +0 -36
- package/scripts/check-secrets.js +0 -132
- package/scripts/copy-runtime-assets.mjs +0 -26
- package/scripts/generate-mcp.mjs +0 -264
- package/scripts/github-release.sh +0 -77
- package/scripts/init-dirs.mjs +0 -13
- package/scripts/install-claude-code.sh +0 -85
- package/scripts/install-cursor.sh +0 -56
- package/scripts/install-hooks.sh +0 -73
- package/scripts/install-interactive.mjs +0 -357
- package/scripts/install-opencode.sh +0 -75
- package/scripts/install-plugin.mjs +0 -415
- package/scripts/install-windsurf.sh +0 -67
- package/scripts/remote-preflight.mjs +0 -62
- package/scripts/squish-fallback.mjs +0 -132
- package/scripts/test-interactive.mjs +0 -131
- package/scripts/verify-mcp.mjs +0 -214
- package/skills/squish-memory/scripts/install.mjs +0 -335
- package/skills/squish-memory/write_skill.js +0 -2
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vector Search - Pure semantic search with optional graph boosting + multi-session support
|
|
3
|
+
*
|
|
4
|
+
* Uses cosine similarity on embeddings + optional graph boost
|
|
5
|
+
* BM25 removed - use qmd-client for hot tier (BM25 + vectors + reranking)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SearchResult, SearchInput } from './memories.js';
|
|
9
|
+
import { getDb } from '../../db/index.js';
|
|
10
|
+
import { createDatabaseClient } from '../storage/database.js';
|
|
11
|
+
import { getEmbedding } from '../../core/embeddings.js';
|
|
12
|
+
import { requireProject } from '../../core/projects.js';
|
|
13
|
+
import { deserializeTags, deserializeMetadata, normalizeTags } from './serialization.js';
|
|
14
|
+
import { computeGraphBoost } from '../search/graph-boost.js';
|
|
15
|
+
import { normalizeTimestamp } from '../lib/utils.js';
|
|
16
|
+
import { parseEmbedding } from '../lib/parse-embedding.js';
|
|
17
|
+
import { cosineSimilarity } from '../utils/vector-operations.js';
|
|
18
|
+
import config from '../../config.js';
|
|
19
|
+
import { getRelatedMemories } from '../associations.js';
|
|
20
|
+
import { getPlaceMemories } from '../places/memory-places.js';
|
|
21
|
+
import { getPlaceByType, type PlaceType } from '../places/places.js';
|
|
22
|
+
import { multiHopSearch } from '../graph/multi-hop-retrieval.js';
|
|
23
|
+
import { logger } from '../logger.js';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Detect if query asks about time (temporal queries)
|
|
27
|
+
*/
|
|
28
|
+
function isTemporalQuery(query: string): boolean {
|
|
29
|
+
const temporalIndicators = [
|
|
30
|
+
'when', 'how long', 'how many', 'ago', 'since', 'until',
|
|
31
|
+
'before', 'after', 'earlier', 'later', 'yesterday', 'tomorrow',
|
|
32
|
+
'last week', 'next week', 'last month', 'next month'
|
|
33
|
+
];
|
|
34
|
+
const lower = query.toLowerCase();
|
|
35
|
+
return temporalIndicators.some(w => lower.includes(w));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Detect if query spans multiple sessions (multi-hop queries)
|
|
40
|
+
*/
|
|
41
|
+
function isMultiSessionQuery(query: string): boolean {
|
|
42
|
+
const multiSessionIndicators = [
|
|
43
|
+
'when', 'then', 'before', 'after', 'earlier', 'later',
|
|
44
|
+
'previous', 'next', 'first', 'later', 'subsequently',
|
|
45
|
+
'across', 'between', 'both', 'another', 'different'
|
|
46
|
+
];
|
|
47
|
+
const lower = query.toLowerCase();
|
|
48
|
+
return multiSessionIndicators.some(w => lower.includes(w));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Check if content contains date/time references
|
|
53
|
+
*/
|
|
54
|
+
function hasDateReference(content: string): boolean {
|
|
55
|
+
const datePatterns = [
|
|
56
|
+
/\b\d{4}\b/, // Years: 2023, 2022
|
|
57
|
+
/\b\d{1,2}\s+(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\b/i, // Month dates
|
|
58
|
+
/\b(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)\s+\d{1,2}\b/i,
|
|
59
|
+
/\b\d{1,2}\/\d{1,2}\/\d{2,4}\b/, // Dates: 5/7/2023
|
|
60
|
+
/\b(yesterday|today|tomorrow|last week|last month|last year)\b/i,
|
|
61
|
+
/\b(\d+)\s+(day|week|month|year)s?\s+(ago|before)\b/i,
|
|
62
|
+
];
|
|
63
|
+
const lower = content.toLowerCase();
|
|
64
|
+
return datePatterns.some(p => p.test(content) || p.test(lower));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Expand query for multi-session retrieval
|
|
69
|
+
*/
|
|
70
|
+
function expandQueryForMultiSession(query: string): string[] {
|
|
71
|
+
const expansions = [query];
|
|
72
|
+
const lower = query.toLowerCase();
|
|
73
|
+
|
|
74
|
+
// Add time-based expansions
|
|
75
|
+
if (lower.includes('when')) {
|
|
76
|
+
expansions.push(query.replace(/when/i, '').trim());
|
|
77
|
+
}
|
|
78
|
+
if (lower.includes('before') || lower.includes('after')) {
|
|
79
|
+
expansions.push(query.replace(/before|after/i, '').trim());
|
|
80
|
+
}
|
|
81
|
+
if (lower.includes('earlier') || lower.includes('later')) {
|
|
82
|
+
expansions.push(query.replace(/earlier|later/i, '').trim());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Add "mentioning" expansions for factual queries
|
|
86
|
+
if (lower.includes('what') || lower.includes('how')) {
|
|
87
|
+
expansions.push(query + ' mentioned');
|
|
88
|
+
expansions.push(query + ' said');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return [...new Set(expansions.filter(e => e.length > 2))];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Expand query for temporal retrieval - add date/temporal context
|
|
96
|
+
*/
|
|
97
|
+
function expandQueryForTemporal(query: string): string[] {
|
|
98
|
+
const expansions = [query];
|
|
99
|
+
const lower = query.toLowerCase();
|
|
100
|
+
|
|
101
|
+
// For "when" questions, search with entity + date context
|
|
102
|
+
if (lower.includes('when')) {
|
|
103
|
+
// Extract entity name (everything after "when did" or "when is")
|
|
104
|
+
const entityMatch = query.match(/when\s+(?:did|is|was|were)\s+(\w+)/i);
|
|
105
|
+
if (entityMatch) {
|
|
106
|
+
const entity = entityMatch[1];
|
|
107
|
+
// Search with entity name alone (might match date mentions)
|
|
108
|
+
expansions.push(entity);
|
|
109
|
+
expansions.push(query.replace(/when\s+(?:did|is|was|were)\s+/i, '').trim());
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Add "date" and "time" expansions
|
|
114
|
+
if (lower.includes('when') || lower.includes('how long') || lower.includes('ago')) {
|
|
115
|
+
expansions.push(query + ' date');
|
|
116
|
+
expansions.push(query + ' time');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return [...new Set(expansions.filter(e => e.length > 2))];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface HybridSearchOptions {
|
|
123
|
+
limit?: number;
|
|
124
|
+
project?: string;
|
|
125
|
+
type?: string;
|
|
126
|
+
tags?: string[];
|
|
127
|
+
enableMultiSession?: boolean; // Enable multi-session expansion
|
|
128
|
+
enableGraphTraversal?: boolean; // Enable multi-hop graph traversal (Task 4)
|
|
129
|
+
enableHeuristics?: boolean;
|
|
130
|
+
includeAssociations?: boolean;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Score with recency + similarity + entity boost (NO LLM required)
|
|
135
|
+
*/
|
|
136
|
+
function scoreWithHeuristics(
|
|
137
|
+
result: SearchResult,
|
|
138
|
+
query: string,
|
|
139
|
+
now: number
|
|
140
|
+
): number {
|
|
141
|
+
let score = result.similarity ?? 0;
|
|
142
|
+
|
|
143
|
+
// 1. Recency boost: Recent = higher (up to +0.1)
|
|
144
|
+
if (result.createdAt) {
|
|
145
|
+
const created = new Date(result.createdAt).getTime();
|
|
146
|
+
const ageHours = (now - created) / (1000 * 60 * 60);
|
|
147
|
+
const recencyScore = Math.max(0, 0.1 * Math.exp(-ageHours / 720)); // Decay over 30 days
|
|
148
|
+
score += recencyScore;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. Entity overlap: Query words appearing in content = boost
|
|
152
|
+
const queryWords = new Set(query.toLowerCase().split(/\s+/).filter(w => w.length > 3));
|
|
153
|
+
const contentWords = new Set((result.content ?? "").toLowerCase().split(/\s+/));
|
|
154
|
+
const overlap = [...queryWords].filter(w => contentWords.has(w)).length;
|
|
155
|
+
score += overlap * 0.02; // Small boost per matching word
|
|
156
|
+
|
|
157
|
+
return score;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Main search function - vectors + graph boost + heuristics + places + sessions
|
|
162
|
+
* Unified search integrating Places, Graph, and Memory
|
|
163
|
+
*/
|
|
164
|
+
export async function hybridSearch(
|
|
165
|
+
input: SearchInput,
|
|
166
|
+
options: HybridSearchOptions = {}
|
|
167
|
+
): Promise<SearchResult[]> {
|
|
168
|
+
const limit = options.limit ?? input.limit ?? 10;
|
|
169
|
+
const enableMultiSession = options.enableMultiSession !== false;
|
|
170
|
+
const enableHeuristics = options.enableHeuristics !== false;
|
|
171
|
+
const isMultiHop = enableMultiSession && isMultiSessionQuery(input.query);
|
|
172
|
+
const isTemporal = isTemporalQuery(input.query);
|
|
173
|
+
|
|
174
|
+
let vectorResults: SearchResult[];
|
|
175
|
+
|
|
176
|
+
if (isMultiHop) {
|
|
177
|
+
// Multi-hop: use expansion to get more coverage
|
|
178
|
+
const expandedQueries = expandQueryForMultiSession(input.query);
|
|
179
|
+
const allResults: SearchResult[] = [];
|
|
180
|
+
|
|
181
|
+
for (const expQuery of expandedQueries) {
|
|
182
|
+
const expResults = await vectorSearch(
|
|
183
|
+
{ ...input, query: expQuery },
|
|
184
|
+
{ ...options, limit: Math.ceil(limit * 2) }
|
|
185
|
+
);
|
|
186
|
+
allResults.push(...expResults);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const byId = new Map<string, SearchResult>();
|
|
190
|
+
for (const r of allResults) {
|
|
191
|
+
const existing = byId.get(r.id);
|
|
192
|
+
if (!existing || (r.similarity ?? 0) > (existing.similarity ?? 0)) {
|
|
193
|
+
byId.set(r.id, r);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
vectorResults = Array.from(byId.values());
|
|
197
|
+
} else if (isTemporal) {
|
|
198
|
+
// Temporal: fetch more results
|
|
199
|
+
vectorResults = await vectorSearch(input, { ...options, limit: limit * 4 });
|
|
200
|
+
} else {
|
|
201
|
+
// Regular query
|
|
202
|
+
vectorResults = await vectorSearch(input, { ...options, limit: limit * 2 });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Task 2: Integrate Places into retrieval
|
|
206
|
+
// If placeId or placeType specified, filter/boost results from that place
|
|
207
|
+
if (input.placeId || input.placeType) {
|
|
208
|
+
vectorResults = await applyPlaceFilterAndBoost(vectorResults, input, limit);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Task 3: Add session temporal scope
|
|
212
|
+
// If sessionId specified, boost memories from same session
|
|
213
|
+
if (input.sessionId) {
|
|
214
|
+
vectorResults = applySessionBoost(vectorResults, input.sessionId);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// TEMPORAL: Boost results with dates for temporal queries
|
|
218
|
+
if (isTemporal) {
|
|
219
|
+
vectorResults = applyTemporalBoost(vectorResults);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Apply heuristics if enabled (recency + entity overlap)
|
|
223
|
+
if (enableHeuristics) {
|
|
224
|
+
const now = Date.now();
|
|
225
|
+
vectorResults = vectorResults.map(r => ({
|
|
226
|
+
...r,
|
|
227
|
+
similarity: scoreWithHeuristics(r, input.query, now)
|
|
228
|
+
}));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Graph boost
|
|
232
|
+
const graphWeight = config.scoringWeights.graphBoost;
|
|
233
|
+
const candidateIds = vectorResults.map(r => r.id);
|
|
234
|
+
const graphBoostMap = await computeGraphBoost(candidateIds);
|
|
235
|
+
|
|
236
|
+
let results = applyGraphBoostWithWeight(vectorResults, graphBoostMap, limit, graphWeight);
|
|
237
|
+
|
|
238
|
+
// Expand with associated memories for better coverage
|
|
239
|
+
if (options.includeAssociations !== false) {
|
|
240
|
+
results = await expandWithAssociations(results, limit);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Task 4: Enable multi-hop graph traversal by default
|
|
244
|
+
// If query is detected as multi-hop, use actual graph traversal
|
|
245
|
+
if (isMultiHop && options.enableGraphTraversal !== false && input.project) {
|
|
246
|
+
try {
|
|
247
|
+
const graphResults = await multiHopSearch({
|
|
248
|
+
query: input.query,
|
|
249
|
+
project: input.project,
|
|
250
|
+
limit: limit,
|
|
251
|
+
includeVectorResults: false,
|
|
252
|
+
includeGraphResults: true,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Merge graph results with vector results
|
|
256
|
+
const existingIds = new Set(results.map(r => r.id));
|
|
257
|
+
for (const gr of graphResults) {
|
|
258
|
+
if (!existingIds.has(gr.id)) {
|
|
259
|
+
results.push({
|
|
260
|
+
...gr,
|
|
261
|
+
similarity: (gr.similarity ?? 0) * 0.9 // Slightly lower weight
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Re-sort
|
|
267
|
+
results.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
268
|
+
} catch (e) {
|
|
269
|
+
// Multi-hop failed, continue with vector results
|
|
270
|
+
logger.debug(`[HybridSearch] Multi-hop failed: ${e}`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return results;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Task 2: Filter and boost results by place
|
|
279
|
+
*/
|
|
280
|
+
async function applyPlaceFilterAndBoost(
|
|
281
|
+
results: SearchResult[],
|
|
282
|
+
input: SearchInput,
|
|
283
|
+
limit: number
|
|
284
|
+
): Promise<SearchResult[]> {
|
|
285
|
+
let placeIds: string[] = [];
|
|
286
|
+
|
|
287
|
+
// Get place IDs from placeId or placeType
|
|
288
|
+
if (input.placeType && input.project) {
|
|
289
|
+
const project = await requireProject(input.project);
|
|
290
|
+
const place = await getPlaceByType(project.id, input.placeType as any);
|
|
291
|
+
if (place) {
|
|
292
|
+
placeIds = [place.id];
|
|
293
|
+
}
|
|
294
|
+
} else if (input.placeId) {
|
|
295
|
+
placeIds = [input.placeId];
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (placeIds.length === 0) {
|
|
299
|
+
return results;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Get memories in this place
|
|
303
|
+
const placeMemories = new Set<string>();
|
|
304
|
+
for (const pid of placeIds) {
|
|
305
|
+
const memIds = await getPlaceMemories(pid, limit * 3);
|
|
306
|
+
for (const mid of memIds) {
|
|
307
|
+
placeMemories.add(mid);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Boost memories from the specified place
|
|
312
|
+
const PLACE_BOOST = 0.15;
|
|
313
|
+
const boosted = results.map(r => {
|
|
314
|
+
if (placeMemories.has(r.id)) {
|
|
315
|
+
return {
|
|
316
|
+
...r,
|
|
317
|
+
similarity: (r.similarity ?? 0) + PLACE_BOOST
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return r;
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Re-sort with place boost
|
|
324
|
+
boosted.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
325
|
+
|
|
326
|
+
return boosted;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Task 3: Boost memories from the same session (temporal)
|
|
331
|
+
*/
|
|
332
|
+
function applySessionBoost(
|
|
333
|
+
results: SearchResult[],
|
|
334
|
+
sessionId: string
|
|
335
|
+
): SearchResult[] {
|
|
336
|
+
const SESSION_BOOST = 0.1;
|
|
337
|
+
|
|
338
|
+
const boosted = results.map(r => {
|
|
339
|
+
// Check if memory's session matches query's session
|
|
340
|
+
const memSession = r.metadata?.sessionMetadata?.sessionId as string | undefined;
|
|
341
|
+
if (memSession === sessionId) {
|
|
342
|
+
return {
|
|
343
|
+
...r,
|
|
344
|
+
similarity: (r.similarity ?? 0) + SESSION_BOOST
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
return r;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Re-sort with session boost
|
|
351
|
+
boosted.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
352
|
+
|
|
353
|
+
return boosted;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* TEMPORAL FIX: Boost memories that contain date references for "when" questions
|
|
358
|
+
* Also boost by date RECENCY - closer to today = higher for temporal queries
|
|
359
|
+
*/
|
|
360
|
+
function applyTemporalBoost(results: SearchResult[]): SearchResult[] {
|
|
361
|
+
const TEMPORAL_BOOST = 0.25; // Moderate boost for date-containing memories
|
|
362
|
+
|
|
363
|
+
const boosted = results.map(r => {
|
|
364
|
+
let boost = 0;
|
|
365
|
+
|
|
366
|
+
// Boost 1: Has date reference - high priority for temporal
|
|
367
|
+
if (hasDateReference(r.content ?? "")) {
|
|
368
|
+
boost += TEMPORAL_BOOST;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
...r,
|
|
373
|
+
similarity: (r.similarity ?? 0) + boost
|
|
374
|
+
};
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Re-sort with temporal boost
|
|
378
|
+
boosted.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
379
|
+
|
|
380
|
+
return boosted;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* MULTI-HOP FIX: Get memories from adjacent sessions using DB query
|
|
385
|
+
* This provides actual cross-session retrieval for multi-hop queries
|
|
386
|
+
*/
|
|
387
|
+
async function applySessionAdjacencyBoost(
|
|
388
|
+
results: SearchResult[],
|
|
389
|
+
input: SearchInput,
|
|
390
|
+
limit: number
|
|
391
|
+
): Promise<SearchResult[]> {
|
|
392
|
+
if (!input.project) return results;
|
|
393
|
+
|
|
394
|
+
// Extract session tags from top results
|
|
395
|
+
const sessionTags = new Set<string>();
|
|
396
|
+
for (const r of results.slice(0, 5)) {
|
|
397
|
+
const sessionTag = extractSessionTag(r.tags);
|
|
398
|
+
if (sessionTag) {
|
|
399
|
+
sessionTags.add(sessionTag);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (sessionTags.size === 0) {
|
|
404
|
+
return results;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Get adjacent session tags
|
|
408
|
+
const adjacentSessionTags = new Set<string>();
|
|
409
|
+
for (const tag of sessionTags) {
|
|
410
|
+
const adjacent = getAdjacentSessionTags(tag);
|
|
411
|
+
for (const adj of adjacent) {
|
|
412
|
+
adjacentSessionTags.add(adj);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (adjacentSessionTags.size === 0) {
|
|
417
|
+
return results;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Fetch memories from adjacent sessions
|
|
421
|
+
const project = await requireProject(input.project);
|
|
422
|
+
const adjacentMemories: SearchResult[] = [];
|
|
423
|
+
|
|
424
|
+
for (const sessionTag of adjacentSessionTags) {
|
|
425
|
+
const memories = await getMemoriesBySession(project.id, sessionTag, limit);
|
|
426
|
+
adjacentMemories.push(...memories);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (adjacentMemories.length === 0) {
|
|
430
|
+
return results;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Merge with boost
|
|
434
|
+
const existingIds = new Set(results.map(r => r.id));
|
|
435
|
+
const ADJACENT_BOOST = 0.3;
|
|
436
|
+
const merged = [...results];
|
|
437
|
+
|
|
438
|
+
for (const mem of adjacentMemories) {
|
|
439
|
+
if (!existingIds.has(mem.id)) {
|
|
440
|
+
merged.push({
|
|
441
|
+
...mem,
|
|
442
|
+
similarity: (mem.similarity ?? 0) + ADJACENT_BOOST
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// Re-sort
|
|
448
|
+
merged.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
449
|
+
|
|
450
|
+
return merged.slice(0, limit * 2);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Expand results with directly associated memories
|
|
455
|
+
*/
|
|
456
|
+
async function expandWithAssociations(
|
|
457
|
+
results: SearchResult[],
|
|
458
|
+
limit: number
|
|
459
|
+
): Promise<SearchResult[]> {
|
|
460
|
+
const allIds = new Set(results.map(r => r.id));
|
|
461
|
+
const expanded: SearchResult[] = [...results];
|
|
462
|
+
|
|
463
|
+
// Get related for top results only
|
|
464
|
+
for (const r of results.slice(0, 3)) {
|
|
465
|
+
try {
|
|
466
|
+
const related = await getRelatedMemories(r.id, 5);
|
|
467
|
+
for (const rel of related) {
|
|
468
|
+
if (!allIds.has(rel.id)) {
|
|
469
|
+
allIds.add(rel.id);
|
|
470
|
+
expanded.push({
|
|
471
|
+
...rel,
|
|
472
|
+
similarity: (rel.similarity ?? 0) * 0.8 // Slightly lower weight
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
} catch (e) {
|
|
477
|
+
// Skip errors
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Re-sort and return top results
|
|
482
|
+
expanded.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
483
|
+
return expanded.slice(0, limit);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get memories by session ID (stored in tags as "dialogue-1", "session-2", etc.)
|
|
488
|
+
*/
|
|
489
|
+
async function getMemoriesBySession(
|
|
490
|
+
projectId: string,
|
|
491
|
+
sessionTag: string,
|
|
492
|
+
limit: number
|
|
493
|
+
): Promise<SearchResult[]> {
|
|
494
|
+
try {
|
|
495
|
+
const db = createDatabaseClient(await getDb());
|
|
496
|
+
const sqlite = db.$client as any;
|
|
497
|
+
|
|
498
|
+
// Query memories where tags contain the session tag
|
|
499
|
+
const statement = sqlite.prepare(`
|
|
500
|
+
SELECT
|
|
501
|
+
m.id as id,
|
|
502
|
+
m.project_id as projectId,
|
|
503
|
+
m.type as type,
|
|
504
|
+
m.content as content,
|
|
505
|
+
m.summary as summary,
|
|
506
|
+
m.tags as tags,
|
|
507
|
+
m.metadata as metadata,
|
|
508
|
+
m.embedding as embedding,
|
|
509
|
+
m.embedding_json as embeddingJson,
|
|
510
|
+
m.created_at as createdAt
|
|
511
|
+
FROM memories m
|
|
512
|
+
WHERE m.project_id = ? AND m.tags LIKE ?
|
|
513
|
+
ORDER BY m.created_at DESC
|
|
514
|
+
LIMIT ?
|
|
515
|
+
`);
|
|
516
|
+
|
|
517
|
+
const rows = statement.all(projectId, `%${sessionTag}%`, limit) as Array<{
|
|
518
|
+
id: string;
|
|
519
|
+
projectId: string | null;
|
|
520
|
+
type: string;
|
|
521
|
+
content: string;
|
|
522
|
+
summary: string | null;
|
|
523
|
+
tags: string | null;
|
|
524
|
+
metadata: string | null;
|
|
525
|
+
embedding: any;
|
|
526
|
+
embeddingJson: any;
|
|
527
|
+
createdAt: string | null;
|
|
528
|
+
}>;
|
|
529
|
+
|
|
530
|
+
return rows.map((row) => ({
|
|
531
|
+
id: row.id,
|
|
532
|
+
projectId: row.projectId,
|
|
533
|
+
type: row.type as any,
|
|
534
|
+
content: row.content,
|
|
535
|
+
summary: row.summary ?? undefined,
|
|
536
|
+
tags: deserializeTags(row.tags ?? null),
|
|
537
|
+
metadata: deserializeMetadata(row.metadata ?? null),
|
|
538
|
+
createdAt: row.createdAt ? (normalizeTimestamp(Number(row.createdAt)) ?? undefined) : undefined,
|
|
539
|
+
similarity: 0.5, // Default similarity for fetched memories
|
|
540
|
+
}));
|
|
541
|
+
} catch (error) {
|
|
542
|
+
logger.debug(`[HybridSearch] getMemoriesBySession failed: ${error}`);
|
|
543
|
+
return [];
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Extract session tag from memory tags (e.g., "dialogue-1", "session_1", "D1", "D2")
|
|
549
|
+
*/
|
|
550
|
+
function extractSessionTag(tags: string[] | undefined): string | null {
|
|
551
|
+
if (!tags) return null;
|
|
552
|
+
// Look for patterns:
|
|
553
|
+
// - "dialogue-1", "dialogue_1", "dialogue1"
|
|
554
|
+
// - "session-2", "session_2", "session2"
|
|
555
|
+
// - "conv-1", "conv_1", "conv1"
|
|
556
|
+
// - "D1", "D2" (capital D followed by number)
|
|
557
|
+
for (const tag of tags) {
|
|
558
|
+
const lower = tag.toLowerCase();
|
|
559
|
+
if (
|
|
560
|
+
/^(dialogue|session|conv|d)[_-]?\d+$/.test(lower)
|
|
561
|
+
) {
|
|
562
|
+
return tag;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return null;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* Get adjacent session tags (e.g., dialogue-1 -> dialogue-2, D1 -> D2)
|
|
570
|
+
*/
|
|
571
|
+
function getAdjacentSessionTags(currentTag: string): string[] {
|
|
572
|
+
const adjacent: string[] = [];
|
|
573
|
+
|
|
574
|
+
// Pattern: dialogue-N, session-N, D-N, conv-N (with or without hyphen)
|
|
575
|
+
const match = currentTag.match(/^(.*?)(\d+)$/i);
|
|
576
|
+
if (match) {
|
|
577
|
+
const prefix = match[1];
|
|
578
|
+
const num = parseInt(match[2], 10);
|
|
579
|
+
|
|
580
|
+
// Adjacent: previous and next
|
|
581
|
+
if (num > 1) {
|
|
582
|
+
adjacent.push(`${prefix}${num - 1}`);
|
|
583
|
+
}
|
|
584
|
+
adjacent.push(`${prefix}${num + 1}`);
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
return adjacent;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Vector semantic search using cosine similarity
|
|
592
|
+
*/
|
|
593
|
+
async function vectorSearch(
|
|
594
|
+
input: SearchInput,
|
|
595
|
+
options: HybridSearchOptions
|
|
596
|
+
): Promise<SearchResult[]> {
|
|
597
|
+
try {
|
|
598
|
+
const db = createDatabaseClient(await getDb());
|
|
599
|
+
const sqlite = db.$client as any;
|
|
600
|
+
const limit = options.limit ?? 10;
|
|
601
|
+
const tags = normalizeTags(options.tags ?? input.tags);
|
|
602
|
+
|
|
603
|
+
// Check for empty query
|
|
604
|
+
const isEmptyQuery = !input.query || input.query.trim() === '';
|
|
605
|
+
|
|
606
|
+
// Get query embedding (only for non-empty queries)
|
|
607
|
+
let queryEmbedding: number[] | null = null;
|
|
608
|
+
if (!isEmptyQuery) {
|
|
609
|
+
queryEmbedding = await getEmbedding(input.query);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Build WHERE conditions
|
|
613
|
+
const conditions: string[] = [];
|
|
614
|
+
const params: any[] = [];
|
|
615
|
+
|
|
616
|
+
if (input.type) {
|
|
617
|
+
conditions.push('m.type = ?');
|
|
618
|
+
params.push(input.type);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
if (tags.length) {
|
|
622
|
+
conditions.push('(' + tags.map(() => 'm.tags LIKE ?').join(' OR ') + ')');
|
|
623
|
+
params.push(...tags.map((tag) => `%${tag}%`));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
let projectId: string | null = null;
|
|
627
|
+
if (input.project) {
|
|
628
|
+
const project = await requireProject(input.project);
|
|
629
|
+
projectId = project.id;
|
|
630
|
+
conditions.push('m.project_id = ?');
|
|
631
|
+
params.push(project.id);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const whereClause = conditions.length > 0
|
|
635
|
+
? 'WHERE ' + conditions.join(' AND ')
|
|
636
|
+
: '';
|
|
637
|
+
|
|
638
|
+
// Fetch candidates for vector search
|
|
639
|
+
const statement = sqlite.prepare(`
|
|
640
|
+
SELECT
|
|
641
|
+
m.id as id,
|
|
642
|
+
m.project_id as projectId,
|
|
643
|
+
m.type as type,
|
|
644
|
+
m.content as content,
|
|
645
|
+
m.summary as summary,
|
|
646
|
+
m.tags as tags,
|
|
647
|
+
m.metadata as metadata,
|
|
648
|
+
m.embedding as embedding,
|
|
649
|
+
m.embedding_json as embeddingJson,
|
|
650
|
+
m.created_at as createdAt
|
|
651
|
+
FROM memories m
|
|
652
|
+
${whereClause}
|
|
653
|
+
ORDER BY m.created_at DESC
|
|
654
|
+
LIMIT ?
|
|
655
|
+
`);
|
|
656
|
+
|
|
657
|
+
const rows = statement.all(...params, limit * 3) as Array<{
|
|
658
|
+
id: string;
|
|
659
|
+
projectId: string | null;
|
|
660
|
+
type: string;
|
|
661
|
+
content: string;
|
|
662
|
+
summary: string | null;
|
|
663
|
+
tags: string | null;
|
|
664
|
+
metadata: string | null;
|
|
665
|
+
embedding: any;
|
|
666
|
+
embeddingJson: any;
|
|
667
|
+
createdAt: string | null;
|
|
668
|
+
}>;
|
|
669
|
+
|
|
670
|
+
// If no embedding available, return results ordered by recency
|
|
671
|
+
if (!queryEmbedding) {
|
|
672
|
+
return rows.slice(0, limit * 2).map((item) => ({
|
|
673
|
+
id: item.id,
|
|
674
|
+
projectId: item.projectId,
|
|
675
|
+
type: item.type as any,
|
|
676
|
+
content: item.content,
|
|
677
|
+
summary: item.summary ?? undefined,
|
|
678
|
+
tags: deserializeTags(item.tags ?? null),
|
|
679
|
+
metadata: deserializeMetadata(item.metadata ?? null),
|
|
680
|
+
createdAt: item.createdAt ? (normalizeTimestamp(Number(item.createdAt)) ?? undefined) : undefined,
|
|
681
|
+
similarity: 0,
|
|
682
|
+
}));
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Calculate cosine similarity for each result
|
|
686
|
+
const scored = rows
|
|
687
|
+
.map((row) => {
|
|
688
|
+
const embedding = parseEmbedding(row.embedding) ?? parseEmbedding(row.embeddingJson);
|
|
689
|
+
if (!embedding) return null;
|
|
690
|
+
|
|
691
|
+
const similarity = cosineSimilarity(queryEmbedding!, embedding);
|
|
692
|
+
return {
|
|
693
|
+
id: row.id,
|
|
694
|
+
projectId: row.projectId,
|
|
695
|
+
type: row.type,
|
|
696
|
+
content: row.content,
|
|
697
|
+
summary: row.summary,
|
|
698
|
+
tags: row.tags,
|
|
699
|
+
metadata: row.metadata,
|
|
700
|
+
createdAt: row.createdAt,
|
|
701
|
+
similarity,
|
|
702
|
+
};
|
|
703
|
+
})
|
|
704
|
+
.filter((item): item is NonNullable<typeof item> => item !== null);
|
|
705
|
+
|
|
706
|
+
// Sort by similarity (descending) and return
|
|
707
|
+
scored.sort((a, b) => b.similarity - a.similarity);
|
|
708
|
+
|
|
709
|
+
return scored.slice(0, limit * 2).map((item) => ({
|
|
710
|
+
id: item.id,
|
|
711
|
+
projectId: item.projectId,
|
|
712
|
+
type: item.type as any,
|
|
713
|
+
content: item.content,
|
|
714
|
+
summary: item.summary ?? undefined,
|
|
715
|
+
tags: deserializeTags(item.tags ?? null),
|
|
716
|
+
metadata: deserializeMetadata(item.metadata ?? null),
|
|
717
|
+
createdAt: item.createdAt ? (normalizeTimestamp(Number(item.createdAt)) ?? undefined) : undefined,
|
|
718
|
+
similarity: item.similarity,
|
|
719
|
+
}));
|
|
720
|
+
} catch (error: any) {
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Apply small graph boost to results
|
|
727
|
+
* Graph boost is ADDITIVE (not dominating)
|
|
728
|
+
*/
|
|
729
|
+
function applyGraphBoostWithWeight(
|
|
730
|
+
results: SearchResult[],
|
|
731
|
+
graphBoostMap: Record<string, number>,
|
|
732
|
+
limit: number,
|
|
733
|
+
graphWeight: number
|
|
734
|
+
): SearchResult[] {
|
|
735
|
+
// Apply SMALL additive boost to each result
|
|
736
|
+
// graphWeight should be 0.1-0.3, not > 1
|
|
737
|
+
const boosted = results.map(result => {
|
|
738
|
+
const boost = graphBoostMap[result.id] ?? 0;
|
|
739
|
+
// Add small nudge, don't replace similarity
|
|
740
|
+
const boostedSimilarity = (result.similarity ?? 0) + (boost * graphWeight);
|
|
741
|
+
return { ...result, similarity: boostedSimilarity };
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Re-sort by boosted similarity
|
|
745
|
+
boosted.sort((a, b) => (b.similarity ?? 0) - (a.similarity ?? 0));
|
|
746
|
+
|
|
747
|
+
return boosted.slice(0, limit);
|
|
748
|
+
}
|