opencode-codebase-index 0.2.4 → 0.3.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.
package/dist/index.js CHANGED
@@ -491,7 +491,7 @@ var require_ignore = __commonJS({
491
491
  // path matching.
492
492
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
493
493
  // @returns {TestResult} true if a file is ignored
494
- test(path8, checkUnignored, mode) {
494
+ test(path9, checkUnignored, mode) {
495
495
  let ignored = false;
496
496
  let unignored = false;
497
497
  let matchedRule;
@@ -500,7 +500,7 @@ var require_ignore = __commonJS({
500
500
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
501
501
  return;
502
502
  }
503
- const matched = rule[mode].test(path8);
503
+ const matched = rule[mode].test(path9);
504
504
  if (!matched) {
505
505
  return;
506
506
  }
@@ -521,17 +521,17 @@ var require_ignore = __commonJS({
521
521
  var throwError = (message, Ctor) => {
522
522
  throw new Ctor(message);
523
523
  };
524
- var checkPath = (path8, originalPath, doThrow) => {
525
- if (!isString(path8)) {
524
+ var checkPath = (path9, originalPath, doThrow) => {
525
+ if (!isString(path9)) {
526
526
  return doThrow(
527
527
  `path must be a string, but got \`${originalPath}\``,
528
528
  TypeError
529
529
  );
530
530
  }
531
- if (!path8) {
531
+ if (!path9) {
532
532
  return doThrow(`path must not be empty`, TypeError);
533
533
  }
534
- if (checkPath.isNotRelative(path8)) {
534
+ if (checkPath.isNotRelative(path9)) {
535
535
  const r = "`path.relative()`d";
536
536
  return doThrow(
537
537
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -540,7 +540,7 @@ var require_ignore = __commonJS({
540
540
  }
541
541
  return true;
542
542
  };
543
- var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
543
+ var isNotRelative = (path9) => REGEX_TEST_INVALID_PATH.test(path9);
544
544
  checkPath.isNotRelative = isNotRelative;
545
545
  checkPath.convert = (p) => p;
546
546
  var Ignore2 = class {
@@ -570,19 +570,19 @@ var require_ignore = __commonJS({
570
570
  }
571
571
  // @returns {TestResult}
572
572
  _test(originalPath, cache, checkUnignored, slices) {
573
- const path8 = originalPath && checkPath.convert(originalPath);
573
+ const path9 = originalPath && checkPath.convert(originalPath);
574
574
  checkPath(
575
- path8,
575
+ path9,
576
576
  originalPath,
577
577
  this._strictPathCheck ? throwError : RETURN_FALSE
578
578
  );
579
- return this._t(path8, cache, checkUnignored, slices);
579
+ return this._t(path9, cache, checkUnignored, slices);
580
580
  }
581
- checkIgnore(path8) {
582
- if (!REGEX_TEST_TRAILING_SLASH.test(path8)) {
583
- return this.test(path8);
581
+ checkIgnore(path9) {
582
+ if (!REGEX_TEST_TRAILING_SLASH.test(path9)) {
583
+ return this.test(path9);
584
584
  }
585
- const slices = path8.split(SLASH2).filter(Boolean);
585
+ const slices = path9.split(SLASH2).filter(Boolean);
586
586
  slices.pop();
587
587
  if (slices.length) {
588
588
  const parent = this._t(
@@ -595,18 +595,18 @@ var require_ignore = __commonJS({
595
595
  return parent;
596
596
  }
597
597
  }
598
- return this._rules.test(path8, false, MODE_CHECK_IGNORE);
598
+ return this._rules.test(path9, false, MODE_CHECK_IGNORE);
599
599
  }
600
- _t(path8, cache, checkUnignored, slices) {
601
- if (path8 in cache) {
602
- return cache[path8];
600
+ _t(path9, cache, checkUnignored, slices) {
601
+ if (path9 in cache) {
602
+ return cache[path9];
603
603
  }
604
604
  if (!slices) {
605
- slices = path8.split(SLASH2).filter(Boolean);
605
+ slices = path9.split(SLASH2).filter(Boolean);
606
606
  }
607
607
  slices.pop();
608
608
  if (!slices.length) {
609
- return cache[path8] = this._rules.test(path8, checkUnignored, MODE_IGNORE);
609
+ return cache[path9] = this._rules.test(path9, checkUnignored, MODE_IGNORE);
610
610
  }
611
611
  const parent = this._t(
612
612
  slices.join(SLASH2) + SLASH2,
@@ -614,29 +614,29 @@ var require_ignore = __commonJS({
614
614
  checkUnignored,
615
615
  slices
616
616
  );
617
- return cache[path8] = parent.ignored ? parent : this._rules.test(path8, checkUnignored, MODE_IGNORE);
617
+ return cache[path9] = parent.ignored ? parent : this._rules.test(path9, checkUnignored, MODE_IGNORE);
618
618
  }
619
- ignores(path8) {
620
- return this._test(path8, this._ignoreCache, false).ignored;
619
+ ignores(path9) {
620
+ return this._test(path9, this._ignoreCache, false).ignored;
621
621
  }
622
622
  createFilter() {
623
- return (path8) => !this.ignores(path8);
623
+ return (path9) => !this.ignores(path9);
624
624
  }
625
625
  filter(paths) {
626
626
  return makeArray(paths).filter(this.createFilter());
627
627
  }
628
628
  // @returns {TestResult}
629
- test(path8) {
630
- return this._test(path8, this._testCache, true);
629
+ test(path9) {
630
+ return this._test(path9, this._testCache, true);
631
631
  }
632
632
  };
633
633
  var factory = (options) => new Ignore2(options);
634
- var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, RETURN_FALSE);
634
+ var isPathValid = (path9) => checkPath(path9 && checkPath.convert(path9), path9, RETURN_FALSE);
635
635
  var setupWindows = () => {
636
636
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
637
637
  checkPath.convert = makePosix;
638
638
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
639
- checkPath.isNotRelative = (path8) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
639
+ checkPath.isNotRelative = (path9) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path9) || isNotRelative(path9);
640
640
  };
641
641
  if (
642
642
  // Detect `process` so that it can run in browsers.
@@ -652,9 +652,10 @@ var require_ignore = __commonJS({
652
652
  });
653
653
 
654
654
  // src/index.ts
655
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
656
- import * as path7 from "path";
655
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
656
+ import * as path8 from "path";
657
657
  import * as os3 from "os";
658
+ import { fileURLToPath as fileURLToPath2 } from "url";
658
659
 
659
660
  // src/config/schema.ts
660
661
  var DEFAULT_INCLUDE = [
@@ -692,7 +693,10 @@ function getDefaultIndexingConfig() {
692
693
  maxChunksPerFile: 100,
693
694
  semanticOnly: false,
694
695
  retries: 3,
695
- retryDelayMs: 1e3
696
+ retryDelayMs: 1e3,
697
+ autoGc: true,
698
+ gcIntervalDays: 7,
699
+ gcOrphanThreshold: 100
696
700
  };
697
701
  }
698
702
  function getDefaultSearchConfig() {
@@ -727,7 +731,10 @@ function parseConfig(raw) {
727
731
  maxChunksPerFile: typeof rawIndexing.maxChunksPerFile === "number" ? Math.max(1, rawIndexing.maxChunksPerFile) : defaultIndexing.maxChunksPerFile,
728
732
  semanticOnly: typeof rawIndexing.semanticOnly === "boolean" ? rawIndexing.semanticOnly : defaultIndexing.semanticOnly,
729
733
  retries: typeof rawIndexing.retries === "number" ? rawIndexing.retries : defaultIndexing.retries,
730
- retryDelayMs: typeof rawIndexing.retryDelayMs === "number" ? rawIndexing.retryDelayMs : defaultIndexing.retryDelayMs
734
+ retryDelayMs: typeof rawIndexing.retryDelayMs === "number" ? rawIndexing.retryDelayMs : defaultIndexing.retryDelayMs,
735
+ autoGc: typeof rawIndexing.autoGc === "boolean" ? rawIndexing.autoGc : defaultIndexing.autoGc,
736
+ gcIntervalDays: typeof rawIndexing.gcIntervalDays === "number" ? Math.max(1, rawIndexing.gcIntervalDays) : defaultIndexing.gcIntervalDays,
737
+ gcOrphanThreshold: typeof rawIndexing.gcOrphanThreshold === "number" ? Math.max(0, rawIndexing.gcOrphanThreshold) : defaultIndexing.gcOrphanThreshold
731
738
  };
732
739
  const rawSearch = input.search && typeof input.search === "object" ? input.search : {};
733
740
  const search = {
@@ -2066,34 +2073,36 @@ var GoogleEmbeddingProvider = class {
2066
2073
  };
2067
2074
  }
2068
2075
  async embedBatch(texts) {
2069
- const embeddings = [];
2070
- let totalTokens = 0;
2071
- for (const text of texts) {
2072
- const response = await fetch(
2073
- `${this.credentials.baseUrl}/models/${this.modelInfo.model}:embedContent?key=${this.credentials.apiKey}`,
2074
- {
2075
- method: "POST",
2076
- headers: {
2077
- "Content-Type": "application/json"
2078
- },
2079
- body: JSON.stringify({
2080
- content: {
2081
- parts: [{ text }]
2082
- }
2083
- })
2076
+ const results = await Promise.all(
2077
+ texts.map(async (text) => {
2078
+ const response = await fetch(
2079
+ `${this.credentials.baseUrl}/models/${this.modelInfo.model}:embedContent?key=${this.credentials.apiKey}`,
2080
+ {
2081
+ method: "POST",
2082
+ headers: {
2083
+ "Content-Type": "application/json"
2084
+ },
2085
+ body: JSON.stringify({
2086
+ content: {
2087
+ parts: [{ text }]
2088
+ }
2089
+ })
2090
+ }
2091
+ );
2092
+ if (!response.ok) {
2093
+ const error = await response.text();
2094
+ throw new Error(`Google embedding API error: ${response.status} - ${error}`);
2084
2095
  }
2085
- );
2086
- if (!response.ok) {
2087
- const error = await response.text();
2088
- throw new Error(`Google embedding API error: ${response.status} - ${error}`);
2089
- }
2090
- const data = await response.json();
2091
- embeddings.push(data.embedding.values);
2092
- totalTokens += Math.ceil(text.length / 4);
2093
- }
2096
+ const data = await response.json();
2097
+ return {
2098
+ embedding: data.embedding.values,
2099
+ tokensUsed: Math.ceil(text.length / 4)
2100
+ };
2101
+ })
2102
+ );
2094
2103
  return {
2095
- embeddings,
2096
- totalTokensUsed: totalTokens
2104
+ embeddings: results.map((r) => r.embedding),
2105
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
2097
2106
  };
2098
2107
  }
2099
2108
  getModelInfo() {
@@ -2127,16 +2136,10 @@ var OllamaEmbeddingProvider = class {
2127
2136
  };
2128
2137
  }
2129
2138
  async embedBatch(texts) {
2130
- const embeddings = [];
2131
- let totalTokens = 0;
2132
- for (const text of texts) {
2133
- const result = await this.embed(text);
2134
- embeddings.push(result.embedding);
2135
- totalTokens += result.tokensUsed;
2136
- }
2139
+ const results = await Promise.all(texts.map((text) => this.embed(text)));
2137
2140
  return {
2138
- embeddings,
2139
- totalTokensUsed: totalTokens
2141
+ embeddings: results.map((r) => r.embedding),
2142
+ totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
2140
2143
  };
2141
2144
  }
2142
2145
  getModelInfo() {
@@ -2677,12 +2680,20 @@ var Database = class {
2677
2680
  upsertEmbedding(contentHash, embedding, chunkText, model) {
2678
2681
  this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
2679
2682
  }
2683
+ upsertEmbeddingsBatch(items) {
2684
+ if (items.length === 0) return;
2685
+ this.inner.upsertEmbeddingsBatch(items);
2686
+ }
2680
2687
  getMissingEmbeddings(contentHashes) {
2681
2688
  return this.inner.getMissingEmbeddings(contentHashes);
2682
2689
  }
2683
2690
  upsertChunk(chunk) {
2684
2691
  this.inner.upsertChunk(chunk);
2685
2692
  }
2693
+ upsertChunksBatch(chunks) {
2694
+ if (chunks.length === 0) return;
2695
+ this.inner.upsertChunksBatch(chunks);
2696
+ }
2686
2697
  getChunk(chunkId) {
2687
2698
  return this.inner.getChunk(chunkId) ?? null;
2688
2699
  }
@@ -2695,6 +2706,10 @@ var Database = class {
2695
2706
  addChunksToBranch(branch, chunkIds) {
2696
2707
  this.inner.addChunksToBranch(branch, chunkIds);
2697
2708
  }
2709
+ addChunksToBranchBatch(branch, chunkIds) {
2710
+ if (chunkIds.length === 0) return;
2711
+ this.inner.addChunksToBranchBatch(branch, chunkIds);
2712
+ }
2698
2713
  clearBranch(branch) {
2699
2714
  return this.inner.clearBranch(branch);
2700
2715
  }
@@ -2899,6 +2914,20 @@ var Indexer = class {
2899
2914
  });
2900
2915
  this.saveFailedBatches(existing);
2901
2916
  }
2917
+ getProviderRateLimits(provider) {
2918
+ switch (provider) {
2919
+ case "github-copilot":
2920
+ return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
2921
+ case "openai":
2922
+ return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
2923
+ case "google":
2924
+ return { concurrency: 5, intervalMs: 200, minRetryMs: 1e3, maxRetryMs: 3e4 };
2925
+ case "ollama":
2926
+ return { concurrency: 5, intervalMs: 0, minRetryMs: 500, maxRetryMs: 5e3 };
2927
+ default:
2928
+ return { concurrency: 3, intervalMs: 1e3, minRetryMs: 1e3, maxRetryMs: 3e4 };
2929
+ }
2930
+ }
2902
2931
  async initialize() {
2903
2932
  this.detectedProvider = await detectEmbeddingProvider(this.config.embeddingProvider);
2904
2933
  if (!this.detectedProvider) {
@@ -2941,11 +2970,45 @@ var Indexer = class {
2941
2970
  this.currentBranch = "default";
2942
2971
  this.baseBranch = "default";
2943
2972
  }
2973
+ if (this.config.indexing.autoGc) {
2974
+ await this.maybeRunAutoGc();
2975
+ }
2976
+ }
2977
+ async maybeRunAutoGc() {
2978
+ if (!this.database) return;
2979
+ const lastGcTimestamp = this.database.getMetadata("lastGcTimestamp");
2980
+ const now = Date.now();
2981
+ const intervalMs = this.config.indexing.gcIntervalDays * 24 * 60 * 60 * 1e3;
2982
+ let shouldRunGc = false;
2983
+ if (!lastGcTimestamp) {
2984
+ shouldRunGc = true;
2985
+ } else {
2986
+ const lastGcTime = parseInt(lastGcTimestamp, 10);
2987
+ if (!isNaN(lastGcTime) && now - lastGcTime > intervalMs) {
2988
+ shouldRunGc = true;
2989
+ }
2990
+ }
2991
+ if (shouldRunGc) {
2992
+ await this.healthCheck();
2993
+ this.database.setMetadata("lastGcTimestamp", now.toString());
2994
+ }
2995
+ }
2996
+ async maybeRunOrphanGc() {
2997
+ if (!this.database) return;
2998
+ const stats = this.database.getStats();
2999
+ if (!stats) return;
3000
+ const orphanCount = stats.embeddingCount - stats.chunkCount;
3001
+ if (orphanCount > this.config.indexing.gcOrphanThreshold) {
3002
+ this.database.gcOrphanEmbeddings();
3003
+ this.database.gcOrphanChunks();
3004
+ this.database.setMetadata("lastGcTimestamp", Date.now().toString());
3005
+ }
2944
3006
  }
2945
3007
  migrateFromLegacyIndex() {
2946
3008
  if (!this.store || !this.database) return;
2947
3009
  const allMetadata = this.store.getAllMetadata();
2948
3010
  const chunkIds = [];
3011
+ const chunkDataBatch = [];
2949
3012
  for (const { key, metadata } of allMetadata) {
2950
3013
  const chunkData = {
2951
3014
  chunkId: key,
@@ -2957,10 +3020,13 @@ var Indexer = class {
2957
3020
  name: metadata.name,
2958
3021
  language: metadata.language
2959
3022
  };
2960
- this.database.upsertChunk(chunkData);
3023
+ chunkDataBatch.push(chunkData);
2961
3024
  chunkIds.push(key);
2962
3025
  }
2963
- this.database.addChunksToBranch(this.currentBranch || "default", chunkIds);
3026
+ if (chunkDataBatch.length > 0) {
3027
+ this.database.upsertChunksBatch(chunkDataBatch);
3028
+ }
3029
+ this.database.addChunksToBranchBatch(this.currentBranch || "default", chunkIds);
2964
3030
  }
2965
3031
  async ensureInitialized() {
2966
3032
  if (!this.store || !this.provider || !this.invertedIndex || !this.detectedProvider || !this.database) {
@@ -3056,6 +3122,7 @@ var Indexer = class {
3056
3122
  }
3057
3123
  }
3058
3124
  }
3125
+ const chunkDataBatch = [];
3059
3126
  for (const parsed of parsedFiles) {
3060
3127
  currentFilePaths.add(parsed.path);
3061
3128
  if (parsed.chunks.length === 0) {
@@ -3083,7 +3150,7 @@ var Indexer = class {
3083
3150
  name: chunk.name,
3084
3151
  language: chunk.language
3085
3152
  };
3086
- database.upsertChunk(chunkData);
3153
+ chunkDataBatch.push(chunkData);
3087
3154
  if (existingChunks.get(id) === contentHash) {
3088
3155
  fileChunkCount++;
3089
3156
  continue;
@@ -3102,6 +3169,9 @@ var Indexer = class {
3102
3169
  fileChunkCount++;
3103
3170
  }
3104
3171
  }
3172
+ if (chunkDataBatch.length > 0) {
3173
+ database.upsertChunksBatch(chunkDataBatch);
3174
+ }
3105
3175
  let removedCount = 0;
3106
3176
  for (const [chunkId] of existingChunks) {
3107
3177
  if (!currentChunkIds.has(chunkId)) {
@@ -3115,7 +3185,7 @@ var Indexer = class {
3115
3185
  stats.removedChunks = removedCount;
3116
3186
  if (pendingChunks.length === 0 && removedCount === 0) {
3117
3187
  database.clearBranch(this.currentBranch);
3118
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3188
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3119
3189
  this.fileHashCache = currentFileHashes;
3120
3190
  this.saveFileHashCache();
3121
3191
  stats.durationMs = Date.now() - startTime;
@@ -3130,7 +3200,7 @@ var Indexer = class {
3130
3200
  }
3131
3201
  if (pendingChunks.length === 0) {
3132
3202
  database.clearBranch(this.currentBranch);
3133
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3203
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3134
3204
  store.save();
3135
3205
  invertedIndex.save();
3136
3206
  this.fileHashCache = currentFileHashes;
@@ -3166,7 +3236,12 @@ var Indexer = class {
3166
3236
  stats.indexedChunks++;
3167
3237
  }
3168
3238
  }
3169
- const queue = new PQueue({ concurrency: 1, interval: 4e3, intervalCap: 1 });
3239
+ const providerRateLimits = this.getProviderRateLimits(detectedProvider.provider);
3240
+ const queue = new PQueue({
3241
+ concurrency: providerRateLimits.concurrency,
3242
+ interval: providerRateLimits.intervalMs,
3243
+ intervalCap: providerRateLimits.concurrency
3244
+ });
3170
3245
  const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
3171
3246
  let rateLimitBackoffMs = 0;
3172
3247
  for (const batch of dynamicBatches) {
@@ -3182,15 +3257,13 @@ var Indexer = class {
3182
3257
  },
3183
3258
  {
3184
3259
  retries: this.config.indexing.retries,
3185
- minTimeout: Math.max(this.config.indexing.retryDelayMs, 5e3),
3186
- // Minimum 5s between retries
3187
- maxTimeout: 6e4,
3188
- // Max 60s backoff
3260
+ minTimeout: Math.max(this.config.indexing.retryDelayMs, providerRateLimits.minRetryMs),
3261
+ maxTimeout: providerRateLimits.maxRetryMs,
3189
3262
  factor: 2,
3190
3263
  onFailedAttempt: (error) => {
3191
3264
  const message = getErrorMessage(error);
3192
3265
  if (isRateLimitError(error)) {
3193
- rateLimitBackoffMs = Math.min(6e4, (rateLimitBackoffMs || 5e3) * 2);
3266
+ rateLimitBackoffMs = Math.min(providerRateLimits.maxRetryMs, (rateLimitBackoffMs || providerRateLimits.minRetryMs) * 2);
3194
3267
  console.error(
3195
3268
  `Rate limited (attempt ${error.attemptNumber}/${error.retriesLeft + error.attemptNumber}): waiting ${rateLimitBackoffMs / 1e3}s before retry...`
3196
3269
  );
@@ -3211,15 +3284,14 @@ var Indexer = class {
3211
3284
  metadata: chunk.metadata
3212
3285
  }));
3213
3286
  store.addBatch(items);
3214
- for (let i = 0; i < batch.length; i++) {
3215
- const chunk = batch[i];
3216
- const embedding = result.embeddings[i];
3217
- database.upsertEmbedding(
3218
- chunk.contentHash,
3219
- float32ArrayToBuffer(embedding),
3220
- chunk.text,
3221
- detectedProvider.modelInfo.model
3222
- );
3287
+ const embeddingBatchItems = batch.map((chunk, i) => ({
3288
+ contentHash: chunk.contentHash,
3289
+ embedding: float32ArrayToBuffer(result.embeddings[i]),
3290
+ chunkText: chunk.text,
3291
+ model: detectedProvider.modelInfo.model
3292
+ }));
3293
+ database.upsertEmbeddingsBatch(embeddingBatchItems);
3294
+ for (const chunk of batch) {
3223
3295
  invertedIndex.removeChunk(chunk.id);
3224
3296
  invertedIndex.addChunk(chunk.id, chunk.content);
3225
3297
  }
@@ -3248,11 +3320,14 @@ var Indexer = class {
3248
3320
  totalChunks: pendingChunks.length
3249
3321
  });
3250
3322
  database.clearBranch(this.currentBranch);
3251
- database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
3323
+ database.addChunksToBranchBatch(this.currentBranch, Array.from(currentChunkIds));
3252
3324
  store.save();
3253
3325
  invertedIndex.save();
3254
3326
  this.fileHashCache = currentFileHashes;
3255
3327
  this.saveFileHashCache();
3328
+ if (this.config.indexing.autoGc && stats.removedChunks > 0) {
3329
+ await this.maybeRunOrphanGc();
3330
+ }
3256
3331
  stats.durationMs = Date.now() - startTime;
3257
3332
  if (stats.failedChunks > 0) {
3258
3333
  stats.failedBatchesPath = this.failedBatchesPath;
@@ -3392,11 +3467,14 @@ var Indexer = class {
3392
3467
  };
3393
3468
  }
3394
3469
  async clearIndex() {
3395
- const { store, invertedIndex } = await this.ensureInitialized();
3470
+ const { store, invertedIndex, database } = await this.ensureInitialized();
3396
3471
  store.clear();
3397
3472
  store.save();
3398
3473
  invertedIndex.clear();
3399
3474
  invertedIndex.save();
3475
+ this.fileHashCache.clear();
3476
+ this.saveFileHashCache();
3477
+ database.clearBranch(this.currentBranch);
3400
3478
  }
3401
3479
  async healthCheck() {
3402
3480
  const { store, invertedIndex, database } = await this.ensureInitialized();
@@ -3588,7 +3666,7 @@ var ReaddirpStream = class extends Readable {
3588
3666
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
3589
3667
  const statMethod = opts.lstat ? lstat : stat;
3590
3668
  if (wantBigintFsStats) {
3591
- this._stat = (path8) => statMethod(path8, { bigint: true });
3669
+ this._stat = (path9) => statMethod(path9, { bigint: true });
3592
3670
  } else {
3593
3671
  this._stat = statMethod;
3594
3672
  }
@@ -3613,8 +3691,8 @@ var ReaddirpStream = class extends Readable {
3613
3691
  const par = this.parent;
3614
3692
  const fil = par && par.files;
3615
3693
  if (fil && fil.length > 0) {
3616
- const { path: path8, depth } = par;
3617
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
3694
+ const { path: path9, depth } = par;
3695
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path9));
3618
3696
  const awaited = await Promise.all(slice);
3619
3697
  for (const entry of awaited) {
3620
3698
  if (!entry)
@@ -3654,21 +3732,21 @@ var ReaddirpStream = class extends Readable {
3654
3732
  this.reading = false;
3655
3733
  }
3656
3734
  }
3657
- async _exploreDir(path8, depth) {
3735
+ async _exploreDir(path9, depth) {
3658
3736
  let files;
3659
3737
  try {
3660
- files = await readdir(path8, this._rdOptions);
3738
+ files = await readdir(path9, this._rdOptions);
3661
3739
  } catch (error) {
3662
3740
  this._onError(error);
3663
3741
  }
3664
- return { files, depth, path: path8 };
3742
+ return { files, depth, path: path9 };
3665
3743
  }
3666
- async _formatEntry(dirent, path8) {
3744
+ async _formatEntry(dirent, path9) {
3667
3745
  let entry;
3668
- const basename3 = this._isDirent ? dirent.name : dirent;
3746
+ const basename4 = this._isDirent ? dirent.name : dirent;
3669
3747
  try {
3670
- const fullPath = presolve(pjoin(path8, basename3));
3671
- entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
3748
+ const fullPath = presolve(pjoin(path9, basename4));
3749
+ entry = { path: prelative(this._root, fullPath), fullPath, basename: basename4 };
3672
3750
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
3673
3751
  } catch (err) {
3674
3752
  this._onError(err);
@@ -4067,16 +4145,16 @@ var delFromSet = (main, prop, item) => {
4067
4145
  };
4068
4146
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
4069
4147
  var FsWatchInstances = /* @__PURE__ */ new Map();
4070
- function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
4148
+ function createFsWatchInstance(path9, options, listener, errHandler, emitRaw) {
4071
4149
  const handleEvent = (rawEvent, evPath) => {
4072
- listener(path8);
4073
- emitRaw(rawEvent, evPath, { watchedPath: path8 });
4074
- if (evPath && path8 !== evPath) {
4075
- fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
4150
+ listener(path9);
4151
+ emitRaw(rawEvent, evPath, { watchedPath: path9 });
4152
+ if (evPath && path9 !== evPath) {
4153
+ fsWatchBroadcast(sp.resolve(path9, evPath), KEY_LISTENERS, sp.join(path9, evPath));
4076
4154
  }
4077
4155
  };
4078
4156
  try {
4079
- return fs_watch(path8, {
4157
+ return fs_watch(path9, {
4080
4158
  persistent: options.persistent
4081
4159
  }, handleEvent);
4082
4160
  } catch (error) {
@@ -4092,12 +4170,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
4092
4170
  listener(val1, val2, val3);
4093
4171
  });
4094
4172
  };
4095
- var setFsWatchListener = (path8, fullPath, options, handlers) => {
4173
+ var setFsWatchListener = (path9, fullPath, options, handlers) => {
4096
4174
  const { listener, errHandler, rawEmitter } = handlers;
4097
4175
  let cont = FsWatchInstances.get(fullPath);
4098
4176
  let watcher;
4099
4177
  if (!options.persistent) {
4100
- watcher = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
4178
+ watcher = createFsWatchInstance(path9, options, listener, errHandler, rawEmitter);
4101
4179
  if (!watcher)
4102
4180
  return;
4103
4181
  return watcher.close.bind(watcher);
@@ -4108,7 +4186,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4108
4186
  addAndConvert(cont, KEY_RAW, rawEmitter);
4109
4187
  } else {
4110
4188
  watcher = createFsWatchInstance(
4111
- path8,
4189
+ path9,
4112
4190
  options,
4113
4191
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
4114
4192
  errHandler,
@@ -4123,7 +4201,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4123
4201
  cont.watcherUnusable = true;
4124
4202
  if (isWindows && error.code === "EPERM") {
4125
4203
  try {
4126
- const fd = await open(path8, "r");
4204
+ const fd = await open(path9, "r");
4127
4205
  await fd.close();
4128
4206
  broadcastErr(error);
4129
4207
  } catch (err) {
@@ -4154,7 +4232,7 @@ var setFsWatchListener = (path8, fullPath, options, handlers) => {
4154
4232
  };
4155
4233
  };
4156
4234
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
4157
- var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
4235
+ var setFsWatchFileListener = (path9, fullPath, options, handlers) => {
4158
4236
  const { listener, rawEmitter } = handlers;
4159
4237
  let cont = FsWatchFileInstances.get(fullPath);
4160
4238
  const copts = cont && cont.options;
@@ -4176,7 +4254,7 @@ var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
4176
4254
  });
4177
4255
  const currmtime = curr.mtimeMs;
4178
4256
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
4179
- foreach(cont.listeners, (listener2) => listener2(path8, curr));
4257
+ foreach(cont.listeners, (listener2) => listener2(path9, curr));
4180
4258
  }
4181
4259
  })
4182
4260
  };
@@ -4206,13 +4284,13 @@ var NodeFsHandler = class {
4206
4284
  * @param listener on fs change
4207
4285
  * @returns closer for the watcher instance
4208
4286
  */
4209
- _watchWithNodeFs(path8, listener) {
4287
+ _watchWithNodeFs(path9, listener) {
4210
4288
  const opts = this.fsw.options;
4211
- const directory = sp.dirname(path8);
4212
- const basename3 = sp.basename(path8);
4289
+ const directory = sp.dirname(path9);
4290
+ const basename4 = sp.basename(path9);
4213
4291
  const parent = this.fsw._getWatchedDir(directory);
4214
- parent.add(basename3);
4215
- const absolutePath = sp.resolve(path8);
4292
+ parent.add(basename4);
4293
+ const absolutePath = sp.resolve(path9);
4216
4294
  const options = {
4217
4295
  persistent: opts.persistent
4218
4296
  };
@@ -4221,13 +4299,13 @@ var NodeFsHandler = class {
4221
4299
  let closer;
4222
4300
  if (opts.usePolling) {
4223
4301
  const enableBin = opts.interval !== opts.binaryInterval;
4224
- options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
4225
- closer = setFsWatchFileListener(path8, absolutePath, options, {
4302
+ options.interval = enableBin && isBinaryPath(basename4) ? opts.binaryInterval : opts.interval;
4303
+ closer = setFsWatchFileListener(path9, absolutePath, options, {
4226
4304
  listener,
4227
4305
  rawEmitter: this.fsw._emitRaw
4228
4306
  });
4229
4307
  } else {
4230
- closer = setFsWatchListener(path8, absolutePath, options, {
4308
+ closer = setFsWatchListener(path9, absolutePath, options, {
4231
4309
  listener,
4232
4310
  errHandler: this._boundHandleError,
4233
4311
  rawEmitter: this.fsw._emitRaw
@@ -4243,13 +4321,13 @@ var NodeFsHandler = class {
4243
4321
  if (this.fsw.closed) {
4244
4322
  return;
4245
4323
  }
4246
- const dirname4 = sp.dirname(file);
4247
- const basename3 = sp.basename(file);
4248
- const parent = this.fsw._getWatchedDir(dirname4);
4324
+ const dirname5 = sp.dirname(file);
4325
+ const basename4 = sp.basename(file);
4326
+ const parent = this.fsw._getWatchedDir(dirname5);
4249
4327
  let prevStats = stats;
4250
- if (parent.has(basename3))
4328
+ if (parent.has(basename4))
4251
4329
  return;
4252
- const listener = async (path8, newStats) => {
4330
+ const listener = async (path9, newStats) => {
4253
4331
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
4254
4332
  return;
4255
4333
  if (!newStats || newStats.mtimeMs === 0) {
@@ -4263,18 +4341,18 @@ var NodeFsHandler = class {
4263
4341
  this.fsw._emit(EV.CHANGE, file, newStats2);
4264
4342
  }
4265
4343
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
4266
- this.fsw._closeFile(path8);
4344
+ this.fsw._closeFile(path9);
4267
4345
  prevStats = newStats2;
4268
4346
  const closer2 = this._watchWithNodeFs(file, listener);
4269
4347
  if (closer2)
4270
- this.fsw._addPathCloser(path8, closer2);
4348
+ this.fsw._addPathCloser(path9, closer2);
4271
4349
  } else {
4272
4350
  prevStats = newStats2;
4273
4351
  }
4274
4352
  } catch (error) {
4275
- this.fsw._remove(dirname4, basename3);
4353
+ this.fsw._remove(dirname5, basename4);
4276
4354
  }
4277
- } else if (parent.has(basename3)) {
4355
+ } else if (parent.has(basename4)) {
4278
4356
  const at = newStats.atimeMs;
4279
4357
  const mt = newStats.mtimeMs;
4280
4358
  if (!at || at <= mt || mt !== prevStats.mtimeMs) {
@@ -4299,7 +4377,7 @@ var NodeFsHandler = class {
4299
4377
  * @param item basename of this item
4300
4378
  * @returns true if no more processing is needed for this entry.
4301
4379
  */
4302
- async _handleSymlink(entry, directory, path8, item) {
4380
+ async _handleSymlink(entry, directory, path9, item) {
4303
4381
  if (this.fsw.closed) {
4304
4382
  return;
4305
4383
  }
@@ -4309,7 +4387,7 @@ var NodeFsHandler = class {
4309
4387
  this.fsw._incrReadyCount();
4310
4388
  let linkPath;
4311
4389
  try {
4312
- linkPath = await fsrealpath(path8);
4390
+ linkPath = await fsrealpath(path9);
4313
4391
  } catch (e) {
4314
4392
  this.fsw._emitReady();
4315
4393
  return true;
@@ -4319,12 +4397,12 @@ var NodeFsHandler = class {
4319
4397
  if (dir.has(item)) {
4320
4398
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
4321
4399
  this.fsw._symlinkPaths.set(full, linkPath);
4322
- this.fsw._emit(EV.CHANGE, path8, entry.stats);
4400
+ this.fsw._emit(EV.CHANGE, path9, entry.stats);
4323
4401
  }
4324
4402
  } else {
4325
4403
  dir.add(item);
4326
4404
  this.fsw._symlinkPaths.set(full, linkPath);
4327
- this.fsw._emit(EV.ADD, path8, entry.stats);
4405
+ this.fsw._emit(EV.ADD, path9, entry.stats);
4328
4406
  }
4329
4407
  this.fsw._emitReady();
4330
4408
  return true;
@@ -4354,9 +4432,9 @@ var NodeFsHandler = class {
4354
4432
  return;
4355
4433
  }
4356
4434
  const item = entry.path;
4357
- let path8 = sp.join(directory, item);
4435
+ let path9 = sp.join(directory, item);
4358
4436
  current.add(item);
4359
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
4437
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path9, item)) {
4360
4438
  return;
4361
4439
  }
4362
4440
  if (this.fsw.closed) {
@@ -4365,8 +4443,8 @@ var NodeFsHandler = class {
4365
4443
  }
4366
4444
  if (item === target || !target && !previous.has(item)) {
4367
4445
  this.fsw._incrReadyCount();
4368
- path8 = sp.join(dir, sp.relative(dir, path8));
4369
- this._addToNodeFs(path8, initialAdd, wh, depth + 1);
4446
+ path9 = sp.join(dir, sp.relative(dir, path9));
4447
+ this._addToNodeFs(path9, initialAdd, wh, depth + 1);
4370
4448
  }
4371
4449
  }).on(EV.ERROR, this._boundHandleError);
4372
4450
  return new Promise((resolve4, reject) => {
@@ -4435,13 +4513,13 @@ var NodeFsHandler = class {
4435
4513
  * @param depth Child path actually targeted for watch
4436
4514
  * @param target Child path actually targeted for watch
4437
4515
  */
4438
- async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
4516
+ async _addToNodeFs(path9, initialAdd, priorWh, depth, target) {
4439
4517
  const ready = this.fsw._emitReady;
4440
- if (this.fsw._isIgnored(path8) || this.fsw.closed) {
4518
+ if (this.fsw._isIgnored(path9) || this.fsw.closed) {
4441
4519
  ready();
4442
4520
  return false;
4443
4521
  }
4444
- const wh = this.fsw._getWatchHelpers(path8);
4522
+ const wh = this.fsw._getWatchHelpers(path9);
4445
4523
  if (priorWh) {
4446
4524
  wh.filterPath = (entry) => priorWh.filterPath(entry);
4447
4525
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -4457,8 +4535,8 @@ var NodeFsHandler = class {
4457
4535
  const follow = this.fsw.options.followSymlinks;
4458
4536
  let closer;
4459
4537
  if (stats.isDirectory()) {
4460
- const absPath = sp.resolve(path8);
4461
- const targetPath = follow ? await fsrealpath(path8) : path8;
4538
+ const absPath = sp.resolve(path9);
4539
+ const targetPath = follow ? await fsrealpath(path9) : path9;
4462
4540
  if (this.fsw.closed)
4463
4541
  return;
4464
4542
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -4468,29 +4546,29 @@ var NodeFsHandler = class {
4468
4546
  this.fsw._symlinkPaths.set(absPath, targetPath);
4469
4547
  }
4470
4548
  } else if (stats.isSymbolicLink()) {
4471
- const targetPath = follow ? await fsrealpath(path8) : path8;
4549
+ const targetPath = follow ? await fsrealpath(path9) : path9;
4472
4550
  if (this.fsw.closed)
4473
4551
  return;
4474
4552
  const parent = sp.dirname(wh.watchPath);
4475
4553
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
4476
4554
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
4477
- closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
4555
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path9, wh, targetPath);
4478
4556
  if (this.fsw.closed)
4479
4557
  return;
4480
4558
  if (targetPath !== void 0) {
4481
- this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
4559
+ this.fsw._symlinkPaths.set(sp.resolve(path9), targetPath);
4482
4560
  }
4483
4561
  } else {
4484
4562
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
4485
4563
  }
4486
4564
  ready();
4487
4565
  if (closer)
4488
- this.fsw._addPathCloser(path8, closer);
4566
+ this.fsw._addPathCloser(path9, closer);
4489
4567
  return false;
4490
4568
  } catch (error) {
4491
4569
  if (this.fsw._handleError(error)) {
4492
4570
  ready();
4493
- return path8;
4571
+ return path9;
4494
4572
  }
4495
4573
  }
4496
4574
  }
@@ -4533,24 +4611,24 @@ function createPattern(matcher) {
4533
4611
  }
4534
4612
  return () => false;
4535
4613
  }
4536
- function normalizePath(path8) {
4537
- if (typeof path8 !== "string")
4614
+ function normalizePath(path9) {
4615
+ if (typeof path9 !== "string")
4538
4616
  throw new Error("string expected");
4539
- path8 = sp2.normalize(path8);
4540
- path8 = path8.replace(/\\/g, "/");
4617
+ path9 = sp2.normalize(path9);
4618
+ path9 = path9.replace(/\\/g, "/");
4541
4619
  let prepend = false;
4542
- if (path8.startsWith("//"))
4620
+ if (path9.startsWith("//"))
4543
4621
  prepend = true;
4544
- path8 = path8.replace(DOUBLE_SLASH_RE, "/");
4622
+ path9 = path9.replace(DOUBLE_SLASH_RE, "/");
4545
4623
  if (prepend)
4546
- path8 = "/" + path8;
4547
- return path8;
4624
+ path9 = "/" + path9;
4625
+ return path9;
4548
4626
  }
4549
4627
  function matchPatterns(patterns, testString, stats) {
4550
- const path8 = normalizePath(testString);
4628
+ const path9 = normalizePath(testString);
4551
4629
  for (let index = 0; index < patterns.length; index++) {
4552
4630
  const pattern = patterns[index];
4553
- if (pattern(path8, stats)) {
4631
+ if (pattern(path9, stats)) {
4554
4632
  return true;
4555
4633
  }
4556
4634
  }
@@ -4588,19 +4666,19 @@ var toUnix = (string) => {
4588
4666
  }
4589
4667
  return str;
4590
4668
  };
4591
- var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
4592
- var normalizeIgnored = (cwd = "") => (path8) => {
4593
- if (typeof path8 === "string") {
4594
- return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
4669
+ var normalizePathToUnix = (path9) => toUnix(sp2.normalize(toUnix(path9)));
4670
+ var normalizeIgnored = (cwd = "") => (path9) => {
4671
+ if (typeof path9 === "string") {
4672
+ return normalizePathToUnix(sp2.isAbsolute(path9) ? path9 : sp2.join(cwd, path9));
4595
4673
  } else {
4596
- return path8;
4674
+ return path9;
4597
4675
  }
4598
4676
  };
4599
- var getAbsolutePath = (path8, cwd) => {
4600
- if (sp2.isAbsolute(path8)) {
4601
- return path8;
4677
+ var getAbsolutePath = (path9, cwd) => {
4678
+ if (sp2.isAbsolute(path9)) {
4679
+ return path9;
4602
4680
  }
4603
- return sp2.join(cwd, path8);
4681
+ return sp2.join(cwd, path9);
4604
4682
  };
4605
4683
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
4606
4684
  var DirEntry = class {
@@ -4665,10 +4743,10 @@ var WatchHelper = class {
4665
4743
  dirParts;
4666
4744
  followSymlinks;
4667
4745
  statMethod;
4668
- constructor(path8, follow, fsw) {
4746
+ constructor(path9, follow, fsw) {
4669
4747
  this.fsw = fsw;
4670
- const watchPath = path8;
4671
- this.path = path8 = path8.replace(REPLACER_RE, "");
4748
+ const watchPath = path9;
4749
+ this.path = path9 = path9.replace(REPLACER_RE, "");
4672
4750
  this.watchPath = watchPath;
4673
4751
  this.fullWatchPath = sp2.resolve(watchPath);
4674
4752
  this.dirParts = [];
@@ -4808,20 +4886,20 @@ var FSWatcher = class extends EventEmitter2 {
4808
4886
  this._closePromise = void 0;
4809
4887
  let paths = unifyPaths(paths_);
4810
4888
  if (cwd) {
4811
- paths = paths.map((path8) => {
4812
- const absPath = getAbsolutePath(path8, cwd);
4889
+ paths = paths.map((path9) => {
4890
+ const absPath = getAbsolutePath(path9, cwd);
4813
4891
  return absPath;
4814
4892
  });
4815
4893
  }
4816
- paths.forEach((path8) => {
4817
- this._removeIgnoredPath(path8);
4894
+ paths.forEach((path9) => {
4895
+ this._removeIgnoredPath(path9);
4818
4896
  });
4819
4897
  this._userIgnored = void 0;
4820
4898
  if (!this._readyCount)
4821
4899
  this._readyCount = 0;
4822
4900
  this._readyCount += paths.length;
4823
- Promise.all(paths.map(async (path8) => {
4824
- const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
4901
+ Promise.all(paths.map(async (path9) => {
4902
+ const res = await this._nodeFsHandler._addToNodeFs(path9, !_internal, void 0, 0, _origAdd);
4825
4903
  if (res)
4826
4904
  this._emitReady();
4827
4905
  return res;
@@ -4843,17 +4921,17 @@ var FSWatcher = class extends EventEmitter2 {
4843
4921
  return this;
4844
4922
  const paths = unifyPaths(paths_);
4845
4923
  const { cwd } = this.options;
4846
- paths.forEach((path8) => {
4847
- if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
4924
+ paths.forEach((path9) => {
4925
+ if (!sp2.isAbsolute(path9) && !this._closers.has(path9)) {
4848
4926
  if (cwd)
4849
- path8 = sp2.join(cwd, path8);
4850
- path8 = sp2.resolve(path8);
4927
+ path9 = sp2.join(cwd, path9);
4928
+ path9 = sp2.resolve(path9);
4851
4929
  }
4852
- this._closePath(path8);
4853
- this._addIgnoredPath(path8);
4854
- if (this._watched.has(path8)) {
4930
+ this._closePath(path9);
4931
+ this._addIgnoredPath(path9);
4932
+ if (this._watched.has(path9)) {
4855
4933
  this._addIgnoredPath({
4856
- path: path8,
4934
+ path: path9,
4857
4935
  recursive: true
4858
4936
  });
4859
4937
  }
@@ -4917,38 +4995,38 @@ var FSWatcher = class extends EventEmitter2 {
4917
4995
  * @param stats arguments to be passed with event
4918
4996
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
4919
4997
  */
4920
- async _emit(event, path8, stats) {
4998
+ async _emit(event, path9, stats) {
4921
4999
  if (this.closed)
4922
5000
  return;
4923
5001
  const opts = this.options;
4924
5002
  if (isWindows)
4925
- path8 = sp2.normalize(path8);
5003
+ path9 = sp2.normalize(path9);
4926
5004
  if (opts.cwd)
4927
- path8 = sp2.relative(opts.cwd, path8);
4928
- const args = [path8];
5005
+ path9 = sp2.relative(opts.cwd, path9);
5006
+ const args = [path9];
4929
5007
  if (stats != null)
4930
5008
  args.push(stats);
4931
5009
  const awf = opts.awaitWriteFinish;
4932
5010
  let pw;
4933
- if (awf && (pw = this._pendingWrites.get(path8))) {
5011
+ if (awf && (pw = this._pendingWrites.get(path9))) {
4934
5012
  pw.lastChange = /* @__PURE__ */ new Date();
4935
5013
  return this;
4936
5014
  }
4937
5015
  if (opts.atomic) {
4938
5016
  if (event === EVENTS.UNLINK) {
4939
- this._pendingUnlinks.set(path8, [event, ...args]);
5017
+ this._pendingUnlinks.set(path9, [event, ...args]);
4940
5018
  setTimeout(() => {
4941
- this._pendingUnlinks.forEach((entry, path9) => {
5019
+ this._pendingUnlinks.forEach((entry, path10) => {
4942
5020
  this.emit(...entry);
4943
5021
  this.emit(EVENTS.ALL, ...entry);
4944
- this._pendingUnlinks.delete(path9);
5022
+ this._pendingUnlinks.delete(path10);
4945
5023
  });
4946
5024
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
4947
5025
  return this;
4948
5026
  }
4949
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
5027
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path9)) {
4950
5028
  event = EVENTS.CHANGE;
4951
- this._pendingUnlinks.delete(path8);
5029
+ this._pendingUnlinks.delete(path9);
4952
5030
  }
4953
5031
  }
4954
5032
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -4966,16 +5044,16 @@ var FSWatcher = class extends EventEmitter2 {
4966
5044
  this.emitWithAll(event, args);
4967
5045
  }
4968
5046
  };
4969
- this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
5047
+ this._awaitWriteFinish(path9, awf.stabilityThreshold, event, awfEmit);
4970
5048
  return this;
4971
5049
  }
4972
5050
  if (event === EVENTS.CHANGE) {
4973
- const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
5051
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path9, 50);
4974
5052
  if (isThrottled)
4975
5053
  return this;
4976
5054
  }
4977
5055
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
4978
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
5056
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path9) : path9;
4979
5057
  let stats2;
4980
5058
  try {
4981
5059
  stats2 = await stat3(fullPath);
@@ -5006,23 +5084,23 @@ var FSWatcher = class extends EventEmitter2 {
5006
5084
  * @param timeout duration of time to suppress duplicate actions
5007
5085
  * @returns tracking object or false if action should be suppressed
5008
5086
  */
5009
- _throttle(actionType, path8, timeout) {
5087
+ _throttle(actionType, path9, timeout) {
5010
5088
  if (!this._throttled.has(actionType)) {
5011
5089
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
5012
5090
  }
5013
5091
  const action = this._throttled.get(actionType);
5014
5092
  if (!action)
5015
5093
  throw new Error("invalid throttle");
5016
- const actionPath = action.get(path8);
5094
+ const actionPath = action.get(path9);
5017
5095
  if (actionPath) {
5018
5096
  actionPath.count++;
5019
5097
  return false;
5020
5098
  }
5021
5099
  let timeoutObject;
5022
5100
  const clear = () => {
5023
- const item = action.get(path8);
5101
+ const item = action.get(path9);
5024
5102
  const count = item ? item.count : 0;
5025
- action.delete(path8);
5103
+ action.delete(path9);
5026
5104
  clearTimeout(timeoutObject);
5027
5105
  if (item)
5028
5106
  clearTimeout(item.timeoutObject);
@@ -5030,7 +5108,7 @@ var FSWatcher = class extends EventEmitter2 {
5030
5108
  };
5031
5109
  timeoutObject = setTimeout(clear, timeout);
5032
5110
  const thr = { timeoutObject, clear, count: 0 };
5033
- action.set(path8, thr);
5111
+ action.set(path9, thr);
5034
5112
  return thr;
5035
5113
  }
5036
5114
  _incrReadyCount() {
@@ -5044,44 +5122,44 @@ var FSWatcher = class extends EventEmitter2 {
5044
5122
  * @param event
5045
5123
  * @param awfEmit Callback to be called when ready for event to be emitted.
5046
5124
  */
5047
- _awaitWriteFinish(path8, threshold, event, awfEmit) {
5125
+ _awaitWriteFinish(path9, threshold, event, awfEmit) {
5048
5126
  const awf = this.options.awaitWriteFinish;
5049
5127
  if (typeof awf !== "object")
5050
5128
  return;
5051
5129
  const pollInterval = awf.pollInterval;
5052
5130
  let timeoutHandler;
5053
- let fullPath = path8;
5054
- if (this.options.cwd && !sp2.isAbsolute(path8)) {
5055
- fullPath = sp2.join(this.options.cwd, path8);
5131
+ let fullPath = path9;
5132
+ if (this.options.cwd && !sp2.isAbsolute(path9)) {
5133
+ fullPath = sp2.join(this.options.cwd, path9);
5056
5134
  }
5057
5135
  const now = /* @__PURE__ */ new Date();
5058
5136
  const writes = this._pendingWrites;
5059
5137
  function awaitWriteFinishFn(prevStat) {
5060
5138
  statcb(fullPath, (err, curStat) => {
5061
- if (err || !writes.has(path8)) {
5139
+ if (err || !writes.has(path9)) {
5062
5140
  if (err && err.code !== "ENOENT")
5063
5141
  awfEmit(err);
5064
5142
  return;
5065
5143
  }
5066
5144
  const now2 = Number(/* @__PURE__ */ new Date());
5067
5145
  if (prevStat && curStat.size !== prevStat.size) {
5068
- writes.get(path8).lastChange = now2;
5146
+ writes.get(path9).lastChange = now2;
5069
5147
  }
5070
- const pw = writes.get(path8);
5148
+ const pw = writes.get(path9);
5071
5149
  const df = now2 - pw.lastChange;
5072
5150
  if (df >= threshold) {
5073
- writes.delete(path8);
5151
+ writes.delete(path9);
5074
5152
  awfEmit(void 0, curStat);
5075
5153
  } else {
5076
5154
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
5077
5155
  }
5078
5156
  });
5079
5157
  }
5080
- if (!writes.has(path8)) {
5081
- writes.set(path8, {
5158
+ if (!writes.has(path9)) {
5159
+ writes.set(path9, {
5082
5160
  lastChange: now,
5083
5161
  cancelWait: () => {
5084
- writes.delete(path8);
5162
+ writes.delete(path9);
5085
5163
  clearTimeout(timeoutHandler);
5086
5164
  return event;
5087
5165
  }
@@ -5092,8 +5170,8 @@ var FSWatcher = class extends EventEmitter2 {
5092
5170
  /**
5093
5171
  * Determines whether user has asked to ignore this path.
5094
5172
  */
5095
- _isIgnored(path8, stats) {
5096
- if (this.options.atomic && DOT_RE.test(path8))
5173
+ _isIgnored(path9, stats) {
5174
+ if (this.options.atomic && DOT_RE.test(path9))
5097
5175
  return true;
5098
5176
  if (!this._userIgnored) {
5099
5177
  const { cwd } = this.options;
@@ -5103,17 +5181,17 @@ var FSWatcher = class extends EventEmitter2 {
5103
5181
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
5104
5182
  this._userIgnored = anymatch(list, void 0);
5105
5183
  }
5106
- return this._userIgnored(path8, stats);
5184
+ return this._userIgnored(path9, stats);
5107
5185
  }
5108
- _isntIgnored(path8, stat4) {
5109
- return !this._isIgnored(path8, stat4);
5186
+ _isntIgnored(path9, stat4) {
5187
+ return !this._isIgnored(path9, stat4);
5110
5188
  }
5111
5189
  /**
5112
5190
  * Provides a set of common helpers and properties relating to symlink handling.
5113
5191
  * @param path file or directory pattern being watched
5114
5192
  */
5115
- _getWatchHelpers(path8) {
5116
- return new WatchHelper(path8, this.options.followSymlinks, this);
5193
+ _getWatchHelpers(path9) {
5194
+ return new WatchHelper(path9, this.options.followSymlinks, this);
5117
5195
  }
5118
5196
  // Directory helpers
5119
5197
  // -----------------
@@ -5145,63 +5223,63 @@ var FSWatcher = class extends EventEmitter2 {
5145
5223
  * @param item base path of item/directory
5146
5224
  */
5147
5225
  _remove(directory, item, isDirectory) {
5148
- const path8 = sp2.join(directory, item);
5149
- const fullPath = sp2.resolve(path8);
5150
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
5151
- if (!this._throttle("remove", path8, 100))
5226
+ const path9 = sp2.join(directory, item);
5227
+ const fullPath = sp2.resolve(path9);
5228
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path9) || this._watched.has(fullPath);
5229
+ if (!this._throttle("remove", path9, 100))
5152
5230
  return;
5153
5231
  if (!isDirectory && this._watched.size === 1) {
5154
5232
  this.add(directory, item, true);
5155
5233
  }
5156
- const wp = this._getWatchedDir(path8);
5234
+ const wp = this._getWatchedDir(path9);
5157
5235
  const nestedDirectoryChildren = wp.getChildren();
5158
- nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
5236
+ nestedDirectoryChildren.forEach((nested) => this._remove(path9, nested));
5159
5237
  const parent = this._getWatchedDir(directory);
5160
5238
  const wasTracked = parent.has(item);
5161
5239
  parent.remove(item);
5162
5240
  if (this._symlinkPaths.has(fullPath)) {
5163
5241
  this._symlinkPaths.delete(fullPath);
5164
5242
  }
5165
- let relPath = path8;
5243
+ let relPath = path9;
5166
5244
  if (this.options.cwd)
5167
- relPath = sp2.relative(this.options.cwd, path8);
5245
+ relPath = sp2.relative(this.options.cwd, path9);
5168
5246
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
5169
5247
  const event = this._pendingWrites.get(relPath).cancelWait();
5170
5248
  if (event === EVENTS.ADD)
5171
5249
  return;
5172
5250
  }
5173
- this._watched.delete(path8);
5251
+ this._watched.delete(path9);
5174
5252
  this._watched.delete(fullPath);
5175
5253
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
5176
- if (wasTracked && !this._isIgnored(path8))
5177
- this._emit(eventName, path8);
5178
- this._closePath(path8);
5254
+ if (wasTracked && !this._isIgnored(path9))
5255
+ this._emit(eventName, path9);
5256
+ this._closePath(path9);
5179
5257
  }
5180
5258
  /**
5181
5259
  * Closes all watchers for a path
5182
5260
  */
5183
- _closePath(path8) {
5184
- this._closeFile(path8);
5185
- const dir = sp2.dirname(path8);
5186
- this._getWatchedDir(dir).remove(sp2.basename(path8));
5261
+ _closePath(path9) {
5262
+ this._closeFile(path9);
5263
+ const dir = sp2.dirname(path9);
5264
+ this._getWatchedDir(dir).remove(sp2.basename(path9));
5187
5265
  }
5188
5266
  /**
5189
5267
  * Closes only file-specific watchers
5190
5268
  */
5191
- _closeFile(path8) {
5192
- const closers = this._closers.get(path8);
5269
+ _closeFile(path9) {
5270
+ const closers = this._closers.get(path9);
5193
5271
  if (!closers)
5194
5272
  return;
5195
5273
  closers.forEach((closer) => closer());
5196
- this._closers.delete(path8);
5274
+ this._closers.delete(path9);
5197
5275
  }
5198
- _addPathCloser(path8, closer) {
5276
+ _addPathCloser(path9, closer) {
5199
5277
  if (!closer)
5200
5278
  return;
5201
- let list = this._closers.get(path8);
5279
+ let list = this._closers.get(path9);
5202
5280
  if (!list) {
5203
5281
  list = [];
5204
- this._closers.set(path8, list);
5282
+ this._closers.set(path9, list);
5205
5283
  }
5206
5284
  list.push(closer);
5207
5285
  }
@@ -5296,7 +5374,7 @@ var FileWatcher = class {
5296
5374
  return;
5297
5375
  }
5298
5376
  const changes = Array.from(this.pendingChanges.entries()).map(
5299
- ([path8, type]) => ({ path: path8, type })
5377
+ ([path9, type]) => ({ path: path9, type })
5300
5378
  );
5301
5379
  this.pendingChanges.clear();
5302
5380
  try {
@@ -5476,7 +5554,7 @@ var index_codebase = tool({
5476
5554
  estimateOnly: z.boolean().optional().default(false).describe("Only show cost estimate without indexing"),
5477
5555
  verbose: z.boolean().optional().default(false).describe("Show detailed info about skipped files and parsing failures")
5478
5556
  },
5479
- async execute(args) {
5557
+ async execute(args, context) {
5480
5558
  const indexer = getIndexer();
5481
5559
  if (args.estimateOnly) {
5482
5560
  const estimate = await indexer.estimateCost();
@@ -5485,7 +5563,19 @@ var index_codebase = tool({
5485
5563
  if (args.force) {
5486
5564
  await indexer.clearIndex();
5487
5565
  }
5488
- const stats = await indexer.index();
5566
+ const stats = await indexer.index((progress) => {
5567
+ context.metadata({
5568
+ title: formatProgressTitle(progress),
5569
+ metadata: {
5570
+ phase: progress.phase,
5571
+ filesProcessed: progress.filesProcessed,
5572
+ totalFiles: progress.totalFiles,
5573
+ chunksProcessed: progress.chunksProcessed,
5574
+ totalChunks: progress.totalChunks,
5575
+ percentage: calculatePercentage(progress)
5576
+ }
5577
+ });
5578
+ });
5489
5579
  return formatIndexStats(stats, args.verbose ?? false);
5490
5580
  }
5491
5581
  });
@@ -5584,12 +5674,90 @@ function formatStatus(status) {
5584
5674
  }
5585
5675
  return lines.join("\n");
5586
5676
  }
5677
+ function formatProgressTitle(progress) {
5678
+ switch (progress.phase) {
5679
+ case "scanning":
5680
+ return "Scanning files...";
5681
+ case "parsing":
5682
+ return `Parsing: ${progress.filesProcessed}/${progress.totalFiles} files`;
5683
+ case "embedding":
5684
+ return `Embedding: ${progress.chunksProcessed}/${progress.totalChunks} chunks`;
5685
+ case "storing":
5686
+ return "Storing index...";
5687
+ case "complete":
5688
+ return "Indexing complete";
5689
+ default:
5690
+ return "Indexing...";
5691
+ }
5692
+ }
5693
+ function calculatePercentage(progress) {
5694
+ if (progress.phase === "scanning") return 0;
5695
+ if (progress.phase === "complete") return 100;
5696
+ if (progress.phase === "parsing") {
5697
+ if (progress.totalFiles === 0) return 5;
5698
+ return Math.round(5 + progress.filesProcessed / progress.totalFiles * 15);
5699
+ }
5700
+ if (progress.phase === "embedding") {
5701
+ if (progress.totalChunks === 0) return 20;
5702
+ return Math.round(20 + progress.chunksProcessed / progress.totalChunks * 70);
5703
+ }
5704
+ if (progress.phase === "storing") return 95;
5705
+ return 0;
5706
+ }
5707
+
5708
+ // src/commands/loader.ts
5709
+ import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync5 } from "fs";
5710
+ import * as path7 from "path";
5711
+ function parseFrontmatter(content) {
5712
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
5713
+ const match = content.match(frontmatterRegex);
5714
+ if (!match) {
5715
+ return { frontmatter: {}, body: content.trim() };
5716
+ }
5717
+ const frontmatterLines = match[1].split("\n");
5718
+ const frontmatter = {};
5719
+ for (const line of frontmatterLines) {
5720
+ const colonIndex = line.indexOf(":");
5721
+ if (colonIndex > 0) {
5722
+ const key = line.slice(0, colonIndex).trim();
5723
+ const value = line.slice(colonIndex + 1).trim();
5724
+ frontmatter[key] = value;
5725
+ }
5726
+ }
5727
+ return { frontmatter, body: match[2].trim() };
5728
+ }
5729
+ function loadCommandsFromDirectory(commandsDir) {
5730
+ const commands = /* @__PURE__ */ new Map();
5731
+ if (!existsSync5(commandsDir)) {
5732
+ return commands;
5733
+ }
5734
+ const files = readdirSync2(commandsDir).filter((f) => f.endsWith(".md"));
5735
+ for (const file of files) {
5736
+ const filePath = path7.join(commandsDir, file);
5737
+ const content = readFileSync5(filePath, "utf-8");
5738
+ const { frontmatter, body } = parseFrontmatter(content);
5739
+ const name = path7.basename(file, ".md");
5740
+ const description = frontmatter.description || `Run the ${name} command`;
5741
+ commands.set(name, {
5742
+ description,
5743
+ template: body
5744
+ });
5745
+ }
5746
+ return commands;
5747
+ }
5587
5748
 
5588
5749
  // src/index.ts
5750
+ function getCommandsDir() {
5751
+ let currentDir = process.cwd();
5752
+ if (typeof import.meta !== "undefined" && import.meta.url) {
5753
+ currentDir = path8.dirname(fileURLToPath2(import.meta.url));
5754
+ }
5755
+ return path8.join(currentDir, "..", "commands");
5756
+ }
5589
5757
  function loadJsonFile(filePath) {
5590
5758
  try {
5591
- if (existsSync5(filePath)) {
5592
- const content = readFileSync5(filePath, "utf-8");
5759
+ if (existsSync6(filePath)) {
5760
+ const content = readFileSync6(filePath, "utf-8");
5593
5761
  return JSON.parse(content);
5594
5762
  }
5595
5763
  } catch {
@@ -5597,11 +5765,11 @@ function loadJsonFile(filePath) {
5597
5765
  return null;
5598
5766
  }
5599
5767
  function loadPluginConfig(projectRoot) {
5600
- const projectConfig = loadJsonFile(path7.join(projectRoot, ".opencode", "codebase-index.json"));
5768
+ const projectConfig = loadJsonFile(path8.join(projectRoot, ".opencode", "codebase-index.json"));
5601
5769
  if (projectConfig) {
5602
5770
  return projectConfig;
5603
5771
  }
5604
- const globalConfigPath = path7.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
5772
+ const globalConfigPath = path8.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
5605
5773
  const globalConfig = loadJsonFile(globalConfigPath);
5606
5774
  if (globalConfig) {
5607
5775
  return globalConfig;
@@ -5633,36 +5801,11 @@ var plugin = async ({ directory }) => {
5633
5801
  },
5634
5802
  async config(cfg) {
5635
5803
  cfg.command = cfg.command ?? {};
5636
- cfg.command["search"] = {
5637
- description: "Search codebase by meaning using semantic search",
5638
- template: `Use the \`codebase_search\` tool to find code related to: $ARGUMENTS
5639
-
5640
- If the index doesn't exist yet, run \`index_codebase\` first.
5641
-
5642
- Return the most relevant results with file paths and line numbers.`
5643
- };
5644
- cfg.command["find"] = {
5645
- description: "Find code using hybrid approach (semantic + grep)",
5646
- template: `Find code related to: $ARGUMENTS
5647
-
5648
- Strategy:
5649
- 1. First use \`codebase_search\` to find semantically related code
5650
- 2. From the results, identify specific function/class names
5651
- 3. Use grep to find all occurrences of those identifiers
5652
- 4. Combine findings into a comprehensive answer
5653
-
5654
- If the semantic index doesn't exist, run \`index_codebase\` first.`
5655
- };
5656
- cfg.command["index"] = {
5657
- description: "Index the codebase for semantic search",
5658
- template: `Run the \`index_codebase\` tool to create or update the semantic search index.
5659
-
5660
- Show progress and final statistics including:
5661
- - Number of files processed
5662
- - Number of chunks indexed
5663
- - Tokens used
5664
- - Duration`
5665
- };
5804
+ const commandsDir = getCommandsDir();
5805
+ const commands = loadCommandsFromDirectory(commandsDir);
5806
+ for (const [name, definition] of commands) {
5807
+ cfg.command[name] = definition;
5808
+ }
5666
5809
  }
5667
5810
  };
5668
5811
  };