raggrep 0.1.4 → 0.1.6

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/dist/cli/main.js CHANGED
@@ -235,7 +235,8 @@ var init_config = __esm(() => {
235
235
  ".go",
236
236
  ".rs",
237
237
  ".java",
238
- ".md"
238
+ ".md",
239
+ ".txt"
239
240
  ];
240
241
  });
241
242
 
@@ -248,20 +249,40 @@ var init_entities = __esm(() => {
248
249
  // src/infrastructure/config/configLoader.ts
249
250
  import * as path2 from "path";
250
251
  import * as fs from "fs/promises";
251
- function getRaggrepDir(rootDir, config = DEFAULT_CONFIG) {
252
- return path2.join(rootDir, config.indexDir);
252
+ import * as os2 from "os";
253
+ import * as crypto from "crypto";
254
+ function hashPath(inputPath) {
255
+ return crypto.createHash("sha256").update(inputPath).digest("hex").slice(0, 12);
256
+ }
257
+ function getRaggrepDir(rootDir, _config = DEFAULT_CONFIG) {
258
+ const absoluteRoot = path2.resolve(rootDir);
259
+ const projectHash = hashPath(absoluteRoot);
260
+ return path2.join(RAGGREP_TEMP_BASE, projectHash);
261
+ }
262
+ function getIndexLocation(rootDir) {
263
+ const absoluteRoot = path2.resolve(rootDir);
264
+ const projectHash = hashPath(absoluteRoot);
265
+ return {
266
+ indexDir: path2.join(RAGGREP_TEMP_BASE, projectHash),
267
+ projectRoot: absoluteRoot,
268
+ projectHash
269
+ };
253
270
  }
254
271
  function getModuleIndexPath(rootDir, moduleId, config = DEFAULT_CONFIG) {
255
- return path2.join(rootDir, config.indexDir, "index", moduleId);
272
+ const indexDir = getRaggrepDir(rootDir, config);
273
+ return path2.join(indexDir, "index", moduleId);
256
274
  }
257
275
  function getModuleManifestPath(rootDir, moduleId, config = DEFAULT_CONFIG) {
258
- return path2.join(rootDir, config.indexDir, "index", moduleId, "manifest.json");
276
+ const indexDir = getRaggrepDir(rootDir, config);
277
+ return path2.join(indexDir, "index", moduleId, "manifest.json");
259
278
  }
260
279
  function getGlobalManifestPath(rootDir, config = DEFAULT_CONFIG) {
261
- return path2.join(rootDir, config.indexDir, "manifest.json");
280
+ const indexDir = getRaggrepDir(rootDir, config);
281
+ return path2.join(indexDir, "manifest.json");
262
282
  }
263
283
  function getConfigPath(rootDir, config = DEFAULT_CONFIG) {
264
- return path2.join(rootDir, config.indexDir, "config.json");
284
+ const indexDir = getRaggrepDir(rootDir, config);
285
+ return path2.join(indexDir, "config.json");
265
286
  }
266
287
  async function loadConfig(rootDir) {
267
288
  const configPath = getConfigPath(rootDir, DEFAULT_CONFIG);
@@ -288,10 +309,11 @@ function getEmbeddingConfigFromModule(moduleConfig) {
288
309
  showProgress: options.showProgress !== false
289
310
  };
290
311
  }
291
- var DEFAULT_CONFIG, EMBEDDING_MODELS2;
312
+ var DEFAULT_CONFIG, RAGGREP_TEMP_BASE, EMBEDDING_MODELS2;
292
313
  var init_configLoader = __esm(() => {
293
314
  init_entities();
294
315
  DEFAULT_CONFIG = createDefaultConfig();
316
+ RAGGREP_TEMP_BASE = path2.join(os2.tmpdir(), "raggrep-indexes");
295
317
  EMBEDDING_MODELS2 = {
296
318
  "all-MiniLM-L6-v2": "Xenova/all-MiniLM-L6-v2",
297
319
  "all-MiniLM-L12-v2": "Xenova/all-MiniLM-L12-v2",
@@ -407,1788 +429,2606 @@ function normalizeScore(score, midpoint = 5) {
407
429
  }
408
430
  var BM25_K1 = 1.5, BM25_B = 0.75;
409
431
 
410
- // src/modules/core/symbols.ts
411
- function extractSymbols(content) {
412
- const symbols = [];
413
- const seenSymbols = new Set;
414
- const lines = content.split(`
415
- `);
416
- for (const { type, pattern, exported } of SYMBOL_PATTERNS) {
417
- pattern.lastIndex = 0;
418
- let match;
419
- while ((match = pattern.exec(content)) !== null) {
420
- const name = match[1];
421
- const symbolKey = `${name}:${type}`;
422
- if (seenSymbols.has(symbolKey))
423
- continue;
424
- seenSymbols.add(symbolKey);
425
- const beforeMatch = content.substring(0, match.index);
426
- const line = beforeMatch.split(`
427
- `).length;
428
- symbols.push({
429
- name,
430
- type,
431
- line,
432
- isExported: exported
433
- });
432
+ // src/introspection/projectDetector.ts
433
+ import * as path3 from "path";
434
+ import * as fs2 from "fs/promises";
435
+ function detectScopeFromName(name) {
436
+ const nameLower = name.toLowerCase();
437
+ for (const [scope, keywords] of Object.entries(SCOPE_KEYWORDS)) {
438
+ if (scope === "unknown")
439
+ continue;
440
+ for (const keyword of keywords) {
441
+ if (nameLower.includes(keyword)) {
442
+ return scope;
443
+ }
434
444
  }
435
445
  }
436
- return symbols.sort((a, b) => a.line - b.line);
446
+ return "unknown";
437
447
  }
438
- function symbolsToKeywords(symbols) {
439
- const keywords = new Set;
440
- for (const symbol of symbols) {
441
- keywords.add(symbol.name.toLowerCase());
442
- const parts = symbol.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/\s+/);
443
- for (const part of parts) {
444
- if (part.length > 2) {
445
- keywords.add(part);
448
+ async function scanForPackageJsons(rootDir, currentDir = "", depth = 0) {
449
+ if (depth > MAX_SCAN_DEPTH)
450
+ return [];
451
+ const results = [];
452
+ const fullDir = currentDir ? path3.join(rootDir, currentDir) : rootDir;
453
+ try {
454
+ const entries = await fs2.readdir(fullDir, { withFileTypes: true });
455
+ const hasPackageJson = entries.some((e) => e.isFile() && e.name === "package.json");
456
+ if (hasPackageJson && currentDir) {
457
+ const info = await parsePackageJson(rootDir, currentDir);
458
+ if (info) {
459
+ results.push(info);
446
460
  }
447
461
  }
448
- }
449
- return Array.from(keywords);
462
+ for (const entry of entries) {
463
+ if (!entry.isDirectory())
464
+ continue;
465
+ if (SKIP_DIRS.has(entry.name))
466
+ continue;
467
+ const subPath = currentDir ? `${currentDir}/${entry.name}` : entry.name;
468
+ const subResults = await scanForPackageJsons(rootDir, subPath, depth + 1);
469
+ results.push(...subResults);
470
+ }
471
+ } catch {}
472
+ return results;
450
473
  }
451
- var SYMBOL_PATTERNS;
452
- var init_symbols = __esm(() => {
453
- SYMBOL_PATTERNS = [
454
- {
455
- type: "function",
456
- pattern: /^export\s+(?:async\s+)?function\s+(\w+)/gm,
457
- exported: true
458
- },
459
- {
460
- type: "function",
461
- pattern: /^export\s+(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/gm,
462
- exported: true
463
- },
464
- {
465
- type: "class",
466
- pattern: /^export\s+(?:abstract\s+)?class\s+(\w+)/gm,
467
- exported: true
468
- },
469
- {
470
- type: "interface",
471
- pattern: /^export\s+interface\s+(\w+)/gm,
472
- exported: true
473
- },
474
- {
475
- type: "type",
476
- pattern: /^export\s+type\s+(\w+)/gm,
477
- exported: true
478
- },
479
- {
480
- type: "enum",
481
- pattern: /^export\s+(?:const\s+)?enum\s+(\w+)/gm,
482
- exported: true
483
- },
484
- {
485
- type: "variable",
486
- pattern: /^export\s+(?:const|let|var)\s+(\w+)\s*(?::|=)/gm,
487
- exported: true
488
- },
489
- {
490
- type: "function",
491
- pattern: /^export\s+default\s+(?:async\s+)?function\s+(\w+)/gm,
492
- exported: true
493
- },
494
- {
495
- type: "class",
496
- pattern: /^export\s+default\s+class\s+(\w+)/gm,
497
- exported: true
498
- },
499
- {
500
- type: "function",
501
- pattern: /^(?:async\s+)?function\s+(\w+)/gm,
502
- exported: false
503
- },
504
- {
505
- type: "function",
506
- pattern: /^(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/gm,
507
- exported: false
508
- },
509
- {
510
- type: "class",
511
- pattern: /^(?:abstract\s+)?class\s+(\w+)/gm,
512
- exported: false
513
- },
514
- {
515
- type: "interface",
516
- pattern: /^interface\s+(\w+)/gm,
517
- exported: false
518
- },
519
- {
520
- type: "type",
521
- pattern: /^type\s+(\w+)/gm,
522
- exported: false
523
- },
524
- {
525
- type: "enum",
526
- pattern: /^(?:const\s+)?enum\s+(\w+)/gm,
527
- exported: false
528
- },
529
- {
530
- type: "function",
531
- pattern: /^def\s+(\w+)\s*\(/gm,
532
- exported: false
533
- },
534
- {
535
- type: "class",
536
- pattern: /^class\s+(\w+)(?:\s*\(|:)/gm,
537
- exported: false
538
- },
539
- {
540
- type: "function",
541
- pattern: /^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/gm,
542
- exported: false
543
- },
544
- {
545
- type: "type",
546
- pattern: /^type\s+(\w+)\s+(?:struct|interface)/gm,
547
- exported: false
548
- },
549
- {
550
- type: "function",
551
- pattern: /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/gm,
552
- exported: false
553
- },
554
- {
555
- type: "type",
556
- pattern: /^(?:pub\s+)?struct\s+(\w+)/gm,
557
- exported: false
558
- },
559
- {
560
- type: "enum",
561
- pattern: /^(?:pub\s+)?enum\s+(\w+)/gm,
562
- exported: false
563
- },
564
- {
565
- type: "interface",
566
- pattern: /^(?:pub\s+)?trait\s+(\w+)/gm,
567
- exported: false
474
+ async function parsePackageJson(rootDir, relativePath) {
475
+ try {
476
+ const packageJsonPath = path3.join(rootDir, relativePath, "package.json");
477
+ const content = await fs2.readFile(packageJsonPath, "utf-8");
478
+ const pkg = JSON.parse(content);
479
+ const name = pkg.name || path3.basename(relativePath);
480
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
481
+ let type = "unknown";
482
+ if (deps["next"] || deps["react"] || deps["vue"] || deps["svelte"]) {
483
+ type = "app";
484
+ } else if (deps["express"] || deps["fastify"] || deps["koa"] || deps["hono"]) {
485
+ type = "service";
486
+ } else if (pkg.main || pkg.exports) {
487
+ type = "library";
568
488
  }
569
- ];
570
- });
571
-
572
- // src/modules/core/index.ts
573
- var exports_core = {};
574
- __export(exports_core, {
575
- CoreModule: () => CoreModule
576
- });
577
- import * as path3 from "path";
578
- import * as fs2 from "fs/promises";
579
-
580
- class CoreModule {
581
- id = "core";
582
- name = "Core Search";
583
- description = "Language-agnostic text search with symbol extraction";
584
- version = "1.0.0";
585
- symbolIndex = new Map;
586
- bm25Index = null;
587
- rootDir = "";
588
- async initialize(_config) {}
589
- async indexFile(filepath, content, ctx) {
590
- this.rootDir = ctx.rootDir;
591
- const symbols = extractSymbols(content);
592
- const symbolKeywords = symbolsToKeywords(symbols);
593
- const contentTokens = tokenize(content);
594
- const allTokens = [...new Set([...contentTokens, ...symbolKeywords])];
595
- const chunks = this.createChunks(filepath, content, symbols);
596
- const stats = await ctx.getFileStats(filepath);
597
- this.symbolIndex.set(filepath, {
598
- filepath,
599
- symbols,
600
- tokens: allTokens
601
- });
602
- const moduleData = {
603
- symbols,
604
- tokens: allTokens
605
- };
606
- return {
607
- filepath,
608
- lastModified: stats.lastModified,
609
- chunks,
610
- moduleData
611
- };
489
+ const hasWorkspaces = Boolean(pkg.workspaces);
490
+ return { name, relativePath, type, hasWorkspaces };
491
+ } catch {
492
+ return null;
612
493
  }
613
- createChunks(filepath, content, symbols) {
614
- const lines = content.split(`
615
- `);
616
- const chunks = [];
617
- for (let start = 0;start < lines.length; start += LINES_PER_CHUNK - CHUNK_OVERLAP) {
618
- const end = Math.min(start + LINES_PER_CHUNK, lines.length);
619
- const chunkLines = lines.slice(start, end);
620
- const chunkContent = chunkLines.join(`
621
- `);
622
- const chunkSymbols = symbols.filter((s) => s.line >= start + 1 && s.line <= end);
623
- let chunkType = "block";
624
- let chunkName;
625
- let isExported = false;
626
- if (chunkSymbols.length > 0) {
627
- const primarySymbol = chunkSymbols[0];
628
- chunkType = this.symbolTypeToChunkType(primarySymbol.type);
629
- chunkName = primarySymbol.name;
630
- isExported = primarySymbol.isExported;
631
- }
632
- const chunkId = `${filepath}:${start + 1}-${end}`;
633
- chunks.push({
634
- id: chunkId,
635
- content: chunkContent,
636
- startLine: start + 1,
637
- endLine: end,
638
- type: chunkType,
639
- name: chunkName,
640
- isExported
641
- });
642
- if (end >= lines.length)
643
- break;
644
- }
645
- return chunks;
646
- }
647
- symbolTypeToChunkType(symbolType) {
648
- switch (symbolType) {
649
- case "function":
650
- case "method":
651
- return "function";
652
- case "class":
653
- return "class";
654
- case "interface":
655
- return "interface";
656
- case "type":
657
- return "type";
658
- case "enum":
659
- return "enum";
660
- case "variable":
661
- return "variable";
662
- default:
663
- return "block";
664
- }
665
- }
666
- async finalize(ctx) {
667
- const config = ctx.config;
668
- const coreDir = path3.join(getRaggrepDir(ctx.rootDir, config), "index", "core");
669
- await fs2.mkdir(coreDir, { recursive: true });
670
- this.bm25Index = new BM25Index;
671
- for (const [filepath, entry] of this.symbolIndex) {
672
- this.bm25Index.addDocument(filepath, entry.tokens);
673
- }
674
- const symbolIndexData = {
675
- version: this.version,
676
- lastUpdated: new Date().toISOString(),
677
- files: Object.fromEntries(this.symbolIndex),
678
- bm25Data: this.bm25Index.serialize()
679
- };
680
- await fs2.writeFile(path3.join(coreDir, "symbols.json"), JSON.stringify(symbolIndexData, null, 2));
681
- console.log(` [Core] Symbol index built with ${this.symbolIndex.size} files`);
682
- }
683
- async search(query, ctx, options) {
684
- const config = ctx.config;
685
- const topK = options?.topK ?? DEFAULT_TOP_K;
686
- const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
687
- if (this.symbolIndex.size === 0) {
688
- await this.loadSymbolIndex(ctx.rootDir, config);
689
- }
690
- if (!this.bm25Index || this.symbolIndex.size === 0) {
691
- return [];
692
- }
693
- const queryTokens = tokenize(query);
694
- const bm25Results = this.bm25Index.search(query, topK * 2);
695
- const bm25Scores = new Map(bm25Results.map((r) => [r.id, r.score]));
696
- const symbolMatches = this.findSymbolMatches(queryTokens);
697
- const results = [];
698
- for (const filepath of this.symbolIndex.keys()) {
699
- const entry = this.symbolIndex.get(filepath);
700
- const bm25Score = bm25Scores.get(filepath) ?? 0;
701
- const symbolScore = symbolMatches.get(filepath) ?? 0;
702
- if (bm25Score === 0 && symbolScore === 0)
703
- continue;
704
- const combinedScore = 0.6 * normalizeScore(bm25Score) + 0.4 * symbolScore;
705
- if (combinedScore >= minScore) {
706
- const fileIndex = await ctx.loadFileIndex(filepath);
707
- if (!fileIndex)
494
+ }
495
+ async function detectProjectStructure(rootDir) {
496
+ const projectMap = new Map;
497
+ let isMonorepo = false;
498
+ try {
499
+ const entries = await fs2.readdir(rootDir, { withFileTypes: true });
500
+ const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
501
+ const monorepoPatterns = ["apps", "packages", "libs", "services"];
502
+ const hasMonorepoStructure = monorepoPatterns.some((p) => dirNames.includes(p));
503
+ if (hasMonorepoStructure) {
504
+ isMonorepo = true;
505
+ for (const pattern of monorepoPatterns) {
506
+ if (!dirNames.includes(pattern))
708
507
  continue;
709
- const bestChunk = this.findBestChunk(fileIndex.chunks, queryTokens, entry.symbols);
710
- results.push({
711
- filepath,
712
- chunk: bestChunk,
713
- score: combinedScore,
714
- moduleId: this.id,
715
- context: {
716
- bm25Score: normalizeScore(bm25Score),
717
- symbolScore
718
- }
719
- });
720
- }
721
- }
722
- return results.sort((a, b) => b.score - a.score).slice(0, topK);
723
- }
724
- findSymbolMatches(queryTokens) {
725
- const matches = new Map;
726
- for (const [filepath, entry] of this.symbolIndex) {
727
- let matchScore = 0;
728
- for (const symbol of entry.symbols) {
729
- const symbolName = symbol.name.toLowerCase();
730
- const symbolParts = symbolsToKeywords([symbol]);
731
- for (const token of queryTokens) {
732
- if (symbolName === token) {
733
- matchScore += symbol.isExported ? 1 : 0.8;
734
- } else if (symbolName.includes(token) || token.includes(symbolName)) {
735
- matchScore += symbol.isExported ? 0.5 : 0.4;
736
- } else if (symbolParts.some((p) => p === token)) {
737
- matchScore += symbol.isExported ? 0.3 : 0.2;
508
+ const patternDir = path3.join(rootDir, pattern);
509
+ try {
510
+ const subDirs = await fs2.readdir(patternDir, { withFileTypes: true });
511
+ for (const subDir of subDirs) {
512
+ if (!subDir.isDirectory())
513
+ continue;
514
+ const projectRoot = `${pattern}/${subDir.name}`;
515
+ const type = getProjectType(pattern);
516
+ projectMap.set(projectRoot, {
517
+ name: subDir.name,
518
+ root: projectRoot,
519
+ type
520
+ });
738
521
  }
739
- }
740
- }
741
- if (matchScore > 0) {
742
- matches.set(filepath, Math.min(1, matchScore / queryTokens.length));
522
+ } catch {}
743
523
  }
744
524
  }
745
- return matches;
746
- }
747
- findBestChunk(chunks, queryTokens, symbols) {
748
- let bestChunk = chunks[0];
749
- let bestScore = 0;
750
- for (const chunk of chunks) {
751
- let score = 0;
752
- const chunkContent = chunk.content.toLowerCase();
753
- for (const token of queryTokens) {
754
- if (chunkContent.includes(token)) {
755
- score += 1;
756
- }
757
- }
758
- if (chunk.name) {
759
- const nameLower = chunk.name.toLowerCase();
760
- for (const token of queryTokens) {
761
- if (nameLower.includes(token)) {
762
- score += 2;
763
- }
764
- }
765
- }
766
- if (chunk.isExported) {
767
- score += 0.5;
525
+ const packageJsons = await scanForPackageJsons(rootDir);
526
+ for (const pkg of packageJsons) {
527
+ if (pkg.hasWorkspaces) {
528
+ isMonorepo = true;
768
529
  }
769
- if (score > bestScore) {
770
- bestScore = score;
771
- bestChunk = chunk;
530
+ if (packageJsons.length > 1) {
531
+ isMonorepo = true;
772
532
  }
533
+ projectMap.set(pkg.relativePath, {
534
+ name: pkg.name,
535
+ root: pkg.relativePath,
536
+ type: pkg.type
537
+ });
773
538
  }
774
- return bestChunk;
775
- }
776
- async loadSymbolIndex(rootDir, config) {
777
- const coreDir = path3.join(getRaggrepDir(rootDir, config), "index", "core");
778
- const symbolsPath = path3.join(coreDir, "symbols.json");
539
+ let rootType = "unknown";
779
540
  try {
780
- const content = await fs2.readFile(symbolsPath, "utf-8");
781
- const data = JSON.parse(content);
782
- this.symbolIndex = new Map(Object.entries(data.files));
783
- if (data.bm25Data) {
784
- this.bm25Index = BM25Index.deserialize(data.bm25Data);
541
+ const rootPkgPath = path3.join(rootDir, "package.json");
542
+ const rootPkg = JSON.parse(await fs2.readFile(rootPkgPath, "utf-8"));
543
+ if (rootPkg.workspaces) {
544
+ isMonorepo = true;
785
545
  }
786
- } catch (error) {
787
- this.symbolIndex = new Map;
788
- this.bm25Index = null;
789
- }
546
+ const deps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
547
+ if (deps["next"] || deps["react"] || deps["vue"]) {
548
+ rootType = "app";
549
+ } else if (deps["express"] || deps["fastify"] || deps["koa"]) {
550
+ rootType = "service";
551
+ }
552
+ } catch {}
553
+ const projects = Array.from(projectMap.values()).sort((a, b) => a.root.length - b.root.length);
554
+ return {
555
+ projects,
556
+ isMonorepo,
557
+ rootType: isMonorepo ? undefined : rootType
558
+ };
559
+ } catch {
560
+ return {
561
+ projects: [],
562
+ isMonorepo: false,
563
+ rootType: "unknown"
564
+ };
790
565
  }
791
- async dispose() {
792
- this.symbolIndex.clear();
793
- this.bm25Index = null;
566
+ }
567
+ function getProjectType(patternDir) {
568
+ switch (patternDir) {
569
+ case "apps":
570
+ return "app";
571
+ case "packages":
572
+ case "libs":
573
+ return "library";
574
+ case "services":
575
+ return "service";
576
+ case "scripts":
577
+ case "tools":
578
+ return "script";
579
+ default:
580
+ return "unknown";
794
581
  }
795
582
  }
796
- var DEFAULT_MIN_SCORE = 0.1, DEFAULT_TOP_K = 20, LINES_PER_CHUNK = 50, CHUNK_OVERLAP = 10;
797
- var init_core = __esm(() => {
798
- init_config2();
799
- init_symbols();
800
- });
801
-
802
- // src/domain/services/similarity.ts
803
- function cosineSimilarity(a, b) {
804
- if (a.length !== b.length) {
805
- throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
583
+ function findProjectForFile(filepath, structure) {
584
+ const normalizedPath = filepath.replace(/\\/g, "/");
585
+ const matches = [];
586
+ for (const project of structure.projects) {
587
+ if (normalizedPath === project.root || normalizedPath.startsWith(project.root + "/")) {
588
+ matches.push(project);
589
+ }
806
590
  }
807
- let dotProduct = 0;
808
- let normA = 0;
809
- let normB = 0;
810
- for (let i = 0;i < a.length; i++) {
811
- dotProduct += a[i] * b[i];
812
- normA += a[i] * a[i];
813
- normB += b[i] * b[i];
591
+ if (matches.length > 0) {
592
+ return matches.reduce((best, current) => current.root.length > best.root.length ? current : best);
814
593
  }
815
- const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
816
- if (magnitude === 0)
817
- return 0;
818
- return dotProduct / magnitude;
594
+ for (const { pattern, type } of PROJECT_PATTERNS) {
595
+ const match = normalizedPath.match(pattern);
596
+ if (match) {
597
+ return {
598
+ name: match[1],
599
+ root: match[0],
600
+ type
601
+ };
602
+ }
603
+ }
604
+ return {
605
+ name: "root",
606
+ root: "",
607
+ type: structure.rootType ?? "unknown"
608
+ };
819
609
  }
610
+ var MAX_SCAN_DEPTH = 4, SKIP_DIRS, PROJECT_PATTERNS, SCOPE_KEYWORDS;
611
+ var init_projectDetector = __esm(() => {
612
+ SKIP_DIRS = new Set([
613
+ "node_modules",
614
+ ".git",
615
+ "dist",
616
+ "build",
617
+ ".next",
618
+ ".nuxt",
619
+ "coverage",
620
+ ".raggrep"
621
+ ]);
622
+ PROJECT_PATTERNS = [
623
+ { pattern: /^apps\/([^/]+)/, type: "app", defaultScope: "unknown" },
624
+ { pattern: /^packages\/([^/]+)/, type: "library", defaultScope: "shared" },
625
+ { pattern: /^libs\/([^/]+)/, type: "library", defaultScope: "shared" },
626
+ { pattern: /^services\/([^/]+)/, type: "service", defaultScope: "backend" },
627
+ { pattern: /^scripts\/([^/]+)/, type: "script", defaultScope: "tooling" },
628
+ { pattern: /^tools\/([^/]+)/, type: "script", defaultScope: "tooling" }
629
+ ];
630
+ SCOPE_KEYWORDS = {
631
+ frontend: [
632
+ "web",
633
+ "webapp",
634
+ "frontend",
635
+ "client",
636
+ "ui",
637
+ "app",
638
+ "mobile",
639
+ "react",
640
+ "vue",
641
+ "angular",
642
+ "next",
643
+ "nuxt"
644
+ ],
645
+ backend: [
646
+ "api",
647
+ "server",
648
+ "backend",
649
+ "service",
650
+ "worker",
651
+ "lambda",
652
+ "functions"
653
+ ],
654
+ shared: ["shared", "common", "utils", "lib", "core", "types", "models"],
655
+ tooling: [
656
+ "scripts",
657
+ "tools",
658
+ "cli",
659
+ "devtools",
660
+ "build",
661
+ "config",
662
+ "infra"
663
+ ],
664
+ unknown: []
665
+ };
666
+ });
820
667
 
821
- // src/modules/language/typescript/parseCode.ts
822
- import * as ts from "typescript";
823
- function parseCode(content, filepath) {
824
- const ext = filepath.split(".").pop()?.toLowerCase();
825
- if (["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"].includes(ext || "")) {
826
- return parseTypeScript(content, filepath);
827
- }
828
- return parseGenericCode(content);
668
+ // src/introspection/conventions/entryPoints.ts
669
+ import * as path4 from "path";
670
+ function getParentFolder(filepath) {
671
+ const dir = path4.dirname(filepath);
672
+ return path4.basename(dir);
829
673
  }
830
- function parseTypeScript(content, filepath) {
831
- const chunks = [];
832
- const lines = content.split(`
833
- `);
834
- const sourceFile = ts.createSourceFile(filepath, content, ts.ScriptTarget.Latest, true, filepath.endsWith(".tsx") || filepath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
835
- function getLineNumbers(node) {
836
- const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
837
- const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
838
- return {
839
- startLine: start.line + 1,
840
- endLine: end.line + 1
841
- };
842
- }
843
- function getNodeText(node) {
844
- return node.getText(sourceFile);
845
- }
846
- function isExported(node) {
847
- if (!ts.canHaveModifiers(node))
848
- return false;
849
- const modifiers = ts.getModifiers(node);
850
- return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
851
- }
852
- function getJSDoc(node) {
853
- const jsDocNodes = ts.getJSDocCommentsAndTags(node);
854
- if (jsDocNodes.length === 0)
855
- return;
856
- return jsDocNodes.map((doc) => doc.getText(sourceFile)).join(`
857
- `);
858
- }
859
- function getFunctionName(node) {
860
- if (ts.isFunctionDeclaration(node) && node.name) {
861
- return node.name.text;
862
- }
863
- if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
864
- return node.name.text;
865
- }
866
- if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
867
- return node.name.text;
868
- }
869
- return;
870
- }
871
- function visit(node) {
872
- const { startLine, endLine } = getLineNumbers(node);
873
- if (ts.isFunctionDeclaration(node) && node.name) {
874
- chunks.push({
875
- content: getNodeText(node),
876
- startLine,
877
- endLine,
878
- type: "function",
879
- name: node.name.text,
880
- isExported: isExported(node),
881
- jsDoc: getJSDoc(node)
882
- });
883
- return;
884
- }
885
- if (ts.isVariableStatement(node)) {
886
- for (const decl of node.declarationList.declarations) {
887
- if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
888
- const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
889
- chunks.push({
890
- content: getNodeText(node),
891
- startLine,
892
- endLine,
893
- type: "function",
894
- name,
895
- isExported: isExported(node),
896
- jsDoc: getJSDoc(node)
897
- });
898
- return;
674
+ var entryPointConventions;
675
+ var init_entryPoints = __esm(() => {
676
+ entryPointConventions = [
677
+ {
678
+ id: "index-file",
679
+ name: "Index/Barrel File",
680
+ description: "Module entry point that typically re-exports from other files",
681
+ category: "entry-point",
682
+ match: (filepath, filename) => {
683
+ return /^index\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filename);
684
+ },
685
+ keywords: ["entry", "barrel", "exports", "module"],
686
+ dynamicKeywords: (filepath) => {
687
+ const parent = getParentFolder(filepath);
688
+ if (["src", "lib", "dist", "build", ".", ""].includes(parent)) {
689
+ return [];
899
690
  }
691
+ return [parent.toLowerCase()];
900
692
  }
693
+ },
694
+ {
695
+ id: "main-file",
696
+ name: "Main Entry Point",
697
+ description: "Application main entry point",
698
+ category: "entry-point",
699
+ match: (filepath, filename) => {
700
+ return /^main\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filename);
701
+ },
702
+ keywords: ["entry", "main", "entrypoint", "bootstrap", "startup"]
703
+ },
704
+ {
705
+ id: "app-component",
706
+ name: "Root App Component",
707
+ description: "Root application component (React, Vue, etc.)",
708
+ category: "entry-point",
709
+ match: (filepath, filename) => {
710
+ return /^App\.(tsx|jsx|vue|svelte)$/.test(filename);
711
+ },
712
+ keywords: ["root", "app", "application", "component", "main"]
713
+ },
714
+ {
715
+ id: "deno-mod",
716
+ name: "Deno Module Entry",
717
+ description: "Deno module entry point",
718
+ category: "entry-point",
719
+ match: (filepath, filename) => {
720
+ return filename === "mod.ts";
721
+ },
722
+ keywords: ["entry", "module", "deno", "exports"],
723
+ dynamicKeywords: (filepath) => {
724
+ const parent = getParentFolder(filepath);
725
+ if (["src", "lib", ".", ""].includes(parent)) {
726
+ return [];
727
+ }
728
+ return [parent.toLowerCase()];
729
+ }
730
+ },
731
+ {
732
+ id: "python-init",
733
+ name: "Python Package Init",
734
+ description: "Python package initialization file",
735
+ category: "entry-point",
736
+ match: (filepath, filename) => {
737
+ return filename === "__init__.py";
738
+ },
739
+ keywords: ["entry", "package", "init", "python", "module"],
740
+ dynamicKeywords: (filepath) => {
741
+ const parent = getParentFolder(filepath);
742
+ if (["src", "lib", ".", ""].includes(parent)) {
743
+ return [];
744
+ }
745
+ return [parent.toLowerCase()];
746
+ }
747
+ },
748
+ {
749
+ id: "rust-lib",
750
+ name: "Rust Library Entry",
751
+ description: "Rust library crate entry point",
752
+ category: "entry-point",
753
+ match: (filepath, filename) => {
754
+ return filename === "lib.rs" || filename === "main.rs";
755
+ },
756
+ keywords: ["entry", "crate", "rust", "module"]
901
757
  }
902
- if (ts.isClassDeclaration(node) && node.name) {
903
- chunks.push({
904
- content: getNodeText(node),
905
- startLine,
906
- endLine,
907
- type: "class",
908
- name: node.name.text,
909
- isExported: isExported(node),
910
- jsDoc: getJSDoc(node)
911
- });
912
- return;
913
- }
914
- if (ts.isInterfaceDeclaration(node)) {
915
- chunks.push({
916
- content: getNodeText(node),
917
- startLine,
918
- endLine,
919
- type: "interface",
920
- name: node.name.text,
921
- isExported: isExported(node),
922
- jsDoc: getJSDoc(node)
923
- });
924
- return;
925
- }
926
- if (ts.isTypeAliasDeclaration(node)) {
927
- chunks.push({
928
- content: getNodeText(node),
929
- startLine,
930
- endLine,
931
- type: "type",
932
- name: node.name.text,
933
- isExported: isExported(node),
934
- jsDoc: getJSDoc(node)
935
- });
936
- return;
758
+ ];
759
+ });
760
+
761
+ // src/introspection/conventions/configFiles.ts
762
+ var configFileConventions;
763
+ var init_configFiles = __esm(() => {
764
+ configFileConventions = [
765
+ {
766
+ id: "package-json",
767
+ name: "Package.json",
768
+ description: "Node.js package manifest",
769
+ category: "configuration",
770
+ match: (filepath, filename) => filename === "package.json",
771
+ keywords: ["package", "dependencies", "npm", "scripts", "manifest", "node"]
772
+ },
773
+ {
774
+ id: "pnpm-workspace",
775
+ name: "PNPM Workspace",
776
+ description: "PNPM monorepo workspace configuration",
777
+ category: "configuration",
778
+ match: (filepath, filename) => filename === "pnpm-workspace.yaml" || filename === "pnpm-workspace.yml",
779
+ keywords: ["workspace", "monorepo", "pnpm", "packages"]
780
+ },
781
+ {
782
+ id: "yarn-lock",
783
+ name: "Yarn Lock",
784
+ description: "Yarn dependency lock file",
785
+ category: "configuration",
786
+ match: (filepath, filename) => filename === "yarn.lock",
787
+ keywords: ["dependencies", "lock", "yarn", "versions"]
788
+ },
789
+ {
790
+ id: "package-lock",
791
+ name: "Package Lock",
792
+ description: "NPM dependency lock file",
793
+ category: "configuration",
794
+ match: (filepath, filename) => filename === "package-lock.json",
795
+ keywords: ["dependencies", "lock", "npm", "versions"]
796
+ },
797
+ {
798
+ id: "bun-lockb",
799
+ name: "Bun Lock",
800
+ description: "Bun dependency lock file",
801
+ category: "configuration",
802
+ match: (filepath, filename) => filename === "bun.lockb" || filename === "bun.lock",
803
+ keywords: ["dependencies", "lock", "bun", "versions"]
804
+ },
805
+ {
806
+ id: "tsconfig",
807
+ name: "TypeScript Config",
808
+ description: "TypeScript compiler configuration",
809
+ category: "configuration",
810
+ match: (filepath, filename) => filename === "tsconfig.json" || filename.startsWith("tsconfig.") && filename.endsWith(".json"),
811
+ keywords: [
812
+ "typescript",
813
+ "config",
814
+ "compiler",
815
+ "ts",
816
+ "settings",
817
+ "paths",
818
+ "types"
819
+ ]
820
+ },
821
+ {
822
+ id: "jsconfig",
823
+ name: "JavaScript Config",
824
+ description: "JavaScript project configuration",
825
+ category: "configuration",
826
+ match: (filepath, filename) => filename === "jsconfig.json",
827
+ keywords: ["javascript", "config", "compiler", "js", "settings", "paths"]
828
+ },
829
+ {
830
+ id: "eslint-config",
831
+ name: "ESLint Config",
832
+ description: "ESLint linting configuration",
833
+ category: "configuration",
834
+ match: (filepath, filename) => filename === ".eslintrc" || filename === ".eslintrc.js" || filename === ".eslintrc.cjs" || filename === ".eslintrc.json" || filename === ".eslintrc.yml" || filename === ".eslintrc.yaml" || filename === "eslint.config.js" || filename === "eslint.config.mjs" || filename === "eslint.config.cjs",
835
+ keywords: ["eslint", "linting", "lint", "rules", "code quality"]
836
+ },
837
+ {
838
+ id: "prettier-config",
839
+ name: "Prettier Config",
840
+ description: "Prettier code formatting configuration",
841
+ category: "configuration",
842
+ match: (filepath, filename) => filename === ".prettierrc" || filename === ".prettierrc.js" || filename === ".prettierrc.cjs" || filename === ".prettierrc.json" || filename === ".prettierrc.yml" || filename === ".prettierrc.yaml" || filename === "prettier.config.js" || filename === "prettier.config.cjs" || filename === "prettier.config.mjs",
843
+ keywords: ["prettier", "formatting", "format", "code style", "style"]
844
+ },
845
+ {
846
+ id: "biome-config",
847
+ name: "Biome Config",
848
+ description: "Biome linting and formatting configuration",
849
+ category: "configuration",
850
+ match: (filepath, filename) => filename === "biome.json" || filename === "biome.jsonc",
851
+ keywords: ["biome", "linting", "formatting", "lint", "format"]
852
+ },
853
+ {
854
+ id: "vite-config",
855
+ name: "Vite Config",
856
+ description: "Vite build tool configuration",
857
+ category: "build",
858
+ match: (filepath, filename) => filename === "vite.config.ts" || filename === "vite.config.js" || filename === "vite.config.mjs",
859
+ keywords: ["vite", "bundler", "build", "dev server", "hmr"]
860
+ },
861
+ {
862
+ id: "webpack-config",
863
+ name: "Webpack Config",
864
+ description: "Webpack bundler configuration",
865
+ category: "build",
866
+ match: (filepath, filename) => filename === "webpack.config.js" || filename === "webpack.config.ts" || filename.startsWith("webpack.") && (filename.endsWith(".js") || filename.endsWith(".ts")),
867
+ keywords: ["webpack", "bundler", "build", "loaders", "plugins"]
868
+ },
869
+ {
870
+ id: "rollup-config",
871
+ name: "Rollup Config",
872
+ description: "Rollup bundler configuration",
873
+ category: "build",
874
+ match: (filepath, filename) => filename === "rollup.config.js" || filename === "rollup.config.ts" || filename === "rollup.config.mjs",
875
+ keywords: ["rollup", "bundler", "build", "esm", "bundle"]
876
+ },
877
+ {
878
+ id: "esbuild-config",
879
+ name: "esbuild Config",
880
+ description: "esbuild bundler configuration",
881
+ category: "build",
882
+ match: (filepath, filename) => filename === "esbuild.config.js" || filename === "esbuild.config.ts" || filename === "esbuild.config.mjs",
883
+ keywords: ["esbuild", "bundler", "build", "fast"]
884
+ },
885
+ {
886
+ id: "jest-config",
887
+ name: "Jest Config",
888
+ description: "Jest testing framework configuration",
889
+ category: "test",
890
+ match: (filepath, filename) => filename === "jest.config.js" || filename === "jest.config.ts" || filename === "jest.config.mjs" || filename === "jest.config.cjs" || filename === "jest.config.json",
891
+ keywords: ["jest", "testing", "test", "unit test", "config"]
892
+ },
893
+ {
894
+ id: "vitest-config",
895
+ name: "Vitest Config",
896
+ description: "Vitest testing framework configuration",
897
+ category: "test",
898
+ match: (filepath, filename) => filename === "vitest.config.ts" || filename === "vitest.config.js" || filename === "vitest.config.mts",
899
+ keywords: ["vitest", "testing", "test", "unit test", "config"]
900
+ },
901
+ {
902
+ id: "playwright-config",
903
+ name: "Playwright Config",
904
+ description: "Playwright E2E testing configuration",
905
+ category: "test",
906
+ match: (filepath, filename) => filename === "playwright.config.ts" || filename === "playwright.config.js",
907
+ keywords: ["playwright", "testing", "e2e", "end-to-end", "browser test"]
908
+ },
909
+ {
910
+ id: "cypress-config",
911
+ name: "Cypress Config",
912
+ description: "Cypress E2E testing configuration",
913
+ category: "test",
914
+ match: (filepath, filename) => filename === "cypress.config.ts" || filename === "cypress.config.js" || filename === "cypress.json",
915
+ keywords: ["cypress", "testing", "e2e", "end-to-end", "browser test"]
916
+ },
917
+ {
918
+ id: "tailwind-config",
919
+ name: "Tailwind Config",
920
+ description: "Tailwind CSS configuration",
921
+ category: "configuration",
922
+ match: (filepath, filename) => filename === "tailwind.config.js" || filename === "tailwind.config.ts" || filename === "tailwind.config.cjs" || filename === "tailwind.config.mjs",
923
+ keywords: ["tailwind", "css", "styling", "utility", "design"]
924
+ },
925
+ {
926
+ id: "postcss-config",
927
+ name: "PostCSS Config",
928
+ description: "PostCSS configuration",
929
+ category: "configuration",
930
+ match: (filepath, filename) => filename === "postcss.config.js" || filename === "postcss.config.cjs" || filename === "postcss.config.mjs" || filename === ".postcssrc" || filename === ".postcssrc.json",
931
+ keywords: ["postcss", "css", "styling", "transforms"]
932
+ },
933
+ {
934
+ id: "env-file",
935
+ name: "Environment File",
936
+ description: "Environment variables file",
937
+ category: "configuration",
938
+ match: (filepath, filename) => filename === ".env" || filename === ".env.local" || filename === ".env.development" || filename === ".env.production" || filename === ".env.test" || filename.startsWith(".env."),
939
+ keywords: ["environment", "env", "variables", "secrets", "config"]
940
+ },
941
+ {
942
+ id: "env-example",
943
+ name: "Environment Example",
944
+ description: "Example environment variables file",
945
+ category: "documentation",
946
+ match: (filepath, filename) => filename === ".env.example" || filename === ".env.sample" || filename === ".env.template",
947
+ keywords: ["environment", "env", "example", "template", "setup"]
948
+ },
949
+ {
950
+ id: "dockerfile",
951
+ name: "Dockerfile",
952
+ description: "Docker container image definition",
953
+ category: "deployment",
954
+ match: (filepath, filename) => filename === "Dockerfile" || filename.startsWith("Dockerfile."),
955
+ keywords: ["docker", "container", "image", "deployment", "build"]
956
+ },
957
+ {
958
+ id: "docker-compose",
959
+ name: "Docker Compose",
960
+ description: "Docker Compose multi-container configuration",
961
+ category: "deployment",
962
+ match: (filepath, filename) => filename === "docker-compose.yml" || filename === "docker-compose.yaml" || filename === "compose.yml" || filename === "compose.yaml" || filename.startsWith("docker-compose."),
963
+ keywords: ["docker", "compose", "containers", "services", "deployment"]
964
+ },
965
+ {
966
+ id: "github-actions",
967
+ name: "GitHub Actions Workflow",
968
+ description: "GitHub Actions CI/CD workflow",
969
+ category: "deployment",
970
+ match: (filepath) => filepath.includes(".github/workflows/") && filepath.endsWith(".yml"),
971
+ keywords: ["github", "actions", "ci", "cd", "workflow", "automation"]
972
+ },
973
+ {
974
+ id: "vercel-config",
975
+ name: "Vercel Config",
976
+ description: "Vercel deployment configuration",
977
+ category: "deployment",
978
+ match: (filepath, filename) => filename === "vercel.json",
979
+ keywords: ["vercel", "deployment", "hosting", "serverless"]
980
+ },
981
+ {
982
+ id: "netlify-config",
983
+ name: "Netlify Config",
984
+ description: "Netlify deployment configuration",
985
+ category: "deployment",
986
+ match: (filepath, filename) => filename === "netlify.toml",
987
+ keywords: ["netlify", "deployment", "hosting", "functions"]
988
+ },
989
+ {
990
+ id: "gitignore",
991
+ name: "Git Ignore",
992
+ description: "Git ignored files configuration",
993
+ category: "configuration",
994
+ match: (filepath, filename) => filename === ".gitignore",
995
+ keywords: ["git", "ignore", "version control", "excluded"]
996
+ },
997
+ {
998
+ id: "gitattributes",
999
+ name: "Git Attributes",
1000
+ description: "Git file attributes configuration",
1001
+ category: "configuration",
1002
+ match: (filepath, filename) => filename === ".gitattributes",
1003
+ keywords: ["git", "attributes", "version control", "line endings"]
1004
+ },
1005
+ {
1006
+ id: "readme",
1007
+ name: "README",
1008
+ description: "Project documentation",
1009
+ category: "documentation",
1010
+ match: (filepath, filename) => filename.toLowerCase() === "readme.md" || filename.toLowerCase() === "readme",
1011
+ keywords: [
1012
+ "readme",
1013
+ "documentation",
1014
+ "docs",
1015
+ "overview",
1016
+ "getting started"
1017
+ ]
1018
+ },
1019
+ {
1020
+ id: "changelog",
1021
+ name: "Changelog",
1022
+ description: "Project changelog",
1023
+ category: "documentation",
1024
+ match: (filepath, filename) => filename.toLowerCase() === "changelog.md" || filename.toLowerCase() === "changelog",
1025
+ keywords: ["changelog", "changes", "releases", "history", "versions"]
1026
+ },
1027
+ {
1028
+ id: "contributing",
1029
+ name: "Contributing Guide",
1030
+ description: "Contribution guidelines",
1031
+ category: "documentation",
1032
+ match: (filepath, filename) => filename.toLowerCase() === "contributing.md" || filename.toLowerCase() === "contributing",
1033
+ keywords: ["contributing", "contribution", "guidelines", "development"]
1034
+ },
1035
+ {
1036
+ id: "license",
1037
+ name: "License",
1038
+ description: "Project license",
1039
+ category: "documentation",
1040
+ match: (filepath, filename) => filename.toLowerCase() === "license" || filename.toLowerCase() === "license.md" || filename.toLowerCase() === "license.txt",
1041
+ keywords: ["license", "legal", "copyright", "terms"]
937
1042
  }
938
- if (ts.isEnumDeclaration(node)) {
939
- chunks.push({
940
- content: getNodeText(node),
941
- startLine,
942
- endLine,
943
- type: "enum",
944
- name: node.name.text,
945
- isExported: isExported(node),
946
- jsDoc: getJSDoc(node)
947
- });
948
- return;
1043
+ ];
1044
+ });
1045
+
1046
+ // src/introspection/conventions/frameworks/nextjs.ts
1047
+ var nextjsConventions, nextjsFramework;
1048
+ var init_nextjs = __esm(() => {
1049
+ nextjsConventions = [
1050
+ {
1051
+ id: "next-config",
1052
+ name: "Next.js Config",
1053
+ description: "Next.js framework configuration",
1054
+ category: "configuration",
1055
+ match: (filepath, filename) => filename === "next.config.js" || filename === "next.config.mjs" || filename === "next.config.ts",
1056
+ keywords: ["nextjs", "next", "config", "framework", "settings"]
1057
+ },
1058
+ {
1059
+ id: "next-env",
1060
+ name: "Next.js Environment Types",
1061
+ description: "Next.js TypeScript environment declarations",
1062
+ category: "types",
1063
+ match: (filepath, filename) => filename === "next-env.d.ts",
1064
+ keywords: ["nextjs", "types", "typescript", "declarations"]
1065
+ },
1066
+ {
1067
+ id: "next-layout",
1068
+ name: "Next.js Layout",
1069
+ description: "Next.js layout component (App Router)",
1070
+ category: "framework",
1071
+ match: (filepath, filename) => (filename === "layout.tsx" || filename === "layout.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1072
+ keywords: ["nextjs", "layout", "wrapper", "template", "app router"],
1073
+ dynamicKeywords: (filepath) => {
1074
+ const match = filepath.match(/app\/(.+?)\/layout\./);
1075
+ if (match) {
1076
+ const segments = match[1].split("/").filter((s) => !s.startsWith("(") && !s.startsWith("["));
1077
+ return segments.map((s) => s.toLowerCase());
1078
+ }
1079
+ if (filepath === "app/layout.tsx" || filepath === "app/layout.js") {
1080
+ return ["root", "main"];
1081
+ }
1082
+ return [];
1083
+ }
1084
+ },
1085
+ {
1086
+ id: "next-page",
1087
+ name: "Next.js Page",
1088
+ description: "Next.js page component (App Router)",
1089
+ category: "framework",
1090
+ match: (filepath, filename) => (filename === "page.tsx" || filename === "page.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1091
+ keywords: ["nextjs", "page", "route", "view", "app router"],
1092
+ dynamicKeywords: (filepath) => {
1093
+ const match = filepath.match(/app\/(.+?)\/page\./);
1094
+ if (match) {
1095
+ const segments = match[1].split("/").filter((s) => !s.startsWith("(")).map((s) => s.replace(/^\[(.+?)\]$/, "$1"));
1096
+ return segments.map((s) => s.toLowerCase());
1097
+ }
1098
+ if (filepath === "app/page.tsx" || filepath === "app/page.js") {
1099
+ return ["home", "index", "root"];
1100
+ }
1101
+ return [];
1102
+ }
1103
+ },
1104
+ {
1105
+ id: "next-loading",
1106
+ name: "Next.js Loading",
1107
+ description: "Next.js loading UI component",
1108
+ category: "framework",
1109
+ match: (filepath, filename) => (filename === "loading.tsx" || filename === "loading.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1110
+ keywords: ["nextjs", "loading", "suspense", "skeleton", "spinner"]
1111
+ },
1112
+ {
1113
+ id: "next-error",
1114
+ name: "Next.js Error",
1115
+ description: "Next.js error boundary component",
1116
+ category: "framework",
1117
+ match: (filepath, filename) => (filename === "error.tsx" || filename === "error.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1118
+ keywords: ["nextjs", "error", "boundary", "fallback", "catch"]
1119
+ },
1120
+ {
1121
+ id: "next-not-found",
1122
+ name: "Next.js Not Found",
1123
+ description: "Next.js 404 page component",
1124
+ category: "framework",
1125
+ match: (filepath, filename) => (filename === "not-found.tsx" || filename === "not-found.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1126
+ keywords: ["nextjs", "404", "not found", "missing", "error"]
1127
+ },
1128
+ {
1129
+ id: "next-template",
1130
+ name: "Next.js Template",
1131
+ description: "Next.js template component",
1132
+ category: "framework",
1133
+ match: (filepath, filename) => (filename === "template.tsx" || filename === "template.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1134
+ keywords: ["nextjs", "template", "wrapper", "app router"]
1135
+ },
1136
+ {
1137
+ id: "next-route-handler",
1138
+ name: "Next.js Route Handler",
1139
+ description: "Next.js API route handler (App Router)",
1140
+ category: "framework",
1141
+ match: (filepath, filename) => (filename === "route.ts" || filename === "route.js") && (filepath.includes("/app/") || filepath.startsWith("app/")),
1142
+ keywords: ["nextjs", "api", "route", "handler", "endpoint", "rest"],
1143
+ dynamicKeywords: (filepath) => {
1144
+ const match = filepath.match(/app\/api\/(.+?)\/route\./);
1145
+ if (match) {
1146
+ const segments = match[1].split("/").filter((s) => !s.startsWith("(")).map((s) => s.replace(/^\[(.+?)\]$/, "$1"));
1147
+ return ["api", ...segments.map((s) => s.toLowerCase())];
1148
+ }
1149
+ return ["api"];
1150
+ }
1151
+ },
1152
+ {
1153
+ id: "next-middleware",
1154
+ name: "Next.js Middleware",
1155
+ description: "Next.js edge middleware",
1156
+ category: "framework",
1157
+ match: (filepath, filename) => filename === "middleware.ts" || filename === "middleware.js",
1158
+ keywords: ["nextjs", "middleware", "edge", "request", "interceptor"]
1159
+ },
1160
+ {
1161
+ id: "next-global-error",
1162
+ name: "Next.js Global Error",
1163
+ description: "Next.js global error handler",
1164
+ category: "framework",
1165
+ match: (filepath, filename) => filename === "global-error.tsx" || filename === "global-error.js",
1166
+ keywords: ["nextjs", "error", "global", "boundary", "catch"]
1167
+ },
1168
+ {
1169
+ id: "next-pages-api",
1170
+ name: "Next.js API Route (Pages)",
1171
+ description: "Next.js API route (Pages Router)",
1172
+ category: "framework",
1173
+ match: (filepath) => filepath.includes("/pages/api/") || filepath.startsWith("pages/api/"),
1174
+ keywords: ["nextjs", "api", "route", "handler", "endpoint", "pages router"],
1175
+ dynamicKeywords: (filepath) => {
1176
+ const match = filepath.match(/pages\/api\/(.+?)\.(ts|js)/);
1177
+ if (match) {
1178
+ const segments = match[1].split("/").map((s) => s.replace(/^\[(.+?)\]$/, "$1"));
1179
+ return ["api", ...segments.map((s) => s.toLowerCase())];
1180
+ }
1181
+ return ["api"];
1182
+ }
1183
+ },
1184
+ {
1185
+ id: "next-pages-document",
1186
+ name: "Next.js Document",
1187
+ description: "Next.js custom document (Pages Router)",
1188
+ category: "framework",
1189
+ match: (filepath, filename) => (filename === "_document.tsx" || filename === "_document.js") && (filepath.includes("/pages/") || filepath.startsWith("pages/")),
1190
+ keywords: ["nextjs", "document", "html", "head", "body", "pages router"]
1191
+ },
1192
+ {
1193
+ id: "next-pages-app",
1194
+ name: "Next.js App (Pages)",
1195
+ description: "Next.js custom app (Pages Router)",
1196
+ category: "framework",
1197
+ match: (filepath, filename) => (filename === "_app.tsx" || filename === "_app.js") && (filepath.includes("/pages/") || filepath.startsWith("pages/")),
1198
+ keywords: ["nextjs", "app", "wrapper", "provider", "pages router"]
949
1199
  }
950
- if (ts.isVariableStatement(node) && isExported(node)) {
951
- for (const decl of node.declarationList.declarations) {
952
- if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
953
- continue;
1200
+ ];
1201
+ nextjsFramework = {
1202
+ id: "nextjs",
1203
+ name: "Next.js",
1204
+ detect: (filepath) => {
1205
+ return filepath === "next.config.js" || filepath === "next.config.mjs" || filepath === "next.config.ts" || filepath.includes("/app/page.") || filepath.includes("/pages/_app.");
1206
+ },
1207
+ conventions: nextjsConventions
1208
+ };
1209
+ });
1210
+
1211
+ // src/introspection/conventions/frameworks/convex.ts
1212
+ var convexConventions, convexFramework;
1213
+ var init_convex = __esm(() => {
1214
+ convexConventions = [
1215
+ {
1216
+ id: "convex-config",
1217
+ name: "Convex Config",
1218
+ description: "Convex project configuration",
1219
+ category: "configuration",
1220
+ match: (filepath, filename) => filename === "convex.json",
1221
+ keywords: ["convex", "config", "backend", "settings"]
1222
+ },
1223
+ {
1224
+ id: "convex-schema",
1225
+ name: "Convex Schema",
1226
+ description: "Convex database schema definition",
1227
+ category: "framework",
1228
+ match: (filepath, filename) => filename === "schema.ts" && (filepath.includes("/convex/") || filepath.startsWith("convex/")),
1229
+ keywords: ["convex", "schema", "database", "tables", "types", "model"]
1230
+ },
1231
+ {
1232
+ id: "convex-function",
1233
+ name: "Convex Function File",
1234
+ description: "Convex backend function file",
1235
+ category: "framework",
1236
+ match: (filepath, filename, extension) => (extension === ".ts" || extension === ".js") && (filepath.includes("/convex/") || filepath.startsWith("convex/")) && !filepath.includes("/_generated/") && filename !== "schema.ts" && !filename.startsWith("_"),
1237
+ keywords: ["convex", "function", "backend", "query", "mutation", "action"],
1238
+ dynamicKeywords: (filepath) => {
1239
+ const match = filepath.match(/convex\/(.+?)\.(ts|js)/);
1240
+ if (match) {
1241
+ const name = match[1].replace(/\//g, " ").split(" ").pop() || "";
1242
+ if (name && !["schema", "http", "crons"].includes(name)) {
1243
+ return [name.toLowerCase()];
1244
+ }
954
1245
  }
955
- const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
956
- chunks.push({
957
- content: getNodeText(node),
958
- startLine,
959
- endLine,
960
- type: "variable",
961
- name,
962
- isExported: true,
963
- jsDoc: getJSDoc(node)
964
- });
1246
+ return [];
965
1247
  }
966
- return;
1248
+ },
1249
+ {
1250
+ id: "convex-http",
1251
+ name: "Convex HTTP Routes",
1252
+ description: "Convex HTTP endpoint definitions",
1253
+ category: "framework",
1254
+ match: (filepath, filename) => filename === "http.ts" && (filepath.includes("/convex/") || filepath.startsWith("convex/")),
1255
+ keywords: ["convex", "http", "routes", "api", "endpoints", "rest"]
1256
+ },
1257
+ {
1258
+ id: "convex-crons",
1259
+ name: "Convex Cron Jobs",
1260
+ description: "Convex scheduled function definitions",
1261
+ category: "framework",
1262
+ match: (filepath, filename) => filename === "crons.ts" && (filepath.includes("/convex/") || filepath.startsWith("convex/")),
1263
+ keywords: [
1264
+ "convex",
1265
+ "crons",
1266
+ "scheduled",
1267
+ "jobs",
1268
+ "background",
1269
+ "recurring"
1270
+ ]
1271
+ },
1272
+ {
1273
+ id: "convex-generated",
1274
+ name: "Convex Generated",
1275
+ description: "Convex auto-generated files",
1276
+ category: "framework",
1277
+ match: (filepath) => filepath.includes("/convex/_generated/") || filepath.startsWith("convex/_generated/"),
1278
+ keywords: ["convex", "generated", "types", "api"]
1279
+ },
1280
+ {
1281
+ id: "convex-auth",
1282
+ name: "Convex Auth",
1283
+ description: "Convex authentication configuration",
1284
+ category: "framework",
1285
+ match: (filepath, filename) => filename === "auth.ts" && (filepath.includes("/convex/") || filepath.startsWith("convex/")),
1286
+ keywords: ["convex", "auth", "authentication", "login", "users"]
1287
+ },
1288
+ {
1289
+ id: "convex-auth-config",
1290
+ name: "Convex Auth Config",
1291
+ description: "Convex auth configuration file",
1292
+ category: "configuration",
1293
+ match: (filepath, filename) => filename === "auth.config.ts",
1294
+ keywords: ["convex", "auth", "config", "providers", "oauth"]
967
1295
  }
968
- ts.forEachChild(node, visit);
969
- }
970
- ts.forEachChild(sourceFile, visit);
971
- if (chunks.length === 0) {
972
- return parseGenericCode(content);
973
- }
974
- return chunks;
1296
+ ];
1297
+ convexFramework = {
1298
+ id: "convex",
1299
+ name: "Convex",
1300
+ detect: (filepath) => {
1301
+ return filepath === "convex.json" || filepath.startsWith("convex/") || filepath.includes("/convex/");
1302
+ },
1303
+ conventions: convexConventions
1304
+ };
1305
+ });
1306
+
1307
+ // src/introspection/conventions/frameworks/index.ts
1308
+ function getAllFrameworkConventions() {
1309
+ return frameworkProviders.flatMap((f) => f.conventions);
1310
+ }
1311
+ var frameworkProviders;
1312
+ var init_frameworks = __esm(() => {
1313
+ init_nextjs();
1314
+ init_convex();
1315
+ init_nextjs();
1316
+ init_convex();
1317
+ frameworkProviders = [
1318
+ nextjsFramework,
1319
+ convexFramework
1320
+ ];
1321
+ });
1322
+
1323
+ // src/introspection/conventions/index.ts
1324
+ import * as path5 from "path";
1325
+ function getAllConventions() {
1326
+ return [
1327
+ ...entryPointConventions,
1328
+ ...configFileConventions,
1329
+ ...getAllFrameworkConventions()
1330
+ ];
975
1331
  }
976
- function parseGenericCode(content) {
977
- const chunks = [];
978
- const lines = content.split(`
979
- `);
980
- const CHUNK_SIZE = 30;
981
- const OVERLAP = 5;
982
- if (lines.length <= CHUNK_SIZE) {
983
- return [
984
- {
985
- content,
986
- startLine: 1,
987
- endLine: lines.length,
988
- type: "file"
1332
+ function getConventions() {
1333
+ return [
1334
+ ...getAllConventions(),
1335
+ ...typeDefinitionConventions,
1336
+ ...testFileConventions
1337
+ ];
1338
+ }
1339
+ function getConventionKeywords(filepath) {
1340
+ const conventions = getConventions();
1341
+ const filename = path5.basename(filepath);
1342
+ const extension = path5.extname(filepath);
1343
+ const keywords = new Set;
1344
+ for (const convention of conventions) {
1345
+ try {
1346
+ if (convention.match(filepath, filename, extension)) {
1347
+ for (const keyword of convention.keywords) {
1348
+ keywords.add(keyword.toLowerCase());
1349
+ }
1350
+ if (convention.dynamicKeywords) {
1351
+ const dynamicKws = convention.dynamicKeywords(filepath);
1352
+ for (const kw of dynamicKws) {
1353
+ if (kw && kw.length > 1) {
1354
+ keywords.add(kw.toLowerCase());
1355
+ }
1356
+ }
1357
+ }
989
1358
  }
990
- ];
991
- }
992
- for (let i = 0;i < lines.length; i += CHUNK_SIZE - OVERLAP) {
993
- const endIdx = Math.min(i + CHUNK_SIZE, lines.length);
994
- chunks.push({
995
- content: lines.slice(i, endIdx).join(`
996
- `),
997
- startLine: i + 1,
998
- endLine: endIdx,
999
- type: "block"
1000
- });
1001
- if (endIdx >= lines.length)
1002
- break;
1359
+ } catch {}
1003
1360
  }
1004
- return chunks;
1361
+ return Array.from(keywords);
1005
1362
  }
1006
- function generateChunkId(filepath, startLine, endLine) {
1007
- const safePath = filepath.replace(/[/\\]/g, "-").replace(/\./g, "_");
1008
- return `${safePath}-${startLine}-${endLine}`;
1363
+ var typeDefinitionConventions, testFileConventions;
1364
+ var init_conventions = __esm(() => {
1365
+ init_entryPoints();
1366
+ init_configFiles();
1367
+ init_frameworks();
1368
+ init_entryPoints();
1369
+ init_configFiles();
1370
+ init_frameworks();
1371
+ typeDefinitionConventions = [
1372
+ {
1373
+ id: "dts-file",
1374
+ name: "TypeScript Declaration",
1375
+ description: "TypeScript type declaration file",
1376
+ category: "types",
1377
+ match: (filepath, filename) => filename.endsWith(".d.ts"),
1378
+ keywords: ["types", "declarations", "typescript", "definitions"]
1379
+ },
1380
+ {
1381
+ id: "types-file",
1382
+ name: "Types File",
1383
+ description: "TypeScript types file",
1384
+ category: "types",
1385
+ match: (filepath, filename) => filename.endsWith(".types.ts") || filename === "types.ts",
1386
+ keywords: ["types", "definitions", "typescript", "interfaces"],
1387
+ dynamicKeywords: (filepath) => {
1388
+ const match = filepath.match(/([^/]+)\.types\.ts$/);
1389
+ if (match) {
1390
+ return [match[1].toLowerCase()];
1391
+ }
1392
+ return [];
1393
+ }
1394
+ },
1395
+ {
1396
+ id: "types-folder",
1397
+ name: "Types Folder File",
1398
+ description: "File in a types folder",
1399
+ category: "types",
1400
+ match: (filepath) => filepath.includes("/types/") || filepath.startsWith("types/"),
1401
+ keywords: ["types", "definitions"]
1402
+ }
1403
+ ];
1404
+ testFileConventions = [
1405
+ {
1406
+ id: "test-file",
1407
+ name: "Test File",
1408
+ description: "Unit/integration test file",
1409
+ category: "test",
1410
+ match: (filepath, filename) => filename.includes(".test.") || filename.includes(".spec.") || filename.includes("_test."),
1411
+ keywords: ["test", "spec", "unit test"],
1412
+ dynamicKeywords: (filepath) => {
1413
+ const match = filepath.match(/([^/]+)\.(test|spec)\./);
1414
+ if (match) {
1415
+ return [match[1].toLowerCase()];
1416
+ }
1417
+ return [];
1418
+ }
1419
+ },
1420
+ {
1421
+ id: "test-folder",
1422
+ name: "Test Folder File",
1423
+ description: "File in a test folder",
1424
+ category: "test",
1425
+ match: (filepath) => filepath.includes("/__tests__/") || filepath.includes("/test/") || filepath.includes("/tests/") || filepath.startsWith("__tests__/") || filepath.startsWith("test/") || filepath.startsWith("tests/"),
1426
+ keywords: ["test", "testing"]
1427
+ }
1428
+ ];
1429
+ });
1430
+
1431
+ // src/introspection/fileIntrospector.ts
1432
+ import * as path6 from "path";
1433
+ function introspectFile(filepath, structure, fileContent) {
1434
+ const normalizedPath = filepath.replace(/\\/g, "/");
1435
+ const segments = normalizedPath.split("/").filter((s) => s.length > 0);
1436
+ const filename = segments[segments.length - 1] || "";
1437
+ const ext = path6.extname(filename);
1438
+ const project = findProjectForFile(normalizedPath, structure);
1439
+ const language = EXTENSION_TO_LANGUAGE[ext] || "unknown";
1440
+ const layer = detectLayer(segments, filename);
1441
+ const domain = detectDomain(segments);
1442
+ const scope = detectScope(segments, project, layer);
1443
+ let framework;
1444
+ if (fileContent) {
1445
+ framework = detectFramework(fileContent);
1446
+ }
1447
+ return {
1448
+ filepath: normalizedPath,
1449
+ project,
1450
+ scope,
1451
+ layer,
1452
+ domain,
1453
+ language,
1454
+ framework,
1455
+ depth: segments.length - 1,
1456
+ pathSegments: segments.slice(0, -1)
1457
+ };
1009
1458
  }
1010
- var init_parseCode = () => {};
1011
-
1012
- // src/infrastructure/storage/fileIndexStorage.ts
1013
- var init_fileIndexStorage = __esm(() => {
1014
- init_entities();
1015
- });
1016
-
1017
- // src/domain/services/keywords.ts
1018
- function extractKeywords(content, name, maxKeywords = 50) {
1019
- const keywords = new Set;
1020
- if (name) {
1021
- keywords.add(name.toLowerCase());
1022
- const parts = name.split(/(?=[A-Z])/).map((p) => p.toLowerCase());
1023
- parts.forEach((p) => p.length > 2 && keywords.add(p));
1459
+ function detectLayer(segments, filename) {
1460
+ const filenameLower = filename.toLowerCase();
1461
+ for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
1462
+ for (const pattern of patterns) {
1463
+ if (filenameLower.includes(pattern)) {
1464
+ return layer;
1465
+ }
1466
+ }
1024
1467
  }
1025
- const identifierRegex = /\b([a-zA-Z_][a-zA-Z0-9_]{2,})\b/g;
1026
- let match;
1027
- while ((match = identifierRegex.exec(content)) !== null) {
1028
- const word = match[1].toLowerCase();
1029
- if (!COMMON_KEYWORDS.has(word) && word.length > 2) {
1030
- keywords.add(word);
1468
+ for (let i = segments.length - 2;i >= 0; i--) {
1469
+ const segment = segments[i].toLowerCase();
1470
+ for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
1471
+ if (patterns.includes(segment)) {
1472
+ return layer;
1473
+ }
1031
1474
  }
1032
1475
  }
1033
- return Array.from(keywords).slice(0, maxKeywords);
1034
- }
1035
- function splitIdentifier(str) {
1036
- return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/).map((s) => s.toLowerCase()).filter((s) => s.length > 1);
1476
+ return;
1037
1477
  }
1038
- function extractPathKeywords(filepath) {
1039
- const keywords = new Set;
1040
- const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
1041
- const segments = pathWithoutExt.split(/[/\\]/);
1478
+ function detectDomain(segments) {
1479
+ const skipSegments = new Set([
1480
+ "src",
1481
+ "lib",
1482
+ "app",
1483
+ "apps",
1484
+ "packages",
1485
+ "services",
1486
+ "modules",
1487
+ "features",
1488
+ ...Object.values(LAYER_PATTERNS).flat()
1489
+ ]);
1042
1490
  for (const segment of segments) {
1043
- if (segment.length < 2)
1491
+ const segmentLower = segment.toLowerCase();
1492
+ if (skipSegments.has(segmentLower))
1044
1493
  continue;
1045
- const lower = segment.toLowerCase();
1046
- if (!COMMON_KEYWORDS.has(lower) && lower.length > 2) {
1047
- keywords.add(lower);
1494
+ if (DOMAIN_PATTERNS.includes(segmentLower)) {
1495
+ return segmentLower;
1048
1496
  }
1049
- const parts = splitIdentifier(segment);
1050
- for (const part of parts) {
1051
- if (!COMMON_KEYWORDS.has(part) && part.length > 2) {
1052
- keywords.add(part);
1497
+ for (const domain of DOMAIN_PATTERNS) {
1498
+ if (segmentLower.startsWith(domain) || segmentLower.endsWith(domain)) {
1499
+ return domain;
1053
1500
  }
1054
1501
  }
1055
1502
  }
1056
- return Array.from(keywords);
1503
+ return;
1057
1504
  }
1058
- function parsePathContext(filepath) {
1059
- const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
1060
- const allSegments = pathWithoutExt.split(/[/\\]/);
1061
- const filename = allSegments[allSegments.length - 1];
1062
- const dirSegments = allSegments.slice(0, -1);
1063
- const keywords = extractPathKeywords(filepath);
1064
- let layer;
1065
- const allLower = [...dirSegments, filename].map((s) => s.toLowerCase()).join(" ");
1066
- const filenameLower = filename.toLowerCase();
1067
- for (const [layerName, patterns] of Object.entries(LAYER_PATTERNS)) {
1068
- for (const pattern of patterns) {
1069
- if (filenameLower.includes(pattern)) {
1070
- layer = layerName;
1071
- break;
1072
- }
1073
- if (dirSegments.some((s) => s.toLowerCase() === pattern)) {
1074
- layer = layerName;
1075
- break;
1076
- }
1505
+ function detectScope(segments, project, layer) {
1506
+ const projectScope = detectScopeFromName(project.name);
1507
+ if (projectScope !== "unknown") {
1508
+ return projectScope;
1509
+ }
1510
+ if (layer) {
1511
+ switch (layer) {
1512
+ case "controller":
1513
+ case "repository":
1514
+ case "middleware":
1515
+ return "backend";
1516
+ case "presentation":
1517
+ return "frontend";
1518
+ case "util":
1519
+ case "model":
1520
+ return "shared";
1521
+ case "test":
1522
+ return "tooling";
1077
1523
  }
1078
- if (layer)
1079
- break;
1080
1524
  }
1081
- let domain;
1082
- const layerPatternSet = new Set(Object.values(LAYER_PATTERNS).flat());
1083
- const reversedSegments = [...dirSegments].reverse();
1084
- for (const segment of reversedSegments) {
1085
- const lower = segment.toLowerCase();
1086
- if (["src", "lib", "app", "packages", "modules"].includes(lower))
1087
- continue;
1088
- if (layerPatternSet.has(lower))
1089
- continue;
1090
- if (lower.length > 2) {
1091
- domain = lower;
1092
- break;
1525
+ for (const segment of segments) {
1526
+ const segmentLower = segment.toLowerCase();
1527
+ if (["server", "api", "backend"].includes(segmentLower)) {
1528
+ return "backend";
1529
+ }
1530
+ if (["client", "web", "frontend", "ui"].includes(segmentLower)) {
1531
+ return "frontend";
1532
+ }
1533
+ if (["shared", "common", "lib", "libs"].includes(segmentLower)) {
1534
+ return "shared";
1093
1535
  }
1094
1536
  }
1095
- return {
1096
- segments: dirSegments,
1097
- layer,
1098
- domain,
1099
- depth: dirSegments.length,
1100
- keywords
1101
- };
1537
+ return "unknown";
1102
1538
  }
1103
- function formatPathContextForEmbedding(pathContext) {
1104
- const parts = [];
1105
- if (pathContext.domain) {
1106
- parts.push(pathContext.domain);
1539
+ function detectFramework(content) {
1540
+ for (const [framework, indicators] of Object.entries(FRAMEWORK_INDICATORS)) {
1541
+ for (const indicator of indicators) {
1542
+ if (content.includes(`from '${indicator}`) || content.includes(`from "${indicator}`) || content.includes(`require('${indicator}`) || content.includes(`require("${indicator}`)) {
1543
+ return framework;
1544
+ }
1545
+ }
1107
1546
  }
1108
- if (pathContext.layer) {
1109
- parts.push(pathContext.layer);
1547
+ return;
1548
+ }
1549
+ function introspectionToKeywords(intro) {
1550
+ const keywords = [];
1551
+ const filename = path6.basename(intro.filepath);
1552
+ const filenameWithoutExt = filename.replace(/\.[^.]+$/, "");
1553
+ const filenameParts = filenameWithoutExt.split(/[-_.]/).flatMap((part) => part.split(/(?=[A-Z])/)).map((part) => part.toLowerCase()).filter((part) => part.length > 1);
1554
+ keywords.push(...filenameParts);
1555
+ keywords.push(filenameWithoutExt.toLowerCase());
1556
+ if (intro.project.name && intro.project.name !== "root") {
1557
+ keywords.push(intro.project.name.toLowerCase());
1110
1558
  }
1111
- const significantSegments = pathContext.segments.slice(-3).filter((s) => s.length > 2 && !["src", "lib", "app"].includes(s.toLowerCase()));
1112
- if (significantSegments.length > 0) {
1113
- parts.push(...significantSegments.map((s) => s.toLowerCase()));
1559
+ if (intro.scope !== "unknown") {
1560
+ keywords.push(intro.scope);
1114
1561
  }
1115
- if (parts.length === 0)
1116
- return "";
1117
- const unique = [...new Set(parts)];
1118
- return `[${unique.join(" ")}]`;
1562
+ if (intro.layer) {
1563
+ keywords.push(intro.layer);
1564
+ }
1565
+ if (intro.domain) {
1566
+ keywords.push(intro.domain);
1567
+ }
1568
+ if (intro.language !== "unknown") {
1569
+ keywords.push(intro.language);
1570
+ }
1571
+ if (intro.framework) {
1572
+ keywords.push(intro.framework);
1573
+ }
1574
+ const skipSegments = new Set(["src", "lib", "index"]);
1575
+ for (const segment of intro.pathSegments) {
1576
+ if (!skipSegments.has(segment.toLowerCase()) && segment.length > 2) {
1577
+ keywords.push(segment.toLowerCase());
1578
+ }
1579
+ }
1580
+ const conventionKeywords = getConventionKeywords(intro.filepath);
1581
+ keywords.push(...conventionKeywords);
1582
+ return [...new Set(keywords)];
1119
1583
  }
1120
- var COMMON_KEYWORDS, LAYER_PATTERNS;
1121
- var init_keywords = __esm(() => {
1122
- COMMON_KEYWORDS = new Set([
1123
- "const",
1124
- "let",
1125
- "var",
1126
- "function",
1127
- "class",
1128
- "interface",
1129
- "type",
1130
- "enum",
1131
- "export",
1132
- "import",
1133
- "from",
1134
- "return",
1135
- "async",
1136
- "await",
1137
- "new",
1138
- "this",
1139
- "true",
1140
- "false",
1141
- "null",
1142
- "undefined",
1143
- "if",
1144
- "else",
1145
- "for",
1146
- "while",
1147
- "switch",
1148
- "case",
1149
- "break",
1150
- "continue",
1151
- "try",
1152
- "catch",
1153
- "finally",
1154
- "throw",
1155
- "typeof",
1156
- "instanceof",
1157
- "void",
1158
- "delete",
1159
- "in",
1160
- "of",
1161
- "string",
1162
- "number",
1163
- "boolean",
1164
- "any",
1165
- "unknown",
1166
- "never",
1167
- "object",
1168
- "public",
1169
- "private",
1170
- "protected",
1171
- "static",
1172
- "readonly",
1173
- "abstract",
1174
- "implements",
1175
- "extends",
1176
- "super",
1177
- "get",
1178
- "set",
1179
- "constructor",
1180
- "the",
1181
- "and",
1182
- "for",
1183
- "not",
1184
- "with",
1185
- "are",
1186
- "was",
1187
- "has",
1188
- "have"
1189
- ]);
1584
+ var LAYER_PATTERNS, DOMAIN_PATTERNS, FRAMEWORK_INDICATORS, EXTENSION_TO_LANGUAGE;
1585
+ var init_fileIntrospector = __esm(() => {
1586
+ init_projectDetector();
1587
+ init_conventions();
1190
1588
  LAYER_PATTERNS = {
1191
- controller: ["controller", "controllers", "handler", "handlers", "route", "routes", "api"],
1192
- service: ["service", "services", "usecase", "usecases", "application"],
1193
- repository: ["repository", "repositories", "repo", "repos", "dao", "store", "storage"],
1194
- model: ["model", "models", "entity", "entities", "schema", "schemas"],
1195
- util: ["util", "utils", "utility", "utilities", "helper", "helpers", "common", "shared"],
1196
- config: ["config", "configs", "configuration", "settings"],
1197
- middleware: ["middleware", "middlewares", "interceptor", "interceptors"],
1198
- domain: ["domain", "core", "business"],
1199
- infrastructure: ["infrastructure", "infra", "external", "adapters"],
1200
- presentation: ["presentation", "view", "views", "component", "components", "ui"],
1201
- test: ["test", "tests", "spec", "specs", "__tests__", "__test__"]
1589
+ controller: ["controller", "api", "routes", "route", "handler"],
1590
+ service: ["service", "logic", "usecase", "usecases", "handler"],
1591
+ repository: ["repository", "repo", "dao", "store", "persistence"],
1592
+ model: [
1593
+ "model",
1594
+ "models",
1595
+ "entity",
1596
+ "entities",
1597
+ "schema",
1598
+ "schemas",
1599
+ "types",
1600
+ "type"
1601
+ ],
1602
+ util: ["util", "utils", "helper", "helpers", "common", "lib"],
1603
+ config: ["config", "configuration", "settings"],
1604
+ middleware: ["middleware", "middlewares"],
1605
+ domain: ["domain"],
1606
+ infrastructure: ["infrastructure", "infra"],
1607
+ application: ["application", "app"],
1608
+ presentation: [
1609
+ "presentation",
1610
+ "ui",
1611
+ "views",
1612
+ "view",
1613
+ "component",
1614
+ "components"
1615
+ ],
1616
+ test: ["test", "tests", "spec", "specs", "__tests__", "e2e"]
1617
+ };
1618
+ DOMAIN_PATTERNS = [
1619
+ "auth",
1620
+ "authentication",
1621
+ "user",
1622
+ "users",
1623
+ "account",
1624
+ "accounts",
1625
+ "profile",
1626
+ "profiles",
1627
+ "product",
1628
+ "products",
1629
+ "item",
1630
+ "items",
1631
+ "catalog",
1632
+ "order",
1633
+ "orders",
1634
+ "cart",
1635
+ "checkout",
1636
+ "payment",
1637
+ "payments",
1638
+ "billing",
1639
+ "subscription",
1640
+ "subscriptions",
1641
+ "notification",
1642
+ "notifications",
1643
+ "email",
1644
+ "sms",
1645
+ "report",
1646
+ "reports",
1647
+ "analytics",
1648
+ "metrics",
1649
+ "dashboard",
1650
+ "admin",
1651
+ "settings",
1652
+ "search",
1653
+ "chat",
1654
+ "message",
1655
+ "messages",
1656
+ "feed",
1657
+ "post",
1658
+ "posts",
1659
+ "comment",
1660
+ "comments",
1661
+ "media",
1662
+ "upload",
1663
+ "file",
1664
+ "files",
1665
+ "storage",
1666
+ "cache",
1667
+ "session",
1668
+ "log",
1669
+ "logs",
1670
+ "audit"
1671
+ ];
1672
+ FRAMEWORK_INDICATORS = {
1673
+ nextjs: ["next", "next/"],
1674
+ express: ["express"],
1675
+ fastify: ["fastify"],
1676
+ react: ["react"],
1677
+ vue: ["vue"],
1678
+ angular: ["@angular/"],
1679
+ nestjs: ["@nestjs/"],
1680
+ koa: ["koa"]
1681
+ };
1682
+ EXTENSION_TO_LANGUAGE = {
1683
+ ".ts": "typescript",
1684
+ ".tsx": "typescript",
1685
+ ".js": "javascript",
1686
+ ".jsx": "javascript",
1687
+ ".mjs": "javascript",
1688
+ ".cjs": "javascript",
1689
+ ".py": "python",
1690
+ ".go": "go",
1691
+ ".rs": "rust",
1692
+ ".java": "java",
1693
+ ".kt": "kotlin",
1694
+ ".swift": "swift",
1695
+ ".rb": "ruby",
1696
+ ".php": "php",
1697
+ ".cs": "csharp",
1698
+ ".cpp": "cpp",
1699
+ ".c": "c",
1700
+ ".h": "c",
1701
+ ".hpp": "cpp",
1702
+ ".md": "markdown",
1703
+ ".json": "json",
1704
+ ".yaml": "yaml",
1705
+ ".yml": "yaml"
1202
1706
  };
1203
1707
  });
1204
1708
 
1205
- // src/infrastructure/storage/symbolicIndex.ts
1709
+ // src/introspection/index.ts
1710
+ import * as path7 from "path";
1206
1711
  import * as fs3 from "fs/promises";
1207
- import * as path4 from "path";
1208
1712
 
1209
- class SymbolicIndex {
1210
- meta = null;
1211
- fileSummaries = new Map;
1212
- bm25Index = null;
1213
- symbolicPath;
1214
- moduleId;
1215
- constructor(indexDir, moduleId) {
1216
- this.symbolicPath = path4.join(indexDir, "index", moduleId, "symbolic");
1217
- this.moduleId = moduleId;
1713
+ class IntrospectionIndex {
1714
+ rootDir;
1715
+ structure = null;
1716
+ files = new Map;
1717
+ config = {};
1718
+ constructor(rootDir) {
1719
+ this.rootDir = rootDir;
1218
1720
  }
1219
1721
  async initialize() {
1722
+ this.structure = await detectProjectStructure(this.rootDir);
1220
1723
  try {
1221
- await this.load();
1222
- } catch {
1223
- this.meta = {
1224
- version: "1.0.0",
1225
- lastUpdated: new Date().toISOString(),
1226
- moduleId: this.moduleId,
1227
- fileCount: 0,
1228
- bm25Data: {
1229
- avgDocLength: 0,
1230
- documentFrequencies: {},
1231
- totalDocs: 0
1232
- }
1233
- };
1234
- this.bm25Index = new BM25Index;
1235
- }
1236
- }
1237
- addFile(summary) {
1238
- this.fileSummaries.set(summary.filepath, summary);
1724
+ const configPath = path7.join(this.rootDir, ".raggrep", "config.json");
1725
+ const configContent = await fs3.readFile(configPath, "utf-8");
1726
+ const config = JSON.parse(configContent);
1727
+ this.config = config.introspection || {};
1728
+ } catch {}
1239
1729
  }
1240
- removeFile(filepath) {
1241
- return this.fileSummaries.delete(filepath);
1730
+ getStructure() {
1731
+ return this.structure;
1242
1732
  }
1243
- buildBM25Index() {
1244
- this.bm25Index = new BM25Index;
1245
- for (const [filepath, summary] of this.fileSummaries) {
1246
- const content = [
1247
- ...summary.keywords,
1248
- ...summary.exports,
1249
- ...extractPathKeywords(filepath)
1250
- ].join(" ");
1251
- this.bm25Index.addDocuments([{ id: filepath, content }]);
1252
- }
1253
- if (this.meta) {
1254
- this.meta.fileCount = this.fileSummaries.size;
1255
- this.meta.bm25Data.totalDocs = this.fileSummaries.size;
1733
+ addFile(filepath, content) {
1734
+ if (!this.structure) {
1735
+ throw new Error("IntrospectionIndex not initialized");
1256
1736
  }
1737
+ const intro = introspectFile(filepath, this.structure, content);
1738
+ this.applyOverrides(intro);
1739
+ this.files.set(filepath, intro);
1740
+ return intro;
1257
1741
  }
1258
- findCandidates(query, maxCandidates = 20) {
1259
- if (!this.bm25Index) {
1260
- return Array.from(this.fileSummaries.keys());
1261
- }
1262
- const results = this.bm25Index.search(query, maxCandidates);
1263
- return results.map((r) => r.id);
1742
+ getFile(filepath) {
1743
+ return this.files.get(filepath);
1264
1744
  }
1265
1745
  getAllFiles() {
1266
- return Array.from(this.fileSummaries.keys());
1746
+ return Array.from(this.files.values());
1267
1747
  }
1268
- getFileSummary(filepath) {
1269
- return this.fileSummaries.get(filepath);
1748
+ applyOverrides(intro) {
1749
+ if (!this.config.projects)
1750
+ return;
1751
+ for (const [projectPath, overrides] of Object.entries(this.config.projects)) {
1752
+ if (intro.filepath.startsWith(projectPath + "/") || intro.project.root === projectPath) {
1753
+ if (overrides.scope) {
1754
+ intro.scope = overrides.scope;
1755
+ }
1756
+ if (overrides.framework) {
1757
+ intro.framework = overrides.framework;
1758
+ }
1759
+ break;
1760
+ }
1761
+ }
1270
1762
  }
1271
- async save() {
1272
- if (!this.meta)
1273
- throw new Error("Index not initialized");
1274
- this.meta.lastUpdated = new Date().toISOString();
1275
- this.meta.fileCount = this.fileSummaries.size;
1276
- await fs3.mkdir(this.symbolicPath, { recursive: true });
1277
- const metaPath = path4.join(this.symbolicPath, "_meta.json");
1278
- await fs3.writeFile(metaPath, JSON.stringify(this.meta, null, 2));
1279
- for (const [filepath, summary] of this.fileSummaries) {
1280
- const summaryPath = this.getFileSummaryPath(filepath);
1281
- await fs3.mkdir(path4.dirname(summaryPath), { recursive: true });
1282
- await fs3.writeFile(summaryPath, JSON.stringify(summary, null, 2));
1763
+ async save(config) {
1764
+ const introDir = path7.join(getRaggrepDir(this.rootDir, config), "introspection");
1765
+ await fs3.mkdir(introDir, { recursive: true });
1766
+ const projectPath = path7.join(introDir, "_project.json");
1767
+ await fs3.writeFile(projectPath, JSON.stringify({
1768
+ version: "1.0.0",
1769
+ lastUpdated: new Date().toISOString(),
1770
+ structure: this.structure
1771
+ }, null, 2));
1772
+ for (const [filepath, intro] of this.files) {
1773
+ const introFilePath = path7.join(introDir, "files", filepath.replace(/\.[^.]+$/, ".json"));
1774
+ await fs3.mkdir(path7.dirname(introFilePath), { recursive: true });
1775
+ await fs3.writeFile(introFilePath, JSON.stringify(intro, null, 2));
1283
1776
  }
1777
+ console.log(` [Introspection] Saved metadata for ${this.files.size} files`);
1284
1778
  }
1285
- async load() {
1286
- const metaPath = path4.join(this.symbolicPath, "_meta.json");
1287
- const metaContent = await fs3.readFile(metaPath, "utf-8");
1288
- this.meta = JSON.parse(metaContent);
1289
- this.fileSummaries.clear();
1290
- await this.loadFileSummariesRecursive(this.symbolicPath);
1291
- this.buildBM25Index();
1779
+ async load(config) {
1780
+ const introDir = path7.join(getRaggrepDir(this.rootDir, config), "introspection");
1781
+ try {
1782
+ const projectPath = path7.join(introDir, "_project.json");
1783
+ const projectContent = await fs3.readFile(projectPath, "utf-8");
1784
+ const projectData = JSON.parse(projectContent);
1785
+ this.structure = projectData.structure;
1786
+ await this.loadFilesRecursive(path7.join(introDir, "files"), "");
1787
+ } catch {
1788
+ this.structure = null;
1789
+ this.files.clear();
1790
+ }
1292
1791
  }
1293
- async loadFileSummariesRecursive(dir) {
1792
+ async loadFilesRecursive(basePath, prefix) {
1294
1793
  try {
1295
- const entries = await fs3.readdir(dir, { withFileTypes: true });
1794
+ const entries = await fs3.readdir(basePath, { withFileTypes: true });
1296
1795
  for (const entry of entries) {
1297
- const fullPath = path4.join(dir, entry.name);
1796
+ const entryPath = path7.join(basePath, entry.name);
1797
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
1298
1798
  if (entry.isDirectory()) {
1299
- await this.loadFileSummariesRecursive(fullPath);
1300
- } else if (entry.name.endsWith(".json") && entry.name !== "_meta.json") {
1301
- try {
1302
- const content = await fs3.readFile(fullPath, "utf-8");
1303
- const summary = JSON.parse(content);
1304
- if (summary.filepath) {
1305
- this.fileSummaries.set(summary.filepath, summary);
1306
- }
1307
- } catch {}
1799
+ await this.loadFilesRecursive(entryPath, relativePath);
1800
+ } else if (entry.name.endsWith(".json")) {
1801
+ const content = await fs3.readFile(entryPath, "utf-8");
1802
+ const intro = JSON.parse(content);
1803
+ this.files.set(intro.filepath, intro);
1308
1804
  }
1309
1805
  }
1310
1806
  } catch {}
1311
1807
  }
1312
- getFileSummaryPath(filepath) {
1313
- const jsonPath = filepath.replace(/\.[^.]+$/, ".json");
1314
- return path4.join(this.symbolicPath, jsonPath);
1315
- }
1316
- async deleteFileSummary(filepath) {
1317
- try {
1318
- await fs3.unlink(this.getFileSummaryPath(filepath));
1319
- } catch {}
1320
- this.fileSummaries.delete(filepath);
1808
+ clear() {
1809
+ this.files.clear();
1810
+ this.structure = null;
1321
1811
  }
1322
- async exists() {
1323
- try {
1324
- const metaPath = path4.join(this.symbolicPath, "_meta.json");
1325
- await fs3.access(metaPath);
1326
- return true;
1327
- } catch {
1328
- return false;
1812
+ }
1813
+ var init_introspection = __esm(() => {
1814
+ init_projectDetector();
1815
+ init_fileIntrospector();
1816
+ init_config2();
1817
+ init_fileIntrospector();
1818
+ init_projectDetector();
1819
+ });
1820
+
1821
+ // src/modules/core/symbols.ts
1822
+ function extractSymbols(content) {
1823
+ const symbols = [];
1824
+ const seenSymbols = new Set;
1825
+ const lines = content.split(`
1826
+ `);
1827
+ for (const { type, pattern, exported } of SYMBOL_PATTERNS) {
1828
+ pattern.lastIndex = 0;
1829
+ let match;
1830
+ while ((match = pattern.exec(content)) !== null) {
1831
+ const name = match[1];
1832
+ const symbolKey = `${name}:${type}`;
1833
+ if (seenSymbols.has(symbolKey))
1834
+ continue;
1835
+ seenSymbols.add(symbolKey);
1836
+ const beforeMatch = content.substring(0, match.index);
1837
+ const line = beforeMatch.split(`
1838
+ `).length;
1839
+ symbols.push({
1840
+ name,
1841
+ type,
1842
+ line,
1843
+ isExported: exported
1844
+ });
1329
1845
  }
1330
1846
  }
1331
- get size() {
1332
- return this.fileSummaries.size;
1333
- }
1334
- clear() {
1335
- this.fileSummaries.clear();
1336
- if (this.meta) {
1337
- this.meta.fileCount = 0;
1338
- this.meta.bm25Data = {
1339
- avgDocLength: 0,
1340
- documentFrequencies: {},
1341
- totalDocs: 0
1342
- };
1847
+ return symbols.sort((a, b) => a.line - b.line);
1848
+ }
1849
+ function symbolsToKeywords(symbols) {
1850
+ const keywords = new Set;
1851
+ for (const symbol of symbols) {
1852
+ keywords.add(symbol.name.toLowerCase());
1853
+ const parts = symbol.name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/\s+/);
1854
+ for (const part of parts) {
1855
+ if (part.length > 2) {
1856
+ keywords.add(part);
1857
+ }
1343
1858
  }
1344
- this.bm25Index = new BM25Index;
1345
1859
  }
1860
+ return Array.from(keywords);
1346
1861
  }
1347
- var init_symbolicIndex = __esm(() => {
1348
- init_keywords();
1349
- });
1350
-
1351
- // src/infrastructure/storage/index.ts
1352
- var init_storage = __esm(() => {
1353
- init_fileIndexStorage();
1354
- init_symbolicIndex();
1862
+ var SYMBOL_PATTERNS;
1863
+ var init_symbols = __esm(() => {
1864
+ SYMBOL_PATTERNS = [
1865
+ {
1866
+ type: "function",
1867
+ pattern: /^export\s+(?:async\s+)?function\s+(\w+)/gm,
1868
+ exported: true
1869
+ },
1870
+ {
1871
+ type: "function",
1872
+ pattern: /^export\s+(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/gm,
1873
+ exported: true
1874
+ },
1875
+ {
1876
+ type: "class",
1877
+ pattern: /^export\s+(?:abstract\s+)?class\s+(\w+)/gm,
1878
+ exported: true
1879
+ },
1880
+ {
1881
+ type: "interface",
1882
+ pattern: /^export\s+interface\s+(\w+)/gm,
1883
+ exported: true
1884
+ },
1885
+ {
1886
+ type: "type",
1887
+ pattern: /^export\s+type\s+(\w+)/gm,
1888
+ exported: true
1889
+ },
1890
+ {
1891
+ type: "enum",
1892
+ pattern: /^export\s+(?:const\s+)?enum\s+(\w+)/gm,
1893
+ exported: true
1894
+ },
1895
+ {
1896
+ type: "variable",
1897
+ pattern: /^export\s+(?:const|let|var)\s+(\w+)\s*(?::|=)/gm,
1898
+ exported: true
1899
+ },
1900
+ {
1901
+ type: "function",
1902
+ pattern: /^export\s+default\s+(?:async\s+)?function\s+(\w+)/gm,
1903
+ exported: true
1904
+ },
1905
+ {
1906
+ type: "class",
1907
+ pattern: /^export\s+default\s+class\s+(\w+)/gm,
1908
+ exported: true
1909
+ },
1910
+ {
1911
+ type: "function",
1912
+ pattern: /^(?:async\s+)?function\s+(\w+)/gm,
1913
+ exported: false
1914
+ },
1915
+ {
1916
+ type: "function",
1917
+ pattern: /^(?:const|let)\s+(\w+)\s*=\s*(?:async\s*)?\(/gm,
1918
+ exported: false
1919
+ },
1920
+ {
1921
+ type: "class",
1922
+ pattern: /^(?:abstract\s+)?class\s+(\w+)/gm,
1923
+ exported: false
1924
+ },
1925
+ {
1926
+ type: "interface",
1927
+ pattern: /^interface\s+(\w+)/gm,
1928
+ exported: false
1929
+ },
1930
+ {
1931
+ type: "type",
1932
+ pattern: /^type\s+(\w+)/gm,
1933
+ exported: false
1934
+ },
1935
+ {
1936
+ type: "enum",
1937
+ pattern: /^(?:const\s+)?enum\s+(\w+)/gm,
1938
+ exported: false
1939
+ },
1940
+ {
1941
+ type: "function",
1942
+ pattern: /^def\s+(\w+)\s*\(/gm,
1943
+ exported: false
1944
+ },
1945
+ {
1946
+ type: "class",
1947
+ pattern: /^class\s+(\w+)(?:\s*\(|:)/gm,
1948
+ exported: false
1949
+ },
1950
+ {
1951
+ type: "function",
1952
+ pattern: /^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(/gm,
1953
+ exported: false
1954
+ },
1955
+ {
1956
+ type: "type",
1957
+ pattern: /^type\s+(\w+)\s+(?:struct|interface)/gm,
1958
+ exported: false
1959
+ },
1960
+ {
1961
+ type: "function",
1962
+ pattern: /^(?:pub\s+)?(?:async\s+)?fn\s+(\w+)/gm,
1963
+ exported: false
1964
+ },
1965
+ {
1966
+ type: "type",
1967
+ pattern: /^(?:pub\s+)?struct\s+(\w+)/gm,
1968
+ exported: false
1969
+ },
1970
+ {
1971
+ type: "enum",
1972
+ pattern: /^(?:pub\s+)?enum\s+(\w+)/gm,
1973
+ exported: false
1974
+ },
1975
+ {
1976
+ type: "interface",
1977
+ pattern: /^(?:pub\s+)?trait\s+(\w+)/gm,
1978
+ exported: false
1979
+ }
1980
+ ];
1355
1981
  });
1356
1982
 
1357
- // src/modules/language/typescript/index.ts
1358
- var exports_typescript = {};
1359
- __export(exports_typescript, {
1360
- TypeScriptModule: () => TypeScriptModule,
1361
- DEFAULT_TOP_K: () => DEFAULT_TOP_K2,
1362
- DEFAULT_MIN_SCORE: () => DEFAULT_MIN_SCORE2
1983
+ // src/modules/core/index.ts
1984
+ var exports_core = {};
1985
+ __export(exports_core, {
1986
+ CoreModule: () => CoreModule
1363
1987
  });
1364
- import * as path5 from "path";
1988
+ import * as path8 from "path";
1989
+ import * as fs4 from "fs/promises";
1365
1990
 
1366
- class TypeScriptModule {
1367
- id = "language/typescript";
1368
- name = "TypeScript Search";
1369
- description = "TypeScript-aware code search with AST parsing and semantic embeddings";
1991
+ class CoreModule {
1992
+ id = "core";
1993
+ name = "Core Search";
1994
+ description = "Language-agnostic text search with symbol extraction";
1370
1995
  version = "1.0.0";
1371
- embeddingConfig = null;
1372
- symbolicIndex = null;
1373
- pendingSummaries = new Map;
1996
+ symbolIndex = new Map;
1997
+ bm25Index = null;
1374
1998
  rootDir = "";
1375
- async initialize(config) {
1376
- this.embeddingConfig = getEmbeddingConfigFromModule(config);
1377
- configureEmbeddings(this.embeddingConfig);
1378
- this.pendingSummaries.clear();
1379
- }
1999
+ async initialize(_config) {}
1380
2000
  async indexFile(filepath, content, ctx) {
1381
2001
  this.rootDir = ctx.rootDir;
1382
- const parsedChunks = parseCode(content, filepath);
1383
- if (parsedChunks.length === 0) {
1384
- return null;
1385
- }
1386
- const pathContext = parsePathContext(filepath);
1387
- const pathPrefix = formatPathContextForEmbedding(pathContext);
1388
- const chunkContents = parsedChunks.map((c) => {
1389
- const namePrefix = c.name ? `${c.name}: ` : "";
1390
- return `${pathPrefix} ${namePrefix}${c.content}`;
1391
- });
1392
- const embeddings = await getEmbeddings(chunkContents);
1393
- const chunks = parsedChunks.map((pc) => ({
1394
- id: generateChunkId(filepath, pc.startLine, pc.endLine),
1395
- content: pc.content,
1396
- startLine: pc.startLine,
1397
- endLine: pc.endLine,
1398
- type: pc.type,
1399
- name: pc.name,
1400
- isExported: pc.isExported,
1401
- jsDoc: pc.jsDoc
1402
- }));
1403
- const references = this.extractReferences(content, filepath);
1404
- const stats = await ctx.getFileStats(filepath);
1405
- const currentConfig = getEmbeddingConfig();
1406
- const moduleData = {
1407
- embeddings,
1408
- embeddingModel: currentConfig.model
1409
- };
1410
- const chunkTypes = [...new Set(parsedChunks.map((pc) => pc.type))];
1411
- const exports = parsedChunks.filter((pc) => pc.isExported && pc.name).map((pc) => pc.name);
1412
- const allKeywords = new Set;
1413
- for (const pc of parsedChunks) {
1414
- const keywords = extractKeywords(pc.content, pc.name);
1415
- keywords.forEach((k) => allKeywords.add(k));
1416
- }
1417
- pathContext.keywords.forEach((k) => allKeywords.add(k));
1418
- const fileSummary = {
1419
- filepath,
1420
- chunkCount: chunks.length,
1421
- chunkTypes,
1422
- keywords: Array.from(allKeywords),
1423
- exports,
1424
- lastModified: stats.lastModified,
1425
- pathContext: {
1426
- segments: pathContext.segments,
1427
- layer: pathContext.layer,
1428
- domain: pathContext.domain,
1429
- depth: pathContext.depth
1430
- }
2002
+ const symbols = extractSymbols(content);
2003
+ const symbolKeywords = symbolsToKeywords(symbols);
2004
+ const contentTokens = tokenize(content);
2005
+ const intro = ctx.getIntrospection?.(filepath);
2006
+ const introKeywords = intro ? introspectionToKeywords(intro) : [];
2007
+ const allTokens = [...new Set([...contentTokens, ...symbolKeywords, ...introKeywords])];
2008
+ const chunks = this.createChunks(filepath, content, symbols);
2009
+ const stats = await ctx.getFileStats(filepath);
2010
+ this.symbolIndex.set(filepath, {
2011
+ filepath,
2012
+ symbols,
2013
+ tokens: allTokens
2014
+ });
2015
+ const moduleData = {
2016
+ symbols,
2017
+ tokens: allTokens
1431
2018
  };
1432
- this.pendingSummaries.set(filepath, fileSummary);
1433
2019
  return {
1434
2020
  filepath,
1435
2021
  lastModified: stats.lastModified,
1436
2022
  chunks,
1437
- moduleData,
1438
- references
2023
+ moduleData
1439
2024
  };
1440
2025
  }
1441
- async finalize(ctx) {
1442
- const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
1443
- this.symbolicIndex = new SymbolicIndex(indexDir, this.id);
1444
- await this.symbolicIndex.initialize();
1445
- for (const [filepath, summary] of this.pendingSummaries) {
1446
- this.symbolicIndex.addFile(summary);
1447
- }
1448
- this.symbolicIndex.buildBM25Index();
1449
- await this.symbolicIndex.save();
1450
- console.log(` Symbolic index built with ${this.pendingSummaries.size} file summaries`);
1451
- this.pendingSummaries.clear();
1452
- }
1453
- async search(query, ctx, options = {}) {
1454
- const { topK = DEFAULT_TOP_K2, minScore = DEFAULT_MIN_SCORE2, filePatterns } = options;
1455
- const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
1456
- const symbolicIndex = new SymbolicIndex(indexDir, this.id);
1457
- let candidateFiles;
1458
- try {
1459
- await symbolicIndex.initialize();
1460
- const maxCandidates = topK * TIER1_CANDIDATE_MULTIPLIER;
1461
- candidateFiles = symbolicIndex.findCandidates(query, maxCandidates);
1462
- if (candidateFiles.length === 0) {
1463
- candidateFiles = symbolicIndex.getAllFiles();
2026
+ createChunks(filepath, content, symbols) {
2027
+ const lines = content.split(`
2028
+ `);
2029
+ const chunks = [];
2030
+ for (let start = 0;start < lines.length; start += LINES_PER_CHUNK - CHUNK_OVERLAP) {
2031
+ const end = Math.min(start + LINES_PER_CHUNK, lines.length);
2032
+ const chunkLines = lines.slice(start, end);
2033
+ const chunkContent = chunkLines.join(`
2034
+ `);
2035
+ const chunkSymbols = symbols.filter((s) => s.line >= start + 1 && s.line <= end);
2036
+ let chunkType = "block";
2037
+ let chunkName;
2038
+ let isExported = false;
2039
+ if (chunkSymbols.length > 0) {
2040
+ const primarySymbol = chunkSymbols[0];
2041
+ chunkType = this.symbolTypeToChunkType(primarySymbol.type);
2042
+ chunkName = primarySymbol.name;
2043
+ isExported = primarySymbol.isExported;
1464
2044
  }
1465
- } catch {
1466
- candidateFiles = await ctx.listIndexedFiles();
1467
- }
1468
- if (filePatterns && filePatterns.length > 0) {
1469
- candidateFiles = candidateFiles.filter((filepath) => {
1470
- return filePatterns.some((pattern) => {
1471
- if (pattern.startsWith("*.")) {
1472
- const ext = pattern.slice(1);
1473
- return filepath.endsWith(ext);
1474
- }
1475
- return filepath.includes(pattern);
1476
- });
2045
+ const chunkId = `${filepath}:${start + 1}-${end}`;
2046
+ chunks.push({
2047
+ id: chunkId,
2048
+ content: chunkContent,
2049
+ startLine: start + 1,
2050
+ endLine: end,
2051
+ type: chunkType,
2052
+ name: chunkName,
2053
+ isExported
1477
2054
  });
2055
+ if (end >= lines.length)
2056
+ break;
1478
2057
  }
1479
- const queryEmbedding = await getEmbedding(query);
1480
- const bm25Index = new BM25Index;
1481
- const allChunksData = [];
1482
- for (const filepath of candidateFiles) {
1483
- const fileIndex = await ctx.loadFileIndex(filepath);
1484
- if (!fileIndex)
1485
- continue;
1486
- const moduleData = fileIndex.moduleData;
1487
- if (!moduleData?.embeddings)
1488
- continue;
1489
- for (let i = 0;i < fileIndex.chunks.length; i++) {
1490
- const chunk = fileIndex.chunks[i];
1491
- const embedding = moduleData.embeddings[i];
1492
- if (!embedding)
1493
- continue;
1494
- allChunksData.push({
1495
- filepath: fileIndex.filepath,
1496
- chunk,
1497
- embedding
1498
- });
1499
- bm25Index.addDocuments([{ id: chunk.id, content: chunk.content }]);
1500
- }
2058
+ return chunks;
2059
+ }
2060
+ symbolTypeToChunkType(symbolType) {
2061
+ switch (symbolType) {
2062
+ case "function":
2063
+ case "method":
2064
+ return "function";
2065
+ case "class":
2066
+ return "class";
2067
+ case "interface":
2068
+ return "interface";
2069
+ case "type":
2070
+ return "type";
2071
+ case "enum":
2072
+ return "enum";
2073
+ case "variable":
2074
+ return "variable";
2075
+ default:
2076
+ return "block";
1501
2077
  }
1502
- const bm25Results = bm25Index.search(query, topK * 3);
1503
- const bm25Scores = new Map;
1504
- for (const result of bm25Results) {
1505
- bm25Scores.set(result.id, normalizeScore(result.score, 3));
2078
+ }
2079
+ async finalize(ctx) {
2080
+ const config = ctx.config;
2081
+ const coreDir = path8.join(getRaggrepDir(ctx.rootDir, config), "index", "core");
2082
+ await fs4.mkdir(coreDir, { recursive: true });
2083
+ this.bm25Index = new BM25Index;
2084
+ for (const [filepath, entry] of this.symbolIndex) {
2085
+ this.bm25Index.addDocument(filepath, entry.tokens);
1506
2086
  }
1507
- const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
1508
- const pathBoosts = new Map;
1509
- for (const filepath of candidateFiles) {
1510
- const summary = symbolicIndex.getFileSummary(filepath);
1511
- if (summary?.pathContext) {
1512
- let boost = 0;
1513
- const ctx2 = summary.pathContext;
1514
- if (ctx2.domain && queryTerms.some((t) => ctx2.domain.includes(t) || t.includes(ctx2.domain))) {
1515
- boost += 0.1;
1516
- }
1517
- if (ctx2.layer && queryTerms.some((t) => ctx2.layer.includes(t) || t.includes(ctx2.layer))) {
1518
- boost += 0.05;
1519
- }
1520
- const segmentMatch = ctx2.segments.some((seg) => queryTerms.some((t) => seg.toLowerCase().includes(t) || t.includes(seg.toLowerCase())));
1521
- if (segmentMatch) {
1522
- boost += 0.05;
1523
- }
1524
- pathBoosts.set(filepath, boost);
1525
- }
2087
+ const symbolIndexData = {
2088
+ version: this.version,
2089
+ lastUpdated: new Date().toISOString(),
2090
+ files: Object.fromEntries(this.symbolIndex),
2091
+ bm25Data: this.bm25Index.serialize()
2092
+ };
2093
+ await fs4.writeFile(path8.join(coreDir, "symbols.json"), JSON.stringify(symbolIndexData, null, 2));
2094
+ console.log(` [Core] Symbol index built with ${this.symbolIndex.size} files`);
2095
+ }
2096
+ async search(query, ctx, options) {
2097
+ const config = ctx.config;
2098
+ const topK = options?.topK ?? DEFAULT_TOP_K;
2099
+ const minScore = options?.minScore ?? DEFAULT_MIN_SCORE;
2100
+ if (this.symbolIndex.size === 0) {
2101
+ await this.loadSymbolIndex(ctx.rootDir, config);
2102
+ }
2103
+ if (!this.bm25Index || this.symbolIndex.size === 0) {
2104
+ return [];
1526
2105
  }
2106
+ const queryTokens = tokenize(query);
2107
+ const bm25Results = this.bm25Index.search(query, topK * 2);
2108
+ const bm25Scores = new Map(bm25Results.map((r) => [r.id, r.score]));
2109
+ const symbolMatches = this.findSymbolMatches(queryTokens);
1527
2110
  const results = [];
1528
- for (const { filepath, chunk, embedding } of allChunksData) {
1529
- const semanticScore = cosineSimilarity(queryEmbedding, embedding);
1530
- const bm25Score = bm25Scores.get(chunk.id) || 0;
1531
- const pathBoost = pathBoosts.get(filepath) || 0;
1532
- const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score + pathBoost;
1533
- if (hybridScore >= minScore || bm25Score > 0.3) {
2111
+ for (const filepath of this.symbolIndex.keys()) {
2112
+ const entry = this.symbolIndex.get(filepath);
2113
+ const bm25Score = bm25Scores.get(filepath) ?? 0;
2114
+ const symbolScore = symbolMatches.get(filepath) ?? 0;
2115
+ if (bm25Score === 0 && symbolScore === 0)
2116
+ continue;
2117
+ const combinedScore = 0.6 * normalizeScore(bm25Score) + 0.4 * symbolScore;
2118
+ if (combinedScore >= minScore) {
2119
+ const fileIndex = await ctx.loadFileIndex(filepath);
2120
+ if (!fileIndex)
2121
+ continue;
2122
+ const bestChunk = this.findBestChunk(fileIndex.chunks, queryTokens, entry.symbols);
1534
2123
  results.push({
1535
2124
  filepath,
1536
- chunk,
1537
- score: hybridScore,
2125
+ chunk: bestChunk,
2126
+ score: combinedScore,
1538
2127
  moduleId: this.id,
1539
2128
  context: {
1540
- semanticScore,
1541
- bm25Score,
1542
- pathBoost
2129
+ bm25Score: normalizeScore(bm25Score),
2130
+ symbolScore
1543
2131
  }
1544
2132
  });
1545
2133
  }
1546
2134
  }
1547
- results.sort((a, b) => b.score - a.score);
1548
- return results.slice(0, topK);
2135
+ return results.sort((a, b) => b.score - a.score).slice(0, topK);
1549
2136
  }
1550
- extractReferences(content, filepath) {
1551
- const references = [];
1552
- const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
1553
- const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1554
- let match;
1555
- while ((match = importRegex.exec(content)) !== null) {
1556
- const importPath = match[1];
1557
- if (importPath.startsWith(".")) {
1558
- const dir = path5.dirname(filepath);
1559
- const resolved = path5.normalize(path5.join(dir, importPath));
1560
- references.push(resolved);
2137
+ findSymbolMatches(queryTokens) {
2138
+ const matches = new Map;
2139
+ for (const [filepath, entry] of this.symbolIndex) {
2140
+ let matchScore = 0;
2141
+ for (const symbol of entry.symbols) {
2142
+ const symbolName = symbol.name.toLowerCase();
2143
+ const symbolParts = symbolsToKeywords([symbol]);
2144
+ for (const token of queryTokens) {
2145
+ if (symbolName === token) {
2146
+ matchScore += symbol.isExported ? 1 : 0.8;
2147
+ } else if (symbolName.includes(token) || token.includes(symbolName)) {
2148
+ matchScore += symbol.isExported ? 0.5 : 0.4;
2149
+ } else if (symbolParts.some((p) => p === token)) {
2150
+ matchScore += symbol.isExported ? 0.3 : 0.2;
2151
+ }
2152
+ }
2153
+ }
2154
+ if (matchScore > 0) {
2155
+ matches.set(filepath, Math.min(1, matchScore / queryTokens.length));
2156
+ }
2157
+ }
2158
+ return matches;
2159
+ }
2160
+ findBestChunk(chunks, queryTokens, symbols) {
2161
+ let bestChunk = chunks[0];
2162
+ let bestScore = 0;
2163
+ for (const chunk of chunks) {
2164
+ let score = 0;
2165
+ const chunkContent = chunk.content.toLowerCase();
2166
+ for (const token of queryTokens) {
2167
+ if (chunkContent.includes(token)) {
2168
+ score += 1;
2169
+ }
2170
+ }
2171
+ if (chunk.name) {
2172
+ const nameLower = chunk.name.toLowerCase();
2173
+ for (const token of queryTokens) {
2174
+ if (nameLower.includes(token)) {
2175
+ score += 2;
2176
+ }
2177
+ }
2178
+ }
2179
+ if (chunk.isExported) {
2180
+ score += 0.5;
2181
+ }
2182
+ if (score > bestScore) {
2183
+ bestScore = score;
2184
+ bestChunk = chunk;
1561
2185
  }
1562
2186
  }
1563
- while ((match = requireRegex.exec(content)) !== null) {
1564
- const importPath = match[1];
1565
- if (importPath.startsWith(".")) {
1566
- const dir = path5.dirname(filepath);
1567
- const resolved = path5.normalize(path5.join(dir, importPath));
1568
- references.push(resolved);
2187
+ return bestChunk;
2188
+ }
2189
+ async loadSymbolIndex(rootDir, config) {
2190
+ const coreDir = path8.join(getRaggrepDir(rootDir, config), "index", "core");
2191
+ const symbolsPath = path8.join(coreDir, "symbols.json");
2192
+ try {
2193
+ const content = await fs4.readFile(symbolsPath, "utf-8");
2194
+ const data = JSON.parse(content);
2195
+ this.symbolIndex = new Map(Object.entries(data.files));
2196
+ if (data.bm25Data) {
2197
+ this.bm25Index = BM25Index.deserialize(data.bm25Data);
1569
2198
  }
2199
+ } catch (error) {
2200
+ this.symbolIndex = new Map;
2201
+ this.bm25Index = null;
1570
2202
  }
1571
- return references;
2203
+ }
2204
+ async dispose() {
2205
+ this.symbolIndex.clear();
2206
+ this.bm25Index = null;
1572
2207
  }
1573
2208
  }
1574
- var DEFAULT_MIN_SCORE2 = 0.15, DEFAULT_TOP_K2 = 10, SEMANTIC_WEIGHT = 0.7, BM25_WEIGHT = 0.3, TIER1_CANDIDATE_MULTIPLIER = 3;
1575
- var init_typescript = __esm(() => {
1576
- init_embeddings();
2209
+ var DEFAULT_MIN_SCORE = 0.1, DEFAULT_TOP_K = 20, LINES_PER_CHUNK = 50, CHUNK_OVERLAP = 10;
2210
+ var init_core = __esm(() => {
1577
2211
  init_config2();
1578
- init_parseCode();
1579
- init_storage();
1580
- init_keywords();
1581
- init_keywords();
2212
+ init_introspection();
2213
+ init_symbols();
1582
2214
  });
1583
2215
 
1584
- // src/modules/registry.ts
1585
- class ModuleRegistryImpl {
1586
- modules = new Map;
1587
- register(module) {
1588
- if (this.modules.has(module.id)) {
1589
- console.warn(`Module '${module.id}' is already registered, overwriting...`);
1590
- }
1591
- this.modules.set(module.id, module);
1592
- }
1593
- get(id) {
1594
- return this.modules.get(id);
1595
- }
1596
- list() {
1597
- return Array.from(this.modules.values());
2216
+ // src/domain/services/similarity.ts
2217
+ function cosineSimilarity(a, b) {
2218
+ if (a.length !== b.length) {
2219
+ throw new Error(`Vector length mismatch: ${a.length} vs ${b.length}`);
1598
2220
  }
1599
- getEnabled(config) {
1600
- const enabledIds = new Set(config.modules.filter((m) => m.enabled).map((m) => m.id));
1601
- return this.list().filter((m) => enabledIds.has(m.id));
2221
+ let dotProduct = 0;
2222
+ let normA = 0;
2223
+ let normB = 0;
2224
+ for (let i = 0;i < a.length; i++) {
2225
+ dotProduct += a[i] * b[i];
2226
+ normA += a[i] * a[i];
2227
+ normB += b[i] * b[i];
1602
2228
  }
2229
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
2230
+ if (magnitude === 0)
2231
+ return 0;
2232
+ return dotProduct / magnitude;
1603
2233
  }
1604
- async function registerBuiltInModules() {
1605
- const { CoreModule: CoreModule2 } = await Promise.resolve().then(() => (init_core(), exports_core));
1606
- const { TypeScriptModule: TypeScriptModule2 } = await Promise.resolve().then(() => (init_typescript(), exports_typescript));
1607
- registry.register(new CoreModule2);
1608
- registry.register(new TypeScriptModule2);
1609
- }
1610
- var registry;
1611
- var init_registry = __esm(() => {
1612
- registry = new ModuleRegistryImpl;
1613
- });
1614
2234
 
1615
- // src/introspection/projectDetector.ts
1616
- import * as path6 from "path";
1617
- import * as fs4 from "fs/promises";
1618
- function detectScopeFromName(name) {
1619
- const nameLower = name.toLowerCase();
1620
- for (const [scope, keywords] of Object.entries(SCOPE_KEYWORDS)) {
1621
- if (scope === "unknown")
1622
- continue;
1623
- for (const keyword of keywords) {
1624
- if (nameLower.includes(keyword)) {
1625
- return scope;
1626
- }
1627
- }
2235
+ // src/modules/language/typescript/parseCode.ts
2236
+ import * as ts from "typescript";
2237
+ function parseCode(content, filepath) {
2238
+ const ext = filepath.split(".").pop()?.toLowerCase();
2239
+ if (["ts", "tsx", "js", "jsx", "mts", "cts", "mjs", "cjs"].includes(ext || "")) {
2240
+ return parseTypeScript(content, filepath);
1628
2241
  }
1629
- return "unknown";
2242
+ return parseGenericCode(content);
1630
2243
  }
1631
- async function scanForPackageJsons(rootDir, currentDir = "", depth = 0) {
1632
- if (depth > MAX_SCAN_DEPTH)
1633
- return [];
1634
- const results = [];
1635
- const fullDir = currentDir ? path6.join(rootDir, currentDir) : rootDir;
1636
- try {
1637
- const entries = await fs4.readdir(fullDir, { withFileTypes: true });
1638
- const hasPackageJson = entries.some((e) => e.isFile() && e.name === "package.json");
1639
- if (hasPackageJson && currentDir) {
1640
- const info = await parsePackageJson(rootDir, currentDir);
1641
- if (info) {
1642
- results.push(info);
1643
- }
2244
+ function parseTypeScript(content, filepath) {
2245
+ const chunks = [];
2246
+ const lines = content.split(`
2247
+ `);
2248
+ const sourceFile = ts.createSourceFile(filepath, content, ts.ScriptTarget.Latest, true, filepath.endsWith(".tsx") || filepath.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
2249
+ function getLineNumbers(node) {
2250
+ const start = sourceFile.getLineAndCharacterOfPosition(node.getStart());
2251
+ const end = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
2252
+ return {
2253
+ startLine: start.line + 1,
2254
+ endLine: end.line + 1
2255
+ };
2256
+ }
2257
+ function getNodeText(node) {
2258
+ return node.getText(sourceFile);
2259
+ }
2260
+ function isExported(node) {
2261
+ if (!ts.canHaveModifiers(node))
2262
+ return false;
2263
+ const modifiers = ts.getModifiers(node);
2264
+ return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
2265
+ }
2266
+ function getJSDoc(node) {
2267
+ const jsDocNodes = ts.getJSDocCommentsAndTags(node);
2268
+ if (jsDocNodes.length === 0)
2269
+ return;
2270
+ return jsDocNodes.map((doc) => doc.getText(sourceFile)).join(`
2271
+ `);
2272
+ }
2273
+ function getFunctionName(node) {
2274
+ if (ts.isFunctionDeclaration(node) && node.name) {
2275
+ return node.name.text;
1644
2276
  }
1645
- for (const entry of entries) {
1646
- if (!entry.isDirectory())
1647
- continue;
1648
- if (SKIP_DIRS.has(entry.name))
1649
- continue;
1650
- const subPath = currentDir ? `${currentDir}/${entry.name}` : entry.name;
1651
- const subResults = await scanForPackageJsons(rootDir, subPath, depth + 1);
1652
- results.push(...subResults);
2277
+ if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
2278
+ return node.name.text;
1653
2279
  }
1654
- } catch {}
1655
- return results;
1656
- }
1657
- async function parsePackageJson(rootDir, relativePath) {
1658
- try {
1659
- const packageJsonPath = path6.join(rootDir, relativePath, "package.json");
1660
- const content = await fs4.readFile(packageJsonPath, "utf-8");
1661
- const pkg = JSON.parse(content);
1662
- const name = pkg.name || path6.basename(relativePath);
1663
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
1664
- let type = "unknown";
1665
- if (deps["next"] || deps["react"] || deps["vue"] || deps["svelte"]) {
1666
- type = "app";
1667
- } else if (deps["express"] || deps["fastify"] || deps["koa"] || deps["hono"]) {
1668
- type = "service";
1669
- } else if (pkg.main || pkg.exports) {
1670
- type = "library";
2280
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
2281
+ return node.name.text;
1671
2282
  }
1672
- const hasWorkspaces = Boolean(pkg.workspaces);
1673
- return { name, relativePath, type, hasWorkspaces };
1674
- } catch {
1675
- return null;
2283
+ return;
1676
2284
  }
1677
- }
1678
- async function detectProjectStructure(rootDir) {
1679
- const projectMap = new Map;
1680
- let isMonorepo = false;
1681
- try {
1682
- const entries = await fs4.readdir(rootDir, { withFileTypes: true });
1683
- const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
1684
- const monorepoPatterns = ["apps", "packages", "libs", "services"];
1685
- const hasMonorepoStructure = monorepoPatterns.some((p) => dirNames.includes(p));
1686
- if (hasMonorepoStructure) {
1687
- isMonorepo = true;
1688
- for (const pattern of monorepoPatterns) {
1689
- if (!dirNames.includes(pattern))
1690
- continue;
1691
- const patternDir = path6.join(rootDir, pattern);
1692
- try {
1693
- const subDirs = await fs4.readdir(patternDir, { withFileTypes: true });
1694
- for (const subDir of subDirs) {
1695
- if (!subDir.isDirectory())
1696
- continue;
1697
- const projectRoot = `${pattern}/${subDir.name}`;
1698
- const type = getProjectType(pattern);
1699
- projectMap.set(projectRoot, {
1700
- name: subDir.name,
1701
- root: projectRoot,
1702
- type
1703
- });
1704
- }
1705
- } catch {}
2285
+ function visit(node) {
2286
+ const { startLine, endLine } = getLineNumbers(node);
2287
+ if (ts.isFunctionDeclaration(node) && node.name) {
2288
+ chunks.push({
2289
+ content: getNodeText(node),
2290
+ startLine,
2291
+ endLine,
2292
+ type: "function",
2293
+ name: node.name.text,
2294
+ isExported: isExported(node),
2295
+ jsDoc: getJSDoc(node)
2296
+ });
2297
+ return;
2298
+ }
2299
+ if (ts.isVariableStatement(node)) {
2300
+ for (const decl of node.declarationList.declarations) {
2301
+ if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
2302
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
2303
+ chunks.push({
2304
+ content: getNodeText(node),
2305
+ startLine,
2306
+ endLine,
2307
+ type: "function",
2308
+ name,
2309
+ isExported: isExported(node),
2310
+ jsDoc: getJSDoc(node)
2311
+ });
2312
+ return;
2313
+ }
1706
2314
  }
1707
2315
  }
1708
- const packageJsons = await scanForPackageJsons(rootDir);
1709
- for (const pkg of packageJsons) {
1710
- if (pkg.hasWorkspaces) {
1711
- isMonorepo = true;
1712
- }
1713
- if (packageJsons.length > 1) {
1714
- isMonorepo = true;
1715
- }
1716
- projectMap.set(pkg.relativePath, {
1717
- name: pkg.name,
1718
- root: pkg.relativePath,
1719
- type: pkg.type
2316
+ if (ts.isClassDeclaration(node) && node.name) {
2317
+ chunks.push({
2318
+ content: getNodeText(node),
2319
+ startLine,
2320
+ endLine,
2321
+ type: "class",
2322
+ name: node.name.text,
2323
+ isExported: isExported(node),
2324
+ jsDoc: getJSDoc(node)
2325
+ });
2326
+ return;
2327
+ }
2328
+ if (ts.isInterfaceDeclaration(node)) {
2329
+ chunks.push({
2330
+ content: getNodeText(node),
2331
+ startLine,
2332
+ endLine,
2333
+ type: "interface",
2334
+ name: node.name.text,
2335
+ isExported: isExported(node),
2336
+ jsDoc: getJSDoc(node)
2337
+ });
2338
+ return;
2339
+ }
2340
+ if (ts.isTypeAliasDeclaration(node)) {
2341
+ chunks.push({
2342
+ content: getNodeText(node),
2343
+ startLine,
2344
+ endLine,
2345
+ type: "type",
2346
+ name: node.name.text,
2347
+ isExported: isExported(node),
2348
+ jsDoc: getJSDoc(node)
2349
+ });
2350
+ return;
2351
+ }
2352
+ if (ts.isEnumDeclaration(node)) {
2353
+ chunks.push({
2354
+ content: getNodeText(node),
2355
+ startLine,
2356
+ endLine,
2357
+ type: "enum",
2358
+ name: node.name.text,
2359
+ isExported: isExported(node),
2360
+ jsDoc: getJSDoc(node)
1720
2361
  });
2362
+ return;
1721
2363
  }
1722
- let rootType = "unknown";
1723
- try {
1724
- const rootPkgPath = path6.join(rootDir, "package.json");
1725
- const rootPkg = JSON.parse(await fs4.readFile(rootPkgPath, "utf-8"));
1726
- if (rootPkg.workspaces) {
1727
- isMonorepo = true;
1728
- }
1729
- const deps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
1730
- if (deps["next"] || deps["react"] || deps["vue"]) {
1731
- rootType = "app";
1732
- } else if (deps["express"] || deps["fastify"] || deps["koa"]) {
1733
- rootType = "service";
2364
+ if (ts.isVariableStatement(node) && isExported(node)) {
2365
+ for (const decl of node.declarationList.declarations) {
2366
+ if (decl.initializer && (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
2367
+ continue;
2368
+ }
2369
+ const name = ts.isIdentifier(decl.name) ? decl.name.text : undefined;
2370
+ chunks.push({
2371
+ content: getNodeText(node),
2372
+ startLine,
2373
+ endLine,
2374
+ type: "variable",
2375
+ name,
2376
+ isExported: true,
2377
+ jsDoc: getJSDoc(node)
2378
+ });
1734
2379
  }
1735
- } catch {}
1736
- const projects = Array.from(projectMap.values()).sort((a, b) => a.root.length - b.root.length);
1737
- return {
1738
- projects,
1739
- isMonorepo,
1740
- rootType: isMonorepo ? undefined : rootType
1741
- };
1742
- } catch {
1743
- return {
1744
- projects: [],
1745
- isMonorepo: false,
1746
- rootType: "unknown"
1747
- };
2380
+ return;
2381
+ }
2382
+ ts.forEachChild(node, visit);
1748
2383
  }
1749
- }
1750
- function getProjectType(patternDir) {
1751
- switch (patternDir) {
1752
- case "apps":
1753
- return "app";
1754
- case "packages":
1755
- case "libs":
1756
- return "library";
1757
- case "services":
1758
- return "service";
1759
- case "scripts":
1760
- case "tools":
1761
- return "script";
1762
- default:
1763
- return "unknown";
2384
+ ts.forEachChild(sourceFile, visit);
2385
+ if (chunks.length === 0) {
2386
+ return parseGenericCode(content);
1764
2387
  }
2388
+ return chunks;
1765
2389
  }
1766
- function findProjectForFile(filepath, structure) {
1767
- const normalizedPath = filepath.replace(/\\/g, "/");
1768
- const matches = [];
1769
- for (const project of structure.projects) {
1770
- if (normalizedPath === project.root || normalizedPath.startsWith(project.root + "/")) {
1771
- matches.push(project);
1772
- }
1773
- }
1774
- if (matches.length > 0) {
1775
- return matches.reduce((best, current) => current.root.length > best.root.length ? current : best);
2390
+ function parseGenericCode(content) {
2391
+ const chunks = [];
2392
+ const lines = content.split(`
2393
+ `);
2394
+ const CHUNK_SIZE = 30;
2395
+ const OVERLAP = 5;
2396
+ if (lines.length <= CHUNK_SIZE) {
2397
+ return [
2398
+ {
2399
+ content,
2400
+ startLine: 1,
2401
+ endLine: lines.length,
2402
+ type: "file"
2403
+ }
2404
+ ];
1776
2405
  }
1777
- for (const { pattern, type } of PROJECT_PATTERNS) {
1778
- const match = normalizedPath.match(pattern);
1779
- if (match) {
1780
- return {
1781
- name: match[1],
1782
- root: match[0],
1783
- type
1784
- };
1785
- }
2406
+ for (let i = 0;i < lines.length; i += CHUNK_SIZE - OVERLAP) {
2407
+ const endIdx = Math.min(i + CHUNK_SIZE, lines.length);
2408
+ chunks.push({
2409
+ content: lines.slice(i, endIdx).join(`
2410
+ `),
2411
+ startLine: i + 1,
2412
+ endLine: endIdx,
2413
+ type: "block"
2414
+ });
2415
+ if (endIdx >= lines.length)
2416
+ break;
1786
2417
  }
1787
- return {
1788
- name: "root",
1789
- root: "",
1790
- type: structure.rootType ?? "unknown"
1791
- };
2418
+ return chunks;
1792
2419
  }
1793
- var MAX_SCAN_DEPTH = 4, SKIP_DIRS, PROJECT_PATTERNS, SCOPE_KEYWORDS;
1794
- var init_projectDetector = __esm(() => {
1795
- SKIP_DIRS = new Set([
1796
- "node_modules",
1797
- ".git",
1798
- "dist",
1799
- "build",
1800
- ".next",
1801
- ".nuxt",
1802
- "coverage",
1803
- ".raggrep"
1804
- ]);
1805
- PROJECT_PATTERNS = [
1806
- { pattern: /^apps\/([^/]+)/, type: "app", defaultScope: "unknown" },
1807
- { pattern: /^packages\/([^/]+)/, type: "library", defaultScope: "shared" },
1808
- { pattern: /^libs\/([^/]+)/, type: "library", defaultScope: "shared" },
1809
- { pattern: /^services\/([^/]+)/, type: "service", defaultScope: "backend" },
1810
- { pattern: /^scripts\/([^/]+)/, type: "script", defaultScope: "tooling" },
1811
- { pattern: /^tools\/([^/]+)/, type: "script", defaultScope: "tooling" }
1812
- ];
1813
- SCOPE_KEYWORDS = {
1814
- frontend: [
1815
- "web",
1816
- "webapp",
1817
- "frontend",
1818
- "client",
1819
- "ui",
1820
- "app",
1821
- "mobile",
1822
- "react",
1823
- "vue",
1824
- "angular",
1825
- "next",
1826
- "nuxt"
1827
- ],
1828
- backend: [
1829
- "api",
1830
- "server",
1831
- "backend",
1832
- "service",
1833
- "worker",
1834
- "lambda",
1835
- "functions"
1836
- ],
1837
- shared: ["shared", "common", "utils", "lib", "core", "types", "models"],
1838
- tooling: [
1839
- "scripts",
1840
- "tools",
1841
- "cli",
1842
- "devtools",
1843
- "build",
1844
- "config",
1845
- "infra"
1846
- ],
1847
- unknown: []
1848
- };
2420
+ function generateChunkId(filepath, startLine, endLine) {
2421
+ const safePath = filepath.replace(/[/\\]/g, "-").replace(/\./g, "_");
2422
+ return `${safePath}-${startLine}-${endLine}`;
2423
+ }
2424
+ var init_parseCode = () => {};
2425
+
2426
+ // src/infrastructure/storage/fileIndexStorage.ts
2427
+ var init_fileIndexStorage = __esm(() => {
2428
+ init_entities();
1849
2429
  });
1850
2430
 
1851
- // src/introspection/fileIntrospector.ts
1852
- import * as path7 from "path";
1853
- function introspectFile(filepath, structure, fileContent) {
1854
- const normalizedPath = filepath.replace(/\\/g, "/");
1855
- const segments = normalizedPath.split("/").filter((s) => s.length > 0);
1856
- const filename = segments[segments.length - 1] || "";
1857
- const ext = path7.extname(filename);
1858
- const project = findProjectForFile(normalizedPath, structure);
1859
- const language = EXTENSION_TO_LANGUAGE[ext] || "unknown";
1860
- const layer = detectLayer(segments, filename);
1861
- const domain = detectDomain(segments);
1862
- const scope = detectScope(segments, project, layer);
1863
- let framework;
1864
- if (fileContent) {
1865
- framework = detectFramework(fileContent);
2431
+ // src/domain/services/keywords.ts
2432
+ function extractKeywords(content, name, maxKeywords = 50) {
2433
+ const keywords = new Set;
2434
+ if (name) {
2435
+ keywords.add(name.toLowerCase());
2436
+ const parts = name.split(/(?=[A-Z])/).map((p) => p.toLowerCase());
2437
+ parts.forEach((p) => p.length > 2 && keywords.add(p));
2438
+ }
2439
+ const identifierRegex = /\b([a-zA-Z_][a-zA-Z0-9_]{2,})\b/g;
2440
+ let match;
2441
+ while ((match = identifierRegex.exec(content)) !== null) {
2442
+ const word = match[1].toLowerCase();
2443
+ if (!COMMON_KEYWORDS.has(word) && word.length > 2) {
2444
+ keywords.add(word);
2445
+ }
2446
+ }
2447
+ return Array.from(keywords).slice(0, maxKeywords);
2448
+ }
2449
+ function splitIdentifier(str) {
2450
+ return str.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[_-]/g, " ").split(/\s+/).map((s) => s.toLowerCase()).filter((s) => s.length > 1);
2451
+ }
2452
+ function extractPathKeywords(filepath) {
2453
+ const keywords = new Set;
2454
+ const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
2455
+ const segments = pathWithoutExt.split(/[/\\]/);
2456
+ for (const segment of segments) {
2457
+ if (segment.length < 2)
2458
+ continue;
2459
+ const lower = segment.toLowerCase();
2460
+ if (!COMMON_KEYWORDS.has(lower) && lower.length > 2) {
2461
+ keywords.add(lower);
2462
+ }
2463
+ const parts = splitIdentifier(segment);
2464
+ for (const part of parts) {
2465
+ if (!COMMON_KEYWORDS.has(part) && part.length > 2) {
2466
+ keywords.add(part);
2467
+ }
2468
+ }
1866
2469
  }
1867
- return {
1868
- filepath: normalizedPath,
1869
- project,
1870
- scope,
1871
- layer,
1872
- domain,
1873
- language,
1874
- framework,
1875
- depth: segments.length - 1,
1876
- pathSegments: segments.slice(0, -1)
1877
- };
2470
+ return Array.from(keywords);
1878
2471
  }
1879
- function detectLayer(segments, filename) {
2472
+ function parsePathContext(filepath) {
2473
+ const pathWithoutExt = filepath.replace(/\.[^.]+$/, "");
2474
+ const allSegments = pathWithoutExt.split(/[/\\]/);
2475
+ const filename = allSegments[allSegments.length - 1];
2476
+ const dirSegments = allSegments.slice(0, -1);
2477
+ const keywords = extractPathKeywords(filepath);
2478
+ let layer;
2479
+ const allLower = [...dirSegments, filename].map((s) => s.toLowerCase()).join(" ");
1880
2480
  const filenameLower = filename.toLowerCase();
1881
- for (const [layer, patterns] of Object.entries(LAYER_PATTERNS2)) {
2481
+ for (const [layerName, patterns] of Object.entries(LAYER_PATTERNS2)) {
1882
2482
  for (const pattern of patterns) {
1883
2483
  if (filenameLower.includes(pattern)) {
1884
- return layer;
2484
+ layer = layerName;
2485
+ break;
2486
+ }
2487
+ if (dirSegments.some((s) => s.toLowerCase() === pattern)) {
2488
+ layer = layerName;
2489
+ break;
1885
2490
  }
1886
2491
  }
2492
+ if (layer)
2493
+ break;
1887
2494
  }
1888
- for (let i = segments.length - 2;i >= 0; i--) {
1889
- const segment = segments[i].toLowerCase();
1890
- for (const [layer, patterns] of Object.entries(LAYER_PATTERNS2)) {
1891
- if (patterns.includes(segment)) {
1892
- return layer;
1893
- }
2495
+ let domain;
2496
+ const layerPatternSet = new Set(Object.values(LAYER_PATTERNS2).flat());
2497
+ const reversedSegments = [...dirSegments].reverse();
2498
+ for (const segment of reversedSegments) {
2499
+ const lower = segment.toLowerCase();
2500
+ if (["src", "lib", "app", "packages", "modules"].includes(lower))
2501
+ continue;
2502
+ if (layerPatternSet.has(lower))
2503
+ continue;
2504
+ if (lower.length > 2) {
2505
+ domain = lower;
2506
+ break;
1894
2507
  }
1895
2508
  }
1896
- return;
2509
+ return {
2510
+ segments: dirSegments,
2511
+ layer,
2512
+ domain,
2513
+ depth: dirSegments.length,
2514
+ keywords
2515
+ };
1897
2516
  }
1898
- function detectDomain(segments) {
1899
- const skipSegments = new Set([
1900
- "src",
1901
- "lib",
1902
- "app",
1903
- "apps",
1904
- "packages",
1905
- "services",
1906
- "modules",
1907
- "features",
1908
- ...Object.values(LAYER_PATTERNS2).flat()
2517
+ function formatPathContextForEmbedding(pathContext) {
2518
+ const parts = [];
2519
+ if (pathContext.domain) {
2520
+ parts.push(pathContext.domain);
2521
+ }
2522
+ if (pathContext.layer) {
2523
+ parts.push(pathContext.layer);
2524
+ }
2525
+ const significantSegments = pathContext.segments.slice(-3).filter((s) => s.length > 2 && !["src", "lib", "app"].includes(s.toLowerCase()));
2526
+ if (significantSegments.length > 0) {
2527
+ parts.push(...significantSegments.map((s) => s.toLowerCase()));
2528
+ }
2529
+ if (parts.length === 0)
2530
+ return "";
2531
+ const unique = [...new Set(parts)];
2532
+ return `[${unique.join(" ")}]`;
2533
+ }
2534
+ var COMMON_KEYWORDS, LAYER_PATTERNS2;
2535
+ var init_keywords = __esm(() => {
2536
+ COMMON_KEYWORDS = new Set([
2537
+ "const",
2538
+ "let",
2539
+ "var",
2540
+ "function",
2541
+ "class",
2542
+ "interface",
2543
+ "type",
2544
+ "enum",
2545
+ "export",
2546
+ "import",
2547
+ "from",
2548
+ "return",
2549
+ "async",
2550
+ "await",
2551
+ "new",
2552
+ "this",
2553
+ "true",
2554
+ "false",
2555
+ "null",
2556
+ "undefined",
2557
+ "if",
2558
+ "else",
2559
+ "for",
2560
+ "while",
2561
+ "switch",
2562
+ "case",
2563
+ "break",
2564
+ "continue",
2565
+ "try",
2566
+ "catch",
2567
+ "finally",
2568
+ "throw",
2569
+ "typeof",
2570
+ "instanceof",
2571
+ "void",
2572
+ "delete",
2573
+ "in",
2574
+ "of",
2575
+ "string",
2576
+ "number",
2577
+ "boolean",
2578
+ "any",
2579
+ "unknown",
2580
+ "never",
2581
+ "object",
2582
+ "public",
2583
+ "private",
2584
+ "protected",
2585
+ "static",
2586
+ "readonly",
2587
+ "abstract",
2588
+ "implements",
2589
+ "extends",
2590
+ "super",
2591
+ "get",
2592
+ "set",
2593
+ "constructor",
2594
+ "the",
2595
+ "and",
2596
+ "for",
2597
+ "not",
2598
+ "with",
2599
+ "are",
2600
+ "was",
2601
+ "has",
2602
+ "have"
1909
2603
  ]);
1910
- for (const segment of segments) {
1911
- const segmentLower = segment.toLowerCase();
1912
- if (skipSegments.has(segmentLower))
1913
- continue;
1914
- if (DOMAIN_PATTERNS.includes(segmentLower)) {
1915
- return segmentLower;
2604
+ LAYER_PATTERNS2 = {
2605
+ controller: ["controller", "controllers", "handler", "handlers", "route", "routes", "api"],
2606
+ service: ["service", "services", "usecase", "usecases", "application"],
2607
+ repository: ["repository", "repositories", "repo", "repos", "dao", "store", "storage"],
2608
+ model: ["model", "models", "entity", "entities", "schema", "schemas"],
2609
+ util: ["util", "utils", "utility", "utilities", "helper", "helpers", "common", "shared"],
2610
+ config: ["config", "configs", "configuration", "settings"],
2611
+ middleware: ["middleware", "middlewares", "interceptor", "interceptors"],
2612
+ domain: ["domain", "core", "business"],
2613
+ infrastructure: ["infrastructure", "infra", "external", "adapters"],
2614
+ presentation: ["presentation", "view", "views", "component", "components", "ui"],
2615
+ test: ["test", "tests", "spec", "specs", "__tests__", "__test__"]
2616
+ };
2617
+ });
2618
+
2619
+ // src/infrastructure/storage/symbolicIndex.ts
2620
+ import * as fs5 from "fs/promises";
2621
+ import * as path9 from "path";
2622
+
2623
+ class SymbolicIndex {
2624
+ meta = null;
2625
+ fileSummaries = new Map;
2626
+ bm25Index = null;
2627
+ symbolicPath;
2628
+ moduleId;
2629
+ constructor(indexDir, moduleId) {
2630
+ this.symbolicPath = path9.join(indexDir, "index", moduleId, "symbolic");
2631
+ this.moduleId = moduleId;
2632
+ }
2633
+ async initialize() {
2634
+ try {
2635
+ await this.load();
2636
+ } catch {
2637
+ this.meta = {
2638
+ version: "1.0.0",
2639
+ lastUpdated: new Date().toISOString(),
2640
+ moduleId: this.moduleId,
2641
+ fileCount: 0,
2642
+ bm25Data: {
2643
+ avgDocLength: 0,
2644
+ documentFrequencies: {},
2645
+ totalDocs: 0
2646
+ }
2647
+ };
2648
+ this.bm25Index = new BM25Index;
1916
2649
  }
1917
- for (const domain of DOMAIN_PATTERNS) {
1918
- if (segmentLower.startsWith(domain) || segmentLower.endsWith(domain)) {
1919
- return domain;
2650
+ }
2651
+ addFile(summary) {
2652
+ this.fileSummaries.set(summary.filepath, summary);
2653
+ }
2654
+ removeFile(filepath) {
2655
+ return this.fileSummaries.delete(filepath);
2656
+ }
2657
+ buildBM25Index() {
2658
+ this.bm25Index = new BM25Index;
2659
+ for (const [filepath, summary] of this.fileSummaries) {
2660
+ const content = [
2661
+ ...summary.keywords,
2662
+ ...summary.exports,
2663
+ ...extractPathKeywords(filepath)
2664
+ ].join(" ");
2665
+ this.bm25Index.addDocuments([{ id: filepath, content }]);
2666
+ }
2667
+ if (this.meta) {
2668
+ this.meta.fileCount = this.fileSummaries.size;
2669
+ this.meta.bm25Data.totalDocs = this.fileSummaries.size;
2670
+ }
2671
+ }
2672
+ findCandidates(query, maxCandidates = 20) {
2673
+ if (!this.bm25Index) {
2674
+ return Array.from(this.fileSummaries.keys());
2675
+ }
2676
+ const results = this.bm25Index.search(query, maxCandidates);
2677
+ return results.map((r) => r.id);
2678
+ }
2679
+ getAllFiles() {
2680
+ return Array.from(this.fileSummaries.keys());
2681
+ }
2682
+ getFileSummary(filepath) {
2683
+ return this.fileSummaries.get(filepath);
2684
+ }
2685
+ async save() {
2686
+ if (!this.meta)
2687
+ throw new Error("Index not initialized");
2688
+ this.meta.lastUpdated = new Date().toISOString();
2689
+ this.meta.fileCount = this.fileSummaries.size;
2690
+ await fs5.mkdir(this.symbolicPath, { recursive: true });
2691
+ const metaPath = path9.join(this.symbolicPath, "_meta.json");
2692
+ await fs5.writeFile(metaPath, JSON.stringify(this.meta, null, 2));
2693
+ for (const [filepath, summary] of this.fileSummaries) {
2694
+ const summaryPath = this.getFileSummaryPath(filepath);
2695
+ await fs5.mkdir(path9.dirname(summaryPath), { recursive: true });
2696
+ await fs5.writeFile(summaryPath, JSON.stringify(summary, null, 2));
2697
+ }
2698
+ }
2699
+ async load() {
2700
+ const metaPath = path9.join(this.symbolicPath, "_meta.json");
2701
+ const metaContent = await fs5.readFile(metaPath, "utf-8");
2702
+ this.meta = JSON.parse(metaContent);
2703
+ this.fileSummaries.clear();
2704
+ await this.loadFileSummariesRecursive(this.symbolicPath);
2705
+ this.buildBM25Index();
2706
+ }
2707
+ async loadFileSummariesRecursive(dir) {
2708
+ try {
2709
+ const entries = await fs5.readdir(dir, { withFileTypes: true });
2710
+ for (const entry of entries) {
2711
+ const fullPath = path9.join(dir, entry.name);
2712
+ if (entry.isDirectory()) {
2713
+ await this.loadFileSummariesRecursive(fullPath);
2714
+ } else if (entry.name.endsWith(".json") && entry.name !== "_meta.json") {
2715
+ try {
2716
+ const content = await fs5.readFile(fullPath, "utf-8");
2717
+ const summary = JSON.parse(content);
2718
+ if (summary.filepath) {
2719
+ this.fileSummaries.set(summary.filepath, summary);
2720
+ }
2721
+ } catch {}
2722
+ }
1920
2723
  }
1921
- }
2724
+ } catch {}
1922
2725
  }
1923
- return;
1924
- }
1925
- function detectScope(segments, project, layer) {
1926
- const projectScope = detectScopeFromName(project.name);
1927
- if (projectScope !== "unknown") {
1928
- return projectScope;
2726
+ getFileSummaryPath(filepath) {
2727
+ const jsonPath = filepath.replace(/\.[^.]+$/, ".json");
2728
+ return path9.join(this.symbolicPath, jsonPath);
1929
2729
  }
1930
- if (layer) {
1931
- switch (layer) {
1932
- case "controller":
1933
- case "repository":
1934
- case "middleware":
1935
- return "backend";
1936
- case "presentation":
1937
- return "frontend";
1938
- case "util":
1939
- case "model":
1940
- return "shared";
1941
- case "test":
1942
- return "tooling";
1943
- }
2730
+ async deleteFileSummary(filepath) {
2731
+ try {
2732
+ await fs5.unlink(this.getFileSummaryPath(filepath));
2733
+ } catch {}
2734
+ this.fileSummaries.delete(filepath);
1944
2735
  }
1945
- for (const segment of segments) {
1946
- const segmentLower = segment.toLowerCase();
1947
- if (["server", "api", "backend"].includes(segmentLower)) {
1948
- return "backend";
1949
- }
1950
- if (["client", "web", "frontend", "ui"].includes(segmentLower)) {
1951
- return "frontend";
1952
- }
1953
- if (["shared", "common", "lib", "libs"].includes(segmentLower)) {
1954
- return "shared";
2736
+ async exists() {
2737
+ try {
2738
+ const metaPath = path9.join(this.symbolicPath, "_meta.json");
2739
+ await fs5.access(metaPath);
2740
+ return true;
2741
+ } catch {
2742
+ return false;
1955
2743
  }
1956
2744
  }
1957
- return "unknown";
1958
- }
1959
- function detectFramework(content) {
1960
- for (const [framework, indicators] of Object.entries(FRAMEWORK_INDICATORS)) {
1961
- for (const indicator of indicators) {
1962
- if (content.includes(`from '${indicator}`) || content.includes(`from "${indicator}`) || content.includes(`require('${indicator}`) || content.includes(`require("${indicator}`)) {
1963
- return framework;
1964
- }
2745
+ get size() {
2746
+ return this.fileSummaries.size;
2747
+ }
2748
+ clear() {
2749
+ this.fileSummaries.clear();
2750
+ if (this.meta) {
2751
+ this.meta.fileCount = 0;
2752
+ this.meta.bm25Data = {
2753
+ avgDocLength: 0,
2754
+ documentFrequencies: {},
2755
+ totalDocs: 0
2756
+ };
1965
2757
  }
2758
+ this.bm25Index = new BM25Index;
1966
2759
  }
1967
- return;
1968
2760
  }
1969
- var LAYER_PATTERNS2, DOMAIN_PATTERNS, FRAMEWORK_INDICATORS, EXTENSION_TO_LANGUAGE;
1970
- var init_fileIntrospector = __esm(() => {
1971
- init_projectDetector();
1972
- LAYER_PATTERNS2 = {
1973
- controller: ["controller", "api", "routes", "route", "handler"],
1974
- service: ["service", "logic", "usecase", "usecases", "handler"],
1975
- repository: ["repository", "repo", "dao", "store", "persistence"],
1976
- model: ["model", "models", "entity", "entities", "schema", "schemas", "types", "type"],
1977
- util: ["util", "utils", "helper", "helpers", "common", "lib"],
1978
- config: ["config", "configuration", "settings"],
1979
- middleware: ["middleware", "middlewares"],
1980
- domain: ["domain"],
1981
- infrastructure: ["infrastructure", "infra"],
1982
- application: ["application", "app"],
1983
- presentation: ["presentation", "ui", "views", "view", "component", "components"],
1984
- test: ["test", "tests", "spec", "specs", "__tests__", "e2e"]
1985
- };
1986
- DOMAIN_PATTERNS = [
1987
- "auth",
1988
- "authentication",
1989
- "user",
1990
- "users",
1991
- "account",
1992
- "accounts",
1993
- "profile",
1994
- "profiles",
1995
- "product",
1996
- "products",
1997
- "item",
1998
- "items",
1999
- "catalog",
2000
- "order",
2001
- "orders",
2002
- "cart",
2003
- "checkout",
2004
- "payment",
2005
- "payments",
2006
- "billing",
2007
- "subscription",
2008
- "subscriptions",
2009
- "notification",
2010
- "notifications",
2011
- "email",
2012
- "sms",
2013
- "report",
2014
- "reports",
2015
- "analytics",
2016
- "metrics",
2017
- "dashboard",
2018
- "admin",
2019
- "settings",
2020
- "search",
2021
- "chat",
2022
- "message",
2023
- "messages",
2024
- "feed",
2025
- "post",
2026
- "posts",
2027
- "comment",
2028
- "comments",
2029
- "media",
2030
- "upload",
2031
- "file",
2032
- "files",
2033
- "storage",
2034
- "cache",
2035
- "session",
2036
- "log",
2037
- "logs",
2038
- "audit"
2039
- ];
2040
- FRAMEWORK_INDICATORS = {
2041
- nextjs: ["next", "next/"],
2042
- express: ["express"],
2043
- fastify: ["fastify"],
2044
- react: ["react"],
2045
- vue: ["vue"],
2046
- angular: ["@angular/"],
2047
- nestjs: ["@nestjs/"],
2048
- koa: ["koa"]
2049
- };
2050
- EXTENSION_TO_LANGUAGE = {
2051
- ".ts": "typescript",
2052
- ".tsx": "typescript",
2053
- ".js": "javascript",
2054
- ".jsx": "javascript",
2055
- ".mjs": "javascript",
2056
- ".cjs": "javascript",
2057
- ".py": "python",
2058
- ".go": "go",
2059
- ".rs": "rust",
2060
- ".java": "java",
2061
- ".kt": "kotlin",
2062
- ".swift": "swift",
2063
- ".rb": "ruby",
2064
- ".php": "php",
2065
- ".cs": "csharp",
2066
- ".cpp": "cpp",
2067
- ".c": "c",
2068
- ".h": "c",
2069
- ".hpp": "cpp",
2070
- ".md": "markdown",
2071
- ".json": "json",
2072
- ".yaml": "yaml",
2073
- ".yml": "yaml"
2074
- };
2761
+ var init_symbolicIndex = __esm(() => {
2762
+ init_keywords();
2075
2763
  });
2076
2764
 
2077
- // src/introspection/index.ts
2078
- import * as path8 from "path";
2079
- import * as fs5 from "fs/promises";
2765
+ // src/infrastructure/storage/index.ts
2766
+ var init_storage = __esm(() => {
2767
+ init_fileIndexStorage();
2768
+ init_symbolicIndex();
2769
+ });
2080
2770
 
2081
- class IntrospectionIndex {
2082
- rootDir;
2083
- structure = null;
2084
- files = new Map;
2085
- config = {};
2086
- constructor(rootDir) {
2087
- this.rootDir = rootDir;
2088
- }
2089
- async initialize() {
2090
- this.structure = await detectProjectStructure(this.rootDir);
2091
- try {
2092
- const configPath = path8.join(this.rootDir, ".raggrep", "config.json");
2093
- const configContent = await fs5.readFile(configPath, "utf-8");
2094
- const config = JSON.parse(configContent);
2095
- this.config = config.introspection || {};
2096
- } catch {}
2097
- }
2098
- getStructure() {
2099
- return this.structure;
2771
+ // src/modules/language/typescript/index.ts
2772
+ var exports_typescript = {};
2773
+ __export(exports_typescript, {
2774
+ TypeScriptModule: () => TypeScriptModule,
2775
+ DEFAULT_TOP_K: () => DEFAULT_TOP_K2,
2776
+ DEFAULT_MIN_SCORE: () => DEFAULT_MIN_SCORE2
2777
+ });
2778
+ import * as path10 from "path";
2779
+
2780
+ class TypeScriptModule {
2781
+ id = "language/typescript";
2782
+ name = "TypeScript Search";
2783
+ description = "TypeScript-aware code search with AST parsing and semantic embeddings";
2784
+ version = "1.0.0";
2785
+ embeddingConfig = null;
2786
+ symbolicIndex = null;
2787
+ pendingSummaries = new Map;
2788
+ rootDir = "";
2789
+ async initialize(config) {
2790
+ this.embeddingConfig = getEmbeddingConfigFromModule(config);
2791
+ configureEmbeddings(this.embeddingConfig);
2792
+ this.pendingSummaries.clear();
2100
2793
  }
2101
- addFile(filepath, content) {
2102
- if (!this.structure) {
2103
- throw new Error("IntrospectionIndex not initialized");
2794
+ async indexFile(filepath, content, ctx) {
2795
+ this.rootDir = ctx.rootDir;
2796
+ const parsedChunks = parseCode(content, filepath);
2797
+ if (parsedChunks.length === 0) {
2798
+ return null;
2799
+ }
2800
+ const pathContext = parsePathContext(filepath);
2801
+ const pathPrefix = formatPathContextForEmbedding(pathContext);
2802
+ const chunkContents = parsedChunks.map((c) => {
2803
+ const namePrefix = c.name ? `${c.name}: ` : "";
2804
+ return `${pathPrefix} ${namePrefix}${c.content}`;
2805
+ });
2806
+ const embeddings = await getEmbeddings(chunkContents);
2807
+ const chunks = parsedChunks.map((pc) => ({
2808
+ id: generateChunkId(filepath, pc.startLine, pc.endLine),
2809
+ content: pc.content,
2810
+ startLine: pc.startLine,
2811
+ endLine: pc.endLine,
2812
+ type: pc.type,
2813
+ name: pc.name,
2814
+ isExported: pc.isExported,
2815
+ jsDoc: pc.jsDoc
2816
+ }));
2817
+ const references = this.extractReferences(content, filepath);
2818
+ const stats = await ctx.getFileStats(filepath);
2819
+ const currentConfig = getEmbeddingConfig();
2820
+ const moduleData = {
2821
+ embeddings,
2822
+ embeddingModel: currentConfig.model
2823
+ };
2824
+ const chunkTypes = [...new Set(parsedChunks.map((pc) => pc.type))];
2825
+ const exports = parsedChunks.filter((pc) => pc.isExported && pc.name).map((pc) => pc.name);
2826
+ const allKeywords = new Set;
2827
+ for (const pc of parsedChunks) {
2828
+ const keywords = extractKeywords(pc.content, pc.name);
2829
+ keywords.forEach((k) => allKeywords.add(k));
2104
2830
  }
2105
- const intro = introspectFile(filepath, this.structure, content);
2106
- this.applyOverrides(intro);
2107
- this.files.set(filepath, intro);
2108
- return intro;
2109
- }
2110
- getFile(filepath) {
2111
- return this.files.get(filepath);
2831
+ pathContext.keywords.forEach((k) => allKeywords.add(k));
2832
+ const fileSummary = {
2833
+ filepath,
2834
+ chunkCount: chunks.length,
2835
+ chunkTypes,
2836
+ keywords: Array.from(allKeywords),
2837
+ exports,
2838
+ lastModified: stats.lastModified,
2839
+ pathContext: {
2840
+ segments: pathContext.segments,
2841
+ layer: pathContext.layer,
2842
+ domain: pathContext.domain,
2843
+ depth: pathContext.depth
2844
+ }
2845
+ };
2846
+ this.pendingSummaries.set(filepath, fileSummary);
2847
+ return {
2848
+ filepath,
2849
+ lastModified: stats.lastModified,
2850
+ chunks,
2851
+ moduleData,
2852
+ references
2853
+ };
2112
2854
  }
2113
- getAllFiles() {
2114
- return Array.from(this.files.values());
2855
+ async finalize(ctx) {
2856
+ const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
2857
+ this.symbolicIndex = new SymbolicIndex(indexDir, this.id);
2858
+ await this.symbolicIndex.initialize();
2859
+ for (const [filepath, summary] of this.pendingSummaries) {
2860
+ this.symbolicIndex.addFile(summary);
2861
+ }
2862
+ this.symbolicIndex.buildBM25Index();
2863
+ await this.symbolicIndex.save();
2864
+ console.log(` Symbolic index built with ${this.pendingSummaries.size} file summaries`);
2865
+ this.pendingSummaries.clear();
2115
2866
  }
2116
- applyOverrides(intro) {
2117
- if (!this.config.projects)
2118
- return;
2119
- for (const [projectPath, overrides] of Object.entries(this.config.projects)) {
2120
- if (intro.filepath.startsWith(projectPath + "/") || intro.project.root === projectPath) {
2121
- if (overrides.scope) {
2122
- intro.scope = overrides.scope;
2867
+ async search(query, ctx, options = {}) {
2868
+ const { topK = DEFAULT_TOP_K2, minScore = DEFAULT_MIN_SCORE2, filePatterns } = options;
2869
+ const indexDir = getRaggrepDir(ctx.rootDir, ctx.config);
2870
+ const symbolicIndex = new SymbolicIndex(indexDir, this.id);
2871
+ let candidateFiles;
2872
+ try {
2873
+ await symbolicIndex.initialize();
2874
+ const maxCandidates = topK * TIER1_CANDIDATE_MULTIPLIER;
2875
+ candidateFiles = symbolicIndex.findCandidates(query, maxCandidates);
2876
+ if (candidateFiles.length === 0) {
2877
+ candidateFiles = symbolicIndex.getAllFiles();
2878
+ }
2879
+ } catch {
2880
+ candidateFiles = await ctx.listIndexedFiles();
2881
+ }
2882
+ if (filePatterns && filePatterns.length > 0) {
2883
+ candidateFiles = candidateFiles.filter((filepath) => {
2884
+ return filePatterns.some((pattern) => {
2885
+ if (pattern.startsWith("*.")) {
2886
+ const ext = pattern.slice(1);
2887
+ return filepath.endsWith(ext);
2888
+ }
2889
+ return filepath.includes(pattern);
2890
+ });
2891
+ });
2892
+ }
2893
+ const queryEmbedding = await getEmbedding(query);
2894
+ const bm25Index = new BM25Index;
2895
+ const allChunksData = [];
2896
+ for (const filepath of candidateFiles) {
2897
+ const fileIndex = await ctx.loadFileIndex(filepath);
2898
+ if (!fileIndex)
2899
+ continue;
2900
+ const moduleData = fileIndex.moduleData;
2901
+ if (!moduleData?.embeddings)
2902
+ continue;
2903
+ for (let i = 0;i < fileIndex.chunks.length; i++) {
2904
+ const chunk = fileIndex.chunks[i];
2905
+ const embedding = moduleData.embeddings[i];
2906
+ if (!embedding)
2907
+ continue;
2908
+ allChunksData.push({
2909
+ filepath: fileIndex.filepath,
2910
+ chunk,
2911
+ embedding
2912
+ });
2913
+ bm25Index.addDocuments([{ id: chunk.id, content: chunk.content }]);
2914
+ }
2915
+ }
2916
+ const bm25Results = bm25Index.search(query, topK * 3);
2917
+ const bm25Scores = new Map;
2918
+ for (const result of bm25Results) {
2919
+ bm25Scores.set(result.id, normalizeScore(result.score, 3));
2920
+ }
2921
+ const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
2922
+ const pathBoosts = new Map;
2923
+ for (const filepath of candidateFiles) {
2924
+ const summary = symbolicIndex.getFileSummary(filepath);
2925
+ if (summary?.pathContext) {
2926
+ let boost = 0;
2927
+ const ctx2 = summary.pathContext;
2928
+ if (ctx2.domain && queryTerms.some((t) => ctx2.domain.includes(t) || t.includes(ctx2.domain))) {
2929
+ boost += 0.1;
2123
2930
  }
2124
- if (overrides.framework) {
2125
- intro.framework = overrides.framework;
2931
+ if (ctx2.layer && queryTerms.some((t) => ctx2.layer.includes(t) || t.includes(ctx2.layer))) {
2932
+ boost += 0.05;
2126
2933
  }
2127
- break;
2934
+ const segmentMatch = ctx2.segments.some((seg) => queryTerms.some((t) => seg.toLowerCase().includes(t) || t.includes(seg.toLowerCase())));
2935
+ if (segmentMatch) {
2936
+ boost += 0.05;
2937
+ }
2938
+ pathBoosts.set(filepath, boost);
2939
+ }
2940
+ }
2941
+ const results = [];
2942
+ for (const { filepath, chunk, embedding } of allChunksData) {
2943
+ const semanticScore = cosineSimilarity(queryEmbedding, embedding);
2944
+ const bm25Score = bm25Scores.get(chunk.id) || 0;
2945
+ const pathBoost = pathBoosts.get(filepath) || 0;
2946
+ const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score + pathBoost;
2947
+ if (hybridScore >= minScore || bm25Score > 0.3) {
2948
+ results.push({
2949
+ filepath,
2950
+ chunk,
2951
+ score: hybridScore,
2952
+ moduleId: this.id,
2953
+ context: {
2954
+ semanticScore,
2955
+ bm25Score,
2956
+ pathBoost
2957
+ }
2958
+ });
2128
2959
  }
2129
2960
  }
2961
+ results.sort((a, b) => b.score - a.score);
2962
+ return results.slice(0, topK);
2130
2963
  }
2131
- async save(config) {
2132
- const introDir = path8.join(getRaggrepDir(this.rootDir, config), "introspection");
2133
- await fs5.mkdir(introDir, { recursive: true });
2134
- const projectPath = path8.join(introDir, "_project.json");
2135
- await fs5.writeFile(projectPath, JSON.stringify({
2136
- version: "1.0.0",
2137
- lastUpdated: new Date().toISOString(),
2138
- structure: this.structure
2139
- }, null, 2));
2140
- for (const [filepath, intro] of this.files) {
2141
- const introFilePath = path8.join(introDir, "files", filepath.replace(/\.[^.]+$/, ".json"));
2142
- await fs5.mkdir(path8.dirname(introFilePath), { recursive: true });
2143
- await fs5.writeFile(introFilePath, JSON.stringify(intro, null, 2));
2964
+ extractReferences(content, filepath) {
2965
+ const references = [];
2966
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
2967
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2968
+ let match;
2969
+ while ((match = importRegex.exec(content)) !== null) {
2970
+ const importPath = match[1];
2971
+ if (importPath.startsWith(".")) {
2972
+ const dir = path10.dirname(filepath);
2973
+ const resolved = path10.normalize(path10.join(dir, importPath));
2974
+ references.push(resolved);
2975
+ }
2144
2976
  }
2145
- console.log(` [Introspection] Saved metadata for ${this.files.size} files`);
2977
+ while ((match = requireRegex.exec(content)) !== null) {
2978
+ const importPath = match[1];
2979
+ if (importPath.startsWith(".")) {
2980
+ const dir = path10.dirname(filepath);
2981
+ const resolved = path10.normalize(path10.join(dir, importPath));
2982
+ references.push(resolved);
2983
+ }
2984
+ }
2985
+ return references;
2146
2986
  }
2147
- async load(config) {
2148
- const introDir = path8.join(getRaggrepDir(this.rootDir, config), "introspection");
2149
- try {
2150
- const projectPath = path8.join(introDir, "_project.json");
2151
- const projectContent = await fs5.readFile(projectPath, "utf-8");
2152
- const projectData = JSON.parse(projectContent);
2153
- this.structure = projectData.structure;
2154
- await this.loadFilesRecursive(path8.join(introDir, "files"), "");
2155
- } catch {
2156
- this.structure = null;
2157
- this.files.clear();
2987
+ }
2988
+ var DEFAULT_MIN_SCORE2 = 0.15, DEFAULT_TOP_K2 = 10, SEMANTIC_WEIGHT = 0.7, BM25_WEIGHT = 0.3, TIER1_CANDIDATE_MULTIPLIER = 3;
2989
+ var init_typescript = __esm(() => {
2990
+ init_embeddings();
2991
+ init_config2();
2992
+ init_parseCode();
2993
+ init_storage();
2994
+ init_keywords();
2995
+ init_keywords();
2996
+ });
2997
+
2998
+ // src/modules/registry.ts
2999
+ class ModuleRegistryImpl {
3000
+ modules = new Map;
3001
+ register(module) {
3002
+ if (this.modules.has(module.id)) {
3003
+ console.warn(`Module '${module.id}' is already registered, overwriting...`);
2158
3004
  }
3005
+ this.modules.set(module.id, module);
2159
3006
  }
2160
- async loadFilesRecursive(basePath, prefix) {
2161
- try {
2162
- const entries = await fs5.readdir(basePath, { withFileTypes: true });
2163
- for (const entry of entries) {
2164
- const entryPath = path8.join(basePath, entry.name);
2165
- const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
2166
- if (entry.isDirectory()) {
2167
- await this.loadFilesRecursive(entryPath, relativePath);
2168
- } else if (entry.name.endsWith(".json")) {
2169
- const content = await fs5.readFile(entryPath, "utf-8");
2170
- const intro = JSON.parse(content);
2171
- this.files.set(intro.filepath, intro);
2172
- }
2173
- }
2174
- } catch {}
3007
+ get(id) {
3008
+ return this.modules.get(id);
2175
3009
  }
2176
- clear() {
2177
- this.files.clear();
2178
- this.structure = null;
3010
+ list() {
3011
+ return Array.from(this.modules.values());
3012
+ }
3013
+ getEnabled(config) {
3014
+ const enabledIds = new Set(config.modules.filter((m) => m.enabled).map((m) => m.id));
3015
+ return this.list().filter((m) => enabledIds.has(m.id));
2179
3016
  }
2180
3017
  }
2181
- var init_introspection = __esm(() => {
2182
- init_projectDetector();
2183
- init_fileIntrospector();
2184
- init_config2();
2185
- init_fileIntrospector();
2186
- init_projectDetector();
3018
+ async function registerBuiltInModules() {
3019
+ const { CoreModule: CoreModule2 } = await Promise.resolve().then(() => (init_core(), exports_core));
3020
+ const { TypeScriptModule: TypeScriptModule2 } = await Promise.resolve().then(() => (init_typescript(), exports_typescript));
3021
+ registry.register(new CoreModule2);
3022
+ registry.register(new TypeScriptModule2);
3023
+ }
3024
+ var registry;
3025
+ var init_registry = __esm(() => {
3026
+ registry = new ModuleRegistryImpl;
2187
3027
  });
2188
3028
 
2189
3029
  // src/app/indexer/watcher.ts
2190
3030
  import { watch } from "chokidar";
2191
- import * as path9 from "path";
3031
+ import * as path11 from "path";
2192
3032
  async function watchDirectory(rootDir, options = {}) {
2193
3033
  const {
2194
3034
  debounceMs = DEFAULT_DEBOUNCE_MS,
@@ -2199,13 +3039,19 @@ async function watchDirectory(rootDir, options = {}) {
2199
3039
  onFileChange,
2200
3040
  onError
2201
3041
  } = options;
2202
- rootDir = path9.resolve(rootDir);
3042
+ rootDir = path11.resolve(rootDir);
2203
3043
  const config = await loadConfig(rootDir);
2204
- const watchPatterns = config.extensions.map((ext) => `**/*${ext}`);
3044
+ const indexLocation = getIndexLocation(rootDir);
3045
+ const validExtensions = new Set(config.extensions);
2205
3046
  const ignorePatterns = [
2206
3047
  ...config.ignorePaths.map((p) => `**/${p}/**`),
2207
- `**/${config.indexDir}/**`
3048
+ "**/node_modules/**",
3049
+ "**/.git/**"
2208
3050
  ];
3051
+ function shouldWatchFile(filepath) {
3052
+ const ext = path11.extname(filepath);
3053
+ return validExtensions.has(ext);
3054
+ }
2209
3055
  let isRunning = true;
2210
3056
  let isIndexing = false;
2211
3057
  let pendingChanges = new Map;
@@ -2285,7 +3131,10 @@ async function watchDirectory(rootDir, options = {}) {
2285
3131
  function handleFileEvent(event, filepath) {
2286
3132
  if (!isRunning)
2287
3133
  return;
2288
- const relativePath = path9.relative(rootDir, filepath);
3134
+ const relativePath = path11.relative(rootDir, filepath);
3135
+ if (!shouldWatchFile(filepath)) {
3136
+ return;
3137
+ }
2289
3138
  for (const ignorePath of config.ignorePaths) {
2290
3139
  if (relativePath.startsWith(ignorePath) || relativePath.includes(`/${ignorePath}/`)) {
2291
3140
  return;
@@ -2301,8 +3150,7 @@ async function watchDirectory(rootDir, options = {}) {
2301
3150
  pendingChanges.set(relativePath, event);
2302
3151
  scheduleProcessing();
2303
3152
  }
2304
- watcher = watch(watchPatterns, {
2305
- cwd: rootDir,
3153
+ watcher = watch(rootDir, {
2306
3154
  ignored: ignorePatterns,
2307
3155
  persistent: true,
2308
3156
  ignoreInitial: true,
@@ -2311,11 +3159,12 @@ async function watchDirectory(rootDir, options = {}) {
2311
3159
  pollInterval: 50
2312
3160
  },
2313
3161
  usePolling: false,
2314
- atomic: true
3162
+ atomic: true,
3163
+ depth: 99
2315
3164
  });
2316
- watcher.on("add", (filepath) => handleFileEvent("add", path9.join(rootDir, filepath)));
2317
- watcher.on("change", (filepath) => handleFileEvent("change", path9.join(rootDir, filepath)));
2318
- watcher.on("unlink", (filepath) => handleFileEvent("unlink", path9.join(rootDir, filepath)));
3165
+ watcher.on("add", (filepath) => handleFileEvent("add", filepath));
3166
+ watcher.on("change", (filepath) => handleFileEvent("change", filepath));
3167
+ watcher.on("unlink", (filepath) => handleFileEvent("unlink", filepath));
2319
3168
  watcher.on("error", (error) => {
2320
3169
  const err = error instanceof Error ? error : new Error(String(error));
2321
3170
  console.error("[Watch] Watcher error:", err);
@@ -2323,9 +3172,9 @@ async function watchDirectory(rootDir, options = {}) {
2323
3172
  onError(err);
2324
3173
  }
2325
3174
  });
2326
- await new Promise((resolve2) => {
3175
+ await new Promise((resolve3) => {
2327
3176
  watcher.on("ready", () => {
2328
- resolve2();
3177
+ resolve3();
2329
3178
  });
2330
3179
  });
2331
3180
  return {
@@ -2359,11 +3208,13 @@ __export(exports_indexer, {
2359
3208
  });
2360
3209
  import { glob } from "glob";
2361
3210
  import * as fs6 from "fs/promises";
2362
- import * as path10 from "path";
3211
+ import * as path12 from "path";
2363
3212
  async function indexDirectory(rootDir, options = {}) {
2364
3213
  const verbose = options.verbose ?? false;
2365
- rootDir = path10.resolve(rootDir);
3214
+ rootDir = path12.resolve(rootDir);
3215
+ const location = getIndexLocation(rootDir);
2366
3216
  console.log(`Indexing directory: ${rootDir}`);
3217
+ console.log(`Index location: ${location.indexDir}`);
2367
3218
  const config = await loadConfig(rootDir);
2368
3219
  const introspection = new IntrospectionIndex(rootDir);
2369
3220
  await introspection.initialize();
@@ -2405,11 +3256,11 @@ async function indexDirectory(rootDir, options = {}) {
2405
3256
  rootDir,
2406
3257
  config,
2407
3258
  readFile: async (filepath) => {
2408
- const fullPath = path10.isAbsolute(filepath) ? filepath : path10.join(rootDir, filepath);
3259
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
2409
3260
  return fs6.readFile(fullPath, "utf-8");
2410
3261
  },
2411
3262
  getFileStats: async (filepath) => {
2412
- const fullPath = path10.isAbsolute(filepath) ? filepath : path10.join(rootDir, filepath);
3263
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
2413
3264
  const stats = await fs6.stat(fullPath);
2414
3265
  return { lastModified: stats.mtime.toISOString() };
2415
3266
  }
@@ -2434,18 +3285,18 @@ async function indexWithModule(rootDir, files, module, config, verbose, introspe
2434
3285
  rootDir,
2435
3286
  config,
2436
3287
  readFile: async (filepath) => {
2437
- const fullPath = path10.isAbsolute(filepath) ? filepath : path10.join(rootDir, filepath);
3288
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
2438
3289
  return fs6.readFile(fullPath, "utf-8");
2439
3290
  },
2440
3291
  getFileStats: async (filepath) => {
2441
- const fullPath = path10.isAbsolute(filepath) ? filepath : path10.join(rootDir, filepath);
3292
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
2442
3293
  const stats = await fs6.stat(fullPath);
2443
3294
  return { lastModified: stats.mtime.toISOString() };
2444
3295
  },
2445
3296
  getIntrospection: (filepath) => introspection.getFile(filepath)
2446
3297
  };
2447
3298
  for (const filepath of files) {
2448
- const relativePath = path10.relative(rootDir, filepath);
3299
+ const relativePath = path12.relative(rootDir, filepath);
2449
3300
  try {
2450
3301
  const stats = await fs6.stat(filepath);
2451
3302
  const lastModified = stats.mtime.toISOString();
@@ -2515,13 +3366,13 @@ async function loadModuleManifest(rootDir, moduleId, config) {
2515
3366
  }
2516
3367
  async function writeModuleManifest(rootDir, moduleId, manifest, config) {
2517
3368
  const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
2518
- await fs6.mkdir(path10.dirname(manifestPath), { recursive: true });
3369
+ await fs6.mkdir(path12.dirname(manifestPath), { recursive: true });
2519
3370
  await fs6.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
2520
3371
  }
2521
3372
  async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
2522
3373
  const indexPath = getModuleIndexPath(rootDir, moduleId, config);
2523
- const indexFilePath = path10.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
2524
- await fs6.mkdir(path10.dirname(indexFilePath), { recursive: true });
3374
+ const indexFilePath = path12.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
3375
+ await fs6.mkdir(path12.dirname(indexFilePath), { recursive: true });
2525
3376
  await fs6.writeFile(indexFilePath, JSON.stringify(fileIndex, null, 2));
2526
3377
  }
2527
3378
  async function updateGlobalManifest(rootDir, modules, config) {
@@ -2531,12 +3382,12 @@ async function updateGlobalManifest(rootDir, modules, config) {
2531
3382
  lastUpdated: new Date().toISOString(),
2532
3383
  modules: modules.map((m) => m.id)
2533
3384
  };
2534
- await fs6.mkdir(path10.dirname(manifestPath), { recursive: true });
3385
+ await fs6.mkdir(path12.dirname(manifestPath), { recursive: true });
2535
3386
  await fs6.writeFile(manifestPath, JSON.stringify(manifest, null, 2));
2536
3387
  }
2537
3388
  async function cleanupIndex(rootDir, options = {}) {
2538
3389
  const verbose = options.verbose ?? false;
2539
- rootDir = path10.resolve(rootDir);
3390
+ rootDir = path12.resolve(rootDir);
2540
3391
  console.log(`Cleaning up index in: ${rootDir}`);
2541
3392
  const config = await loadConfig(rootDir);
2542
3393
  await registerBuiltInModules();
@@ -2566,7 +3417,7 @@ async function cleanupModuleIndex(rootDir, moduleId, config, verbose) {
2566
3417
  const filesToRemove = [];
2567
3418
  const updatedFiles = {};
2568
3419
  for (const [filepath, entry] of Object.entries(manifest.files)) {
2569
- const fullPath = path10.join(rootDir, filepath);
3420
+ const fullPath = path12.join(rootDir, filepath);
2570
3421
  try {
2571
3422
  await fs6.access(fullPath);
2572
3423
  updatedFiles[filepath] = entry;
@@ -2580,7 +3431,7 @@ async function cleanupModuleIndex(rootDir, moduleId, config, verbose) {
2580
3431
  }
2581
3432
  }
2582
3433
  for (const filepath of filesToRemove) {
2583
- const indexFilePath = path10.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
3434
+ const indexFilePath = path12.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
2584
3435
  try {
2585
3436
  await fs6.unlink(indexFilePath);
2586
3437
  } catch {}
@@ -2596,7 +3447,7 @@ async function cleanupEmptyDirectories(dir) {
2596
3447
  const entries = await fs6.readdir(dir, { withFileTypes: true });
2597
3448
  for (const entry of entries) {
2598
3449
  if (entry.isDirectory()) {
2599
- const subDir = path10.join(dir, entry.name);
3450
+ const subDir = path12.join(dir, entry.name);
2600
3451
  await cleanupEmptyDirectories(subDir);
2601
3452
  }
2602
3453
  }
@@ -2611,9 +3462,10 @@ async function cleanupEmptyDirectories(dir) {
2611
3462
  }
2612
3463
  }
2613
3464
  async function getIndexStatus(rootDir) {
2614
- rootDir = path10.resolve(rootDir);
3465
+ rootDir = path12.resolve(rootDir);
2615
3466
  const config = await loadConfig(rootDir);
2616
- const indexDir = path10.join(rootDir, config.indexDir);
3467
+ const location = getIndexLocation(rootDir);
3468
+ const indexDir = location.indexDir;
2617
3469
  const status = {
2618
3470
  exists: false,
2619
3471
  rootDir,
@@ -2646,7 +3498,7 @@ async function getIndexStatus(rootDir) {
2646
3498
  }
2647
3499
  } catch {
2648
3500
  try {
2649
- const entries = await fs6.readdir(path10.join(indexDir, "index"));
3501
+ const entries = await fs6.readdir(path12.join(indexDir, "index"));
2650
3502
  if (entries.length > 0) {
2651
3503
  status.exists = true;
2652
3504
  for (const entry of entries) {
@@ -2680,9 +3532,9 @@ __export(exports_search, {
2680
3532
  formatSearchResults: () => formatSearchResults
2681
3533
  });
2682
3534
  import * as fs7 from "fs/promises";
2683
- import * as path11 from "path";
3535
+ import * as path13 from "path";
2684
3536
  async function search(rootDir, query, options = {}) {
2685
- rootDir = path11.resolve(rootDir);
3537
+ rootDir = path13.resolve(rootDir);
2686
3538
  console.log(`Searching for: "${query}"`);
2687
3539
  const config = await loadConfig(rootDir);
2688
3540
  await registerBuiltInModules();
@@ -2723,7 +3575,7 @@ function createSearchContext(rootDir, moduleId, config) {
2723
3575
  config,
2724
3576
  loadFileIndex: async (filepath) => {
2725
3577
  const hasExtension = /\.[^./]+$/.test(filepath);
2726
- const indexFilePath = hasExtension ? path11.join(indexPath, filepath.replace(/\.[^.]+$/, ".json")) : path11.join(indexPath, filepath + ".json");
3578
+ const indexFilePath = hasExtension ? path13.join(indexPath, filepath.replace(/\.[^.]+$/, ".json")) : path13.join(indexPath, filepath + ".json");
2727
3579
  try {
2728
3580
  const content = await fs7.readFile(indexFilePath, "utf-8");
2729
3581
  return JSON.parse(content);
@@ -2735,7 +3587,7 @@ function createSearchContext(rootDir, moduleId, config) {
2735
3587
  const files = [];
2736
3588
  await traverseDirectory(indexPath, files, indexPath);
2737
3589
  return files.filter((f) => f.endsWith(".json") && !f.endsWith("manifest.json")).map((f) => {
2738
- const relative4 = path11.relative(indexPath, f);
3590
+ const relative4 = path13.relative(indexPath, f);
2739
3591
  return relative4.replace(/\.json$/, "");
2740
3592
  });
2741
3593
  }
@@ -2745,7 +3597,7 @@ async function traverseDirectory(dir, files, basePath) {
2745
3597
  try {
2746
3598
  const entries = await fs7.readdir(dir, { withFileTypes: true });
2747
3599
  for (const entry of entries) {
2748
- const fullPath = path11.join(dir, entry.name);
3600
+ const fullPath = path13.join(dir, entry.name);
2749
3601
  if (entry.isDirectory()) {
2750
3602
  await traverseDirectory(fullPath, files, basePath);
2751
3603
  } else if (entry.isFile()) {
@@ -2802,10 +3654,77 @@ var init_search = __esm(() => {
2802
3654
 
2803
3655
  // src/app/cli/main.ts
2804
3656
  init_embeddings();
2805
- import { createRequire } from "module";
2806
- var require2 = createRequire(import.meta.url);
2807
- var pkg = require2("../../../package.json");
2808
- var VERSION = pkg.version;
3657
+ // package.json
3658
+ var package_default = {
3659
+ name: "raggrep",
3660
+ version: "0.1.6",
3661
+ description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
3662
+ type: "module",
3663
+ main: "./dist/index.js",
3664
+ types: "./dist/index.d.ts",
3665
+ exports: {
3666
+ ".": {
3667
+ import: "./dist/index.js",
3668
+ types: "./dist/index.d.ts"
3669
+ }
3670
+ },
3671
+ bin: {
3672
+ raggrep: "dist/cli/main.js"
3673
+ },
3674
+ files: [
3675
+ "dist",
3676
+ "README.md",
3677
+ "LICENSE"
3678
+ ],
3679
+ scripts: {
3680
+ build: "bun run build:clean && bun run build:bundle && bun run build:types && bun run build:shebang",
3681
+ "build:clean": "rm -rf dist",
3682
+ "build:bundle": "bun build src/index.ts --outdir dist --target node --sourcemap=external --external '@xenova/transformers' --external 'glob' --external 'typescript' --external 'chokidar' && bun build src/app/cli/main.ts --outdir dist/cli --target node --sourcemap=external --external '@xenova/transformers' --external 'glob' --external 'typescript' --external 'chokidar'",
3683
+ "build:types": "tsc --emitDeclarationOnly --outDir dist",
3684
+ "build:shebang": "echo '#!/usr/bin/env node' | cat - dist/cli/main.js > temp && mv temp dist/cli/main.js && chmod +x dist/cli/main.js",
3685
+ prepublishOnly: "bun run build",
3686
+ raggrep: "bun run src/app/cli/main.ts",
3687
+ test: "bun test",
3688
+ dev: "bun run src/app/cli/main.ts"
3689
+ },
3690
+ keywords: [
3691
+ "rag",
3692
+ "search",
3693
+ "semantic-search",
3694
+ "embeddings",
3695
+ "codebase",
3696
+ "local",
3697
+ "ai",
3698
+ "code-search",
3699
+ "transformers"
3700
+ ],
3701
+ author: "",
3702
+ license: "MIT",
3703
+ repository: {
3704
+ type: "git",
3705
+ url: "git+https://github.com/conradkoh/raggrep.git"
3706
+ },
3707
+ bugs: {
3708
+ url: "https://github.com/conradkoh/raggrep/issues"
3709
+ },
3710
+ homepage: "https://github.com/conradkoh/raggrep#readme",
3711
+ engines: {
3712
+ node: ">=18.0.0"
3713
+ },
3714
+ dependencies: {
3715
+ "@xenova/transformers": "^2.17.0",
3716
+ chokidar: "^5.0.0",
3717
+ glob: "^10.0.0",
3718
+ typescript: "^5.0.0"
3719
+ },
3720
+ devDependencies: {
3721
+ "@types/bun": "latest",
3722
+ "@types/node": "^20.0.0"
3723
+ }
3724
+ };
3725
+
3726
+ // src/app/cli/main.ts
3727
+ var VERSION = package_default.version;
2809
3728
  var args = process.argv.slice(2);
2810
3729
  var command = args[0];
2811
3730
  if (command === "--version" || command === "-v") {
@@ -3173,4 +4092,4 @@ Run 'raggrep <command> --help' for more information.
3173
4092
  }
3174
4093
  main();
3175
4094
 
3176
- //# debugId=9D236CA9876AFE5464756E2164756E21
4095
+ //# debugId=03A02811A6080C5964756E2164756E21