raggrep 0.10.2 → 0.10.4

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.
@@ -23,6 +23,29 @@ export interface IndexOptions {
23
23
  logger?: Logger;
24
24
  /** Number of files to process in parallel (default: auto based on CPU cores) */
25
25
  concurrency?: number;
26
+ /** Show timing information for each stage */
27
+ timing?: boolean;
28
+ }
29
+ /** Timing information for performance profiling */
30
+ export interface TimingInfo {
31
+ /** Total time in milliseconds */
32
+ totalMs: number;
33
+ /** Time spent on file discovery (glob) */
34
+ fileDiscoveryMs: number;
35
+ /** Time spent on stat checks */
36
+ statCheckMs: number;
37
+ /** Time spent on indexing changed files */
38
+ indexingMs: number;
39
+ /** Time spent on cleanup operations */
40
+ cleanupMs: number;
41
+ /** Number of files discovered */
42
+ filesDiscovered: number;
43
+ /** Number of files that needed stat check */
44
+ filesStatChecked: number;
45
+ /** Number of files that needed indexing */
46
+ filesIndexed: number;
47
+ /** Whether result was from cache */
48
+ fromCache: boolean;
26
49
  }
27
50
  export interface EnsureFreshResult {
28
51
  /** Number of files indexed (new or modified) */
@@ -31,6 +54,8 @@ export interface EnsureFreshResult {
31
54
  removed: number;
32
55
  /** Number of files unchanged (used cache) */
33
56
  unchanged: number;
57
+ /** Timing information (only present if timing option was enabled) */
58
+ timing?: TimingInfo;
34
59
  }
35
60
  export interface CleanupResult {
36
61
  moduleId: string;
package/dist/cli/main.js CHANGED
@@ -11545,6 +11545,15 @@ async function resetIndex(rootDir) {
11545
11545
  async function ensureIndexFresh(rootDir, options = {}) {
11546
11546
  const verbose = options.verbose ?? false;
11547
11547
  const quiet = options.quiet ?? false;
11548
+ const showTiming = options.timing ?? false;
11549
+ const startTime = Date.now();
11550
+ let fileDiscoveryMs = 0;
11551
+ let statCheckMs = 0;
11552
+ let indexingMs = 0;
11553
+ let cleanupMs = 0;
11554
+ let filesDiscovered = 0;
11555
+ let filesStatChecked = 0;
11556
+ let filesIndexed = 0;
11548
11557
  const logger = options.logger ? options.logger : quiet ? createSilentLogger() : createLogger({ verbose });
11549
11558
  rootDir = path22.resolve(rootDir);
11550
11559
  const status = await getIndexStatus(rootDir);
@@ -11576,16 +11585,35 @@ async function ensureIndexFresh(rootDir, options = {}) {
11576
11585
  const now = Date.now();
11577
11586
  if (freshnessCache && freshnessCache.rootDir === rootDir && now - freshnessCache.timestamp < FRESHNESS_CACHE_TTL_MS && freshnessCache.manifestMtime === currentManifestMtime) {
11578
11587
  logger.debug("Using cached freshness check result");
11579
- return freshnessCache.result;
11588
+ const cachedResult = { ...freshnessCache.result };
11589
+ if (showTiming) {
11590
+ cachedResult.timing = {
11591
+ totalMs: Date.now() - startTime,
11592
+ fileDiscoveryMs: 0,
11593
+ statCheckMs: 0,
11594
+ indexingMs: 0,
11595
+ cleanupMs: 0,
11596
+ filesDiscovered: 0,
11597
+ filesStatChecked: 0,
11598
+ filesIndexed: 0,
11599
+ fromCache: true
11600
+ };
11601
+ }
11602
+ return cachedResult;
11580
11603
  }
11581
11604
  await registerBuiltInModules();
11582
11605
  const enabledModules = registry.getEnabled(config);
11583
11606
  if (enabledModules.length === 0) {
11584
11607
  return { indexed: 0, removed: 0, unchanged: 0 };
11585
11608
  }
11609
+ const fileDiscoveryStart = Date.now();
11586
11610
  const introspection = new IntrospectionIndex(rootDir);
11587
- await introspection.initialize();
11588
- const currentFiles = await findFiles(rootDir, config);
11611
+ const [, currentFiles] = await Promise.all([
11612
+ introspection.initialize(),
11613
+ findFiles(rootDir, config)
11614
+ ]);
11615
+ fileDiscoveryMs = Date.now() - fileDiscoveryStart;
11616
+ filesDiscovered = currentFiles.length;
11589
11617
  const currentFileSet = new Set(currentFiles.map((f) => path22.relative(rootDir, f)));
11590
11618
  let totalIndexed = 0;
11591
11619
  let totalRemoved = 0;
@@ -11614,20 +11642,21 @@ async function ensureIndexFresh(rootDir, options = {}) {
11614
11642
  filesToRemove.push(filepath);
11615
11643
  }
11616
11644
  }
11645
+ const cleanupStart = Date.now();
11617
11646
  const removedFilepaths = [];
11618
- for (const filepath of filesToRemove) {
11619
- logger.debug(` Removing stale: ${filepath}`);
11620
- const indexFilePath = path22.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
11621
- try {
11622
- await fs8.unlink(indexFilePath);
11623
- } catch {}
11624
- const symbolicFilePath = path22.join(indexPath, "symbolic", filepath.replace(/\.[^.]+$/, ".json"));
11625
- try {
11626
- await fs8.unlink(symbolicFilePath);
11627
- } catch {}
11628
- delete manifest.files[filepath];
11629
- removedFilepaths.push(filepath);
11630
- totalRemoved++;
11647
+ if (filesToRemove.length > 0) {
11648
+ await Promise.all(filesToRemove.map(async (filepath) => {
11649
+ logger.debug(` Removing stale: ${filepath}`);
11650
+ const indexFilePath = path22.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
11651
+ const symbolicFilePath = path22.join(indexPath, "symbolic", filepath.replace(/\.[^.]+$/, ".json"));
11652
+ await Promise.all([
11653
+ fs8.unlink(indexFilePath).catch(() => {}),
11654
+ fs8.unlink(symbolicFilePath).catch(() => {})
11655
+ ]);
11656
+ delete manifest.files[filepath];
11657
+ removedFilepaths.push(filepath);
11658
+ }));
11659
+ totalRemoved += removedFilepaths.length;
11631
11660
  }
11632
11661
  if (removedFilepaths.length > 0) {
11633
11662
  try {
@@ -11641,6 +11670,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11641
11670
  await literalIndex.save();
11642
11671
  } catch {}
11643
11672
  }
11673
+ cleanupMs += Date.now() - cleanupStart;
11644
11674
  const ctx = {
11645
11675
  rootDir,
11646
11676
  config,
@@ -11655,21 +11685,51 @@ async function ensureIndexFresh(rootDir, options = {}) {
11655
11685
  },
11656
11686
  getIntrospection: (filepath) => introspection.getFile(filepath)
11657
11687
  };
11658
- const totalFiles = currentFiles.length;
11659
- let completedCount = 0;
11660
- const processIncrementalFile = async (filepath) => {
11688
+ const statCheck = async (filepath) => {
11661
11689
  const relativePath = path22.relative(rootDir, filepath);
11662
11690
  try {
11663
11691
  const stats = await fs8.stat(filepath);
11664
11692
  const lastModified = stats.mtime.toISOString();
11665
11693
  const existingEntry = manifest.files[relativePath];
11666
- if (existingEntry && existingEntry.lastModified === lastModified) {
11667
- completedCount++;
11668
- return { relativePath, status: "unchanged" };
11694
+ if (!existingEntry) {
11695
+ return { filepath, relativePath, lastModified, needsCheck: true, isNew: true };
11669
11696
  }
11697
+ if (existingEntry.lastModified === lastModified) {
11698
+ return { filepath, relativePath, lastModified, needsCheck: false, isNew: false };
11699
+ }
11700
+ return { filepath, relativePath, lastModified, needsCheck: true, isNew: false };
11701
+ } catch {
11702
+ return null;
11703
+ }
11704
+ };
11705
+ const statCheckStart = Date.now();
11706
+ const statResults = await parallelMap(currentFiles, statCheck, STAT_CONCURRENCY);
11707
+ statCheckMs += Date.now() - statCheckStart;
11708
+ filesStatChecked += currentFiles.length;
11709
+ const filesToProcess = [];
11710
+ let unchangedCount = 0;
11711
+ for (const result2 of statResults) {
11712
+ if (!result2.success || !result2.value)
11713
+ continue;
11714
+ if (result2.value.needsCheck) {
11715
+ filesToProcess.push(result2.value);
11716
+ } else {
11717
+ unchangedCount++;
11718
+ }
11719
+ }
11720
+ if (filesToProcess.length === 0) {
11721
+ totalUnchanged += unchangedCount;
11722
+ continue;
11723
+ }
11724
+ let completedCount = 0;
11725
+ const totalToProcess = filesToProcess.length;
11726
+ const processChangedFile = async (statResult) => {
11727
+ const { filepath, relativePath, lastModified, isNew } = statResult;
11728
+ try {
11670
11729
  const content = await fs8.readFile(filepath, "utf-8");
11671
11730
  const contentHash = computeContentHash(content);
11672
- if (existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
11731
+ const existingEntry = manifest.files[relativePath];
11732
+ if (!isNew && existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
11673
11733
  completedCount++;
11674
11734
  return {
11675
11735
  relativePath,
@@ -11679,7 +11739,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11679
11739
  };
11680
11740
  }
11681
11741
  completedCount++;
11682
- logger.progress(` [${completedCount}/${totalFiles}] Indexing: ${relativePath}`);
11742
+ logger.progress(` [${completedCount}/${totalToProcess}] Indexing: ${relativePath}`);
11683
11743
  introspection.addFile(relativePath, content);
11684
11744
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
11685
11745
  if (!fileIndex) {
@@ -11698,8 +11758,12 @@ async function ensureIndexFresh(rootDir, options = {}) {
11698
11758
  return { relativePath, status: "error", error };
11699
11759
  }
11700
11760
  };
11761
+ const indexingStart = Date.now();
11701
11762
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
11702
- const results = await parallelMap(currentFiles, processIncrementalFile, concurrency);
11763
+ const results = await parallelMap(filesToProcess, processChangedFile, concurrency);
11764
+ indexingMs += Date.now() - indexingStart;
11765
+ filesIndexed += filesToProcess.length;
11766
+ totalUnchanged += unchangedCount;
11703
11767
  logger.clearProgress();
11704
11768
  let mtimeUpdates = 0;
11705
11769
  for (const item of results) {
@@ -11760,6 +11824,19 @@ async function ensureIndexFresh(rootDir, options = {}) {
11760
11824
  removed: totalRemoved,
11761
11825
  unchanged: totalUnchanged
11762
11826
  };
11827
+ if (showTiming) {
11828
+ result.timing = {
11829
+ totalMs: Date.now() - startTime,
11830
+ fileDiscoveryMs,
11831
+ statCheckMs,
11832
+ indexingMs,
11833
+ cleanupMs,
11834
+ filesDiscovered,
11835
+ filesStatChecked,
11836
+ filesIndexed,
11837
+ fromCache: false
11838
+ };
11839
+ }
11763
11840
  let finalManifestMtime = currentManifestMtime;
11764
11841
  try {
11765
11842
  const manifestStats = await fs8.stat(globalManifestPath);
@@ -11767,7 +11844,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11767
11844
  } catch {}
11768
11845
  freshnessCache = {
11769
11846
  rootDir,
11770
- result,
11847
+ result: { indexed: totalIndexed, removed: totalRemoved, unchanged: totalUnchanged },
11771
11848
  timestamp: Date.now(),
11772
11849
  manifestMtime: finalManifestMtime
11773
11850
  };
@@ -11909,16 +11986,13 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
11909
11986
  async function findFiles(rootDir, config) {
11910
11987
  const patterns = config.extensions.map((ext) => `**/*${ext}`);
11911
11988
  const ignorePatterns = config.ignorePaths.map((p) => `**/${p}/**`);
11912
- const files = [];
11913
- for (const pattern of patterns) {
11914
- const matches = await glob(pattern, {
11915
- cwd: rootDir,
11916
- absolute: true,
11917
- ignore: ignorePatterns
11918
- });
11919
- files.push(...matches);
11920
- }
11921
- return [...new Set(files)];
11989
+ const results = await Promise.all(patterns.map((pattern) => glob(pattern, {
11990
+ cwd: rootDir,
11991
+ absolute: true,
11992
+ ignore: ignorePatterns
11993
+ })));
11994
+ const allFiles = results.flat();
11995
+ return [...new Set(allFiles)];
11922
11996
  }
11923
11997
  async function loadModuleManifest(rootDir, moduleId, config) {
11924
11998
  const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
@@ -12087,7 +12161,7 @@ async function getIndexStatus(rootDir) {
12087
12161
  }
12088
12162
  return status;
12089
12163
  }
12090
- var FRESHNESS_CACHE_TTL_MS = 5000, freshnessCache = null, INDEX_SCHEMA_VERSION = "2.0.0", DEFAULT_CONCURRENCY;
12164
+ var FRESHNESS_CACHE_TTL_MS = 5000, freshnessCache = null, INDEX_SCHEMA_VERSION = "2.0.0", DEFAULT_CONCURRENCY, STAT_CONCURRENCY;
12091
12165
  var init_indexer = __esm(() => {
12092
12166
  init_config2();
12093
12167
  init_registry();
@@ -12095,6 +12169,7 @@ var init_indexer = __esm(() => {
12095
12169
  init_logger();
12096
12170
  init_watcher();
12097
12171
  DEFAULT_CONCURRENCY = getOptimalConcurrency();
12172
+ STAT_CONCURRENCY = Math.max(32, getOptimalConcurrency() * 4);
12098
12173
  });
12099
12174
 
12100
12175
  // node_modules/balanced-match/index.js
@@ -13668,7 +13743,7 @@ init_logger();
13668
13743
  // package.json
13669
13744
  var package_default = {
13670
13745
  name: "raggrep",
13671
- version: "0.10.2",
13746
+ version: "0.10.4",
13672
13747
  description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
13673
13748
  type: "module",
13674
13749
  main: "./dist/index.js",
@@ -13765,6 +13840,7 @@ function parseFlags(args3) {
13765
13840
  help: false,
13766
13841
  verbose: false,
13767
13842
  watch: false,
13843
+ timing: false,
13768
13844
  remaining: []
13769
13845
  };
13770
13846
  for (let i2 = 0;i2 < args3.length; i2++) {
@@ -13775,6 +13851,8 @@ function parseFlags(args3) {
13775
13851
  flags2.verbose = true;
13776
13852
  } else if (arg === "--watch" || arg === "-w") {
13777
13853
  flags2.watch = true;
13854
+ } else if (arg === "--timing" || arg === "-T") {
13855
+ flags2.timing = true;
13778
13856
  } else if (arg === "--model" || arg === "-m") {
13779
13857
  const modelName = args3[++i2];
13780
13858
  if (modelName && modelName in EMBEDDING_MODELS) {
@@ -13933,6 +14011,7 @@ Options:
13933
14011
  -s, --min-score <n> Minimum similarity score 0-1 (default: 0.15)
13934
14012
  -t, --type <ext> Filter by file extension (e.g., ts, tsx, js)
13935
14013
  -f, --filter <path> Filter by path or glob pattern (can be used multiple times)
14014
+ -T, --timing Show timing breakdown for performance profiling
13936
14015
  -h, --help Show this help message
13937
14016
 
13938
14017
  Note:
@@ -13986,7 +14065,8 @@ Examples:
13986
14065
  const freshStats = await ensureIndexFresh2(process.cwd(), {
13987
14066
  model: flags2.model,
13988
14067
  quiet: true,
13989
- logger: silentLogger
14068
+ logger: silentLogger,
14069
+ timing: flags2.timing
13990
14070
  });
13991
14071
  console.log("RAGgrep Search");
13992
14072
  console.log(`==============
@@ -14003,6 +14083,23 @@ Examples:
14003
14083
  `);
14004
14084
  } else {
14005
14085
  console.log(`Using cached index (no changes detected).
14086
+ `);
14087
+ }
14088
+ if (flags2.timing && freshStats.timing) {
14089
+ const t = freshStats.timing;
14090
+ console.log("┌─ Timing ─────────────────────────────────────┐");
14091
+ if (t.fromCache) {
14092
+ console.log(`│ Cache hit (TTL-based) │`);
14093
+ console.log(`│ Total: ${t.totalMs.toFixed(0).padStart(6)}ms │`);
14094
+ } else {
14095
+ console.log(`│ File discovery: ${t.fileDiscoveryMs.toFixed(0).padStart(6)}ms (${t.filesDiscovered} files)`.padEnd(47) + "│");
14096
+ console.log(`│ Stat checks: ${t.statCheckMs.toFixed(0).padStart(6)}ms (${t.filesStatChecked} files)`.padEnd(47) + "│");
14097
+ console.log(`│ Indexing: ${t.indexingMs.toFixed(0).padStart(6)}ms (${t.filesIndexed} files)`.padEnd(47) + "│");
14098
+ console.log(`│ Cleanup: ${t.cleanupMs.toFixed(0).padStart(6)}ms`.padEnd(47) + "│");
14099
+ console.log(`│ ─────────────────────────────────────────── │`);
14100
+ console.log(`│ Total: ${t.totalMs.toFixed(0).padStart(6)}ms`.padEnd(47) + "│");
14101
+ }
14102
+ console.log(`└──────────────────────────────────────────────┘
14006
14103
  `);
14007
14104
  }
14008
14105
  const filePatterns = flags2.fileType ? [`*.${flags2.fileType}`] : undefined;
@@ -14262,4 +14359,4 @@ Run 'raggrep <command> --help' for more information.
14262
14359
  }
14263
14360
  main();
14264
14361
 
14265
- //# debugId=04D75A20D6C87C2264756E2164756E21
14362
+ //# debugId=5A4116C9AF3188E264756E2164756E21