raggrep 0.10.7 → 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,14 +12092,13 @@ 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
12103
  const logger = options.logger ? options.logger : quiet ? createSilentLogger() : createLogger({ verbose });
12105
12104
  rootDir = path21.resolve(rootDir);
@@ -12137,12 +12136,10 @@ async function ensureIndexFresh(rootDir, options = {}) {
12137
12136
  cachedResult.timing = {
12138
12137
  totalMs: Date.now() - startTime,
12139
12138
  fileDiscoveryMs: 0,
12140
- statCheckMs: 0,
12141
12139
  indexingMs: 0,
12142
12140
  cleanupMs: 0,
12143
12141
  filesDiscovered: 0,
12144
- filesStatChecked: 0,
12145
- filesWithChanges: 0,
12142
+ filesChanged: 0,
12146
12143
  filesReindexed: 0,
12147
12144
  fromCache: true
12148
12145
  };
@@ -12154,15 +12151,20 @@ async function ensureIndexFresh(rootDir, options = {}) {
12154
12151
  if (enabledModules.length === 0) {
12155
12152
  return { indexed: 0, removed: 0, unchanged: 0 };
12156
12153
  }
12154
+ const globalManifest = await loadGlobalManifest(rootDir, config);
12155
+ const lastIndexStarted = globalManifest?.lastIndexStarted ? new Date(globalManifest.lastIndexStarted) : null;
12157
12156
  const fileDiscoveryStart = Date.now();
12158
12157
  const introspection = new IntrospectionIndex(rootDir);
12159
- const [, currentFiles] = await Promise.all([
12158
+ const [, discoveryResult] = await Promise.all([
12160
12159
  introspection.initialize(),
12161
- findFiles(rootDir, config)
12160
+ findFilesWithStats(rootDir, config, lastIndexStarted)
12162
12161
  ]);
12163
12162
  fileDiscoveryMs = Date.now() - fileDiscoveryStart;
12163
+ const { allFiles: currentFiles, changedFiles, changedFileMtimes } = discoveryResult;
12164
12164
  filesDiscovered = currentFiles.length;
12165
+ filesChanged = changedFiles.length;
12165
12166
  const currentFileSet = new Set(currentFiles.map((f) => path21.relative(rootDir, f)));
12167
+ const changedFileSet = new Set(changedFiles);
12166
12168
  let totalIndexed = 0;
12167
12169
  let totalRemoved = 0;
12168
12170
  let totalUnchanged = 0;
@@ -12233,82 +12235,38 @@ async function ensureIndexFresh(rootDir, options = {}) {
12233
12235
  },
12234
12236
  getIntrospection: (filepath) => introspection.getFile(filepath)
12235
12237
  };
12236
- const statCheck = async (filepath) => {
12238
+ const moduleChangedFiles = module2.supportsFile ? changedFiles.filter((f) => module2.supportsFile(f)) : changedFiles;
12239
+ const filesToProcess = moduleChangedFiles.map((filepath) => {
12237
12240
  const relativePath = path21.relative(rootDir, filepath);
12238
- try {
12239
- const stats = await fs8.stat(filepath);
12240
- const lastModified = stats.mtime.toISOString();
12241
- const fileSize = stats.size;
12242
- const existingEntry = manifest.files[relativePath];
12243
- if (!existingEntry) {
12244
- return { filepath, relativePath, lastModified, fileSize, needsCheck: true, isNew: true };
12245
- }
12246
- if (existingEntry.lastModified === lastModified) {
12247
- return { filepath, relativePath, lastModified, fileSize, needsCheck: false, isNew: false };
12248
- }
12249
- if (existingEntry.fileSize !== undefined && existingEntry.fileSize === fileSize && existingEntry.contentHash) {
12250
- return { filepath, relativePath, lastModified, fileSize, needsCheck: false, isNew: false, existingContentHash: existingEntry.contentHash };
12251
- }
12252
- return { filepath, relativePath, lastModified, fileSize, needsCheck: true, isNew: false, existingContentHash: existingEntry.contentHash };
12253
- } catch {
12254
- return null;
12255
- }
12256
- };
12257
- const statCheckStart = Date.now();
12258
- const statResults = await parallelMap(currentFiles, statCheck, STAT_CONCURRENCY);
12259
- statCheckMs += Date.now() - statCheckStart;
12260
- filesStatChecked += currentFiles.length;
12261
- const filesToProcess = [];
12262
- const filesWithMtimeOnlyChange = [];
12263
- let unchangedCount = 0;
12264
- for (const result2 of statResults) {
12265
- if (!result2.success || !result2.value)
12266
- continue;
12267
- if (result2.value.needsCheck) {
12268
- filesToProcess.push(result2.value);
12269
- } else {
12270
- unchangedCount++;
12271
- const existingEntry = manifest.files[result2.value.relativePath];
12272
- if (existingEntry && existingEntry.lastModified !== result2.value.lastModified) {
12273
- filesWithMtimeOnlyChange.push(result2.value);
12274
- }
12275
- }
12276
- }
12277
- let mtimeOnlyUpdates = 0;
12278
- for (const file of filesWithMtimeOnlyChange) {
12279
- const existingEntry = manifest.files[file.relativePath];
12280
- if (existingEntry) {
12281
- manifest.files[file.relativePath] = {
12282
- ...existingEntry,
12283
- lastModified: file.lastModified,
12284
- fileSize: file.fileSize
12285
- };
12286
- mtimeOnlyUpdates++;
12287
- }
12288
- }
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;
12289
12253
  if (filesToProcess.length === 0) {
12290
12254
  totalUnchanged += unchangedCount;
12291
- if (mtimeOnlyUpdates > 0) {
12292
- manifest.lastUpdated = new Date().toISOString();
12293
- await writeModuleManifest(rootDir, module2.id, manifest, config);
12294
- }
12295
12255
  continue;
12296
12256
  }
12297
12257
  let completedCount = 0;
12298
12258
  const totalToProcess = filesToProcess.length;
12299
- const processChangedFile = async (statResult) => {
12300
- const { filepath, relativePath, lastModified, fileSize, isNew } = statResult;
12259
+ const processChangedFile = async (fileToProcess) => {
12260
+ const { filepath, relativePath, lastModified, isNew, existingContentHash } = fileToProcess;
12301
12261
  try {
12302
12262
  const content = await fs8.readFile(filepath, "utf-8");
12303
12263
  const contentHash = computeContentHash(content);
12304
- const existingEntry = manifest.files[relativePath];
12305
- if (!isNew && existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
12264
+ if (!isNew && existingContentHash && existingContentHash === contentHash) {
12306
12265
  completedCount++;
12307
12266
  return {
12308
12267
  relativePath,
12309
12268
  status: "mtime_updated",
12310
12269
  lastModified,
12311
- fileSize,
12312
12270
  contentHash
12313
12271
  };
12314
12272
  }
@@ -12317,14 +12275,13 @@ async function ensureIndexFresh(rootDir, options = {}) {
12317
12275
  introspection.addFile(relativePath, content);
12318
12276
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12319
12277
  if (!fileIndex) {
12320
- return { relativePath, status: "unchanged", fileSize };
12278
+ return { relativePath, status: "unchanged" };
12321
12279
  }
12322
12280
  await writeFileIndex(rootDir, module2.id, relativePath, fileIndex, config);
12323
12281
  return {
12324
12282
  relativePath,
12325
12283
  status: "indexed",
12326
12284
  lastModified,
12327
- fileSize,
12328
12285
  chunkCount: fileIndex.chunks.length,
12329
12286
  contentHash
12330
12287
  };
@@ -12337,7 +12294,6 @@ async function ensureIndexFresh(rootDir, options = {}) {
12337
12294
  const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
12338
12295
  const results = await parallelMap(filesToProcess, processChangedFile, concurrency);
12339
12296
  indexingMs += Date.now() - indexingStart;
12340
- filesWithChanges += filesToProcess.length;
12341
12297
  totalUnchanged += unchangedCount;
12342
12298
  logger.clearProgress();
12343
12299
  let mtimeUpdates = 0;
@@ -12351,18 +12307,17 @@ async function ensureIndexFresh(rootDir, options = {}) {
12351
12307
  manifest.files[fileResult.relativePath] = {
12352
12308
  lastModified: fileResult.lastModified,
12353
12309
  chunkCount: fileResult.chunkCount,
12354
- contentHash: fileResult.contentHash,
12355
- fileSize: fileResult.fileSize
12310
+ contentHash: fileResult.contentHash
12356
12311
  };
12357
12312
  totalIndexed++;
12313
+ filesReindexed++;
12358
12314
  break;
12359
12315
  case "mtime_updated":
12360
12316
  if (manifest.files[fileResult.relativePath]) {
12361
12317
  manifest.files[fileResult.relativePath] = {
12362
12318
  ...manifest.files[fileResult.relativePath],
12363
12319
  lastModified: fileResult.lastModified,
12364
- contentHash: fileResult.contentHash,
12365
- fileSize: fileResult.fileSize
12320
+ contentHash: fileResult.contentHash
12366
12321
  };
12367
12322
  mtimeUpdates++;
12368
12323
  }
@@ -12376,7 +12331,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
12376
12331
  break;
12377
12332
  }
12378
12333
  }
12379
- const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0 || mtimeOnlyUpdates > 0;
12334
+ const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0;
12380
12335
  if (hasManifestChanges) {
12381
12336
  manifest.lastUpdated = new Date().toISOString();
12382
12337
  await writeModuleManifest(rootDir, module2.id, manifest, config);
@@ -12392,8 +12347,8 @@ async function ensureIndexFresh(rootDir, options = {}) {
12392
12347
  if (totalIndexed > 0) {
12393
12348
  await introspection.save(config);
12394
12349
  }
12350
+ await updateGlobalManifest(rootDir, enabledModules, config, indexStartTime);
12395
12351
  if (totalIndexed > 0 || totalRemoved > 0) {
12396
- await updateGlobalManifest(rootDir, enabledModules, config);
12397
12352
  clearFreshnessCache();
12398
12353
  }
12399
12354
  const result = {
@@ -12405,13 +12360,11 @@ async function ensureIndexFresh(rootDir, options = {}) {
12405
12360
  result.timing = {
12406
12361
  totalMs: Date.now() - startTime,
12407
12362
  fileDiscoveryMs,
12408
- statCheckMs,
12409
12363
  indexingMs,
12410
12364
  cleanupMs,
12411
12365
  filesDiscovered,
12412
- filesStatChecked,
12413
- filesWithChanges,
12414
- filesReindexed: totalIndexed,
12366
+ filesChanged,
12367
+ filesReindexed,
12415
12368
  fromCache: false
12416
12369
  };
12417
12370
  }
@@ -12481,7 +12434,6 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12481
12434
  try {
12482
12435
  const stats = await fs8.stat(filepath);
12483
12436
  const lastModified = stats.mtime.toISOString();
12484
- const fileSize = stats.size;
12485
12437
  const existingEntry = manifest.files[relativePath];
12486
12438
  if (existingEntry && existingEntry.lastModified === lastModified) {
12487
12439
  completedCount++;
@@ -12497,7 +12449,6 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12497
12449
  relativePath,
12498
12450
  status: "skipped",
12499
12451
  lastModified,
12500
- fileSize,
12501
12452
  contentHash
12502
12453
  };
12503
12454
  }
@@ -12507,14 +12458,13 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12507
12458
  const fileIndex = await module2.indexFile(relativePath, content, ctx);
12508
12459
  if (!fileIndex) {
12509
12460
  logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (no chunks)`);
12510
- return { relativePath, status: "skipped", fileSize };
12461
+ return { relativePath, status: "skipped" };
12511
12462
  }
12512
12463
  await writeFileIndex(rootDir, module2.id, relativePath, fileIndex, config);
12513
12464
  return {
12514
12465
  relativePath,
12515
12466
  status: "indexed",
12516
12467
  lastModified,
12517
- fileSize,
12518
12468
  chunkCount: fileIndex.chunks.length,
12519
12469
  contentHash
12520
12470
  };
@@ -12537,8 +12487,7 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12537
12487
  manifest.files[fileResult.relativePath] = {
12538
12488
  lastModified: fileResult.lastModified,
12539
12489
  chunkCount: fileResult.chunkCount,
12540
- contentHash: fileResult.contentHash,
12541
- fileSize: fileResult.fileSize
12490
+ contentHash: fileResult.contentHash
12542
12491
  };
12543
12492
  result.indexed++;
12544
12493
  break;
@@ -12549,8 +12498,7 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12549
12498
  manifest.files[fileResult.relativePath] = {
12550
12499
  ...existingEntry,
12551
12500
  lastModified: fileResult.lastModified,
12552
- contentHash: fileResult.contentHash,
12553
- fileSize: fileResult.fileSize
12501
+ contentHash: fileResult.contentHash
12554
12502
  };
12555
12503
  }
12556
12504
  }
@@ -12566,15 +12514,50 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
12566
12514
  await writeModuleManifest(rootDir, module2.id, manifest, config);
12567
12515
  return result;
12568
12516
  }
12569
- async function findFiles(rootDir, config) {
12517
+ var STAT_CONCURRENCY = 64;
12518
+ async function findFilesWithStats(rootDir, config, lastIndexStarted) {
12570
12519
  const validExtensions = new Set(config.extensions);
12571
12520
  const ignoreDirs = new Set(config.ignorePaths);
12521
+ const lastIndexMs = lastIndexStarted?.getTime() ?? 0;
12572
12522
  const crawler = new Builder().withFullPaths().exclude((dirName) => ignoreDirs.has(dirName)).filter((filePath) => {
12573
12523
  const ext = path21.extname(filePath);
12574
12524
  return validExtensions.has(ext);
12575
12525
  }).crawl(rootDir);
12576
- const files = await crawler.withPromise();
12577
- 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;
12578
12561
  }
12579
12562
  async function loadModuleManifest(rootDir, moduleId, config) {
12580
12563
  const manifestPath = getModuleManifestPath(rootDir, moduleId, config);
@@ -12601,11 +12584,21 @@ async function writeFileIndex(rootDir, moduleId, filepath, fileIndex, config) {
12601
12584
  await fs8.mkdir(path21.dirname(indexFilePath), { recursive: true });
12602
12585
  await fs8.writeFile(indexFilePath, JSON.stringify(fileIndex, null, 2));
12603
12586
  }
12604
- 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) {
12605
12597
  const manifestPath = getGlobalManifestPath(rootDir, config);
12606
12598
  const manifest = {
12607
12599
  version: INDEX_SCHEMA_VERSION,
12608
12600
  lastUpdated: new Date().toISOString(),
12601
+ lastIndexStarted: indexStartTime,
12609
12602
  modules: modules.map((m) => m.id)
12610
12603
  };
12611
12604
  await fs8.mkdir(path21.dirname(manifestPath), { recursive: true });
@@ -13975,7 +13968,7 @@ async function search(rootDir, query, options = {}) {
13975
13968
  console.log(`Searching for: "${query}"`);
13976
13969
  const config = await loadConfig(rootDir);
13977
13970
  await registerBuiltInModules();
13978
- const globalManifest = await loadGlobalManifest(rootDir, config);
13971
+ const globalManifest = await loadGlobalManifest2(rootDir, config);
13979
13972
  if (!globalManifest || globalManifest.modules.length === 0) {
13980
13973
  console.log('No index found. Run "raggrep index" first.');
13981
13974
  return [];
@@ -14059,7 +14052,7 @@ async function traverseDirectory(dir, files, basePath) {
14059
14052
  }
14060
14053
  } catch {}
14061
14054
  }
14062
- async function loadGlobalManifest(rootDir, config) {
14055
+ async function loadGlobalManifest2(rootDir, config) {
14063
14056
  const manifestPath = getGlobalManifestPath(rootDir, config);
14064
14057
  try {
14065
14058
  const content = await fs9.readFile(manifestPath, "utf-8");
@@ -14152,4 +14145,4 @@ export {
14152
14145
  ConsoleLogger
14153
14146
  };
14154
14147
 
14155
- //# debugId=120D75FB1F633A7A64756E2164756E21
14148
+ //# debugId=EA3CF8830C1070D164756E2164756E21