raggrep 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/README.md +62 -95
  2. package/dist/app/indexer/index.d.ts +27 -2
  3. package/dist/cli/main.js +967 -604
  4. package/dist/cli/main.js.map +18 -17
  5. package/dist/{introspection/conventions/types.d.ts → domain/entities/conventions.d.ts} +6 -5
  6. package/dist/domain/entities/index.d.ts +2 -0
  7. package/dist/{introspection → domain/services}/conventions/configFiles.d.ts +1 -1
  8. package/dist/{introspection → domain/services}/conventions/entryPoints.d.ts +1 -1
  9. package/dist/{introspection → domain/services}/conventions/frameworks/convex.d.ts +1 -1
  10. package/dist/{introspection → domain/services}/conventions/frameworks/index.d.ts +1 -1
  11. package/dist/{introspection → domain/services}/conventions/frameworks/nextjs.d.ts +1 -1
  12. package/dist/{introspection → domain/services}/conventions/index.d.ts +5 -5
  13. package/dist/domain/services/introspection.d.ts +31 -0
  14. package/dist/index.js +671 -474
  15. package/dist/index.js.map +16 -16
  16. package/dist/{introspection/index.d.ts → infrastructure/introspection/IntrospectionIndex.d.ts} +3 -14
  17. package/dist/infrastructure/introspection/index.d.ts +9 -0
  18. package/dist/{introspection → infrastructure/introspection}/projectDetector.d.ts +3 -12
  19. package/dist/types.d.ts +4 -4
  20. package/package.json +1 -1
  21. package/dist/introspection/fileIntrospector.d.ts +0 -14
  22. /package/dist/{introspection/types.d.ts → domain/entities/introspection.d.ts} +0 -0
  23. /package/dist/{introspection → domain/services}/conventions/conventions.test.d.ts +0 -0
  24. /package/dist/{introspection → domain/services}/introspection.test.d.ts +0 -0
package/dist/cli/main.js CHANGED
@@ -429,247 +429,11 @@ function normalizeScore(score, midpoint = 5) {
429
429
  }
430
430
  var BM25_K1 = 1.5, BM25_B = 0.75;
431
431
 
432
- // src/introspection/projectDetector.ts
432
+ // src/domain/services/conventions/entryPoints.ts
433
433
  import * as path3 from "path";
434
- import * as fs2 from "fs/promises";
435
- function detectScopeFromName(name) {
436
- const nameLower = name.toLowerCase();
437
- for (const [scope, keywords] of Object.entries(SCOPE_KEYWORDS)) {
438
- if (scope === "unknown")
439
- continue;
440
- for (const keyword of keywords) {
441
- if (nameLower.includes(keyword)) {
442
- return scope;
443
- }
444
- }
445
- }
446
- return "unknown";
447
- }
448
- async function scanForPackageJsons(rootDir, currentDir = "", depth = 0) {
449
- if (depth > MAX_SCAN_DEPTH)
450
- return [];
451
- const results = [];
452
- const fullDir = currentDir ? path3.join(rootDir, currentDir) : rootDir;
453
- try {
454
- const entries = await fs2.readdir(fullDir, { withFileTypes: true });
455
- const hasPackageJson = entries.some((e) => e.isFile() && e.name === "package.json");
456
- if (hasPackageJson && currentDir) {
457
- const info = await parsePackageJson(rootDir, currentDir);
458
- if (info) {
459
- results.push(info);
460
- }
461
- }
462
- for (const entry of entries) {
463
- if (!entry.isDirectory())
464
- continue;
465
- if (SKIP_DIRS.has(entry.name))
466
- continue;
467
- const subPath = currentDir ? `${currentDir}/${entry.name}` : entry.name;
468
- const subResults = await scanForPackageJsons(rootDir, subPath, depth + 1);
469
- results.push(...subResults);
470
- }
471
- } catch {}
472
- return results;
473
- }
474
- async function parsePackageJson(rootDir, relativePath) {
475
- try {
476
- const packageJsonPath = path3.join(rootDir, relativePath, "package.json");
477
- const content = await fs2.readFile(packageJsonPath, "utf-8");
478
- const pkg = JSON.parse(content);
479
- const name = pkg.name || path3.basename(relativePath);
480
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
481
- let type = "unknown";
482
- if (deps["next"] || deps["react"] || deps["vue"] || deps["svelte"]) {
483
- type = "app";
484
- } else if (deps["express"] || deps["fastify"] || deps["koa"] || deps["hono"]) {
485
- type = "service";
486
- } else if (pkg.main || pkg.exports) {
487
- type = "library";
488
- }
489
- const hasWorkspaces = Boolean(pkg.workspaces);
490
- return { name, relativePath, type, hasWorkspaces };
491
- } catch {
492
- return null;
493
- }
494
- }
495
- async function detectProjectStructure(rootDir) {
496
- const projectMap = new Map;
497
- let isMonorepo = false;
498
- try {
499
- const entries = await fs2.readdir(rootDir, { withFileTypes: true });
500
- const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
501
- const monorepoPatterns = ["apps", "packages", "libs", "services"];
502
- const hasMonorepoStructure = monorepoPatterns.some((p) => dirNames.includes(p));
503
- if (hasMonorepoStructure) {
504
- isMonorepo = true;
505
- for (const pattern of monorepoPatterns) {
506
- if (!dirNames.includes(pattern))
507
- continue;
508
- const patternDir = path3.join(rootDir, pattern);
509
- try {
510
- const subDirs = await fs2.readdir(patternDir, { withFileTypes: true });
511
- for (const subDir of subDirs) {
512
- if (!subDir.isDirectory())
513
- continue;
514
- const projectRoot = `${pattern}/${subDir.name}`;
515
- const type = getProjectType(pattern);
516
- projectMap.set(projectRoot, {
517
- name: subDir.name,
518
- root: projectRoot,
519
- type
520
- });
521
- }
522
- } catch {}
523
- }
524
- }
525
- const packageJsons = await scanForPackageJsons(rootDir);
526
- for (const pkg of packageJsons) {
527
- if (pkg.hasWorkspaces) {
528
- isMonorepo = true;
529
- }
530
- if (packageJsons.length > 1) {
531
- isMonorepo = true;
532
- }
533
- projectMap.set(pkg.relativePath, {
534
- name: pkg.name,
535
- root: pkg.relativePath,
536
- type: pkg.type
537
- });
538
- }
539
- let rootType = "unknown";
540
- try {
541
- const rootPkgPath = path3.join(rootDir, "package.json");
542
- const rootPkg = JSON.parse(await fs2.readFile(rootPkgPath, "utf-8"));
543
- if (rootPkg.workspaces) {
544
- isMonorepo = true;
545
- }
546
- const deps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
547
- if (deps["next"] || deps["react"] || deps["vue"]) {
548
- rootType = "app";
549
- } else if (deps["express"] || deps["fastify"] || deps["koa"]) {
550
- rootType = "service";
551
- }
552
- } catch {}
553
- const projects = Array.from(projectMap.values()).sort((a, b) => a.root.length - b.root.length);
554
- return {
555
- projects,
556
- isMonorepo,
557
- rootType: isMonorepo ? undefined : rootType
558
- };
559
- } catch {
560
- return {
561
- projects: [],
562
- isMonorepo: false,
563
- rootType: "unknown"
564
- };
565
- }
566
- }
567
- function getProjectType(patternDir) {
568
- switch (patternDir) {
569
- case "apps":
570
- return "app";
571
- case "packages":
572
- case "libs":
573
- return "library";
574
- case "services":
575
- return "service";
576
- case "scripts":
577
- case "tools":
578
- return "script";
579
- default:
580
- return "unknown";
581
- }
582
- }
583
- function findProjectForFile(filepath, structure) {
584
- const normalizedPath = filepath.replace(/\\/g, "/");
585
- const matches = [];
586
- for (const project of structure.projects) {
587
- if (normalizedPath === project.root || normalizedPath.startsWith(project.root + "/")) {
588
- matches.push(project);
589
- }
590
- }
591
- if (matches.length > 0) {
592
- return matches.reduce((best, current) => current.root.length > best.root.length ? current : best);
593
- }
594
- for (const { pattern, type } of PROJECT_PATTERNS) {
595
- const match = normalizedPath.match(pattern);
596
- if (match) {
597
- return {
598
- name: match[1],
599
- root: match[0],
600
- type
601
- };
602
- }
603
- }
604
- return {
605
- name: "root",
606
- root: "",
607
- type: structure.rootType ?? "unknown"
608
- };
609
- }
610
- var MAX_SCAN_DEPTH = 4, SKIP_DIRS, PROJECT_PATTERNS, SCOPE_KEYWORDS;
611
- var init_projectDetector = __esm(() => {
612
- SKIP_DIRS = new Set([
613
- "node_modules",
614
- ".git",
615
- "dist",
616
- "build",
617
- ".next",
618
- ".nuxt",
619
- "coverage",
620
- ".raggrep"
621
- ]);
622
- PROJECT_PATTERNS = [
623
- { pattern: /^apps\/([^/]+)/, type: "app", defaultScope: "unknown" },
624
- { pattern: /^packages\/([^/]+)/, type: "library", defaultScope: "shared" },
625
- { pattern: /^libs\/([^/]+)/, type: "library", defaultScope: "shared" },
626
- { pattern: /^services\/([^/]+)/, type: "service", defaultScope: "backend" },
627
- { pattern: /^scripts\/([^/]+)/, type: "script", defaultScope: "tooling" },
628
- { pattern: /^tools\/([^/]+)/, type: "script", defaultScope: "tooling" }
629
- ];
630
- SCOPE_KEYWORDS = {
631
- frontend: [
632
- "web",
633
- "webapp",
634
- "frontend",
635
- "client",
636
- "ui",
637
- "app",
638
- "mobile",
639
- "react",
640
- "vue",
641
- "angular",
642
- "next",
643
- "nuxt"
644
- ],
645
- backend: [
646
- "api",
647
- "server",
648
- "backend",
649
- "service",
650
- "worker",
651
- "lambda",
652
- "functions"
653
- ],
654
- shared: ["shared", "common", "utils", "lib", "core", "types", "models"],
655
- tooling: [
656
- "scripts",
657
- "tools",
658
- "cli",
659
- "devtools",
660
- "build",
661
- "config",
662
- "infra"
663
- ],
664
- unknown: []
665
- };
666
- });
667
-
668
- // src/introspection/conventions/entryPoints.ts
669
- import * as path4 from "path";
670
434
  function getParentFolder(filepath) {
671
- const dir = path4.dirname(filepath);
672
- return path4.basename(dir);
435
+ const dir = path3.dirname(filepath);
436
+ return path3.basename(dir);
673
437
  }
674
438
  var entryPointConventions;
675
439
  var init_entryPoints = __esm(() => {
@@ -754,11 +518,75 @@ var init_entryPoints = __esm(() => {
754
518
  return filename === "lib.rs" || filename === "main.rs";
755
519
  },
756
520
  keywords: ["entry", "crate", "rust", "module"]
521
+ },
522
+ {
523
+ id: "go-main",
524
+ name: "Go Main Entry",
525
+ description: "Go application main entry point",
526
+ category: "entry-point",
527
+ match: (filepath, filename) => {
528
+ return filename === "main.go";
529
+ },
530
+ keywords: ["entry", "main", "go", "golang", "entrypoint"],
531
+ dynamicKeywords: (filepath) => {
532
+ const parent = getParentFolder(filepath);
533
+ if (parent && !["cmd", "src", ".", ""].includes(parent)) {
534
+ return [parent.toLowerCase()];
535
+ }
536
+ return [];
537
+ }
538
+ },
539
+ {
540
+ id: "python-main",
541
+ name: "Python Main Module",
542
+ description: "Python package main entry point",
543
+ category: "entry-point",
544
+ match: (filepath, filename) => {
545
+ return filename === "__main__.py";
546
+ },
547
+ keywords: ["entry", "main", "python", "entrypoint", "cli"],
548
+ dynamicKeywords: (filepath) => {
549
+ const parent = getParentFolder(filepath);
550
+ if (["src", "lib", ".", ""].includes(parent)) {
551
+ return [];
552
+ }
553
+ return [parent.toLowerCase()];
554
+ }
555
+ },
556
+ {
557
+ id: "python-app",
558
+ name: "Python App Entry",
559
+ description: "Common Python application entry points",
560
+ category: "entry-point",
561
+ match: (filepath, filename) => {
562
+ return filename === "app.py" || filename === "main.py" || filename === "run.py";
563
+ },
564
+ keywords: ["entry", "main", "python", "app", "entrypoint"]
565
+ },
566
+ {
567
+ id: "python-manage",
568
+ name: "Django Manage",
569
+ description: "Django management script",
570
+ category: "entry-point",
571
+ match: (filepath, filename) => {
572
+ return filename === "manage.py";
573
+ },
574
+ keywords: ["entry", "django", "python", "manage", "cli", "admin"]
575
+ },
576
+ {
577
+ id: "python-wsgi",
578
+ name: "Python WSGI Entry",
579
+ description: "Python WSGI application entry point",
580
+ category: "entry-point",
581
+ match: (filepath, filename) => {
582
+ return filename === "wsgi.py" || filename === "asgi.py";
583
+ },
584
+ keywords: ["entry", "wsgi", "asgi", "python", "server", "web"]
757
585
  }
758
586
  ];
759
587
  });
760
588
 
761
- // src/introspection/conventions/configFiles.ts
589
+ // src/domain/services/conventions/configFiles.ts
762
590
  var configFileConventions;
763
591
  var init_configFiles = __esm(() => {
764
592
  configFileConventions = [
@@ -767,40 +595,191 @@ var init_configFiles = __esm(() => {
767
595
  name: "Package.json",
768
596
  description: "Node.js package manifest",
769
597
  category: "configuration",
770
- match: (filepath, filename) => filename === "package.json",
771
- keywords: ["package", "dependencies", "npm", "scripts", "manifest", "node"]
598
+ match: (filepath, filename) => filename === "package.json",
599
+ keywords: ["package", "dependencies", "npm", "scripts", "manifest", "node"]
600
+ },
601
+ {
602
+ id: "pnpm-workspace",
603
+ name: "PNPM Workspace",
604
+ description: "PNPM monorepo workspace configuration",
605
+ category: "configuration",
606
+ match: (filepath, filename) => filename === "pnpm-workspace.yaml" || filename === "pnpm-workspace.yml",
607
+ keywords: ["workspace", "monorepo", "pnpm", "packages"]
608
+ },
609
+ {
610
+ id: "yarn-lock",
611
+ name: "Yarn Lock",
612
+ description: "Yarn dependency lock file",
613
+ category: "configuration",
614
+ match: (filepath, filename) => filename === "yarn.lock",
615
+ keywords: ["dependencies", "lock", "yarn", "versions"]
616
+ },
617
+ {
618
+ id: "package-lock",
619
+ name: "Package Lock",
620
+ description: "NPM dependency lock file",
621
+ category: "configuration",
622
+ match: (filepath, filename) => filename === "package-lock.json",
623
+ keywords: ["dependencies", "lock", "npm", "versions"]
624
+ },
625
+ {
626
+ id: "bun-lockb",
627
+ name: "Bun Lock",
628
+ description: "Bun dependency lock file",
629
+ category: "configuration",
630
+ match: (filepath, filename) => filename === "bun.lockb" || filename === "bun.lock",
631
+ keywords: ["dependencies", "lock", "bun", "versions"]
632
+ },
633
+ {
634
+ id: "go-mod",
635
+ name: "Go Module",
636
+ description: "Go module definition file",
637
+ category: "configuration",
638
+ match: (filepath, filename) => filename === "go.mod",
639
+ keywords: [
640
+ "go",
641
+ "golang",
642
+ "module",
643
+ "dependencies",
644
+ "package",
645
+ "workspace"
646
+ ]
647
+ },
648
+ {
649
+ id: "go-sum",
650
+ name: "Go Sum",
651
+ description: "Go module checksum file",
652
+ category: "configuration",
653
+ match: (filepath, filename) => filename === "go.sum",
654
+ keywords: ["go", "golang", "dependencies", "checksum", "lock", "versions"]
655
+ },
656
+ {
657
+ id: "go-work",
658
+ name: "Go Workspace",
659
+ description: "Go workspace configuration for multi-module development",
660
+ category: "configuration",
661
+ match: (filepath, filename) => filename === "go.work" || filename === "go.work.sum",
662
+ keywords: ["go", "golang", "workspace", "monorepo", "modules"]
663
+ },
664
+ {
665
+ id: "makefile",
666
+ name: "Makefile",
667
+ description: "Make build automation file",
668
+ category: "build",
669
+ match: (filepath, filename) => filename === "Makefile" || filename === "makefile" || filename === "GNUmakefile",
670
+ keywords: ["make", "build", "automation", "tasks", "compile"]
671
+ },
672
+ {
673
+ id: "requirements-txt",
674
+ name: "Python Requirements",
675
+ description: "Python pip requirements file",
676
+ category: "configuration",
677
+ match: (filepath, filename) => filename === "requirements.txt" || filename.startsWith("requirements-") || filename.startsWith("requirements_"),
678
+ keywords: ["python", "pip", "dependencies", "packages", "requirements"]
679
+ },
680
+ {
681
+ id: "pyproject-toml",
682
+ name: "Python Project",
683
+ description: "Python project configuration (PEP 518/621)",
684
+ category: "configuration",
685
+ match: (filepath, filename) => filename === "pyproject.toml",
686
+ keywords: [
687
+ "python",
688
+ "project",
689
+ "config",
690
+ "poetry",
691
+ "build",
692
+ "dependencies",
693
+ "package"
694
+ ]
695
+ },
696
+ {
697
+ id: "setup-py",
698
+ name: "Python Setup",
699
+ description: "Python package setup script",
700
+ category: "configuration",
701
+ match: (filepath, filename) => filename === "setup.py",
702
+ keywords: ["python", "setup", "package", "install", "distribution"]
703
+ },
704
+ {
705
+ id: "setup-cfg",
706
+ name: "Python Setup Config",
707
+ description: "Python setup configuration file",
708
+ category: "configuration",
709
+ match: (filepath, filename) => filename === "setup.cfg",
710
+ keywords: ["python", "setup", "config", "package", "metadata"]
711
+ },
712
+ {
713
+ id: "pipfile",
714
+ name: "Pipfile",
715
+ description: "Pipenv dependency file",
716
+ category: "configuration",
717
+ match: (filepath, filename) => filename === "Pipfile" || filename === "Pipfile.lock",
718
+ keywords: ["python", "pipenv", "dependencies", "packages", "virtualenv"]
719
+ },
720
+ {
721
+ id: "poetry-lock",
722
+ name: "Poetry Lock",
723
+ description: "Poetry dependency lock file",
724
+ category: "configuration",
725
+ match: (filepath, filename) => filename === "poetry.lock",
726
+ keywords: ["python", "poetry", "dependencies", "lock", "versions"]
727
+ },
728
+ {
729
+ id: "tox-ini",
730
+ name: "Tox Config",
731
+ description: "Tox testing automation configuration",
732
+ category: "test",
733
+ match: (filepath, filename) => filename === "tox.ini",
734
+ keywords: ["python", "tox", "testing", "automation", "environments"]
735
+ },
736
+ {
737
+ id: "pytest-ini",
738
+ name: "Pytest Config",
739
+ description: "Pytest configuration file",
740
+ category: "test",
741
+ match: (filepath, filename) => filename === "pytest.ini" || filename === "conftest.py",
742
+ keywords: ["python", "pytest", "testing", "test", "fixtures"]
743
+ },
744
+ {
745
+ id: "mypy-ini",
746
+ name: "Mypy Config",
747
+ description: "Mypy type checker configuration",
748
+ category: "configuration",
749
+ match: (filepath, filename) => filename === "mypy.ini" || filename === ".mypy.ini",
750
+ keywords: ["python", "mypy", "types", "type checking", "static analysis"]
772
751
  },
773
752
  {
774
- id: "pnpm-workspace",
775
- name: "PNPM Workspace",
776
- description: "PNPM monorepo workspace configuration",
753
+ id: "flake8",
754
+ name: "Flake8 Config",
755
+ description: "Flake8 linter configuration",
777
756
  category: "configuration",
778
- match: (filepath, filename) => filename === "pnpm-workspace.yaml" || filename === "pnpm-workspace.yml",
779
- keywords: ["workspace", "monorepo", "pnpm", "packages"]
757
+ match: (filepath, filename) => filename === ".flake8",
758
+ keywords: ["python", "flake8", "linting", "lint", "style"]
780
759
  },
781
760
  {
782
- id: "yarn-lock",
783
- name: "Yarn Lock",
784
- description: "Yarn dependency lock file",
761
+ id: "pylintrc",
762
+ name: "Pylint Config",
763
+ description: "Pylint linter configuration",
785
764
  category: "configuration",
786
- match: (filepath, filename) => filename === "yarn.lock",
787
- keywords: ["dependencies", "lock", "yarn", "versions"]
765
+ match: (filepath, filename) => filename === ".pylintrc" || filename === "pylintrc" || filename === "pylint.toml",
766
+ keywords: ["python", "pylint", "linting", "lint", "code quality"]
788
767
  },
789
768
  {
790
- id: "package-lock",
791
- name: "Package Lock",
792
- description: "NPM dependency lock file",
769
+ id: "ruff-toml",
770
+ name: "Ruff Config",
771
+ description: "Ruff linter/formatter configuration",
793
772
  category: "configuration",
794
- match: (filepath, filename) => filename === "package-lock.json",
795
- keywords: ["dependencies", "lock", "npm", "versions"]
773
+ match: (filepath, filename) => filename === "ruff.toml" || filename === ".ruff.toml",
774
+ keywords: ["python", "ruff", "linting", "formatting", "fast"]
796
775
  },
797
776
  {
798
- id: "bun-lockb",
799
- name: "Bun Lock",
800
- description: "Bun dependency lock file",
777
+ id: "black-toml",
778
+ name: "Black Config",
779
+ description: "Black formatter configuration",
801
780
  category: "configuration",
802
- match: (filepath, filename) => filename === "bun.lockb" || filename === "bun.lock",
803
- keywords: ["dependencies", "lock", "bun", "versions"]
781
+ match: (filepath, filename) => filename === ".black.toml",
782
+ keywords: ["python", "black", "formatting", "format", "style"]
804
783
  },
805
784
  {
806
785
  id: "tsconfig",
@@ -1043,7 +1022,7 @@ var init_configFiles = __esm(() => {
1043
1022
  ];
1044
1023
  });
1045
1024
 
1046
- // src/introspection/conventions/frameworks/nextjs.ts
1025
+ // src/domain/services/conventions/frameworks/nextjs.ts
1047
1026
  var nextjsConventions, nextjsFramework;
1048
1027
  var init_nextjs = __esm(() => {
1049
1028
  nextjsConventions = [
@@ -1208,7 +1187,7 @@ var init_nextjs = __esm(() => {
1208
1187
  };
1209
1188
  });
1210
1189
 
1211
- // src/introspection/conventions/frameworks/convex.ts
1190
+ // src/domain/services/conventions/frameworks/convex.ts
1212
1191
  var convexConventions, convexFramework;
1213
1192
  var init_convex = __esm(() => {
1214
1193
  convexConventions = [
@@ -1304,7 +1283,7 @@ var init_convex = __esm(() => {
1304
1283
  };
1305
1284
  });
1306
1285
 
1307
- // src/introspection/conventions/frameworks/index.ts
1286
+ // src/domain/services/conventions/frameworks/index.ts
1308
1287
  function getAllFrameworkConventions() {
1309
1288
  return frameworkProviders.flatMap((f) => f.conventions);
1310
1289
  }
@@ -1320,26 +1299,21 @@ var init_frameworks = __esm(() => {
1320
1299
  ];
1321
1300
  });
1322
1301
 
1323
- // src/introspection/conventions/index.ts
1324
- import * as path5 from "path";
1325
- function getAllConventions() {
1302
+ // src/domain/services/conventions/index.ts
1303
+ import * as path4 from "path";
1304
+ function getConventions() {
1326
1305
  return [
1327
1306
  ...entryPointConventions,
1328
1307
  ...configFileConventions,
1329
- ...getAllFrameworkConventions()
1330
- ];
1331
- }
1332
- function getConventions() {
1333
- return [
1334
- ...getAllConventions(),
1308
+ ...getAllFrameworkConventions(),
1335
1309
  ...typeDefinitionConventions,
1336
1310
  ...testFileConventions
1337
1311
  ];
1338
1312
  }
1339
1313
  function getConventionKeywords(filepath) {
1340
1314
  const conventions = getConventions();
1341
- const filename = path5.basename(filepath);
1342
- const extension = path5.extname(filepath);
1315
+ const filename = path4.basename(filepath);
1316
+ const extension = path4.extname(filepath);
1343
1317
  const keywords = new Set;
1344
1318
  for (const convention of conventions) {
1345
1319
  try {
@@ -1386,9 +1360,8 @@ var init_conventions = __esm(() => {
1386
1360
  keywords: ["types", "definitions", "typescript", "interfaces"],
1387
1361
  dynamicKeywords: (filepath) => {
1388
1362
  const match = filepath.match(/([^/]+)\.types\.ts$/);
1389
- if (match) {
1363
+ if (match)
1390
1364
  return [match[1].toLowerCase()];
1391
- }
1392
1365
  return [];
1393
1366
  }
1394
1367
  },
@@ -1411,9 +1384,8 @@ var init_conventions = __esm(() => {
1411
1384
  keywords: ["test", "spec", "unit test"],
1412
1385
  dynamicKeywords: (filepath) => {
1413
1386
  const match = filepath.match(/([^/]+)\.(test|spec)\./);
1414
- if (match) {
1387
+ if (match)
1415
1388
  return [match[1].toLowerCase()];
1416
- }
1417
1389
  return [];
1418
1390
  }
1419
1391
  },
@@ -1428,22 +1400,19 @@ var init_conventions = __esm(() => {
1428
1400
  ];
1429
1401
  });
1430
1402
 
1431
- // src/introspection/fileIntrospector.ts
1432
- import * as path6 from "path";
1403
+ // src/domain/services/introspection.ts
1404
+ import * as path5 from "path";
1433
1405
  function introspectFile(filepath, structure, fileContent) {
1434
1406
  const normalizedPath = filepath.replace(/\\/g, "/");
1435
1407
  const segments = normalizedPath.split("/").filter((s) => s.length > 0);
1436
1408
  const filename = segments[segments.length - 1] || "";
1437
- const ext = path6.extname(filename);
1409
+ const ext = path5.extname(filename);
1438
1410
  const project = findProjectForFile(normalizedPath, structure);
1439
1411
  const language = EXTENSION_TO_LANGUAGE[ext] || "unknown";
1440
1412
  const layer = detectLayer(segments, filename);
1441
1413
  const domain = detectDomain(segments);
1442
1414
  const scope = detectScope(segments, project, layer);
1443
- let framework;
1444
- if (fileContent) {
1445
- framework = detectFramework(fileContent);
1446
- }
1415
+ const framework = fileContent ? detectFramework(fileContent) : undefined;
1447
1416
  return {
1448
1417
  filepath: normalizedPath,
1449
1418
  project,
@@ -1456,21 +1425,81 @@ function introspectFile(filepath, structure, fileContent) {
1456
1425
  pathSegments: segments.slice(0, -1)
1457
1426
  };
1458
1427
  }
1428
+ function introspectionToKeywords(intro) {
1429
+ const keywords = [];
1430
+ const filename = path5.basename(intro.filepath);
1431
+ const filenameWithoutExt = filename.replace(/\.[^.]+$/, "");
1432
+ const filenameParts = filenameWithoutExt.split(/[-_.]/).flatMap((part) => part.split(/(?=[A-Z])/)).map((part) => part.toLowerCase()).filter((part) => part.length > 1);
1433
+ keywords.push(...filenameParts);
1434
+ keywords.push(filenameWithoutExt.toLowerCase());
1435
+ if (intro.project.name && intro.project.name !== "root") {
1436
+ keywords.push(intro.project.name.toLowerCase());
1437
+ }
1438
+ if (intro.scope !== "unknown")
1439
+ keywords.push(intro.scope);
1440
+ if (intro.layer)
1441
+ keywords.push(intro.layer);
1442
+ if (intro.domain)
1443
+ keywords.push(intro.domain);
1444
+ if (intro.language !== "unknown")
1445
+ keywords.push(intro.language);
1446
+ if (intro.framework)
1447
+ keywords.push(intro.framework);
1448
+ const skipSegments = new Set(["src", "lib", "index"]);
1449
+ for (const segment of intro.pathSegments) {
1450
+ if (!skipSegments.has(segment.toLowerCase()) && segment.length > 2) {
1451
+ keywords.push(segment.toLowerCase());
1452
+ }
1453
+ }
1454
+ const conventionKeywords = getConventionKeywords(intro.filepath);
1455
+ keywords.push(...conventionKeywords);
1456
+ return [...new Set(keywords)];
1457
+ }
1458
+ function detectScopeFromName(name) {
1459
+ const nameLower = name.toLowerCase();
1460
+ for (const [scope, keywords] of Object.entries(SCOPE_KEYWORDS)) {
1461
+ if (scope === "unknown")
1462
+ continue;
1463
+ for (const keyword of keywords) {
1464
+ if (nameLower.includes(keyword)) {
1465
+ return scope;
1466
+ }
1467
+ }
1468
+ }
1469
+ return "unknown";
1470
+ }
1471
+ function findProjectForFile(filepath, structure) {
1472
+ const normalizedPath = filepath.replace(/\\/g, "/");
1473
+ const matches = [];
1474
+ for (const project of structure.projects) {
1475
+ if (normalizedPath === project.root || normalizedPath.startsWith(project.root + "/")) {
1476
+ matches.push(project);
1477
+ }
1478
+ }
1479
+ if (matches.length > 0) {
1480
+ return matches.reduce((best, current) => current.root.length > best.root.length ? current : best);
1481
+ }
1482
+ for (const { pattern, type } of PROJECT_PATTERNS) {
1483
+ const match = normalizedPath.match(pattern);
1484
+ if (match) {
1485
+ return { name: match[1], root: match[0], type };
1486
+ }
1487
+ }
1488
+ return { name: "root", root: "", type: structure.rootType ?? "unknown" };
1489
+ }
1459
1490
  function detectLayer(segments, filename) {
1460
1491
  const filenameLower = filename.toLowerCase();
1461
1492
  for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
1462
1493
  for (const pattern of patterns) {
1463
- if (filenameLower.includes(pattern)) {
1494
+ if (filenameLower.includes(pattern))
1464
1495
  return layer;
1465
- }
1466
1496
  }
1467
1497
  }
1468
1498
  for (let i = segments.length - 2;i >= 0; i--) {
1469
1499
  const segment = segments[i].toLowerCase();
1470
1500
  for (const [layer, patterns] of Object.entries(LAYER_PATTERNS)) {
1471
- if (patterns.includes(segment)) {
1501
+ if (patterns.includes(segment))
1472
1502
  return layer;
1473
- }
1474
1503
  }
1475
1504
  }
1476
1505
  return;
@@ -1491,9 +1520,8 @@ function detectDomain(segments) {
1491
1520
  const segmentLower = segment.toLowerCase();
1492
1521
  if (skipSegments.has(segmentLower))
1493
1522
  continue;
1494
- if (DOMAIN_PATTERNS.includes(segmentLower)) {
1523
+ if (DOMAIN_PATTERNS.includes(segmentLower))
1495
1524
  return segmentLower;
1496
- }
1497
1525
  for (const domain of DOMAIN_PATTERNS) {
1498
1526
  if (segmentLower.startsWith(domain) || segmentLower.endsWith(domain)) {
1499
1527
  return domain;
@@ -1504,9 +1532,8 @@ function detectDomain(segments) {
1504
1532
  }
1505
1533
  function detectScope(segments, project, layer) {
1506
1534
  const projectScope = detectScopeFromName(project.name);
1507
- if (projectScope !== "unknown") {
1535
+ if (projectScope !== "unknown")
1508
1536
  return projectScope;
1509
- }
1510
1537
  if (layer) {
1511
1538
  switch (layer) {
1512
1539
  case "controller":
@@ -1524,15 +1551,12 @@ function detectScope(segments, project, layer) {
1524
1551
  }
1525
1552
  for (const segment of segments) {
1526
1553
  const segmentLower = segment.toLowerCase();
1527
- if (["server", "api", "backend"].includes(segmentLower)) {
1554
+ if (["server", "api", "backend"].includes(segmentLower))
1528
1555
  return "backend";
1529
- }
1530
- if (["client", "web", "frontend", "ui"].includes(segmentLower)) {
1556
+ if (["client", "web", "frontend", "ui"].includes(segmentLower))
1531
1557
  return "frontend";
1532
- }
1533
- if (["shared", "common", "lib", "libs"].includes(segmentLower)) {
1558
+ if (["shared", "common", "lib", "libs"].includes(segmentLower))
1534
1559
  return "shared";
1535
- }
1536
1560
  }
1537
1561
  return "unknown";
1538
1562
  }
@@ -1546,44 +1570,8 @@ function detectFramework(content) {
1546
1570
  }
1547
1571
  return;
1548
1572
  }
1549
- function introspectionToKeywords(intro) {
1550
- const keywords = [];
1551
- const filename = path6.basename(intro.filepath);
1552
- const filenameWithoutExt = filename.replace(/\.[^.]+$/, "");
1553
- const filenameParts = filenameWithoutExt.split(/[-_.]/).flatMap((part) => part.split(/(?=[A-Z])/)).map((part) => part.toLowerCase()).filter((part) => part.length > 1);
1554
- keywords.push(...filenameParts);
1555
- keywords.push(filenameWithoutExt.toLowerCase());
1556
- if (intro.project.name && intro.project.name !== "root") {
1557
- keywords.push(intro.project.name.toLowerCase());
1558
- }
1559
- if (intro.scope !== "unknown") {
1560
- keywords.push(intro.scope);
1561
- }
1562
- if (intro.layer) {
1563
- keywords.push(intro.layer);
1564
- }
1565
- if (intro.domain) {
1566
- keywords.push(intro.domain);
1567
- }
1568
- if (intro.language !== "unknown") {
1569
- keywords.push(intro.language);
1570
- }
1571
- if (intro.framework) {
1572
- keywords.push(intro.framework);
1573
- }
1574
- const skipSegments = new Set(["src", "lib", "index"]);
1575
- for (const segment of intro.pathSegments) {
1576
- if (!skipSegments.has(segment.toLowerCase()) && segment.length > 2) {
1577
- keywords.push(segment.toLowerCase());
1578
- }
1579
- }
1580
- const conventionKeywords = getConventionKeywords(intro.filepath);
1581
- keywords.push(...conventionKeywords);
1582
- return [...new Set(keywords)];
1583
- }
1584
- var LAYER_PATTERNS, DOMAIN_PATTERNS, FRAMEWORK_INDICATORS, EXTENSION_TO_LANGUAGE;
1585
- var init_fileIntrospector = __esm(() => {
1586
- init_projectDetector();
1573
+ var LAYER_PATTERNS, DOMAIN_PATTERNS, FRAMEWORK_INDICATORS, EXTENSION_TO_LANGUAGE, SCOPE_KEYWORDS, PROJECT_PATTERNS;
1574
+ var init_introspection = __esm(() => {
1587
1575
  init_conventions();
1588
1576
  LAYER_PATTERNS = {
1589
1577
  controller: ["controller", "api", "routes", "route", "handler"],
@@ -1702,120 +1690,45 @@ var init_fileIntrospector = __esm(() => {
1702
1690
  ".md": "markdown",
1703
1691
  ".json": "json",
1704
1692
  ".yaml": "yaml",
1705
- ".yml": "yaml"
1693
+ ".yml": "yaml",
1694
+ ".txt": "text"
1706
1695
  };
1707
- });
1708
-
1709
- // src/introspection/index.ts
1710
- import * as path7 from "path";
1711
- import * as fs3 from "fs/promises";
1712
-
1713
- class IntrospectionIndex {
1714
- rootDir;
1715
- structure = null;
1716
- files = new Map;
1717
- config = {};
1718
- constructor(rootDir) {
1719
- this.rootDir = rootDir;
1720
- }
1721
- async initialize() {
1722
- this.structure = await detectProjectStructure(this.rootDir);
1723
- try {
1724
- const configPath = path7.join(this.rootDir, ".raggrep", "config.json");
1725
- const configContent = await fs3.readFile(configPath, "utf-8");
1726
- const config = JSON.parse(configContent);
1727
- this.config = config.introspection || {};
1728
- } catch {}
1729
- }
1730
- getStructure() {
1731
- return this.structure;
1732
- }
1733
- addFile(filepath, content) {
1734
- if (!this.structure) {
1735
- throw new Error("IntrospectionIndex not initialized");
1736
- }
1737
- const intro = introspectFile(filepath, this.structure, content);
1738
- this.applyOverrides(intro);
1739
- this.files.set(filepath, intro);
1740
- return intro;
1741
- }
1742
- getFile(filepath) {
1743
- return this.files.get(filepath);
1744
- }
1745
- getAllFiles() {
1746
- return Array.from(this.files.values());
1747
- }
1748
- applyOverrides(intro) {
1749
- if (!this.config.projects)
1750
- return;
1751
- for (const [projectPath, overrides] of Object.entries(this.config.projects)) {
1752
- if (intro.filepath.startsWith(projectPath + "/") || intro.project.root === projectPath) {
1753
- if (overrides.scope) {
1754
- intro.scope = overrides.scope;
1755
- }
1756
- if (overrides.framework) {
1757
- intro.framework = overrides.framework;
1758
- }
1759
- break;
1760
- }
1761
- }
1762
- }
1763
- async save(config) {
1764
- const introDir = path7.join(getRaggrepDir(this.rootDir, config), "introspection");
1765
- await fs3.mkdir(introDir, { recursive: true });
1766
- const projectPath = path7.join(introDir, "_project.json");
1767
- await fs3.writeFile(projectPath, JSON.stringify({
1768
- version: "1.0.0",
1769
- lastUpdated: new Date().toISOString(),
1770
- structure: this.structure
1771
- }, null, 2));
1772
- for (const [filepath, intro] of this.files) {
1773
- const introFilePath = path7.join(introDir, "files", filepath.replace(/\.[^.]+$/, ".json"));
1774
- await fs3.mkdir(path7.dirname(introFilePath), { recursive: true });
1775
- await fs3.writeFile(introFilePath, JSON.stringify(intro, null, 2));
1776
- }
1777
- console.log(` [Introspection] Saved metadata for ${this.files.size} files`);
1778
- }
1779
- async load(config) {
1780
- const introDir = path7.join(getRaggrepDir(this.rootDir, config), "introspection");
1781
- try {
1782
- const projectPath = path7.join(introDir, "_project.json");
1783
- const projectContent = await fs3.readFile(projectPath, "utf-8");
1784
- const projectData = JSON.parse(projectContent);
1785
- this.structure = projectData.structure;
1786
- await this.loadFilesRecursive(path7.join(introDir, "files"), "");
1787
- } catch {
1788
- this.structure = null;
1789
- this.files.clear();
1790
- }
1791
- }
1792
- async loadFilesRecursive(basePath, prefix) {
1793
- try {
1794
- const entries = await fs3.readdir(basePath, { withFileTypes: true });
1795
- for (const entry of entries) {
1796
- const entryPath = path7.join(basePath, entry.name);
1797
- const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
1798
- if (entry.isDirectory()) {
1799
- await this.loadFilesRecursive(entryPath, relativePath);
1800
- } else if (entry.name.endsWith(".json")) {
1801
- const content = await fs3.readFile(entryPath, "utf-8");
1802
- const intro = JSON.parse(content);
1803
- this.files.set(intro.filepath, intro);
1804
- }
1805
- }
1806
- } catch {}
1807
- }
1808
- clear() {
1809
- this.files.clear();
1810
- this.structure = null;
1811
- }
1812
- }
1813
- var init_introspection = __esm(() => {
1814
- init_projectDetector();
1815
- init_fileIntrospector();
1816
- init_config2();
1817
- init_fileIntrospector();
1818
- init_projectDetector();
1696
+ SCOPE_KEYWORDS = {
1697
+ frontend: [
1698
+ "web",
1699
+ "webapp",
1700
+ "frontend",
1701
+ "client",
1702
+ "ui",
1703
+ "app",
1704
+ "mobile",
1705
+ "react",
1706
+ "vue",
1707
+ "angular",
1708
+ "next",
1709
+ "nuxt"
1710
+ ],
1711
+ backend: [
1712
+ "api",
1713
+ "server",
1714
+ "backend",
1715
+ "service",
1716
+ "worker",
1717
+ "lambda",
1718
+ "functions"
1719
+ ],
1720
+ shared: ["shared", "common", "utils", "lib", "core", "types", "models"],
1721
+ tooling: ["scripts", "tools", "cli", "devtools", "build", "config", "infra"],
1722
+ unknown: []
1723
+ };
1724
+ PROJECT_PATTERNS = [
1725
+ { pattern: /^apps\/([^/]+)/, type: "app", defaultScope: "unknown" },
1726
+ { pattern: /^packages\/([^/]+)/, type: "library", defaultScope: "shared" },
1727
+ { pattern: /^libs\/([^/]+)/, type: "library", defaultScope: "shared" },
1728
+ { pattern: /^services\/([^/]+)/, type: "service", defaultScope: "backend" },
1729
+ { pattern: /^scripts\/([^/]+)/, type: "script", defaultScope: "tooling" },
1730
+ { pattern: /^tools\/([^/]+)/, type: "script", defaultScope: "tooling" }
1731
+ ];
1819
1732
  });
1820
1733
 
1821
1734
  // src/modules/core/symbols.ts
@@ -1985,8 +1898,8 @@ var exports_core = {};
1985
1898
  __export(exports_core, {
1986
1899
  CoreModule: () => CoreModule
1987
1900
  });
1988
- import * as path8 from "path";
1989
- import * as fs4 from "fs/promises";
1901
+ import * as path6 from "path";
1902
+ import * as fs2 from "fs/promises";
1990
1903
 
1991
1904
  class CoreModule {
1992
1905
  id = "core";
@@ -2004,7 +1917,9 @@ class CoreModule {
2004
1917
  const contentTokens = tokenize(content);
2005
1918
  const intro = ctx.getIntrospection?.(filepath);
2006
1919
  const introKeywords = intro ? introspectionToKeywords(intro) : [];
2007
- const allTokens = [...new Set([...contentTokens, ...symbolKeywords, ...introKeywords])];
1920
+ const allTokens = [
1921
+ ...new Set([...contentTokens, ...symbolKeywords, ...introKeywords])
1922
+ ];
2008
1923
  const chunks = this.createChunks(filepath, content, symbols);
2009
1924
  const stats = await ctx.getFileStats(filepath);
2010
1925
  this.symbolIndex.set(filepath, {
@@ -2078,8 +1993,8 @@ class CoreModule {
2078
1993
  }
2079
1994
  async finalize(ctx) {
2080
1995
  const config = ctx.config;
2081
- const coreDir = path8.join(getRaggrepDir(ctx.rootDir, config), "index", "core");
2082
- await fs4.mkdir(coreDir, { recursive: true });
1996
+ const coreDir = path6.join(getRaggrepDir(ctx.rootDir, config), "index", "core");
1997
+ await fs2.mkdir(coreDir, { recursive: true });
2083
1998
  this.bm25Index = new BM25Index;
2084
1999
  for (const [filepath, entry] of this.symbolIndex) {
2085
2000
  this.bm25Index.addDocument(filepath, entry.tokens);
@@ -2090,7 +2005,7 @@ class CoreModule {
2090
2005
  files: Object.fromEntries(this.symbolIndex),
2091
2006
  bm25Data: this.bm25Index.serialize()
2092
2007
  };
2093
- await fs4.writeFile(path8.join(coreDir, "symbols.json"), JSON.stringify(symbolIndexData, null, 2));
2008
+ await fs2.writeFile(path6.join(coreDir, "symbols.json"), JSON.stringify(symbolIndexData, null, 2));
2094
2009
  console.log(` [Core] Symbol index built with ${this.symbolIndex.size} files`);
2095
2010
  }
2096
2011
  async search(query, ctx, options) {
@@ -2187,10 +2102,10 @@ class CoreModule {
2187
2102
  return bestChunk;
2188
2103
  }
2189
2104
  async loadSymbolIndex(rootDir, config) {
2190
- const coreDir = path8.join(getRaggrepDir(rootDir, config), "index", "core");
2191
- const symbolsPath = path8.join(coreDir, "symbols.json");
2105
+ const coreDir = path6.join(getRaggrepDir(rootDir, config), "index", "core");
2106
+ const symbolsPath = path6.join(coreDir, "symbols.json");
2192
2107
  try {
2193
- const content = await fs4.readFile(symbolsPath, "utf-8");
2108
+ const content = await fs2.readFile(symbolsPath, "utf-8");
2194
2109
  const data = JSON.parse(content);
2195
2110
  this.symbolIndex = new Map(Object.entries(data.files));
2196
2111
  if (data.bm25Data) {
@@ -2617,8 +2532,8 @@ var init_keywords = __esm(() => {
2617
2532
  });
2618
2533
 
2619
2534
  // src/infrastructure/storage/symbolicIndex.ts
2620
- import * as fs5 from "fs/promises";
2621
- import * as path9 from "path";
2535
+ import * as fs3 from "fs/promises";
2536
+ import * as path7 from "path";
2622
2537
 
2623
2538
  class SymbolicIndex {
2624
2539
  meta = null;
@@ -2627,7 +2542,7 @@ class SymbolicIndex {
2627
2542
  symbolicPath;
2628
2543
  moduleId;
2629
2544
  constructor(indexDir, moduleId) {
2630
- this.symbolicPath = path9.join(indexDir, "index", moduleId, "symbolic");
2545
+ this.symbolicPath = path7.join(indexDir, "index", moduleId, "symbolic");
2631
2546
  this.moduleId = moduleId;
2632
2547
  }
2633
2548
  async initialize() {
@@ -2687,18 +2602,18 @@ class SymbolicIndex {
2687
2602
  throw new Error("Index not initialized");
2688
2603
  this.meta.lastUpdated = new Date().toISOString();
2689
2604
  this.meta.fileCount = this.fileSummaries.size;
2690
- await fs5.mkdir(this.symbolicPath, { recursive: true });
2691
- const metaPath = path9.join(this.symbolicPath, "_meta.json");
2692
- await fs5.writeFile(metaPath, JSON.stringify(this.meta, null, 2));
2605
+ await fs3.mkdir(this.symbolicPath, { recursive: true });
2606
+ const metaPath = path7.join(this.symbolicPath, "_meta.json");
2607
+ await fs3.writeFile(metaPath, JSON.stringify(this.meta, null, 2));
2693
2608
  for (const [filepath, summary] of this.fileSummaries) {
2694
2609
  const summaryPath = this.getFileSummaryPath(filepath);
2695
- await fs5.mkdir(path9.dirname(summaryPath), { recursive: true });
2696
- await fs5.writeFile(summaryPath, JSON.stringify(summary, null, 2));
2610
+ await fs3.mkdir(path7.dirname(summaryPath), { recursive: true });
2611
+ await fs3.writeFile(summaryPath, JSON.stringify(summary, null, 2));
2697
2612
  }
2698
2613
  }
2699
2614
  async load() {
2700
- const metaPath = path9.join(this.symbolicPath, "_meta.json");
2701
- const metaContent = await fs5.readFile(metaPath, "utf-8");
2615
+ const metaPath = path7.join(this.symbolicPath, "_meta.json");
2616
+ const metaContent = await fs3.readFile(metaPath, "utf-8");
2702
2617
  this.meta = JSON.parse(metaContent);
2703
2618
  this.fileSummaries.clear();
2704
2619
  await this.loadFileSummariesRecursive(this.symbolicPath);
@@ -2706,14 +2621,14 @@ class SymbolicIndex {
2706
2621
  }
2707
2622
  async loadFileSummariesRecursive(dir) {
2708
2623
  try {
2709
- const entries = await fs5.readdir(dir, { withFileTypes: true });
2624
+ const entries = await fs3.readdir(dir, { withFileTypes: true });
2710
2625
  for (const entry of entries) {
2711
- const fullPath = path9.join(dir, entry.name);
2626
+ const fullPath = path7.join(dir, entry.name);
2712
2627
  if (entry.isDirectory()) {
2713
2628
  await this.loadFileSummariesRecursive(fullPath);
2714
2629
  } else if (entry.name.endsWith(".json") && entry.name !== "_meta.json") {
2715
2630
  try {
2716
- const content = await fs5.readFile(fullPath, "utf-8");
2631
+ const content = await fs3.readFile(fullPath, "utf-8");
2717
2632
  const summary = JSON.parse(content);
2718
2633
  if (summary.filepath) {
2719
2634
  this.fileSummaries.set(summary.filepath, summary);
@@ -2725,18 +2640,18 @@ class SymbolicIndex {
2725
2640
  }
2726
2641
  getFileSummaryPath(filepath) {
2727
2642
  const jsonPath = filepath.replace(/\.[^.]+$/, ".json");
2728
- return path9.join(this.symbolicPath, jsonPath);
2643
+ return path7.join(this.symbolicPath, jsonPath);
2729
2644
  }
2730
2645
  async deleteFileSummary(filepath) {
2731
2646
  try {
2732
- await fs5.unlink(this.getFileSummaryPath(filepath));
2647
+ await fs3.unlink(this.getFileSummaryPath(filepath));
2733
2648
  } catch {}
2734
2649
  this.fileSummaries.delete(filepath);
2735
2650
  }
2736
2651
  async exists() {
2737
2652
  try {
2738
- const metaPath = path9.join(this.symbolicPath, "_meta.json");
2739
- await fs5.access(metaPath);
2653
+ const metaPath = path7.join(this.symbolicPath, "_meta.json");
2654
+ await fs3.access(metaPath);
2740
2655
  return true;
2741
2656
  } catch {
2742
2657
  return false;
@@ -2775,7 +2690,7 @@ __export(exports_typescript, {
2775
2690
  DEFAULT_TOP_K: () => DEFAULT_TOP_K2,
2776
2691
  DEFAULT_MIN_SCORE: () => DEFAULT_MIN_SCORE2
2777
2692
  });
2778
- import * as path10 from "path";
2693
+ import * as path8 from "path";
2779
2694
 
2780
2695
  class TypeScriptModule {
2781
2696
  id = "language/typescript";
@@ -2918,112 +2833,376 @@ class TypeScriptModule {
2918
2833
  for (const result of bm25Results) {
2919
2834
  bm25Scores.set(result.id, normalizeScore(result.score, 3));
2920
2835
  }
2921
- const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
2922
- const pathBoosts = new Map;
2923
- for (const filepath of candidateFiles) {
2924
- const summary = symbolicIndex.getFileSummary(filepath);
2925
- if (summary?.pathContext) {
2926
- let boost = 0;
2927
- const ctx2 = summary.pathContext;
2928
- if (ctx2.domain && queryTerms.some((t) => ctx2.domain.includes(t) || t.includes(ctx2.domain))) {
2929
- boost += 0.1;
2930
- }
2931
- if (ctx2.layer && queryTerms.some((t) => ctx2.layer.includes(t) || t.includes(ctx2.layer))) {
2932
- boost += 0.05;
2836
+ const queryTerms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
2837
+ const pathBoosts = new Map;
2838
+ for (const filepath of candidateFiles) {
2839
+ const summary = symbolicIndex.getFileSummary(filepath);
2840
+ if (summary?.pathContext) {
2841
+ let boost = 0;
2842
+ const ctx2 = summary.pathContext;
2843
+ if (ctx2.domain && queryTerms.some((t) => ctx2.domain.includes(t) || t.includes(ctx2.domain))) {
2844
+ boost += 0.1;
2845
+ }
2846
+ if (ctx2.layer && queryTerms.some((t) => ctx2.layer.includes(t) || t.includes(ctx2.layer))) {
2847
+ boost += 0.05;
2848
+ }
2849
+ const segmentMatch = ctx2.segments.some((seg) => queryTerms.some((t) => seg.toLowerCase().includes(t) || t.includes(seg.toLowerCase())));
2850
+ if (segmentMatch) {
2851
+ boost += 0.05;
2852
+ }
2853
+ pathBoosts.set(filepath, boost);
2854
+ }
2855
+ }
2856
+ const results = [];
2857
+ for (const { filepath, chunk, embedding } of allChunksData) {
2858
+ const semanticScore = cosineSimilarity(queryEmbedding, embedding);
2859
+ const bm25Score = bm25Scores.get(chunk.id) || 0;
2860
+ const pathBoost = pathBoosts.get(filepath) || 0;
2861
+ const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score + pathBoost;
2862
+ if (hybridScore >= minScore || bm25Score > 0.3) {
2863
+ results.push({
2864
+ filepath,
2865
+ chunk,
2866
+ score: hybridScore,
2867
+ moduleId: this.id,
2868
+ context: {
2869
+ semanticScore,
2870
+ bm25Score,
2871
+ pathBoost
2872
+ }
2873
+ });
2874
+ }
2875
+ }
2876
+ results.sort((a, b) => b.score - a.score);
2877
+ return results.slice(0, topK);
2878
+ }
2879
+ extractReferences(content, filepath) {
2880
+ const references = [];
2881
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
2882
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2883
+ let match;
2884
+ while ((match = importRegex.exec(content)) !== null) {
2885
+ const importPath = match[1];
2886
+ if (importPath.startsWith(".")) {
2887
+ const dir = path8.dirname(filepath);
2888
+ const resolved = path8.normalize(path8.join(dir, importPath));
2889
+ references.push(resolved);
2890
+ }
2891
+ }
2892
+ while ((match = requireRegex.exec(content)) !== null) {
2893
+ const importPath = match[1];
2894
+ if (importPath.startsWith(".")) {
2895
+ const dir = path8.dirname(filepath);
2896
+ const resolved = path8.normalize(path8.join(dir, importPath));
2897
+ references.push(resolved);
2898
+ }
2899
+ }
2900
+ return references;
2901
+ }
2902
+ }
2903
+ var DEFAULT_MIN_SCORE2 = 0.15, DEFAULT_TOP_K2 = 10, SEMANTIC_WEIGHT = 0.7, BM25_WEIGHT = 0.3, TIER1_CANDIDATE_MULTIPLIER = 3;
2904
+ var init_typescript = __esm(() => {
2905
+ init_embeddings();
2906
+ init_config2();
2907
+ init_parseCode();
2908
+ init_storage();
2909
+ init_keywords();
2910
+ init_keywords();
2911
+ });
2912
+
2913
+ // src/modules/registry.ts
2914
+ class ModuleRegistryImpl {
2915
+ modules = new Map;
2916
+ register(module) {
2917
+ if (this.modules.has(module.id)) {
2918
+ console.warn(`Module '${module.id}' is already registered, overwriting...`);
2919
+ }
2920
+ this.modules.set(module.id, module);
2921
+ }
2922
+ get(id) {
2923
+ return this.modules.get(id);
2924
+ }
2925
+ list() {
2926
+ return Array.from(this.modules.values());
2927
+ }
2928
+ getEnabled(config) {
2929
+ const enabledIds = new Set(config.modules.filter((m) => m.enabled).map((m) => m.id));
2930
+ return this.list().filter((m) => enabledIds.has(m.id));
2931
+ }
2932
+ }
2933
+ async function registerBuiltInModules() {
2934
+ const { CoreModule: CoreModule2 } = await Promise.resolve().then(() => (init_core(), exports_core));
2935
+ const { TypeScriptModule: TypeScriptModule2 } = await Promise.resolve().then(() => (init_typescript(), exports_typescript));
2936
+ registry.register(new CoreModule2);
2937
+ registry.register(new TypeScriptModule2);
2938
+ }
2939
+ var registry;
2940
+ var init_registry = __esm(() => {
2941
+ registry = new ModuleRegistryImpl;
2942
+ });
2943
+
2944
+ // src/infrastructure/introspection/projectDetector.ts
2945
+ import * as path9 from "path";
2946
+ import * as fs4 from "fs/promises";
2947
+ async function scanForPackageJsons(rootDir, currentDir = "", depth = 0) {
2948
+ if (depth > MAX_SCAN_DEPTH)
2949
+ return [];
2950
+ const results = [];
2951
+ const fullDir = currentDir ? path9.join(rootDir, currentDir) : rootDir;
2952
+ try {
2953
+ const entries = await fs4.readdir(fullDir, { withFileTypes: true });
2954
+ const hasPackageJson = entries.some((e) => e.isFile() && e.name === "package.json");
2955
+ if (hasPackageJson && currentDir) {
2956
+ const info = await parsePackageJson(rootDir, currentDir);
2957
+ if (info)
2958
+ results.push(info);
2959
+ }
2960
+ for (const entry of entries) {
2961
+ if (!entry.isDirectory())
2962
+ continue;
2963
+ if (SKIP_DIRS.has(entry.name))
2964
+ continue;
2965
+ const subPath = currentDir ? `${currentDir}/${entry.name}` : entry.name;
2966
+ const subResults = await scanForPackageJsons(rootDir, subPath, depth + 1);
2967
+ results.push(...subResults);
2968
+ }
2969
+ } catch {}
2970
+ return results;
2971
+ }
2972
+ async function parsePackageJson(rootDir, relativePath) {
2973
+ try {
2974
+ const packageJsonPath = path9.join(rootDir, relativePath, "package.json");
2975
+ const content = await fs4.readFile(packageJsonPath, "utf-8");
2976
+ const pkg = JSON.parse(content);
2977
+ const name = pkg.name || path9.basename(relativePath);
2978
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
2979
+ let type = "unknown";
2980
+ if (deps["next"] || deps["react"] || deps["vue"] || deps["svelte"]) {
2981
+ type = "app";
2982
+ } else if (deps["express"] || deps["fastify"] || deps["koa"] || deps["hono"]) {
2983
+ type = "service";
2984
+ } else if (pkg.main || pkg.exports) {
2985
+ type = "library";
2986
+ }
2987
+ const hasWorkspaces = Boolean(pkg.workspaces);
2988
+ return { name, relativePath, type, hasWorkspaces };
2989
+ } catch {
2990
+ return null;
2991
+ }
2992
+ }
2993
+ function getProjectType(patternDir) {
2994
+ switch (patternDir) {
2995
+ case "apps":
2996
+ return "app";
2997
+ case "packages":
2998
+ case "libs":
2999
+ return "library";
3000
+ case "services":
3001
+ return "service";
3002
+ case "scripts":
3003
+ case "tools":
3004
+ return "script";
3005
+ default:
3006
+ return "unknown";
3007
+ }
3008
+ }
3009
+ async function detectProjectStructure(rootDir) {
3010
+ const projectMap = new Map;
3011
+ let isMonorepo = false;
3012
+ try {
3013
+ const entries = await fs4.readdir(rootDir, { withFileTypes: true });
3014
+ const dirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name);
3015
+ const monorepoPatterns = ["apps", "packages", "libs", "services"];
3016
+ const hasMonorepoStructure = monorepoPatterns.some((p) => dirNames.includes(p));
3017
+ if (hasMonorepoStructure) {
3018
+ isMonorepo = true;
3019
+ for (const pattern of monorepoPatterns) {
3020
+ if (!dirNames.includes(pattern))
3021
+ continue;
3022
+ const patternDir = path9.join(rootDir, pattern);
3023
+ try {
3024
+ const subDirs = await fs4.readdir(patternDir, { withFileTypes: true });
3025
+ for (const subDir of subDirs) {
3026
+ if (!subDir.isDirectory())
3027
+ continue;
3028
+ const projectRoot = `${pattern}/${subDir.name}`;
3029
+ const type = getProjectType(pattern);
3030
+ projectMap.set(projectRoot, {
3031
+ name: subDir.name,
3032
+ root: projectRoot,
3033
+ type
3034
+ });
3035
+ }
3036
+ } catch {}
3037
+ }
3038
+ }
3039
+ const packageJsons = await scanForPackageJsons(rootDir);
3040
+ for (const pkg of packageJsons) {
3041
+ if (pkg.hasWorkspaces)
3042
+ isMonorepo = true;
3043
+ if (packageJsons.length > 1)
3044
+ isMonorepo = true;
3045
+ projectMap.set(pkg.relativePath, {
3046
+ name: pkg.name,
3047
+ root: pkg.relativePath,
3048
+ type: pkg.type
3049
+ });
3050
+ }
3051
+ let rootType = "unknown";
3052
+ try {
3053
+ const rootPkgPath = path9.join(rootDir, "package.json");
3054
+ const rootPkg = JSON.parse(await fs4.readFile(rootPkgPath, "utf-8"));
3055
+ if (rootPkg.workspaces)
3056
+ isMonorepo = true;
3057
+ const deps = { ...rootPkg.dependencies, ...rootPkg.devDependencies };
3058
+ if (deps["next"] || deps["react"] || deps["vue"]) {
3059
+ rootType = "app";
3060
+ } else if (deps["express"] || deps["fastify"] || deps["koa"]) {
3061
+ rootType = "service";
3062
+ }
3063
+ } catch {}
3064
+ const projects = Array.from(projectMap.values()).sort((a, b) => a.root.length - b.root.length);
3065
+ return {
3066
+ projects,
3067
+ isMonorepo,
3068
+ rootType: isMonorepo ? undefined : rootType
3069
+ };
3070
+ } catch {
3071
+ return {
3072
+ projects: [],
3073
+ isMonorepo: false,
3074
+ rootType: "unknown"
3075
+ };
3076
+ }
3077
+ }
3078
+ var MAX_SCAN_DEPTH = 4, SKIP_DIRS;
3079
+ var init_projectDetector = __esm(() => {
3080
+ SKIP_DIRS = new Set([
3081
+ "node_modules",
3082
+ ".git",
3083
+ "dist",
3084
+ "build",
3085
+ ".next",
3086
+ ".nuxt",
3087
+ "coverage",
3088
+ ".raggrep"
3089
+ ]);
3090
+ });
3091
+
3092
+ // src/infrastructure/introspection/IntrospectionIndex.ts
3093
+ import * as path10 from "path";
3094
+ import * as fs5 from "fs/promises";
3095
+
3096
+ class IntrospectionIndex {
3097
+ rootDir;
3098
+ structure = null;
3099
+ files = new Map;
3100
+ config = {};
3101
+ constructor(rootDir) {
3102
+ this.rootDir = rootDir;
3103
+ }
3104
+ async initialize() {
3105
+ this.structure = await detectProjectStructure(this.rootDir);
3106
+ try {
3107
+ const configPath = path10.join(this.rootDir, ".raggrep", "config.json");
3108
+ const configContent = await fs5.readFile(configPath, "utf-8");
3109
+ const config = JSON.parse(configContent);
3110
+ this.config = config.introspection || {};
3111
+ } catch {}
3112
+ }
3113
+ getStructure() {
3114
+ return this.structure;
3115
+ }
3116
+ addFile(filepath, content) {
3117
+ if (!this.structure) {
3118
+ throw new Error("IntrospectionIndex not initialized");
3119
+ }
3120
+ const intro = introspectFile(filepath, this.structure, content);
3121
+ this.applyOverrides(intro);
3122
+ this.files.set(filepath, intro);
3123
+ return intro;
3124
+ }
3125
+ getFile(filepath) {
3126
+ return this.files.get(filepath);
3127
+ }
3128
+ getAllFiles() {
3129
+ return Array.from(this.files.values());
3130
+ }
3131
+ applyOverrides(intro) {
3132
+ if (!this.config.projects)
3133
+ return;
3134
+ for (const [projectPath, overrides] of Object.entries(this.config.projects)) {
3135
+ if (intro.filepath.startsWith(projectPath + "/") || intro.project.root === projectPath) {
3136
+ if (overrides.scope) {
3137
+ intro.scope = overrides.scope;
2933
3138
  }
2934
- const segmentMatch = ctx2.segments.some((seg) => queryTerms.some((t) => seg.toLowerCase().includes(t) || t.includes(seg.toLowerCase())));
2935
- if (segmentMatch) {
2936
- boost += 0.05;
3139
+ if (overrides.framework) {
3140
+ intro.framework = overrides.framework;
2937
3141
  }
2938
- pathBoosts.set(filepath, boost);
3142
+ break;
2939
3143
  }
2940
3144
  }
2941
- const results = [];
2942
- for (const { filepath, chunk, embedding } of allChunksData) {
2943
- const semanticScore = cosineSimilarity(queryEmbedding, embedding);
2944
- const bm25Score = bm25Scores.get(chunk.id) || 0;
2945
- const pathBoost = pathBoosts.get(filepath) || 0;
2946
- const hybridScore = SEMANTIC_WEIGHT * semanticScore + BM25_WEIGHT * bm25Score + pathBoost;
2947
- if (hybridScore >= minScore || bm25Score > 0.3) {
2948
- results.push({
2949
- filepath,
2950
- chunk,
2951
- score: hybridScore,
2952
- moduleId: this.id,
2953
- context: {
2954
- semanticScore,
2955
- bm25Score,
2956
- pathBoost
2957
- }
2958
- });
2959
- }
3145
+ }
3146
+ async save(config) {
3147
+ const introDir = path10.join(getRaggrepDir(this.rootDir, config), "introspection");
3148
+ await fs5.mkdir(introDir, { recursive: true });
3149
+ const projectPath = path10.join(introDir, "_project.json");
3150
+ await fs5.writeFile(projectPath, JSON.stringify({
3151
+ version: "1.0.0",
3152
+ lastUpdated: new Date().toISOString(),
3153
+ structure: this.structure
3154
+ }, null, 2));
3155
+ for (const [filepath, intro] of this.files) {
3156
+ const introFilePath = path10.join(introDir, "files", filepath.replace(/\.[^.]+$/, ".json"));
3157
+ await fs5.mkdir(path10.dirname(introFilePath), { recursive: true });
3158
+ await fs5.writeFile(introFilePath, JSON.stringify(intro, null, 2));
2960
3159
  }
2961
- results.sort((a, b) => b.score - a.score);
2962
- return results.slice(0, topK);
3160
+ console.log(` [Introspection] Saved metadata for ${this.files.size} files`);
2963
3161
  }
2964
- extractReferences(content, filepath) {
2965
- const references = [];
2966
- const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
2967
- const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2968
- let match;
2969
- while ((match = importRegex.exec(content)) !== null) {
2970
- const importPath = match[1];
2971
- if (importPath.startsWith(".")) {
2972
- const dir = path10.dirname(filepath);
2973
- const resolved = path10.normalize(path10.join(dir, importPath));
2974
- references.push(resolved);
2975
- }
3162
+ async load(config) {
3163
+ const introDir = path10.join(getRaggrepDir(this.rootDir, config), "introspection");
3164
+ try {
3165
+ const projectPath = path10.join(introDir, "_project.json");
3166
+ const projectContent = await fs5.readFile(projectPath, "utf-8");
3167
+ const projectData = JSON.parse(projectContent);
3168
+ this.structure = projectData.structure;
3169
+ await this.loadFilesRecursive(path10.join(introDir, "files"), "");
3170
+ } catch {
3171
+ this.structure = null;
3172
+ this.files.clear();
2976
3173
  }
2977
- while ((match = requireRegex.exec(content)) !== null) {
2978
- const importPath = match[1];
2979
- if (importPath.startsWith(".")) {
2980
- const dir = path10.dirname(filepath);
2981
- const resolved = path10.normalize(path10.join(dir, importPath));
2982
- references.push(resolved);
3174
+ }
3175
+ async loadFilesRecursive(basePath, prefix) {
3176
+ try {
3177
+ const entries = await fs5.readdir(basePath, { withFileTypes: true });
3178
+ for (const entry of entries) {
3179
+ const entryPath = path10.join(basePath, entry.name);
3180
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
3181
+ if (entry.isDirectory()) {
3182
+ await this.loadFilesRecursive(entryPath, relativePath);
3183
+ } else if (entry.name.endsWith(".json")) {
3184
+ const content = await fs5.readFile(entryPath, "utf-8");
3185
+ const intro = JSON.parse(content);
3186
+ this.files.set(intro.filepath, intro);
3187
+ }
2983
3188
  }
2984
- }
2985
- return references;
3189
+ } catch {}
3190
+ }
3191
+ clear() {
3192
+ this.files.clear();
3193
+ this.structure = null;
2986
3194
  }
2987
3195
  }
2988
- var DEFAULT_MIN_SCORE2 = 0.15, DEFAULT_TOP_K2 = 10, SEMANTIC_WEIGHT = 0.7, BM25_WEIGHT = 0.3, TIER1_CANDIDATE_MULTIPLIER = 3;
2989
- var init_typescript = __esm(() => {
2990
- init_embeddings();
3196
+ var init_IntrospectionIndex = __esm(() => {
3197
+ init_projectDetector();
3198
+ init_introspection();
2991
3199
  init_config2();
2992
- init_parseCode();
2993
- init_storage();
2994
- init_keywords();
2995
- init_keywords();
2996
3200
  });
2997
3201
 
2998
- // src/modules/registry.ts
2999
- class ModuleRegistryImpl {
3000
- modules = new Map;
3001
- register(module) {
3002
- if (this.modules.has(module.id)) {
3003
- console.warn(`Module '${module.id}' is already registered, overwriting...`);
3004
- }
3005
- this.modules.set(module.id, module);
3006
- }
3007
- get(id) {
3008
- return this.modules.get(id);
3009
- }
3010
- list() {
3011
- return Array.from(this.modules.values());
3012
- }
3013
- getEnabled(config) {
3014
- const enabledIds = new Set(config.modules.filter((m) => m.enabled).map((m) => m.id));
3015
- return this.list().filter((m) => enabledIds.has(m.id));
3016
- }
3017
- }
3018
- async function registerBuiltInModules() {
3019
- const { CoreModule: CoreModule2 } = await Promise.resolve().then(() => (init_core(), exports_core));
3020
- const { TypeScriptModule: TypeScriptModule2 } = await Promise.resolve().then(() => (init_typescript(), exports_typescript));
3021
- registry.register(new CoreModule2);
3022
- registry.register(new TypeScriptModule2);
3023
- }
3024
- var registry;
3025
- var init_registry = __esm(() => {
3026
- registry = new ModuleRegistryImpl;
3202
+ // src/infrastructure/introspection/index.ts
3203
+ var init_introspection2 = __esm(() => {
3204
+ init_IntrospectionIndex();
3205
+ init_projectDetector();
3027
3206
  });
3028
3207
 
3029
3208
  // src/app/indexer/watcher.ts
@@ -3204,6 +3383,7 @@ __export(exports_indexer, {
3204
3383
  watchDirectory: () => watchDirectory,
3205
3384
  indexDirectory: () => indexDirectory,
3206
3385
  getIndexStatus: () => getIndexStatus,
3386
+ ensureIndexFresh: () => ensureIndexFresh,
3207
3387
  cleanupIndex: () => cleanupIndex
3208
3388
  });
3209
3389
  import { glob } from "glob";
@@ -3211,10 +3391,13 @@ import * as fs6 from "fs/promises";
3211
3391
  import * as path12 from "path";
3212
3392
  async function indexDirectory(rootDir, options = {}) {
3213
3393
  const verbose = options.verbose ?? false;
3394
+ const quiet = options.quiet ?? false;
3214
3395
  rootDir = path12.resolve(rootDir);
3215
3396
  const location = getIndexLocation(rootDir);
3216
- console.log(`Indexing directory: ${rootDir}`);
3217
- console.log(`Index location: ${location.indexDir}`);
3397
+ if (!quiet) {
3398
+ console.log(`Indexing directory: ${rootDir}`);
3399
+ console.log(`Index location: ${location.indexDir}`);
3400
+ }
3218
3401
  const config = await loadConfig(rootDir);
3219
3402
  const introspection = new IntrospectionIndex(rootDir);
3220
3403
  await introspection.initialize();
@@ -3227,16 +3410,24 @@ async function indexDirectory(rootDir, options = {}) {
3227
3410
  await registerBuiltInModules();
3228
3411
  const enabledModules = registry.getEnabled(config);
3229
3412
  if (enabledModules.length === 0) {
3230
- console.log("No modules enabled. Check your configuration.");
3413
+ if (!quiet) {
3414
+ console.log("No modules enabled. Check your configuration.");
3415
+ }
3231
3416
  return [];
3232
3417
  }
3233
- console.log(`Enabled modules: ${enabledModules.map((m) => m.id).join(", ")}`);
3418
+ if (!quiet) {
3419
+ console.log(`Enabled modules: ${enabledModules.map((m) => m.id).join(", ")}`);
3420
+ }
3234
3421
  const files = await findFiles(rootDir, config);
3235
- console.log(`Found ${files.length} files to index`);
3422
+ if (!quiet) {
3423
+ console.log(`Found ${files.length} files to index`);
3424
+ }
3236
3425
  const results = [];
3237
3426
  for (const module of enabledModules) {
3238
- console.log(`
3427
+ if (!quiet) {
3428
+ console.log(`
3239
3429
  [${module.name}] Starting indexing...`);
3430
+ }
3240
3431
  const moduleConfig = getModuleConfig(config, module.id);
3241
3432
  if (module.initialize && moduleConfig) {
3242
3433
  const configWithOverrides = { ...moduleConfig };
@@ -3251,7 +3442,9 @@ async function indexDirectory(rootDir, options = {}) {
3251
3442
  const result = await indexWithModule(rootDir, files, module, config, verbose, introspection);
3252
3443
  results.push(result);
3253
3444
  if (module.finalize) {
3254
- console.log(`[${module.name}] Building secondary indexes...`);
3445
+ if (!quiet) {
3446
+ console.log(`[${module.name}] Building secondary indexes...`);
3447
+ }
3255
3448
  const ctx = {
3256
3449
  rootDir,
3257
3450
  config,
@@ -3267,12 +3460,167 @@ async function indexDirectory(rootDir, options = {}) {
3267
3460
  };
3268
3461
  await module.finalize(ctx);
3269
3462
  }
3270
- console.log(`[${module.name}] Complete: ${result.indexed} indexed, ${result.skipped} skipped, ${result.errors} errors`);
3463
+ if (!quiet) {
3464
+ console.log(`[${module.name}] Complete: ${result.indexed} indexed, ${result.skipped} skipped, ${result.errors} errors`);
3465
+ }
3271
3466
  }
3272
3467
  await introspection.save(config);
3273
3468
  await updateGlobalManifest(rootDir, enabledModules, config);
3274
3469
  return results;
3275
3470
  }
3471
+ async function isIndexVersionCompatible(rootDir) {
3472
+ const config = await loadConfig(rootDir);
3473
+ const globalManifestPath = getGlobalManifestPath(rootDir, config);
3474
+ try {
3475
+ const content = await fs6.readFile(globalManifestPath, "utf-8");
3476
+ const manifest = JSON.parse(content);
3477
+ return manifest.version === INDEX_SCHEMA_VERSION;
3478
+ } catch {
3479
+ return false;
3480
+ }
3481
+ }
3482
+ async function deleteIndex(rootDir) {
3483
+ const indexDir = getRaggrepDir(rootDir);
3484
+ try {
3485
+ await fs6.rm(indexDir, { recursive: true, force: true });
3486
+ } catch {}
3487
+ }
3488
+ async function ensureIndexFresh(rootDir, options = {}) {
3489
+ const verbose = options.verbose ?? false;
3490
+ const quiet = options.quiet ?? false;
3491
+ rootDir = path12.resolve(rootDir);
3492
+ const status = await getIndexStatus(rootDir);
3493
+ if (!status.exists) {
3494
+ if (!quiet) {
3495
+ console.log(`No index found. Creating index...
3496
+ `);
3497
+ }
3498
+ const results = await indexDirectory(rootDir, { ...options, quiet });
3499
+ const totalIndexed2 = results.reduce((sum, r) => sum + r.indexed, 0);
3500
+ return { indexed: totalIndexed2, removed: 0, unchanged: 0 };
3501
+ }
3502
+ const versionCompatible = await isIndexVersionCompatible(rootDir);
3503
+ if (!versionCompatible) {
3504
+ if (!quiet) {
3505
+ console.log(`Index version incompatible. Rebuilding...
3506
+ `);
3507
+ }
3508
+ await deleteIndex(rootDir);
3509
+ const results = await indexDirectory(rootDir, { ...options, quiet });
3510
+ const totalIndexed2 = results.reduce((sum, r) => sum + r.indexed, 0);
3511
+ return { indexed: totalIndexed2, removed: 0, unchanged: 0 };
3512
+ }
3513
+ const config = await loadConfig(rootDir);
3514
+ await registerBuiltInModules();
3515
+ const enabledModules = registry.getEnabled(config);
3516
+ if (enabledModules.length === 0) {
3517
+ return { indexed: 0, removed: 0, unchanged: 0 };
3518
+ }
3519
+ const introspection = new IntrospectionIndex(rootDir);
3520
+ await introspection.initialize();
3521
+ const currentFiles = await findFiles(rootDir, config);
3522
+ const currentFileSet = new Set(currentFiles.map((f) => path12.relative(rootDir, f)));
3523
+ let totalIndexed = 0;
3524
+ let totalRemoved = 0;
3525
+ let totalUnchanged = 0;
3526
+ for (const module of enabledModules) {
3527
+ const moduleConfig = getModuleConfig(config, module.id);
3528
+ if (module.initialize && moduleConfig) {
3529
+ const configWithOverrides = { ...moduleConfig };
3530
+ if (options.model && module.id === "language/typescript") {
3531
+ configWithOverrides.options = {
3532
+ ...configWithOverrides.options,
3533
+ embeddingModel: options.model
3534
+ };
3535
+ }
3536
+ await module.initialize(configWithOverrides);
3537
+ }
3538
+ const manifest = await loadModuleManifest(rootDir, module.id, config);
3539
+ const indexPath = getModuleIndexPath(rootDir, module.id, config);
3540
+ const filesToRemove = [];
3541
+ for (const filepath of Object.keys(manifest.files)) {
3542
+ if (!currentFileSet.has(filepath)) {
3543
+ filesToRemove.push(filepath);
3544
+ }
3545
+ }
3546
+ for (const filepath of filesToRemove) {
3547
+ if (verbose) {
3548
+ console.log(` Removing stale: ${filepath}`);
3549
+ }
3550
+ const indexFilePath = path12.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
3551
+ try {
3552
+ await fs6.unlink(indexFilePath);
3553
+ } catch {}
3554
+ delete manifest.files[filepath];
3555
+ totalRemoved++;
3556
+ }
3557
+ const ctx = {
3558
+ rootDir,
3559
+ config,
3560
+ readFile: async (filepath) => {
3561
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
3562
+ return fs6.readFile(fullPath, "utf-8");
3563
+ },
3564
+ getFileStats: async (filepath) => {
3565
+ const fullPath = path12.isAbsolute(filepath) ? filepath : path12.join(rootDir, filepath);
3566
+ const stats = await fs6.stat(fullPath);
3567
+ return { lastModified: stats.mtime.toISOString() };
3568
+ },
3569
+ getIntrospection: (filepath) => introspection.getFile(filepath)
3570
+ };
3571
+ for (const filepath of currentFiles) {
3572
+ const relativePath = path12.relative(rootDir, filepath);
3573
+ try {
3574
+ const stats = await fs6.stat(filepath);
3575
+ const lastModified = stats.mtime.toISOString();
3576
+ const existingEntry = manifest.files[relativePath];
3577
+ if (existingEntry && existingEntry.lastModified === lastModified) {
3578
+ totalUnchanged++;
3579
+ continue;
3580
+ }
3581
+ if (verbose) {
3582
+ console.log(` Indexing: ${relativePath}`);
3583
+ }
3584
+ const content = await fs6.readFile(filepath, "utf-8");
3585
+ introspection.addFile(relativePath, content);
3586
+ const fileIndex = await module.indexFile(relativePath, content, ctx);
3587
+ if (fileIndex) {
3588
+ await writeFileIndex(rootDir, module.id, relativePath, fileIndex, config);
3589
+ manifest.files[relativePath] = {
3590
+ lastModified,
3591
+ chunkCount: fileIndex.chunks.length
3592
+ };
3593
+ totalIndexed++;
3594
+ }
3595
+ } catch (error) {
3596
+ if (verbose) {
3597
+ console.error(` Error indexing ${relativePath}:`, error);
3598
+ }
3599
+ }
3600
+ }
3601
+ if (totalIndexed > 0 || totalRemoved > 0) {
3602
+ manifest.lastUpdated = new Date().toISOString();
3603
+ await writeModuleManifest(rootDir, module.id, manifest, config);
3604
+ if (module.finalize) {
3605
+ await module.finalize(ctx);
3606
+ }
3607
+ }
3608
+ if (totalRemoved > 0) {
3609
+ await cleanupEmptyDirectories(indexPath);
3610
+ }
3611
+ }
3612
+ if (totalIndexed > 0) {
3613
+ await introspection.save(config);
3614
+ }
3615
+ if (totalIndexed > 0 || totalRemoved > 0) {
3616
+ await updateGlobalManifest(rootDir, enabledModules, config);
3617
+ }
3618
+ return {
3619
+ indexed: totalIndexed,
3620
+ removed: totalRemoved,
3621
+ unchanged: totalUnchanged
3622
+ };
3623
+ }
3276
3624
  async function indexWithModule(rootDir, files, module, config, verbose, introspection) {
3277
3625
  const result = {
3278
3626
  moduleId: module.id,
@@ -3378,7 +3726,7 @@ async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
3378
3726
  async function updateGlobalManifest(rootDir, modules, config) {
3379
3727
  const manifestPath = getGlobalManifestPath(rootDir, config);
3380
3728
  const manifest = {
3381
- version: config.version,
3729
+ version: INDEX_SCHEMA_VERSION,
3382
3730
  lastUpdated: new Date().toISOString(),
3383
3731
  modules: modules.map((m) => m.id)
3384
3732
  };
@@ -3518,10 +3866,11 @@ async function getIndexStatus(rootDir) {
3518
3866
  }
3519
3867
  return status;
3520
3868
  }
3869
+ var INDEX_SCHEMA_VERSION = "1.0.0";
3521
3870
  var init_indexer = __esm(() => {
3522
3871
  init_config2();
3523
3872
  init_registry();
3524
- init_introspection();
3873
+ init_introspection2();
3525
3874
  init_watcher();
3526
3875
  });
3527
3876
 
@@ -3615,6 +3964,20 @@ async function loadGlobalManifest(rootDir, config) {
3615
3964
  return null;
3616
3965
  }
3617
3966
  }
3967
+ function formatModuleName(moduleId) {
3968
+ switch (moduleId) {
3969
+ case "core":
3970
+ return "Core";
3971
+ case "language/typescript":
3972
+ return "TypeScript";
3973
+ default:
3974
+ if (moduleId.startsWith("language/")) {
3975
+ const lang = moduleId.replace("language/", "");
3976
+ return lang.charAt(0).toUpperCase() + lang.slice(1);
3977
+ }
3978
+ return moduleId;
3979
+ }
3980
+ }
3618
3981
  function formatSearchResults(results) {
3619
3982
  if (results.length === 0) {
3620
3983
  return "No results found.";
@@ -3630,6 +3993,7 @@ function formatSearchResults(results) {
3630
3993
  output += `${i + 1}. ${location}${nameInfo}
3631
3994
  `;
3632
3995
  output += ` Score: ${(result.score * 100).toFixed(1)}% | Type: ${chunk.type}`;
3996
+ output += ` | via ${formatModuleName(result.moduleId)}`;
3633
3997
  if (chunk.isExported) {
3634
3998
  output += " | exported";
3635
3999
  }
@@ -3657,7 +4021,7 @@ init_embeddings();
3657
4021
  // package.json
3658
4022
  var package_default = {
3659
4023
  name: "raggrep",
3660
- version: "0.1.6",
4024
+ version: "0.2.0",
3661
4025
  description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
3662
4026
  type: "module",
3663
4027
  main: "./dist/index.js",
@@ -3899,8 +4263,11 @@ Options:
3899
4263
  -h, --help Show this help message
3900
4264
 
3901
4265
  Note:
3902
- If the current directory has not been indexed, raggrep will
3903
- automatically index it before searching.
4266
+ The index is managed automatically like a cache:
4267
+ - First query creates the index
4268
+ - Changed files are re-indexed automatically
4269
+ - Deleted files are cleaned up automatically
4270
+ - Unchanged files use the cached index (instant)
3904
4271
 
3905
4272
  Examples:
3906
4273
  raggrep query "user authentication"
@@ -3911,7 +4278,7 @@ Examples:
3911
4278
  process.exit(0);
3912
4279
  }
3913
4280
  const { search: search2, formatSearchResults: formatSearchResults2 } = await Promise.resolve().then(() => (init_search(), exports_search));
3914
- const { getIndexStatus: getIndexStatus2, indexDirectory: indexDirectory2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
4281
+ const { ensureIndexFresh: ensureIndexFresh2 } = await Promise.resolve().then(() => (init_indexer(), exports_indexer));
3915
4282
  const query = flags.remaining[0];
3916
4283
  if (!query) {
3917
4284
  console.error("Usage: raggrep query <search query>");
@@ -3919,24 +4286,20 @@ Examples:
3919
4286
  process.exit(1);
3920
4287
  }
3921
4288
  try {
3922
- const status = await getIndexStatus2(process.cwd());
3923
- if (!status.exists) {
3924
- console.log(`No index found. Indexing directory first...
3925
- `);
3926
- console.log("RAGgrep Indexer");
3927
- console.log(`================
3928
- `);
3929
- const indexResults = await indexDirectory2(process.cwd(), {
3930
- model: flags.model,
3931
- verbose: false
3932
- });
3933
- console.log(`
3934
- ================`);
3935
- console.log("Summary:");
3936
- for (const result of indexResults) {
3937
- console.log(` ${result.moduleId}: ${result.indexed} indexed, ${result.skipped} skipped, ${result.errors} errors`);
4289
+ const freshStats = await ensureIndexFresh2(process.cwd(), {
4290
+ model: flags.model,
4291
+ quiet: true
4292
+ });
4293
+ if (freshStats.indexed > 0 || freshStats.removed > 0) {
4294
+ const parts = [];
4295
+ if (freshStats.indexed > 0) {
4296
+ parts.push(`${freshStats.indexed} indexed`);
3938
4297
  }
3939
- console.log("");
4298
+ if (freshStats.removed > 0) {
4299
+ parts.push(`${freshStats.removed} removed`);
4300
+ }
4301
+ console.log(`Index updated: ${parts.join(", ")}
4302
+ `);
3940
4303
  }
3941
4304
  console.log("RAGgrep Search");
3942
4305
  console.log(`==============
@@ -4092,4 +4455,4 @@ Run 'raggrep <command> --help' for more information.
4092
4455
  }
4093
4456
  main();
4094
4457
 
4095
- //# debugId=03A02811A6080C5964756E2164756E21
4458
+ //# debugId=4B6F0EA9EEB7164864756E2164756E21