opencode-codebase-index 0.22.5 → 0.24.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/cli.cjs CHANGED
@@ -496,7 +496,7 @@ var require_ignore = __commonJS({
496
496
  // path matching.
497
497
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
498
498
  // @returns {TestResult} true if a file is ignored
499
- test(path31, checkUnignored, mode) {
499
+ test(path33, checkUnignored, mode) {
500
500
  let ignored = false;
501
501
  let unignored = false;
502
502
  let matchedRule;
@@ -505,7 +505,7 @@ var require_ignore = __commonJS({
505
505
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
506
506
  return;
507
507
  }
508
- const matched = rule[mode].test(path31);
508
+ const matched = rule[mode].test(path33);
509
509
  if (!matched) {
510
510
  return;
511
511
  }
@@ -526,17 +526,17 @@ var require_ignore = __commonJS({
526
526
  var throwError = (message, Ctor) => {
527
527
  throw new Ctor(message);
528
528
  };
529
- var checkPath = (path31, originalPath, doThrow) => {
530
- if (!isString(path31)) {
529
+ var checkPath = (path33, originalPath, doThrow) => {
530
+ if (!isString(path33)) {
531
531
  return doThrow(
532
532
  `path must be a string, but got \`${originalPath}\``,
533
533
  TypeError
534
534
  );
535
535
  }
536
- if (!path31) {
536
+ if (!path33) {
537
537
  return doThrow(`path must not be empty`, TypeError);
538
538
  }
539
- if (checkPath.isNotRelative(path31)) {
539
+ if (checkPath.isNotRelative(path33)) {
540
540
  const r = "`path.relative()`d";
541
541
  return doThrow(
542
542
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -545,7 +545,7 @@ var require_ignore = __commonJS({
545
545
  }
546
546
  return true;
547
547
  };
548
- var isNotRelative = (path31) => REGEX_TEST_INVALID_PATH.test(path31);
548
+ var isNotRelative = (path33) => REGEX_TEST_INVALID_PATH.test(path33);
549
549
  checkPath.isNotRelative = isNotRelative;
550
550
  checkPath.convert = (p) => p;
551
551
  var Ignore2 = class {
@@ -575,19 +575,19 @@ var require_ignore = __commonJS({
575
575
  }
576
576
  // @returns {TestResult}
577
577
  _test(originalPath, cache, checkUnignored, slices) {
578
- const path31 = originalPath && checkPath.convert(originalPath);
578
+ const path33 = originalPath && checkPath.convert(originalPath);
579
579
  checkPath(
580
- path31,
580
+ path33,
581
581
  originalPath,
582
582
  this._strictPathCheck ? throwError : RETURN_FALSE
583
583
  );
584
- return this._t(path31, cache, checkUnignored, slices);
584
+ return this._t(path33, cache, checkUnignored, slices);
585
585
  }
586
- checkIgnore(path31) {
587
- if (!REGEX_TEST_TRAILING_SLASH.test(path31)) {
588
- return this.test(path31);
586
+ checkIgnore(path33) {
587
+ if (!REGEX_TEST_TRAILING_SLASH.test(path33)) {
588
+ return this.test(path33);
589
589
  }
590
- const slices = path31.split(SLASH2).filter(Boolean);
590
+ const slices = path33.split(SLASH2).filter(Boolean);
591
591
  slices.pop();
592
592
  if (slices.length) {
593
593
  const parent = this._t(
@@ -600,18 +600,18 @@ var require_ignore = __commonJS({
600
600
  return parent;
601
601
  }
602
602
  }
603
- return this._rules.test(path31, false, MODE_CHECK_IGNORE);
603
+ return this._rules.test(path33, false, MODE_CHECK_IGNORE);
604
604
  }
605
- _t(path31, cache, checkUnignored, slices) {
606
- if (path31 in cache) {
607
- return cache[path31];
605
+ _t(path33, cache, checkUnignored, slices) {
606
+ if (path33 in cache) {
607
+ return cache[path33];
608
608
  }
609
609
  if (!slices) {
610
- slices = path31.split(SLASH2).filter(Boolean);
610
+ slices = path33.split(SLASH2).filter(Boolean);
611
611
  }
612
612
  slices.pop();
613
613
  if (!slices.length) {
614
- return cache[path31] = this._rules.test(path31, checkUnignored, MODE_IGNORE);
614
+ return cache[path33] = this._rules.test(path33, checkUnignored, MODE_IGNORE);
615
615
  }
616
616
  const parent = this._t(
617
617
  slices.join(SLASH2) + SLASH2,
@@ -619,29 +619,29 @@ var require_ignore = __commonJS({
619
619
  checkUnignored,
620
620
  slices
621
621
  );
622
- return cache[path31] = parent.ignored ? parent : this._rules.test(path31, checkUnignored, MODE_IGNORE);
622
+ return cache[path33] = parent.ignored ? parent : this._rules.test(path33, checkUnignored, MODE_IGNORE);
623
623
  }
624
- ignores(path31) {
625
- return this._test(path31, this._ignoreCache, false).ignored;
624
+ ignores(path33) {
625
+ return this._test(path33, this._ignoreCache, false).ignored;
626
626
  }
627
627
  createFilter() {
628
- return (path31) => !this.ignores(path31);
628
+ return (path33) => !this.ignores(path33);
629
629
  }
630
630
  filter(paths) {
631
631
  return makeArray(paths).filter(this.createFilter());
632
632
  }
633
633
  // @returns {TestResult}
634
- test(path31) {
635
- return this._test(path31, this._testCache, true);
634
+ test(path33) {
635
+ return this._test(path33, this._testCache, true);
636
636
  }
637
637
  };
638
638
  var factory = (options) => new Ignore2(options);
639
- var isPathValid = (path31) => checkPath(path31 && checkPath.convert(path31), path31, RETURN_FALSE);
639
+ var isPathValid = (path33) => checkPath(path33 && checkPath.convert(path33), path33, RETURN_FALSE);
640
640
  var setupWindows = () => {
641
641
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
642
642
  checkPath.convert = makePosix;
643
643
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
644
- checkPath.isNotRelative = (path31) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path31) || isNotRelative(path31);
644
+ checkPath.isNotRelative = (path33) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path33) || isNotRelative(path33);
645
645
  };
646
646
  if (
647
647
  // Detect `process` so that it can run in browsers.
@@ -669,7 +669,7 @@ module.exports = __toCommonJS(cli_exports);
669
669
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
670
670
  var import_fs20 = require("fs");
671
671
  var os8 = __toESM(require("os"), 1);
672
- var path30 = __toESM(require("path"), 1);
672
+ var path32 = __toESM(require("path"), 1);
673
673
  var import_url = require("url");
674
674
 
675
675
  // src/config/constants.ts
@@ -730,6 +730,17 @@ var EMBEDDING_MODELS = {
730
730
  maxTokens: 2048,
731
731
  costPer1MTokens: 0.15,
732
732
  taskAble: true
733
+ },
734
+ "gemini-embedding-2": {
735
+ provider: "google",
736
+ model: "gemini-embedding-2",
737
+ // Keep a conservative, testable default embedding dimension. Gemini Embedding 2 supports
738
+ // flexible dimensions via outputDimensionality.
739
+ dimensions: 1536,
740
+ maxTokens: 8192,
741
+ costPer1MTokens: 0.15,
742
+ taskAble: false,
743
+ promptStyle: "embedding-2"
733
744
  }
734
745
  },
735
746
  "openai": {
@@ -763,26 +774,15 @@ var EMBEDDING_MODELS = {
763
774
  maxTokens: 512,
764
775
  costPer1MTokens: 0
765
776
  }
766
- },
767
- "github-copilot": {
768
- "text-embedding-3-small": {
769
- provider: "github-copilot",
770
- model: "text-embedding-3-small",
771
- dimensions: 1536,
772
- maxTokens: 8191,
773
- costPer1MTokens: 0
774
- }
775
777
  }
776
778
  };
777
779
  var DEFAULT_PROVIDER_MODELS = {
778
- "github-copilot": "text-embedding-3-small",
779
780
  "openai": "text-embedding-3-small",
780
781
  "google": "gemini-embedding-001",
781
782
  "ollama": "nomic-embed-text"
782
783
  };
783
784
  var AUTO_DETECT_PROVIDER_ORDER = [
784
785
  "ollama",
785
- "github-copilot",
786
786
  "openai",
787
787
  "google"
788
788
  ];
@@ -808,6 +808,9 @@ function getDefaultIndexingConfig() {
808
808
  maxDepth: 5,
809
809
  maxFilesPerDirectory: 100,
810
810
  fallbackToTextOnMaxChunks: true,
811
+ // Must stay in sync with DEFAULT_LINES_PER_CHUNK in native/src/lib.rs (the napi
812
+ // fallback used when a native caller omits the argument).
813
+ linesPerChunk: 30,
811
814
  gitBlame: { enabled: false }
812
815
  };
813
816
  }
@@ -941,6 +944,7 @@ function parseConfig(raw) {
941
944
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
942
945
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
943
946
  fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
947
+ linesPerChunk: typeof rawIndexing.linesPerChunk === "number" && Number.isFinite(rawIndexing.linesPerChunk) ? Math.min(Math.max(1, Math.floor(rawIndexing.linesPerChunk)), 4294967295) : defaultIndexing.linesPerChunk,
944
948
  gitBlame: {
945
949
  enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
946
950
  }
@@ -983,6 +987,7 @@ function parseConfig(raw) {
983
987
  let embeddingModel;
984
988
  let customProvider;
985
989
  let reranker;
990
+ const githubCopilotDeprecationMessage = '`embeddingProvider: "github-copilot"` is deprecated and no longer available. Migrate existing configs to `embeddingProvider: "google"` and select an explicit Google model. For existing indexes, run `index_codebase` with `force: true` after changing to `gemini-embedding-001` or `gemini-embedding-2` to rebuild embeddings. See docs/configuration.md for details.';
986
991
  if (embeddingProviderValue === "custom") {
987
992
  embeddingProvider = "custom";
988
993
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -1022,6 +1027,8 @@ function parseConfig(raw) {
1022
1027
  } else if (rawEmbeddingModel) {
1023
1028
  embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
1024
1029
  }
1030
+ } else if (embeddingProviderValue === "github-copilot") {
1031
+ throw new Error(githubCopilotDeprecationMessage);
1025
1032
  } else {
1026
1033
  embeddingProvider = "auto";
1027
1034
  }
@@ -1052,10 +1059,21 @@ function parseConfig(raw) {
1052
1059
  timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
1053
1060
  };
1054
1061
  }
1062
+ const rawEmbedding = input.embedding && typeof input.embedding === "object" ? input.embedding : {};
1063
+ const rawEmbeddingBatch = rawEmbedding.batch && typeof rawEmbedding.batch === "object" ? rawEmbedding.batch : null;
1064
+ const embeddingMaxBatchItems = typeof rawEmbeddingBatch?.maxBatchItems === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchItems) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchItems)) : void 0;
1065
+ const embeddingMaxBatchTokens = typeof rawEmbeddingBatch?.maxBatchTokens === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchTokens) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchTokens)) : void 0;
1066
+ const embedding = embeddingMaxBatchItems !== void 0 || embeddingMaxBatchTokens !== void 0 ? {
1067
+ batch: {
1068
+ ...embeddingMaxBatchItems !== void 0 ? { maxBatchItems: embeddingMaxBatchItems } : {},
1069
+ ...embeddingMaxBatchTokens !== void 0 ? { maxBatchTokens: embeddingMaxBatchTokens } : {}
1070
+ }
1071
+ } : {};
1055
1072
  return {
1056
1073
  embeddingProvider,
1057
1074
  embeddingModel,
1058
1075
  customProvider,
1076
+ embedding,
1059
1077
  scope: isValidScope(scopeValue) ? scopeValue : "project",
1060
1078
  include: includeValue ?? DEFAULT_INCLUDE,
1061
1079
  exclude: excludeValue ?? DEFAULT_EXCLUDE,
@@ -1179,9 +1197,9 @@ var import_fs = require("fs");
1179
1197
  var path = __toESM(require("path"), 1);
1180
1198
 
1181
1199
  // src/eval/report-formatters.ts
1182
- function assertFiniteNumber(value, path31) {
1200
+ function assertFiniteNumber(value, path33) {
1183
1201
  if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
1184
- throw new Error(`${path31} must be a finite number`);
1202
+ throw new Error(`${path33} must be a finite number`);
1185
1203
  }
1186
1204
  return value;
1187
1205
  }
@@ -1453,7 +1471,7 @@ function pTimeout(promise, options) {
1453
1471
  } = options;
1454
1472
  let timer;
1455
1473
  let abortHandler;
1456
- const wrappedPromise = new Promise((resolve18, reject) => {
1474
+ const wrappedPromise = new Promise((resolve20, reject) => {
1457
1475
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
1458
1476
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
1459
1477
  }
@@ -1467,7 +1485,7 @@ function pTimeout(promise, options) {
1467
1485
  };
1468
1486
  signal.addEventListener("abort", abortHandler, { once: true });
1469
1487
  }
1470
- promise.then(resolve18, reject);
1488
+ promise.then(resolve20, reject);
1471
1489
  if (milliseconds === Number.POSITIVE_INFINITY) {
1472
1490
  return;
1473
1491
  }
@@ -1475,7 +1493,7 @@ function pTimeout(promise, options) {
1475
1493
  timer = customTimers.setTimeout.call(void 0, () => {
1476
1494
  if (fallback) {
1477
1495
  try {
1478
- resolve18(fallback());
1496
+ resolve20(fallback());
1479
1497
  } catch (error) {
1480
1498
  reject(error);
1481
1499
  }
@@ -1485,7 +1503,7 @@ function pTimeout(promise, options) {
1485
1503
  promise.cancel();
1486
1504
  }
1487
1505
  if (message === false) {
1488
- resolve18();
1506
+ resolve20();
1489
1507
  } else if (message instanceof Error) {
1490
1508
  reject(message);
1491
1509
  } else {
@@ -1887,7 +1905,7 @@ var PQueue = class extends import_index.default {
1887
1905
  // Assign unique ID if not provided
1888
1906
  id: options.id ?? (this.#idAssigner++).toString()
1889
1907
  };
1890
- return new Promise((resolve18, reject) => {
1908
+ return new Promise((resolve20, reject) => {
1891
1909
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1892
1910
  let cleanupQueueAbortHandler = () => void 0;
1893
1911
  const run = async () => {
@@ -1927,7 +1945,7 @@ var PQueue = class extends import_index.default {
1927
1945
  })]);
1928
1946
  }
1929
1947
  const result = await operation;
1930
- resolve18(result);
1948
+ resolve20(result);
1931
1949
  this.emit("completed", result);
1932
1950
  } catch (error) {
1933
1951
  reject(error);
@@ -2115,13 +2133,13 @@ var PQueue = class extends import_index.default {
2115
2133
  });
2116
2134
  }
2117
2135
  async #onEvent(event, filter) {
2118
- return new Promise((resolve18) => {
2136
+ return new Promise((resolve20) => {
2119
2137
  const listener = () => {
2120
2138
  if (filter && !filter()) {
2121
2139
  return;
2122
2140
  }
2123
2141
  this.off(event, listener);
2124
- resolve18();
2142
+ resolve20();
2125
2143
  };
2126
2144
  this.on(event, listener);
2127
2145
  });
@@ -2407,7 +2425,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2407
2425
  const finalDelay = Math.min(delayTime, remainingTime);
2408
2426
  options.signal?.throwIfAborted();
2409
2427
  if (finalDelay > 0) {
2410
- await new Promise((resolve18, reject) => {
2428
+ await new Promise((resolve20, reject) => {
2411
2429
  const onAbort = () => {
2412
2430
  clearTimeout(timeoutToken);
2413
2431
  options.signal?.removeEventListener("abort", onAbort);
@@ -2415,7 +2433,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2415
2433
  };
2416
2434
  const timeoutToken = setTimeout(() => {
2417
2435
  options.signal?.removeEventListener("abort", onAbort);
2418
- resolve18();
2436
+ resolve20();
2419
2437
  }, finalDelay);
2420
2438
  if (options.unref) {
2421
2439
  timeoutToken.unref?.();
@@ -2551,8 +2569,6 @@ async function tryDetectProvider() {
2551
2569
  }
2552
2570
  async function getProviderCredentials(provider) {
2553
2571
  switch (provider) {
2554
- case "github-copilot":
2555
- return getGitHubCopilotCredentials();
2556
2572
  case "openai":
2557
2573
  return getOpenAICredentials();
2558
2574
  case "google":
@@ -2563,22 +2579,6 @@ async function getProviderCredentials(provider) {
2563
2579
  return null;
2564
2580
  }
2565
2581
  }
2566
- function getGitHubCopilotCredentials() {
2567
- const authData = loadOpenCodeAuth();
2568
- const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
2569
- if (!copilotAuth || copilotAuth.type !== "oauth") {
2570
- return null;
2571
- }
2572
- const auth = copilotAuth;
2573
- const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
2574
- return {
2575
- provider: "github-copilot",
2576
- baseUrl,
2577
- refreshToken: copilotAuth.refresh,
2578
- accessToken: copilotAuth.access,
2579
- tokenExpires: copilotAuth.expires
2580
- };
2581
- }
2582
2582
  function getOpenAICredentials() {
2583
2583
  const authData = loadOpenCodeAuth();
2584
2584
  const openaiAuth = authData["openai"];
@@ -2704,8 +2704,6 @@ async function tryDetectOllamaProvider() {
2704
2704
  }
2705
2705
  function getProviderDisplayName(provider) {
2706
2706
  switch (provider) {
2707
- case "github-copilot":
2708
- return "GitHub Copilot";
2709
2707
  case "openai":
2710
2708
  return "OpenAI";
2711
2709
  case "google":
@@ -2930,44 +2928,6 @@ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
2930
2928
  }
2931
2929
  };
2932
2930
 
2933
- // src/embeddings/providers/github-copilot.ts
2934
- var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
2935
- constructor(credentials, modelInfo) {
2936
- super(credentials, modelInfo);
2937
- }
2938
- getToken() {
2939
- if (!this.credentials.refreshToken) {
2940
- throw new Error("No OAuth token available for GitHub");
2941
- }
2942
- return this.credentials.refreshToken;
2943
- }
2944
- async embedBatch(texts) {
2945
- const token = this.getToken();
2946
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
2947
- method: "POST",
2948
- headers: {
2949
- Authorization: `Bearer ${token}`,
2950
- "Content-Type": "application/json",
2951
- Accept: "application/vnd.github+json",
2952
- "X-GitHub-Api-Version": "2022-11-28"
2953
- },
2954
- body: JSON.stringify({
2955
- model: `openai/${this.modelInfo.model}`,
2956
- input: texts
2957
- })
2958
- });
2959
- if (!response.ok) {
2960
- const error = (await response.text()).slice(0, 500);
2961
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
2962
- }
2963
- const data = await response.json();
2964
- return {
2965
- embeddings: data.data.map((d) => d.embedding),
2966
- totalTokensUsed: data.usage.total_tokens
2967
- };
2968
- }
2969
- };
2970
-
2971
2931
  // src/embeddings/providers/google.ts
2972
2932
  var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
2973
2933
  static BATCH_SIZE = 20;
@@ -2975,24 +2935,30 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
2975
2935
  super(credentials, modelInfo);
2976
2936
  }
2977
2937
  async embedQuery(query) {
2978
- const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
2979
- const result = await this.embedWithTaskType([query], taskType);
2938
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
2939
+ const texts = [
2940
+ this.modelInfo.model === "gemini-embedding-2" ? `task: code retrieval | query: ${query}` : query
2941
+ ];
2942
+ const result = await this.embedWithTaskType(texts, taskType);
2980
2943
  return {
2981
2944
  embedding: result.embeddings[0],
2982
2945
  tokensUsed: result.totalTokensUsed
2983
2946
  };
2984
2947
  }
2985
2948
  async embedDocument(document) {
2986
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2987
- const result = await this.embedWithTaskType([document], taskType);
2949
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2950
+ const result = await this.embedWithTaskType([
2951
+ this.modelInfo.model === "gemini-embedding-2" ? `title: none | text: ${document}` : document
2952
+ ], taskType);
2988
2953
  return {
2989
2954
  embedding: result.embeddings[0],
2990
2955
  tokensUsed: result.totalTokensUsed
2991
2956
  };
2992
2957
  }
2993
2958
  async embedBatch(texts) {
2994
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2995
- return this.embedWithTaskType(texts, taskType);
2959
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2960
+ const formattedTexts = this.modelInfo.model === "gemini-embedding-2" ? texts.map((text) => `title: none | text: ${text}`) : texts;
2961
+ return this.embedWithTaskType(formattedTexts, taskType);
2996
2962
  }
2997
2963
  async embedWithTaskType(texts, taskType) {
2998
2964
  const batches = [];
@@ -3042,6 +3008,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3042
3008
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
3043
3009
  static MIN_TRUNCATION_CHARS = 512;
3044
3010
  static REQUEST_TIMEOUT_MS = 12e4;
3011
+ // Set when /api/embed returns 404 so subsequent multi-text batches skip the
3012
+ // batched endpoint and go straight to the legacy per-text path (one probe per
3013
+ // old ollama install, not one probe per batch).
3014
+ batchEndpointUnavailable = false;
3045
3015
  constructor(credentials, modelInfo) {
3046
3016
  super(credentials, modelInfo);
3047
3017
  }
@@ -3059,6 +3029,21 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3059
3029
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
3060
3030
  return message.includes("context length") && (message.includes("exceed") || message.includes("exceeded") || message.includes("too long")) || message.includes("input length exceeds the context length") || message.includes("context length exceeded");
3061
3031
  }
3032
+ // True for a 404 from the newer /api/embed endpoint, i.e. an ollama version that
3033
+ // does not provide it. embedBatch uses this to fall back to the legacy per-text
3034
+ // /api/embeddings path so old ollama installs do not regress.
3035
+ isBatchEndpointUnavailableError(error) {
3036
+ const message = error instanceof Error ? error.message : String(error);
3037
+ return message.includes("Ollama /api/embed not available");
3038
+ }
3039
+ // True for a malformed /api/embed response (wrong vector count or a bad vector).
3040
+ // embedBatch falls back to the per-text path on this so a bad batch response
3041
+ // re-embeds each text cleanly. A text that then fails per-text is not isolated
3042
+ // here; it is isolated on the recovery run, which re-embeds one text per request.
3043
+ isBatchValidationError(error) {
3044
+ const message = error instanceof Error ? error.message : String(error);
3045
+ return message.includes("invalid embedding batch");
3046
+ }
3062
3047
  buildTruncationCandidates(text) {
3063
3048
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
3064
3049
  const candidateLimits = /* @__PURE__ */ new Set();
@@ -3160,7 +3145,74 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3160
3145
  tokensUsed: this.estimateTokens(text)
3161
3146
  };
3162
3147
  }
3163
- async embedBatch(texts) {
3148
+ // Embeds many texts in one POST /api/embed request (input: string[]). Ollama
3149
+ // encodes each input independently, so the model context length applies per input
3150
+ // (the upstream splitter already bounds each input), not over the batch. This
3151
+ // amortizes N HTTP round-trips into one.
3152
+ async embedMany(texts) {
3153
+ const controller = new AbortController();
3154
+ const timeout = setTimeout(
3155
+ () => controller.abort(),
3156
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3157
+ );
3158
+ let response;
3159
+ try {
3160
+ response = await fetch(`${this.credentials.baseUrl}/api/embed`, {
3161
+ method: "POST",
3162
+ headers: {
3163
+ "Content-Type": "application/json"
3164
+ },
3165
+ body: JSON.stringify({
3166
+ model: this.modelInfo.model,
3167
+ input: texts,
3168
+ truncate: false
3169
+ }),
3170
+ signal: controller.signal
3171
+ });
3172
+ } catch (error) {
3173
+ if (error instanceof Error && error.name === "AbortError") {
3174
+ throw new Error(
3175
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3176
+ );
3177
+ }
3178
+ throw error;
3179
+ } finally {
3180
+ clearTimeout(timeout);
3181
+ }
3182
+ if (!response.ok) {
3183
+ const error = (await response.text()).slice(0, 500);
3184
+ if (response.status === 404) {
3185
+ throw new Error(`Ollama /api/embed not available: ${response.status} - ${error}`);
3186
+ }
3187
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
3188
+ }
3189
+ let parsed;
3190
+ try {
3191
+ parsed = await response.json();
3192
+ } catch {
3193
+ throw new Error(
3194
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
3195
+ );
3196
+ }
3197
+ const data = parsed && typeof parsed === "object" ? parsed : {};
3198
+ if (!Array.isArray(data.embeddings) || data.embeddings.length !== texts.length || data.embeddings.some(
3199
+ (value) => !Array.isArray(value) || value.length !== this.modelInfo.dimensions || value.some((v) => typeof v !== "number" || !Number.isFinite(v))
3200
+ )) {
3201
+ throw new Error(
3202
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
3203
+ );
3204
+ }
3205
+ return {
3206
+ embeddings: data.embeddings,
3207
+ totalTokensUsed: texts.reduce((sum, text) => sum + this.estimateTokens(text), 0)
3208
+ };
3209
+ }
3210
+ // Per-text /api/embeddings path shared by the single-text fast path and the
3211
+ // batch fallback. Uses the legacy endpoint one text at a time, so each text gets
3212
+ // its own truncation safety net and a vector validated on its own. A text that
3213
+ // hard-fails per-text throws here and fails the whole request batch; the recovery
3214
+ // run re-embeds one text per request to isolate it.
3215
+ async embedOneByOne(texts) {
3164
3216
  const results = [];
3165
3217
  for (const text of texts) {
3166
3218
  results.push(await this.embedSingleWithFallback(text));
@@ -3170,6 +3222,26 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3170
3222
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
3171
3223
  };
3172
3224
  }
3225
+ async embedBatch(texts) {
3226
+ if (texts.length === 0) {
3227
+ return { embeddings: [], totalTokensUsed: 0 };
3228
+ }
3229
+ if (texts.length === 1 || this.batchEndpointUnavailable) {
3230
+ return this.embedOneByOne(texts);
3231
+ }
3232
+ try {
3233
+ return await this.embedMany(texts);
3234
+ } catch (error) {
3235
+ if (this.isBatchEndpointUnavailableError(error)) {
3236
+ this.batchEndpointUnavailable = true;
3237
+ return this.embedOneByOne(texts);
3238
+ }
3239
+ if (!this.isContextLengthError(error) && !this.isBatchValidationError(error)) {
3240
+ throw error;
3241
+ }
3242
+ return this.embedOneByOne(texts);
3243
+ }
3244
+ }
3173
3245
  };
3174
3246
 
3175
3247
  // src/embeddings/providers/openai.ts
@@ -3204,8 +3276,6 @@ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
3204
3276
  // src/embeddings/provider.ts
3205
3277
  function createEmbeddingProvider(configuredProviderInfo) {
3206
3278
  switch (configuredProviderInfo.provider) {
3207
- case "github-copilot":
3208
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
3209
3279
  case "openai":
3210
3280
  return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
3211
3281
  case "google":
@@ -3221,85 +3291,6 @@ function createEmbeddingProvider(configuredProviderInfo) {
3221
3291
  }
3222
3292
  }
3223
3293
 
3224
- // src/rerank/index.ts
3225
- function createReranker(config) {
3226
- if (!config.enabled) {
3227
- return new NoOpReranker();
3228
- }
3229
- return new SiliconFlowReranker(config);
3230
- }
3231
- var NoOpReranker = class {
3232
- isAvailable() {
3233
- return false;
3234
- }
3235
- async rerank(_query, documents, _topN) {
3236
- return {
3237
- results: documents.map((_, index) => ({ index, relevanceScore: 0 }))
3238
- };
3239
- }
3240
- };
3241
- var SiliconFlowReranker = class {
3242
- config;
3243
- constructor(config) {
3244
- this.config = config;
3245
- }
3246
- isAvailable() {
3247
- return this.config.enabled && !!this.config.baseUrl && !!this.config.model;
3248
- }
3249
- async rerank(query, documents, topN) {
3250
- if (documents.length === 0) {
3251
- return { results: [] };
3252
- }
3253
- const headers = {
3254
- "Content-Type": "application/json"
3255
- };
3256
- if (this.config.apiKey) {
3257
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
3258
- }
3259
- const baseUrl = this.config.baseUrl;
3260
- if (!baseUrl) {
3261
- throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
3262
- }
3263
- const timeoutMs = this.config.timeoutMs ?? 3e4;
3264
- const controller = new AbortController();
3265
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
3266
- try {
3267
- const response = await fetch(`${baseUrl}/rerank`, {
3268
- method: "POST",
3269
- headers,
3270
- body: JSON.stringify({
3271
- model: this.config.model,
3272
- query,
3273
- documents,
3274
- top_n: topN ?? this.config.topN ?? 20,
3275
- return_documents: false
3276
- }),
3277
- signal: controller.signal
3278
- });
3279
- clearTimeout(timeout);
3280
- if (!response.ok) {
3281
- const errorText = await response.text();
3282
- throw new Error(`Rerank API error: ${response.status} - ${errorText}`);
3283
- }
3284
- const data = await response.json();
3285
- return {
3286
- results: data.results.map((r) => ({
3287
- index: r.index,
3288
- relevanceScore: r.relevance_score,
3289
- document: r.document?.text
3290
- })),
3291
- tokensUsed: data.meta?.tokens?.input_tokens
3292
- };
3293
- } catch (error) {
3294
- clearTimeout(timeout);
3295
- if (error instanceof Error && error.name === "AbortError") {
3296
- throw new Error(`Rerank API request timed out after ${timeoutMs}ms`);
3297
- }
3298
- throw error;
3299
- }
3300
- }
3301
- };
3302
-
3303
3294
  // src/utils/files.ts
3304
3295
  var import_ignore = __toESM(require_ignore(), 1);
3305
3296
  var import_fs3 = require("fs");
@@ -3461,8 +3452,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3461
3452
  if (entry.isDirectory()) {
3462
3453
  subdirs.push({ fullPath, relativePath });
3463
3454
  } else if (entry.isFile()) {
3464
- const stat4 = await import_fs3.promises.stat(fullPath);
3465
- if (stat4.size > maxFileSize) {
3455
+ const stat5 = await import_fs3.promises.stat(fullPath);
3456
+ if (stat5.size > maxFileSize) {
3466
3457
  skipped.push({ path: relativePath, reason: "too_large" });
3467
3458
  continue;
3468
3459
  }
@@ -3480,7 +3471,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3480
3471
  }
3481
3472
  }
3482
3473
  if (matched) {
3483
- filesInDir.push({ path: fullPath, size: stat4.size });
3474
+ filesInDir.push({ path: fullPath, size: stat5.size });
3484
3475
  }
3485
3476
  }
3486
3477
  }
@@ -3537,8 +3528,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3537
3528
  }
3538
3529
  for (const resolvedKbRoot of normalizedRoots) {
3539
3530
  try {
3540
- const stat4 = await import_fs3.promises.stat(resolvedKbRoot);
3541
- if (!stat4.isDirectory()) {
3531
+ const stat5 = await import_fs3.promises.stat(resolvedKbRoot);
3532
+ if (!stat5.isDirectory()) {
3542
3533
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3543
3534
  continue;
3544
3535
  }
@@ -4303,12 +4294,12 @@ try {
4303
4294
  }
4304
4295
 
4305
4296
  // src/native/parsing.ts
4306
- function parseFileAsText(filePath, content) {
4307
- const result = native.parseFileAsText(filePath, content);
4297
+ function parseFileAsText(filePath, content, linesPerChunk) {
4298
+ const result = native.parseFileAsText(filePath, content, linesPerChunk);
4308
4299
  return result.map(mapChunk);
4309
4300
  }
4310
- function parseFiles(files) {
4311
- const result = native.parseFiles(files);
4301
+ function parseFiles(files, linesPerChunk) {
4302
+ const result = native.parseFiles(files, linesPerChunk);
4312
4303
  return result.map((f) => ({
4313
4304
  path: f.path,
4314
4305
  chunks: f.chunks.map(mapChunk),
@@ -4385,13 +4376,13 @@ var VectorStore = class {
4385
4376
  const metadata = items.map((i) => JSON.stringify(i.metadata));
4386
4377
  this.inner.addBatch(ids, vectors, metadata);
4387
4378
  }
4388
- search(queryVector, limit = 10) {
4379
+ search(queryVector, limit = 10, allowedIds) {
4389
4380
  if (queryVector.length !== this.dimensions) {
4390
4381
  throw new Error(
4391
4382
  `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
4392
4383
  );
4393
4384
  }
4394
- const results = this.inner.search(queryVector, limit);
4385
+ const results = allowedIds === void 0 ? this.inner.search(queryVector, limit) : this.inner.searchFiltered(queryVector, limit, allowedIds);
4395
4386
  return results.map((r) => ({
4396
4387
  id: r.id,
4397
4388
  score: r.score,
@@ -4619,6 +4610,10 @@ var Database = class _Database {
4619
4610
  this.throwIfClosed();
4620
4611
  return this.inner.getBranchChunkIds(branch);
4621
4612
  }
4613
+ getChunkIdsByBlameDate(since, until) {
4614
+ this.throwIfClosed();
4615
+ return this.inner.getChunkIdsByBlameDate(since, until);
4616
+ }
4622
4617
  getBranchDelta(branch, baseBranch) {
4623
4618
  this.throwIfClosed();
4624
4619
  return this.inner.getBranchDelta(branch, baseBranch);
@@ -4865,11 +4860,11 @@ function resolveGitDir(repoRoot) {
4865
4860
  return null;
4866
4861
  }
4867
4862
  try {
4868
- const stat4 = (0, import_fs5.statSync)(gitPath);
4869
- if (stat4.isDirectory()) {
4863
+ const stat5 = (0, import_fs5.statSync)(gitPath);
4864
+ if (stat5.isDirectory()) {
4870
4865
  return gitPath;
4871
4866
  }
4872
- if (stat4.isFile()) {
4867
+ if (stat5.isFile()) {
4873
4868
  const content = (0, import_fs5.readFileSync)(gitPath, "utf-8").trim();
4874
4869
  const match = content.match(/^gitdir:\s*(.+)$/);
4875
4870
  if (match) {
@@ -5374,8 +5369,8 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
5374
5369
  return false;
5375
5370
  }
5376
5371
  function isPathWithinRoot(filePath, rootPath) {
5377
- const relative12 = path9.relative(path9.resolve(rootPath), path9.resolve(filePath));
5378
- return relative12 === "" || !relative12.startsWith(`..${path9.sep}`) && relative12 !== ".." && !path9.isAbsolute(relative12);
5372
+ const relative14 = path9.relative(path9.resolve(rootPath), path9.resolve(filePath));
5373
+ return relative14 === "" || !relative14.startsWith(`..${path9.sep}`) && relative14 !== ".." && !path9.isAbsolute(relative14);
5379
5374
  }
5380
5375
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
5381
5376
  if (await pathExists(worktreePath)) return false;
@@ -5580,6 +5575,9 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
5580
5575
  const fallbackPath = path10.join(mainRepoRoot, relativePath);
5581
5576
  return (0, import_fs7.existsSync)(fallbackPath) ? fallbackPath : null;
5582
5577
  }
5578
+ function getHostProjectConfigRelativePath(host) {
5579
+ return getProjectConfigRelativePath(host);
5580
+ }
5583
5581
  function getProjectConfigCandidatePaths(projectRoot, host) {
5584
5582
  const candidates = [path10.join(projectRoot, getProjectConfigRelativePath(host))];
5585
5583
  if (host !== "opencode") {
@@ -5671,6 +5669,9 @@ function resolveProjectConfigPath(projectRoot, host) {
5671
5669
  const candidates = getProjectConfigCandidatePaths(projectRoot, host);
5672
5670
  return candidates.find((candidate) => (0, import_fs7.existsSync)(candidate)) ?? path10.join(projectRoot, getProjectConfigRelativePath(host));
5673
5671
  }
5672
+ function resolveWritableProjectConfigPath(projectRoot, host) {
5673
+ return path10.join(projectRoot, getProjectConfigRelativePath(host));
5674
+ }
5674
5675
  function resolveProjectIndexPath(projectRoot, scope, host) {
5675
5676
  if (scope === "global") {
5676
5677
  return resolveGlobalIndexPath(host);
@@ -5933,11 +5934,11 @@ function normalizeFiles(rawFiles, projectRoot) {
5933
5934
  for (const raw of rawFiles) {
5934
5935
  if (raw.length === 0) continue;
5935
5936
  const absolute = path11.resolve(root, raw);
5936
- const relative12 = path11.relative(root, absolute);
5937
- if (path11.isAbsolute(raw) || relative12 === ".." || relative12.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative12)) {
5937
+ const relative14 = path11.relative(root, absolute);
5938
+ if (path11.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative14)) {
5938
5939
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
5939
5940
  }
5940
- const cleaned = relative12.startsWith(`.${path11.sep}`) ? relative12.slice(2) : relative12;
5941
+ const cleaned = relative14.startsWith(`.${path11.sep}`) ? relative14.slice(2) : relative14;
5941
5942
  if (!seen.has(cleaned)) {
5942
5943
  seen.add(cleaned);
5943
5944
  result.push(cleaned);
@@ -6274,7 +6275,7 @@ function analyzeQueryIntent(query) {
6274
6275
  }
6275
6276
  function isTestPath(filePath) {
6276
6277
  const normalized = normalizePath(filePath);
6277
- return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /\.(?:test|spec)\.[^/]+$/u.test(normalized);
6278
+ return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /(?:\.(?:test|spec)|_(?:test|spec))\.[^/]+$/u.test(normalized) || /(?:^|\/)(?:test|spec)_[^/]+\.[^/]+$/u.test(normalized);
6278
6279
  }
6279
6280
  function isFixturePath(filePath) {
6280
6281
  const normalized = normalizePath(filePath);
@@ -6376,6 +6377,10 @@ function scoreCandidate(query, intent, candidate, originalIndex) {
6376
6377
  let boost = 0;
6377
6378
  if (intent.primary === "conceptual") {
6378
6379
  boost += Math.min(0.14, overlap * 0.14);
6380
+ if (intent.preferSourcePaths) {
6381
+ boost += implementationPath ? 0.32 : 0;
6382
+ if (testPath || fixturePath || docsPath) boost -= 0.35;
6383
+ }
6379
6384
  if (generatedOrVendor) boost -= 0.18;
6380
6385
  if (importChunk || weakContainer) boost -= 0.04;
6381
6386
  } else if (intent.primary === "test") {
@@ -6681,7 +6686,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
6681
6686
  return cached;
6682
6687
  }
6683
6688
  }
6684
- const overfetchLimit = Math.max(options.limit * 4, options.limit);
6689
+ const overfetchFactor = prioritizeSourcePaths ? 12 : 4;
6690
+ const overfetchLimit = Math.max(options.limit * overfetchFactor, options.limit);
6685
6691
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
6686
6692
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
6687
6693
  const rerankPool = fused.slice(0, rerankPoolLimit);
@@ -7289,6 +7295,19 @@ function parseOwner(value) {
7289
7295
  if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
7290
7296
  if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
7291
7297
  if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
7298
+ if (candidate.recoveryProtocolVersion !== void 0 && candidate.recoveryProtocolVersion !== 1) return null;
7299
+ if (candidate.projectRoot !== void 0 && typeof candidate.projectRoot !== "string") return null;
7300
+ if (candidate.scopedRoots !== void 0) {
7301
+ if (!Array.isArray(candidate.scopedRoots) || candidate.scopedRoots.some((root) => typeof root !== "string")) {
7302
+ return null;
7303
+ }
7304
+ }
7305
+ if (candidate.clearRecovery !== void 0) {
7306
+ const recovery = candidate.clearRecovery;
7307
+ if (typeof recovery !== "object" || recovery === null || recovery.phase !== "clearing" || typeof recovery.embeddingProvider !== "string" || recovery.embeddingProvider.length === 0 || typeof recovery.embeddingModel !== "string" || recovery.embeddingModel.length === 0 || !Number.isInteger(recovery.embeddingDimensions) || (recovery.embeddingDimensions ?? 0) <= 0 || typeof recovery.embeddingStrategyVersion !== "string" || recovery.embeddingStrategyVersion.length === 0 || recovery.compatibilityDecision !== "compatible" && recovery.compatibilityDecision !== "embedding-strategy-mismatch" && recovery.compatibilityDecision !== "incompatible" || candidate.operation !== "clear" && candidate.operation !== "force-index") {
7308
+ return null;
7309
+ }
7310
+ }
7292
7311
  return candidate;
7293
7312
  }
7294
7313
  function parseReclaimOwner(value) {
@@ -7529,13 +7548,18 @@ function isTransientIndexLockContention(error) {
7529
7548
  if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
7530
7549
  return error.reason === "active" || error.reason === "reclaiming";
7531
7550
  }
7532
- function acquireIndexLock(indexPath, operation) {
7551
+ function acquireIndexLock(indexPath, operation, recoveryScope) {
7533
7552
  (0, import_fs9.mkdirSync)(indexPath, { recursive: true });
7534
7553
  const canonicalIndexPath = import_fs9.realpathSync.native(indexPath);
7535
7554
  const lockPath = path13.join(canonicalIndexPath, "indexing.lock");
7536
7555
  cleanupDeadPublicationCandidates(canonicalIndexPath);
7537
7556
  for (let attempt = 0; attempt < 6; attempt += 1) {
7538
- const owner = createOwner(operation);
7557
+ const owner = recoveryScope === void 0 ? createOwner(operation) : {
7558
+ ...createOwner(operation),
7559
+ recoveryProtocolVersion: 1,
7560
+ projectRoot: recoveryScope.projectRoot,
7561
+ scopedRoots: recoveryScope.scopedRoots
7562
+ };
7539
7563
  if (publishJsonDirectory(lockPath, owner)) {
7540
7564
  const lease = {
7541
7565
  canonicalIndexPath,
@@ -7600,6 +7624,33 @@ function releaseIndexLock(lease) {
7600
7624
  }
7601
7625
  return true;
7602
7626
  }
7627
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
7628
+ const currentOwner = readDirectoryOwner(lease.lockPath);
7629
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
7630
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
7631
+ }
7632
+ const nextOwner = { ...currentOwner };
7633
+ if (clearRecovery === null) {
7634
+ delete nextOwner.clearRecovery;
7635
+ } else {
7636
+ nextOwner.clearRecovery = clearRecovery;
7637
+ }
7638
+ const ownerPath = path13.join(lease.lockPath, OWNER_FILE_NAME);
7639
+ const temporaryPath = path13.join(
7640
+ lease.lockPath,
7641
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${(0, import_crypto2.randomUUID)()}`
7642
+ );
7643
+ try {
7644
+ (0, import_fs9.writeFileSync)(temporaryPath, JSON.stringify(nextOwner), {
7645
+ encoding: "utf-8",
7646
+ flag: "wx",
7647
+ mode: 384
7648
+ });
7649
+ retryTransientFilesystemOperation(() => (0, import_fs9.renameSync)(temporaryPath, ownerPath));
7650
+ } finally {
7651
+ if ((0, import_fs9.existsSync)(temporaryPath)) (0, import_fs9.rmSync)(temporaryPath, { force: true });
7652
+ }
7653
+ }
7603
7654
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
7604
7655
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
7605
7656
  temporaryCounter += 1;
@@ -7837,6 +7888,18 @@ function createFailedBatchWriter(targetPath) {
7837
7888
  temporaryPath
7838
7889
  };
7839
7890
  }
7891
+ function writeFailedBatchRecords(targetPath, records) {
7892
+ const writer = createFailedBatchWriter(targetPath);
7893
+ try {
7894
+ for (const record of records) {
7895
+ writer.write(record);
7896
+ }
7897
+ writer.commit();
7898
+ } catch (error) {
7899
+ writer.cleanup();
7900
+ throw error;
7901
+ }
7902
+ }
7840
7903
  function* readLegacyFailedBatchRecords(filePath, options) {
7841
7904
  const rawData = fs2.readFileSync(filePath, "utf-8");
7842
7905
  const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
@@ -8119,14 +8182,18 @@ function getSafeEmbeddingChunkTokenLimit(provider) {
8119
8182
  const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
8120
8183
  return Math.min(2e3, maxChunkTokens);
8121
8184
  }
8122
- function getDynamicBatchOptions(provider) {
8123
- if (provider.provider === "ollama") {
8124
- return {
8125
- maxBatchTokens: provider.modelInfo.maxTokens,
8126
- maxBatchItems: 1
8127
- };
8185
+ var DEFAULT_OLLAMA_MAX_BATCH_ITEMS = 16;
8186
+ var DEFAULT_OLLAMA_MAX_BATCH_TOKENS = 65536;
8187
+ function getDynamicBatchOptions(provider, embeddingBatch) {
8188
+ if (provider.provider !== "ollama") {
8189
+ return {};
8128
8190
  }
8129
- return {};
8191
+ const base = { maxBatchTokens: DEFAULT_OLLAMA_MAX_BATCH_TOKENS, maxBatchItems: DEFAULT_OLLAMA_MAX_BATCH_ITEMS };
8192
+ return {
8193
+ ...base,
8194
+ ...typeof embeddingBatch?.maxBatchTokens === "number" && Number.isFinite(embeddingBatch.maxBatchTokens) ? { maxBatchTokens: embeddingBatch.maxBatchTokens } : {},
8195
+ ...typeof embeddingBatch?.maxBatchItems === "number" && Number.isFinite(embeddingBatch.maxBatchItems) ? { maxBatchItems: embeddingBatch.maxBatchItems } : {}
8196
+ };
8130
8197
  }
8131
8198
  function isSqliteCorruptionError(error) {
8132
8199
  const message = getErrorMessage3(error).toLowerCase();
@@ -8144,6 +8211,14 @@ function getPendingChunkId(rawChunk) {
8144
8211
  const id = rawChunk.id;
8145
8212
  return typeof id === "string" ? id : null;
8146
8213
  }
8214
+ function parseBlameTimestamp(value, endOfDay) {
8215
+ let timestampMs = Date.parse(value);
8216
+ if (Number.isNaN(timestampMs)) return null;
8217
+ if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
8218
+ timestampMs += 24 * 60 * 60 * 1e3 - 1;
8219
+ }
8220
+ return Math.floor(timestampMs / 1e3);
8221
+ }
8147
8222
  function metadataFromBlame(blame) {
8148
8223
  if (!blame) {
8149
8224
  return {};
@@ -8290,7 +8365,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
8290
8365
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
8291
8366
  return [...promoted, ...remainder];
8292
8367
  }
8293
- function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
8368
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source", allowNonSourcePaths = false) {
8294
8369
  if (!prioritizeSourcePaths) {
8295
8370
  return [];
8296
8371
  }
@@ -8310,7 +8385,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8310
8385
  if (!isImplementationChunkType(chunkType)) {
8311
8386
  return false;
8312
8387
  }
8313
- if (!isLikelyImplementationPath2(chunk.filePath)) {
8388
+ if (!allowNonSourcePaths && !isLikelyImplementationPath2(chunk.filePath)) {
8314
8389
  return false;
8315
8390
  }
8316
8391
  const nameLower = (chunk.name ?? "").toLowerCase();
@@ -8374,7 +8449,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8374
8449
  }
8375
8450
  foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
8376
8451
  }
8377
- if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
8452
+ if (foundCoveringChunk || !allowNonSourcePaths && !isLikelyImplementationPath2(symbol.filePath)) {
8378
8453
  continue;
8379
8454
  }
8380
8455
  const symbolName = symbol.name.toLowerCase();
@@ -8428,7 +8503,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8428
8503
  const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
8429
8504
  if (ranked.length === 0) {
8430
8505
  const implementationFallback = fallbackCandidates.filter(
8431
- (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath2(candidate.metadata.filePath)
8506
+ (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && (allowNonSourcePaths || isLikelyImplementationPath2(candidate.metadata.filePath))
8432
8507
  );
8433
8508
  for (const candidate of implementationFallback) {
8434
8509
  const nameLower = (candidate.metadata.name ?? "").toLowerCase();
@@ -8544,10 +8619,16 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
8544
8619
  return false;
8545
8620
  }
8546
8621
  if (options?.blameSince) {
8547
- const sinceMs = Date.parse(options.blameSince);
8548
- if (Number.isNaN(sinceMs)) return false;
8622
+ const since = parseBlameTimestamp(options.blameSince, false);
8623
+ if (since === null) return false;
8549
8624
  const committedAt = candidate.metadata.blameCommittedAt;
8550
- if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
8625
+ if (committedAt === void 0 || committedAt < since) return false;
8626
+ }
8627
+ if (options?.blameUntil) {
8628
+ const until = parseBlameTimestamp(options.blameUntil, true);
8629
+ if (until === null) return false;
8630
+ const committedAt = candidate.metadata.blameCommittedAt;
8631
+ if (committedAt === void 0 || committedAt > until) return false;
8551
8632
  }
8552
8633
  return true;
8553
8634
  }
@@ -8583,7 +8664,6 @@ var Indexer = class _Indexer {
8583
8664
  database = null;
8584
8665
  provider = null;
8585
8666
  configuredProviderInfo = null;
8586
- reranker = null;
8587
8667
  fileHashCache = /* @__PURE__ */ new Map();
8588
8668
  fileHashCachePath = "";
8589
8669
  failedBatchesPath = "";
@@ -8604,9 +8684,10 @@ var Indexer = class _Indexer {
8604
8684
  writerArtifactFingerprint = null;
8605
8685
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
8606
8686
  fileBatchLimits;
8687
+ checkpointIntervalChunks;
8607
8688
  constructor(projectRoot, config, host, runtimeOptions = {}) {
8608
8689
  this.projectRoot = projectRoot;
8609
- this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8690
+ this.projectIdentityHash = this.getProjectIdentityHash(projectRoot);
8610
8691
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
8611
8692
  this.branchNameOverride = runtimeOptions.branchName;
8612
8693
  this.catalogIdentityOverride = runtimeOptions.catalogIdentity;
@@ -8616,6 +8697,7 @@ var Indexer = class _Indexer {
8616
8697
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
8617
8698
  this.indexPathOverride = runtimeOptions.indexPath;
8618
8699
  this.fileBatchLimits = runtimeOptions.fileBatchLimits;
8700
+ this.checkpointIntervalChunks = runtimeOptions.checkpointIntervalChunks;
8619
8701
  this.config = config;
8620
8702
  this.host = host;
8621
8703
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -8727,6 +8809,9 @@ var Indexer = class _Indexer {
8727
8809
  return path15.resolve(targetPath);
8728
8810
  }
8729
8811
  }
8812
+ getProjectIdentityHash(projectRoot) {
8813
+ return hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8814
+ }
8730
8815
  isProjectOwnedIndexPath() {
8731
8816
  return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
8732
8817
  }
@@ -8743,7 +8828,6 @@ var Indexer = class _Indexer {
8743
8828
  this.database = null;
8744
8829
  this.provider = null;
8745
8830
  this.configuredProviderInfo = null;
8746
- this.reranker = null;
8747
8831
  this.indexCompatibility = null;
8748
8832
  this.initializationMode = "none";
8749
8833
  this.readIssues = [];
@@ -8764,7 +8848,10 @@ var Indexer = class _Indexer {
8764
8848
  }
8765
8849
  async withIndexMutationLease(operation, callback) {
8766
8850
  this.refreshBranchInfo();
8767
- const lease = acquireIndexLock(this.indexPath, operation);
8851
+ const lease = acquireIndexLock(this.indexPath, operation, {
8852
+ projectRoot: this.projectRoot,
8853
+ scopedRoots: this.getScopedRoots()
8854
+ });
8768
8855
  this.indexPath = lease.canonicalIndexPath;
8769
8856
  this.refreshRuntimeArtifactPaths();
8770
8857
  this.activeIndexLease = lease;
@@ -8819,6 +8906,7 @@ var Indexer = class _Indexer {
8819
8906
  }
8820
8907
  loadFileHashCache() {
8821
8908
  if (!(0, import_fs10.existsSync)(this.fileHashCachePath)) {
8909
+ this.fileHashCache = /* @__PURE__ */ new Map();
8822
8910
  return;
8823
8911
  }
8824
8912
  try {
@@ -8858,10 +8946,10 @@ var Indexer = class _Indexer {
8858
8946
  invertedIndex.serialize()
8859
8947
  );
8860
8948
  }
8861
- getScopedRoots() {
8862
- const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
8949
+ getScopedRoots(projectRoot = this.projectRoot) {
8950
+ const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
8863
8951
  for (const kbRoot of this.config.knowledgeBases) {
8864
- roots.add(this.getCanonicalPath(path15.resolve(this.projectRoot, kbRoot)));
8952
+ roots.add(this.getCanonicalPath(path15.resolve(projectRoot, kbRoot)));
8865
8953
  }
8866
8954
  return Array.from(roots);
8867
8955
  }
@@ -8932,14 +9020,17 @@ var Indexer = class _Indexer {
8932
9020
  getLegacyBranchCatalogKey() {
8933
9021
  return this.currentBranch || "default";
8934
9022
  }
8935
- getLegacyMigrationMetadataKey() {
8936
- return `index.globalBranchMigration.${this.projectIdentityHash}`;
9023
+ getLegacyMigrationMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9024
+ return `index.globalBranchMigration.${projectIdentityHash}`;
8937
9025
  }
8938
- getProjectEmbeddingStrategyMetadataKey() {
8939
- return `index.embeddingStrategyVersion.${this.projectIdentityHash}`;
9026
+ getProjectEmbeddingStrategyMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9027
+ return `index.embeddingStrategyVersion.${projectIdentityHash}`;
8940
9028
  }
8941
- getProjectForceReembedMetadataKey() {
8942
- return `index.forceReembed.${this.projectIdentityHash}`;
9029
+ getProjectForceReembedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9030
+ return `index.forceReembed.${projectIdentityHash}`;
9031
+ }
9032
+ getProjectMigrationFinalizedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9033
+ return `index.migrationFinalized.${projectIdentityHash}`;
8943
9034
  }
8944
9035
  getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
8945
9036
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
@@ -9045,7 +9136,7 @@ var Indexer = class _Indexer {
9045
9136
  const legacy = this.getLegacyBranchCatalogKey();
9046
9137
  return primary === legacy ? [primary] : [primary, legacy];
9047
9138
  }
9048
- getProjectLocalScopedOwnershipIds(roots) {
9139
+ getProjectLocalScopedOwnershipIds(roots, projectRoot = this.projectRoot) {
9049
9140
  const chunkIds = /* @__PURE__ */ new Set();
9050
9141
  const symbolIds = /* @__PURE__ */ new Set();
9051
9142
  if (!this.database) {
@@ -9053,10 +9144,10 @@ var Indexer = class _Indexer {
9053
9144
  }
9054
9145
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
9055
9146
  ...Array.from(this.fileHashCache.keys()).filter(
9056
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
9147
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
9057
9148
  ),
9058
9149
  ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
9059
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
9150
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
9060
9151
  )
9061
9152
  ]);
9062
9153
  for (const filePath of projectLocalFilePaths) {
@@ -9069,15 +9160,16 @@ var Indexer = class _Indexer {
9069
9160
  }
9070
9161
  return { chunkIds, symbolIds };
9071
9162
  }
9072
- getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
9163
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds, projectRoot = this.projectRoot) {
9073
9164
  if (this.config.scope !== "global") {
9074
9165
  return this.getBranchCatalogCleanupKeys();
9075
9166
  }
9076
9167
  const keys = /* @__PURE__ */ new Set();
9077
9168
  const projectChunkIdSet = new Set(projectChunkIds);
9078
9169
  const projectSymbolIdSet = new Set(projectSymbolIds);
9170
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
9079
9171
  for (const branchKey of this.database?.getAllBranches() ?? []) {
9080
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
9172
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
9081
9173
  keys.add(branchKey);
9082
9174
  continue;
9083
9175
  }
@@ -9087,8 +9179,10 @@ var Indexer = class _Indexer {
9087
9179
  keys.add(branchKey);
9088
9180
  }
9089
9181
  }
9090
- for (const branchKey of this.getBranchCatalogCleanupKeys()) {
9091
- keys.add(branchKey);
9182
+ if (projectRoot === this.projectRoot) {
9183
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
9184
+ keys.add(branchKey);
9185
+ }
9092
9186
  }
9093
9187
  return Array.from(keys);
9094
9188
  }
@@ -9096,10 +9190,10 @@ var Indexer = class _Indexer {
9096
9190
  const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
9097
9191
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
9098
9192
  }
9099
- isFileInProjectRoot(filePath) {
9193
+ isFileInProjectRoot(filePath, projectRoot = this.projectRoot) {
9100
9194
  return isPathWithinRoot2(
9101
9195
  this.getCanonicalStoredFilePath(filePath),
9102
- this.getCanonicalPath(this.projectRoot)
9196
+ this.getCanonicalPath(projectRoot)
9103
9197
  );
9104
9198
  }
9105
9199
  clearScopedFileHashCache(roots) {
@@ -9141,12 +9235,12 @@ var Indexer = class _Indexer {
9141
9235
  }
9142
9236
  return false;
9143
9237
  }
9144
- hasForeignScopedBranchData() {
9238
+ hasForeignScopedBranchData(projectRoot = this.projectRoot, roots = this.getScopedRoots(projectRoot)) {
9145
9239
  if (!this.database || this.config.scope !== "global") {
9146
9240
  return false;
9147
9241
  }
9148
- const roots = this.getScopedRoots();
9149
- const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
9242
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
9243
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots, projectRoot);
9150
9244
  return this.database.getAllBranches().some(
9151
9245
  (branchKey) => {
9152
9246
  const branchChunkIds = this.database.getBranchChunkIds(branchKey);
@@ -9155,7 +9249,7 @@ var Indexer = class _Indexer {
9155
9249
  if (!hasBranchData) {
9156
9250
  return false;
9157
9251
  }
9158
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
9252
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
9159
9253
  return false;
9160
9254
  }
9161
9255
  const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
@@ -9164,7 +9258,7 @@ var Indexer = class _Indexer {
9164
9258
  }
9165
9259
  );
9166
9260
  }
9167
- clearSharedIndexProjectData(store, invertedIndex, database, roots) {
9261
+ clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot = this.projectRoot) {
9168
9262
  const allMetadata = store.getAllMetadata();
9169
9263
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
9170
9264
  const filePaths = /* @__PURE__ */ new Set([
@@ -9172,7 +9266,7 @@ var Indexer = class _Indexer {
9172
9266
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
9173
9267
  ]);
9174
9268
  const projectLocalFilePaths = new Set(
9175
- Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath))
9269
+ Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath, projectRoot))
9176
9270
  );
9177
9271
  const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
9178
9272
  for (const filePath of filePaths) {
@@ -9182,7 +9276,7 @@ var Indexer = class _Indexer {
9182
9276
  }
9183
9277
  const removedChunkIdList = Array.from(removedChunkIds);
9184
9278
  const projectLocalChunkIds = new Set(
9185
- scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath)).map(({ key }) => key)
9279
+ scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath, projectRoot)).map(({ key }) => key)
9186
9280
  );
9187
9281
  for (const filePath of projectLocalFilePaths) {
9188
9282
  for (const chunk of database.getChunksByFile(filePath)) {
@@ -9201,7 +9295,8 @@ var Indexer = class _Indexer {
9201
9295
  }
9202
9296
  const branchCleanupKeys = this.getProjectScopedBranchCatalogCleanupKeys(
9203
9297
  Array.from(projectLocalChunkIds),
9204
- Array.from(projectLocalSymbolIds)
9298
+ Array.from(projectLocalSymbolIds),
9299
+ projectRoot
9205
9300
  );
9206
9301
  for (const branchKey of branchCleanupKeys) {
9207
9302
  database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
@@ -9236,29 +9331,96 @@ var Indexer = class _Indexer {
9236
9331
  database.gcOrphanSymbols();
9237
9332
  database.gcOrphanEmbeddings();
9238
9333
  database.gcOrphanChunks();
9239
- store.save();
9240
9334
  this.saveInvertedIndex(invertedIndex);
9335
+ store.save();
9241
9336
  return {
9242
9337
  removedChunkIds: removedChunkIdList,
9243
9338
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
9244
9339
  };
9245
9340
  }
9341
+ getCurrentClearRecoveryState() {
9342
+ if (!this.configuredProviderInfo) {
9343
+ throw new Error("Cannot persist clear recovery state before the embedding provider is initialized");
9344
+ }
9345
+ const compatibility = this.checkCompatibility();
9346
+ const compatibilityDecision = compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible";
9347
+ return {
9348
+ phase: "clearing",
9349
+ embeddingProvider: this.configuredProviderInfo.provider,
9350
+ embeddingModel: this.configuredProviderInfo.modelInfo.model,
9351
+ embeddingDimensions: this.configuredProviderInfo.modelInfo.dimensions,
9352
+ embeddingStrategyVersion: EMBEDDING_STRATEGY_VERSION,
9353
+ compatibilityDecision
9354
+ };
9355
+ }
9356
+ beginClearRecoveryState() {
9357
+ const recovery = this.getCurrentClearRecoveryState();
9358
+ setIndexLockClearRecoveryState(this.requireActiveLease(), recovery);
9359
+ return recovery;
9360
+ }
9361
+ finishClearRecoveryState() {
9362
+ setIndexLockClearRecoveryState(this.requireActiveLease(), null);
9363
+ }
9364
+ matchesCurrentClearRecoveryConfiguration(recovery) {
9365
+ const configuredProviderInfo = this.configuredProviderInfo;
9366
+ return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
9367
+ }
9368
+ hasUnknownLegacyForceIndexClear(owner) {
9369
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && (0, import_fs10.existsSync)(path15.join(this.indexPath, "force-index-phase"));
9370
+ }
9246
9371
  async recoverFromInterruptedIndexingUnlocked(owners) {
9247
9372
  for (const owner of owners) {
9248
9373
  this.logger.warn("Detected interrupted indexing session, recovering...", {
9249
9374
  pid: owner.pid,
9250
9375
  hostname: owner.hostname,
9251
9376
  operation: owner.operation,
9252
- startedAt: owner.startedAt
9377
+ startedAt: owner.startedAt,
9378
+ projectRoot: owner.projectRoot
9253
9379
  });
9254
9380
  }
9255
9381
  if (this.config.scope === "global") {
9256
- if ((0, import_fs10.existsSync)(this.fileHashCachePath)) {
9257
- (0, import_fs10.unlinkSync)(this.fileHashCachePath);
9382
+ const clearScopes = [];
9383
+ for (const owner of owners) {
9384
+ if (this.hasUnknownLegacyForceIndexClear(owner)) {
9385
+ throw new Error(
9386
+ `Cannot automatically recover interrupted force-index ${owner.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
9387
+ );
9388
+ }
9389
+ if (owner.operation === "clear" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1) {
9390
+ throw new Error(
9391
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating recovery state is unknown. The recovery marker was retained for manual inspection.`
9392
+ );
9393
+ }
9394
+ if (owner.clearRecovery === void 0) continue;
9395
+ if (!owner.projectRoot || !owner.scopedRoots || owner.scopedRoots.length === 0) {
9396
+ throw new Error(
9397
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating project scope is unknown. The recovery marker was retained for manual inspection.`
9398
+ );
9399
+ }
9400
+ if (!this.matchesCurrentClearRecoveryConfiguration(owner.clearRecovery)) {
9401
+ throw new Error(
9402
+ `Cannot automatically recover interrupted global clear ${owner.token}: the current embedding configuration does not match the originating lease. The recovery marker was retained; retry from the originating project with matching settings.`
9403
+ );
9404
+ }
9405
+ clearScopes.push({
9406
+ projectRoot: owner.projectRoot,
9407
+ scopedRoots: owner.scopedRoots,
9408
+ compatibilityDecision: owner.clearRecovery.compatibilityDecision
9409
+ });
9410
+ }
9411
+ if (clearScopes.length > 0) {
9412
+ this.loadFileHashCache();
9413
+ }
9414
+ for (const { projectRoot, scopedRoots, compatibilityDecision } of clearScopes) {
9415
+ this.clearGlobalIndexUnlocked(projectRoot, scopedRoots, compatibilityDecision);
9258
9416
  }
9259
9417
  await this.healthCheckUnlocked();
9418
+ this.logger.info(
9419
+ clearScopes.length > 0 ? "Recovery complete, next index will rebuild all files" : "Recovery complete, next index will resume from the last checkpoint"
9420
+ );
9421
+ return;
9260
9422
  }
9261
- this.logger.info("Recovery complete, next index will re-process all files");
9423
+ this.logger.info("Recovery complete, next index will resume from the last checkpoint");
9262
9424
  }
9263
9425
  *loadSerializedFailedBatches() {
9264
9426
  let warned = false;
@@ -9296,33 +9458,118 @@ var Indexer = class _Indexer {
9296
9458
  state.writer.write(record);
9297
9459
  state.recordsWritten += record.chunks.length;
9298
9460
  }
9299
- finalizeFailedBatchWriteState(state) {
9461
+ finalizeFailedBatchWriteState(state, resolvedChunkIds = /* @__PURE__ */ new Set()) {
9300
9462
  if (state.recordsWritten > 0) {
9301
- state.writer.commit();
9463
+ const seenChunkIds = /* @__PURE__ */ new Set();
9464
+ const retained = [];
9465
+ const records = Array.from(readFailedBatchRecords(state.writer.temporaryPath));
9466
+ for (let i = records.length - 1; i >= 0; i--) {
9467
+ const chunks = records[i].chunks.filter((rawChunk) => {
9468
+ const chunkId = getPendingChunkId(rawChunk);
9469
+ if (chunkId !== null) {
9470
+ if (resolvedChunkIds.has(chunkId)) return false;
9471
+ if (seenChunkIds.has(chunkId)) return false;
9472
+ seenChunkIds.add(chunkId);
9473
+ }
9474
+ return true;
9475
+ });
9476
+ if (chunks.length > 0) {
9477
+ retained.unshift({ ...records[i], chunks });
9478
+ }
9479
+ }
9480
+ state.writer.cleanup();
9481
+ if (retained.length > 0) {
9482
+ writeFailedBatchRecords(this.failedBatchesPath, retained);
9483
+ } else {
9484
+ writeFailedBatchRecords(this.failedBatchesPath, []);
9485
+ this.clearFailedBatchState();
9486
+ }
9302
9487
  return;
9303
9488
  }
9304
- state.writer.cleanup();
9489
+ state.writer.commit();
9305
9490
  this.clearFailedBatchState();
9306
9491
  }
9307
- clearFailedBatchState() {
9308
- if ((0, import_fs10.existsSync)(this.failedBatchesPath)) {
9309
- try {
9310
- (0, import_fs10.unlinkSync)(this.failedBatchesPath);
9311
- } catch {
9312
- }
9313
- }
9492
+ getCheckpointIntervalChunks(totalChunks) {
9493
+ return Math.max(
9494
+ this.checkpointIntervalChunks ?? 2e3,
9495
+ Math.floor(totalChunks / 10)
9496
+ );
9314
9497
  }
9315
- rewriteFailedBatchState(shouldRetain) {
9316
- const state = this.createFailedBatchWriteState();
9317
- try {
9318
- for (const batch of this.loadSerializedFailedBatches()) {
9319
- const retainedChunks = batch.chunks.filter(shouldRetain);
9320
- if (retainedChunks.length > 0) {
9321
- this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
9322
- }
9323
- }
9324
- this.finalizeFailedBatchWriteState(state);
9325
- } catch (error) {
9498
+ checkpointIndexRun(database, store, invertedIndex, failedProcessing, resolvedRetryChunkIds, currentFileHashes, committedFilePaths, scopedRoots, configuredProviderInfo) {
9499
+ if (!this.hasProjectForceReembedPending()) {
9500
+ this.saveIndexMetadata(configuredProviderInfo);
9501
+ this.indexCompatibility = { compatible: true };
9502
+ }
9503
+ database.commitWriteTransaction();
9504
+ database.beginWriteTransaction();
9505
+ this.saveInvertedIndex(invertedIndex);
9506
+ store.save();
9507
+ if (failedProcessing.state.recordsWritten > 0 || failedProcessing.latestById.size > 0 || failedProcessing.discardedExistingRecords) {
9508
+ for (const metadata of failedProcessing.latestById.values()) {
9509
+ const alreadyMaterialized = metadata.chunks.some((rawChunk) => {
9510
+ const chunkId = getPendingChunkId(rawChunk);
9511
+ return chunkId !== null && failedProcessing.materializedRetryIds.has(chunkId);
9512
+ });
9513
+ if (alreadyMaterialized) continue;
9514
+ this.writeFailedBatchRecord(failedProcessing.state, {
9515
+ chunks: metadata.chunks,
9516
+ attemptCount: metadata.attemptCount,
9517
+ error: metadata.error,
9518
+ lastAttempt: metadata.lastAttempt
9519
+ });
9520
+ for (const rawChunk of metadata.chunks) {
9521
+ const chunkId = getPendingChunkId(rawChunk);
9522
+ if (chunkId !== null) {
9523
+ failedProcessing.materializedRetryIds.add(chunkId);
9524
+ }
9525
+ }
9526
+ }
9527
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
9528
+ failedProcessing.state = this.createFailedBatchWriteState();
9529
+ failedProcessing.discardedExistingRecords = false;
9530
+ for (const record of this.loadSerializedFailedBatches()) {
9531
+ for (const rawChunk of record.chunks) {
9532
+ const chunkId = getPendingChunkId(rawChunk);
9533
+ this.writeFailedBatchRecord(failedProcessing.state, { ...record, chunks: [rawChunk] });
9534
+ if (chunkId !== null) {
9535
+ failedProcessing.materializedRetryIds.add(chunkId);
9536
+ }
9537
+ }
9538
+ }
9539
+ }
9540
+ const partialHashes = /* @__PURE__ */ new Map();
9541
+ for (const filePath of committedFilePaths) {
9542
+ const hash = currentFileHashes.get(filePath);
9543
+ if (hash !== void 0) {
9544
+ partialHashes.set(filePath, hash);
9545
+ }
9546
+ }
9547
+ if (scopedRoots) {
9548
+ this.replaceScopedFileHashCache(partialHashes, scopedRoots);
9549
+ } else {
9550
+ this.fileHashCache = partialHashes;
9551
+ this.saveFileHashCache();
9552
+ }
9553
+ }
9554
+ clearFailedBatchState() {
9555
+ if ((0, import_fs10.existsSync)(this.failedBatchesPath)) {
9556
+ try {
9557
+ (0, import_fs10.unlinkSync)(this.failedBatchesPath);
9558
+ } catch {
9559
+ }
9560
+ }
9561
+ }
9562
+ rewriteFailedBatchState(shouldRetain) {
9563
+ const state = this.createFailedBatchWriteState();
9564
+ try {
9565
+ for (const batch of this.loadSerializedFailedBatches()) {
9566
+ const retainedChunks = batch.chunks.filter(shouldRetain);
9567
+ if (retainedChunks.length > 0) {
9568
+ this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
9569
+ }
9570
+ }
9571
+ this.finalizeFailedBatchWriteState(state);
9572
+ } catch (error) {
9326
9573
  state.writer.cleanup();
9327
9574
  throw error;
9328
9575
  }
@@ -9330,6 +9577,7 @@ var Indexer = class _Indexer {
9330
9577
  prepareFailedBatchProcessing(roots, shouldProcess) {
9331
9578
  const state = this.createFailedBatchWriteState();
9332
9579
  const latestById = /* @__PURE__ */ new Map();
9580
+ let discardedExistingRecords = false;
9333
9581
  try {
9334
9582
  for (const batch of this.loadSerializedFailedBatches()) {
9335
9583
  for (const rawChunk of batch.chunks) {
@@ -9340,10 +9588,12 @@ var Indexer = class _Indexer {
9340
9588
  continue;
9341
9589
  }
9342
9590
  if (!shouldProcess(filePath)) {
9591
+ discardedExistingRecords = true;
9343
9592
  continue;
9344
9593
  }
9345
9594
  const chunkId = getPendingChunkId(rawChunk);
9346
9595
  if (!chunkId) {
9596
+ discardedExistingRecords = true;
9347
9597
  continue;
9348
9598
  }
9349
9599
  const existing = latestById.get(chunkId);
@@ -9351,12 +9601,18 @@ var Indexer = class _Indexer {
9351
9601
  latestById.set(chunkId, {
9352
9602
  attemptCount: batch.attemptCount,
9353
9603
  error: batch.error,
9354
- lastAttempt: batch.lastAttempt
9604
+ lastAttempt: batch.lastAttempt,
9605
+ chunks: [rawChunk]
9355
9606
  });
9356
9607
  }
9357
9608
  }
9358
9609
  }
9359
- return { state, latestById };
9610
+ return {
9611
+ state,
9612
+ latestById,
9613
+ materializedRetryIds: /* @__PURE__ */ new Set(),
9614
+ discardedExistingRecords
9615
+ };
9360
9616
  } catch (error) {
9361
9617
  state.writer.cleanup();
9362
9618
  throw error;
@@ -9392,10 +9648,34 @@ var Indexer = class _Indexer {
9392
9648
  }
9393
9649
  }
9394
9650
  }
9651
+ restoreMissingChunkRows(database, chunks) {
9652
+ const missing = [];
9653
+ for (const chunk of chunks) {
9654
+ if (database.getChunk(chunk.id)) {
9655
+ continue;
9656
+ }
9657
+ missing.push({
9658
+ chunkId: chunk.id,
9659
+ contentHash: chunk.contentHash,
9660
+ filePath: chunk.metadata.filePath,
9661
+ startLine: chunk.metadata.startLine,
9662
+ endLine: chunk.metadata.endLine,
9663
+ nodeType: chunk.metadata.chunkType,
9664
+ name: chunk.metadata.name,
9665
+ language: chunk.metadata.language,
9666
+ blameSha: chunk.metadata.blameSha,
9667
+ blameAuthor: chunk.metadata.blameAuthor,
9668
+ blameAuthorEmail: chunk.metadata.blameAuthorEmail,
9669
+ blameCommittedAt: chunk.metadata.blameCommittedAt,
9670
+ blameSummary: chunk.metadata.blameSummary
9671
+ });
9672
+ }
9673
+ if (missing.length > 0) {
9674
+ database.upsertChunksBatch(missing);
9675
+ }
9676
+ }
9395
9677
  getProviderRateLimits(provider) {
9396
9678
  switch (provider) {
9397
- case "github-copilot":
9398
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
9399
9679
  case "openai":
9400
9680
  return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
9401
9681
  case "google":
@@ -9464,16 +9744,17 @@ var Indexer = class _Indexer {
9464
9744
  const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9465
9745
  const completedVectorsByChunkId = /* @__PURE__ */ new Map();
9466
9746
  const completedChunkIds = /* @__PURE__ */ new Set();
9467
- const requestBatches = createPendingEmbeddingRequestBatches(
9468
- chunksNeedingEmbedding,
9469
- getDynamicBatchOptions(options.configuredProviderInfo)
9470
- );
9747
+ const batchOptions = getDynamicBatchOptions(options.configuredProviderInfo, this.config.embedding?.batch);
9748
+ if (options.forceSingleItemBatches && options.configuredProviderInfo.provider === "ollama") {
9749
+ batchOptions.maxBatchItems = 1;
9750
+ }
9751
+ const requestBatches = createPendingEmbeddingRequestBatches(chunksNeedingEmbedding, batchOptions);
9471
9752
  let fatalError;
9472
9753
  for (const requestBatch of requestBatches) {
9473
9754
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
9474
9755
  const task = options.queue.add(async () => {
9475
9756
  if (options.rateLimitState.backoffMs > 0) {
9476
- await new Promise((resolve18) => setTimeout(resolve18, options.rateLimitState.backoffMs));
9757
+ await new Promise((resolve20) => setTimeout(resolve20, options.rateLimitState.backoffMs));
9477
9758
  }
9478
9759
  try {
9479
9760
  const embeddingResult = await pRetry(
@@ -10030,7 +10311,7 @@ var Indexer = class _Indexer {
10030
10311
  }
10031
10312
  if (!this.configuredProviderInfo) {
10032
10313
  throw new Error(
10033
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
10314
+ "No embedding provider available. Configure OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
10034
10315
  );
10035
10316
  }
10036
10317
  this.logger.info("Initializing indexer", {
@@ -10040,15 +10321,6 @@ var Indexer = class _Indexer {
10040
10321
  rerankerEnabled: this.config.reranker?.enabled ?? false
10041
10322
  });
10042
10323
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
10043
- if (this.config.reranker?.enabled) {
10044
- this.reranker = createReranker(this.config.reranker);
10045
- if (this.reranker.isAvailable()) {
10046
- this.logger.info("Reranker initialized", {
10047
- model: this.config.reranker.model,
10048
- baseUrl: this.config.reranker.baseUrl
10049
- });
10050
- }
10051
- }
10052
10324
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
10053
10325
  const storePath = path15.join(this.indexPath, "vectors");
10054
10326
  const vectorMetadataPath = `${storePath}.meta.json`;
@@ -10070,7 +10342,20 @@ var Indexer = class _Indexer {
10070
10342
  ]);
10071
10343
  }
10072
10344
  if (recoveredOwners.length > 0 && this.config.scope === "project") {
10073
- await this.resetLocalIndexArtifacts();
10345
+ const unknownLegacyForceIndex = recoveredOwners.find(
10346
+ (owner) => this.hasUnknownLegacyForceIndexClear(owner)
10347
+ );
10348
+ if (unknownLegacyForceIndex) {
10349
+ throw new Error(
10350
+ `Cannot automatically recover interrupted force-index ${unknownLegacyForceIndex.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
10351
+ );
10352
+ }
10353
+ const shouldReset = recoveredOwners.some(
10354
+ (owner) => owner.clearRecovery !== void 0 || owner.operation === "clear" && owner.recoveryProtocolVersion !== 1
10355
+ );
10356
+ if (shouldReset) {
10357
+ await this.resetLocalIndexArtifacts();
10358
+ }
10074
10359
  }
10075
10360
  this.store = new VectorStore(storePath, dimensions);
10076
10361
  if ((0, import_fs10.existsSync)(storePath) || (0, import_fs10.existsSync)(vectorMetadataPath)) {
@@ -10706,7 +10991,17 @@ var Indexer = class _Indexer {
10706
10991
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
10707
10992
  for (const file of files) {
10708
10993
  const storedPath = this.toStoredFilePath(file.path);
10709
- const currentHash = hashFile(file.path);
10994
+ let currentHash;
10995
+ try {
10996
+ currentHash = hashFile(file.path);
10997
+ } catch (error) {
10998
+ stats.skippedFiles.push({ path: this.toCanonicalFilePath(file.path), reason: "unreadable" });
10999
+ this.logger.warn("Skipped unreadable file during indexing", {
11000
+ path: file.path,
11001
+ error: getErrorMessage3(error)
11002
+ });
11003
+ continue;
11004
+ }
10710
11005
  currentFileHashes.set(storedPath, currentHash);
10711
11006
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
10712
11007
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
@@ -10714,7 +11009,8 @@ var Indexer = class _Indexer {
10714
11009
  );
10715
11010
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path15.extname(storedPath).toLowerCase() === ".swift";
10716
11011
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path15.extname(storedPath).toLowerCase() === ".metal";
10717
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
11012
+ const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
11013
+ if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
10718
11014
  unchangedFilePaths.add(storedPath);
10719
11015
  this.logger.recordCacheHit();
10720
11016
  } else {
@@ -10840,6 +11136,9 @@ var Indexer = class _Indexer {
10840
11136
  }
10841
11137
  }
10842
11138
  let processedChangedFiles = 0;
11139
+ let lastCheckpointChunks = 0;
11140
+ const committedFilePaths = new Set(unchangedFilePaths);
11141
+ const resolvedRetryChunkIds = /* @__PURE__ */ new Set();
10843
11142
  for (const descriptorBatch of iterateOrderedFileBatches(
10844
11143
  changedFileDescriptors,
10845
11144
  (descriptor) => descriptor.sourceBytes,
@@ -10853,7 +11152,7 @@ var Indexer = class _Indexer {
10853
11152
  const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
10854
11153
  const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
10855
11154
  const parseStartTime = import_perf_hooks.performance.now();
10856
- const parsedFiles = parseFiles(loadedFiles);
11155
+ const parsedFiles = parseFiles(loadedFiles, this.config.indexing.linesPerChunk);
10857
11156
  const parseMs = import_perf_hooks.performance.now() - parseStartTime;
10858
11157
  this.logger.recordFilesParsed(parsedFiles.length);
10859
11158
  this.logger.recordParseDuration(parseMs);
@@ -10876,7 +11175,7 @@ var Indexer = class _Indexer {
10876
11175
  }
10877
11176
  let chunksToProcess = parsed.chunks;
10878
11177
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
10879
- chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
11178
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content, this.config.indexing.linesPerChunk);
10880
11179
  }
10881
11180
  chunksToProcess = selectIndexableChunks(
10882
11181
  chunksToProcess,
@@ -11010,6 +11309,10 @@ var Indexer = class _Indexer {
11010
11309
  }
11011
11310
  if (symbolBatch.length > 0) {
11012
11311
  database.upsertSymbolsBatch(symbolBatch);
11312
+ database.addSymbolsToBranchBatch(
11313
+ this.getBranchCatalogKey(),
11314
+ symbolBatch.map((symbol) => symbol.id)
11315
+ );
11013
11316
  }
11014
11317
  if (edgeBatch.length > 0) {
11015
11318
  database.upsertCallEdgesBatch(edgeBatch);
@@ -11045,6 +11348,12 @@ var Indexer = class _Indexer {
11045
11348
  forceReembed: forceScopedReembed,
11046
11349
  reuseCachedEmbeddings: true,
11047
11350
  incrementRepeatedFailures: true,
11351
+ onSucceeded: (succeededChunks) => {
11352
+ database.addChunksToBranchBatch(
11353
+ this.getBranchCatalogKey(),
11354
+ succeededChunks.map((chunk) => chunk.id)
11355
+ );
11356
+ },
11048
11357
  onProgress: (batchProgress) => onProgress?.({
11049
11358
  phase: "embedding",
11050
11359
  filesProcessed: unchangedFilePaths.size + processedChangedFiles,
@@ -11063,6 +11372,27 @@ var Indexer = class _Indexer {
11063
11372
  }
11064
11373
  }
11065
11374
  }
11375
+ for (const descriptor of descriptorBatch) {
11376
+ const existingFileChunks = existingChunksByFile.get(descriptor.storedPath);
11377
+ if (!existingFileChunks || existingFileChunks.size === 0) {
11378
+ committedFilePaths.add(descriptor.storedPath);
11379
+ }
11380
+ }
11381
+ const checkpointInterval = this.getCheckpointIntervalChunks(stats.totalChunks);
11382
+ if (stats.totalChunks - lastCheckpointChunks >= checkpointInterval) {
11383
+ lastCheckpointChunks = stats.totalChunks;
11384
+ this.checkpointIndexRun(
11385
+ database,
11386
+ store,
11387
+ invertedIndex,
11388
+ failedProcessing,
11389
+ resolvedRetryChunkIds,
11390
+ currentFileHashes,
11391
+ committedFilePaths,
11392
+ scopedRoots,
11393
+ configuredProviderInfo
11394
+ );
11395
+ }
11066
11396
  }
11067
11397
  const retryableFailedChunks = this.iterateLatestFailedChunks(
11068
11398
  failedProcessing.latestById,
@@ -11083,6 +11413,7 @@ var Indexer = class _Indexer {
11083
11413
  retryableChunksWithExistingData.add(chunk.id);
11084
11414
  }
11085
11415
  }
11416
+ this.restoreMissingChunkRows(database, pendingChunks);
11086
11417
  stats.totalChunks += pendingChunks.length;
11087
11418
  onProgress?.({
11088
11419
  phase: "embedding",
@@ -11105,6 +11436,17 @@ var Indexer = class _Indexer {
11105
11436
  forceReembed: forceScopedReembed,
11106
11437
  reuseCachedEmbeddings: true,
11107
11438
  incrementRepeatedFailures: true,
11439
+ forceSingleItemBatches: true,
11440
+ onSucceeded: (succeededChunks) => {
11441
+ database.addChunksToBranchBatch(
11442
+ this.getBranchCatalogKey(),
11443
+ succeededChunks.map((chunk) => chunk.id)
11444
+ );
11445
+ for (const chunk of succeededChunks) {
11446
+ failedProcessing.latestById.delete(chunk.id);
11447
+ resolvedRetryChunkIds.add(chunk.id);
11448
+ }
11449
+ },
11108
11450
  onProgress: (batchProgress) => onProgress?.({
11109
11451
  phase: "embedding",
11110
11452
  filesProcessed: files.length,
@@ -11122,6 +11464,20 @@ var Indexer = class _Indexer {
11122
11464
  failedForcedChunkIds.add(chunkId);
11123
11465
  }
11124
11466
  }
11467
+ if (stats.totalChunks - lastCheckpointChunks >= this.getCheckpointIntervalChunks(stats.totalChunks)) {
11468
+ lastCheckpointChunks = stats.totalChunks;
11469
+ this.checkpointIndexRun(
11470
+ database,
11471
+ store,
11472
+ invertedIndex,
11473
+ failedProcessing,
11474
+ resolvedRetryChunkIds,
11475
+ currentFileHashes,
11476
+ committedFilePaths,
11477
+ scopedRoots,
11478
+ configuredProviderInfo
11479
+ );
11480
+ }
11125
11481
  }
11126
11482
  const removedChunkIds = [];
11127
11483
  for (const [chunkId] of existingChunks) {
@@ -11158,13 +11514,6 @@ var Indexer = class _Indexer {
11158
11514
  if (removedStoredChunks) {
11159
11515
  this.saveInvertedIndex(invertedIndex);
11160
11516
  }
11161
- if (scopedRoots) {
11162
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11163
- } else {
11164
- this.fileHashCache = currentFileHashes;
11165
- this.saveFileHashCache();
11166
- }
11167
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11168
11517
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11169
11518
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11170
11519
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11173,6 +11522,13 @@ var Indexer = class _Indexer {
11173
11522
  this.indexCompatibility = { compatible: true };
11174
11523
  database.commitWriteTransaction();
11175
11524
  writeTransactionActive = false;
11525
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11526
+ if (scopedRoots) {
11527
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11528
+ } else {
11529
+ this.fileHashCache = currentFileHashes;
11530
+ this.saveFileHashCache();
11531
+ }
11176
11532
  stats.durationMs = Date.now() - startTime;
11177
11533
  onProgress?.({
11178
11534
  phase: "complete",
@@ -11196,13 +11552,6 @@ var Indexer = class _Indexer {
11196
11552
  );
11197
11553
  store.save();
11198
11554
  this.saveInvertedIndex(invertedIndex);
11199
- if (scopedRoots) {
11200
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11201
- } else {
11202
- this.fileHashCache = currentFileHashes;
11203
- this.saveFileHashCache();
11204
- }
11205
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11206
11555
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11207
11556
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11208
11557
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11211,6 +11560,13 @@ var Indexer = class _Indexer {
11211
11560
  this.indexCompatibility = { compatible: true };
11212
11561
  database.commitWriteTransaction();
11213
11562
  writeTransactionActive = false;
11563
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11564
+ if (scopedRoots) {
11565
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11566
+ } else {
11567
+ this.fileHashCache = currentFileHashes;
11568
+ this.saveFileHashCache();
11569
+ }
11214
11570
  stats.durationMs = Date.now() - startTime;
11215
11571
  onProgress?.({
11216
11572
  phase: "complete",
@@ -11245,15 +11601,15 @@ var Indexer = class _Indexer {
11245
11601
  );
11246
11602
  store.save();
11247
11603
  this.saveInvertedIndex(invertedIndex);
11604
+ database.commitWriteTransaction();
11605
+ writeTransactionActive = false;
11606
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11248
11607
  if (scopedRoots) {
11249
11608
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11250
11609
  } else {
11251
11610
  this.fileHashCache = currentFileHashes;
11252
11611
  this.saveFileHashCache();
11253
11612
  }
11254
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11255
- database.commitWriteTransaction();
11256
- writeTransactionActive = false;
11257
11613
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
11258
11614
  const gcReset = await this.maybeRunOrphanGc();
11259
11615
  if (gcReset) {
@@ -11277,6 +11633,9 @@ var Indexer = class _Indexer {
11277
11633
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
11278
11634
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
11279
11635
  }
11636
+ if (forceScopedReembed) {
11637
+ database.setMetadata(this.getProjectMigrationFinalizedMetadataKey(), "true");
11638
+ }
11280
11639
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11281
11640
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11282
11641
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11387,26 +11746,41 @@ var Indexer = class _Indexer {
11387
11746
  shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
11388
11747
  };
11389
11748
  }
11390
- searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
11749
+ searchCandidatesWithAllowedIds(initialLimit, totalCount, allowedChunkIds, shouldPrefilter, search, getChunkId) {
11391
11750
  const normalizedLimit = Math.max(0, Math.floor(initialLimit));
11392
11751
  if (normalizedLimit === 0) return [];
11393
- if (!shouldPrefilterByBranch || !branchChunkIds) {
11752
+ if (!shouldPrefilter || !allowedChunkIds) {
11394
11753
  return search(normalizedLimit);
11395
11754
  }
11396
- const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
11755
+ const targetCount = Math.min(normalizedLimit, allowedChunkIds.size);
11397
11756
  if (targetCount === 0 || totalCount === 0) return [];
11398
11757
  let requestedLimit = Math.min(normalizedLimit, totalCount);
11399
11758
  while (true) {
11400
11759
  const results = search(requestedLimit);
11401
- const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
11402
- if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
11403
- return branchResults;
11760
+ const allowedResults = results.filter((candidate) => allowedChunkIds.has(getChunkId(candidate)));
11761
+ if (allowedResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
11762
+ return allowedResults;
11404
11763
  }
11405
11764
  const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
11406
- if (nextLimit === requestedLimit) return branchResults;
11765
+ if (nextLimit === requestedLimit) return allowedResults;
11407
11766
  requestedLimit = nextLimit;
11408
11767
  }
11409
11768
  }
11769
+ getTemporalChunkIds(database, options) {
11770
+ if (!options?.blameSince && !options?.blameUntil) return null;
11771
+ const since = options.blameSince ? parseBlameTimestamp(options.blameSince, false) : void 0;
11772
+ const until = options.blameUntil ? parseBlameTimestamp(options.blameUntil, true) : void 0;
11773
+ if (since === null || until === null) {
11774
+ return /* @__PURE__ */ new Set();
11775
+ }
11776
+ return new Set(database.getChunkIdsByBlameDate(since, until));
11777
+ }
11778
+ intersectChunkIdSets(first, second) {
11779
+ if (first === null) return second;
11780
+ if (second === null) return first;
11781
+ const [smaller, larger] = first.size <= second.size ? [first, second] : [second, first];
11782
+ return new Set(Array.from(smaller).filter((chunkId) => larger.has(chunkId)));
11783
+ }
11410
11784
  buildCandidateSnapshot(candidate) {
11411
11785
  return {
11412
11786
  id: candidate.id,
@@ -11421,13 +11795,16 @@ var Indexer = class _Indexer {
11421
11795
  buildCandidateSnapshotList(candidates) {
11422
11796
  return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
11423
11797
  }
11424
- searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
11425
- return this.searchCandidatesWithBranchPrefilter(
11426
- initialLimit,
11427
- store.count(),
11798
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch, temporalChunkIds) {
11799
+ const availableCount = temporalChunkIds?.size ?? store.count();
11800
+ if (availableCount === 0) return [];
11801
+ const allowedIds = temporalChunkIds === null ? void 0 : Array.from(temporalChunkIds);
11802
+ return this.searchCandidatesWithAllowedIds(
11803
+ Math.min(initialLimit, availableCount),
11804
+ availableCount,
11428
11805
  branchChunkIds,
11429
11806
  shouldPrefilterByBranch,
11430
- (requestedLimit) => store.search(embedding, requestedLimit),
11807
+ (requestedLimit) => store.search(embedding, requestedLimit, allowedIds),
11431
11808
  (candidate) => candidate.id
11432
11809
  );
11433
11810
  }
@@ -11452,7 +11829,9 @@ var Indexer = class _Indexer {
11452
11829
  const rerankTopN = this.config.search.rerankTopN;
11453
11830
  const filterByBranch = options?.filterByBranch ?? true;
11454
11831
  const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
11832
+ const prioritizeSourcePaths = sourceIntent || options?.prioritizeSourcePaths === true;
11455
11833
  const identifierHints = extractIdentifierHints(query);
11834
+ const candidateLimit = maxResults * (prioritizeSourcePaths ? 12 : 4);
11456
11835
  this.logger.search("debug", "Starting search", {
11457
11836
  query,
11458
11837
  maxResults,
@@ -11483,25 +11862,28 @@ var Indexer = class _Indexer {
11483
11862
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
11484
11863
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
11485
11864
  }
11865
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
11486
11866
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
11487
11867
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
11488
11868
  const vectorStartTime = import_perf_hooks.performance.now();
11489
11869
  const semanticCandidates = embedding ? this.searchSemanticCandidates(
11490
11870
  store,
11491
11871
  embedding,
11492
- maxResults * 4,
11872
+ candidateLimit,
11493
11873
  branchChunkIds,
11494
- shouldPrefilterByBranch
11874
+ shouldPrefilterByBranch,
11875
+ temporalChunkIds
11495
11876
  ) : [];
11496
11877
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
11497
11878
  const keywordStartTime = import_perf_hooks.performance.now();
11498
11879
  const keywordCandidates = await this.keywordSearch(
11499
11880
  query,
11500
- maxResults * 4,
11881
+ candidateLimit,
11501
11882
  store,
11502
11883
  invertedIndex,
11503
11884
  branchChunkIds,
11504
- shouldPrefilterByBranch
11885
+ shouldPrefilterByBranch,
11886
+ temporalChunkIds
11505
11887
  );
11506
11888
  const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
11507
11889
  const scopedSemanticCandidates = semanticCandidates.filter(
@@ -11523,7 +11905,7 @@ var Indexer = class _Indexer {
11523
11905
  rerankTopN,
11524
11906
  limit: maxResults,
11525
11907
  hybridWeight: rankingHybridWeight,
11526
- prioritizeSourcePaths: sourceIntent
11908
+ prioritizeSourcePaths
11527
11909
  });
11528
11910
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
11529
11911
  definitionIntent: options?.definitionIntent === true,
@@ -11559,10 +11941,11 @@ var Indexer = class _Indexer {
11559
11941
  branchSymbolIds,
11560
11942
  maxResults,
11561
11943
  union,
11562
- sourceIntent
11944
+ sourceIntent,
11945
+ options?.definitionIntent === true && ((options.directory?.trim().length ?? 0) > 0 || (options.fileType?.trim().length ?? 0) > 0)
11563
11946
  );
11564
11947
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
11565
- const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
11948
+ const primaryLane = options?.definitionIntent === true ? mergeTieredResults(symbolLane, prePrimaryLane, maxResults * 4) : mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
11566
11949
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
11567
11950
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
11568
11951
  const baseFiltered = tiered.filter(
@@ -11657,14 +12040,18 @@ var Indexer = class _Indexer {
11657
12040
  })
11658
12041
  );
11659
12042
  }
11660
- async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
12043
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false, temporalChunkIds = null) {
11661
12044
  const normalizedLimit = Math.max(0, Math.floor(limit));
11662
12045
  if (normalizedLimit === 0) return [];
11663
- const scoreEntries = this.searchCandidatesWithBranchPrefilter(
12046
+ const allowedChunkIds = this.intersectChunkIdSets(
12047
+ shouldPrefilterByBranch ? branchChunkIds : null,
12048
+ temporalChunkIds
12049
+ );
12050
+ const scoreEntries = this.searchCandidatesWithAllowedIds(
11664
12051
  normalizedLimit,
11665
12052
  invertedIndex.getDocumentCount(),
11666
- branchChunkIds,
11667
- shouldPrefilterByBranch,
12053
+ allowedChunkIds,
12054
+ allowedChunkIds !== null,
11668
12055
  (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
11669
12056
  ([chunkId]) => chunkId
11670
12057
  );
@@ -11749,7 +12136,17 @@ var Indexer = class _Indexer {
11749
12136
  );
11750
12137
  const currentFileHashes = /* @__PURE__ */ new Map();
11751
12138
  for (const file of files) {
11752
- currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
12139
+ let hash;
12140
+ try {
12141
+ hash = hashFile(file.path);
12142
+ } catch (error) {
12143
+ this.logger.warn("Skipped unreadable file during freshness check", {
12144
+ path: file.path,
12145
+ error: getErrorMessage3(error)
12146
+ });
12147
+ return { readable: false, current: false, reason: "unreadable" };
12148
+ }
12149
+ currentFileHashes.set(this.toStoredFilePath(file.path), hash);
11753
12150
  }
11754
12151
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
11755
12152
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -11775,69 +12172,87 @@ var Indexer = class _Indexer {
11775
12172
  async forceIndex(onProgress) {
11776
12173
  return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
11777
12174
  await this.ensureInitializedUnlocked(recoveredOwners);
11778
- await this.clearIndexUnlocked();
12175
+ const recovery = this.beginClearRecoveryState();
12176
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
12177
+ this.finishClearRecoveryState();
11779
12178
  return this.indexUnlocked(onProgress, [], true);
11780
12179
  });
11781
12180
  }
11782
12181
  async clearIndex() {
11783
12182
  await this.withIndexMutationLease("clear", async (recoveredOwners) => {
11784
12183
  await this.ensureInitializedUnlocked(recoveredOwners);
11785
- await this.clearIndexUnlocked();
12184
+ const recovery = this.beginClearRecoveryState();
12185
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
11786
12186
  });
11787
12187
  }
11788
- async clearIndexUnlocked() {
12188
+ clearGlobalIndexDataUnlocked(projectRoot = this.projectRoot) {
11789
12189
  const { store, invertedIndex, database } = this.requireLoadedIndexState();
11790
- if (this.config.scope === "global") {
11791
- store.load();
11792
- invertedIndex.load();
11793
- this.loadFileHashCache();
11794
- const roots = this.getScopedRoots();
11795
- const compatibility = this.checkCompatibility();
11796
- const allMetadata = store.getAllMetadata();
11797
- const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
11798
- if (!compatibility.compatible && hasForeignData) {
11799
- if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
11800
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
11801
- this.clearScopedFileHashCache(roots);
11802
- this.clearScopedFailedBatches(roots);
11803
- database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
11804
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
12190
+ const clearedBranchKeys = database.getAllBranches();
12191
+ store.clear();
12192
+ store.save();
12193
+ invertedIndex.clear();
12194
+ this.saveInvertedIndex(invertedIndex);
12195
+ this.fileHashCache.clear();
12196
+ this.saveFileHashCache();
12197
+ database.clearAllIndexedData();
12198
+ this.deleteBranchCommitMetadata(database, clearedBranchKeys);
12199
+ this.clearFailedBatchState();
12200
+ database.deleteMetadata("index.version");
12201
+ database.deleteMetadata("index.pathStorageVersion");
12202
+ database.deleteMetadata("index.embeddingProvider");
12203
+ database.deleteMetadata("index.embeddingModel");
12204
+ database.deleteMetadata("index.embeddingDimensions");
12205
+ database.deleteMetadata("index.embeddingStrategyVersion");
12206
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
12207
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
12208
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash));
12209
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey(projectIdentityHash));
12210
+ database.deleteMetadata("index.createdAt");
12211
+ database.deleteMetadata("index.updatedAt");
12212
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
12213
+ }
12214
+ clearGlobalIndexUnlocked(projectRoot = this.projectRoot, roots = this.getScopedRoots(), recoveryDecision) {
12215
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
12216
+ store.load();
12217
+ invertedIndex.load();
12218
+ this.loadFileHashCache();
12219
+ const compatibility = this.checkCompatibility();
12220
+ const compatibilityDecision = recoveryDecision ?? (compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible");
12221
+ const allMetadata = store.getAllMetadata();
12222
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData(projectRoot, roots) || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
12223
+ if (compatibilityDecision !== "compatible" && hasForeignData) {
12224
+ if (compatibilityDecision === "embedding-strategy-mismatch") {
12225
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
12226
+ this.clearScopedFileHashCache(roots);
12227
+ this.clearScopedFailedBatches(roots);
12228
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
12229
+ database.setMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash), "true");
12230
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
12231
+ database.deleteMetadata(this.getProjectMigrationFinalizedMetadataKey(projectIdentityHash));
12232
+ if (projectRoot === this.projectRoot) {
11805
12233
  this.indexCompatibility = { compatible: true };
11806
- return;
11807
12234
  }
11808
- throw new Error(
11809
- `Global index compatibility reset is unsafe because the shared index contains files from other projects. The current global index cannot be force-rebuilt for ${this.projectRoot} without deleting other repositories' indexed data. Use scope="project" for isolated rebuilds, or manually delete the shared global index if you intend to rebuild all projects.`
11810
- );
11811
- }
11812
- if (!hasForeignData) {
11813
- const clearedBranchKeys2 = database.getAllBranches();
11814
- store.clear();
11815
- store.save();
11816
- invertedIndex.clear();
11817
- this.saveInvertedIndex(invertedIndex);
11818
- this.fileHashCache.clear();
11819
- this.saveFileHashCache();
11820
- database.clearAllIndexedData();
11821
- this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
11822
- this.clearFailedBatchState();
11823
- database.deleteMetadata("index.version");
11824
- database.deleteMetadata("index.pathStorageVersion");
11825
- database.deleteMetadata("index.embeddingProvider");
11826
- database.deleteMetadata("index.embeddingModel");
11827
- database.deleteMetadata("index.embeddingDimensions");
11828
- database.deleteMetadata("index.embeddingStrategyVersion");
11829
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
11830
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
11831
- database.deleteMetadata(this.getLegacyMigrationMetadataKey());
11832
- database.deleteMetadata("index.createdAt");
11833
- database.deleteMetadata("index.updatedAt");
11834
- this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
11835
12235
  return;
11836
12236
  }
11837
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
11838
- this.clearScopedFileHashCache(roots);
11839
- this.clearScopedFailedBatches(roots);
12237
+ throw new Error(
12238
+ `Global index compatibility reset is unsafe because the shared index contains files from other projects. The current global index cannot be force-rebuilt for ${projectRoot} without deleting other repositories' indexed data. Use scope="project" for isolated rebuilds, or manually delete the shared global index if you intend to rebuild all projects.`
12239
+ );
12240
+ }
12241
+ if (!hasForeignData) {
12242
+ this.clearGlobalIndexDataUnlocked(projectRoot);
12243
+ return;
12244
+ }
12245
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
12246
+ this.clearScopedFileHashCache(roots);
12247
+ this.clearScopedFailedBatches(roots);
12248
+ if (projectRoot === this.projectRoot) {
11840
12249
  this.indexCompatibility = compatibility;
12250
+ }
12251
+ }
12252
+ async clearIndexUnlocked(recoveryDecision) {
12253
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
12254
+ if (this.config.scope === "global") {
12255
+ this.clearGlobalIndexUnlocked(this.projectRoot, this.getScopedRoots(), recoveryDecision);
11841
12256
  return;
11842
12257
  }
11843
12258
  if (!this.isProjectOwnedIndexPath()) {
@@ -12003,6 +12418,7 @@ var Indexer = class _Indexer {
12003
12418
  )) {
12004
12419
  const chunks = retryBatch.map(({ chunk }) => chunk);
12005
12420
  const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
12421
+ this.restoreMissingChunkRows(database, chunks);
12006
12422
  const batchResult = await this.processPendingChunkBatch(chunks, {
12007
12423
  store,
12008
12424
  provider,
@@ -12017,6 +12433,7 @@ var Indexer = class _Indexer {
12017
12433
  forceReembed: false,
12018
12434
  reuseCachedEmbeddings: false,
12019
12435
  incrementRepeatedFailures: false,
12436
+ forceSingleItemBatches: true,
12020
12437
  onSucceeded: (succeededChunks) => {
12021
12438
  database.addChunksToBranchBatch(
12022
12439
  this.getBranchCatalogKey(),
@@ -12038,9 +12455,12 @@ var Indexer = class _Indexer {
12038
12455
  this.saveInvertedIndex(invertedIndex);
12039
12456
  }
12040
12457
  if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
12041
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12042
- this.saveIndexMetadata(configuredProviderInfo);
12043
- this.indexCompatibility = { compatible: true };
12458
+ const migrationFinalized = database.getMetadata(this.getProjectMigrationFinalizedMetadataKey()) === "true";
12459
+ if (migrationFinalized) {
12460
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12461
+ this.saveIndexMetadata(configuredProviderInfo);
12462
+ this.indexCompatibility = { compatible: true };
12463
+ }
12044
12464
  }
12045
12465
  return { succeeded, failed, remaining };
12046
12466
  }
@@ -12062,7 +12482,8 @@ var Indexer = class _Indexer {
12062
12482
  latestById.set(chunkId, {
12063
12483
  attemptCount: batch.attemptCount,
12064
12484
  error: batch.error,
12065
- lastAttempt: batch.lastAttempt
12485
+ lastAttempt: batch.lastAttempt,
12486
+ chunks: [rawChunk]
12066
12487
  });
12067
12488
  }
12068
12489
  }
@@ -12129,6 +12550,7 @@ var Indexer = class _Indexer {
12129
12550
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
12130
12551
  );
12131
12552
  }
12553
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
12132
12554
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
12133
12555
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
12134
12556
  const vectorStartTime = import_perf_hooks.performance.now();
@@ -12137,7 +12559,8 @@ var Indexer = class _Indexer {
12137
12559
  embedding,
12138
12560
  limit * 2,
12139
12561
  branchChunkIds,
12140
- shouldPrefilterByBranch
12562
+ shouldPrefilterByBranch,
12563
+ temporalChunkIds
12141
12564
  );
12142
12565
  const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
12143
12566
  if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
@@ -12255,9 +12678,9 @@ var Indexer = class _Indexer {
12255
12678
  this.requireReadableComponents(readIssues, "database");
12256
12679
  let shortest = [];
12257
12680
  for (const branchKey of this.getBranchCatalogKeys()) {
12258
- const path31 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
12259
- if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
12260
- shortest = path31;
12681
+ const path33 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
12682
+ if (path33.length > 0 && (shortest.length === 0 || path33.length < shortest.length)) {
12683
+ shortest = path33;
12261
12684
  }
12262
12685
  }
12263
12686
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -12305,13 +12728,13 @@ var Indexer = class _Indexer {
12305
12728
  }
12306
12729
  }
12307
12730
  if (!found) continue;
12308
- const path31 = [];
12731
+ const path33 = [];
12309
12732
  let currentSymbolId = toSymbolId;
12310
12733
  while (true) {
12311
12734
  const symbol = symbolsById.get(currentSymbolId);
12312
12735
  if (!symbol) break;
12313
12736
  const parent = parentBySymbolId.get(currentSymbolId);
12314
- path31.push({
12737
+ path33.push({
12315
12738
  symbolId: symbol.id,
12316
12739
  symbolName: symbol.name,
12317
12740
  filePath: symbol.filePath,
@@ -12321,9 +12744,9 @@ var Indexer = class _Indexer {
12321
12744
  if (!parent) break;
12322
12745
  currentSymbolId = parent.parentId;
12323
12746
  }
12324
- path31.reverse();
12325
- if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
12326
- shortest = path31;
12747
+ path33.reverse();
12748
+ if (path33.length > 0 && (shortest.length === 0 || path33.length < shortest.length)) {
12749
+ shortest = path33;
12327
12750
  }
12328
12751
  }
12329
12752
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -12659,7 +13082,6 @@ var Indexer = class _Indexer {
12659
13082
  this.store = null;
12660
13083
  this.invertedIndex = null;
12661
13084
  this.provider = null;
12662
- this.reranker = null;
12663
13085
  this.configuredProviderInfo = null;
12664
13086
  this.indexCompatibility = null;
12665
13087
  this.initializationMode = "none";
@@ -12720,6 +13142,36 @@ function resolveConfigPathValue(value, baseDir) {
12720
13142
  const absolutePath = path16.isAbsolute(trimmed) ? trimmed : path16.resolve(baseDir, trimmed);
12721
13143
  return path16.normalize(absolutePath);
12722
13144
  }
13145
+ function serializeConfigPathValue(value, baseDir) {
13146
+ const trimmed = value.trim();
13147
+ if (!trimmed) {
13148
+ return trimmed;
13149
+ }
13150
+ if (!path16.isAbsolute(trimmed)) {
13151
+ return normalizePathSeparators(path16.normalize(trimmed));
13152
+ }
13153
+ const relativePath = path16.relative(baseDir, trimmed);
13154
+ if (!relativePath || !relativePath.startsWith("..") && !path16.isAbsolute(relativePath)) {
13155
+ return normalizePathSeparators(path16.normalize(relativePath || "."));
13156
+ }
13157
+ return path16.normalize(trimmed);
13158
+ }
13159
+ function resolveKnowledgeBasePath(value, projectRoot) {
13160
+ return path16.isAbsolute(value) ? value : path16.resolve(projectRoot, value);
13161
+ }
13162
+ function normalizeKnowledgeBasePath(value, projectRoot) {
13163
+ return path16.normalize(resolveKnowledgeBasePath(value, projectRoot));
13164
+ }
13165
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
13166
+ const normalizedInput = path16.normalize(inputPath);
13167
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath(kb, projectRoot) === normalizedInput);
13168
+ }
13169
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
13170
+ const normalizedInput = path16.normalize(inputPath);
13171
+ return knowledgeBases.findIndex(
13172
+ (kb) => path16.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath(kb, projectRoot) === normalizedInput
13173
+ );
13174
+ }
12723
13175
 
12724
13176
  // src/tools/format-communities.ts
12725
13177
  function compareText(left, right) {
@@ -12973,8 +13425,8 @@ function formatExactSearchHandoff(results) {
12973
13425
  }
12974
13426
  function formatContextEvidence(result, index) {
12975
13427
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
12976
- const path31 = compactEvidenceValue(result.filePath, 120);
12977
- return `[${index}] ${result.chunkType}${symbol} in ${path31}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
13428
+ const path33 = compactEvidenceValue(result.filePath, 120);
13429
+ return `[${index}] ${result.chunkType}${symbol} in ${path33}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
12978
13430
  }
12979
13431
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
12980
13432
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -13628,7 +14080,7 @@ function getErrorMessage4(error) {
13628
14080
  return error instanceof Error ? error.message : String(error);
13629
14081
  }
13630
14082
  function runCommand(file, args, options) {
13631
- return new Promise((resolve18, reject) => {
14083
+ return new Promise((resolve20, reject) => {
13632
14084
  childProcess.execFile(
13633
14085
  file,
13634
14086
  args,
@@ -13638,7 +14090,7 @@ function runCommand(file, args, options) {
13638
14090
  reject(error);
13639
14091
  return;
13640
14092
  }
13641
- resolve18(stdout);
14093
+ resolve20(stdout);
13642
14094
  }
13643
14095
  );
13644
14096
  });
@@ -13783,10 +14235,10 @@ function safeFailureMessage(error) {
13783
14235
  }
13784
14236
  function cancellableDelay(delayMs, signal) {
13785
14237
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
13786
- return new Promise((resolve18, reject) => {
14238
+ return new Promise((resolve20, reject) => {
13787
14239
  const timer = setTimeout(() => {
13788
14240
  signal.removeEventListener("abort", onAbort);
13789
- resolve18();
14241
+ resolve20();
13790
14242
  }, delayMs);
13791
14243
  timer.unref?.();
13792
14244
  const onAbort = () => {
@@ -13798,15 +14250,15 @@ function cancellableDelay(delayMs, signal) {
13798
14250
  }
13799
14251
  function withTimeout(promise, timeoutMs) {
13800
14252
  if (timeoutMs <= 0) return Promise.resolve(void 0);
13801
- return new Promise((resolve18) => {
13802
- const timer = setTimeout(() => resolve18(void 0), timeoutMs);
14253
+ return new Promise((resolve20) => {
14254
+ const timer = setTimeout(() => resolve20(void 0), timeoutMs);
13803
14255
  timer.unref?.();
13804
14256
  void promise.then((value) => {
13805
14257
  clearTimeout(timer);
13806
- resolve18(value);
14258
+ resolve20(value);
13807
14259
  }, () => {
13808
14260
  clearTimeout(timer);
13809
- resolve18(void 0);
14261
+ resolve20(void 0);
13810
14262
  });
13811
14263
  });
13812
14264
  }
@@ -14188,17 +14640,17 @@ var AutoIndexCoordinator = class {
14188
14640
  }
14189
14641
  }
14190
14642
  waitForBatteryRetry(delayMs) {
14191
- return new Promise((resolve18) => {
14643
+ return new Promise((resolve20) => {
14192
14644
  const timer = setTimeout(() => {
14193
14645
  if (this.batteryRetryTimer === timer) {
14194
14646
  this.batteryRetryTimer = null;
14195
14647
  this.resolveBatteryRetry = null;
14196
14648
  }
14197
- resolve18();
14649
+ resolve20();
14198
14650
  }, delayMs);
14199
14651
  timer.unref?.();
14200
14652
  this.batteryRetryTimer = timer;
14201
- this.resolveBatteryRetry = resolve18;
14653
+ this.resolveBatteryRetry = resolve20;
14202
14654
  });
14203
14655
  }
14204
14656
  cancelBatteryRetry() {
@@ -14206,9 +14658,9 @@ var AutoIndexCoordinator = class {
14206
14658
  clearTimeout(this.batteryRetryTimer);
14207
14659
  this.batteryRetryTimer = null;
14208
14660
  }
14209
- const resolve18 = this.resolveBatteryRetry;
14661
+ const resolve20 = this.resolveBatteryRetry;
14210
14662
  this.resolveBatteryRetry = null;
14211
- resolve18?.();
14663
+ resolve20?.();
14212
14664
  }
14213
14665
  finishBatteryCheck(batteryCheck) {
14214
14666
  if (this.batteryCheck !== batteryCheck) return;
@@ -14426,7 +14878,7 @@ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key
14426
14878
  function mergeUniqueStringArray(values) {
14427
14879
  return [...new Set(values.map((value) => String(value).trim()))];
14428
14880
  }
14429
- function normalizeKnowledgeBasePath(value) {
14881
+ function normalizeKnowledgeBasePath2(value) {
14430
14882
  let normalized = path19.normalize(String(value).trim());
14431
14883
  const root = path19.parse(normalized).root;
14432
14884
  while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
@@ -14435,7 +14887,7 @@ function normalizeKnowledgeBasePath(value) {
14435
14887
  return normalized;
14436
14888
  }
14437
14889
  function mergeKnowledgeBasePaths(values) {
14438
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
14890
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath2(value)).filter((value) => value.length > 0))];
14439
14891
  }
14440
14892
  function validateConfigLayerShape(rawConfig, filePath) {
14441
14893
  if (!isRecord(rawConfig)) {
@@ -14565,9 +15017,30 @@ function toConfigRecord(rawConfig) {
14565
15017
  }
14566
15018
  return { ...rawConfig };
14567
15019
  }
15020
+ function getConfigPath(projectRoot, host) {
15021
+ return resolveWritableProjectConfigPath(projectRoot, host);
15022
+ }
14568
15023
  function loadRuntimeConfig(projectRoot, host) {
14569
15024
  return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot, host)), projectRoot);
14570
15025
  }
15026
+ function loadEditableConfig(projectRoot, host) {
15027
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot, host)), projectRoot);
15028
+ }
15029
+ function saveConfig(projectRoot, config, host) {
15030
+ const configPath = getConfigPath(projectRoot, host);
15031
+ const configDir = path20.dirname(configPath);
15032
+ const configBaseDir = path20.dirname(configDir);
15033
+ if (!(0, import_fs13.existsSync)(configDir)) {
15034
+ (0, import_fs13.mkdirSync)(configDir, { recursive: true });
15035
+ }
15036
+ const serializableConfig = { ...config };
15037
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
15038
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
15039
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
15040
+ );
15041
+ }
15042
+ (0, import_fs13.writeFileSync)(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
15043
+ }
14571
15044
 
14572
15045
  // src/tools/operation-runtime.ts
14573
15046
  var indexerCache = /* @__PURE__ */ new Map();
@@ -14786,9 +15259,11 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14786
15259
  contextLines: options.contextLines,
14787
15260
  metadataOnly: options.metadataOnly,
14788
15261
  definitionIntent: options.definitionIntent,
15262
+ prioritizeSourcePaths: options.prioritizeSourcePaths,
14789
15263
  blameAuthor: options.blameAuthor,
14790
15264
  blameSha: options.blameSha,
14791
15265
  blameSince: options.blameSince,
15266
+ blameUntil: options.blameUntil,
14792
15267
  trace: options.trace
14793
15268
  });
14794
15269
  }
@@ -14834,7 +15309,9 @@ async function findSimilarCode(projectRoot, host, code, options = {}) {
14834
15309
  fileType: options.fileType,
14835
15310
  directory: options.directory,
14836
15311
  chunkType: options.chunkType,
14837
- excludeFile: options.excludeFile
15312
+ excludeFile: options.excludeFile,
15313
+ blameSince: options.blameSince,
15314
+ blameUntil: options.blameUntil
14838
15315
  });
14839
15316
  }
14840
15317
  async function implementationLookup(projectRoot, host, query, options = {}) {
@@ -14883,12 +15360,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14883
15360
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14884
15361
  return { from: fromResolution, to: toResolution, path: [] };
14885
15362
  }
14886
- const path31 = await indexer.findCallPathBySymbolIds(
15363
+ const path33 = await indexer.findCallPathBySymbolIds(
14887
15364
  fromResolution.symbolId,
14888
15365
  toResolution.symbolId,
14889
15366
  maxDepth
14890
15367
  );
14891
- return { from: fromResolution, to: toResolution, path: path31 };
15368
+ return { from: fromResolution, to: toResolution, path: path33 };
14892
15369
  }
14893
15370
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
14894
15371
  const root = getProjectRoot(projectRoot, host);
@@ -15080,6 +15557,141 @@ async function getIndexLogs(projectRoot, host, args) {
15080
15557
  }).join("\n");
15081
15558
  return { kind: "entries", text };
15082
15559
  }
15560
+ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15561
+ const root = getProjectRoot(projectRoot, host);
15562
+ const inputPath = knowledgeBasePath.trim();
15563
+ const normalizedPath3 = path21.resolve(
15564
+ path21.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
15565
+ );
15566
+ if (!(0, import_fs14.existsSync)(normalizedPath3)) {
15567
+ return `Error: Directory does not exist: ${normalizedPath3}`;
15568
+ }
15569
+ let realPath;
15570
+ try {
15571
+ realPath = (0, import_fs14.realpathSync)(normalizedPath3);
15572
+ } catch {
15573
+ return `Error: Cannot resolve path: ${normalizedPath3}`;
15574
+ }
15575
+ const blockedPrefixes = [
15576
+ "/etc",
15577
+ "/proc",
15578
+ "/sys",
15579
+ "/dev",
15580
+ "/boot",
15581
+ "/root",
15582
+ "/var/run",
15583
+ "/var/log"
15584
+ ];
15585
+ const homeDir = process.platform === "win32" ? process.env.USERPROFILE ?? "" : process.env.HOME ?? "";
15586
+ const sensitiveDotDirs = [
15587
+ ".ssh",
15588
+ ".gnupg",
15589
+ ".aws",
15590
+ ".config/gcloud",
15591
+ ".docker",
15592
+ ".kube"
15593
+ ];
15594
+ for (const prefix of blockedPrefixes) {
15595
+ if (realPath === prefix || realPath.startsWith(`${prefix}/`)) {
15596
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath3}`;
15597
+ }
15598
+ }
15599
+ for (const dotDir of sensitiveDotDirs) {
15600
+ const sensitiveDir = path21.join(homeDir, dotDir);
15601
+ if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
15602
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath3}`;
15603
+ }
15604
+ }
15605
+ try {
15606
+ const stat5 = (0, import_fs14.statSync)(normalizedPath3);
15607
+ if (!stat5.isDirectory()) {
15608
+ return `Error: Path is not a directory: ${normalizedPath3}`;
15609
+ }
15610
+ } catch (error) {
15611
+ return `Error: Cannot access directory: ${normalizedPath3} - ${error instanceof Error ? error.message : String(error)}`;
15612
+ }
15613
+ const config = loadEditableConfig(root, host);
15614
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15615
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath3, root);
15616
+ if (alreadyExists) {
15617
+ return `Knowledge base already configured: ${normalizedPath3}`;
15618
+ }
15619
+ knowledgeBases.push(normalizedPath3);
15620
+ config.knowledgeBases = knowledgeBases;
15621
+ saveConfig(root, config, host);
15622
+ refreshIndexerForDirectory(root, host);
15623
+ let result = `${normalizedPath3}
15624
+ `;
15625
+ result += `Total knowledge bases: ${knowledgeBases.length}
15626
+ `;
15627
+ result += `Config path: ${getConfigPath(root, host)}
15628
+ `;
15629
+ result += `
15630
+ Run /index to rebuild the index with the new knowledge base.`;
15631
+ return result;
15632
+ }
15633
+ function listKnowledgeBases(projectRoot, host) {
15634
+ const root = getProjectRoot(projectRoot, host);
15635
+ const config = loadRuntimeConfig(root, host);
15636
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15637
+ if (knowledgeBases.length === 0) {
15638
+ return "No knowledge bases configured. Use add_knowledge_base to add folders.";
15639
+ }
15640
+ let result = `Knowledge Bases (${knowledgeBases.length}):
15641
+
15642
+ `;
15643
+ for (let i = 0; i < knowledgeBases.length; i++) {
15644
+ const kb = knowledgeBases[i];
15645
+ const resolvedPath = resolveKnowledgeBasePath(kb, root);
15646
+ const exists = (0, import_fs14.existsSync)(resolvedPath);
15647
+ result += `[${i + 1}] ${kb}
15648
+ `;
15649
+ result += ` Resolved: ${resolvedPath}
15650
+ `;
15651
+ result += ` Status: ${exists ? "Exists" : "NOT FOUND"}
15652
+ `;
15653
+ if (exists) {
15654
+ try {
15655
+ const stat5 = (0, import_fs14.statSync)(resolvedPath);
15656
+ result += ` Type: ${stat5.isDirectory() ? "Directory" : "File"}
15657
+ `;
15658
+ } catch {
15659
+ }
15660
+ }
15661
+ result += "\n";
15662
+ }
15663
+ const hasHostConfig = (0, import_fs14.existsSync)(path21.join(root, getHostProjectConfigRelativePath(host)));
15664
+ if (hasHostConfig) {
15665
+ result += `
15666
+ Config sources: 1 file(s).`;
15667
+ }
15668
+ result += `
15669
+ Config file: ${getConfigPath(root, host)}`;
15670
+ return result;
15671
+ }
15672
+ function removeKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15673
+ const root = getProjectRoot(projectRoot, host);
15674
+ const config = loadEditableConfig(root, host);
15675
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15676
+ const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
15677
+ if (index === -1) {
15678
+ return `Knowledge base not found: ${knowledgeBasePath}`;
15679
+ }
15680
+ const removed = knowledgeBases.splice(index, 1)[0];
15681
+ config.knowledgeBases = knowledgeBases;
15682
+ saveConfig(root, config, host);
15683
+ refreshIndexerForDirectory(root, host);
15684
+ let result = `Removed: ${removed}
15685
+
15686
+ `;
15687
+ result += `Remaining knowledge bases: ${knowledgeBases.length}
15688
+ `;
15689
+ result += `Config saved to: ${getConfigPath(root, host)}
15690
+ `;
15691
+ result += `
15692
+ Run /index to rebuild the index without the removed knowledge base.`;
15693
+ return result;
15694
+ }
15083
15695
 
15084
15696
  // src/tools/context-search.ts
15085
15697
  var MIN_CONTEXT_RESULT_LIMIT = 1;
@@ -15308,13 +15920,19 @@ async function resolveSearchContext(input, operations) {
15308
15920
  (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
15309
15921
  );
15310
15922
  };
15311
- const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
15923
+ const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt, prioritizeSourcePaths) => {
15312
15924
  return recordAttempt(
15313
15925
  "conceptual",
15314
15926
  searchQuery,
15315
15927
  scope,
15316
15928
  relaxedFieldsForAttempt,
15317
- (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
15929
+ (trace) => operations.search(
15930
+ searchQuery,
15931
+ MAX_CONTEXT_RESULT_LIMIT,
15932
+ scope,
15933
+ input.diagnostic ? trace : void 0,
15934
+ { prioritizeSourcePaths }
15935
+ )
15318
15936
  );
15319
15937
  };
15320
15938
  const findSuccessfulAttemptState = (route) => {
@@ -15442,10 +16060,12 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15442
16060
  }
15443
16061
  }
15444
16062
  for (const attempt of conceptualAttemptPlan) {
16063
+ const attemptIntent = analyzeQueryIntent(attempt.queryText);
16064
+ const prioritizeSourcePaths = attemptIntent.primary !== "docs" && attemptIntent.primary !== "test";
15445
16065
  if (inferredSymbol && attempt.queryText === inferredSymbol && attempt.queryText !== query) {
15446
16066
  decisions.fallbackFromOriginalConceptualToInferred = true;
15447
16067
  }
15448
- const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
16068
+ const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed, prioritizeSourcePaths);
15449
16069
  if (results.length > 0) {
15450
16070
  const heading = buildPackHeading("conceptual", decisions);
15451
16071
  const intent = analyzeQueryIntent(attempt.queryText);
@@ -15513,7 +16133,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15513
16133
  const directory = input.directory ?? void 0;
15514
16134
  const tokenBudget = input.tokenBudget ?? void 0;
15515
16135
  if (from && to) {
15516
- const path31 = await getCallGraphPath(
16136
+ const path33 = await getCallGraphPath(
15517
16137
  projectRoot,
15518
16138
  host,
15519
16139
  from,
@@ -15522,25 +16142,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15522
16142
  fromFilePath,
15523
16143
  toFilePath
15524
16144
  );
15525
- const pathText = formatCallGraphPathResult(path31);
15526
- if (path31.path.length > 0) {
16145
+ const pathText = formatCallGraphPathResult(path33);
16146
+ if (path33.path.length > 0) {
15527
16147
  const fitted2 = fitTextToContextBudget(
15528
16148
  pathText,
15529
16149
  tokenBudget
15530
16150
  );
15531
16151
  return {
15532
16152
  text: fitted2.text,
15533
- details: fittedDetails("path", fitted2, path31.path.length)
16153
+ details: fittedDetails("path", fitted2, path33.path.length)
15534
16154
  };
15535
16155
  }
15536
- if (path31.from.status !== "resolved" || path31.to.status !== "resolved") {
16156
+ if (path33.from.status !== "resolved" || path33.to.status !== "resolved") {
15537
16157
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
15538
16158
  return {
15539
16159
  text: fitted2.text,
15540
16160
  details: fittedDetails("path", fitted2, 0)
15541
16161
  };
15542
16162
  }
15543
- const resolvedFrom = path31.from;
16163
+ const resolvedFrom = path33.from;
15544
16164
  const { callers } = await getCallGraphData(projectRoot, host, {
15545
16165
  name: to,
15546
16166
  direction: "callers",
@@ -15582,12 +16202,13 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15582
16202
  directory: scope.directory,
15583
16203
  trace
15584
16204
  }),
15585
- search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
16205
+ search: (queryText, retrievalLimit, scope, trace, searchOptions) => searchCodebase(projectRoot, host, queryText, {
15586
16206
  limit: retrievalLimit,
15587
16207
  fileType: scope.fileType,
15588
16208
  directory: scope.directory,
15589
16209
  metadataOnly: true,
15590
- trace
16210
+ trace,
16211
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
15591
16212
  })
15592
16213
  });
15593
16214
  }
@@ -16017,9 +16638,9 @@ function getRelevantEvidence(query) {
16017
16638
  });
16018
16639
  }
16019
16640
  if (query.expected.acceptableFiles) {
16020
- for (const path31 of query.expected.acceptableFiles) {
16641
+ for (const path33 of query.expected.acceptableFiles) {
16021
16642
  legacyEvidence.push({
16022
- path: path31,
16643
+ path: path33,
16023
16644
  ...query.expected.symbol !== void 0 ? { symbol: query.expected.symbol } : {},
16024
16645
  relevance: 1
16025
16646
  });
@@ -16516,68 +17137,68 @@ function isStringArray4(value) {
16516
17137
  function isNonEmptyString(value) {
16517
17138
  return typeof value === "string" && value.trim().length > 0;
16518
17139
  }
16519
- function asPositiveNumber(value, path31) {
17140
+ function asPositiveNumber(value, path33) {
16520
17141
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
16521
- throw new Error(`${path31} must be a non-negative number`);
17142
+ throw new Error(`${path33} must be a non-negative number`);
16522
17143
  }
16523
17144
  return value;
16524
17145
  }
16525
- function parseQueryType(value, path31) {
17146
+ function parseQueryType(value, path33) {
16526
17147
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy" || value === "conceptual") {
16527
17148
  return value;
16528
17149
  }
16529
17150
  throw new Error(
16530
- `${path31} must be one of: definition, implementation-intent, similarity, keyword-heavy, conceptual`
17151
+ `${path33} must be one of: definition, implementation-intent, similarity, keyword-heavy, conceptual`
16531
17152
  );
16532
17153
  }
16533
- function parseExpectedRoute(value, path31) {
17154
+ function parseExpectedRoute(value, path33) {
16534
17155
  if (value === void 0) return void 0;
16535
17156
  if (value === "search" || value === "definition") return value;
16536
- throw new Error(`${path31} must be one of: search, definition`);
17157
+ throw new Error(`${path33} must be one of: search, definition`);
16537
17158
  }
16538
- function parseExpectedOutcome(value, path31) {
17159
+ function parseExpectedOutcome(value, path33) {
16539
17160
  if (value === void 0) return void 0;
16540
17161
  if (value === "results" || value === "no-results") {
16541
17162
  return value;
16542
17163
  }
16543
- throw new Error(`${path31} must be one of: results, no-results`);
17164
+ throw new Error(`${path33} must be one of: results, no-results`);
16544
17165
  }
16545
- function parseRecoveryExpectation(value, path31) {
17166
+ function parseRecoveryExpectation(value, path33) {
16546
17167
  if (value === void 0) return void 0;
16547
17168
  if (value === "none" || value === "filter-relaxed") {
16548
17169
  return value;
16549
17170
  }
16550
- throw new Error(`${path31} must be one of: none, filter-relaxed`);
17171
+ throw new Error(`${path33} must be one of: none, filter-relaxed`);
16551
17172
  }
16552
- function parseQueryDifficulty(value, path31) {
17173
+ function parseQueryDifficulty(value, path33) {
16553
17174
  if (value === void 0) return void 0;
16554
17175
  if (value === "easy" || value === "medium" || value === "hard") {
16555
17176
  return value;
16556
17177
  }
16557
- throw new Error(`${path31} must be one of: easy, medium, hard`);
17178
+ throw new Error(`${path33} must be one of: easy, medium, hard`);
16558
17179
  }
16559
- function parseQueryTags(value, path31) {
17180
+ function parseQueryTags(value, path33) {
16560
17181
  if (value === void 0) return void 0;
16561
17182
  if (!isStringArray4(value) || value.some((tag) => tag.trim().length === 0)) {
16562
- throw new Error(`${path31} must be an array of non-empty strings`);
17183
+ throw new Error(`${path33} must be an array of non-empty strings`);
16563
17184
  }
16564
17185
  if (value.length > 16) {
16565
- throw new Error(`${path31} must contain at most 16 tags`);
17186
+ throw new Error(`${path33} must contain at most 16 tags`);
16566
17187
  }
16567
17188
  return value;
16568
17189
  }
16569
- function parseQueryArgs(value, path31) {
17190
+ function parseQueryArgs(value, path33) {
16570
17191
  if (value === void 0) return void 0;
16571
17192
  if (!isRecord3(value)) {
16572
- throw new Error(`${path31} must be an object`);
16573
- }
16574
- const symbol = parseStringOrUndefined(value.symbol, `${path31}.symbol`);
16575
- const filePath = parseStringOrUndefined(value.filePath, `${path31}.filePath`);
16576
- const fileType = parseStringOrUndefined(value.fileType, `${path31}.fileType`);
16577
- const directory = parseStringOrUndefined(value.directory, `${path31}.directory`);
16578
- const callerLimit = parsePositiveIntegerOrUndefined(value.callerLimit, `${path31}.callerLimit`);
16579
- const calleeLimit = parsePositiveIntegerOrUndefined(value.calleeLimit, `${path31}.calleeLimit`);
16580
- const tokenBudget = parsePositiveIntegerOrUndefined(value.tokenBudget, `${path31}.tokenBudget`);
17193
+ throw new Error(`${path33} must be an object`);
17194
+ }
17195
+ const symbol = parseStringOrUndefined(value.symbol, `${path33}.symbol`);
17196
+ const filePath = parseStringOrUndefined(value.filePath, `${path33}.filePath`);
17197
+ const fileType = parseStringOrUndefined(value.fileType, `${path33}.fileType`);
17198
+ const directory = parseStringOrUndefined(value.directory, `${path33}.directory`);
17199
+ const callerLimit = parsePositiveIntegerOrUndefined(value.callerLimit, `${path33}.callerLimit`);
17200
+ const calleeLimit = parsePositiveIntegerOrUndefined(value.calleeLimit, `${path33}.calleeLimit`);
17201
+ const tokenBudget = parsePositiveIntegerOrUndefined(value.tokenBudget, `${path33}.tokenBudget`);
16581
17202
  return {
16582
17203
  ...symbol !== void 0 ? { symbol } : {},
16583
17204
  ...filePath !== void 0 ? { filePath } : {},
@@ -16588,50 +17209,50 @@ function parseQueryArgs(value, path31) {
16588
17209
  ...tokenBudget !== void 0 ? { tokenBudget } : {}
16589
17210
  };
16590
17211
  }
16591
- function parsePositiveIntegerOrUndefined(value, path31) {
17212
+ function parsePositiveIntegerOrUndefined(value, path33) {
16592
17213
  if (value === void 0 || value === null) return void 0;
16593
17214
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
16594
- throw new Error(`${path31} must be a positive integer`);
17215
+ throw new Error(`${path33} must be a positive integer`);
16595
17216
  }
16596
17217
  return value;
16597
17218
  }
16598
17219
  var SEMVER_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
16599
- function parseSemanticVersion(value, path31) {
17220
+ function parseSemanticVersion(value, path33) {
16600
17221
  if (!isNonEmptyString(value)) {
16601
- throw new Error(`${path31} must be a non-empty string`);
17222
+ throw new Error(`${path33} must be a non-empty string`);
16602
17223
  }
16603
17224
  if (!SEMVER_VERSION_PATTERN.test(value)) {
16604
- throw new Error(`${path31} must be a valid semantic version (MAJOR.MINOR.PATCH)`);
17225
+ throw new Error(`${path33} must be a valid semantic version (MAJOR.MINOR.PATCH)`);
16605
17226
  }
16606
17227
  return value;
16607
17228
  }
16608
- function parseRetrievalMode(value, path31) {
17229
+ function parseRetrievalMode(value, path33) {
16609
17230
  if (value === void 0 || value === "search") return "search";
16610
17231
  if (value === "context" || value === "edit-context") return value;
16611
- throw new Error(`${path31} must be one of: search, context, edit-context`);
17232
+ throw new Error(`${path33} must be one of: search, context, edit-context`);
16612
17233
  }
16613
- function parseStringOrUndefined(value, path31) {
17234
+ function parseStringOrUndefined(value, path33) {
16614
17235
  if (value === void 0 || value === null) return void 0;
16615
17236
  if (!isNonEmptyString(value)) {
16616
- throw new Error(`${path31} must be a non-empty string`);
17237
+ throw new Error(`${path33} must be a non-empty string`);
16617
17238
  }
16618
17239
  return value;
16619
17240
  }
16620
- function parseGradedEvidence(value, path31) {
17241
+ function parseGradedEvidence(value, path33) {
16621
17242
  if (value === void 0) return [];
16622
17243
  if (!Array.isArray(value)) {
16623
- throw new Error(`${path31} must be an array`);
17244
+ throw new Error(`${path33} must be an array`);
16624
17245
  }
16625
17246
  return value.map((entry, index) => {
16626
17247
  if (!isRecord3(entry)) {
16627
- throw new Error(`${path31}[${index}] must be an object`);
17248
+ throw new Error(`${path33}[${index}] must be an object`);
16628
17249
  }
16629
- const evidencePath = parseStringOrUndefined(entry.path, `${path31}[${index}].path`);
17250
+ const evidencePath = parseStringOrUndefined(entry.path, `${path33}[${index}].path`);
16630
17251
  if (evidencePath === void 0) {
16631
- throw new Error(`${path31}[${index}].path is required`);
17252
+ throw new Error(`${path33}[${index}].path is required`);
16632
17253
  }
16633
- const symbol = parseStringOrUndefined(entry.symbol, `${path31}[${index}].symbol`);
16634
- const relevance = parseEvidenceRelevance(entry.relevance, `${path31}[${index}].relevance`);
17254
+ const symbol = parseStringOrUndefined(entry.symbol, `${path33}[${index}].symbol`);
17255
+ const relevance = parseEvidenceRelevance(entry.relevance, `${path33}[${index}].relevance`);
16635
17256
  return {
16636
17257
  path: evidencePath,
16637
17258
  ...symbol !== void 0 ? { symbol } : {},
@@ -16639,27 +17260,27 @@ function parseGradedEvidence(value, path31) {
16639
17260
  };
16640
17261
  });
16641
17262
  }
16642
- function parseEvidenceRelevance(value, path31) {
17263
+ function parseEvidenceRelevance(value, path33) {
16643
17264
  if (value === void 0) {
16644
- throw new Error(`${path31} is required`);
17265
+ throw new Error(`${path33} is required`);
16645
17266
  }
16646
17267
  if (value !== 1 && value !== 2 && value !== 3) {
16647
- throw new Error(`${path31} must be 1, 2, or 3`);
17268
+ throw new Error(`${path33} must be 1, 2, or 3`);
16648
17269
  }
16649
17270
  return value;
16650
17271
  }
16651
- function parseExpectedGraphNeighbor(value, path31) {
17272
+ function parseExpectedGraphNeighbor(value, path33) {
16652
17273
  if (value === void 0) return void 0;
16653
17274
  if (!isRecord3(value)) {
16654
- throw new Error(`${path31} must be an object`);
17275
+ throw new Error(`${path33} must be an object`);
16655
17276
  }
16656
17277
  if (value.direction !== "caller" && value.direction !== "callee") {
16657
- throw new Error(`${path31}.direction must be one of: caller, callee`);
17278
+ throw new Error(`${path33}.direction must be one of: caller, callee`);
16658
17279
  }
16659
- const filePath = parseStringOrUndefined(value.filePath, `${path31}.filePath`);
16660
- const symbol = parseStringOrUndefined(value.symbol, `${path31}.symbol`);
17280
+ const filePath = parseStringOrUndefined(value.filePath, `${path33}.filePath`);
17281
+ const symbol = parseStringOrUndefined(value.symbol, `${path33}.symbol`);
16661
17282
  if (filePath === void 0 && symbol === void 0) {
16662
- throw new Error(`${path31} must include filePath or symbol`);
17283
+ throw new Error(`${path33} must include filePath or symbol`);
16663
17284
  }
16664
17285
  return {
16665
17286
  direction: value.direction,
@@ -16667,9 +17288,9 @@ function parseExpectedGraphNeighbor(value, path31) {
16667
17288
  ...symbol !== void 0 ? { symbol } : {}
16668
17289
  };
16669
17290
  }
16670
- function parseExpected(input, path31) {
17291
+ function parseExpected(input, path33) {
16671
17292
  if (!isRecord3(input)) {
16672
- throw new Error(`${path31} must be an object`);
17293
+ throw new Error(`${path33} must be an object`);
16673
17294
  }
16674
17295
  const filePathRaw = input.filePath;
16675
17296
  const acceptableFilesRaw = input.acceptableFiles;
@@ -16680,29 +17301,29 @@ function parseExpected(input, path31) {
16680
17301
  const recoveryExpectationRaw = input.recoveryExpectation;
16681
17302
  const gradedEvidenceRaw = input.gradedEvidence;
16682
17303
  const graphNeighborRaw = input.graphNeighbor;
16683
- const filePath = parseStringOrUndefined(filePathRaw, `${path31}.filePath`);
17304
+ const filePath = parseStringOrUndefined(filePathRaw, `${path33}.filePath`);
16684
17305
  const acceptableFiles = isStringArray4(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
16685
- const gradedEvidence = parseGradedEvidence(gradedEvidenceRaw, `${path31}.gradedEvidence`);
16686
- const graphNeighbor = parseExpectedGraphNeighbor(graphNeighborRaw, `${path31}.graphNeighbor`);
16687
- const expectedOutcome = parseExpectedOutcome(expectedOutcomeRaw, `${path31}.expectedOutcome`);
17306
+ const gradedEvidence = parseGradedEvidence(gradedEvidenceRaw, `${path33}.gradedEvidence`);
17307
+ const graphNeighbor = parseExpectedGraphNeighbor(graphNeighborRaw, `${path33}.graphNeighbor`);
17308
+ const expectedOutcome = parseExpectedOutcome(expectedOutcomeRaw, `${path33}.expectedOutcome`);
16688
17309
  if (expectedOutcome !== "no-results" && !filePath && (!acceptableFiles || acceptableFiles.length === 0) && gradedEvidence.length === 0) {
16689
17310
  throw new Error(
16690
- `${path31} must include expected.filePath, expected.acceptableFiles, or expected.gradedEvidence`
17311
+ `${path33} must include expected.filePath, expected.acceptableFiles, or expected.gradedEvidence`
16691
17312
  );
16692
17313
  }
16693
17314
  if (acceptableFilesRaw !== void 0 && !isStringArray4(acceptableFilesRaw)) {
16694
- throw new Error(`${path31}.acceptableFiles must be an array of strings`);
17315
+ throw new Error(`${path33}.acceptableFiles must be an array of strings`);
16695
17316
  }
16696
17317
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
16697
- throw new Error(`${path31}.symbol must be a string when provided`);
17318
+ throw new Error(`${path33}.symbol must be a string when provided`);
16698
17319
  }
16699
17320
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
16700
- throw new Error(`${path31}.branch must be a string when provided`);
17321
+ throw new Error(`${path33}.branch must be a string when provided`);
16701
17322
  }
16702
- const expectedRoute = parseExpectedRoute(expectedRouteRaw, `${path31}.expectedRoute`);
17323
+ const expectedRoute = parseExpectedRoute(expectedRouteRaw, `${path33}.expectedRoute`);
16703
17324
  const recoveryExpectation = parseRecoveryExpectation(
16704
17325
  recoveryExpectationRaw,
16705
- `${path31}.recoveryExpectation`
17326
+ `${path33}.recoveryExpectation`
16706
17327
  );
16707
17328
  return {
16708
17329
  filePath,
@@ -16716,13 +17337,13 @@ function parseExpected(input, path31) {
16716
17337
  ...graphNeighbor !== void 0 ? { graphNeighbor } : {}
16717
17338
  };
16718
17339
  }
16719
- function parseQueryLanguage(value, path31) {
16720
- return parseStringOrUndefined(value, path31);
17340
+ function parseQueryLanguage(value, path33) {
17341
+ return parseStringOrUndefined(value, path33);
16721
17342
  }
16722
17343
  function parseQuery(input, index) {
16723
- const path31 = `queries[${index}]`;
17344
+ const path33 = `queries[${index}]`;
16724
17345
  if (!isRecord3(input)) {
16725
- throw new Error(`${path31} must be an object`);
17346
+ throw new Error(`${path33} must be an object`);
16726
17347
  }
16727
17348
  const id = input.id;
16728
17349
  const query = input.query;
@@ -16734,21 +17355,21 @@ function parseQuery(input, index) {
16734
17355
  const tags = input.tags;
16735
17356
  const args = input.args;
16736
17357
  if (typeof id !== "string" || id.trim().length === 0) {
16737
- throw new Error(`${path31}.id must be a non-empty string`);
17358
+ throw new Error(`${path33}.id must be a non-empty string`);
16738
17359
  }
16739
17360
  if (typeof query !== "string" || query.trim().length === 0) {
16740
- throw new Error(`${path31}.query must be a non-empty string`);
17361
+ throw new Error(`${path33}.query must be a non-empty string`);
16741
17362
  }
16742
17363
  return {
16743
17364
  id,
16744
17365
  query,
16745
- queryType: parseQueryType(queryType, `${path31}.queryType`),
16746
- retrievalMode: parseRetrievalMode(retrievalMode, `${path31}.retrievalMode`),
16747
- language: parseQueryLanguage(language, `${path31}.language`),
16748
- difficulty: parseQueryDifficulty(difficulty, `${path31}.difficulty`),
16749
- args: parseQueryArgs(args, `${path31}.args`),
16750
- tags: parseQueryTags(tags, `${path31}.tags`),
16751
- expected: parseExpected(expected, `${path31}.expected`)
17366
+ queryType: parseQueryType(queryType, `${path33}.queryType`),
17367
+ retrievalMode: parseRetrievalMode(retrievalMode, `${path33}.retrievalMode`),
17368
+ language: parseQueryLanguage(language, `${path33}.language`),
17369
+ difficulty: parseQueryDifficulty(difficulty, `${path33}.difficulty`),
17370
+ args: parseQueryArgs(args, `${path33}.args`),
17371
+ tags: parseQueryTags(tags, `${path33}.tags`),
17372
+ expected: parseExpected(expected, `${path33}.expected`)
16752
17373
  };
16753
17374
  }
16754
17375
  function parseGoldenDataset(raw, sourceLabel) {
@@ -17088,12 +17709,13 @@ async function runEvaluation(options) {
17088
17709
  fileType: scope.fileType,
17089
17710
  directory: scope.directory
17090
17711
  }),
17091
- search: (searchQuery, limit, scope) => indexer.search(searchQuery, limit, {
17712
+ search: (searchQuery, limit, scope, _trace, searchOptions) => indexer.search(searchQuery, limit, {
17092
17713
  metadataOnly: true,
17093
17714
  filterByBranch: !!query.expected.branch,
17094
17715
  definitionIntent: false,
17095
17716
  fileType: scope.fileType,
17096
- directory: scope.directory
17717
+ directory: scope.directory,
17718
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
17097
17719
  })
17098
17720
  }) : void 0;
17099
17721
  const result = editContextResult?.results ?? contextResult?.details?.results ?? await indexer.search(query.query, 10, {
@@ -17715,7 +18337,7 @@ async function executeCallGraph(projectRoot, host, args) {
17715
18337
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
17716
18338
  }
17717
18339
  async function executeCallGraphPath(projectRoot, host, args) {
17718
- const path31 = await getCallGraphPath(
18340
+ const path33 = await getCallGraphPath(
17719
18341
  projectRoot,
17720
18342
  host,
17721
18343
  args.from,
@@ -17724,7 +18346,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
17724
18346
  args.fromFilePath,
17725
18347
  args.toFilePath
17726
18348
  );
17727
- return { text: formatCallGraphPathResult(path31) };
18349
+ return { text: formatCallGraphPathResult(path33) };
17728
18350
  }
17729
18351
  async function executeCodeCommunities(projectRoot, host, args) {
17730
18352
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -17870,11 +18492,21 @@ var PI_TOOL_NAMES = [
17870
18492
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
17871
18493
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
17872
18494
  ];
18495
+ var MCP_TOOL_NAMES = [
18496
+ ...PORTABLE_TOOL_NAMES,
18497
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
18498
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
18499
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE
18500
+ ];
17873
18501
 
17874
18502
  // src/adapters/mcp/register-tools.ts
17875
18503
  function allowNullAsUndefined(schema) {
17876
18504
  return import_zod2.z.preprocess((value) => value === null ? void 0 : value, schema);
17877
18505
  }
18506
+ function knowledgeBaseResult(text) {
18507
+ const content = [{ type: "text", text }];
18508
+ return text.startsWith("Error: ") ? { content, isError: true } : { content };
18509
+ }
17878
18510
  function registerMcpTools(server, runtime) {
17879
18511
  server.tool(
17880
18512
  TOOL_NAME.CODEBASE_CONTEXT,
@@ -17935,7 +18567,8 @@ function registerMcpTools(server, runtime) {
17935
18567
  contextLines: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Number of extra lines to include before/after each match (default: 0)"),
17936
18568
  blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
17937
18569
  blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
17938
- blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
18570
+ blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date"),
18571
+ blameUntil: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or before this date")
17939
18572
  },
17940
18573
  async (args) => {
17941
18574
  return searchCodebaseWithEffectiveness(runtime.projectRoot, runtime.host, "search", args.query, {
@@ -17946,7 +18579,8 @@ function registerMcpTools(server, runtime) {
17946
18579
  contextLines: args.contextLines,
17947
18580
  blameAuthor: args.blameAuthor,
17948
18581
  blameSha: args.blameSha,
17949
- blameSince: args.blameSince
18582
+ blameSince: args.blameSince,
18583
+ blameUntil: args.blameUntil
17950
18584
  }, (results) => {
17951
18585
  const text = results.length === 0 ? "No matching code found. Try a different query or run index_codebase first." : `Found ${results.length} results for "${args.query}":
17952
18586
 
@@ -17966,7 +18600,8 @@ ${formatSearchResults(results, "score")}`;
17966
18600
  chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPES).optional()).describe("Filter by code chunk type"),
17967
18601
  blameAuthor: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame author name or email"),
17968
18602
  blameSha: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by git blame commit SHA or prefix"),
17969
- blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date")
18603
+ blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date"),
18604
+ blameUntil: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or before this date")
17970
18605
  },
17971
18606
  async (args) => {
17972
18607
  return searchCodebaseWithEffectiveness(runtime.projectRoot, runtime.host, "peek", args.query, {
@@ -17977,7 +18612,8 @@ ${formatSearchResults(results, "score")}`;
17977
18612
  metadataOnly: true,
17978
18613
  blameAuthor: args.blameAuthor,
17979
18614
  blameSha: args.blameSha,
17980
- blameSince: args.blameSince
18615
+ blameSince: args.blameSince,
18616
+ blameUntil: args.blameUntil
17981
18617
  }, (results) => {
17982
18618
  const text = results.length === 0 ? "No matching code found. Try a different query or run index_codebase first." : `Found ${results.length} locations for "${args.query}":
17983
18619
 
@@ -18054,7 +18690,9 @@ ${formatCodebasePeek(results)}`;
18054
18690
  fileType: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
18055
18691
  directory: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
18056
18692
  chunkType: allowNullAsUndefined(import_zod2.z.enum(CHUNK_TYPES).optional()).describe("Filter by code chunk type"),
18057
- excludeFile: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exclude results from this file path")
18693
+ excludeFile: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Exclude results from this file path"),
18694
+ blameSince: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or after this date"),
18695
+ blameUntil: allowNullAsUndefined(import_zod2.z.string().optional()).describe("Filter to chunks last changed on or before this date")
18058
18696
  },
18059
18697
  async (args) => {
18060
18698
  const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
@@ -18062,7 +18700,9 @@ ${formatCodebasePeek(results)}`;
18062
18700
  fileType: args.fileType,
18063
18701
  directory: args.directory,
18064
18702
  chunkType: args.chunkType,
18065
- excludeFile: args.excludeFile
18703
+ excludeFile: args.excludeFile,
18704
+ blameSince: args.blameSince,
18705
+ blameUntil: args.blameUntil
18066
18706
  });
18067
18707
  if (results.length === 0) {
18068
18708
  return { content: [{ type: "text", text: "No similar code found. Try a different snippet or run index_codebase first." }] };
@@ -18166,6 +18806,37 @@ ${formatSearchResults(results)}` }] };
18166
18806
  return { content: [{ type: "text", text: result.text }] };
18167
18807
  }
18168
18808
  );
18809
+ server.tool(
18810
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
18811
+ "Add a folder as a knowledge base to the semantic search index. The folder is indexed alongside the project code on the next index run. Provide an absolute path or a path relative to the project root. The path is written to the project-local host config of this MCP server (under the server project root), not to a user-global config, and the index is refreshed. Git blame metadata is collected only for files in the project git repo; knowledge-base files outside the repo remain searchable by content but have no blame. A knowledge base that appears in list_knowledge_bases but was inherited from a global config cannot be removed by this tool.",
18812
+ {
18813
+ path: import_zod2.z.string().describe("Path to the folder to add as a knowledge base (absolute or relative to the project root)")
18814
+ },
18815
+ async (args) => {
18816
+ const result = addKnowledgeBase(runtime.projectRoot, runtime.host, args.path);
18817
+ return knowledgeBaseResult(result);
18818
+ }
18819
+ );
18820
+ server.tool(
18821
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
18822
+ "List the configured knowledge base folders that the index includes alongside the project code. The list is the union of project-local and user-global knowledge bases; each entry shows the resolved path and whether it exists.",
18823
+ {},
18824
+ async () => {
18825
+ const result = listKnowledgeBases(runtime.projectRoot, runtime.host);
18826
+ return knowledgeBaseResult(result);
18827
+ }
18828
+ );
18829
+ server.tool(
18830
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE,
18831
+ "Remove a knowledge base folder from the semantic search index and refresh the index. The path must match a project-local configured path exactly. Knowledge bases inherited from a user-global config are not removable by this tool.",
18832
+ {
18833
+ path: import_zod2.z.string().describe("Path of the knowledge base to remove (must match a project-local configured path exactly)")
18834
+ },
18835
+ async (args) => {
18836
+ const result = removeKnowledgeBase(runtime.projectRoot, runtime.host, args.path.trim());
18837
+ return knowledgeBaseResult(result);
18838
+ }
18839
+ );
18169
18840
  }
18170
18841
 
18171
18842
  // src/adapters/mcp/server.ts
@@ -18303,7 +18974,7 @@ var ReaddirpStream = class extends import_node_stream.Readable {
18303
18974
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
18304
18975
  const statMethod = opts.lstat ? import_promises.lstat : import_promises.stat;
18305
18976
  if (wantBigintFsStats) {
18306
- this._stat = (path31) => statMethod(path31, { bigint: true });
18977
+ this._stat = (path33) => statMethod(path33, { bigint: true });
18307
18978
  } else {
18308
18979
  this._stat = statMethod;
18309
18980
  }
@@ -18328,8 +18999,8 @@ var ReaddirpStream = class extends import_node_stream.Readable {
18328
18999
  const par = this.parent;
18329
19000
  const fil = par && par.files;
18330
19001
  if (fil && fil.length > 0) {
18331
- const { path: path31, depth } = par;
18332
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
19002
+ const { path: path33, depth } = par;
19003
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path33));
18333
19004
  const awaited = await Promise.all(slice);
18334
19005
  for (const entry of awaited) {
18335
19006
  if (!entry)
@@ -18369,20 +19040,20 @@ var ReaddirpStream = class extends import_node_stream.Readable {
18369
19040
  this.reading = false;
18370
19041
  }
18371
19042
  }
18372
- async _exploreDir(path31, depth) {
19043
+ async _exploreDir(path33, depth) {
18373
19044
  let files;
18374
19045
  try {
18375
- files = await (0, import_promises.readdir)(path31, this._rdOptions);
19046
+ files = await (0, import_promises.readdir)(path33, this._rdOptions);
18376
19047
  } catch (error) {
18377
19048
  this._onError(error);
18378
19049
  }
18379
- return { files, depth, path: path31 };
19050
+ return { files, depth, path: path33 };
18380
19051
  }
18381
- async _formatEntry(dirent, path31) {
19052
+ async _formatEntry(dirent, path33) {
18382
19053
  let entry;
18383
19054
  const basename8 = this._isDirent ? dirent.name : dirent;
18384
19055
  try {
18385
- const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path31, basename8));
19056
+ const fullPath = (0, import_node_path.resolve)((0, import_node_path.join)(path33, basename8));
18386
19057
  entry = { path: (0, import_node_path.relative)(this._root, fullPath), fullPath, basename: basename8 };
18387
19058
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
18388
19059
  } catch (err) {
@@ -18782,16 +19453,16 @@ var delFromSet = (main, prop, item) => {
18782
19453
  };
18783
19454
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
18784
19455
  var FsWatchInstances = /* @__PURE__ */ new Map();
18785
- function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
19456
+ function createFsWatchInstance(path33, options, listener, errHandler, emitRaw) {
18786
19457
  const handleEvent = (rawEvent, evPath) => {
18787
- listener(path31);
18788
- emitRaw(rawEvent, evPath, { watchedPath: path31 });
18789
- if (evPath && path31 !== evPath) {
18790
- fsWatchBroadcast(sp.resolve(path31, evPath), KEY_LISTENERS, sp.join(path31, evPath));
19458
+ listener(path33);
19459
+ emitRaw(rawEvent, evPath, { watchedPath: path33 });
19460
+ if (evPath && path33 !== evPath) {
19461
+ fsWatchBroadcast(sp.resolve(path33, evPath), KEY_LISTENERS, sp.join(path33, evPath));
18791
19462
  }
18792
19463
  };
18793
19464
  try {
18794
- return (0, import_node_fs.watch)(path31, {
19465
+ return (0, import_node_fs.watch)(path33, {
18795
19466
  persistent: options.persistent
18796
19467
  }, handleEvent);
18797
19468
  } catch (error) {
@@ -18807,12 +19478,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
18807
19478
  listener(val1, val2, val3);
18808
19479
  });
18809
19480
  };
18810
- var setFsWatchListener = (path31, fullPath, options, handlers) => {
19481
+ var setFsWatchListener = (path33, fullPath, options, handlers) => {
18811
19482
  const { listener, errHandler, rawEmitter } = handlers;
18812
19483
  let cont = FsWatchInstances.get(fullPath);
18813
19484
  let watcher;
18814
19485
  if (!options.persistent) {
18815
- watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
19486
+ watcher = createFsWatchInstance(path33, options, listener, errHandler, rawEmitter);
18816
19487
  if (!watcher)
18817
19488
  return;
18818
19489
  return watcher.close.bind(watcher);
@@ -18823,7 +19494,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18823
19494
  addAndConvert(cont, KEY_RAW, rawEmitter);
18824
19495
  } else {
18825
19496
  watcher = createFsWatchInstance(
18826
- path31,
19497
+ path33,
18827
19498
  options,
18828
19499
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
18829
19500
  errHandler,
@@ -18838,7 +19509,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18838
19509
  cont.watcherUnusable = true;
18839
19510
  if (isWindows && error.code === "EPERM") {
18840
19511
  try {
18841
- const fd = await (0, import_promises2.open)(path31, "r");
19512
+ const fd = await (0, import_promises2.open)(path33, "r");
18842
19513
  await fd.close();
18843
19514
  broadcastErr(error);
18844
19515
  } catch (err) {
@@ -18869,7 +19540,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18869
19540
  };
18870
19541
  };
18871
19542
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
18872
- var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
19543
+ var setFsWatchFileListener = (path33, fullPath, options, handlers) => {
18873
19544
  const { listener, rawEmitter } = handlers;
18874
19545
  let cont = FsWatchFileInstances.get(fullPath);
18875
19546
  const copts = cont && cont.options;
@@ -18891,7 +19562,7 @@ var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
18891
19562
  });
18892
19563
  const currmtime = curr.mtimeMs;
18893
19564
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
18894
- foreach(cont.listeners, (listener2) => listener2(path31, curr));
19565
+ foreach(cont.listeners, (listener2) => listener2(path33, curr));
18895
19566
  }
18896
19567
  })
18897
19568
  };
@@ -18921,13 +19592,13 @@ var NodeFsHandler = class {
18921
19592
  * @param listener on fs change
18922
19593
  * @returns closer for the watcher instance
18923
19594
  */
18924
- _watchWithNodeFs(path31, listener) {
19595
+ _watchWithNodeFs(path33, listener) {
18925
19596
  const opts = this.fsw.options;
18926
- const directory = sp.dirname(path31);
18927
- const basename8 = sp.basename(path31);
19597
+ const directory = sp.dirname(path33);
19598
+ const basename8 = sp.basename(path33);
18928
19599
  const parent = this.fsw._getWatchedDir(directory);
18929
19600
  parent.add(basename8);
18930
- const absolutePath = sp.resolve(path31);
19601
+ const absolutePath = sp.resolve(path33);
18931
19602
  const options = {
18932
19603
  persistent: opts.persistent
18933
19604
  };
@@ -18937,12 +19608,12 @@ var NodeFsHandler = class {
18937
19608
  if (opts.usePolling) {
18938
19609
  const enableBin = opts.interval !== opts.binaryInterval;
18939
19610
  options.interval = enableBin && isBinaryPath(basename8) ? opts.binaryInterval : opts.interval;
18940
- closer = setFsWatchFileListener(path31, absolutePath, options, {
19611
+ closer = setFsWatchFileListener(path33, absolutePath, options, {
18941
19612
  listener,
18942
19613
  rawEmitter: this.fsw._emitRaw
18943
19614
  });
18944
19615
  } else {
18945
- closer = setFsWatchListener(path31, absolutePath, options, {
19616
+ closer = setFsWatchListener(path33, absolutePath, options, {
18946
19617
  listener,
18947
19618
  errHandler: this._boundHandleError,
18948
19619
  rawEmitter: this.fsw._emitRaw
@@ -18964,7 +19635,7 @@ var NodeFsHandler = class {
18964
19635
  let prevStats = stats;
18965
19636
  if (parent.has(basename8))
18966
19637
  return;
18967
- const listener = async (path31, newStats) => {
19638
+ const listener = async (path33, newStats) => {
18968
19639
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
18969
19640
  return;
18970
19641
  if (!newStats || newStats.mtimeMs === 0) {
@@ -18978,11 +19649,11 @@ var NodeFsHandler = class {
18978
19649
  this.fsw._emit(EV.CHANGE, file, newStats2);
18979
19650
  }
18980
19651
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
18981
- this.fsw._closeFile(path31);
19652
+ this.fsw._closeFile(path33);
18982
19653
  prevStats = newStats2;
18983
19654
  const closer2 = this._watchWithNodeFs(file, listener);
18984
19655
  if (closer2)
18985
- this.fsw._addPathCloser(path31, closer2);
19656
+ this.fsw._addPathCloser(path33, closer2);
18986
19657
  } else {
18987
19658
  prevStats = newStats2;
18988
19659
  }
@@ -19014,7 +19685,7 @@ var NodeFsHandler = class {
19014
19685
  * @param item basename of this item
19015
19686
  * @returns true if no more processing is needed for this entry.
19016
19687
  */
19017
- async _handleSymlink(entry, directory, path31, item) {
19688
+ async _handleSymlink(entry, directory, path33, item) {
19018
19689
  if (this.fsw.closed) {
19019
19690
  return;
19020
19691
  }
@@ -19024,7 +19695,7 @@ var NodeFsHandler = class {
19024
19695
  this.fsw._incrReadyCount();
19025
19696
  let linkPath;
19026
19697
  try {
19027
- linkPath = await (0, import_promises2.realpath)(path31);
19698
+ linkPath = await (0, import_promises2.realpath)(path33);
19028
19699
  } catch (e) {
19029
19700
  this.fsw._emitReady();
19030
19701
  return true;
@@ -19034,12 +19705,12 @@ var NodeFsHandler = class {
19034
19705
  if (dir.has(item)) {
19035
19706
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
19036
19707
  this.fsw._symlinkPaths.set(full, linkPath);
19037
- this.fsw._emit(EV.CHANGE, path31, entry.stats);
19708
+ this.fsw._emit(EV.CHANGE, path33, entry.stats);
19038
19709
  }
19039
19710
  } else {
19040
19711
  dir.add(item);
19041
19712
  this.fsw._symlinkPaths.set(full, linkPath);
19042
- this.fsw._emit(EV.ADD, path31, entry.stats);
19713
+ this.fsw._emit(EV.ADD, path33, entry.stats);
19043
19714
  }
19044
19715
  this.fsw._emitReady();
19045
19716
  return true;
@@ -19069,9 +19740,9 @@ var NodeFsHandler = class {
19069
19740
  return;
19070
19741
  }
19071
19742
  const item = entry.path;
19072
- let path31 = sp.join(directory, item);
19743
+ let path33 = sp.join(directory, item);
19073
19744
  current.add(item);
19074
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
19745
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path33, item)) {
19075
19746
  return;
19076
19747
  }
19077
19748
  if (this.fsw.closed) {
@@ -19080,11 +19751,11 @@ var NodeFsHandler = class {
19080
19751
  }
19081
19752
  if (item === target || !target && !previous.has(item)) {
19082
19753
  this.fsw._incrReadyCount();
19083
- path31 = sp.join(dir, sp.relative(dir, path31));
19084
- this._addToNodeFs(path31, initialAdd, wh, depth + 1);
19754
+ path33 = sp.join(dir, sp.relative(dir, path33));
19755
+ this._addToNodeFs(path33, initialAdd, wh, depth + 1);
19085
19756
  }
19086
19757
  }).on(EV.ERROR, this._boundHandleError);
19087
- return new Promise((resolve18, reject) => {
19758
+ return new Promise((resolve20, reject) => {
19088
19759
  if (!stream)
19089
19760
  return reject();
19090
19761
  stream.once(STR_END, () => {
@@ -19093,7 +19764,7 @@ var NodeFsHandler = class {
19093
19764
  return;
19094
19765
  }
19095
19766
  const wasThrottled = throttler ? throttler.clear() : false;
19096
- resolve18(void 0);
19767
+ resolve20(void 0);
19097
19768
  previous.getChildren().filter((item) => {
19098
19769
  return item !== directory && !current.has(item);
19099
19770
  }).forEach((item) => {
@@ -19150,13 +19821,13 @@ var NodeFsHandler = class {
19150
19821
  * @param depth Child path actually targeted for watch
19151
19822
  * @param target Child path actually targeted for watch
19152
19823
  */
19153
- async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
19824
+ async _addToNodeFs(path33, initialAdd, priorWh, depth, target) {
19154
19825
  const ready = this.fsw._emitReady;
19155
- if (this.fsw._isIgnored(path31) || this.fsw.closed) {
19826
+ if (this.fsw._isIgnored(path33) || this.fsw.closed) {
19156
19827
  ready();
19157
19828
  return false;
19158
19829
  }
19159
- const wh = this.fsw._getWatchHelpers(path31);
19830
+ const wh = this.fsw._getWatchHelpers(path33);
19160
19831
  if (priorWh) {
19161
19832
  wh.filterPath = (entry) => priorWh.filterPath(entry);
19162
19833
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -19172,8 +19843,8 @@ var NodeFsHandler = class {
19172
19843
  const follow = this.fsw.options.followSymlinks;
19173
19844
  let closer;
19174
19845
  if (stats.isDirectory()) {
19175
- const absPath = sp.resolve(path31);
19176
- const targetPath = follow ? await (0, import_promises2.realpath)(path31) : path31;
19846
+ const absPath = sp.resolve(path33);
19847
+ const targetPath = follow ? await (0, import_promises2.realpath)(path33) : path33;
19177
19848
  if (this.fsw.closed)
19178
19849
  return;
19179
19850
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -19183,29 +19854,29 @@ var NodeFsHandler = class {
19183
19854
  this.fsw._symlinkPaths.set(absPath, targetPath);
19184
19855
  }
19185
19856
  } else if (stats.isSymbolicLink()) {
19186
- const targetPath = follow ? await (0, import_promises2.realpath)(path31) : path31;
19857
+ const targetPath = follow ? await (0, import_promises2.realpath)(path33) : path33;
19187
19858
  if (this.fsw.closed)
19188
19859
  return;
19189
19860
  const parent = sp.dirname(wh.watchPath);
19190
19861
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
19191
19862
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
19192
- closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
19863
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path33, wh, targetPath);
19193
19864
  if (this.fsw.closed)
19194
19865
  return;
19195
19866
  if (targetPath !== void 0) {
19196
- this.fsw._symlinkPaths.set(sp.resolve(path31), targetPath);
19867
+ this.fsw._symlinkPaths.set(sp.resolve(path33), targetPath);
19197
19868
  }
19198
19869
  } else {
19199
19870
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
19200
19871
  }
19201
19872
  ready();
19202
19873
  if (closer)
19203
- this.fsw._addPathCloser(path31, closer);
19874
+ this.fsw._addPathCloser(path33, closer);
19204
19875
  return false;
19205
19876
  } catch (error) {
19206
19877
  if (this.fsw._handleError(error)) {
19207
19878
  ready();
19208
- return path31;
19879
+ return path33;
19209
19880
  }
19210
19881
  }
19211
19882
  }
@@ -19237,35 +19908,35 @@ function createPattern(matcher) {
19237
19908
  if (matcher.path === string)
19238
19909
  return true;
19239
19910
  if (matcher.recursive) {
19240
- const relative12 = sp2.relative(matcher.path, string);
19241
- if (!relative12) {
19911
+ const relative14 = sp2.relative(matcher.path, string);
19912
+ if (!relative14) {
19242
19913
  return false;
19243
19914
  }
19244
- return !relative12.startsWith("..") && !sp2.isAbsolute(relative12);
19915
+ return !relative14.startsWith("..") && !sp2.isAbsolute(relative14);
19245
19916
  }
19246
19917
  return false;
19247
19918
  };
19248
19919
  }
19249
19920
  return () => false;
19250
19921
  }
19251
- function normalizePath3(path31) {
19252
- if (typeof path31 !== "string")
19922
+ function normalizePath3(path33) {
19923
+ if (typeof path33 !== "string")
19253
19924
  throw new Error("string expected");
19254
- path31 = sp2.normalize(path31);
19255
- path31 = path31.replace(/\\/g, "/");
19925
+ path33 = sp2.normalize(path33);
19926
+ path33 = path33.replace(/\\/g, "/");
19256
19927
  let prepend = false;
19257
- if (path31.startsWith("//"))
19928
+ if (path33.startsWith("//"))
19258
19929
  prepend = true;
19259
- path31 = path31.replace(DOUBLE_SLASH_RE, "/");
19930
+ path33 = path33.replace(DOUBLE_SLASH_RE, "/");
19260
19931
  if (prepend)
19261
- path31 = "/" + path31;
19262
- return path31;
19932
+ path33 = "/" + path33;
19933
+ return path33;
19263
19934
  }
19264
19935
  function matchPatterns(patterns, testString, stats) {
19265
- const path31 = normalizePath3(testString);
19936
+ const path33 = normalizePath3(testString);
19266
19937
  for (let index = 0; index < patterns.length; index++) {
19267
19938
  const pattern = patterns[index];
19268
- if (pattern(path31, stats)) {
19939
+ if (pattern(path33, stats)) {
19269
19940
  return true;
19270
19941
  }
19271
19942
  }
@@ -19303,19 +19974,19 @@ var toUnix = (string) => {
19303
19974
  }
19304
19975
  return str;
19305
19976
  };
19306
- var normalizePathToUnix = (path31) => toUnix(sp2.normalize(toUnix(path31)));
19307
- var normalizeIgnored = (cwd = "") => (path31) => {
19308
- if (typeof path31 === "string") {
19309
- return normalizePathToUnix(sp2.isAbsolute(path31) ? path31 : sp2.join(cwd, path31));
19977
+ var normalizePathToUnix = (path33) => toUnix(sp2.normalize(toUnix(path33)));
19978
+ var normalizeIgnored = (cwd = "") => (path33) => {
19979
+ if (typeof path33 === "string") {
19980
+ return normalizePathToUnix(sp2.isAbsolute(path33) ? path33 : sp2.join(cwd, path33));
19310
19981
  } else {
19311
- return path31;
19982
+ return path33;
19312
19983
  }
19313
19984
  };
19314
- var getAbsolutePath = (path31, cwd) => {
19315
- if (sp2.isAbsolute(path31)) {
19316
- return path31;
19985
+ var getAbsolutePath = (path33, cwd) => {
19986
+ if (sp2.isAbsolute(path33)) {
19987
+ return path33;
19317
19988
  }
19318
- return sp2.join(cwd, path31);
19989
+ return sp2.join(cwd, path33);
19319
19990
  };
19320
19991
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
19321
19992
  var DirEntry = class {
@@ -19380,10 +20051,10 @@ var WatchHelper = class {
19380
20051
  dirParts;
19381
20052
  followSymlinks;
19382
20053
  statMethod;
19383
- constructor(path31, follow, fsw) {
20054
+ constructor(path33, follow, fsw) {
19384
20055
  this.fsw = fsw;
19385
- const watchPath = path31;
19386
- this.path = path31 = path31.replace(REPLACER_RE, "");
20056
+ const watchPath = path33;
20057
+ this.path = path33 = path33.replace(REPLACER_RE, "");
19387
20058
  this.watchPath = watchPath;
19388
20059
  this.fullWatchPath = sp2.resolve(watchPath);
19389
20060
  this.dirParts = [];
@@ -19523,20 +20194,20 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19523
20194
  this._closePromise = void 0;
19524
20195
  let paths = unifyPaths(paths_);
19525
20196
  if (cwd) {
19526
- paths = paths.map((path31) => {
19527
- const absPath = getAbsolutePath(path31, cwd);
20197
+ paths = paths.map((path33) => {
20198
+ const absPath = getAbsolutePath(path33, cwd);
19528
20199
  return absPath;
19529
20200
  });
19530
20201
  }
19531
- paths.forEach((path31) => {
19532
- this._removeIgnoredPath(path31);
20202
+ paths.forEach((path33) => {
20203
+ this._removeIgnoredPath(path33);
19533
20204
  });
19534
20205
  this._userIgnored = void 0;
19535
20206
  if (!this._readyCount)
19536
20207
  this._readyCount = 0;
19537
20208
  this._readyCount += paths.length;
19538
- Promise.all(paths.map(async (path31) => {
19539
- const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
20209
+ Promise.all(paths.map(async (path33) => {
20210
+ const res = await this._nodeFsHandler._addToNodeFs(path33, !_internal, void 0, 0, _origAdd);
19540
20211
  if (res)
19541
20212
  this._emitReady();
19542
20213
  return res;
@@ -19558,17 +20229,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19558
20229
  return this;
19559
20230
  const paths = unifyPaths(paths_);
19560
20231
  const { cwd } = this.options;
19561
- paths.forEach((path31) => {
19562
- if (!sp2.isAbsolute(path31) && !this._closers.has(path31)) {
20232
+ paths.forEach((path33) => {
20233
+ if (!sp2.isAbsolute(path33) && !this._closers.has(path33)) {
19563
20234
  if (cwd)
19564
- path31 = sp2.join(cwd, path31);
19565
- path31 = sp2.resolve(path31);
20235
+ path33 = sp2.join(cwd, path33);
20236
+ path33 = sp2.resolve(path33);
19566
20237
  }
19567
- this._closePath(path31);
19568
- this._addIgnoredPath(path31);
19569
- if (this._watched.has(path31)) {
20238
+ this._closePath(path33);
20239
+ this._addIgnoredPath(path33);
20240
+ if (this._watched.has(path33)) {
19570
20241
  this._addIgnoredPath({
19571
- path: path31,
20242
+ path: path33,
19572
20243
  recursive: true
19573
20244
  });
19574
20245
  }
@@ -19632,38 +20303,38 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19632
20303
  * @param stats arguments to be passed with event
19633
20304
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
19634
20305
  */
19635
- async _emit(event, path31, stats) {
20306
+ async _emit(event, path33, stats) {
19636
20307
  if (this.closed)
19637
20308
  return;
19638
20309
  const opts = this.options;
19639
20310
  if (isWindows)
19640
- path31 = sp2.normalize(path31);
20311
+ path33 = sp2.normalize(path33);
19641
20312
  if (opts.cwd)
19642
- path31 = sp2.relative(opts.cwd, path31);
19643
- const args = [path31];
20313
+ path33 = sp2.relative(opts.cwd, path33);
20314
+ const args = [path33];
19644
20315
  if (stats != null)
19645
20316
  args.push(stats);
19646
20317
  const awf = opts.awaitWriteFinish;
19647
20318
  let pw;
19648
- if (awf && (pw = this._pendingWrites.get(path31))) {
20319
+ if (awf && (pw = this._pendingWrites.get(path33))) {
19649
20320
  pw.lastChange = /* @__PURE__ */ new Date();
19650
20321
  return this;
19651
20322
  }
19652
20323
  if (opts.atomic) {
19653
20324
  if (event === EVENTS.UNLINK) {
19654
- this._pendingUnlinks.set(path31, [event, ...args]);
20325
+ this._pendingUnlinks.set(path33, [event, ...args]);
19655
20326
  setTimeout(() => {
19656
- this._pendingUnlinks.forEach((entry, path32) => {
20327
+ this._pendingUnlinks.forEach((entry, path34) => {
19657
20328
  this.emit(...entry);
19658
20329
  this.emit(EVENTS.ALL, ...entry);
19659
- this._pendingUnlinks.delete(path32);
20330
+ this._pendingUnlinks.delete(path34);
19660
20331
  });
19661
20332
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
19662
20333
  return this;
19663
20334
  }
19664
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
20335
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path33)) {
19665
20336
  event = EVENTS.CHANGE;
19666
- this._pendingUnlinks.delete(path31);
20337
+ this._pendingUnlinks.delete(path33);
19667
20338
  }
19668
20339
  }
19669
20340
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -19681,16 +20352,16 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19681
20352
  this.emitWithAll(event, args);
19682
20353
  }
19683
20354
  };
19684
- this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
20355
+ this._awaitWriteFinish(path33, awf.stabilityThreshold, event, awfEmit);
19685
20356
  return this;
19686
20357
  }
19687
20358
  if (event === EVENTS.CHANGE) {
19688
- const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
20359
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path33, 50);
19689
20360
  if (isThrottled)
19690
20361
  return this;
19691
20362
  }
19692
20363
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
19693
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path31) : path31;
20364
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path33) : path33;
19694
20365
  let stats2;
19695
20366
  try {
19696
20367
  stats2 = await (0, import_promises3.stat)(fullPath);
@@ -19721,23 +20392,23 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19721
20392
  * @param timeout duration of time to suppress duplicate actions
19722
20393
  * @returns tracking object or false if action should be suppressed
19723
20394
  */
19724
- _throttle(actionType, path31, timeout) {
20395
+ _throttle(actionType, path33, timeout) {
19725
20396
  if (!this._throttled.has(actionType)) {
19726
20397
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
19727
20398
  }
19728
20399
  const action = this._throttled.get(actionType);
19729
20400
  if (!action)
19730
20401
  throw new Error("invalid throttle");
19731
- const actionPath = action.get(path31);
20402
+ const actionPath = action.get(path33);
19732
20403
  if (actionPath) {
19733
20404
  actionPath.count++;
19734
20405
  return false;
19735
20406
  }
19736
20407
  let timeoutObject;
19737
20408
  const clear = () => {
19738
- const item = action.get(path31);
20409
+ const item = action.get(path33);
19739
20410
  const count = item ? item.count : 0;
19740
- action.delete(path31);
20411
+ action.delete(path33);
19741
20412
  clearTimeout(timeoutObject);
19742
20413
  if (item)
19743
20414
  clearTimeout(item.timeoutObject);
@@ -19745,7 +20416,7 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19745
20416
  };
19746
20417
  timeoutObject = setTimeout(clear, timeout);
19747
20418
  const thr = { timeoutObject, clear, count: 0 };
19748
- action.set(path31, thr);
20419
+ action.set(path33, thr);
19749
20420
  return thr;
19750
20421
  }
19751
20422
  _incrReadyCount() {
@@ -19759,44 +20430,44 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19759
20430
  * @param event
19760
20431
  * @param awfEmit Callback to be called when ready for event to be emitted.
19761
20432
  */
19762
- _awaitWriteFinish(path31, threshold, event, awfEmit) {
20433
+ _awaitWriteFinish(path33, threshold, event, awfEmit) {
19763
20434
  const awf = this.options.awaitWriteFinish;
19764
20435
  if (typeof awf !== "object")
19765
20436
  return;
19766
20437
  const pollInterval = awf.pollInterval;
19767
20438
  let timeoutHandler;
19768
- let fullPath = path31;
19769
- if (this.options.cwd && !sp2.isAbsolute(path31)) {
19770
- fullPath = sp2.join(this.options.cwd, path31);
20439
+ let fullPath = path33;
20440
+ if (this.options.cwd && !sp2.isAbsolute(path33)) {
20441
+ fullPath = sp2.join(this.options.cwd, path33);
19771
20442
  }
19772
20443
  const now2 = /* @__PURE__ */ new Date();
19773
20444
  const writes = this._pendingWrites;
19774
20445
  function awaitWriteFinishFn(prevStat) {
19775
20446
  (0, import_node_fs2.stat)(fullPath, (err, curStat) => {
19776
- if (err || !writes.has(path31)) {
20447
+ if (err || !writes.has(path33)) {
19777
20448
  if (err && err.code !== "ENOENT")
19778
20449
  awfEmit(err);
19779
20450
  return;
19780
20451
  }
19781
20452
  const now3 = Number(/* @__PURE__ */ new Date());
19782
20453
  if (prevStat && curStat.size !== prevStat.size) {
19783
- writes.get(path31).lastChange = now3;
20454
+ writes.get(path33).lastChange = now3;
19784
20455
  }
19785
- const pw = writes.get(path31);
20456
+ const pw = writes.get(path33);
19786
20457
  const df = now3 - pw.lastChange;
19787
20458
  if (df >= threshold) {
19788
- writes.delete(path31);
20459
+ writes.delete(path33);
19789
20460
  awfEmit(void 0, curStat);
19790
20461
  } else {
19791
20462
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
19792
20463
  }
19793
20464
  });
19794
20465
  }
19795
- if (!writes.has(path31)) {
19796
- writes.set(path31, {
20466
+ if (!writes.has(path33)) {
20467
+ writes.set(path33, {
19797
20468
  lastChange: now2,
19798
20469
  cancelWait: () => {
19799
- writes.delete(path31);
20470
+ writes.delete(path33);
19800
20471
  clearTimeout(timeoutHandler);
19801
20472
  return event;
19802
20473
  }
@@ -19807,8 +20478,8 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19807
20478
  /**
19808
20479
  * Determines whether user has asked to ignore this path.
19809
20480
  */
19810
- _isIgnored(path31, stats) {
19811
- if (this.options.atomic && DOT_RE.test(path31))
20481
+ _isIgnored(path33, stats) {
20482
+ if (this.options.atomic && DOT_RE.test(path33))
19812
20483
  return true;
19813
20484
  if (!this._userIgnored) {
19814
20485
  const { cwd } = this.options;
@@ -19818,17 +20489,17 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19818
20489
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
19819
20490
  this._userIgnored = anymatch(list, void 0);
19820
20491
  }
19821
- return this._userIgnored(path31, stats);
20492
+ return this._userIgnored(path33, stats);
19822
20493
  }
19823
- _isntIgnored(path31, stat4) {
19824
- return !this._isIgnored(path31, stat4);
20494
+ _isntIgnored(path33, stat5) {
20495
+ return !this._isIgnored(path33, stat5);
19825
20496
  }
19826
20497
  /**
19827
20498
  * Provides a set of common helpers and properties relating to symlink handling.
19828
20499
  * @param path file or directory pattern being watched
19829
20500
  */
19830
- _getWatchHelpers(path31) {
19831
- return new WatchHelper(path31, this.options.followSymlinks, this);
20501
+ _getWatchHelpers(path33) {
20502
+ return new WatchHelper(path33, this.options.followSymlinks, this);
19832
20503
  }
19833
20504
  // Directory helpers
19834
20505
  // -----------------
@@ -19860,63 +20531,63 @@ var FSWatcher = class extends import_node_events.EventEmitter {
19860
20531
  * @param item base path of item/directory
19861
20532
  */
19862
20533
  _remove(directory, item, isDirectory) {
19863
- const path31 = sp2.join(directory, item);
19864
- const fullPath = sp2.resolve(path31);
19865
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
19866
- if (!this._throttle("remove", path31, 100))
20534
+ const path33 = sp2.join(directory, item);
20535
+ const fullPath = sp2.resolve(path33);
20536
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path33) || this._watched.has(fullPath);
20537
+ if (!this._throttle("remove", path33, 100))
19867
20538
  return;
19868
20539
  if (!isDirectory && this._watched.size === 1) {
19869
20540
  this.add(directory, item, true);
19870
20541
  }
19871
- const wp = this._getWatchedDir(path31);
20542
+ const wp = this._getWatchedDir(path33);
19872
20543
  const nestedDirectoryChildren = wp.getChildren();
19873
- nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
20544
+ nestedDirectoryChildren.forEach((nested) => this._remove(path33, nested));
19874
20545
  const parent = this._getWatchedDir(directory);
19875
20546
  const wasTracked = parent.has(item);
19876
20547
  parent.remove(item);
19877
20548
  if (this._symlinkPaths.has(fullPath)) {
19878
20549
  this._symlinkPaths.delete(fullPath);
19879
20550
  }
19880
- let relPath = path31;
20551
+ let relPath = path33;
19881
20552
  if (this.options.cwd)
19882
- relPath = sp2.relative(this.options.cwd, path31);
20553
+ relPath = sp2.relative(this.options.cwd, path33);
19883
20554
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
19884
20555
  const event = this._pendingWrites.get(relPath).cancelWait();
19885
20556
  if (event === EVENTS.ADD)
19886
20557
  return;
19887
20558
  }
19888
- this._watched.delete(path31);
20559
+ this._watched.delete(path33);
19889
20560
  this._watched.delete(fullPath);
19890
20561
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
19891
- if (wasTracked && !this._isIgnored(path31))
19892
- this._emit(eventName, path31);
19893
- this._closePath(path31);
20562
+ if (wasTracked && !this._isIgnored(path33))
20563
+ this._emit(eventName, path33);
20564
+ this._closePath(path33);
19894
20565
  }
19895
20566
  /**
19896
20567
  * Closes all watchers for a path
19897
20568
  */
19898
- _closePath(path31) {
19899
- this._closeFile(path31);
19900
- const dir = sp2.dirname(path31);
19901
- this._getWatchedDir(dir).remove(sp2.basename(path31));
20569
+ _closePath(path33) {
20570
+ this._closeFile(path33);
20571
+ const dir = sp2.dirname(path33);
20572
+ this._getWatchedDir(dir).remove(sp2.basename(path33));
19902
20573
  }
19903
20574
  /**
19904
20575
  * Closes only file-specific watchers
19905
20576
  */
19906
- _closeFile(path31) {
19907
- const closers = this._closers.get(path31);
20577
+ _closeFile(path33) {
20578
+ const closers = this._closers.get(path33);
19908
20579
  if (!closers)
19909
20580
  return;
19910
20581
  closers.forEach((closer) => closer());
19911
- this._closers.delete(path31);
20582
+ this._closers.delete(path33);
19912
20583
  }
19913
- _addPathCloser(path31, closer) {
20584
+ _addPathCloser(path33, closer) {
19914
20585
  if (!closer)
19915
20586
  return;
19916
- let list = this._closers.get(path31);
20587
+ let list = this._closers.get(path33);
19917
20588
  if (!list) {
19918
20589
  list = [];
19919
- this._closers.set(path31, list);
20590
+ this._closers.set(path33, list);
19920
20591
  }
19921
20592
  list.push(closer);
19922
20593
  }
@@ -19946,12 +20617,291 @@ function watch(paths, options = {}) {
19946
20617
  var chokidar_default = { watch, FSWatcher };
19947
20618
 
19948
20619
  // src/watcher/file-watcher.ts
20620
+ var path28 = __toESM(require("path"), 1);
20621
+
20622
+ // src/watcher/native-recursive-watcher.ts
20623
+ var import_node_fs3 = require("fs");
19949
20624
  var path26 = __toESM(require("path"), 1);
20625
+ var NativeRecursiveWatcher = class {
20626
+ constructor(root, onChange, options = {}) {
20627
+ this.root = root;
20628
+ this.onChange = onChange;
20629
+ this.watchFactory = options.watchFactory ?? this.defaultWatchFactory;
20630
+ this.onError = options.onError;
20631
+ }
20632
+ root;
20633
+ onChange;
20634
+ watcher = null;
20635
+ listenerToken = 0;
20636
+ watchFactory;
20637
+ onError;
20638
+ start() {
20639
+ if (this.watcher) return;
20640
+ const token = ++this.listenerToken;
20641
+ const listener = (_eventType, filename) => {
20642
+ if (this.watcher === null || this.listenerToken !== token) return;
20643
+ const absolutePath = this.toAbsolutePath(filename);
20644
+ const nextResult = this.onChange(absolutePath);
20645
+ if (nextResult instanceof Promise) {
20646
+ void nextResult.catch((error) => {
20647
+ console.error("[codebase-index] Error handling native watcher event:", error);
20648
+ });
20649
+ }
20650
+ };
20651
+ const watcher = this.watchFactory(this.root, listener, {
20652
+ persistent: true,
20653
+ recursive: true
20654
+ });
20655
+ watcher.on?.("error", (error) => {
20656
+ if (this.watcher === watcher && this.listenerToken === token) {
20657
+ this.onError?.(error);
20658
+ }
20659
+ });
20660
+ this.watcher = watcher;
20661
+ }
20662
+ async stop() {
20663
+ const watcher = this.watcher;
20664
+ this.watcher = null;
20665
+ this.listenerToken += 1;
20666
+ if (!watcher) return;
20667
+ await watcher.close();
20668
+ }
20669
+ toAbsolutePath(filename) {
20670
+ if (filename == null) return null;
20671
+ const normalizedFilename = typeof filename === "string" ? filename : filename.toString();
20672
+ const absolutePath = path26.resolve(this.root, normalizedFilename);
20673
+ const relativePath = path26.relative(this.root, absolutePath);
20674
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path26.sep}`) || path26.isAbsolute(relativePath);
20675
+ return outsideRoot ? null : absolutePath;
20676
+ }
20677
+ defaultWatchFactory = (root, listener, options) => (0, import_node_fs3.watch)(root, options, listener);
20678
+ };
20679
+
20680
+ // src/watcher/snapshot.ts
20681
+ var fsPromises4 = __toESM(require("fs/promises"), 1);
20682
+ var path27 = __toESM(require("path"), 1);
20683
+ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
20684
+ const normalizedProjectRoot = path27.resolve(projectRoot);
20685
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
20686
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
20687
+ const maxDepth = config.indexing?.maxDepth ?? -1;
20688
+ const snapshot = /* @__PURE__ */ new Map();
20689
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20690
+ const includeFile = async (filePath) => {
20691
+ const normalizedPath3 = path27.resolve(filePath);
20692
+ if (!shouldIncludeFile(normalizedPath3, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
20693
+ const stat5 = await readStatIfFile(normalizedPath3, unreadablePrefixes);
20694
+ if (stat5) snapshot.set(normalizedPath3, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20695
+ };
20696
+ const walk = async (directoryPath, depth) => {
20697
+ let entries;
20698
+ try {
20699
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
20700
+ } catch (error) {
20701
+ if (isMissingFsError(error)) return;
20702
+ if (isPermissionFsError(error)) {
20703
+ unreadablePrefixes.add(path27.resolve(directoryPath));
20704
+ return;
20705
+ }
20706
+ throw error;
20707
+ }
20708
+ for (const entry of entries) {
20709
+ const fullPath = path27.join(directoryPath, entry.name);
20710
+ const relativePath = path27.relative(normalizedProjectRoot, fullPath);
20711
+ if (entry.isDirectory()) {
20712
+ if (hasFilteredPathSegment(relativePath, path27.sep) || isRestrictedDirectory(relativePath, path27.sep)) continue;
20713
+ if (ignoreFilter.ignores(relativePath)) continue;
20714
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
20715
+ } else if (entry.isFile()) {
20716
+ await includeFile(fullPath);
20717
+ }
20718
+ }
20719
+ };
20720
+ await walk(normalizedProjectRoot, 0);
20721
+ await includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths);
20722
+ return { entries: snapshot, unreadablePrefixes };
20723
+ }
20724
+ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
20725
+ const normalizedProjectRoot = path27.resolve(projectRoot);
20726
+ const normalizedTargetPath = path27.resolve(targetPath);
20727
+ if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
20728
+ return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
20729
+ }
20730
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
20731
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
20732
+ const maxDepth = config.indexing?.maxDepth ?? -1;
20733
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path27.resolve(configPath)));
20734
+ const snapshot = /* @__PURE__ */ new Map();
20735
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20736
+ const includeFile = async (filePath) => {
20737
+ const normalizedPath3 = path27.resolve(filePath);
20738
+ if (!explicitConfigPaths.has(normalizedPath3) && !shouldIncludeFile(
20739
+ normalizedPath3,
20740
+ normalizedProjectRoot,
20741
+ includePatterns,
20742
+ config.exclude,
20743
+ ignoreFilter
20744
+ )) return;
20745
+ const stat5 = await readStatIfFile(normalizedPath3, unreadablePrefixes);
20746
+ if (stat5) snapshot.set(normalizedPath3, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20747
+ };
20748
+ const walk = async (directoryPath, depth) => {
20749
+ let entries;
20750
+ try {
20751
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
20752
+ } catch (error) {
20753
+ if (isMissingFsError(error)) return;
20754
+ if (isPermissionFsError(error)) {
20755
+ unreadablePrefixes.add(path27.resolve(directoryPath));
20756
+ return;
20757
+ }
20758
+ throw error;
20759
+ }
20760
+ for (const entry of entries) {
20761
+ const fullPath = path27.join(directoryPath, entry.name);
20762
+ const relativePath = path27.relative(normalizedProjectRoot, fullPath);
20763
+ if (entry.isDirectory()) {
20764
+ if (hasFilteredPathSegment(relativePath, path27.sep) || isRestrictedDirectory(relativePath, path27.sep)) continue;
20765
+ if (ignoreFilter.ignores(relativePath)) continue;
20766
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
20767
+ } else if (entry.isFile()) {
20768
+ await includeFile(fullPath);
20769
+ }
20770
+ }
20771
+ };
20772
+ const targetStat = await readStatIfFile(normalizedTargetPath, unreadablePrefixes);
20773
+ if (targetStat) await includeFile(normalizedTargetPath);
20774
+ else await walk(normalizedTargetPath, 0);
20775
+ await includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, normalizedTargetPath);
20776
+ return { entries: snapshot, unreadablePrefixes };
20777
+ }
20778
+ function completeFileSnapshot(previous, scan) {
20779
+ const completed = new Map(scan.entries);
20780
+ for (const unreadablePrefix of scan.unreadablePrefixes) {
20781
+ for (const [entryPath, entry] of previous) {
20782
+ if (isWithinPath(unreadablePrefix, entryPath) && !completed.has(entryPath)) completed.set(entryPath, entry);
20783
+ }
20784
+ }
20785
+ return completed;
20786
+ }
20787
+ async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
20788
+ for (const configPath of [...new Set(configPaths.map((value) => path27.resolve(value)))]) {
20789
+ if (snapshot.has(configPath)) continue;
20790
+ const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
20791
+ if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20792
+ }
20793
+ }
20794
+ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, targetPath) {
20795
+ await includeExplicitConfigPaths(
20796
+ snapshot,
20797
+ unreadablePrefixes,
20798
+ configPaths.filter((configPath) => isWithinPath(targetPath, path27.resolve(configPath)))
20799
+ );
20800
+ }
20801
+ function isWithinPath(parentPath, childPath) {
20802
+ const relativePath = path27.relative(parentPath, childPath);
20803
+ return relativePath === "" || !relativePath.startsWith(`..${path27.sep}`) && relativePath !== ".." && !path27.isAbsolute(relativePath);
20804
+ }
20805
+ async function readStatIfFile(filePath, unreadablePrefixes) {
20806
+ try {
20807
+ const stat5 = await fsPromises4.stat(filePath);
20808
+ return stat5.isFile() ? stat5 : null;
20809
+ } catch (error) {
20810
+ if (isMissingFsError(error)) return null;
20811
+ if (isPermissionFsError(error)) {
20812
+ unreadablePrefixes.add(path27.resolve(filePath));
20813
+ return null;
20814
+ }
20815
+ throw error;
20816
+ }
20817
+ }
20818
+ function isMissingFsError(error) {
20819
+ return error instanceof Error && ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
20820
+ }
20821
+ function isPermissionFsError(error) {
20822
+ return error instanceof Error && ["EACCES", "EPERM"].includes(error.code ?? "");
20823
+ }
20824
+ var diffTypeOrder = { add: 0, change: 1, unlink: 2 };
20825
+ function diffFileSnapshots(previous, current, forcedChanges = /* @__PURE__ */ new Set()) {
20826
+ const changes = [];
20827
+ for (const [filePath, previousEntry] of previous) {
20828
+ const currentEntry = current.get(filePath);
20829
+ if (!currentEntry) changes.push({ type: "unlink", path: filePath });
20830
+ else if (forcedChanges.has(filePath) || currentEntry.size !== previousEntry.size || currentEntry.mtimeMs !== previousEntry.mtimeMs) {
20831
+ changes.push({ type: "change", path: filePath });
20832
+ }
20833
+ }
20834
+ for (const [filePath] of current) {
20835
+ if (!previous.has(filePath)) changes.push({ type: "add", path: filePath });
20836
+ }
20837
+ return changes.sort((left, right) => left.path.localeCompare(right.path) || diffTypeOrder[left.type] - diffTypeOrder[right.type]);
20838
+ }
20839
+
20840
+ // src/watcher/snapshot-reconciler.ts
20841
+ var FileSnapshotReconciler = class {
20842
+ constructor(projectRoot, config, configPaths) {
20843
+ this.projectRoot = projectRoot;
20844
+ this.config = config;
20845
+ this.configPaths = configPaths;
20846
+ }
20847
+ projectRoot;
20848
+ config;
20849
+ configPaths;
20850
+ snapshot = null;
20851
+ reconciliationTail = Promise.resolve();
20852
+ async initialize() {
20853
+ this.snapshot = (await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths)).entries;
20854
+ }
20855
+ async reconcile(invalidations = []) {
20856
+ if (this.snapshot === null) {
20857
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
20858
+ }
20859
+ const reconciliation = this.reconciliationTail.then(async () => {
20860
+ const previousSnapshot = this.snapshot;
20861
+ if (previousSnapshot === null) {
20862
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
20863
+ }
20864
+ const normalizedInvalidations = invalidations.map((invalidation) => typeof invalidation === "string" || invalidation === null ? { path: invalidation, forceChange: false } : { path: invalidation.path, forceChange: invalidation.forceChange === true });
20865
+ const scopedPaths = normalizedInvalidations.map((invalidation) => invalidation.path).filter((filePath) => filePath !== null);
20866
+ const scan = scopedPaths.length === 0 || scopedPaths.length !== normalizedInvalidations.length ? await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths) : await this.reconcilePaths(previousSnapshot, scopedPaths);
20867
+ const nextSnapshot = completeFileSnapshot(previousSnapshot, scan);
20868
+ const forcedChanges = new Set(normalizedInvalidations.filter((invalidation) => invalidation.path !== null && invalidation.forceChange).map((invalidation) => invalidation.path));
20869
+ const changes = diffFileSnapshots(previousSnapshot, nextSnapshot, forcedChanges);
20870
+ this.snapshot = nextSnapshot;
20871
+ return changes;
20872
+ });
20873
+ this.reconciliationTail = reconciliation.then(() => void 0, () => void 0);
20874
+ return reconciliation;
20875
+ }
20876
+ async reconcilePaths(previousSnapshot, invalidatedPaths) {
20877
+ const scopes = this.getScopes(invalidatedPaths);
20878
+ const entries = new Map(previousSnapshot);
20879
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20880
+ for (const scope of scopes) {
20881
+ for (const previousPath of entries.keys()) {
20882
+ if (isWithinPath(scope, previousPath)) entries.delete(previousPath);
20883
+ }
20884
+ const scopedScan = await buildFileSnapshotForPathScan(this.projectRoot, this.config, this.configPaths, scope);
20885
+ for (const [filePath, entry] of scopedScan.entries) entries.set(filePath, entry);
20886
+ for (const unreadablePrefix of scopedScan.unreadablePrefixes) unreadablePrefixes.add(unreadablePrefix);
20887
+ }
20888
+ return { entries, unreadablePrefixes };
20889
+ }
20890
+ getScopes(invalidatedPaths) {
20891
+ const uniquePaths = [...new Set(invalidatedPaths)].sort((left, right) => left.length - right.length);
20892
+ return uniquePaths.filter((candidate, index) => !uniquePaths.slice(0, index).some(
20893
+ (ancestor) => isWithinPath(ancestor, candidate)
20894
+ ));
20895
+ }
20896
+ };
20897
+
20898
+ // src/watcher/file-watcher.ts
19950
20899
  var FileWatcher = class {
19951
20900
  watcher = null;
19952
20901
  projectRoot;
19953
20902
  config;
19954
20903
  configPath;
20904
+ backend;
19955
20905
  projectConfigPaths;
19956
20906
  pendingChanges = /* @__PURE__ */ new Map();
19957
20907
  debounceTimer = null;
@@ -19961,44 +20911,74 @@ var FileWatcher = class {
19961
20911
  resolveReady = null;
19962
20912
  pollingFallbackAttempted = false;
19963
20913
  pendingClose = null;
20914
+ startupReadySignals = 1;
20915
+ nativeWatcher = null;
20916
+ nativeReconciler = null;
20917
+ nativeSetupGeneration = 0;
20918
+ nativeStarting = false;
20919
+ nativeInitializing = false;
20920
+ nativeReconcileTimer = null;
20921
+ nativeInvalidatedPaths = /* @__PURE__ */ new Map();
20922
+ configPathStates = /* @__PURE__ */ new Map();
19964
20923
  constructor(projectRoot, config, host, options = {}) {
19965
20924
  this.projectRoot = projectRoot;
19966
20925
  this.config = config;
20926
+ this.backend = options.backend ?? "auto";
19967
20927
  this.configPath = options.configPath;
19968
20928
  this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
19969
20929
  }
19970
20930
  start(handler) {
19971
- if (this.watcher) {
20931
+ if (this.watcher || this.nativeWatcher || this.nativeStarting) {
19972
20932
  return;
19973
20933
  }
19974
20934
  this.onChanges = handler;
19975
20935
  this.pollingFallbackAttempted = false;
19976
20936
  this.resetReady();
20937
+ if (this.shouldUseNativeWatcher()) {
20938
+ if (this.hasExternalConfigWatchTarget()) {
20939
+ this.setStartupReadySignals(2);
20940
+ this.startExternalConfigWatcher();
20941
+ }
20942
+ this.nativeStarting = true;
20943
+ void this.createNativeWatcher();
20944
+ return;
20945
+ }
19977
20946
  this.createWatcher();
19978
20947
  }
19979
20948
  resetReady() {
19980
- this.readyPromise = new Promise((resolve18) => {
19981
- this.resolveReady = resolve18;
20949
+ this.readyPromise = new Promise((resolve20) => {
20950
+ this.resolveReady = resolve20;
19982
20951
  });
20952
+ this.startupReadySignals = 1;
19983
20953
  }
19984
- createWatcher(usePolling = false) {
19985
- const ignoreFilter = createIgnoreFilter(this.projectRoot);
19986
- let watchTargets = this.projectRoot;
19987
- if (this.configPath) {
19988
- watchTargets = [this.projectRoot, this.configPath];
19989
- } else {
19990
- const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
19991
- const relativeConfigPath = path26.relative(this.projectRoot, projectConfigPath);
19992
- return this.isOutsideProjectPath(relativeConfigPath);
19993
- }).map((projectConfigPath) => (0, import_fs19.existsSync)(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path26.dirname(projectConfigPath)));
19994
- const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
19995
- if (uniqueExternalConfigTargets.length > 0) {
19996
- watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
19997
- }
20954
+ setStartupReadySignals(expectedSignals) {
20955
+ if (!this.readyPromise) {
20956
+ return;
19998
20957
  }
20958
+ this.startupReadySignals = Math.max(0, expectedSignals);
20959
+ }
20960
+ reportStartupReadySignal() {
20961
+ if (!this.readyPromise || !this.resolveReady) {
20962
+ return;
20963
+ }
20964
+ if (this.startupReadySignals <= 0) {
20965
+ return;
20966
+ }
20967
+ this.startupReadySignals -= 1;
20968
+ if (this.startupReadySignals !== 0) {
20969
+ return;
20970
+ }
20971
+ this.resolveReady();
20972
+ this.resolveReady = null;
20973
+ }
20974
+ createWatcher(watchTargets, usePolling = false, reportsStartupReady = true) {
20975
+ let reportedStartupReady = false;
20976
+ this.configPathStates = this.getConfigPathStates();
20977
+ const ignoreFilter = createIgnoreFilter(this.projectRoot);
20978
+ const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
19999
20979
  const watcherOptions = {
20000
20980
  ignored: (filePath) => {
20001
- const relativePath = path26.relative(this.projectRoot, filePath);
20981
+ const relativePath = path28.relative(this.projectRoot, filePath);
20002
20982
  if (!relativePath) return false;
20003
20983
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
20004
20984
  return false;
@@ -20006,10 +20986,10 @@ var FileWatcher = class {
20006
20986
  if (this.isOutsideProjectPath(relativePath)) {
20007
20987
  return true;
20008
20988
  }
20009
- if (hasFilteredPathSegment(relativePath, path26.sep)) {
20989
+ if (hasFilteredPathSegment(relativePath, path28.sep)) {
20010
20990
  return true;
20011
20991
  }
20012
- if (isRestrictedDirectory(relativePath, path26.sep)) {
20992
+ if (isRestrictedDirectory(relativePath, path28.sep)) {
20013
20993
  return true;
20014
20994
  }
20015
20995
  if (ignoreFilter.ignores(relativePath)) {
@@ -20042,10 +21022,13 @@ var FileWatcher = class {
20042
21022
  watcher = new FSWatcher(watcherOptions);
20043
21023
  }
20044
21024
  this.watcher = watcher;
20045
- watcher.once("ready", () => {
21025
+ watcher.on("ready", () => {
20046
21026
  if (this.watcher !== watcher) return;
20047
- this.resolveReady?.();
20048
- this.resolveReady = null;
21027
+ this.reconcileConfigPathStates();
21028
+ if (reportsStartupReady) {
21029
+ this.reportStartupReadySignal();
21030
+ reportedStartupReady = true;
21031
+ }
20049
21032
  });
20050
21033
  watcher.on("error", (error) => {
20051
21034
  const err = error instanceof Error ? error : null;
@@ -20059,10 +21042,13 @@ var FileWatcher = class {
20059
21042
  console.error("[codebase-index] Failed to close exhausted file watcher:", closeError);
20060
21043
  });
20061
21044
  if (this.onChanges) {
21045
+ const replacementReportsStartupReady = reportsStartupReady || reportedStartupReady;
20062
21046
  if (!this.resolveReady) {
20063
21047
  this.resetReady();
21048
+ } else if (reportedStartupReady) {
21049
+ this.startupReadySignals += 1;
20064
21050
  }
20065
- this.createWatcher(true);
21051
+ this.createWatcher(resolvedWatchTargets, true, replacementReportsStartupReady);
20066
21052
  } else {
20067
21053
  this.watcher = null;
20068
21054
  }
@@ -20073,13 +21059,166 @@ var FileWatcher = class {
20073
21059
  watcher.on("add", (filePath) => this.handleChange(watcher, "add", filePath));
20074
21060
  watcher.on("change", (filePath) => this.handleChange(watcher, "change", filePath));
20075
21061
  watcher.on("unlink", (filePath) => this.handleChange(watcher, "unlink", filePath));
20076
- watcher.add(watchTargets);
21062
+ watcher.add(resolvedWatchTargets);
21063
+ }
21064
+ shouldUseNativeWatcher() {
21065
+ if (this.backend === "chokidar") {
21066
+ return false;
21067
+ }
21068
+ return true;
21069
+ }
21070
+ getFullChokidarWatchTargets() {
21071
+ if (this.configPath) {
21072
+ return [this.projectRoot, this.configPath];
21073
+ }
21074
+ const externalConfigTargets = this.getExternalConfigWatchTargets();
21075
+ if (externalConfigTargets.length === 0) {
21076
+ return this.projectRoot;
21077
+ }
21078
+ return [this.projectRoot, ...externalConfigTargets];
21079
+ }
21080
+ getExternalConfigWatchTargets() {
21081
+ return [...new Set(
21082
+ this.projectConfigPaths.filter((projectConfigPath) => {
21083
+ const relativeConfigPath = path28.relative(this.projectRoot, projectConfigPath);
21084
+ return this.isOutsideProjectPath(relativeConfigPath);
21085
+ }).map((projectConfigPath) => {
21086
+ if ((0, import_fs19.existsSync)(projectConfigPath)) {
21087
+ return projectConfigPath;
21088
+ }
21089
+ return this.getNearestExistingDirectory(path28.dirname(projectConfigPath));
21090
+ })
21091
+ )];
21092
+ }
21093
+ hasExternalConfigWatchTarget() {
21094
+ return this.getExternalConfigWatchTargets().length > 0;
21095
+ }
21096
+ startExternalConfigWatcher(usePolling = false) {
21097
+ const externalTargets = this.getExternalConfigWatchTargets();
21098
+ if (externalTargets.length === 0) {
21099
+ return;
21100
+ }
21101
+ this.createWatcher(externalTargets, usePolling);
21102
+ }
21103
+ async createNativeWatcher() {
21104
+ const generation = ++this.nativeSetupGeneration;
21105
+ const reconciler = new FileSnapshotReconciler(this.projectRoot, this.config, this.projectConfigPaths);
21106
+ const watcher = new NativeRecursiveWatcher(
21107
+ this.projectRoot,
21108
+ (filePath) => this.scheduleNativeReconciliation(generation, filePath),
21109
+ { onError: (error) => void this.fallbackFromNativeWatcher(generation, error) }
21110
+ );
21111
+ this.nativeReconciler = reconciler;
21112
+ this.nativeWatcher = watcher;
21113
+ this.nativeInitializing = true;
21114
+ try {
21115
+ watcher.start();
21116
+ if (!this.isCurrentNativeSetup(generation)) {
21117
+ await watcher.stop();
21118
+ return;
21119
+ }
21120
+ await reconciler.initialize();
21121
+ if (!this.isCurrentNativeSetup(generation) || this.nativeWatcher !== watcher) {
21122
+ await watcher.stop();
21123
+ return;
21124
+ }
21125
+ this.nativeStarting = false;
21126
+ this.nativeInitializing = false;
21127
+ await this.reconcileNativeWatcherWithPendingInvalidations(generation);
21128
+ this.reportStartupReadySignal();
21129
+ } catch (error) {
21130
+ if (!this.isCurrentNativeSetup(generation)) return;
21131
+ this.nativeInitializing = false;
21132
+ if (this.nativeWatcher) {
21133
+ await this.fallbackFromNativeWatcher(generation, error);
21134
+ return;
21135
+ }
21136
+ this.nativeStarting = false;
21137
+ const externalWatcher = this.watcher;
21138
+ this.watcher = null;
21139
+ this.nativeReconciler = null;
21140
+ await externalWatcher?.close();
21141
+ console.warn("[codebase-index] Native recursive watcher unavailable; using Chokidar fallback.", error);
21142
+ this.setStartupReadySignals(1);
21143
+ this.createWatcher();
21144
+ }
21145
+ }
21146
+ isCurrentNativeSetup(generation) {
21147
+ return this.nativeSetupGeneration === generation && this.onChanges !== null;
21148
+ }
21149
+ scheduleNativeReconciliation(generation, filePath) {
21150
+ if (!this.isCurrentNativeSetup(generation)) return;
21151
+ const requiresFullReconciliation = filePath === path28.join(this.projectRoot, ".gitignore");
21152
+ const invalidatedPath = requiresFullReconciliation ? null : filePath;
21153
+ this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
21154
+ if (this.nativeReconcileTimer) {
21155
+ clearTimeout(this.nativeReconcileTimer);
21156
+ }
21157
+ this.nativeReconcileTimer = setTimeout(() => {
21158
+ this.nativeReconcileTimer = null;
21159
+ void this.reconcileNativeWatcherFromQueue(generation);
21160
+ }, 100);
21161
+ }
21162
+ reconcileNativeWatcherFromQueue(generation) {
21163
+ if (!this.isCurrentNativeSetup(generation) || this.nativeInitializing) return;
21164
+ const invalidatedPaths = this.popNativeInvalidations();
21165
+ if (invalidatedPaths.length === 0) return;
21166
+ void this.reconcileNativeWatcher(generation, invalidatedPaths);
21167
+ }
21168
+ async reconcileNativeWatcher(generation, invalidatedPaths) {
21169
+ if (!this.isCurrentNativeSetup(generation) || !this.nativeReconciler) return;
21170
+ try {
21171
+ const reconciler = this.nativeReconciler;
21172
+ const changes = await reconciler.reconcile(invalidatedPaths);
21173
+ if (!this.isCurrentNativeSetup(generation) || this.nativeReconciler !== reconciler) return;
21174
+ this.recordChanges(changes);
21175
+ } catch (error) {
21176
+ await this.fallbackFromNativeWatcher(generation, error);
21177
+ }
21178
+ }
21179
+ async reconcileNativeWatcherWithPendingInvalidations(generation) {
21180
+ const invalidatedPaths = this.popNativeInvalidations();
21181
+ if (invalidatedPaths.length === 0) return;
21182
+ await this.reconcileNativeWatcher(generation, invalidatedPaths);
21183
+ }
21184
+ popNativeInvalidations() {
21185
+ if (this.nativeInvalidatedPaths.size === 0) return [];
21186
+ const invalidations = [...this.nativeInvalidatedPaths].map(([invalidatedPath, forceChange]) => ({
21187
+ path: invalidatedPath,
21188
+ forceChange
21189
+ }));
21190
+ this.nativeInvalidatedPaths.clear();
21191
+ return invalidations;
21192
+ }
21193
+ async fallbackFromNativeWatcher(generation, error) {
21194
+ if (!this.isCurrentNativeSetup(generation)) return;
21195
+ const watcher = this.nativeWatcher;
21196
+ const externalWatcher = this.watcher;
21197
+ this.nativeWatcher = null;
21198
+ this.watcher = null;
21199
+ this.nativeReconciler = null;
21200
+ this.nativeStarting = false;
21201
+ this.nativeInitializing = false;
21202
+ this.nativeSetupGeneration += 1;
21203
+ if (this.nativeReconcileTimer) {
21204
+ clearTimeout(this.nativeReconcileTimer);
21205
+ this.nativeReconcileTimer = null;
21206
+ }
21207
+ this.nativeInvalidatedPaths.clear();
21208
+ this.setStartupReadySignals(1);
21209
+ console.warn("[codebase-index] Native recursive watcher failed; using Chokidar fallback.", error);
21210
+ await watcher?.stop();
21211
+ await externalWatcher?.close();
21212
+ if (this.onChanges) {
21213
+ this.createWatcher();
21214
+ }
20077
21215
  }
20078
21216
  handleChange(watcher, type, filePath) {
20079
21217
  if (this.watcher !== watcher) {
20080
21218
  return;
20081
21219
  }
20082
21220
  if (this.isProjectConfigPath(filePath)) {
21221
+ this.updateConfigPathState(filePath);
20083
21222
  this.pendingChanges.set(filePath, type);
20084
21223
  this.scheduleFlush();
20085
21224
  return;
@@ -20094,27 +21233,33 @@ var FileWatcher = class {
20094
21233
  )) {
20095
21234
  return;
20096
21235
  }
20097
- this.pendingChanges.set(filePath, type);
21236
+ this.recordChanges([{ path: filePath, type }]);
21237
+ }
21238
+ recordChanges(changes) {
21239
+ if (changes.length === 0) return;
21240
+ for (const change of changes) {
21241
+ this.pendingChanges.set(change.path, change.type);
21242
+ }
20098
21243
  this.scheduleFlush();
20099
21244
  }
20100
21245
  isProjectConfigPath(filePath) {
20101
- const relativePath = path26.relative(this.projectRoot, filePath);
20102
- const normalizedRelativePath = path26.normalize(relativePath);
21246
+ const relativePath = path28.relative(this.projectRoot, filePath);
21247
+ const normalizedRelativePath = path28.normalize(relativePath);
20103
21248
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
20104
21249
  }
20105
21250
  isProjectConfigPathOrAncestor(relativePath) {
20106
- const normalizedRelativePath = path26.normalize(relativePath);
21251
+ const normalizedRelativePath = path28.normalize(relativePath);
20107
21252
  return this.getProjectConfigRelativePaths().some(
20108
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path26.sep}`)
21253
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path28.sep}`)
20109
21254
  );
20110
21255
  }
20111
21256
  isOutsideProjectPath(relativePath) {
20112
- return relativePath === ".." || relativePath.startsWith(`..${path26.sep}`) || path26.isAbsolute(relativePath);
21257
+ return relativePath === ".." || relativePath.startsWith(`..${path28.sep}`) || path28.isAbsolute(relativePath);
20113
21258
  }
20114
21259
  getNearestExistingDirectory(directoryPath) {
20115
21260
  let candidate = directoryPath;
20116
21261
  while (!(0, import_fs19.existsSync)(candidate)) {
20117
- const parent = path26.dirname(candidate);
21262
+ const parent = path28.dirname(candidate);
20118
21263
  if (parent === candidate) break;
20119
21264
  candidate = parent;
20120
21265
  }
@@ -20122,9 +21267,51 @@ var FileWatcher = class {
20122
21267
  }
20123
21268
  getProjectConfigRelativePaths() {
20124
21269
  return this.projectConfigPaths.map(
20125
- (configPath) => path26.normalize(path26.relative(this.projectRoot, configPath))
21270
+ (configPath) => path28.normalize(path28.relative(this.projectRoot, configPath))
20126
21271
  );
20127
21272
  }
21273
+ getConfigPathStates() {
21274
+ const states = /* @__PURE__ */ new Map();
21275
+ for (const configPath of this.projectConfigPaths) {
21276
+ const state = this.getConfigPathState(configPath);
21277
+ if (state) states.set(configPath, state);
21278
+ }
21279
+ return states;
21280
+ }
21281
+ getConfigPathState(configPath) {
21282
+ try {
21283
+ const stats = (0, import_fs19.statSync)(configPath);
21284
+ return stats.isFile() ? { mtimeMs: stats.mtimeMs, size: stats.size } : void 0;
21285
+ } catch (error) {
21286
+ void error;
21287
+ return void 0;
21288
+ }
21289
+ }
21290
+ updateConfigPathState(configPath) {
21291
+ const state = this.getConfigPathState(configPath);
21292
+ if (state) {
21293
+ this.configPathStates.set(configPath, state);
21294
+ } else {
21295
+ this.configPathStates.delete(configPath);
21296
+ }
21297
+ }
21298
+ reconcileConfigPathStates() {
21299
+ const nextStates = this.getConfigPathStates();
21300
+ const changes = [];
21301
+ for (const configPath of this.projectConfigPaths) {
21302
+ const previous = this.configPathStates.get(configPath);
21303
+ const next = nextStates.get(configPath);
21304
+ if (!previous && next) {
21305
+ changes.push({ path: configPath, type: "add" });
21306
+ } else if (previous && !next) {
21307
+ changes.push({ path: configPath, type: "unlink" });
21308
+ } else if (previous && next && (previous.size !== next.size || previous.mtimeMs !== next.mtimeMs)) {
21309
+ changes.push({ path: configPath, type: "change" });
21310
+ }
21311
+ }
21312
+ this.configPathStates = nextStates;
21313
+ this.recordChanges(changes);
21314
+ }
20128
21315
  scheduleFlush() {
20129
21316
  if (this.debounceTimer) {
20130
21317
  clearTimeout(this.debounceTimer);
@@ -20138,7 +21325,7 @@ var FileWatcher = class {
20138
21325
  return;
20139
21326
  }
20140
21327
  const changes = Array.from(this.pendingChanges.entries()).map(
20141
- ([path31, type]) => ({ path: path31, type })
21328
+ ([path33, type]) => ({ path: path33, type })
20142
21329
  );
20143
21330
  this.pendingChanges.clear();
20144
21331
  try {
@@ -20152,20 +21339,31 @@ var FileWatcher = class {
20152
21339
  clearTimeout(this.debounceTimer);
20153
21340
  this.debounceTimer = null;
20154
21341
  }
21342
+ if (this.nativeReconcileTimer) {
21343
+ clearTimeout(this.nativeReconcileTimer);
21344
+ this.nativeReconcileTimer = null;
21345
+ }
21346
+ this.nativeInvalidatedPaths.clear();
20155
21347
  const watcher = this.watcher;
21348
+ const nativeWatcher = this.nativeWatcher;
20156
21349
  const pendingClose = this.pendingClose;
20157
21350
  const resolveReady = this.resolveReady;
20158
21351
  this.watcher = null;
21352
+ this.nativeWatcher = null;
21353
+ this.nativeReconciler = null;
21354
+ this.nativeStarting = false;
21355
+ this.nativeInitializing = false;
21356
+ this.nativeSetupGeneration += 1;
20159
21357
  this.pendingClose = null;
20160
21358
  this.resolveReady = null;
20161
21359
  this.readyPromise = null;
20162
21360
  this.pendingChanges.clear();
20163
21361
  this.onChanges = null;
20164
- await Promise.all([watcher?.close(), pendingClose]);
21362
+ await Promise.all([watcher?.close(), nativeWatcher?.stop(), pendingClose]);
20165
21363
  resolveReady?.();
20166
21364
  }
20167
21365
  isRunning() {
20168
- return this.watcher !== null;
21366
+ return this.watcher !== null || this.nativeWatcher !== null || this.nativeStarting;
20169
21367
  }
20170
21368
  async waitUntilReady() {
20171
21369
  await (this.readyPromise ?? Promise.resolve());
@@ -20173,7 +21371,7 @@ var FileWatcher = class {
20173
21371
  };
20174
21372
 
20175
21373
  // src/watcher/git-head-watcher.ts
20176
- var path27 = __toESM(require("path"), 1);
21374
+ var path29 = __toESM(require("path"), 1);
20177
21375
  var GitHeadWatcher = class {
20178
21376
  watcher = null;
20179
21377
  projectRoot;
@@ -20195,13 +21393,13 @@ var GitHeadWatcher = class {
20195
21393
  this.readyPromise = Promise.resolve();
20196
21394
  return;
20197
21395
  }
20198
- this.readyPromise = new Promise((resolve18) => {
20199
- this.resolveReady = resolve18;
21396
+ this.readyPromise = new Promise((resolve20) => {
21397
+ this.resolveReady = resolve20;
20200
21398
  });
20201
21399
  this.onBranchChange = handler;
20202
21400
  this.currentBranch = getCurrentBranch(this.projectRoot);
20203
21401
  const headPath = getHeadPath(this.projectRoot);
20204
- const refsPath = path27.join(this.projectRoot, ".git", "refs", "heads");
21402
+ const refsPath = path29.join(this.projectRoot, ".git", "refs", "heads");
20205
21403
  this.watcher = chokidar_default.watch([headPath, refsPath], {
20206
21404
  persistent: true,
20207
21405
  ignoreInitial: true,
@@ -20337,7 +21535,7 @@ function getConfigPaths(projectRoot, host, options) {
20337
21535
 
20338
21536
  // src/tools/visualize/activity.ts
20339
21537
  var import_child_process5 = require("child_process");
20340
- var path28 = __toESM(require("path"), 1);
21538
+ var path30 = __toESM(require("path"), 1);
20341
21539
  function attachRecentActivity(data, projectRoot) {
20342
21540
  const activity = readGitActivity(projectRoot);
20343
21541
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -20499,7 +21697,7 @@ function normalizePath4(filePath) {
20499
21697
  return filePath.replace(/\\/g, "/");
20500
21698
  }
20501
21699
  function toGitRelativePath(projectRoot, filePath) {
20502
- const relativePath = path28.isAbsolute(filePath) ? path28.relative(projectRoot, filePath) : filePath;
21700
+ const relativePath = path30.isAbsolute(filePath) ? path30.relative(projectRoot, filePath) : filePath;
20503
21701
  return normalizePath4(relativePath);
20504
21702
  }
20505
21703
 
@@ -20757,7 +21955,7 @@ render();
20757
21955
  }
20758
21956
 
20759
21957
  // src/tools/visualize/transform.ts
20760
- var path29 = __toESM(require("path"), 1);
21958
+ var path31 = __toESM(require("path"), 1);
20761
21959
 
20762
21960
  // src/tools/visualize/modules.ts
20763
21961
  var MAX_MODULES = 18;
@@ -20890,8 +22088,8 @@ function compactModules(prefixToNodes) {
20890
22088
  function deriveModules(nodes) {
20891
22089
  const initial = /* @__PURE__ */ new Map();
20892
22090
  for (const node of nodes) {
20893
- const relative12 = stripToProjectRelative(node.filePath);
20894
- const prefix = modulePrefixFromRelativePath(relative12);
22091
+ const relative14 = stripToProjectRelative(node.filePath);
22092
+ const prefix = modulePrefixFromRelativePath(relative14);
20895
22093
  if (!initial.has(prefix)) initial.set(prefix, []);
20896
22094
  initial.get(prefix)?.push(node);
20897
22095
  }
@@ -21017,7 +22215,7 @@ function transformForVisualization(symbols, edges, options = {}) {
21017
22215
  filePath: s.filePath,
21018
22216
  kind: s.kind,
21019
22217
  line: s.startLine,
21020
- directory: path29.dirname(s.filePath),
22218
+ directory: path31.dirname(s.filePath),
21021
22219
  moduleId: "",
21022
22220
  moduleLabel: ""
21023
22221
  }));
@@ -21045,9 +22243,9 @@ function parseArgs(argv) {
21045
22243
  let host = "opencode";
21046
22244
  for (let i = 2; i < argv.length; i++) {
21047
22245
  if (argv[i] === "--project" && argv[i + 1]) {
21048
- project = path30.resolve(argv[++i]);
22246
+ project = path32.resolve(argv[++i]);
21049
22247
  } else if (argv[i] === "--config" && argv[i + 1]) {
21050
- config = path30.resolve(argv[++i]);
22248
+ config = path32.resolve(argv[++i]);
21051
22249
  } else if (argv[i] === "--host" && argv[i + 1]) {
21052
22250
  host = parseHostMode(argv[++i]);
21053
22251
  } else if (argv[i] === "--host") {
@@ -21074,7 +22272,7 @@ function parseIndexArgs(argv, cwd) {
21074
22272
  if (!arg.startsWith("--project=")) {
21075
22273
  i += 1;
21076
22274
  }
21077
- project = path30.resolve(cwd, value);
22275
+ project = path32.resolve(cwd, value);
21078
22276
  continue;
21079
22277
  }
21080
22278
  if (arg === "--config" || arg.startsWith("--config=")) {
@@ -21085,7 +22283,7 @@ function parseIndexArgs(argv, cwd) {
21085
22283
  if (!arg.startsWith("--config=")) {
21086
22284
  i += 1;
21087
22285
  }
21088
- config = path30.resolve(cwd, value);
22286
+ config = path32.resolve(cwd, value);
21089
22287
  continue;
21090
22288
  }
21091
22289
  if (arg === "--host" || arg.startsWith("--host=")) {
@@ -21151,7 +22349,7 @@ function parseVisualizeArgs(argv, cwd) {
21151
22349
  for (let i = 0; i < argv.length; i++) {
21152
22350
  const arg = argv[i];
21153
22351
  if (arg === "--project" && argv[i + 1]) {
21154
- project = path30.resolve(argv[++i]);
22352
+ project = path32.resolve(argv[++i]);
21155
22353
  } else if (arg === "--max" && argv[i + 1]) {
21156
22354
  maxNodes = Number(argv[++i]);
21157
22355
  } else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
@@ -21188,7 +22386,7 @@ async function handleVisualizeCommand(argv, cwd) {
21188
22386
  console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
21189
22387
  return 1;
21190
22388
  }
21191
- const outputPath = path30.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
22389
+ const outputPath = path32.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
21192
22390
  (0, import_fs20.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
21193
22391
  console.log(`Temporal call graph visualization generated: ${outputPath}`);
21194
22392
  console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
@@ -21220,8 +22418,60 @@ async function runMcpCli(argv) {
21220
22418
  const config = parseConfig(rawConfig);
21221
22419
  const server = createMcpServer(args.project, config, args.host);
21222
22420
  const transport = new import_stdio.StdioServerTransport();
21223
- await server.connect(transport);
21224
22421
  let watcher = null;
22422
+ let shutdownPromise;
22423
+ const onServerClose = server.server.onclose;
22424
+ const shutdown = () => {
22425
+ if (shutdownPromise) return shutdownPromise;
22426
+ process.stdin.removeListener("end", requestShutdown);
22427
+ process.stdin.removeListener("close", requestShutdown);
22428
+ process.removeListener("SIGHUP", requestShutdown);
22429
+ process.removeListener("SIGINT", requestShutdown);
22430
+ process.removeListener("SIGTERM", requestShutdown);
22431
+ server.server.onclose = onServerClose;
22432
+ shutdownPromise = (async () => {
22433
+ let exitCode = 0;
22434
+ try {
22435
+ await watcher?.stop();
22436
+ } catch (error) {
22437
+ exitCode = 1;
22438
+ console.error("Failed to stop MCP file watcher cleanly:", error);
22439
+ }
22440
+ try {
22441
+ await stopAutoIndex(args.project, args.host);
22442
+ } catch (error) {
22443
+ exitCode = 1;
22444
+ console.error("Failed to stop automatic indexing cleanly:", error);
22445
+ }
22446
+ try {
22447
+ await server.close();
22448
+ } catch (error) {
22449
+ exitCode = 1;
22450
+ console.error("Failed to close MCP server cleanly:", error);
22451
+ }
22452
+ process.exit(exitCode);
22453
+ })();
22454
+ return shutdownPromise;
22455
+ };
22456
+ const requestShutdown = () => {
22457
+ void shutdown();
22458
+ };
22459
+ server.server.onclose = () => {
22460
+ try {
22461
+ onServerClose?.();
22462
+ } finally {
22463
+ requestShutdown();
22464
+ }
22465
+ };
22466
+ process.stdin.once("end", requestShutdown);
22467
+ process.stdin.once("close", requestShutdown);
22468
+ process.once("SIGINT", requestShutdown);
22469
+ if (process.platform !== "win32") {
22470
+ process.once("SIGHUP", requestShutdown);
22471
+ process.once("SIGTERM", requestShutdown);
22472
+ }
22473
+ await server.connect(transport);
22474
+ if (shutdownPromise) return;
21225
22475
  const isHomeDir = isHomeDirectory(args.project);
21226
22476
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
21227
22477
  if (config.indexing.watchFiles && isValidProject) {
@@ -21233,26 +22483,6 @@ async function runMcpCli(argv) {
21233
22483
  args.config ? { configPath: args.config } : {}
21234
22484
  );
21235
22485
  }
21236
- let shuttingDown = false;
21237
- const shutdown = async () => {
21238
- if (shuttingDown) return;
21239
- shuttingDown = true;
21240
- try {
21241
- await watcher?.stop();
21242
- await stopAutoIndex(args.project, args.host);
21243
- await server.close();
21244
- process.exit(0);
21245
- } catch (error) {
21246
- console.error("Failed to stop MCP server cleanly:", error);
21247
- process.exit(1);
21248
- }
21249
- };
21250
- process.on("SIGINT", () => {
21251
- void shutdown();
21252
- });
21253
- process.on("SIGTERM", () => {
21254
- void shutdown();
21255
- });
21256
22486
  }
21257
22487
  function printIndexProgress(onProgress, title, metadata) {
21258
22488
  const details = Object.entries(metadata).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${isSensitiveKey(key) ? "[REDACTED]" : String(value)}`).join(" ");