opencode-codebase-index 0.1.11 → 0.2.1

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(path7, checkUnignored, mode) {
494
+ test(path8, 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(path7);
503
+ const matched = rule[mode].test(path8);
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 = (path7, originalPath, doThrow) => {
525
- if (!isString(path7)) {
524
+ var checkPath = (path8, originalPath, doThrow) => {
525
+ if (!isString(path8)) {
526
526
  return doThrow(
527
527
  `path must be a string, but got \`${originalPath}\``,
528
528
  TypeError
529
529
  );
530
530
  }
531
- if (!path7) {
531
+ if (!path8) {
532
532
  return doThrow(`path must not be empty`, TypeError);
533
533
  }
534
- if (checkPath.isNotRelative(path7)) {
534
+ if (checkPath.isNotRelative(path8)) {
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 = (path7) => REGEX_TEST_INVALID_PATH.test(path7);
543
+ var isNotRelative = (path8) => REGEX_TEST_INVALID_PATH.test(path8);
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 path7 = originalPath && checkPath.convert(originalPath);
573
+ const path8 = originalPath && checkPath.convert(originalPath);
574
574
  checkPath(
575
- path7,
575
+ path8,
576
576
  originalPath,
577
577
  this._strictPathCheck ? throwError : RETURN_FALSE
578
578
  );
579
- return this._t(path7, cache, checkUnignored, slices);
579
+ return this._t(path8, cache, checkUnignored, slices);
580
580
  }
581
- checkIgnore(path7) {
582
- if (!REGEX_TEST_TRAILING_SLASH.test(path7)) {
583
- return this.test(path7);
581
+ checkIgnore(path8) {
582
+ if (!REGEX_TEST_TRAILING_SLASH.test(path8)) {
583
+ return this.test(path8);
584
584
  }
585
- const slices = path7.split(SLASH2).filter(Boolean);
585
+ const slices = path8.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(path7, false, MODE_CHECK_IGNORE);
598
+ return this._rules.test(path8, false, MODE_CHECK_IGNORE);
599
599
  }
600
- _t(path7, cache, checkUnignored, slices) {
601
- if (path7 in cache) {
602
- return cache[path7];
600
+ _t(path8, cache, checkUnignored, slices) {
601
+ if (path8 in cache) {
602
+ return cache[path8];
603
603
  }
604
604
  if (!slices) {
605
- slices = path7.split(SLASH2).filter(Boolean);
605
+ slices = path8.split(SLASH2).filter(Boolean);
606
606
  }
607
607
  slices.pop();
608
608
  if (!slices.length) {
609
- return cache[path7] = this._rules.test(path7, checkUnignored, MODE_IGNORE);
609
+ return cache[path8] = this._rules.test(path8, 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[path7] = parent.ignored ? parent : this._rules.test(path7, checkUnignored, MODE_IGNORE);
617
+ return cache[path8] = parent.ignored ? parent : this._rules.test(path8, checkUnignored, MODE_IGNORE);
618
618
  }
619
- ignores(path7) {
620
- return this._test(path7, this._ignoreCache, false).ignored;
619
+ ignores(path8) {
620
+ return this._test(path8, this._ignoreCache, false).ignored;
621
621
  }
622
622
  createFilter() {
623
- return (path7) => !this.ignores(path7);
623
+ return (path8) => !this.ignores(path8);
624
624
  }
625
625
  filter(paths) {
626
626
  return makeArray(paths).filter(this.createFilter());
627
627
  }
628
628
  // @returns {TestResult}
629
- test(path7) {
630
- return this._test(path7, this._testCache, true);
629
+ test(path8) {
630
+ return this._test(path8, this._testCache, true);
631
631
  }
632
632
  };
633
633
  var factory = (options) => new Ignore2(options);
634
- var isPathValid = (path7) => checkPath(path7 && checkPath.convert(path7), path7, RETURN_FALSE);
634
+ var isPathValid = (path8) => checkPath(path8 && checkPath.convert(path8), path8, 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 = (path7) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path7) || isNotRelative(path7);
639
+ checkPath.isNotRelative = (path8) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path8) || isNotRelative(path8);
640
640
  };
641
641
  if (
642
642
  // Detect `process` so that it can run in browsers.
@@ -652,8 +652,8 @@ var require_ignore = __commonJS({
652
652
  });
653
653
 
654
654
  // src/index.ts
655
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
656
- import * as path6 from "path";
655
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
656
+ import * as path7 from "path";
657
657
 
658
658
  // src/config/schema.ts
659
659
  var DEFAULT_INCLUDE = [
@@ -806,8 +806,8 @@ function getDefaultModelForProvider(provider) {
806
806
  }
807
807
 
808
808
  // src/indexer/index.ts
809
- import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync, promises as fsPromises2 } from "fs";
810
- import * as path4 from "path";
809
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, promises as fsPromises2 } from "fs";
810
+ import * as path5 from "path";
811
811
 
812
812
  // node_modules/eventemitter3/index.mjs
813
813
  var import_index = __toESM(require_eventemitter3(), 1);
@@ -2188,7 +2188,10 @@ function shouldIncludeFile(filePath, projectRoot, includePatterns, excludePatter
2188
2188
  return false;
2189
2189
  }
2190
2190
  function matchGlob(filePath, pattern) {
2191
- const regexPattern = pattern.replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, p1) => `(${p1.split(",").join("|")})`);
2191
+ let regexPattern = pattern.replace(/\*\*/g, "<<<DOUBLESTAR>>>").replace(/\*/g, "[^/]*").replace(/<<<DOUBLESTAR>>>/g, ".*").replace(/\?/g, ".").replace(/\{([^}]+)\}/g, (_, p1) => `(${p1.split(",").join("|")})`);
2192
+ if (regexPattern.startsWith(".*/")) {
2193
+ regexPattern = `(.*\\/)?${regexPattern.slice(3)}`;
2194
+ }
2192
2195
  const regex = new RegExp(`^${regexPattern}$`);
2193
2196
  return regex.test(filePath);
2194
2197
  }
@@ -2659,37 +2662,181 @@ var InvertedIndex = class {
2659
2662
  return this.inner.documentCount();
2660
2663
  }
2661
2664
  };
2665
+ var Database = class {
2666
+ inner;
2667
+ constructor(dbPath) {
2668
+ this.inner = new native.Database(dbPath);
2669
+ }
2670
+ embeddingExists(contentHash) {
2671
+ return this.inner.embeddingExists(contentHash);
2672
+ }
2673
+ getEmbedding(contentHash) {
2674
+ return this.inner.getEmbedding(contentHash) ?? null;
2675
+ }
2676
+ upsertEmbedding(contentHash, embedding, chunkText, model) {
2677
+ this.inner.upsertEmbedding(contentHash, embedding, chunkText, model);
2678
+ }
2679
+ getMissingEmbeddings(contentHashes) {
2680
+ return this.inner.getMissingEmbeddings(contentHashes);
2681
+ }
2682
+ upsertChunk(chunk) {
2683
+ this.inner.upsertChunk(chunk);
2684
+ }
2685
+ getChunk(chunkId) {
2686
+ return this.inner.getChunk(chunkId) ?? null;
2687
+ }
2688
+ getChunksByFile(filePath) {
2689
+ return this.inner.getChunksByFile(filePath);
2690
+ }
2691
+ deleteChunksByFile(filePath) {
2692
+ return this.inner.deleteChunksByFile(filePath);
2693
+ }
2694
+ addChunksToBranch(branch, chunkIds) {
2695
+ this.inner.addChunksToBranch(branch, chunkIds);
2696
+ }
2697
+ clearBranch(branch) {
2698
+ return this.inner.clearBranch(branch);
2699
+ }
2700
+ getBranchChunkIds(branch) {
2701
+ return this.inner.getBranchChunkIds(branch);
2702
+ }
2703
+ getBranchDelta(branch, baseBranch) {
2704
+ return this.inner.getBranchDelta(branch, baseBranch);
2705
+ }
2706
+ chunkExistsOnBranch(branch, chunkId) {
2707
+ return this.inner.chunkExistsOnBranch(branch, chunkId);
2708
+ }
2709
+ getAllBranches() {
2710
+ return this.inner.getAllBranches();
2711
+ }
2712
+ getMetadata(key) {
2713
+ return this.inner.getMetadata(key) ?? null;
2714
+ }
2715
+ setMetadata(key, value) {
2716
+ this.inner.setMetadata(key, value);
2717
+ }
2718
+ deleteMetadata(key) {
2719
+ return this.inner.deleteMetadata(key);
2720
+ }
2721
+ gcOrphanEmbeddings() {
2722
+ return this.inner.gcOrphanEmbeddings();
2723
+ }
2724
+ gcOrphanChunks() {
2725
+ return this.inner.gcOrphanChunks();
2726
+ }
2727
+ getStats() {
2728
+ return this.inner.getStats();
2729
+ }
2730
+ };
2731
+
2732
+ // src/git/index.ts
2733
+ import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
2734
+ import * as path4 from "path";
2735
+ import { execSync } from "child_process";
2736
+ function isGitRepo(dir) {
2737
+ return existsSync3(path4.join(dir, ".git"));
2738
+ }
2739
+ function getCurrentBranch(repoRoot) {
2740
+ const headPath = path4.join(repoRoot, ".git", "HEAD");
2741
+ if (!existsSync3(headPath)) {
2742
+ return null;
2743
+ }
2744
+ try {
2745
+ const headContent = readFileSync3(headPath, "utf-8").trim();
2746
+ const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
2747
+ if (match) {
2748
+ return match[1];
2749
+ }
2750
+ if (/^[0-9a-f]{40}$/i.test(headContent)) {
2751
+ return headContent.slice(0, 7);
2752
+ }
2753
+ return null;
2754
+ } catch {
2755
+ return null;
2756
+ }
2757
+ }
2758
+ function getBaseBranch(repoRoot) {
2759
+ const candidates = ["main", "master", "develop", "trunk"];
2760
+ for (const candidate of candidates) {
2761
+ const refPath = path4.join(repoRoot, ".git", "refs", "heads", candidate);
2762
+ if (existsSync3(refPath)) {
2763
+ return candidate;
2764
+ }
2765
+ const packedRefsPath = path4.join(repoRoot, ".git", "packed-refs");
2766
+ if (existsSync3(packedRefsPath)) {
2767
+ try {
2768
+ const content = readFileSync3(packedRefsPath, "utf-8");
2769
+ if (content.includes(`refs/heads/${candidate}`)) {
2770
+ return candidate;
2771
+ }
2772
+ } catch {
2773
+ }
2774
+ }
2775
+ }
2776
+ try {
2777
+ const result = execSync("git remote show origin", {
2778
+ cwd: repoRoot,
2779
+ encoding: "utf-8",
2780
+ stdio: ["pipe", "pipe", "pipe"]
2781
+ });
2782
+ const match = result.match(/HEAD branch: (.+)/);
2783
+ if (match) {
2784
+ return match[1].trim();
2785
+ }
2786
+ } catch {
2787
+ }
2788
+ return getCurrentBranch(repoRoot) ?? "main";
2789
+ }
2790
+ function getBranchOrDefault(repoRoot) {
2791
+ if (!isGitRepo(repoRoot)) {
2792
+ return "default";
2793
+ }
2794
+ return getCurrentBranch(repoRoot) ?? "default";
2795
+ }
2796
+ function getHeadPath(repoRoot) {
2797
+ return path4.join(repoRoot, ".git", "HEAD");
2798
+ }
2662
2799
 
2663
2800
  // src/indexer/index.ts
2801
+ function float32ArrayToBuffer(arr) {
2802
+ const float32 = new Float32Array(arr);
2803
+ return Buffer.from(float32.buffer);
2804
+ }
2805
+ function bufferToFloat32Array(buf) {
2806
+ return new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4);
2807
+ }
2664
2808
  var Indexer = class {
2665
2809
  config;
2666
2810
  projectRoot;
2667
2811
  indexPath;
2668
2812
  store = null;
2669
2813
  invertedIndex = null;
2814
+ database = null;
2670
2815
  provider = null;
2671
2816
  detectedProvider = null;
2672
2817
  fileHashCache = /* @__PURE__ */ new Map();
2673
2818
  fileHashCachePath = "";
2674
2819
  failedBatchesPath = "";
2820
+ currentBranch = "default";
2821
+ baseBranch = "main";
2675
2822
  constructor(projectRoot, config) {
2676
2823
  this.projectRoot = projectRoot;
2677
2824
  this.config = config;
2678
2825
  this.indexPath = this.getIndexPath();
2679
- this.fileHashCachePath = path4.join(this.indexPath, "file-hashes.json");
2680
- this.failedBatchesPath = path4.join(this.indexPath, "failed-batches.json");
2826
+ this.fileHashCachePath = path5.join(this.indexPath, "file-hashes.json");
2827
+ this.failedBatchesPath = path5.join(this.indexPath, "failed-batches.json");
2681
2828
  }
2682
2829
  getIndexPath() {
2683
2830
  if (this.config.scope === "global") {
2684
2831
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2685
- return path4.join(homeDir, ".opencode", "global-index");
2832
+ return path5.join(homeDir, ".opencode", "global-index");
2686
2833
  }
2687
- return path4.join(this.projectRoot, ".opencode", "index");
2834
+ return path5.join(this.projectRoot, ".opencode", "index");
2688
2835
  }
2689
2836
  loadFileHashCache() {
2690
2837
  try {
2691
- if (existsSync3(this.fileHashCachePath)) {
2692
- const data = readFileSync3(this.fileHashCachePath, "utf-8");
2838
+ if (existsSync4(this.fileHashCachePath)) {
2839
+ const data = readFileSync4(this.fileHashCachePath, "utf-8");
2693
2840
  const parsed = JSON.parse(data);
2694
2841
  this.fileHashCache = new Map(Object.entries(parsed));
2695
2842
  }
@@ -2706,8 +2853,8 @@ var Indexer = class {
2706
2853
  }
2707
2854
  loadFailedBatches() {
2708
2855
  try {
2709
- if (existsSync3(this.failedBatchesPath)) {
2710
- const data = readFileSync3(this.failedBatchesPath, "utf-8");
2856
+ if (existsSync4(this.failedBatchesPath)) {
2857
+ const data = readFileSync4(this.failedBatchesPath, "utf-8");
2711
2858
  return JSON.parse(data);
2712
2859
  }
2713
2860
  } catch {
@@ -2717,7 +2864,7 @@ var Indexer = class {
2717
2864
  }
2718
2865
  saveFailedBatches(batches) {
2719
2866
  if (batches.length === 0) {
2720
- if (existsSync3(this.failedBatchesPath)) {
2867
+ if (existsSync4(this.failedBatchesPath)) {
2721
2868
  fsPromises2.unlink(this.failedBatchesPath).catch(() => {
2722
2869
  });
2723
2870
  }
@@ -2748,25 +2895,66 @@ var Indexer = class {
2748
2895
  );
2749
2896
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
2750
2897
  const dimensions = this.detectedProvider.modelInfo.dimensions;
2751
- const storePath = path4.join(this.indexPath, "vectors");
2898
+ const storePath = path5.join(this.indexPath, "vectors");
2752
2899
  this.store = new VectorStore(storePath, dimensions);
2753
- const indexFilePath = path4.join(this.indexPath, "vectors.usearch");
2754
- if (existsSync3(indexFilePath)) {
2900
+ const indexFilePath = path5.join(this.indexPath, "vectors.usearch");
2901
+ if (existsSync4(indexFilePath)) {
2755
2902
  this.store.load();
2756
2903
  }
2757
- const invertedIndexPath = path4.join(this.indexPath, "inverted-index.json");
2904
+ const invertedIndexPath = path5.join(this.indexPath, "inverted-index.json");
2758
2905
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
2759
- this.invertedIndex.load();
2906
+ try {
2907
+ this.invertedIndex.load();
2908
+ } catch {
2909
+ if (existsSync4(invertedIndexPath)) {
2910
+ await fsPromises2.unlink(invertedIndexPath);
2911
+ }
2912
+ this.invertedIndex = new InvertedIndex(invertedIndexPath);
2913
+ }
2914
+ const dbPath = path5.join(this.indexPath, "codebase.db");
2915
+ const dbIsNew = !existsSync4(dbPath);
2916
+ this.database = new Database(dbPath);
2917
+ if (dbIsNew && this.store.count() > 0) {
2918
+ this.migrateFromLegacyIndex();
2919
+ }
2920
+ if (isGitRepo(this.projectRoot)) {
2921
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
2922
+ this.baseBranch = getBaseBranch(this.projectRoot);
2923
+ } else {
2924
+ this.currentBranch = "default";
2925
+ this.baseBranch = "default";
2926
+ }
2927
+ }
2928
+ migrateFromLegacyIndex() {
2929
+ if (!this.store || !this.database) return;
2930
+ const allMetadata = this.store.getAllMetadata();
2931
+ const chunkIds = [];
2932
+ for (const { key, metadata } of allMetadata) {
2933
+ const chunkData = {
2934
+ chunkId: key,
2935
+ contentHash: metadata.hash,
2936
+ filePath: metadata.filePath,
2937
+ startLine: metadata.startLine,
2938
+ endLine: metadata.endLine,
2939
+ nodeType: metadata.chunkType,
2940
+ name: metadata.name,
2941
+ language: metadata.language
2942
+ };
2943
+ this.database.upsertChunk(chunkData);
2944
+ chunkIds.push(key);
2945
+ }
2946
+ this.database.addChunksToBranch(this.currentBranch || "default", chunkIds);
2760
2947
  }
2761
2948
  async ensureInitialized() {
2762
- if (!this.store || !this.provider || !this.invertedIndex || !this.detectedProvider) {
2949
+ if (!this.store || !this.provider || !this.invertedIndex || !this.detectedProvider || !this.database) {
2763
2950
  await this.initialize();
2764
2951
  }
2765
2952
  return {
2766
2953
  store: this.store,
2767
2954
  provider: this.provider,
2768
2955
  invertedIndex: this.invertedIndex,
2769
- detectedProvider: this.detectedProvider
2956
+ detectedProvider: this.detectedProvider,
2957
+ database: this.database
2770
2958
  };
2771
2959
  }
2772
2960
  async estimateCost() {
@@ -2780,7 +2968,7 @@ var Indexer = class {
2780
2968
  return createCostEstimate(files, detectedProvider);
2781
2969
  }
2782
2970
  async index(onProgress) {
2783
- const { store, provider, invertedIndex } = await this.ensureInitialized();
2971
+ const { store, provider, invertedIndex, database, detectedProvider } = await this.ensureInitialized();
2784
2972
  const startTime = Date.now();
2785
2973
  const stats = {
2786
2974
  totalFiles: 0,
@@ -2854,7 +3042,7 @@ var Indexer = class {
2854
3042
  for (const parsed of parsedFiles) {
2855
3043
  currentFilePaths.add(parsed.path);
2856
3044
  if (parsed.chunks.length === 0) {
2857
- const relativePath = path4.relative(this.projectRoot, parsed.path);
3045
+ const relativePath = path5.relative(this.projectRoot, parsed.path);
2858
3046
  stats.parseFailures.push(relativePath);
2859
3047
  }
2860
3048
  let fileChunkCount = 0;
@@ -2868,6 +3056,17 @@ var Indexer = class {
2868
3056
  const id = generateChunkId(parsed.path, chunk);
2869
3057
  const contentHash = generateChunkHash(chunk);
2870
3058
  currentChunkIds.add(id);
3059
+ const chunkData = {
3060
+ chunkId: id,
3061
+ contentHash,
3062
+ filePath: parsed.path,
3063
+ startLine: chunk.startLine,
3064
+ endLine: chunk.endLine,
3065
+ nodeType: chunk.chunkType,
3066
+ name: chunk.name,
3067
+ language: chunk.language
3068
+ };
3069
+ database.upsertChunk(chunkData);
2871
3070
  if (existingChunks.get(id) === contentHash) {
2872
3071
  fileChunkCount++;
2873
3072
  continue;
@@ -2882,7 +3081,7 @@ var Indexer = class {
2882
3081
  language: chunk.language,
2883
3082
  hash: contentHash
2884
3083
  };
2885
- pendingChunks.push({ id, text, content: chunk.content, metadata });
3084
+ pendingChunks.push({ id, text, content: chunk.content, contentHash, metadata });
2886
3085
  fileChunkCount++;
2887
3086
  }
2888
3087
  }
@@ -2898,6 +3097,8 @@ var Indexer = class {
2898
3097
  stats.existingChunks = currentChunkIds.size - pendingChunks.length;
2899
3098
  stats.removedChunks = removedCount;
2900
3099
  if (pendingChunks.length === 0 && removedCount === 0) {
3100
+ database.clearBranch(this.currentBranch);
3101
+ database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
2901
3102
  this.fileHashCache = currentFileHashes;
2902
3103
  this.saveFileHashCache();
2903
3104
  stats.durationMs = Date.now() - startTime;
@@ -2911,6 +3112,8 @@ var Indexer = class {
2911
3112
  return stats;
2912
3113
  }
2913
3114
  if (pendingChunks.length === 0) {
3115
+ database.clearBranch(this.currentBranch);
3116
+ database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
2914
3117
  store.save();
2915
3118
  invertedIndex.save();
2916
3119
  this.fileHashCache = currentFileHashes;
@@ -2932,8 +3135,22 @@ var Indexer = class {
2932
3135
  chunksProcessed: 0,
2933
3136
  totalChunks: pendingChunks.length
2934
3137
  });
3138
+ const allContentHashes = pendingChunks.map((c) => c.contentHash);
3139
+ const missingHashes = new Set(database.getMissingEmbeddings(allContentHashes));
3140
+ const chunksNeedingEmbedding = pendingChunks.filter((c) => missingHashes.has(c.contentHash));
3141
+ const chunksWithExistingEmbedding = pendingChunks.filter((c) => !missingHashes.has(c.contentHash));
3142
+ for (const chunk of chunksWithExistingEmbedding) {
3143
+ const embeddingBuffer = database.getEmbedding(chunk.contentHash);
3144
+ if (embeddingBuffer) {
3145
+ const vector = bufferToFloat32Array(embeddingBuffer);
3146
+ store.add(chunk.id, Array.from(vector), chunk.metadata);
3147
+ invertedIndex.removeChunk(chunk.id);
3148
+ invertedIndex.addChunk(chunk.id, chunk.content);
3149
+ stats.indexedChunks++;
3150
+ }
3151
+ }
2935
3152
  const queue = new PQueue({ concurrency: 3 });
2936
- const dynamicBatches = createDynamicBatches(pendingChunks);
3153
+ const dynamicBatches = createDynamicBatches(chunksNeedingEmbedding);
2937
3154
  for (const batch of dynamicBatches) {
2938
3155
  queue.add(async () => {
2939
3156
  try {
@@ -2958,7 +3175,15 @@ var Indexer = class {
2958
3175
  metadata: chunk.metadata
2959
3176
  }));
2960
3177
  store.addBatch(items);
2961
- for (const chunk of batch) {
3178
+ for (let i = 0; i < batch.length; i++) {
3179
+ const chunk = batch[i];
3180
+ const embedding = result.embeddings[i];
3181
+ database.upsertEmbedding(
3182
+ chunk.contentHash,
3183
+ float32ArrayToBuffer(embedding),
3184
+ chunk.text,
3185
+ detectedProvider.modelInfo.model
3186
+ );
2962
3187
  invertedIndex.removeChunk(chunk.id);
2963
3188
  invertedIndex.addChunk(chunk.id, chunk.content);
2964
3189
  }
@@ -2986,6 +3211,8 @@ var Indexer = class {
2986
3211
  chunksProcessed: stats.indexedChunks,
2987
3212
  totalChunks: pendingChunks.length
2988
3213
  });
3214
+ database.clearBranch(this.currentBranch);
3215
+ database.addChunksToBranch(this.currentBranch, Array.from(currentChunkIds));
2989
3216
  store.save();
2990
3217
  invertedIndex.save();
2991
3218
  this.fileHashCache = currentFileHashes;
@@ -3004,18 +3231,24 @@ var Indexer = class {
3004
3231
  return stats;
3005
3232
  }
3006
3233
  async search(query, limit, options) {
3007
- const { store, provider } = await this.ensureInitialized();
3234
+ const { store, provider, database } = await this.ensureInitialized();
3008
3235
  if (store.count() === 0) {
3009
3236
  return [];
3010
3237
  }
3011
3238
  const maxResults = limit ?? this.config.search.maxResults;
3012
3239
  const hybridWeight = options?.hybridWeight ?? this.config.search.hybridWeight;
3240
+ const filterByBranch = options?.filterByBranch ?? true;
3013
3241
  const { embedding } = await provider.embed(query);
3014
3242
  const semanticResults = store.search(embedding, maxResults * 4);
3015
3243
  const keywordResults = await this.keywordSearch(query, maxResults * 4);
3016
3244
  const combined = this.fuseResults(semanticResults, keywordResults, hybridWeight, maxResults * 4);
3245
+ let branchChunkIds = null;
3246
+ if (filterByBranch && this.currentBranch !== "default") {
3247
+ branchChunkIds = new Set(database.getBranchChunkIds(this.currentBranch));
3248
+ }
3017
3249
  const filtered = combined.filter((r) => {
3018
3250
  if (r.score < this.config.search.minScore) return false;
3251
+ if (branchChunkIds && !branchChunkIds.has(r.id)) return false;
3019
3252
  if (options?.fileType) {
3020
3253
  const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
3021
3254
  if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
@@ -3117,7 +3350,9 @@ var Indexer = class {
3117
3350
  vectorCount: store.count(),
3118
3351
  provider: detectedProvider.provider,
3119
3352
  model: detectedProvider.modelInfo.model,
3120
- indexPath: this.indexPath
3353
+ indexPath: this.indexPath,
3354
+ currentBranch: this.currentBranch,
3355
+ baseBranch: this.baseBranch
3121
3356
  };
3122
3357
  }
3123
3358
  async clearIndex() {
@@ -3128,7 +3363,7 @@ var Indexer = class {
3128
3363
  invertedIndex.save();
3129
3364
  }
3130
3365
  async healthCheck() {
3131
- const { store, invertedIndex } = await this.ensureInitialized();
3366
+ const { store, invertedIndex, database } = await this.ensureInitialized();
3132
3367
  const allMetadata = store.getAllMetadata();
3133
3368
  const filePathsToChunkKeys = /* @__PURE__ */ new Map();
3134
3369
  for (const { key, metadata } of allMetadata) {
@@ -3139,12 +3374,13 @@ var Indexer = class {
3139
3374
  const removedFilePaths = [];
3140
3375
  let removedCount = 0;
3141
3376
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
3142
- if (!existsSync3(filePath)) {
3377
+ if (!existsSync4(filePath)) {
3143
3378
  for (const key of chunkKeys) {
3144
3379
  store.remove(key);
3145
3380
  invertedIndex.removeChunk(key);
3146
3381
  removedCount++;
3147
3382
  }
3383
+ database.deleteChunksByFile(filePath);
3148
3384
  removedFilePaths.push(filePath);
3149
3385
  }
3150
3386
  }
@@ -3152,7 +3388,9 @@ var Indexer = class {
3152
3388
  store.save();
3153
3389
  invertedIndex.save();
3154
3390
  }
3155
- return { removed: removedCount, filePaths: removedFilePaths };
3391
+ const gcOrphanEmbeddings = database.gcOrphanEmbeddings();
3392
+ const gcOrphanChunks = database.gcOrphanChunks();
3393
+ return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks };
3156
3394
  }
3157
3395
  async retryFailedBatches() {
3158
3396
  const { store, provider, invertedIndex } = await this.ensureInitialized();
@@ -3206,6 +3444,22 @@ var Indexer = class {
3206
3444
  getFailedBatchesCount() {
3207
3445
  return this.loadFailedBatches().length;
3208
3446
  }
3447
+ getCurrentBranch() {
3448
+ return this.currentBranch;
3449
+ }
3450
+ getBaseBranch() {
3451
+ return this.baseBranch;
3452
+ }
3453
+ refreshBranchInfo() {
3454
+ if (isGitRepo(this.projectRoot)) {
3455
+ this.currentBranch = getBranchOrDefault(this.projectRoot);
3456
+ this.baseBranch = getBaseBranch(this.projectRoot);
3457
+ }
3458
+ }
3459
+ async getDatabaseStats() {
3460
+ const { database } = await this.ensureInitialized();
3461
+ return database.getStats();
3462
+ }
3209
3463
  };
3210
3464
 
3211
3465
  // node_modules/chokidar/index.js
@@ -3298,7 +3552,7 @@ var ReaddirpStream = class extends Readable {
3298
3552
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
3299
3553
  const statMethod = opts.lstat ? lstat : stat;
3300
3554
  if (wantBigintFsStats) {
3301
- this._stat = (path7) => statMethod(path7, { bigint: true });
3555
+ this._stat = (path8) => statMethod(path8, { bigint: true });
3302
3556
  } else {
3303
3557
  this._stat = statMethod;
3304
3558
  }
@@ -3323,8 +3577,8 @@ var ReaddirpStream = class extends Readable {
3323
3577
  const par = this.parent;
3324
3578
  const fil = par && par.files;
3325
3579
  if (fil && fil.length > 0) {
3326
- const { path: path7, depth } = par;
3327
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path7));
3580
+ const { path: path8, depth } = par;
3581
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path8));
3328
3582
  const awaited = await Promise.all(slice);
3329
3583
  for (const entry of awaited) {
3330
3584
  if (!entry)
@@ -3364,20 +3618,20 @@ var ReaddirpStream = class extends Readable {
3364
3618
  this.reading = false;
3365
3619
  }
3366
3620
  }
3367
- async _exploreDir(path7, depth) {
3621
+ async _exploreDir(path8, depth) {
3368
3622
  let files;
3369
3623
  try {
3370
- files = await readdir(path7, this._rdOptions);
3624
+ files = await readdir(path8, this._rdOptions);
3371
3625
  } catch (error) {
3372
3626
  this._onError(error);
3373
3627
  }
3374
- return { files, depth, path: path7 };
3628
+ return { files, depth, path: path8 };
3375
3629
  }
3376
- async _formatEntry(dirent, path7) {
3630
+ async _formatEntry(dirent, path8) {
3377
3631
  let entry;
3378
3632
  const basename3 = this._isDirent ? dirent.name : dirent;
3379
3633
  try {
3380
- const fullPath = presolve(pjoin(path7, basename3));
3634
+ const fullPath = presolve(pjoin(path8, basename3));
3381
3635
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename3 };
3382
3636
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
3383
3637
  } catch (err) {
@@ -3777,16 +4031,16 @@ var delFromSet = (main, prop, item) => {
3777
4031
  };
3778
4032
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
3779
4033
  var FsWatchInstances = /* @__PURE__ */ new Map();
3780
- function createFsWatchInstance(path7, options, listener, errHandler, emitRaw) {
4034
+ function createFsWatchInstance(path8, options, listener, errHandler, emitRaw) {
3781
4035
  const handleEvent = (rawEvent, evPath) => {
3782
- listener(path7);
3783
- emitRaw(rawEvent, evPath, { watchedPath: path7 });
3784
- if (evPath && path7 !== evPath) {
3785
- fsWatchBroadcast(sp.resolve(path7, evPath), KEY_LISTENERS, sp.join(path7, evPath));
4036
+ listener(path8);
4037
+ emitRaw(rawEvent, evPath, { watchedPath: path8 });
4038
+ if (evPath && path8 !== evPath) {
4039
+ fsWatchBroadcast(sp.resolve(path8, evPath), KEY_LISTENERS, sp.join(path8, evPath));
3786
4040
  }
3787
4041
  };
3788
4042
  try {
3789
- return fs_watch(path7, {
4043
+ return fs_watch(path8, {
3790
4044
  persistent: options.persistent
3791
4045
  }, handleEvent);
3792
4046
  } catch (error) {
@@ -3802,12 +4056,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
3802
4056
  listener(val1, val2, val3);
3803
4057
  });
3804
4058
  };
3805
- var setFsWatchListener = (path7, fullPath, options, handlers) => {
4059
+ var setFsWatchListener = (path8, fullPath, options, handlers) => {
3806
4060
  const { listener, errHandler, rawEmitter } = handlers;
3807
4061
  let cont = FsWatchInstances.get(fullPath);
3808
4062
  let watcher;
3809
4063
  if (!options.persistent) {
3810
- watcher = createFsWatchInstance(path7, options, listener, errHandler, rawEmitter);
4064
+ watcher = createFsWatchInstance(path8, options, listener, errHandler, rawEmitter);
3811
4065
  if (!watcher)
3812
4066
  return;
3813
4067
  return watcher.close.bind(watcher);
@@ -3818,7 +4072,7 @@ var setFsWatchListener = (path7, fullPath, options, handlers) => {
3818
4072
  addAndConvert(cont, KEY_RAW, rawEmitter);
3819
4073
  } else {
3820
4074
  watcher = createFsWatchInstance(
3821
- path7,
4075
+ path8,
3822
4076
  options,
3823
4077
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
3824
4078
  errHandler,
@@ -3833,7 +4087,7 @@ var setFsWatchListener = (path7, fullPath, options, handlers) => {
3833
4087
  cont.watcherUnusable = true;
3834
4088
  if (isWindows && error.code === "EPERM") {
3835
4089
  try {
3836
- const fd = await open(path7, "r");
4090
+ const fd = await open(path8, "r");
3837
4091
  await fd.close();
3838
4092
  broadcastErr(error);
3839
4093
  } catch (err) {
@@ -3864,7 +4118,7 @@ var setFsWatchListener = (path7, fullPath, options, handlers) => {
3864
4118
  };
3865
4119
  };
3866
4120
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
3867
- var setFsWatchFileListener = (path7, fullPath, options, handlers) => {
4121
+ var setFsWatchFileListener = (path8, fullPath, options, handlers) => {
3868
4122
  const { listener, rawEmitter } = handlers;
3869
4123
  let cont = FsWatchFileInstances.get(fullPath);
3870
4124
  const copts = cont && cont.options;
@@ -3886,7 +4140,7 @@ var setFsWatchFileListener = (path7, fullPath, options, handlers) => {
3886
4140
  });
3887
4141
  const currmtime = curr.mtimeMs;
3888
4142
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
3889
- foreach(cont.listeners, (listener2) => listener2(path7, curr));
4143
+ foreach(cont.listeners, (listener2) => listener2(path8, curr));
3890
4144
  }
3891
4145
  })
3892
4146
  };
@@ -3916,13 +4170,13 @@ var NodeFsHandler = class {
3916
4170
  * @param listener on fs change
3917
4171
  * @returns closer for the watcher instance
3918
4172
  */
3919
- _watchWithNodeFs(path7, listener) {
4173
+ _watchWithNodeFs(path8, listener) {
3920
4174
  const opts = this.fsw.options;
3921
- const directory = sp.dirname(path7);
3922
- const basename3 = sp.basename(path7);
4175
+ const directory = sp.dirname(path8);
4176
+ const basename3 = sp.basename(path8);
3923
4177
  const parent = this.fsw._getWatchedDir(directory);
3924
4178
  parent.add(basename3);
3925
- const absolutePath = sp.resolve(path7);
4179
+ const absolutePath = sp.resolve(path8);
3926
4180
  const options = {
3927
4181
  persistent: opts.persistent
3928
4182
  };
@@ -3932,12 +4186,12 @@ var NodeFsHandler = class {
3932
4186
  if (opts.usePolling) {
3933
4187
  const enableBin = opts.interval !== opts.binaryInterval;
3934
4188
  options.interval = enableBin && isBinaryPath(basename3) ? opts.binaryInterval : opts.interval;
3935
- closer = setFsWatchFileListener(path7, absolutePath, options, {
4189
+ closer = setFsWatchFileListener(path8, absolutePath, options, {
3936
4190
  listener,
3937
4191
  rawEmitter: this.fsw._emitRaw
3938
4192
  });
3939
4193
  } else {
3940
- closer = setFsWatchListener(path7, absolutePath, options, {
4194
+ closer = setFsWatchListener(path8, absolutePath, options, {
3941
4195
  listener,
3942
4196
  errHandler: this._boundHandleError,
3943
4197
  rawEmitter: this.fsw._emitRaw
@@ -3959,7 +4213,7 @@ var NodeFsHandler = class {
3959
4213
  let prevStats = stats;
3960
4214
  if (parent.has(basename3))
3961
4215
  return;
3962
- const listener = async (path7, newStats) => {
4216
+ const listener = async (path8, newStats) => {
3963
4217
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
3964
4218
  return;
3965
4219
  if (!newStats || newStats.mtimeMs === 0) {
@@ -3973,11 +4227,11 @@ var NodeFsHandler = class {
3973
4227
  this.fsw._emit(EV.CHANGE, file, newStats2);
3974
4228
  }
3975
4229
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
3976
- this.fsw._closeFile(path7);
4230
+ this.fsw._closeFile(path8);
3977
4231
  prevStats = newStats2;
3978
4232
  const closer2 = this._watchWithNodeFs(file, listener);
3979
4233
  if (closer2)
3980
- this.fsw._addPathCloser(path7, closer2);
4234
+ this.fsw._addPathCloser(path8, closer2);
3981
4235
  } else {
3982
4236
  prevStats = newStats2;
3983
4237
  }
@@ -4009,7 +4263,7 @@ var NodeFsHandler = class {
4009
4263
  * @param item basename of this item
4010
4264
  * @returns true if no more processing is needed for this entry.
4011
4265
  */
4012
- async _handleSymlink(entry, directory, path7, item) {
4266
+ async _handleSymlink(entry, directory, path8, item) {
4013
4267
  if (this.fsw.closed) {
4014
4268
  return;
4015
4269
  }
@@ -4019,7 +4273,7 @@ var NodeFsHandler = class {
4019
4273
  this.fsw._incrReadyCount();
4020
4274
  let linkPath;
4021
4275
  try {
4022
- linkPath = await fsrealpath(path7);
4276
+ linkPath = await fsrealpath(path8);
4023
4277
  } catch (e) {
4024
4278
  this.fsw._emitReady();
4025
4279
  return true;
@@ -4029,12 +4283,12 @@ var NodeFsHandler = class {
4029
4283
  if (dir.has(item)) {
4030
4284
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
4031
4285
  this.fsw._symlinkPaths.set(full, linkPath);
4032
- this.fsw._emit(EV.CHANGE, path7, entry.stats);
4286
+ this.fsw._emit(EV.CHANGE, path8, entry.stats);
4033
4287
  }
4034
4288
  } else {
4035
4289
  dir.add(item);
4036
4290
  this.fsw._symlinkPaths.set(full, linkPath);
4037
- this.fsw._emit(EV.ADD, path7, entry.stats);
4291
+ this.fsw._emit(EV.ADD, path8, entry.stats);
4038
4292
  }
4039
4293
  this.fsw._emitReady();
4040
4294
  return true;
@@ -4064,9 +4318,9 @@ var NodeFsHandler = class {
4064
4318
  return;
4065
4319
  }
4066
4320
  const item = entry.path;
4067
- let path7 = sp.join(directory, item);
4321
+ let path8 = sp.join(directory, item);
4068
4322
  current.add(item);
4069
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path7, item)) {
4323
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path8, item)) {
4070
4324
  return;
4071
4325
  }
4072
4326
  if (this.fsw.closed) {
@@ -4075,8 +4329,8 @@ var NodeFsHandler = class {
4075
4329
  }
4076
4330
  if (item === target || !target && !previous.has(item)) {
4077
4331
  this.fsw._incrReadyCount();
4078
- path7 = sp.join(dir, sp.relative(dir, path7));
4079
- this._addToNodeFs(path7, initialAdd, wh, depth + 1);
4332
+ path8 = sp.join(dir, sp.relative(dir, path8));
4333
+ this._addToNodeFs(path8, initialAdd, wh, depth + 1);
4080
4334
  }
4081
4335
  }).on(EV.ERROR, this._boundHandleError);
4082
4336
  return new Promise((resolve4, reject) => {
@@ -4145,13 +4399,13 @@ var NodeFsHandler = class {
4145
4399
  * @param depth Child path actually targeted for watch
4146
4400
  * @param target Child path actually targeted for watch
4147
4401
  */
4148
- async _addToNodeFs(path7, initialAdd, priorWh, depth, target) {
4402
+ async _addToNodeFs(path8, initialAdd, priorWh, depth, target) {
4149
4403
  const ready = this.fsw._emitReady;
4150
- if (this.fsw._isIgnored(path7) || this.fsw.closed) {
4404
+ if (this.fsw._isIgnored(path8) || this.fsw.closed) {
4151
4405
  ready();
4152
4406
  return false;
4153
4407
  }
4154
- const wh = this.fsw._getWatchHelpers(path7);
4408
+ const wh = this.fsw._getWatchHelpers(path8);
4155
4409
  if (priorWh) {
4156
4410
  wh.filterPath = (entry) => priorWh.filterPath(entry);
4157
4411
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -4167,8 +4421,8 @@ var NodeFsHandler = class {
4167
4421
  const follow = this.fsw.options.followSymlinks;
4168
4422
  let closer;
4169
4423
  if (stats.isDirectory()) {
4170
- const absPath = sp.resolve(path7);
4171
- const targetPath = follow ? await fsrealpath(path7) : path7;
4424
+ const absPath = sp.resolve(path8);
4425
+ const targetPath = follow ? await fsrealpath(path8) : path8;
4172
4426
  if (this.fsw.closed)
4173
4427
  return;
4174
4428
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -4178,29 +4432,29 @@ var NodeFsHandler = class {
4178
4432
  this.fsw._symlinkPaths.set(absPath, targetPath);
4179
4433
  }
4180
4434
  } else if (stats.isSymbolicLink()) {
4181
- const targetPath = follow ? await fsrealpath(path7) : path7;
4435
+ const targetPath = follow ? await fsrealpath(path8) : path8;
4182
4436
  if (this.fsw.closed)
4183
4437
  return;
4184
4438
  const parent = sp.dirname(wh.watchPath);
4185
4439
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
4186
4440
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
4187
- closer = await this._handleDir(parent, stats, initialAdd, depth, path7, wh, targetPath);
4441
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path8, wh, targetPath);
4188
4442
  if (this.fsw.closed)
4189
4443
  return;
4190
4444
  if (targetPath !== void 0) {
4191
- this.fsw._symlinkPaths.set(sp.resolve(path7), targetPath);
4445
+ this.fsw._symlinkPaths.set(sp.resolve(path8), targetPath);
4192
4446
  }
4193
4447
  } else {
4194
4448
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
4195
4449
  }
4196
4450
  ready();
4197
4451
  if (closer)
4198
- this.fsw._addPathCloser(path7, closer);
4452
+ this.fsw._addPathCloser(path8, closer);
4199
4453
  return false;
4200
4454
  } catch (error) {
4201
4455
  if (this.fsw._handleError(error)) {
4202
4456
  ready();
4203
- return path7;
4457
+ return path8;
4204
4458
  }
4205
4459
  }
4206
4460
  }
@@ -4243,24 +4497,24 @@ function createPattern(matcher) {
4243
4497
  }
4244
4498
  return () => false;
4245
4499
  }
4246
- function normalizePath(path7) {
4247
- if (typeof path7 !== "string")
4500
+ function normalizePath(path8) {
4501
+ if (typeof path8 !== "string")
4248
4502
  throw new Error("string expected");
4249
- path7 = sp2.normalize(path7);
4250
- path7 = path7.replace(/\\/g, "/");
4503
+ path8 = sp2.normalize(path8);
4504
+ path8 = path8.replace(/\\/g, "/");
4251
4505
  let prepend = false;
4252
- if (path7.startsWith("//"))
4506
+ if (path8.startsWith("//"))
4253
4507
  prepend = true;
4254
- path7 = path7.replace(DOUBLE_SLASH_RE, "/");
4508
+ path8 = path8.replace(DOUBLE_SLASH_RE, "/");
4255
4509
  if (prepend)
4256
- path7 = "/" + path7;
4257
- return path7;
4510
+ path8 = "/" + path8;
4511
+ return path8;
4258
4512
  }
4259
4513
  function matchPatterns(patterns, testString, stats) {
4260
- const path7 = normalizePath(testString);
4514
+ const path8 = normalizePath(testString);
4261
4515
  for (let index = 0; index < patterns.length; index++) {
4262
4516
  const pattern = patterns[index];
4263
- if (pattern(path7, stats)) {
4517
+ if (pattern(path8, stats)) {
4264
4518
  return true;
4265
4519
  }
4266
4520
  }
@@ -4298,19 +4552,19 @@ var toUnix = (string) => {
4298
4552
  }
4299
4553
  return str;
4300
4554
  };
4301
- var normalizePathToUnix = (path7) => toUnix(sp2.normalize(toUnix(path7)));
4302
- var normalizeIgnored = (cwd = "") => (path7) => {
4303
- if (typeof path7 === "string") {
4304
- return normalizePathToUnix(sp2.isAbsolute(path7) ? path7 : sp2.join(cwd, path7));
4555
+ var normalizePathToUnix = (path8) => toUnix(sp2.normalize(toUnix(path8)));
4556
+ var normalizeIgnored = (cwd = "") => (path8) => {
4557
+ if (typeof path8 === "string") {
4558
+ return normalizePathToUnix(sp2.isAbsolute(path8) ? path8 : sp2.join(cwd, path8));
4305
4559
  } else {
4306
- return path7;
4560
+ return path8;
4307
4561
  }
4308
4562
  };
4309
- var getAbsolutePath = (path7, cwd) => {
4310
- if (sp2.isAbsolute(path7)) {
4311
- return path7;
4563
+ var getAbsolutePath = (path8, cwd) => {
4564
+ if (sp2.isAbsolute(path8)) {
4565
+ return path8;
4312
4566
  }
4313
- return sp2.join(cwd, path7);
4567
+ return sp2.join(cwd, path8);
4314
4568
  };
4315
4569
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
4316
4570
  var DirEntry = class {
@@ -4375,10 +4629,10 @@ var WatchHelper = class {
4375
4629
  dirParts;
4376
4630
  followSymlinks;
4377
4631
  statMethod;
4378
- constructor(path7, follow, fsw) {
4632
+ constructor(path8, follow, fsw) {
4379
4633
  this.fsw = fsw;
4380
- const watchPath = path7;
4381
- this.path = path7 = path7.replace(REPLACER_RE, "");
4634
+ const watchPath = path8;
4635
+ this.path = path8 = path8.replace(REPLACER_RE, "");
4382
4636
  this.watchPath = watchPath;
4383
4637
  this.fullWatchPath = sp2.resolve(watchPath);
4384
4638
  this.dirParts = [];
@@ -4518,20 +4772,20 @@ var FSWatcher = class extends EventEmitter2 {
4518
4772
  this._closePromise = void 0;
4519
4773
  let paths = unifyPaths(paths_);
4520
4774
  if (cwd) {
4521
- paths = paths.map((path7) => {
4522
- const absPath = getAbsolutePath(path7, cwd);
4775
+ paths = paths.map((path8) => {
4776
+ const absPath = getAbsolutePath(path8, cwd);
4523
4777
  return absPath;
4524
4778
  });
4525
4779
  }
4526
- paths.forEach((path7) => {
4527
- this._removeIgnoredPath(path7);
4780
+ paths.forEach((path8) => {
4781
+ this._removeIgnoredPath(path8);
4528
4782
  });
4529
4783
  this._userIgnored = void 0;
4530
4784
  if (!this._readyCount)
4531
4785
  this._readyCount = 0;
4532
4786
  this._readyCount += paths.length;
4533
- Promise.all(paths.map(async (path7) => {
4534
- const res = await this._nodeFsHandler._addToNodeFs(path7, !_internal, void 0, 0, _origAdd);
4787
+ Promise.all(paths.map(async (path8) => {
4788
+ const res = await this._nodeFsHandler._addToNodeFs(path8, !_internal, void 0, 0, _origAdd);
4535
4789
  if (res)
4536
4790
  this._emitReady();
4537
4791
  return res;
@@ -4553,17 +4807,17 @@ var FSWatcher = class extends EventEmitter2 {
4553
4807
  return this;
4554
4808
  const paths = unifyPaths(paths_);
4555
4809
  const { cwd } = this.options;
4556
- paths.forEach((path7) => {
4557
- if (!sp2.isAbsolute(path7) && !this._closers.has(path7)) {
4810
+ paths.forEach((path8) => {
4811
+ if (!sp2.isAbsolute(path8) && !this._closers.has(path8)) {
4558
4812
  if (cwd)
4559
- path7 = sp2.join(cwd, path7);
4560
- path7 = sp2.resolve(path7);
4813
+ path8 = sp2.join(cwd, path8);
4814
+ path8 = sp2.resolve(path8);
4561
4815
  }
4562
- this._closePath(path7);
4563
- this._addIgnoredPath(path7);
4564
- if (this._watched.has(path7)) {
4816
+ this._closePath(path8);
4817
+ this._addIgnoredPath(path8);
4818
+ if (this._watched.has(path8)) {
4565
4819
  this._addIgnoredPath({
4566
- path: path7,
4820
+ path: path8,
4567
4821
  recursive: true
4568
4822
  });
4569
4823
  }
@@ -4627,38 +4881,38 @@ var FSWatcher = class extends EventEmitter2 {
4627
4881
  * @param stats arguments to be passed with event
4628
4882
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
4629
4883
  */
4630
- async _emit(event, path7, stats) {
4884
+ async _emit(event, path8, stats) {
4631
4885
  if (this.closed)
4632
4886
  return;
4633
4887
  const opts = this.options;
4634
4888
  if (isWindows)
4635
- path7 = sp2.normalize(path7);
4889
+ path8 = sp2.normalize(path8);
4636
4890
  if (opts.cwd)
4637
- path7 = sp2.relative(opts.cwd, path7);
4638
- const args = [path7];
4891
+ path8 = sp2.relative(opts.cwd, path8);
4892
+ const args = [path8];
4639
4893
  if (stats != null)
4640
4894
  args.push(stats);
4641
4895
  const awf = opts.awaitWriteFinish;
4642
4896
  let pw;
4643
- if (awf && (pw = this._pendingWrites.get(path7))) {
4897
+ if (awf && (pw = this._pendingWrites.get(path8))) {
4644
4898
  pw.lastChange = /* @__PURE__ */ new Date();
4645
4899
  return this;
4646
4900
  }
4647
4901
  if (opts.atomic) {
4648
4902
  if (event === EVENTS.UNLINK) {
4649
- this._pendingUnlinks.set(path7, [event, ...args]);
4903
+ this._pendingUnlinks.set(path8, [event, ...args]);
4650
4904
  setTimeout(() => {
4651
- this._pendingUnlinks.forEach((entry, path8) => {
4905
+ this._pendingUnlinks.forEach((entry, path9) => {
4652
4906
  this.emit(...entry);
4653
4907
  this.emit(EVENTS.ALL, ...entry);
4654
- this._pendingUnlinks.delete(path8);
4908
+ this._pendingUnlinks.delete(path9);
4655
4909
  });
4656
4910
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
4657
4911
  return this;
4658
4912
  }
4659
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path7)) {
4913
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path8)) {
4660
4914
  event = EVENTS.CHANGE;
4661
- this._pendingUnlinks.delete(path7);
4915
+ this._pendingUnlinks.delete(path8);
4662
4916
  }
4663
4917
  }
4664
4918
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -4676,16 +4930,16 @@ var FSWatcher = class extends EventEmitter2 {
4676
4930
  this.emitWithAll(event, args);
4677
4931
  }
4678
4932
  };
4679
- this._awaitWriteFinish(path7, awf.stabilityThreshold, event, awfEmit);
4933
+ this._awaitWriteFinish(path8, awf.stabilityThreshold, event, awfEmit);
4680
4934
  return this;
4681
4935
  }
4682
4936
  if (event === EVENTS.CHANGE) {
4683
- const isThrottled = !this._throttle(EVENTS.CHANGE, path7, 50);
4937
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path8, 50);
4684
4938
  if (isThrottled)
4685
4939
  return this;
4686
4940
  }
4687
4941
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
4688
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path7) : path7;
4942
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path8) : path8;
4689
4943
  let stats2;
4690
4944
  try {
4691
4945
  stats2 = await stat3(fullPath);
@@ -4716,23 +4970,23 @@ var FSWatcher = class extends EventEmitter2 {
4716
4970
  * @param timeout duration of time to suppress duplicate actions
4717
4971
  * @returns tracking object or false if action should be suppressed
4718
4972
  */
4719
- _throttle(actionType, path7, timeout) {
4973
+ _throttle(actionType, path8, timeout) {
4720
4974
  if (!this._throttled.has(actionType)) {
4721
4975
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
4722
4976
  }
4723
4977
  const action = this._throttled.get(actionType);
4724
4978
  if (!action)
4725
4979
  throw new Error("invalid throttle");
4726
- const actionPath = action.get(path7);
4980
+ const actionPath = action.get(path8);
4727
4981
  if (actionPath) {
4728
4982
  actionPath.count++;
4729
4983
  return false;
4730
4984
  }
4731
4985
  let timeoutObject;
4732
4986
  const clear = () => {
4733
- const item = action.get(path7);
4987
+ const item = action.get(path8);
4734
4988
  const count = item ? item.count : 0;
4735
- action.delete(path7);
4989
+ action.delete(path8);
4736
4990
  clearTimeout(timeoutObject);
4737
4991
  if (item)
4738
4992
  clearTimeout(item.timeoutObject);
@@ -4740,7 +4994,7 @@ var FSWatcher = class extends EventEmitter2 {
4740
4994
  };
4741
4995
  timeoutObject = setTimeout(clear, timeout);
4742
4996
  const thr = { timeoutObject, clear, count: 0 };
4743
- action.set(path7, thr);
4997
+ action.set(path8, thr);
4744
4998
  return thr;
4745
4999
  }
4746
5000
  _incrReadyCount() {
@@ -4754,44 +5008,44 @@ var FSWatcher = class extends EventEmitter2 {
4754
5008
  * @param event
4755
5009
  * @param awfEmit Callback to be called when ready for event to be emitted.
4756
5010
  */
4757
- _awaitWriteFinish(path7, threshold, event, awfEmit) {
5011
+ _awaitWriteFinish(path8, threshold, event, awfEmit) {
4758
5012
  const awf = this.options.awaitWriteFinish;
4759
5013
  if (typeof awf !== "object")
4760
5014
  return;
4761
5015
  const pollInterval = awf.pollInterval;
4762
5016
  let timeoutHandler;
4763
- let fullPath = path7;
4764
- if (this.options.cwd && !sp2.isAbsolute(path7)) {
4765
- fullPath = sp2.join(this.options.cwd, path7);
5017
+ let fullPath = path8;
5018
+ if (this.options.cwd && !sp2.isAbsolute(path8)) {
5019
+ fullPath = sp2.join(this.options.cwd, path8);
4766
5020
  }
4767
5021
  const now = /* @__PURE__ */ new Date();
4768
5022
  const writes = this._pendingWrites;
4769
5023
  function awaitWriteFinishFn(prevStat) {
4770
5024
  statcb(fullPath, (err, curStat) => {
4771
- if (err || !writes.has(path7)) {
5025
+ if (err || !writes.has(path8)) {
4772
5026
  if (err && err.code !== "ENOENT")
4773
5027
  awfEmit(err);
4774
5028
  return;
4775
5029
  }
4776
5030
  const now2 = Number(/* @__PURE__ */ new Date());
4777
5031
  if (prevStat && curStat.size !== prevStat.size) {
4778
- writes.get(path7).lastChange = now2;
5032
+ writes.get(path8).lastChange = now2;
4779
5033
  }
4780
- const pw = writes.get(path7);
5034
+ const pw = writes.get(path8);
4781
5035
  const df = now2 - pw.lastChange;
4782
5036
  if (df >= threshold) {
4783
- writes.delete(path7);
5037
+ writes.delete(path8);
4784
5038
  awfEmit(void 0, curStat);
4785
5039
  } else {
4786
5040
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
4787
5041
  }
4788
5042
  });
4789
5043
  }
4790
- if (!writes.has(path7)) {
4791
- writes.set(path7, {
5044
+ if (!writes.has(path8)) {
5045
+ writes.set(path8, {
4792
5046
  lastChange: now,
4793
5047
  cancelWait: () => {
4794
- writes.delete(path7);
5048
+ writes.delete(path8);
4795
5049
  clearTimeout(timeoutHandler);
4796
5050
  return event;
4797
5051
  }
@@ -4802,8 +5056,8 @@ var FSWatcher = class extends EventEmitter2 {
4802
5056
  /**
4803
5057
  * Determines whether user has asked to ignore this path.
4804
5058
  */
4805
- _isIgnored(path7, stats) {
4806
- if (this.options.atomic && DOT_RE.test(path7))
5059
+ _isIgnored(path8, stats) {
5060
+ if (this.options.atomic && DOT_RE.test(path8))
4807
5061
  return true;
4808
5062
  if (!this._userIgnored) {
4809
5063
  const { cwd } = this.options;
@@ -4813,17 +5067,17 @@ var FSWatcher = class extends EventEmitter2 {
4813
5067
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
4814
5068
  this._userIgnored = anymatch(list, void 0);
4815
5069
  }
4816
- return this._userIgnored(path7, stats);
5070
+ return this._userIgnored(path8, stats);
4817
5071
  }
4818
- _isntIgnored(path7, stat4) {
4819
- return !this._isIgnored(path7, stat4);
5072
+ _isntIgnored(path8, stat4) {
5073
+ return !this._isIgnored(path8, stat4);
4820
5074
  }
4821
5075
  /**
4822
5076
  * Provides a set of common helpers and properties relating to symlink handling.
4823
5077
  * @param path file or directory pattern being watched
4824
5078
  */
4825
- _getWatchHelpers(path7) {
4826
- return new WatchHelper(path7, this.options.followSymlinks, this);
5079
+ _getWatchHelpers(path8) {
5080
+ return new WatchHelper(path8, this.options.followSymlinks, this);
4827
5081
  }
4828
5082
  // Directory helpers
4829
5083
  // -----------------
@@ -4855,63 +5109,63 @@ var FSWatcher = class extends EventEmitter2 {
4855
5109
  * @param item base path of item/directory
4856
5110
  */
4857
5111
  _remove(directory, item, isDirectory) {
4858
- const path7 = sp2.join(directory, item);
4859
- const fullPath = sp2.resolve(path7);
4860
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path7) || this._watched.has(fullPath);
4861
- if (!this._throttle("remove", path7, 100))
5112
+ const path8 = sp2.join(directory, item);
5113
+ const fullPath = sp2.resolve(path8);
5114
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path8) || this._watched.has(fullPath);
5115
+ if (!this._throttle("remove", path8, 100))
4862
5116
  return;
4863
5117
  if (!isDirectory && this._watched.size === 1) {
4864
5118
  this.add(directory, item, true);
4865
5119
  }
4866
- const wp = this._getWatchedDir(path7);
5120
+ const wp = this._getWatchedDir(path8);
4867
5121
  const nestedDirectoryChildren = wp.getChildren();
4868
- nestedDirectoryChildren.forEach((nested) => this._remove(path7, nested));
5122
+ nestedDirectoryChildren.forEach((nested) => this._remove(path8, nested));
4869
5123
  const parent = this._getWatchedDir(directory);
4870
5124
  const wasTracked = parent.has(item);
4871
5125
  parent.remove(item);
4872
5126
  if (this._symlinkPaths.has(fullPath)) {
4873
5127
  this._symlinkPaths.delete(fullPath);
4874
5128
  }
4875
- let relPath = path7;
5129
+ let relPath = path8;
4876
5130
  if (this.options.cwd)
4877
- relPath = sp2.relative(this.options.cwd, path7);
5131
+ relPath = sp2.relative(this.options.cwd, path8);
4878
5132
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
4879
5133
  const event = this._pendingWrites.get(relPath).cancelWait();
4880
5134
  if (event === EVENTS.ADD)
4881
5135
  return;
4882
5136
  }
4883
- this._watched.delete(path7);
5137
+ this._watched.delete(path8);
4884
5138
  this._watched.delete(fullPath);
4885
5139
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
4886
- if (wasTracked && !this._isIgnored(path7))
4887
- this._emit(eventName, path7);
4888
- this._closePath(path7);
5140
+ if (wasTracked && !this._isIgnored(path8))
5141
+ this._emit(eventName, path8);
5142
+ this._closePath(path8);
4889
5143
  }
4890
5144
  /**
4891
5145
  * Closes all watchers for a path
4892
5146
  */
4893
- _closePath(path7) {
4894
- this._closeFile(path7);
4895
- const dir = sp2.dirname(path7);
4896
- this._getWatchedDir(dir).remove(sp2.basename(path7));
5147
+ _closePath(path8) {
5148
+ this._closeFile(path8);
5149
+ const dir = sp2.dirname(path8);
5150
+ this._getWatchedDir(dir).remove(sp2.basename(path8));
4897
5151
  }
4898
5152
  /**
4899
5153
  * Closes only file-specific watchers
4900
5154
  */
4901
- _closeFile(path7) {
4902
- const closers = this._closers.get(path7);
5155
+ _closeFile(path8) {
5156
+ const closers = this._closers.get(path8);
4903
5157
  if (!closers)
4904
5158
  return;
4905
5159
  closers.forEach((closer) => closer());
4906
- this._closers.delete(path7);
5160
+ this._closers.delete(path8);
4907
5161
  }
4908
- _addPathCloser(path7, closer) {
5162
+ _addPathCloser(path8, closer) {
4909
5163
  if (!closer)
4910
5164
  return;
4911
- let list = this._closers.get(path7);
5165
+ let list = this._closers.get(path8);
4912
5166
  if (!list) {
4913
5167
  list = [];
4914
- this._closers.set(path7, list);
5168
+ this._closers.set(path8, list);
4915
5169
  }
4916
5170
  list.push(closer);
4917
5171
  }
@@ -4941,7 +5195,7 @@ function watch(paths, options = {}) {
4941
5195
  var chokidar_default = { watch, FSWatcher };
4942
5196
 
4943
5197
  // src/watcher/index.ts
4944
- import * as path5 from "path";
5198
+ import * as path6 from "path";
4945
5199
  var FileWatcher = class {
4946
5200
  watcher = null;
4947
5201
  projectRoot;
@@ -4962,7 +5216,7 @@ var FileWatcher = class {
4962
5216
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
4963
5217
  this.watcher = chokidar_default.watch(this.projectRoot, {
4964
5218
  ignored: (filePath) => {
4965
- const relativePath = path5.relative(this.projectRoot, filePath);
5219
+ const relativePath = path6.relative(this.projectRoot, filePath);
4966
5220
  if (!relativePath) return false;
4967
5221
  if (ignoreFilter.ignores(relativePath)) {
4968
5222
  return true;
@@ -5006,7 +5260,7 @@ var FileWatcher = class {
5006
5260
  return;
5007
5261
  }
5008
5262
  const changes = Array.from(this.pendingChanges.entries()).map(
5009
- ([path7, type]) => ({ path: path7, type })
5263
+ ([path8, type]) => ({ path: path8, type })
5010
5264
  );
5011
5265
  this.pendingChanges.clear();
5012
5266
  try {
@@ -5031,9 +5285,82 @@ var FileWatcher = class {
5031
5285
  return this.watcher !== null;
5032
5286
  }
5033
5287
  };
5288
+ var GitHeadWatcher = class {
5289
+ watcher = null;
5290
+ projectRoot;
5291
+ currentBranch = null;
5292
+ onBranchChange = null;
5293
+ debounceTimer = null;
5294
+ debounceMs = 100;
5295
+ // Short debounce for git operations
5296
+ constructor(projectRoot) {
5297
+ this.projectRoot = projectRoot;
5298
+ }
5299
+ start(handler) {
5300
+ if (this.watcher) {
5301
+ return;
5302
+ }
5303
+ if (!isGitRepo(this.projectRoot)) {
5304
+ return;
5305
+ }
5306
+ this.onBranchChange = handler;
5307
+ this.currentBranch = getCurrentBranch(this.projectRoot);
5308
+ const headPath = getHeadPath(this.projectRoot);
5309
+ const refsPath = path6.join(this.projectRoot, ".git", "refs", "heads");
5310
+ this.watcher = chokidar_default.watch([headPath, refsPath], {
5311
+ persistent: true,
5312
+ ignoreInitial: true,
5313
+ awaitWriteFinish: {
5314
+ stabilityThreshold: 50,
5315
+ pollInterval: 10
5316
+ }
5317
+ });
5318
+ this.watcher.on("change", () => this.handleHeadChange());
5319
+ this.watcher.on("add", () => this.handleHeadChange());
5320
+ }
5321
+ handleHeadChange() {
5322
+ if (this.debounceTimer) {
5323
+ clearTimeout(this.debounceTimer);
5324
+ }
5325
+ this.debounceTimer = setTimeout(() => {
5326
+ this.checkBranchChange();
5327
+ }, this.debounceMs);
5328
+ }
5329
+ async checkBranchChange() {
5330
+ const newBranch = getCurrentBranch(this.projectRoot);
5331
+ if (newBranch && newBranch !== this.currentBranch && this.onBranchChange) {
5332
+ const oldBranch = this.currentBranch;
5333
+ this.currentBranch = newBranch;
5334
+ try {
5335
+ await this.onBranchChange(oldBranch, newBranch);
5336
+ } catch (error) {
5337
+ console.error("Error handling branch change:", error);
5338
+ }
5339
+ } else if (newBranch) {
5340
+ this.currentBranch = newBranch;
5341
+ }
5342
+ }
5343
+ getCurrentBranch() {
5344
+ return this.currentBranch;
5345
+ }
5346
+ stop() {
5347
+ if (this.debounceTimer) {
5348
+ clearTimeout(this.debounceTimer);
5349
+ this.debounceTimer = null;
5350
+ }
5351
+ if (this.watcher) {
5352
+ this.watcher.close();
5353
+ this.watcher = null;
5354
+ }
5355
+ this.onBranchChange = null;
5356
+ }
5357
+ isRunning() {
5358
+ return this.watcher !== null;
5359
+ }
5360
+ };
5034
5361
  function createWatcherWithIndexer(indexer, projectRoot, config) {
5035
- const watcher = new FileWatcher(projectRoot, config);
5036
- watcher.start(async (changes) => {
5362
+ const fileWatcher = new FileWatcher(projectRoot, config);
5363
+ fileWatcher.start(async (changes) => {
5037
5364
  const hasAddOrChange = changes.some(
5038
5365
  (c) => c.type === "add" || c.type === "change"
5039
5366
  );
@@ -5042,7 +5369,22 @@ function createWatcherWithIndexer(indexer, projectRoot, config) {
5042
5369
  await indexer.index();
5043
5370
  }
5044
5371
  });
5045
- return watcher;
5372
+ let gitWatcher = null;
5373
+ if (isGitRepo(projectRoot)) {
5374
+ gitWatcher = new GitHeadWatcher(projectRoot);
5375
+ gitWatcher.start(async (oldBranch, newBranch) => {
5376
+ console.log(`Branch changed: ${oldBranch ?? "(none)"} -> ${newBranch}`);
5377
+ await indexer.index();
5378
+ });
5379
+ }
5380
+ return {
5381
+ fileWatcher,
5382
+ gitWatcher,
5383
+ stop() {
5384
+ fileWatcher.stop();
5385
+ gitWatcher?.stop();
5386
+ }
5387
+ };
5046
5388
  }
5047
5389
 
5048
5390
  // src/tools/index.ts
@@ -5126,13 +5468,19 @@ var index_health_check = tool({
5126
5468
  async execute() {
5127
5469
  const indexer = getIndexer();
5128
5470
  const result = await indexer.healthCheck();
5129
- if (result.removed === 0) {
5471
+ if (result.removed === 0 && result.gcOrphanEmbeddings === 0 && result.gcOrphanChunks === 0) {
5130
5472
  return "Index is healthy. No stale entries found.";
5131
5473
  }
5132
- const lines = [
5133
- `Health check complete:`,
5134
- ` Removed stale entries: ${result.removed}`
5135
- ];
5474
+ const lines = [`Health check complete:`];
5475
+ if (result.removed > 0) {
5476
+ lines.push(` Removed stale entries: ${result.removed}`);
5477
+ }
5478
+ if (result.gcOrphanEmbeddings > 0) {
5479
+ lines.push(` Garbage collected orphan embeddings: ${result.gcOrphanEmbeddings}`);
5480
+ }
5481
+ if (result.gcOrphanChunks > 0) {
5482
+ lines.push(` Garbage collected orphan chunks: ${result.gcOrphanChunks}`);
5483
+ }
5136
5484
  if (result.filePaths.length > 0) {
5137
5485
  lines.push(` Cleaned paths: ${result.filePaths.join(", ")}`);
5138
5486
  }
@@ -5187,21 +5535,26 @@ function formatStatus(status) {
5187
5535
  if (!status.indexed) {
5188
5536
  return "Codebase is not indexed. Run index_codebase to create an index.";
5189
5537
  }
5190
- return [
5538
+ const lines = [
5191
5539
  `Index status:`,
5192
5540
  ` Indexed chunks: ${status.vectorCount.toLocaleString()}`,
5193
5541
  ` Provider: ${status.provider}`,
5194
5542
  ` Model: ${status.model}`,
5195
5543
  ` Location: ${status.indexPath}`
5196
- ].join("\n");
5544
+ ];
5545
+ if (status.currentBranch !== "default") {
5546
+ lines.push(` Current branch: ${status.currentBranch}`);
5547
+ lines.push(` Base branch: ${status.baseBranch}`);
5548
+ }
5549
+ return lines.join("\n");
5197
5550
  }
5198
5551
 
5199
5552
  // src/index.ts
5200
5553
  function loadPluginConfig(projectRoot) {
5201
- const configPath = path6.join(projectRoot, ".opencode", "codebase-index.json");
5554
+ const configPath = path7.join(projectRoot, ".opencode", "codebase-index.json");
5202
5555
  try {
5203
- if (existsSync4(configPath)) {
5204
- const content = readFileSync4(configPath, "utf-8");
5556
+ if (existsSync5(configPath)) {
5557
+ const content = readFileSync5(configPath, "utf-8");
5205
5558
  return JSON.parse(content);
5206
5559
  }
5207
5560
  } catch {