codesearch-mcp 0.3.0-alpha → 0.4.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/CHANGELOG.md +59 -6
- package/LICENSE +661 -21
- package/README.md +33 -17
- package/dist/app.js +13 -20
- package/dist/src/ast/CallExtractor.d.ts +8 -0
- package/dist/src/ast/CallExtractor.js +68 -0
- package/dist/src/ast/GraphqlNameExtractor.d.ts +22 -0
- package/dist/src/ast/GraphqlNameExtractor.js +240 -0
- package/dist/src/ast/ImportEdgeExtractor.d.ts +11 -0
- package/dist/src/ast/ImportEdgeExtractor.js +129 -0
- package/dist/src/ast/IndexedNodeResolver.d.ts +14 -0
- package/dist/src/ast/IndexedNodeResolver.js +217 -0
- package/dist/src/ast/MemberExtractor.d.ts +8 -0
- package/dist/src/ast/MemberExtractor.js +129 -0
- package/dist/src/ast/TopLevelSymbolExtractor.d.ts +5 -0
- package/dist/src/ast/TopLevelSymbolExtractor.js +86 -0
- package/dist/src/ast/graphql-grammar.d.ts +24 -0
- package/dist/src/ast/graphql-grammar.js +93 -0
- package/dist/src/ast/node-helpers.d.ts +11 -0
- package/dist/src/ast/node-helpers.js +55 -0
- package/dist/src/commands/EvalCommand.d.ts +7 -3
- package/dist/src/commands/EvalCommand.js +36 -158
- package/dist/src/commands/IndexCommand.d.ts +10 -4
- package/dist/src/commands/IndexCommand.js +52 -124
- package/dist/src/commands/McpCommand.d.ts +6 -3
- package/dist/src/commands/McpCommand.js +19 -14
- package/dist/src/commands/WebCommand.d.ts +9 -3
- package/dist/src/commands/WebCommand.js +17 -8
- package/dist/src/commands/eval/baseline-comparison.d.ts +6 -0
- package/dist/src/commands/eval/baseline-comparison.js +54 -0
- package/dist/src/commands/eval/eval-args.d.ts +6 -0
- package/dist/src/commands/eval/eval-args.js +71 -0
- package/dist/src/commands/eval/eval-corpora.d.ts +6 -0
- package/dist/src/commands/eval/eval-corpora.js +76 -0
- package/dist/src/commands/eval/eval-metadata.d.ts +17 -0
- package/dist/src/commands/eval/eval-metadata.js +75 -0
- package/dist/src/commands/eval/index-inspector.d.ts +3 -0
- package/dist/src/commands/eval/index-inspector.js +21 -0
- package/dist/src/commands/eval/staged-search.d.ts +3 -0
- package/dist/src/commands/eval/staged-search.js +20 -0
- package/dist/src/commands/eval/suite-loader.d.ts +8 -0
- package/dist/src/commands/eval/suite-loader.js +52 -0
- package/dist/src/commands/index/backend-probe.d.ts +2 -0
- package/dist/src/commands/index/backend-probe.js +15 -0
- package/dist/src/commands/index/embedding-setup.d.ts +3 -0
- package/dist/src/commands/index/embedding-setup.js +16 -0
- package/dist/src/commands/index/index-summary.d.ts +2 -0
- package/dist/src/commands/index/index-summary.js +22 -0
- package/dist/src/commands/index/legacy-ollama-guard.d.ts +3 -0
- package/dist/src/commands/index/legacy-ollama-guard.js +23 -0
- package/dist/src/commands/index/prune-step.d.ts +3 -0
- package/dist/src/commands/index/prune-step.js +22 -0
- package/dist/src/commands/index/scan-targets.d.ts +8 -0
- package/dist/src/commands/index/scan-targets.js +107 -0
- package/dist/src/config-store.d.ts +0 -1
- package/dist/src/config-store.js +3 -4
- package/dist/src/config.d.ts +1 -1
- package/dist/src/config.js +1 -1
- package/dist/src/constants.d.ts +15 -0
- package/dist/src/constants.js +34 -6
- package/dist/src/container.d.ts +4 -2
- package/dist/src/container.js +7 -5
- package/dist/src/db/drizzle-types.d.ts +7 -0
- package/dist/src/db/index.d.ts +1 -0
- package/dist/src/db/schema-steps.d.ts +8 -0
- package/dist/src/db/schema-steps.js +105 -0
- package/dist/src/declarations/DeclarationTree.d.ts +1 -1
- package/dist/src/eval-results.js +16 -2
- package/dist/src/eval-vector-index.d.ts +5 -0
- package/dist/src/eval-vector-index.js +60 -0
- package/dist/src/help.js +9 -1
- package/dist/src/index-args.d.ts +13 -0
- package/dist/src/index-args.js +48 -0
- package/dist/src/indexing/AnalysisBatcher.d.ts +12 -0
- package/dist/src/indexing/AnalysisBatcher.js +28 -0
- package/dist/src/indexing/ChunkEmbedder.d.ts +24 -0
- package/dist/src/indexing/ChunkEmbedder.js +37 -0
- package/dist/src/indexing/ConsoleScanReporter.d.ts +10 -0
- package/dist/src/indexing/ConsoleScanReporter.js +73 -0
- package/dist/src/indexing/EmbeddingSchemaPreparer.d.ts +14 -0
- package/dist/src/indexing/EmbeddingSchemaPreparer.js +66 -0
- package/dist/src/indexing/FileIngestor.d.ts +22 -0
- package/dist/src/indexing/FileIngestor.js +116 -0
- package/dist/src/indexing/IndexStore.d.ts +24 -0
- package/dist/src/indexing/IndexStore.js +69 -0
- package/dist/src/indexing/StaleFilePruner.d.ts +8 -0
- package/dist/src/indexing/StaleFilePruner.js +55 -0
- package/dist/src/indexing/analysis-rows.d.ts +4 -0
- package/dist/src/indexing/analysis-rows.js +65 -0
- package/dist/src/indexing/project-notices.d.ts +2 -0
- package/dist/src/indexing/project-notices.js +63 -0
- package/dist/src/interfaces/IAstParser.d.ts +2 -1
- package/dist/src/interfaces/ICallGraph.d.ts +28 -0
- package/dist/src/interfaces/ICallGraph.js +2 -0
- package/dist/src/interfaces/IChunkSearch.d.ts +42 -0
- package/dist/src/interfaces/IChunkSearch.js +2 -0
- package/dist/src/interfaces/IDatabase.d.ts +6 -2
- package/dist/src/interfaces/IDependencyGraph.d.ts +24 -0
- package/dist/src/interfaces/IDependencyGraph.js +2 -0
- package/dist/src/interfaces/IIndexIdentityGuard.d.ts +3 -0
- package/dist/src/interfaces/IIndexIdentityGuard.js +2 -0
- package/dist/src/interfaces/IIndexedNodeResolver.d.ts +10 -0
- package/dist/src/interfaces/IIndexedNodeResolver.js +2 -0
- package/dist/src/interfaces/IMcpReadiness.d.ts +12 -0
- package/dist/src/interfaces/IMcpReadiness.js +2 -0
- package/dist/src/interfaces/IProjectCatalog.d.ts +13 -0
- package/dist/src/interfaces/IProjectCatalog.js +2 -0
- package/dist/src/interfaces/IProjectRoots.d.ts +3 -0
- package/dist/src/interfaces/IProjectRoots.js +2 -0
- package/dist/src/interfaces/IRetrievalEvaluator.d.ts +21 -2
- package/dist/src/interfaces/IScanReporter.d.ts +13 -0
- package/dist/src/interfaces/IScanReporter.js +2 -0
- package/dist/src/interfaces/ISymbolLookup.d.ts +49 -0
- package/dist/src/interfaces/ISymbolLookup.js +2 -0
- package/dist/src/interfaces/IToolPaths.d.ts +62 -0
- package/dist/src/interfaces/IToolPaths.js +2 -0
- package/dist/src/interfaces/IWorkspaceFiles.d.ts +28 -0
- package/dist/src/interfaces/IWorkspaceFiles.js +2 -0
- package/dist/src/interfaces/SymbolKind.d.ts +1 -0
- package/dist/src/interfaces/SymbolKind.js +2 -0
- package/dist/src/interfaces/index.d.ts +12 -1
- package/dist/src/project-scope.d.ts +1 -0
- package/dist/src/project-scope.js +5 -0
- package/dist/src/retrieval-eval.d.ts +2 -1
- package/dist/src/retrieval-eval.js +45 -2
- package/dist/src/search/CallGraph.d.ts +9 -0
- package/dist/src/search/CallGraph.js +84 -0
- package/dist/src/search/ChunkSearch.d.ts +23 -0
- package/dist/src/search/ChunkSearch.js +115 -0
- package/dist/src/search/DependencyGraph.d.ts +19 -0
- package/dist/src/search/DependencyGraph.js +180 -0
- package/dist/src/search/IndexIdentityGuard.d.ts +11 -0
- package/dist/src/search/IndexIdentityGuard.js +37 -0
- package/dist/src/search/ProjectCatalog.d.ts +11 -0
- package/dist/src/search/ProjectCatalog.js +38 -0
- package/dist/src/search/SymbolLookup.d.ts +24 -0
- package/dist/src/search/SymbolLookup.js +97 -0
- package/dist/src/search/chunks/CandidateFetcher.d.ts +19 -0
- package/dist/src/search/chunks/CandidateFetcher.js +40 -0
- package/dist/src/search/chunks/IterativeScanGate.d.ts +10 -0
- package/dist/src/search/chunks/IterativeScanGate.js +42 -0
- package/dist/src/search/chunks/RerankStage.d.ts +15 -0
- package/dist/src/search/chunks/RerankStage.js +42 -0
- package/dist/src/search/chunks/search-metrics.d.ts +18 -0
- package/dist/src/search/chunks/search-metrics.js +32 -0
- package/dist/src/search/chunks/search-plan.d.ts +18 -0
- package/dist/src/search/chunks/search-plan.js +19 -0
- package/dist/src/search/create-search-services.d.ts +26 -0
- package/dist/src/search/create-search-services.js +29 -0
- package/dist/src/search/index-paths.d.ts +1 -0
- package/dist/src/search/index-paths.js +5 -0
- package/dist/src/search/symbol-sql.d.ts +13 -0
- package/dist/src/search/symbol-sql.js +66 -0
- package/dist/src/search/symbols/SymbolGrepFallback.d.ts +19 -0
- package/dist/src/search/symbols/SymbolGrepFallback.js +106 -0
- package/dist/src/search/symbols/SymbolIndexQuery.d.ts +14 -0
- package/dist/src/search/symbols/SymbolIndexQuery.js +52 -0
- package/dist/src/search/symbols/SymbolTextSearch.d.ts +13 -0
- package/dist/src/search/symbols/SymbolTextSearch.js +84 -0
- package/dist/src/search/symbols/TargetColumnGate.d.ts +7 -0
- package/dist/src/search/symbols/TargetColumnGate.js +23 -0
- package/dist/src/search/symbols/find-symbol-constants.d.ts +4 -0
- package/dist/src/search/symbols/find-symbol-constants.js +8 -0
- package/dist/src/search/symbols/grep-args.d.ts +3 -0
- package/dist/src/search/symbols/grep-args.js +7 -0
- package/dist/src/search/symbols/grep-patterns.d.ts +4 -0
- package/dist/src/search/symbols/grep-patterns.js +60 -0
- package/dist/src/services/AstParserService.d.ts +8 -44
- package/dist/src/services/AstParserService.js +19 -938
- package/dist/src/services/DatabaseService.d.ts +8 -10
- package/dist/src/services/DatabaseService.js +29 -86
- package/dist/src/services/IndexerService.d.ts +7 -10
- package/dist/src/services/IndexerService.js +36 -255
- package/dist/src/services/McpReadiness.d.ts +18 -0
- package/dist/src/services/McpReadiness.js +73 -0
- package/dist/src/services/McpServer.d.ts +32 -9
- package/dist/src/services/McpServer.js +78 -41
- package/dist/src/services/WebServerService.d.ts +4 -3
- package/dist/src/services/WebServerService.js +16 -8
- package/dist/src/setup.d.ts +16 -4
- package/dist/src/setup.js +115 -40
- package/dist/src/symbol-name.d.ts +1 -0
- package/dist/src/symbol-name.js +16 -12
- package/dist/src/tools/FindSymbolTool.d.ts +17 -7
- package/dist/src/tools/FindSymbolTool.js +54 -35
- package/dist/src/tools/GetCallersTool.d.ts +16 -8
- package/dist/src/tools/GetCallersTool.js +65 -35
- package/dist/src/tools/GetDependenciesTool.d.ts +16 -8
- package/dist/src/tools/GetDependenciesTool.js +35 -28
- package/dist/src/tools/GetDependentsTool.d.ts +20 -8
- package/dist/src/tools/GetDependentsTool.js +47 -33
- package/dist/src/tools/ListFilesTool.d.ts +16 -8
- package/dist/src/tools/ListFilesTool.js +20 -26
- package/dist/src/tools/ListProjectsTool.d.ts +11 -7
- package/dist/src/tools/ListProjectsTool.js +13 -11
- package/dist/src/tools/McpTool.d.ts +17 -0
- package/dist/src/tools/McpTool.js +28 -0
- package/dist/src/tools/ReadFileRangeTool.d.ts +16 -8
- package/dist/src/tools/ReadFileRangeTool.js +23 -25
- package/dist/src/tools/ReadFileTool.d.ts +15 -8
- package/dist/src/tools/ReadFileTool.js +19 -24
- package/dist/src/tools/SearchCodebaseTool.d.ts +22 -10
- package/dist/src/tools/SearchCodebaseTool.js +69 -51
- package/dist/src/tools/format-helpers.d.ts +9 -3
- package/dist/src/tools/format-helpers.js +59 -14
- package/dist/src/tools/tool-args.d.ts +38 -0
- package/dist/src/tools/tool-args.js +95 -0
- package/dist/src/workspace/GrepRunner.d.ts +15 -0
- package/dist/src/workspace/GrepRunner.js +104 -0
- package/dist/src/workspace/ProjectRoots.d.ts +22 -0
- package/dist/src/workspace/ProjectRoots.js +102 -0
- package/dist/src/workspace/ToolPaths.d.ts +31 -0
- package/dist/src/workspace/ToolPaths.js +312 -0
- package/dist/src/workspace/WorkspaceFiles.d.ts +19 -0
- package/dist/src/workspace/WorkspaceFiles.js +285 -0
- package/dist/src/workspace/fs-access.d.ts +9 -0
- package/dist/src/workspace/fs-access.js +47 -0
- package/dist/src/workspace/path-helpers.d.ts +3 -0
- package/dist/src/workspace/path-helpers.js +43 -0
- package/package.json +4 -3
- package/dist/src/interfaces/ISearcher.d.ts +0 -46
- package/dist/src/services/SearcherService.d.ts +0 -235
- package/dist/src/services/SearcherService.js +0 -1118
- /package/dist/src/{interfaces/ISearcher.js → db/drizzle-types.js} +0 -0
|
@@ -8,8 +8,11 @@ exports.checkCaseCorpora = checkCaseCorpora;
|
|
|
8
8
|
exports.mergeSuites = mergeSuites;
|
|
9
9
|
exports.signTest = signTest;
|
|
10
10
|
exports.pairedChange = pairedChange;
|
|
11
|
+
exports.formatThresholdConditions = formatThresholdConditions;
|
|
11
12
|
exports.compare = compare;
|
|
12
13
|
const zod_1 = require("zod");
|
|
14
|
+
const constants_1 = require("./constants");
|
|
15
|
+
const eval_vector_index_1 = require("./eval-vector-index");
|
|
13
16
|
const CATEGORIES = ['identifier', 'behaviour', 'concept'];
|
|
14
17
|
exports.STAGE_ORDER = ['vector', 'reranked'];
|
|
15
18
|
const STAGE_SOURCE = { reranked: 'vector' };
|
|
@@ -285,6 +288,13 @@ function runFigures(cases, stages, shortPool) {
|
|
|
285
288
|
stageChanges: stageChangesOf(cases, stages),
|
|
286
289
|
};
|
|
287
290
|
}
|
|
291
|
+
function distinctThresholds(pairs) {
|
|
292
|
+
const byKey = new Map(pairs.map(p => [`${p.value}|${p.source}`, p]));
|
|
293
|
+
return [...byKey.values()].sort((a, b) => b.value - a.value || a.source.localeCompare(b.source));
|
|
294
|
+
}
|
|
295
|
+
function formatThresholdConditions(pairs) {
|
|
296
|
+
return pairs.map(p => `${(0, constants_1.formatThreshold)(p.value)} (${p.source})`).join(', ') || '—';
|
|
297
|
+
}
|
|
288
298
|
class RetrievalEvaluator {
|
|
289
299
|
async evaluate(suite, search, inspect, onCase) {
|
|
290
300
|
await checkCaseCorpora(suite.cases, p => inspect.projectDir(p));
|
|
@@ -295,6 +305,7 @@ class RetrievalEvaluator {
|
|
|
295
305
|
const fetchLimits = [];
|
|
296
306
|
const efSearches = [];
|
|
297
307
|
const iterativeScans = [];
|
|
308
|
+
const thresholds = [];
|
|
298
309
|
let stages = null;
|
|
299
310
|
const total = suite.cases.length;
|
|
300
311
|
for (const [i, c] of suite.cases.entries()) {
|
|
@@ -334,6 +345,7 @@ class RetrievalEvaluator {
|
|
|
334
345
|
fetchLimits.push(hits.fetchLimit);
|
|
335
346
|
efSearches.push(hits.indexParams.efSearch);
|
|
336
347
|
iterativeScans.push(hits.indexParams.iterativeScan);
|
|
348
|
+
thresholds.push(hits.threshold);
|
|
337
349
|
report(result, null);
|
|
338
350
|
}
|
|
339
351
|
const runStages = stages ?? ['vector'];
|
|
@@ -347,6 +359,7 @@ class RetrievalEvaluator {
|
|
|
347
359
|
fetchLimit: sortedDistinct(fetchLimits),
|
|
348
360
|
efSearch: sortedDistinct(efSearches),
|
|
349
361
|
iterativeScan: sortedDistinct(iterativeScans),
|
|
362
|
+
thresholds: distinctThresholds(thresholds),
|
|
350
363
|
};
|
|
351
364
|
return {
|
|
352
365
|
run: {
|
|
@@ -393,8 +406,18 @@ function poolDelta(baseline, current) {
|
|
|
393
406
|
short: poolValueDelta(baseline?.short ?? null, current.short),
|
|
394
407
|
};
|
|
395
408
|
}
|
|
409
|
+
function isThresholdCondition(v) {
|
|
410
|
+
return typeof v === 'object' && v !== null && 'value' in v && 'source' in v;
|
|
411
|
+
}
|
|
396
412
|
function formatCondition(value) {
|
|
397
|
-
|
|
413
|
+
if (!Array.isArray(value))
|
|
414
|
+
return String(value);
|
|
415
|
+
if (value.length > 0 && value.every(isThresholdCondition))
|
|
416
|
+
return `[${formatThresholdConditions(value)}]`;
|
|
417
|
+
return `[${value.map(v => (v === null ? 'not sent' : String(v))).join(', ')}]`;
|
|
418
|
+
}
|
|
419
|
+
function conditionKey(value) {
|
|
420
|
+
return JSON.stringify(Array.isArray(value) ? value.map(v => JSON.stringify(v)).sort() : value);
|
|
398
421
|
}
|
|
399
422
|
function corpusWarningsOf(b, cur) {
|
|
400
423
|
const warnings = [];
|
|
@@ -426,6 +449,25 @@ function corpusWarningsOf(b, cur) {
|
|
|
426
449
|
warnings.push(live('current run', cur.liveCases));
|
|
427
450
|
return warnings;
|
|
428
451
|
}
|
|
452
|
+
function vectorIndexWarningsOf(b, cur) {
|
|
453
|
+
if (!b.vectorIndex || !cur.vectorIndex) {
|
|
454
|
+
return [...(b.vectorIndex ? [] : ['baseline records no vector index']), ...(cur.vectorIndex ? [] : ['current run records no vector index'])];
|
|
455
|
+
}
|
|
456
|
+
const warnings = [];
|
|
457
|
+
if (b.vectorIndex.pgvector !== cur.vectorIndex.pgvector) {
|
|
458
|
+
warnings.push(`pgvector differs: baseline ${b.vectorIndex.pgvector ?? 'not installed'} vs current ${cur.vectorIndex.pgvector ?? 'not installed'}`);
|
|
459
|
+
}
|
|
460
|
+
if (JSON.stringify(b.vectorIndex.indexes) !== JSON.stringify(cur.vectorIndex.indexes)) {
|
|
461
|
+
warnings.push(`vector index differs: baseline ${(0, eval_vector_index_1.formatIndexes)(b.vectorIndex)} vs current ${(0, eval_vector_index_1.formatIndexes)(cur.vectorIndex)}`);
|
|
462
|
+
}
|
|
463
|
+
const settings = [['hnsw.max_scan_tuples', 'maxScanTuples'], ['hnsw.scan_mem_multiplier', 'scanMemMultiplier']];
|
|
464
|
+
for (const [name, key] of settings) {
|
|
465
|
+
if (b.vectorIndex[key] !== cur.vectorIndex[key]) {
|
|
466
|
+
warnings.push(`${name} differs: baseline ${b.vectorIndex[key] ?? 'not defined'} vs current ${cur.vectorIndex[key] ?? 'not defined'}`);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return warnings;
|
|
470
|
+
}
|
|
429
471
|
function conditionWarningsOf(baseline, current) {
|
|
430
472
|
const warnings = [];
|
|
431
473
|
const b = baseline.metadata, cur = current.metadata;
|
|
@@ -450,11 +492,12 @@ function conditionWarningsOf(baseline, current) {
|
|
|
450
492
|
warnings.push(`search condition ${key} recorded only by the current run (${formatCondition(cc[key])})`);
|
|
451
493
|
else if (!(key in cc))
|
|
452
494
|
warnings.push(`search condition ${key} recorded only by the baseline (${formatCondition(bc[key])})`);
|
|
453
|
-
else if (
|
|
495
|
+
else if (conditionKey(bc[key]) !== conditionKey(cc[key])) {
|
|
454
496
|
warnings.push(`${key} differs: baseline ${formatCondition(bc[key])} vs current ${formatCondition(cc[key])}`);
|
|
455
497
|
}
|
|
456
498
|
}
|
|
457
499
|
}
|
|
500
|
+
warnings.push(...vectorIndexWarningsOf(b, cur));
|
|
458
501
|
warnings.push(...corpusWarningsOf(b, cur));
|
|
459
502
|
if (baseline.run.rerankFailures > 0)
|
|
460
503
|
warnings.push(`baseline has ${baseline.run.rerankFailures} rerank failure(s)`);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { IDatabase, ILogger } from '../interfaces';
|
|
2
|
+
import type { CallerRow, ICallGraph, NoCallersCause } from '../interfaces/ICallGraph';
|
|
3
|
+
export declare class CallGraph implements ICallGraph {
|
|
4
|
+
private db;
|
|
5
|
+
private logger;
|
|
6
|
+
constructor(db: IDatabase, logger: ILogger);
|
|
7
|
+
getCallers(symbol: string, project: string, calleeFile?: string | null): Promise<CallerRow[]>;
|
|
8
|
+
explainNoCallers(symbol: string, project: string): Promise<NoCallersCause>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CallGraph = void 0;
|
|
4
|
+
const symbol_name_1 = require("../symbol-name");
|
|
5
|
+
const symbol_sql_1 = require("./symbol-sql");
|
|
6
|
+
const NO_CALLERS_LIST_LIMIT = 5;
|
|
7
|
+
class CallGraph {
|
|
8
|
+
db;
|
|
9
|
+
logger;
|
|
10
|
+
constructor(db, logger) {
|
|
11
|
+
this.db = db;
|
|
12
|
+
this.logger = logger;
|
|
13
|
+
}
|
|
14
|
+
async getCallers(symbol, project, calleeFile = null) {
|
|
15
|
+
const t0 = Date.now();
|
|
16
|
+
this.logger.debug('CallGraph: getCallers', { symbol, project, calleeFile });
|
|
17
|
+
const parsed = (0, symbol_name_1.parseCallerSymbol)(symbol);
|
|
18
|
+
if (!parsed)
|
|
19
|
+
throw new Error('Invalid symbol name');
|
|
20
|
+
const params = [project];
|
|
21
|
+
let nameFilter;
|
|
22
|
+
if (parsed.form === 'bare') {
|
|
23
|
+
params.push(parsed.name);
|
|
24
|
+
nameFilter = `"calleeName" = $${params.length}`;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
params.push(`${parsed.container}.${parsed.name}`);
|
|
28
|
+
const exactParam = params.length;
|
|
29
|
+
params.push(`%.${(0, symbol_name_1.escapeLike)(parsed.container)}.${(0, symbol_name_1.escapeLike)(parsed.name)}`);
|
|
30
|
+
const suffixParam = params.length;
|
|
31
|
+
nameFilter = `("calleeSymbol" = $${exactParam} OR "calleeSymbol" LIKE $${suffixParam} ESCAPE '\\')`;
|
|
32
|
+
}
|
|
33
|
+
let fileFilter = '';
|
|
34
|
+
if (calleeFile) {
|
|
35
|
+
params.push(calleeFile);
|
|
36
|
+
fileFilter = `AND "calleeFile" = $${params.length}`;
|
|
37
|
+
}
|
|
38
|
+
const rows = await this.db.query(`SELECT "fromFile", "fromSymbol", "calleeName", "calleeFile", line
|
|
39
|
+
FROM code_call
|
|
40
|
+
WHERE project = $1 AND ${nameFilter}
|
|
41
|
+
${fileFilter}
|
|
42
|
+
ORDER BY "fromFile", line
|
|
43
|
+
LIMIT 200`, params);
|
|
44
|
+
this.logger.debug('CallGraph: getCallers result', { count: rows.length, ms: Date.now() - t0 });
|
|
45
|
+
return rows;
|
|
46
|
+
}
|
|
47
|
+
async explainNoCallers(symbol, project) {
|
|
48
|
+
const t0 = Date.now();
|
|
49
|
+
const parsed = (0, symbol_name_1.parseCallerSymbol)(symbol);
|
|
50
|
+
if (!parsed)
|
|
51
|
+
throw new Error('Invalid symbol name');
|
|
52
|
+
const container = parsed.form === 'qualified' ? parsed.container : null;
|
|
53
|
+
try {
|
|
54
|
+
const calls = await this.db.query(`SELECT EXISTS (SELECT 1 FROM code_call WHERE project = $1) AS present`, [project]);
|
|
55
|
+
if (!calls[0]?.present)
|
|
56
|
+
return { kind: 'no-call-graph' };
|
|
57
|
+
const params = [project];
|
|
58
|
+
const match = (0, symbol_sql_1.symbolMatch)(parsed.name, container, params);
|
|
59
|
+
params.push(NO_CALLERS_LIST_LIMIT + 1);
|
|
60
|
+
const definitions = await this.db.query(`SELECT "filePath", line, qualified
|
|
61
|
+
FROM (
|
|
62
|
+
SELECT DISTINCT "filePath", line, ${symbol_sql_1.QUALIFIED_SQL} AS qualified${match.exactRank ? `, ${match.exactRank} AS exact_rank` : ''}
|
|
63
|
+
FROM code_symbol
|
|
64
|
+
WHERE project = $1 AND ${match.filter}
|
|
65
|
+
) AS definitions
|
|
66
|
+
ORDER BY ${match.exactRank ? 'exact_rank, ' : ''}"filePath", line, qualified COLLATE "C"
|
|
67
|
+
LIMIT $${params.length}`, params);
|
|
68
|
+
if (definitions.length > 0) {
|
|
69
|
+
return {
|
|
70
|
+
kind: 'no-recorded-calls',
|
|
71
|
+
definitions: definitions.slice(0, NO_CALLERS_LIST_LIMIT).map(({ qualified, filePath, line }) => ({ qualified, filePath, line })),
|
|
72
|
+
truncated: definitions.length > NO_CALLERS_LIST_LIMIT,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const { names, truncated } = await (0, symbol_sql_1.similarSymbolNames)(this.db, parsed.name, project, false);
|
|
76
|
+
return { kind: 'unknown-symbol', suggestions: names, truncated };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
this.logger.warn('CallGraph: explainNoCallers lookup failed', { err, symbol, project, ms: Date.now() - t0 });
|
|
80
|
+
return { kind: 'lookup-failed' };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.CallGraph = CallGraph;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Config } from '../config';
|
|
2
|
+
import type { IDatabase, IEmbedder, ILogger } from '../interfaces';
|
|
3
|
+
import type { IChunkSearch, NeighborResult, NoResultsCause, SearchResult, StagedSearchResult } from '../interfaces/IChunkSearch';
|
|
4
|
+
import type { IndexIdentityGuard } from './IndexIdentityGuard';
|
|
5
|
+
export declare class ChunkSearch implements IChunkSearch {
|
|
6
|
+
private db;
|
|
7
|
+
private embedder;
|
|
8
|
+
private identityGuard;
|
|
9
|
+
private config;
|
|
10
|
+
private logger;
|
|
11
|
+
private readonly gate;
|
|
12
|
+
private readonly fetcher;
|
|
13
|
+
private readonly rerankStage;
|
|
14
|
+
constructor(db: IDatabase, embedder: IEmbedder, identityGuard: IndexIdentityGuard, config: Config, logger: ILogger);
|
|
15
|
+
prepareQuery(raw: string): Promise<{
|
|
16
|
+
query: string;
|
|
17
|
+
translated: boolean;
|
|
18
|
+
}>;
|
|
19
|
+
searchChunks(query: string, limit: number, project: string | null, threshold?: number, rerankOverride?: boolean, perFile?: number): Promise<SearchResult[]>;
|
|
20
|
+
searchChunksWithStages(query: string, limit: number, project: string | null, threshold?: number, rerankOverride?: boolean, perFile?: number): Promise<StagedSearchResult>;
|
|
21
|
+
fetchNeighbors(results: SearchResult[]): Promise<NeighborResult[]>;
|
|
22
|
+
explainNoResults(project: string | null): Promise<NoResultsCause>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ChunkSearch = void 0;
|
|
4
|
+
const result_shaping_1 = require("./result-shaping");
|
|
5
|
+
const search_plan_1 = require("./chunks/search-plan");
|
|
6
|
+
const IterativeScanGate_1 = require("./chunks/IterativeScanGate");
|
|
7
|
+
const CandidateFetcher_1 = require("./chunks/CandidateFetcher");
|
|
8
|
+
const RerankStage_1 = require("./chunks/RerankStage");
|
|
9
|
+
const search_metrics_1 = require("./chunks/search-metrics");
|
|
10
|
+
class ChunkSearch {
|
|
11
|
+
db;
|
|
12
|
+
embedder;
|
|
13
|
+
identityGuard;
|
|
14
|
+
config;
|
|
15
|
+
logger;
|
|
16
|
+
gate;
|
|
17
|
+
fetcher;
|
|
18
|
+
rerankStage;
|
|
19
|
+
constructor(db, embedder, identityGuard, config, logger) {
|
|
20
|
+
this.db = db;
|
|
21
|
+
this.embedder = embedder;
|
|
22
|
+
this.identityGuard = identityGuard;
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.logger = logger;
|
|
25
|
+
this.gate = new IterativeScanGate_1.IterativeScanGate(db, logger);
|
|
26
|
+
this.fetcher = new CandidateFetcher_1.CandidateFetcher(db, this.gate);
|
|
27
|
+
this.rerankStage = new RerankStage_1.RerankStage(embedder, logger);
|
|
28
|
+
}
|
|
29
|
+
async prepareQuery(raw) {
|
|
30
|
+
return { query: raw, translated: false };
|
|
31
|
+
}
|
|
32
|
+
async searchChunks(query, limit, project, threshold, rerankOverride, perFile = result_shaping_1.DEFAULT_PER_FILE) {
|
|
33
|
+
const { stages } = await this.searchChunksWithStages(query, limit, project, threshold, rerankOverride, perFile);
|
|
34
|
+
return stages.reranked ?? stages.vector;
|
|
35
|
+
}
|
|
36
|
+
async searchChunksWithStages(query, limit, project, threshold, rerankOverride, perFile = result_shaping_1.DEFAULT_PER_FILE) {
|
|
37
|
+
const t0 = Date.now();
|
|
38
|
+
await this.identityGuard.assertIndexMatches();
|
|
39
|
+
const plan = (0, search_plan_1.planSearch)({ limit, perFile, threshold, rerankOverride, rerankAvailable: !!this.embedder.rerank }, this.config);
|
|
40
|
+
this.logger.debug('ChunkSearch: searchChunks', {
|
|
41
|
+
query: query.slice(0, 120), limit, fetchLimit: plan.fetchLimit, project, threshold: plan.threshold.value,
|
|
42
|
+
});
|
|
43
|
+
const tEmbed0 = Date.now();
|
|
44
|
+
const embedding = await this.embedder.embed(query, 'query');
|
|
45
|
+
const embedMs = Date.now() - tEmbed0;
|
|
46
|
+
const pool = await this.fetcher.fetch(embedding, plan, project);
|
|
47
|
+
this.logger.debug('ChunkSearch: searchChunks candidates', {
|
|
48
|
+
count: pool.candidates.length,
|
|
49
|
+
scores: pool.candidates.map(r => r.score),
|
|
50
|
+
files: pool.candidates.map(r => r.filePath),
|
|
51
|
+
ms: Date.now() - t0,
|
|
52
|
+
});
|
|
53
|
+
const vector = (0, result_shaping_1.limitPerFile)(pool.candidates, limit, perFile);
|
|
54
|
+
const rerank = plan.reranking ? await this.rerankStage.run(query, pool.candidates, vector, plan, t0) : undefined;
|
|
55
|
+
this.logger.info('search metrics', (0, search_metrics_1.searchMetrics)(query, {
|
|
56
|
+
fetchLimit: plan.fetchLimit, candidates: pool.candidates, results: rerank?.reranked ?? vector, poolSaturated: pool.poolSaturated, indexParams: pool.indexParams, threshold: plan.threshold,
|
|
57
|
+
embedMs, dbMs: pool.dbMs, rerankMs: rerank?.rerankMs ?? 0, totalMs: Date.now() - t0,
|
|
58
|
+
}));
|
|
59
|
+
return {
|
|
60
|
+
pool: pool.candidates,
|
|
61
|
+
stages: rerank ? { vector, reranked: rerank.reranked } : { vector },
|
|
62
|
+
rerankFailed: rerank?.rerankFailed ?? false,
|
|
63
|
+
fetchLimit: plan.fetchLimit,
|
|
64
|
+
indexParams: pool.indexParams,
|
|
65
|
+
threshold: plan.threshold,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async fetchNeighbors(results) {
|
|
69
|
+
const t0 = Date.now();
|
|
70
|
+
if (results.length === 0)
|
|
71
|
+
return [];
|
|
72
|
+
this.logger.debug('ChunkSearch: fetchNeighbors', { count: results.length });
|
|
73
|
+
const values = results.map((_, i) => `(${i}, $${i * 2 + 1}, $${i * 2 + 2}::int)`).join(',');
|
|
74
|
+
const params = results.flatMap(r => [r.filePath, r.startLine]);
|
|
75
|
+
const rows = await this.db.query(`SELECT r.idx,
|
|
76
|
+
prev."serviceName" AS prev_sn, prev."filePath" AS prev_fp, prev."startLine" AS prev_sl, prev.content AS prev_c, prev."ownerPath" AS prev_op,
|
|
77
|
+
next."serviceName" AS next_sn, next."filePath" AS next_fp, next."startLine" AS next_sl, next.content AS next_c, next."ownerPath" AS next_op
|
|
78
|
+
FROM (VALUES ${values}) r(idx, fp, sl)
|
|
79
|
+
LEFT JOIN LATERAL (
|
|
80
|
+
SELECT "serviceName", "filePath", "startLine", content, "ownerPath"
|
|
81
|
+
FROM code_chunk WHERE "filePath" = r.fp AND "startLine" < r.sl
|
|
82
|
+
ORDER BY "startLine" DESC LIMIT 1
|
|
83
|
+
) prev ON true
|
|
84
|
+
LEFT JOIN LATERAL (
|
|
85
|
+
SELECT "serviceName", "filePath", "startLine", content, "ownerPath"
|
|
86
|
+
FROM code_chunk WHERE "filePath" = r.fp AND "startLine" > r.sl
|
|
87
|
+
ORDER BY "startLine" ASC LIMIT 1
|
|
88
|
+
) next ON true
|
|
89
|
+
ORDER BY r.idx`, params);
|
|
90
|
+
const neighbors = rows.map((row, i) => ({
|
|
91
|
+
result: results[i],
|
|
92
|
+
prevChunk: row.prev_fp ? { serviceName: row.prev_sn, filePath: row.prev_fp, startLine: row.prev_sl, content: row.prev_c, ownerPath: row.prev_op ?? null } : null,
|
|
93
|
+
nextChunk: row.next_fp ? { serviceName: row.next_sn, filePath: row.next_fp, startLine: row.next_sl, content: row.next_c, ownerPath: row.next_op ?? null } : null,
|
|
94
|
+
}));
|
|
95
|
+
this.logger.debug('ChunkSearch: fetchNeighbors result', {
|
|
96
|
+
withPrev: neighbors.filter(n => n.prevChunk).length,
|
|
97
|
+
withNext: neighbors.filter(n => n.nextChunk).length,
|
|
98
|
+
ms: Date.now() - t0,
|
|
99
|
+
});
|
|
100
|
+
return neighbors;
|
|
101
|
+
}
|
|
102
|
+
async explainNoResults(project) {
|
|
103
|
+
try {
|
|
104
|
+
const rows = await this.db.query(project
|
|
105
|
+
? `SELECT EXISTS (SELECT 1 FROM code_chunk WHERE "serviceName" = $1) AS present`
|
|
106
|
+
: `SELECT EXISTS (SELECT 1 FROM code_chunk) AS present`, project ? [project] : []);
|
|
107
|
+
return rows[0]?.present ? 'no-candidates' : 'empty-scope';
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
this.logger.warn('ChunkSearch: explainNoResults lookup failed', { err, project });
|
|
111
|
+
return 'lookup-failed';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.ChunkSearch = ChunkSearch;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Config } from '../config';
|
|
2
|
+
import type { IDatabase, ILogger } from '../interfaces';
|
|
3
|
+
import type { DependencyRow, GraphContext, IDependencyGraph } from '../interfaces/IDependencyGraph';
|
|
4
|
+
import type { SearchResult } from '../interfaces/IChunkSearch';
|
|
5
|
+
import type { ProjectRoots } from '../workspace/ProjectRoots';
|
|
6
|
+
export declare class DependencyGraph implements IDependencyGraph {
|
|
7
|
+
private db;
|
|
8
|
+
private roots;
|
|
9
|
+
private config;
|
|
10
|
+
private logger;
|
|
11
|
+
constructor(db: IDatabase, roots: ProjectRoots, config: Config, logger: ILogger);
|
|
12
|
+
getProjectEdges(): Promise<{
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
}[]>;
|
|
16
|
+
fetchGraphContext(results: SearchResult[]): Promise<Map<string, GraphContext>>;
|
|
17
|
+
getDependencies(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
18
|
+
getDependents(filePath: string, symbol?: string | null, transitive?: boolean): Promise<DependencyRow[]>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DependencyGraph = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const dependency_traversal_1 = require("../dependency-traversal");
|
|
39
|
+
const index_paths_1 = require("./index-paths");
|
|
40
|
+
class DependencyGraph {
|
|
41
|
+
db;
|
|
42
|
+
roots;
|
|
43
|
+
config;
|
|
44
|
+
logger;
|
|
45
|
+
constructor(db, roots, config, logger) {
|
|
46
|
+
this.db = db;
|
|
47
|
+
this.roots = roots;
|
|
48
|
+
this.config = config;
|
|
49
|
+
this.logger = logger;
|
|
50
|
+
}
|
|
51
|
+
async getProjectEdges() {
|
|
52
|
+
const rows = await this.db.query(`SELECT DISTINCT project, "toFile" FROM code_dependency`);
|
|
53
|
+
const byRelPath = [...this.roots.entries()]
|
|
54
|
+
.map(([name, abs]) => ({ name, rel: path.relative(this.config.workspaceRoot, abs) }))
|
|
55
|
+
.sort((a, b) => b.rel.length - a.rel.length);
|
|
56
|
+
const ownerOf = (toFile) => {
|
|
57
|
+
for (const { name, rel } of byRelPath) {
|
|
58
|
+
if (toFile === rel || toFile.startsWith(rel + path.sep) || toFile.startsWith(rel + '/'))
|
|
59
|
+
return name;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
const seen = new Set();
|
|
64
|
+
const edges = [];
|
|
65
|
+
for (const { project, toFile } of rows) {
|
|
66
|
+
const to = ownerOf(toFile);
|
|
67
|
+
if (!to || to === project)
|
|
68
|
+
continue;
|
|
69
|
+
const key = project + ' ' + to;
|
|
70
|
+
if (seen.has(key))
|
|
71
|
+
continue;
|
|
72
|
+
seen.add(key);
|
|
73
|
+
edges.push({ from: project, to });
|
|
74
|
+
}
|
|
75
|
+
return edges;
|
|
76
|
+
}
|
|
77
|
+
async fetchGraphContext(results) {
|
|
78
|
+
const t0 = Date.now();
|
|
79
|
+
const files = [...new Set(results.map(r => r.filePath))];
|
|
80
|
+
this.logger.debug('DependencyGraph: fetchGraphContext', { files: files.length });
|
|
81
|
+
if (files.length === 0)
|
|
82
|
+
return new Map();
|
|
83
|
+
const proportionalLimit = Math.max(2000, files.length * 100);
|
|
84
|
+
const values = files.map((_, i) => `($${i + 1}::text)`).join(', ');
|
|
85
|
+
const rows = await this.db.query(`SELECT r.fp AS "filePath", 'import' AS direction, d."toFile" AS related
|
|
86
|
+
FROM (VALUES ${values}) AS r(fp)
|
|
87
|
+
JOIN code_dependency d ON d."fromFile" = r.fp
|
|
88
|
+
WHERE NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('d."toFile"')}
|
|
89
|
+
|
|
90
|
+
UNION ALL
|
|
91
|
+
|
|
92
|
+
SELECT r.fp AS "filePath", 'usedby' AS direction, d."fromFile" AS related
|
|
93
|
+
FROM (VALUES ${values}) AS r(fp)
|
|
94
|
+
JOIN code_dependency d ON d."toFile" = r.fp
|
|
95
|
+
WHERE NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('d."fromFile"')}
|
|
96
|
+
|
|
97
|
+
LIMIT ${proportionalLimit}`, files);
|
|
98
|
+
const ctx = new Map();
|
|
99
|
+
for (const filePath of files)
|
|
100
|
+
ctx.set(filePath, { imports: new Set(), usedBy: new Set() });
|
|
101
|
+
for (const row of rows) {
|
|
102
|
+
const g = ctx.get(row.filePath);
|
|
103
|
+
if (row.direction === 'import')
|
|
104
|
+
g.imports.add(row.related);
|
|
105
|
+
else
|
|
106
|
+
g.usedBy.add(row.related);
|
|
107
|
+
}
|
|
108
|
+
this.logger.debug('DependencyGraph: fetchGraphContext result', { files: files.length, rows: rows.length, ms: Date.now() - t0 });
|
|
109
|
+
const result = new Map();
|
|
110
|
+
for (const [fp, g] of ctx)
|
|
111
|
+
result.set(fp, {
|
|
112
|
+
imports: [...g.imports].slice(0, 50),
|
|
113
|
+
usedBy: [...g.usedBy].slice(0, 50),
|
|
114
|
+
});
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
async getDependencies(filePath, symbol = null, transitive = false) {
|
|
118
|
+
const t0 = Date.now();
|
|
119
|
+
this.logger.debug('DependencyGraph: getDependencies', { filePath, symbol, transitive });
|
|
120
|
+
const params = [filePath];
|
|
121
|
+
let symbolFilter = '';
|
|
122
|
+
if (symbol) {
|
|
123
|
+
params.push(symbol);
|
|
124
|
+
symbolFilter = `AND "toSymbol" = $${params.length}`;
|
|
125
|
+
}
|
|
126
|
+
const direct = await this.db.query(`SELECT "fromFile", "fromSymbol", "toFile", "toSymbol", "depType", min("id"::text) AS "id"
|
|
127
|
+
FROM code_dependency
|
|
128
|
+
WHERE "fromFile" = $1 AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"toFile"')}
|
|
129
|
+
${symbolFilter}
|
|
130
|
+
GROUP BY "fromFile", "fromSymbol", "toFile", "toSymbol", "depType"
|
|
131
|
+
ORDER BY "toFile", min("id"::text)`, params);
|
|
132
|
+
if (!transitive) {
|
|
133
|
+
this.logger.debug('DependencyGraph: getDependencies result', { direct: direct.length, ms: Date.now() - t0 });
|
|
134
|
+
return direct;
|
|
135
|
+
}
|
|
136
|
+
const neighbours = async (frontier) => {
|
|
137
|
+
const rows = await this.db.query(`SELECT DISTINCT "fromFile", "toFile" FROM code_dependency
|
|
138
|
+
WHERE "fromFile" = ANY($1::text[]) AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"toFile"')}`, [frontier]);
|
|
139
|
+
return rows.map(r => ({ from: r.fromFile, to: r.toFile }));
|
|
140
|
+
};
|
|
141
|
+
const indirect = (await (0, dependency_traversal_1.traverse)(filePath, neighbours)).map(r => ({
|
|
142
|
+
toFile: r.file, depth: r.depth, via: r.via,
|
|
143
|
+
fromFile: null, fromSymbol: null, toSymbol: null, depType: null,
|
|
144
|
+
}));
|
|
145
|
+
this.logger.debug('DependencyGraph: getDependencies result', { direct: direct.length, indirect: indirect.length, ms: Date.now() - t0 });
|
|
146
|
+
return [...direct, ...indirect];
|
|
147
|
+
}
|
|
148
|
+
async getDependents(filePath, symbol = null, transitive = false) {
|
|
149
|
+
const t0 = Date.now();
|
|
150
|
+
this.logger.debug('DependencyGraph: getDependents', { filePath, symbol, transitive });
|
|
151
|
+
const params = [filePath];
|
|
152
|
+
let symbolFilter = '';
|
|
153
|
+
if (symbol) {
|
|
154
|
+
params.push(symbol);
|
|
155
|
+
symbolFilter = `AND "toSymbol" = $${params.length}`;
|
|
156
|
+
}
|
|
157
|
+
const direct = await this.db.query(`SELECT "fromFile", "fromSymbol", "toFile", "toSymbol", "depType", min("id"::text) AS "id"
|
|
158
|
+
FROM code_dependency
|
|
159
|
+
WHERE "toFile" = $1 AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"fromFile"')}
|
|
160
|
+
${symbolFilter}
|
|
161
|
+
GROUP BY "fromFile", "fromSymbol", "toFile", "toSymbol", "depType"
|
|
162
|
+
ORDER BY "fromFile", min("id"::text)`, params);
|
|
163
|
+
if (!transitive) {
|
|
164
|
+
this.logger.debug('DependencyGraph: getDependents result', { direct: direct.length, ms: Date.now() - t0 });
|
|
165
|
+
return direct;
|
|
166
|
+
}
|
|
167
|
+
const neighbours = async (frontier) => {
|
|
168
|
+
const rows = await this.db.query(`SELECT DISTINCT "fromFile", "toFile" FROM code_dependency
|
|
169
|
+
WHERE "toFile" = ANY($1::text[]) AND NOT ${(0, index_paths_1.OUTSIDE_WORKSPACE_VALUE)('"fromFile"')}`, [frontier]);
|
|
170
|
+
return rows.map(r => ({ from: r.toFile, to: r.fromFile }));
|
|
171
|
+
};
|
|
172
|
+
const indirect = (await (0, dependency_traversal_1.traverse)(filePath, neighbours)).map(r => ({
|
|
173
|
+
fromFile: r.file, depth: r.depth, via: r.via,
|
|
174
|
+
toFile: null, fromSymbol: null, toSymbol: null, depType: null,
|
|
175
|
+
}));
|
|
176
|
+
this.logger.debug('DependencyGraph: getDependents result', { direct: direct.length, indirect: indirect.length, ms: Date.now() - t0 });
|
|
177
|
+
return [...direct, ...indirect];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.DependencyGraph = DependencyGraph;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Config } from '../config';
|
|
2
|
+
import type { IDatabase, IEmbedder } from '../interfaces';
|
|
3
|
+
import type { IIndexIdentityGuard } from '../interfaces/IIndexIdentityGuard';
|
|
4
|
+
export declare class IndexIdentityGuard implements IIndexIdentityGuard {
|
|
5
|
+
private db;
|
|
6
|
+
private embedder;
|
|
7
|
+
private config;
|
|
8
|
+
constructor(db: IDatabase, embedder: IEmbedder, config: Config);
|
|
9
|
+
assertIndexMatches(): Promise<void>;
|
|
10
|
+
private compareIndexed;
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.IndexIdentityGuard = void 0;
|
|
4
|
+
const legacy_ollama_index_1 = require("../services/legacy-ollama-index");
|
|
5
|
+
class IndexIdentityGuard {
|
|
6
|
+
db;
|
|
7
|
+
embedder;
|
|
8
|
+
config;
|
|
9
|
+
constructor(db, embedder, config) {
|
|
10
|
+
this.db = db;
|
|
11
|
+
this.embedder = embedder;
|
|
12
|
+
this.config = config;
|
|
13
|
+
}
|
|
14
|
+
async assertIndexMatches() {
|
|
15
|
+
const meta = await this.db.readIndexMeta();
|
|
16
|
+
if (meta) {
|
|
17
|
+
this.compareIndexed(meta.provider, meta.model, meta.dimensions);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (await (0, legacy_ollama_index_1.hasIndexedChunks)(this.db)) {
|
|
21
|
+
this.compareIndexed(legacy_ollama_index_1.LEGACY_OLLAMA_IDENTITY.provider, legacy_ollama_index_1.LEGACY_OLLAMA_IDENTITY.model, null);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
compareIndexed(indexedProvider, indexedModel, indexedDimensions) {
|
|
25
|
+
const { provider, model } = this.config.embedding;
|
|
26
|
+
if (indexedProvider !== provider || indexedModel !== model) {
|
|
27
|
+
throw new Error(`Index was built with ${indexedProvider}/${indexedModel} but the config selects ${provider}/${model}. ` +
|
|
28
|
+
`Run \`rag-index --reindex\` to rebuild the index for the new model.`);
|
|
29
|
+
}
|
|
30
|
+
const dim = this.embedder.dimensions;
|
|
31
|
+
if (indexedDimensions != null && dim > 0 && dim !== indexedDimensions) {
|
|
32
|
+
throw new Error(`Index was built at ${indexedDimensions} dimensions but ${provider}/${model} now resolves to ${dim}. ` +
|
|
33
|
+
`Run \`rag-index --reindex\` to rebuild the index at the new dimension.`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.IndexIdentityGuard = IndexIdentityGuard;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IDatabase } from '../interfaces';
|
|
2
|
+
import type { IProjectCatalog, ProjectRow } from '../interfaces/IProjectCatalog';
|
|
3
|
+
import { type WorkspaceFs } from '../workspace/fs-access';
|
|
4
|
+
import type { ProjectRoots } from '../workspace/ProjectRoots';
|
|
5
|
+
export declare class ProjectCatalog implements IProjectCatalog {
|
|
6
|
+
private db;
|
|
7
|
+
private roots;
|
|
8
|
+
private fs;
|
|
9
|
+
constructor(db: IDatabase, roots: ProjectRoots, fs: WorkspaceFs);
|
|
10
|
+
listProjects(): Promise<ProjectRow[]>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProjectCatalog = void 0;
|
|
4
|
+
const file_utils_1 = require("../file-utils");
|
|
5
|
+
const fs_access_1 = require("../workspace/fs-access");
|
|
6
|
+
const EMPTY_META = { description: null, stack: null, notes: null, relations: null };
|
|
7
|
+
class ProjectCatalog {
|
|
8
|
+
db;
|
|
9
|
+
roots;
|
|
10
|
+
fs;
|
|
11
|
+
constructor(db, roots, fs) {
|
|
12
|
+
this.db = db;
|
|
13
|
+
this.roots = roots;
|
|
14
|
+
this.fs = fs;
|
|
15
|
+
}
|
|
16
|
+
async listProjects() {
|
|
17
|
+
const rows = await this.db.query(`SELECT "serviceName", COUNT(*)::int AS chunks
|
|
18
|
+
FROM code_chunk
|
|
19
|
+
GROUP BY "serviceName"
|
|
20
|
+
ORDER BY "serviceName"`);
|
|
21
|
+
return Promise.all(rows.map(async (r) => {
|
|
22
|
+
const projectRoot = this.roots.resolveProjectRoot(r.serviceName);
|
|
23
|
+
const meta = (0, file_utils_1.readRagMeta)(projectRoot);
|
|
24
|
+
let rootError = null;
|
|
25
|
+
try {
|
|
26
|
+
await this.fs.stat(projectRoot);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
rootError = (0, fs_access_1.errorCode)(err);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
...r, path: projectRoot, relPath: this.roots.toRel(projectRoot), rootError,
|
|
33
|
+
...(typeof meta === 'object' ? meta : EMPTY_META),
|
|
34
|
+
};
|
|
35
|
+
}));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.ProjectCatalog = ProjectCatalog;
|