grepmind-core 0.1.0-alpha
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +190 -0
- package/dist/config/types.d.ts +174 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +137 -0
- package/dist/config/types.js.map +1 -0
- package/dist/git.d.ts +98 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +298 -0
- package/dist/git.js.map +1 -0
- package/dist/git.test.d.ts +7 -0
- package/dist/git.test.d.ts.map +1 -0
- package/dist/git.test.js +242 -0
- package/dist/git.test.js.map +1 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/indexer/branch.d.ts +121 -0
- package/dist/indexer/branch.d.ts.map +1 -0
- package/dist/indexer/branch.js +451 -0
- package/dist/indexer/branch.js.map +1 -0
- package/dist/indexer/chunker.d.ts +9 -0
- package/dist/indexer/chunker.d.ts.map +1 -0
- package/dist/indexer/chunker.js +70 -0
- package/dist/indexer/chunker.js.map +1 -0
- package/dist/indexer/chunker.test.d.ts +2 -0
- package/dist/indexer/chunker.test.d.ts.map +1 -0
- package/dist/indexer/chunker.test.js +180 -0
- package/dist/indexer/chunker.test.js.map +1 -0
- package/dist/indexer/code/branch.d.ts +155 -0
- package/dist/indexer/code/branch.d.ts.map +1 -0
- package/dist/indexer/code/branch.js +550 -0
- package/dist/indexer/code/branch.js.map +1 -0
- package/dist/indexer/code/branch.test.d.ts +7 -0
- package/dist/indexer/code/branch.test.d.ts.map +1 -0
- package/dist/indexer/code/branch.test.js +241 -0
- package/dist/indexer/code/branch.test.js.map +1 -0
- package/dist/indexer/code/chunker.d.ts +61 -0
- package/dist/indexer/code/chunker.d.ts.map +1 -0
- package/dist/indexer/code/chunker.js +311 -0
- package/dist/indexer/code/chunker.js.map +1 -0
- package/dist/indexer/code/chunker.test.d.ts +2 -0
- package/dist/indexer/code/chunker.test.d.ts.map +1 -0
- package/dist/indexer/code/chunker.test.js +552 -0
- package/dist/indexer/code/chunker.test.js.map +1 -0
- package/dist/indexer/code/fts.test.d.ts +2 -0
- package/dist/indexer/code/fts.test.d.ts.map +1 -0
- package/dist/indexer/code/fts.test.js +14 -0
- package/dist/indexer/code/fts.test.js.map +1 -0
- package/dist/indexer/code/graph/embedded.d.ts +11 -0
- package/dist/indexer/code/graph/embedded.d.ts.map +1 -0
- package/dist/indexer/code/graph/embedded.js +152 -0
- package/dist/indexer/code/graph/embedded.js.map +1 -0
- package/dist/indexer/code/graph/embedded.test.d.ts +2 -0
- package/dist/indexer/code/graph/embedded.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/embedded.test.js +105 -0
- package/dist/indexer/code/graph/embedded.test.js.map +1 -0
- package/dist/indexer/code/graph/facts.d.ts +11 -0
- package/dist/indexer/code/graph/facts.d.ts.map +1 -0
- package/dist/indexer/code/graph/facts.js +456 -0
- package/dist/indexer/code/graph/facts.js.map +1 -0
- package/dist/indexer/code/graph/facts.test.d.ts +2 -0
- package/dist/indexer/code/graph/facts.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/facts.test.js +181 -0
- package/dist/indexer/code/graph/facts.test.js.map +1 -0
- package/dist/indexer/code/graph/id.d.ts +14 -0
- package/dist/indexer/code/graph/id.d.ts.map +1 -0
- package/dist/indexer/code/graph/id.js +40 -0
- package/dist/indexer/code/graph/id.js.map +1 -0
- package/dist/indexer/code/graph/id.test.d.ts +2 -0
- package/dist/indexer/code/graph/id.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/id.test.js +86 -0
- package/dist/indexer/code/graph/id.test.js.map +1 -0
- package/dist/indexer/code/graph/index.d.ts +133 -0
- package/dist/indexer/code/graph/index.d.ts.map +1 -0
- package/dist/indexer/code/graph/index.js +1876 -0
- package/dist/indexer/code/graph/index.js.map +1 -0
- package/dist/indexer/code/graph/index.test.d.ts +2 -0
- package/dist/indexer/code/graph/index.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/index.test.js +210 -0
- package/dist/indexer/code/graph/index.test.js.map +1 -0
- package/dist/indexer/code/graph/queries.d.ts +22 -0
- package/dist/indexer/code/graph/queries.d.ts.map +1 -0
- package/dist/indexer/code/graph/queries.js +79 -0
- package/dist/indexer/code/graph/queries.js.map +1 -0
- package/dist/indexer/code/graph/queries.test.d.ts +2 -0
- package/dist/indexer/code/graph/queries.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/queries.test.js +108 -0
- package/dist/indexer/code/graph/queries.test.js.map +1 -0
- package/dist/indexer/code/graph/resolver.d.ts +136 -0
- package/dist/indexer/code/graph/resolver.d.ts.map +1 -0
- package/dist/indexer/code/graph/resolver.js +839 -0
- package/dist/indexer/code/graph/resolver.js.map +1 -0
- package/dist/indexer/code/graph/resolver.test.d.ts +2 -0
- package/dist/indexer/code/graph/resolver.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/resolver.test.js +482 -0
- package/dist/indexer/code/graph/resolver.test.js.map +1 -0
- package/dist/indexer/code/graph/semantic.d.ts +33 -0
- package/dist/indexer/code/graph/semantic.d.ts.map +1 -0
- package/dist/indexer/code/graph/semantic.js +279 -0
- package/dist/indexer/code/graph/semantic.js.map +1 -0
- package/dist/indexer/code/graph/semantic.test.d.ts +2 -0
- package/dist/indexer/code/graph/semantic.test.d.ts.map +1 -0
- package/dist/indexer/code/graph/semantic.test.js +127 -0
- package/dist/indexer/code/graph/semantic.test.js.map +1 -0
- package/dist/indexer/code/index.d.ts +404 -0
- package/dist/indexer/code/index.d.ts.map +1 -0
- package/dist/indexer/code/index.js +2070 -0
- package/dist/indexer/code/index.js.map +1 -0
- package/dist/indexer/code/languages/bash.d.ts +14 -0
- package/dist/indexer/code/languages/bash.d.ts.map +1 -0
- package/dist/indexer/code/languages/bash.js +125 -0
- package/dist/indexer/code/languages/bash.js.map +1 -0
- package/dist/indexer/code/languages/css.d.ts +16 -0
- package/dist/indexer/code/languages/css.d.ts.map +1 -0
- package/dist/indexer/code/languages/css.js +204 -0
- package/dist/indexer/code/languages/css.js.map +1 -0
- package/dist/indexer/code/languages/generic.d.ts +61 -0
- package/dist/indexer/code/languages/generic.d.ts.map +1 -0
- package/dist/indexer/code/languages/generic.js +150 -0
- package/dist/indexer/code/languages/generic.js.map +1 -0
- package/dist/indexer/code/languages/graphql.d.ts +13 -0
- package/dist/indexer/code/languages/graphql.d.ts.map +1 -0
- package/dist/indexer/code/languages/graphql.js +180 -0
- package/dist/indexer/code/languages/graphql.js.map +1 -0
- package/dist/indexer/code/languages/html.d.ts +16 -0
- package/dist/indexer/code/languages/html.d.ts.map +1 -0
- package/dist/indexer/code/languages/html.js +138 -0
- package/dist/indexer/code/languages/html.js.map +1 -0
- package/dist/indexer/code/languages/index.d.ts +9 -0
- package/dist/indexer/code/languages/index.d.ts.map +1 -0
- package/dist/indexer/code/languages/index.js +12 -0
- package/dist/indexer/code/languages/index.js.map +1 -0
- package/dist/indexer/code/languages/json.d.ts +12 -0
- package/dist/indexer/code/languages/json.d.ts.map +1 -0
- package/dist/indexer/code/languages/json.js +66 -0
- package/dist/indexer/code/languages/json.js.map +1 -0
- package/dist/indexer/code/languages/registry.d.ts +78 -0
- package/dist/indexer/code/languages/registry.d.ts.map +1 -0
- package/dist/indexer/code/languages/registry.js +72 -0
- package/dist/indexer/code/languages/registry.js.map +1 -0
- package/dist/indexer/code/languages/typescript.d.ts +39 -0
- package/dist/indexer/code/languages/typescript.d.ts.map +1 -0
- package/dist/indexer/code/languages/typescript.js +300 -0
- package/dist/indexer/code/languages/typescript.js.map +1 -0
- package/dist/indexer/code/languages/yaml.d.ts +13 -0
- package/dist/indexer/code/languages/yaml.d.ts.map +1 -0
- package/dist/indexer/code/languages/yaml.js +90 -0
- package/dist/indexer/code/languages/yaml.js.map +1 -0
- package/dist/indexer/code/parser.d.ts +26 -0
- package/dist/indexer/code/parser.d.ts.map +1 -0
- package/dist/indexer/code/parser.js +332 -0
- package/dist/indexer/code/parser.js.map +1 -0
- package/dist/indexer/code/retry.d.ts +58 -0
- package/dist/indexer/code/retry.d.ts.map +1 -0
- package/dist/indexer/code/retry.js +192 -0
- package/dist/indexer/code/retry.js.map +1 -0
- package/dist/indexer/code/tree/builder.d.ts +30 -0
- package/dist/indexer/code/tree/builder.d.ts.map +1 -0
- package/dist/indexer/code/tree/builder.js +132 -0
- package/dist/indexer/code/tree/builder.js.map +1 -0
- package/dist/indexer/code/tree/builder.test.d.ts +2 -0
- package/dist/indexer/code/tree/builder.test.d.ts.map +1 -0
- package/dist/indexer/code/tree/builder.test.js +31 -0
- package/dist/indexer/code/tree/builder.test.js.map +1 -0
- package/dist/indexer/code/tree/cache.d.ts +22 -0
- package/dist/indexer/code/tree/cache.d.ts.map +1 -0
- package/dist/indexer/code/tree/cache.js +85 -0
- package/dist/indexer/code/tree/cache.js.map +1 -0
- package/dist/indexer/code/tree/context.d.ts +32 -0
- package/dist/indexer/code/tree/context.d.ts.map +1 -0
- package/dist/indexer/code/tree/context.js +78 -0
- package/dist/indexer/code/tree/context.js.map +1 -0
- package/dist/indexer/code/tree/embedding.d.ts +9 -0
- package/dist/indexer/code/tree/embedding.d.ts.map +1 -0
- package/dist/indexer/code/tree/embedding.js +53 -0
- package/dist/indexer/code/tree/embedding.js.map +1 -0
- package/dist/indexer/code/tree/embedding.test.d.ts +2 -0
- package/dist/indexer/code/tree/embedding.test.d.ts.map +1 -0
- package/dist/indexer/code/tree/embedding.test.js +57 -0
- package/dist/indexer/code/tree/embedding.test.js.map +1 -0
- package/dist/indexer/code/tree/id.d.ts +3 -0
- package/dist/indexer/code/tree/id.d.ts.map +1 -0
- package/dist/indexer/code/tree/id.js +8 -0
- package/dist/indexer/code/tree/id.js.map +1 -0
- package/dist/indexer/code/tree/index.d.ts +113 -0
- package/dist/indexer/code/tree/index.d.ts.map +1 -0
- package/dist/indexer/code/tree/index.js +1146 -0
- package/dist/indexer/code/tree/index.js.map +1 -0
- package/dist/indexer/code/tree/rename.d.ts +13 -0
- package/dist/indexer/code/tree/rename.d.ts.map +1 -0
- package/dist/indexer/code/tree/rename.js +46 -0
- package/dist/indexer/code/tree/rename.js.map +1 -0
- package/dist/indexer/code/tree/repomap.d.ts +29 -0
- package/dist/indexer/code/tree/repomap.d.ts.map +1 -0
- package/dist/indexer/code/tree/repomap.js +95 -0
- package/dist/indexer/code/tree/repomap.js.map +1 -0
- package/dist/indexer/code/tree/repomap.test.d.ts +2 -0
- package/dist/indexer/code/tree/repomap.test.d.ts.map +1 -0
- package/dist/indexer/code/tree/repomap.test.js +93 -0
- package/dist/indexer/code/tree/repomap.test.js.map +1 -0
- package/dist/indexer/code/tree/stats.d.ts +26 -0
- package/dist/indexer/code/tree/stats.d.ts.map +1 -0
- package/dist/indexer/code/tree/stats.js +49 -0
- package/dist/indexer/code/tree/stats.js.map +1 -0
- package/dist/indexer/code/tree/types.d.ts +186 -0
- package/dist/indexer/code/tree/types.d.ts.map +1 -0
- package/dist/indexer/code/tree/types.js +10 -0
- package/dist/indexer/code/tree/types.js.map +1 -0
- package/dist/indexer/code/wal.d.ts +144 -0
- package/dist/indexer/code/wal.d.ts.map +1 -0
- package/dist/indexer/code/wal.js +283 -0
- package/dist/indexer/code/wal.js.map +1 -0
- package/dist/indexer/embeddings.d.ts +113 -0
- package/dist/indexer/embeddings.d.ts.map +1 -0
- package/dist/indexer/embeddings.js +477 -0
- package/dist/indexer/embeddings.js.map +1 -0
- package/dist/indexer/git-sync.d.ts +117 -0
- package/dist/indexer/git-sync.d.ts.map +1 -0
- package/dist/indexer/git-sync.js +398 -0
- package/dist/indexer/git-sync.js.map +1 -0
- package/dist/indexer/index.d.ts +175 -0
- package/dist/indexer/index.d.ts.map +1 -0
- package/dist/indexer/index.js +1096 -0
- package/dist/indexer/index.js.map +1 -0
- package/dist/indexer/mocks/mock-reranker.d.ts +12 -0
- package/dist/indexer/mocks/mock-reranker.d.ts.map +1 -0
- package/dist/indexer/mocks/mock-reranker.js +26 -0
- package/dist/indexer/mocks/mock-reranker.js.map +1 -0
- package/dist/indexer/parser.d.ts +8 -0
- package/dist/indexer/parser.d.ts.map +1 -0
- package/dist/indexer/parser.js +44 -0
- package/dist/indexer/parser.js.map +1 -0
- package/dist/indexer/parser.test.d.ts +2 -0
- package/dist/indexer/parser.test.d.ts.map +1 -0
- package/dist/indexer/parser.test.js +197 -0
- package/dist/indexer/parser.test.js.map +1 -0
- package/dist/indexer/reranking.d.ts +71 -0
- package/dist/indexer/reranking.d.ts.map +1 -0
- package/dist/indexer/reranking.integration.test.d.ts +2 -0
- package/dist/indexer/reranking.integration.test.d.ts.map +1 -0
- package/dist/indexer/reranking.integration.test.js +104 -0
- package/dist/indexer/reranking.integration.test.js.map +1 -0
- package/dist/indexer/reranking.js +256 -0
- package/dist/indexer/reranking.js.map +1 -0
- package/dist/indexer/reranking.test.d.ts +2 -0
- package/dist/indexer/reranking.test.d.ts.map +1 -0
- package/dist/indexer/reranking.test.js +130 -0
- package/dist/indexer/reranking.test.js.map +1 -0
- package/dist/indexer/wal/file-storage.d.ts +60 -0
- package/dist/indexer/wal/file-storage.d.ts.map +1 -0
- package/dist/indexer/wal/file-storage.js +277 -0
- package/dist/indexer/wal/file-storage.js.map +1 -0
- package/dist/indexer/wal/file-storage.test.d.ts +8 -0
- package/dist/indexer/wal/file-storage.test.d.ts.map +1 -0
- package/dist/indexer/wal/file-storage.test.js +444 -0
- package/dist/indexer/wal/file-storage.test.js.map +1 -0
- package/dist/indexer/wal/index.d.ts +41 -0
- package/dist/indexer/wal/index.d.ts.map +1 -0
- package/dist/indexer/wal/index.js +61 -0
- package/dist/indexer/wal/index.js.map +1 -0
- package/dist/indexer/wal/integration.test.d.ts +11 -0
- package/dist/indexer/wal/integration.test.d.ts.map +1 -0
- package/dist/indexer/wal/integration.test.js +378 -0
- package/dist/indexer/wal/integration.test.js.map +1 -0
- package/dist/indexer/wal/lancedb-storage.d.ts +72 -0
- package/dist/indexer/wal/lancedb-storage.d.ts.map +1 -0
- package/dist/indexer/wal/lancedb-storage.js +462 -0
- package/dist/indexer/wal/lancedb-storage.js.map +1 -0
- package/dist/indexer/wal/lancedb-storage.test.d.ts +8 -0
- package/dist/indexer/wal/lancedb-storage.test.d.ts.map +1 -0
- package/dist/indexer/wal/lancedb-storage.test.js +415 -0
- package/dist/indexer/wal/lancedb-storage.test.js.map +1 -0
- package/dist/indexer/wal/sync-wal.d.ts +144 -0
- package/dist/indexer/wal/sync-wal.d.ts.map +1 -0
- package/dist/indexer/wal/sync-wal.js +863 -0
- package/dist/indexer/wal/sync-wal.js.map +1 -0
- package/dist/indexer/wal/sync-wal.test.d.ts +8 -0
- package/dist/indexer/wal/sync-wal.test.d.ts.map +1 -0
- package/dist/indexer/wal/sync-wal.test.js +752 -0
- package/dist/indexer/wal/sync-wal.test.js.map +1 -0
- package/dist/indexer/wal/types.d.ts +167 -0
- package/dist/indexer/wal/types.d.ts.map +1 -0
- package/dist/indexer/wal/types.js +12 -0
- package/dist/indexer/wal/types.js.map +1 -0
- package/dist/indexer/watcher.d.ts +36 -0
- package/dist/indexer/watcher.d.ts.map +1 -0
- package/dist/indexer/watcher.js +110 -0
- package/dist/indexer/watcher.js.map +1 -0
- package/dist/search/explore.d.ts +62 -0
- package/dist/search/explore.d.ts.map +1 -0
- package/dist/search/explore.js +111 -0
- package/dist/search/explore.js.map +1 -0
- package/dist/search/fts.d.ts +23 -0
- package/dist/search/fts.d.ts.map +1 -0
- package/dist/search/fts.js +64 -0
- package/dist/search/fts.js.map +1 -0
- package/dist/search/fts.test.d.ts +2 -0
- package/dist/search/fts.test.d.ts.map +1 -0
- package/dist/search/fts.test.js +27 -0
- package/dist/search/fts.test.js.map +1 -0
- package/dist/search/grep.d.ts +75 -0
- package/dist/search/grep.d.ts.map +1 -0
- package/dist/search/grep.js +96 -0
- package/dist/search/grep.js.map +1 -0
- package/dist/search/grep.test.d.ts +2 -0
- package/dist/search/grep.test.d.ts.map +1 -0
- package/dist/search/grep.test.js +178 -0
- package/dist/search/grep.test.js.map +1 -0
- package/dist/search/hybrid-grep.d.ts +43 -0
- package/dist/search/hybrid-grep.d.ts.map +1 -0
- package/dist/search/hybrid-grep.js +130 -0
- package/dist/search/hybrid-grep.js.map +1 -0
- package/dist/search/hybrid-grep.test.d.ts +2 -0
- package/dist/search/hybrid-grep.test.d.ts.map +1 -0
- package/dist/search/hybrid-grep.test.js +133 -0
- package/dist/search/hybrid-grep.test.js.map +1 -0
- package/dist/search/rg-executor.d.ts +63 -0
- package/dist/search/rg-executor.d.ts.map +1 -0
- package/dist/search/rg-executor.js +146 -0
- package/dist/search/rg-executor.js.map +1 -0
- package/dist/search/rg-executor.test.d.ts +2 -0
- package/dist/search/rg-executor.test.d.ts.map +1 -0
- package/dist/search/rg-executor.test.js +104 -0
- package/dist/search/rg-executor.test.js.map +1 -0
- package/dist/search/rg-parser/extractor.d.ts +14 -0
- package/dist/search/rg-parser/extractor.d.ts.map +1 -0
- package/dist/search/rg-parser/extractor.js +82 -0
- package/dist/search/rg-parser/extractor.js.map +1 -0
- package/dist/search/rg-parser/extractor.test.d.ts +2 -0
- package/dist/search/rg-parser/extractor.test.d.ts.map +1 -0
- package/dist/search/rg-parser/extractor.test.js +35 -0
- package/dist/search/rg-parser/extractor.test.js.map +1 -0
- package/dist/search/rg-parser/fts-builder.d.ts +7 -0
- package/dist/search/rg-parser/fts-builder.d.ts.map +1 -0
- package/dist/search/rg-parser/fts-builder.js +18 -0
- package/dist/search/rg-parser/fts-builder.js.map +1 -0
- package/dist/search/rg-parser/fts-builder.test.d.ts +2 -0
- package/dist/search/rg-parser/fts-builder.test.d.ts.map +1 -0
- package/dist/search/rg-parser/fts-builder.test.js +26 -0
- package/dist/search/rg-parser/fts-builder.test.js.map +1 -0
- package/dist/search/rg-parser/index.d.ts +36 -0
- package/dist/search/rg-parser/index.d.ts.map +1 -0
- package/dist/search/rg-parser/index.js +83 -0
- package/dist/search/rg-parser/index.js.map +1 -0
- package/dist/search/rg-parser/index.test.d.ts +2 -0
- package/dist/search/rg-parser/index.test.d.ts.map +1 -0
- package/dist/search/rg-parser/index.test.js +34 -0
- package/dist/search/rg-parser/index.test.js.map +1 -0
- package/dist/search/rg-parser/strategy.d.ts +14 -0
- package/dist/search/rg-parser/strategy.d.ts.map +1 -0
- package/dist/search/rg-parser/strategy.js +31 -0
- package/dist/search/rg-parser/strategy.js.map +1 -0
- package/dist/search/rg-parser/strategy.test.d.ts +2 -0
- package/dist/search/rg-parser/strategy.test.d.ts.map +1 -0
- package/dist/search/rg-parser/strategy.test.js +29 -0
- package/dist/search/rg-parser/strategy.test.js.map +1 -0
- package/dist/types.d.ts +345 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/vault.d.ts +84 -0
- package/dist/utils/vault.d.ts.map +1 -0
- package/dist/utils/vault.js +138 -0
- package/dist/utils/vault.js.map +1 -0
- package/dist/utils/vault.test.d.ts +2 -0
- package/dist/utils/vault.test.d.ts.map +1 -0
- package/dist/utils/vault.test.js +153 -0
- package/dist/utils/vault.test.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,1146 @@
|
|
|
1
|
+
import * as lancedb from '@lancedb/lancedb';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import commonAncestorPath from 'common-ancestor-path';
|
|
6
|
+
import { createError, isError } from '../../../types.js';
|
|
7
|
+
import { TreeBuilder } from './builder.js';
|
|
8
|
+
import { TreeContextExpander } from './context.js';
|
|
9
|
+
import { computeFileCentroid, topKeySymbols } from './embedding.js';
|
|
10
|
+
import { generateTreeId } from './id.js';
|
|
11
|
+
import { RepoMapCache, TreeCache } from './cache.js';
|
|
12
|
+
import { generateRepoMap } from './repomap.js';
|
|
13
|
+
function sanitizeSql(value) {
|
|
14
|
+
return value.replace(/'/g, "''");
|
|
15
|
+
}
|
|
16
|
+
function normalizePath(filePath) {
|
|
17
|
+
return filePath.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
18
|
+
}
|
|
19
|
+
function pathDir(filePath) {
|
|
20
|
+
const dir = path.dirname(filePath);
|
|
21
|
+
return dir === '.' ? '' : normalizePath(dir);
|
|
22
|
+
}
|
|
23
|
+
function pathBase(filePath) {
|
|
24
|
+
return path.basename(filePath);
|
|
25
|
+
}
|
|
26
|
+
function pathExt(filePath) {
|
|
27
|
+
return path.extname(filePath).replace('.', '').toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
function toNumberVector(value) {
|
|
30
|
+
if (!value)
|
|
31
|
+
return undefined;
|
|
32
|
+
const sanitize = (arr) => {
|
|
33
|
+
const out = arr.map((x) => {
|
|
34
|
+
const n = Number(x);
|
|
35
|
+
return Number.isFinite(n) ? n : 0;
|
|
36
|
+
});
|
|
37
|
+
return out.length > 0 ? out : undefined;
|
|
38
|
+
};
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return sanitize(value);
|
|
41
|
+
}
|
|
42
|
+
if (ArrayBuffer.isView(value) && Symbol.iterator in value) {
|
|
43
|
+
return sanitize(Array.from(value));
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === 'object') {
|
|
46
|
+
const vectorLike = value;
|
|
47
|
+
if (typeof vectorLike.length === 'number' && typeof vectorLike.get === 'function') {
|
|
48
|
+
const out = [];
|
|
49
|
+
for (let i = 0; i < vectorLike.length; i++) {
|
|
50
|
+
const n = Number(vectorLike.get(i));
|
|
51
|
+
out.push(Number.isFinite(n) ? n : 0);
|
|
52
|
+
}
|
|
53
|
+
return out.length > 0 ? out : undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
function parseDirRecord(row) {
|
|
59
|
+
return {
|
|
60
|
+
id: row.id,
|
|
61
|
+
path: row.path,
|
|
62
|
+
name: row.name,
|
|
63
|
+
parent_id: (row.parent_id || '') || null,
|
|
64
|
+
depth: row.depth,
|
|
65
|
+
file_count: row.file_count,
|
|
66
|
+
dir_count: row.dir_count,
|
|
67
|
+
total_files: row.total_files,
|
|
68
|
+
total_symbols: row.total_symbols,
|
|
69
|
+
modified: row.modified,
|
|
70
|
+
indexed_at: row.indexed_at,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function parseFileRecord(row) {
|
|
74
|
+
return {
|
|
75
|
+
id: row.id,
|
|
76
|
+
previous_id: row.previous_id || undefined,
|
|
77
|
+
path: row.path,
|
|
78
|
+
name: row.name,
|
|
79
|
+
dir_id: row.dir_id,
|
|
80
|
+
ext: row.ext,
|
|
81
|
+
language: row.language,
|
|
82
|
+
size: row.size,
|
|
83
|
+
content_hash: row.content_hash,
|
|
84
|
+
symbol_count: row.symbol_count,
|
|
85
|
+
import_count: row.import_count,
|
|
86
|
+
export_count: row.export_count,
|
|
87
|
+
modified: row.modified,
|
|
88
|
+
indexed_at: row.indexed_at,
|
|
89
|
+
vector: toNumberVector(row.vector),
|
|
90
|
+
key_symbols: row.key_symbols || '[]',
|
|
91
|
+
import_specs: row.import_specs || '[]',
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export class TreeIndexer {
|
|
95
|
+
db = null;
|
|
96
|
+
treeDirsTable = null;
|
|
97
|
+
treeFilesTable = null;
|
|
98
|
+
codeChunksTable = null;
|
|
99
|
+
codeFileIndexTable = null;
|
|
100
|
+
initialized = false;
|
|
101
|
+
rootPath;
|
|
102
|
+
dbPath;
|
|
103
|
+
localStateDir;
|
|
104
|
+
treeConfig;
|
|
105
|
+
embeddingProvider;
|
|
106
|
+
embeddingDimensions;
|
|
107
|
+
lancedbStorageOptions;
|
|
108
|
+
dirCache = new TreeCache(1000, 60_000);
|
|
109
|
+
fileCache = new TreeCache(1000, 60_000);
|
|
110
|
+
repoMapCache = new RepoMapCache(10);
|
|
111
|
+
metaPath;
|
|
112
|
+
meta = {
|
|
113
|
+
dirty: false,
|
|
114
|
+
indexVersion: 0,
|
|
115
|
+
updatedAt: 0,
|
|
116
|
+
indexedAt: 0,
|
|
117
|
+
};
|
|
118
|
+
constructor(rootPath, dbPath, embeddingProvider, embeddingDimensions, treeConfig, options = {}) {
|
|
119
|
+
this.rootPath = path.resolve(rootPath);
|
|
120
|
+
this.dbPath = dbPath;
|
|
121
|
+
this.localStateDir = options.localStateDir ?? dbPath;
|
|
122
|
+
this.treeConfig = treeConfig;
|
|
123
|
+
this.embeddingProvider = embeddingProvider;
|
|
124
|
+
this.embeddingDimensions = embeddingDimensions;
|
|
125
|
+
this.lancedbStorageOptions = options.lancedbStorageOptions;
|
|
126
|
+
this.metaPath = path.join(this.localStateDir, 'tree_meta.json');
|
|
127
|
+
}
|
|
128
|
+
getRootDirId() {
|
|
129
|
+
return generateTreeId('');
|
|
130
|
+
}
|
|
131
|
+
async initialize() {
|
|
132
|
+
if (this.initialized)
|
|
133
|
+
return;
|
|
134
|
+
const isRemoteDbPath = /^[a-z][a-z0-9+.-]*:\/\//i.test(this.dbPath);
|
|
135
|
+
if (!isRemoteDbPath) {
|
|
136
|
+
await fs.mkdir(this.dbPath, { recursive: true });
|
|
137
|
+
}
|
|
138
|
+
await fs.mkdir(this.localStateDir, { recursive: true });
|
|
139
|
+
this.db = await lancedb.connect(this.dbPath, {
|
|
140
|
+
storageOptions: this.lancedbStorageOptions,
|
|
141
|
+
});
|
|
142
|
+
const tableNames = await this.db.tableNames();
|
|
143
|
+
if (tableNames.includes('tree_dirs')) {
|
|
144
|
+
this.treeDirsTable = await this.db.openTable('tree_dirs');
|
|
145
|
+
}
|
|
146
|
+
if (tableNames.includes('tree_files')) {
|
|
147
|
+
this.treeFilesTable = await this.db.openTable('tree_files');
|
|
148
|
+
}
|
|
149
|
+
if (tableNames.includes('code_chunks')) {
|
|
150
|
+
this.codeChunksTable = await this.db.openTable('code_chunks');
|
|
151
|
+
}
|
|
152
|
+
if (tableNames.includes('code_file_index')) {
|
|
153
|
+
this.codeFileIndexTable = await this.db.openTable('code_file_index');
|
|
154
|
+
}
|
|
155
|
+
this.meta = await this.loadMeta();
|
|
156
|
+
this.initialized = true;
|
|
157
|
+
await this.ensureAllTables();
|
|
158
|
+
}
|
|
159
|
+
async ensureAllTables() {
|
|
160
|
+
if (!this.initialized) {
|
|
161
|
+
await this.initialize();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await this.ensureTreeDirsTable();
|
|
165
|
+
await this.ensureTreeFilesTable();
|
|
166
|
+
}
|
|
167
|
+
async ensureTreeDirsTable() {
|
|
168
|
+
if (this.treeDirsTable)
|
|
169
|
+
return this.treeDirsTable;
|
|
170
|
+
const sampleData = [
|
|
171
|
+
{
|
|
172
|
+
id: '__init__',
|
|
173
|
+
path: '',
|
|
174
|
+
name: '',
|
|
175
|
+
parent_id: '',
|
|
176
|
+
depth: 0,
|
|
177
|
+
file_count: 0,
|
|
178
|
+
dir_count: 0,
|
|
179
|
+
total_files: 0,
|
|
180
|
+
total_symbols: 0,
|
|
181
|
+
modified: 0,
|
|
182
|
+
indexed_at: 0,
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
this.treeDirsTable = await this.db.createTable('tree_dirs', sampleData, { mode: 'overwrite' });
|
|
186
|
+
await this.treeDirsTable.delete("id = '__init__'");
|
|
187
|
+
return this.treeDirsTable;
|
|
188
|
+
}
|
|
189
|
+
async ensureTreeFilesTable() {
|
|
190
|
+
if (this.treeFilesTable)
|
|
191
|
+
return this.treeFilesTable;
|
|
192
|
+
const sampleData = [
|
|
193
|
+
{
|
|
194
|
+
id: '__init__',
|
|
195
|
+
previous_id: '',
|
|
196
|
+
path: '',
|
|
197
|
+
name: '',
|
|
198
|
+
dir_id: '',
|
|
199
|
+
ext: '',
|
|
200
|
+
language: '',
|
|
201
|
+
size: 0,
|
|
202
|
+
content_hash: '',
|
|
203
|
+
symbol_count: 0,
|
|
204
|
+
import_count: 0,
|
|
205
|
+
export_count: 0,
|
|
206
|
+
modified: 0,
|
|
207
|
+
indexed_at: 0,
|
|
208
|
+
vector: new Array(this.embeddingDimensions).fill(0),
|
|
209
|
+
key_symbols: '[]',
|
|
210
|
+
import_specs: '[]',
|
|
211
|
+
},
|
|
212
|
+
];
|
|
213
|
+
this.treeFilesTable = await this.db.createTable('tree_files', sampleData, { mode: 'overwrite' });
|
|
214
|
+
await this.treeFilesTable.delete("id = '__init__'");
|
|
215
|
+
return this.treeFilesTable;
|
|
216
|
+
}
|
|
217
|
+
async loadMeta() {
|
|
218
|
+
if (!existsSync(this.metaPath)) {
|
|
219
|
+
return {
|
|
220
|
+
dirty: false,
|
|
221
|
+
indexVersion: 0,
|
|
222
|
+
updatedAt: 0,
|
|
223
|
+
indexedAt: 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
try {
|
|
227
|
+
const content = await fs.readFile(this.metaPath, 'utf-8');
|
|
228
|
+
const parsed = JSON.parse(content);
|
|
229
|
+
return {
|
|
230
|
+
dirty: Boolean(parsed.dirty),
|
|
231
|
+
indexVersion: Number.isFinite(parsed.indexVersion) ? parsed.indexVersion : 0,
|
|
232
|
+
updatedAt: Number.isFinite(parsed.updatedAt) ? parsed.updatedAt : 0,
|
|
233
|
+
indexedAt: Number.isFinite(parsed.indexedAt) ? parsed.indexedAt : 0,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
return {
|
|
238
|
+
dirty: false,
|
|
239
|
+
indexVersion: 0,
|
|
240
|
+
updatedAt: 0,
|
|
241
|
+
indexedAt: 0,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async saveMeta() {
|
|
246
|
+
await fs.writeFile(this.metaPath, JSON.stringify(this.meta, null, 2), 'utf-8');
|
|
247
|
+
}
|
|
248
|
+
async setDirty(dirty) {
|
|
249
|
+
this.meta.dirty = dirty;
|
|
250
|
+
this.meta.updatedAt = Date.now();
|
|
251
|
+
await this.saveMeta();
|
|
252
|
+
}
|
|
253
|
+
async touchTreeVersion() {
|
|
254
|
+
this.meta.indexVersion += 1;
|
|
255
|
+
this.meta.indexedAt = Date.now();
|
|
256
|
+
this.meta.updatedAt = this.meta.indexedAt;
|
|
257
|
+
this.repoMapCache.incrementVersion();
|
|
258
|
+
await this.saveMeta();
|
|
259
|
+
}
|
|
260
|
+
getFreshnessInfo() {
|
|
261
|
+
const indexedAt = this.meta.indexedAt;
|
|
262
|
+
const isStale = this.meta.dirty || indexedAt === 0 || (Date.now() - indexedAt > this.treeConfig.staleAfterMs);
|
|
263
|
+
return {
|
|
264
|
+
indexVersion: this.meta.indexVersion,
|
|
265
|
+
indexed_at: indexedAt,
|
|
266
|
+
isStale,
|
|
267
|
+
staleReason: this.meta.dirty ? 'wal_recovery_pending' : (isStale ? 'unknown' : undefined),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async ensureCodeChunksTable() {
|
|
271
|
+
if (this.codeChunksTable)
|
|
272
|
+
return this.codeChunksTable;
|
|
273
|
+
const names = await this.db.tableNames();
|
|
274
|
+
if (!names.includes('code_chunks'))
|
|
275
|
+
return null;
|
|
276
|
+
this.codeChunksTable = await this.db.openTable('code_chunks');
|
|
277
|
+
return this.codeChunksTable;
|
|
278
|
+
}
|
|
279
|
+
async ensureCodeFileIndexTable() {
|
|
280
|
+
if (this.codeFileIndexTable)
|
|
281
|
+
return this.codeFileIndexTable;
|
|
282
|
+
const names = await this.db.tableNames();
|
|
283
|
+
if (!names.includes('code_file_index'))
|
|
284
|
+
return null;
|
|
285
|
+
this.codeFileIndexTable = await this.db.openTable('code_file_index');
|
|
286
|
+
return this.codeFileIndexTable;
|
|
287
|
+
}
|
|
288
|
+
async listIndexedPaths() {
|
|
289
|
+
const table = await this.ensureCodeFileIndexTable();
|
|
290
|
+
if (!table)
|
|
291
|
+
return [];
|
|
292
|
+
const rows = await table.query().select(['path']).toArray();
|
|
293
|
+
return rows.map((row) => normalizePath(row.path));
|
|
294
|
+
}
|
|
295
|
+
async upsertDir(dir, indexedAt) {
|
|
296
|
+
const table = await this.ensureTreeDirsTable();
|
|
297
|
+
await table.delete(`id = '${sanitizeSql(dir.id)}'`);
|
|
298
|
+
await table.add([
|
|
299
|
+
{
|
|
300
|
+
id: dir.id,
|
|
301
|
+
path: dir.path,
|
|
302
|
+
name: dir.name,
|
|
303
|
+
parent_id: dir.parent_id ?? '',
|
|
304
|
+
depth: dir.depth,
|
|
305
|
+
file_count: dir.file_count,
|
|
306
|
+
dir_count: dir.dir_count,
|
|
307
|
+
total_files: dir.total_files,
|
|
308
|
+
total_symbols: dir.total_symbols,
|
|
309
|
+
modified: dir.modified,
|
|
310
|
+
indexed_at: indexedAt,
|
|
311
|
+
},
|
|
312
|
+
]);
|
|
313
|
+
this.dirCache.set(dir.id, { ...dir, indexed_at: indexedAt });
|
|
314
|
+
}
|
|
315
|
+
async upsertTreeFile(file) {
|
|
316
|
+
const table = await this.ensureTreeFilesTable();
|
|
317
|
+
await table.delete(`id = '${sanitizeSql(file.id)}'`);
|
|
318
|
+
await table.add([
|
|
319
|
+
{
|
|
320
|
+
id: file.id,
|
|
321
|
+
previous_id: file.previous_id ?? '',
|
|
322
|
+
path: file.path,
|
|
323
|
+
name: file.name,
|
|
324
|
+
dir_id: file.dir_id,
|
|
325
|
+
ext: file.ext,
|
|
326
|
+
language: file.language,
|
|
327
|
+
size: file.size,
|
|
328
|
+
content_hash: file.content_hash,
|
|
329
|
+
symbol_count: file.symbol_count,
|
|
330
|
+
import_count: file.import_count,
|
|
331
|
+
export_count: file.export_count,
|
|
332
|
+
modified: file.modified,
|
|
333
|
+
indexed_at: file.indexed_at,
|
|
334
|
+
vector: file.vector ?? new Array(this.embeddingDimensions).fill(0),
|
|
335
|
+
key_symbols: file.key_symbols ?? '[]',
|
|
336
|
+
import_specs: file.import_specs ?? '[]',
|
|
337
|
+
},
|
|
338
|
+
]);
|
|
339
|
+
this.fileCache.set(file.id, file);
|
|
340
|
+
}
|
|
341
|
+
async ensureDirChain(dirPath) {
|
|
342
|
+
const normalizedDirPath = normalizePath(dirPath);
|
|
343
|
+
const parts = normalizedDirPath.split('/').filter(Boolean);
|
|
344
|
+
let current = '';
|
|
345
|
+
const now = Date.now();
|
|
346
|
+
const rootId = this.getRootDirId();
|
|
347
|
+
const rootExists = await this.getDirById(rootId);
|
|
348
|
+
if (!rootExists) {
|
|
349
|
+
await this.upsertDir({
|
|
350
|
+
id: rootId,
|
|
351
|
+
path: '',
|
|
352
|
+
name: '',
|
|
353
|
+
parent_id: null,
|
|
354
|
+
depth: 0,
|
|
355
|
+
file_count: 0,
|
|
356
|
+
dir_count: 0,
|
|
357
|
+
total_files: 0,
|
|
358
|
+
total_symbols: 0,
|
|
359
|
+
modified: 0,
|
|
360
|
+
}, now);
|
|
361
|
+
}
|
|
362
|
+
for (let i = 0; i < parts.length; i++) {
|
|
363
|
+
current = current ? `${current}/${parts[i]}` : parts[i];
|
|
364
|
+
const id = generateTreeId(current);
|
|
365
|
+
const existing = await this.getDirById(id);
|
|
366
|
+
if (existing)
|
|
367
|
+
continue;
|
|
368
|
+
const parentPath = i === 0 ? '' : parts.slice(0, i).join('/');
|
|
369
|
+
await this.upsertDir({
|
|
370
|
+
id,
|
|
371
|
+
path: current,
|
|
372
|
+
name: parts[i],
|
|
373
|
+
parent_id: generateTreeId(parentPath),
|
|
374
|
+
depth: i + 1,
|
|
375
|
+
file_count: 0,
|
|
376
|
+
dir_count: 0,
|
|
377
|
+
total_files: 0,
|
|
378
|
+
total_symbols: 0,
|
|
379
|
+
modified: 0,
|
|
380
|
+
}, now);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
async loadBuildFile(pathValue, deps) {
|
|
384
|
+
const relativePath = normalizePath(pathValue);
|
|
385
|
+
const [record, symbols] = await Promise.all([
|
|
386
|
+
deps.getFileRecord(relativePath),
|
|
387
|
+
deps.getSymbols(relativePath),
|
|
388
|
+
]);
|
|
389
|
+
if (!record)
|
|
390
|
+
return null;
|
|
391
|
+
const size = symbols.reduce((sum, symbol) => sum + Buffer.byteLength(symbol.content, 'utf-8'), 0);
|
|
392
|
+
const symbolCount = symbols.filter((symbol) => symbol.symbolType !== 'file').length;
|
|
393
|
+
return {
|
|
394
|
+
path: relativePath,
|
|
395
|
+
language: record.language,
|
|
396
|
+
content_hash: record.content_hash,
|
|
397
|
+
modified: record.mtime,
|
|
398
|
+
size,
|
|
399
|
+
symbol_count: symbolCount,
|
|
400
|
+
import_count: 0,
|
|
401
|
+
export_count: 0,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
async buildTree(deps, options = {}) {
|
|
405
|
+
const { onProgress } = options;
|
|
406
|
+
await this.initialize();
|
|
407
|
+
await this.setDirty(true);
|
|
408
|
+
const indexedPaths = await this.listIndexedPaths();
|
|
409
|
+
const buildInputs = [];
|
|
410
|
+
// Phase 1: Loading files
|
|
411
|
+
for (let i = 0; i < indexedPaths.length; i++) {
|
|
412
|
+
const item = await this.loadBuildFile(indexedPaths[i], deps);
|
|
413
|
+
if (item)
|
|
414
|
+
buildInputs.push(item);
|
|
415
|
+
onProgress?.(i + 1, indexedPaths.length, 'loading');
|
|
416
|
+
}
|
|
417
|
+
// Phase 2: Building tree structure
|
|
418
|
+
onProgress?.(0, 1, 'building');
|
|
419
|
+
const builder = new TreeBuilder(this.rootPath);
|
|
420
|
+
const build = await builder.build(buildInputs);
|
|
421
|
+
onProgress?.(1, 1, 'building');
|
|
422
|
+
const dirsTable = await this.ensureTreeDirsTable();
|
|
423
|
+
const filesTable = await this.ensureTreeFilesTable();
|
|
424
|
+
await dirsTable.delete("id != '__never__'");
|
|
425
|
+
await filesTable.delete("id != '__never__'");
|
|
426
|
+
// Phase 3: Indexing dirs and files
|
|
427
|
+
const totalItems = build.dirs.length + build.files.length;
|
|
428
|
+
let processed = 0;
|
|
429
|
+
const indexedAt = Date.now();
|
|
430
|
+
for (const dir of build.dirs) {
|
|
431
|
+
await this.upsertDir(dir, indexedAt);
|
|
432
|
+
processed++;
|
|
433
|
+
onProgress?.(processed, totalItems, 'indexing');
|
|
434
|
+
}
|
|
435
|
+
// Phase 3b: Indexing files with embeddings
|
|
436
|
+
for (let i = 0; i < build.files.length; i++) {
|
|
437
|
+
const file = build.files[i];
|
|
438
|
+
const symbols = await deps.getSymbols(file.path);
|
|
439
|
+
const enriched = await this.enrichFileEmbedding(file, symbols);
|
|
440
|
+
await this.upsertTreeFile(enriched);
|
|
441
|
+
processed++;
|
|
442
|
+
// Report as indexing for dirs, embedding for files (includes vector computation)
|
|
443
|
+
onProgress?.(processed, totalItems, this.treeConfig.embedFiles ? 'embedding' : 'indexing');
|
|
444
|
+
}
|
|
445
|
+
// Phase 4: Rebuilding directory stats
|
|
446
|
+
onProgress?.(0, build.dirs.length, 'stats');
|
|
447
|
+
await this.rebuildDirStats();
|
|
448
|
+
onProgress?.(build.dirs.length, build.dirs.length, 'stats');
|
|
449
|
+
await this.touchTreeVersion();
|
|
450
|
+
await this.setDirty(false);
|
|
451
|
+
return {
|
|
452
|
+
totalDirs: build.stats.totalDirs,
|
|
453
|
+
totalFiles: build.stats.total_files,
|
|
454
|
+
maxDepth: build.stats.maxDepth,
|
|
455
|
+
buildTimeMs: build.stats.buildTimeMs,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
async enrichFileEmbedding(file, symbols) {
|
|
459
|
+
if (!this.treeConfig.embedFiles) {
|
|
460
|
+
return {
|
|
461
|
+
...file,
|
|
462
|
+
key_symbols: JSON.stringify(topKeySymbols(symbols, 5)),
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
const symbolsWithVectors = await this.getSymbolVectors(file.path);
|
|
466
|
+
const vector = computeFileCentroid(symbolsWithVectors.map((s) => ({ vector: s.vector, type: s.type })));
|
|
467
|
+
return {
|
|
468
|
+
...file,
|
|
469
|
+
vector: vector.length > 0 ? vector : undefined,
|
|
470
|
+
key_symbols: JSON.stringify(topKeySymbols(symbols, 5)),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
async getSymbolVectors(filePath) {
|
|
474
|
+
const table = await this.ensureCodeChunksTable();
|
|
475
|
+
if (!table)
|
|
476
|
+
return [];
|
|
477
|
+
const rows = await table
|
|
478
|
+
.query()
|
|
479
|
+
.where(`path = '${sanitizeSql(filePath)}'`)
|
|
480
|
+
.select(['vector', 'symbol_type'])
|
|
481
|
+
.toArray();
|
|
482
|
+
const vectors = [];
|
|
483
|
+
for (const row of rows) {
|
|
484
|
+
const vector = toNumberVector(row.vector);
|
|
485
|
+
if (!vector || vector.length === 0)
|
|
486
|
+
continue;
|
|
487
|
+
const type = row.symbol_type;
|
|
488
|
+
vectors.push({ vector, type });
|
|
489
|
+
}
|
|
490
|
+
return vectors;
|
|
491
|
+
}
|
|
492
|
+
async upsertFile(filePath, deps) {
|
|
493
|
+
await this.initialize();
|
|
494
|
+
await this.setDirty(true);
|
|
495
|
+
const normalized = normalizePath(filePath);
|
|
496
|
+
const [record, symbols] = await Promise.all([
|
|
497
|
+
deps.getFileRecord(normalized),
|
|
498
|
+
deps.getSymbols(normalized),
|
|
499
|
+
]);
|
|
500
|
+
if (!record) {
|
|
501
|
+
await this.setDirty(false);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const dirPath = pathDir(normalized);
|
|
505
|
+
await this.ensureDirChain(dirPath);
|
|
506
|
+
const file = {
|
|
507
|
+
id: generateTreeId(normalized),
|
|
508
|
+
path: normalized,
|
|
509
|
+
name: pathBase(normalized),
|
|
510
|
+
dir_id: generateTreeId(dirPath),
|
|
511
|
+
ext: pathExt(normalized),
|
|
512
|
+
language: record.language,
|
|
513
|
+
size: symbols.reduce((sum, symbol) => sum + Buffer.byteLength(symbol.content, 'utf-8'), 0),
|
|
514
|
+
content_hash: record.content_hash,
|
|
515
|
+
symbol_count: symbols.filter((symbol) => symbol.symbolType !== 'file').length,
|
|
516
|
+
import_count: 0,
|
|
517
|
+
export_count: 0,
|
|
518
|
+
modified: record.mtime,
|
|
519
|
+
indexed_at: Date.now(),
|
|
520
|
+
key_symbols: '[]',
|
|
521
|
+
import_specs: '[]',
|
|
522
|
+
};
|
|
523
|
+
const enriched = await this.enrichFileEmbedding(file, symbols);
|
|
524
|
+
await this.upsertTreeFile(enriched);
|
|
525
|
+
await this.rebuildDirStats();
|
|
526
|
+
await this.touchTreeVersion();
|
|
527
|
+
await this.setDirty(false);
|
|
528
|
+
}
|
|
529
|
+
async removeFile(filePath) {
|
|
530
|
+
await this.initialize();
|
|
531
|
+
await this.setDirty(true);
|
|
532
|
+
const normalized = normalizePath(filePath);
|
|
533
|
+
const fileId = generateTreeId(normalized);
|
|
534
|
+
const table = await this.ensureTreeFilesTable();
|
|
535
|
+
await table.delete(`id = '${sanitizeSql(fileId)}'`);
|
|
536
|
+
this.fileCache.delete(fileId);
|
|
537
|
+
await this.rebuildDirStats();
|
|
538
|
+
await this.touchTreeVersion();
|
|
539
|
+
await this.setDirty(false);
|
|
540
|
+
}
|
|
541
|
+
async handleFileBatch(changes, deps) {
|
|
542
|
+
await this.initialize();
|
|
543
|
+
const result = {
|
|
544
|
+
success: 0,
|
|
545
|
+
failed: 0,
|
|
546
|
+
warnings: [],
|
|
547
|
+
};
|
|
548
|
+
await this.setDirty(true);
|
|
549
|
+
for (const deleted of changes.deleted) {
|
|
550
|
+
try {
|
|
551
|
+
await this.removeFile(deleted);
|
|
552
|
+
result.success += 1;
|
|
553
|
+
}
|
|
554
|
+
catch (error) {
|
|
555
|
+
result.failed += 1;
|
|
556
|
+
result.warnings.push(`remove failed: ${deleted}: ${error.message}`);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
for (const upsert of changes.addedOrUpdated) {
|
|
560
|
+
try {
|
|
561
|
+
await this.upsertFile(upsert, deps);
|
|
562
|
+
result.success += 1;
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
result.failed += 1;
|
|
566
|
+
result.warnings.push(`upsert failed: ${upsert}: ${error.message}`);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
await this.setDirty(false);
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
async reconcileAfterWalRecovery(deps) {
|
|
573
|
+
await this.buildTree(deps);
|
|
574
|
+
}
|
|
575
|
+
async rebuildDirStats() {
|
|
576
|
+
await this.initialize();
|
|
577
|
+
const dirsTable = await this.ensureTreeDirsTable();
|
|
578
|
+
const filesTable = await this.ensureTreeFilesTable();
|
|
579
|
+
const dirRows = await dirsTable.query().toArray();
|
|
580
|
+
const fileRows = await filesTable.query().toArray();
|
|
581
|
+
const dirs = new Map();
|
|
582
|
+
for (const row of dirRows) {
|
|
583
|
+
const dir = parseDirRecord(row);
|
|
584
|
+
dirs.set(dir.id, dir);
|
|
585
|
+
}
|
|
586
|
+
const files = fileRows.map((row) => parseFileRecord(row));
|
|
587
|
+
const childrenByParent = new Map();
|
|
588
|
+
for (const dir of dirs.values()) {
|
|
589
|
+
if (!dir.parent_id)
|
|
590
|
+
continue;
|
|
591
|
+
const arr = childrenByParent.get(dir.parent_id) ?? [];
|
|
592
|
+
arr.push(dir);
|
|
593
|
+
childrenByParent.set(dir.parent_id, arr);
|
|
594
|
+
}
|
|
595
|
+
for (const dir of dirs.values()) {
|
|
596
|
+
dir.file_count = 0;
|
|
597
|
+
dir.dir_count = (childrenByParent.get(dir.id) ?? []).length;
|
|
598
|
+
dir.total_files = 0;
|
|
599
|
+
dir.total_symbols = 0;
|
|
600
|
+
dir.modified = 0;
|
|
601
|
+
}
|
|
602
|
+
for (const file of files) {
|
|
603
|
+
const dir = dirs.get(file.dir_id);
|
|
604
|
+
if (!dir)
|
|
605
|
+
continue;
|
|
606
|
+
dir.file_count += 1;
|
|
607
|
+
dir.total_symbols += file.symbol_count;
|
|
608
|
+
dir.modified = Math.max(dir.modified, file.modified);
|
|
609
|
+
}
|
|
610
|
+
const sortedDirs = [...dirs.values()].sort((a, b) => b.depth - a.depth);
|
|
611
|
+
for (const dir of sortedDirs) {
|
|
612
|
+
dir.total_files = dir.file_count;
|
|
613
|
+
for (const child of childrenByParent.get(dir.id) ?? []) {
|
|
614
|
+
dir.total_files += child.total_files;
|
|
615
|
+
dir.total_symbols += child.total_symbols;
|
|
616
|
+
dir.modified = Math.max(dir.modified, child.modified);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
const indexedAt = Date.now();
|
|
620
|
+
for (const dir of dirs.values()) {
|
|
621
|
+
await this.upsertDir(dir, indexedAt);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
async getDirById(id) {
|
|
625
|
+
await this.initialize();
|
|
626
|
+
const cached = this.dirCache.get(id);
|
|
627
|
+
if (cached)
|
|
628
|
+
return cached;
|
|
629
|
+
const table = await this.ensureTreeDirsTable();
|
|
630
|
+
const rows = await table.query().where(`id = '${sanitizeSql(id)}'`).toArray();
|
|
631
|
+
if (rows.length === 0)
|
|
632
|
+
return null;
|
|
633
|
+
const dir = parseDirRecord(rows[0]);
|
|
634
|
+
this.dirCache.set(id, dir);
|
|
635
|
+
return dir;
|
|
636
|
+
}
|
|
637
|
+
async getFileById(id) {
|
|
638
|
+
await this.initialize();
|
|
639
|
+
const cached = this.fileCache.get(id);
|
|
640
|
+
if (cached)
|
|
641
|
+
return cached;
|
|
642
|
+
const table = await this.ensureTreeFilesTable();
|
|
643
|
+
const rows = await table.query().where(`id = '${sanitizeSql(id)}'`).toArray();
|
|
644
|
+
if (rows.length === 0)
|
|
645
|
+
return null;
|
|
646
|
+
const file = parseFileRecord(rows[0]);
|
|
647
|
+
this.fileCache.set(id, file);
|
|
648
|
+
return file;
|
|
649
|
+
}
|
|
650
|
+
async getDirByPath(dirPath) {
|
|
651
|
+
await this.initialize();
|
|
652
|
+
const normalized = normalizePath(dirPath);
|
|
653
|
+
const id = generateTreeId(normalized);
|
|
654
|
+
return this.getDirById(id);
|
|
655
|
+
}
|
|
656
|
+
async getFileByPath(filePath) {
|
|
657
|
+
await this.initialize();
|
|
658
|
+
const normalized = normalizePath(filePath);
|
|
659
|
+
const id = generateTreeId(normalized);
|
|
660
|
+
return this.getFileById(id);
|
|
661
|
+
}
|
|
662
|
+
async getChildren(dir_id, options = {}) {
|
|
663
|
+
await this.initialize();
|
|
664
|
+
const limit = options.limit ?? 100;
|
|
665
|
+
const offset = options.offset ?? 0;
|
|
666
|
+
const sortBy = options.sort_by ?? 'name';
|
|
667
|
+
const sortOrder = options.sort_order ?? 'asc';
|
|
668
|
+
const dirsTable = await this.ensureTreeDirsTable();
|
|
669
|
+
const filesTable = await this.ensureTreeFilesTable();
|
|
670
|
+
const dirRows = await dirsTable
|
|
671
|
+
.query()
|
|
672
|
+
.where(`parent_id = '${sanitizeSql(dir_id)}'`)
|
|
673
|
+
.toArray();
|
|
674
|
+
const fileRows = await filesTable
|
|
675
|
+
.query()
|
|
676
|
+
.where(`dir_id = '${sanitizeSql(dir_id)}'`)
|
|
677
|
+
.toArray();
|
|
678
|
+
const dirs = dirRows.map(parseDirRecord);
|
|
679
|
+
const files = fileRows.map(parseFileRecord);
|
|
680
|
+
const sortedDirs = [...dirs].sort((a, b) => {
|
|
681
|
+
if (sortBy === 'modified') {
|
|
682
|
+
return sortOrder === 'asc' ? a.modified - b.modified : b.modified - a.modified;
|
|
683
|
+
}
|
|
684
|
+
return sortOrder === 'asc'
|
|
685
|
+
? a.name.localeCompare(b.name)
|
|
686
|
+
: b.name.localeCompare(a.name);
|
|
687
|
+
});
|
|
688
|
+
const sortedFiles = [...files].sort((a, b) => {
|
|
689
|
+
if (sortBy === 'modified') {
|
|
690
|
+
return sortOrder === 'asc' ? a.modified - b.modified : b.modified - a.modified;
|
|
691
|
+
}
|
|
692
|
+
if (sortBy === 'size') {
|
|
693
|
+
return sortOrder === 'asc' ? a.size - b.size : b.size - a.size;
|
|
694
|
+
}
|
|
695
|
+
return sortOrder === 'asc'
|
|
696
|
+
? a.name.localeCompare(b.name)
|
|
697
|
+
: b.name.localeCompare(a.name);
|
|
698
|
+
});
|
|
699
|
+
return {
|
|
700
|
+
dirs: sortedDirs.slice(offset, offset + limit),
|
|
701
|
+
files: sortedFiles.slice(offset, offset + limit),
|
|
702
|
+
total: {
|
|
703
|
+
dirs: dirs.length,
|
|
704
|
+
files: files.length,
|
|
705
|
+
},
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async getAncestors(nodeId) {
|
|
709
|
+
await this.initialize();
|
|
710
|
+
const file = await this.getFileById(nodeId);
|
|
711
|
+
let currentDirId = file ? file.dir_id : nodeId;
|
|
712
|
+
const ancestors = [];
|
|
713
|
+
while (currentDirId) {
|
|
714
|
+
const dir = await this.getDirById(currentDirId);
|
|
715
|
+
if (!dir)
|
|
716
|
+
break;
|
|
717
|
+
ancestors.push(dir);
|
|
718
|
+
currentDirId = dir.parent_id;
|
|
719
|
+
}
|
|
720
|
+
return ancestors;
|
|
721
|
+
}
|
|
722
|
+
async getAncestorIds(nodeId) {
|
|
723
|
+
const ancestors = await this.getAncestors(nodeId);
|
|
724
|
+
return ancestors.map((item) => item.id);
|
|
725
|
+
}
|
|
726
|
+
async getSiblings(nodeId, options = {}) {
|
|
727
|
+
await this.initialize();
|
|
728
|
+
const limit = options.limit ?? 100;
|
|
729
|
+
const offset = options.offset ?? 0;
|
|
730
|
+
const file = await this.getFileById(nodeId);
|
|
731
|
+
if (file) {
|
|
732
|
+
const children = await this.getChildren(file.dir_id, {
|
|
733
|
+
limit: 10_000,
|
|
734
|
+
offset: 0,
|
|
735
|
+
sort_by: 'name',
|
|
736
|
+
sort_order: 'asc',
|
|
737
|
+
});
|
|
738
|
+
const siblingFiles = children.files.filter((entry) => entry.id !== file.id);
|
|
739
|
+
return {
|
|
740
|
+
dirs: children.dirs,
|
|
741
|
+
files: siblingFiles.slice(offset, offset + limit),
|
|
742
|
+
total: {
|
|
743
|
+
dirs: children.total.dirs,
|
|
744
|
+
files: siblingFiles.length,
|
|
745
|
+
},
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
const dir = await this.getDirById(nodeId);
|
|
749
|
+
if (!dir || !dir.parent_id) {
|
|
750
|
+
return {
|
|
751
|
+
dirs: [],
|
|
752
|
+
files: [],
|
|
753
|
+
total: { dirs: 0, files: 0 },
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const children = await this.getChildren(dir.parent_id, {
|
|
757
|
+
limit: 10_000,
|
|
758
|
+
offset: 0,
|
|
759
|
+
sort_by: 'name',
|
|
760
|
+
sort_order: 'asc',
|
|
761
|
+
});
|
|
762
|
+
const siblingDirs = children.dirs.filter((entry) => entry.id !== dir.id);
|
|
763
|
+
return {
|
|
764
|
+
dirs: siblingDirs.slice(offset, offset + limit),
|
|
765
|
+
files: children.files.slice(offset, offset + limit),
|
|
766
|
+
total: {
|
|
767
|
+
dirs: siblingDirs.length,
|
|
768
|
+
files: children.total.files,
|
|
769
|
+
},
|
|
770
|
+
};
|
|
771
|
+
}
|
|
772
|
+
async getSymbolsInFile(fileId, options = {}) {
|
|
773
|
+
await this.initialize();
|
|
774
|
+
const file = await this.getFileById(fileId);
|
|
775
|
+
if (!file) {
|
|
776
|
+
return { symbols: [], total: 0 };
|
|
777
|
+
}
|
|
778
|
+
const table = await this.ensureCodeChunksTable();
|
|
779
|
+
if (!table) {
|
|
780
|
+
return { symbols: [], total: 0 };
|
|
781
|
+
}
|
|
782
|
+
const rows = await table
|
|
783
|
+
.query()
|
|
784
|
+
.where(`path = '${sanitizeSql(file.path)}'`)
|
|
785
|
+
.toArray();
|
|
786
|
+
let symbols = rows.map((row) => ({
|
|
787
|
+
id: row.id,
|
|
788
|
+
path: row.path,
|
|
789
|
+
language: row.language,
|
|
790
|
+
symbolType: row.symbol_type,
|
|
791
|
+
symbolName: row.symbol_name,
|
|
792
|
+
signature: row.signature || null,
|
|
793
|
+
parentSymbol: row.parent_symbol || null,
|
|
794
|
+
scope: JSON.parse(row.scope || '[]'),
|
|
795
|
+
content: row.content,
|
|
796
|
+
startLine: row.start_line,
|
|
797
|
+
endLine: row.end_line,
|
|
798
|
+
docstring: row.docstring || null,
|
|
799
|
+
modified: row.modified,
|
|
800
|
+
contentHash: row.content_hash,
|
|
801
|
+
}));
|
|
802
|
+
if (options.types && options.types.length > 0) {
|
|
803
|
+
const set = new Set(options.types);
|
|
804
|
+
symbols = symbols.filter((symbol) => set.has(symbol.symbolType));
|
|
805
|
+
}
|
|
806
|
+
const total = symbols.length;
|
|
807
|
+
const offset = options.offset ?? 0;
|
|
808
|
+
const limit = options.limit ?? 100;
|
|
809
|
+
return {
|
|
810
|
+
symbols: symbols.slice(offset, offset + limit),
|
|
811
|
+
total,
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
async getSymbolsInDir(dir_id, options = {}) {
|
|
815
|
+
await this.initialize();
|
|
816
|
+
const dir = await this.getDirById(dir_id);
|
|
817
|
+
if (!dir)
|
|
818
|
+
return { symbols: [], total: 0, truncated: false };
|
|
819
|
+
const recursive = options.recursive ?? false;
|
|
820
|
+
const maxFiles = options.maxFiles ?? 100;
|
|
821
|
+
const filesTable = await this.ensureTreeFilesTable();
|
|
822
|
+
let rows;
|
|
823
|
+
if (recursive) {
|
|
824
|
+
const prefix = dir.path === '' ? '' : `${dir.path}/`;
|
|
825
|
+
const where = dir.path === ''
|
|
826
|
+
? "path != '__never__'"
|
|
827
|
+
: `path LIKE '${sanitizeSql(prefix)}%'`;
|
|
828
|
+
rows = await filesTable.query().where(where).select(['id']).toArray();
|
|
829
|
+
}
|
|
830
|
+
else {
|
|
831
|
+
rows = await filesTable.query().where(`dir_id = '${sanitizeSql(dir.id)}'`).select(['id']).toArray();
|
|
832
|
+
}
|
|
833
|
+
const fileIds = rows.map((row) => row.id);
|
|
834
|
+
const truncated = fileIds.length > maxFiles;
|
|
835
|
+
const selectedIds = fileIds.slice(0, maxFiles);
|
|
836
|
+
const symbols = [];
|
|
837
|
+
for (const fileId of selectedIds) {
|
|
838
|
+
const fileSymbols = await this.getSymbolsInFile(fileId, {
|
|
839
|
+
types: options.types,
|
|
840
|
+
limit: options.limit ?? 100,
|
|
841
|
+
});
|
|
842
|
+
symbols.push(...fileSymbols.symbols);
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
symbols,
|
|
846
|
+
total: symbols.length,
|
|
847
|
+
truncated,
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
async searchFiles(query, options = {}) {
|
|
851
|
+
await this.initialize();
|
|
852
|
+
const table = await this.ensureTreeFilesTable();
|
|
853
|
+
const count = await table.countRows();
|
|
854
|
+
if (count === 0) {
|
|
855
|
+
return {
|
|
856
|
+
items: [],
|
|
857
|
+
freshness: this.getFreshnessInfo(),
|
|
858
|
+
budget: {
|
|
859
|
+
estimatedTokens: 0,
|
|
860
|
+
budgetTokens: options.budgetTokens ?? this.treeConfig.retrievalBudgetTokens,
|
|
861
|
+
truncated: false,
|
|
862
|
+
},
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
const queryEmbedding = await this.embeddingProvider.getEmbedding(query, 'query');
|
|
866
|
+
if (isError(queryEmbedding)) {
|
|
867
|
+
return createError('TREE_SEARCH_EMBEDDING_ERROR', queryEmbedding.message);
|
|
868
|
+
}
|
|
869
|
+
const limit = options.limit ?? 20;
|
|
870
|
+
const threshold = options.threshold ?? 0.4;
|
|
871
|
+
let search = table.search(queryEmbedding).limit(limit * 3);
|
|
872
|
+
if (options.dir_id) {
|
|
873
|
+
search = search.where(`dir_id = '${sanitizeSql(options.dir_id)}'`);
|
|
874
|
+
}
|
|
875
|
+
const rows = await search.toArray();
|
|
876
|
+
const files = [];
|
|
877
|
+
for (const row of rows) {
|
|
878
|
+
const score = 1 - (row._distance / 2);
|
|
879
|
+
if (score < threshold)
|
|
880
|
+
continue;
|
|
881
|
+
files.push(parseFileRecord(row));
|
|
882
|
+
if (files.length >= limit)
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
const budgetTokens = options.budgetTokens ?? this.treeConfig.retrievalBudgetTokens;
|
|
886
|
+
const estimatedTokens = Math.ceil(files.reduce((sum, file) => sum + file.path.length + (file.key_symbols?.length ?? 0) + 12, 0) / 4);
|
|
887
|
+
return {
|
|
888
|
+
items: files,
|
|
889
|
+
freshness: this.getFreshnessInfo(),
|
|
890
|
+
budget: {
|
|
891
|
+
estimatedTokens,
|
|
892
|
+
budgetTokens,
|
|
893
|
+
truncated: estimatedTokens > budgetTokens,
|
|
894
|
+
truncationReason: estimatedTokens > budgetTokens ? 'token_budget' : undefined,
|
|
895
|
+
},
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
async getFileContext(seedPaths, options = {}) {
|
|
899
|
+
const expander = new TreeContextExpander(this);
|
|
900
|
+
return expander.expand(seedPaths, {
|
|
901
|
+
budgetTokens: options.budgetTokens ?? this.treeConfig.retrievalBudgetTokens,
|
|
902
|
+
ancestorLevels: options.ancestorLevels,
|
|
903
|
+
includeSiblings: options.includeSiblings,
|
|
904
|
+
maxFilesPerDir: options.maxFilesPerDir,
|
|
905
|
+
includeFreshness: options.includeFreshness,
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
async generateRepoMap(options = {}) {
|
|
909
|
+
await this.initialize();
|
|
910
|
+
const cacheKey = this.repoMapCache.makeKey('repomap', JSON.stringify(options));
|
|
911
|
+
const cached = this.repoMapCache.get(cacheKey);
|
|
912
|
+
if (cached) {
|
|
913
|
+
return {
|
|
914
|
+
text: cached,
|
|
915
|
+
freshness: this.getFreshnessInfo(),
|
|
916
|
+
budget: {
|
|
917
|
+
estimatedTokens: Math.ceil(cached.length / 4),
|
|
918
|
+
budgetTokens: options.budgetTokens ?? this.treeConfig.retrievalBudgetTokens,
|
|
919
|
+
truncated: cached.includes('[truncated:'),
|
|
920
|
+
},
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
const result = await generateRepoMap(this, {
|
|
924
|
+
maxDepth: options.maxDepth ?? this.treeConfig.repoMapMaxDepth,
|
|
925
|
+
maxSymbolsPerFile: options.maxSymbolsPerFile ?? this.treeConfig.repoMapMaxSymbols,
|
|
926
|
+
maxFilesPerDir: options.maxFilesPerDir,
|
|
927
|
+
dirId: options.dirId,
|
|
928
|
+
sort_by: options.sort_by,
|
|
929
|
+
includeSignatures: options.includeSignatures,
|
|
930
|
+
budgetTokens: options.budgetTokens ?? this.treeConfig.retrievalBudgetTokens,
|
|
931
|
+
});
|
|
932
|
+
this.repoMapCache.set(cacheKey, result.text);
|
|
933
|
+
return result;
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Get tree with symbols for semantic search results.
|
|
937
|
+
* Finds files by query, groups them by directory, and includes symbols.
|
|
938
|
+
*/
|
|
939
|
+
async getTreeWithSymbols(options) {
|
|
940
|
+
await this.initialize();
|
|
941
|
+
// Clamp parameters to safe bounds
|
|
942
|
+
const limit = Math.min(Math.max(options.limit ?? 20, 1), 100);
|
|
943
|
+
const symbolsPerFile = Math.min(Math.max(options.symbolsPerFile ?? 3, 1), 20);
|
|
944
|
+
const maxSiblingsFiles = Math.min(Math.max(options.maxSiblingsFiles ?? 5, 0), 50);
|
|
945
|
+
const includeFileContext = options.includeFileContext ?? true;
|
|
946
|
+
// 1. Semantic search for files
|
|
947
|
+
let dirId;
|
|
948
|
+
if (options.dirPath) {
|
|
949
|
+
const dir = await this.getDirByPath(options.dirPath);
|
|
950
|
+
if (dir) {
|
|
951
|
+
dirId = dir.id;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
const searchResult = await this.searchFiles(options.query, {
|
|
955
|
+
limit,
|
|
956
|
+
threshold: options.threshold,
|
|
957
|
+
dir_id: dirId,
|
|
958
|
+
});
|
|
959
|
+
if (isError(searchResult)) {
|
|
960
|
+
return { ok: false, error: searchResult.message };
|
|
961
|
+
}
|
|
962
|
+
const matchedFiles = searchResult.items;
|
|
963
|
+
if (matchedFiles.length === 0) {
|
|
964
|
+
return {
|
|
965
|
+
ok: true,
|
|
966
|
+
root: '',
|
|
967
|
+
groups: {},
|
|
968
|
+
orderedDirs: [],
|
|
969
|
+
matchedFiles: 0,
|
|
970
|
+
totalSymbols: 0,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
// 2. Find LCA of all matched paths
|
|
974
|
+
const filePaths = matchedFiles.map((f) => f.path);
|
|
975
|
+
const lca = filePaths.length === 1
|
|
976
|
+
? path.dirname(filePaths[0])
|
|
977
|
+
: commonAncestorPath(...filePaths) ?? '';
|
|
978
|
+
// 3. Group matched files by directory (relative to LCA)
|
|
979
|
+
const matchedByDir = {};
|
|
980
|
+
for (const file of matchedFiles) {
|
|
981
|
+
const relDir = path.relative(lca, path.dirname(file.path)) || '.';
|
|
982
|
+
const normalizedDir = relDir.replace(/\\/g, '/');
|
|
983
|
+
(matchedByDir[normalizedDir] ??= []).push(file);
|
|
984
|
+
}
|
|
985
|
+
// 4. Batch fetch symbols for matched files
|
|
986
|
+
const symbols = await this.batchGetSymbols(filePaths, { limit: symbolsPerFile });
|
|
987
|
+
// 5. Batch fetch file context (docstrings) for matched files
|
|
988
|
+
const fileContexts = includeFileContext
|
|
989
|
+
? await this.batchGetFileContext(filePaths)
|
|
990
|
+
: {};
|
|
991
|
+
// 6. Batch fetch siblings for each directory
|
|
992
|
+
const groups = {};
|
|
993
|
+
for (const [relDir, files] of Object.entries(matchedByDir)) {
|
|
994
|
+
// Get parent directory ID
|
|
995
|
+
const absDir = relDir === '.' ? lca : path.join(lca, relDir).replace(/\\/g, '/');
|
|
996
|
+
const parentDir = await this.getDirByPath(absDir);
|
|
997
|
+
// Build matched file entries
|
|
998
|
+
const fileEntries = [];
|
|
999
|
+
for (const file of files.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
1000
|
+
fileEntries.push({
|
|
1001
|
+
name: file.name,
|
|
1002
|
+
matched: true,
|
|
1003
|
+
fileContext: fileContexts[file.path],
|
|
1004
|
+
symbols: symbols[file.path] ?? [],
|
|
1005
|
+
});
|
|
1006
|
+
}
|
|
1007
|
+
// Get sibling files (non-matched files in same directory)
|
|
1008
|
+
let truncatedFiles = 0;
|
|
1009
|
+
if (parentDir && maxSiblingsFiles > 0) {
|
|
1010
|
+
const children = await this.getChildren(parentDir.id, {
|
|
1011
|
+
limit: 10000,
|
|
1012
|
+
sort_by: 'name',
|
|
1013
|
+
sort_order: 'asc',
|
|
1014
|
+
});
|
|
1015
|
+
const matchedNames = new Set(files.map((f) => f.name));
|
|
1016
|
+
const siblings = children.files.filter((f) => !matchedNames.has(f.name));
|
|
1017
|
+
if (siblings.length > maxSiblingsFiles) {
|
|
1018
|
+
truncatedFiles = siblings.length - maxSiblingsFiles;
|
|
1019
|
+
}
|
|
1020
|
+
for (const sibling of siblings.slice(0, maxSiblingsFiles)) {
|
|
1021
|
+
fileEntries.push({
|
|
1022
|
+
name: sibling.name,
|
|
1023
|
+
matched: false,
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
groups[relDir] = {
|
|
1028
|
+
files: fileEntries,
|
|
1029
|
+
truncatedFiles: truncatedFiles > 0 ? truncatedFiles : undefined,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
// Sort directories for stable output order
|
|
1033
|
+
const orderedDirs = Object.keys(groups).sort((a, b) => a.localeCompare(b));
|
|
1034
|
+
// Calculate total symbols
|
|
1035
|
+
const totalSymbols = Object.values(symbols).reduce((sum, fileSymbols) => sum + fileSymbols.length, 0);
|
|
1036
|
+
return {
|
|
1037
|
+
ok: true,
|
|
1038
|
+
root: lca,
|
|
1039
|
+
groups,
|
|
1040
|
+
orderedDirs,
|
|
1041
|
+
matchedFiles: matchedFiles.length,
|
|
1042
|
+
totalSymbols,
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* Batch fetch symbols for multiple files from code_chunks table.
|
|
1047
|
+
*/
|
|
1048
|
+
async batchGetSymbols(paths, options) {
|
|
1049
|
+
if (paths.length === 0)
|
|
1050
|
+
return {};
|
|
1051
|
+
const table = await this.ensureCodeChunksTable();
|
|
1052
|
+
if (!table)
|
|
1053
|
+
return {};
|
|
1054
|
+
// Query all symbols for the given paths
|
|
1055
|
+
const placeholders = paths.map((p) => `'${sanitizeSql(p)}'`).join(',');
|
|
1056
|
+
const rows = await table
|
|
1057
|
+
.query()
|
|
1058
|
+
.where(`path IN (${placeholders}) AND symbol_type IN ('class', 'interface', 'function', 'method', 'type')`)
|
|
1059
|
+
.toArray();
|
|
1060
|
+
// Group by path and apply limit per file
|
|
1061
|
+
const result = {};
|
|
1062
|
+
// Sort rows by priority: class > interface > function > method > type
|
|
1063
|
+
const typeOrder = {
|
|
1064
|
+
class: 1,
|
|
1065
|
+
interface: 2,
|
|
1066
|
+
function: 3,
|
|
1067
|
+
method: 4,
|
|
1068
|
+
type: 5,
|
|
1069
|
+
};
|
|
1070
|
+
const sortedRows = rows.sort((a, b) => {
|
|
1071
|
+
const pathCompare = a.path.localeCompare(b.path);
|
|
1072
|
+
if (pathCompare !== 0)
|
|
1073
|
+
return pathCompare;
|
|
1074
|
+
return (typeOrder[a.symbol_type] ?? 99) - (typeOrder[b.symbol_type] ?? 99);
|
|
1075
|
+
});
|
|
1076
|
+
for (const row of sortedRows) {
|
|
1077
|
+
const filePath = row.path;
|
|
1078
|
+
const fileSymbols = result[filePath] ?? [];
|
|
1079
|
+
if (fileSymbols.length >= options.limit)
|
|
1080
|
+
continue;
|
|
1081
|
+
const symbolType = row.symbol_type;
|
|
1082
|
+
const validTypes = ['class', 'function', 'method', 'interface', 'type'];
|
|
1083
|
+
if (!validTypes.includes(symbolType))
|
|
1084
|
+
continue;
|
|
1085
|
+
fileSymbols.push({
|
|
1086
|
+
name: row.symbol_name,
|
|
1087
|
+
type: symbolType,
|
|
1088
|
+
signature: row.signature || undefined,
|
|
1089
|
+
description: row.docstring
|
|
1090
|
+
? row.docstring.split('\n')[0].trim() || undefined
|
|
1091
|
+
: undefined,
|
|
1092
|
+
});
|
|
1093
|
+
result[filePath] = fileSymbols;
|
|
1094
|
+
}
|
|
1095
|
+
return result;
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Batch fetch file context (file-level docstrings) for multiple files.
|
|
1099
|
+
*/
|
|
1100
|
+
async batchGetFileContext(paths) {
|
|
1101
|
+
if (paths.length === 0)
|
|
1102
|
+
return {};
|
|
1103
|
+
const table = await this.ensureCodeChunksTable();
|
|
1104
|
+
if (!table)
|
|
1105
|
+
return {};
|
|
1106
|
+
const placeholders = paths.map((p) => `'${sanitizeSql(p)}'`).join(',');
|
|
1107
|
+
const rows = await table
|
|
1108
|
+
.query()
|
|
1109
|
+
.where(`path IN (${placeholders}) AND symbol_type = 'file' AND docstring IS NOT NULL`)
|
|
1110
|
+
.select(['path', 'docstring'])
|
|
1111
|
+
.toArray();
|
|
1112
|
+
const result = {};
|
|
1113
|
+
for (const row of rows) {
|
|
1114
|
+
const docstring = row.docstring;
|
|
1115
|
+
if (docstring && docstring.trim()) {
|
|
1116
|
+
result[row.path] = docstring.trim();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return result;
|
|
1120
|
+
}
|
|
1121
|
+
async clear() {
|
|
1122
|
+
await this.initialize();
|
|
1123
|
+
if (this.treeDirsTable) {
|
|
1124
|
+
await this.db.dropTable('tree_dirs');
|
|
1125
|
+
this.treeDirsTable = null;
|
|
1126
|
+
}
|
|
1127
|
+
if (this.treeFilesTable) {
|
|
1128
|
+
await this.db.dropTable('tree_files');
|
|
1129
|
+
this.treeFilesTable = null;
|
|
1130
|
+
}
|
|
1131
|
+
// Reset references to external tables (owned by codeIndexer)
|
|
1132
|
+
this.codeChunksTable = null;
|
|
1133
|
+
this.codeFileIndexTable = null;
|
|
1134
|
+
this.dirCache.clear();
|
|
1135
|
+
this.fileCache.clear();
|
|
1136
|
+
this.repoMapCache.clear();
|
|
1137
|
+
this.meta = {
|
|
1138
|
+
dirty: false,
|
|
1139
|
+
indexVersion: 0,
|
|
1140
|
+
updatedAt: Date.now(),
|
|
1141
|
+
indexedAt: 0,
|
|
1142
|
+
};
|
|
1143
|
+
await this.saveMeta();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
//# sourceMappingURL=index.js.map
|