ragcode-context-engine 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +366 -0
- package/README.zh-CN.md +363 -0
- package/dist/src/cli/configure/app.d.ts +6 -0
- package/dist/src/cli/configure/app.js +81 -0
- package/dist/src/cli/configure/run.d.ts +5 -0
- package/dist/src/cli/configure/run.js +85 -0
- package/dist/src/cli/configure/state.d.ts +42 -0
- package/dist/src/cli/configure/state.js +174 -0
- package/dist/src/cli/configure.d.ts +31 -0
- package/dist/src/cli/configure.js +101 -0
- package/dist/src/cli/index.d.ts +2 -0
- package/dist/src/cli/index.js +503 -0
- package/dist/src/cli/tui/index-progress.d.ts +12 -0
- package/dist/src/cli/tui/index-progress.js +49 -0
- package/dist/src/cli/tui/watch-status.d.ts +10 -0
- package/dist/src/cli/tui/watch-status.js +27 -0
- package/dist/src/cli/update.d.ts +18 -0
- package/dist/src/cli/update.js +111 -0
- package/dist/src/config/dotenv.d.ts +1 -0
- package/dist/src/config/dotenv.js +14 -0
- package/dist/src/config/graph-runtime.d.ts +13 -0
- package/dist/src/config/graph-runtime.js +29 -0
- package/dist/src/config/runtime-config.d.ts +87 -0
- package/dist/src/config/runtime-config.js +215 -0
- package/dist/src/config/semantic-runtime.d.ts +24 -0
- package/dist/src/config/semantic-runtime.js +89 -0
- package/dist/src/context/context-builder.d.ts +20 -0
- package/dist/src/context/context-builder.js +277 -0
- package/dist/src/context/expansion-policy.d.ts +6 -0
- package/dist/src/context/expansion-policy.js +49 -0
- package/dist/src/context/skeletonizer.d.ts +2 -0
- package/dist/src/context/skeletonizer.js +79 -0
- package/dist/src/context/snippet-renderer.d.ts +2 -0
- package/dist/src/context/snippet-renderer.js +67 -0
- package/dist/src/core/contracts.d.ts +74 -0
- package/dist/src/core/contracts.js +1 -0
- package/dist/src/core/engine.d.ts +64 -0
- package/dist/src/core/engine.js +442 -0
- package/dist/src/core/types.d.ts +490 -0
- package/dist/src/core/types.js +1 -0
- package/dist/src/diagnostics/doctor.d.ts +66 -0
- package/dist/src/diagnostics/doctor.js +193 -0
- package/dist/src/diagnostics/embedding-test.d.ts +24 -0
- package/dist/src/diagnostics/embedding-test.js +83 -0
- package/dist/src/graph/diff-files.d.ts +1 -0
- package/dist/src/graph/diff-files.js +14 -0
- package/dist/src/graph/impact-report.d.ts +10 -0
- package/dist/src/graph/impact-report.js +173 -0
- package/dist/src/graph/in-memory-graph-store.d.ts +36 -0
- package/dist/src/graph/in-memory-graph-store.js +395 -0
- package/dist/src/graph/owner-ranking.d.ts +2 -0
- package/dist/src/graph/owner-ranking.js +41 -0
- package/dist/src/graph/sqlite-graph-store.d.ts +51 -0
- package/dist/src/graph/sqlite-graph-store.js +724 -0
- package/dist/src/graph/sqlite-statements.d.ts +36 -0
- package/dist/src/graph/sqlite-statements.js +105 -0
- package/dist/src/graph/target-matcher.d.ts +13 -0
- package/dist/src/graph/target-matcher.js +64 -0
- package/dist/src/index.d.ts +32 -0
- package/dist/src/index.js +32 -0
- package/dist/src/indexing/analyzers/fallback-analyzer.d.ts +6 -0
- package/dist/src/indexing/analyzers/fallback-analyzer.js +45 -0
- package/dist/src/indexing/analyzers/go-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/go-treesitter-analyzer.js +87 -0
- package/dist/src/indexing/analyzers/java-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/java-treesitter-analyzer.js +88 -0
- package/dist/src/indexing/analyzers/python-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/python-treesitter-analyzer.js +96 -0
- package/dist/src/indexing/analyzers/registry.d.ts +5 -0
- package/dist/src/indexing/analyzers/registry.js +23 -0
- package/dist/src/indexing/analyzers/rust-treesitter-analyzer.d.ts +2 -0
- package/dist/src/indexing/analyzers/rust-treesitter-analyzer.js +96 -0
- package/dist/src/indexing/analyzers/tree-sitter-base.d.ts +30 -0
- package/dist/src/indexing/analyzers/tree-sitter-base.js +163 -0
- package/dist/src/indexing/analyzers/types.d.ts +17 -0
- package/dist/src/indexing/analyzers/types.js +1 -0
- package/dist/src/indexing/analyzers/typescript-analyzer.d.ts +5 -0
- package/dist/src/indexing/analyzers/typescript-analyzer.js +199 -0
- package/dist/src/indexing/ast-analyzer.d.ts +11 -0
- package/dist/src/indexing/ast-analyzer.js +11 -0
- package/dist/src/indexing/chunker.d.ts +11 -0
- package/dist/src/indexing/chunker.js +157 -0
- package/dist/src/indexing/ignore-policy.d.ts +6 -0
- package/dist/src/indexing/ignore-policy.js +40 -0
- package/dist/src/indexing/indexer.d.ts +13 -0
- package/dist/src/indexing/indexer.js +189 -0
- package/dist/src/indexing/language.d.ts +3 -0
- package/dist/src/indexing/language.js +24 -0
- package/dist/src/indexing/scanner.d.ts +13 -0
- package/dist/src/indexing/scanner.js +87 -0
- package/dist/src/lsp/definition-resolver.d.ts +6 -0
- package/dist/src/lsp/definition-resolver.js +60 -0
- package/dist/src/lsp/typescript-language-service.d.ts +21 -0
- package/dist/src/lsp/typescript-language-service.js +82 -0
- package/dist/src/mcp/server.d.ts +11 -0
- package/dist/src/mcp/server.js +64 -0
- package/dist/src/mcp/tools.d.ts +266 -0
- package/dist/src/mcp/tools.js +309 -0
- package/dist/src/project/project-identity.d.ts +2 -0
- package/dist/src/project/project-identity.js +24 -0
- package/dist/src/project/project-registry.d.ts +12 -0
- package/dist/src/project/project-registry.js +49 -0
- package/dist/src/project/workspace-resolver.d.ts +20 -0
- package/dist/src/project/workspace-resolver.js +62 -0
- package/dist/src/retrieval/graph-reranker.d.ts +11 -0
- package/dist/src/retrieval/graph-reranker.js +0 -0
- package/dist/src/retrieval/hybrid-retriever.d.ts +31 -0
- package/dist/src/retrieval/hybrid-retriever.js +111 -0
- package/dist/src/retrieval/path-classification.d.ts +6 -0
- package/dist/src/retrieval/path-classification.js +22 -0
- package/dist/src/retrieval/query-matching.d.ts +22 -0
- package/dist/src/retrieval/query-matching.js +166 -0
- package/dist/src/retrieval/query-planner.d.ts +5 -0
- package/dist/src/retrieval/query-planner.js +77 -0
- package/dist/src/retrieval/ranking-signals.d.ts +19 -0
- package/dist/src/retrieval/ranking-signals.js +97 -0
- package/dist/src/retrieval/topology-distance.d.ts +21 -0
- package/dist/src/retrieval/topology-distance.js +116 -0
- package/dist/src/reuse/reuse-detector.d.ts +12 -0
- package/dist/src/reuse/reuse-detector.js +564 -0
- package/dist/src/semantic/deterministic-embedding.d.ts +7 -0
- package/dist/src/semantic/deterministic-embedding.js +31 -0
- package/dist/src/semantic/in-memory-semantic-store.d.ts +11 -0
- package/dist/src/semantic/in-memory-semantic-store.js +65 -0
- package/dist/src/semantic/lance-semantic-store.d.ts +131 -0
- package/dist/src/semantic/lance-semantic-store.js +623 -0
- package/dist/src/semantic/openai-compatible-embedding.d.ts +19 -0
- package/dist/src/semantic/openai-compatible-embedding.js +75 -0
- package/dist/src/service/service-identity.d.ts +13 -0
- package/dist/src/service/service-identity.js +48 -0
- package/dist/src/service/service-manager.d.ts +29 -0
- package/dist/src/service/service-manager.js +231 -0
- package/dist/src/service/service-templates.d.ts +22 -0
- package/dist/src/service/service-templates.js +101 -0
- package/dist/src/subgraph/impact-explainer.d.ts +2 -0
- package/dist/src/subgraph/impact-explainer.js +54 -0
- package/dist/src/subgraph/node-expander.d.ts +13 -0
- package/dist/src/subgraph/node-expander.js +139 -0
- package/dist/src/subgraph/output-preset.d.ts +3 -0
- package/dist/src/subgraph/output-preset.js +102 -0
- package/dist/src/subgraph/subgraph-builder.d.ts +17 -0
- package/dist/src/subgraph/subgraph-builder.js +688 -0
- package/dist/src/topology/export-index.d.ts +7 -0
- package/dist/src/topology/export-index.js +14 -0
- package/dist/src/topology/framework-topology.d.ts +3 -0
- package/dist/src/topology/framework-topology.js +460 -0
- package/dist/src/topology/import-resolver.d.ts +2 -0
- package/dist/src/topology/import-resolver.js +29 -0
- package/dist/src/topology/orm-topology.d.ts +3 -0
- package/dist/src/topology/orm-topology.js +200 -0
- package/dist/src/topology/runtime-topology.d.ts +3 -0
- package/dist/src/topology/runtime-topology.js +204 -0
- package/dist/src/topology/symbol-resolver.d.ts +6 -0
- package/dist/src/topology/symbol-resolver.js +74 -0
- package/dist/src/topology/test-topology.d.ts +2 -0
- package/dist/src/topology/test-topology.js +82 -0
- package/dist/src/utils/hash.d.ts +2 -0
- package/dist/src/utils/hash.js +7 -0
- package/dist/src/utils/path.d.ts +2 -0
- package/dist/src/utils/path.js +7 -0
- package/dist/src/watch/event-journal.d.ts +17 -0
- package/dist/src/watch/event-journal.js +81 -0
- package/dist/src/watch/file-event-coalescer.d.ts +9 -0
- package/dist/src/watch/file-event-coalescer.js +39 -0
- package/dist/src/watch/index-scheduler.d.ts +52 -0
- package/dist/src/watch/index-scheduler.js +190 -0
- package/dist/src/watch/watch-daemon.d.ts +73 -0
- package/dist/src/watch/watch-daemon.js +368 -0
- package/dist/src/watch/watcher-liveness.d.ts +47 -0
- package/dist/src/watch/watcher-liveness.js +168 -0
- package/dist/src/web/server.d.ts +1 -0
- package/dist/src/web/server.js +375 -0
- package/package.json +94 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ContextBuilder } from "../context/context-builder.js";
|
|
3
|
+
import { createGraphRuntimeFromEnv } from "../config/graph-runtime.js";
|
|
4
|
+
import { createSemanticRuntimeFromEnv } from "../config/semantic-runtime.js";
|
|
5
|
+
import { InMemoryGraphStore } from "../graph/in-memory-graph-store.js";
|
|
6
|
+
import { RepoIndexer } from "../indexing/indexer.js";
|
|
7
|
+
import { scanRepo } from "../indexing/scanner.js";
|
|
8
|
+
import { ProjectRegistry } from "../project/project-registry.js";
|
|
9
|
+
import { WorkspaceResolver } from "../project/workspace-resolver.js";
|
|
10
|
+
import { hasSemanticParticipation, HybridRetriever } from "../retrieval/hybrid-retriever.js";
|
|
11
|
+
import { DeterministicEmbeddingProvider } from "../semantic/deterministic-embedding.js";
|
|
12
|
+
import { InMemorySemanticStore } from "../semantic/in-memory-semantic-store.js";
|
|
13
|
+
import { SubgraphBuilder } from "../subgraph/subgraph-builder.js";
|
|
14
|
+
import { normalizeUserPath } from "../utils/path.js";
|
|
15
|
+
import { buildReuseCandidateReport } from "../reuse/reuse-detector.js";
|
|
16
|
+
export class RagCodeEngine {
|
|
17
|
+
graphStore;
|
|
18
|
+
semanticStore;
|
|
19
|
+
embeddingProvider;
|
|
20
|
+
projectRegistry = new ProjectRegistry();
|
|
21
|
+
workspaceResolver;
|
|
22
|
+
contextBuilder = new ContextBuilder();
|
|
23
|
+
indexedAtByRepo = new Map();
|
|
24
|
+
cwd;
|
|
25
|
+
workspaceRoots;
|
|
26
|
+
hydratedRoots = new Set();
|
|
27
|
+
hydratedAllProjects = false;
|
|
28
|
+
constructor(options = {}) {
|
|
29
|
+
const env = options.env ?? process.env;
|
|
30
|
+
this.cwd = path.resolve(options.cwd ?? process.cwd());
|
|
31
|
+
this.workspaceRoots = (options.workspaceRoots ?? []).map((root) => path.resolve(root));
|
|
32
|
+
const graphRuntime = options.graphStore ? undefined : createGraphRuntimeFromEnv(env, this.cwd);
|
|
33
|
+
this.graphStore = options.graphStore ?? graphRuntime?.graphStore ?? new InMemoryGraphStore();
|
|
34
|
+
const semanticRuntime = (options.semanticStore && options.embeddingProvider)
|
|
35
|
+
? undefined
|
|
36
|
+
: createSemanticRuntimeFromEnv(env, this.cwd);
|
|
37
|
+
this.semanticStore = options.semanticStore ?? semanticRuntime?.semanticStore ?? new InMemorySemanticStore();
|
|
38
|
+
this.embeddingProvider = options.embeddingProvider ?? semanticRuntime?.embeddingProvider ?? new DeterministicEmbeddingProvider();
|
|
39
|
+
this.workspaceResolver = new WorkspaceResolver(this.projectRegistry, {
|
|
40
|
+
cwd: this.cwd,
|
|
41
|
+
roots: this.workspaceRoots
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
close() {
|
|
45
|
+
this.graphStore.close?.();
|
|
46
|
+
}
|
|
47
|
+
async indexRepo(repoRoot, options) {
|
|
48
|
+
const project = await this.projectRegistry.register(repoRoot);
|
|
49
|
+
this.workspaceResolver.setActive(project, "repoRoot");
|
|
50
|
+
const absoluteRoot = path.resolve(repoRoot);
|
|
51
|
+
this.indexedAtByRepo.set(absoluteRoot, Date.now());
|
|
52
|
+
const index = await new RepoIndexer({
|
|
53
|
+
graphStore: this.graphStore,
|
|
54
|
+
semanticStore: this.semanticStore,
|
|
55
|
+
embeddingProvider: this.embeddingProvider
|
|
56
|
+
}).indexRepo(absoluteRoot, project.projectId, project, options);
|
|
57
|
+
const indexedProject = this.projectRegistry.upsert({
|
|
58
|
+
...project,
|
|
59
|
+
lastIndexedAtMs: index.indexedAtMs
|
|
60
|
+
});
|
|
61
|
+
this.indexedAtByRepo.set(indexedProject.repoRoot, index.indexedAtMs);
|
|
62
|
+
this.indexedAtByRepo.set(indexedProject.canonicalRoot, index.indexedAtMs);
|
|
63
|
+
return {
|
|
64
|
+
...index,
|
|
65
|
+
project: indexedProject
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async refreshIndex(repoRoot, options) {
|
|
69
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
70
|
+
return this.indexRepo(scope.activeRepoRoot, options);
|
|
71
|
+
}
|
|
72
|
+
async indexStatus(repoRoot) {
|
|
73
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
74
|
+
const [files, chunks, symbols, edges, freshness] = await Promise.all([
|
|
75
|
+
this.graphStore.getFiles(scope.activeRepoRoot),
|
|
76
|
+
this.graphStore.getChunks(scope.activeRepoRoot),
|
|
77
|
+
this.graphStore.getSymbols(scope.activeRepoRoot),
|
|
78
|
+
this.graphStore.getEdges(scope.activeRepoRoot),
|
|
79
|
+
this.computeFreshness(scope)
|
|
80
|
+
]);
|
|
81
|
+
const stale = new Set(freshness.staleFiles);
|
|
82
|
+
return {
|
|
83
|
+
repoRoot: scope.activeRepoRoot,
|
|
84
|
+
projectId: scope.activeProjectId,
|
|
85
|
+
indexedAtMs: freshness.indexedAtMs,
|
|
86
|
+
fileCount: files.length,
|
|
87
|
+
chunkCount: chunks.length,
|
|
88
|
+
symbolCount: symbols.length,
|
|
89
|
+
edgeCount: edges.length,
|
|
90
|
+
freshFileCount: files.filter((file) => !stale.has(file.path)).length,
|
|
91
|
+
staleFileCount: freshness.staleFiles.length,
|
|
92
|
+
pendingFileCount: freshness.pendingFiles.length,
|
|
93
|
+
indexingFileCount: freshness.indexingFiles.length,
|
|
94
|
+
skippedFileCount: freshness.skippedFiles.length,
|
|
95
|
+
burstMode: freshness.burstMode,
|
|
96
|
+
droppedEventCount: freshness.droppedEvents,
|
|
97
|
+
freshness
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async recordFileEvents(repoRoot, filePaths, options) {
|
|
101
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
102
|
+
if (!this.graphStore.recordFileEvents) {
|
|
103
|
+
throw new Error("Current graph store does not support watcher dirty-file state.");
|
|
104
|
+
}
|
|
105
|
+
return this.graphStore.recordFileEvents(scope.activeRepoRoot, filePaths, options);
|
|
106
|
+
}
|
|
107
|
+
async markDirtyFilesIndexing(repoRoot, filePaths) {
|
|
108
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
109
|
+
if (!this.graphStore.markDirtyFilesIndexing) {
|
|
110
|
+
throw new Error("Current graph store does not support watcher indexing state.");
|
|
111
|
+
}
|
|
112
|
+
return this.graphStore.markDirtyFilesIndexing(scope.activeRepoRoot, filePaths);
|
|
113
|
+
}
|
|
114
|
+
async markDirtyFilesDeadLetter(repoRoot, filePaths, reason) {
|
|
115
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
116
|
+
if (!this.graphStore.markDirtyFilesDeadLetter) {
|
|
117
|
+
throw new Error("Current graph store does not support watcher dead-letter state.");
|
|
118
|
+
}
|
|
119
|
+
return this.graphStore.markDirtyFilesDeadLetter(scope.activeRepoRoot, filePaths, reason);
|
|
120
|
+
}
|
|
121
|
+
async searchCode(query) {
|
|
122
|
+
const scope = await this.resolveWorkspace(query);
|
|
123
|
+
const { hits } = await this.searchWithFreshness({ ...query, repoRoot: scope.activeRepoRoot, projectId: scope.activeProjectId }, scope);
|
|
124
|
+
return hits;
|
|
125
|
+
}
|
|
126
|
+
async searchCodeWithDiagnostics(query) {
|
|
127
|
+
const scope = await this.resolveWorkspace(query);
|
|
128
|
+
const { hits, diagnostics } = await this.searchWithFreshness({ ...query, repoRoot: scope.activeRepoRoot, projectId: scope.activeProjectId }, scope);
|
|
129
|
+
return { hits, diagnostics };
|
|
130
|
+
}
|
|
131
|
+
async getContext(request) {
|
|
132
|
+
const scope = await this.resolveWorkspace(request);
|
|
133
|
+
const normalized = { ...request, repoRoot: scope.activeRepoRoot, projectId: scope.activeProjectId };
|
|
134
|
+
const { hits, freshness } = await this.searchWithFreshness(normalized, scope);
|
|
135
|
+
const edges = filterFreshEdges(await this.graphStore.getEdges(normalized.repoRoot), freshness);
|
|
136
|
+
return this.contextBuilder.build(normalized, hits, edges, {
|
|
137
|
+
projectId: scope.activeProjectId,
|
|
138
|
+
repoRoot: scope.activeRepoRoot,
|
|
139
|
+
indexedAtMs: this.indexedAtByRepo.get(scope.activeRepoRoot) ?? Date.now(),
|
|
140
|
+
indexGeneration: freshness.indexGeneration,
|
|
141
|
+
staleFiles: freshness.staleFiles,
|
|
142
|
+
pendingFiles: freshness.pendingFiles,
|
|
143
|
+
indexingFiles: freshness.indexingFiles,
|
|
144
|
+
skippedFiles: freshness.skippedFiles,
|
|
145
|
+
dirtyFiles: freshness.dirtyFiles,
|
|
146
|
+
burstMode: freshness.burstMode,
|
|
147
|
+
droppedEvents: freshness.droppedEvents
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
async verifiedSubgraph(request) {
|
|
151
|
+
const scope = await this.resolveWorkspace(request);
|
|
152
|
+
const freshness = await this.computeFreshness(scope);
|
|
153
|
+
const [symbols, edges, chunks] = await Promise.all([
|
|
154
|
+
this.graphStore.getSymbols(scope.activeRepoRoot),
|
|
155
|
+
this.graphStore.getEdges(scope.activeRepoRoot),
|
|
156
|
+
this.graphStore.getChunks(scope.activeRepoRoot)
|
|
157
|
+
]);
|
|
158
|
+
const freshSymbols = filterFreshSymbols(symbols, freshness);
|
|
159
|
+
const freshChunks = filterFreshChunks(chunks, freshness);
|
|
160
|
+
const freshEdges = filterFreshEdges(edges, freshness);
|
|
161
|
+
const seed = request.seed ?? request.query;
|
|
162
|
+
const seedSymbols = selectSeedSymbols(freshSymbols, seed);
|
|
163
|
+
const missingEvidence = freshness.staleFiles.length > 0
|
|
164
|
+
? [`Stale indexed files excluded: ${freshness.staleFiles.slice(0, 8).join(", ")}.`]
|
|
165
|
+
: [];
|
|
166
|
+
return new SubgraphBuilder().build({
|
|
167
|
+
query: request.query,
|
|
168
|
+
repoRoot: scope.activeRepoRoot,
|
|
169
|
+
projectId: scope.activeProjectId,
|
|
170
|
+
mode: request.mode ?? "impact",
|
|
171
|
+
seedSymbols,
|
|
172
|
+
symbols: freshSymbols,
|
|
173
|
+
edges: freshEdges,
|
|
174
|
+
chunks: freshChunks,
|
|
175
|
+
budgetChars: request.budgetChars,
|
|
176
|
+
maxHops: request.maxHops,
|
|
177
|
+
missingEvidence
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
async topologyMap(request) {
|
|
181
|
+
const pack = await this.getContext({
|
|
182
|
+
...request,
|
|
183
|
+
mode: request.mode ?? "feature",
|
|
184
|
+
budgetChars: request.budgetChars ?? 12_000
|
|
185
|
+
});
|
|
186
|
+
return {
|
|
187
|
+
query: pack.query,
|
|
188
|
+
repoRoot: pack.repoRoot,
|
|
189
|
+
projectId: pack.projectId,
|
|
190
|
+
freshness: pack.freshness,
|
|
191
|
+
owners: pack.ownerChain,
|
|
192
|
+
edges: pack.topology.slice(0, request.maxEdges ?? 24),
|
|
193
|
+
missingEvidence: pack.missingEvidence,
|
|
194
|
+
nextQueries: pack.nextQueries
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
async findSymbol(repoRoot, name) {
|
|
198
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
199
|
+
return this.graphStore.findSymbol(scope.activeRepoRoot, name);
|
|
200
|
+
}
|
|
201
|
+
async graphSnapshot(repoRoot) {
|
|
202
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
203
|
+
const [symbols, edges] = await Promise.all([
|
|
204
|
+
this.graphStore.getSymbols(scope.activeRepoRoot),
|
|
205
|
+
this.graphStore.getEdges(scope.activeRepoRoot)
|
|
206
|
+
]);
|
|
207
|
+
return { symbols, edges };
|
|
208
|
+
}
|
|
209
|
+
async explainFile(repoRoot, filePath) {
|
|
210
|
+
const scope = await this.resolveWorkspace({ repoRoot, workspace: path.isAbsolute(filePath) ? { filePath } : undefined });
|
|
211
|
+
return this.graphStore.explainFile(scope.activeRepoRoot, filePath);
|
|
212
|
+
}
|
|
213
|
+
async findOwner(repoRoot, query, limit) {
|
|
214
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
215
|
+
return this.graphStore.findOwner(scope.activeRepoRoot, query, limit);
|
|
216
|
+
}
|
|
217
|
+
async findReuseCandidates(request) {
|
|
218
|
+
const scope = await this.resolveWorkspace(request);
|
|
219
|
+
const normalized = { ...request, repoRoot: scope.activeRepoRoot, projectId: scope.activeProjectId };
|
|
220
|
+
const { hits, freshness } = await this.searchWithFreshness(normalized, scope);
|
|
221
|
+
const [owners, symbols, edges, chunks] = await Promise.all([
|
|
222
|
+
this.graphStore.findOwner(scope.activeRepoRoot, request.query, request.limit ?? 8),
|
|
223
|
+
this.graphStore.getSymbols(scope.activeRepoRoot),
|
|
224
|
+
this.graphStore.getEdges(scope.activeRepoRoot),
|
|
225
|
+
this.graphStore.getChunks(scope.activeRepoRoot)
|
|
226
|
+
]);
|
|
227
|
+
return buildReuseCandidateReport({
|
|
228
|
+
query: request.query,
|
|
229
|
+
hits,
|
|
230
|
+
owners: owners.filter((owner) => !freshness.staleFiles.includes(owner.filePath)),
|
|
231
|
+
symbols: filterFreshSymbols(symbols, freshness),
|
|
232
|
+
edges: filterFreshEdges(edges, freshness),
|
|
233
|
+
chunks: filterFreshChunks(chunks, freshness),
|
|
234
|
+
limit: request.limit,
|
|
235
|
+
reuseGuard: request.reuseGuard
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
async impactAnalysis(repoRoot, target) {
|
|
239
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
240
|
+
return this.graphStore.impactAnalysis(scope.activeRepoRoot, target);
|
|
241
|
+
}
|
|
242
|
+
async relatedTests(repoRoot, target) {
|
|
243
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
244
|
+
return this.graphStore.relatedTests(scope.activeRepoRoot, target);
|
|
245
|
+
}
|
|
246
|
+
async traceFlow(repoRoot, entry, maxSteps) {
|
|
247
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
248
|
+
return this.graphStore.traceFlow(scope.activeRepoRoot, entry, maxSteps);
|
|
249
|
+
}
|
|
250
|
+
async reviewDiff(repoRoot, diff, changedFiles) {
|
|
251
|
+
const scope = await this.resolveWorkspace({ repoRoot });
|
|
252
|
+
return this.graphStore.reviewDiff(scope.activeRepoRoot, diff, changedFiles);
|
|
253
|
+
}
|
|
254
|
+
async resolveWorkspace(input = {}) {
|
|
255
|
+
await this.hydrateForInput(input);
|
|
256
|
+
return this.workspaceResolver.resolve(input);
|
|
257
|
+
}
|
|
258
|
+
async hydrateForInput(input) {
|
|
259
|
+
if (input.repoRoot)
|
|
260
|
+
await this.hydrateProjectByRoot(input.repoRoot);
|
|
261
|
+
if (input.workspace?.root)
|
|
262
|
+
await this.hydrateProjectByRoot(input.workspace.root);
|
|
263
|
+
if (input.workspace?.filePath)
|
|
264
|
+
await this.hydrateAllPersistedProjects();
|
|
265
|
+
if (!input.repoRoot && !input.workspace?.root && !input.workspace?.filePath) {
|
|
266
|
+
for (const root of this.workspaceRoots)
|
|
267
|
+
await this.hydrateProjectByRoot(root);
|
|
268
|
+
await this.hydrateProjectByRoot(this.cwd);
|
|
269
|
+
await this.hydrateAllPersistedProjects();
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async hydrateProjectByRoot(root) {
|
|
273
|
+
if (this.projectRegistry.findByRoot(root))
|
|
274
|
+
return;
|
|
275
|
+
const normalized = path.resolve(root).toLowerCase();
|
|
276
|
+
if (this.hydratedRoots.has(normalized))
|
|
277
|
+
return;
|
|
278
|
+
this.hydratedRoots.add(normalized);
|
|
279
|
+
const project = await this.graphStore.getProjectByRoot?.(root);
|
|
280
|
+
if (project)
|
|
281
|
+
this.rememberHydratedProject(project);
|
|
282
|
+
}
|
|
283
|
+
async hydrateAllPersistedProjects() {
|
|
284
|
+
if (this.hydratedAllProjects)
|
|
285
|
+
return;
|
|
286
|
+
this.hydratedAllProjects = true;
|
|
287
|
+
const projects = await this.graphStore.listProjects?.();
|
|
288
|
+
for (const project of projects ?? [])
|
|
289
|
+
this.rememberHydratedProject(project);
|
|
290
|
+
}
|
|
291
|
+
rememberHydratedProject(project) {
|
|
292
|
+
const merged = this.projectRegistry.upsert(project);
|
|
293
|
+
if (merged.lastIndexedAtMs) {
|
|
294
|
+
this.indexedAtByRepo.set(merged.repoRoot, merged.lastIndexedAtMs);
|
|
295
|
+
this.indexedAtByRepo.set(merged.canonicalRoot, merged.lastIndexedAtMs);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async searchWithFreshness(query, scope) {
|
|
299
|
+
const freshness = await this.computeFreshness(scope);
|
|
300
|
+
const result = await new HybridRetriever({
|
|
301
|
+
graphStore: this.graphStore,
|
|
302
|
+
semanticStore: this.semanticStore,
|
|
303
|
+
embeddingProvider: this.embeddingProvider
|
|
304
|
+
}).searchWithDiagnostics(query);
|
|
305
|
+
const hits = filterFreshHits(result.hits, freshness);
|
|
306
|
+
return { hits, freshness, diagnostics: diagnosticsForFreshHits(result.diagnostics, hits) };
|
|
307
|
+
}
|
|
308
|
+
async computeFreshness(scope) {
|
|
309
|
+
const watcherStatePromise = this.graphStore.getWatcherState
|
|
310
|
+
? this.graphStore.getWatcherState(scope.activeRepoRoot).catch(() => undefined)
|
|
311
|
+
: Promise.resolve(undefined);
|
|
312
|
+
const [indexedFiles, scan, watcherState] = await Promise.all([
|
|
313
|
+
this.graphStore.getFiles(scope.activeRepoRoot),
|
|
314
|
+
scanRepo(scope.activeRepoRoot, scope.activeProjectId),
|
|
315
|
+
watcherStatePromise
|
|
316
|
+
]);
|
|
317
|
+
const indexedByPath = new Map(indexedFiles.map((file) => [file.path, file]));
|
|
318
|
+
const currentByPath = new Map(scan.files.map((file) => [file.path, file]));
|
|
319
|
+
const staleFiles = new Set();
|
|
320
|
+
const pendingFiles = new Set();
|
|
321
|
+
for (const indexed of indexedFiles) {
|
|
322
|
+
const current = currentByPath.get(indexed.path);
|
|
323
|
+
if (!current) {
|
|
324
|
+
staleFiles.add(indexed.path);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (current.contentHash !== indexed.contentHash) {
|
|
328
|
+
staleFiles.add(indexed.path);
|
|
329
|
+
pendingFiles.add(indexed.path);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
for (const current of scan.files) {
|
|
333
|
+
const indexed = indexedByPath.get(current.path);
|
|
334
|
+
if (!indexed || indexed.contentHash !== current.contentHash)
|
|
335
|
+
pendingFiles.add(current.path);
|
|
336
|
+
}
|
|
337
|
+
for (const filePath of watcherState?.pendingFiles ?? []) {
|
|
338
|
+
pendingFiles.add(filePath);
|
|
339
|
+
if (indexedByPath.has(filePath))
|
|
340
|
+
staleFiles.add(filePath);
|
|
341
|
+
}
|
|
342
|
+
for (const filePath of watcherState?.indexingFiles ?? []) {
|
|
343
|
+
if (indexedByPath.has(filePath))
|
|
344
|
+
staleFiles.add(filePath);
|
|
345
|
+
}
|
|
346
|
+
const indexGeneration = this.graphStore.getIndexGeneration
|
|
347
|
+
? await this.graphStore.getIndexGeneration(scope.activeRepoRoot).catch(() => 1)
|
|
348
|
+
: 1;
|
|
349
|
+
return {
|
|
350
|
+
projectId: scope.activeProjectId,
|
|
351
|
+
indexGeneration,
|
|
352
|
+
indexedAtMs: this.indexedAtByRepo.get(scope.activeRepoRoot) ?? Date.now(),
|
|
353
|
+
staleFiles: [...staleFiles].sort(),
|
|
354
|
+
pendingFiles: [...pendingFiles].sort(),
|
|
355
|
+
indexingFiles: [...new Set(watcherState?.indexingFiles ?? [])].sort(),
|
|
356
|
+
skippedFiles: scan.skippedFiles,
|
|
357
|
+
dirtyFiles: watcherState?.dirtyFiles ?? [],
|
|
358
|
+
burstMode: watcherState?.burstMode ?? false,
|
|
359
|
+
droppedEvents: watcherState?.droppedEvents ?? 0
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function filterFreshHits(hits, freshness) {
|
|
364
|
+
if (freshness.staleFiles.length === 0)
|
|
365
|
+
return hits;
|
|
366
|
+
const stale = new Set(freshness.staleFiles);
|
|
367
|
+
return hits.filter((hit) => !stale.has(hit.chunk.filePath));
|
|
368
|
+
}
|
|
369
|
+
function diagnosticsForFreshHits(diagnostics, hits) {
|
|
370
|
+
return {
|
|
371
|
+
...diagnostics,
|
|
372
|
+
fusion: {
|
|
373
|
+
...diagnostics.fusion,
|
|
374
|
+
semanticTopNParticipation: hits.filter(hasSemanticParticipation).length
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function filterFreshEdges(edges, freshness) {
|
|
379
|
+
if (freshness.staleFiles.length === 0)
|
|
380
|
+
return edges;
|
|
381
|
+
const stale = new Set(freshness.staleFiles);
|
|
382
|
+
return edges.filter((edge) => {
|
|
383
|
+
const sourceFile = edge.metadata?.sourceFile;
|
|
384
|
+
const targetFile = edge.metadata?.targetFile;
|
|
385
|
+
return (typeof sourceFile !== "string" || !stale.has(sourceFile))
|
|
386
|
+
&& (typeof targetFile !== "string" || !stale.has(targetFile));
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
function filterFreshSymbols(symbols, freshness) {
|
|
390
|
+
if (freshness.staleFiles.length === 0)
|
|
391
|
+
return symbols;
|
|
392
|
+
const stale = new Set(freshness.staleFiles);
|
|
393
|
+
return symbols.filter((symbol) => !stale.has(symbol.filePath));
|
|
394
|
+
}
|
|
395
|
+
function filterFreshChunks(chunks, freshness) {
|
|
396
|
+
if (freshness.staleFiles.length === 0)
|
|
397
|
+
return chunks;
|
|
398
|
+
const stale = new Set(freshness.staleFiles);
|
|
399
|
+
return chunks.filter((chunk) => !stale.has(chunk.filePath));
|
|
400
|
+
}
|
|
401
|
+
function selectSeedSymbols(symbols, seed) {
|
|
402
|
+
const normalized = normalizeUserPath(seed);
|
|
403
|
+
const scoped = scopedSeed(seed);
|
|
404
|
+
const loweredSeed = seed.toLowerCase();
|
|
405
|
+
const exact = symbols.filter((symbol) => {
|
|
406
|
+
if (scoped) {
|
|
407
|
+
return symbol.filePath === scoped.filePath && symbol.name.toLowerCase() === scoped.symbolName.toLowerCase();
|
|
408
|
+
}
|
|
409
|
+
return symbol.name.toLowerCase() === loweredSeed || symbol.filePath === normalized;
|
|
410
|
+
});
|
|
411
|
+
if (exact.length > 0)
|
|
412
|
+
return sortSeedSymbols(exact);
|
|
413
|
+
const partial = symbols.filter((symbol) => {
|
|
414
|
+
if (scoped) {
|
|
415
|
+
return symbol.filePath.includes(scoped.filePath) && symbol.name.toLowerCase().includes(scoped.symbolName.toLowerCase());
|
|
416
|
+
}
|
|
417
|
+
return symbol.name.toLowerCase().includes(loweredSeed) || symbol.filePath.includes(normalized);
|
|
418
|
+
});
|
|
419
|
+
return sortSeedSymbols(partial).slice(0, 8);
|
|
420
|
+
}
|
|
421
|
+
function scopedSeed(seed) {
|
|
422
|
+
const separator = seed.lastIndexOf(":");
|
|
423
|
+
if (separator <= 0 || /^[a-zA-Z]:[\\/]/.test(seed))
|
|
424
|
+
return undefined;
|
|
425
|
+
const filePath = normalizeUserPath(seed.slice(0, separator));
|
|
426
|
+
const symbolName = seed.slice(separator + 1).trim();
|
|
427
|
+
if (!filePath || !symbolName)
|
|
428
|
+
return undefined;
|
|
429
|
+
return { filePath, symbolName };
|
|
430
|
+
}
|
|
431
|
+
function sortSeedSymbols(symbols) {
|
|
432
|
+
return [...symbols].sort((a, b) => seedPriority(a) - seedPriority(b)
|
|
433
|
+
|| a.filePath.localeCompare(b.filePath)
|
|
434
|
+
|| a.startLine - b.startLine);
|
|
435
|
+
}
|
|
436
|
+
function seedPriority(symbol) {
|
|
437
|
+
if (symbol.kind === "file")
|
|
438
|
+
return 5;
|
|
439
|
+
if (symbol.exported)
|
|
440
|
+
return 0;
|
|
441
|
+
return 1;
|
|
442
|
+
}
|