opencode-codebase-index 0.23.0 → 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/index.js CHANGED
@@ -712,6 +712,17 @@ var EMBEDDING_MODELS = {
712
712
  maxTokens: 2048,
713
713
  costPer1MTokens: 0.15,
714
714
  taskAble: true
715
+ },
716
+ "gemini-embedding-2": {
717
+ provider: "google",
718
+ model: "gemini-embedding-2",
719
+ // Keep a conservative, testable default embedding dimension. Gemini Embedding 2 supports
720
+ // flexible dimensions via outputDimensionality.
721
+ dimensions: 1536,
722
+ maxTokens: 8192,
723
+ costPer1MTokens: 0.15,
724
+ taskAble: false,
725
+ promptStyle: "embedding-2"
715
726
  }
716
727
  },
717
728
  "openai": {
@@ -745,26 +756,15 @@ var EMBEDDING_MODELS = {
745
756
  maxTokens: 512,
746
757
  costPer1MTokens: 0
747
758
  }
748
- },
749
- "github-copilot": {
750
- "text-embedding-3-small": {
751
- provider: "github-copilot",
752
- model: "text-embedding-3-small",
753
- dimensions: 1536,
754
- maxTokens: 8191,
755
- costPer1MTokens: 0
756
- }
757
759
  }
758
760
  };
759
761
  var DEFAULT_PROVIDER_MODELS = {
760
- "github-copilot": "text-embedding-3-small",
761
762
  "openai": "text-embedding-3-small",
762
763
  "google": "gemini-embedding-001",
763
764
  "ollama": "nomic-embed-text"
764
765
  };
765
766
  var AUTO_DETECT_PROVIDER_ORDER = [
766
767
  "ollama",
767
- "github-copilot",
768
768
  "openai",
769
769
  "google"
770
770
  ];
@@ -790,6 +790,9 @@ function getDefaultIndexingConfig() {
790
790
  maxDepth: 5,
791
791
  maxFilesPerDirectory: 100,
792
792
  fallbackToTextOnMaxChunks: true,
793
+ // Must stay in sync with DEFAULT_LINES_PER_CHUNK in native/src/lib.rs (the napi
794
+ // fallback used when a native caller omits the argument).
795
+ linesPerChunk: 30,
793
796
  gitBlame: { enabled: false }
794
797
  };
795
798
  }
@@ -923,6 +926,7 @@ function parseConfig(raw) {
923
926
  maxDepth: typeof rawIndexing.maxDepth === "number" ? rawIndexing.maxDepth < -1 ? -1 : rawIndexing.maxDepth : defaultIndexing.maxDepth,
924
927
  maxFilesPerDirectory: typeof rawIndexing.maxFilesPerDirectory === "number" ? Math.max(1, rawIndexing.maxFilesPerDirectory) : defaultIndexing.maxFilesPerDirectory,
925
928
  fallbackToTextOnMaxChunks: typeof rawIndexing.fallbackToTextOnMaxChunks === "boolean" ? rawIndexing.fallbackToTextOnMaxChunks : defaultIndexing.fallbackToTextOnMaxChunks,
929
+ linesPerChunk: typeof rawIndexing.linesPerChunk === "number" && Number.isFinite(rawIndexing.linesPerChunk) ? Math.min(Math.max(1, Math.floor(rawIndexing.linesPerChunk)), 4294967295) : defaultIndexing.linesPerChunk,
926
930
  gitBlame: {
927
931
  enabled: rawIndexing.gitBlame && typeof rawIndexing.gitBlame === "object" && typeof rawIndexing.gitBlame.enabled === "boolean" ? rawIndexing.gitBlame.enabled : defaultIndexing.gitBlame.enabled
928
932
  }
@@ -965,6 +969,7 @@ function parseConfig(raw) {
965
969
  let embeddingModel;
966
970
  let customProvider;
967
971
  let reranker;
972
+ 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.';
968
973
  if (embeddingProviderValue === "custom") {
969
974
  embeddingProvider = "custom";
970
975
  const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
@@ -1004,6 +1009,8 @@ function parseConfig(raw) {
1004
1009
  } else if (rawEmbeddingModel) {
1005
1010
  embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
1006
1011
  }
1012
+ } else if (embeddingProviderValue === "github-copilot") {
1013
+ throw new Error(githubCopilotDeprecationMessage);
1007
1014
  } else {
1008
1015
  embeddingProvider = "auto";
1009
1016
  }
@@ -1034,10 +1041,21 @@ function parseConfig(raw) {
1034
1041
  timeoutMs: typeof rawReranker.timeoutMs === "number" ? Math.max(1e3, Math.floor(rawReranker.timeoutMs)) : 1e4
1035
1042
  };
1036
1043
  }
1044
+ const rawEmbedding = input.embedding && typeof input.embedding === "object" ? input.embedding : {};
1045
+ const rawEmbeddingBatch = rawEmbedding.batch && typeof rawEmbedding.batch === "object" ? rawEmbedding.batch : null;
1046
+ const embeddingMaxBatchItems = typeof rawEmbeddingBatch?.maxBatchItems === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchItems) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchItems)) : void 0;
1047
+ const embeddingMaxBatchTokens = typeof rawEmbeddingBatch?.maxBatchTokens === "number" && Number.isFinite(rawEmbeddingBatch.maxBatchTokens) ? Math.max(1, Math.floor(rawEmbeddingBatch.maxBatchTokens)) : void 0;
1048
+ const embedding = embeddingMaxBatchItems !== void 0 || embeddingMaxBatchTokens !== void 0 ? {
1049
+ batch: {
1050
+ ...embeddingMaxBatchItems !== void 0 ? { maxBatchItems: embeddingMaxBatchItems } : {},
1051
+ ...embeddingMaxBatchTokens !== void 0 ? { maxBatchTokens: embeddingMaxBatchTokens } : {}
1052
+ }
1053
+ } : {};
1037
1054
  return {
1038
1055
  embeddingProvider,
1039
1056
  embeddingModel,
1040
1057
  customProvider,
1058
+ embedding,
1041
1059
  scope: isValidScope(scopeValue) ? scopeValue : "project",
1042
1060
  include: includeValue ?? DEFAULT_INCLUDE,
1043
1061
  exclude: excludeValue ?? DEFAULT_EXCLUDE,
@@ -2304,6 +2322,10 @@ function scoreCandidate(query, intent, candidate, originalIndex) {
2304
2322
  let boost = 0;
2305
2323
  if (intent.primary === "conceptual") {
2306
2324
  boost += Math.min(0.14, overlap * 0.14);
2325
+ if (intent.preferSourcePaths) {
2326
+ boost += implementationPath ? 0.32 : 0;
2327
+ if (testPath || fixturePath || docsPath) boost -= 0.35;
2328
+ }
2307
2329
  if (generatedOrVendor) boost -= 0.18;
2308
2330
  if (importChunk || weakContainer) boost -= 0.04;
2309
2331
  } else if (intent.primary === "test") {
@@ -3281,6 +3303,19 @@ function parseOwner(value) {
3281
3303
  if (typeof candidate.startedAt !== "string" || Number.isNaN(Date.parse(candidate.startedAt))) return null;
3282
3304
  if (typeof candidate.operation !== "string" || !VALID_OPERATIONS.has(candidate.operation)) return null;
3283
3305
  if (typeof candidate.token !== "string" || !UUID_PATTERN.test(candidate.token)) return null;
3306
+ if (candidate.recoveryProtocolVersion !== void 0 && candidate.recoveryProtocolVersion !== 1) return null;
3307
+ if (candidate.projectRoot !== void 0 && typeof candidate.projectRoot !== "string") return null;
3308
+ if (candidate.scopedRoots !== void 0) {
3309
+ if (!Array.isArray(candidate.scopedRoots) || candidate.scopedRoots.some((root) => typeof root !== "string")) {
3310
+ return null;
3311
+ }
3312
+ }
3313
+ if (candidate.clearRecovery !== void 0) {
3314
+ const recovery = candidate.clearRecovery;
3315
+ 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") {
3316
+ return null;
3317
+ }
3318
+ }
3284
3319
  return candidate;
3285
3320
  }
3286
3321
  function parseReclaimOwner(value) {
@@ -3521,13 +3556,18 @@ function isTransientIndexLockContention(error) {
3521
3556
  if (!isIndexLockContentionError(error) || !("reason" in error)) return false;
3522
3557
  return error.reason === "active" || error.reason === "reclaiming";
3523
3558
  }
3524
- function acquireIndexLock(indexPath, operation) {
3559
+ function acquireIndexLock(indexPath, operation, recoveryScope) {
3525
3560
  mkdirSync(indexPath, { recursive: true });
3526
3561
  const canonicalIndexPath = realpathSync2.native(indexPath);
3527
3562
  const lockPath = path9.join(canonicalIndexPath, "indexing.lock");
3528
3563
  cleanupDeadPublicationCandidates(canonicalIndexPath);
3529
3564
  for (let attempt = 0; attempt < 6; attempt += 1) {
3530
- const owner = createOwner(operation);
3565
+ const owner = recoveryScope === void 0 ? createOwner(operation) : {
3566
+ ...createOwner(operation),
3567
+ recoveryProtocolVersion: 1,
3568
+ projectRoot: recoveryScope.projectRoot,
3569
+ scopedRoots: recoveryScope.scopedRoots
3570
+ };
3531
3571
  if (publishJsonDirectory(lockPath, owner)) {
3532
3572
  const lease = {
3533
3573
  canonicalIndexPath,
@@ -3592,6 +3632,33 @@ function releaseIndexLock(lease) {
3592
3632
  }
3593
3633
  return true;
3594
3634
  }
3635
+ function setIndexLockClearRecoveryState(lease, clearRecovery) {
3636
+ const currentOwner = readDirectoryOwner(lease.lockPath);
3637
+ if (!currentOwner || !sameOwner(currentOwner, lease.owner)) {
3638
+ throw new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
3639
+ }
3640
+ const nextOwner = { ...currentOwner };
3641
+ if (clearRecovery === null) {
3642
+ delete nextOwner.clearRecovery;
3643
+ } else {
3644
+ nextOwner.clearRecovery = clearRecovery;
3645
+ }
3646
+ const ownerPath = path9.join(lease.lockPath, OWNER_FILE_NAME);
3647
+ const temporaryPath = path9.join(
3648
+ lease.lockPath,
3649
+ `${OWNER_FILE_NAME}.tmp.${lease.owner.pid}.${lease.owner.token}.${randomUUID()}`
3650
+ );
3651
+ try {
3652
+ writeFileSync(temporaryPath, JSON.stringify(nextOwner), {
3653
+ encoding: "utf-8",
3654
+ flag: "wx",
3655
+ mode: 384
3656
+ });
3657
+ retryTransientFilesystemOperation(() => renameSync(temporaryPath, ownerPath));
3658
+ } finally {
3659
+ if (existsSync5(temporaryPath)) rmSync(temporaryPath, { force: true });
3660
+ }
3661
+ }
3595
3662
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
3596
3663
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
3597
3664
  temporaryCounter += 1;
@@ -5735,8 +5802,6 @@ async function tryDetectProvider() {
5735
5802
  }
5736
5803
  async function getProviderCredentials(provider) {
5737
5804
  switch (provider) {
5738
- case "github-copilot":
5739
- return getGitHubCopilotCredentials();
5740
5805
  case "openai":
5741
5806
  return getOpenAICredentials();
5742
5807
  case "google":
@@ -5747,22 +5812,6 @@ async function getProviderCredentials(provider) {
5747
5812
  return null;
5748
5813
  }
5749
5814
  }
5750
- function getGitHubCopilotCredentials() {
5751
- const authData = loadOpenCodeAuth();
5752
- const copilotAuth = authData["github-copilot"] || authData["github-copilot-enterprise"];
5753
- if (!copilotAuth || copilotAuth.type !== "oauth") {
5754
- return null;
5755
- }
5756
- const auth = copilotAuth;
5757
- const baseUrl = auth.enterpriseUrl ? `https://copilot-api.${auth.enterpriseUrl.replace(/^https?:\/\//, "").replace(/\/$/, "")}` : "https://models.github.ai";
5758
- return {
5759
- provider: "github-copilot",
5760
- baseUrl,
5761
- refreshToken: copilotAuth.refresh,
5762
- accessToken: copilotAuth.access,
5763
- tokenExpires: copilotAuth.expires
5764
- };
5765
- }
5766
5815
  function getOpenAICredentials() {
5767
5816
  const authData = loadOpenCodeAuth();
5768
5817
  const openaiAuth = authData["openai"];
@@ -5888,8 +5937,6 @@ async function tryDetectOllamaProvider() {
5888
5937
  }
5889
5938
  function getProviderDisplayName(provider) {
5890
5939
  switch (provider) {
5891
- case "github-copilot":
5892
- return "GitHub Copilot";
5893
5940
  case "openai":
5894
5941
  return "OpenAI";
5895
5942
  case "google":
@@ -6114,44 +6161,6 @@ var CustomEmbeddingProvider = class extends BaseEmbeddingProvider {
6114
6161
  }
6115
6162
  };
6116
6163
 
6117
- // src/embeddings/providers/github-copilot.ts
6118
- var GitHubCopilotEmbeddingProvider = class extends BaseEmbeddingProvider {
6119
- constructor(credentials, modelInfo) {
6120
- super(credentials, modelInfo);
6121
- }
6122
- getToken() {
6123
- if (!this.credentials.refreshToken) {
6124
- throw new Error("No OAuth token available for GitHub");
6125
- }
6126
- return this.credentials.refreshToken;
6127
- }
6128
- async embedBatch(texts) {
6129
- const token = this.getToken();
6130
- const response = await fetch(`${this.credentials.baseUrl}/inference/embeddings`, {
6131
- method: "POST",
6132
- headers: {
6133
- Authorization: `Bearer ${token}`,
6134
- "Content-Type": "application/json",
6135
- Accept: "application/vnd.github+json",
6136
- "X-GitHub-Api-Version": "2022-11-28"
6137
- },
6138
- body: JSON.stringify({
6139
- model: `openai/${this.modelInfo.model}`,
6140
- input: texts
6141
- })
6142
- });
6143
- if (!response.ok) {
6144
- const error = (await response.text()).slice(0, 500);
6145
- throw new Error(`GitHub Copilot embedding API error: ${response.status} - ${error}`);
6146
- }
6147
- const data = await response.json();
6148
- return {
6149
- embeddings: data.data.map((d) => d.embedding),
6150
- totalTokensUsed: data.usage.total_tokens
6151
- };
6152
- }
6153
- };
6154
-
6155
6164
  // src/embeddings/providers/google.ts
6156
6165
  var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddingProvider {
6157
6166
  static BATCH_SIZE = 20;
@@ -6159,24 +6168,30 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6159
6168
  super(credentials, modelInfo);
6160
6169
  }
6161
6170
  async embedQuery(query) {
6162
- const taskType = this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6163
- const result = await this.embedWithTaskType([query], taskType);
6171
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "CODE_RETRIEVAL_QUERY" : void 0;
6172
+ const texts = [
6173
+ this.modelInfo.model === "gemini-embedding-2" ? `task: code retrieval | query: ${query}` : query
6174
+ ];
6175
+ const result = await this.embedWithTaskType(texts, taskType);
6164
6176
  return {
6165
6177
  embedding: result.embeddings[0],
6166
6178
  tokensUsed: result.totalTokensUsed
6167
6179
  };
6168
6180
  }
6169
6181
  async embedDocument(document) {
6170
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6171
- const result = await this.embedWithTaskType([document], taskType);
6182
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6183
+ const result = await this.embedWithTaskType([
6184
+ this.modelInfo.model === "gemini-embedding-2" ? `title: none | text: ${document}` : document
6185
+ ], taskType);
6172
6186
  return {
6173
6187
  embedding: result.embeddings[0],
6174
6188
  tokensUsed: result.totalTokensUsed
6175
6189
  };
6176
6190
  }
6177
6191
  async embedBatch(texts) {
6178
- const taskType = this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6179
- return this.embedWithTaskType(texts, taskType);
6192
+ const taskType = this.modelInfo.model === "gemini-embedding-001" && this.modelInfo.taskAble ? "RETRIEVAL_DOCUMENT" : void 0;
6193
+ const formattedTexts = this.modelInfo.model === "gemini-embedding-2" ? texts.map((text) => `title: none | text: ${text}`) : texts;
6194
+ return this.embedWithTaskType(formattedTexts, taskType);
6180
6195
  }
6181
6196
  async embedWithTaskType(texts, taskType) {
6182
6197
  const batches = [];
@@ -6226,6 +6241,10 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
6226
6241
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
6227
6242
  static MIN_TRUNCATION_CHARS = 512;
6228
6243
  static REQUEST_TIMEOUT_MS = 12e4;
6244
+ // Set when /api/embed returns 404 so subsequent multi-text batches skip the
6245
+ // batched endpoint and go straight to the legacy per-text path (one probe per
6246
+ // old ollama install, not one probe per batch).
6247
+ batchEndpointUnavailable = false;
6229
6248
  constructor(credentials, modelInfo) {
6230
6249
  super(credentials, modelInfo);
6231
6250
  }
@@ -6243,6 +6262,21 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6243
6262
  const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
6244
6263
  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");
6245
6264
  }
6265
+ // True for a 404 from the newer /api/embed endpoint, i.e. an ollama version that
6266
+ // does not provide it. embedBatch uses this to fall back to the legacy per-text
6267
+ // /api/embeddings path so old ollama installs do not regress.
6268
+ isBatchEndpointUnavailableError(error) {
6269
+ const message = error instanceof Error ? error.message : String(error);
6270
+ return message.includes("Ollama /api/embed not available");
6271
+ }
6272
+ // True for a malformed /api/embed response (wrong vector count or a bad vector).
6273
+ // embedBatch falls back to the per-text path on this so a bad batch response
6274
+ // re-embeds each text cleanly. A text that then fails per-text is not isolated
6275
+ // here; it is isolated on the recovery run, which re-embeds one text per request.
6276
+ isBatchValidationError(error) {
6277
+ const message = error instanceof Error ? error.message : String(error);
6278
+ return message.includes("invalid embedding batch");
6279
+ }
6246
6280
  buildTruncationCandidates(text) {
6247
6281
  const baseMaxChars = Math.max(1, this.modelInfo.maxTokens * 4);
6248
6282
  const candidateLimits = /* @__PURE__ */ new Set();
@@ -6344,7 +6378,74 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6344
6378
  tokensUsed: this.estimateTokens(text)
6345
6379
  };
6346
6380
  }
6347
- async embedBatch(texts) {
6381
+ // Embeds many texts in one POST /api/embed request (input: string[]). Ollama
6382
+ // encodes each input independently, so the model context length applies per input
6383
+ // (the upstream splitter already bounds each input), not over the batch. This
6384
+ // amortizes N HTTP round-trips into one.
6385
+ async embedMany(texts) {
6386
+ const controller = new AbortController();
6387
+ const timeout = setTimeout(
6388
+ () => controller.abort(),
6389
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
6390
+ );
6391
+ let response;
6392
+ try {
6393
+ response = await fetch(`${this.credentials.baseUrl}/api/embed`, {
6394
+ method: "POST",
6395
+ headers: {
6396
+ "Content-Type": "application/json"
6397
+ },
6398
+ body: JSON.stringify({
6399
+ model: this.modelInfo.model,
6400
+ input: texts,
6401
+ truncate: false
6402
+ }),
6403
+ signal: controller.signal
6404
+ });
6405
+ } catch (error) {
6406
+ if (error instanceof Error && error.name === "AbortError") {
6407
+ throw new Error(
6408
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
6409
+ );
6410
+ }
6411
+ throw error;
6412
+ } finally {
6413
+ clearTimeout(timeout);
6414
+ }
6415
+ if (!response.ok) {
6416
+ const error = (await response.text()).slice(0, 500);
6417
+ if (response.status === 404) {
6418
+ throw new Error(`Ollama /api/embed not available: ${response.status} - ${error}`);
6419
+ }
6420
+ throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
6421
+ }
6422
+ let parsed;
6423
+ try {
6424
+ parsed = await response.json();
6425
+ } catch {
6426
+ throw new Error(
6427
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6428
+ );
6429
+ }
6430
+ const data = parsed && typeof parsed === "object" ? parsed : {};
6431
+ if (!Array.isArray(data.embeddings) || data.embeddings.length !== texts.length || data.embeddings.some(
6432
+ (value) => !Array.isArray(value) || value.length !== this.modelInfo.dimensions || value.some((v) => typeof v !== "number" || !Number.isFinite(v))
6433
+ )) {
6434
+ throw new Error(
6435
+ `Ollama returned an invalid embedding batch; expected ${texts.length} vectors of ${this.modelInfo.dimensions} finite dimensions`
6436
+ );
6437
+ }
6438
+ return {
6439
+ embeddings: data.embeddings,
6440
+ totalTokensUsed: texts.reduce((sum, text) => sum + this.estimateTokens(text), 0)
6441
+ };
6442
+ }
6443
+ // Per-text /api/embeddings path shared by the single-text fast path and the
6444
+ // batch fallback. Uses the legacy endpoint one text at a time, so each text gets
6445
+ // its own truncation safety net and a vector validated on its own. A text that
6446
+ // hard-fails per-text throws here and fails the whole request batch; the recovery
6447
+ // run re-embeds one text per request to isolate it.
6448
+ async embedOneByOne(texts) {
6348
6449
  const results = [];
6349
6450
  for (const text of texts) {
6350
6451
  results.push(await this.embedSingleWithFallback(text));
@@ -6354,6 +6455,26 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
6354
6455
  totalTokensUsed: results.reduce((sum, r) => sum + r.tokensUsed, 0)
6355
6456
  };
6356
6457
  }
6458
+ async embedBatch(texts) {
6459
+ if (texts.length === 0) {
6460
+ return { embeddings: [], totalTokensUsed: 0 };
6461
+ }
6462
+ if (texts.length === 1 || this.batchEndpointUnavailable) {
6463
+ return this.embedOneByOne(texts);
6464
+ }
6465
+ try {
6466
+ return await this.embedMany(texts);
6467
+ } catch (error) {
6468
+ if (this.isBatchEndpointUnavailableError(error)) {
6469
+ this.batchEndpointUnavailable = true;
6470
+ return this.embedOneByOne(texts);
6471
+ }
6472
+ if (!this.isContextLengthError(error) && !this.isBatchValidationError(error)) {
6473
+ throw error;
6474
+ }
6475
+ return this.embedOneByOne(texts);
6476
+ }
6477
+ }
6357
6478
  };
6358
6479
 
6359
6480
  // src/embeddings/providers/openai.ts
@@ -6388,8 +6509,6 @@ var OpenAIEmbeddingProvider = class extends BaseEmbeddingProvider {
6388
6509
  // src/embeddings/provider.ts
6389
6510
  function createEmbeddingProvider(configuredProviderInfo) {
6390
6511
  switch (configuredProviderInfo.provider) {
6391
- case "github-copilot":
6392
- return new GitHubCopilotEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6393
6512
  case "openai":
6394
6513
  return new OpenAIEmbeddingProvider(configuredProviderInfo.credentials, configuredProviderInfo.modelInfo);
6395
6514
  case "google":
@@ -7140,12 +7259,12 @@ try {
7140
7259
  }
7141
7260
 
7142
7261
  // src/native/parsing.ts
7143
- function parseFileAsText(filePath, content) {
7144
- const result = native.parseFileAsText(filePath, content);
7262
+ function parseFileAsText(filePath, content, linesPerChunk) {
7263
+ const result = native.parseFileAsText(filePath, content, linesPerChunk);
7145
7264
  return result.map(mapChunk);
7146
7265
  }
7147
- function parseFiles(files) {
7148
- const result = native.parseFiles(files);
7266
+ function parseFiles(files, linesPerChunk) {
7267
+ const result = native.parseFiles(files, linesPerChunk);
7149
7268
  return result.map((f) => ({
7150
7269
  path: f.path,
7151
7270
  chunks: f.chunks.map(mapChunk),
@@ -7222,13 +7341,13 @@ var VectorStore = class {
7222
7341
  const metadata = items.map((i) => JSON.stringify(i.metadata));
7223
7342
  this.inner.addBatch(ids, vectors, metadata);
7224
7343
  }
7225
- search(queryVector, limit = 10) {
7344
+ search(queryVector, limit = 10, allowedIds) {
7226
7345
  if (queryVector.length !== this.dimensions) {
7227
7346
  throw new Error(
7228
7347
  `Query vector dimension mismatch: expected ${this.dimensions}, got ${queryVector.length}`
7229
7348
  );
7230
7349
  }
7231
- const results = this.inner.search(queryVector, limit);
7350
+ const results = allowedIds === void 0 ? this.inner.search(queryVector, limit) : this.inner.searchFiltered(queryVector, limit, allowedIds);
7232
7351
  return results.map((r) => ({
7233
7352
  id: r.id,
7234
7353
  score: r.score,
@@ -7456,6 +7575,10 @@ var Database = class _Database {
7456
7575
  this.throwIfClosed();
7457
7576
  return this.inner.getBranchChunkIds(branch);
7458
7577
  }
7578
+ getChunkIdsByBlameDate(since, until) {
7579
+ this.throwIfClosed();
7580
+ return this.inner.getChunkIdsByBlameDate(since, until);
7581
+ }
7459
7582
  getBranchDelta(branch, baseBranch) {
7460
7583
  this.throwIfClosed();
7461
7584
  return this.inner.getBranchDelta(branch, baseBranch);
@@ -9344,6 +9467,18 @@ function createFailedBatchWriter(targetPath) {
9344
9467
  temporaryPath
9345
9468
  };
9346
9469
  }
9470
+ function writeFailedBatchRecords(targetPath, records) {
9471
+ const writer = createFailedBatchWriter(targetPath);
9472
+ try {
9473
+ for (const record of records) {
9474
+ writer.write(record);
9475
+ }
9476
+ writer.commit();
9477
+ } catch (error) {
9478
+ writer.cleanup();
9479
+ throw error;
9480
+ }
9481
+ }
9347
9482
  function* readLegacyFailedBatchRecords(filePath, options) {
9348
9483
  const rawData = fs2.readFileSync(filePath, "utf-8");
9349
9484
  const trimmed = stripLeadingBomAndWhitespace(rawData).trim();
@@ -9626,14 +9761,18 @@ function getSafeEmbeddingChunkTokenLimit(provider) {
9626
9761
  const maxChunkTokens = Math.max(256, Math.floor(providerMaxTokens * 0.75));
9627
9762
  return Math.min(2e3, maxChunkTokens);
9628
9763
  }
9629
- function getDynamicBatchOptions(provider) {
9630
- if (provider.provider === "ollama") {
9631
- return {
9632
- maxBatchTokens: provider.modelInfo.maxTokens,
9633
- maxBatchItems: 1
9634
- };
9764
+ var DEFAULT_OLLAMA_MAX_BATCH_ITEMS = 16;
9765
+ var DEFAULT_OLLAMA_MAX_BATCH_TOKENS = 65536;
9766
+ function getDynamicBatchOptions(provider, embeddingBatch) {
9767
+ if (provider.provider !== "ollama") {
9768
+ return {};
9635
9769
  }
9636
- return {};
9770
+ const base = { maxBatchTokens: DEFAULT_OLLAMA_MAX_BATCH_TOKENS, maxBatchItems: DEFAULT_OLLAMA_MAX_BATCH_ITEMS };
9771
+ return {
9772
+ ...base,
9773
+ ...typeof embeddingBatch?.maxBatchTokens === "number" && Number.isFinite(embeddingBatch.maxBatchTokens) ? { maxBatchTokens: embeddingBatch.maxBatchTokens } : {},
9774
+ ...typeof embeddingBatch?.maxBatchItems === "number" && Number.isFinite(embeddingBatch.maxBatchItems) ? { maxBatchItems: embeddingBatch.maxBatchItems } : {}
9775
+ };
9637
9776
  }
9638
9777
  function isSqliteCorruptionError(error) {
9639
9778
  const message = getErrorMessage4(error).toLowerCase();
@@ -9651,6 +9790,14 @@ function getPendingChunkId(rawChunk) {
9651
9790
  const id = rawChunk.id;
9652
9791
  return typeof id === "string" ? id : null;
9653
9792
  }
9793
+ function parseBlameTimestamp(value, endOfDay) {
9794
+ let timestampMs = Date.parse(value);
9795
+ if (Number.isNaN(timestampMs)) return null;
9796
+ if (endOfDay && /^\d{4}-\d{2}-\d{2}$/.test(value.trim())) {
9797
+ timestampMs += 24 * 60 * 60 * 1e3 - 1;
9798
+ }
9799
+ return Math.floor(timestampMs / 1e3);
9800
+ }
9654
9801
  function metadataFromBlame(blame) {
9655
9802
  if (!blame) {
9656
9803
  return {};
@@ -9797,7 +9944,7 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
9797
9944
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
9798
9945
  return [...promoted, ...remainder];
9799
9946
  }
9800
- function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
9947
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source", allowNonSourcePaths = false) {
9801
9948
  if (!prioritizeSourcePaths) {
9802
9949
  return [];
9803
9950
  }
@@ -9817,7 +9964,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9817
9964
  if (!isImplementationChunkType(chunkType)) {
9818
9965
  return false;
9819
9966
  }
9820
- if (!isLikelyImplementationPath2(chunk.filePath)) {
9967
+ if (!allowNonSourcePaths && !isLikelyImplementationPath2(chunk.filePath)) {
9821
9968
  return false;
9822
9969
  }
9823
9970
  const nameLower = (chunk.name ?? "").toLowerCase();
@@ -9881,7 +10028,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9881
10028
  }
9882
10029
  foundCoveringChunk = upsertChunkCandidate(chunk, identifier, normalizedIdentifier) || foundCoveringChunk;
9883
10030
  }
9884
- if (foundCoveringChunk || !isLikelyImplementationPath2(symbol.filePath)) {
10031
+ if (foundCoveringChunk || !allowNonSourcePaths && !isLikelyImplementationPath2(symbol.filePath)) {
9885
10032
  continue;
9886
10033
  }
9887
10034
  const symbolName = symbol.name.toLowerCase();
@@ -9935,7 +10082,7 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbol
9935
10082
  const ranked = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
9936
10083
  if (ranked.length === 0) {
9937
10084
  const implementationFallback = fallbackCandidates.filter(
9938
- (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && isLikelyImplementationPath2(candidate.metadata.filePath)
10085
+ (candidate) => isImplementationChunkType(candidate.metadata.chunkType) && (allowNonSourcePaths || isLikelyImplementationPath2(candidate.metadata.filePath))
9939
10086
  );
9940
10087
  for (const candidate of implementationFallback) {
9941
10088
  const nameLower = (candidate.metadata.name ?? "").toLowerCase();
@@ -10051,10 +10198,16 @@ function matchesHardSearchFilters(candidate, options, projectRoot) {
10051
10198
  return false;
10052
10199
  }
10053
10200
  if (options?.blameSince) {
10054
- const sinceMs = Date.parse(options.blameSince);
10055
- if (Number.isNaN(sinceMs)) return false;
10201
+ const since = parseBlameTimestamp(options.blameSince, false);
10202
+ if (since === null) return false;
10203
+ const committedAt = candidate.metadata.blameCommittedAt;
10204
+ if (committedAt === void 0 || committedAt < since) return false;
10205
+ }
10206
+ if (options?.blameUntil) {
10207
+ const until = parseBlameTimestamp(options.blameUntil, true);
10208
+ if (until === null) return false;
10056
10209
  const committedAt = candidate.metadata.blameCommittedAt;
10057
- if (committedAt === void 0 || committedAt < Math.floor(sinceMs / 1e3)) return false;
10210
+ if (committedAt === void 0 || committedAt > until) return false;
10058
10211
  }
10059
10212
  return true;
10060
10213
  }
@@ -10110,9 +10263,10 @@ var Indexer = class _Indexer {
10110
10263
  writerArtifactFingerprint = null;
10111
10264
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
10112
10265
  fileBatchLimits;
10266
+ checkpointIntervalChunks;
10113
10267
  constructor(projectRoot, config, host, runtimeOptions = {}) {
10114
10268
  this.projectRoot = projectRoot;
10115
- this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10269
+ this.projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10116
10270
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
10117
10271
  this.branchNameOverride = runtimeOptions.branchName;
10118
10272
  this.catalogIdentityOverride = runtimeOptions.catalogIdentity;
@@ -10122,6 +10276,7 @@ var Indexer = class _Indexer {
10122
10276
  this.expectedCommitOverride = runtimeOptions.expectedCommit?.toLowerCase();
10123
10277
  this.indexPathOverride = runtimeOptions.indexPath;
10124
10278
  this.fileBatchLimits = runtimeOptions.fileBatchLimits;
10279
+ this.checkpointIntervalChunks = runtimeOptions.checkpointIntervalChunks;
10125
10280
  this.config = config;
10126
10281
  this.host = host;
10127
10282
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -10233,6 +10388,9 @@ var Indexer = class _Indexer {
10233
10388
  return path19.resolve(targetPath);
10234
10389
  }
10235
10390
  }
10391
+ getProjectIdentityHash(projectRoot) {
10392
+ return hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
10393
+ }
10236
10394
  isProjectOwnedIndexPath() {
10237
10395
  return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
10238
10396
  }
@@ -10269,7 +10427,10 @@ var Indexer = class _Indexer {
10269
10427
  }
10270
10428
  async withIndexMutationLease(operation, callback) {
10271
10429
  this.refreshBranchInfo();
10272
- const lease = acquireIndexLock(this.indexPath, operation);
10430
+ const lease = acquireIndexLock(this.indexPath, operation, {
10431
+ projectRoot: this.projectRoot,
10432
+ scopedRoots: this.getScopedRoots()
10433
+ });
10273
10434
  this.indexPath = lease.canonicalIndexPath;
10274
10435
  this.refreshRuntimeArtifactPaths();
10275
10436
  this.activeIndexLease = lease;
@@ -10324,6 +10485,7 @@ var Indexer = class _Indexer {
10324
10485
  }
10325
10486
  loadFileHashCache() {
10326
10487
  if (!existsSync11(this.fileHashCachePath)) {
10488
+ this.fileHashCache = /* @__PURE__ */ new Map();
10327
10489
  return;
10328
10490
  }
10329
10491
  try {
@@ -10363,10 +10525,10 @@ var Indexer = class _Indexer {
10363
10525
  invertedIndex.serialize()
10364
10526
  );
10365
10527
  }
10366
- getScopedRoots() {
10367
- const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(this.projectRoot)]);
10528
+ getScopedRoots(projectRoot = this.projectRoot) {
10529
+ const roots = /* @__PURE__ */ new Set([this.getCanonicalPath(projectRoot)]);
10368
10530
  for (const kbRoot of this.config.knowledgeBases) {
10369
- roots.add(this.getCanonicalPath(path19.resolve(this.projectRoot, kbRoot)));
10531
+ roots.add(this.getCanonicalPath(path19.resolve(projectRoot, kbRoot)));
10370
10532
  }
10371
10533
  return Array.from(roots);
10372
10534
  }
@@ -10437,14 +10599,17 @@ var Indexer = class _Indexer {
10437
10599
  getLegacyBranchCatalogKey() {
10438
10600
  return this.currentBranch || "default";
10439
10601
  }
10440
- getLegacyMigrationMetadataKey() {
10441
- return `index.globalBranchMigration.${this.projectIdentityHash}`;
10602
+ getLegacyMigrationMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10603
+ return `index.globalBranchMigration.${projectIdentityHash}`;
10604
+ }
10605
+ getProjectEmbeddingStrategyMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10606
+ return `index.embeddingStrategyVersion.${projectIdentityHash}`;
10442
10607
  }
10443
- getProjectEmbeddingStrategyMetadataKey() {
10444
- return `index.embeddingStrategyVersion.${this.projectIdentityHash}`;
10608
+ getProjectForceReembedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10609
+ return `index.forceReembed.${projectIdentityHash}`;
10445
10610
  }
10446
- getProjectForceReembedMetadataKey() {
10447
- return `index.forceReembed.${this.projectIdentityHash}`;
10611
+ getProjectMigrationFinalizedMetadataKey(projectIdentityHash = this.projectIdentityHash) {
10612
+ return `index.migrationFinalized.${projectIdentityHash}`;
10448
10613
  }
10449
10614
  getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
10450
10615
  const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
@@ -10550,7 +10715,7 @@ var Indexer = class _Indexer {
10550
10715
  const legacy = this.getLegacyBranchCatalogKey();
10551
10716
  return primary === legacy ? [primary] : [primary, legacy];
10552
10717
  }
10553
- getProjectLocalScopedOwnershipIds(roots) {
10718
+ getProjectLocalScopedOwnershipIds(roots, projectRoot = this.projectRoot) {
10554
10719
  const chunkIds = /* @__PURE__ */ new Set();
10555
10720
  const symbolIds = /* @__PURE__ */ new Set();
10556
10721
  if (!this.database) {
@@ -10558,10 +10723,10 @@ var Indexer = class _Indexer {
10558
10723
  }
10559
10724
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
10560
10725
  ...Array.from(this.fileHashCache.keys()).filter(
10561
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10726
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10562
10727
  ),
10563
10728
  ...(this.store?.getAllMetadata() ?? []).map(({ metadata }) => metadata.filePath).filter(
10564
- (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath)
10729
+ (filePath) => this.isFileInCurrentScope(filePath, roots) && this.isFileInProjectRoot(filePath, projectRoot)
10565
10730
  )
10566
10731
  ]);
10567
10732
  for (const filePath of projectLocalFilePaths) {
@@ -10574,15 +10739,16 @@ var Indexer = class _Indexer {
10574
10739
  }
10575
10740
  return { chunkIds, symbolIds };
10576
10741
  }
10577
- getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds) {
10742
+ getProjectScopedBranchCatalogCleanupKeys(projectChunkIds, projectSymbolIds, projectRoot = this.projectRoot) {
10578
10743
  if (this.config.scope !== "global") {
10579
10744
  return this.getBranchCatalogCleanupKeys();
10580
10745
  }
10581
10746
  const keys = /* @__PURE__ */ new Set();
10582
10747
  const projectChunkIdSet = new Set(projectChunkIds);
10583
10748
  const projectSymbolIdSet = new Set(projectSymbolIds);
10749
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10584
10750
  for (const branchKey of this.database?.getAllBranches() ?? []) {
10585
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10751
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10586
10752
  keys.add(branchKey);
10587
10753
  continue;
10588
10754
  }
@@ -10592,8 +10758,10 @@ var Indexer = class _Indexer {
10592
10758
  keys.add(branchKey);
10593
10759
  }
10594
10760
  }
10595
- for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10596
- keys.add(branchKey);
10761
+ if (projectRoot === this.projectRoot) {
10762
+ for (const branchKey of this.getBranchCatalogCleanupKeys()) {
10763
+ keys.add(branchKey);
10764
+ }
10597
10765
  }
10598
10766
  return Array.from(keys);
10599
10767
  }
@@ -10601,10 +10769,10 @@ var Indexer = class _Indexer {
10601
10769
  const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
10602
10770
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
10603
10771
  }
10604
- isFileInProjectRoot(filePath) {
10772
+ isFileInProjectRoot(filePath, projectRoot = this.projectRoot) {
10605
10773
  return isPathWithinRoot2(
10606
10774
  this.getCanonicalStoredFilePath(filePath),
10607
- this.getCanonicalPath(this.projectRoot)
10775
+ this.getCanonicalPath(projectRoot)
10608
10776
  );
10609
10777
  }
10610
10778
  clearScopedFileHashCache(roots) {
@@ -10646,12 +10814,12 @@ var Indexer = class _Indexer {
10646
10814
  }
10647
10815
  return false;
10648
10816
  }
10649
- hasForeignScopedBranchData() {
10817
+ hasForeignScopedBranchData(projectRoot = this.projectRoot, roots = this.getScopedRoots(projectRoot)) {
10650
10818
  if (!this.database || this.config.scope !== "global") {
10651
10819
  return false;
10652
10820
  }
10653
- const roots = this.getScopedRoots();
10654
- const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
10821
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
10822
+ const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots, projectRoot);
10655
10823
  return this.database.getAllBranches().some(
10656
10824
  (branchKey) => {
10657
10825
  const branchChunkIds = this.database.getBranchChunkIds(branchKey);
@@ -10660,7 +10828,7 @@ var Indexer = class _Indexer {
10660
10828
  if (!hasBranchData) {
10661
10829
  return false;
10662
10830
  }
10663
- if (branchKey.startsWith(`${this.projectIdentityHash}:`)) {
10831
+ if (branchKey.startsWith(`${projectIdentityHash}:`)) {
10664
10832
  return false;
10665
10833
  }
10666
10834
  const referencesCurrentProjectChunks = branchChunkIds.some((chunkId) => projectLocalChunkIds.has(chunkId));
@@ -10669,7 +10837,7 @@ var Indexer = class _Indexer {
10669
10837
  }
10670
10838
  );
10671
10839
  }
10672
- clearSharedIndexProjectData(store, invertedIndex, database, roots) {
10840
+ clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot = this.projectRoot) {
10673
10841
  const allMetadata = store.getAllMetadata();
10674
10842
  const scopedEntries = allMetadata.filter(({ metadata }) => this.isFileInCurrentScope(metadata.filePath, roots));
10675
10843
  const filePaths = /* @__PURE__ */ new Set([
@@ -10677,7 +10845,7 @@ var Indexer = class _Indexer {
10677
10845
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
10678
10846
  ]);
10679
10847
  const projectLocalFilePaths = new Set(
10680
- Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath))
10848
+ Array.from(filePaths).filter((filePath) => this.isFileInProjectRoot(filePath, projectRoot))
10681
10849
  );
10682
10850
  const removedChunkIds = new Set(scopedEntries.map(({ key }) => key));
10683
10851
  for (const filePath of filePaths) {
@@ -10687,7 +10855,7 @@ var Indexer = class _Indexer {
10687
10855
  }
10688
10856
  const removedChunkIdList = Array.from(removedChunkIds);
10689
10857
  const projectLocalChunkIds = new Set(
10690
- scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath)).map(({ key }) => key)
10858
+ scopedEntries.filter(({ metadata }) => this.isFileInProjectRoot(metadata.filePath, projectRoot)).map(({ key }) => key)
10691
10859
  );
10692
10860
  for (const filePath of projectLocalFilePaths) {
10693
10861
  for (const chunk of database.getChunksByFile(filePath)) {
@@ -10706,7 +10874,8 @@ var Indexer = class _Indexer {
10706
10874
  }
10707
10875
  const branchCleanupKeys = this.getProjectScopedBranchCatalogCleanupKeys(
10708
10876
  Array.from(projectLocalChunkIds),
10709
- Array.from(projectLocalSymbolIds)
10877
+ Array.from(projectLocalSymbolIds),
10878
+ projectRoot
10710
10879
  );
10711
10880
  for (const branchKey of branchCleanupKeys) {
10712
10881
  database.deleteBranchChunksForBranch(branchKey, removedChunkIdList);
@@ -10741,29 +10910,96 @@ var Indexer = class _Indexer {
10741
10910
  database.gcOrphanSymbols();
10742
10911
  database.gcOrphanEmbeddings();
10743
10912
  database.gcOrphanChunks();
10744
- store.save();
10745
10913
  this.saveInvertedIndex(invertedIndex);
10914
+ store.save();
10746
10915
  return {
10747
10916
  removedChunkIds: removedChunkIdList,
10748
10917
  hasForeignData: allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots))
10749
10918
  };
10750
10919
  }
10920
+ getCurrentClearRecoveryState() {
10921
+ if (!this.configuredProviderInfo) {
10922
+ throw new Error("Cannot persist clear recovery state before the embedding provider is initialized");
10923
+ }
10924
+ const compatibility = this.checkCompatibility();
10925
+ const compatibilityDecision = compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible";
10926
+ return {
10927
+ phase: "clearing",
10928
+ embeddingProvider: this.configuredProviderInfo.provider,
10929
+ embeddingModel: this.configuredProviderInfo.modelInfo.model,
10930
+ embeddingDimensions: this.configuredProviderInfo.modelInfo.dimensions,
10931
+ embeddingStrategyVersion: EMBEDDING_STRATEGY_VERSION,
10932
+ compatibilityDecision
10933
+ };
10934
+ }
10935
+ beginClearRecoveryState() {
10936
+ const recovery = this.getCurrentClearRecoveryState();
10937
+ setIndexLockClearRecoveryState(this.requireActiveLease(), recovery);
10938
+ return recovery;
10939
+ }
10940
+ finishClearRecoveryState() {
10941
+ setIndexLockClearRecoveryState(this.requireActiveLease(), null);
10942
+ }
10943
+ matchesCurrentClearRecoveryConfiguration(recovery) {
10944
+ const configuredProviderInfo = this.configuredProviderInfo;
10945
+ return configuredProviderInfo !== null && recovery.embeddingProvider === configuredProviderInfo.provider && recovery.embeddingModel === configuredProviderInfo.modelInfo.model && recovery.embeddingDimensions === configuredProviderInfo.modelInfo.dimensions && recovery.embeddingStrategyVersion === EMBEDDING_STRATEGY_VERSION;
10946
+ }
10947
+ hasUnknownLegacyForceIndexClear(owner) {
10948
+ return owner.operation === "force-index" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1 && existsSync11(path19.join(this.indexPath, "force-index-phase"));
10949
+ }
10751
10950
  async recoverFromInterruptedIndexingUnlocked(owners) {
10752
10951
  for (const owner of owners) {
10753
10952
  this.logger.warn("Detected interrupted indexing session, recovering...", {
10754
10953
  pid: owner.pid,
10755
10954
  hostname: owner.hostname,
10756
10955
  operation: owner.operation,
10757
- startedAt: owner.startedAt
10956
+ startedAt: owner.startedAt,
10957
+ projectRoot: owner.projectRoot
10758
10958
  });
10759
10959
  }
10760
10960
  if (this.config.scope === "global") {
10761
- if (existsSync11(this.fileHashCachePath)) {
10762
- unlinkSync2(this.fileHashCachePath);
10961
+ const clearScopes = [];
10962
+ for (const owner of owners) {
10963
+ if (this.hasUnknownLegacyForceIndexClear(owner)) {
10964
+ throw new Error(
10965
+ `Cannot automatically recover interrupted force-index ${owner.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
10966
+ );
10967
+ }
10968
+ if (owner.operation === "clear" && owner.clearRecovery === void 0 && owner.recoveryProtocolVersion !== 1) {
10969
+ throw new Error(
10970
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating recovery state is unknown. The recovery marker was retained for manual inspection.`
10971
+ );
10972
+ }
10973
+ if (owner.clearRecovery === void 0) continue;
10974
+ if (!owner.projectRoot || !owner.scopedRoots || owner.scopedRoots.length === 0) {
10975
+ throw new Error(
10976
+ `Cannot automatically recover interrupted global clear ${owner.token}: the originating project scope is unknown. The recovery marker was retained for manual inspection.`
10977
+ );
10978
+ }
10979
+ if (!this.matchesCurrentClearRecoveryConfiguration(owner.clearRecovery)) {
10980
+ throw new Error(
10981
+ `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.`
10982
+ );
10983
+ }
10984
+ clearScopes.push({
10985
+ projectRoot: owner.projectRoot,
10986
+ scopedRoots: owner.scopedRoots,
10987
+ compatibilityDecision: owner.clearRecovery.compatibilityDecision
10988
+ });
10989
+ }
10990
+ if (clearScopes.length > 0) {
10991
+ this.loadFileHashCache();
10992
+ }
10993
+ for (const { projectRoot, scopedRoots, compatibilityDecision } of clearScopes) {
10994
+ this.clearGlobalIndexUnlocked(projectRoot, scopedRoots, compatibilityDecision);
10763
10995
  }
10764
10996
  await this.healthCheckUnlocked();
10997
+ this.logger.info(
10998
+ clearScopes.length > 0 ? "Recovery complete, next index will rebuild all files" : "Recovery complete, next index will resume from the last checkpoint"
10999
+ );
11000
+ return;
10765
11001
  }
10766
- this.logger.info("Recovery complete, next index will re-process all files");
11002
+ this.logger.info("Recovery complete, next index will resume from the last checkpoint");
10767
11003
  }
10768
11004
  *loadSerializedFailedBatches() {
10769
11005
  let warned = false;
@@ -10801,14 +11037,99 @@ var Indexer = class _Indexer {
10801
11037
  state.writer.write(record);
10802
11038
  state.recordsWritten += record.chunks.length;
10803
11039
  }
10804
- finalizeFailedBatchWriteState(state) {
11040
+ finalizeFailedBatchWriteState(state, resolvedChunkIds = /* @__PURE__ */ new Set()) {
10805
11041
  if (state.recordsWritten > 0) {
10806
- state.writer.commit();
11042
+ const seenChunkIds = /* @__PURE__ */ new Set();
11043
+ const retained = [];
11044
+ const records = Array.from(readFailedBatchRecords(state.writer.temporaryPath));
11045
+ for (let i = records.length - 1; i >= 0; i--) {
11046
+ const chunks = records[i].chunks.filter((rawChunk) => {
11047
+ const chunkId = getPendingChunkId(rawChunk);
11048
+ if (chunkId !== null) {
11049
+ if (resolvedChunkIds.has(chunkId)) return false;
11050
+ if (seenChunkIds.has(chunkId)) return false;
11051
+ seenChunkIds.add(chunkId);
11052
+ }
11053
+ return true;
11054
+ });
11055
+ if (chunks.length > 0) {
11056
+ retained.unshift({ ...records[i], chunks });
11057
+ }
11058
+ }
11059
+ state.writer.cleanup();
11060
+ if (retained.length > 0) {
11061
+ writeFailedBatchRecords(this.failedBatchesPath, retained);
11062
+ } else {
11063
+ writeFailedBatchRecords(this.failedBatchesPath, []);
11064
+ this.clearFailedBatchState();
11065
+ }
10807
11066
  return;
10808
11067
  }
10809
- state.writer.cleanup();
11068
+ state.writer.commit();
10810
11069
  this.clearFailedBatchState();
10811
11070
  }
11071
+ getCheckpointIntervalChunks(totalChunks) {
11072
+ return Math.max(
11073
+ this.checkpointIntervalChunks ?? 2e3,
11074
+ Math.floor(totalChunks / 10)
11075
+ );
11076
+ }
11077
+ checkpointIndexRun(database, store, invertedIndex, failedProcessing, resolvedRetryChunkIds, currentFileHashes, committedFilePaths, scopedRoots, configuredProviderInfo) {
11078
+ if (!this.hasProjectForceReembedPending()) {
11079
+ this.saveIndexMetadata(configuredProviderInfo);
11080
+ this.indexCompatibility = { compatible: true };
11081
+ }
11082
+ database.commitWriteTransaction();
11083
+ database.beginWriteTransaction();
11084
+ this.saveInvertedIndex(invertedIndex);
11085
+ store.save();
11086
+ if (failedProcessing.state.recordsWritten > 0 || failedProcessing.latestById.size > 0 || failedProcessing.discardedExistingRecords) {
11087
+ for (const metadata of failedProcessing.latestById.values()) {
11088
+ const alreadyMaterialized = metadata.chunks.some((rawChunk) => {
11089
+ const chunkId = getPendingChunkId(rawChunk);
11090
+ return chunkId !== null && failedProcessing.materializedRetryIds.has(chunkId);
11091
+ });
11092
+ if (alreadyMaterialized) continue;
11093
+ this.writeFailedBatchRecord(failedProcessing.state, {
11094
+ chunks: metadata.chunks,
11095
+ attemptCount: metadata.attemptCount,
11096
+ error: metadata.error,
11097
+ lastAttempt: metadata.lastAttempt
11098
+ });
11099
+ for (const rawChunk of metadata.chunks) {
11100
+ const chunkId = getPendingChunkId(rawChunk);
11101
+ if (chunkId !== null) {
11102
+ failedProcessing.materializedRetryIds.add(chunkId);
11103
+ }
11104
+ }
11105
+ }
11106
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
11107
+ failedProcessing.state = this.createFailedBatchWriteState();
11108
+ failedProcessing.discardedExistingRecords = false;
11109
+ for (const record of this.loadSerializedFailedBatches()) {
11110
+ for (const rawChunk of record.chunks) {
11111
+ const chunkId = getPendingChunkId(rawChunk);
11112
+ this.writeFailedBatchRecord(failedProcessing.state, { ...record, chunks: [rawChunk] });
11113
+ if (chunkId !== null) {
11114
+ failedProcessing.materializedRetryIds.add(chunkId);
11115
+ }
11116
+ }
11117
+ }
11118
+ }
11119
+ const partialHashes = /* @__PURE__ */ new Map();
11120
+ for (const filePath of committedFilePaths) {
11121
+ const hash = currentFileHashes.get(filePath);
11122
+ if (hash !== void 0) {
11123
+ partialHashes.set(filePath, hash);
11124
+ }
11125
+ }
11126
+ if (scopedRoots) {
11127
+ this.replaceScopedFileHashCache(partialHashes, scopedRoots);
11128
+ } else {
11129
+ this.fileHashCache = partialHashes;
11130
+ this.saveFileHashCache();
11131
+ }
11132
+ }
10812
11133
  clearFailedBatchState() {
10813
11134
  if (existsSync11(this.failedBatchesPath)) {
10814
11135
  try {
@@ -10835,6 +11156,7 @@ var Indexer = class _Indexer {
10835
11156
  prepareFailedBatchProcessing(roots, shouldProcess) {
10836
11157
  const state = this.createFailedBatchWriteState();
10837
11158
  const latestById = /* @__PURE__ */ new Map();
11159
+ let discardedExistingRecords = false;
10838
11160
  try {
10839
11161
  for (const batch of this.loadSerializedFailedBatches()) {
10840
11162
  for (const rawChunk of batch.chunks) {
@@ -10845,10 +11167,12 @@ var Indexer = class _Indexer {
10845
11167
  continue;
10846
11168
  }
10847
11169
  if (!shouldProcess(filePath)) {
11170
+ discardedExistingRecords = true;
10848
11171
  continue;
10849
11172
  }
10850
11173
  const chunkId = getPendingChunkId(rawChunk);
10851
11174
  if (!chunkId) {
11175
+ discardedExistingRecords = true;
10852
11176
  continue;
10853
11177
  }
10854
11178
  const existing = latestById.get(chunkId);
@@ -10856,12 +11180,18 @@ var Indexer = class _Indexer {
10856
11180
  latestById.set(chunkId, {
10857
11181
  attemptCount: batch.attemptCount,
10858
11182
  error: batch.error,
10859
- lastAttempt: batch.lastAttempt
11183
+ lastAttempt: batch.lastAttempt,
11184
+ chunks: [rawChunk]
10860
11185
  });
10861
11186
  }
10862
11187
  }
10863
11188
  }
10864
- return { state, latestById };
11189
+ return {
11190
+ state,
11191
+ latestById,
11192
+ materializedRetryIds: /* @__PURE__ */ new Set(),
11193
+ discardedExistingRecords
11194
+ };
10865
11195
  } catch (error) {
10866
11196
  state.writer.cleanup();
10867
11197
  throw error;
@@ -10897,10 +11227,34 @@ var Indexer = class _Indexer {
10897
11227
  }
10898
11228
  }
10899
11229
  }
11230
+ restoreMissingChunkRows(database, chunks) {
11231
+ const missing = [];
11232
+ for (const chunk of chunks) {
11233
+ if (database.getChunk(chunk.id)) {
11234
+ continue;
11235
+ }
11236
+ missing.push({
11237
+ chunkId: chunk.id,
11238
+ contentHash: chunk.contentHash,
11239
+ filePath: chunk.metadata.filePath,
11240
+ startLine: chunk.metadata.startLine,
11241
+ endLine: chunk.metadata.endLine,
11242
+ nodeType: chunk.metadata.chunkType,
11243
+ name: chunk.metadata.name,
11244
+ language: chunk.metadata.language,
11245
+ blameSha: chunk.metadata.blameSha,
11246
+ blameAuthor: chunk.metadata.blameAuthor,
11247
+ blameAuthorEmail: chunk.metadata.blameAuthorEmail,
11248
+ blameCommittedAt: chunk.metadata.blameCommittedAt,
11249
+ blameSummary: chunk.metadata.blameSummary
11250
+ });
11251
+ }
11252
+ if (missing.length > 0) {
11253
+ database.upsertChunksBatch(missing);
11254
+ }
11255
+ }
10900
11256
  getProviderRateLimits(provider) {
10901
11257
  switch (provider) {
10902
- case "github-copilot":
10903
- return { concurrency: 1, intervalMs: 4e3, minRetryMs: 5e3, maxRetryMs: 6e4 };
10904
11258
  case "openai":
10905
11259
  return { concurrency: 3, intervalMs: 500, minRetryMs: 1e3, maxRetryMs: 3e4 };
10906
11260
  case "google":
@@ -10969,10 +11323,11 @@ var Indexer = class _Indexer {
10969
11323
  const embeddingPartsByChunk = /* @__PURE__ */ new Map();
10970
11324
  const completedVectorsByChunkId = /* @__PURE__ */ new Map();
10971
11325
  const completedChunkIds = /* @__PURE__ */ new Set();
10972
- const requestBatches = createPendingEmbeddingRequestBatches(
10973
- chunksNeedingEmbedding,
10974
- getDynamicBatchOptions(options.configuredProviderInfo)
10975
- );
11326
+ const batchOptions = getDynamicBatchOptions(options.configuredProviderInfo, this.config.embedding?.batch);
11327
+ if (options.forceSingleItemBatches && options.configuredProviderInfo.provider === "ollama") {
11328
+ batchOptions.maxBatchItems = 1;
11329
+ }
11330
+ const requestBatches = createPendingEmbeddingRequestBatches(chunksNeedingEmbedding, batchOptions);
10976
11331
  let fatalError;
10977
11332
  for (const requestBatch of requestBatches) {
10978
11333
  await options.queue.onSizeLessThan(Math.max(1, options.providerRateLimits.concurrency));
@@ -11535,7 +11890,7 @@ var Indexer = class _Indexer {
11535
11890
  }
11536
11891
  if (!this.configuredProviderInfo) {
11537
11892
  throw new Error(
11538
- "No embedding provider available. Configure GitHub Copilot, OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11893
+ "No embedding provider available. Configure OpenAI, Google, Ollama, or a custom OpenAI-compatible endpoint."
11539
11894
  );
11540
11895
  }
11541
11896
  this.logger.info("Initializing indexer", {
@@ -11566,7 +11921,20 @@ var Indexer = class _Indexer {
11566
11921
  ]);
11567
11922
  }
11568
11923
  if (recoveredOwners.length > 0 && this.config.scope === "project") {
11569
- await this.resetLocalIndexArtifacts();
11924
+ const unknownLegacyForceIndex = recoveredOwners.find(
11925
+ (owner) => this.hasUnknownLegacyForceIndexClear(owner)
11926
+ );
11927
+ if (unknownLegacyForceIndex) {
11928
+ throw new Error(
11929
+ `Cannot automatically recover interrupted force-index ${unknownLegacyForceIndex.token}: the legacy clearing phase ownership is unknown. The recovery marker was retained for manual inspection.`
11930
+ );
11931
+ }
11932
+ const shouldReset = recoveredOwners.some(
11933
+ (owner) => owner.clearRecovery !== void 0 || owner.operation === "clear" && owner.recoveryProtocolVersion !== 1
11934
+ );
11935
+ if (shouldReset) {
11936
+ await this.resetLocalIndexArtifacts();
11937
+ }
11570
11938
  }
11571
11939
  this.store = new VectorStore(storePath, dimensions);
11572
11940
  if (existsSync11(storePath) || existsSync11(vectorMetadataPath)) {
@@ -12202,7 +12570,17 @@ var Indexer = class _Indexer {
12202
12570
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
12203
12571
  for (const file of files) {
12204
12572
  const storedPath = this.toStoredFilePath(file.path);
12205
- const currentHash = hashFile(file.path);
12573
+ let currentHash;
12574
+ try {
12575
+ currentHash = hashFile(file.path);
12576
+ } catch (error) {
12577
+ stats.skippedFiles.push({ path: this.toCanonicalFilePath(file.path), reason: "unreadable" });
12578
+ this.logger.warn("Skipped unreadable file during indexing", {
12579
+ path: file.path,
12580
+ error: getErrorMessage4(error)
12581
+ });
12582
+ continue;
12583
+ }
12206
12584
  currentFileHashes.set(storedPath, currentHash);
12207
12585
  const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
12208
12586
  const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
@@ -12210,7 +12588,8 @@ var Indexer = class _Indexer {
12210
12588
  );
12211
12589
  const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path19.extname(storedPath).toLowerCase() === ".swift";
12212
12590
  const requiresMetalParserUpgrade = reparseCachedMetalFiles && path19.extname(storedPath).toLowerCase() === ".metal";
12213
- if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12591
+ const inMigrationScope = forceScopedReembed && scopedRoots !== null && this.isFileInCurrentScope(storedPath, scopedRoots);
12592
+ if (cachedHashMatches && !inMigrationScope && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
12214
12593
  unchangedFilePaths.add(storedPath);
12215
12594
  this.logger.recordCacheHit();
12216
12595
  } else {
@@ -12336,6 +12715,9 @@ var Indexer = class _Indexer {
12336
12715
  }
12337
12716
  }
12338
12717
  let processedChangedFiles = 0;
12718
+ let lastCheckpointChunks = 0;
12719
+ const committedFilePaths = new Set(unchangedFilePaths);
12720
+ const resolvedRetryChunkIds = /* @__PURE__ */ new Set();
12339
12721
  for (const descriptorBatch of iterateOrderedFileBatches(
12340
12722
  changedFileDescriptors,
12341
12723
  (descriptor) => descriptor.sourceBytes,
@@ -12349,7 +12731,7 @@ var Indexer = class _Indexer {
12349
12731
  const loadedByPath = new Map(loadedFiles.map((file) => [file.path, file]));
12350
12732
  const descriptorByPath = new Map(descriptorBatch.map((descriptor) => [descriptor.storedPath, descriptor]));
12351
12733
  const parseStartTime = performance2.now();
12352
- const parsedFiles = parseFiles(loadedFiles);
12734
+ const parsedFiles = parseFiles(loadedFiles, this.config.indexing.linesPerChunk);
12353
12735
  const parseMs = performance2.now() - parseStartTime;
12354
12736
  this.logger.recordFilesParsed(parsedFiles.length);
12355
12737
  this.logger.recordParseDuration(parseMs);
@@ -12372,7 +12754,7 @@ var Indexer = class _Indexer {
12372
12754
  }
12373
12755
  let chunksToProcess = parsed.chunks;
12374
12756
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
12375
- chunksToProcess = parseFileAsText(parsed.path, loadedFile.content);
12757
+ chunksToProcess = parseFileAsText(parsed.path, loadedFile.content, this.config.indexing.linesPerChunk);
12376
12758
  }
12377
12759
  chunksToProcess = selectIndexableChunks(
12378
12760
  chunksToProcess,
@@ -12506,6 +12888,10 @@ var Indexer = class _Indexer {
12506
12888
  }
12507
12889
  if (symbolBatch.length > 0) {
12508
12890
  database.upsertSymbolsBatch(symbolBatch);
12891
+ database.addSymbolsToBranchBatch(
12892
+ this.getBranchCatalogKey(),
12893
+ symbolBatch.map((symbol) => symbol.id)
12894
+ );
12509
12895
  }
12510
12896
  if (edgeBatch.length > 0) {
12511
12897
  database.upsertCallEdgesBatch(edgeBatch);
@@ -12541,6 +12927,12 @@ var Indexer = class _Indexer {
12541
12927
  forceReembed: forceScopedReembed,
12542
12928
  reuseCachedEmbeddings: true,
12543
12929
  incrementRepeatedFailures: true,
12930
+ onSucceeded: (succeededChunks) => {
12931
+ database.addChunksToBranchBatch(
12932
+ this.getBranchCatalogKey(),
12933
+ succeededChunks.map((chunk) => chunk.id)
12934
+ );
12935
+ },
12544
12936
  onProgress: (batchProgress) => onProgress?.({
12545
12937
  phase: "embedding",
12546
12938
  filesProcessed: unchangedFilePaths.size + processedChangedFiles,
@@ -12559,6 +12951,27 @@ var Indexer = class _Indexer {
12559
12951
  }
12560
12952
  }
12561
12953
  }
12954
+ for (const descriptor of descriptorBatch) {
12955
+ const existingFileChunks = existingChunksByFile.get(descriptor.storedPath);
12956
+ if (!existingFileChunks || existingFileChunks.size === 0) {
12957
+ committedFilePaths.add(descriptor.storedPath);
12958
+ }
12959
+ }
12960
+ const checkpointInterval = this.getCheckpointIntervalChunks(stats.totalChunks);
12961
+ if (stats.totalChunks - lastCheckpointChunks >= checkpointInterval) {
12962
+ lastCheckpointChunks = stats.totalChunks;
12963
+ this.checkpointIndexRun(
12964
+ database,
12965
+ store,
12966
+ invertedIndex,
12967
+ failedProcessing,
12968
+ resolvedRetryChunkIds,
12969
+ currentFileHashes,
12970
+ committedFilePaths,
12971
+ scopedRoots,
12972
+ configuredProviderInfo
12973
+ );
12974
+ }
12562
12975
  }
12563
12976
  const retryableFailedChunks = this.iterateLatestFailedChunks(
12564
12977
  failedProcessing.latestById,
@@ -12579,6 +12992,7 @@ var Indexer = class _Indexer {
12579
12992
  retryableChunksWithExistingData.add(chunk.id);
12580
12993
  }
12581
12994
  }
12995
+ this.restoreMissingChunkRows(database, pendingChunks);
12582
12996
  stats.totalChunks += pendingChunks.length;
12583
12997
  onProgress?.({
12584
12998
  phase: "embedding",
@@ -12601,6 +13015,17 @@ var Indexer = class _Indexer {
12601
13015
  forceReembed: forceScopedReembed,
12602
13016
  reuseCachedEmbeddings: true,
12603
13017
  incrementRepeatedFailures: true,
13018
+ forceSingleItemBatches: true,
13019
+ onSucceeded: (succeededChunks) => {
13020
+ database.addChunksToBranchBatch(
13021
+ this.getBranchCatalogKey(),
13022
+ succeededChunks.map((chunk) => chunk.id)
13023
+ );
13024
+ for (const chunk of succeededChunks) {
13025
+ failedProcessing.latestById.delete(chunk.id);
13026
+ resolvedRetryChunkIds.add(chunk.id);
13027
+ }
13028
+ },
12604
13029
  onProgress: (batchProgress) => onProgress?.({
12605
13030
  phase: "embedding",
12606
13031
  filesProcessed: files.length,
@@ -12618,6 +13043,20 @@ var Indexer = class _Indexer {
12618
13043
  failedForcedChunkIds.add(chunkId);
12619
13044
  }
12620
13045
  }
13046
+ if (stats.totalChunks - lastCheckpointChunks >= this.getCheckpointIntervalChunks(stats.totalChunks)) {
13047
+ lastCheckpointChunks = stats.totalChunks;
13048
+ this.checkpointIndexRun(
13049
+ database,
13050
+ store,
13051
+ invertedIndex,
13052
+ failedProcessing,
13053
+ resolvedRetryChunkIds,
13054
+ currentFileHashes,
13055
+ committedFilePaths,
13056
+ scopedRoots,
13057
+ configuredProviderInfo
13058
+ );
13059
+ }
12621
13060
  }
12622
13061
  const removedChunkIds = [];
12623
13062
  for (const [chunkId] of existingChunks) {
@@ -12654,13 +13093,6 @@ var Indexer = class _Indexer {
12654
13093
  if (removedStoredChunks) {
12655
13094
  this.saveInvertedIndex(invertedIndex);
12656
13095
  }
12657
- if (scopedRoots) {
12658
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12659
- } else {
12660
- this.fileHashCache = currentFileHashes;
12661
- this.saveFileHashCache();
12662
- }
12663
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12664
13096
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12665
13097
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12666
13098
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12669,6 +13101,13 @@ var Indexer = class _Indexer {
12669
13101
  this.indexCompatibility = { compatible: true };
12670
13102
  database.commitWriteTransaction();
12671
13103
  writeTransactionActive = false;
13104
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13105
+ if (scopedRoots) {
13106
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13107
+ } else {
13108
+ this.fileHashCache = currentFileHashes;
13109
+ this.saveFileHashCache();
13110
+ }
12672
13111
  stats.durationMs = Date.now() - startTime;
12673
13112
  onProgress?.({
12674
13113
  phase: "complete",
@@ -12692,13 +13131,6 @@ var Indexer = class _Indexer {
12692
13131
  );
12693
13132
  store.save();
12694
13133
  this.saveInvertedIndex(invertedIndex);
12695
- if (scopedRoots) {
12696
- this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12697
- } else {
12698
- this.fileHashCache = currentFileHashes;
12699
- this.saveFileHashCache();
12700
- }
12701
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12702
13134
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12703
13135
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12704
13136
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12707,6 +13139,13 @@ var Indexer = class _Indexer {
12707
13139
  this.indexCompatibility = { compatible: true };
12708
13140
  database.commitWriteTransaction();
12709
13141
  writeTransactionActive = false;
13142
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
13143
+ if (scopedRoots) {
13144
+ this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
13145
+ } else {
13146
+ this.fileHashCache = currentFileHashes;
13147
+ this.saveFileHashCache();
13148
+ }
12710
13149
  stats.durationMs = Date.now() - startTime;
12711
13150
  onProgress?.({
12712
13151
  phase: "complete",
@@ -12741,15 +13180,15 @@ var Indexer = class _Indexer {
12741
13180
  );
12742
13181
  store.save();
12743
13182
  this.saveInvertedIndex(invertedIndex);
13183
+ database.commitWriteTransaction();
13184
+ writeTransactionActive = false;
13185
+ this.finalizeFailedBatchWriteState(failedProcessing.state, resolvedRetryChunkIds);
12744
13186
  if (scopedRoots) {
12745
13187
  this.replaceScopedFileHashCache(currentFileHashes, scopedRoots);
12746
13188
  } else {
12747
13189
  this.fileHashCache = currentFileHashes;
12748
13190
  this.saveFileHashCache();
12749
13191
  }
12750
- this.finalizeFailedBatchWriteState(failedProcessing.state);
12751
- database.commitWriteTransaction();
12752
- writeTransactionActive = false;
12753
13192
  if (this.config.indexing.autoGc && stats.removedChunks > 0) {
12754
13193
  const gcReset = await this.maybeRunOrphanGc();
12755
13194
  if (gcReset) {
@@ -12773,6 +13212,9 @@ var Indexer = class _Indexer {
12773
13212
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
12774
13213
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
12775
13214
  }
13215
+ if (forceScopedReembed) {
13216
+ database.setMetadata(this.getProjectMigrationFinalizedMetadataKey(), "true");
13217
+ }
12776
13218
  database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
12777
13219
  database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
12778
13220
  database.setMetadata(symbolExtractorMetadataKey, SYMBOL_EXTRACTOR_VERSION);
@@ -12883,26 +13325,41 @@ var Indexer = class _Indexer {
12883
13325
  shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
12884
13326
  };
12885
13327
  }
12886
- searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
13328
+ searchCandidatesWithAllowedIds(initialLimit, totalCount, allowedChunkIds, shouldPrefilter, search, getChunkId) {
12887
13329
  const normalizedLimit = Math.max(0, Math.floor(initialLimit));
12888
13330
  if (normalizedLimit === 0) return [];
12889
- if (!shouldPrefilterByBranch || !branchChunkIds) {
13331
+ if (!shouldPrefilter || !allowedChunkIds) {
12890
13332
  return search(normalizedLimit);
12891
13333
  }
12892
- const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
13334
+ const targetCount = Math.min(normalizedLimit, allowedChunkIds.size);
12893
13335
  if (targetCount === 0 || totalCount === 0) return [];
12894
13336
  let requestedLimit = Math.min(normalizedLimit, totalCount);
12895
13337
  while (true) {
12896
13338
  const results = search(requestedLimit);
12897
- const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
12898
- if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
12899
- return branchResults;
13339
+ const allowedResults = results.filter((candidate) => allowedChunkIds.has(getChunkId(candidate)));
13340
+ if (allowedResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
13341
+ return allowedResults;
12900
13342
  }
12901
13343
  const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
12902
- if (nextLimit === requestedLimit) return branchResults;
13344
+ if (nextLimit === requestedLimit) return allowedResults;
12903
13345
  requestedLimit = nextLimit;
12904
13346
  }
12905
13347
  }
13348
+ getTemporalChunkIds(database, options) {
13349
+ if (!options?.blameSince && !options?.blameUntil) return null;
13350
+ const since = options.blameSince ? parseBlameTimestamp(options.blameSince, false) : void 0;
13351
+ const until = options.blameUntil ? parseBlameTimestamp(options.blameUntil, true) : void 0;
13352
+ if (since === null || until === null) {
13353
+ return /* @__PURE__ */ new Set();
13354
+ }
13355
+ return new Set(database.getChunkIdsByBlameDate(since, until));
13356
+ }
13357
+ intersectChunkIdSets(first, second) {
13358
+ if (first === null) return second;
13359
+ if (second === null) return first;
13360
+ const [smaller, larger] = first.size <= second.size ? [first, second] : [second, first];
13361
+ return new Set(Array.from(smaller).filter((chunkId) => larger.has(chunkId)));
13362
+ }
12906
13363
  buildCandidateSnapshot(candidate) {
12907
13364
  return {
12908
13365
  id: candidate.id,
@@ -12917,13 +13374,16 @@ var Indexer = class _Indexer {
12917
13374
  buildCandidateSnapshotList(candidates) {
12918
13375
  return candidates.map((candidate) => this.buildCandidateSnapshot(candidate));
12919
13376
  }
12920
- searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
12921
- return this.searchCandidatesWithBranchPrefilter(
12922
- initialLimit,
12923
- store.count(),
13377
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch, temporalChunkIds) {
13378
+ const availableCount = temporalChunkIds?.size ?? store.count();
13379
+ if (availableCount === 0) return [];
13380
+ const allowedIds = temporalChunkIds === null ? void 0 : Array.from(temporalChunkIds);
13381
+ return this.searchCandidatesWithAllowedIds(
13382
+ Math.min(initialLimit, availableCount),
13383
+ availableCount,
12924
13384
  branchChunkIds,
12925
13385
  shouldPrefilterByBranch,
12926
- (requestedLimit) => store.search(embedding, requestedLimit),
13386
+ (requestedLimit) => store.search(embedding, requestedLimit, allowedIds),
12927
13387
  (candidate) => candidate.id
12928
13388
  );
12929
13389
  }
@@ -12948,8 +13408,9 @@ var Indexer = class _Indexer {
12948
13408
  const rerankTopN = this.config.search.rerankTopN;
12949
13409
  const filterByBranch = options?.filterByBranch ?? true;
12950
13410
  const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
13411
+ const prioritizeSourcePaths = sourceIntent || options?.prioritizeSourcePaths === true;
12951
13412
  const identifierHints = extractIdentifierHints(query);
12952
- const candidateLimit = maxResults * (sourceIntent ? 12 : 4);
13413
+ const candidateLimit = maxResults * (prioritizeSourcePaths ? 12 : 4);
12953
13414
  this.logger.search("debug", "Starting search", {
12954
13415
  query,
12955
13416
  maxResults,
@@ -12980,6 +13441,7 @@ var Indexer = class _Indexer {
12980
13441
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
12981
13442
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
12982
13443
  }
13444
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
12983
13445
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
12984
13446
  const prefilterMs = performance2.now() - prefilterStartTime;
12985
13447
  const vectorStartTime = performance2.now();
@@ -12988,7 +13450,8 @@ var Indexer = class _Indexer {
12988
13450
  embedding,
12989
13451
  candidateLimit,
12990
13452
  branchChunkIds,
12991
- shouldPrefilterByBranch
13453
+ shouldPrefilterByBranch,
13454
+ temporalChunkIds
12992
13455
  ) : [];
12993
13456
  const vectorMs = performance2.now() - vectorStartTime;
12994
13457
  const keywordStartTime = performance2.now();
@@ -12998,7 +13461,8 @@ var Indexer = class _Indexer {
12998
13461
  store,
12999
13462
  invertedIndex,
13000
13463
  branchChunkIds,
13001
- shouldPrefilterByBranch
13464
+ shouldPrefilterByBranch,
13465
+ temporalChunkIds
13002
13466
  );
13003
13467
  const keywordMs = performance2.now() - keywordStartTime;
13004
13468
  const scopedSemanticCandidates = semanticCandidates.filter(
@@ -13020,7 +13484,7 @@ var Indexer = class _Indexer {
13020
13484
  rerankTopN,
13021
13485
  limit: maxResults,
13022
13486
  hybridWeight: rankingHybridWeight,
13023
- prioritizeSourcePaths: sourceIntent
13487
+ prioritizeSourcePaths
13024
13488
  });
13025
13489
  const rerankedCombined = await this.rerankCandidatesWithApi(query, combined, {
13026
13490
  definitionIntent: options?.definitionIntent === true,
@@ -13056,10 +13520,11 @@ var Indexer = class _Indexer {
13056
13520
  branchSymbolIds,
13057
13521
  maxResults,
13058
13522
  union,
13059
- sourceIntent
13523
+ sourceIntent,
13524
+ options?.definitionIntent === true && ((options.directory?.trim().length ?? 0) > 0 || (options.fileType?.trim().length ?? 0) > 0)
13060
13525
  );
13061
13526
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
13062
- const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13527
+ const primaryLane = options?.definitionIntent === true ? mergeTieredResults(symbolLane, prePrimaryLane, maxResults * 4) : mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
13063
13528
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
13064
13529
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
13065
13530
  const baseFiltered = tiered.filter(
@@ -13154,14 +13619,18 @@ var Indexer = class _Indexer {
13154
13619
  })
13155
13620
  );
13156
13621
  }
13157
- async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
13622
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false, temporalChunkIds = null) {
13158
13623
  const normalizedLimit = Math.max(0, Math.floor(limit));
13159
13624
  if (normalizedLimit === 0) return [];
13160
- const scoreEntries = this.searchCandidatesWithBranchPrefilter(
13625
+ const allowedChunkIds = this.intersectChunkIdSets(
13626
+ shouldPrefilterByBranch ? branchChunkIds : null,
13627
+ temporalChunkIds
13628
+ );
13629
+ const scoreEntries = this.searchCandidatesWithAllowedIds(
13161
13630
  normalizedLimit,
13162
13631
  invertedIndex.getDocumentCount(),
13163
- branchChunkIds,
13164
- shouldPrefilterByBranch,
13632
+ allowedChunkIds,
13633
+ allowedChunkIds !== null,
13165
13634
  (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
13166
13635
  ([chunkId]) => chunkId
13167
13636
  );
@@ -13246,7 +13715,17 @@ var Indexer = class _Indexer {
13246
13715
  );
13247
13716
  const currentFileHashes = /* @__PURE__ */ new Map();
13248
13717
  for (const file of files) {
13249
- currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
13718
+ let hash;
13719
+ try {
13720
+ hash = hashFile(file.path);
13721
+ } catch (error) {
13722
+ this.logger.warn("Skipped unreadable file during freshness check", {
13723
+ path: file.path,
13724
+ error: getErrorMessage4(error)
13725
+ });
13726
+ return { readable: false, current: false, reason: "unreadable" };
13727
+ }
13728
+ currentFileHashes.set(this.toStoredFilePath(file.path), hash);
13250
13729
  }
13251
13730
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
13252
13731
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -13272,69 +13751,87 @@ var Indexer = class _Indexer {
13272
13751
  async forceIndex(onProgress) {
13273
13752
  return this.withIndexMutationLease("force-index", async (recoveredOwners) => {
13274
13753
  await this.ensureInitializedUnlocked(recoveredOwners);
13275
- await this.clearIndexUnlocked();
13754
+ const recovery = this.beginClearRecoveryState();
13755
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13756
+ this.finishClearRecoveryState();
13276
13757
  return this.indexUnlocked(onProgress, [], true);
13277
13758
  });
13278
13759
  }
13279
13760
  async clearIndex() {
13280
13761
  await this.withIndexMutationLease("clear", async (recoveredOwners) => {
13281
13762
  await this.ensureInitializedUnlocked(recoveredOwners);
13282
- await this.clearIndexUnlocked();
13763
+ const recovery = this.beginClearRecoveryState();
13764
+ await this.clearIndexUnlocked(recovery.compatibilityDecision);
13283
13765
  });
13284
13766
  }
13285
- async clearIndexUnlocked() {
13767
+ clearGlobalIndexDataUnlocked(projectRoot = this.projectRoot) {
13286
13768
  const { store, invertedIndex, database } = this.requireLoadedIndexState();
13287
- if (this.config.scope === "global") {
13288
- store.load();
13289
- invertedIndex.load();
13290
- this.loadFileHashCache();
13291
- const roots = this.getScopedRoots();
13292
- const compatibility = this.checkCompatibility();
13293
- const allMetadata = store.getAllMetadata();
13294
- const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData() || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13295
- if (!compatibility.compatible && hasForeignData) {
13296
- if (compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */) {
13297
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13298
- this.clearScopedFileHashCache(roots);
13299
- this.clearScopedFailedBatches(roots);
13300
- database.setMetadata(this.getProjectForceReembedMetadataKey(), "true");
13301
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13769
+ const clearedBranchKeys = database.getAllBranches();
13770
+ store.clear();
13771
+ store.save();
13772
+ invertedIndex.clear();
13773
+ this.saveInvertedIndex(invertedIndex);
13774
+ this.fileHashCache.clear();
13775
+ this.saveFileHashCache();
13776
+ database.clearAllIndexedData();
13777
+ this.deleteBranchCommitMetadata(database, clearedBranchKeys);
13778
+ this.clearFailedBatchState();
13779
+ database.deleteMetadata("index.version");
13780
+ database.deleteMetadata("index.pathStorageVersion");
13781
+ database.deleteMetadata("index.embeddingProvider");
13782
+ database.deleteMetadata("index.embeddingModel");
13783
+ database.deleteMetadata("index.embeddingDimensions");
13784
+ database.deleteMetadata("index.embeddingStrategyVersion");
13785
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13786
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13787
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash));
13788
+ database.deleteMetadata(this.getLegacyMigrationMetadataKey(projectIdentityHash));
13789
+ database.deleteMetadata("index.createdAt");
13790
+ database.deleteMetadata("index.updatedAt");
13791
+ this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13792
+ }
13793
+ clearGlobalIndexUnlocked(projectRoot = this.projectRoot, roots = this.getScopedRoots(), recoveryDecision) {
13794
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13795
+ store.load();
13796
+ invertedIndex.load();
13797
+ this.loadFileHashCache();
13798
+ const compatibility = this.checkCompatibility();
13799
+ const compatibilityDecision = recoveryDecision ?? (compatibility.compatible ? "compatible" : compatibility.code === "EMBEDDING_STRATEGY_MISMATCH" /* EMBEDDING_STRATEGY_MISMATCH */ ? "embedding-strategy-mismatch" : "incompatible");
13800
+ const allMetadata = store.getAllMetadata();
13801
+ const hasForeignData = allMetadata.some(({ metadata }) => !this.isFileInCurrentScope(metadata.filePath, roots)) || this.hasForeignScopedBranchData(projectRoot, roots) || this.hasForeignScopedFileHashData(roots) || this.hasForeignScopedFailedBatches(roots);
13802
+ if (compatibilityDecision !== "compatible" && hasForeignData) {
13803
+ if (compatibilityDecision === "embedding-strategy-mismatch") {
13804
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13805
+ this.clearScopedFileHashCache(roots);
13806
+ this.clearScopedFailedBatches(roots);
13807
+ const projectIdentityHash = this.getProjectIdentityHash(projectRoot);
13808
+ database.setMetadata(this.getProjectForceReembedMetadataKey(projectIdentityHash), "true");
13809
+ database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey(projectIdentityHash));
13810
+ database.deleteMetadata(this.getProjectMigrationFinalizedMetadataKey(projectIdentityHash));
13811
+ if (projectRoot === this.projectRoot) {
13302
13812
  this.indexCompatibility = { compatible: true };
13303
- return;
13304
13813
  }
13305
- throw new Error(
13306
- `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.`
13307
- );
13308
- }
13309
- if (!hasForeignData) {
13310
- const clearedBranchKeys2 = database.getAllBranches();
13311
- store.clear();
13312
- store.save();
13313
- invertedIndex.clear();
13314
- this.saveInvertedIndex(invertedIndex);
13315
- this.fileHashCache.clear();
13316
- this.saveFileHashCache();
13317
- database.clearAllIndexedData();
13318
- this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
13319
- this.clearFailedBatchState();
13320
- database.deleteMetadata("index.version");
13321
- database.deleteMetadata("index.pathStorageVersion");
13322
- database.deleteMetadata("index.embeddingProvider");
13323
- database.deleteMetadata("index.embeddingModel");
13324
- database.deleteMetadata("index.embeddingDimensions");
13325
- database.deleteMetadata("index.embeddingStrategyVersion");
13326
- database.deleteMetadata(this.getProjectEmbeddingStrategyMetadataKey());
13327
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13328
- database.deleteMetadata(this.getLegacyMigrationMetadataKey());
13329
- database.deleteMetadata("index.createdAt");
13330
- database.deleteMetadata("index.updatedAt");
13331
- this.indexCompatibility = this.validateIndexCompatibility(this.configuredProviderInfo);
13332
13814
  return;
13333
13815
  }
13334
- this.clearSharedIndexProjectData(store, invertedIndex, database, roots);
13335
- this.clearScopedFileHashCache(roots);
13336
- this.clearScopedFailedBatches(roots);
13816
+ throw new Error(
13817
+ `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.`
13818
+ );
13819
+ }
13820
+ if (!hasForeignData) {
13821
+ this.clearGlobalIndexDataUnlocked(projectRoot);
13822
+ return;
13823
+ }
13824
+ this.clearSharedIndexProjectData(store, invertedIndex, database, roots, projectRoot);
13825
+ this.clearScopedFileHashCache(roots);
13826
+ this.clearScopedFailedBatches(roots);
13827
+ if (projectRoot === this.projectRoot) {
13337
13828
  this.indexCompatibility = compatibility;
13829
+ }
13830
+ }
13831
+ async clearIndexUnlocked(recoveryDecision) {
13832
+ const { store, invertedIndex, database } = this.requireLoadedIndexState();
13833
+ if (this.config.scope === "global") {
13834
+ this.clearGlobalIndexUnlocked(this.projectRoot, this.getScopedRoots(), recoveryDecision);
13338
13835
  return;
13339
13836
  }
13340
13837
  if (!this.isProjectOwnedIndexPath()) {
@@ -13500,6 +13997,7 @@ var Indexer = class _Indexer {
13500
13997
  )) {
13501
13998
  const chunks = retryBatch.map(({ chunk }) => chunk);
13502
13999
  const attemptCounts = new Map(retryBatch.map(({ chunk, attemptCount }) => [chunk.id, attemptCount]));
14000
+ this.restoreMissingChunkRows(database, chunks);
13503
14001
  const batchResult = await this.processPendingChunkBatch(chunks, {
13504
14002
  store,
13505
14003
  provider,
@@ -13514,6 +14012,7 @@ var Indexer = class _Indexer {
13514
14012
  forceReembed: false,
13515
14013
  reuseCachedEmbeddings: false,
13516
14014
  incrementRepeatedFailures: false,
14015
+ forceSingleItemBatches: true,
13517
14016
  onSucceeded: (succeededChunks) => {
13518
14017
  database.addChunksToBranchBatch(
13519
14018
  this.getBranchCatalogKey(),
@@ -13535,9 +14034,12 @@ var Indexer = class _Indexer {
13535
14034
  this.saveInvertedIndex(invertedIndex);
13536
14035
  }
13537
14036
  if (roots && succeeded > 0 && remaining === 0 && this.hasProjectForceReembedPending()) {
13538
- database.deleteMetadata(this.getProjectForceReembedMetadataKey());
13539
- this.saveIndexMetadata(configuredProviderInfo);
13540
- this.indexCompatibility = { compatible: true };
14037
+ const migrationFinalized = database.getMetadata(this.getProjectMigrationFinalizedMetadataKey()) === "true";
14038
+ if (migrationFinalized) {
14039
+ database.deleteMetadata(this.getProjectForceReembedMetadataKey());
14040
+ this.saveIndexMetadata(configuredProviderInfo);
14041
+ this.indexCompatibility = { compatible: true };
14042
+ }
13541
14043
  }
13542
14044
  return { succeeded, failed, remaining };
13543
14045
  }
@@ -13559,7 +14061,8 @@ var Indexer = class _Indexer {
13559
14061
  latestById.set(chunkId, {
13560
14062
  attemptCount: batch.attemptCount,
13561
14063
  error: batch.error,
13562
- lastAttempt: batch.lastAttempt
14064
+ lastAttempt: batch.lastAttempt,
14065
+ chunks: [rawChunk]
13563
14066
  });
13564
14067
  }
13565
14068
  }
@@ -13626,6 +14129,7 @@ var Indexer = class _Indexer {
13626
14129
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
13627
14130
  );
13628
14131
  }
14132
+ const temporalChunkIds = this.getTemporalChunkIds(database, options);
13629
14133
  const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
13630
14134
  const prefilterMs = performance2.now() - prefilterStartTime;
13631
14135
  const vectorStartTime = performance2.now();
@@ -13634,7 +14138,8 @@ var Indexer = class _Indexer {
13634
14138
  embedding,
13635
14139
  limit * 2,
13636
14140
  branchChunkIds,
13637
- shouldPrefilterByBranch
14141
+ shouldPrefilterByBranch,
14142
+ temporalChunkIds
13638
14143
  );
13639
14144
  const vectorMs = performance2.now() - vectorStartTime;
13640
14145
  if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
@@ -14383,9 +14888,11 @@ async function searchCodebase(projectRoot, host, query, options = {}) {
14383
14888
  contextLines: options.contextLines,
14384
14889
  metadataOnly: options.metadataOnly,
14385
14890
  definitionIntent: options.definitionIntent,
14891
+ prioritizeSourcePaths: options.prioritizeSourcePaths,
14386
14892
  blameAuthor: options.blameAuthor,
14387
14893
  blameSha: options.blameSha,
14388
14894
  blameSince: options.blameSince,
14895
+ blameUntil: options.blameUntil,
14389
14896
  trace: options.trace
14390
14897
  });
14391
14898
  }
@@ -14431,7 +14938,9 @@ async function findSimilarCode(projectRoot, host, code, options = {}) {
14431
14938
  fileType: options.fileType,
14432
14939
  directory: options.directory,
14433
14940
  chunkType: options.chunkType,
14434
- excludeFile: options.excludeFile
14941
+ excludeFile: options.excludeFile,
14942
+ blameSince: options.blameSince,
14943
+ blameUntil: options.blameUntil
14435
14944
  });
14436
14945
  }
14437
14946
  async function implementationLookup(projectRoot, host, query, options = {}) {
@@ -17970,13 +18479,19 @@ async function resolveSearchContext(input, operations) {
17970
18479
  (trace) => operations.lookup(symbol, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
17971
18480
  );
17972
18481
  };
17973
- const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt) => {
18482
+ const tryConceptualSearch = async (searchQuery, scope, relaxedFieldsForAttempt, prioritizeSourcePaths) => {
17974
18483
  return recordAttempt(
17975
18484
  "conceptual",
17976
18485
  searchQuery,
17977
18486
  scope,
17978
18487
  relaxedFieldsForAttempt,
17979
- (trace) => operations.search(searchQuery, MAX_CONTEXT_RESULT_LIMIT, scope, input.diagnostic ? trace : void 0)
18488
+ (trace) => operations.search(
18489
+ searchQuery,
18490
+ MAX_CONTEXT_RESULT_LIMIT,
18491
+ scope,
18492
+ input.diagnostic ? trace : void 0,
18493
+ { prioritizeSourcePaths }
18494
+ )
17980
18495
  );
17981
18496
  };
17982
18497
  const findSuccessfulAttemptState = (route) => {
@@ -18104,10 +18619,12 @@ Explicit symbol lookup only; conceptual search was not attempted.`
18104
18619
  }
18105
18620
  }
18106
18621
  for (const attempt of conceptualAttemptPlan) {
18622
+ const attemptIntent = analyzeQueryIntent(attempt.queryText);
18623
+ const prioritizeSourcePaths = attemptIntent.primary !== "docs" && attemptIntent.primary !== "test";
18107
18624
  if (inferredSymbol && attempt.queryText === inferredSymbol && attempt.queryText !== query) {
18108
18625
  decisions.fallbackFromOriginalConceptualToInferred = true;
18109
18626
  }
18110
- const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed);
18627
+ const results = await tryConceptualSearch(attempt.queryText, attempt.scope, attempt.relaxed, prioritizeSourcePaths);
18111
18628
  if (results.length > 0) {
18112
18629
  const heading = buildPackHeading("conceptual", decisions);
18113
18630
  const intent = analyzeQueryIntent(attempt.queryText);
@@ -18244,12 +18761,13 @@ async function resolveCodebaseContextUnmeasured(projectRoot, host, input) {
18244
18761
  directory: scope.directory,
18245
18762
  trace
18246
18763
  }),
18247
- search: (queryText, retrievalLimit, scope, trace) => searchCodebase(projectRoot, host, queryText, {
18764
+ search: (queryText, retrievalLimit, scope, trace, searchOptions) => searchCodebase(projectRoot, host, queryText, {
18248
18765
  limit: retrievalLimit,
18249
18766
  fileType: scope.fileType,
18250
18767
  directory: scope.directory,
18251
18768
  metadataOnly: true,
18252
- trace
18769
+ trace,
18770
+ prioritizeSourcePaths: searchOptions?.prioritizeSourcePaths
18253
18771
  })
18254
18772
  });
18255
18773
  }
@@ -19164,7 +19682,8 @@ var codebase_peek = tool({
19164
19682
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
19165
19683
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
19166
19684
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
19167
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19685
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19686
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
19168
19687
  },
19169
19688
  async execute(args, context) {
19170
19689
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "peek", args.query, {
@@ -19175,7 +19694,8 @@ var codebase_peek = tool({
19175
19694
  metadataOnly: true,
19176
19695
  blameAuthor: args.blameAuthor,
19177
19696
  blameSha: args.blameSha,
19178
- blameSince: args.blameSince
19697
+ blameSince: args.blameSince,
19698
+ blameUntil: args.blameUntil
19179
19699
  }, (results) => {
19180
19700
  const text = formatCodebasePeek(results);
19181
19701
  return { output: text, text };
@@ -19237,7 +19757,9 @@ var find_similar = tool({
19237
19757
  fileType: z3.string().optional().describe("Filter by file extension (e.g., 'ts', 'py', 'rs')"),
19238
19758
  directory: z3.string().optional().describe("Filter by directory path (e.g., 'src/utils', 'lib')"),
19239
19759
  chunkType: z3.enum(CHUNK_TYPE_VALUES).optional().describe("Filter by code chunk type"),
19240
- excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)")
19760
+ excludeFile: z3.string().optional().describe("Exclude results from this file path (useful when searching for duplicates of code from a specific file)"),
19761
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19762
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
19241
19763
  },
19242
19764
  async execute(args, context) {
19243
19765
  const results = await findSimilarCode(context?.worktree, DEFAULT_HOST, args.code, {
@@ -19245,7 +19767,9 @@ var find_similar = tool({
19245
19767
  fileType: args.fileType,
19246
19768
  directory: args.directory,
19247
19769
  chunkType: args.chunkType,
19248
- excludeFile: args.excludeFile
19770
+ excludeFile: args.excludeFile,
19771
+ blameSince: args.blameSince,
19772
+ blameUntil: args.blameUntil
19249
19773
  });
19250
19774
  if (results.length === 0) {
19251
19775
  return "No similar code found. Try a different snippet or run index_codebase first.";
@@ -19264,7 +19788,8 @@ var codebase_search = tool({
19264
19788
  contextLines: z3.number().optional().describe("Number of extra lines to include before/after each match (default: 0)"),
19265
19789
  blameAuthor: z3.string().optional().describe("Filter by git blame author name or email"),
19266
19790
  blameSha: z3.string().optional().describe("Filter by git blame commit SHA or prefix"),
19267
- blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)")
19791
+ blameSince: z3.string().optional().describe("Filter to chunks last changed on or after this date (e.g., 2025-01-01)"),
19792
+ blameUntil: z3.string().optional().describe("Filter to chunks last changed on or before this date (e.g., 2025-01-31)")
19268
19793
  },
19269
19794
  async execute(args, context) {
19270
19795
  return searchCodebaseWithEffectiveness(context?.worktree, DEFAULT_HOST, "search", args.query, {
@@ -19275,7 +19800,8 @@ var codebase_search = tool({
19275
19800
  contextLines: args.contextLines,
19276
19801
  blameAuthor: args.blameAuthor,
19277
19802
  blameSha: args.blameSha,
19278
- blameSince: args.blameSince
19803
+ blameSince: args.blameSince,
19804
+ blameUntil: args.blameUntil
19279
19805
  }, (results) => {
19280
19806
  const text = results.length === 0 ? "No matching code found. Try a different query or run index_codebase first." : formatSearchResults(results, "score");
19281
19807
  return { output: text, text };
@@ -19488,6 +20014,12 @@ var PI_TOOL_NAMES = [
19488
20014
  TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
19489
20015
  TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
19490
20016
  ];
20017
+ var MCP_TOOL_NAMES = [
20018
+ ...PORTABLE_TOOL_NAMES,
20019
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
20020
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
20021
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE
20022
+ ];
19491
20023
 
19492
20024
  // src/commands/loader.ts
19493
20025
  import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";