@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.
Files changed (141) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +7 -0
  3. package/apps/cli/bin/codeforge.cmd +2 -0
  4. package/apps/cli/src/bootstrap.js +115 -0
  5. package/apps/cli/src/cli.js +189 -0
  6. package/apps/cli/src/dev/git.js +17 -0
  7. package/apps/cli/src/dev/index.js +68 -0
  8. package/apps/cli/src/dev/provider.js +48 -0
  9. package/apps/cli/src/dev/scanner.js +14 -0
  10. package/apps/cli/src/index.js +31 -0
  11. package/bin/codeforge.js +2 -0
  12. package/package.json +63 -0
  13. package/packages/agent/contextBuilder.js +234 -0
  14. package/packages/agent/executor.js +1729 -0
  15. package/packages/agent/fallback.js +129 -0
  16. package/packages/agent/index.js +26 -0
  17. package/packages/agent/intent.js +73 -0
  18. package/packages/agent/modelSelector.js +469 -0
  19. package/packages/agent/planner.js +379 -0
  20. package/packages/agent/rag.js +412 -0
  21. package/packages/agent/ragSelector.js +570 -0
  22. package/packages/agent/router.js +121 -0
  23. package/packages/agent/thinking.js +54 -0
  24. package/packages/agent/toolCalls.js +258 -0
  25. package/packages/config/config.js +391 -0
  26. package/packages/config/defaults.js +72 -0
  27. package/packages/config/index.js +2 -0
  28. package/packages/core/approvalPolicy.js +140 -0
  29. package/packages/core/context.js +0 -0
  30. package/packages/core/message.js +0 -0
  31. package/packages/core/plan.js +0 -0
  32. package/packages/core/reasoning.js +0 -0
  33. package/packages/core/result.js +0 -0
  34. package/packages/core/retrieval.js +0 -0
  35. package/packages/core/tool.js +536 -0
  36. package/packages/git/branch.js +7 -0
  37. package/packages/git/commit.js +16 -0
  38. package/packages/git/diff.js +15 -0
  39. package/packages/git/git.js +35 -0
  40. package/packages/git/index.js +6 -0
  41. package/packages/git/remote.js +9 -0
  42. package/packages/git/status.js +11 -0
  43. package/packages/memory/index.js +0 -0
  44. package/packages/memory/project.js +0 -0
  45. package/packages/memory/session.js +0 -0
  46. package/packages/prompts/builder.js +43 -0
  47. package/packages/prompts/coder.js +38 -0
  48. package/packages/prompts/index.js +5 -0
  49. package/packages/prompts/planner.js +49 -0
  50. package/packages/prompts/reviewer.js +38 -0
  51. package/packages/prompts/system.js +94 -0
  52. package/packages/providers/index.js +31 -0
  53. package/packages/providers/openrouter.js +380 -0
  54. package/packages/reasoning/budget.js +0 -0
  55. package/packages/reasoning/chain.js +0 -0
  56. package/packages/reasoning/coder.js +0 -0
  57. package/packages/reasoning/evaluator.js +0 -0
  58. package/packages/reasoning/index.js +8 -0
  59. package/packages/reasoning/planner.js +0 -0
  60. package/packages/reasoning/reflector.js +0 -0
  61. package/packages/reasoning/reviewer.js +0 -0
  62. package/packages/reasoning/router.js +0 -0
  63. package/packages/retrieval/budget.js +92 -0
  64. package/packages/retrieval/cache.js +84 -0
  65. package/packages/retrieval/embeddings.js +147 -0
  66. package/packages/retrieval/graph.js +151 -0
  67. package/packages/retrieval/imports.js +74 -0
  68. package/packages/retrieval/index.js +9 -0
  69. package/packages/retrieval/parser/extractors/calls.js +43 -0
  70. package/packages/retrieval/parser/extractors/comments.js +49 -0
  71. package/packages/retrieval/parser/extractors/diagnostics.js +21 -0
  72. package/packages/retrieval/parser/extractors/exports.js +71 -0
  73. package/packages/retrieval/parser/extractors/imports.js +40 -0
  74. package/packages/retrieval/parser/extractors/index.js +12 -0
  75. package/packages/retrieval/parser/extractors/metrics.js +78 -0
  76. package/packages/retrieval/parser/extractors/symbols.js +51 -0
  77. package/packages/retrieval/parser/extractors/todos.js +53 -0
  78. package/packages/retrieval/parser/helpers.js +138 -0
  79. package/packages/retrieval/parser/index.js +10 -0
  80. package/packages/retrieval/parser/languages/c.js +14 -0
  81. package/packages/retrieval/parser/languages/cpp.js +21 -0
  82. package/packages/retrieval/parser/languages/csharp.js +14 -0
  83. package/packages/retrieval/parser/languages/go.js +14 -0
  84. package/packages/retrieval/parser/languages/index.js +12 -0
  85. package/packages/retrieval/parser/languages/java.js +14 -0
  86. package/packages/retrieval/parser/languages/javascript.js +14 -0
  87. package/packages/retrieval/parser/languages/php.js +14 -0
  88. package/packages/retrieval/parser/languages/python.js +14 -0
  89. package/packages/retrieval/parser/languages/ruby.js +14 -0
  90. package/packages/retrieval/parser/languages/rust.js +14 -0
  91. package/packages/retrieval/parser/languages/swift.js +14 -0
  92. package/packages/retrieval/parser/languages/typescript.js +14 -0
  93. package/packages/retrieval/parser/languages.js +218 -0
  94. package/packages/retrieval/parser/parseFile.js +79 -0
  95. package/packages/retrieval/parser/parseRepository.js +78 -0
  96. package/packages/retrieval/parser/queries/c.js +148 -0
  97. package/packages/retrieval/parser/queries/cpp.js +198 -0
  98. package/packages/retrieval/parser/queries/csharp.js +151 -0
  99. package/packages/retrieval/parser/queries/go.js +124 -0
  100. package/packages/retrieval/parser/queries/index.js +35 -0
  101. package/packages/retrieval/parser/queries/java.js +128 -0
  102. package/packages/retrieval/parser/queries/javascript.js +181 -0
  103. package/packages/retrieval/parser/queries/php.js +140 -0
  104. package/packages/retrieval/parser/queries/python.js +110 -0
  105. package/packages/retrieval/parser/queries/ruby.js +105 -0
  106. package/packages/retrieval/parser/queries/rust.js +175 -0
  107. package/packages/retrieval/parser/queries/swift.js +134 -0
  108. package/packages/retrieval/parser/queries/typescript.js +202 -0
  109. package/packages/retrieval/parser/registry.js +107 -0
  110. package/packages/retrieval/parser/traverse.js +82 -0
  111. package/packages/retrieval/parser/types.js +239 -0
  112. package/packages/retrieval/parser.js +78 -0
  113. package/packages/retrieval/ranker.js +73 -0
  114. package/packages/retrieval/retriever.js +910 -0
  115. package/packages/retrieval/symbols.js +145 -0
  116. package/packages/scanner/detect.js +69 -0
  117. package/packages/scanner/ignore.js +12 -0
  118. package/packages/scanner/index.js +2 -0
  119. package/packages/scanner/root.js +21 -0
  120. package/packages/scanner/scanner.js +35 -0
  121. package/packages/scanner/tree.js +45 -0
  122. package/packages/terminal/activity.js +514 -0
  123. package/packages/terminal/banner.js +34 -0
  124. package/packages/terminal/colors.js +16 -0
  125. package/packages/terminal/dashboard.js +94 -0
  126. package/packages/terminal/index.js +19 -0
  127. package/packages/terminal/logger.js +43 -0
  128. package/packages/terminal/prompt.js +11 -0
  129. package/packages/terminal/renderer.js +1209 -0
  130. package/packages/terminal/spinner.js +9 -0
  131. package/packages/tools/editFile.js +261 -0
  132. package/packages/tools/filesystem.js +82 -0
  133. package/packages/tools/index.js +24 -0
  134. package/packages/tools/readFile.js +101 -0
  135. package/packages/tools/registry.js +227 -0
  136. package/packages/tools/searchFiles.js +322 -0
  137. package/packages/tools/shell.js +352 -0
  138. package/packages/tools/writeFile.js +212 -0
  139. package/packages/utils/constants.js +0 -0
  140. package/packages/utils/helpers.js +0 -0
  141. 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
+ }