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.js CHANGED
@@ -491,7 +491,7 @@ var require_ignore = __commonJS({
491
491
  // path matching.
492
492
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
493
493
  // @returns {TestResult} true if a file is ignored
494
- test(path31, checkUnignored, mode) {
494
+ test(path33, checkUnignored, mode) {
495
495
  let ignored = false;
496
496
  let unignored = false;
497
497
  let matchedRule;
@@ -500,7 +500,7 @@ var require_ignore = __commonJS({
500
500
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
501
501
  return;
502
502
  }
503
- const matched = rule[mode].test(path31);
503
+ const matched = rule[mode].test(path33);
504
504
  if (!matched) {
505
505
  return;
506
506
  }
@@ -521,17 +521,17 @@ var require_ignore = __commonJS({
521
521
  var throwError = (message, Ctor) => {
522
522
  throw new Ctor(message);
523
523
  };
524
- var checkPath = (path31, originalPath, doThrow) => {
525
- if (!isString(path31)) {
524
+ var checkPath = (path33, originalPath, doThrow) => {
525
+ if (!isString(path33)) {
526
526
  return doThrow(
527
527
  `path must be a string, but got \`${originalPath}\``,
528
528
  TypeError
529
529
  );
530
530
  }
531
- if (!path31) {
531
+ if (!path33) {
532
532
  return doThrow(`path must not be empty`, TypeError);
533
533
  }
534
- if (checkPath.isNotRelative(path31)) {
534
+ if (checkPath.isNotRelative(path33)) {
535
535
  const r = "`path.relative()`d";
536
536
  return doThrow(
537
537
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -540,7 +540,7 @@ var require_ignore = __commonJS({
540
540
  }
541
541
  return true;
542
542
  };
543
- var isNotRelative = (path31) => REGEX_TEST_INVALID_PATH.test(path31);
543
+ var isNotRelative = (path33) => REGEX_TEST_INVALID_PATH.test(path33);
544
544
  checkPath.isNotRelative = isNotRelative;
545
545
  checkPath.convert = (p) => p;
546
546
  var Ignore2 = class {
@@ -570,19 +570,19 @@ var require_ignore = __commonJS({
570
570
  }
571
571
  // @returns {TestResult}
572
572
  _test(originalPath, cache, checkUnignored, slices) {
573
- const path31 = originalPath && checkPath.convert(originalPath);
573
+ const path33 = originalPath && checkPath.convert(originalPath);
574
574
  checkPath(
575
- path31,
575
+ path33,
576
576
  originalPath,
577
577
  this._strictPathCheck ? throwError : RETURN_FALSE
578
578
  );
579
- return this._t(path31, cache, checkUnignored, slices);
579
+ return this._t(path33, cache, checkUnignored, slices);
580
580
  }
581
- checkIgnore(path31) {
582
- if (!REGEX_TEST_TRAILING_SLASH.test(path31)) {
583
- return this.test(path31);
581
+ checkIgnore(path33) {
582
+ if (!REGEX_TEST_TRAILING_SLASH.test(path33)) {
583
+ return this.test(path33);
584
584
  }
585
- const slices = path31.split(SLASH2).filter(Boolean);
585
+ const slices = path33.split(SLASH2).filter(Boolean);
586
586
  slices.pop();
587
587
  if (slices.length) {
588
588
  const parent = this._t(
@@ -595,18 +595,18 @@ var require_ignore = __commonJS({
595
595
  return parent;
596
596
  }
597
597
  }
598
- return this._rules.test(path31, false, MODE_CHECK_IGNORE);
598
+ return this._rules.test(path33, false, MODE_CHECK_IGNORE);
599
599
  }
600
- _t(path31, cache, checkUnignored, slices) {
601
- if (path31 in cache) {
602
- return cache[path31];
600
+ _t(path33, cache, checkUnignored, slices) {
601
+ if (path33 in cache) {
602
+ return cache[path33];
603
603
  }
604
604
  if (!slices) {
605
- slices = path31.split(SLASH2).filter(Boolean);
605
+ slices = path33.split(SLASH2).filter(Boolean);
606
606
  }
607
607
  slices.pop();
608
608
  if (!slices.length) {
609
- return cache[path31] = this._rules.test(path31, checkUnignored, MODE_IGNORE);
609
+ return cache[path33] = this._rules.test(path33, checkUnignored, MODE_IGNORE);
610
610
  }
611
611
  const parent = this._t(
612
612
  slices.join(SLASH2) + SLASH2,
@@ -614,29 +614,29 @@ var require_ignore = __commonJS({
614
614
  checkUnignored,
615
615
  slices
616
616
  );
617
- return cache[path31] = parent.ignored ? parent : this._rules.test(path31, checkUnignored, MODE_IGNORE);
617
+ return cache[path33] = parent.ignored ? parent : this._rules.test(path33, checkUnignored, MODE_IGNORE);
618
618
  }
619
- ignores(path31) {
620
- return this._test(path31, this._ignoreCache, false).ignored;
619
+ ignores(path33) {
620
+ return this._test(path33, this._ignoreCache, false).ignored;
621
621
  }
622
622
  createFilter() {
623
- return (path31) => !this.ignores(path31);
623
+ return (path33) => !this.ignores(path33);
624
624
  }
625
625
  filter(paths) {
626
626
  return makeArray(paths).filter(this.createFilter());
627
627
  }
628
628
  // @returns {TestResult}
629
- test(path31) {
630
- return this._test(path31, this._testCache, true);
629
+ test(path33) {
630
+ return this._test(path33, this._testCache, true);
631
631
  }
632
632
  };
633
633
  var factory = (options) => new Ignore2(options);
634
- var isPathValid = (path31) => checkPath(path31 && checkPath.convert(path31), path31, RETURN_FALSE);
634
+ var isPathValid = (path33) => checkPath(path33 && checkPath.convert(path33), path33, RETURN_FALSE);
635
635
  var setupWindows = () => {
636
636
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
637
637
  checkPath.convert = makePosix;
638
638
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
639
- checkPath.isNotRelative = (path31) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path31) || isNotRelative(path31);
639
+ checkPath.isNotRelative = (path33) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path33) || isNotRelative(path33);
640
640
  };
641
641
  if (
642
642
  // Detect `process` so that it can run in browsers.
@@ -655,7 +655,7 @@ var require_ignore = __commonJS({
655
655
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
656
656
  import { realpathSync as realpathSync6, writeFileSync as writeFileSync6 } from "fs";
657
657
  import * as os8 from "os";
658
- import * as path30 from "path";
658
+ import * as path32 from "path";
659
659
  import { fileURLToPath as fileURLToPath2 } from "url";
660
660
 
661
661
  // src/config/constants.ts
@@ -716,6 +716,17 @@ var EMBEDDING_MODELS = {
716
716
  maxTokens: 2048,
717
717
  costPer1MTokens: 0.15,
718
718
  taskAble: true
719
+ },
720
+ "gemini-embedding-2": {
721
+ provider: "google",
722
+ model: "gemini-embedding-2",
723
+ // Keep a conservative, testable default embedding dimension. Gemini Embedding 2 supports
724
+ // flexible dimensions via outputDimensionality.
725
+ dimensions: 1536,
726
+ maxTokens: 8192,
727
+ costPer1MTokens: 0.15,
728
+ taskAble: false,
729
+ promptStyle: "embedding-2"
719
730
  }
720
731
  },
721
732
  "openai": {
@@ -749,26 +760,15 @@ var EMBEDDING_MODELS = {
749
760
  maxTokens: 512,
750
761
  costPer1MTokens: 0
751
762
  }
752
- },
753
- "github-copilot": {
754
- "text-embedding-3-small": {
755
- provider: "github-copilot",
756
- model: "text-embedding-3-small",
757
- dimensions: 1536,
758
- maxTokens: 8191,
759
- costPer1MTokens: 0
760
- }
761
763
  }
762
764
  };
763
765
  var DEFAULT_PROVIDER_MODELS = {
764
- "github-copilot": "text-embedding-3-small",
765
766
  "openai": "text-embedding-3-small",
766
767
  "google": "gemini-embedding-001",
767
768
  "ollama": "nomic-embed-text"
768
769
  };
769
770
  var AUTO_DETECT_PROVIDER_ORDER = [
770
771
  "ollama",
771
- "github-copilot",
772
772
  "openai",
773
773
  "google"
774
774
  ];
@@ -794,6 +794,9 @@ function getDefaultIndexingConfig() {
794
794
  maxDepth: 5,
795
795
  maxFilesPerDirectory: 100,
796
796
  fallbackToTextOnMaxChunks: true,
797
+ // Must stay in sync with DEFAULT_LINES_PER_CHUNK in native/src/lib.rs (the napi
798
+ // fallback used when a native caller omits the argument).
799
+ linesPerChunk: 30,
797
800
  gitBlame: { enabled: false }
798
801
  };
799
802
  }
@@ -927,6 +930,7 @@ function parseConfig(raw) {
927
930
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
928
931
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
929
932
  fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
933
+ linesPerChunk: typeof rawIndexing.linesPerChunk === "number" && Number.isFinite(rawIndexing.linesPerChunk) ? Math.min(Math.max(1, Math.floor(rawIndexing.linesPerChunk)), 4294967295) : defaultIndexing.linesPerChunk,
930
934
  gitBlame: {
931
935
  enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
932
936
  }
@@ -969,6 +973,7 @@ function parseConfig(raw) {
969
973
  let embeddingModel;
970
974
  let customProvider;
971
975
  let reranker;
976
+ 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.';
972
977
  if (embeddingProviderValue === "custom") {
973
978
  embeddingProvider = "custom";
974
979
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -1008,6 +1013,8 @@ function parseConfig(raw) {
1008
1013
  } else if (rawEmbeddingModel) {
1009
1014
  embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
1010
1015
  }
1016
+ } else if (embeddingProviderValue === "github-copilot") {
1017
+ throw new Error(githubCopilotDeprecationMessage);
1011
1018
  } else {
1012
1019
  embeddingProvider = "auto";
1013
1020
  }
@@ -1038,10 +1045,21 @@ function parseConfig(raw) {
1038
1045
  timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
1039
1046
  };
1040
1047
  }
1048
+ const rawEmbedding = input.embedding && typeof input.embedding === "object" ? input.embedding : {};
1049
+ const rawEmbeddingBatch = rawEmbedding.batch && typeof rawEmbedding.batch === "object" ? rawEmbedding.batch : null;
1050
+ const embeddingMaxBatchItems = typeof rawEmbeddingBatch?.maxBatchItems === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchItems) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchItems)) : void 0;
1051
+ const embeddingMaxBatchTokens = typeof rawEmbeddingBatch?.maxBatchTokens === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchTokens) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchTokens)) : void 0;
1052
+ const embedding = embeddingMaxBatchItems !== void 0 || embeddingMaxBatchTokens !== void 0 ? {
1053
+ batch: {
1054
+ ...embeddingMaxBatchItems !== void 0 ? { maxBatchItems: embeddingMaxBatchItems } : {},
1055
+ ...embeddingMaxBatchTokens !== void 0 ? { maxBatchTokens: embeddingMaxBatchTokens } : {}
1056
+ }
1057
+ } : {};
1041
1058
  return {
1042
1059
  embeddingProvider,
1043
1060
  embeddingModel,
1044
1061
  customProvider,
1062
+ embedding,
1045
1063
  scope: isValidScope(scopeValue) ? scopeValue : "project",
1046
1064
  include: includeValue ?? DEFAULT_INCLUDE,
1047
1065
  exclude: excludeValue ?? DEFAULT_EXCLUDE,
@@ -1165,9 +1183,9 @@ import { mkdirSync, readFileSync, writeFileSync } from "fs";
1165
1183
  import * as path from "path";
1166
1184
 
1167
1185
  // src/eval/report-formatters.ts
1168
- function assertFiniteNumber(value, path31) {
1186
+ function assertFiniteNumber(value, path33) {
1169
1187
  if (typeof value !== "number" || Number.isNaN(value) || !Number.isFinite(value)) {
1170
- throw new Error(`${path31} must be a finite number`);
1188
+ throw new Error(`${path33} must be a finite number`);
1171
1189
  }
1172
1190
  return value;
1173
1191
  }
@@ -1439,7 +1457,7 @@ function pTimeout(promise, options) {
1439
1457
  } = options;
1440
1458
  let timer;
1441
1459
  let abortHandler;
1442
- const wrappedPromise = new Promise((resolve18, reject) => {
1460
+ const wrappedPromise = new Promise((resolve20, reject) => {
1443
1461
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
1444
1462
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
1445
1463
  }
@@ -1453,7 +1471,7 @@ function pTimeout(promise, options) {
1453
1471
  };
1454
1472
  signal.addEventListener("abort", abortHandler, { once: true });
1455
1473
  }
1456
- promise.then(resolve18, reject);
1474
+ promise.then(resolve20, reject);
1457
1475
  if (milliseconds === Number.POSITIVE_INFINITY) {
1458
1476
  return;
1459
1477
  }
@@ -1461,7 +1479,7 @@ function pTimeout(promise, options) {
1461
1479
  timer = customTimers.setTimeout.call(void 0, () => {
1462
1480
  if (fallback) {
1463
1481
  try {
1464
- resolve18(fallback());
1482
+ resolve20(fallback());
1465
1483
  } catch (error) {
1466
1484
  reject(error);
1467
1485
  }
@@ -1471,7 +1489,7 @@ function pTimeout(promise, options) {
1471
1489
  promise.cancel();
1472
1490
  }
1473
1491
  if (message === false) {
1474
- resolve18();
1492
+ resolve20();
1475
1493
  } else if (message instanceof Error) {
1476
1494
  reject(message);
1477
1495
  } else {
@@ -1873,7 +1891,7 @@ var PQueue = class extends import_index.default {
1873
1891
  // Assign unique ID if not provided
1874
1892
  id: options.id ?? (this.#idAssigner++).toString()
1875
1893
  };
1876
- return new Promise((resolve18, reject) => {
1894
+ return new Promise((resolve20, reject) => {
1877
1895
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1878
1896
  let cleanupQueueAbortHandler = () => void 0;
1879
1897
  const run = async () => {
@@ -1913,7 +1931,7 @@ var PQueue = class extends import_index.default {
1913
1931
  })]);
1914
1932
  }
1915
1933
  const result = await operation;
1916
- resolve18(result);
1934
+ resolve20(result);
1917
1935
  this.emit("completed", result);
1918
1936
  } catch (error) {
1919
1937
  reject(error);
@@ -2101,13 +2119,13 @@ var PQueue = class extends import_index.default {
2101
2119
  });
2102
2120
  }
2103
2121
  async #onEvent(event, filter) {
2104
- return new Promise((resolve18) => {
2122
+ return new Promise((resolve20) => {
2105
2123
  const listener = () => {
2106
2124
  if (filter && !filter()) {
2107
2125
  return;
2108
2126
  }
2109
2127
  this.off(event, listener);
2110
- resolve18();
2128
+ resolve20();
2111
2129
  };
2112
2130
  this.on(event, listener);
2113
2131
  });
@@ -2393,7 +2411,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2393
2411
  const finalDelay = Math.min(delayTime, remainingTime);
2394
2412
  options.signal?.throwIfAborted();
2395
2413
  if (finalDelay > 0) {
2396
- await new Promise((resolve18, reject) => {
2414
+ await new Promise((resolve20, reject) => {
2397
2415
  const onAbort = () => {
2398
2416
  clearTimeout(timeoutToken);
2399
2417
  options.signal?.removeEventListener("abort", onAbort);
@@ -2401,7 +2419,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
2401
2419
  };
2402
2420
  const timeoutToken = setTimeout(() => {
2403
2421
  options.signal?.removeEventListener("abort", onAbort);
2404
- resolve18();
2422
+ resolve20();
2405
2423
  }, finalDelay);
2406
2424
  if (options.unref) {
2407
2425
  timeoutToken.unref?.();
@@ -2537,8 +2555,6 @@ async function tryDetectProvider() {
2537
2555
  }
2538
2556
  async function getProviderCredentials(provider) {
2539
2557
  switch (provider) {
2540
- case "github-copilot":
2541
- return getGitHubCopilotCredentials();
2542
2558
  case "openai":
2543
2559
  return getOpenAICredentials();
2544
2560
  case "google":
@@ -2549,22 +2565,6 @@ async function getProviderCredentials(provider) {
2549
2565
  return null;
2550
2566
  }
2551
2567
  }
2552
- function getGitHubCopilotCredentials() {
2553
- const authData = loadOpenCodeAuth();
2554
- const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
2555
- if (!copilotAuth || copilotAuth.type !== "oauth") {
2556
- return null;
2557
- }
2558
- const auth = copilotAuth;
2559
- const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
2560
- return {
2561
- provider: "github-copilot",
2562
- baseUrl,
2563
- refreshToken: copilotAuth.refresh,
2564
- accessToken: copilotAuth.access,
2565
- tokenExpires: copilotAuth.expires
2566
- };
2567
- }
2568
2568
  function getOpenAICredentials() {
2569
2569
  const authData = loadOpenCodeAuth();
2570
2570
  const openaiAuth = authData["openai"];
@@ -2690,8 +2690,6 @@ async function tryDetectOllamaProvider() {
2690
2690
  }
2691
2691
  function getProviderDisplayName(provider) {
2692
2692
  switch (provider) {
2693
- case "github-copilot":
2694
- return "GitHub Copilot";
2695
2693
  case "openai":
2696
2694
  return "OpenAI";
2697
2695
  case "google":
@@ -2916,44 +2914,6 @@ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
2916
2914
  }
2917
2915
  };
2918
2916
 
2919
- // src/embeddings/providers/github-copilot.ts
2920
- var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
2921
- constructor(credentials, modelInfo) {
2922
- super(credentials, modelInfo);
2923
- }
2924
- getToken() {
2925
- if (!this.credentials.refreshToken) {
2926
- throw new Error("No OAuth token available for GitHub");
2927
- }
2928
- return this.credentials.refreshToken;
2929
- }
2930
- async embedBatch(texts) {
2931
- const token = this.getToken();
2932
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
2933
- method: "POST",
2934
- headers: {
2935
- Authorization: `Bearer ${token}`,
2936
- "Content-Type": "application/json",
2937
- Accept: "application/vnd.github+json",
2938
- "X-GitHub-Api-Version": "2022-11-28"
2939
- },
2940
- body: JSON.stringify({
2941
- model: `openai/${this.modelInfo.model}`,
2942
- input: texts
2943
- })
2944
- });
2945
- if (!response.ok) {
2946
- const error = (await response.text()).slice(0, 500);
2947
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
2948
- }
2949
- const data = await response.json();
2950
- return {
2951
- embeddings: data.data.map((d) => d.embedding),
2952
- totalTokensUsed: data.usage.total_tokens
2953
- };
2954
- }
2955
- };
2956
-
2957
2917
  // src/embeddings/providers/google.ts
2958
2918
  var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
2959
2919
  static BATCH_SIZE = 20;
@@ -2961,24 +2921,30 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
2961
2921
  super(credentials, modelInfo);
2962
2922
  }
2963
2923
  async embedQuery(query) {
2964
- const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
2965
- const result = await this.embedWithTaskType([query], taskType);
2924
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
2925
+ const texts = [
2926
+ this.modelInfo.model === "gemini-embedding-2" ? `task: code retrieval | query: ${query}` : query
2927
+ ];
2928
+ const result = await this.embedWithTaskType(texts, taskType);
2966
2929
  return {
2967
2930
  embedding: result.embeddings[0],
2968
2931
  tokensUsed: result.totalTokensUsed
2969
2932
  };
2970
2933
  }
2971
2934
  async embedDocument(document) {
2972
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2973
- const result = await this.embedWithTaskType([document], taskType);
2935
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2936
+ const result = await this.embedWithTaskType([
2937
+ this.modelInfo.model === "gemini-embedding-2" ? `title: none | text: ${document}` : document
2938
+ ], taskType);
2974
2939
  return {
2975
2940
  embedding: result.embeddings[0],
2976
2941
  tokensUsed: result.totalTokensUsed
2977
2942
  };
2978
2943
  }
2979
2944
  async embedBatch(texts) {
2980
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2981
- return this.embedWithTaskType(texts, taskType);
2945
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
2946
+ const formattedTexts = this.modelInfo.model === "gemini-embedding-2" ? texts.map((text) => `title: none | text: ${text}`) : texts;
2947
+ return this.embedWithTaskType(formattedTexts, taskType);
2982
2948
  }
2983
2949
  async embedWithTaskType(texts, taskType) {
2984
2950
  const batches = [];
@@ -3028,6 +2994,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3028
2994
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
3029
2995
  static MIN_TRUNCATION_CHARS = 512;
3030
2996
  static REQUEST_TIMEOUT_MS = 12e4;
2997
+ // Set when /api/embed returns 404 so subsequent multi-text batches skip the
2998
+ // batched endpoint and go straight to the legacy per-text path (one probe per
2999
+ // old ollama install, not one probe per batch).
3000
+ batchEndpointUnavailable = false;
3031
3001
  constructor(credentials, modelInfo) {
3032
3002
  super(credentials, modelInfo);
3033
3003
  }
@@ -3045,6 +3015,21 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3045
3015
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
3046
3016
  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");
3047
3017
  }
3018
+ // True for a 404 from the newer /api/embed endpoint, i.e. an ollama version that
3019
+ // does not provide it. embedBatch uses this to fall back to the legacy per-text
3020
+ // /api/embeddings path so old ollama installs do not regress.
3021
+ isBatchEndpointUnavailableError(error) {
3022
+ const message = error instanceof Error ? error.message : String(error);
3023
+ return message.includes("Ollama /api/embed not available");
3024
+ }
3025
+ // True for a malformed /api/embed response (wrong vector count or a bad vector).
3026
+ // embedBatch falls back to the per-text path on this so a bad batch response
3027
+ // re-embeds each text cleanly. A text that then fails per-text is not isolated
3028
+ // here; it is isolated on the recovery run, which re-embeds one text per request.
3029
+ isBatchValidationError(error) {
3030
+ const message = error instanceof Error ? error.message : String(error);
3031
+ return message.includes("invalid embedding batch");
3032
+ }
3048
3033
  buildTruncationCandidates(text) {
3049
3034
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
3050
3035
  const candidateLimits = /* @__PURE__ */ new Set();
@@ -3146,7 +3131,74 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3146
3131
  tokensUsed: this.estimateTokens(text)
3147
3132
  };
3148
3133
  }
3149
- async embedBatch(texts) {
3134
+ // Embeds many texts in one POST /api/embed request (input: string[]). Ollama
3135
+ // encodes each input independently, so the model context length applies per input
3136
+ // (the upstream splitter already bounds each input), not over the batch. This
3137
+ // amortizes N HTTP round-trips into one.
3138
+ async embedMany(texts) {
3139
+ const controller = new AbortController();
3140
+ const timeout = setTimeout(
3141
+ () => controller.abort(),
3142
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3143
+ );
3144
+ let response;
3145
+ try {
3146
+ response = await fetch(`${this.credentials.baseUrl}/api/embed`, {
3147
+ method: "POST",
3148
+ headers: {
3149
+ "Content-Type": "application/json"
3150
+ },
3151
+ body: JSON.stringify({
3152
+ model: this.modelInfo.model,
3153
+ input: texts,
3154
+ truncate: false
3155
+ }),
3156
+ signal: controller.signal
3157
+ });
3158
+ } catch (error) {
3159
+ if (error instanceof Error && error.name === "AbortError") {
3160
+ throw new Error(
3161
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3162
+ );
3163
+ }
3164
+ throw error;
3165
+ } finally {
3166
+ clearTimeout(timeout);
3167
+ }
3168
+ if (!response.ok) {
3169
+ const error = (await response.text()).slice(0, 500);
3170
+ if (response.status === 404) {
3171
+ throw new Error(`Ollama /api/embed not available: ${response.status} - ${error}`);
3172
+ }
3173
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
3174
+ }
3175
+ let parsed;
3176
+ try {
3177
+ parsed = await response.json();
3178
+ } catch {
3179
+ throw new Error(
3180
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
3181
+ );
3182
+ }
3183
+ const data = parsed && typeof parsed === "object" ? parsed : {};
3184
+ if (!Array.isArray(data.embeddings) || data.embeddings.length !== texts.length || data.embeddings.some(
3185
+ (value) => !Array.isArray(value) || value.length !== this.modelInfo.dimensions || value.some((v) => typeof v !== "number" || !Number.isFinite(v))
3186
+ )) {
3187
+ throw new Error(
3188
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
3189
+ );
3190
+ }
3191
+ return {
3192
+ embeddings: data.embeddings,
3193
+ totalTokensUsed: texts.reduce((sum, text) => sum + this.estimateTokens(text), 0)
3194
+ };
3195
+ }
3196
+ // Per-text /api/embeddings path shared by the single-text fast path and the
3197
+ // batch fallback. Uses the legacy endpoint one text at a time, so each text gets
3198
+ // its own truncation safety net and a vector validated on its own. A text that
3199
+ // hard-fails per-text throws here and fails the whole request batch; the recovery
3200
+ // run re-embeds one text per request to isolate it.
3201
+ async embedOneByOne(texts) {
3150
3202
  const results = [];
3151
3203
  for (const text of texts) {
3152
3204
  results.push(await this.embedSingleWithFallback(text));
@@ -3156,6 +3208,26 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3156
3208
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
3157
3209
  };
3158
3210
  }
3211
+ async embedBatch(texts) {
3212
+ if (texts.length === 0) {
3213
+ return { embeddings: [], totalTokensUsed: 0 };
3214
+ }
3215
+ if (texts.length === 1 || this.batchEndpointUnavailable) {
3216
+ return this.embedOneByOne(texts);
3217
+ }
3218
+ try {
3219
+ return await this.embedMany(texts);
3220
+ } catch (error) {
3221
+ if (this.isBatchEndpointUnavailableError(error)) {
3222
+ this.batchEndpointUnavailable = true;
3223
+ return this.embedOneByOne(texts);
3224
+ }
3225
+ if (!this.isContextLengthError(error) && !this.isBatchValidationError(error)) {
3226
+ throw error;
3227
+ }
3228
+ return this.embedOneByOne(texts);
3229
+ }
3230
+ }
3159
3231
  };
3160
3232
 
3161
3233
  // src/embeddings/providers/openai.ts
@@ -3190,8 +3262,6 @@ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
3190
3262
  // src/embeddings/provider.ts
3191
3263
  function createEmbeddingProvider(configuredProviderInfo) {
3192
3264
  switch (configuredProviderInfo.provider) {
3193
- case "github-copilot":
3194
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
3195
3265
  case "openai":
3196
3266
  return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
3197
3267
  case "google":
@@ -3207,85 +3277,6 @@ function createEmbeddingProvider(configuredProviderInfo) {
3207
3277
  }
3208
3278
  }
3209
3279
 
3210
- // src/rerank/index.ts
3211
- function createReranker(config) {
3212
- if (!config.enabled) {
3213
- return new NoOpReranker();
3214
- }
3215
- return new SiliconFlowReranker(config);
3216
- }
3217
- var NoOpReranker = class {
3218
- isAvailable() {
3219
- return false;
3220
- }
3221
- async rerank(_query, documents, _topN) {
3222
- return {
3223
- results: documents.map((_, index) => ({ index, relevanceScore: 0 }))
3224
- };
3225
- }
3226
- };
3227
- var SiliconFlowReranker = class {
3228
- config;
3229
- constructor(config) {
3230
- this.config = config;
3231
- }
3232
- isAvailable() {
3233
- return this.config.enabled && !!this.config.baseUrl && !!this.config.model;
3234
- }
3235
- async rerank(query, documents, topN) {
3236
- if (documents.length === 0) {
3237
- return { results: [] };
3238
- }
3239
- const headers = {
3240
- "Content-Type": "application/json"
3241
- };
3242
- if (this.config.apiKey) {
3243
- headers["Authorization"] = `Bearer ${this.config.apiKey}`;
3244
- }
3245
- const baseUrl = this.config.baseUrl;
3246
- if (!baseUrl) {
3247
- throw new Error("Reranker baseUrl is required. Configure reranker.baseUrl in your codebase-index.json.");
3248
- }
3249
- const timeoutMs = this.config.timeoutMs ?? 3e4;
3250
- const controller = new AbortController();
3251
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
3252
- try {
3253
- const response = await fetch(`${baseUrl}/rerank`, {
3254
- method: "POST",
3255
- headers,
3256
- body: JSON.stringify({
3257
- model: this.config.model,
3258
- query,
3259
- documents,
3260
- top_n: topN ?? this.config.topN ?? 20,
3261
- return_documents: false
3262
- }),
3263
- signal: controller.signal
3264
- });
3265
- clearTimeout(timeout);
3266
- if (!response.ok) {
3267
- const errorText = await response.text();
3268
- throw new Error(`Rerank API error: ${response.status} - ${errorText}`);
3269
- }
3270
- const data = await response.json();
3271
- return {
3272
- results: data.results.map((r) => ({
3273
- index: r.index,
3274
- relevanceScore: r.relevance_score,
3275
- document: r.document?.text
3276
- })),
3277
- tokensUsed: data.meta?.tokens?.input_tokens
3278
- };
3279
- } catch (error) {
3280
- clearTimeout(timeout);
3281
- if (error instanceof Error && error.name === "AbortError") {
3282
- throw new Error(`Rerank API request timed out after ${timeoutMs}ms`);
3283
- }
3284
- throw error;
3285
- }
3286
- }
3287
- };
3288
-
3289
3280
  // src/utils/files.ts
3290
3281
  var import_ignore = __toESM(require_ignore(), 1);
3291
3282
  import { existsSync as existsSync2, readFileSync as readFileSync3, promises as fsPromises } from "fs";
@@ -3447,8 +3438,8 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3447
3438
  if (entry.isDirectory()) {
3448
3439
  subdirs.push({ fullPath, relativePath });
3449
3440
  } else if (entry.isFile()) {
3450
- const stat4 = await fsPromises.stat(fullPath);
3451
- if (stat4.size > maxFileSize) {
3441
+ const stat5 = await fsPromises.stat(fullPath);
3442
+ if (stat5.size > maxFileSize) {
3452
3443
  skipped.push({ path: relativePath, reason: "too_large" });
3453
3444
  continue;
3454
3445
  }
@@ -3466,7 +3457,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
3466
3457
  }
3467
3458
  }
3468
3459
  if (matched) {
3469
- filesInDir.push({ path: fullPath, size: stat4.size });
3460
+ filesInDir.push({ path: fullPath, size: stat5.size });
3470
3461
  }
3471
3462
  }
3472
3463
  }
@@ -3523,8 +3514,8 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
3523
3514
  }
3524
3515
  for (const resolvedKbRoot of normalizedRoots) {
3525
3516
  try {
3526
- const stat4 = await fsPromises.stat(resolvedKbRoot);
3527
- if (!stat4.isDirectory()) {
3517
+ const stat5 = await fsPromises.stat(resolvedKbRoot);
3518
+ if (!stat5.isDirectory()) {
3528
3519
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3529
3520
  continue;
3530
3521
  }
@@ -4288,12 +4279,12 @@ try {
4288
4279
  }
4289
4280
 
4290
4281
  // src/native/parsing.ts
4291
- function parseFileAsText(filePath, content) {
4292
- const result = native.parseFileAsText(filePath, content);
4282
+ function parseFileAsText(filePath, content, linesPerChunk) {
4283
+ const result = native.parseFileAsText(filePath, content, linesPerChunk);
4293
4284
  return result.map(mapChunk);
4294
4285
  }
4295
- function parseFiles(files) {
4296
- const result = native.parseFiles(files);
4286
+ function parseFiles(files, linesPerChunk) {
4287
+ const result = native.parseFiles(files, linesPerChunk);
4297
4288
  return result.map((f) => ({
4298
4289
  path: f.path,
4299
4290
  chunks: f.chunks.map(mapChunk),
@@ -4370,13 +4361,13 @@ var VectorStore = class {
4370
4361
  const metadata = items.map((i) => JSON.stringify(i.metadata));
4371
4362
  this.inner.addBatch(ids, vectors, metadata);
4372
4363
  }
4373
- search(queryVector, limit = 10) {
4364
+ search(queryVector, limit = 10, allowedIds) {
4374
4365
  if (queryVector.length !== this.dimensions) {
4375
4366
  throw new Error(
4376
4367
  `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
4377
4368
  );
4378
4369
  }
4379
- const results = this.inner.search(queryVector, limit);
4370
+ const results = allowedIds === void 0 ? this.inner.search(queryVector, limit) : this.inner.searchFiltered(queryVector, limit, allowedIds);
4380
4371
  return results.map((r) => ({
4381
4372
  id: r.id,
4382
4373
  score: r.score,
@@ -4604,6 +4595,10 @@ var Database = class _Database {
4604
4595
  this.throwIfClosed();
4605
4596
  return this.inner.getBranchChunkIds(branch);
4606
4597
  }
4598
+ getChunkIdsByBlameDate(since, until) {
4599
+ this.throwIfClosed();
4600
+ return this.inner.getChunkIdsByBlameDate(since, until);
4601
+ }
4607
4602
  getBranchDelta(branch, baseBranch) {
4608
4603
  this.throwIfClosed();
4609
4604
  return this.inner.getBranchDelta(branch, baseBranch);
@@ -4850,11 +4845,11 @@ function resolveGitDir(repoRoot) {
4850
4845
  return null;
4851
4846
  }
4852
4847
  try {
4853
- const stat4 = statSync2(gitPath);
4854
- if (stat4.isDirectory()) {
4848
+ const stat5 = statSync2(gitPath);
4849
+ if (stat5.isDirectory()) {
4855
4850
  return gitPath;
4856
4851
  }
4857
- if (stat4.isFile()) {
4852
+ if (stat5.isFile()) {
4858
4853
  const content = readFileSync5(gitPath, "utf-8").trim();
4859
4854
  const match = content.match(/^gitdir:\s*(.+)$/);
4860
4855
  if (match) {
@@ -5359,8 +5354,8 @@ async function isWorktreeRegistered(projectRoot, worktreePath) {
5359
5354
  return false;
5360
5355
  }
5361
5356
  function isPathWithinRoot(filePath, rootPath) {
5362
- const relative12 = path9.relative(path9.resolve(rootPath), path9.resolve(filePath));
5363
- return relative12 === "" || !relative12.startsWith(`..${path9.sep}`) && relative12 !== ".." && !path9.isAbsolute(relative12);
5357
+ const relative14 = path9.relative(path9.resolve(rootPath), path9.resolve(filePath));
5358
+ return relative14 === "" || !relative14.startsWith(`..${path9.sep}`) && relative14 !== ".." && !path9.isAbsolute(relative14);
5364
5359
  }
5365
5360
  async function pruneExactMissingWorktreeRegistration(projectRoot, worktreePath) {
5366
5361
  if (await pathExists(worktreePath)) return false;
@@ -5565,6 +5560,9 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
5565
5560
  const fallbackPath = path10.join(mainRepoRoot, relativePath);
5566
5561
  return existsSync5(fallbackPath) ? fallbackPath : null;
5567
5562
  }
5563
+ function getHostProjectConfigRelativePath(host) {
5564
+ return getProjectConfigRelativePath(host);
5565
+ }
5568
5566
  function getProjectConfigCandidatePaths(projectRoot, host) {
5569
5567
  const candidates = [path10.join(projectRoot, getProjectConfigRelativePath(host))];
5570
5568
  if (host !== "opencode") {
@@ -5656,6 +5654,9 @@ function resolveProjectConfigPath(projectRoot, host) {
5656
5654
  const candidates = getProjectConfigCandidatePaths(projectRoot, host);
5657
5655
  return candidates.find((candidate) => existsSync5(candidate)) ?? path10.join(projectRoot, getProjectConfigRelativePath(host));
5658
5656
  }
5657
+ function resolveWritableProjectConfigPath(projectRoot, host) {
5658
+ return path10.join(projectRoot, getProjectConfigRelativePath(host));
5659
+ }
5659
5660
  function resolveProjectIndexPath(projectRoot, scope, host) {
5660
5661
  if (scope === "global") {
5661
5662
  return resolveGlobalIndexPath(host);
@@ -5918,11 +5919,11 @@ function normalizeFiles(rawFiles, projectRoot) {
5918
5919
  for (const raw of rawFiles) {
5919
5920
  if (raw.length === 0) continue;
5920
5921
  const absolute = path11.resolve(root, raw);
5921
- const relative12 = path11.relative(root, absolute);
5922
- if (path11.isAbsolute(raw) || relative12 === ".." || relative12.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative12)) {
5922
+ const relative14 = path11.relative(root, absolute);
5923
+ if (path11.isAbsolute(raw) || relative14 === ".." || relative14.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative14)) {
5923
5924
  throw new Error(`Changed file escapes the project root: ${JSON.stringify(raw)}`);
5924
5925
  }
5925
- const cleaned = relative12.startsWith(`.${path11.sep}`) ? relative12.slice(2) : relative12;
5926
+ const cleaned = relative14.startsWith(`.${path11.sep}`) ? relative14.slice(2) : relative14;
5926
5927
  if (!seen.has(cleaned)) {
5927
5928
  seen.add(cleaned);
5928
5929
  result.push(cleaned);
@@ -6259,7 +6260,7 @@ function analyzeQueryIntent(query) {
6259
6260
  }
6260
6261
  function isTestPath(filePath) {
6261
6262
  const normalized = normalizePath(filePath);
6262
- return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /\.(?:test|spec)\.[^/]+$/u.test(normalized);
6263
+ return /(?:^|\/)(?:test|tests|__tests__|spec|specs)(?:\/|$)/u.test(normalized) || /(?:\.(?:test|spec)|_(?:test|spec))\.[^/]+$/u.test(normalized) || /(?:^|\/)(?:test|spec)_[^/]+\.[^/]+$/u.test(normalized);
6263
6264
  }
6264
6265
  function isFixturePath(filePath) {
6265
6266
  const normalized = normalizePath(filePath);
@@ -6361,6 +6362,10 @@ function scoreCandidate(query, intent, candidate, originalIndex) {
6361
6362
  let boost = 0;
6362
6363
  if (intent.primary === "conceptual") {
6363
6364
  boost += Math.min(0.14, overlap * 0.14);
6365
+ if (intent.preferSourcePaths) {
6366
+ boost += implementationPath ? 0.32 : 0;
6367
+ if (testPath || fixturePath || docsPath) boost -= 0.35;
6368
+ }
6364
6369
  if (generatedOrVendor) boost -= 0.18;
6365
6370
  if (importChunk || weakContainer) boost -= 0.04;
6366
6371
  } else if (intent.primary === "test") {
@@ -6666,7 +6671,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
6666
6671
  return cached;
6667
6672
  }
6668
6673
  }
6669
- const overfetchLimit = Math.max(options.limit * 4, options.limit);
6674
+ const overfetchFactor = prioritizeSourcePaths ? 12 : 4;
6675
+ const overfetchLimit = Math.max(options.limit * overfetchFactor, options.limit);
6670
6676
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
6671
6677
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
6672
6678
  const rerankPool = fused.slice(0, rerankPoolLimit);
@@ -7284,6 +7290,19 @@ function parseOwner(value) {
7284
7290
  if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
7285
7291
  if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
7286
7292
  if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
7293
+ if (candidate.recoveryProtocolVersion !== void 0 && candidate.recoveryProtocolVersion !== 1) return null;
7294
+ if (candidate.projectRoot !== void 0 && typeof candidate.projectRoot !== "string") return null;
7295
+ if (candidate.scopedRoots !== void 0) {
7296
+ if (!Array.isArray(candidate.scopedRoots) || candidate.scopedRoots.some((root) => typeof root !== "string")) {
7297
+ return null;
7298
+ }
7299
+ }
7300
+ if (candidate.clearRecovery !== void 0) {
7301
+ const recovery = candidate.clearRecovery;
7302
+ 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") {
7303
+ return null;
7304
+ }
7305
+ }
7287
7306
  return candidate;
7288
7307
  }
7289
7308
  function parseReclaimOwner(value) {
@@ -7524,13 +7543,18 @@ function isTransientIndexLockContention(error) {
7524
7543
  if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
7525
7544
  return error.reason === "active" || error.reason === "reclaiming";
7526
7545
  }
7527
- function acquireIndexLock(indexPath, operation) {
7546
+ function acquireIndexLock(indexPath, operation, recoveryScope) {
7528
7547
  mkdirSync2(indexPath, { recursive: true });
7529
7548
  const canonicalIndexPath = realpathSync3.native(indexPath);
7530
7549
  const lockPath = path13.join(canonicalIndexPath, "indexing.lock");
7531
7550
  cleanupDeadPublicationCandidates(canonicalIndexPath);
7532
7551
  for (let attempt = 0; attempt < 6; attempt += 1) {
7533
- const owner = createOwner(operation);
7552
+ const owner = recoveryScope === void 0 ? createOwner(operation) : {
7553
+ ...createOwner(operation),
7554
+ recoveryProtocolVersion: 1,
7555
+ projectRoot: recoveryScope.projectRoot,
7556
+ scopedRoots: recoveryScope.scopedRoots
7557
+ };
7534
7558
  if (publishJsonDirectory(lockPath, owner)) {
7535
7559
  const lease = {
7536
7560
  canonicalIndexPath,
@@ -7595,6 +7619,33 @@ function releaseIndexLock(lease) {
7595
7619
  }
7596
7620
  return true;
7597
7621
  }
7622
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
7623
+ const currentOwner = readDirectoryOwner(lease.lockPath);
7624
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
7625
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
7626
+ }
7627
+ const nextOwner = { ...currentOwner };
7628
+ if (clearRecovery === null) {
7629
+ delete nextOwner.clearRecovery;
7630
+ } else {
7631
+ nextOwner.clearRecovery = clearRecovery;
7632
+ }
7633
+ const ownerPath = path13.join(lease.lockPath, OWNER_FILE_NAME);
7634
+ const temporaryPath = path13.join(
7635
+ lease.lockPath,
7636
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${randomUUID()}`
7637
+ );
7638
+ try {
7639
+ writeFileSync2(temporaryPath, JSON.stringify(nextOwner), {
7640
+ encoding: "utf-8",
7641
+ flag: "wx",
7642
+ mode: 384
7643
+ });
7644
+ retryTransientFilesystemOperation(() => renameSync(temporaryPath, ownerPath));
7645
+ } finally {
7646
+ if (existsSync6(temporaryPath)) rmSync(temporaryPath, { force: true });
7647
+ }
7648
+ }
7598
7649
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
7599
7650
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
7600
7651
  temporaryCounter += 1;
@@ -7832,6 +7883,18 @@ function createFailedBatchWriter(targetPath) {
7832
7883
  temporaryPath
7833
7884
  };
7834
7885
  }
7886
+ function writeFailedBatchRecords(targetPath, records) {
7887
+ const writer = createFailedBatchWriter(targetPath);
7888
+ try {
7889
+ for (const record of records) {
7890
+ writer.write(record);
7891
+ }
7892
+ writer.commit();
7893
+ } catch (error) {
7894
+ writer.cleanup();
7895
+ throw error;
7896
+ }
7897
+ }
7835
7898
  function* readLegacyFailedBatchRecords(filePath, options) {
7836
7899
  const rawData = fs2.readFileSync(filePath, "utf-8");
7837
7900
  const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
@@ -8114,14 +8177,18 @@ function getSafeEmbeddingChunkTokenLimit(provider) {
8114
8177
  const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
8115
8178
  return Math.min(2e3, maxChunkTokens);
8116
8179
  }
8117
- function getDynamicBatchOptions(provider) {
8118
- if (provider.provider === "ollama") {
8119
- return {
8120
- maxBatchTokens: provider.modelInfo.maxTokens,
8121
- maxBatchItems: 1
8122
- };
8180
+ var DEFAULT_OLLAMA_MAX_BATCH_ITEMS = 16;
8181
+ var DEFAULT_OLLAMA_MAX_BATCH_TOKENS = 65536;
8182
+ function getDynamicBatchOptions(provider, embeddingBatch) {
8183
+ if (provider.provider !== "ollama") {
8184
+ return {};
8123
8185
  }
8124
- return {};
8186
+ const base = { maxBatchTokens: DEFAULT_OLLAMA_MAX_BATCH_TOKENS, maxBatchItems: DEFAULT_OLLAMA_MAX_BATCH_ITEMS };
8187
+ return {
8188
+ ...base,
8189
+ ...typeof embeddingBatch?.maxBatchTokens === "number" && Number.isFinite(embeddingBatch.maxBatchTokens) ? { maxBatchTokens: embeddingBatch.maxBatchTokens } : {},
8190
+ ...typeof embeddingBatch?.maxBatchItems === "number" && Number.isFinite(embeddingBatch.maxBatchItems) ? { maxBatchItems: embeddingBatch.maxBatchItems } : {}
8191
+ };
8125
8192
  }
8126
8193
  function isSqliteCorruptionError(error) {
8127
8194
  const message = getErrorMessage3(error).toLowerCase();
@@ -8139,6 +8206,14 @@ function getPendingChunkId(rawChunk) {
8139
8206
  const id = rawChunk.id;
8140
8207
  return typeof id === "string" ? id : null;
8141
8208
  }
8209
+ function parseBlameTimestamp(value, endOfDay) {
8210
+ let timestampMs = Date.parse(value);
8211
+ if (Number.isNaN(timestampMs)) return null;
8212
+ if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
8213
+ timestampMs += 24 * 60 * 60 * 1e3 - 1;
8214
+ }
8215
+ return Math.floor(timestampMs / 1e3);
8216
+ }
8142
8217
  function metadataFromBlame(blame) {
8143
8218
  if (!blame) {
8144
8219
  return {};
@@ -8285,7 +8360,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
8285
8360
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
8286
8361
  return [...promoted, ...remainder];
8287
8362
  }
8288
- function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
8363
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source", allowNonSourcePaths = false) {
8289
8364
  if (!prioritizeSourcePaths) {
8290
8365
  return [];
8291
8366
  }
@@ -8305,7 +8380,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8305
8380
  if (!isImplementationChunkType(chunkType)) {
8306
8381
  return false;
8307
8382
  }
8308
- if (!isLikelyImplementationPath2(chunk.filePath)) {
8383
+ if (!allowNonSourcePaths && !isLikelyImplementationPath2(chunk.filePath)) {
8309
8384
  return false;
8310
8385
  }
8311
8386
  const nameLower = (chunk.name ?? "").toLowerCase();
@@ -8369,7 +8444,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8369
8444
  }
8370
8445
  foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
8371
8446
  }
8372
- if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
8447
+ if (foundCoveringChunk || !allowNonSourcePaths && !isLikelyImplementationPath2(symbol.filePath)) {
8373
8448
  continue;
8374
8449
  }
8375
8450
  const symbolName = symbol.name.toLowerCase();
@@ -8423,7 +8498,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
8423
8498
  const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
8424
8499
  if (ranked.length === 0) {
8425
8500
  const implementationFallback = fallbackCandidates.filter(
8426
- (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath2(candidate.metadata.filePath)
8501
+ (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && (allowNonSourcePaths || isLikelyImplementationPath2(candidate.metadata.filePath))
8427
8502
  );
8428
8503
  for (const candidate of implementationFallback) {
8429
8504
  const nameLower = (candidate.metadata.name ?? "").toLowerCase();
@@ -8539,10 +8614,16 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
8539
8614
  return false;
8540
8615
  }
8541
8616
  if (options?.blameSince) {
8542
- const sinceMs = Date.parse(options.blameSince);
8543
- if (Number.isNaN(sinceMs)) return false;
8617
+ const since = parseBlameTimestamp(options.blameSince, false);
8618
+ if (since === null) return false;
8544
8619
  const committedAt = candidate.metadata.blameCommittedAt;
8545
- if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
8620
+ if (committedAt === void 0 || committedAt < since) return false;
8621
+ }
8622
+ if (options?.blameUntil) {
8623
+ const until = parseBlameTimestamp(options.blameUntil, true);
8624
+ if (until === null) return false;
8625
+ const committedAt = candidate.metadata.blameCommittedAt;
8626
+ if (committedAt === void 0 || committedAt > until) return false;
8546
8627
  }
8547
8628
  return true;
8548
8629
  }
@@ -8578,7 +8659,6 @@ var Indexer = class _Indexer {
8578
8659
  database = null;
8579
8660
  provider = null;
8580
8661
  configuredProviderInfo = null;
8581
- reranker = null;
8582
8662
  fileHashCache = /* @__PURE__ */ new Map();
8583
8663
  fileHashCachePath = "";
8584
8664
  failedBatchesPath = "";
@@ -8599,9 +8679,10 @@ var Indexer = class _Indexer {
8599
8679
  writerArtifactFingerprint = null;
8600
8680
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
8601
8681
  fileBatchLimits;
8682
+ checkpointIntervalChunks;
8602
8683
  constructor(projectRoot, config, host, runtimeOptions = {}) {
8603
8684
  this.projectRoot = projectRoot;
8604
- this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8685
+ this.projectIdentityHash = this.getProjectIdentityHash(projectRoot);
8605
8686
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
8606
8687
  this.branchNameOverride = runtimeOptions.branchName;
8607
8688
  this.catalogIdentityOverride = runtimeOptions.catalogIdentity;
@@ -8611,6 +8692,7 @@ var Indexer = class _Indexer {
8611
8692
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
8612
8693
  this.indexPathOverride = runtimeOptions.indexPath;
8613
8694
  this.fileBatchLimits = runtimeOptions.fileBatchLimits;
8695
+ this.checkpointIntervalChunks = runtimeOptions.checkpointIntervalChunks;
8614
8696
  this.config = config;
8615
8697
  this.host = host;
8616
8698
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -8722,6 +8804,9 @@ var Indexer = class _Indexer {
8722
8804
  return path15.resolve(targetPath);
8723
8805
  }
8724
8806
  }
8807
+ getProjectIdentityHash(projectRoot) {
8808
+ return hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8809
+ }
8725
8810
  isProjectOwnedIndexPath() {
8726
8811
  return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
8727
8812
  }
@@ -8738,7 +8823,6 @@ var Indexer = class _Indexer {
8738
8823
  this.database = null;
8739
8824
  this.provider = null;
8740
8825
  this.configuredProviderInfo = null;
8741
- this.reranker = null;
8742
8826
  this.indexCompatibility = null;
8743
8827
  this.initializationMode = "none";
8744
8828
  this.readIssues = [];
@@ -8759,7 +8843,10 @@ var Indexer = class _Indexer {
8759
8843
  }
8760
8844
  async withIndexMutationLease(operation, callback) {
8761
8845
  this.refreshBranchInfo();
8762
- const lease = acquireIndexLock(this.indexPath, operation);
8846
+ const lease = acquireIndexLock(this.indexPath, operation, {
8847
+ projectRoot: this.projectRoot,
8848
+ scopedRoots: this.getScopedRoots()
8849
+ });
8763
8850
  this.indexPath = lease.canonicalIndexPath;
8764
8851
  this.refreshRuntimeArtifactPaths();
8765
8852
  this.activeIndexLease = lease;
@@ -8814,6 +8901,7 @@ var Indexer = class _Indexer {
8814
8901
  }
8815
8902
  loadFileHashCache() {
8816
8903
  if (!existsSync8(this.fileHashCachePath)) {
8904
+ this.fileHashCache = /* @__PURE__ */ new Map();
8817
8905
  return;
8818
8906
  }
8819
8907
  try {
@@ -8853,10 +8941,10 @@ var Indexer = class _Indexer {
8853
8941
  invertedIndex.serialize()
8854
8942
  );
8855
8943
  }
8856
- getScopedRoots() {
8857
- const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
8944
+ getScopedRoots(projectRoot = this.projectRoot) {
8945
+ const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
8858
8946
  for (const kbRoot of this.config.knowledgeBases) {
8859
- roots.add(this.getCanonicalPath(path15.resolve(this.projectRoot, kbRoot)));
8947
+ roots.add(this.getCanonicalPath(path15.resolve(projectRoot, kbRoot)));
8860
8948
  }
8861
8949
  return Array.from(roots);
8862
8950
  }
@@ -8927,14 +9015,17 @@ var Indexer = class _Indexer {
8927
9015
  getLegacyBranchCatalogKey() {
8928
9016
  return this.currentBranch || "default";
8929
9017
  }
8930
- getLegacyMigrationMetadataKey() {
8931
- return `index.globalBranchMigration.${this.projectIdentityHash}`;
9018
+ getLegacyMigrationMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9019
+ return `index.globalBranchMigration.${projectIdentityHash}`;
8932
9020
  }
8933
- getProjectEmbeddingStrategyMetadataKey() {
8934
- return `index.embeddingStrategyVersion.${this.projectIdentityHash}`;
9021
+ getProjectEmbeddingStrategyMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9022
+ return `index.embeddingStrategyVersion.${projectIdentityHash}`;
8935
9023
  }
8936
- getProjectForceReembedMetadataKey() {
8937
- return `index.forceReembed.${this.projectIdentityHash}`;
9024
+ getProjectForceReembedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9025
+ return `index.forceReembed.${projectIdentityHash}`;
9026
+ }
9027
+ getProjectMigrationFinalizedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
9028
+ return `index.migrationFinalized.${projectIdentityHash}`;
8938
9029
  }
8939
9030
  getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
8940
9031
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
@@ -9040,7 +9131,7 @@ var Indexer = class _Indexer {
9040
9131
  const legacy = this.getLegacyBranchCatalogKey();
9041
9132
  return primary === legacy ? [primary] : [primary, legacy];
9042
9133
  }
9043
- getProjectLocalScopedOwnershipIds(roots) {
9134
+ getProjectLocalScopedOwnershipIds(roots, projectRoot = this.projectRoot) {
9044
9135
  const chunkIds = /* @__PURE__ */ new Set();
9045
9136
  const symbolIds = /* @__PURE__ */ new Set();
9046
9137
  if (!this.database) {
@@ -9048,10 +9139,10 @@ var Indexer = class _Indexer {
9048
9139
  }
9049
9140
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
9050
9141
  ...Array.from(this.fileHashCache.keys()).filter(
9051
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
9142
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
9052
9143
  ),
9053
9144
  ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
9054
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
9145
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
9055
9146
  )
9056
9147
  ]);
9057
9148
  for (const filePath of projectLocalFilePaths) {
@@ -9064,15 +9155,16 @@ var Indexer = class _Indexer {
9064
9155
  }
9065
9156
  return { chunkIds, symbolIds };
9066
9157
  }
9067
- getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
9158
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds, projectRoot = this.projectRoot) {
9068
9159
  if (this.config.scope !== "global") {
9069
9160
  return this.getBranchCatalogCleanupKeys();
9070
9161
  }
9071
9162
  const keys = /* @__PURE__ */ new Set();
9072
9163
  const projectChunkIdSet = new Set(projectChunkIds);
9073
9164
  const projectSymbolIdSet = new Set(projectSymbolIds);
9165
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
9074
9166
  for (const branchKey of this.database?.getAllBranches() ?? []) {
9075
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
9167
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
9076
9168
  keys.add(branchKey);
9077
9169
  continue;
9078
9170
  }
@@ -9082,8 +9174,10 @@ var Indexer = class _Indexer {
9082
9174
  keys.add(branchKey);
9083
9175
  }
9084
9176
  }
9085
- for (const branchKey of this.getBranchCatalogCleanupKeys()) {
9086
- keys.add(branchKey);
9177
+ if (projectRoot === this.projectRoot) {
9178
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
9179
+ keys.add(branchKey);
9180
+ }
9087
9181
  }
9088
9182
  return Array.from(keys);
9089
9183
  }
@@ -9091,10 +9185,10 @@ var Indexer = class _Indexer {
9091
9185
  const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
9092
9186
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
9093
9187
  }
9094
- isFileInProjectRoot(filePath) {
9188
+ isFileInProjectRoot(filePath, projectRoot = this.projectRoot) {
9095
9189
  return isPathWithinRoot2(
9096
9190
  this.getCanonicalStoredFilePath(filePath),
9097
- this.getCanonicalPath(this.projectRoot)
9191
+ this.getCanonicalPath(projectRoot)
9098
9192
  );
9099
9193
  }
9100
9194
  clearScopedFileHashCache(roots) {
@@ -9136,12 +9230,12 @@ var Indexer = class _Indexer {
9136
9230
  }
9137
9231
  return false;
9138
9232
  }
9139
- hasForeignScopedBranchData() {
9233
+ hasForeignScopedBranchData(projectRoot = this.projectRoot, roots = this.getScopedRoots(projectRoot)) {
9140
9234
  if (!this.database || this.config.scope !== "global") {
9141
9235
  return false;
9142
9236
  }
9143
- const roots = this.getScopedRoots();
9144
- const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
9237
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
9238
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots, projectRoot);
9145
9239
  return this.database.getAllBranches().some(
9146
9240
  (branchKey) => {
9147
9241
  const branchChunkIds = this.database.getBranchChunkIds(branchKey);
@@ -9150,7 +9244,7 @@ var Indexer = class _Indexer {
9150
9244
  if (!hasBranchData) {
9151
9245
  return false;
9152
9246
  }
9153
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
9247
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
9154
9248
  return false;
9155
9249
  }
9156
9250
  const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
@@ -9159,7 +9253,7 @@ var Indexer = class _Indexer {
9159
9253
  }
9160
9254
  );
9161
9255
  }
9162
- clearSharedIndexProjectData(store, invertedIndex, database, roots) {
9256
+ clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot = this.projectRoot) {
9163
9257
  const allMetadata = store.getAllMetadata();
9164
9258
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
9165
9259
  const filePaths = /* @__PURE__ */ new Set([
@@ -9167,7 +9261,7 @@ var Indexer = class _Indexer {
9167
9261
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
9168
9262
  ]);
9169
9263
  const projectLocalFilePaths = new Set(
9170
- Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath))
9264
+ Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath, projectRoot))
9171
9265
  );
9172
9266
  const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
9173
9267
  for (const filePath of filePaths) {
@@ -9177,7 +9271,7 @@ var Indexer = class _Indexer {
9177
9271
  }
9178
9272
  const removedChunkIdList = Array.from(removedChunkIds);
9179
9273
  const projectLocalChunkIds = new Set(
9180
- scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath)).map(({ key }) => key)
9274
+ scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath, projectRoot)).map(({ key }) => key)
9181
9275
  );
9182
9276
  for (const filePath of projectLocalFilePaths) {
9183
9277
  for (const chunk of database.getChunksByFile(filePath)) {
@@ -9196,7 +9290,8 @@ var Indexer = class _Indexer {
9196
9290
  }
9197
9291
  const branchCleanupKeys = this.getProjectScopedBranchCatalogCleanupKeys(
9198
9292
  Array.from(projectLocalChunkIds),
9199
- Array.from(projectLocalSymbolIds)
9293
+ Array.from(projectLocalSymbolIds),
9294
+ projectRoot
9200
9295
  );
9201
9296
  for (const branchKey of branchCleanupKeys) {
9202
9297
  database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
@@ -9231,29 +9326,96 @@ var Indexer = class _Indexer {
9231
9326
  database.gcOrphanSymbols();
9232
9327
  database.gcOrphanEmbeddings();
9233
9328
  database.gcOrphanChunks();
9234
- store.save();
9235
9329
  this.saveInvertedIndex(invertedIndex);
9330
+ store.save();
9236
9331
  return {
9237
9332
  removedChunkIds: removedChunkIdList,
9238
9333
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
9239
9334
  };
9240
9335
  }
9336
+ getCurrentClearRecoveryState() {
9337
+ if (!this.configuredProviderInfo) {
9338
+ throw new Error("Cannot persist clear recovery state before the embedding provider is initialized");
9339
+ }
9340
+ const compatibility = this.checkCompatibility();
9341
+ const compatibilityDecision = compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible";
9342
+ return {
9343
+ phase: "clearing",
9344
+ embeddingProvider: this.configuredProviderInfo.provider,
9345
+ embeddingModel: this.configuredProviderInfo.modelInfo.model,
9346
+ embeddingDimensions: this.configuredProviderInfo.modelInfo.dimensions,
9347
+ embeddingStrategyVersion: EMBEDDING_STRATEGY_VERSION,
9348
+ compatibilityDecision
9349
+ };
9350
+ }
9351
+ beginClearRecoveryState() {
9352
+ const recovery = this.getCurrentClearRecoveryState();
9353
+ setIndexLockClearRecoveryState(this.requireActiveLease(), recovery);
9354
+ return recovery;
9355
+ }
9356
+ finishClearRecoveryState() {
9357
+ setIndexLockClearRecoveryState(this.requireActiveLease(), null);
9358
+ }
9359
+ matchesCurrentClearRecoveryConfiguration(recovery) {
9360
+ const configuredProviderInfo = this.configuredProviderInfo;
9361
+ return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
9362
+ }
9363
+ hasUnknownLegacyForceIndexClear(owner) {
9364
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && existsSync8(path15.join(this.indexPath, "force-index-phase"));
9365
+ }
9241
9366
  async recoverFromInterruptedIndexingUnlocked(owners) {
9242
9367
  for (const owner of owners) {
9243
9368
  this.logger.warn("Detected interrupted indexing session, recovering...", {
9244
9369
  pid: owner.pid,
9245
9370
  hostname: owner.hostname,
9246
9371
  operation: owner.operation,
9247
- startedAt: owner.startedAt
9372
+ startedAt: owner.startedAt,
9373
+ projectRoot: owner.projectRoot
9248
9374
  });
9249
9375
  }
9250
9376
  if (this.config.scope === "global") {
9251
- if (existsSync8(this.fileHashCachePath)) {
9252
- unlinkSync2(this.fileHashCachePath);
9377
+ const clearScopes = [];
9378
+ for (const owner of owners) {
9379
+ if (this.hasUnknownLegacyForceIndexClear(owner)) {
9380
+ throw new Error(
9381
+ `Cannot automatically recover interrupted force-index ${owner.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
9382
+ );
9383
+ }
9384
+ if (owner.operation === "clear" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1) {
9385
+ throw new Error(
9386
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating recovery state is unknown. The recovery marker was retained for manual inspection.`
9387
+ );
9388
+ }
9389
+ if (owner.clearRecovery === void 0) continue;
9390
+ if (!owner.projectRoot || !owner.scopedRoots || owner.scopedRoots.length === 0) {
9391
+ throw new Error(
9392
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating project scope is unknown. The recovery marker was retained for manual inspection.`
9393
+ );
9394
+ }
9395
+ if (!this.matchesCurrentClearRecoveryConfiguration(owner.clearRecovery)) {
9396
+ throw new Error(
9397
+ `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.`
9398
+ );
9399
+ }
9400
+ clearScopes.push({
9401
+ projectRoot: owner.projectRoot,
9402
+ scopedRoots: owner.scopedRoots,
9403
+ compatibilityDecision: owner.clearRecovery.compatibilityDecision
9404
+ });
9405
+ }
9406
+ if (clearScopes.length > 0) {
9407
+ this.loadFileHashCache();
9408
+ }
9409
+ for (const { projectRoot, scopedRoots, compatibilityDecision } of clearScopes) {
9410
+ this.clearGlobalIndexUnlocked(projectRoot, scopedRoots, compatibilityDecision);
9253
9411
  }
9254
9412
  await this.healthCheckUnlocked();
9413
+ this.logger.info(
9414
+ clearScopes.length > 0 ? "Recovery complete, next index will rebuild all files" : "Recovery complete, next index will resume from the last checkpoint"
9415
+ );
9416
+ return;
9255
9417
  }
9256
- this.logger.info("Recovery complete, next index will re-process all files");
9418
+ this.logger.info("Recovery complete, next index will resume from the last checkpoint");
9257
9419
  }
9258
9420
  *loadSerializedFailedBatches() {
9259
9421
  let warned = false;
@@ -9291,33 +9453,118 @@ var Indexer = class _Indexer {
9291
9453
  state.writer.write(record);
9292
9454
  state.recordsWritten += record.chunks.length;
9293
9455
  }
9294
- finalizeFailedBatchWriteState(state) {
9456
+ finalizeFailedBatchWriteState(state, resolvedChunkIds = /* @__PURE__ */ new Set()) {
9295
9457
  if (state.recordsWritten > 0) {
9296
- state.writer.commit();
9458
+ const seenChunkIds = /* @__PURE__ */ new Set();
9459
+ const retained = [];
9460
+ const records = Array.from(readFailedBatchRecords(state.writer.temporaryPath));
9461
+ for (let i = records.length - 1; i >= 0; i--) {
9462
+ const chunks = records[i].chunks.filter((rawChunk) => {
9463
+ const chunkId = getPendingChunkId(rawChunk);
9464
+ if (chunkId !== null) {
9465
+ if (resolvedChunkIds.has(chunkId)) return false;
9466
+ if (seenChunkIds.has(chunkId)) return false;
9467
+ seenChunkIds.add(chunkId);
9468
+ }
9469
+ return true;
9470
+ });
9471
+ if (chunks.length > 0) {
9472
+ retained.unshift({ ...records[i], chunks });
9473
+ }
9474
+ }
9475
+ state.writer.cleanup();
9476
+ if (retained.length > 0) {
9477
+ writeFailedBatchRecords(this.failedBatchesPath, retained);
9478
+ } else {
9479
+ writeFailedBatchRecords(this.failedBatchesPath, []);
9480
+ this.clearFailedBatchState();
9481
+ }
9297
9482
  return;
9298
9483
  }
9299
- state.writer.cleanup();
9484
+ state.writer.commit();
9300
9485
  this.clearFailedBatchState();
9301
9486
  }
9302
- clearFailedBatchState() {
9303
- if (existsSync8(this.failedBatchesPath)) {
9304
- try {
9305
- unlinkSync2(this.failedBatchesPath);
9306
- } catch {
9307
- }
9308
- }
9487
+ getCheckpointIntervalChunks(totalChunks) {
9488
+ return Math.max(
9489
+ this.checkpointIntervalChunks ?? 2e3,
9490
+ Math.floor(totalChunks / 10)
9491
+ );
9309
9492
  }
9310
- rewriteFailedBatchState(shouldRetain) {
9311
- const state = this.createFailedBatchWriteState();
9312
- try {
9313
- for (const batch of this.loadSerializedFailedBatches()) {
9314
- const retainedChunks = batch.chunks.filter(shouldRetain);
9315
- if (retainedChunks.length > 0) {
9316
- this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
9317
- }
9318
- }
9319
- this.finalizeFailedBatchWriteState(state);
9320
- } catch (error) {
9493
+ checkpointIndexRun(database, store, invertedIndex, failedProcessing, resolvedRetryChunkIds, currentFileHashes, committedFilePaths, scopedRoots, configuredProviderInfo) {
9494
+ if (!this.hasProjectForceReembedPending()) {
9495
+ this.saveIndexMetadata(configuredProviderInfo);
9496
+ this.indexCompatibility = { compatible: true };
9497
+ }
9498
+ database.commitWriteTransaction();
9499
+ database.beginWriteTransaction();
9500
+ this.saveInvertedIndex(invertedIndex);
9501
+ store.save();
9502
+ if (failedProcessing.state.recordsWritten > 0 || failedProcessing.latestById.size > 0 || failedProcessing.discardedExistingRecords) {
9503
+ for (const metadata of failedProcessing.latestById.values()) {
9504
+ const alreadyMaterialized = metadata.chunks.some((rawChunk) => {
9505
+ const chunkId = getPendingChunkId(rawChunk);
9506
+ return chunkId !== null && failedProcessing.materializedRetryIds.has(chunkId);
9507
+ });
9508
+ if (alreadyMaterialized) continue;
9509
+ this.writeFailedBatchRecord(failedProcessing.state, {
9510
+ chunks: metadata.chunks,
9511
+ attemptCount: metadata.attemptCount,
9512
+ error: metadata.error,
9513
+ lastAttempt: metadata.lastAttempt
9514
+ });
9515
+ for (const rawChunk of metadata.chunks) {
9516
+ const chunkId = getPendingChunkId(rawChunk);
9517
+ if (chunkId !== null) {
9518
+ failedProcessing.materializedRetryIds.add(chunkId);
9519
+ }
9520
+ }
9521
+ }
9522
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
9523
+ failedProcessing.state = this.createFailedBatchWriteState();
9524
+ failedProcessing.discardedExistingRecords = false;
9525
+ for (const record of this.loadSerializedFailedBatches()) {
9526
+ for (const rawChunk of record.chunks) {
9527
+ const chunkId = getPendingChunkId(rawChunk);
9528
+ this.writeFailedBatchRecord(failedProcessing.state, { ...record, chunks: [rawChunk] });
9529
+ if (chunkId !== null) {
9530
+ failedProcessing.materializedRetryIds.add(chunkId);
9531
+ }
9532
+ }
9533
+ }
9534
+ }
9535
+ const partialHashes = /* @__PURE__ */ new Map();
9536
+ for (const filePath of committedFilePaths) {
9537
+ const hash = currentFileHashes.get(filePath);
9538
+ if (hash !== void 0) {
9539
+ partialHashes.set(filePath, hash);
9540
+ }
9541
+ }
9542
+ if (scopedRoots) {
9543
+ this.replaceScopedFileHashCache(partialHashes, scopedRoots);
9544
+ } else {
9545
+ this.fileHashCache = partialHashes;
9546
+ this.saveFileHashCache();
9547
+ }
9548
+ }
9549
+ clearFailedBatchState() {
9550
+ if (existsSync8(this.failedBatchesPath)) {
9551
+ try {
9552
+ unlinkSync2(this.failedBatchesPath);
9553
+ } catch {
9554
+ }
9555
+ }
9556
+ }
9557
+ rewriteFailedBatchState(shouldRetain) {
9558
+ const state = this.createFailedBatchWriteState();
9559
+ try {
9560
+ for (const batch of this.loadSerializedFailedBatches()) {
9561
+ const retainedChunks = batch.chunks.filter(shouldRetain);
9562
+ if (retainedChunks.length > 0) {
9563
+ this.writeFailedBatchRecord(state, { ...batch, chunks: retainedChunks });
9564
+ }
9565
+ }
9566
+ this.finalizeFailedBatchWriteState(state);
9567
+ } catch (error) {
9321
9568
  state.writer.cleanup();
9322
9569
  throw error;
9323
9570
  }
@@ -9325,6 +9572,7 @@ var Indexer = class _Indexer {
9325
9572
  prepareFailedBatchProcessing(roots, shouldProcess) {
9326
9573
  const state = this.createFailedBatchWriteState();
9327
9574
  const latestById = /* @__PURE__ */ new Map();
9575
+ let discardedExistingRecords = false;
9328
9576
  try {
9329
9577
  for (const batch of this.loadSerializedFailedBatches()) {
9330
9578
  for (const rawChunk of batch.chunks) {
@@ -9335,10 +9583,12 @@ var Indexer = class _Indexer {
9335
9583
  continue;
9336
9584
  }
9337
9585
  if (!shouldProcess(filePath)) {
9586
+ discardedExistingRecords = true;
9338
9587
  continue;
9339
9588
  }
9340
9589
  const chunkId = getPendingChunkId(rawChunk);
9341
9590
  if (!chunkId) {
9591
+ discardedExistingRecords = true;
9342
9592
  continue;
9343
9593
  }
9344
9594
  const existing = latestById.get(chunkId);
@@ -9346,12 +9596,18 @@ var Indexer = class _Indexer {
9346
9596
  latestById.set(chunkId, {
9347
9597
  attemptCount: batch.attemptCount,
9348
9598
  error: batch.error,
9349
- lastAttempt: batch.lastAttempt
9599
+ lastAttempt: batch.lastAttempt,
9600
+ chunks: [rawChunk]
9350
9601
  });
9351
9602
  }
9352
9603
  }
9353
9604
  }
9354
- return { state, latestById };
9605
+ return {
9606
+ state,
9607
+ latestById,
9608
+ materializedRetryIds: /* @__PURE__ */ new Set(),
9609
+ discardedExistingRecords
9610
+ };
9355
9611
  } catch (error) {
9356
9612
  state.writer.cleanup();
9357
9613
  throw error;
@@ -9387,10 +9643,34 @@ var Indexer = class _Indexer {
9387
9643
  }
9388
9644
  }
9389
9645
  }
9646
+ restoreMissingChunkRows(database, chunks) {
9647
+ const missing = [];
9648
+ for (const chunk of chunks) {
9649
+ if (database.getChunk(chunk.id)) {
9650
+ continue;
9651
+ }
9652
+ missing.push({
9653
+ chunkId: chunk.id,
9654
+ contentHash: chunk.contentHash,
9655
+ filePath: chunk.metadata.filePath,
9656
+ startLine: chunk.metadata.startLine,
9657
+ endLine: chunk.metadata.endLine,
9658
+ nodeType: chunk.metadata.chunkType,
9659
+ name: chunk.metadata.name,
9660
+ language: chunk.metadata.language,
9661
+ blameSha: chunk.metadata.blameSha,
9662
+ blameAuthor: chunk.metadata.blameAuthor,
9663
+ blameAuthorEmail: chunk.metadata.blameAuthorEmail,
9664
+ blameCommittedAt: chunk.metadata.blameCommittedAt,
9665
+ blameSummary: chunk.metadata.blameSummary
9666
+ });
9667
+ }
9668
+ if (missing.length > 0) {
9669
+ database.upsertChunksBatch(missing);
9670
+ }
9671
+ }
9390
9672
  getProviderRateLimits(provider) {
9391
9673
  switch (provider) {
9392
- case "github-copilot":
9393
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
9394
9674
  case "openai":
9395
9675
  return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
9396
9676
  case "google":
@@ -9459,16 +9739,17 @@ var Indexer = class _Indexer {
9459
9739
  const embeddingPartsByChunk = /* @__PURE__ */ new Map();
9460
9740
  const completedVectorsByChunkId = /* @__PURE__ */ new Map();
9461
9741
  const completedChunkIds = /* @__PURE__ */ new Set();
9462
- const requestBatches = createPendingEmbeddingRequestBatches(
9463
- chunksNeedingEmbedding,
9464
- getDynamicBatchOptions(options.configuredProviderInfo)
9465
- );
9742
+ const batchOptions = getDynamicBatchOptions(options.configuredProviderInfo, this.config.embedding?.batch);
9743
+ if (options.forceSingleItemBatches && options.configuredProviderInfo.provider === "ollama") {
9744
+ batchOptions.maxBatchItems = 1;
9745
+ }
9746
+ const requestBatches = createPendingEmbeddingRequestBatches(chunksNeedingEmbedding, batchOptions);
9466
9747
  let fatalError;
9467
9748
  for (const requestBatch of requestBatches) {
9468
9749
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
9469
9750
  const task = options.queue.add(async () => {
9470
9751
  if (options.rateLimitState.backoffMs > 0) {
9471
- await new Promise((resolve18) => setTimeout(resolve18, options.rateLimitState.backoffMs));
9752
+ await new Promise((resolve20) => setTimeout(resolve20, options.rateLimitState.backoffMs));
9472
9753
  }
9473
9754
  try {
9474
9755
  const embeddingResult = await pRetry(
@@ -10025,7 +10306,7 @@ var Indexer = class _Indexer {
10025
10306
  }
10026
10307
  if (!this.configuredProviderInfo) {
10027
10308
  throw new Error(
10028
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
10309
+ "No embedding provider available. Configure OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
10029
10310
  );
10030
10311
  }
10031
10312
  this.logger.info("Initializing indexer", {
@@ -10035,15 +10316,6 @@ var Indexer = class _Indexer {
10035
10316
  rerankerEnabled: this.config.reranker?.enabled ?? false
10036
10317
  });
10037
10318
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
10038
- if (this.config.reranker?.enabled) {
10039
- this.reranker = createReranker(this.config.reranker);
10040
- if (this.reranker.isAvailable()) {
10041
- this.logger.info("Reranker initialized", {
10042
- model: this.config.reranker.model,
10043
- baseUrl: this.config.reranker.baseUrl
10044
- });
10045
- }
10046
- }
10047
10319
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
10048
10320
  const storePath = path15.join(this.indexPath, "vectors");
10049
10321
  const vectorMetadataPath = `${storePath}.meta.json`;
@@ -10065,7 +10337,20 @@ var Indexer = class _Indexer {
10065
10337
  ]);
10066
10338
  }
10067
10339
  if (recoveredOwners.length > 0 && this.config.scope === "project") {
10068
- await this.resetLocalIndexArtifacts();
10340
+ const unknownLegacyForceIndex = recoveredOwners.find(
10341
+ (owner) => this.hasUnknownLegacyForceIndexClear(owner)
10342
+ );
10343
+ if (unknownLegacyForceIndex) {
10344
+ throw new Error(
10345
+ `Cannot automatically recover interrupted force-index ${unknownLegacyForceIndex.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
10346
+ );
10347
+ }
10348
+ const shouldReset = recoveredOwners.some(
10349
+ (owner) => owner.clearRecovery !== void 0 || owner.operation === "clear" && owner.recoveryProtocolVersion !== 1
10350
+ );
10351
+ if (shouldReset) {
10352
+ await this.resetLocalIndexArtifacts();
10353
+ }
10069
10354
  }
10070
10355
  this.store = new VectorStore(storePath, dimensions);
10071
10356
  if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
@@ -10701,7 +10986,17 @@ var Indexer = class _Indexer {
10701
10986
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
10702
10987
  for (const file of files) {
10703
10988
  const storedPath = this.toStoredFilePath(file.path);
10704
- const currentHash = hashFile(file.path);
10989
+ let currentHash;
10990
+ try {
10991
+ currentHash = hashFile(file.path);
10992
+ } catch (error) {
10993
+ stats.skippedFiles.push({ path: this.toCanonicalFilePath(file.path), reason: "unreadable" });
10994
+ this.logger.warn("Skipped unreadable file during indexing", {
10995
+ path: file.path,
10996
+ error: getErrorMessage3(error)
10997
+ });
10998
+ continue;
10999
+ }
10705
11000
  currentFileHashes.set(storedPath, currentHash);
10706
11001
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
10707
11002
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
@@ -10709,7 +11004,8 @@ var Indexer = class _Indexer {
10709
11004
  );
10710
11005
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path15.extname(storedPath).toLowerCase() === ".swift";
10711
11006
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path15.extname(storedPath).toLowerCase() === ".metal";
10712
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
11007
+ const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
11008
+ if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
10713
11009
  unchangedFilePaths.add(storedPath);
10714
11010
  this.logger.recordCacheHit();
10715
11011
  } else {
@@ -10835,6 +11131,9 @@ var Indexer = class _Indexer {
10835
11131
  }
10836
11132
  }
10837
11133
  let processedChangedFiles = 0;
11134
+ let lastCheckpointChunks = 0;
11135
+ const committedFilePaths = new Set(unchangedFilePaths);
11136
+ const resolvedRetryChunkIds = /* @__PURE__ */ new Set();
10838
11137
  for (const descriptorBatch of iterateOrderedFileBatches(
10839
11138
  changedFileDescriptors,
10840
11139
  (descriptor) => descriptor.sourceBytes,
@@ -10848,7 +11147,7 @@ var Indexer = class _Indexer {
10848
11147
  const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
10849
11148
  const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
10850
11149
  const parseStartTime = performance2.now();
10851
- const parsedFiles = parseFiles(loadedFiles);
11150
+ const parsedFiles = parseFiles(loadedFiles, this.config.indexing.linesPerChunk);
10852
11151
  const parseMs = performance2.now() - parseStartTime;
10853
11152
  this.logger.recordFilesParsed(parsedFiles.length);
10854
11153
  this.logger.recordParseDuration(parseMs);
@@ -10871,7 +11170,7 @@ var Indexer = class _Indexer {
10871
11170
  }
10872
11171
  let chunksToProcess = parsed.chunks;
10873
11172
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
10874
- chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
11173
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content, this.config.indexing.linesPerChunk);
10875
11174
  }
10876
11175
  chunksToProcess = selectIndexableChunks(
10877
11176
  chunksToProcess,
@@ -11005,6 +11304,10 @@ var Indexer = class _Indexer {
11005
11304
  }
11006
11305
  if (symbolBatch.length > 0) {
11007
11306
  database.upsertSymbolsBatch(symbolBatch);
11307
+ database.addSymbolsToBranchBatch(
11308
+ this.getBranchCatalogKey(),
11309
+ symbolBatch.map((symbol) => symbol.id)
11310
+ );
11008
11311
  }
11009
11312
  if (edgeBatch.length > 0) {
11010
11313
  database.upsertCallEdgesBatch(edgeBatch);
@@ -11040,6 +11343,12 @@ var Indexer = class _Indexer {
11040
11343
  forceReembed: forceScopedReembed,
11041
11344
  reuseCachedEmbeddings: true,
11042
11345
  incrementRepeatedFailures: true,
11346
+ onSucceeded: (succeededChunks) => {
11347
+ database.addChunksToBranchBatch(
11348
+ this.getBranchCatalogKey(),
11349
+ succeededChunks.map((chunk) => chunk.id)
11350
+ );
11351
+ },
11043
11352
  onProgress: (batchProgress) => onProgress?.({
11044
11353
  phase: "embedding",
11045
11354
  filesProcessed: unchangedFilePaths.size + processedChangedFiles,
@@ -11058,6 +11367,27 @@ var Indexer = class _Indexer {
11058
11367
  }
11059
11368
  }
11060
11369
  }
11370
+ for (const descriptor of descriptorBatch) {
11371
+ const existingFileChunks = existingChunksByFile.get(descriptor.storedPath);
11372
+ if (!existingFileChunks || existingFileChunks.size === 0) {
11373
+ committedFilePaths.add(descriptor.storedPath);
11374
+ }
11375
+ }
11376
+ const checkpointInterval = this.getCheckpointIntervalChunks(stats.totalChunks);
11377
+ if (stats.totalChunks - lastCheckpointChunks >= checkpointInterval) {
11378
+ lastCheckpointChunks = stats.totalChunks;
11379
+ this.checkpointIndexRun(
11380
+ database,
11381
+ store,
11382
+ invertedIndex,
11383
+ failedProcessing,
11384
+ resolvedRetryChunkIds,
11385
+ currentFileHashes,
11386
+ committedFilePaths,
11387
+ scopedRoots,
11388
+ configuredProviderInfo
11389
+ );
11390
+ }
11061
11391
  }
11062
11392
  const retryableFailedChunks = this.iterateLatestFailedChunks(
11063
11393
  failedProcessing.latestById,
@@ -11078,6 +11408,7 @@ var Indexer = class _Indexer {
11078
11408
  retryableChunksWithExistingData.add(chunk.id);
11079
11409
  }
11080
11410
  }
11411
+ this.restoreMissingChunkRows(database, pendingChunks);
11081
11412
  stats.totalChunks += pendingChunks.length;
11082
11413
  onProgress?.({
11083
11414
  phase: "embedding",
@@ -11100,6 +11431,17 @@ var Indexer = class _Indexer {
11100
11431
  forceReembed: forceScopedReembed,
11101
11432
  reuseCachedEmbeddings: true,
11102
11433
  incrementRepeatedFailures: true,
11434
+ forceSingleItemBatches: true,
11435
+ onSucceeded: (succeededChunks) => {
11436
+ database.addChunksToBranchBatch(
11437
+ this.getBranchCatalogKey(),
11438
+ succeededChunks.map((chunk) => chunk.id)
11439
+ );
11440
+ for (const chunk of succeededChunks) {
11441
+ failedProcessing.latestById.delete(chunk.id);
11442
+ resolvedRetryChunkIds.add(chunk.id);
11443
+ }
11444
+ },
11103
11445
  onProgress: (batchProgress) => onProgress?.({
11104
11446
  phase: "embedding",
11105
11447
  filesProcessed: files.length,
@@ -11117,6 +11459,20 @@ var Indexer = class _Indexer {
11117
11459
  failedForcedChunkIds.add(chunkId);
11118
11460
  }
11119
11461
  }
11462
+ if (stats.totalChunks - lastCheckpointChunks >= this.getCheckpointIntervalChunks(stats.totalChunks)) {
11463
+ lastCheckpointChunks = stats.totalChunks;
11464
+ this.checkpointIndexRun(
11465
+ database,
11466
+ store,
11467
+ invertedIndex,
11468
+ failedProcessing,
11469
+ resolvedRetryChunkIds,
11470
+ currentFileHashes,
11471
+ committedFilePaths,
11472
+ scopedRoots,
11473
+ configuredProviderInfo
11474
+ );
11475
+ }
11120
11476
  }
11121
11477
  const removedChunkIds = [];
11122
11478
  for (const [chunkId] of existingChunks) {
@@ -11153,13 +11509,6 @@ var Indexer = class _Indexer {
11153
11509
  if (removedStoredChunks) {
11154
11510
  this.saveInvertedIndex(invertedIndex);
11155
11511
  }
11156
- if (scopedRoots) {
11157
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11158
- } else {
11159
- this.fileHashCache = currentFileHashes;
11160
- this.saveFileHashCache();
11161
- }
11162
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11163
11512
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11164
11513
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11165
11514
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11168,6 +11517,13 @@ var Indexer = class _Indexer {
11168
11517
  this.indexCompatibility = { compatible: true };
11169
11518
  database.commitWriteTransaction();
11170
11519
  writeTransactionActive = false;
11520
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11521
+ if (scopedRoots) {
11522
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11523
+ } else {
11524
+ this.fileHashCache = currentFileHashes;
11525
+ this.saveFileHashCache();
11526
+ }
11171
11527
  stats.durationMs = Date.now() - startTime;
11172
11528
  onProgress?.({
11173
11529
  phase: "complete",
@@ -11191,13 +11547,6 @@ var Indexer = class _Indexer {
11191
11547
  );
11192
11548
  store.save();
11193
11549
  this.saveInvertedIndex(invertedIndex);
11194
- if (scopedRoots) {
11195
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11196
- } else {
11197
- this.fileHashCache = currentFileHashes;
11198
- this.saveFileHashCache();
11199
- }
11200
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11201
11550
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11202
11551
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11203
11552
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11206,6 +11555,13 @@ var Indexer = class _Indexer {
11206
11555
  this.indexCompatibility = { compatible: true };
11207
11556
  database.commitWriteTransaction();
11208
11557
  writeTransactionActive = false;
11558
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11559
+ if (scopedRoots) {
11560
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11561
+ } else {
11562
+ this.fileHashCache = currentFileHashes;
11563
+ this.saveFileHashCache();
11564
+ }
11209
11565
  stats.durationMs = Date.now() - startTime;
11210
11566
  onProgress?.({
11211
11567
  phase: "complete",
@@ -11240,15 +11596,15 @@ var Indexer = class _Indexer {
11240
11596
  );
11241
11597
  store.save();
11242
11598
  this.saveInvertedIndex(invertedIndex);
11599
+ database.commitWriteTransaction();
11600
+ writeTransactionActive = false;
11601
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11243
11602
  if (scopedRoots) {
11244
11603
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
11245
11604
  } else {
11246
11605
  this.fileHashCache = currentFileHashes;
11247
11606
  this.saveFileHashCache();
11248
11607
  }
11249
- this.finalizeFailedBatchWriteState(failedProcessing.state);
11250
- database.commitWriteTransaction();
11251
- writeTransactionActive = false;
11252
11608
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
11253
11609
  const gcReset = await this.maybeRunOrphanGc();
11254
11610
  if (gcReset) {
@@ -11272,6 +11628,9 @@ var Indexer = class _Indexer {
11272
11628
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
11273
11629
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
11274
11630
  }
11631
+ if (forceScopedReembed) {
11632
+ database.setMetadata(this.getProjectMigrationFinalizedMetadataKey(), "true");
11633
+ }
11275
11634
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
11276
11635
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
11277
11636
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -11382,26 +11741,41 @@ var Indexer = class _Indexer {
11382
11741
  shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
11383
11742
  };
11384
11743
  }
11385
- searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
11744
+ searchCandidatesWithAllowedIds(initialLimit, totalCount, allowedChunkIds, shouldPrefilter, search, getChunkId) {
11386
11745
  const normalizedLimit = Math.max(0, Math.floor(initialLimit));
11387
11746
  if (normalizedLimit === 0) return [];
11388
- if (!shouldPrefilterByBranch || !branchChunkIds) {
11747
+ if (!shouldPrefilter || !allowedChunkIds) {
11389
11748
  return search(normalizedLimit);
11390
11749
  }
11391
- const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
11750
+ const targetCount = Math.min(normalizedLimit, allowedChunkIds.size);
11392
11751
  if (targetCount === 0 || totalCount === 0) return [];
11393
11752
  let requestedLimit = Math.min(normalizedLimit, totalCount);
11394
11753
  while (true) {
11395
11754
  const results = search(requestedLimit);
11396
- const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
11397
- if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
11398
- return branchResults;
11755
+ const allowedResults = results.filter((candidate) => allowedChunkIds.has(getChunkId(candidate)));
11756
+ if (allowedResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
11757
+ return allowedResults;
11399
11758
  }
11400
11759
  const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
11401
- if (nextLimit === requestedLimit) return branchResults;
11760
+ if (nextLimit === requestedLimit) return allowedResults;
11402
11761
  requestedLimit = nextLimit;
11403
11762
  }
11404
11763
  }
11764
+ getTemporalChunkIds(database, options) {
11765
+ if (!options?.blameSince && !options?.blameUntil) return null;
11766
+ const since = options.blameSince ? parseBlameTimestamp(options.blameSince, false) : void 0;
11767
+ const until = options.blameUntil ? parseBlameTimestamp(options.blameUntil, true) : void 0;
11768
+ if (since === null || until === null) {
11769
+ return /* @__PURE__ */ new Set();
11770
+ }
11771
+ return new Set(database.getChunkIdsByBlameDate(since, until));
11772
+ }
11773
+ intersectChunkIdSets(first, second) {
11774
+ if (first === null) return second;
11775
+ if (second === null) return first;
11776
+ const [smaller, larger] = first.size <= second.size ? [first, second] : [second, first];
11777
+ return new Set(Array.from(smaller).filter((chunkId) => larger.has(chunkId)));
11778
+ }
11405
11779
  buildCandidateSnapshot(candidate) {
11406
11780
  return {
11407
11781
  id: candidate.id,
@@ -11416,13 +11790,16 @@ var Indexer = class _Indexer {
11416
11790
  buildCandidateSnapshotList(candidates) {
11417
11791
  return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
11418
11792
  }
11419
- searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
11420
- return this.searchCandidatesWithBranchPrefilter(
11421
- initialLimit,
11422
- store.count(),
11793
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch, temporalChunkIds) {
11794
+ const availableCount = temporalChunkIds?.size ?? store.count();
11795
+ if (availableCount === 0) return [];
11796
+ const allowedIds = temporalChunkIds === null ? void 0 : Array.from(temporalChunkIds);
11797
+ return this.searchCandidatesWithAllowedIds(
11798
+ Math.min(initialLimit, availableCount),
11799
+ availableCount,
11423
11800
  branchChunkIds,
11424
11801
  shouldPrefilterByBranch,
11425
- (requestedLimit) => store.search(embedding, requestedLimit),
11802
+ (requestedLimit) => store.search(embedding, requestedLimit, allowedIds),
11426
11803
  (candidate) => candidate.id
11427
11804
  );
11428
11805
  }
@@ -11447,7 +11824,9 @@ var Indexer = class _Indexer {
11447
11824
  const rerankTopN = this.config.search.rerankTopN;
11448
11825
  const filterByBranch = options?.filterByBranch ?? true;
11449
11826
  const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
11827
+ const prioritizeSourcePaths = sourceIntent || options?.prioritizeSourcePaths === true;
11450
11828
  const identifierHints = extractIdentifierHints(query);
11829
+ const candidateLimit = maxResults * (prioritizeSourcePaths ? 12 : 4);
11451
11830
  this.logger.search("debug", "Starting search", {
11452
11831
  query,
11453
11832
  maxResults,
@@ -11478,25 +11857,28 @@ var Indexer = class _Indexer {
11478
11857
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
11479
11858
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
11480
11859
  }
11860
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
11481
11861
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
11482
11862
  const prefilterMs = performance2.now() - prefilterStartTime;
11483
11863
  const vectorStartTime = performance2.now();
11484
11864
  const semanticCandidates = embedding ? this.searchSemanticCandidates(
11485
11865
  store,
11486
11866
  embedding,
11487
- maxResults * 4,
11867
+ candidateLimit,
11488
11868
  branchChunkIds,
11489
- shouldPrefilterByBranch
11869
+ shouldPrefilterByBranch,
11870
+ temporalChunkIds
11490
11871
  ) : [];
11491
11872
  const vectorMs = performance2.now() - vectorStartTime;
11492
11873
  const keywordStartTime = performance2.now();
11493
11874
  const keywordCandidates = await this.keywordSearch(
11494
11875
  query,
11495
- maxResults * 4,
11876
+ candidateLimit,
11496
11877
  store,
11497
11878
  invertedIndex,
11498
11879
  branchChunkIds,
11499
- shouldPrefilterByBranch
11880
+ shouldPrefilterByBranch,
11881
+ temporalChunkIds
11500
11882
  );
11501
11883
  const keywordMs = performance2.now() - keywordStartTime;
11502
11884
  const scopedSemanticCandidates = semanticCandidates.filter(
@@ -11518,7 +11900,7 @@ var Indexer = class _Indexer {
11518
11900
  rerankTopN,
11519
11901
  limit: maxResults,
11520
11902
  hybridWeight: rankingHybridWeight,
11521
- prioritizeSourcePaths: sourceIntent
11903
+ prioritizeSourcePaths
11522
11904
  });
11523
11905
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
11524
11906
  definitionIntent: options?.definitionIntent === true,
@@ -11554,10 +11936,11 @@ var Indexer = class _Indexer {
11554
11936
  branchSymbolIds,
11555
11937
  maxResults,
11556
11938
  union,
11557
- sourceIntent
11939
+ sourceIntent,
11940
+ options?.definitionIntent === true && ((options.directory?.trim().length ?? 0) > 0 || (options.fileType?.trim().length ?? 0) > 0)
11558
11941
  );
11559
11942
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
11560
- const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
11943
+ const primaryLane = options?.definitionIntent === true ? mergeTieredResults(symbolLane, prePrimaryLane, maxResults * 4) : mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
11561
11944
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
11562
11945
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
11563
11946
  const baseFiltered = tiered.filter(
@@ -11652,14 +12035,18 @@ var Indexer = class _Indexer {
11652
12035
  })
11653
12036
  );
11654
12037
  }
11655
- async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
12038
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false, temporalChunkIds = null) {
11656
12039
  const normalizedLimit = Math.max(0, Math.floor(limit));
11657
12040
  if (normalizedLimit === 0) return [];
11658
- const scoreEntries = this.searchCandidatesWithBranchPrefilter(
12041
+ const allowedChunkIds = this.intersectChunkIdSets(
12042
+ shouldPrefilterByBranch ? branchChunkIds : null,
12043
+ temporalChunkIds
12044
+ );
12045
+ const scoreEntries = this.searchCandidatesWithAllowedIds(
11659
12046
  normalizedLimit,
11660
12047
  invertedIndex.getDocumentCount(),
11661
- branchChunkIds,
11662
- shouldPrefilterByBranch,
12048
+ allowedChunkIds,
12049
+ allowedChunkIds !== null,
11663
12050
  (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
11664
12051
  ([chunkId]) => chunkId
11665
12052
  );
@@ -11744,7 +12131,17 @@ var Indexer = class _Indexer {
11744
12131
  );
11745
12132
  const currentFileHashes = /* @__PURE__ */ new Map();
11746
12133
  for (const file of files) {
11747
- currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
12134
+ let hash;
12135
+ try {
12136
+ hash = hashFile(file.path);
12137
+ } catch (error) {
12138
+ this.logger.warn("Skipped unreadable file during freshness check", {
12139
+ path: file.path,
12140
+ error: getErrorMessage3(error)
12141
+ });
12142
+ return { readable: false, current: false, reason: "unreadable" };
12143
+ }
12144
+ currentFileHashes.set(this.toStoredFilePath(file.path), hash);
11748
12145
  }
11749
12146
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
11750
12147
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -11770,69 +12167,87 @@ var Indexer = class _Indexer {
11770
12167
  async forceIndex(onProgress) {
11771
12168
  return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
11772
12169
  await this.ensureInitializedUnlocked(recoveredOwners);
11773
- await this.clearIndexUnlocked();
12170
+ const recovery = this.beginClearRecoveryState();
12171
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
12172
+ this.finishClearRecoveryState();
11774
12173
  return this.indexUnlocked(onProgress, [], true);
11775
12174
  });
11776
12175
  }
11777
12176
  async clearIndex() {
11778
12177
  await this.withIndexMutationLease("clear", async (recoveredOwners) => {
11779
12178
  await this.ensureInitializedUnlocked(recoveredOwners);
11780
- await this.clearIndexUnlocked();
12179
+ const recovery = this.beginClearRecoveryState();
12180
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
11781
12181
  });
11782
12182
  }
11783
- async clearIndexUnlocked() {
12183
+ clearGlobalIndexDataUnlocked(projectRoot = this.projectRoot) {
11784
12184
  const { store, invertedIndex, database } = this.requireLoadedIndexState();
11785
- if (this.config.scope === "global") {
11786
- store.load();
11787
- invertedIndex.load();
11788
- this.loadFileHashCache();
11789
- const roots = this.getScopedRoots();
11790
- const compatibility = this.checkCompatibility();
11791
- const allMetadata = store.getAllMetadata();
11792
- const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
11793
- if (!compatibility.compatible && hasForeignData) {
11794
- if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
11795
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
11796
- this.clearScopedFileHashCache(roots);
11797
- this.clearScopedFailedBatches(roots);
11798
- database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
11799
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
12185
+ const clearedBranchKeys = database.getAllBranches();
12186
+ store.clear();
12187
+ store.save();
12188
+ invertedIndex.clear();
12189
+ this.saveInvertedIndex(invertedIndex);
12190
+ this.fileHashCache.clear();
12191
+ this.saveFileHashCache();
12192
+ database.clearAllIndexedData();
12193
+ this.deleteBranchCommitMetadata(database, clearedBranchKeys);
12194
+ this.clearFailedBatchState();
12195
+ database.deleteMetadata("index.version");
12196
+ database.deleteMetadata("index.pathStorageVersion");
12197
+ database.deleteMetadata("index.embeddingProvider");
12198
+ database.deleteMetadata("index.embeddingModel");
12199
+ database.deleteMetadata("index.embeddingDimensions");
12200
+ database.deleteMetadata("index.embeddingStrategyVersion");
12201
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
12202
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
12203
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash));
12204
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey(projectIdentityHash));
12205
+ database.deleteMetadata("index.createdAt");
12206
+ database.deleteMetadata("index.updatedAt");
12207
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
12208
+ }
12209
+ clearGlobalIndexUnlocked(projectRoot = this.projectRoot, roots = this.getScopedRoots(), recoveryDecision) {
12210
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
12211
+ store.load();
12212
+ invertedIndex.load();
12213
+ this.loadFileHashCache();
12214
+ const compatibility = this.checkCompatibility();
12215
+ const compatibilityDecision = recoveryDecision ?? (compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible");
12216
+ const allMetadata = store.getAllMetadata();
12217
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData(projectRoot, roots) || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
12218
+ if (compatibilityDecision !== "compatible" && hasForeignData) {
12219
+ if (compatibilityDecision === "embedding-strategy-mismatch") {
12220
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
12221
+ this.clearScopedFileHashCache(roots);
12222
+ this.clearScopedFailedBatches(roots);
12223
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
12224
+ database.setMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash), "true");
12225
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
12226
+ database.deleteMetadata(this.getProjectMigrationFinalizedMetadataKey(projectIdentityHash));
12227
+ if (projectRoot === this.projectRoot) {
11800
12228
  this.indexCompatibility = { compatible: true };
11801
- return;
11802
12229
  }
11803
- throw new Error(
11804
- `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.`
11805
- );
11806
- }
11807
- if (!hasForeignData) {
11808
- const clearedBranchKeys2 = database.getAllBranches();
11809
- store.clear();
11810
- store.save();
11811
- invertedIndex.clear();
11812
- this.saveInvertedIndex(invertedIndex);
11813
- this.fileHashCache.clear();
11814
- this.saveFileHashCache();
11815
- database.clearAllIndexedData();
11816
- this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
11817
- this.clearFailedBatchState();
11818
- database.deleteMetadata("index.version");
11819
- database.deleteMetadata("index.pathStorageVersion");
11820
- database.deleteMetadata("index.embeddingProvider");
11821
- database.deleteMetadata("index.embeddingModel");
11822
- database.deleteMetadata("index.embeddingDimensions");
11823
- database.deleteMetadata("index.embeddingStrategyVersion");
11824
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
11825
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
11826
- database.deleteMetadata(this.getLegacyMigrationMetadataKey());
11827
- database.deleteMetadata("index.createdAt");
11828
- database.deleteMetadata("index.updatedAt");
11829
- this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
11830
12230
  return;
11831
12231
  }
11832
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
11833
- this.clearScopedFileHashCache(roots);
11834
- this.clearScopedFailedBatches(roots);
12232
+ throw new Error(
12233
+ `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.`
12234
+ );
12235
+ }
12236
+ if (!hasForeignData) {
12237
+ this.clearGlobalIndexDataUnlocked(projectRoot);
12238
+ return;
12239
+ }
12240
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
12241
+ this.clearScopedFileHashCache(roots);
12242
+ this.clearScopedFailedBatches(roots);
12243
+ if (projectRoot === this.projectRoot) {
11835
12244
  this.indexCompatibility = compatibility;
12245
+ }
12246
+ }
12247
+ async clearIndexUnlocked(recoveryDecision) {
12248
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
12249
+ if (this.config.scope === "global") {
12250
+ this.clearGlobalIndexUnlocked(this.projectRoot, this.getScopedRoots(), recoveryDecision);
11836
12251
  return;
11837
12252
  }
11838
12253
  if (!this.isProjectOwnedIndexPath()) {
@@ -11998,6 +12413,7 @@ var Indexer = class _Indexer {
11998
12413
  )) {
11999
12414
  const chunks = retryBatch.map(({ chunk }) => chunk);
12000
12415
  const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
12416
+ this.restoreMissingChunkRows(database, chunks);
12001
12417
  const batchResult = await this.processPendingChunkBatch(chunks, {
12002
12418
  store,
12003
12419
  provider,
@@ -12012,6 +12428,7 @@ var Indexer = class _Indexer {
12012
12428
  forceReembed: false,
12013
12429
  reuseCachedEmbeddings: false,
12014
12430
  incrementRepeatedFailures: false,
12431
+ forceSingleItemBatches: true,
12015
12432
  onSucceeded: (succeededChunks) => {
12016
12433
  database.addChunksToBranchBatch(
12017
12434
  this.getBranchCatalogKey(),
@@ -12033,9 +12450,12 @@ var Indexer = class _Indexer {
12033
12450
  this.saveInvertedIndex(invertedIndex);
12034
12451
  }
12035
12452
  if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
12036
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12037
- this.saveIndexMetadata(configuredProviderInfo);
12038
- this.indexCompatibility = { compatible: true };
12453
+ const migrationFinalized = database.getMetadata(this.getProjectMigrationFinalizedMetadataKey()) === "true";
12454
+ if (migrationFinalized) {
12455
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12456
+ this.saveIndexMetadata(configuredProviderInfo);
12457
+ this.indexCompatibility = { compatible: true };
12458
+ }
12039
12459
  }
12040
12460
  return { succeeded, failed, remaining };
12041
12461
  }
@@ -12057,7 +12477,8 @@ var Indexer = class _Indexer {
12057
12477
  latestById.set(chunkId, {
12058
12478
  attemptCount: batch.attemptCount,
12059
12479
  error: batch.error,
12060
- lastAttempt: batch.lastAttempt
12480
+ lastAttempt: batch.lastAttempt,
12481
+ chunks: [rawChunk]
12061
12482
  });
12062
12483
  }
12063
12484
  }
@@ -12124,6 +12545,7 @@ var Indexer = class _Indexer {
12124
12545
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
12125
12546
  );
12126
12547
  }
12548
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
12127
12549
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
12128
12550
  const prefilterMs = performance2.now() - prefilterStartTime;
12129
12551
  const vectorStartTime = performance2.now();
@@ -12132,7 +12554,8 @@ var Indexer = class _Indexer {
12132
12554
  embedding,
12133
12555
  limit * 2,
12134
12556
  branchChunkIds,
12135
- shouldPrefilterByBranch
12557
+ shouldPrefilterByBranch,
12558
+ temporalChunkIds
12136
12559
  );
12137
12560
  const vectorMs = performance2.now() - vectorStartTime;
12138
12561
  if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
@@ -12250,9 +12673,9 @@ var Indexer = class _Indexer {
12250
12673
  this.requireReadableComponents(readIssues, "database");
12251
12674
  let shortest = [];
12252
12675
  for (const branchKey of this.getBranchCatalogKeys()) {
12253
- const path31 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
12254
- if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
12255
- shortest = path31;
12676
+ const path33 = database.findShortestPath(fromName, toName, branchKey, maxDepth);
12677
+ if (path33.length > 0 && (shortest.length === 0 || path33.length < shortest.length)) {
12678
+ shortest = path33;
12256
12679
  }
12257
12680
  }
12258
12681
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -12300,13 +12723,13 @@ var Indexer = class _Indexer {
12300
12723
  }
12301
12724
  }
12302
12725
  if (!found) continue;
12303
- const path31 = [];
12726
+ const path33 = [];
12304
12727
  let currentSymbolId = toSymbolId;
12305
12728
  while (true) {
12306
12729
  const symbol = symbolsById.get(currentSymbolId);
12307
12730
  if (!symbol) break;
12308
12731
  const parent = parentBySymbolId.get(currentSymbolId);
12309
- path31.push({
12732
+ path33.push({
12310
12733
  symbolId: symbol.id,
12311
12734
  symbolName: symbol.name,
12312
12735
  filePath: symbol.filePath,
@@ -12316,9 +12739,9 @@ var Indexer = class _Indexer {
12316
12739
  if (!parent) break;
12317
12740
  currentSymbolId = parent.parentId;
12318
12741
  }
12319
- path31.reverse();
12320
- if (path31.length > 0 && (shortest.length === 0 || path31.length < shortest.length)) {
12321
- shortest = path31;
12742
+ path33.reverse();
12743
+ if (path33.length > 0 && (shortest.length === 0 || path33.length < shortest.length)) {
12744
+ shortest = path33;
12322
12745
  }
12323
12746
  }
12324
12747
  return shortest.map((hop) => this.resolveFilePathRecord(hop));
@@ -12654,7 +13077,6 @@ var Indexer = class _Indexer {
12654
13077
  this.store = null;
12655
13078
  this.invertedIndex = null;
12656
13079
  this.provider = null;
12657
- this.reranker = null;
12658
13080
  this.configuredProviderInfo = null;
12659
13081
  this.indexCompatibility = null;
12660
13082
  this.initializationMode = "none";
@@ -12715,6 +13137,36 @@ function resolveConfigPathValue(value, baseDir) {
12715
13137
  const absolutePath = path16.isAbsolute(trimmed) ? trimmed : path16.resolve(baseDir, trimmed);
12716
13138
  return path16.normalize(absolutePath);
12717
13139
  }
13140
+ function serializeConfigPathValue(value, baseDir) {
13141
+ const trimmed = value.trim();
13142
+ if (!trimmed) {
13143
+ return trimmed;
13144
+ }
13145
+ if (!path16.isAbsolute(trimmed)) {
13146
+ return normalizePathSeparators(path16.normalize(trimmed));
13147
+ }
13148
+ const relativePath = path16.relative(baseDir, trimmed);
13149
+ if (!relativePath || !relativePath.startsWith("..") && !path16.isAbsolute(relativePath)) {
13150
+ return normalizePathSeparators(path16.normalize(relativePath || "."));
13151
+ }
13152
+ return path16.normalize(trimmed);
13153
+ }
13154
+ function resolveKnowledgeBasePath(value, projectRoot) {
13155
+ return path16.isAbsolute(value) ? value : path16.resolve(projectRoot, value);
13156
+ }
13157
+ function normalizeKnowledgeBasePath(value, projectRoot) {
13158
+ return path16.normalize(resolveKnowledgeBasePath(value, projectRoot));
13159
+ }
13160
+ function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot) {
13161
+ const normalizedInput = path16.normalize(inputPath);
13162
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath(kb, projectRoot) === normalizedInput);
13163
+ }
13164
+ function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot) {
13165
+ const normalizedInput = path16.normalize(inputPath);
13166
+ return knowledgeBases.findIndex(
13167
+ (kb) => path16.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath(kb, projectRoot) === normalizedInput
13168
+ );
13169
+ }
12718
13170
 
12719
13171
  // src/tools/format-communities.ts
12720
13172
  function compareText(left, right) {
@@ -12968,8 +13420,8 @@ function formatExactSearchHandoff(results) {
12968
13420
  }
12969
13421
  function formatContextEvidence(result, index) {
12970
13422
  const symbol = result.name ? ` ${JSON.stringify(compactEvidenceValue(result.name, 80))}` : "";
12971
- const path31 = compactEvidenceValue(result.filePath, 120);
12972
- return `[${index}] ${result.chunkType}${symbol} in ${path31}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
13423
+ const path33 = compactEvidenceValue(result.filePath, 120);
13424
+ return `[${index}] ${result.chunkType}${symbol} in ${path33}:${result.startLine}-${result.endLine} (score ${result.score.toFixed(2)})`;
12973
13425
  }
12974
13426
  function formatContextPack(heading, selected, candidateCount, duplicateCount, limitOmittedCount, budgetOmittedCount, includeExactSearchHandoff) {
12975
13427
  const lines = selected.map((result, index) => formatContextEvidence(result, index + 1));
@@ -13623,7 +14075,7 @@ function getErrorMessage4(error) {
13623
14075
  return error instanceof Error ? error.message : String(error);
13624
14076
  }
13625
14077
  function runCommand(file, args, options) {
13626
- return new Promise((resolve18, reject) => {
14078
+ return new Promise((resolve20, reject) => {
13627
14079
  childProcess.execFile(
13628
14080
  file,
13629
14081
  args,
@@ -13633,7 +14085,7 @@ function runCommand(file, args, options) {
13633
14085
  reject(error);
13634
14086
  return;
13635
14087
  }
13636
- resolve18(stdout);
14088
+ resolve20(stdout);
13637
14089
  }
13638
14090
  );
13639
14091
  });
@@ -13778,10 +14230,10 @@ function safeFailureMessage(error) {
13778
14230
  }
13779
14231
  function cancellableDelay(delayMs, signal) {
13780
14232
  if (signal.aborted) return Promise.reject(new AutoIndexCancelledError());
13781
- return new Promise((resolve18, reject) => {
14233
+ return new Promise((resolve20, reject) => {
13782
14234
  const timer = setTimeout(() => {
13783
14235
  signal.removeEventListener("abort", onAbort);
13784
- resolve18();
14236
+ resolve20();
13785
14237
  }, delayMs);
13786
14238
  timer.unref?.();
13787
14239
  const onAbort = () => {
@@ -13793,15 +14245,15 @@ function cancellableDelay(delayMs, signal) {
13793
14245
  }
13794
14246
  function withTimeout(promise, timeoutMs) {
13795
14247
  if (timeoutMs <= 0) return Promise.resolve(void 0);
13796
- return new Promise((resolve18) => {
13797
- const timer = setTimeout(() => resolve18(void 0), timeoutMs);
14248
+ return new Promise((resolve20) => {
14249
+ const timer = setTimeout(() => resolve20(void 0), timeoutMs);
13798
14250
  timer.unref?.();
13799
14251
  void promise.then((value) => {
13800
14252
  clearTimeout(timer);
13801
- resolve18(value);
14253
+ resolve20(value);
13802
14254
  }, () => {
13803
14255
  clearTimeout(timer);
13804
- resolve18(void 0);
14256
+ resolve20(void 0);
13805
14257
  });
13806
14258
  });
13807
14259
  }
@@ -14183,17 +14635,17 @@ var AutoIndexCoordinator = class {
14183
14635
  }
14184
14636
  }
14185
14637
  waitForBatteryRetry(delayMs) {
14186
- return new Promise((resolve18) => {
14638
+ return new Promise((resolve20) => {
14187
14639
  const timer = setTimeout(() => {
14188
14640
  if (this.batteryRetryTimer === timer) {
14189
14641
  this.batteryRetryTimer = null;
14190
14642
  this.resolveBatteryRetry = null;
14191
14643
  }
14192
- resolve18();
14644
+ resolve20();
14193
14645
  }, delayMs);
14194
14646
  timer.unref?.();
14195
14647
  this.batteryRetryTimer = timer;
14196
- this.resolveBatteryRetry = resolve18;
14648
+ this.resolveBatteryRetry = resolve20;
14197
14649
  });
14198
14650
  }
14199
14651
  cancelBatteryRetry() {
@@ -14201,9 +14653,9 @@ var AutoIndexCoordinator = class {
14201
14653
  clearTimeout(this.batteryRetryTimer);
14202
14654
  this.batteryRetryTimer = null;
14203
14655
  }
14204
- const resolve18 = this.resolveBatteryRetry;
14656
+ const resolve20 = this.resolveBatteryRetry;
14205
14657
  this.resolveBatteryRetry = null;
14206
- resolve18?.();
14658
+ resolve20?.();
14207
14659
  }
14208
14660
  finishBatteryCheck(batteryCheck) {
14209
14661
  if (this.batteryCheck !== batteryCheck) return;
@@ -14421,7 +14873,7 @@ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key
14421
14873
  function mergeUniqueStringArray(values) {
14422
14874
  return [...new Set(values.map((value) => String(value).trim()))];
14423
14875
  }
14424
- function normalizeKnowledgeBasePath(value) {
14876
+ function normalizeKnowledgeBasePath2(value) {
14425
14877
  let normalized = path19.normalize(String(value).trim());
14426
14878
  const root = path19.parse(normalized).root;
14427
14879
  while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
@@ -14430,7 +14882,7 @@ function normalizeKnowledgeBasePath(value) {
14430
14882
  return normalized;
14431
14883
  }
14432
14884
  function mergeKnowledgeBasePaths(values) {
14433
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
14885
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath2(value)).filter((value) => value.length > 0))];
14434
14886
  }
14435
14887
  function validateConfigLayerShape(rawConfig, filePath) {
14436
14888
  if (!isRecord(rawConfig)) {
@@ -14560,9 +15012,30 @@ function toConfigRecord(rawConfig) {
14560
15012
  }
14561
15013
  return { ...rawConfig };
14562
15014
  }
15015
+ function getConfigPath(projectRoot, host) {
15016
+ return resolveWritableProjectConfigPath(projectRoot, host);
15017
+ }
14563
15018
  function loadRuntimeConfig(projectRoot, host) {
14564
15019
  return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot, host)), projectRoot);
14565
15020
  }
15021
+ function loadEditableConfig(projectRoot, host) {
15022
+ return normalizeKnowledgeBasePaths(toConfigRecord(loadProjectConfigLayer(projectRoot, host)), projectRoot);
15023
+ }
15024
+ function saveConfig(projectRoot, config, host) {
15025
+ const configPath = getConfigPath(projectRoot, host);
15026
+ const configDir = path20.dirname(configPath);
15027
+ const configBaseDir = path20.dirname(configDir);
15028
+ if (!existsSync11(configDir)) {
15029
+ mkdirSync5(configDir, { recursive: true });
15030
+ }
15031
+ const serializableConfig = { ...config };
15032
+ if (Array.isArray(serializableConfig.knowledgeBases)) {
15033
+ serializableConfig.knowledgeBases = serializableConfig.knowledgeBases.map(
15034
+ (kb) => serializeConfigPathValue(kb, configBaseDir)
15035
+ );
15036
+ }
15037
+ writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
15038
+ }
14566
15039
 
14567
15040
  // src/tools/operation-runtime.ts
14568
15041
  var indexerCache = /* @__PURE__ */ new Map();
@@ -14781,9 +15254,11 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14781
15254
  contextLines: options.contextLines,
14782
15255
  metadataOnly: options.metadataOnly,
14783
15256
  definitionIntent: options.definitionIntent,
15257
+ prioritizeSourcePaths: options.prioritizeSourcePaths,
14784
15258
  blameAuthor: options.blameAuthor,
14785
15259
  blameSha: options.blameSha,
14786
15260
  blameSince: options.blameSince,
15261
+ blameUntil: options.blameUntil,
14787
15262
  trace: options.trace
14788
15263
  });
14789
15264
  }
@@ -14829,7 +15304,9 @@ async function findSimilarCode(projectRoot, host, code, options = {}) {
14829
15304
  fileType: options.fileType,
14830
15305
  directory: options.directory,
14831
15306
  chunkType: options.chunkType,
14832
- excludeFile: options.excludeFile
15307
+ excludeFile: options.excludeFile,
15308
+ blameSince: options.blameSince,
15309
+ blameUntil: options.blameUntil
14833
15310
  });
14834
15311
  }
14835
15312
  async function implementationLookup(projectRoot, host, query, options = {}) {
@@ -14878,12 +15355,12 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth, fromFileP
14878
15355
  if (fromResolution.status !== "resolved" || toResolution.status !== "resolved") {
14879
15356
  return { from: fromResolution, to: toResolution, path: [] };
14880
15357
  }
14881
- const path31 = await indexer.findCallPathBySymbolIds(
15358
+ const path33 = await indexer.findCallPathBySymbolIds(
14882
15359
  fromResolution.symbolId,
14883
15360
  toResolution.symbolId,
14884
15361
  maxDepth
14885
15362
  );
14886
- return { from: fromResolution, to: toResolution, path: path31 };
15363
+ return { from: fromResolution, to: toResolution, path: path33 };
14887
15364
  }
14888
15365
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
14889
15366
  const root = getProjectRoot(projectRoot, host);
@@ -15075,6 +15552,141 @@ async function getIndexLogs(projectRoot, host, args) {
15075
15552
  }).join("\n");
15076
15553
  return { kind: "entries", text };
15077
15554
  }
15555
+ function addKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15556
+ const root = getProjectRoot(projectRoot, host);
15557
+ const inputPath = knowledgeBasePath.trim();
15558
+ const normalizedPath3 = path21.resolve(
15559
+ path21.isAbsolute(inputPath) ? inputPath : resolveKnowledgeBasePath(inputPath, root)
15560
+ );
15561
+ if (!existsSync12(normalizedPath3)) {
15562
+ return `Error: Directory does not exist: ${normalizedPath3}`;
15563
+ }
15564
+ let realPath;
15565
+ try {
15566
+ realPath = realpathSync5(normalizedPath3);
15567
+ } catch {
15568
+ return `Error: Cannot resolve path: ${normalizedPath3}`;
15569
+ }
15570
+ const blockedPrefixes = [
15571
+ "/etc",
15572
+ "/proc",
15573
+ "/sys",
15574
+ "/dev",
15575
+ "/boot",
15576
+ "/root",
15577
+ "/var/run",
15578
+ "/var/log"
15579
+ ];
15580
+ const homeDir = process.platform === "win32" ? process.env.USERPROFILE ?? "" : process.env.HOME ?? "";
15581
+ const sensitiveDotDirs = [
15582
+ ".ssh",
15583
+ ".gnupg",
15584
+ ".aws",
15585
+ ".config/gcloud",
15586
+ ".docker",
15587
+ ".kube"
15588
+ ];
15589
+ for (const prefix of blockedPrefixes) {
15590
+ if (realPath === prefix || realPath.startsWith(`${prefix}/`)) {
15591
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath3}`;
15592
+ }
15593
+ }
15594
+ for (const dotDir of sensitiveDotDirs) {
15595
+ const sensitiveDir = path21.join(homeDir, dotDir);
15596
+ if (sensitiveDir && (realPath === sensitiveDir || realPath.startsWith(`${sensitiveDir}/`))) {
15597
+ return `Error: Adding sensitive directory as knowledge base is not allowed: ${normalizedPath3}`;
15598
+ }
15599
+ }
15600
+ try {
15601
+ const stat5 = statSync5(normalizedPath3);
15602
+ if (!stat5.isDirectory()) {
15603
+ return `Error: Path is not a directory: ${normalizedPath3}`;
15604
+ }
15605
+ } catch (error) {
15606
+ return `Error: Cannot access directory: ${normalizedPath3} - ${error instanceof Error ? error.message : String(error)}`;
15607
+ }
15608
+ const config = loadEditableConfig(root, host);
15609
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15610
+ const alreadyExists = hasMatchingKnowledgeBasePath(knowledgeBases, normalizedPath3, root);
15611
+ if (alreadyExists) {
15612
+ return `Knowledge base already configured: ${normalizedPath3}`;
15613
+ }
15614
+ knowledgeBases.push(normalizedPath3);
15615
+ config.knowledgeBases = knowledgeBases;
15616
+ saveConfig(root, config, host);
15617
+ refreshIndexerForDirectory(root, host);
15618
+ let result = `${normalizedPath3}
15619
+ `;
15620
+ result += `Total knowledge bases: ${knowledgeBases.length}
15621
+ `;
15622
+ result += `Config path: ${getConfigPath(root, host)}
15623
+ `;
15624
+ result += `
15625
+ Run /index to rebuild the index with the new knowledge base.`;
15626
+ return result;
15627
+ }
15628
+ function listKnowledgeBases(projectRoot, host) {
15629
+ const root = getProjectRoot(projectRoot, host);
15630
+ const config = loadRuntimeConfig(root, host);
15631
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15632
+ if (knowledgeBases.length === 0) {
15633
+ return "No knowledge bases configured. Use add_knowledge_base to add folders.";
15634
+ }
15635
+ let result = `Knowledge Bases (${knowledgeBases.length}):
15636
+
15637
+ `;
15638
+ for (let i = 0; i < knowledgeBases.length; i++) {
15639
+ const kb = knowledgeBases[i];
15640
+ const resolvedPath = resolveKnowledgeBasePath(kb, root);
15641
+ const exists = existsSync12(resolvedPath);
15642
+ result += `[${i + 1}] ${kb}
15643
+ `;
15644
+ result += ` Resolved: ${resolvedPath}
15645
+ `;
15646
+ result += ` Status: ${exists ? "Exists" : "NOT FOUND"}
15647
+ `;
15648
+ if (exists) {
15649
+ try {
15650
+ const stat5 = statSync5(resolvedPath);
15651
+ result += ` Type: ${stat5.isDirectory() ? "Directory" : "File"}
15652
+ `;
15653
+ } catch {
15654
+ }
15655
+ }
15656
+ result += "\n";
15657
+ }
15658
+ const hasHostConfig = existsSync12(path21.join(root, getHostProjectConfigRelativePath(host)));
15659
+ if (hasHostConfig) {
15660
+ result += `
15661
+ Config sources: 1 file(s).`;
15662
+ }
15663
+ result += `
15664
+ Config file: ${getConfigPath(root, host)}`;
15665
+ return result;
15666
+ }
15667
+ function removeKnowledgeBase(projectRoot, host, knowledgeBasePath) {
15668
+ const root = getProjectRoot(projectRoot, host);
15669
+ const config = loadEditableConfig(root, host);
15670
+ const knowledgeBases = Array.isArray(config.knowledgeBases) ? config.knowledgeBases : [];
15671
+ const index = findKnowledgeBasePathIndex(knowledgeBases, knowledgeBasePath, root);
15672
+ if (index === -1) {
15673
+ return `Knowledge base not found: ${knowledgeBasePath}`;
15674
+ }
15675
+ const removed = knowledgeBases.splice(index, 1)[0];
15676
+ config.knowledgeBases = knowledgeBases;
15677
+ saveConfig(root, config, host);
15678
+ refreshIndexerForDirectory(root, host);
15679
+ let result = `Removed: ${removed}
15680
+
15681
+ `;
15682
+ result += `Remaining knowledge bases: ${knowledgeBases.length}
15683
+ `;
15684
+ result += `Config saved to: ${getConfigPath(root, host)}
15685
+ `;
15686
+ result += `
15687
+ Run /index to rebuild the index without the removed knowledge base.`;
15688
+ return result;
15689
+ }
15078
15690
 
15079
15691
  // src/tools/context-search.ts
15080
15692
  var MIN_CONTEXT_RESULT_LIMIT = 1;
@@ -15303,13 +15915,19 @@ async function resolveSearchContext(input, operations) {
15303
15915
  (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
15304
15916
  );
15305
15917
  };
15306
- const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
15918
+ const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt, prioritizeSourcePaths) => {
15307
15919
  return recordAttempt(
15308
15920
  "conceptual",
15309
15921
  searchQuery,
15310
15922
  scope,
15311
15923
  relaxedFieldsForAttempt,
15312
- (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
15924
+ (trace) => operations.search(
15925
+ searchQuery,
15926
+ MAX_CONTEXT_RESULT_LIMIT,
15927
+ scope,
15928
+ input.diagnostic ? trace : void 0,
15929
+ { prioritizeSourcePaths }
15930
+ )
15313
15931
  );
15314
15932
  };
15315
15933
  const findSuccessfulAttemptState = (route) => {
@@ -15437,10 +16055,12 @@ Explicit symbol lookup only; conceptual search was not attempted.`
15437
16055
  }
15438
16056
  }
15439
16057
  for (const attempt of conceptualAttemptPlan) {
16058
+ const attemptIntent = analyzeQueryIntent(attempt.queryText);
16059
+ const prioritizeSourcePaths = attemptIntent.primary !== "docs" && attemptIntent.primary !== "test";
15440
16060
  if (inferredSymbol && attempt.queryText === inferredSymbol && attempt.queryText !== query) {
15441
16061
  decisions.fallbackFromOriginalConceptualToInferred = true;
15442
16062
  }
15443
- const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
16063
+ const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed, prioritizeSourcePaths);
15444
16064
  if (results.length > 0) {
15445
16065
  const heading = buildPackHeading("conceptual", decisions);
15446
16066
  const intent = analyzeQueryIntent(attempt.queryText);
@@ -15508,7 +16128,7 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15508
16128
  const directory = input.directory ?? void 0;
15509
16129
  const tokenBudget = input.tokenBudget ?? void 0;
15510
16130
  if (from && to) {
15511
- const path31 = await getCallGraphPath(
16131
+ const path33 = await getCallGraphPath(
15512
16132
  projectRoot,
15513
16133
  host,
15514
16134
  from,
@@ -15517,25 +16137,25 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15517
16137
  fromFilePath,
15518
16138
  toFilePath
15519
16139
  );
15520
- const pathText = formatCallGraphPathResult(path31);
15521
- if (path31.path.length > 0) {
16140
+ const pathText = formatCallGraphPathResult(path33);
16141
+ if (path33.path.length > 0) {
15522
16142
  const fitted2 = fitTextToContextBudget(
15523
16143
  pathText,
15524
16144
  tokenBudget
15525
16145
  );
15526
16146
  return {
15527
16147
  text: fitted2.text,
15528
- details: fittedDetails("path", fitted2, path31.path.length)
16148
+ details: fittedDetails("path", fitted2, path33.path.length)
15529
16149
  };
15530
16150
  }
15531
- if (path31.from.status !== "resolved" || path31.to.status !== "resolved") {
16151
+ if (path33.from.status !== "resolved" || path33.to.status !== "resolved") {
15532
16152
  const fitted2 = fitTextToContextBudget(pathText, tokenBudget);
15533
16153
  return {
15534
16154
  text: fitted2.text,
15535
16155
  details: fittedDetails("path", fitted2, 0)
15536
16156
  };
15537
16157
  }
15538
- const resolvedFrom = path31.from;
16158
+ const resolvedFrom = path33.from;
15539
16159
  const { callers } = await getCallGraphData(projectRoot, host, {
15540
16160
  name: to,
15541
16161
  direction: "callers",
@@ -15577,12 +16197,13 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
15577
16197
  directory: scope.directory,
15578
16198
  trace
15579
16199
  }),
15580
- search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
16200
+ search: (queryText, retrievalLimit, scope, trace, searchOptions) => searchCodebase(projectRoot, host, queryText, {
15581
16201
  limit: retrievalLimit,
15582
16202
  fileType: scope.fileType,
15583
16203
  directory: scope.directory,
15584
16204
  metadataOnly: true,
15585
- trace
16205
+ trace,
16206
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
15586
16207
  })
15587
16208
  });
15588
16209
  }
@@ -16012,9 +16633,9 @@ function getRelevantEvidence(query) {
16012
16633
  });
16013
16634
  }
16014
16635
  if (query.expected.acceptableFiles) {
16015
- for (const path31 of query.expected.acceptableFiles) {
16636
+ for (const path33 of query.expected.acceptableFiles) {
16016
16637
  legacyEvidence.push({
16017
- path: path31,
16638
+ path: path33,
16018
16639
  ...query.expected.symbol !== void 0 ? { symbol: query.expected.symbol } : {},
16019
16640
  relevance: 1
16020
16641
  });
@@ -16511,68 +17132,68 @@ function isStringArray4(value) {
16511
17132
  function isNonEmptyString(value) {
16512
17133
  return typeof value === "string" && value.trim().length > 0;
16513
17134
  }
16514
- function asPositiveNumber(value, path31) {
17135
+ function asPositiveNumber(value, path33) {
16515
17136
  if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
16516
- throw new Error(`${path31} must be a non-negative number`);
17137
+ throw new Error(`${path33} must be a non-negative number`);
16517
17138
  }
16518
17139
  return value;
16519
17140
  }
16520
- function parseQueryType(value, path31) {
17141
+ function parseQueryType(value, path33) {
16521
17142
  if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy" || value === "conceptual") {
16522
17143
  return value;
16523
17144
  }
16524
17145
  throw new Error(
16525
- `${path31} must be one of: definition, implementation-intent, similarity, keyword-heavy, conceptual`
17146
+ `${path33} must be one of: definition, implementation-intent, similarity, keyword-heavy, conceptual`
16526
17147
  );
16527
17148
  }
16528
- function parseExpectedRoute(value, path31) {
17149
+ function parseExpectedRoute(value, path33) {
16529
17150
  if (value === void 0) return void 0;
16530
17151
  if (value === "search" || value === "definition") return value;
16531
- throw new Error(`${path31} must be one of: search, definition`);
17152
+ throw new Error(`${path33} must be one of: search, definition`);
16532
17153
  }
16533
- function parseExpectedOutcome(value, path31) {
17154
+ function parseExpectedOutcome(value, path33) {
16534
17155
  if (value === void 0) return void 0;
16535
17156
  if (value === "results" || value === "no-results") {
16536
17157
  return value;
16537
17158
  }
16538
- throw new Error(`${path31} must be one of: results, no-results`);
17159
+ throw new Error(`${path33} must be one of: results, no-results`);
16539
17160
  }
16540
- function parseRecoveryExpectation(value, path31) {
17161
+ function parseRecoveryExpectation(value, path33) {
16541
17162
  if (value === void 0) return void 0;
16542
17163
  if (value === "none" || value === "filter-relaxed") {
16543
17164
  return value;
16544
17165
  }
16545
- throw new Error(`${path31} must be one of: none, filter-relaxed`);
17166
+ throw new Error(`${path33} must be one of: none, filter-relaxed`);
16546
17167
  }
16547
- function parseQueryDifficulty(value, path31) {
17168
+ function parseQueryDifficulty(value, path33) {
16548
17169
  if (value === void 0) return void 0;
16549
17170
  if (value === "easy" || value === "medium" || value === "hard") {
16550
17171
  return value;
16551
17172
  }
16552
- throw new Error(`${path31} must be one of: easy, medium, hard`);
17173
+ throw new Error(`${path33} must be one of: easy, medium, hard`);
16553
17174
  }
16554
- function parseQueryTags(value, path31) {
17175
+ function parseQueryTags(value, path33) {
16555
17176
  if (value === void 0) return void 0;
16556
17177
  if (!isStringArray4(value) || value.some((tag) => tag.trim().length === 0)) {
16557
- throw new Error(`${path31} must be an array of non-empty strings`);
17178
+ throw new Error(`${path33} must be an array of non-empty strings`);
16558
17179
  }
16559
17180
  if (value.length > 16) {
16560
- throw new Error(`${path31} must contain at most 16 tags`);
17181
+ throw new Error(`${path33} must contain at most 16 tags`);
16561
17182
  }
16562
17183
  return value;
16563
17184
  }
16564
- function parseQueryArgs(value, path31) {
17185
+ function parseQueryArgs(value, path33) {
16565
17186
  if (value === void 0) return void 0;
16566
17187
  if (!isRecord3(value)) {
16567
- throw new Error(`${path31} must be an object`);
16568
- }
16569
- const symbol = parseStringOrUndefined(value.symbol, `${path31}.symbol`);
16570
- const filePath = parseStringOrUndefined(value.filePath, `${path31}.filePath`);
16571
- const fileType = parseStringOrUndefined(value.fileType, `${path31}.fileType`);
16572
- const directory = parseStringOrUndefined(value.directory, `${path31}.directory`);
16573
- const callerLimit = parsePositiveIntegerOrUndefined(value.callerLimit, `${path31}.callerLimit`);
16574
- const calleeLimit = parsePositiveIntegerOrUndefined(value.calleeLimit, `${path31}.calleeLimit`);
16575
- const tokenBudget = parsePositiveIntegerOrUndefined(value.tokenBudget, `${path31}.tokenBudget`);
17188
+ throw new Error(`${path33} must be an object`);
17189
+ }
17190
+ const symbol = parseStringOrUndefined(value.symbol, `${path33}.symbol`);
17191
+ const filePath = parseStringOrUndefined(value.filePath, `${path33}.filePath`);
17192
+ const fileType = parseStringOrUndefined(value.fileType, `${path33}.fileType`);
17193
+ const directory = parseStringOrUndefined(value.directory, `${path33}.directory`);
17194
+ const callerLimit = parsePositiveIntegerOrUndefined(value.callerLimit, `${path33}.callerLimit`);
17195
+ const calleeLimit = parsePositiveIntegerOrUndefined(value.calleeLimit, `${path33}.calleeLimit`);
17196
+ const tokenBudget = parsePositiveIntegerOrUndefined(value.tokenBudget, `${path33}.tokenBudget`);
16576
17197
  return {
16577
17198
  ...symbol !== void 0 ? { symbol } : {},
16578
17199
  ...filePath !== void 0 ? { filePath } : {},
@@ -16583,50 +17204,50 @@ function parseQueryArgs(value, path31) {
16583
17204
  ...tokenBudget !== void 0 ? { tokenBudget } : {}
16584
17205
  };
16585
17206
  }
16586
- function parsePositiveIntegerOrUndefined(value, path31) {
17207
+ function parsePositiveIntegerOrUndefined(value, path33) {
16587
17208
  if (value === void 0 || value === null) return void 0;
16588
17209
  if (typeof value !== "number" || !Number.isInteger(value) || value <= 0) {
16589
- throw new Error(`${path31} must be a positive integer`);
17210
+ throw new Error(`${path33} must be a positive integer`);
16590
17211
  }
16591
17212
  return value;
16592
17213
  }
16593
17214
  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-]+)*)?$/;
16594
- function parseSemanticVersion(value, path31) {
17215
+ function parseSemanticVersion(value, path33) {
16595
17216
  if (!isNonEmptyString(value)) {
16596
- throw new Error(`${path31} must be a non-empty string`);
17217
+ throw new Error(`${path33} must be a non-empty string`);
16597
17218
  }
16598
17219
  if (!SEMVER_VERSION_PATTERN.test(value)) {
16599
- throw new Error(`${path31} must be a valid semantic version (MAJOR.MINOR.PATCH)`);
17220
+ throw new Error(`${path33} must be a valid semantic version (MAJOR.MINOR.PATCH)`);
16600
17221
  }
16601
17222
  return value;
16602
17223
  }
16603
- function parseRetrievalMode(value, path31) {
17224
+ function parseRetrievalMode(value, path33) {
16604
17225
  if (value === void 0 || value === "search") return "search";
16605
17226
  if (value === "context" || value === "edit-context") return value;
16606
- throw new Error(`${path31} must be one of: search, context, edit-context`);
17227
+ throw new Error(`${path33} must be one of: search, context, edit-context`);
16607
17228
  }
16608
- function parseStringOrUndefined(value, path31) {
17229
+ function parseStringOrUndefined(value, path33) {
16609
17230
  if (value === void 0 || value === null) return void 0;
16610
17231
  if (!isNonEmptyString(value)) {
16611
- throw new Error(`${path31} must be a non-empty string`);
17232
+ throw new Error(`${path33} must be a non-empty string`);
16612
17233
  }
16613
17234
  return value;
16614
17235
  }
16615
- function parseGradedEvidence(value, path31) {
17236
+ function parseGradedEvidence(value, path33) {
16616
17237
  if (value === void 0) return [];
16617
17238
  if (!Array.isArray(value)) {
16618
- throw new Error(`${path31} must be an array`);
17239
+ throw new Error(`${path33} must be an array`);
16619
17240
  }
16620
17241
  return value.map((entry, index) => {
16621
17242
  if (!isRecord3(entry)) {
16622
- throw new Error(`${path31}[${index}] must be an object`);
17243
+ throw new Error(`${path33}[${index}] must be an object`);
16623
17244
  }
16624
- const evidencePath = parseStringOrUndefined(entry.path, `${path31}[${index}].path`);
17245
+ const evidencePath = parseStringOrUndefined(entry.path, `${path33}[${index}].path`);
16625
17246
  if (evidencePath === void 0) {
16626
- throw new Error(`${path31}[${index}].path is required`);
17247
+ throw new Error(`${path33}[${index}].path is required`);
16627
17248
  }
16628
- const symbol = parseStringOrUndefined(entry.symbol, `${path31}[${index}].symbol`);
16629
- const relevance = parseEvidenceRelevance(entry.relevance, `${path31}[${index}].relevance`);
17249
+ const symbol = parseStringOrUndefined(entry.symbol, `${path33}[${index}].symbol`);
17250
+ const relevance = parseEvidenceRelevance(entry.relevance, `${path33}[${index}].relevance`);
16630
17251
  return {
16631
17252
  path: evidencePath,
16632
17253
  ...symbol !== void 0 ? { symbol } : {},
@@ -16634,27 +17255,27 @@ function parseGradedEvidence(value, path31) {
16634
17255
  };
16635
17256
  });
16636
17257
  }
16637
- function parseEvidenceRelevance(value, path31) {
17258
+ function parseEvidenceRelevance(value, path33) {
16638
17259
  if (value === void 0) {
16639
- throw new Error(`${path31} is required`);
17260
+ throw new Error(`${path33} is required`);
16640
17261
  }
16641
17262
  if (value !== 1 && value !== 2 && value !== 3) {
16642
- throw new Error(`${path31} must be 1, 2, or 3`);
17263
+ throw new Error(`${path33} must be 1, 2, or 3`);
16643
17264
  }
16644
17265
  return value;
16645
17266
  }
16646
- function parseExpectedGraphNeighbor(value, path31) {
17267
+ function parseExpectedGraphNeighbor(value, path33) {
16647
17268
  if (value === void 0) return void 0;
16648
17269
  if (!isRecord3(value)) {
16649
- throw new Error(`${path31} must be an object`);
17270
+ throw new Error(`${path33} must be an object`);
16650
17271
  }
16651
17272
  if (value.direction !== "caller" && value.direction !== "callee") {
16652
- throw new Error(`${path31}.direction must be one of: caller, callee`);
17273
+ throw new Error(`${path33}.direction must be one of: caller, callee`);
16653
17274
  }
16654
- const filePath = parseStringOrUndefined(value.filePath, `${path31}.filePath`);
16655
- const symbol = parseStringOrUndefined(value.symbol, `${path31}.symbol`);
17275
+ const filePath = parseStringOrUndefined(value.filePath, `${path33}.filePath`);
17276
+ const symbol = parseStringOrUndefined(value.symbol, `${path33}.symbol`);
16656
17277
  if (filePath === void 0 && symbol === void 0) {
16657
- throw new Error(`${path31} must include filePath or symbol`);
17278
+ throw new Error(`${path33} must include filePath or symbol`);
16658
17279
  }
16659
17280
  return {
16660
17281
  direction: value.direction,
@@ -16662,9 +17283,9 @@ function parseExpectedGraphNeighbor(value, path31) {
16662
17283
  ...symbol !== void 0 ? { symbol } : {}
16663
17284
  };
16664
17285
  }
16665
- function parseExpected(input, path31) {
17286
+ function parseExpected(input, path33) {
16666
17287
  if (!isRecord3(input)) {
16667
- throw new Error(`${path31} must be an object`);
17288
+ throw new Error(`${path33} must be an object`);
16668
17289
  }
16669
17290
  const filePathRaw = input.filePath;
16670
17291
  const acceptableFilesRaw = input.acceptableFiles;
@@ -16675,29 +17296,29 @@ function parseExpected(input, path31) {
16675
17296
  const recoveryExpectationRaw = input.recoveryExpectation;
16676
17297
  const gradedEvidenceRaw = input.gradedEvidence;
16677
17298
  const graphNeighborRaw = input.graphNeighbor;
16678
- const filePath = parseStringOrUndefined(filePathRaw, `${path31}.filePath`);
17299
+ const filePath = parseStringOrUndefined(filePathRaw, `${path33}.filePath`);
16679
17300
  const acceptableFiles = isStringArray4(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
16680
- const gradedEvidence = parseGradedEvidence(gradedEvidenceRaw, `${path31}.gradedEvidence`);
16681
- const graphNeighbor = parseExpectedGraphNeighbor(graphNeighborRaw, `${path31}.graphNeighbor`);
16682
- const expectedOutcome = parseExpectedOutcome(expectedOutcomeRaw, `${path31}.expectedOutcome`);
17301
+ const gradedEvidence = parseGradedEvidence(gradedEvidenceRaw, `${path33}.gradedEvidence`);
17302
+ const graphNeighbor = parseExpectedGraphNeighbor(graphNeighborRaw, `${path33}.graphNeighbor`);
17303
+ const expectedOutcome = parseExpectedOutcome(expectedOutcomeRaw, `${path33}.expectedOutcome`);
16683
17304
  if (expectedOutcome !== "no-results" && !filePath && (!acceptableFiles || acceptableFiles.length === 0) && gradedEvidence.length === 0) {
16684
17305
  throw new Error(
16685
- `${path31} must include expected.filePath, expected.acceptableFiles, or expected.gradedEvidence`
17306
+ `${path33} must include expected.filePath, expected.acceptableFiles, or expected.gradedEvidence`
16686
17307
  );
16687
17308
  }
16688
17309
  if (acceptableFilesRaw !== void 0 && !isStringArray4(acceptableFilesRaw)) {
16689
- throw new Error(`${path31}.acceptableFiles must be an array of strings`);
17310
+ throw new Error(`${path33}.acceptableFiles must be an array of strings`);
16690
17311
  }
16691
17312
  if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
16692
- throw new Error(`${path31}.symbol must be a string when provided`);
17313
+ throw new Error(`${path33}.symbol must be a string when provided`);
16693
17314
  }
16694
17315
  if (branchRaw !== void 0 && typeof branchRaw !== "string") {
16695
- throw new Error(`${path31}.branch must be a string when provided`);
17316
+ throw new Error(`${path33}.branch must be a string when provided`);
16696
17317
  }
16697
- const expectedRoute = parseExpectedRoute(expectedRouteRaw, `${path31}.expectedRoute`);
17318
+ const expectedRoute = parseExpectedRoute(expectedRouteRaw, `${path33}.expectedRoute`);
16698
17319
  const recoveryExpectation = parseRecoveryExpectation(
16699
17320
  recoveryExpectationRaw,
16700
- `${path31}.recoveryExpectation`
17321
+ `${path33}.recoveryExpectation`
16701
17322
  );
16702
17323
  return {
16703
17324
  filePath,
@@ -16711,13 +17332,13 @@ function parseExpected(input, path31) {
16711
17332
  ...graphNeighbor !== void 0 ? { graphNeighbor } : {}
16712
17333
  };
16713
17334
  }
16714
- function parseQueryLanguage(value, path31) {
16715
- return parseStringOrUndefined(value, path31);
17335
+ function parseQueryLanguage(value, path33) {
17336
+ return parseStringOrUndefined(value, path33);
16716
17337
  }
16717
17338
  function parseQuery(input, index) {
16718
- const path31 = `queries[${index}]`;
17339
+ const path33 = `queries[${index}]`;
16719
17340
  if (!isRecord3(input)) {
16720
- throw new Error(`${path31} must be an object`);
17341
+ throw new Error(`${path33} must be an object`);
16721
17342
  }
16722
17343
  const id = input.id;
16723
17344
  const query = input.query;
@@ -16729,21 +17350,21 @@ function parseQuery(input, index) {
16729
17350
  const tags = input.tags;
16730
17351
  const args = input.args;
16731
17352
  if (typeof id !== "string" || id.trim().length === 0) {
16732
- throw new Error(`${path31}.id must be a non-empty string`);
17353
+ throw new Error(`${path33}.id must be a non-empty string`);
16733
17354
  }
16734
17355
  if (typeof query !== "string" || query.trim().length === 0) {
16735
- throw new Error(`${path31}.query must be a non-empty string`);
17356
+ throw new Error(`${path33}.query must be a non-empty string`);
16736
17357
  }
16737
17358
  return {
16738
17359
  id,
16739
17360
  query,
16740
- queryType: parseQueryType(queryType, `${path31}.queryType`),
16741
- retrievalMode: parseRetrievalMode(retrievalMode, `${path31}.retrievalMode`),
16742
- language: parseQueryLanguage(language, `${path31}.language`),
16743
- difficulty: parseQueryDifficulty(difficulty, `${path31}.difficulty`),
16744
- args: parseQueryArgs(args, `${path31}.args`),
16745
- tags: parseQueryTags(tags, `${path31}.tags`),
16746
- expected: parseExpected(expected, `${path31}.expected`)
17361
+ queryType: parseQueryType(queryType, `${path33}.queryType`),
17362
+ retrievalMode: parseRetrievalMode(retrievalMode, `${path33}.retrievalMode`),
17363
+ language: parseQueryLanguage(language, `${path33}.language`),
17364
+ difficulty: parseQueryDifficulty(difficulty, `${path33}.difficulty`),
17365
+ args: parseQueryArgs(args, `${path33}.args`),
17366
+ tags: parseQueryTags(tags, `${path33}.tags`),
17367
+ expected: parseExpected(expected, `${path33}.expected`)
16747
17368
  };
16748
17369
  }
16749
17370
  function parseGoldenDataset(raw, sourceLabel) {
@@ -17083,12 +17704,13 @@ async function runEvaluation(options) {
17083
17704
  fileType: scope.fileType,
17084
17705
  directory: scope.directory
17085
17706
  }),
17086
- search: (searchQuery, limit, scope) => indexer.search(searchQuery, limit, {
17707
+ search: (searchQuery, limit, scope, _trace, searchOptions) => indexer.search(searchQuery, limit, {
17087
17708
  metadataOnly: true,
17088
17709
  filterByBranch: !!query.expected.branch,
17089
17710
  definitionIntent: false,
17090
17711
  fileType: scope.fileType,
17091
- directory: scope.directory
17712
+ directory: scope.directory,
17713
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
17092
17714
  })
17093
17715
  }) : void 0;
17094
17716
  const result = editContextResult?.results ?? contextResult?.details?.results ?? await indexer.search(query.query, 10, {
@@ -17709,7 +18331,7 @@ async function executeCallGraph(projectRoot, host, args) {
17709
18331
  return { text: formatCallGraphResult(await getCallGraphData(projectRoot, host, args)) };
17710
18332
  }
17711
18333
  async function executeCallGraphPath(projectRoot, host, args) {
17712
- const path31 = await getCallGraphPath(
18334
+ const path33 = await getCallGraphPath(
17713
18335
  projectRoot,
17714
18336
  host,
17715
18337
  args.from,
@@ -17718,7 +18340,7 @@ async function executeCallGraphPath(projectRoot, host, args) {
17718
18340
  args.fromFilePath,
17719
18341
  args.toFilePath
17720
18342
  );
17721
- return { text: formatCallGraphPathResult(path31) };
18343
+ return { text: formatCallGraphPathResult(path33) };
17722
18344
  }
17723
18345
  async function executeCodeCommunities(projectRoot, host, args) {
17724
18346
  const result = await getCodeCommunities(projectRoot, host, args);
@@ -17864,11 +18486,21 @@ var PI_TOOL_NAMES = [
17864
18486
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
17865
18487
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
17866
18488
  ];
18489
+ var MCP_TOOL_NAMES = [
18490
+ ...PORTABLE_TOOL_NAMES,
18491
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
18492
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
18493
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE
18494
+ ];
17867
18495
 
17868
18496
  // src/adapters/mcp/register-tools.ts
17869
18497
  function allowNullAsUndefined(schema) {
17870
18498
  return z2.preprocess((value) => value === null ? void 0 : value, schema);
17871
18499
  }
18500
+ function knowledgeBaseResult(text) {
18501
+ const content = [{ type: "text", text }];
18502
+ return text.startsWith("Error: ") ? { content, isError: true } : { content };
18503
+ }
17872
18504
  function registerMcpTools(server, runtime) {
17873
18505
  server.tool(
17874
18506
  TOOL_NAME.CODEBASE_CONTEXT,
@@ -17929,7 +18561,8 @@ function registerMcpTools(server, runtime) {
17929
18561
  contextLines: allowNullAsUndefined(z2.number().optional()).describe("Number of extra lines to include before/after each match (default: 0)"),
17930
18562
  blameAuthor: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame author name or email"),
17931
18563
  blameSha: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame commit SHA or prefix"),
17932
- blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date")
18564
+ blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date"),
18565
+ blameUntil: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or before this date")
17933
18566
  },
17934
18567
  async (args) => {
17935
18568
  return searchCodebaseWithEffectiveness(runtime.projectRoot, runtime.host, "search", args.query, {
@@ -17940,7 +18573,8 @@ function registerMcpTools(server, runtime) {
17940
18573
  contextLines: args.contextLines,
17941
18574
  blameAuthor: args.blameAuthor,
17942
18575
  blameSha: args.blameSha,
17943
- blameSince: args.blameSince
18576
+ blameSince: args.blameSince,
18577
+ blameUntil: args.blameUntil
17944
18578
  }, (results) => {
17945
18579
  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}":
17946
18580
 
@@ -17960,7 +18594,8 @@ ${formatSearchResults(results, "score")}`;
17960
18594
  chunkType: allowNullAsUndefined(z2.enum(CHUNK_TYPES).optional()).describe("Filter by code chunk type"),
17961
18595
  blameAuthor: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame author name or email"),
17962
18596
  blameSha: allowNullAsUndefined(z2.string().optional()).describe("Filter by git blame commit SHA or prefix"),
17963
- blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date")
18597
+ blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date"),
18598
+ blameUntil: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or before this date")
17964
18599
  },
17965
18600
  async (args) => {
17966
18601
  return searchCodebaseWithEffectiveness(runtime.projectRoot, runtime.host, "peek", args.query, {
@@ -17971,7 +18606,8 @@ ${formatSearchResults(results, "score")}`;
17971
18606
  metadataOnly: true,
17972
18607
  blameAuthor: args.blameAuthor,
17973
18608
  blameSha: args.blameSha,
17974
- blameSince: args.blameSince
18609
+ blameSince: args.blameSince,
18610
+ blameUntil: args.blameUntil
17975
18611
  }, (results) => {
17976
18612
  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}":
17977
18613
 
@@ -18048,7 +18684,9 @@ ${formatCodebasePeek(results)}`;
18048
18684
  fileType: allowNullAsUndefined(z2.string().optional()).describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
18049
18685
  directory: allowNullAsUndefined(z2.string().optional()).describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
18050
18686
  chunkType: allowNullAsUndefined(z2.enum(CHUNK_TYPES).optional()).describe("Filter by code chunk type"),
18051
- excludeFile: allowNullAsUndefined(z2.string().optional()).describe("Exclude results from this file path")
18687
+ excludeFile: allowNullAsUndefined(z2.string().optional()).describe("Exclude results from this file path"),
18688
+ blameSince: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or after this date"),
18689
+ blameUntil: allowNullAsUndefined(z2.string().optional()).describe("Filter to chunks last changed on or before this date")
18052
18690
  },
18053
18691
  async (args) => {
18054
18692
  const results = await findSimilarCode(runtime.projectRoot, runtime.host, args.code, {
@@ -18056,7 +18694,9 @@ ${formatCodebasePeek(results)}`;
18056
18694
  fileType: args.fileType,
18057
18695
  directory: args.directory,
18058
18696
  chunkType: args.chunkType,
18059
- excludeFile: args.excludeFile
18697
+ excludeFile: args.excludeFile,
18698
+ blameSince: args.blameSince,
18699
+ blameUntil: args.blameUntil
18060
18700
  });
18061
18701
  if (results.length === 0) {
18062
18702
  return { content: [{ type: "text", text: "No similar code found. Try a different snippet or run index_codebase first." }] };
@@ -18160,6 +18800,37 @@ ${formatSearchResults(results)}` }] };
18160
18800
  return { content: [{ type: "text", text: result.text }] };
18161
18801
  }
18162
18802
  );
18803
+ server.tool(
18804
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
18805
+ "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.",
18806
+ {
18807
+ path: z2.string().describe("Path to the folder to add as a knowledge base (absolute or relative to the project root)")
18808
+ },
18809
+ async (args) => {
18810
+ const result = addKnowledgeBase(runtime.projectRoot, runtime.host, args.path);
18811
+ return knowledgeBaseResult(result);
18812
+ }
18813
+ );
18814
+ server.tool(
18815
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
18816
+ "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.",
18817
+ {},
18818
+ async () => {
18819
+ const result = listKnowledgeBases(runtime.projectRoot, runtime.host);
18820
+ return knowledgeBaseResult(result);
18821
+ }
18822
+ );
18823
+ server.tool(
18824
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE,
18825
+ "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.",
18826
+ {
18827
+ path: z2.string().describe("Path of the knowledge base to remove (must match a project-local configured path exactly)")
18828
+ },
18829
+ async (args) => {
18830
+ const result = removeKnowledgeBase(runtime.projectRoot, runtime.host, args.path.trim());
18831
+ return knowledgeBaseResult(result);
18832
+ }
18833
+ );
18163
18834
  }
18164
18835
 
18165
18836
  // src/adapters/mcp/server.ts
@@ -18205,7 +18876,7 @@ function createMcpServer(projectRoot, config, host) {
18205
18876
  }
18206
18877
 
18207
18878
  // src/watcher/file-watcher.ts
18208
- import { existsSync as existsSync15 } from "fs";
18879
+ import { existsSync as existsSync15, statSync as statSync6 } from "fs";
18209
18880
 
18210
18881
  // node_modules/chokidar/index.js
18211
18882
  import { EventEmitter as EventEmitter2 } from "events";
@@ -18297,7 +18968,7 @@ var ReaddirpStream = class extends Readable {
18297
18968
  this._directoryFilter = normalizeFilter(opts.directoryFilter);
18298
18969
  const statMethod = opts.lstat ? lstat : stat;
18299
18970
  if (wantBigintFsStats) {
18300
- this._stat = (path31) => statMethod(path31, { bigint: true });
18971
+ this._stat = (path33) => statMethod(path33, { bigint: true });
18301
18972
  } else {
18302
18973
  this._stat = statMethod;
18303
18974
  }
@@ -18322,8 +18993,8 @@ var ReaddirpStream = class extends Readable {
18322
18993
  const par = this.parent;
18323
18994
  const fil = par && par.files;
18324
18995
  if (fil && fil.length > 0) {
18325
- const { path: path31, depth } = par;
18326
- const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path31));
18996
+ const { path: path33, depth } = par;
18997
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path33));
18327
18998
  const awaited = await Promise.all(slice);
18328
18999
  for (const entry of awaited) {
18329
19000
  if (!entry)
@@ -18363,20 +19034,20 @@ var ReaddirpStream = class extends Readable {
18363
19034
  this.reading = false;
18364
19035
  }
18365
19036
  }
18366
- async _exploreDir(path31, depth) {
19037
+ async _exploreDir(path33, depth) {
18367
19038
  let files;
18368
19039
  try {
18369
- files = await readdir(path31, this._rdOptions);
19040
+ files = await readdir(path33, this._rdOptions);
18370
19041
  } catch (error) {
18371
19042
  this._onError(error);
18372
19043
  }
18373
- return { files, depth, path: path31 };
19044
+ return { files, depth, path: path33 };
18374
19045
  }
18375
- async _formatEntry(dirent, path31) {
19046
+ async _formatEntry(dirent, path33) {
18376
19047
  let entry;
18377
19048
  const basename8 = this._isDirent ? dirent.name : dirent;
18378
19049
  try {
18379
- const fullPath = presolve(pjoin(path31, basename8));
19050
+ const fullPath = presolve(pjoin(path33, basename8));
18380
19051
  entry = { path: prelative(this._root, fullPath), fullPath, basename: basename8 };
18381
19052
  entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
18382
19053
  } catch (err) {
@@ -18776,16 +19447,16 @@ var delFromSet = (main, prop, item) => {
18776
19447
  };
18777
19448
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
18778
19449
  var FsWatchInstances = /* @__PURE__ */ new Map();
18779
- function createFsWatchInstance(path31, options, listener, errHandler, emitRaw) {
19450
+ function createFsWatchInstance(path33, options, listener, errHandler, emitRaw) {
18780
19451
  const handleEvent = (rawEvent, evPath) => {
18781
- listener(path31);
18782
- emitRaw(rawEvent, evPath, { watchedPath: path31 });
18783
- if (evPath && path31 !== evPath) {
18784
- fsWatchBroadcast(sp.resolve(path31, evPath), KEY_LISTENERS, sp.join(path31, evPath));
19452
+ listener(path33);
19453
+ emitRaw(rawEvent, evPath, { watchedPath: path33 });
19454
+ if (evPath && path33 !== evPath) {
19455
+ fsWatchBroadcast(sp.resolve(path33, evPath), KEY_LISTENERS, sp.join(path33, evPath));
18785
19456
  }
18786
19457
  };
18787
19458
  try {
18788
- return fs_watch(path31, {
19459
+ return fs_watch(path33, {
18789
19460
  persistent: options.persistent
18790
19461
  }, handleEvent);
18791
19462
  } catch (error) {
@@ -18801,12 +19472,12 @@ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
18801
19472
  listener(val1, val2, val3);
18802
19473
  });
18803
19474
  };
18804
- var setFsWatchListener = (path31, fullPath, options, handlers) => {
19475
+ var setFsWatchListener = (path33, fullPath, options, handlers) => {
18805
19476
  const { listener, errHandler, rawEmitter } = handlers;
18806
19477
  let cont = FsWatchInstances.get(fullPath);
18807
19478
  let watcher;
18808
19479
  if (!options.persistent) {
18809
- watcher = createFsWatchInstance(path31, options, listener, errHandler, rawEmitter);
19480
+ watcher = createFsWatchInstance(path33, options, listener, errHandler, rawEmitter);
18810
19481
  if (!watcher)
18811
19482
  return;
18812
19483
  return watcher.close.bind(watcher);
@@ -18817,7 +19488,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18817
19488
  addAndConvert(cont, KEY_RAW, rawEmitter);
18818
19489
  } else {
18819
19490
  watcher = createFsWatchInstance(
18820
- path31,
19491
+ path33,
18821
19492
  options,
18822
19493
  fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS),
18823
19494
  errHandler,
@@ -18832,7 +19503,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18832
19503
  cont.watcherUnusable = true;
18833
19504
  if (isWindows && error.code === "EPERM") {
18834
19505
  try {
18835
- const fd = await open(path31, "r");
19506
+ const fd = await open(path33, "r");
18836
19507
  await fd.close();
18837
19508
  broadcastErr(error);
18838
19509
  } catch (err) {
@@ -18863,7 +19534,7 @@ var setFsWatchListener = (path31, fullPath, options, handlers) => {
18863
19534
  };
18864
19535
  };
18865
19536
  var FsWatchFileInstances = /* @__PURE__ */ new Map();
18866
- var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
19537
+ var setFsWatchFileListener = (path33, fullPath, options, handlers) => {
18867
19538
  const { listener, rawEmitter } = handlers;
18868
19539
  let cont = FsWatchFileInstances.get(fullPath);
18869
19540
  const copts = cont && cont.options;
@@ -18885,7 +19556,7 @@ var setFsWatchFileListener = (path31, fullPath, options, handlers) => {
18885
19556
  });
18886
19557
  const currmtime = curr.mtimeMs;
18887
19558
  if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
18888
- foreach(cont.listeners, (listener2) => listener2(path31, curr));
19559
+ foreach(cont.listeners, (listener2) => listener2(path33, curr));
18889
19560
  }
18890
19561
  })
18891
19562
  };
@@ -18915,13 +19586,13 @@ var NodeFsHandler = class {
18915
19586
  * @param listener on fs change
18916
19587
  * @returns closer for the watcher instance
18917
19588
  */
18918
- _watchWithNodeFs(path31, listener) {
19589
+ _watchWithNodeFs(path33, listener) {
18919
19590
  const opts = this.fsw.options;
18920
- const directory = sp.dirname(path31);
18921
- const basename8 = sp.basename(path31);
19591
+ const directory = sp.dirname(path33);
19592
+ const basename8 = sp.basename(path33);
18922
19593
  const parent = this.fsw._getWatchedDir(directory);
18923
19594
  parent.add(basename8);
18924
- const absolutePath = sp.resolve(path31);
19595
+ const absolutePath = sp.resolve(path33);
18925
19596
  const options = {
18926
19597
  persistent: opts.persistent
18927
19598
  };
@@ -18931,12 +19602,12 @@ var NodeFsHandler = class {
18931
19602
  if (opts.usePolling) {
18932
19603
  const enableBin = opts.interval !== opts.binaryInterval;
18933
19604
  options.interval = enableBin && isBinaryPath(basename8) ? opts.binaryInterval : opts.interval;
18934
- closer = setFsWatchFileListener(path31, absolutePath, options, {
19605
+ closer = setFsWatchFileListener(path33, absolutePath, options, {
18935
19606
  listener,
18936
19607
  rawEmitter: this.fsw._emitRaw
18937
19608
  });
18938
19609
  } else {
18939
- closer = setFsWatchListener(path31, absolutePath, options, {
19610
+ closer = setFsWatchListener(path33, absolutePath, options, {
18940
19611
  listener,
18941
19612
  errHandler: this._boundHandleError,
18942
19613
  rawEmitter: this.fsw._emitRaw
@@ -18958,7 +19629,7 @@ var NodeFsHandler = class {
18958
19629
  let prevStats = stats;
18959
19630
  if (parent.has(basename8))
18960
19631
  return;
18961
- const listener = async (path31, newStats) => {
19632
+ const listener = async (path33, newStats) => {
18962
19633
  if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
18963
19634
  return;
18964
19635
  if (!newStats || newStats.mtimeMs === 0) {
@@ -18972,11 +19643,11 @@ var NodeFsHandler = class {
18972
19643
  this.fsw._emit(EV.CHANGE, file, newStats2);
18973
19644
  }
18974
19645
  if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
18975
- this.fsw._closeFile(path31);
19646
+ this.fsw._closeFile(path33);
18976
19647
  prevStats = newStats2;
18977
19648
  const closer2 = this._watchWithNodeFs(file, listener);
18978
19649
  if (closer2)
18979
- this.fsw._addPathCloser(path31, closer2);
19650
+ this.fsw._addPathCloser(path33, closer2);
18980
19651
  } else {
18981
19652
  prevStats = newStats2;
18982
19653
  }
@@ -19008,7 +19679,7 @@ var NodeFsHandler = class {
19008
19679
  * @param item basename of this item
19009
19680
  * @returns true if no more processing is needed for this entry.
19010
19681
  */
19011
- async _handleSymlink(entry, directory, path31, item) {
19682
+ async _handleSymlink(entry, directory, path33, item) {
19012
19683
  if (this.fsw.closed) {
19013
19684
  return;
19014
19685
  }
@@ -19018,7 +19689,7 @@ var NodeFsHandler = class {
19018
19689
  this.fsw._incrReadyCount();
19019
19690
  let linkPath;
19020
19691
  try {
19021
- linkPath = await fsrealpath(path31);
19692
+ linkPath = await fsrealpath(path33);
19022
19693
  } catch (e) {
19023
19694
  this.fsw._emitReady();
19024
19695
  return true;
@@ -19028,12 +19699,12 @@ var NodeFsHandler = class {
19028
19699
  if (dir.has(item)) {
19029
19700
  if (this.fsw._symlinkPaths.get(full) !== linkPath) {
19030
19701
  this.fsw._symlinkPaths.set(full, linkPath);
19031
- this.fsw._emit(EV.CHANGE, path31, entry.stats);
19702
+ this.fsw._emit(EV.CHANGE, path33, entry.stats);
19032
19703
  }
19033
19704
  } else {
19034
19705
  dir.add(item);
19035
19706
  this.fsw._symlinkPaths.set(full, linkPath);
19036
- this.fsw._emit(EV.ADD, path31, entry.stats);
19707
+ this.fsw._emit(EV.ADD, path33, entry.stats);
19037
19708
  }
19038
19709
  this.fsw._emitReady();
19039
19710
  return true;
@@ -19063,9 +19734,9 @@ var NodeFsHandler = class {
19063
19734
  return;
19064
19735
  }
19065
19736
  const item = entry.path;
19066
- let path31 = sp.join(directory, item);
19737
+ let path33 = sp.join(directory, item);
19067
19738
  current.add(item);
19068
- if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path31, item)) {
19739
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path33, item)) {
19069
19740
  return;
19070
19741
  }
19071
19742
  if (this.fsw.closed) {
@@ -19074,11 +19745,11 @@ var NodeFsHandler = class {
19074
19745
  }
19075
19746
  if (item === target || !target && !previous.has(item)) {
19076
19747
  this.fsw._incrReadyCount();
19077
- path31 = sp.join(dir, sp.relative(dir, path31));
19078
- this._addToNodeFs(path31, initialAdd, wh, depth + 1);
19748
+ path33 = sp.join(dir, sp.relative(dir, path33));
19749
+ this._addToNodeFs(path33, initialAdd, wh, depth + 1);
19079
19750
  }
19080
19751
  }).on(EV.ERROR, this._boundHandleError);
19081
- return new Promise((resolve18, reject) => {
19752
+ return new Promise((resolve20, reject) => {
19082
19753
  if (!stream)
19083
19754
  return reject();
19084
19755
  stream.once(STR_END, () => {
@@ -19087,7 +19758,7 @@ var NodeFsHandler = class {
19087
19758
  return;
19088
19759
  }
19089
19760
  const wasThrottled = throttler ? throttler.clear() : false;
19090
- resolve18(void 0);
19761
+ resolve20(void 0);
19091
19762
  previous.getChildren().filter((item) => {
19092
19763
  return item !== directory && !current.has(item);
19093
19764
  }).forEach((item) => {
@@ -19144,13 +19815,13 @@ var NodeFsHandler = class {
19144
19815
  * @param depth Child path actually targeted for watch
19145
19816
  * @param target Child path actually targeted for watch
19146
19817
  */
19147
- async _addToNodeFs(path31, initialAdd, priorWh, depth, target) {
19818
+ async _addToNodeFs(path33, initialAdd, priorWh, depth, target) {
19148
19819
  const ready = this.fsw._emitReady;
19149
- if (this.fsw._isIgnored(path31) || this.fsw.closed) {
19820
+ if (this.fsw._isIgnored(path33) || this.fsw.closed) {
19150
19821
  ready();
19151
19822
  return false;
19152
19823
  }
19153
- const wh = this.fsw._getWatchHelpers(path31);
19824
+ const wh = this.fsw._getWatchHelpers(path33);
19154
19825
  if (priorWh) {
19155
19826
  wh.filterPath = (entry) => priorWh.filterPath(entry);
19156
19827
  wh.filterDir = (entry) => priorWh.filterDir(entry);
@@ -19166,8 +19837,8 @@ var NodeFsHandler = class {
19166
19837
  const follow = this.fsw.options.followSymlinks;
19167
19838
  let closer;
19168
19839
  if (stats.isDirectory()) {
19169
- const absPath = sp.resolve(path31);
19170
- const targetPath = follow ? await fsrealpath(path31) : path31;
19840
+ const absPath = sp.resolve(path33);
19841
+ const targetPath = follow ? await fsrealpath(path33) : path33;
19171
19842
  if (this.fsw.closed)
19172
19843
  return;
19173
19844
  closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
@@ -19177,29 +19848,29 @@ var NodeFsHandler = class {
19177
19848
  this.fsw._symlinkPaths.set(absPath, targetPath);
19178
19849
  }
19179
19850
  } else if (stats.isSymbolicLink()) {
19180
- const targetPath = follow ? await fsrealpath(path31) : path31;
19851
+ const targetPath = follow ? await fsrealpath(path33) : path33;
19181
19852
  if (this.fsw.closed)
19182
19853
  return;
19183
19854
  const parent = sp.dirname(wh.watchPath);
19184
19855
  this.fsw._getWatchedDir(parent).add(wh.watchPath);
19185
19856
  this.fsw._emit(EV.ADD, wh.watchPath, stats);
19186
- closer = await this._handleDir(parent, stats, initialAdd, depth, path31, wh, targetPath);
19857
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path33, wh, targetPath);
19187
19858
  if (this.fsw.closed)
19188
19859
  return;
19189
19860
  if (targetPath !== void 0) {
19190
- this.fsw._symlinkPaths.set(sp.resolve(path31), targetPath);
19861
+ this.fsw._symlinkPaths.set(sp.resolve(path33), targetPath);
19191
19862
  }
19192
19863
  } else {
19193
19864
  closer = this._handleFile(wh.watchPath, stats, initialAdd);
19194
19865
  }
19195
19866
  ready();
19196
19867
  if (closer)
19197
- this.fsw._addPathCloser(path31, closer);
19868
+ this.fsw._addPathCloser(path33, closer);
19198
19869
  return false;
19199
19870
  } catch (error) {
19200
19871
  if (this.fsw._handleError(error)) {
19201
19872
  ready();
19202
- return path31;
19873
+ return path33;
19203
19874
  }
19204
19875
  }
19205
19876
  }
@@ -19231,35 +19902,35 @@ function createPattern(matcher) {
19231
19902
  if (matcher.path === string)
19232
19903
  return true;
19233
19904
  if (matcher.recursive) {
19234
- const relative12 = sp2.relative(matcher.path, string);
19235
- if (!relative12) {
19905
+ const relative14 = sp2.relative(matcher.path, string);
19906
+ if (!relative14) {
19236
19907
  return false;
19237
19908
  }
19238
- return !relative12.startsWith("..") && !sp2.isAbsolute(relative12);
19909
+ return !relative14.startsWith("..") && !sp2.isAbsolute(relative14);
19239
19910
  }
19240
19911
  return false;
19241
19912
  };
19242
19913
  }
19243
19914
  return () => false;
19244
19915
  }
19245
- function normalizePath3(path31) {
19246
- if (typeof path31 !== "string")
19916
+ function normalizePath3(path33) {
19917
+ if (typeof path33 !== "string")
19247
19918
  throw new Error("string expected");
19248
- path31 = sp2.normalize(path31);
19249
- path31 = path31.replace(/\\/g, "/");
19919
+ path33 = sp2.normalize(path33);
19920
+ path33 = path33.replace(/\\/g, "/");
19250
19921
  let prepend = false;
19251
- if (path31.startsWith("//"))
19922
+ if (path33.startsWith("//"))
19252
19923
  prepend = true;
19253
- path31 = path31.replace(DOUBLE_SLASH_RE, "/");
19924
+ path33 = path33.replace(DOUBLE_SLASH_RE, "/");
19254
19925
  if (prepend)
19255
- path31 = "/" + path31;
19256
- return path31;
19926
+ path33 = "/" + path33;
19927
+ return path33;
19257
19928
  }
19258
19929
  function matchPatterns(patterns, testString, stats) {
19259
- const path31 = normalizePath3(testString);
19930
+ const path33 = normalizePath3(testString);
19260
19931
  for (let index = 0; index < patterns.length; index++) {
19261
19932
  const pattern = patterns[index];
19262
- if (pattern(path31, stats)) {
19933
+ if (pattern(path33, stats)) {
19263
19934
  return true;
19264
19935
  }
19265
19936
  }
@@ -19297,19 +19968,19 @@ var toUnix = (string) => {
19297
19968
  }
19298
19969
  return str;
19299
19970
  };
19300
- var normalizePathToUnix = (path31) => toUnix(sp2.normalize(toUnix(path31)));
19301
- var normalizeIgnored = (cwd = "") => (path31) => {
19302
- if (typeof path31 === "string") {
19303
- return normalizePathToUnix(sp2.isAbsolute(path31) ? path31 : sp2.join(cwd, path31));
19971
+ var normalizePathToUnix = (path33) => toUnix(sp2.normalize(toUnix(path33)));
19972
+ var normalizeIgnored = (cwd = "") => (path33) => {
19973
+ if (typeof path33 === "string") {
19974
+ return normalizePathToUnix(sp2.isAbsolute(path33) ? path33 : sp2.join(cwd, path33));
19304
19975
  } else {
19305
- return path31;
19976
+ return path33;
19306
19977
  }
19307
19978
  };
19308
- var getAbsolutePath = (path31, cwd) => {
19309
- if (sp2.isAbsolute(path31)) {
19310
- return path31;
19979
+ var getAbsolutePath = (path33, cwd) => {
19980
+ if (sp2.isAbsolute(path33)) {
19981
+ return path33;
19311
19982
  }
19312
- return sp2.join(cwd, path31);
19983
+ return sp2.join(cwd, path33);
19313
19984
  };
19314
19985
  var EMPTY_SET = Object.freeze(/* @__PURE__ */ new Set());
19315
19986
  var DirEntry = class {
@@ -19374,10 +20045,10 @@ var WatchHelper = class {
19374
20045
  dirParts;
19375
20046
  followSymlinks;
19376
20047
  statMethod;
19377
- constructor(path31, follow, fsw) {
20048
+ constructor(path33, follow, fsw) {
19378
20049
  this.fsw = fsw;
19379
- const watchPath = path31;
19380
- this.path = path31 = path31.replace(REPLACER_RE, "");
20050
+ const watchPath = path33;
20051
+ this.path = path33 = path33.replace(REPLACER_RE, "");
19381
20052
  this.watchPath = watchPath;
19382
20053
  this.fullWatchPath = sp2.resolve(watchPath);
19383
20054
  this.dirParts = [];
@@ -19517,20 +20188,20 @@ var FSWatcher = class extends EventEmitter2 {
19517
20188
  this._closePromise = void 0;
19518
20189
  let paths = unifyPaths(paths_);
19519
20190
  if (cwd) {
19520
- paths = paths.map((path31) => {
19521
- const absPath = getAbsolutePath(path31, cwd);
20191
+ paths = paths.map((path33) => {
20192
+ const absPath = getAbsolutePath(path33, cwd);
19522
20193
  return absPath;
19523
20194
  });
19524
20195
  }
19525
- paths.forEach((path31) => {
19526
- this._removeIgnoredPath(path31);
20196
+ paths.forEach((path33) => {
20197
+ this._removeIgnoredPath(path33);
19527
20198
  });
19528
20199
  this._userIgnored = void 0;
19529
20200
  if (!this._readyCount)
19530
20201
  this._readyCount = 0;
19531
20202
  this._readyCount += paths.length;
19532
- Promise.all(paths.map(async (path31) => {
19533
- const res = await this._nodeFsHandler._addToNodeFs(path31, !_internal, void 0, 0, _origAdd);
20203
+ Promise.all(paths.map(async (path33) => {
20204
+ const res = await this._nodeFsHandler._addToNodeFs(path33, !_internal, void 0, 0, _origAdd);
19534
20205
  if (res)
19535
20206
  this._emitReady();
19536
20207
  return res;
@@ -19552,17 +20223,17 @@ var FSWatcher = class extends EventEmitter2 {
19552
20223
  return this;
19553
20224
  const paths = unifyPaths(paths_);
19554
20225
  const { cwd } = this.options;
19555
- paths.forEach((path31) => {
19556
- if (!sp2.isAbsolute(path31) && !this._closers.has(path31)) {
20226
+ paths.forEach((path33) => {
20227
+ if (!sp2.isAbsolute(path33) && !this._closers.has(path33)) {
19557
20228
  if (cwd)
19558
- path31 = sp2.join(cwd, path31);
19559
- path31 = sp2.resolve(path31);
20229
+ path33 = sp2.join(cwd, path33);
20230
+ path33 = sp2.resolve(path33);
19560
20231
  }
19561
- this._closePath(path31);
19562
- this._addIgnoredPath(path31);
19563
- if (this._watched.has(path31)) {
20232
+ this._closePath(path33);
20233
+ this._addIgnoredPath(path33);
20234
+ if (this._watched.has(path33)) {
19564
20235
  this._addIgnoredPath({
19565
- path: path31,
20236
+ path: path33,
19566
20237
  recursive: true
19567
20238
  });
19568
20239
  }
@@ -19626,38 +20297,38 @@ var FSWatcher = class extends EventEmitter2 {
19626
20297
  * @param stats arguments to be passed with event
19627
20298
  * @returns the error if defined, otherwise the value of the FSWatcher instance's `closed` flag
19628
20299
  */
19629
- async _emit(event, path31, stats) {
20300
+ async _emit(event, path33, stats) {
19630
20301
  if (this.closed)
19631
20302
  return;
19632
20303
  const opts = this.options;
19633
20304
  if (isWindows)
19634
- path31 = sp2.normalize(path31);
20305
+ path33 = sp2.normalize(path33);
19635
20306
  if (opts.cwd)
19636
- path31 = sp2.relative(opts.cwd, path31);
19637
- const args = [path31];
20307
+ path33 = sp2.relative(opts.cwd, path33);
20308
+ const args = [path33];
19638
20309
  if (stats != null)
19639
20310
  args.push(stats);
19640
20311
  const awf = opts.awaitWriteFinish;
19641
20312
  let pw;
19642
- if (awf && (pw = this._pendingWrites.get(path31))) {
20313
+ if (awf && (pw = this._pendingWrites.get(path33))) {
19643
20314
  pw.lastChange = /* @__PURE__ */ new Date();
19644
20315
  return this;
19645
20316
  }
19646
20317
  if (opts.atomic) {
19647
20318
  if (event === EVENTS.UNLINK) {
19648
- this._pendingUnlinks.set(path31, [event, ...args]);
20319
+ this._pendingUnlinks.set(path33, [event, ...args]);
19649
20320
  setTimeout(() => {
19650
- this._pendingUnlinks.forEach((entry, path32) => {
20321
+ this._pendingUnlinks.forEach((entry, path34) => {
19651
20322
  this.emit(...entry);
19652
20323
  this.emit(EVENTS.ALL, ...entry);
19653
- this._pendingUnlinks.delete(path32);
20324
+ this._pendingUnlinks.delete(path34);
19654
20325
  });
19655
20326
  }, typeof opts.atomic === "number" ? opts.atomic : 100);
19656
20327
  return this;
19657
20328
  }
19658
- if (event === EVENTS.ADD && this._pendingUnlinks.has(path31)) {
20329
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path33)) {
19659
20330
  event = EVENTS.CHANGE;
19660
- this._pendingUnlinks.delete(path31);
20331
+ this._pendingUnlinks.delete(path33);
19661
20332
  }
19662
20333
  }
19663
20334
  if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
@@ -19675,16 +20346,16 @@ var FSWatcher = class extends EventEmitter2 {
19675
20346
  this.emitWithAll(event, args);
19676
20347
  }
19677
20348
  };
19678
- this._awaitWriteFinish(path31, awf.stabilityThreshold, event, awfEmit);
20349
+ this._awaitWriteFinish(path33, awf.stabilityThreshold, event, awfEmit);
19679
20350
  return this;
19680
20351
  }
19681
20352
  if (event === EVENTS.CHANGE) {
19682
- const isThrottled = !this._throttle(EVENTS.CHANGE, path31, 50);
20353
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path33, 50);
19683
20354
  if (isThrottled)
19684
20355
  return this;
19685
20356
  }
19686
20357
  if (opts.alwaysStat && stats === void 0 && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
19687
- const fullPath = opts.cwd ? sp2.join(opts.cwd, path31) : path31;
20358
+ const fullPath = opts.cwd ? sp2.join(opts.cwd, path33) : path33;
19688
20359
  let stats2;
19689
20360
  try {
19690
20361
  stats2 = await stat3(fullPath);
@@ -19715,23 +20386,23 @@ var FSWatcher = class extends EventEmitter2 {
19715
20386
  * @param timeout duration of time to suppress duplicate actions
19716
20387
  * @returns tracking object or false if action should be suppressed
19717
20388
  */
19718
- _throttle(actionType, path31, timeout) {
20389
+ _throttle(actionType, path33, timeout) {
19719
20390
  if (!this._throttled.has(actionType)) {
19720
20391
  this._throttled.set(actionType, /* @__PURE__ */ new Map());
19721
20392
  }
19722
20393
  const action = this._throttled.get(actionType);
19723
20394
  if (!action)
19724
20395
  throw new Error("invalid throttle");
19725
- const actionPath = action.get(path31);
20396
+ const actionPath = action.get(path33);
19726
20397
  if (actionPath) {
19727
20398
  actionPath.count++;
19728
20399
  return false;
19729
20400
  }
19730
20401
  let timeoutObject;
19731
20402
  const clear = () => {
19732
- const item = action.get(path31);
20403
+ const item = action.get(path33);
19733
20404
  const count = item ? item.count : 0;
19734
- action.delete(path31);
20405
+ action.delete(path33);
19735
20406
  clearTimeout(timeoutObject);
19736
20407
  if (item)
19737
20408
  clearTimeout(item.timeoutObject);
@@ -19739,7 +20410,7 @@ var FSWatcher = class extends EventEmitter2 {
19739
20410
  };
19740
20411
  timeoutObject = setTimeout(clear, timeout);
19741
20412
  const thr = { timeoutObject, clear, count: 0 };
19742
- action.set(path31, thr);
20413
+ action.set(path33, thr);
19743
20414
  return thr;
19744
20415
  }
19745
20416
  _incrReadyCount() {
@@ -19753,44 +20424,44 @@ var FSWatcher = class extends EventEmitter2 {
19753
20424
  * @param event
19754
20425
  * @param awfEmit Callback to be called when ready for event to be emitted.
19755
20426
  */
19756
- _awaitWriteFinish(path31, threshold, event, awfEmit) {
20427
+ _awaitWriteFinish(path33, threshold, event, awfEmit) {
19757
20428
  const awf = this.options.awaitWriteFinish;
19758
20429
  if (typeof awf !== "object")
19759
20430
  return;
19760
20431
  const pollInterval = awf.pollInterval;
19761
20432
  let timeoutHandler;
19762
- let fullPath = path31;
19763
- if (this.options.cwd && !sp2.isAbsolute(path31)) {
19764
- fullPath = sp2.join(this.options.cwd, path31);
20433
+ let fullPath = path33;
20434
+ if (this.options.cwd && !sp2.isAbsolute(path33)) {
20435
+ fullPath = sp2.join(this.options.cwd, path33);
19765
20436
  }
19766
20437
  const now2 = /* @__PURE__ */ new Date();
19767
20438
  const writes = this._pendingWrites;
19768
20439
  function awaitWriteFinishFn(prevStat) {
19769
20440
  statcb(fullPath, (err, curStat) => {
19770
- if (err || !writes.has(path31)) {
20441
+ if (err || !writes.has(path33)) {
19771
20442
  if (err && err.code !== "ENOENT")
19772
20443
  awfEmit(err);
19773
20444
  return;
19774
20445
  }
19775
20446
  const now3 = Number(/* @__PURE__ */ new Date());
19776
20447
  if (prevStat && curStat.size !== prevStat.size) {
19777
- writes.get(path31).lastChange = now3;
20448
+ writes.get(path33).lastChange = now3;
19778
20449
  }
19779
- const pw = writes.get(path31);
20450
+ const pw = writes.get(path33);
19780
20451
  const df = now3 - pw.lastChange;
19781
20452
  if (df >= threshold) {
19782
- writes.delete(path31);
20453
+ writes.delete(path33);
19783
20454
  awfEmit(void 0, curStat);
19784
20455
  } else {
19785
20456
  timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
19786
20457
  }
19787
20458
  });
19788
20459
  }
19789
- if (!writes.has(path31)) {
19790
- writes.set(path31, {
20460
+ if (!writes.has(path33)) {
20461
+ writes.set(path33, {
19791
20462
  lastChange: now2,
19792
20463
  cancelWait: () => {
19793
- writes.delete(path31);
20464
+ writes.delete(path33);
19794
20465
  clearTimeout(timeoutHandler);
19795
20466
  return event;
19796
20467
  }
@@ -19801,8 +20472,8 @@ var FSWatcher = class extends EventEmitter2 {
19801
20472
  /**
19802
20473
  * Determines whether user has asked to ignore this path.
19803
20474
  */
19804
- _isIgnored(path31, stats) {
19805
- if (this.options.atomic && DOT_RE.test(path31))
20475
+ _isIgnored(path33, stats) {
20476
+ if (this.options.atomic && DOT_RE.test(path33))
19806
20477
  return true;
19807
20478
  if (!this._userIgnored) {
19808
20479
  const { cwd } = this.options;
@@ -19812,17 +20483,17 @@ var FSWatcher = class extends EventEmitter2 {
19812
20483
  const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
19813
20484
  this._userIgnored = anymatch(list, void 0);
19814
20485
  }
19815
- return this._userIgnored(path31, stats);
20486
+ return this._userIgnored(path33, stats);
19816
20487
  }
19817
- _isntIgnored(path31, stat4) {
19818
- return !this._isIgnored(path31, stat4);
20488
+ _isntIgnored(path33, stat5) {
20489
+ return !this._isIgnored(path33, stat5);
19819
20490
  }
19820
20491
  /**
19821
20492
  * Provides a set of common helpers and properties relating to symlink handling.
19822
20493
  * @param path file or directory pattern being watched
19823
20494
  */
19824
- _getWatchHelpers(path31) {
19825
- return new WatchHelper(path31, this.options.followSymlinks, this);
20495
+ _getWatchHelpers(path33) {
20496
+ return new WatchHelper(path33, this.options.followSymlinks, this);
19826
20497
  }
19827
20498
  // Directory helpers
19828
20499
  // -----------------
@@ -19854,63 +20525,63 @@ var FSWatcher = class extends EventEmitter2 {
19854
20525
  * @param item base path of item/directory
19855
20526
  */
19856
20527
  _remove(directory, item, isDirectory) {
19857
- const path31 = sp2.join(directory, item);
19858
- const fullPath = sp2.resolve(path31);
19859
- isDirectory = isDirectory != null ? isDirectory : this._watched.has(path31) || this._watched.has(fullPath);
19860
- if (!this._throttle("remove", path31, 100))
20528
+ const path33 = sp2.join(directory, item);
20529
+ const fullPath = sp2.resolve(path33);
20530
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path33) || this._watched.has(fullPath);
20531
+ if (!this._throttle("remove", path33, 100))
19861
20532
  return;
19862
20533
  if (!isDirectory && this._watched.size === 1) {
19863
20534
  this.add(directory, item, true);
19864
20535
  }
19865
- const wp = this._getWatchedDir(path31);
20536
+ const wp = this._getWatchedDir(path33);
19866
20537
  const nestedDirectoryChildren = wp.getChildren();
19867
- nestedDirectoryChildren.forEach((nested) => this._remove(path31, nested));
20538
+ nestedDirectoryChildren.forEach((nested) => this._remove(path33, nested));
19868
20539
  const parent = this._getWatchedDir(directory);
19869
20540
  const wasTracked = parent.has(item);
19870
20541
  parent.remove(item);
19871
20542
  if (this._symlinkPaths.has(fullPath)) {
19872
20543
  this._symlinkPaths.delete(fullPath);
19873
20544
  }
19874
- let relPath = path31;
20545
+ let relPath = path33;
19875
20546
  if (this.options.cwd)
19876
- relPath = sp2.relative(this.options.cwd, path31);
20547
+ relPath = sp2.relative(this.options.cwd, path33);
19877
20548
  if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
19878
20549
  const event = this._pendingWrites.get(relPath).cancelWait();
19879
20550
  if (event === EVENTS.ADD)
19880
20551
  return;
19881
20552
  }
19882
- this._watched.delete(path31);
20553
+ this._watched.delete(path33);
19883
20554
  this._watched.delete(fullPath);
19884
20555
  const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
19885
- if (wasTracked && !this._isIgnored(path31))
19886
- this._emit(eventName, path31);
19887
- this._closePath(path31);
20556
+ if (wasTracked && !this._isIgnored(path33))
20557
+ this._emit(eventName, path33);
20558
+ this._closePath(path33);
19888
20559
  }
19889
20560
  /**
19890
20561
  * Closes all watchers for a path
19891
20562
  */
19892
- _closePath(path31) {
19893
- this._closeFile(path31);
19894
- const dir = sp2.dirname(path31);
19895
- this._getWatchedDir(dir).remove(sp2.basename(path31));
20563
+ _closePath(path33) {
20564
+ this._closeFile(path33);
20565
+ const dir = sp2.dirname(path33);
20566
+ this._getWatchedDir(dir).remove(sp2.basename(path33));
19896
20567
  }
19897
20568
  /**
19898
20569
  * Closes only file-specific watchers
19899
20570
  */
19900
- _closeFile(path31) {
19901
- const closers = this._closers.get(path31);
20571
+ _closeFile(path33) {
20572
+ const closers = this._closers.get(path33);
19902
20573
  if (!closers)
19903
20574
  return;
19904
20575
  closers.forEach((closer) => closer());
19905
- this._closers.delete(path31);
20576
+ this._closers.delete(path33);
19906
20577
  }
19907
- _addPathCloser(path31, closer) {
20578
+ _addPathCloser(path33, closer) {
19908
20579
  if (!closer)
19909
20580
  return;
19910
- let list = this._closers.get(path31);
20581
+ let list = this._closers.get(path33);
19911
20582
  if (!list) {
19912
20583
  list = [];
19913
- this._closers.set(path31, list);
20584
+ this._closers.set(path33, list);
19914
20585
  }
19915
20586
  list.push(closer);
19916
20587
  }
@@ -19940,12 +20611,291 @@ function watch(paths, options = {}) {
19940
20611
  var chokidar_default = { watch, FSWatcher };
19941
20612
 
19942
20613
  // src/watcher/file-watcher.ts
20614
+ import * as path28 from "path";
20615
+
20616
+ // src/watcher/native-recursive-watcher.ts
20617
+ import { watch as watch2 } from "fs";
19943
20618
  import * as path26 from "path";
20619
+ var NativeRecursiveWatcher = class {
20620
+ constructor(root, onChange, options = {}) {
20621
+ this.root = root;
20622
+ this.onChange = onChange;
20623
+ this.watchFactory = options.watchFactory ?? this.defaultWatchFactory;
20624
+ this.onError = options.onError;
20625
+ }
20626
+ root;
20627
+ onChange;
20628
+ watcher = null;
20629
+ listenerToken = 0;
20630
+ watchFactory;
20631
+ onError;
20632
+ start() {
20633
+ if (this.watcher) return;
20634
+ const token = ++this.listenerToken;
20635
+ const listener = (_eventType, filename) => {
20636
+ if (this.watcher === null || this.listenerToken !== token) return;
20637
+ const absolutePath = this.toAbsolutePath(filename);
20638
+ const nextResult = this.onChange(absolutePath);
20639
+ if (nextResult instanceof Promise) {
20640
+ void nextResult.catch((error) => {
20641
+ console.error("[codebase-index] Error handling native watcher event:", error);
20642
+ });
20643
+ }
20644
+ };
20645
+ const watcher = this.watchFactory(this.root, listener, {
20646
+ persistent: true,
20647
+ recursive: true
20648
+ });
20649
+ watcher.on?.("error", (error) => {
20650
+ if (this.watcher === watcher && this.listenerToken === token) {
20651
+ this.onError?.(error);
20652
+ }
20653
+ });
20654
+ this.watcher = watcher;
20655
+ }
20656
+ async stop() {
20657
+ const watcher = this.watcher;
20658
+ this.watcher = null;
20659
+ this.listenerToken += 1;
20660
+ if (!watcher) return;
20661
+ await watcher.close();
20662
+ }
20663
+ toAbsolutePath(filename) {
20664
+ if (filename == null) return null;
20665
+ const normalizedFilename = typeof filename === "string" ? filename : filename.toString();
20666
+ const absolutePath = path26.resolve(this.root, normalizedFilename);
20667
+ const relativePath = path26.relative(this.root, absolutePath);
20668
+ const outsideRoot = relativePath === ".." || relativePath.startsWith(`..${path26.sep}`) || path26.isAbsolute(relativePath);
20669
+ return outsideRoot ? null : absolutePath;
20670
+ }
20671
+ defaultWatchFactory = (root, listener, options) => watch2(root, options, listener);
20672
+ };
20673
+
20674
+ // src/watcher/snapshot.ts
20675
+ import * as fsPromises4 from "fs/promises";
20676
+ import * as path27 from "path";
20677
+ async function buildFileSnapshotScan(projectRoot, config, configPaths = []) {
20678
+ const normalizedProjectRoot = path27.resolve(projectRoot);
20679
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
20680
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
20681
+ const maxDepth = config.indexing?.maxDepth ?? -1;
20682
+ const snapshot = /* @__PURE__ */ new Map();
20683
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20684
+ const includeFile = async (filePath) => {
20685
+ const normalizedPath3 = path27.resolve(filePath);
20686
+ if (!shouldIncludeFile(normalizedPath3, normalizedProjectRoot, includePatterns, config.exclude, ignoreFilter)) return;
20687
+ const stat5 = await readStatIfFile(normalizedPath3, unreadablePrefixes);
20688
+ if (stat5) snapshot.set(normalizedPath3, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20689
+ };
20690
+ const walk = async (directoryPath, depth) => {
20691
+ let entries;
20692
+ try {
20693
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
20694
+ } catch (error) {
20695
+ if (isMissingFsError(error)) return;
20696
+ if (isPermissionFsError(error)) {
20697
+ unreadablePrefixes.add(path27.resolve(directoryPath));
20698
+ return;
20699
+ }
20700
+ throw error;
20701
+ }
20702
+ for (const entry of entries) {
20703
+ const fullPath = path27.join(directoryPath, entry.name);
20704
+ const relativePath = path27.relative(normalizedProjectRoot, fullPath);
20705
+ if (entry.isDirectory()) {
20706
+ if (hasFilteredPathSegment(relativePath, path27.sep) || isRestrictedDirectory(relativePath, path27.sep)) continue;
20707
+ if (ignoreFilter.ignores(relativePath)) continue;
20708
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
20709
+ } else if (entry.isFile()) {
20710
+ await includeFile(fullPath);
20711
+ }
20712
+ }
20713
+ };
20714
+ await walk(normalizedProjectRoot, 0);
20715
+ await includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths);
20716
+ return { entries: snapshot, unreadablePrefixes };
20717
+ }
20718
+ async function buildFileSnapshotForPathScan(projectRoot, config, configPaths, targetPath) {
20719
+ const normalizedProjectRoot = path27.resolve(projectRoot);
20720
+ const normalizedTargetPath = path27.resolve(targetPath);
20721
+ if (!isWithinPath(normalizedProjectRoot, normalizedTargetPath)) {
20722
+ return { entries: /* @__PURE__ */ new Map(), unreadablePrefixes: /* @__PURE__ */ new Set() };
20723
+ }
20724
+ const ignoreFilter = createIgnoreFilter(normalizedProjectRoot);
20725
+ const includePatterns = [...config.include, ...config.additionalInclude ?? []];
20726
+ const maxDepth = config.indexing?.maxDepth ?? -1;
20727
+ const explicitConfigPaths = new Set(configPaths.map((configPath) => path27.resolve(configPath)));
20728
+ const snapshot = /* @__PURE__ */ new Map();
20729
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20730
+ const includeFile = async (filePath) => {
20731
+ const normalizedPath3 = path27.resolve(filePath);
20732
+ if (!explicitConfigPaths.has(normalizedPath3) && !shouldIncludeFile(
20733
+ normalizedPath3,
20734
+ normalizedProjectRoot,
20735
+ includePatterns,
20736
+ config.exclude,
20737
+ ignoreFilter
20738
+ )) return;
20739
+ const stat5 = await readStatIfFile(normalizedPath3, unreadablePrefixes);
20740
+ if (stat5) snapshot.set(normalizedPath3, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20741
+ };
20742
+ const walk = async (directoryPath, depth) => {
20743
+ let entries;
20744
+ try {
20745
+ entries = await fsPromises4.readdir(directoryPath, { withFileTypes: true });
20746
+ } catch (error) {
20747
+ if (isMissingFsError(error)) return;
20748
+ if (isPermissionFsError(error)) {
20749
+ unreadablePrefixes.add(path27.resolve(directoryPath));
20750
+ return;
20751
+ }
20752
+ throw error;
20753
+ }
20754
+ for (const entry of entries) {
20755
+ const fullPath = path27.join(directoryPath, entry.name);
20756
+ const relativePath = path27.relative(normalizedProjectRoot, fullPath);
20757
+ if (entry.isDirectory()) {
20758
+ if (hasFilteredPathSegment(relativePath, path27.sep) || isRestrictedDirectory(relativePath, path27.sep)) continue;
20759
+ if (ignoreFilter.ignores(relativePath)) continue;
20760
+ if (maxDepth === -1 || depth < maxDepth) await walk(fullPath, depth + 1);
20761
+ } else if (entry.isFile()) {
20762
+ await includeFile(fullPath);
20763
+ }
20764
+ }
20765
+ };
20766
+ const targetStat = await readStatIfFile(normalizedTargetPath, unreadablePrefixes);
20767
+ if (targetStat) await includeFile(normalizedTargetPath);
20768
+ else await walk(normalizedTargetPath, 0);
20769
+ await includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, normalizedTargetPath);
20770
+ return { entries: snapshot, unreadablePrefixes };
20771
+ }
20772
+ function completeFileSnapshot(previous, scan) {
20773
+ const completed = new Map(scan.entries);
20774
+ for (const unreadablePrefix of scan.unreadablePrefixes) {
20775
+ for (const [entryPath, entry] of previous) {
20776
+ if (isWithinPath(unreadablePrefix, entryPath) && !completed.has(entryPath)) completed.set(entryPath, entry);
20777
+ }
20778
+ }
20779
+ return completed;
20780
+ }
20781
+ async function includeExplicitConfigPaths(snapshot, unreadablePrefixes, configPaths) {
20782
+ for (const configPath of [...new Set(configPaths.map((value) => path27.resolve(value)))]) {
20783
+ if (snapshot.has(configPath)) continue;
20784
+ const stat5 = await readStatIfFile(configPath, unreadablePrefixes);
20785
+ if (stat5) snapshot.set(configPath, { size: stat5.size, mtimeMs: stat5.mtimeMs });
20786
+ }
20787
+ }
20788
+ async function includeExplicitConfigPathsInPath(snapshot, unreadablePrefixes, configPaths, targetPath) {
20789
+ await includeExplicitConfigPaths(
20790
+ snapshot,
20791
+ unreadablePrefixes,
20792
+ configPaths.filter((configPath) => isWithinPath(targetPath, path27.resolve(configPath)))
20793
+ );
20794
+ }
20795
+ function isWithinPath(parentPath, childPath) {
20796
+ const relativePath = path27.relative(parentPath, childPath);
20797
+ return relativePath === "" || !relativePath.startsWith(`..${path27.sep}`) && relativePath !== ".." && !path27.isAbsolute(relativePath);
20798
+ }
20799
+ async function readStatIfFile(filePath, unreadablePrefixes) {
20800
+ try {
20801
+ const stat5 = await fsPromises4.stat(filePath);
20802
+ return stat5.isFile() ? stat5 : null;
20803
+ } catch (error) {
20804
+ if (isMissingFsError(error)) return null;
20805
+ if (isPermissionFsError(error)) {
20806
+ unreadablePrefixes.add(path27.resolve(filePath));
20807
+ return null;
20808
+ }
20809
+ throw error;
20810
+ }
20811
+ }
20812
+ function isMissingFsError(error) {
20813
+ return error instanceof Error && ["ENOENT", "ENOTDIR"].includes(error.code ?? "");
20814
+ }
20815
+ function isPermissionFsError(error) {
20816
+ return error instanceof Error && ["EACCES", "EPERM"].includes(error.code ?? "");
20817
+ }
20818
+ var diffTypeOrder = { add: 0, change: 1, unlink: 2 };
20819
+ function diffFileSnapshots(previous, current, forcedChanges = /* @__PURE__ */ new Set()) {
20820
+ const changes = [];
20821
+ for (const [filePath, previousEntry] of previous) {
20822
+ const currentEntry = current.get(filePath);
20823
+ if (!currentEntry) changes.push({ type: "unlink", path: filePath });
20824
+ else if (forcedChanges.has(filePath) || currentEntry.size !== previousEntry.size || currentEntry.mtimeMs !== previousEntry.mtimeMs) {
20825
+ changes.push({ type: "change", path: filePath });
20826
+ }
20827
+ }
20828
+ for (const [filePath] of current) {
20829
+ if (!previous.has(filePath)) changes.push({ type: "add", path: filePath });
20830
+ }
20831
+ return changes.sort((left, right) => left.path.localeCompare(right.path) || diffTypeOrder[left.type] - diffTypeOrder[right.type]);
20832
+ }
20833
+
20834
+ // src/watcher/snapshot-reconciler.ts
20835
+ var FileSnapshotReconciler = class {
20836
+ constructor(projectRoot, config, configPaths) {
20837
+ this.projectRoot = projectRoot;
20838
+ this.config = config;
20839
+ this.configPaths = configPaths;
20840
+ }
20841
+ projectRoot;
20842
+ config;
20843
+ configPaths;
20844
+ snapshot = null;
20845
+ reconciliationTail = Promise.resolve();
20846
+ async initialize() {
20847
+ this.snapshot = (await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths)).entries;
20848
+ }
20849
+ async reconcile(invalidations = []) {
20850
+ if (this.snapshot === null) {
20851
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
20852
+ }
20853
+ const reconciliation = this.reconciliationTail.then(async () => {
20854
+ const previousSnapshot = this.snapshot;
20855
+ if (previousSnapshot === null) {
20856
+ throw new Error("FileSnapshotReconciler is not initialized. Call initialize() before reconcile().");
20857
+ }
20858
+ const normalizedInvalidations = invalidations.map((invalidation) => typeof invalidation === "string" || invalidation === null ? { path: invalidation, forceChange: false } : { path: invalidation.path, forceChange: invalidation.forceChange === true });
20859
+ const scopedPaths = normalizedInvalidations.map((invalidation) => invalidation.path).filter((filePath) => filePath !== null);
20860
+ const scan = scopedPaths.length === 0 || scopedPaths.length !== normalizedInvalidations.length ? await buildFileSnapshotScan(this.projectRoot, this.config, this.configPaths) : await this.reconcilePaths(previousSnapshot, scopedPaths);
20861
+ const nextSnapshot = completeFileSnapshot(previousSnapshot, scan);
20862
+ const forcedChanges = new Set(normalizedInvalidations.filter((invalidation) => invalidation.path !== null && invalidation.forceChange).map((invalidation) => invalidation.path));
20863
+ const changes = diffFileSnapshots(previousSnapshot, nextSnapshot, forcedChanges);
20864
+ this.snapshot = nextSnapshot;
20865
+ return changes;
20866
+ });
20867
+ this.reconciliationTail = reconciliation.then(() => void 0, () => void 0);
20868
+ return reconciliation;
20869
+ }
20870
+ async reconcilePaths(previousSnapshot, invalidatedPaths) {
20871
+ const scopes = this.getScopes(invalidatedPaths);
20872
+ const entries = new Map(previousSnapshot);
20873
+ const unreadablePrefixes = /* @__PURE__ */ new Set();
20874
+ for (const scope of scopes) {
20875
+ for (const previousPath of entries.keys()) {
20876
+ if (isWithinPath(scope, previousPath)) entries.delete(previousPath);
20877
+ }
20878
+ const scopedScan = await buildFileSnapshotForPathScan(this.projectRoot, this.config, this.configPaths, scope);
20879
+ for (const [filePath, entry] of scopedScan.entries) entries.set(filePath, entry);
20880
+ for (const unreadablePrefix of scopedScan.unreadablePrefixes) unreadablePrefixes.add(unreadablePrefix);
20881
+ }
20882
+ return { entries, unreadablePrefixes };
20883
+ }
20884
+ getScopes(invalidatedPaths) {
20885
+ const uniquePaths = [...new Set(invalidatedPaths)].sort((left, right) => left.length - right.length);
20886
+ return uniquePaths.filter((candidate, index) => !uniquePaths.slice(0, index).some(
20887
+ (ancestor) => isWithinPath(ancestor, candidate)
20888
+ ));
20889
+ }
20890
+ };
20891
+
20892
+ // src/watcher/file-watcher.ts
19944
20893
  var FileWatcher = class {
19945
20894
  watcher = null;
19946
20895
  projectRoot;
19947
20896
  config;
19948
20897
  configPath;
20898
+ backend;
19949
20899
  projectConfigPaths;
19950
20900
  pendingChanges = /* @__PURE__ */ new Map();
19951
20901
  debounceTimer = null;
@@ -19955,44 +20905,74 @@ var FileWatcher = class {
19955
20905
  resolveReady = null;
19956
20906
  pollingFallbackAttempted = false;
19957
20907
  pendingClose = null;
20908
+ startupReadySignals = 1;
20909
+ nativeWatcher = null;
20910
+ nativeReconciler = null;
20911
+ nativeSetupGeneration = 0;
20912
+ nativeStarting = false;
20913
+ nativeInitializing = false;
20914
+ nativeReconcileTimer = null;
20915
+ nativeInvalidatedPaths = /* @__PURE__ */ new Map();
20916
+ configPathStates = /* @__PURE__ */ new Map();
19958
20917
  constructor(projectRoot, config, host, options = {}) {
19959
20918
  this.projectRoot = projectRoot;
19960
20919
  this.config = config;
20920
+ this.backend = options.backend ?? "auto";
19961
20921
  this.configPath = options.configPath;
19962
20922
  this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
19963
20923
  }
19964
20924
  start(handler) {
19965
- if (this.watcher) {
20925
+ if (this.watcher || this.nativeWatcher || this.nativeStarting) {
19966
20926
  return;
19967
20927
  }
19968
20928
  this.onChanges = handler;
19969
20929
  this.pollingFallbackAttempted = false;
19970
20930
  this.resetReady();
20931
+ if (this.shouldUseNativeWatcher()) {
20932
+ if (this.hasExternalConfigWatchTarget()) {
20933
+ this.setStartupReadySignals(2);
20934
+ this.startExternalConfigWatcher();
20935
+ }
20936
+ this.nativeStarting = true;
20937
+ void this.createNativeWatcher();
20938
+ return;
20939
+ }
19971
20940
  this.createWatcher();
19972
20941
  }
19973
20942
  resetReady() {
19974
- this.readyPromise = new Promise((resolve18) => {
19975
- this.resolveReady = resolve18;
20943
+ this.readyPromise = new Promise((resolve20) => {
20944
+ this.resolveReady = resolve20;
19976
20945
  });
20946
+ this.startupReadySignals = 1;
19977
20947
  }
19978
- createWatcher(usePolling = false) {
19979
- const ignoreFilter = createIgnoreFilter(this.projectRoot);
19980
- let watchTargets = this.projectRoot;
19981
- if (this.configPath) {
19982
- watchTargets = [this.projectRoot, this.configPath];
19983
- } else {
19984
- const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
19985
- const relativeConfigPath = path26.relative(this.projectRoot, projectConfigPath);
19986
- return this.isOutsideProjectPath(relativeConfigPath);
19987
- }).map((projectConfigPath) => existsSync15(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path26.dirname(projectConfigPath)));
19988
- const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
19989
- if (uniqueExternalConfigTargets.length > 0) {
19990
- watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
19991
- }
20948
+ setStartupReadySignals(expectedSignals) {
20949
+ if (!this.readyPromise) {
20950
+ return;
19992
20951
  }
20952
+ this.startupReadySignals = Math.max(0, expectedSignals);
20953
+ }
20954
+ reportStartupReadySignal() {
20955
+ if (!this.readyPromise || !this.resolveReady) {
20956
+ return;
20957
+ }
20958
+ if (this.startupReadySignals <= 0) {
20959
+ return;
20960
+ }
20961
+ this.startupReadySignals -= 1;
20962
+ if (this.startupReadySignals !== 0) {
20963
+ return;
20964
+ }
20965
+ this.resolveReady();
20966
+ this.resolveReady = null;
20967
+ }
20968
+ createWatcher(watchTargets, usePolling = false, reportsStartupReady = true) {
20969
+ let reportedStartupReady = false;
20970
+ this.configPathStates = this.getConfigPathStates();
20971
+ const ignoreFilter = createIgnoreFilter(this.projectRoot);
20972
+ const resolvedWatchTargets = watchTargets ?? this.getFullChokidarWatchTargets();
19993
20973
  const watcherOptions = {
19994
20974
  ignored: (filePath) => {
19995
- const relativePath = path26.relative(this.projectRoot, filePath);
20975
+ const relativePath = path28.relative(this.projectRoot, filePath);
19996
20976
  if (!relativePath) return false;
19997
20977
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
19998
20978
  return false;
@@ -20000,10 +20980,10 @@ var FileWatcher = class {
20000
20980
  if (this.isOutsideProjectPath(relativePath)) {
20001
20981
  return true;
20002
20982
  }
20003
- if (hasFilteredPathSegment(relativePath, path26.sep)) {
20983
+ if (hasFilteredPathSegment(relativePath, path28.sep)) {
20004
20984
  return true;
20005
20985
  }
20006
- if (isRestrictedDirectory(relativePath, path26.sep)) {
20986
+ if (isRestrictedDirectory(relativePath, path28.sep)) {
20007
20987
  return true;
20008
20988
  }
20009
20989
  if (ignoreFilter.ignores(relativePath)) {
@@ -20036,10 +21016,13 @@ var FileWatcher = class {
20036
21016
  watcher = new FSWatcher(watcherOptions);
20037
21017
  }
20038
21018
  this.watcher = watcher;
20039
- watcher.once("ready", () => {
21019
+ watcher.on("ready", () => {
20040
21020
  if (this.watcher !== watcher) return;
20041
- this.resolveReady?.();
20042
- this.resolveReady = null;
21021
+ this.reconcileConfigPathStates();
21022
+ if (reportsStartupReady) {
21023
+ this.reportStartupReadySignal();
21024
+ reportedStartupReady = true;
21025
+ }
20043
21026
  });
20044
21027
  watcher.on("error", (error) => {
20045
21028
  const err = error instanceof Error ? error : null;
@@ -20053,10 +21036,13 @@ var FileWatcher = class {
20053
21036
  console.error("[codebase-index] Failed to close exhausted file watcher:", closeError);
20054
21037
  });
20055
21038
  if (this.onChanges) {
21039
+ const replacementReportsStartupReady = reportsStartupReady || reportedStartupReady;
20056
21040
  if (!this.resolveReady) {
20057
21041
  this.resetReady();
21042
+ } else if (reportedStartupReady) {
21043
+ this.startupReadySignals += 1;
20058
21044
  }
20059
- this.createWatcher(true);
21045
+ this.createWatcher(resolvedWatchTargets, true, replacementReportsStartupReady);
20060
21046
  } else {
20061
21047
  this.watcher = null;
20062
21048
  }
@@ -20067,13 +21053,166 @@ var FileWatcher = class {
20067
21053
  watcher.on("add", (filePath) => this.handleChange(watcher, "add", filePath));
20068
21054
  watcher.on("change", (filePath) => this.handleChange(watcher, "change", filePath));
20069
21055
  watcher.on("unlink", (filePath) => this.handleChange(watcher, "unlink", filePath));
20070
- watcher.add(watchTargets);
21056
+ watcher.add(resolvedWatchTargets);
21057
+ }
21058
+ shouldUseNativeWatcher() {
21059
+ if (this.backend === "chokidar") {
21060
+ return false;
21061
+ }
21062
+ return true;
21063
+ }
21064
+ getFullChokidarWatchTargets() {
21065
+ if (this.configPath) {
21066
+ return [this.projectRoot, this.configPath];
21067
+ }
21068
+ const externalConfigTargets = this.getExternalConfigWatchTargets();
21069
+ if (externalConfigTargets.length === 0) {
21070
+ return this.projectRoot;
21071
+ }
21072
+ return [this.projectRoot, ...externalConfigTargets];
21073
+ }
21074
+ getExternalConfigWatchTargets() {
21075
+ return [...new Set(
21076
+ this.projectConfigPaths.filter((projectConfigPath) => {
21077
+ const relativeConfigPath = path28.relative(this.projectRoot, projectConfigPath);
21078
+ return this.isOutsideProjectPath(relativeConfigPath);
21079
+ }).map((projectConfigPath) => {
21080
+ if (existsSync15(projectConfigPath)) {
21081
+ return projectConfigPath;
21082
+ }
21083
+ return this.getNearestExistingDirectory(path28.dirname(projectConfigPath));
21084
+ })
21085
+ )];
21086
+ }
21087
+ hasExternalConfigWatchTarget() {
21088
+ return this.getExternalConfigWatchTargets().length > 0;
21089
+ }
21090
+ startExternalConfigWatcher(usePolling = false) {
21091
+ const externalTargets = this.getExternalConfigWatchTargets();
21092
+ if (externalTargets.length === 0) {
21093
+ return;
21094
+ }
21095
+ this.createWatcher(externalTargets, usePolling);
21096
+ }
21097
+ async createNativeWatcher() {
21098
+ const generation = ++this.nativeSetupGeneration;
21099
+ const reconciler = new FileSnapshotReconciler(this.projectRoot, this.config, this.projectConfigPaths);
21100
+ const watcher = new NativeRecursiveWatcher(
21101
+ this.projectRoot,
21102
+ (filePath) => this.scheduleNativeReconciliation(generation, filePath),
21103
+ { onError: (error) => void this.fallbackFromNativeWatcher(generation, error) }
21104
+ );
21105
+ this.nativeReconciler = reconciler;
21106
+ this.nativeWatcher = watcher;
21107
+ this.nativeInitializing = true;
21108
+ try {
21109
+ watcher.start();
21110
+ if (!this.isCurrentNativeSetup(generation)) {
21111
+ await watcher.stop();
21112
+ return;
21113
+ }
21114
+ await reconciler.initialize();
21115
+ if (!this.isCurrentNativeSetup(generation) || this.nativeWatcher !== watcher) {
21116
+ await watcher.stop();
21117
+ return;
21118
+ }
21119
+ this.nativeStarting = false;
21120
+ this.nativeInitializing = false;
21121
+ await this.reconcileNativeWatcherWithPendingInvalidations(generation);
21122
+ this.reportStartupReadySignal();
21123
+ } catch (error) {
21124
+ if (!this.isCurrentNativeSetup(generation)) return;
21125
+ this.nativeInitializing = false;
21126
+ if (this.nativeWatcher) {
21127
+ await this.fallbackFromNativeWatcher(generation, error);
21128
+ return;
21129
+ }
21130
+ this.nativeStarting = false;
21131
+ const externalWatcher = this.watcher;
21132
+ this.watcher = null;
21133
+ this.nativeReconciler = null;
21134
+ await externalWatcher?.close();
21135
+ console.warn("[codebase-index] Native recursive watcher unavailable; using Chokidar fallback.", error);
21136
+ this.setStartupReadySignals(1);
21137
+ this.createWatcher();
21138
+ }
21139
+ }
21140
+ isCurrentNativeSetup(generation) {
21141
+ return this.nativeSetupGeneration === generation && this.onChanges !== null;
21142
+ }
21143
+ scheduleNativeReconciliation(generation, filePath) {
21144
+ if (!this.isCurrentNativeSetup(generation)) return;
21145
+ const requiresFullReconciliation = filePath === path28.join(this.projectRoot, ".gitignore");
21146
+ const invalidatedPath = requiresFullReconciliation ? null : filePath;
21147
+ this.nativeInvalidatedPaths.set(invalidatedPath, invalidatedPath !== null);
21148
+ if (this.nativeReconcileTimer) {
21149
+ clearTimeout(this.nativeReconcileTimer);
21150
+ }
21151
+ this.nativeReconcileTimer = setTimeout(() => {
21152
+ this.nativeReconcileTimer = null;
21153
+ void this.reconcileNativeWatcherFromQueue(generation);
21154
+ }, 100);
21155
+ }
21156
+ reconcileNativeWatcherFromQueue(generation) {
21157
+ if (!this.isCurrentNativeSetup(generation) || this.nativeInitializing) return;
21158
+ const invalidatedPaths = this.popNativeInvalidations();
21159
+ if (invalidatedPaths.length === 0) return;
21160
+ void this.reconcileNativeWatcher(generation, invalidatedPaths);
21161
+ }
21162
+ async reconcileNativeWatcher(generation, invalidatedPaths) {
21163
+ if (!this.isCurrentNativeSetup(generation) || !this.nativeReconciler) return;
21164
+ try {
21165
+ const reconciler = this.nativeReconciler;
21166
+ const changes = await reconciler.reconcile(invalidatedPaths);
21167
+ if (!this.isCurrentNativeSetup(generation) || this.nativeReconciler !== reconciler) return;
21168
+ this.recordChanges(changes);
21169
+ } catch (error) {
21170
+ await this.fallbackFromNativeWatcher(generation, error);
21171
+ }
21172
+ }
21173
+ async reconcileNativeWatcherWithPendingInvalidations(generation) {
21174
+ const invalidatedPaths = this.popNativeInvalidations();
21175
+ if (invalidatedPaths.length === 0) return;
21176
+ await this.reconcileNativeWatcher(generation, invalidatedPaths);
21177
+ }
21178
+ popNativeInvalidations() {
21179
+ if (this.nativeInvalidatedPaths.size === 0) return [];
21180
+ const invalidations = [...this.nativeInvalidatedPaths].map(([invalidatedPath, forceChange]) => ({
21181
+ path: invalidatedPath,
21182
+ forceChange
21183
+ }));
21184
+ this.nativeInvalidatedPaths.clear();
21185
+ return invalidations;
21186
+ }
21187
+ async fallbackFromNativeWatcher(generation, error) {
21188
+ if (!this.isCurrentNativeSetup(generation)) return;
21189
+ const watcher = this.nativeWatcher;
21190
+ const externalWatcher = this.watcher;
21191
+ this.nativeWatcher = null;
21192
+ this.watcher = null;
21193
+ this.nativeReconciler = null;
21194
+ this.nativeStarting = false;
21195
+ this.nativeInitializing = false;
21196
+ this.nativeSetupGeneration += 1;
21197
+ if (this.nativeReconcileTimer) {
21198
+ clearTimeout(this.nativeReconcileTimer);
21199
+ this.nativeReconcileTimer = null;
21200
+ }
21201
+ this.nativeInvalidatedPaths.clear();
21202
+ this.setStartupReadySignals(1);
21203
+ console.warn("[codebase-index] Native recursive watcher failed; using Chokidar fallback.", error);
21204
+ await watcher?.stop();
21205
+ await externalWatcher?.close();
21206
+ if (this.onChanges) {
21207
+ this.createWatcher();
21208
+ }
20071
21209
  }
20072
21210
  handleChange(watcher, type, filePath) {
20073
21211
  if (this.watcher !== watcher) {
20074
21212
  return;
20075
21213
  }
20076
21214
  if (this.isProjectConfigPath(filePath)) {
21215
+ this.updateConfigPathState(filePath);
20077
21216
  this.pendingChanges.set(filePath, type);
20078
21217
  this.scheduleFlush();
20079
21218
  return;
@@ -20088,27 +21227,33 @@ var FileWatcher = class {
20088
21227
  )) {
20089
21228
  return;
20090
21229
  }
20091
- this.pendingChanges.set(filePath, type);
21230
+ this.recordChanges([{ path: filePath, type }]);
21231
+ }
21232
+ recordChanges(changes) {
21233
+ if (changes.length === 0) return;
21234
+ for (const change of changes) {
21235
+ this.pendingChanges.set(change.path, change.type);
21236
+ }
20092
21237
  this.scheduleFlush();
20093
21238
  }
20094
21239
  isProjectConfigPath(filePath) {
20095
- const relativePath = path26.relative(this.projectRoot, filePath);
20096
- const normalizedRelativePath = path26.normalize(relativePath);
21240
+ const relativePath = path28.relative(this.projectRoot, filePath);
21241
+ const normalizedRelativePath = path28.normalize(relativePath);
20097
21242
  return this.getProjectConfigRelativePaths().some((configPath) => configPath === normalizedRelativePath);
20098
21243
  }
20099
21244
  isProjectConfigPathOrAncestor(relativePath) {
20100
- const normalizedRelativePath = path26.normalize(relativePath);
21245
+ const normalizedRelativePath = path28.normalize(relativePath);
20101
21246
  return this.getProjectConfigRelativePaths().some(
20102
- (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path26.sep}`)
21247
+ (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path28.sep}`)
20103
21248
  );
20104
21249
  }
20105
21250
  isOutsideProjectPath(relativePath) {
20106
- return relativePath === ".." || relativePath.startsWith(`..${path26.sep}`) || path26.isAbsolute(relativePath);
21251
+ return relativePath === ".." || relativePath.startsWith(`..${path28.sep}`) || path28.isAbsolute(relativePath);
20107
21252
  }
20108
21253
  getNearestExistingDirectory(directoryPath) {
20109
21254
  let candidate = directoryPath;
20110
21255
  while (!existsSync15(candidate)) {
20111
- const parent = path26.dirname(candidate);
21256
+ const parent = path28.dirname(candidate);
20112
21257
  if (parent === candidate) break;
20113
21258
  candidate = parent;
20114
21259
  }
@@ -20116,9 +21261,51 @@ var FileWatcher = class {
20116
21261
  }
20117
21262
  getProjectConfigRelativePaths() {
20118
21263
  return this.projectConfigPaths.map(
20119
- (configPath) => path26.normalize(path26.relative(this.projectRoot, configPath))
21264
+ (configPath) => path28.normalize(path28.relative(this.projectRoot, configPath))
20120
21265
  );
20121
21266
  }
21267
+ getConfigPathStates() {
21268
+ const states = /* @__PURE__ */ new Map();
21269
+ for (const configPath of this.projectConfigPaths) {
21270
+ const state = this.getConfigPathState(configPath);
21271
+ if (state) states.set(configPath, state);
21272
+ }
21273
+ return states;
21274
+ }
21275
+ getConfigPathState(configPath) {
21276
+ try {
21277
+ const stats = statSync6(configPath);
21278
+ return stats.isFile() ? { mtimeMs: stats.mtimeMs, size: stats.size } : void 0;
21279
+ } catch (error) {
21280
+ void error;
21281
+ return void 0;
21282
+ }
21283
+ }
21284
+ updateConfigPathState(configPath) {
21285
+ const state = this.getConfigPathState(configPath);
21286
+ if (state) {
21287
+ this.configPathStates.set(configPath, state);
21288
+ } else {
21289
+ this.configPathStates.delete(configPath);
21290
+ }
21291
+ }
21292
+ reconcileConfigPathStates() {
21293
+ const nextStates = this.getConfigPathStates();
21294
+ const changes = [];
21295
+ for (const configPath of this.projectConfigPaths) {
21296
+ const previous = this.configPathStates.get(configPath);
21297
+ const next = nextStates.get(configPath);
21298
+ if (!previous && next) {
21299
+ changes.push({ path: configPath, type: "add" });
21300
+ } else if (previous && !next) {
21301
+ changes.push({ path: configPath, type: "unlink" });
21302
+ } else if (previous && next && (previous.size !== next.size || previous.mtimeMs !== next.mtimeMs)) {
21303
+ changes.push({ path: configPath, type: "change" });
21304
+ }
21305
+ }
21306
+ this.configPathStates = nextStates;
21307
+ this.recordChanges(changes);
21308
+ }
20122
21309
  scheduleFlush() {
20123
21310
  if (this.debounceTimer) {
20124
21311
  clearTimeout(this.debounceTimer);
@@ -20132,7 +21319,7 @@ var FileWatcher = class {
20132
21319
  return;
20133
21320
  }
20134
21321
  const changes = Array.from(this.pendingChanges.entries()).map(
20135
- ([path31, type]) => ({ path: path31, type })
21322
+ ([path33, type]) => ({ path: path33, type })
20136
21323
  );
20137
21324
  this.pendingChanges.clear();
20138
21325
  try {
@@ -20146,20 +21333,31 @@ var FileWatcher = class {
20146
21333
  clearTimeout(this.debounceTimer);
20147
21334
  this.debounceTimer = null;
20148
21335
  }
21336
+ if (this.nativeReconcileTimer) {
21337
+ clearTimeout(this.nativeReconcileTimer);
21338
+ this.nativeReconcileTimer = null;
21339
+ }
21340
+ this.nativeInvalidatedPaths.clear();
20149
21341
  const watcher = this.watcher;
21342
+ const nativeWatcher = this.nativeWatcher;
20150
21343
  const pendingClose = this.pendingClose;
20151
21344
  const resolveReady = this.resolveReady;
20152
21345
  this.watcher = null;
21346
+ this.nativeWatcher = null;
21347
+ this.nativeReconciler = null;
21348
+ this.nativeStarting = false;
21349
+ this.nativeInitializing = false;
21350
+ this.nativeSetupGeneration += 1;
20153
21351
  this.pendingClose = null;
20154
21352
  this.resolveReady = null;
20155
21353
  this.readyPromise = null;
20156
21354
  this.pendingChanges.clear();
20157
21355
  this.onChanges = null;
20158
- await Promise.all([watcher?.close(), pendingClose]);
21356
+ await Promise.all([watcher?.close(), nativeWatcher?.stop(), pendingClose]);
20159
21357
  resolveReady?.();
20160
21358
  }
20161
21359
  isRunning() {
20162
- return this.watcher !== null;
21360
+ return this.watcher !== null || this.nativeWatcher !== null || this.nativeStarting;
20163
21361
  }
20164
21362
  async waitUntilReady() {
20165
21363
  await (this.readyPromise ?? Promise.resolve());
@@ -20167,7 +21365,7 @@ var FileWatcher = class {
20167
21365
  };
20168
21366
 
20169
21367
  // src/watcher/git-head-watcher.ts
20170
- import * as path27 from "path";
21368
+ import * as path29 from "path";
20171
21369
  var GitHeadWatcher = class {
20172
21370
  watcher = null;
20173
21371
  projectRoot;
@@ -20189,13 +21387,13 @@ var GitHeadWatcher = class {
20189
21387
  this.readyPromise = Promise.resolve();
20190
21388
  return;
20191
21389
  }
20192
- this.readyPromise = new Promise((resolve18) => {
20193
- this.resolveReady = resolve18;
21390
+ this.readyPromise = new Promise((resolve20) => {
21391
+ this.resolveReady = resolve20;
20194
21392
  });
20195
21393
  this.onBranchChange = handler;
20196
21394
  this.currentBranch = getCurrentBranch(this.projectRoot);
20197
21395
  const headPath = getHeadPath(this.projectRoot);
20198
- const refsPath = path27.join(this.projectRoot, ".git", "refs", "heads");
21396
+ const refsPath = path29.join(this.projectRoot, ".git", "refs", "heads");
20199
21397
  this.watcher = chokidar_default.watch([headPath, refsPath], {
20200
21398
  persistent: true,
20201
21399
  ignoreInitial: true,
@@ -20331,7 +21529,7 @@ function getConfigPaths(projectRoot, host, options) {
20331
21529
 
20332
21530
  // src/tools/visualize/activity.ts
20333
21531
  import { execFileSync } from "child_process";
20334
- import * as path28 from "path";
21532
+ import * as path30 from "path";
20335
21533
  function attachRecentActivity(data, projectRoot) {
20336
21534
  const activity = readGitActivity(projectRoot);
20337
21535
  const changes = activity.size > 0 ? buildGitChanges(data, activity, projectRoot) : buildGraphChanges(data);
@@ -20493,7 +21691,7 @@ function normalizePath4(filePath) {
20493
21691
  return filePath.replace(/\\/g, "/");
20494
21692
  }
20495
21693
  function toGitRelativePath(projectRoot, filePath) {
20496
- const relativePath = path28.isAbsolute(filePath) ? path28.relative(projectRoot, filePath) : filePath;
21694
+ const relativePath = path30.isAbsolute(filePath) ? path30.relative(projectRoot, filePath) : filePath;
20497
21695
  return normalizePath4(relativePath);
20498
21696
  }
20499
21697
 
@@ -20751,7 +21949,7 @@ render();
20751
21949
  }
20752
21950
 
20753
21951
  // src/tools/visualize/transform.ts
20754
- import * as path29 from "path";
21952
+ import * as path31 from "path";
20755
21953
 
20756
21954
  // src/tools/visualize/modules.ts
20757
21955
  var MAX_MODULES = 18;
@@ -20884,8 +22082,8 @@ function compactModules(prefixToNodes) {
20884
22082
  function deriveModules(nodes) {
20885
22083
  const initial = /* @__PURE__ */ new Map();
20886
22084
  for (const node of nodes) {
20887
- const relative12 = stripToProjectRelative(node.filePath);
20888
- const prefix = modulePrefixFromRelativePath(relative12);
22085
+ const relative14 = stripToProjectRelative(node.filePath);
22086
+ const prefix = modulePrefixFromRelativePath(relative14);
20889
22087
  if (!initial.has(prefix)) initial.set(prefix, []);
20890
22088
  initial.get(prefix)?.push(node);
20891
22089
  }
@@ -21011,7 +22209,7 @@ function transformForVisualization(symbols, edges, options = {}) {
21011
22209
  filePath: s.filePath,
21012
22210
  kind: s.kind,
21013
22211
  line: s.startLine,
21014
- directory: path29.dirname(s.filePath),
22212
+ directory: path31.dirname(s.filePath),
21015
22213
  moduleId: "",
21016
22214
  moduleLabel: ""
21017
22215
  }));
@@ -21039,9 +22237,9 @@ function parseArgs(argv) {
21039
22237
  let host = "opencode";
21040
22238
  for (let i = 2; i < argv.length; i++) {
21041
22239
  if (argv[i] === "--project" && argv[i + 1]) {
21042
- project = path30.resolve(argv[++i]);
22240
+ project = path32.resolve(argv[++i]);
21043
22241
  } else if (argv[i] === "--config" && argv[i + 1]) {
21044
- config = path30.resolve(argv[++i]);
22242
+ config = path32.resolve(argv[++i]);
21045
22243
  } else if (argv[i] === "--host" && argv[i + 1]) {
21046
22244
  host = parseHostMode(argv[++i]);
21047
22245
  } else if (argv[i] === "--host") {
@@ -21068,7 +22266,7 @@ function parseIndexArgs(argv, cwd) {
21068
22266
  if (!arg.startsWith("--project=")) {
21069
22267
  i += 1;
21070
22268
  }
21071
- project = path30.resolve(cwd, value);
22269
+ project = path32.resolve(cwd, value);
21072
22270
  continue;
21073
22271
  }
21074
22272
  if (arg === "--config" || arg.startsWith("--config=")) {
@@ -21079,7 +22277,7 @@ function parseIndexArgs(argv, cwd) {
21079
22277
  if (!arg.startsWith("--config=")) {
21080
22278
  i += 1;
21081
22279
  }
21082
- config = path30.resolve(cwd, value);
22280
+ config = path32.resolve(cwd, value);
21083
22281
  continue;
21084
22282
  }
21085
22283
  if (arg === "--host" || arg.startsWith("--host=")) {
@@ -21145,7 +22343,7 @@ function parseVisualizeArgs(argv, cwd) {
21145
22343
  for (let i = 0; i < argv.length; i++) {
21146
22344
  const arg = argv[i];
21147
22345
  if (arg === "--project" && argv[i + 1]) {
21148
- project = path30.resolve(argv[++i]);
22346
+ project = path32.resolve(argv[++i]);
21149
22347
  } else if (arg === "--max" && argv[i + 1]) {
21150
22348
  maxNodes = Number(argv[++i]);
21151
22349
  } else if (arg.startsWith("--max=") || arg.startsWith("max=")) {
@@ -21182,7 +22380,7 @@ async function handleVisualizeCommand(argv, cwd) {
21182
22380
  console.error("No connected symbols found. Retry with: npm run visualize -- orphans");
21183
22381
  return 1;
21184
22382
  }
21185
- const outputPath = path30.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
22383
+ const outputPath = path32.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
21186
22384
  writeFileSync6(outputPath, generateVisualizationHtml(vizData), "utf-8");
21187
22385
  console.log(`Temporal call graph visualization generated: ${outputPath}`);
21188
22386
  console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
@@ -21214,8 +22412,60 @@ async function runMcpCli(argv) {
21214
22412
  const config = parseConfig(rawConfig);
21215
22413
  const server = createMcpServer(args.project, config, args.host);
21216
22414
  const transport = new StdioServerTransport();
21217
- await server.connect(transport);
21218
22415
  let watcher = null;
22416
+ let shutdownPromise;
22417
+ const onServerClose = server.server.onclose;
22418
+ const shutdown = () => {
22419
+ if (shutdownPromise) return shutdownPromise;
22420
+ process.stdin.removeListener("end", requestShutdown);
22421
+ process.stdin.removeListener("close", requestShutdown);
22422
+ process.removeListener("SIGHUP", requestShutdown);
22423
+ process.removeListener("SIGINT", requestShutdown);
22424
+ process.removeListener("SIGTERM", requestShutdown);
22425
+ server.server.onclose = onServerClose;
22426
+ shutdownPromise = (async () => {
22427
+ let exitCode = 0;
22428
+ try {
22429
+ await watcher?.stop();
22430
+ } catch (error) {
22431
+ exitCode = 1;
22432
+ console.error("Failed to stop MCP file watcher cleanly:", error);
22433
+ }
22434
+ try {
22435
+ await stopAutoIndex(args.project, args.host);
22436
+ } catch (error) {
22437
+ exitCode = 1;
22438
+ console.error("Failed to stop automatic indexing cleanly:", error);
22439
+ }
22440
+ try {
22441
+ await server.close();
22442
+ } catch (error) {
22443
+ exitCode = 1;
22444
+ console.error("Failed to close MCP server cleanly:", error);
22445
+ }
22446
+ process.exit(exitCode);
22447
+ })();
22448
+ return shutdownPromise;
22449
+ };
22450
+ const requestShutdown = () => {
22451
+ void shutdown();
22452
+ };
22453
+ server.server.onclose = () => {
22454
+ try {
22455
+ onServerClose?.();
22456
+ } finally {
22457
+ requestShutdown();
22458
+ }
22459
+ };
22460
+ process.stdin.once("end", requestShutdown);
22461
+ process.stdin.once("close", requestShutdown);
22462
+ process.once("SIGINT", requestShutdown);
22463
+ if (process.platform !== "win32") {
22464
+ process.once("SIGHUP", requestShutdown);
22465
+ process.once("SIGTERM", requestShutdown);
22466
+ }
22467
+ await server.connect(transport);
22468
+ if (shutdownPromise) return;
21219
22469
  const isHomeDir = isHomeDirectory(args.project);
21220
22470
  const isValidProject = !isHomeDir && (!config.indexing.requireProjectMarker || hasProjectMarker(args.project));
21221
22471
  if (config.indexing.watchFiles && isValidProject) {
@@ -21227,26 +22477,6 @@ async function runMcpCli(argv) {
21227
22477
  args.config ? { configPath: args.config } : {}
21228
22478
  );
21229
22479
  }
21230
- let shuttingDown = false;
21231
- const shutdown = async () => {
21232
- if (shuttingDown) return;
21233
- shuttingDown = true;
21234
- try {
21235
- await watcher?.stop();
21236
- await stopAutoIndex(args.project, args.host);
21237
- await server.close();
21238
- process.exit(0);
21239
- } catch (error) {
21240
- console.error("Failed to stop MCP server cleanly:", error);
21241
- process.exit(1);
21242
- }
21243
- };
21244
- process.on("SIGINT", () => {
21245
- void shutdown();
21246
- });
21247
- process.on("SIGTERM", () => {
21248
- void shutdown();
21249
- });
21250
22480
  }
21251
22481
  function printIndexProgress(onProgress, title, metadata) {
21252
22482
  const details = Object.entries(metadata).filter(([, value]) => value !== void 0 && value !== null).map(([key, value]) => `${key}=${isSensitiveKey(key) ? "[REDACTED]" : String(value)}`).join(" ");