@oisheek_c/codeforge 1.0.1
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 +0 -0
- package/README.md +7 -0
- package/apps/cli/bin/codeforge.cmd +2 -0
- package/apps/cli/src/bootstrap.js +115 -0
- package/apps/cli/src/cli.js +189 -0
- package/apps/cli/src/dev/git.js +17 -0
- package/apps/cli/src/dev/index.js +68 -0
- package/apps/cli/src/dev/provider.js +48 -0
- package/apps/cli/src/dev/scanner.js +14 -0
- package/apps/cli/src/index.js +31 -0
- package/bin/codeforge.js +2 -0
- package/package.json +63 -0
- package/packages/agent/contextBuilder.js +234 -0
- package/packages/agent/executor.js +1729 -0
- package/packages/agent/fallback.js +129 -0
- package/packages/agent/index.js +26 -0
- package/packages/agent/intent.js +73 -0
- package/packages/agent/modelSelector.js +469 -0
- package/packages/agent/planner.js +379 -0
- package/packages/agent/rag.js +412 -0
- package/packages/agent/ragSelector.js +570 -0
- package/packages/agent/router.js +121 -0
- package/packages/agent/thinking.js +54 -0
- package/packages/agent/toolCalls.js +258 -0
- package/packages/config/config.js +391 -0
- package/packages/config/defaults.js +72 -0
- package/packages/config/index.js +2 -0
- package/packages/core/approvalPolicy.js +140 -0
- package/packages/core/context.js +0 -0
- package/packages/core/message.js +0 -0
- package/packages/core/plan.js +0 -0
- package/packages/core/reasoning.js +0 -0
- package/packages/core/result.js +0 -0
- package/packages/core/retrieval.js +0 -0
- package/packages/core/tool.js +536 -0
- package/packages/git/branch.js +7 -0
- package/packages/git/commit.js +16 -0
- package/packages/git/diff.js +15 -0
- package/packages/git/git.js +35 -0
- package/packages/git/index.js +6 -0
- package/packages/git/remote.js +9 -0
- package/packages/git/status.js +11 -0
- package/packages/memory/index.js +0 -0
- package/packages/memory/project.js +0 -0
- package/packages/memory/session.js +0 -0
- package/packages/prompts/builder.js +43 -0
- package/packages/prompts/coder.js +38 -0
- package/packages/prompts/index.js +5 -0
- package/packages/prompts/planner.js +49 -0
- package/packages/prompts/reviewer.js +38 -0
- package/packages/prompts/system.js +94 -0
- package/packages/providers/index.js +31 -0
- package/packages/providers/openrouter.js +380 -0
- package/packages/reasoning/budget.js +0 -0
- package/packages/reasoning/chain.js +0 -0
- package/packages/reasoning/coder.js +0 -0
- package/packages/reasoning/evaluator.js +0 -0
- package/packages/reasoning/index.js +8 -0
- package/packages/reasoning/planner.js +0 -0
- package/packages/reasoning/reflector.js +0 -0
- package/packages/reasoning/reviewer.js +0 -0
- package/packages/reasoning/router.js +0 -0
- package/packages/retrieval/budget.js +92 -0
- package/packages/retrieval/cache.js +84 -0
- package/packages/retrieval/embeddings.js +147 -0
- package/packages/retrieval/graph.js +151 -0
- package/packages/retrieval/imports.js +74 -0
- package/packages/retrieval/index.js +9 -0
- package/packages/retrieval/parser/extractors/calls.js +43 -0
- package/packages/retrieval/parser/extractors/comments.js +49 -0
- package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
- package/packages/retrieval/parser/extractors/exports.js +71 -0
- package/packages/retrieval/parser/extractors/imports.js +40 -0
- package/packages/retrieval/parser/extractors/index.js +12 -0
- package/packages/retrieval/parser/extractors/metrics.js +78 -0
- package/packages/retrieval/parser/extractors/symbols.js +51 -0
- package/packages/retrieval/parser/extractors/todos.js +53 -0
- package/packages/retrieval/parser/helpers.js +138 -0
- package/packages/retrieval/parser/index.js +10 -0
- package/packages/retrieval/parser/languages/c.js +14 -0
- package/packages/retrieval/parser/languages/cpp.js +21 -0
- package/packages/retrieval/parser/languages/csharp.js +14 -0
- package/packages/retrieval/parser/languages/go.js +14 -0
- package/packages/retrieval/parser/languages/index.js +12 -0
- package/packages/retrieval/parser/languages/java.js +14 -0
- package/packages/retrieval/parser/languages/javascript.js +14 -0
- package/packages/retrieval/parser/languages/php.js +14 -0
- package/packages/retrieval/parser/languages/python.js +14 -0
- package/packages/retrieval/parser/languages/ruby.js +14 -0
- package/packages/retrieval/parser/languages/rust.js +14 -0
- package/packages/retrieval/parser/languages/swift.js +14 -0
- package/packages/retrieval/parser/languages/typescript.js +14 -0
- package/packages/retrieval/parser/languages.js +218 -0
- package/packages/retrieval/parser/parseFile.js +79 -0
- package/packages/retrieval/parser/parseRepository.js +78 -0
- package/packages/retrieval/parser/queries/c.js +148 -0
- package/packages/retrieval/parser/queries/cpp.js +198 -0
- package/packages/retrieval/parser/queries/csharp.js +151 -0
- package/packages/retrieval/parser/queries/go.js +124 -0
- package/packages/retrieval/parser/queries/index.js +35 -0
- package/packages/retrieval/parser/queries/java.js +128 -0
- package/packages/retrieval/parser/queries/javascript.js +181 -0
- package/packages/retrieval/parser/queries/php.js +140 -0
- package/packages/retrieval/parser/queries/python.js +110 -0
- package/packages/retrieval/parser/queries/ruby.js +105 -0
- package/packages/retrieval/parser/queries/rust.js +175 -0
- package/packages/retrieval/parser/queries/swift.js +134 -0
- package/packages/retrieval/parser/queries/typescript.js +202 -0
- package/packages/retrieval/parser/registry.js +107 -0
- package/packages/retrieval/parser/traverse.js +82 -0
- package/packages/retrieval/parser/types.js +239 -0
- package/packages/retrieval/parser.js +78 -0
- package/packages/retrieval/ranker.js +73 -0
- package/packages/retrieval/retriever.js +910 -0
- package/packages/retrieval/symbols.js +145 -0
- package/packages/scanner/detect.js +69 -0
- package/packages/scanner/ignore.js +12 -0
- package/packages/scanner/index.js +2 -0
- package/packages/scanner/root.js +21 -0
- package/packages/scanner/scanner.js +35 -0
- package/packages/scanner/tree.js +45 -0
- package/packages/terminal/activity.js +514 -0
- package/packages/terminal/banner.js +34 -0
- package/packages/terminal/colors.js +16 -0
- package/packages/terminal/dashboard.js +94 -0
- package/packages/terminal/index.js +19 -0
- package/packages/terminal/logger.js +43 -0
- package/packages/terminal/prompt.js +11 -0
- package/packages/terminal/renderer.js +1209 -0
- package/packages/terminal/spinner.js +9 -0
- package/packages/tools/editFile.js +261 -0
- package/packages/tools/filesystem.js +82 -0
- package/packages/tools/index.js +24 -0
- package/packages/tools/readFile.js +101 -0
- package/packages/tools/registry.js +227 -0
- package/packages/tools/searchFiles.js +322 -0
- package/packages/tools/shell.js +352 -0
- package/packages/tools/writeFile.js +212 -0
- package/packages/utils/constants.js +0 -0
- package/packages/utils/helpers.js +0 -0
- package/packages/utils/logger.js +0 -0
|
@@ -0,0 +1,910 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/retrieval/retriever.js
|
|
3
|
+
*
|
|
4
|
+
* Repository retrieval facade.
|
|
5
|
+
*
|
|
6
|
+
* Provides deterministic repository lookup plus lightweight
|
|
7
|
+
* lexical/structural retrieval for natural-language queries.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const DEFAULT_LIMIT = 8;
|
|
11
|
+
const DEFAULT_SOURCE_LENGTH = 4000;
|
|
12
|
+
|
|
13
|
+
const REPOSITORY_SCOPE_TERMS = new Set([
|
|
14
|
+
"project",
|
|
15
|
+
"repository",
|
|
16
|
+
"repo",
|
|
17
|
+
"codebase",
|
|
18
|
+
"architecture",
|
|
19
|
+
"structure",
|
|
20
|
+
"overview",
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
const STOP_WORDS = new Set([
|
|
24
|
+
"a",
|
|
25
|
+
"an",
|
|
26
|
+
"and",
|
|
27
|
+
"are",
|
|
28
|
+
"can",
|
|
29
|
+
"code",
|
|
30
|
+
"does",
|
|
31
|
+
"explain",
|
|
32
|
+
"for",
|
|
33
|
+
"how",
|
|
34
|
+
"in",
|
|
35
|
+
"is",
|
|
36
|
+
"me",
|
|
37
|
+
"of",
|
|
38
|
+
"please",
|
|
39
|
+
"show",
|
|
40
|
+
"tell",
|
|
41
|
+
"the",
|
|
42
|
+
"this",
|
|
43
|
+
"to",
|
|
44
|
+
"what",
|
|
45
|
+
"where",
|
|
46
|
+
"with",
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
function normalize(value) {
|
|
50
|
+
return String(value ?? "").toLowerCase();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tokenize(query) {
|
|
54
|
+
return normalize(query)
|
|
55
|
+
.split(/[^a-z0-9_$-]+/i)
|
|
56
|
+
.map((token) => token.trim())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getSearchTerms(query) {
|
|
61
|
+
const tokens = tokenize(query);
|
|
62
|
+
|
|
63
|
+
const filtered = tokens.filter(
|
|
64
|
+
(token) => !STOP_WORDS.has(token)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return filtered.length > 0
|
|
68
|
+
? filtered
|
|
69
|
+
: tokens;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const TERM_ALIASES = Object.freeze({
|
|
73
|
+
routing: [
|
|
74
|
+
"route",
|
|
75
|
+
"router",
|
|
76
|
+
"routing",
|
|
77
|
+
],
|
|
78
|
+
|
|
79
|
+
route: [
|
|
80
|
+
"route",
|
|
81
|
+
"router",
|
|
82
|
+
"routing",
|
|
83
|
+
],
|
|
84
|
+
|
|
85
|
+
router: [
|
|
86
|
+
"route",
|
|
87
|
+
"router",
|
|
88
|
+
"routing",
|
|
89
|
+
],
|
|
90
|
+
|
|
91
|
+
selection: [
|
|
92
|
+
"select",
|
|
93
|
+
"selection",
|
|
94
|
+
"selector",
|
|
95
|
+
],
|
|
96
|
+
|
|
97
|
+
selecting: [
|
|
98
|
+
"select",
|
|
99
|
+
"selection",
|
|
100
|
+
"selector",
|
|
101
|
+
],
|
|
102
|
+
|
|
103
|
+
provider: [
|
|
104
|
+
"provider",
|
|
105
|
+
"providers",
|
|
106
|
+
],
|
|
107
|
+
|
|
108
|
+
providers: [
|
|
109
|
+
"provider",
|
|
110
|
+
"providers",
|
|
111
|
+
],
|
|
112
|
+
|
|
113
|
+
fallback: [
|
|
114
|
+
"fallback",
|
|
115
|
+
"failover",
|
|
116
|
+
],
|
|
117
|
+
|
|
118
|
+
configuration: [
|
|
119
|
+
"config",
|
|
120
|
+
"configure",
|
|
121
|
+
"configuration",
|
|
122
|
+
],
|
|
123
|
+
|
|
124
|
+
configure: [
|
|
125
|
+
"config",
|
|
126
|
+
"configure",
|
|
127
|
+
"configuration",
|
|
128
|
+
],
|
|
129
|
+
|
|
130
|
+
authentication: [
|
|
131
|
+
"auth",
|
|
132
|
+
"authenticate",
|
|
133
|
+
"authentication",
|
|
134
|
+
],
|
|
135
|
+
|
|
136
|
+
authenticate: [
|
|
137
|
+
"auth",
|
|
138
|
+
"authenticate",
|
|
139
|
+
"authentication",
|
|
140
|
+
],
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
function expandSearchTerms(terms) {
|
|
144
|
+
const expanded = new Set();
|
|
145
|
+
|
|
146
|
+
for (const term of terms) {
|
|
147
|
+
expanded.add(term);
|
|
148
|
+
|
|
149
|
+
for (
|
|
150
|
+
const alias of
|
|
151
|
+
TERM_ALIASES[term] ?? []
|
|
152
|
+
) {
|
|
153
|
+
expanded.add(alias);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return [...expanded];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function isRepositoryScopeQuery(query) {
|
|
161
|
+
const tokens = tokenize(query);
|
|
162
|
+
|
|
163
|
+
return tokens.some((token) =>
|
|
164
|
+
REPOSITORY_SCOPE_TERMS.has(token)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const TEST_QUERY_TERMS = new Set([
|
|
169
|
+
"test",
|
|
170
|
+
"tests",
|
|
171
|
+
"testing",
|
|
172
|
+
"spec",
|
|
173
|
+
"specs",
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
const IMPLEMENTATION_QUERY_TERMS =
|
|
177
|
+
new Set([
|
|
178
|
+
"implementation",
|
|
179
|
+
"implement",
|
|
180
|
+
"source",
|
|
181
|
+
"logic",
|
|
182
|
+
"behavior",
|
|
183
|
+
"behaviour",
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
function isTestFile(file) {
|
|
187
|
+
const fileName =
|
|
188
|
+
normalize(file?.name);
|
|
189
|
+
|
|
190
|
+
const filePath =
|
|
191
|
+
normalize(file?.path);
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
filePath.includes("/tests/") ||
|
|
195
|
+
filePath.includes("\\tests\\") ||
|
|
196
|
+
filePath.includes("/test/") ||
|
|
197
|
+
filePath.includes("\\test\\") ||
|
|
198
|
+
fileName.endsWith(".test.js") ||
|
|
199
|
+
fileName.endsWith(".test.ts") ||
|
|
200
|
+
fileName.endsWith(".spec.js") ||
|
|
201
|
+
fileName.endsWith(".spec.ts")
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function getQueryPurpose(query) {
|
|
206
|
+
const tokens = tokenize(query);
|
|
207
|
+
|
|
208
|
+
if (
|
|
209
|
+
tokens.some((token) =>
|
|
210
|
+
TEST_QUERY_TERMS.has(token)
|
|
211
|
+
)
|
|
212
|
+
) {
|
|
213
|
+
return "test";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (
|
|
217
|
+
tokens.some((token) =>
|
|
218
|
+
IMPLEMENTATION_QUERY_TERMS.has(
|
|
219
|
+
token
|
|
220
|
+
)
|
|
221
|
+
)
|
|
222
|
+
) {
|
|
223
|
+
return "implementation";
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return "general";
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function purposeScore(
|
|
230
|
+
file,
|
|
231
|
+
queryPurpose
|
|
232
|
+
) {
|
|
233
|
+
const testFile =
|
|
234
|
+
isTestFile(file);
|
|
235
|
+
|
|
236
|
+
if (queryPurpose === "test") {
|
|
237
|
+
return testFile
|
|
238
|
+
? 60
|
|
239
|
+
: 0;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
queryPurpose ===
|
|
244
|
+
"implementation"
|
|
245
|
+
) {
|
|
246
|
+
return testFile
|
|
247
|
+
? -60
|
|
248
|
+
: 20;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return 0;
|
|
252
|
+
}
|
|
253
|
+
function purposeRank(
|
|
254
|
+
file,
|
|
255
|
+
queryPurpose
|
|
256
|
+
) {
|
|
257
|
+
const testFile =
|
|
258
|
+
isTestFile(file);
|
|
259
|
+
|
|
260
|
+
if (
|
|
261
|
+
queryPurpose ===
|
|
262
|
+
"implementation"
|
|
263
|
+
) {
|
|
264
|
+
return testFile
|
|
265
|
+
? 1
|
|
266
|
+
: 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (queryPurpose === "test") {
|
|
270
|
+
return testFile
|
|
271
|
+
? 0
|
|
272
|
+
: 1;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
function countOccurrences(text, term) {
|
|
278
|
+
if (!text || !term) {
|
|
279
|
+
return 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let count = 0;
|
|
283
|
+
let position = 0;
|
|
284
|
+
|
|
285
|
+
while (true) {
|
|
286
|
+
position = text.indexOf(term, position);
|
|
287
|
+
|
|
288
|
+
if (position === -1) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
count += 1;
|
|
293
|
+
position += term.length;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return count;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function sourceExcerpt(source, terms, maxLength) {
|
|
300
|
+
if (!source) {
|
|
301
|
+
return "";
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (source.length <= maxLength) {
|
|
305
|
+
return source;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const normalizedSource = normalize(source);
|
|
309
|
+
|
|
310
|
+
let matchIndex = -1;
|
|
311
|
+
|
|
312
|
+
for (const term of terms) {
|
|
313
|
+
const index = normalizedSource.indexOf(term);
|
|
314
|
+
|
|
315
|
+
if (
|
|
316
|
+
index !== -1 &&
|
|
317
|
+
(matchIndex === -1 || index < matchIndex)
|
|
318
|
+
) {
|
|
319
|
+
matchIndex = index;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (matchIndex === -1) {
|
|
324
|
+
return source.slice(0, maxLength);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const half = Math.floor(maxLength / 2);
|
|
328
|
+
|
|
329
|
+
let start = Math.max(0, matchIndex - half);
|
|
330
|
+
let end = Math.min(
|
|
331
|
+
source.length,
|
|
332
|
+
start + maxLength
|
|
333
|
+
);
|
|
334
|
+
|
|
335
|
+
if (end - start < maxLength) {
|
|
336
|
+
start = Math.max(0, end - maxLength);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const prefix = start > 0 ? "...\n" : "";
|
|
340
|
+
const suffix =
|
|
341
|
+
end < source.length ? "\n..." : "";
|
|
342
|
+
|
|
343
|
+
return (
|
|
344
|
+
prefix +
|
|
345
|
+
source.slice(start, end) +
|
|
346
|
+
suffix
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function symbolMatches(symbol, terms) {
|
|
351
|
+
const name = normalize(symbol?.name);
|
|
352
|
+
const qualifiedName = normalize(
|
|
353
|
+
symbol?.qualifiedName
|
|
354
|
+
);
|
|
355
|
+
const kind = normalize(symbol?.kind);
|
|
356
|
+
|
|
357
|
+
let score = 0;
|
|
358
|
+
|
|
359
|
+
for (const term of terms) {
|
|
360
|
+
if (name === term) {
|
|
361
|
+
score += 40;
|
|
362
|
+
} else if (name.includes(term)) {
|
|
363
|
+
score += 20;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (qualifiedName.includes(term)) {
|
|
367
|
+
score += 12;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
if (kind === term) {
|
|
371
|
+
score += 4;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return score;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function fileMatches(file, terms) {
|
|
379
|
+
const fileName = normalize(file?.name);
|
|
380
|
+
const filePath = normalize(file?.path);
|
|
381
|
+
const source = normalize(file?.source);
|
|
382
|
+
|
|
383
|
+
let score = 0;
|
|
384
|
+
|
|
385
|
+
for (const term of terms) {
|
|
386
|
+
if (fileName === term) {
|
|
387
|
+
score += 35;
|
|
388
|
+
} else if (fileName.includes(term)) {
|
|
389
|
+
score += 24;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (filePath.includes(term)) {
|
|
393
|
+
score += 14;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const sourceHits = Math.min(
|
|
397
|
+
countOccurrences(source, term),
|
|
398
|
+
5
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
score += sourceHits * 2;
|
|
402
|
+
|
|
403
|
+
for (const symbol of file?.symbols ?? []) {
|
|
404
|
+
score += symbolMatches(
|
|
405
|
+
symbol,
|
|
406
|
+
[term]
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
for (const item of file?.imports ?? []) {
|
|
411
|
+
if (
|
|
412
|
+
normalize(item?.source).includes(term)
|
|
413
|
+
) {
|
|
414
|
+
score += 8;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
for (const item of file?.exports ?? []) {
|
|
419
|
+
if (
|
|
420
|
+
normalize(item?.name).includes(term)
|
|
421
|
+
) {
|
|
422
|
+
score += 8;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return score;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
function architecturalScore(file) {
|
|
433
|
+
const fileName = normalize(file?.name);
|
|
434
|
+
const filePath = normalize(file?.path);
|
|
435
|
+
|
|
436
|
+
let score = 0;
|
|
437
|
+
|
|
438
|
+
const importantNames = [
|
|
439
|
+
"index.js",
|
|
440
|
+
"index.ts",
|
|
441
|
+
"main.js",
|
|
442
|
+
"main.ts",
|
|
443
|
+
"cli.js",
|
|
444
|
+
"cli.ts",
|
|
445
|
+
"bootstrap.js",
|
|
446
|
+
"bootstrap.ts",
|
|
447
|
+
"executor.js",
|
|
448
|
+
"executor.ts",
|
|
449
|
+
"package.json",
|
|
450
|
+
"readme.md",
|
|
451
|
+
];
|
|
452
|
+
|
|
453
|
+
if (importantNames.includes(fileName)) {
|
|
454
|
+
score += 30;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (
|
|
458
|
+
filePath.includes("/agent/") ||
|
|
459
|
+
filePath.includes("\\agent\\")
|
|
460
|
+
) {
|
|
461
|
+
score += 15;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (
|
|
465
|
+
filePath.includes("/retrieval/") ||
|
|
466
|
+
filePath.includes("\\retrieval\\")
|
|
467
|
+
) {
|
|
468
|
+
score += 12;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
if (
|
|
472
|
+
filePath.includes("/providers/") ||
|
|
473
|
+
filePath.includes("\\providers\\")
|
|
474
|
+
) {
|
|
475
|
+
score += 10;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (
|
|
479
|
+
filePath.includes("/cli/") ||
|
|
480
|
+
filePath.includes("\\cli\\")
|
|
481
|
+
) {
|
|
482
|
+
score += 10;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
score += Math.min(
|
|
486
|
+
file?.imports?.length ?? 0,
|
|
487
|
+
10
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
score += Math.min(
|
|
491
|
+
file?.exports?.length ?? 0,
|
|
492
|
+
10
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
score += Math.min(
|
|
496
|
+
file?.symbols?.length ?? 0,
|
|
497
|
+
10
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
return score;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function diversifyArchitecturalResults(
|
|
504
|
+
results,
|
|
505
|
+
query
|
|
506
|
+
) {
|
|
507
|
+
const tokens =
|
|
508
|
+
new Set(tokenize(query));
|
|
509
|
+
|
|
510
|
+
const concepts = [];
|
|
511
|
+
|
|
512
|
+
if (
|
|
513
|
+
tokens.has("routing") ||
|
|
514
|
+
tokens.has("route") ||
|
|
515
|
+
tokens.has("router")
|
|
516
|
+
) {
|
|
517
|
+
concepts.push([
|
|
518
|
+
"routing",
|
|
519
|
+
"route",
|
|
520
|
+
"router",
|
|
521
|
+
]);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
if (
|
|
525
|
+
tokens.has("fallback") ||
|
|
526
|
+
tokens.has("failover")
|
|
527
|
+
) {
|
|
528
|
+
concepts.push([
|
|
529
|
+
"fallback",
|
|
530
|
+
"failover",
|
|
531
|
+
]);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
if (
|
|
535
|
+
tokens.has("provider") ||
|
|
536
|
+
tokens.has("providers")
|
|
537
|
+
) {
|
|
538
|
+
concepts.push([
|
|
539
|
+
"provider",
|
|
540
|
+
"providers",
|
|
541
|
+
]);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (concepts.length < 2) {
|
|
545
|
+
return results;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/*
|
|
549
|
+
* Architectural diversification should ensure
|
|
550
|
+
* concept coverage without replacing the normal
|
|
551
|
+
* relevance ranking.
|
|
552
|
+
*
|
|
553
|
+
* First identify the strongest result for each
|
|
554
|
+
* requested architectural concept.
|
|
555
|
+
*/
|
|
556
|
+
const requiredPaths =
|
|
557
|
+
new Set();
|
|
558
|
+
|
|
559
|
+
for (const concept of concepts) {
|
|
560
|
+
const candidates =
|
|
561
|
+
results.filter(
|
|
562
|
+
(result) => {
|
|
563
|
+
const name =
|
|
564
|
+
normalize(
|
|
565
|
+
result?.name
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const path =
|
|
569
|
+
normalize(
|
|
570
|
+
result?.path
|
|
571
|
+
);
|
|
572
|
+
|
|
573
|
+
return concept.some(
|
|
574
|
+
(term) =>
|
|
575
|
+
name.includes(
|
|
576
|
+
term
|
|
577
|
+
) ||
|
|
578
|
+
path.includes(
|
|
579
|
+
term
|
|
580
|
+
)
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
);
|
|
584
|
+
|
|
585
|
+
if (candidates.length === 0) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/*
|
|
590
|
+
* Results have already been sorted by
|
|
591
|
+
* purpose rank and relevance score.
|
|
592
|
+
* Therefore the first matching candidate is
|
|
593
|
+
* the strongest representative.
|
|
594
|
+
*/
|
|
595
|
+
requiredPaths.add(
|
|
596
|
+
candidates[0].path
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (requiredPaths.size === 0) {
|
|
601
|
+
return results;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/*
|
|
605
|
+
* Preserve the original relevance ordering.
|
|
606
|
+
*
|
|
607
|
+
* Diversification is a coverage constraint,
|
|
608
|
+
* not a second ranking algorithm. Required
|
|
609
|
+
* architectural files therefore remain in
|
|
610
|
+
* their natural ranked positions.
|
|
611
|
+
*/
|
|
612
|
+
return results.map(
|
|
613
|
+
(result) => ({
|
|
614
|
+
...result,
|
|
615
|
+
|
|
616
|
+
architecturalRequired:
|
|
617
|
+
requiredPaths.has(
|
|
618
|
+
result.path
|
|
619
|
+
),
|
|
620
|
+
})
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function createFileResult({
|
|
625
|
+
file,
|
|
626
|
+
score,
|
|
627
|
+
terms,
|
|
628
|
+
maxSourceLength,
|
|
629
|
+
reason,
|
|
630
|
+
}) {
|
|
631
|
+
return {
|
|
632
|
+
type: "file",
|
|
633
|
+
path: file.path,
|
|
634
|
+
name: file.name,
|
|
635
|
+
language: file.language,
|
|
636
|
+
supported: file.supported,
|
|
637
|
+
score,
|
|
638
|
+
reason,
|
|
639
|
+
|
|
640
|
+
content: sourceExcerpt(
|
|
641
|
+
file.source,
|
|
642
|
+
terms,
|
|
643
|
+
maxSourceLength
|
|
644
|
+
),
|
|
645
|
+
|
|
646
|
+
symbols: (file.symbols ?? []).map(
|
|
647
|
+
(symbol) => ({
|
|
648
|
+
name: symbol.name,
|
|
649
|
+
qualifiedName:
|
|
650
|
+
symbol.qualifiedName,
|
|
651
|
+
kind: symbol.kind,
|
|
652
|
+
exported: symbol.exported,
|
|
653
|
+
signature: symbol.signature,
|
|
654
|
+
location: symbol.location,
|
|
655
|
+
})
|
|
656
|
+
),
|
|
657
|
+
|
|
658
|
+
imports: file.imports ?? [],
|
|
659
|
+
exports: file.exports ?? [],
|
|
660
|
+
|
|
661
|
+
metrics: file.metrics ?? null,
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
export class Retriever {
|
|
666
|
+
constructor({
|
|
667
|
+
repository,
|
|
668
|
+
symbols,
|
|
669
|
+
imports,
|
|
670
|
+
graph,
|
|
671
|
+
}) {
|
|
672
|
+
this.repository = repository;
|
|
673
|
+
this.symbols = symbols;
|
|
674
|
+
this.imports = imports;
|
|
675
|
+
this.graph = graph;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Repository object.
|
|
680
|
+
*/
|
|
681
|
+
getRepository() {
|
|
682
|
+
return this.repository;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* Find symbols by exact name.
|
|
687
|
+
*/
|
|
688
|
+
findSymbol(name) {
|
|
689
|
+
return this.symbols.find(name);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Find symbols by kind.
|
|
694
|
+
*/
|
|
695
|
+
findSymbolsByKind(kind) {
|
|
696
|
+
return this.symbols.kind(kind);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* All indexed symbols.
|
|
701
|
+
*/
|
|
702
|
+
getAllSymbols() {
|
|
703
|
+
return this.symbols.values();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Symbols in a file.
|
|
708
|
+
*/
|
|
709
|
+
getFileSymbols(path) {
|
|
710
|
+
return this.symbols.file(path);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Imports of a file.
|
|
715
|
+
*/
|
|
716
|
+
getFileImports(path) {
|
|
717
|
+
return this.imports.file(path);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Files importing a module.
|
|
722
|
+
*/
|
|
723
|
+
getImporters(module) {
|
|
724
|
+
return this.imports.source(module);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Dependency graph.
|
|
729
|
+
*/
|
|
730
|
+
getDependencies(path) {
|
|
731
|
+
return this.graph.getDependencies(path);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Reverse dependency graph.
|
|
736
|
+
*/
|
|
737
|
+
getDependents(module) {
|
|
738
|
+
return this.graph.getDependents(module);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Parsed file.
|
|
743
|
+
*/
|
|
744
|
+
getFile(path) {
|
|
745
|
+
return this.graph.getFile(path);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Check if a file exists.
|
|
750
|
+
*/
|
|
751
|
+
hasFile(path) {
|
|
752
|
+
return this.graph.hasFile(path);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Unified repository search.
|
|
757
|
+
*
|
|
758
|
+
* Current implementation combines:
|
|
759
|
+
* - exact symbol lookup
|
|
760
|
+
* - lexical file/path matching
|
|
761
|
+
* - symbol matching
|
|
762
|
+
* - import/export matching
|
|
763
|
+
* - lightweight source matching
|
|
764
|
+
* - repository-scope architectural ranking
|
|
765
|
+
*
|
|
766
|
+
* Semantic/vector retrieval can be added behind
|
|
767
|
+
* this same API later.
|
|
768
|
+
*/
|
|
769
|
+
search(query, options = {}) {
|
|
770
|
+
const {
|
|
771
|
+
limit = DEFAULT_LIMIT,
|
|
772
|
+
maxSourceLength =
|
|
773
|
+
DEFAULT_SOURCE_LENGTH,
|
|
774
|
+
} = options;
|
|
775
|
+
|
|
776
|
+
const files =
|
|
777
|
+
this.repository?.files ??
|
|
778
|
+
this.repository ??
|
|
779
|
+
[];
|
|
780
|
+
|
|
781
|
+
if (!Array.isArray(files)) {
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const terms = expandSearchTerms(
|
|
786
|
+
getSearchTerms(query)
|
|
787
|
+
);
|
|
788
|
+
if (terms.length === 0) {
|
|
789
|
+
return [];
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const repositoryScope =
|
|
793
|
+
isRepositoryScopeQuery(query);
|
|
794
|
+
|
|
795
|
+
const queryPurpose =
|
|
796
|
+
getQueryPurpose(query);
|
|
797
|
+
|
|
798
|
+
const scored = [];
|
|
799
|
+
|
|
800
|
+
for (const file of files) {
|
|
801
|
+
let score = fileMatches(
|
|
802
|
+
file,
|
|
803
|
+
terms
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
let reason = "lexical";
|
|
807
|
+
|
|
808
|
+
const purposeAdjustment =
|
|
809
|
+
purposeScore(
|
|
810
|
+
file,
|
|
811
|
+
queryPurpose
|
|
812
|
+
);
|
|
813
|
+
|
|
814
|
+
score += purposeAdjustment;
|
|
815
|
+
|
|
816
|
+
if (purposeAdjustment !== 0) {
|
|
817
|
+
reason =
|
|
818
|
+
queryPurpose === "test"
|
|
819
|
+
? "test"
|
|
820
|
+
: "implementation";
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (repositoryScope) {
|
|
824
|
+
const architecture =
|
|
825
|
+
architecturalScore(file);
|
|
826
|
+
|
|
827
|
+
score += architecture;
|
|
828
|
+
|
|
829
|
+
if (
|
|
830
|
+
architecture > 0 &&
|
|
831
|
+
purposeAdjustment === 0
|
|
832
|
+
) {
|
|
833
|
+
reason = "architecture";
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (score <= 0) {
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
const result =
|
|
842
|
+
createFileResult({
|
|
843
|
+
file,
|
|
844
|
+
score,
|
|
845
|
+
terms,
|
|
846
|
+
maxSourceLength,
|
|
847
|
+
reason,
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
result.purposeRank =
|
|
851
|
+
purposeRank(
|
|
852
|
+
file,
|
|
853
|
+
queryPurpose
|
|
854
|
+
);
|
|
855
|
+
|
|
856
|
+
scored.push(result);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
scored.sort((a, b) => {
|
|
860
|
+
if (
|
|
861
|
+
a.purposeRank !==
|
|
862
|
+
b.purposeRank
|
|
863
|
+
) {
|
|
864
|
+
return (
|
|
865
|
+
a.purposeRank -
|
|
866
|
+
b.purposeRank
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
if (b.score !== a.score) {
|
|
871
|
+
return b.score - a.score;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return String(a.path).localeCompare(
|
|
875
|
+
String(b.path)
|
|
876
|
+
);
|
|
877
|
+
});
|
|
878
|
+
const diversified =
|
|
879
|
+
repositoryScope
|
|
880
|
+
? diversifyArchitecturalResults(
|
|
881
|
+
scored,
|
|
882
|
+
query
|
|
883
|
+
)
|
|
884
|
+
: scored;
|
|
885
|
+
|
|
886
|
+
return diversified.slice(
|
|
887
|
+
0,
|
|
888
|
+
Math.max(1, limit)
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* Repository statistics.
|
|
894
|
+
*/
|
|
895
|
+
stats() {
|
|
896
|
+
return {
|
|
897
|
+
files: this.graph.fileCount(),
|
|
898
|
+
symbols: this.symbols.stats().total,
|
|
899
|
+
imports: this.imports.stats().modules,
|
|
900
|
+
graph: this.graph.stats(),
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Factory.
|
|
907
|
+
*/
|
|
908
|
+
export function createRetriever(indexes) {
|
|
909
|
+
return new Retriever(indexes);
|
|
910
|
+
}
|