contextpro-mcp 0.0.0-stage → 1.4.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/CHANGELOG.md +416 -0
- package/INSTALL.md +128 -0
- package/INSTRUCTIONS.md +33 -0
- package/LICENSE +21 -0
- package/NOTICE +12 -0
- package/README.md +164 -2
- package/SECURITY.md +27 -0
- package/agent-guide.md +323 -0
- package/agent-instructions.md +1 -0
- package/build/core/agent-setup.d.ts +5 -0
- package/build/core/agent-setup.d.ts.map +1 -0
- package/build/core/agent-setup.js +94 -0
- package/build/core/agent-setup.js.map +1 -0
- package/build/core/artifacts.d.ts +64 -0
- package/build/core/artifacts.d.ts.map +1 -0
- package/build/core/artifacts.js +370 -0
- package/build/core/artifacts.js.map +1 -0
- package/build/core/bounded-source.d.ts +15 -0
- package/build/core/bounded-source.d.ts.map +1 -0
- package/build/core/bounded-source.js +78 -0
- package/build/core/bounded-source.js.map +1 -0
- package/build/core/changed-since.d.ts +20 -0
- package/build/core/changed-since.d.ts.map +1 -0
- package/build/core/changed-since.js +43 -0
- package/build/core/changed-since.js.map +1 -0
- package/build/core/chat-model.d.ts +8 -0
- package/build/core/chat-model.d.ts.map +1 -0
- package/build/core/chat-model.js +135 -0
- package/build/core/chat-model.js.map +1 -0
- package/build/core/clustering.d.ts +6 -0
- package/build/core/clustering.d.ts.map +1 -0
- package/build/core/clustering.js +201 -0
- package/build/core/clustering.js.map +1 -0
- package/build/core/context-metadata.d.ts +50 -0
- package/build/core/context-metadata.d.ts.map +1 -0
- package/build/core/context-metadata.js +118 -0
- package/build/core/context-metadata.js.map +1 -0
- package/build/core/data-dir.d.ts +5 -0
- package/build/core/data-dir.d.ts.map +1 -0
- package/build/core/data-dir.js +53 -0
- package/build/core/data-dir.js.map +1 -0
- package/build/core/db-native.d.ts +28 -0
- package/build/core/db-native.d.ts.map +1 -0
- package/build/core/db-native.js +120 -0
- package/build/core/db-native.js.map +1 -0
- package/build/core/db.d.ts +294 -0
- package/build/core/db.d.ts.map +1 -0
- package/build/core/db.js +1047 -0
- package/build/core/db.js.map +1 -0
- package/build/core/doctor.d.ts +13 -0
- package/build/core/doctor.d.ts.map +1 -0
- package/build/core/doctor.js +55 -0
- package/build/core/doctor.js.map +1 -0
- package/build/core/efficiency.d.ts +16 -0
- package/build/core/efficiency.d.ts.map +1 -0
- package/build/core/efficiency.js +170 -0
- package/build/core/efficiency.js.map +1 -0
- package/build/core/embedding-tracker.d.ts +25 -0
- package/build/core/embedding-tracker.d.ts.map +1 -0
- package/build/core/embedding-tracker.js +228 -0
- package/build/core/embedding-tracker.js.map +1 -0
- package/build/core/embeddings.d.ts +90 -0
- package/build/core/embeddings.d.ts.map +1 -0
- package/build/core/embeddings.js +693 -0
- package/build/core/embeddings.js.map +1 -0
- package/build/core/evidence-state.d.ts +42 -0
- package/build/core/evidence-state.d.ts.map +1 -0
- package/build/core/evidence-state.js +153 -0
- package/build/core/evidence-state.js.map +1 -0
- package/build/core/extractor-stamp.d.ts +10 -0
- package/build/core/extractor-stamp.d.ts.map +1 -0
- package/build/core/extractor-stamp.js +51 -0
- package/build/core/extractor-stamp.js.map +1 -0
- package/build/core/feature-brief-cache.d.ts +37 -0
- package/build/core/feature-brief-cache.d.ts.map +1 -0
- package/build/core/feature-brief-cache.js +100 -0
- package/build/core/feature-brief-cache.js.map +1 -0
- package/build/core/file-fingerprint.d.ts +15 -0
- package/build/core/file-fingerprint.d.ts.map +1 -0
- package/build/core/file-fingerprint.js +24 -0
- package/build/core/file-fingerprint.js.map +1 -0
- package/build/core/graph-analytics.d.ts +46 -0
- package/build/core/graph-analytics.d.ts.map +1 -0
- package/build/core/graph-analytics.js +371 -0
- package/build/core/graph-analytics.js.map +1 -0
- package/build/core/hub.d.ts +23 -0
- package/build/core/hub.d.ts.map +1 -0
- package/build/core/hub.js +123 -0
- package/build/core/hub.js.map +1 -0
- package/build/core/ignore-dirs.d.ts +7 -0
- package/build/core/ignore-dirs.d.ts.map +1 -0
- package/build/core/ignore-dirs.js +84 -0
- package/build/core/ignore-dirs.js.map +1 -0
- package/build/core/index-freshness.d.ts +15 -0
- package/build/core/index-freshness.d.ts.map +1 -0
- package/build/core/index-freshness.js +96 -0
- package/build/core/index-freshness.js.map +1 -0
- package/build/core/index-reconcile.d.ts +28 -0
- package/build/core/index-reconcile.d.ts.map +1 -0
- package/build/core/index-reconcile.js +129 -0
- package/build/core/index-reconcile.js.map +1 -0
- package/build/core/local-daemon.d.ts +5 -0
- package/build/core/local-daemon.d.ts.map +1 -0
- package/build/core/local-daemon.js +282 -0
- package/build/core/local-daemon.js.map +1 -0
- package/build/core/memory-graph.d.ts +68 -0
- package/build/core/memory-graph.d.ts.map +1 -0
- package/build/core/memory-graph.js +152 -0
- package/build/core/memory-graph.js.map +1 -0
- package/build/core/native-repair.d.ts +12 -0
- package/build/core/native-repair.d.ts.map +1 -0
- package/build/core/native-repair.js +167 -0
- package/build/core/native-repair.js.map +1 -0
- package/build/core/native-runtime.d.ts +12 -0
- package/build/core/native-runtime.d.ts.map +1 -0
- package/build/core/native-runtime.js +38 -0
- package/build/core/native-runtime.js.map +1 -0
- package/build/core/pagerank.d.ts +29 -0
- package/build/core/pagerank.d.ts.map +1 -0
- package/build/core/pagerank.js +90 -0
- package/build/core/pagerank.js.map +1 -0
- package/build/core/parser-process.d.ts +21 -0
- package/build/core/parser-process.d.ts.map +1 -0
- package/build/core/parser-process.js +103 -0
- package/build/core/parser-process.js.map +1 -0
- package/build/core/parser-worker.d.ts +2 -0
- package/build/core/parser-worker.d.ts.map +1 -0
- package/build/core/parser-worker.js +17 -0
- package/build/core/parser-worker.js.map +1 -0
- package/build/core/parser.d.ts +66 -0
- package/build/core/parser.d.ts.map +1 -0
- package/build/core/parser.js +750 -0
- package/build/core/parser.js.map +1 -0
- package/build/core/path-guard.d.ts +6 -0
- package/build/core/path-guard.d.ts.map +1 -0
- package/build/core/path-guard.js +55 -0
- package/build/core/path-guard.js.map +1 -0
- package/build/core/process-lifecycle.d.ts +30 -0
- package/build/core/process-lifecycle.d.ts.map +1 -0
- package/build/core/process-lifecycle.js +129 -0
- package/build/core/process-lifecycle.js.map +1 -0
- package/build/core/project-config.d.ts +83 -0
- package/build/core/project-config.d.ts.map +1 -0
- package/build/core/project-config.js +302 -0
- package/build/core/project-config.js.map +1 -0
- package/build/core/project-graph.d.ts +69 -0
- package/build/core/project-graph.d.ts.map +1 -0
- package/build/core/project-graph.js +874 -0
- package/build/core/project-graph.js.map +1 -0
- package/build/core/provider-config.d.ts +15 -0
- package/build/core/provider-config.d.ts.map +1 -0
- package/build/core/provider-config.js +67 -0
- package/build/core/provider-config.js.map +1 -0
- package/build/core/provider-usage.d.ts +21 -0
- package/build/core/provider-usage.d.ts.map +1 -0
- package/build/core/provider-usage.js +52 -0
- package/build/core/provider-usage.js.map +1 -0
- package/build/core/python-language-server.d.ts +23 -0
- package/build/core/python-language-server.d.ts.map +1 -0
- package/build/core/python-language-server.js +217 -0
- package/build/core/python-language-server.js.map +1 -0
- package/build/core/python-runtime-identity.d.ts +3 -0
- package/build/core/python-runtime-identity.d.ts.map +1 -0
- package/build/core/python-runtime-identity.js +53 -0
- package/build/core/python-runtime-identity.js.map +1 -0
- package/build/core/reconcile-scheduler.d.ts +20 -0
- package/build/core/reconcile-scheduler.d.ts.map +1 -0
- package/build/core/reconcile-scheduler.js +58 -0
- package/build/core/reconcile-scheduler.js.map +1 -0
- package/build/core/request-context.d.ts +7 -0
- package/build/core/request-context.d.ts.map +1 -0
- package/build/core/request-context.js +17 -0
- package/build/core/request-context.js.map +1 -0
- package/build/core/request-root.d.ts +19 -0
- package/build/core/request-root.d.ts.map +1 -0
- package/build/core/request-root.js +26 -0
- package/build/core/request-root.js.map +1 -0
- package/build/core/root-guard.d.ts +8 -0
- package/build/core/root-guard.d.ts.map +1 -0
- package/build/core/root-guard.js +53 -0
- package/build/core/root-guard.js.map +1 -0
- package/build/core/roots-detect.d.ts +17 -0
- package/build/core/roots-detect.d.ts.map +1 -0
- package/build/core/roots-detect.js +63 -0
- package/build/core/roots-detect.js.map +1 -0
- package/build/core/runtime-session.d.ts +5 -0
- package/build/core/runtime-session.d.ts.map +1 -0
- package/build/core/runtime-session.js +10 -0
- package/build/core/runtime-session.js.map +1 -0
- package/build/core/scip-index.d.ts +71 -0
- package/build/core/scip-index.d.ts.map +1 -0
- package/build/core/scip-index.js +343 -0
- package/build/core/scip-index.js.map +1 -0
- package/build/core/semantic-diff.d.ts +32 -0
- package/build/core/semantic-diff.d.ts.map +1 -0
- package/build/core/semantic-diff.js +259 -0
- package/build/core/semantic-diff.js.map +1 -0
- package/build/core/source-context.d.ts +3 -0
- package/build/core/source-context.d.ts.map +1 -0
- package/build/core/source-context.js +86 -0
- package/build/core/source-context.js.map +1 -0
- package/build/core/source-mask.d.ts +8 -0
- package/build/core/source-mask.d.ts.map +1 -0
- package/build/core/source-mask.js +156 -0
- package/build/core/source-mask.js.map +1 -0
- package/build/core/source-mutation.d.ts +16 -0
- package/build/core/source-mutation.d.ts.map +1 -0
- package/build/core/source-mutation.js +193 -0
- package/build/core/source-mutation.js.map +1 -0
- package/build/core/sql-schema.d.ts +59 -0
- package/build/core/sql-schema.d.ts.map +1 -0
- package/build/core/sql-schema.js +218 -0
- package/build/core/sql-schema.js.map +1 -0
- package/build/core/startup-health.d.ts +40 -0
- package/build/core/startup-health.d.ts.map +1 -0
- package/build/core/startup-health.js +132 -0
- package/build/core/startup-health.js.map +1 -0
- package/build/core/team-ledger.d.ts +60 -0
- package/build/core/team-ledger.d.ts.map +1 -0
- package/build/core/team-ledger.js +171 -0
- package/build/core/team-ledger.js.map +1 -0
- package/build/core/test-impact.d.ts +23 -0
- package/build/core/test-impact.d.ts.map +1 -0
- package/build/core/test-impact.js +131 -0
- package/build/core/test-impact.js.map +1 -0
- package/build/core/tool-metrics.d.ts +29 -0
- package/build/core/tool-metrics.d.ts.map +1 -0
- package/build/core/tool-metrics.js +74 -0
- package/build/core/tool-metrics.js.map +1 -0
- package/build/core/tool-profile.d.ts +12 -0
- package/build/core/tool-profile.d.ts.map +1 -0
- package/build/core/tool-profile.js +102 -0
- package/build/core/tool-profile.js.map +1 -0
- package/build/core/tool-trace.d.ts +8 -0
- package/build/core/tool-trace.d.ts.map +1 -0
- package/build/core/tool-trace.js +70 -0
- package/build/core/tool-trace.js.map +1 -0
- package/build/core/tree-sitter-runtime.d.ts +20 -0
- package/build/core/tree-sitter-runtime.d.ts.map +1 -0
- package/build/core/tree-sitter-runtime.js +324 -0
- package/build/core/tree-sitter-runtime.js.map +1 -0
- package/build/core/tree-sitter.d.ts +6 -0
- package/build/core/tree-sitter.d.ts.map +1 -0
- package/build/core/tree-sitter.js +24 -0
- package/build/core/tree-sitter.js.map +1 -0
- package/build/core/typescript-reference-process.d.ts +30 -0
- package/build/core/typescript-reference-process.d.ts.map +1 -0
- package/build/core/typescript-reference-process.js +124 -0
- package/build/core/typescript-reference-process.js.map +1 -0
- package/build/core/typescript-reference-worker.d.ts +2 -0
- package/build/core/typescript-reference-worker.d.ts.map +1 -0
- package/build/core/typescript-reference-worker.js +14 -0
- package/build/core/typescript-reference-worker.js.map +1 -0
- package/build/core/typescript-references.d.ts +71 -0
- package/build/core/typescript-references.d.ts.map +1 -0
- package/build/core/typescript-references.js +392 -0
- package/build/core/typescript-references.js.map +1 -0
- package/build/core/typescript-skeleton.d.ts +5 -0
- package/build/core/typescript-skeleton.d.ts.map +1 -0
- package/build/core/typescript-skeleton.js +49 -0
- package/build/core/typescript-skeleton.js.map +1 -0
- package/build/core/walker.d.ts +16 -0
- package/build/core/walker.d.ts.map +1 -0
- package/build/core/walker.js +152 -0
- package/build/core/walker.js.map +1 -0
- package/build/core/workspace-router.d.ts +28 -0
- package/build/core/workspace-router.d.ts.map +1 -0
- package/build/core/workspace-router.js +93 -0
- package/build/core/workspace-router.js.map +1 -0
- package/build/core/worktree-gc.d.ts +16 -0
- package/build/core/worktree-gc.d.ts.map +1 -0
- package/build/core/worktree-gc.js +69 -0
- package/build/core/worktree-gc.js.map +1 -0
- package/build/core/worktree-overlay.d.ts +22 -0
- package/build/core/worktree-overlay.d.ts.map +1 -0
- package/build/core/worktree-overlay.js +84 -0
- package/build/core/worktree-overlay.js.map +1 -0
- package/build/core/worktree-root-guard.d.ts +8 -0
- package/build/core/worktree-root-guard.d.ts.map +1 -0
- package/build/core/worktree-root-guard.js +82 -0
- package/build/core/worktree-root-guard.js.map +1 -0
- package/build/core/worktree-seed.d.ts +14 -0
- package/build/core/worktree-seed.d.ts.map +1 -0
- package/build/core/worktree-seed.js +98 -0
- package/build/core/worktree-seed.js.map +1 -0
- package/build/git/shadow.d.ts +40 -0
- package/build/git/shadow.d.ts.map +1 -0
- package/build/git/shadow.js +298 -0
- package/build/git/shadow.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +599 -0
- package/build/index.js.map +1 -0
- package/build/tools/agentic-context.d.ts +86 -0
- package/build/tools/agentic-context.d.ts.map +1 -0
- package/build/tools/agentic-context.js +1546 -0
- package/build/tools/agentic-context.js.map +1 -0
- package/build/tools/blast-radius.d.ts +24 -0
- package/build/tools/blast-radius.d.ts.map +1 -0
- package/build/tools/blast-radius.js +231 -0
- package/build/tools/blast-radius.js.map +1 -0
- package/build/tools/changed-since.d.ts +7 -0
- package/build/tools/changed-since.d.ts.map +1 -0
- package/build/tools/changed-since.js +68 -0
- package/build/tools/changed-since.js.map +1 -0
- package/build/tools/compiler-actions.d.ts +26 -0
- package/build/tools/compiler-actions.d.ts.map +1 -0
- package/build/tools/compiler-actions.js +121 -0
- package/build/tools/compiler-actions.js.map +1 -0
- package/build/tools/context-tree.d.ts +9 -0
- package/build/tools/context-tree.d.ts.map +1 -0
- package/build/tools/context-tree.js +131 -0
- package/build/tools/context-tree.js.map +1 -0
- package/build/tools/context.d.ts +37 -0
- package/build/tools/context.d.ts.map +1 -0
- package/build/tools/context.js +800 -0
- package/build/tools/context.js.map +1 -0
- package/build/tools/diff.d.ts +9 -0
- package/build/tools/diff.d.ts.map +1 -0
- package/build/tools/diff.js +176 -0
- package/build/tools/diff.js.map +1 -0
- package/build/tools/directory-prior.d.ts +16 -0
- package/build/tools/directory-prior.d.ts.map +1 -0
- package/build/tools/directory-prior.js +121 -0
- package/build/tools/directory-prior.js.map +1 -0
- package/build/tools/discovery-documents.d.ts +11 -0
- package/build/tools/discovery-documents.d.ts.map +1 -0
- package/build/tools/discovery-documents.js +71 -0
- package/build/tools/discovery-documents.js.map +1 -0
- package/build/tools/discovery-hints.d.ts +25 -0
- package/build/tools/discovery-hints.d.ts.map +1 -0
- package/build/tools/discovery-hints.js +329 -0
- package/build/tools/discovery-hints.js.map +1 -0
- package/build/tools/efficiency-report.d.ts +13 -0
- package/build/tools/efficiency-report.d.ts.map +1 -0
- package/build/tools/efficiency-report.js +78 -0
- package/build/tools/efficiency-report.js.map +1 -0
- package/build/tools/evidence-retrieval.d.ts +33 -0
- package/build/tools/evidence-retrieval.d.ts.map +1 -0
- package/build/tools/evidence-retrieval.js +200 -0
- package/build/tools/evidence-retrieval.js.map +1 -0
- package/build/tools/exact-references.d.ts +10 -0
- package/build/tools/exact-references.d.ts.map +1 -0
- package/build/tools/exact-references.js +349 -0
- package/build/tools/exact-references.js.map +1 -0
- package/build/tools/explain.d.ts +14 -0
- package/build/tools/explain.d.ts.map +1 -0
- package/build/tools/explain.js +370 -0
- package/build/tools/explain.js.map +1 -0
- package/build/tools/feature-brief.d.ts +7 -0
- package/build/tools/feature-brief.d.ts.map +1 -0
- package/build/tools/feature-brief.js +89 -0
- package/build/tools/feature-brief.js.map +1 -0
- package/build/tools/feature-hub.d.ts +15 -0
- package/build/tools/feature-hub.d.ts.map +1 -0
- package/build/tools/feature-hub.js +404 -0
- package/build/tools/feature-hub.js.map +1 -0
- package/build/tools/file-skeleton.d.ts +7 -0
- package/build/tools/file-skeleton.d.ts.map +1 -0
- package/build/tools/file-skeleton.js +190 -0
- package/build/tools/file-skeleton.js.map +1 -0
- package/build/tools/grep.d.ts +25 -0
- package/build/tools/grep.d.ts.map +1 -0
- package/build/tools/grep.js +341 -0
- package/build/tools/grep.js.map +1 -0
- package/build/tools/impact-delta.d.ts +7 -0
- package/build/tools/impact-delta.d.ts.map +1 -0
- package/build/tools/impact-delta.js +102 -0
- package/build/tools/impact-delta.js.map +1 -0
- package/build/tools/memory-tools.d.ts +69 -0
- package/build/tools/memory-tools.d.ts.map +1 -0
- package/build/tools/memory-tools.js +366 -0
- package/build/tools/memory-tools.js.map +1 -0
- package/build/tools/planning-helpers.d.ts +26 -0
- package/build/tools/planning-helpers.d.ts.map +1 -0
- package/build/tools/planning-helpers.js +269 -0
- package/build/tools/planning-helpers.js.map +1 -0
- package/build/tools/project-graph.d.ts +19 -0
- package/build/tools/project-graph.d.ts.map +1 -0
- package/build/tools/project-graph.js +259 -0
- package/build/tools/project-graph.js.map +1 -0
- package/build/tools/propose-commit.d.ts +9 -0
- package/build/tools/propose-commit.d.ts.map +1 -0
- package/build/tools/propose-commit.js +156 -0
- package/build/tools/propose-commit.js.map +1 -0
- package/build/tools/register-tools.d.ts +18 -0
- package/build/tools/register-tools.d.ts.map +1 -0
- package/build/tools/register-tools.js +1131 -0
- package/build/tools/register-tools.js.map +1 -0
- package/build/tools/reindex.d.ts +76 -0
- package/build/tools/reindex.d.ts.map +1 -0
- package/build/tools/reindex.js +871 -0
- package/build/tools/reindex.js.map +1 -0
- package/build/tools/related.d.ts +6 -0
- package/build/tools/related.d.ts.map +1 -0
- package/build/tools/related.js +143 -0
- package/build/tools/related.js.map +1 -0
- package/build/tools/repo-map.d.ts +59 -0
- package/build/tools/repo-map.d.ts.map +1 -0
- package/build/tools/repo-map.js +270 -0
- package/build/tools/repo-map.js.map +1 -0
- package/build/tools/resolve-symbol.d.ts +41 -0
- package/build/tools/resolve-symbol.d.ts.map +1 -0
- package/build/tools/resolve-symbol.js +250 -0
- package/build/tools/resolve-symbol.js.map +1 -0
- package/build/tools/retrieval-helpers.d.ts +35 -0
- package/build/tools/retrieval-helpers.d.ts.map +1 -0
- package/build/tools/retrieval-helpers.js +302 -0
- package/build/tools/retrieval-helpers.js.map +1 -0
- package/build/tools/scip-index.d.ts +12 -0
- package/build/tools/scip-index.d.ts.map +1 -0
- package/build/tools/scip-index.js +66 -0
- package/build/tools/scip-index.js.map +1 -0
- package/build/tools/scoped-discovery.d.ts +7 -0
- package/build/tools/scoped-discovery.d.ts.map +1 -0
- package/build/tools/scoped-discovery.js +89 -0
- package/build/tools/scoped-discovery.js.map +1 -0
- package/build/tools/search.d.ts +62 -0
- package/build/tools/search.d.ts.map +1 -0
- package/build/tools/search.js +419 -0
- package/build/tools/search.js.map +1 -0
- package/build/tools/semantic-diff.d.ts +7 -0
- package/build/tools/semantic-diff.d.ts.map +1 -0
- package/build/tools/semantic-diff.js +45 -0
- package/build/tools/semantic-diff.js.map +1 -0
- package/build/tools/semantic-identifiers.d.ts +51 -0
- package/build/tools/semantic-identifiers.d.ts.map +1 -0
- package/build/tools/semantic-identifiers.js +522 -0
- package/build/tools/semantic-identifiers.js.map +1 -0
- package/build/tools/semantic-navigate.d.ts +18 -0
- package/build/tools/semantic-navigate.d.ts.map +1 -0
- package/build/tools/semantic-navigate.js +413 -0
- package/build/tools/semantic-navigate.js.map +1 -0
- package/build/tools/semantic-search.d.ts +30 -0
- package/build/tools/semantic-search.d.ts.map +1 -0
- package/build/tools/semantic-search.js +466 -0
- package/build/tools/semantic-search.js.map +1 -0
- package/build/tools/sql-schema.d.ts +23 -0
- package/build/tools/sql-schema.d.ts.map +1 -0
- package/build/tools/sql-schema.js +155 -0
- package/build/tools/sql-schema.js.map +1 -0
- package/build/tools/static-analysis.d.ts +7 -0
- package/build/tools/static-analysis.d.ts.map +1 -0
- package/build/tools/static-analysis.js +331 -0
- package/build/tools/static-analysis.js.map +1 -0
- package/build/tools/status.d.ts +74 -0
- package/build/tools/status.d.ts.map +1 -0
- package/build/tools/status.js +227 -0
- package/build/tools/status.js.map +1 -0
- package/build/tools/symbolic-edit.d.ts +57 -0
- package/build/tools/symbolic-edit.d.ts.map +1 -0
- package/build/tools/symbolic-edit.js +500 -0
- package/build/tools/symbolic-edit.js.map +1 -0
- package/build/tools/test-impact.d.ts +7 -0
- package/build/tools/test-impact.d.ts.map +1 -0
- package/build/tools/test-impact.js +34 -0
- package/build/tools/test-impact.js.map +1 -0
- package/build/tools/test-map.d.ts +8 -0
- package/build/tools/test-map.d.ts.map +1 -0
- package/build/tools/test-map.js +75 -0
- package/build/tools/test-map.js.map +1 -0
- package/docs/README.md +26 -0
- package/docs/architecture.md +35 -0
- package/docs/configuration.md +178 -0
- package/docs/maintenance.md +77 -0
- package/package.json +80 -4
package/build/core/db.js
ADDED
|
@@ -0,0 +1,1047 @@
|
|
|
1
|
+
// SQLite storage layer with vec0 vector search for embeddings and memory graph
|
|
2
|
+
// FEATURE: Core persistence — replaces JSON files with indexed, queryable storage
|
|
3
|
+
import { createHash } from "crypto";
|
|
4
|
+
import { join, resolve } from "path";
|
|
5
|
+
import { resolveProjectPath } from "./path-guard.js";
|
|
6
|
+
import { CONTEXTPRO_DIR, ensureContextProDir } from "./data-dir.js";
|
|
7
|
+
import { ensureNativeStorage, loadNativeStorageBindings } from "./db-native.js";
|
|
8
|
+
export { describeNativeStorageError, probeNativeStorage } from "./db-native.js";
|
|
9
|
+
const SCHEMA_VERSION = "7";
|
|
10
|
+
const DECAY_LAMBDA = 0.05;
|
|
11
|
+
const STALE_EDGE_THRESHOLD = 0.15;
|
|
12
|
+
const STALE_NODE_DAYS = 7;
|
|
13
|
+
const STALE_NODE_MIN_ACCESS = 2;
|
|
14
|
+
const PRUNE_INTERVAL_HOURS = 24;
|
|
15
|
+
const SCHEMA_SQL = `
|
|
16
|
+
CREATE TABLE IF NOT EXISTS files (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
path TEXT UNIQUE NOT NULL,
|
|
19
|
+
content_hash TEXT NOT NULL,
|
|
20
|
+
header TEXT,
|
|
21
|
+
language TEXT,
|
|
22
|
+
symbol_count INTEGER DEFAULT 0,
|
|
23
|
+
last_indexed_at INTEGER NOT NULL,
|
|
24
|
+
file_size INTEGER DEFAULT 0,
|
|
25
|
+
file_mtime_ms INTEGER DEFAULT 0,
|
|
26
|
+
file_ctime_ms REAL DEFAULT 0
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
30
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
31
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
32
|
+
name TEXT NOT NULL,
|
|
33
|
+
kind TEXT NOT NULL,
|
|
34
|
+
line_start INTEGER NOT NULL,
|
|
35
|
+
line_end INTEGER,
|
|
36
|
+
signature TEXT,
|
|
37
|
+
parent_symbol_id INTEGER REFERENCES symbols(id) ON DELETE SET NULL
|
|
38
|
+
);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_file ON symbols(file_id);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_name ON symbols(name);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_symbols_parent ON symbols(parent_symbol_id);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE IF NOT EXISTS memory_nodes (
|
|
44
|
+
id TEXT PRIMARY KEY,
|
|
45
|
+
type TEXT NOT NULL CHECK(type IN ('concept', 'file', 'symbol', 'note')),
|
|
46
|
+
label TEXT NOT NULL,
|
|
47
|
+
content TEXT NOT NULL,
|
|
48
|
+
created_at INTEGER NOT NULL,
|
|
49
|
+
last_accessed INTEGER NOT NULL,
|
|
50
|
+
access_count INTEGER DEFAULT 0,
|
|
51
|
+
metadata TEXT DEFAULT '{}'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE TABLE IF NOT EXISTS memory_edges (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
source TEXT NOT NULL REFERENCES memory_nodes(id) ON DELETE CASCADE,
|
|
57
|
+
target TEXT NOT NULL REFERENCES memory_nodes(id) ON DELETE CASCADE,
|
|
58
|
+
relation TEXT NOT NULL CHECK(relation IN ('relates_to', 'depends_on', 'implements', 'references', 'similar_to', 'contains', 'resolved_by', 'contradicts')),
|
|
59
|
+
weight REAL DEFAULT 1.0,
|
|
60
|
+
created_at INTEGER NOT NULL,
|
|
61
|
+
metadata TEXT DEFAULT '{}'
|
|
62
|
+
);
|
|
63
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON memory_edges(source);
|
|
64
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON memory_edges(target);
|
|
65
|
+
|
|
66
|
+
CREATE TABLE IF NOT EXISTS imports (
|
|
67
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
68
|
+
source_file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
69
|
+
target_path TEXT NOT NULL,
|
|
70
|
+
target_file_id INTEGER REFERENCES files(id) ON DELETE SET NULL,
|
|
71
|
+
import_type TEXT DEFAULT 'static',
|
|
72
|
+
symbols_imported TEXT
|
|
73
|
+
);
|
|
74
|
+
CREATE INDEX IF NOT EXISTS idx_imports_source ON imports(source_file_id);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_imports_target ON imports(target_file_id);
|
|
76
|
+
|
|
77
|
+
CREATE TABLE IF NOT EXISTS meta (
|
|
78
|
+
key TEXT PRIMARY KEY,
|
|
79
|
+
value TEXT
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
-- Append-only telemetry of MCP tool invocations, so external readers
|
|
83
|
+
-- can show real history (which tools ran, when, latency, errors) across restarts.
|
|
84
|
+
-- Created unconditionally (IF NOT EXISTS) so no schema-version bump is needed.
|
|
85
|
+
CREATE TABLE IF NOT EXISTS tool_calls (
|
|
86
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
87
|
+
tool TEXT NOT NULL,
|
|
88
|
+
status TEXT NOT NULL, -- 'ok' | 'error'
|
|
89
|
+
duration_ms INTEGER NOT NULL,
|
|
90
|
+
called_at INTEGER NOT NULL, -- ms since epoch
|
|
91
|
+
input_tokens_est INTEGER NOT NULL DEFAULT 0,
|
|
92
|
+
output_tokens_est INTEGER NOT NULL DEFAULT 0,
|
|
93
|
+
source_tokens_est INTEGER NOT NULL DEFAULT 0,
|
|
94
|
+
saved_tokens_est INTEGER NOT NULL DEFAULT 0,
|
|
95
|
+
represented_files INTEGER NOT NULL DEFAULT 0,
|
|
96
|
+
session_id TEXT
|
|
97
|
+
);
|
|
98
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_called_at ON tool_calls(called_at);
|
|
99
|
+
CREATE INDEX IF NOT EXISTS idx_tool_calls_tool ON tool_calls(tool);
|
|
100
|
+
|
|
101
|
+
CREATE TABLE IF NOT EXISTS call_sites (
|
|
102
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
103
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
104
|
+
callee TEXT NOT NULL,
|
|
105
|
+
line INTEGER NOT NULL,
|
|
106
|
+
context TEXT
|
|
107
|
+
);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_call_sites_file ON call_sites(file_id);
|
|
109
|
+
CREATE INDEX IF NOT EXISTS idx_call_sites_callee ON call_sites(callee);
|
|
110
|
+
|
|
111
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS file_search_fts USING fts5(
|
|
112
|
+
path UNINDEXED,
|
|
113
|
+
header,
|
|
114
|
+
symbols,
|
|
115
|
+
language,
|
|
116
|
+
body,
|
|
117
|
+
tokenize='porter unicode61'
|
|
118
|
+
);
|
|
119
|
+
`;
|
|
120
|
+
function generateId(prefix) {
|
|
121
|
+
return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
122
|
+
}
|
|
123
|
+
export function hashContent(content) {
|
|
124
|
+
return createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
125
|
+
}
|
|
126
|
+
function vecToBuffer(vec) {
|
|
127
|
+
const f32 = vec instanceof Float32Array ? vec : new Float32Array(vec);
|
|
128
|
+
return Buffer.from(f32.buffer, f32.byteOffset, f32.byteLength);
|
|
129
|
+
}
|
|
130
|
+
export function bufferToVec(buf) {
|
|
131
|
+
const f32 = new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
|
|
132
|
+
return Array.from(f32);
|
|
133
|
+
}
|
|
134
|
+
export class ContextPlusDB {
|
|
135
|
+
db;
|
|
136
|
+
// Other MCP connections can migrate the vector tables while this one lives.
|
|
137
|
+
get vecDimension() {
|
|
138
|
+
const value = this.getMeta("vec_dimension");
|
|
139
|
+
return value ? Number.parseInt(value, 10) : null;
|
|
140
|
+
}
|
|
141
|
+
rootDir;
|
|
142
|
+
constructor(rootDir) {
|
|
143
|
+
this.rootDir = rootDir;
|
|
144
|
+
const dbPath = resolveProjectPath(rootDir, join(CONTEXTPRO_DIR, "index.db"));
|
|
145
|
+
for (const suffix of ["-wal", "-shm", "-journal"])
|
|
146
|
+
resolveProjectPath(rootDir, `${dbPath}${suffix}`);
|
|
147
|
+
const { Database, sqliteVec } = loadNativeStorageBindings();
|
|
148
|
+
this.db = new Database(dbPath);
|
|
149
|
+
sqliteVec.load(this.db);
|
|
150
|
+
// FTS stemming/punctuation folding finds candidates, but a literal key must
|
|
151
|
+
// also occur in the indexed text. Verify before LIMIT, not after truncation.
|
|
152
|
+
this.db.function("contextpro_literal_contains", { deterministic: true }, (body, needle) => Number(String(body ?? "").normalize("NFC").toLowerCase().includes(String(needle))));
|
|
153
|
+
this.db.pragma("journal_mode = WAL");
|
|
154
|
+
this.db.pragma("synchronous = NORMAL");
|
|
155
|
+
this.db.pragma("busy_timeout = 5000");
|
|
156
|
+
this.db.pragma("foreign_keys = ON");
|
|
157
|
+
this.db.exec(SCHEMA_SQL);
|
|
158
|
+
this.initMeta();
|
|
159
|
+
this.runMigrations();
|
|
160
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id, called_at)");
|
|
161
|
+
this.ensureVecTables();
|
|
162
|
+
}
|
|
163
|
+
initMeta() {
|
|
164
|
+
const existing = this.db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get();
|
|
165
|
+
if (!existing) {
|
|
166
|
+
this.db.prepare("INSERT INTO meta (key, value) VALUES ('schema_version', ?)").run(SCHEMA_VERSION);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
runMigrations() {
|
|
170
|
+
const currentStr = this.db.prepare("SELECT value FROM meta WHERE key = 'schema_version'").get()?.value ?? "1";
|
|
171
|
+
if (currentStr === SCHEMA_VERSION)
|
|
172
|
+
return;
|
|
173
|
+
// Numeric comparison so the gates stay correct past version 9 (lexicographic "10" < "2" was a bug).
|
|
174
|
+
const current = Number.parseInt(currentStr, 10) || 1;
|
|
175
|
+
if (current < 2) {
|
|
176
|
+
this.db.exec(`
|
|
177
|
+
CREATE TABLE IF NOT EXISTS memory_edges_new (
|
|
178
|
+
id TEXT PRIMARY KEY,
|
|
179
|
+
source TEXT NOT NULL REFERENCES memory_nodes(id) ON DELETE CASCADE,
|
|
180
|
+
target TEXT NOT NULL REFERENCES memory_nodes(id) ON DELETE CASCADE,
|
|
181
|
+
relation TEXT NOT NULL CHECK(relation IN ('relates_to', 'depends_on', 'implements', 'references', 'similar_to', 'contains', 'resolved_by', 'contradicts')),
|
|
182
|
+
weight REAL DEFAULT 1.0,
|
|
183
|
+
created_at INTEGER NOT NULL,
|
|
184
|
+
metadata TEXT DEFAULT '{}'
|
|
185
|
+
);
|
|
186
|
+
INSERT OR IGNORE INTO memory_edges_new SELECT * FROM memory_edges;
|
|
187
|
+
DROP TABLE memory_edges;
|
|
188
|
+
ALTER TABLE memory_edges_new RENAME TO memory_edges;
|
|
189
|
+
CREATE INDEX IF NOT EXISTS idx_edges_source ON memory_edges(source);
|
|
190
|
+
CREATE INDEX IF NOT EXISTS idx_edges_target ON memory_edges(target);
|
|
191
|
+
`);
|
|
192
|
+
}
|
|
193
|
+
if (current < 3) {
|
|
194
|
+
this.db.exec(`
|
|
195
|
+
CREATE TABLE IF NOT EXISTS call_sites (
|
|
196
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
197
|
+
file_id INTEGER NOT NULL REFERENCES files(id) ON DELETE CASCADE,
|
|
198
|
+
callee TEXT NOT NULL,
|
|
199
|
+
line INTEGER NOT NULL,
|
|
200
|
+
context TEXT
|
|
201
|
+
);
|
|
202
|
+
CREATE INDEX IF NOT EXISTS idx_call_sites_file ON call_sites(file_id);
|
|
203
|
+
CREATE INDEX IF NOT EXISTS idx_call_sites_callee ON call_sites(callee);
|
|
204
|
+
`);
|
|
205
|
+
}
|
|
206
|
+
if (current < 4) {
|
|
207
|
+
this.db.exec(`
|
|
208
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS file_search_fts USING fts5(
|
|
209
|
+
path UNINDEXED,
|
|
210
|
+
header,
|
|
211
|
+
symbols,
|
|
212
|
+
language,
|
|
213
|
+
body,
|
|
214
|
+
tokenize='porter unicode61'
|
|
215
|
+
);
|
|
216
|
+
`);
|
|
217
|
+
}
|
|
218
|
+
if (current < 5) {
|
|
219
|
+
if (!this.columnExists("files", "file_mtime_ms"))
|
|
220
|
+
this.db.exec("ALTER TABLE files ADD COLUMN file_mtime_ms INTEGER DEFAULT 0");
|
|
221
|
+
if (!this.columnExists("tool_calls", "input_tokens_est"))
|
|
222
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN input_tokens_est INTEGER NOT NULL DEFAULT 0");
|
|
223
|
+
if (!this.columnExists("tool_calls", "output_tokens_est"))
|
|
224
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN output_tokens_est INTEGER NOT NULL DEFAULT 0");
|
|
225
|
+
if (!this.columnExists("tool_calls", "source_tokens_est"))
|
|
226
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN source_tokens_est INTEGER NOT NULL DEFAULT 0");
|
|
227
|
+
if (!this.columnExists("tool_calls", "saved_tokens_est"))
|
|
228
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN saved_tokens_est INTEGER NOT NULL DEFAULT 0");
|
|
229
|
+
if (!this.columnExists("tool_calls", "represented_files"))
|
|
230
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN represented_files INTEGER NOT NULL DEFAULT 0");
|
|
231
|
+
}
|
|
232
|
+
if (current < 6) {
|
|
233
|
+
if (!this.columnExists("tool_calls", "session_id"))
|
|
234
|
+
this.db.exec("ALTER TABLE tool_calls ADD COLUMN session_id TEXT");
|
|
235
|
+
}
|
|
236
|
+
if (current < 7 && !this.columnExists("files", "file_ctime_ms"))
|
|
237
|
+
this.db.exec("ALTER TABLE files ADD COLUMN file_ctime_ms REAL DEFAULT 0");
|
|
238
|
+
this.db.exec("CREATE INDEX IF NOT EXISTS idx_tool_calls_session ON tool_calls(session_id, called_at)");
|
|
239
|
+
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('schema_version', ?)").run(SCHEMA_VERSION);
|
|
240
|
+
}
|
|
241
|
+
ensureVecTables() {
|
|
242
|
+
const dimRow = this.db.prepare("SELECT value FROM meta WHERE key = 'vec_dimension'").get();
|
|
243
|
+
if (dimRow) {
|
|
244
|
+
this.createVecTablesIfNeeded(parseInt(dimRow.value, 10));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
createVecTablesIfNeeded(dim) {
|
|
248
|
+
try {
|
|
249
|
+
this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS embeddings USING vec0(vector float[${dim}], +source_type TEXT, +source_id TEXT, +content_hash TEXT)`);
|
|
250
|
+
this.db.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS memory_embeddings USING vec0(vector float[${dim}], +node_id TEXT)`);
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
this.db.exec("DROP TABLE IF EXISTS embeddings");
|
|
254
|
+
this.db.exec("DROP TABLE IF EXISTS memory_embeddings");
|
|
255
|
+
this.db.exec(`CREATE VIRTUAL TABLE embeddings USING vec0(vector float[${dim}], +source_type TEXT, +source_id TEXT, +content_hash TEXT)`);
|
|
256
|
+
this.db.exec(`CREATE VIRTUAL TABLE memory_embeddings USING vec0(vector float[${dim}], +node_id TEXT)`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
ensureVecDimension(dim) {
|
|
260
|
+
if (this.vecDimension === dim) {
|
|
261
|
+
if (!this.hasVectorTables())
|
|
262
|
+
this.createVecTablesIfNeeded(dim);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (this.vecDimension !== null && this.vecDimension !== dim) {
|
|
266
|
+
process.stderr.write(`[contextpro/db] WARNING: embedding dimension changed from ${this.vecDimension} to ${dim}. ` +
|
|
267
|
+
`All cached embeddings are being cleared. Run reindex to rebuild them.\n`);
|
|
268
|
+
this.db.exec("DROP TABLE IF EXISTS embeddings");
|
|
269
|
+
this.db.exec("DROP TABLE IF EXISTS memory_embeddings");
|
|
270
|
+
this.bumpMemoryRevision();
|
|
271
|
+
this.bumpEmbeddingRevision();
|
|
272
|
+
this.setMeta("embeddings_stale", "true");
|
|
273
|
+
this.setMeta("embeddings_stale_reason", `dimension changed from ${this.vecDimension} to ${dim}`);
|
|
274
|
+
}
|
|
275
|
+
this.createVecTablesIfNeeded(dim);
|
|
276
|
+
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES ('vec_dimension', ?)").run(String(dim));
|
|
277
|
+
}
|
|
278
|
+
recordEmbeddingProvider(provider, model) {
|
|
279
|
+
this.db.transaction(() => this.recordEmbeddingProviderInTransaction(provider, model)).immediate();
|
|
280
|
+
}
|
|
281
|
+
recordEmbeddingProviderInTransaction(provider, model) {
|
|
282
|
+
const previousProvider = this.getMeta("embed_provider");
|
|
283
|
+
const previousModel = this.getMeta("embed_model");
|
|
284
|
+
const missingIdentity = !previousProvider || !previousModel;
|
|
285
|
+
const populatedWithoutIdentity = missingIdentity && (() => { const stats = this.getEmbeddingStats(); return stats.total > 0 || stats.memory > 0; })();
|
|
286
|
+
if (populatedWithoutIdentity || (!missingIdentity && (previousProvider !== provider || previousModel !== model))) {
|
|
287
|
+
process.stderr.write(`[contextpro/db] WARNING: embedding provider/model changed from ${previousProvider || "unknown"}/${previousModel || "unknown"} to ${provider}/${model}. ` +
|
|
288
|
+
`Cached vectors are being cleared; run reindex to rebuild.\n`);
|
|
289
|
+
// Content hashes alone cannot validate vectors across embedding models. This is
|
|
290
|
+
// especially important when the old and new models happen to share a dimension:
|
|
291
|
+
// vec0 would accept the stale vectors and silently mix incompatible vector spaces.
|
|
292
|
+
if (this.tableExists("embeddings"))
|
|
293
|
+
this.db.exec("DELETE FROM embeddings");
|
|
294
|
+
if (this.tableExists("memory_embeddings"))
|
|
295
|
+
this.db.exec("DELETE FROM memory_embeddings");
|
|
296
|
+
this.bumpMemoryRevision();
|
|
297
|
+
this.bumpEmbeddingRevision();
|
|
298
|
+
this.setMeta("embeddings_stale", "true");
|
|
299
|
+
this.setMeta("embeddings_stale_reason", `provider/model changed from ${previousProvider || "unknown"}/${previousModel || "unknown"} to ${provider}/${model}`);
|
|
300
|
+
}
|
|
301
|
+
this.setMeta("embed_provider", provider);
|
|
302
|
+
this.setMeta("embed_model", model);
|
|
303
|
+
}
|
|
304
|
+
isEmbeddingsStale() {
|
|
305
|
+
return {
|
|
306
|
+
stale: this.getMeta("embeddings_stale") === "true",
|
|
307
|
+
reason: this.getMeta("embeddings_stale_reason"),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
clearEmbeddingsStaleFlag() {
|
|
311
|
+
this.setMeta("embeddings_stale", "false");
|
|
312
|
+
this.setMeta("embeddings_stale_reason", "");
|
|
313
|
+
}
|
|
314
|
+
hasVectorTables() {
|
|
315
|
+
return this.vecDimension !== null && this.tableExists("embeddings") && this.tableExists("memory_embeddings");
|
|
316
|
+
}
|
|
317
|
+
tableExists(name) {
|
|
318
|
+
const row = this.db.prepare("SELECT name FROM sqlite_master WHERE name = ?").get(name);
|
|
319
|
+
return row !== undefined;
|
|
320
|
+
}
|
|
321
|
+
columnExists(table, column) {
|
|
322
|
+
const rows = this.db.prepare(`PRAGMA table_info(${table})`).all();
|
|
323
|
+
return rows.some((row) => row.name === column);
|
|
324
|
+
}
|
|
325
|
+
hasUsableEmbedding(value) {
|
|
326
|
+
return Array.isArray(value) && value.length > 0;
|
|
327
|
+
}
|
|
328
|
+
upsertFile(path, hash, header, language, fileSize = 0, fileMtimeMs = 0, fileCtimeMs = 0) {
|
|
329
|
+
const existing = this.db.prepare("SELECT id FROM files WHERE path = ?").get(path);
|
|
330
|
+
if (existing) {
|
|
331
|
+
this.db.prepare("UPDATE files SET content_hash = ?, header = ?, language = ?, last_indexed_at = ?, file_size = ?, file_mtime_ms = ?, file_ctime_ms = ? WHERE id = ?")
|
|
332
|
+
.run(hash, header, language, Date.now(), fileSize, fileMtimeMs, fileCtimeMs, existing.id);
|
|
333
|
+
return existing.id;
|
|
334
|
+
}
|
|
335
|
+
const result = this.db.prepare("INSERT INTO files (path, content_hash, header, language, last_indexed_at, file_size, file_mtime_ms, file_ctime_ms) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
|
|
336
|
+
.run(path, hash, header, language, Date.now(), fileSize, fileMtimeMs, fileCtimeMs);
|
|
337
|
+
return Number(result.lastInsertRowid);
|
|
338
|
+
}
|
|
339
|
+
updateFileFingerprint(path, fileSize, fileMtimeMs, fileCtimeMs = 0, expectedHash) {
|
|
340
|
+
this.db.prepare("UPDATE files SET file_size = ?, file_mtime_ms = ?, file_ctime_ms = ? WHERE path = ? AND (? IS NULL OR content_hash = ?)")
|
|
341
|
+
.run(fileSize, fileMtimeMs, fileCtimeMs, path, expectedHash ?? null, expectedHash ?? null);
|
|
342
|
+
}
|
|
343
|
+
getFile(path) {
|
|
344
|
+
return this.db.prepare("SELECT * FROM files WHERE path = ?").get(path) ?? null;
|
|
345
|
+
}
|
|
346
|
+
getFileById(id) {
|
|
347
|
+
return this.db.prepare("SELECT * FROM files WHERE id = ?").get(id) ?? null;
|
|
348
|
+
}
|
|
349
|
+
getAllFiles() {
|
|
350
|
+
return this.db.prepare("SELECT * FROM files").all();
|
|
351
|
+
}
|
|
352
|
+
getStaleFiles(currentHashes) {
|
|
353
|
+
const stale = [];
|
|
354
|
+
const rows = this.db.prepare("SELECT path, content_hash FROM files").all();
|
|
355
|
+
for (const row of rows) {
|
|
356
|
+
const currentHash = currentHashes.get(row.path);
|
|
357
|
+
if (!currentHash || currentHash !== row.content_hash) {
|
|
358
|
+
stale.push(row.path);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return stale;
|
|
362
|
+
}
|
|
363
|
+
getRemovedFiles(currentPaths) {
|
|
364
|
+
const rows = this.db.prepare("SELECT path FROM files").all();
|
|
365
|
+
return rows.filter(r => !currentPaths.has(r.path)).map(r => r.path);
|
|
366
|
+
}
|
|
367
|
+
deleteFile(path) {
|
|
368
|
+
const file = this.getFile(path);
|
|
369
|
+
if (file) {
|
|
370
|
+
if (this.hasVectorTables()) {
|
|
371
|
+
this.db.prepare("DELETE FROM embeddings WHERE source_type = 'file' AND source_id = ?").run(String(file.id));
|
|
372
|
+
this.db.prepare("DELETE FROM embeddings WHERE source_type = 'symbol' AND source_id IN (SELECT CAST(id AS TEXT) FROM symbols WHERE file_id = ?)").run(file.id);
|
|
373
|
+
}
|
|
374
|
+
this.deleteFileSearchDocument(path);
|
|
375
|
+
this.db.prepare("DELETE FROM symbols WHERE file_id = ?").run(file.id);
|
|
376
|
+
this.db.prepare("DELETE FROM files WHERE id = ?").run(file.id);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
upsertSymbols(fileId, symbols) {
|
|
380
|
+
this.db.prepare("DELETE FROM symbols WHERE file_id = ?").run(fileId);
|
|
381
|
+
const insert = this.db.prepare("INSERT INTO symbols (file_id, name, kind, line_start, line_end, signature, parent_symbol_id) VALUES (?, ?, ?, ?, ?, ?, ?)");
|
|
382
|
+
// `parentIndex` (index into `symbols`, always < this entry's own index — parents are
|
|
383
|
+
// inserted in DFS pre-order before their children) resolves to a real row id here,
|
|
384
|
+
// once we know it. Falls back to an explicit `parentSymbolId` if the caller has one.
|
|
385
|
+
const insertedIds = [];
|
|
386
|
+
const tx = this.db.transaction((syms) => {
|
|
387
|
+
for (const s of syms) {
|
|
388
|
+
const parentId = s.parentIndex != null ? insertedIds[s.parentIndex] ?? null : s.parentSymbolId ?? null;
|
|
389
|
+
const info = insert.run(fileId, s.name, s.kind, s.lineStart, s.lineEnd ?? null, s.signature ?? null, parentId);
|
|
390
|
+
insertedIds.push(Number(info.lastInsertRowid));
|
|
391
|
+
}
|
|
392
|
+
});
|
|
393
|
+
tx(symbols);
|
|
394
|
+
this.db.prepare("UPDATE files SET symbol_count = ? WHERE id = ?").run(symbols.length, fileId);
|
|
395
|
+
}
|
|
396
|
+
getSymbolsByFile(fileId) {
|
|
397
|
+
return this.db.prepare("SELECT * FROM symbols WHERE file_id = ?").all(fileId);
|
|
398
|
+
}
|
|
399
|
+
searchSymbolsByName(query) {
|
|
400
|
+
return this.db.prepare("SELECT * FROM symbols WHERE name LIKE ?").all(`%${query}%`);
|
|
401
|
+
}
|
|
402
|
+
upsertEmbedding(sourceType, sourceId, vector, hash) {
|
|
403
|
+
this.upsertEmbeddingsBatch(sourceType, [{ sourceId: String(sourceId), vector, hash }]);
|
|
404
|
+
}
|
|
405
|
+
upsertEmbeddingsBatch(sourceType, entries) {
|
|
406
|
+
if (entries.length === 0)
|
|
407
|
+
return;
|
|
408
|
+
const tx = this.db.transaction((rows) => {
|
|
409
|
+
this.ensureVecDimension(rows[0].vector.length);
|
|
410
|
+
// vec0 auxiliary columns have no index. Resolve existing identities once;
|
|
411
|
+
// scanning the growing vector table before every insert is quadratic.
|
|
412
|
+
const wanted = new Set(rows.map(row => row.sourceId));
|
|
413
|
+
const existing = new Map();
|
|
414
|
+
const stored = this.db.prepare("SELECT rowid, source_id FROM embeddings WHERE source_type = ?").all(sourceType);
|
|
415
|
+
for (const row of stored) {
|
|
416
|
+
if (!wanted.has(row.source_id))
|
|
417
|
+
continue;
|
|
418
|
+
const ids = existing.get(row.source_id) ?? [];
|
|
419
|
+
ids.push(row.rowid);
|
|
420
|
+
existing.set(row.source_id, ids);
|
|
421
|
+
}
|
|
422
|
+
const del = this.db.prepare("DELETE FROM embeddings WHERE rowid = ?");
|
|
423
|
+
const ins = this.db.prepare("INSERT INTO embeddings (vector, source_type, source_id, content_hash) VALUES (?, ?, ?, ?)");
|
|
424
|
+
for (const r of rows) {
|
|
425
|
+
for (const id of existing.get(r.sourceId) ?? [])
|
|
426
|
+
del.run(id);
|
|
427
|
+
const inserted = ins.run(vecToBuffer(r.vector), sourceType, r.sourceId, r.hash);
|
|
428
|
+
existing.set(r.sourceId, [inserted.lastInsertRowid]);
|
|
429
|
+
}
|
|
430
|
+
this.bumpEmbeddingRevision();
|
|
431
|
+
});
|
|
432
|
+
tx.immediate(entries);
|
|
433
|
+
}
|
|
434
|
+
bumpEmbeddingRevision() {
|
|
435
|
+
this.db.prepare("INSERT INTO meta (key, value) VALUES ('embedding_revision', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(value AS INTEGER) + 1").run();
|
|
436
|
+
}
|
|
437
|
+
getEmbeddingsByType(sourceType) {
|
|
438
|
+
if (!this.hasVectorTables())
|
|
439
|
+
return [];
|
|
440
|
+
try {
|
|
441
|
+
return this.db.prepare("SELECT source_id as sourceId, content_hash as contentHash, vector FROM embeddings WHERE source_type = ?").all(sourceType);
|
|
442
|
+
}
|
|
443
|
+
catch {
|
|
444
|
+
return [];
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
getEmbeddingStats() {
|
|
448
|
+
const empty = {
|
|
449
|
+
vectorTablesReady: false,
|
|
450
|
+
dimension: this.vecDimension,
|
|
451
|
+
total: 0,
|
|
452
|
+
fileSearch: 0,
|
|
453
|
+
identifiers: 0,
|
|
454
|
+
memory: 0,
|
|
455
|
+
};
|
|
456
|
+
if (!this.hasVectorTables())
|
|
457
|
+
return empty;
|
|
458
|
+
try {
|
|
459
|
+
const rows = this.db.prepare("SELECT source_type, count(*) as n FROM embeddings GROUP BY source_type").all();
|
|
460
|
+
const memory = this.db.prepare("SELECT count(*) as n FROM memory_embeddings").get();
|
|
461
|
+
const byType = new Map(rows.map((row) => [row.source_type, row.n]));
|
|
462
|
+
const total = rows.reduce((sum, row) => sum + row.n, 0);
|
|
463
|
+
return {
|
|
464
|
+
vectorTablesReady: true,
|
|
465
|
+
dimension: this.vecDimension,
|
|
466
|
+
total,
|
|
467
|
+
fileSearch: byType.get("file_search_cache") ?? 0,
|
|
468
|
+
identifiers: byType.get("identifier_cache") ?? 0,
|
|
469
|
+
memory: memory.n,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
return empty;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
deleteEmbeddingsByPrefix(sourceType, prefix) {
|
|
477
|
+
if (!this.hasVectorTables())
|
|
478
|
+
return;
|
|
479
|
+
const result = this.db.prepare("DELETE FROM embeddings WHERE source_type = ? AND source_id LIKE ?").run(sourceType, `${prefix}%`);
|
|
480
|
+
if (result.changes)
|
|
481
|
+
this.bumpEmbeddingRevision();
|
|
482
|
+
}
|
|
483
|
+
searchEmbeddings(queryVector, topK, sourceType) {
|
|
484
|
+
if (!this.vecDimension || queryVector.length !== this.vecDimension || !this.hasVectorTables())
|
|
485
|
+
return [];
|
|
486
|
+
const buf = vecToBuffer(queryVector);
|
|
487
|
+
const count = sourceType
|
|
488
|
+
? this.db.prepare("SELECT count(*) as n FROM embeddings").get().n
|
|
489
|
+
: topK;
|
|
490
|
+
const fetchK = Math.max(topK, count);
|
|
491
|
+
const sql = "SELECT rowid, source_type, source_id, content_hash, distance FROM embeddings WHERE vector MATCH ? ORDER BY distance LIMIT ?";
|
|
492
|
+
const raw = this.db.prepare(sql).all(buf, fetchK);
|
|
493
|
+
const filtered = sourceType ? raw.filter((r) => r.source_type === sourceType) : raw;
|
|
494
|
+
return filtered.slice(0, topK).map((r) => ({
|
|
495
|
+
rowid: r.rowid,
|
|
496
|
+
sourceType: r.source_type,
|
|
497
|
+
sourceId: r.source_id,
|
|
498
|
+
contentHash: r.content_hash,
|
|
499
|
+
distance: r.distance,
|
|
500
|
+
}));
|
|
501
|
+
}
|
|
502
|
+
getEmbeddingBySource(sourceType, sourceId) {
|
|
503
|
+
if (!this.hasVectorTables())
|
|
504
|
+
return null;
|
|
505
|
+
const row = this.db.prepare("SELECT rowid, content_hash FROM embeddings WHERE source_type = ? AND source_id = ?")
|
|
506
|
+
.get(sourceType, String(sourceId));
|
|
507
|
+
return row ? { vector: Buffer.alloc(0), hash: row.content_hash } : null;
|
|
508
|
+
}
|
|
509
|
+
getEmbeddingHash(sourceType, sourceId) {
|
|
510
|
+
if (!this.hasVectorTables())
|
|
511
|
+
return null;
|
|
512
|
+
const row = this.db.prepare("SELECT content_hash FROM embeddings WHERE source_type = ? AND source_id = ?")
|
|
513
|
+
.get(sourceType, String(sourceId));
|
|
514
|
+
return row?.content_hash ?? null;
|
|
515
|
+
}
|
|
516
|
+
getMemoryRevision() { return Number(this.getMeta("memory_revision") ?? "0"); }
|
|
517
|
+
bumpMemoryRevision() {
|
|
518
|
+
this.db.prepare("INSERT INTO meta (key, value) VALUES ('memory_revision', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(value AS INTEGER) + 1").run();
|
|
519
|
+
}
|
|
520
|
+
upsertMemoryNode(input, embedding, model) {
|
|
521
|
+
return this.db.transaction(() => {
|
|
522
|
+
const compatible = !model || (this.getMeta("embed_provider") === model.provider && this.getMeta("embed_model") === model.model);
|
|
523
|
+
return this.upsertMemoryNodeInTransaction(input, compatible ? embedding : undefined);
|
|
524
|
+
}).immediate();
|
|
525
|
+
}
|
|
526
|
+
upsertMemoryNodeInTransaction(input, embedding) {
|
|
527
|
+
const now = Date.now();
|
|
528
|
+
const existing = this.db.prepare("SELECT id, content FROM memory_nodes WHERE label = ? AND type = ?").get(input.label, input.type);
|
|
529
|
+
if (existing) {
|
|
530
|
+
this.db.prepare("UPDATE memory_nodes SET content = ?, last_accessed = ?, access_count = access_count + 1, metadata = ? WHERE id = ?")
|
|
531
|
+
.run(input.content, now, JSON.stringify(input.metadata ?? {}), existing.id);
|
|
532
|
+
this.bumpMemoryRevision();
|
|
533
|
+
if (existing.content !== input.content && this.hasVectorTables()) {
|
|
534
|
+
this.db.prepare("DELETE FROM memory_embeddings WHERE node_id = ?").run(existing.id);
|
|
535
|
+
}
|
|
536
|
+
if (this.hasUsableEmbedding(embedding)) {
|
|
537
|
+
this.ensureVecDimension(embedding.length);
|
|
538
|
+
this.db.prepare("DELETE FROM memory_embeddings WHERE node_id = ?").run(existing.id);
|
|
539
|
+
this.db.prepare("INSERT INTO memory_embeddings (vector, node_id) VALUES (?, ?)").run(vecToBuffer(embedding), existing.id);
|
|
540
|
+
}
|
|
541
|
+
return existing.id;
|
|
542
|
+
}
|
|
543
|
+
const id = input.id ?? generateId("node");
|
|
544
|
+
this.db.prepare("INSERT INTO memory_nodes (id, type, label, content, created_at, last_accessed, access_count, metadata) VALUES (?, ?, ?, ?, ?, ?, 1, ?)")
|
|
545
|
+
.run(id, input.type, input.label, input.content, now, now, JSON.stringify(input.metadata ?? {}));
|
|
546
|
+
this.bumpMemoryRevision();
|
|
547
|
+
if (this.hasUsableEmbedding(embedding)) {
|
|
548
|
+
this.ensureVecDimension(embedding.length);
|
|
549
|
+
this.db.prepare("INSERT INTO memory_embeddings (vector, node_id) VALUES (?, ?)").run(vecToBuffer(embedding), id);
|
|
550
|
+
}
|
|
551
|
+
return id;
|
|
552
|
+
}
|
|
553
|
+
peekMemoryNode(id) {
|
|
554
|
+
return this.db.prepare("SELECT * FROM memory_nodes WHERE id = ?").get(id) ?? null;
|
|
555
|
+
}
|
|
556
|
+
findMemoryNodeByLabel(type, label) {
|
|
557
|
+
return this.db.prepare("SELECT * FROM memory_nodes WHERE type = ? AND label = ?").get(type, label) ?? null;
|
|
558
|
+
}
|
|
559
|
+
getMemoryNode(id) {
|
|
560
|
+
const row = this.peekMemoryNode(id);
|
|
561
|
+
if (row) {
|
|
562
|
+
this.db.prepare("UPDATE memory_nodes SET last_accessed = ?, access_count = access_count + 1 WHERE id = ?").run(Date.now(), id);
|
|
563
|
+
}
|
|
564
|
+
return row;
|
|
565
|
+
}
|
|
566
|
+
listMemoryNodes() {
|
|
567
|
+
return this.db.prepare("SELECT * FROM memory_nodes ORDER BY last_accessed DESC, created_at DESC").all();
|
|
568
|
+
}
|
|
569
|
+
listMemoryNodesWithoutEmbeddings() {
|
|
570
|
+
if (!this.hasVectorTables())
|
|
571
|
+
return this.listMemoryNodes();
|
|
572
|
+
return this.db.prepare("SELECT * FROM memory_nodes WHERE id NOT IN (SELECT node_id FROM memory_embeddings)").all();
|
|
573
|
+
}
|
|
574
|
+
setMemoryEmbedding(node, embedding) {
|
|
575
|
+
return this.db.transaction(() => {
|
|
576
|
+
const current = this.peekMemoryNode(node.id);
|
|
577
|
+
// Keep the revision check and publication in one SQLite transaction so
|
|
578
|
+
// another process cannot change the node between the check and write.
|
|
579
|
+
if (!current || current.label !== node.label || current.content !== node.content)
|
|
580
|
+
return false;
|
|
581
|
+
if (!this.hasUsableEmbedding(embedding))
|
|
582
|
+
throw new Error(`Invalid memory embedding for ${node.id}`);
|
|
583
|
+
this.ensureVecDimension(embedding.length);
|
|
584
|
+
this.db.prepare("DELETE FROM memory_embeddings WHERE node_id = ?").run(node.id);
|
|
585
|
+
this.db.prepare("INSERT INTO memory_embeddings (vector, node_id) VALUES (?, ?)").run(vecToBuffer(embedding), node.id);
|
|
586
|
+
this.bumpMemoryRevision();
|
|
587
|
+
return true;
|
|
588
|
+
}).immediate();
|
|
589
|
+
}
|
|
590
|
+
deleteMemoryNode(id) {
|
|
591
|
+
if (this.hasVectorTables()) {
|
|
592
|
+
try {
|
|
593
|
+
this.db.prepare("DELETE FROM memory_embeddings WHERE node_id = ?").run(id);
|
|
594
|
+
}
|
|
595
|
+
catch { }
|
|
596
|
+
}
|
|
597
|
+
this.db.prepare("DELETE FROM memory_edges WHERE source = ? OR target = ?").run(id, id);
|
|
598
|
+
if (this.db.prepare("DELETE FROM memory_nodes WHERE id = ?").run(id).changes > 0)
|
|
599
|
+
this.bumpMemoryRevision();
|
|
600
|
+
}
|
|
601
|
+
upsertMemoryEdge(input) {
|
|
602
|
+
const existing = this.db.prepare("SELECT id FROM memory_edges WHERE source = ? AND target = ? AND relation = ?")
|
|
603
|
+
.get(input.source, input.target, input.relation);
|
|
604
|
+
if (existing) {
|
|
605
|
+
this.db.prepare("UPDATE memory_edges SET weight = ?, metadata = ? WHERE id = ?")
|
|
606
|
+
.run(input.weight ?? 1.0, JSON.stringify(input.metadata ?? {}), existing.id);
|
|
607
|
+
this.bumpMemoryRevision();
|
|
608
|
+
return existing.id;
|
|
609
|
+
}
|
|
610
|
+
const id = input.id ?? generateId("edge");
|
|
611
|
+
this.db.prepare("INSERT INTO memory_edges (id, source, target, relation, weight, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?, ?)")
|
|
612
|
+
.run(id, input.source, input.target, input.relation, input.weight ?? 1.0, Date.now(), JSON.stringify(input.metadata ?? {}));
|
|
613
|
+
this.bumpMemoryRevision();
|
|
614
|
+
return id;
|
|
615
|
+
}
|
|
616
|
+
deleteMemoryEdge(id) {
|
|
617
|
+
if (this.db.prepare("DELETE FROM memory_edges WHERE id = ?").run(id).changes > 0)
|
|
618
|
+
this.bumpMemoryRevision();
|
|
619
|
+
}
|
|
620
|
+
getMemoryEdgesForNode(id) {
|
|
621
|
+
return this.db.prepare("SELECT * FROM memory_edges WHERE source = ? OR target = ?").all(id, id);
|
|
622
|
+
}
|
|
623
|
+
searchMemoryNodes(queryVector, topK = 5) {
|
|
624
|
+
if (!this.vecDimension || queryVector.length !== this.vecDimension || !this.hasVectorTables())
|
|
625
|
+
return [];
|
|
626
|
+
const buf = vecToBuffer(queryVector);
|
|
627
|
+
let vecResults = [];
|
|
628
|
+
try {
|
|
629
|
+
vecResults = this.db.prepare("SELECT node_id, distance FROM memory_embeddings WHERE vector MATCH ? ORDER BY distance LIMIT ?")
|
|
630
|
+
.all(buf, topK);
|
|
631
|
+
}
|
|
632
|
+
catch {
|
|
633
|
+
return [];
|
|
634
|
+
}
|
|
635
|
+
const results = [];
|
|
636
|
+
for (const vr of vecResults) {
|
|
637
|
+
const node = this.db.prepare("SELECT * FROM memory_nodes WHERE id = ?").get(vr.node_id);
|
|
638
|
+
if (node)
|
|
639
|
+
results.push({ node, distance: vr.distance });
|
|
640
|
+
}
|
|
641
|
+
return results;
|
|
642
|
+
}
|
|
643
|
+
getNeighbors(nodeId, maxDepth = 2, edgeFilter) {
|
|
644
|
+
const startNode = this.db.prepare("SELECT * FROM memory_nodes WHERE id = ?").get(nodeId);
|
|
645
|
+
if (!startNode)
|
|
646
|
+
return [];
|
|
647
|
+
const visited = new Set();
|
|
648
|
+
const results = [{ node: startNode, depth: 0, pathRelations: [], relevanceScore: 1.0 }];
|
|
649
|
+
visited.add(nodeId);
|
|
650
|
+
let frontier = [{ id: nodeId, depth: 0, pathRelations: [], score: 1.0 }];
|
|
651
|
+
for (let d = 0; d < maxDepth; d++) {
|
|
652
|
+
const nextFrontier = [];
|
|
653
|
+
const pendingNeighbors = [];
|
|
654
|
+
for (const current of frontier) {
|
|
655
|
+
const edges = this.db.prepare("SELECT * FROM memory_edges WHERE source = ? OR target = ?")
|
|
656
|
+
.all(current.id, current.id);
|
|
657
|
+
for (const edge of edges) {
|
|
658
|
+
if (edgeFilter && !edgeFilter.includes(edge.relation))
|
|
659
|
+
continue;
|
|
660
|
+
const neighborId = edge.source === current.id ? edge.target : edge.source;
|
|
661
|
+
if (visited.has(neighborId))
|
|
662
|
+
continue;
|
|
663
|
+
visited.add(neighborId);
|
|
664
|
+
pendingNeighbors.push({ neighborId, edge, current });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
if (pendingNeighbors.length === 0)
|
|
668
|
+
break;
|
|
669
|
+
const ids = pendingNeighbors.map((p) => p.neighborId);
|
|
670
|
+
const placeholders = ids.map(() => "?").join(", ");
|
|
671
|
+
const nodeMap = new Map(this.db.prepare(`SELECT * FROM memory_nodes WHERE id IN (${placeholders})`).all(...ids)
|
|
672
|
+
.map((n) => [n.id, n]));
|
|
673
|
+
const now = Date.now();
|
|
674
|
+
for (const { neighborId, edge, current } of pendingNeighbors) {
|
|
675
|
+
const node = nodeMap.get(neighborId);
|
|
676
|
+
if (!node)
|
|
677
|
+
continue;
|
|
678
|
+
const daysSince = (now - edge.created_at) / 86_400_000;
|
|
679
|
+
const decayed = edge.weight * Math.exp(-DECAY_LAMBDA * daysSince);
|
|
680
|
+
const score = decayed * (1 / (d + 2));
|
|
681
|
+
const pathRelations = [...current.pathRelations, edge.relation];
|
|
682
|
+
results.push({ node, depth: d + 1, pathRelations, relevanceScore: score });
|
|
683
|
+
nextFrontier.push({ id: neighborId, depth: d + 1, pathRelations, score });
|
|
684
|
+
}
|
|
685
|
+
frontier = nextFrontier;
|
|
686
|
+
}
|
|
687
|
+
return results;
|
|
688
|
+
}
|
|
689
|
+
pruneStaleEdges(threshold = STALE_EDGE_THRESHOLD) {
|
|
690
|
+
const now = Date.now();
|
|
691
|
+
const edges = this.db.prepare("SELECT id, weight, created_at FROM memory_edges").all();
|
|
692
|
+
const staleEdgeIds = [];
|
|
693
|
+
for (const edge of edges) {
|
|
694
|
+
const daysSince = (now - edge.created_at) / 86_400_000;
|
|
695
|
+
const decayed = edge.weight * Math.exp(-DECAY_LAMBDA * daysSince);
|
|
696
|
+
if (decayed < threshold)
|
|
697
|
+
staleEdgeIds.push(edge.id);
|
|
698
|
+
}
|
|
699
|
+
const orphans = this.db.prepare(`
|
|
700
|
+
SELECT id FROM memory_nodes
|
|
701
|
+
WHERE id NOT IN (SELECT source FROM memory_edges)
|
|
702
|
+
AND id NOT IN (SELECT target FROM memory_edges)
|
|
703
|
+
AND access_count < ?
|
|
704
|
+
AND last_accessed < ?
|
|
705
|
+
`).all(STALE_NODE_MIN_ACCESS, now - STALE_NODE_DAYS * 86_400_000);
|
|
706
|
+
const prune = this.db.transaction(() => {
|
|
707
|
+
if (staleEdgeIds.length > 0) {
|
|
708
|
+
const placeholders = staleEdgeIds.map(() => "?").join(", ");
|
|
709
|
+
this.db.prepare(`DELETE FROM memory_edges WHERE id IN (${placeholders})`).run(...staleEdgeIds);
|
|
710
|
+
this.bumpMemoryRevision();
|
|
711
|
+
}
|
|
712
|
+
for (const orphan of orphans)
|
|
713
|
+
this.deleteMemoryNode(orphan.id);
|
|
714
|
+
this.setMeta("last_prune", String(now));
|
|
715
|
+
});
|
|
716
|
+
prune();
|
|
717
|
+
return { edgesRemoved: staleEdgeIds.length, nodesRemoved: orphans.length };
|
|
718
|
+
}
|
|
719
|
+
ensureGraphFresh() {
|
|
720
|
+
const lastPrune = this.getMeta("last_prune");
|
|
721
|
+
const hoursSincePrune = (Date.now() - Number(lastPrune ?? "0")) / 3_600_000;
|
|
722
|
+
if (hoursSincePrune > PRUNE_INTERVAL_HOURS) {
|
|
723
|
+
this.pruneStaleEdges();
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
upsertImport(sourceFileId, targetPath, targetFileId, importType = "static", symbolsImported) {
|
|
727
|
+
this.db.prepare("DELETE FROM imports WHERE source_file_id = ? AND target_path = ?").run(sourceFileId, targetPath);
|
|
728
|
+
this.db.prepare("INSERT INTO imports (source_file_id, target_path, target_file_id, import_type, symbols_imported) VALUES (?, ?, ?, ?, ?)")
|
|
729
|
+
.run(sourceFileId, targetPath, targetFileId, importType, symbolsImported ? JSON.stringify(symbolsImported) : null);
|
|
730
|
+
}
|
|
731
|
+
deleteImportsForFile(fileId) {
|
|
732
|
+
this.db.prepare("DELETE FROM imports WHERE source_file_id = ?").run(fileId);
|
|
733
|
+
}
|
|
734
|
+
getImportsFrom(fileId) {
|
|
735
|
+
return this.db.prepare("SELECT * FROM imports WHERE source_file_id = ?").all(fileId);
|
|
736
|
+
}
|
|
737
|
+
getImportersOf(fileId) {
|
|
738
|
+
return this.db.prepare("SELECT * FROM imports WHERE target_file_id = ?").all(fileId);
|
|
739
|
+
}
|
|
740
|
+
getAllImports() {
|
|
741
|
+
return this.db.prepare("SELECT * FROM imports").all();
|
|
742
|
+
}
|
|
743
|
+
updateImportTarget(importId, targetFileId) {
|
|
744
|
+
this.db.prepare("UPDATE imports SET target_file_id = ? WHERE id = ?").run(targetFileId, importId);
|
|
745
|
+
}
|
|
746
|
+
upsertCallSites(fileId, sites) {
|
|
747
|
+
this.db.prepare("DELETE FROM call_sites WHERE file_id = ?").run(fileId);
|
|
748
|
+
const insert = this.db.prepare("INSERT INTO call_sites (file_id, callee, line, context) VALUES (?, ?, ?, ?)");
|
|
749
|
+
const tx = this.db.transaction((rows) => {
|
|
750
|
+
for (const s of rows)
|
|
751
|
+
insert.run(fileId, s.callee, s.line, s.context);
|
|
752
|
+
});
|
|
753
|
+
tx(sites);
|
|
754
|
+
}
|
|
755
|
+
getCallSitesByFile(fileId) {
|
|
756
|
+
return this.db.prepare("SELECT * FROM call_sites WHERE file_id = ?").all(fileId);
|
|
757
|
+
}
|
|
758
|
+
getCallersOf(callee) {
|
|
759
|
+
return this.db.prepare("SELECT * FROM call_sites WHERE callee = ?").all(callee);
|
|
760
|
+
}
|
|
761
|
+
upsertFileSearchDocument(input) {
|
|
762
|
+
const symbolText = (input.symbols ?? [])
|
|
763
|
+
.map((symbol) => `${symbol.name} ${symbol.kind ?? ""} ${symbol.signature ?? ""}`)
|
|
764
|
+
.join(" ");
|
|
765
|
+
const body = `${input.path} ${input.header ?? ""} ${input.language ?? ""} ${symbolText} ${input.content ?? ""}`;
|
|
766
|
+
this.deleteFileSearchDocument(input.path);
|
|
767
|
+
this.db.prepare("INSERT INTO file_search_fts (path, header, symbols, language, body) VALUES (?, ?, ?, ?, ?)")
|
|
768
|
+
.run(input.path, input.header ?? "", symbolText, input.language ?? "", body);
|
|
769
|
+
}
|
|
770
|
+
deleteFileSearchDocument(path) {
|
|
771
|
+
// path is UNINDEXED in FTS5, so filtering it alone scans every stored body
|
|
772
|
+
// once per source file. Every document's body already contains its path;
|
|
773
|
+
// use that inverted index to find candidates, then check the exact path to
|
|
774
|
+
// distinguish punctuation/stemming collisions. No new persisted mapping.
|
|
775
|
+
if (/[\p{L}\p{N}]/u.test(path) && !path.includes("\0")) {
|
|
776
|
+
this.db.prepare(`DELETE FROM file_search_fts WHERE rowid IN (
|
|
777
|
+
SELECT rowid FROM file_search_fts WHERE file_search_fts MATCH ? AND path = ?
|
|
778
|
+
)`).run(`body : "${path.replace(/"/g, '""')}"`, path);
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
// Unusual standalone documents may have no searchable path tokens.
|
|
782
|
+
this.db.prepare("DELETE FROM file_search_fts WHERE path = ?").run(path);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
searchFileText(query, topK = 10, phraseOnly = false, requireAllTerms = false) {
|
|
786
|
+
const stopwords = new Set([
|
|
787
|
+
"a", "an", "and", "are", "as", "at", "be", "by", "can", "do", "does", "for", "from",
|
|
788
|
+
"how", "i", "if", "in", "is", "it", "of", "on", "or", "the", "to", "what", "where",
|
|
789
|
+
"which", "who", "why", "with",
|
|
790
|
+
]);
|
|
791
|
+
const queryWords = (query.normalize("NFC").match(/[\p{L}\p{M}\p{N}_]+/gu) ?? [])
|
|
792
|
+
.map((term) => term.toLowerCase())
|
|
793
|
+
.filter((term) => (term.length > 1 || /[^\x00-\x7F]/u.test(term)) && !stopwords.has(term));
|
|
794
|
+
// Strict topic lookups must not silently discard their final constraints.
|
|
795
|
+
if (requireAllTerms && queryWords.length > 64)
|
|
796
|
+
return [];
|
|
797
|
+
const terms = queryWords.slice(0, requireAllTerms ? 64 : 12);
|
|
798
|
+
if (terms.length === 0 && !phraseOnly)
|
|
799
|
+
return [];
|
|
800
|
+
const limit = Math.max(1, topK);
|
|
801
|
+
const literal = query.normalize("NFC").trim().replace(/^(["'`])([\s\S]*)\1$/, "$2");
|
|
802
|
+
const runQuery = (ftsQuery) => this.db.prepare(`SELECT path, bm25(file_search_fts) as score FROM file_search_fts WHERE file_search_fts MATCH ?${phraseOnly ? " AND contextpro_literal_contains(body, ?)" : ""} ORDER BY score LIMIT ?`)
|
|
803
|
+
.all(...(phraseOnly ? [ftsQuery, literal.toLowerCase(), limit] : [ftsQuery, limit]));
|
|
804
|
+
try {
|
|
805
|
+
// Preserve phrase order before broadening to token/prefix matches. Quoting
|
|
806
|
+
// tokens also keeps Unicode and FTS syntax out of the query grammar.
|
|
807
|
+
const phraseRows = runQuery(`"${literal.replace(/"/g, '""')}"`);
|
|
808
|
+
if (phraseOnly || phraseRows.length > 0)
|
|
809
|
+
return phraseRows;
|
|
810
|
+
if (terms.length > 1) {
|
|
811
|
+
const andRows = runQuery(terms.map((term) => `"${term}"*`).join(" "));
|
|
812
|
+
if (andRows.length > 0 || requireAllTerms)
|
|
813
|
+
return andRows;
|
|
814
|
+
}
|
|
815
|
+
return runQuery(terms.map((term) => `"${term}"*`).join(" OR "));
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return [];
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
getGraphStats() {
|
|
822
|
+
const nodes = this.db.prepare("SELECT count(*) as n FROM memory_nodes").get();
|
|
823
|
+
const edges = this.db.prepare("SELECT count(*) as n FROM memory_edges").get();
|
|
824
|
+
return { totalNodes: nodes.n, totalEdges: edges.n };
|
|
825
|
+
}
|
|
826
|
+
/** Append one tool-call event. Best-effort telemetry — callers must not let a
|
|
827
|
+
* failure here break the tool itself. Prunes opportunistically to a bounded size. */
|
|
828
|
+
recordToolCall(tool, status, durationMs, calledAt, efficiency = {}, sessionId) {
|
|
829
|
+
this.db.prepare(`INSERT INTO tool_calls (
|
|
830
|
+
tool, status, duration_ms, called_at, input_tokens_est, output_tokens_est,
|
|
831
|
+
source_tokens_est, saved_tokens_est, represented_files, session_id
|
|
832
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(tool, status, Math.max(0, Math.round(durationMs)), calledAt, Math.max(0, Math.round(efficiency.inputTokens ?? 0)), Math.max(0, Math.round(efficiency.outputTokens ?? 0)), Math.max(0, Math.round(efficiency.sourceTokens ?? 0)), Math.max(0, Math.round(efficiency.savedTokens ?? 0)), Math.max(0, Math.round(efficiency.representedFiles ?? 0)), sessionId ?? null);
|
|
833
|
+
}
|
|
834
|
+
/** Most-recent tool calls, newest first. */
|
|
835
|
+
getToolCallHistory(limit = 200) {
|
|
836
|
+
const rows = this.db.prepare(`SELECT tool, status, duration_ms as durationMs, called_at as calledAt,
|
|
837
|
+
input_tokens_est as inputTokens, output_tokens_est as outputTokens,
|
|
838
|
+
source_tokens_est as sourceTokens, saved_tokens_est as savedTokens,
|
|
839
|
+
represented_files as representedFiles
|
|
840
|
+
FROM tool_calls ORDER BY called_at DESC, id DESC LIMIT ?`).all(Math.max(1, Math.min(2000, limit)));
|
|
841
|
+
return rows;
|
|
842
|
+
}
|
|
843
|
+
/** Recent calls with a positive savings estimate, optionally bounded by time. */
|
|
844
|
+
getToolSavingsHistory(limit = 20, sinceMs, sessionId) {
|
|
845
|
+
const filters = ["saved_tokens_est > 0"];
|
|
846
|
+
const params = [];
|
|
847
|
+
if (sinceMs !== undefined) {
|
|
848
|
+
filters.push("called_at >= ?");
|
|
849
|
+
params.push(sinceMs);
|
|
850
|
+
}
|
|
851
|
+
if (sessionId !== undefined) {
|
|
852
|
+
filters.push("session_id = ?");
|
|
853
|
+
params.push(sessionId);
|
|
854
|
+
}
|
|
855
|
+
const where = `WHERE ${filters.join(" AND ")}`;
|
|
856
|
+
const stmt = this.db.prepare(`SELECT tool, status, duration_ms as durationMs, called_at as calledAt,
|
|
857
|
+
input_tokens_est as inputTokens, output_tokens_est as outputTokens,
|
|
858
|
+
source_tokens_est as sourceTokens, saved_tokens_est as savedTokens,
|
|
859
|
+
represented_files as representedFiles
|
|
860
|
+
FROM tool_calls ${where}
|
|
861
|
+
ORDER BY called_at DESC, id DESC LIMIT ?`);
|
|
862
|
+
const boundedLimit = Math.max(1, Math.min(100, limit));
|
|
863
|
+
return stmt.all(...params, boundedLimit);
|
|
864
|
+
}
|
|
865
|
+
/** Per-tool aggregates over the whole history (or since a timestamp). */
|
|
866
|
+
getToolCallAggregates(sinceMs, sessionId) {
|
|
867
|
+
const filters = [];
|
|
868
|
+
const params = [];
|
|
869
|
+
if (sinceMs !== undefined) {
|
|
870
|
+
filters.push("called_at >= ?");
|
|
871
|
+
params.push(sinceMs);
|
|
872
|
+
}
|
|
873
|
+
if (sessionId !== undefined) {
|
|
874
|
+
filters.push("session_id = ?");
|
|
875
|
+
params.push(sessionId);
|
|
876
|
+
}
|
|
877
|
+
const where = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
|
|
878
|
+
const stmt = this.db.prepare(`SELECT tool,
|
|
879
|
+
COUNT(*) as calls,
|
|
880
|
+
SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as errors,
|
|
881
|
+
AVG(duration_ms) as avgMs,
|
|
882
|
+
MAX(duration_ms) as maxMs,
|
|
883
|
+
MAX(called_at) as lastAt,
|
|
884
|
+
SUM(input_tokens_est) as inputTokens,
|
|
885
|
+
SUM(output_tokens_est) as outputTokens,
|
|
886
|
+
SUM(source_tokens_est) as sourceTokens,
|
|
887
|
+
SUM(saved_tokens_est) as savedTokens,
|
|
888
|
+
SUM(represented_files) as representedFiles
|
|
889
|
+
FROM tool_calls ${where}
|
|
890
|
+
GROUP BY tool
|
|
891
|
+
ORDER BY calls DESC`);
|
|
892
|
+
const rows = stmt.all(...params);
|
|
893
|
+
return rows.map((r) => ({ ...r, avgMs: Math.round(r.avgMs) }));
|
|
894
|
+
}
|
|
895
|
+
getEfficiencyTotals(sinceMs, sessionId) {
|
|
896
|
+
const filters = [];
|
|
897
|
+
const params = [];
|
|
898
|
+
if (sinceMs !== undefined) {
|
|
899
|
+
filters.push("called_at >= ?");
|
|
900
|
+
params.push(sinceMs);
|
|
901
|
+
}
|
|
902
|
+
if (sessionId !== undefined) {
|
|
903
|
+
filters.push("session_id = ?");
|
|
904
|
+
params.push(sessionId);
|
|
905
|
+
}
|
|
906
|
+
const where = filters.length ? `WHERE ${filters.join(" AND ")}` : "";
|
|
907
|
+
const stmt = this.db.prepare(`SELECT COUNT(*) as calls,
|
|
908
|
+
COALESCE(SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END), 0) as errors,
|
|
909
|
+
COALESCE(AVG(duration_ms), 0) as avgMs,
|
|
910
|
+
COALESCE(MAX(duration_ms), 0) as maxMs,
|
|
911
|
+
COALESCE(MAX(called_at), 0) as lastAt,
|
|
912
|
+
COALESCE(SUM(input_tokens_est), 0) as inputTokens,
|
|
913
|
+
COALESCE(SUM(output_tokens_est), 0) as outputTokens,
|
|
914
|
+
COALESCE(SUM(source_tokens_est), 0) as sourceTokens,
|
|
915
|
+
COALESCE(SUM(saved_tokens_est), 0) as savedTokens,
|
|
916
|
+
COALESCE(SUM(represented_files), 0) as representedFiles
|
|
917
|
+
FROM tool_calls ${where}`);
|
|
918
|
+
const row = stmt.get(...params);
|
|
919
|
+
return { ...row, avgMs: Math.round(row.avgMs) };
|
|
920
|
+
}
|
|
921
|
+
getToolCallTotal() {
|
|
922
|
+
return this.db.prepare("SELECT COUNT(*) as n FROM tool_calls").get().n;
|
|
923
|
+
}
|
|
924
|
+
/** Keep the table bounded: delete all but the newest `keep` rows. */
|
|
925
|
+
pruneToolCalls(keep = 5000) {
|
|
926
|
+
const info = this.db.prepare("DELETE FROM tool_calls WHERE id NOT IN (SELECT id FROM tool_calls ORDER BY id DESC LIMIT ?)").run(keep);
|
|
927
|
+
return info.changes;
|
|
928
|
+
}
|
|
929
|
+
getLatestIndexedAt() {
|
|
930
|
+
const row = this.db.prepare("SELECT MAX(last_indexed_at) as value FROM files").get();
|
|
931
|
+
return row?.value ?? null;
|
|
932
|
+
}
|
|
933
|
+
getIndexGeneration() {
|
|
934
|
+
const value = Number(this.getMeta("index_generation") ?? "0");
|
|
935
|
+
return Number.isFinite(value) ? value : 0;
|
|
936
|
+
}
|
|
937
|
+
bumpIndexGeneration() {
|
|
938
|
+
const next = this.getIndexGeneration() + 1;
|
|
939
|
+
this.setMeta("index_generation", String(next));
|
|
940
|
+
return next;
|
|
941
|
+
}
|
|
942
|
+
getMeta(key) {
|
|
943
|
+
const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
944
|
+
return row?.value ?? null;
|
|
945
|
+
}
|
|
946
|
+
setMeta(key, value) {
|
|
947
|
+
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, value);
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Atomic compare-and-set on the meta table. Returns true if the previous value matched
|
|
951
|
+
* `expected` and `next` was written; false otherwise. Both reads and writes happen inside
|
|
952
|
+
* an IMMEDIATE transaction so concurrent callers cannot race past the guard.
|
|
953
|
+
*/
|
|
954
|
+
compareAndSetMeta(key, expected, next, extra) {
|
|
955
|
+
const txn = this.db.transaction(() => {
|
|
956
|
+
const row = this.db.prepare("SELECT value FROM meta WHERE key = ?").get(key);
|
|
957
|
+
const current = row?.value ?? null;
|
|
958
|
+
if (current !== expected)
|
|
959
|
+
return false;
|
|
960
|
+
if (next === null) {
|
|
961
|
+
this.db.prepare("DELETE FROM meta WHERE key = ?").run(key);
|
|
962
|
+
}
|
|
963
|
+
else {
|
|
964
|
+
this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(key, next);
|
|
965
|
+
}
|
|
966
|
+
if (extra) {
|
|
967
|
+
const insert = this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)");
|
|
968
|
+
for (const [k, v] of extra)
|
|
969
|
+
insert.run(k, v);
|
|
970
|
+
}
|
|
971
|
+
return true;
|
|
972
|
+
});
|
|
973
|
+
// BEGIN IMMEDIATE acquires the write lock up-front so the SELECT inside the transaction
|
|
974
|
+
// can't be raced by a concurrent SELECT-then-INSERT from another tool call.
|
|
975
|
+
return txn.immediate();
|
|
976
|
+
}
|
|
977
|
+
setMetaBatch(entries) {
|
|
978
|
+
const insert = this.db.prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)");
|
|
979
|
+
const tx = this.db.transaction((rows) => {
|
|
980
|
+
for (const [key, value] of rows)
|
|
981
|
+
insert.run(key, value);
|
|
982
|
+
});
|
|
983
|
+
tx(entries);
|
|
984
|
+
}
|
|
985
|
+
close() {
|
|
986
|
+
this.db.close();
|
|
987
|
+
}
|
|
988
|
+
vacuum() {
|
|
989
|
+
this.db.exec("VACUUM");
|
|
990
|
+
}
|
|
991
|
+
/** Commit a synchronous group of related index writes, or roll them all back. */
|
|
992
|
+
transaction(operation) {
|
|
993
|
+
return this.db.transaction(operation)();
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Flush the WAL into the main index.db so an on-disk copy of the file is fully
|
|
997
|
+
* current. Used before seeding a worktree from this base index. TRUNCATE resets
|
|
998
|
+
* the WAL after checkpointing. Best-effort: a busy checkpoint is non-fatal.
|
|
999
|
+
*/
|
|
1000
|
+
checkpoint() {
|
|
1001
|
+
try {
|
|
1002
|
+
this.db.pragma("wal_checkpoint(TRUNCATE)");
|
|
1003
|
+
}
|
|
1004
|
+
catch {
|
|
1005
|
+
// A concurrent writer can block a full checkpoint; the seed tolerates a
|
|
1006
|
+
// slightly stale copy because the worktree's incremental reindex reconciles.
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
/** Checkpoint the base DB (if open) so a file copy captures all committed rows. */
|
|
1011
|
+
export function checkpointDB(rootDir) {
|
|
1012
|
+
const db = dbInstances.get(resolve(rootDir));
|
|
1013
|
+
if (db)
|
|
1014
|
+
db.checkpoint();
|
|
1015
|
+
}
|
|
1016
|
+
const dbInstances = new Map();
|
|
1017
|
+
export async function getDB(rootDir) {
|
|
1018
|
+
const normalizedRoot = resolve(rootDir);
|
|
1019
|
+
const existing = dbInstances.get(normalizedRoot);
|
|
1020
|
+
if (existing)
|
|
1021
|
+
return existing;
|
|
1022
|
+
await ensureNativeStorage();
|
|
1023
|
+
await ensureContextProDir(normalizedRoot);
|
|
1024
|
+
// Another request may have opened this root while native repair was awaited.
|
|
1025
|
+
const opened = dbInstances.get(normalizedRoot);
|
|
1026
|
+
if (opened)
|
|
1027
|
+
return opened;
|
|
1028
|
+
const db = new ContextPlusDB(normalizedRoot);
|
|
1029
|
+
dbInstances.set(normalizedRoot, db);
|
|
1030
|
+
return db;
|
|
1031
|
+
}
|
|
1032
|
+
export function closeDB(rootDir) {
|
|
1033
|
+
if (rootDir) {
|
|
1034
|
+
const normalizedRoot = resolve(rootDir);
|
|
1035
|
+
const db = dbInstances.get(normalizedRoot);
|
|
1036
|
+
if (db) {
|
|
1037
|
+
db.close();
|
|
1038
|
+
dbInstances.delete(normalizedRoot);
|
|
1039
|
+
}
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
for (const [key, db] of dbInstances) {
|
|
1043
|
+
db.close();
|
|
1044
|
+
dbInstances.delete(key);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
//# sourceMappingURL=db.js.map
|