raggrep 0.10.1 → 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.
@@ -1,4 +1,9 @@
1
1
  import type { EmbeddingModelName, Logger } from "../../domain/ports";
2
+ /**
3
+ * Clear the freshness cache.
4
+ * Call this after explicit indexing operations.
5
+ */
6
+ export declare function clearFreshnessCache(): void;
2
7
  export interface IndexResult {
3
8
  moduleId: string;
4
9
  indexed: number;
@@ -18,6 +23,29 @@ export interface IndexOptions {
18
23
  logger?: Logger;
19
24
  /** Number of files to process in parallel (default: auto based on CPU cores) */
20
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;
21
49
  }
22
50
  export interface EnsureFreshResult {
23
51
  /** Number of files indexed (new or modified) */
@@ -26,6 +54,8 @@ export interface EnsureFreshResult {
26
54
  removed: number;
27
55
  /** Number of files unchanged (used cache) */
28
56
  unchanged: number;
57
+ /** Timing information (only present if timing option was enabled) */
58
+ timing?: TimingInfo;
29
59
  }
30
60
  export interface CleanupResult {
31
61
  moduleId: string;
package/dist/cli/main.js CHANGED
@@ -11235,6 +11235,7 @@ async function watchDirectory(rootDir, options = {}) {
11235
11235
  isIndexing = true;
11236
11236
  const changes = new Map(pendingChanges);
11237
11237
  pendingChanges.clear();
11238
+ clearFreshnessCache();
11238
11239
  try {
11239
11240
  const filesToIndex = [];
11240
11241
  const filesToDelete = [];
@@ -11377,6 +11378,7 @@ __export(exports_indexer, {
11377
11378
  indexDirectory: () => indexDirectory,
11378
11379
  getIndexStatus: () => getIndexStatus,
11379
11380
  ensureIndexFresh: () => ensureIndexFresh,
11381
+ clearFreshnessCache: () => clearFreshnessCache,
11380
11382
  cleanupIndex: () => cleanupIndex
11381
11383
  });
11382
11384
  import { glob } from "glob";
@@ -11384,6 +11386,9 @@ import * as fs8 from "fs/promises";
11384
11386
  import * as path22 from "path";
11385
11387
  import * as os3 from "os";
11386
11388
  import * as crypto2 from "crypto";
11389
+ function clearFreshnessCache() {
11390
+ freshnessCache = null;
11391
+ }
11387
11392
  function computeContentHash(content) {
11388
11393
  return crypto2.createHash("sha256").update(content, "utf-8").digest("hex");
11389
11394
  }
@@ -11427,6 +11432,7 @@ async function indexDirectory(rootDir, options = {}) {
11427
11432
  const verbose = options.verbose ?? false;
11428
11433
  const quiet = options.quiet ?? false;
11429
11434
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
11435
+ clearFreshnessCache();
11430
11436
  const logger = options.logger ? options.logger : quiet ? createSilentLogger() : createLogger({ verbose });
11431
11437
  rootDir = path22.resolve(rootDir);
11432
11438
  const location = getIndexLocation(rootDir);
@@ -11525,6 +11531,7 @@ async function deleteIndex(rootDir) {
11525
11531
  }
11526
11532
  async function resetIndex(rootDir) {
11527
11533
  rootDir = path22.resolve(rootDir);
11534
+ clearFreshnessCache();
11528
11535
  const status = await getIndexStatus(rootDir);
11529
11536
  if (!status.exists) {
11530
11537
  throw new Error(`No index found for ${rootDir}`);
@@ -11538,10 +11545,20 @@ async function resetIndex(rootDir) {
11538
11545
  async function ensureIndexFresh(rootDir, options = {}) {
11539
11546
  const verbose = options.verbose ?? false;
11540
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;
11541
11557
  const logger = options.logger ? options.logger : quiet ? createSilentLogger() : createLogger({ verbose });
11542
11558
  rootDir = path22.resolve(rootDir);
11543
11559
  const status = await getIndexStatus(rootDir);
11544
11560
  if (!status.exists) {
11561
+ clearFreshnessCache();
11545
11562
  logger.info(`No index found. Creating index...
11546
11563
  `);
11547
11564
  const results = await indexDirectory(rootDir, { ...options, logger });
@@ -11550,6 +11567,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11550
11567
  }
11551
11568
  const versionCompatible = await isIndexVersionCompatible(rootDir);
11552
11569
  if (!versionCompatible) {
11570
+ clearFreshnessCache();
11553
11571
  logger.info(`Index version incompatible. Rebuilding...
11554
11572
  `);
11555
11573
  await deleteIndex(rootDir);
@@ -11558,14 +11576,44 @@ async function ensureIndexFresh(rootDir, options = {}) {
11558
11576
  return { indexed: totalIndexed2, removed: 0, unchanged: 0 };
11559
11577
  }
11560
11578
  const config = await loadConfig(rootDir);
11579
+ const globalManifestPath = getGlobalManifestPath(rootDir, config);
11580
+ let currentManifestMtime = 0;
11581
+ try {
11582
+ const manifestStats = await fs8.stat(globalManifestPath);
11583
+ currentManifestMtime = manifestStats.mtimeMs;
11584
+ } catch {}
11585
+ const now = Date.now();
11586
+ if (freshnessCache && freshnessCache.rootDir === rootDir && now - freshnessCache.timestamp < FRESHNESS_CACHE_TTL_MS && freshnessCache.manifestMtime === currentManifestMtime) {
11587
+ logger.debug("Using cached freshness check 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;
11603
+ }
11561
11604
  await registerBuiltInModules();
11562
11605
  const enabledModules = registry.getEnabled(config);
11563
11606
  if (enabledModules.length === 0) {
11564
11607
  return { indexed: 0, removed: 0, unchanged: 0 };
11565
11608
  }
11609
+ const fileDiscoveryStart = Date.now();
11566
11610
  const introspection = new IntrospectionIndex(rootDir);
11567
- await introspection.initialize();
11568
- 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;
11569
11617
  const currentFileSet = new Set(currentFiles.map((f) => path22.relative(rootDir, f)));
11570
11618
  let totalIndexed = 0;
11571
11619
  let totalRemoved = 0;
@@ -11594,20 +11642,21 @@ async function ensureIndexFresh(rootDir, options = {}) {
11594
11642
  filesToRemove.push(filepath);
11595
11643
  }
11596
11644
  }
11645
+ const cleanupStart = Date.now();
11597
11646
  const removedFilepaths = [];
11598
- for (const filepath of filesToRemove) {
11599
- logger.debug(` Removing stale: ${filepath}`);
11600
- const indexFilePath = path22.join(indexPath, filepath.replace(/\.[^.]+$/, ".json"));
11601
- try {
11602
- await fs8.unlink(indexFilePath);
11603
- } catch {}
11604
- const symbolicFilePath = path22.join(indexPath, "symbolic", filepath.replace(/\.[^.]+$/, ".json"));
11605
- try {
11606
- await fs8.unlink(symbolicFilePath);
11607
- } catch {}
11608
- delete manifest.files[filepath];
11609
- removedFilepaths.push(filepath);
11610
- 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;
11611
11660
  }
11612
11661
  if (removedFilepaths.length > 0) {
11613
11662
  try {
@@ -11621,6 +11670,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11621
11670
  await literalIndex.save();
11622
11671
  } catch {}
11623
11672
  }
11673
+ cleanupMs += Date.now() - cleanupStart;
11624
11674
  const ctx = {
11625
11675
  rootDir,
11626
11676
  config,
@@ -11635,21 +11685,51 @@ async function ensureIndexFresh(rootDir, options = {}) {
11635
11685
  },
11636
11686
  getIntrospection: (filepath) => introspection.getFile(filepath)
11637
11687
  };
11638
- const totalFiles = currentFiles.length;
11639
- let completedCount = 0;
11640
- const processIncrementalFile = async (filepath) => {
11688
+ const statCheck = async (filepath) => {
11641
11689
  const relativePath = path22.relative(rootDir, filepath);
11642
11690
  try {
11643
11691
  const stats = await fs8.stat(filepath);
11644
11692
  const lastModified = stats.mtime.toISOString();
11645
11693
  const existingEntry = manifest.files[relativePath];
11646
- if (existingEntry && existingEntry.lastModified === lastModified) {
11647
- completedCount++;
11648
- return { relativePath, status: "unchanged" };
11694
+ if (!existingEntry) {
11695
+ return { filepath, relativePath, lastModified, needsCheck: true, isNew: true };
11649
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 {
11650
11729
  const content = await fs8.readFile(filepath, "utf-8");
11651
11730
  const contentHash = computeContentHash(content);
11652
- if (existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
11731
+ const existingEntry = manifest.files[relativePath];
11732
+ if (!isNew && existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
11653
11733
  completedCount++;
11654
11734
  return {
11655
11735
  relativePath,
@@ -11659,7 +11739,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
11659
11739
  };
11660
11740
  }
11661
11741
  completedCount++;
11662
- logger.progress(` [${completedCount}/${totalFiles}] Indexing: ${relativePath}`);
11742
+ logger.progress(` [${completedCount}/${totalToProcess}] Indexing: ${relativePath}`);
11663
11743
  introspection.addFile(relativePath, content);
11664
11744
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
11665
11745
  if (!fileIndex) {
@@ -11678,8 +11758,12 @@ async function ensureIndexFresh(rootDir, options = {}) {
11678
11758
  return { relativePath, status: "error", error };
11679
11759
  }
11680
11760
  };
11761
+ const indexingStart = Date.now();
11681
11762
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
11682
- 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;
11683
11767
  logger.clearProgress();
11684
11768
  let mtimeUpdates = 0;
11685
11769
  for (const item of results) {
@@ -11733,12 +11817,38 @@ async function ensureIndexFresh(rootDir, options = {}) {
11733
11817
  }
11734
11818
  if (totalIndexed > 0 || totalRemoved > 0) {
11735
11819
  await updateGlobalManifest(rootDir, enabledModules, config);
11820
+ clearFreshnessCache();
11736
11821
  }
11737
- return {
11822
+ const result = {
11738
11823
  indexed: totalIndexed,
11739
11824
  removed: totalRemoved,
11740
11825
  unchanged: totalUnchanged
11741
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
+ }
11840
+ let finalManifestMtime = currentManifestMtime;
11841
+ try {
11842
+ const manifestStats = await fs8.stat(globalManifestPath);
11843
+ finalManifestMtime = manifestStats.mtimeMs;
11844
+ } catch {}
11845
+ freshnessCache = {
11846
+ rootDir,
11847
+ result: { indexed: totalIndexed, removed: totalRemoved, unchanged: totalUnchanged },
11848
+ timestamp: Date.now(),
11849
+ manifestMtime: finalManifestMtime
11850
+ };
11851
+ return result;
11742
11852
  }
11743
11853
  async function indexWithModule(rootDir, files, module2, config, verbose, introspection, logger, concurrency = DEFAULT_CONCURRENCY) {
11744
11854
  const result = {
@@ -11876,16 +11986,13 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
11876
11986
  async function findFiles(rootDir, config) {
11877
11987
  const patterns = config.extensions.map((ext) => `**/*${ext}`);
11878
11988
  const ignorePatterns = config.ignorePaths.map((p) => `**/${p}/**`);
11879
- const files = [];
11880
- for (const pattern of patterns) {
11881
- const matches = await glob(pattern, {
11882
- cwd: rootDir,
11883
- absolute: true,
11884
- ignore: ignorePatterns
11885
- });
11886
- files.push(...matches);
11887
- }
11888
- 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)];
11889
11996
  }
11890
11997
  async function loadModuleManifest(rootDir, moduleId, config) {
11891
11998
  const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
@@ -12054,7 +12161,7 @@ async function getIndexStatus(rootDir) {
12054
12161
  }
12055
12162
  return status;
12056
12163
  }
12057
- var 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;
12058
12165
  var init_indexer = __esm(() => {
12059
12166
  init_config2();
12060
12167
  init_registry();
@@ -12062,6 +12169,7 @@ var init_indexer = __esm(() => {
12062
12169
  init_logger();
12063
12170
  init_watcher();
12064
12171
  DEFAULT_CONCURRENCY = getOptimalConcurrency();
12172
+ STAT_CONCURRENCY = Math.max(32, getOptimalConcurrency() * 4);
12065
12173
  });
12066
12174
 
12067
12175
  // node_modules/balanced-match/index.js
@@ -13635,7 +13743,7 @@ init_logger();
13635
13743
  // package.json
13636
13744
  var package_default = {
13637
13745
  name: "raggrep",
13638
- version: "0.10.1",
13746
+ version: "0.10.4",
13639
13747
  description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
13640
13748
  type: "module",
13641
13749
  main: "./dist/index.js",
@@ -13732,6 +13840,7 @@ function parseFlags(args3) {
13732
13840
  help: false,
13733
13841
  verbose: false,
13734
13842
  watch: false,
13843
+ timing: false,
13735
13844
  remaining: []
13736
13845
  };
13737
13846
  for (let i2 = 0;i2 < args3.length; i2++) {
@@ -13742,6 +13851,8 @@ function parseFlags(args3) {
13742
13851
  flags2.verbose = true;
13743
13852
  } else if (arg === "--watch" || arg === "-w") {
13744
13853
  flags2.watch = true;
13854
+ } else if (arg === "--timing" || arg === "-T") {
13855
+ flags2.timing = true;
13745
13856
  } else if (arg === "--model" || arg === "-m") {
13746
13857
  const modelName = args3[++i2];
13747
13858
  if (modelName && modelName in EMBEDDING_MODELS) {
@@ -13900,6 +14011,7 @@ Options:
13900
14011
  -s, --min-score <n> Minimum similarity score 0-1 (default: 0.15)
13901
14012
  -t, --type <ext> Filter by file extension (e.g., ts, tsx, js)
13902
14013
  -f, --filter <path> Filter by path or glob pattern (can be used multiple times)
14014
+ -T, --timing Show timing breakdown for performance profiling
13903
14015
  -h, --help Show this help message
13904
14016
 
13905
14017
  Note:
@@ -13953,7 +14065,8 @@ Examples:
13953
14065
  const freshStats = await ensureIndexFresh2(process.cwd(), {
13954
14066
  model: flags2.model,
13955
14067
  quiet: true,
13956
- logger: silentLogger
14068
+ logger: silentLogger,
14069
+ timing: flags2.timing
13957
14070
  });
13958
14071
  console.log("RAGgrep Search");
13959
14072
  console.log(`==============
@@ -13970,6 +14083,23 @@ Examples:
13970
14083
  `);
13971
14084
  } else {
13972
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(`└──────────────────────────────────────────────┘
13973
14103
  `);
13974
14104
  }
13975
14105
  const filePatterns = flags2.fileType ? [`*.${flags2.fileType}`] : undefined;
@@ -14229,4 +14359,4 @@ Run 'raggrep <command> --help' for more information.
14229
14359
  }
14230
14360
  main();
14231
14361
 
14232
- //# debugId=983FE42C702490DF64756E2164756E21
14362
+ //# debugId=5A4116C9AF3188E264756E2164756E21