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