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,277 @@
1
+ import { nextQueriesForMode, resolveContextMode } from "../retrieval/query-planner.js";
2
+ import { classifyEvidencePath, isExplicitSupportingEvidenceQuery } from "../retrieval/path-classification.js";
3
+ import { renderSnippet } from "./snippet-renderer.js";
4
+ const DEFAULT_BUDGET_CHARS = 18_000;
5
+ export class ContextBuilder {
6
+ build(request, hits, edges = [], metadata) {
7
+ const budgetChars = request.budgetChars ?? DEFAULT_BUDGET_CHARS;
8
+ const mode = resolveContextMode(request.query, request.mode);
9
+ const { snippets, usedChars } = selectDiverseSnippets(request, hits, mode, budgetChars);
10
+ const ownerChain = ownerNodes(snippets);
11
+ const ownerPaths = ownerChain.map((owner) => owner.filePath);
12
+ const relationships = relationshipEvidence(edges, ownerPaths);
13
+ const topology = topologyEdges(edges, ownerPaths);
14
+ const confidence = confidenceFor(snippets);
15
+ return {
16
+ query: request.query,
17
+ repoRoot: metadata.repoRoot,
18
+ projectId: metadata.projectId,
19
+ mode,
20
+ answerable: snippets.length > 0,
21
+ confidence,
22
+ brief: briefFor(request.query, mode, confidence, ownerChain),
23
+ freshness: freshnessFor(metadata),
24
+ ownerChain,
25
+ topology,
26
+ snippets,
27
+ relationships,
28
+ nextQueries: nextQueriesForMode(request.query, mode),
29
+ missingEvidence: missingEvidenceFor(snippets, metadata),
30
+ budgetChars,
31
+ usedChars
32
+ };
33
+ }
34
+ }
35
+ function selectDiverseSnippets(request, hits, mode, budgetChars) {
36
+ const candidates = hits.map((hit, index) => {
37
+ const snippet = renderSnippet(hit, request.query, mode);
38
+ return {
39
+ index,
40
+ key: `${snippet.filePath}\0${snippet.startLine}\0${snippet.endLine}\0${snippet.role}`,
41
+ snippet,
42
+ cost: estimateSnippetCost(snippet)
43
+ };
44
+ });
45
+ const selected = [];
46
+ const selectedKeys = new Set();
47
+ const snippetsByFile = new Map();
48
+ let usedChars = 0;
49
+ const tryAdd = (candidate, maxForFile) => {
50
+ if (selectedKeys.has(candidate.key))
51
+ return;
52
+ const currentForFile = snippetsByFile.get(candidate.snippet.filePath) ?? 0;
53
+ if (currentForFile >= maxForFile)
54
+ return;
55
+ if (usedChars + candidate.cost > budgetChars)
56
+ return;
57
+ selectedKeys.add(candidate.key);
58
+ snippetsByFile.set(candidate.snippet.filePath, currentForFile + 1);
59
+ selected.push(candidate);
60
+ usedChars += candidate.cost;
61
+ };
62
+ for (const candidate of candidates) {
63
+ if ((snippetsByFile.get(candidate.snippet.filePath) ?? 0) === 0) {
64
+ tryAdd(candidate, 1);
65
+ }
66
+ }
67
+ for (const candidate of candidates) {
68
+ tryAdd(candidate, maxSnippetsForFile(candidate.snippet.filePath, request.query));
69
+ }
70
+ return {
71
+ snippets: selected.sort((a, b) => a.index - b.index).map((candidate) => candidate.snippet),
72
+ usedChars
73
+ };
74
+ }
75
+ function missingEvidenceFor(snippets, metadata) {
76
+ const missing = [];
77
+ if (snippets.length === 0)
78
+ missing.push("No indexed context matched the query.");
79
+ if (metadata.staleFiles?.length)
80
+ missing.push(`Stale indexed files excluded: ${metadata.staleFiles.slice(0, 8).join(", ")}.`);
81
+ if (metadata.pendingFiles?.length)
82
+ missing.push(`Pending files need indexing: ${metadata.pendingFiles.slice(0, 8).join(", ")}.`);
83
+ if (metadata.burstMode)
84
+ missing.push(`Watcher burst mode is active; ${metadata.droppedEvents ?? 0} event(s) were dropped or compressed.`);
85
+ return missing;
86
+ }
87
+ function estimateSnippetCost(snippet) {
88
+ return snippet.filePath.length + snippet.reason.length + snippet.content.length + 80;
89
+ }
90
+ function maxSnippetsForFile(filePath, query) {
91
+ const kind = classifyEvidencePath(filePath);
92
+ if (kind === "implementation")
93
+ return 3;
94
+ return isExplicitSupportingEvidenceQuery(query) ? 2 : 1;
95
+ }
96
+ function confidenceFor(snippets) {
97
+ if (snippets.length >= 3 && (snippets[0]?.score ?? 0) > 1)
98
+ return "high";
99
+ if (snippets.length > 0)
100
+ return "medium";
101
+ return "low";
102
+ }
103
+ function relationshipEvidence(edges, ownerChain) {
104
+ if (ownerChain.length === 0)
105
+ return [];
106
+ const ownerSet = new Set(ownerChain);
107
+ return edges
108
+ .filter((edge) => typeof edge.metadata?.sourceFile === "string" && ownerSet.has(edge.metadata.sourceFile))
109
+ .slice(0, 12)
110
+ .map((edge) => ({
111
+ source: String(edge.metadata?.sourceFile ?? edge.sourceId),
112
+ target: String(edge.metadata?.targetName ?? edge.metadata?.source ?? edge.targetId),
113
+ kind: edge.kind,
114
+ reason: `Graph ${edge.kind} edge from indexed structure`
115
+ }));
116
+ }
117
+ function ownerNodes(snippets) {
118
+ const byFile = new Map();
119
+ for (const snippet of snippets) {
120
+ const current = byFile.get(snippet.filePath) ?? {
121
+ filePath: snippet.filePath,
122
+ role: snippet.role,
123
+ reason: snippet.reason,
124
+ score: 0,
125
+ symbols: []
126
+ };
127
+ current.score += snippet.score;
128
+ current.reason = current.reason.length <= snippet.reason.length ? current.reason : snippet.reason;
129
+ addOwnerSymbol(current, snippet);
130
+ byFile.set(snippet.filePath, current);
131
+ }
132
+ return [...byFile.values()].sort((a, b) => b.score - a.score);
133
+ }
134
+ function addOwnerSymbol(owner, snippet) {
135
+ const symbol = symbolFromSnippet(snippet);
136
+ if (!symbol)
137
+ return;
138
+ const key = `${symbol.kind}\0${symbol.name}\0${symbol.startLine}\0${symbol.endLine}`;
139
+ const exists = owner.symbols.some((existing) => `${existing.kind}\0${existing.name}\0${existing.startLine}\0${existing.endLine}` === key);
140
+ if (!exists)
141
+ owner.symbols.push(symbol);
142
+ }
143
+ function symbolFromSnippet(snippet) {
144
+ const match = /^(function|class|method|type|variable|file):\s+(.+)$/.exec(snippet.role);
145
+ if (!match)
146
+ return undefined;
147
+ const [, kind, name] = match;
148
+ return {
149
+ name,
150
+ kind,
151
+ startLine: snippet.startLine,
152
+ endLine: snippet.endLine
153
+ };
154
+ }
155
+ function topologyEdges(edges, ownerPaths) {
156
+ const ownerSet = new Set(ownerPaths);
157
+ const seen = new Set();
158
+ const output = [];
159
+ const candidates = edges
160
+ .filter((edge) => isTopologyEdge(edge.kind) && typeof edge.metadata?.sourceFile === "string" && ownerSet.has(edge.metadata.sourceFile))
161
+ .sort((a, b) => topologyEdgePriority(b) - topologyEdgePriority(a));
162
+ for (const edge of candidates) {
163
+ const topologyEdge = toTopologyEdge(edge);
164
+ const key = topologyEdgeKey(topologyEdge);
165
+ if (seen.has(key))
166
+ continue;
167
+ seen.add(key);
168
+ output.push(topologyEdge);
169
+ if (output.length >= 12)
170
+ break;
171
+ }
172
+ return output;
173
+ }
174
+ function toTopologyEdge(edge) {
175
+ return {
176
+ from: String(edge.metadata?.sourceFile ?? edge.sourceId),
177
+ to: String(edge.metadata?.route ?? edge.metadata?.targetName ?? edge.targetId),
178
+ edge: edge.kind,
179
+ confidence: confidenceForEdge(edge),
180
+ reason: reasonForEdge(edge),
181
+ sourceFile: typeof edge.metadata?.sourceFile === "string" ? edge.metadata.sourceFile : undefined,
182
+ targetFile: typeof edge.metadata?.targetFile === "string" ? edge.metadata.targetFile : undefined
183
+ };
184
+ }
185
+ function topologyEdgeKey(edge) {
186
+ return [edge.from, edge.to, edge.edge, edge.sourceFile ?? "", edge.targetFile ?? ""].join("\0");
187
+ }
188
+ function isTopologyEdge(kind) {
189
+ return kind === "calls"
190
+ || kind === "calls_api"
191
+ || kind === "routes_to"
192
+ || kind === "handles_webhook"
193
+ || kind === "handles_event"
194
+ || kind === "tested_by"
195
+ || kind === "uses_middleware"
196
+ || kind === "reads_from"
197
+ || kind === "writes_to";
198
+ }
199
+ function topologyEdgePriority(edge) {
200
+ if (edge.kind === "handles_webhook")
201
+ return 100;
202
+ if (edge.kind === "calls_api")
203
+ return 90;
204
+ if (edge.kind === "routes_to")
205
+ return 85;
206
+ if (edge.kind === "uses_middleware")
207
+ return 84;
208
+ if (edge.kind === "tested_by")
209
+ return 82;
210
+ if (edge.kind === "writes_to")
211
+ return 78;
212
+ if (edge.kind === "reads_from")
213
+ return 76;
214
+ if (edge.kind === "handles_event")
215
+ return 74;
216
+ if (edge.metadata?.resolution === "resolved_lsp")
217
+ return 80;
218
+ if (edge.metadata?.resolution === "resolved")
219
+ return 75;
220
+ if (edge.kind === "calls")
221
+ return 20;
222
+ return 10;
223
+ }
224
+ function confidenceForEdge(edge) {
225
+ if (typeof edge.metadata?.framework === "string")
226
+ return "high";
227
+ if (edge.metadata?.resolution === "test_import")
228
+ return "high";
229
+ if (edge.metadata?.resolution === "resource_static" || edge.metadata?.resolution === "event_static")
230
+ return "high";
231
+ if (edge.metadata?.resolution === "resolved" || edge.metadata?.resolution === "resolved_lsp")
232
+ return "high";
233
+ if (edge.metadata?.targetName)
234
+ return "medium";
235
+ return "low";
236
+ }
237
+ function reasonForEdge(edge) {
238
+ if (edge.kind === "calls_api")
239
+ return "Static framework topology edge from client API call to route handler.";
240
+ if (edge.kind === "routes_to")
241
+ return "Framework route-to-service edge derived from resolved call graph.";
242
+ if (edge.kind === "handles_webhook")
243
+ return "Framework webhook route recognized from route path.";
244
+ if (edge.kind === "tested_by")
245
+ return "Test coverage edge derived from a resolved test import.";
246
+ if (edge.kind === "uses_middleware")
247
+ return "Framework middleware usage edge derived from route and middleware files.";
248
+ if (edge.kind === "reads_from")
249
+ return "Resource read edge derived from a static data-access call.";
250
+ if (edge.kind === "writes_to")
251
+ return "Resource write edge derived from a static data-access call.";
252
+ if (edge.kind === "handles_event")
253
+ return "Event handler edge derived from a static event subscription.";
254
+ if (edge.metadata?.resolution === "resolved")
255
+ return "Resolved AST import/export call edge.";
256
+ if (edge.metadata?.resolution === "resolved_lsp")
257
+ return "Resolved TypeScript Language Service definition edge.";
258
+ return "AST call edge; may be unresolved until import/LSP resolution lands.";
259
+ }
260
+ function freshnessFor(metadata) {
261
+ return {
262
+ projectId: metadata.projectId,
263
+ indexGeneration: metadata.indexGeneration ?? 1,
264
+ indexedAtMs: metadata.indexedAtMs,
265
+ staleFiles: metadata.staleFiles ?? [],
266
+ pendingFiles: metadata.pendingFiles ?? [],
267
+ indexingFiles: metadata.indexingFiles ?? [],
268
+ skippedFiles: metadata.skippedFiles,
269
+ dirtyFiles: metadata.dirtyFiles ?? [],
270
+ burstMode: metadata.burstMode ?? false,
271
+ droppedEvents: metadata.droppedEvents ?? 0
272
+ };
273
+ }
274
+ function briefFor(query, mode, confidence, owners) {
275
+ const ownerText = owners.length > 0 ? owners.slice(0, 3).map((owner) => owner.filePath).join(", ") : "no indexed owner";
276
+ return `${mode} context for "${query}" (${confidence} confidence). Primary owner evidence: ${ownerText}.`;
277
+ }
@@ -0,0 +1,6 @@
1
+ import type { CodeChunk, ContextMode, ExpansionLevel } from "../core/types.js";
2
+ export interface ExpansionDecision {
3
+ expansionLevel: ExpansionLevel;
4
+ focusLine?: number;
5
+ }
6
+ export declare function chooseExpansion(chunk: CodeChunk, query: string, mode: Exclude<ContextMode, "auto">): ExpansionDecision;
@@ -0,0 +1,49 @@
1
+ const LARGE_CHUNK_LINES = 80;
2
+ export function chooseExpansion(chunk, query, mode) {
3
+ const originalLineCount = lineCount(chunk.content);
4
+ if (explicitFullRequest(query))
5
+ return { expansionLevel: "full_body" };
6
+ if (originalLineCount <= LARGE_CHUNK_LINES)
7
+ return { expansionLevel: "focused_body" };
8
+ const focusLine = bestMatchingLine(chunk.content, query);
9
+ if (focusLine !== undefined && shouldFocusLargeChunk(chunk, query, mode)) {
10
+ return { expansionLevel: "focused_body", focusLine };
11
+ }
12
+ if (mode === "debug" && focusLine !== undefined)
13
+ return { expansionLevel: "focused_body", focusLine };
14
+ return { expansionLevel: "skeleton" };
15
+ }
16
+ function explicitFullRequest(query) {
17
+ return /\b(full body|full source|entire file|完整|全部源码)\b/i.test(query);
18
+ }
19
+ function shouldFocusLargeChunk(chunk, query, mode) {
20
+ if (mode === "debug" || mode === "review")
21
+ return true;
22
+ if (chunk.symbolName && query.toLowerCase().includes(chunk.symbolName.toLowerCase()))
23
+ return true;
24
+ return false;
25
+ }
26
+ function bestMatchingLine(content, query) {
27
+ const terms = queryTerms(query);
28
+ if (terms.length === 0)
29
+ return undefined;
30
+ const lines = content.split(/\r?\n/);
31
+ let best;
32
+ for (let index = 0; index < lines.length; index += 1) {
33
+ const lower = lines[index].toLowerCase();
34
+ const score = terms.reduce((sum, term) => lower.includes(term) ? sum + term.length : sum, 0);
35
+ if (score > 0 && (!best || score > best.score))
36
+ best = { line: index + 1, score };
37
+ }
38
+ return best?.line;
39
+ }
40
+ function queryTerms(query) {
41
+ return query
42
+ .toLowerCase()
43
+ .split(/[^a-z0-9_:-]+/i)
44
+ .map((part) => part.trim())
45
+ .filter((part) => part.length >= 3);
46
+ }
47
+ function lineCount(content) {
48
+ return content.length === 0 ? 0 : content.split(/\r?\n/).length;
49
+ }
@@ -0,0 +1,2 @@
1
+ import type { CodeChunk } from "../core/types.js";
2
+ export declare function skeletonizeChunk(chunk: CodeChunk): string;
@@ -0,0 +1,79 @@
1
+ import ts from "typescript";
2
+ export function skeletonizeChunk(chunk) {
3
+ if (chunk.language !== "typescript" && chunk.language !== "javascript")
4
+ return genericSkeleton(chunk.content);
5
+ const sourceFile = ts.createSourceFile(chunk.filePath, chunk.content, ts.ScriptTarget.Latest, true, scriptKindForPath(chunk.filePath));
6
+ const declarations = [];
7
+ for (const statement of sourceFile.statements) {
8
+ const rendered = renderDeclaration(sourceFile, statement);
9
+ if (rendered)
10
+ declarations.push(rendered);
11
+ }
12
+ if (declarations.length === 0 && chunk.symbolName)
13
+ return `${chunk.kind} ${chunk.symbolName} { ... }`;
14
+ return declarations.length > 0 ? declarations.join("\n\n") : genericSkeleton(chunk.content);
15
+ }
16
+ function renderDeclaration(sourceFile, node) {
17
+ if (ts.isImportDeclaration(node) || ts.isExportDeclaration(node))
18
+ return firstLine(sourceFile, node);
19
+ if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node))
20
+ return sourceText(sourceFile, node);
21
+ if (ts.isFunctionDeclaration(node))
22
+ return `${leadingComment(sourceFile, node)}${functionSignature(sourceFile, node)} { ... }`.trim();
23
+ if (ts.isClassDeclaration(node))
24
+ return renderClass(sourceFile, node);
25
+ if (ts.isVariableStatement(node))
26
+ return `${leadingComment(sourceFile, node)}${firstLine(sourceFile, node).replace(/=\s*.+;?$/, "= ...;")}`.trim();
27
+ return undefined;
28
+ }
29
+ function renderClass(sourceFile, node) {
30
+ const name = node.name?.text ?? "AnonymousClass";
31
+ const heritage = node.heritageClauses?.map((clause) => sourceText(sourceFile, clause)).join(" ") ?? "";
32
+ const members = node.members
33
+ .map((member) => renderClassMember(sourceFile, member))
34
+ .filter(Boolean)
35
+ .map((member) => ` ${member}`)
36
+ .join("\n");
37
+ return `${leadingComment(sourceFile, node)}class ${name}${heritage ? ` ${heritage}` : ""} {\n${members}\n}`.trim();
38
+ }
39
+ function renderClassMember(sourceFile, member) {
40
+ if (ts.isConstructorDeclaration(member))
41
+ return `${firstLine(sourceFile, member).replace(/\{\s*$/, "").trim()} { ... }`;
42
+ if (ts.isMethodDeclaration(member))
43
+ return `${firstLine(sourceFile, member).replace(/\{\s*$/, "").trim()} { ... }`;
44
+ if (ts.isPropertyDeclaration(member))
45
+ return firstLine(sourceFile, member).replace(/=\s*.+;?$/, "= ...;");
46
+ return undefined;
47
+ }
48
+ function functionSignature(sourceFile, node) {
49
+ const text = sourceText(sourceFile, node);
50
+ const bodyStart = text.indexOf("{");
51
+ return (bodyStart >= 0 ? text.slice(0, bodyStart) : text).trim();
52
+ }
53
+ function genericSkeleton(content) {
54
+ const first = content.split(/\r?\n/).find((line) => line.trim())?.trim() ?? "[empty chunk]";
55
+ return `${first}\n...`;
56
+ }
57
+ function leadingComment(sourceFile, node) {
58
+ const comments = ts.getLeadingCommentRanges(sourceFile.text, node.getFullStart()) ?? [];
59
+ const rendered = comments
60
+ .map((comment) => sourceFile.text.slice(comment.pos, comment.end).trim())
61
+ .filter((comment) => comment.startsWith("/**"))
62
+ .join("\n");
63
+ return rendered ? `${rendered}\n` : "";
64
+ }
65
+ function firstLine(sourceFile, node) {
66
+ return sourceText(sourceFile, node).split(/\r?\n/, 1)[0]?.trim() ?? "";
67
+ }
68
+ function sourceText(sourceFile, node) {
69
+ return sourceFile.text.slice(node.getStart(sourceFile), node.getEnd()).trim();
70
+ }
71
+ function scriptKindForPath(filePath) {
72
+ if (filePath.endsWith(".tsx"))
73
+ return ts.ScriptKind.TSX;
74
+ if (filePath.endsWith(".jsx"))
75
+ return ts.ScriptKind.JSX;
76
+ if (filePath.endsWith(".js") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs"))
77
+ return ts.ScriptKind.JS;
78
+ return ts.ScriptKind.TS;
79
+ }
@@ -0,0 +1,2 @@
1
+ import type { ContextMode, ContextSnippet, SearchHit } from "../core/types.js";
2
+ export declare function renderSnippet(hit: SearchHit, query: string, mode: Exclude<ContextMode, "auto">): ContextSnippet;
@@ -0,0 +1,67 @@
1
+ import { chooseExpansion } from "./expansion-policy.js";
2
+ import { skeletonizeChunk } from "./skeletonizer.js";
3
+ const FOCUS_WINDOW_LINES = 28;
4
+ export function renderSnippet(hit, query, mode) {
5
+ const originalLineCount = lineCount(hit.chunk.content);
6
+ const decision = chooseExpansion(hit.chunk, query, mode);
7
+ const rendered = renderContent(hit, decision.expansionLevel, decision.focusLine);
8
+ const returnedLineCount = lineCount(rendered);
9
+ return {
10
+ filePath: hit.chunk.filePath,
11
+ startLine: renderedStartLine(hit, decision.focusLine),
12
+ endLine: renderedEndLine(hit, rendered, decision.focusLine),
13
+ content: rendered,
14
+ score: hit.score,
15
+ reason: hit.reason,
16
+ role: roleForHit(hit),
17
+ expansionLevel: decision.expansionLevel,
18
+ originalLineCount,
19
+ returnedLineCount,
20
+ elidedLineCount: Math.max(0, originalLineCount - returnedLineCount)
21
+ };
22
+ }
23
+ function renderContent(hit, expansionLevel, focusLine) {
24
+ if (expansionLevel === "full_body")
25
+ return hit.chunk.content;
26
+ if (expansionLevel === "skeleton")
27
+ return skeletonizeChunk(hit.chunk);
28
+ if (expansionLevel === "file_card")
29
+ return fileCard(hit);
30
+ if (focusLine !== undefined)
31
+ return focusedWindow(hit.chunk.content, focusLine);
32
+ return hit.chunk.content;
33
+ }
34
+ function focusedWindow(content, focusLine) {
35
+ const lines = content.split(/\r?\n/);
36
+ const half = Math.floor(FOCUS_WINDOW_LINES / 2);
37
+ const start = Math.max(0, focusLine - 1 - half);
38
+ const end = Math.min(lines.length, start + FOCUS_WINDOW_LINES);
39
+ const prefix = start > 0 ? ["..."] : [];
40
+ const suffix = end < lines.length ? ["..."] : [];
41
+ return [...prefix, ...lines.slice(start, end), ...suffix].join("\n");
42
+ }
43
+ function renderedStartLine(hit, focusLine) {
44
+ if (focusLine === undefined)
45
+ return hit.chunk.startLine;
46
+ return hit.chunk.startLine + Math.max(0, focusLine - 1 - Math.floor(FOCUS_WINDOW_LINES / 2));
47
+ }
48
+ function renderedEndLine(hit, content, focusLine) {
49
+ if (focusLine === undefined)
50
+ return hit.chunk.startLine + Math.max(0, lineCount(content) - 1);
51
+ return renderedStartLine(hit, focusLine) + Math.max(0, lineCount(content) - 1);
52
+ }
53
+ function fileCard(hit) {
54
+ return [
55
+ `${hit.chunk.kind}: ${hit.chunk.symbolName ?? hit.chunk.filePath}`,
56
+ `language: ${hit.chunk.language}`,
57
+ `lines: ${hit.chunk.startLine}-${hit.chunk.endLine}`
58
+ ].join("\n");
59
+ }
60
+ function roleForHit(hit) {
61
+ if (hit.chunk.symbolName)
62
+ return `${hit.chunk.kind}: ${hit.chunk.symbolName}`;
63
+ return `${hit.chunk.kind} evidence`;
64
+ }
65
+ function lineCount(content) {
66
+ return content.length === 0 ? 0 : content.split(/\r?\n/).length;
67
+ }
@@ -0,0 +1,74 @@
1
+ import type { CodeChunk, CodeFile, ContextPack, ContextRequest, DiffReview, EdgeKind, GraphEdge, ImpactAnalysis, IndexStatus, IndexRefreshOptions, OwnerCandidate, RelatedTests, ReuseCandidateReport, ReuseCandidateRequest, RepoIndex, SearchHit, SearchQuery, SymbolNode, TopologyMap, TopologyMapRequest, TraceFlow, VerifiedCodeSubgraph, VerifiedSubgraphRequest, ProjectIdentity, WatcherEventOptions, WatcherState } from "./types.js";
2
+ export interface EmbeddingProvider {
3
+ readonly dimensions?: number;
4
+ embed(text: string): Promise<number[]>;
5
+ embedBatch?(texts: string[]): Promise<number[][]>;
6
+ }
7
+ export interface GraphStore {
8
+ close?(): void;
9
+ getProjectByRoot?(repoRoot: string): Promise<ProjectIdentity | undefined>;
10
+ listProjects?(): Promise<ProjectIdentity[]>;
11
+ getIndexGeneration?(repoRoot: string): Promise<number>;
12
+ recordFileEvents?(repoRoot: string, filePaths: string[], options?: WatcherEventOptions): Promise<WatcherState>;
13
+ getWatcherState?(repoRoot: string): Promise<WatcherState>;
14
+ markDirtyFilesIndexing?(repoRoot: string, filePaths: string[]): Promise<WatcherState>;
15
+ markDirtyFilesDeadLetter?(repoRoot: string, filePaths: string[], reason: string): Promise<WatcherState>;
16
+ clearDirtyFiles?(repoRoot: string, filePaths?: string[]): Promise<void>;
17
+ needsRebuild?(repoRoot: string, projectId: string): Promise<boolean>;
18
+ resetRepo(repoRoot: string): Promise<void>;
19
+ upsertIndex(index: RepoIndex): Promise<void>;
20
+ getFiles(repoRoot: string): Promise<CodeFile[]>;
21
+ getChunks(repoRoot: string): Promise<CodeChunk[]>;
22
+ getSkippedFiles(repoRoot: string): Promise<Array<{
23
+ filePath: string;
24
+ reason: string;
25
+ }>>;
26
+ getSymbols(repoRoot: string): Promise<SymbolNode[]>;
27
+ getEdges(repoRoot: string, kind?: EdgeKind): Promise<GraphEdge[]>;
28
+ findSymbol(repoRoot: string, name: string): Promise<SymbolNode[]>;
29
+ explainFile(repoRoot: string, filePath: string): Promise<{
30
+ file?: CodeFile;
31
+ chunks: CodeChunk[];
32
+ symbols: SymbolNode[];
33
+ }>;
34
+ searchText(query: SearchQuery): Promise<SearchHit[]>;
35
+ findOwner(repoRoot: string, query: string, limit?: number): Promise<OwnerCandidate[]>;
36
+ impactAnalysis(repoRoot: string, target: string): Promise<ImpactAnalysis>;
37
+ relatedTests(repoRoot: string, target: string): Promise<RelatedTests>;
38
+ traceFlow(repoRoot: string, entry: string, maxSteps?: number): Promise<TraceFlow>;
39
+ reviewDiff(repoRoot: string, diff?: string, changedFiles?: string[]): Promise<DiffReview>;
40
+ }
41
+ export interface SemanticStore {
42
+ needsRebuild?(repoRoot: string, projectId: string): Promise<boolean>;
43
+ resetRepo(repoRoot: string): Promise<void>;
44
+ deleteFile?(repoRoot: string, projectId: string, filePath: string): Promise<void>;
45
+ upsertChunks(chunks: CodeChunk[], provider: EmbeddingProvider, generation?: number): Promise<void>;
46
+ search(query: SearchQuery, provider: EmbeddingProvider): Promise<SearchHit[]>;
47
+ }
48
+ export interface Indexer {
49
+ indexRepo(repoRoot: string, projectId: string, project?: ProjectIdentity, options?: IndexRefreshOptions): Promise<RepoIndex>;
50
+ }
51
+ export interface ContextEngine {
52
+ indexRepo(repoRoot: string, options?: IndexRefreshOptions): Promise<RepoIndex>;
53
+ refreshIndex(repoRoot: string | undefined, options?: IndexRefreshOptions): Promise<RepoIndex>;
54
+ indexStatus(repoRoot: string | undefined): Promise<IndexStatus>;
55
+ recordFileEvents(repoRoot: string | undefined, filePaths: string[], options?: WatcherEventOptions): Promise<WatcherState>;
56
+ markDirtyFilesIndexing(repoRoot: string | undefined, filePaths: string[]): Promise<WatcherState>;
57
+ markDirtyFilesDeadLetter(repoRoot: string | undefined, filePaths: string[], reason: string): Promise<WatcherState>;
58
+ searchCode(query: SearchQuery): Promise<SearchHit[]>;
59
+ getContext(request: ContextRequest): Promise<ContextPack>;
60
+ verifiedSubgraph(request: VerifiedSubgraphRequest): Promise<VerifiedCodeSubgraph>;
61
+ topologyMap(request: TopologyMapRequest): Promise<TopologyMap>;
62
+ findSymbol(repoRoot: string | undefined, name: string): Promise<SymbolNode[]>;
63
+ explainFile(repoRoot: string | undefined, filePath: string): Promise<{
64
+ file?: CodeFile;
65
+ chunks: CodeChunk[];
66
+ symbols: SymbolNode[];
67
+ }>;
68
+ findOwner(repoRoot: string | undefined, query: string, limit?: number): Promise<OwnerCandidate[]>;
69
+ findReuseCandidates(request: ReuseCandidateRequest): Promise<ReuseCandidateReport>;
70
+ impactAnalysis(repoRoot: string | undefined, target: string): Promise<ImpactAnalysis>;
71
+ relatedTests(repoRoot: string | undefined, target: string): Promise<RelatedTests>;
72
+ traceFlow(repoRoot: string | undefined, entry: string, maxSteps?: number): Promise<TraceFlow>;
73
+ reviewDiff(repoRoot: string | undefined, diff?: string, changedFiles?: string[]): Promise<DiffReview>;
74
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,64 @@
1
+ import type { ContextEngine, EmbeddingProvider, GraphStore, SemanticStore } from "./contracts.js";
2
+ import type { ContextPack, ContextRequest, DiffReview, GraphEdge, ImpactAnalysis, IndexRefreshOptions, IndexStatus, OwnerCandidate, RelatedTests, ReuseCandidateReport, ReuseCandidateRequest, RepoIndex, SearchHit, SearchQuery, SymbolNode, TopologyMap, TopologyMapRequest, TraceFlow, VerifiedCodeSubgraph, VerifiedSubgraphRequest, WatcherEventOptions, WatcherState } from "./types.js";
3
+ import { type HybridSearchDiagnostics } from "../retrieval/hybrid-retriever.js";
4
+ import type { CodeFile, CodeChunk } from "./types.js";
5
+ export interface RagCodeEngineOptions {
6
+ graphStore?: GraphStore;
7
+ semanticStore?: SemanticStore;
8
+ embeddingProvider?: EmbeddingProvider;
9
+ workspaceRoots?: string[];
10
+ cwd?: string;
11
+ env?: NodeJS.ProcessEnv;
12
+ }
13
+ export declare class RagCodeEngine implements ContextEngine {
14
+ private readonly graphStore;
15
+ private readonly semanticStore;
16
+ private readonly embeddingProvider;
17
+ private readonly projectRegistry;
18
+ private readonly workspaceResolver;
19
+ private readonly contextBuilder;
20
+ private readonly indexedAtByRepo;
21
+ private readonly cwd;
22
+ private readonly workspaceRoots;
23
+ private readonly hydratedRoots;
24
+ private hydratedAllProjects;
25
+ constructor(options?: RagCodeEngineOptions);
26
+ close(): void;
27
+ indexRepo(repoRoot: string, options?: IndexRefreshOptions): Promise<RepoIndex>;
28
+ refreshIndex(repoRoot: string | undefined, options?: IndexRefreshOptions): Promise<RepoIndex>;
29
+ indexStatus(repoRoot: string | undefined): Promise<IndexStatus>;
30
+ recordFileEvents(repoRoot: string | undefined, filePaths: string[], options?: WatcherEventOptions): Promise<WatcherState>;
31
+ markDirtyFilesIndexing(repoRoot: string | undefined, filePaths: string[]): Promise<WatcherState>;
32
+ markDirtyFilesDeadLetter(repoRoot: string | undefined, filePaths: string[], reason: string): Promise<WatcherState>;
33
+ searchCode(query: SearchQuery): Promise<SearchHit[]>;
34
+ searchCodeWithDiagnostics(query: SearchQuery): Promise<{
35
+ hits: SearchHit[];
36
+ diagnostics: HybridSearchDiagnostics;
37
+ }>;
38
+ getContext(request: ContextRequest): Promise<ContextPack>;
39
+ verifiedSubgraph(request: VerifiedSubgraphRequest): Promise<VerifiedCodeSubgraph>;
40
+ topologyMap(request: TopologyMapRequest): Promise<TopologyMap>;
41
+ findSymbol(repoRoot: string | undefined, name: string): Promise<SymbolNode[]>;
42
+ graphSnapshot(repoRoot: string | undefined): Promise<{
43
+ symbols: SymbolNode[];
44
+ edges: GraphEdge[];
45
+ }>;
46
+ explainFile(repoRoot: string | undefined, filePath: string): Promise<{
47
+ file?: CodeFile;
48
+ chunks: CodeChunk[];
49
+ symbols: SymbolNode[];
50
+ }>;
51
+ findOwner(repoRoot: string | undefined, query: string, limit?: number): Promise<OwnerCandidate[]>;
52
+ findReuseCandidates(request: ReuseCandidateRequest): Promise<ReuseCandidateReport>;
53
+ impactAnalysis(repoRoot: string | undefined, target: string): Promise<ImpactAnalysis>;
54
+ relatedTests(repoRoot: string | undefined, target: string): Promise<RelatedTests>;
55
+ traceFlow(repoRoot: string | undefined, entry: string, maxSteps?: number): Promise<TraceFlow>;
56
+ reviewDiff(repoRoot: string | undefined, diff?: string, changedFiles?: string[]): Promise<DiffReview>;
57
+ private resolveWorkspace;
58
+ private hydrateForInput;
59
+ private hydrateProjectByRoot;
60
+ private hydrateAllPersistedProjects;
61
+ private rememberHydratedProject;
62
+ private searchWithFreshness;
63
+ private computeFreshness;
64
+ }