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.
Files changed (174) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +366 -0
  3. package/README.zh-CN.md +363 -0
  4. package/dist/src/cli/configure/app.d.ts +6 -0
  5. package/dist/src/cli/configure/app.js +81 -0
  6. package/dist/src/cli/configure/run.d.ts +5 -0
  7. package/dist/src/cli/configure/run.js +85 -0
  8. package/dist/src/cli/configure/state.d.ts +42 -0
  9. package/dist/src/cli/configure/state.js +174 -0
  10. package/dist/src/cli/configure.d.ts +31 -0
  11. package/dist/src/cli/configure.js +101 -0
  12. package/dist/src/cli/index.d.ts +2 -0
  13. package/dist/src/cli/index.js +503 -0
  14. package/dist/src/cli/tui/index-progress.d.ts +12 -0
  15. package/dist/src/cli/tui/index-progress.js +49 -0
  16. package/dist/src/cli/tui/watch-status.d.ts +10 -0
  17. package/dist/src/cli/tui/watch-status.js +27 -0
  18. package/dist/src/cli/update.d.ts +18 -0
  19. package/dist/src/cli/update.js +111 -0
  20. package/dist/src/config/dotenv.d.ts +1 -0
  21. package/dist/src/config/dotenv.js +14 -0
  22. package/dist/src/config/graph-runtime.d.ts +13 -0
  23. package/dist/src/config/graph-runtime.js +29 -0
  24. package/dist/src/config/runtime-config.d.ts +87 -0
  25. package/dist/src/config/runtime-config.js +215 -0
  26. package/dist/src/config/semantic-runtime.d.ts +24 -0
  27. package/dist/src/config/semantic-runtime.js +89 -0
  28. package/dist/src/context/context-builder.d.ts +20 -0
  29. package/dist/src/context/context-builder.js +277 -0
  30. package/dist/src/context/expansion-policy.d.ts +6 -0
  31. package/dist/src/context/expansion-policy.js +49 -0
  32. package/dist/src/context/skeletonizer.d.ts +2 -0
  33. package/dist/src/context/skeletonizer.js +79 -0
  34. package/dist/src/context/snippet-renderer.d.ts +2 -0
  35. package/dist/src/context/snippet-renderer.js +67 -0
  36. package/dist/src/core/contracts.d.ts +74 -0
  37. package/dist/src/core/contracts.js +1 -0
  38. package/dist/src/core/engine.d.ts +64 -0
  39. package/dist/src/core/engine.js +442 -0
  40. package/dist/src/core/types.d.ts +490 -0
  41. package/dist/src/core/types.js +1 -0
  42. package/dist/src/diagnostics/doctor.d.ts +66 -0
  43. package/dist/src/diagnostics/doctor.js +193 -0
  44. package/dist/src/diagnostics/embedding-test.d.ts +24 -0
  45. package/dist/src/diagnostics/embedding-test.js +83 -0
  46. package/dist/src/graph/diff-files.d.ts +1 -0
  47. package/dist/src/graph/diff-files.js +14 -0
  48. package/dist/src/graph/impact-report.d.ts +10 -0
  49. package/dist/src/graph/impact-report.js +173 -0
  50. package/dist/src/graph/in-memory-graph-store.d.ts +36 -0
  51. package/dist/src/graph/in-memory-graph-store.js +395 -0
  52. package/dist/src/graph/owner-ranking.d.ts +2 -0
  53. package/dist/src/graph/owner-ranking.js +41 -0
  54. package/dist/src/graph/sqlite-graph-store.d.ts +51 -0
  55. package/dist/src/graph/sqlite-graph-store.js +724 -0
  56. package/dist/src/graph/sqlite-statements.d.ts +36 -0
  57. package/dist/src/graph/sqlite-statements.js +105 -0
  58. package/dist/src/graph/target-matcher.d.ts +13 -0
  59. package/dist/src/graph/target-matcher.js +64 -0
  60. package/dist/src/index.d.ts +32 -0
  61. package/dist/src/index.js +32 -0
  62. package/dist/src/indexing/analyzers/fallback-analyzer.d.ts +6 -0
  63. package/dist/src/indexing/analyzers/fallback-analyzer.js +45 -0
  64. package/dist/src/indexing/analyzers/go-treesitter-analyzer.d.ts +2 -0
  65. package/dist/src/indexing/analyzers/go-treesitter-analyzer.js +87 -0
  66. package/dist/src/indexing/analyzers/java-treesitter-analyzer.d.ts +2 -0
  67. package/dist/src/indexing/analyzers/java-treesitter-analyzer.js +88 -0
  68. package/dist/src/indexing/analyzers/python-treesitter-analyzer.d.ts +2 -0
  69. package/dist/src/indexing/analyzers/python-treesitter-analyzer.js +96 -0
  70. package/dist/src/indexing/analyzers/registry.d.ts +5 -0
  71. package/dist/src/indexing/analyzers/registry.js +23 -0
  72. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.d.ts +2 -0
  73. package/dist/src/indexing/analyzers/rust-treesitter-analyzer.js +96 -0
  74. package/dist/src/indexing/analyzers/tree-sitter-base.d.ts +30 -0
  75. package/dist/src/indexing/analyzers/tree-sitter-base.js +163 -0
  76. package/dist/src/indexing/analyzers/types.d.ts +17 -0
  77. package/dist/src/indexing/analyzers/types.js +1 -0
  78. package/dist/src/indexing/analyzers/typescript-analyzer.d.ts +5 -0
  79. package/dist/src/indexing/analyzers/typescript-analyzer.js +199 -0
  80. package/dist/src/indexing/ast-analyzer.d.ts +11 -0
  81. package/dist/src/indexing/ast-analyzer.js +11 -0
  82. package/dist/src/indexing/chunker.d.ts +11 -0
  83. package/dist/src/indexing/chunker.js +157 -0
  84. package/dist/src/indexing/ignore-policy.d.ts +6 -0
  85. package/dist/src/indexing/ignore-policy.js +40 -0
  86. package/dist/src/indexing/indexer.d.ts +13 -0
  87. package/dist/src/indexing/indexer.js +189 -0
  88. package/dist/src/indexing/language.d.ts +3 -0
  89. package/dist/src/indexing/language.js +24 -0
  90. package/dist/src/indexing/scanner.d.ts +13 -0
  91. package/dist/src/indexing/scanner.js +87 -0
  92. package/dist/src/lsp/definition-resolver.d.ts +6 -0
  93. package/dist/src/lsp/definition-resolver.js +60 -0
  94. package/dist/src/lsp/typescript-language-service.d.ts +21 -0
  95. package/dist/src/lsp/typescript-language-service.js +82 -0
  96. package/dist/src/mcp/server.d.ts +11 -0
  97. package/dist/src/mcp/server.js +64 -0
  98. package/dist/src/mcp/tools.d.ts +266 -0
  99. package/dist/src/mcp/tools.js +309 -0
  100. package/dist/src/project/project-identity.d.ts +2 -0
  101. package/dist/src/project/project-identity.js +24 -0
  102. package/dist/src/project/project-registry.d.ts +12 -0
  103. package/dist/src/project/project-registry.js +49 -0
  104. package/dist/src/project/workspace-resolver.d.ts +20 -0
  105. package/dist/src/project/workspace-resolver.js +62 -0
  106. package/dist/src/retrieval/graph-reranker.d.ts +11 -0
  107. package/dist/src/retrieval/graph-reranker.js +0 -0
  108. package/dist/src/retrieval/hybrid-retriever.d.ts +31 -0
  109. package/dist/src/retrieval/hybrid-retriever.js +111 -0
  110. package/dist/src/retrieval/path-classification.d.ts +6 -0
  111. package/dist/src/retrieval/path-classification.js +22 -0
  112. package/dist/src/retrieval/query-matching.d.ts +22 -0
  113. package/dist/src/retrieval/query-matching.js +166 -0
  114. package/dist/src/retrieval/query-planner.d.ts +5 -0
  115. package/dist/src/retrieval/query-planner.js +77 -0
  116. package/dist/src/retrieval/ranking-signals.d.ts +19 -0
  117. package/dist/src/retrieval/ranking-signals.js +97 -0
  118. package/dist/src/retrieval/topology-distance.d.ts +21 -0
  119. package/dist/src/retrieval/topology-distance.js +116 -0
  120. package/dist/src/reuse/reuse-detector.d.ts +12 -0
  121. package/dist/src/reuse/reuse-detector.js +564 -0
  122. package/dist/src/semantic/deterministic-embedding.d.ts +7 -0
  123. package/dist/src/semantic/deterministic-embedding.js +31 -0
  124. package/dist/src/semantic/in-memory-semantic-store.d.ts +11 -0
  125. package/dist/src/semantic/in-memory-semantic-store.js +65 -0
  126. package/dist/src/semantic/lance-semantic-store.d.ts +131 -0
  127. package/dist/src/semantic/lance-semantic-store.js +623 -0
  128. package/dist/src/semantic/openai-compatible-embedding.d.ts +19 -0
  129. package/dist/src/semantic/openai-compatible-embedding.js +75 -0
  130. package/dist/src/service/service-identity.d.ts +13 -0
  131. package/dist/src/service/service-identity.js +48 -0
  132. package/dist/src/service/service-manager.d.ts +29 -0
  133. package/dist/src/service/service-manager.js +231 -0
  134. package/dist/src/service/service-templates.d.ts +22 -0
  135. package/dist/src/service/service-templates.js +101 -0
  136. package/dist/src/subgraph/impact-explainer.d.ts +2 -0
  137. package/dist/src/subgraph/impact-explainer.js +54 -0
  138. package/dist/src/subgraph/node-expander.d.ts +13 -0
  139. package/dist/src/subgraph/node-expander.js +139 -0
  140. package/dist/src/subgraph/output-preset.d.ts +3 -0
  141. package/dist/src/subgraph/output-preset.js +102 -0
  142. package/dist/src/subgraph/subgraph-builder.d.ts +17 -0
  143. package/dist/src/subgraph/subgraph-builder.js +688 -0
  144. package/dist/src/topology/export-index.d.ts +7 -0
  145. package/dist/src/topology/export-index.js +14 -0
  146. package/dist/src/topology/framework-topology.d.ts +3 -0
  147. package/dist/src/topology/framework-topology.js +460 -0
  148. package/dist/src/topology/import-resolver.d.ts +2 -0
  149. package/dist/src/topology/import-resolver.js +29 -0
  150. package/dist/src/topology/orm-topology.d.ts +3 -0
  151. package/dist/src/topology/orm-topology.js +200 -0
  152. package/dist/src/topology/runtime-topology.d.ts +3 -0
  153. package/dist/src/topology/runtime-topology.js +204 -0
  154. package/dist/src/topology/symbol-resolver.d.ts +6 -0
  155. package/dist/src/topology/symbol-resolver.js +74 -0
  156. package/dist/src/topology/test-topology.d.ts +2 -0
  157. package/dist/src/topology/test-topology.js +82 -0
  158. package/dist/src/utils/hash.d.ts +2 -0
  159. package/dist/src/utils/hash.js +7 -0
  160. package/dist/src/utils/path.d.ts +2 -0
  161. package/dist/src/utils/path.js +7 -0
  162. package/dist/src/watch/event-journal.d.ts +17 -0
  163. package/dist/src/watch/event-journal.js +81 -0
  164. package/dist/src/watch/file-event-coalescer.d.ts +9 -0
  165. package/dist/src/watch/file-event-coalescer.js +39 -0
  166. package/dist/src/watch/index-scheduler.d.ts +52 -0
  167. package/dist/src/watch/index-scheduler.js +190 -0
  168. package/dist/src/watch/watch-daemon.d.ts +73 -0
  169. package/dist/src/watch/watch-daemon.js +368 -0
  170. package/dist/src/watch/watcher-liveness.d.ts +47 -0
  171. package/dist/src/watch/watcher-liveness.js +168 -0
  172. package/dist/src/web/server.d.ts +1 -0
  173. package/dist/src/web/server.js +375 -0
  174. 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
+ }