raggrep 0.10.8 → 0.11.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.
@@ -36,11 +36,6 @@ export interface FileManifestEntry {
36
36
  * This prevents false positives when git updates mtime on branch switches.
37
37
  */
38
38
  contentHash?: string;
39
- /**
40
- * File size in bytes. Used as a quick filter:
41
- * If mtime changed but size is same, content is likely unchanged.
42
- */
43
- fileSize?: number;
44
39
  }
45
40
  /**
46
41
  * Manifest tracking all indexed files for a specific module.
@@ -61,8 +56,14 @@ export interface ModuleManifest {
61
56
  export interface GlobalManifest {
62
57
  /** RAGgrep version */
63
58
  version: string;
64
- /** ISO timestamp of last update */
59
+ /** ISO timestamp of last update (when index files were written) */
65
60
  lastUpdated: string;
61
+ /**
62
+ * ISO timestamp of when the last index run started.
63
+ * Used to detect files modified since the last indexing pass.
64
+ * This is captured at the START of indexing, before any files are processed.
65
+ */
66
+ lastIndexStarted: string;
66
67
  /** List of active module IDs */
67
68
  modules: string[];
68
69
  }
package/dist/index.js CHANGED
@@ -11973,8 +11973,8 @@ function getOptimalConcurrency() {
11973
11973
  return optimal;
11974
11974
  }
11975
11975
  var DEFAULT_CONCURRENCY = getOptimalConcurrency();
11976
- var STAT_CONCURRENCY = Math.max(32, getOptimalConcurrency() * 4);
11977
11976
  async function indexDirectory(rootDir, options = {}) {
11977
+ const indexStartTime = new Date().toISOString();
11978
11978
  const verbose = options.verbose ?? false;
11979
11979
  const quiet = options.quiet ?? false;
11980
11980
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
@@ -12055,7 +12055,7 @@ Indexing complete in ${formatDuration(overallDuration)}`);
12055
12055
  const totalSkipped = results.reduce((sum, r) => sum + r.skipped, 0);
12056
12056
  const totalErrors = results.reduce((sum, r) => sum + r.errors, 0);
12057
12057
  logger.info(`Total: ${totalIndexed} indexed, ${totalSkipped} skipped, ${totalErrors} errors`);
12058
- await updateGlobalManifest(rootDir, enabledModules, config);
12058
+ await updateGlobalManifest(rootDir, enabledModules, config, indexStartTime);
12059
12059
  return results;
12060
12060
  }
12061
12061
  async function isIndexVersionCompatible(rootDir) {
@@ -12092,19 +12092,14 @@ async function ensureIndexFresh(rootDir, options = {}) {
12092
12092
  const verbose = options.verbose ?? false;
12093
12093
  const quiet = options.quiet ?? false;
12094
12094
  const showTiming = options.timing ?? false;
12095
+ const indexStartTime = new Date().toISOString();
12095
12096
  const startTime = Date.now();
12096
12097
  let fileDiscoveryMs = 0;
12097
- let statCheckMs = 0;
12098
12098
  let indexingMs = 0;
12099
12099
  let cleanupMs = 0;
12100
12100
  let filesDiscovered = 0;
12101
- let filesStatChecked = 0;
12102
- let filesWithChanges = 0;
12101
+ let filesChanged = 0;
12103
12102
  let filesReindexed = 0;
12104
- let totalDiagNewFiles = 0;
12105
- let totalDiagNoFileSize = 0;
12106
- let totalDiagSizeMismatch = 0;
12107
- let totalDiagNoContentHash = 0;
12108
12103
  const logger = options.logger ? options.logger : quiet ? createSilentLogger() : createLogger({ verbose });
12109
12104
  rootDir = path21.resolve(rootDir);
12110
12105
  const status = await getIndexStatus(rootDir);
@@ -12141,12 +12136,10 @@ async function ensureIndexFresh(rootDir, options = {}) {
12141
12136
  cachedResult.timing = {
12142
12137
  totalMs: Date.now() - startTime,
12143
12138
  fileDiscoveryMs: 0,
12144
- statCheckMs: 0,
12145
12139
  indexingMs: 0,
12146
12140
  cleanupMs: 0,
12147
12141
  filesDiscovered: 0,
12148
- filesStatChecked: 0,
12149
- filesWithChanges: 0,
12142
+ filesChanged: 0,
12150
12143
  filesReindexed: 0,
12151
12144
  fromCache: true
12152
12145
  };
@@ -12158,15 +12151,20 @@ async function ensureIndexFresh(rootDir, options = {}) {
12158
12151
  if (enabledModules.length === 0) {
12159
12152
  return { indexed: 0, removed: 0, unchanged: 0 };
12160
12153
  }
12154
+ const globalManifest = await loadGlobalManifest(rootDir, config);
12155
+ const lastIndexStarted = globalManifest?.lastIndexStarted ? new Date(globalManifest.lastIndexStarted) : null;
12161
12156
  const fileDiscoveryStart = Date.now();
12162
12157
  const introspection = new IntrospectionIndex(rootDir);
12163
- const [, currentFiles] = await Promise.all([
12158
+ const [, discoveryResult] = await Promise.all([
12164
12159
  introspection.initialize(),
12165
- findFiles(rootDir, config)
12160
+ findFilesWithStats(rootDir, config, lastIndexStarted)
12166
12161
  ]);
12167
12162
  fileDiscoveryMs = Date.now() - fileDiscoveryStart;
12163
+ const { allFiles: currentFiles, changedFiles, changedFileMtimes } = discoveryResult;
12168
12164
  filesDiscovered = currentFiles.length;
12165
+ filesChanged = changedFiles.length;
12169
12166
  const currentFileSet = new Set(currentFiles.map((f) => path21.relative(rootDir, f)));
12167
+ const changedFileSet = new Set(changedFiles);
12170
12168
  let totalIndexed = 0;
12171
12169
  let totalRemoved = 0;
12172
12170
  let totalUnchanged = 0;
@@ -12237,107 +12235,38 @@ async function ensureIndexFresh(rootDir, options = {}) {
12237
12235
  },
12238
12236
  getIntrospection: (filepath) => introspection.getFile(filepath)
12239
12237
  };
12240
- const statCheck = async (filepath) => {
12238
+ const moduleChangedFiles = module2.supportsFile ? changedFiles.filter((f) => module2.supportsFile(f)) : changedFiles;
12239
+ const filesToProcess = moduleChangedFiles.map((filepath) => {
12241
12240
  const relativePath = path21.relative(rootDir, filepath);
12242
- try {
12243
- const stats = await fs8.stat(filepath);
12244
- const lastModified = stats.mtime.toISOString();
12245
- const fileSize = stats.size;
12246
- const existingEntry = manifest.files[relativePath];
12247
- if (!existingEntry) {
12248
- return { filepath, relativePath, lastModified, fileSize, needsCheck: true, isNew: true };
12249
- }
12250
- if (existingEntry.lastModified === lastModified) {
12251
- return { filepath, relativePath, lastModified, fileSize, needsCheck: false, isNew: false };
12252
- }
12253
- if (existingEntry.fileSize !== undefined && existingEntry.fileSize === fileSize && existingEntry.contentHash) {
12254
- return { filepath, relativePath, lastModified, fileSize, needsCheck: false, isNew: false, existingContentHash: existingEntry.contentHash };
12255
- }
12256
- return { filepath, relativePath, lastModified, fileSize, needsCheck: true, isNew: false, existingContentHash: existingEntry.contentHash };
12257
- } catch {
12258
- return null;
12259
- }
12260
- };
12261
- const moduleFiles = module2.supportsFile ? currentFiles.filter((f) => module2.supportsFile(f)) : currentFiles;
12262
- const statCheckStart = Date.now();
12263
- const statResults = await parallelMap(moduleFiles, statCheck, STAT_CONCURRENCY);
12264
- statCheckMs += Date.now() - statCheckStart;
12265
- filesStatChecked += moduleFiles.length;
12266
- const filesToProcess = [];
12267
- const filesWithMtimeOnlyChange = [];
12268
- let unchangedCount = 0;
12269
- let diagNewFiles = 0;
12270
- let diagNoFileSize = 0;
12271
- let diagSizeMismatch = 0;
12272
- let diagNoContentHash = 0;
12273
- for (const result2 of statResults) {
12274
- if (!result2.success || !result2.value)
12275
- continue;
12276
- if (result2.value.needsCheck) {
12277
- filesToProcess.push(result2.value);
12278
- if (result2.value.isNew) {
12279
- diagNewFiles++;
12280
- } else {
12281
- const existingEntry = manifest.files[result2.value.relativePath];
12282
- if (existingEntry) {
12283
- if (existingEntry.fileSize === undefined)
12284
- diagNoFileSize++;
12285
- else if (existingEntry.fileSize !== result2.value.fileSize)
12286
- diagSizeMismatch++;
12287
- else if (!existingEntry.contentHash)
12288
- diagNoContentHash++;
12289
- }
12290
- }
12291
- } else {
12292
- unchangedCount++;
12293
- const existingEntry = manifest.files[result2.value.relativePath];
12294
- if (existingEntry && existingEntry.lastModified !== result2.value.lastModified) {
12295
- filesWithMtimeOnlyChange.push(result2.value);
12296
- }
12297
- }
12298
- }
12299
- totalDiagNewFiles += diagNewFiles;
12300
- totalDiagNoFileSize += diagNoFileSize;
12301
- totalDiagSizeMismatch += diagSizeMismatch;
12302
- totalDiagNoContentHash += diagNoContentHash;
12303
- if (filesToProcess.length > 100 && verbose) {
12304
- logger.info(` [Diagnostic] Phase 2 reasons: new=${diagNewFiles}, noSize=${diagNoFileSize}, sizeMismatch=${diagSizeMismatch}, noHash=${diagNoContentHash}`);
12305
- }
12306
- let mtimeOnlyUpdates = 0;
12307
- for (const file of filesWithMtimeOnlyChange) {
12308
- const existingEntry = manifest.files[file.relativePath];
12309
- if (existingEntry) {
12310
- manifest.files[file.relativePath] = {
12311
- ...existingEntry,
12312
- lastModified: file.lastModified,
12313
- fileSize: file.fileSize
12314
- };
12315
- mtimeOnlyUpdates++;
12316
- }
12317
- }
12241
+ const existingEntry = manifest.files[relativePath];
12242
+ const lastModified = changedFileMtimes.get(filepath) || new Date().toISOString();
12243
+ return {
12244
+ filepath,
12245
+ relativePath,
12246
+ lastModified,
12247
+ isNew: !existingEntry,
12248
+ existingContentHash: existingEntry?.contentHash
12249
+ };
12250
+ });
12251
+ const moduleAllFiles = module2.supportsFile ? currentFiles.filter((f) => module2.supportsFile(f)) : currentFiles;
12252
+ const unchangedCount = moduleAllFiles.length - filesToProcess.length;
12318
12253
  if (filesToProcess.length === 0) {
12319
12254
  totalUnchanged += unchangedCount;
12320
- if (mtimeOnlyUpdates > 0) {
12321
- manifest.lastUpdated = new Date().toISOString();
12322
- await writeModuleManifest(rootDir, module2.id, manifest, config);
12323
- }
12324
12255
  continue;
12325
12256
  }
12326
12257
  let completedCount = 0;
12327
12258
  const totalToProcess = filesToProcess.length;
12328
- const processChangedFile = async (statResult) => {
12329
- const { filepath, relativePath, lastModified, fileSize, isNew } = statResult;
12259
+ const processChangedFile = async (fileToProcess) => {
12260
+ const { filepath, relativePath, lastModified, isNew, existingContentHash } = fileToProcess;
12330
12261
  try {
12331
12262
  const content = await fs8.readFile(filepath, "utf-8");
12332
12263
  const contentHash = computeContentHash(content);
12333
- const existingEntry = manifest.files[relativePath];
12334
- if (!isNew && existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
12264
+ if (!isNew && existingContentHash && existingContentHash === contentHash) {
12335
12265
  completedCount++;
12336
12266
  return {
12337
12267
  relativePath,
12338
12268
  status: "mtime_updated",
12339
12269
  lastModified,
12340
- fileSize,
12341
12270
  contentHash
12342
12271
  };
12343
12272
  }
@@ -12346,14 +12275,13 @@ async function ensureIndexFresh(rootDir, options = {}) {
12346
12275
  introspection.addFile(relativePath, content);
12347
12276
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12348
12277
  if (!fileIndex) {
12349
- return { relativePath, status: "unchanged", fileSize };
12278
+ return { relativePath, status: "unchanged" };
12350
12279
  }
12351
12280
  await writeFileIndex(rootDir, module2.id, relativePath, fileIndex, config);
12352
12281
  return {
12353
12282
  relativePath,
12354
12283
  status: "indexed",
12355
12284
  lastModified,
12356
- fileSize,
12357
12285
  chunkCount: fileIndex.chunks.length,
12358
12286
  contentHash
12359
12287
  };
@@ -12366,7 +12294,6 @@ async function ensureIndexFresh(rootDir, options = {}) {
12366
12294
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
12367
12295
  const results = await parallelMap(filesToProcess, processChangedFile, concurrency);
12368
12296
  indexingMs += Date.now() - indexingStart;
12369
- filesWithChanges += filesToProcess.length;
12370
12297
  totalUnchanged += unchangedCount;
12371
12298
  logger.clearProgress();
12372
12299
  let mtimeUpdates = 0;
@@ -12380,18 +12307,17 @@ async function ensureIndexFresh(rootDir, options = {}) {
12380
12307
  manifest.files[fileResult.relativePath] = {
12381
12308
  lastModified: fileResult.lastModified,
12382
12309
  chunkCount: fileResult.chunkCount,
12383
- contentHash: fileResult.contentHash,
12384
- fileSize: fileResult.fileSize
12310
+ contentHash: fileResult.contentHash
12385
12311
  };
12386
12312
  totalIndexed++;
12313
+ filesReindexed++;
12387
12314
  break;
12388
12315
  case "mtime_updated":
12389
12316
  if (manifest.files[fileResult.relativePath]) {
12390
12317
  manifest.files[fileResult.relativePath] = {
12391
12318
  ...manifest.files[fileResult.relativePath],
12392
12319
  lastModified: fileResult.lastModified,
12393
- contentHash: fileResult.contentHash,
12394
- fileSize: fileResult.fileSize
12320
+ contentHash: fileResult.contentHash
12395
12321
  };
12396
12322
  mtimeUpdates++;
12397
12323
  }
@@ -12405,7 +12331,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
12405
12331
  break;
12406
12332
  }
12407
12333
  }
12408
- const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0 || mtimeOnlyUpdates > 0;
12334
+ const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0;
12409
12335
  if (hasManifestChanges) {
12410
12336
  manifest.lastUpdated = new Date().toISOString();
12411
12337
  await writeModuleManifest(rootDir, module2.id, manifest, config);
@@ -12421,8 +12347,8 @@ async function ensureIndexFresh(rootDir, options = {}) {
12421
12347
  if (totalIndexed > 0) {
12422
12348
  await introspection.save(config);
12423
12349
  }
12350
+ await updateGlobalManifest(rootDir, enabledModules, config, indexStartTime);
12424
12351
  if (totalIndexed > 0 || totalRemoved > 0) {
12425
- await updateGlobalManifest(rootDir, enabledModules, config);
12426
12352
  clearFreshnessCache();
12427
12353
  }
12428
12354
  const result = {
@@ -12434,20 +12360,12 @@ async function ensureIndexFresh(rootDir, options = {}) {
12434
12360
  result.timing = {
12435
12361
  totalMs: Date.now() - startTime,
12436
12362
  fileDiscoveryMs,
12437
- statCheckMs,
12438
12363
  indexingMs,
12439
12364
  cleanupMs,
12440
12365
  filesDiscovered,
12441
- filesStatChecked,
12442
- filesWithChanges,
12443
- filesReindexed: totalIndexed,
12444
- fromCache: false,
12445
- phase2Reasons: {
12446
- newFiles: totalDiagNewFiles,
12447
- noFileSize: totalDiagNoFileSize,
12448
- sizeMismatch: totalDiagSizeMismatch,
12449
- noContentHash: totalDiagNoContentHash
12450
- }
12366
+ filesChanged,
12367
+ filesReindexed,
12368
+ fromCache: false
12451
12369
  };
12452
12370
  }
12453
12371
  let finalManifestMtime = currentManifestMtime;
@@ -12516,7 +12434,6 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12516
12434
  try {
12517
12435
  const stats = await fs8.stat(filepath);
12518
12436
  const lastModified = stats.mtime.toISOString();
12519
- const fileSize = stats.size;
12520
12437
  const existingEntry = manifest.files[relativePath];
12521
12438
  if (existingEntry && existingEntry.lastModified === lastModified) {
12522
12439
  completedCount++;
@@ -12532,7 +12449,6 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12532
12449
  relativePath,
12533
12450
  status: "skipped",
12534
12451
  lastModified,
12535
- fileSize,
12536
12452
  contentHash
12537
12453
  };
12538
12454
  }
@@ -12542,14 +12458,13 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12542
12458
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12543
12459
  if (!fileIndex) {
12544
12460
  logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (no chunks)`);
12545
- return { relativePath, status: "skipped", fileSize };
12461
+ return { relativePath, status: "skipped" };
12546
12462
  }
12547
12463
  await writeFileIndex(rootDir, module2.id, relativePath, fileIndex, config);
12548
12464
  return {
12549
12465
  relativePath,
12550
12466
  status: "indexed",
12551
12467
  lastModified,
12552
- fileSize,
12553
12468
  chunkCount: fileIndex.chunks.length,
12554
12469
  contentHash
12555
12470
  };
@@ -12572,8 +12487,7 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12572
12487
  manifest.files[fileResult.relativePath] = {
12573
12488
  lastModified: fileResult.lastModified,
12574
12489
  chunkCount: fileResult.chunkCount,
12575
- contentHash: fileResult.contentHash,
12576
- fileSize: fileResult.fileSize
12490
+ contentHash: fileResult.contentHash
12577
12491
  };
12578
12492
  result.indexed++;
12579
12493
  break;
@@ -12584,8 +12498,7 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12584
12498
  manifest.files[fileResult.relativePath] = {
12585
12499
  ...existingEntry,
12586
12500
  lastModified: fileResult.lastModified,
12587
- contentHash: fileResult.contentHash,
12588
- fileSize: fileResult.fileSize
12501
+ contentHash: fileResult.contentHash
12589
12502
  };
12590
12503
  }
12591
12504
  }
@@ -12601,15 +12514,50 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12601
12514
  await writeModuleManifest(rootDir, module2.id, manifest, config);
12602
12515
  return result;
12603
12516
  }
12604
- async function findFiles(rootDir, config) {
12517
+ var STAT_CONCURRENCY = 64;
12518
+ async function findFilesWithStats(rootDir, config, lastIndexStarted) {
12605
12519
  const validExtensions = new Set(config.extensions);
12606
12520
  const ignoreDirs = new Set(config.ignorePaths);
12521
+ const lastIndexMs = lastIndexStarted?.getTime() ?? 0;
12607
12522
  const crawler = new Builder().withFullPaths().exclude((dirName) => ignoreDirs.has(dirName)).filter((filePath) => {
12608
12523
  const ext = path21.extname(filePath);
12609
12524
  return validExtensions.has(ext);
12610
12525
  }).crawl(rootDir);
12611
- const files = await crawler.withPromise();
12612
- return files;
12526
+ const allFiles = await crawler.withPromise();
12527
+ if (!lastIndexStarted) {
12528
+ const changedFileMtimes2 = new Map;
12529
+ await parallelMap(allFiles, async (filePath) => {
12530
+ try {
12531
+ const stats = await fs8.stat(filePath);
12532
+ changedFileMtimes2.set(filePath, stats.mtime.toISOString());
12533
+ } catch {}
12534
+ }, STAT_CONCURRENCY);
12535
+ return {
12536
+ allFiles,
12537
+ changedFiles: allFiles,
12538
+ changedFileMtimes: changedFileMtimes2
12539
+ };
12540
+ }
12541
+ const changedFiles = [];
12542
+ const changedFileMtimes = new Map;
12543
+ await parallelMap(allFiles, async (filePath) => {
12544
+ try {
12545
+ const stats = await fs8.stat(filePath);
12546
+ if (stats.mtimeMs > lastIndexMs) {
12547
+ changedFiles.push(filePath);
12548
+ changedFileMtimes.set(filePath, stats.mtime.toISOString());
12549
+ }
12550
+ } catch {}
12551
+ }, STAT_CONCURRENCY);
12552
+ return {
12553
+ allFiles,
12554
+ changedFiles,
12555
+ changedFileMtimes
12556
+ };
12557
+ }
12558
+ async function findFiles(rootDir, config) {
12559
+ const result = await findFilesWithStats(rootDir, config, null);
12560
+ return result.allFiles;
12613
12561
  }
12614
12562
  async function loadModuleManifest(rootDir, moduleId, config) {
12615
12563
  const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
@@ -12636,11 +12584,21 @@ async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
12636
12584
  await fs8.mkdir(path21.dirname(indexFilePath), { recursive: true });
12637
12585
  await fs8.writeFile(indexFilePath, JSON.stringify(fileIndex, null, 2));
12638
12586
  }
12639
- async function updateGlobalManifest(rootDir, modules, config) {
12587
+ async function loadGlobalManifest(rootDir, config) {
12588
+ const manifestPath = getGlobalManifestPath(rootDir, config);
12589
+ try {
12590
+ const content = await fs8.readFile(manifestPath, "utf-8");
12591
+ return JSON.parse(content);
12592
+ } catch {
12593
+ return null;
12594
+ }
12595
+ }
12596
+ async function updateGlobalManifest(rootDir, modules, config, indexStartTime) {
12640
12597
  const manifestPath = getGlobalManifestPath(rootDir, config);
12641
12598
  const manifest = {
12642
12599
  version: INDEX_SCHEMA_VERSION,
12643
12600
  lastUpdated: new Date().toISOString(),
12601
+ lastIndexStarted: indexStartTime,
12644
12602
  modules: modules.map((m) => m.id)
12645
12603
  };
12646
12604
  await fs8.mkdir(path21.dirname(manifestPath), { recursive: true });
@@ -14010,7 +13968,7 @@ async function search(rootDir, query, options = {}) {
14010
13968
  console.log(`Searching for: "${query}"`);
14011
13969
  const config = await loadConfig(rootDir);
14012
13970
  await registerBuiltInModules();
14013
- const globalManifest = await loadGlobalManifest(rootDir, config);
13971
+ const globalManifest = await loadGlobalManifest2(rootDir, config);
14014
13972
  if (!globalManifest || globalManifest.modules.length === 0) {
14015
13973
  console.log('No index found. Run "raggrep index" first.');
14016
13974
  return [];
@@ -14094,7 +14052,7 @@ async function traverseDirectory(dir, files, basePath) {
14094
14052
  }
14095
14053
  } catch {}
14096
14054
  }
14097
- async function loadGlobalManifest(rootDir, config) {
14055
+ async function loadGlobalManifest2(rootDir, config) {
14098
14056
  const manifestPath = getGlobalManifestPath(rootDir, config);
14099
14057
  try {
14100
14058
  const content = await fs9.readFile(manifestPath, "utf-8");
@@ -14187,4 +14145,4 @@ export {
14187
14145
  ConsoleLogger
14188
14146
  };
14189
14147
 
14190
- //# debugId=3F7DE957CF0C467164756E2164756E21
14148
+ //# debugId=EA3CF8830C1070D164756E2164756E21