opencode-codebase-index 0.17.1 → 0.18.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.cjs CHANGED
@@ -680,7 +680,8 @@ var DEFAULT_INCLUDE = [
680
680
  "**/*.{sh,bash,zsh}",
681
681
  "**/*.{txt,html,htm}",
682
682
  "**/*.zig",
683
- "**/*.gd"
683
+ "**/*.gd",
684
+ "**/*.metal"
684
685
  ];
685
686
  var DEFAULT_EXCLUDE = [
686
687
  "**/node_modules/**",
@@ -871,6 +872,9 @@ function isValidProvider(value) {
871
872
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
872
873
  }
873
874
  function isValidModel(value, provider) {
875
+ if (typeof value === "string" && provider === "ollama" && value.trim().length > 0) {
876
+ return true;
877
+ }
874
878
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS[provider]).includes(value);
875
879
  }
876
880
  function isValidScope(value) {
@@ -1235,16 +1239,6 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
1235
1239
  const fallbackPath = path3.join(mainRepoRoot, relativePath);
1236
1240
  return (0, import_fs3.existsSync)(fallbackPath) ? fallbackPath : null;
1237
1241
  }
1238
- function resolveWorktreeFallbackProjectIndexPath(projectRoot, host) {
1239
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
1240
- if (inheritedHostPath) {
1241
- return inheritedHostPath;
1242
- }
1243
- if (host !== "opencode") {
1244
- return resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1245
- }
1246
- return null;
1247
- }
1248
1242
  function getHostProjectConfigRelativePath(host) {
1249
1243
  return getProjectConfigRelativePath(host);
1250
1244
  }
@@ -1349,6 +1343,9 @@ function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
1349
1343
  if (hasHostProjectConfig(projectRoot, host)) {
1350
1344
  return localIndexPath;
1351
1345
  }
1346
+ if (resolveWorktreeMainRepoRoot(projectRoot)) {
1347
+ return localIndexPath;
1348
+ }
1352
1349
  const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
1353
1350
  if (hostFallback) {
1354
1351
  return hostFallback;
@@ -1521,12 +1518,6 @@ function loadJsonFile(filePath) {
1521
1518
  function loadConfigFile(filePath) {
1522
1519
  return loadJsonFile(filePath);
1523
1520
  }
1524
- function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
1525
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
1526
- (0, import_fs4.mkdirSync)(path6.dirname(localConfigPath), { recursive: true });
1527
- (0, import_fs4.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1528
- return localConfigPath;
1529
- }
1530
1521
  function loadProjectConfigLayer(projectRoot, host = "opencode") {
1531
1522
  const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
1532
1523
  const projectConfig = loadJsonFile(projectConfigPath);
@@ -1955,40 +1946,6 @@ function releaseIndexLock(lease) {
1955
1946
  }
1956
1947
  return true;
1957
1948
  }
1958
- async function withIndexLock(indexPath, operation, callback, options = {}) {
1959
- const lease = acquireIndexLock(indexPath, operation);
1960
- let result;
1961
- let callbackError;
1962
- let callbackFailed = false;
1963
- try {
1964
- result = await callback(lease);
1965
- } catch (error) {
1966
- callbackFailed = true;
1967
- callbackError = error;
1968
- }
1969
- if (!callbackFailed && options.completeRecoveries !== false) {
1970
- try {
1971
- completeLeaseRecovery(lease);
1972
- } catch (error) {
1973
- callbackFailed = true;
1974
- callbackError = error;
1975
- }
1976
- }
1977
- let releaseError;
1978
- try {
1979
- if (!releaseIndexLock(lease)) {
1980
- releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
1981
- }
1982
- } catch (error) {
1983
- releaseError = error;
1984
- }
1985
- if (releaseError !== void 0) {
1986
- if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
1987
- throw releaseError;
1988
- }
1989
- if (callbackFailed) throw callbackError;
1990
- return result;
1991
- }
1992
1949
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
1993
1950
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
1994
1951
  temporaryCounter += 1;
@@ -3095,6 +3052,9 @@ function loadOpenCodeAuth() {
3095
3052
  return {};
3096
3053
  }
3097
3054
  async function detectEmbeddingProvider(preferredProvider, model) {
3055
+ if (preferredProvider === "ollama") {
3056
+ return detectOllamaProvider(model);
3057
+ }
3098
3058
  const credentials = await getProviderCredentials(preferredProvider);
3099
3059
  if (credentials) {
3100
3060
  if (!model) {
@@ -3110,10 +3070,16 @@ async function detectEmbeddingProvider(preferredProvider, model) {
3110
3070
  );
3111
3071
  }
3112
3072
  const providerModels = EMBEDDING_MODELS[preferredProvider];
3073
+ const modelInfo = Object.values(providerModels).find((candidate) => candidate.model === model);
3074
+ if (!modelInfo) {
3075
+ throw new Error(
3076
+ `Model '${model}' is not supported by provider '${preferredProvider}'`
3077
+ );
3078
+ }
3113
3079
  return {
3114
3080
  provider: preferredProvider,
3115
3081
  credentials,
3116
- modelInfo: providerModels[model]
3082
+ modelInfo
3117
3083
  };
3118
3084
  }
3119
3085
  throw new Error(
@@ -3122,6 +3088,13 @@ async function detectEmbeddingProvider(preferredProvider, model) {
3122
3088
  }
3123
3089
  async function tryDetectProvider() {
3124
3090
  for (const provider of autoDetectProviders) {
3091
+ if (provider === "ollama") {
3092
+ const ollamaProvider = await tryDetectOllamaProvider();
3093
+ if (ollamaProvider) {
3094
+ return ollamaProvider;
3095
+ }
3096
+ continue;
3097
+ }
3125
3098
  const credentials = await getProviderCredentials(provider);
3126
3099
  if (credentials) {
3127
3100
  return {
@@ -3189,32 +3162,105 @@ function getGoogleCredentials() {
3189
3162
  }
3190
3163
  return null;
3191
3164
  }
3192
- async function getOllamaCredentials() {
3193
- const baseUrl = process.env.OLLAMA_HOST || "http://localhost:11434";
3165
+ async function fetchOllama(url, init) {
3166
+ const controller = new AbortController();
3167
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
3194
3168
  try {
3195
- const controller = new AbortController();
3196
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
3197
- const response = await fetch(`${baseUrl}/api/tags`, {
3198
- signal: controller.signal
3199
- });
3169
+ return await fetch(url, { ...init, signal: controller.signal });
3170
+ } finally {
3200
3171
  clearTimeout(timeoutId);
3172
+ }
3173
+ }
3174
+ async function getOllamaCredentials() {
3175
+ const baseUrl = (process.env.OLLAMA_HOST || "http://localhost:11434").replace(/\/+$/, "");
3176
+ try {
3177
+ const response = await fetchOllama(`${baseUrl}/api/tags`);
3201
3178
  if (response.ok) {
3202
- const data = await response.json();
3203
- const hasEmbeddingModel = data.models?.some(
3204
- (m) => m.name.includes("nomic-embed") || m.name.includes("mxbai-embed") || m.name.includes("all-minilm")
3205
- );
3206
- if (hasEmbeddingModel) {
3207
- return {
3208
- provider: "ollama",
3209
- baseUrl
3210
- };
3211
- }
3179
+ return {
3180
+ provider: "ollama",
3181
+ baseUrl
3182
+ };
3212
3183
  }
3213
3184
  } catch {
3214
3185
  return null;
3215
3186
  }
3216
3187
  return null;
3217
3188
  }
3189
+ function findCatalogOllamaModel(model) {
3190
+ const stableName = model.endsWith(":latest") ? model.slice(0, -":latest".length) : model;
3191
+ return Object.values(EMBEDDING_MODELS.ollama).find((candidate) => candidate.model === stableName) ?? null;
3192
+ }
3193
+ function getPositiveIntegerMetadata(modelInfo, suffix) {
3194
+ const values = Object.entries(modelInfo).filter(([key]) => key.endsWith(suffix)).map(([, value]) => value).filter((value) => typeof value === "number" && Number.isInteger(value) && value > 0);
3195
+ return values.length === 1 ? values[0] : null;
3196
+ }
3197
+ async function fetchOllamaModelInfo(credentials, model) {
3198
+ const response = await fetchOllama(`${credentials.baseUrl}/api/show`, {
3199
+ method: "POST",
3200
+ headers: { "Content-Type": "application/json" },
3201
+ body: JSON.stringify({ model })
3202
+ });
3203
+ if (!response.ok) {
3204
+ return null;
3205
+ }
3206
+ const data = await response.json();
3207
+ if (!data.capabilities?.includes("embedding") || !data.model_info) {
3208
+ return null;
3209
+ }
3210
+ const dimensions = getPositiveIntegerMetadata(data.model_info, ".embedding_length");
3211
+ const maxTokens = getPositiveIntegerMetadata(data.model_info, ".context_length");
3212
+ if (!dimensions || !maxTokens) {
3213
+ return null;
3214
+ }
3215
+ return {
3216
+ provider: "ollama",
3217
+ model,
3218
+ dimensions,
3219
+ maxTokens,
3220
+ costPer1MTokens: 0
3221
+ };
3222
+ }
3223
+ async function listOllamaModels(credentials) {
3224
+ const response = await fetchOllama(`${credentials.baseUrl}/api/tags`);
3225
+ if (!response.ok) {
3226
+ return [];
3227
+ }
3228
+ const data = await response.json();
3229
+ return [...new Set((data.models ?? []).map((entry) => entry.name ?? entry.model).filter((name) => typeof name === "string" && name.trim().length > 0))];
3230
+ }
3231
+ async function detectOllamaProvider(model) {
3232
+ const credentials = await getOllamaCredentials();
3233
+ if (!credentials) {
3234
+ throw new Error("Preferred provider 'ollama' is not configured or authenticated");
3235
+ }
3236
+ const requestedModel = model?.trim();
3237
+ if (requestedModel) {
3238
+ const catalogModel = findCatalogOllamaModel(requestedModel);
3239
+ if (catalogModel) {
3240
+ return { provider: "ollama", credentials, modelInfo: catalogModel };
3241
+ }
3242
+ }
3243
+ const candidates = requestedModel ? [requestedModel] : await listOllamaModels(credentials);
3244
+ for (const candidate of candidates) {
3245
+ const modelInfo = await fetchOllamaModelInfo(credentials, candidate);
3246
+ if (modelInfo) {
3247
+ return {
3248
+ provider: "ollama",
3249
+ credentials,
3250
+ modelInfo: findCatalogOllamaModel(candidate) ?? modelInfo
3251
+ };
3252
+ }
3253
+ }
3254
+ const detail = model ? `Model '${model}' is not installed or is not embedding-capable` : "No installed embedding-capable Ollama model was found";
3255
+ throw new Error(detail);
3256
+ }
3257
+ async function tryDetectOllamaProvider() {
3258
+ try {
3259
+ return await detectOllamaProvider();
3260
+ } catch {
3261
+ return null;
3262
+ }
3263
+ }
3218
3264
  function getProviderDisplayName(provider) {
3219
3265
  switch (provider) {
3220
3266
  case "github-copilot":
@@ -3554,6 +3600,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3554
3600
  // src/embeddings/providers/ollama.ts
3555
3601
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
3556
3602
  static MIN_TRUNCATION_CHARS = 512;
3603
+ static REQUEST_TIMEOUT_MS = 12e4;
3557
3604
  constructor(credentials, modelInfo) {
3558
3605
  super(credentials, modelInfo);
3559
3606
  }
@@ -3628,22 +3675,45 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3628
3675
  }
3629
3676
  }
3630
3677
  async embedSingle(text) {
3631
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3632
- method: "POST",
3633
- headers: {
3634
- "Content-Type": "application/json"
3635
- },
3636
- body: JSON.stringify({
3637
- model: this.modelInfo.model,
3638
- prompt: text,
3639
- truncate: false
3640
- })
3641
- });
3678
+ const controller = new AbortController();
3679
+ const timeout = setTimeout(
3680
+ () => controller.abort(),
3681
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3682
+ );
3683
+ let response;
3684
+ try {
3685
+ response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3686
+ method: "POST",
3687
+ headers: {
3688
+ "Content-Type": "application/json"
3689
+ },
3690
+ body: JSON.stringify({
3691
+ model: this.modelInfo.model,
3692
+ prompt: text,
3693
+ truncate: false
3694
+ }),
3695
+ signal: controller.signal
3696
+ });
3697
+ } catch (error) {
3698
+ if (error instanceof Error && error.name === "AbortError") {
3699
+ throw new Error(
3700
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3701
+ );
3702
+ }
3703
+ throw error;
3704
+ } finally {
3705
+ clearTimeout(timeout);
3706
+ }
3642
3707
  if (!response.ok) {
3643
3708
  const error = (await response.text()).slice(0, 500);
3644
3709
  throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
3645
3710
  }
3646
3711
  const data = await response.json();
3712
+ if (!Array.isArray(data.embedding) || data.embedding.length !== this.modelInfo.dimensions || data.embedding.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
3713
+ throw new Error(
3714
+ `Ollama returned an invalid embedding; expected ${this.modelInfo.dimensions} finite dimensions`
3715
+ );
3716
+ }
3647
3717
  return {
3648
3718
  embedding: data.embedding,
3649
3719
  tokensUsed: this.estimateTokens(text)
@@ -4507,7 +4577,9 @@ function mapChunk(c) {
4507
4577
  return {
4508
4578
  content: c.content,
4509
4579
  startLine: c.startLine ?? c.start_line,
4580
+ startCol: c.startCol ?? c.start_col,
4510
4581
  endLine: c.endLine ?? c.end_line,
4582
+ endCol: c.endCol ?? c.end_col,
4511
4583
  chunkType: c.chunkType ?? c.chunk_type,
4512
4584
  name: c.name ?? void 0,
4513
4585
  language: c.language
@@ -4628,6 +4700,7 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4628
4700
  javascript: "JavaScript",
4629
4701
  python: "Python",
4630
4702
  rust: "Rust",
4703
+ swift: "Swift",
4631
4704
  go: "Go",
4632
4705
  java: "Java"
4633
4706
  };
@@ -4636,7 +4709,16 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4636
4709
  function: "function",
4637
4710
  arrow_function: "arrow function",
4638
4711
  method_definition: "method",
4712
+ method_declaration: "method",
4713
+ protocol_function_declaration: "protocol requirement",
4714
+ init_declaration: "initializer",
4715
+ deinit_declaration: "deinitializer",
4716
+ subscript_declaration: "subscript",
4639
4717
  class_declaration: "class",
4718
+ actor_declaration: "actor",
4719
+ extension_declaration: "extension",
4720
+ protocol_declaration: "protocol",
4721
+ struct_declaration: "struct",
4640
4722
  interface_declaration: "interface",
4641
4723
  type_alias_declaration: "type alias",
4642
4724
  enum_declaration: "enum",
@@ -5307,8 +5389,18 @@ async function getChunkGitBlame(projectRoot, filePath, startLine, endLine) {
5307
5389
  }
5308
5390
 
5309
5391
  // src/indexer/index.ts
5310
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
5311
- var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5392
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "swift", "php", "apex", "zig", "gdscript", "matlab", "bash", "c", "cpp", "metal"]);
5393
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex", "php"]);
5394
+ var CALL_GRAPH_RESOLUTION_VERSION = "4";
5395
+ var PHP_FUNCTION_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5396
+ "function_declaration",
5397
+ "function",
5398
+ "function_definition"
5399
+ ]);
5400
+ var PHP_CLASS_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5401
+ "class_declaration",
5402
+ "class_definition"
5403
+ ]);
5312
5404
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5313
5405
  "function_declaration",
5314
5406
  "function",
@@ -5321,6 +5413,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5321
5413
  "enum_declaration",
5322
5414
  "function_definition",
5323
5415
  "class_definition",
5416
+ "class_specifier",
5417
+ "struct_specifier",
5418
+ "namespace_definition",
5324
5419
  "decorated_definition",
5325
5420
  "method_declaration",
5326
5421
  "type_declaration",
@@ -5336,6 +5431,14 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5336
5431
  "test_declaration",
5337
5432
  "struct_declaration",
5338
5433
  "union_declaration",
5434
+ // Synthetic Swift declarations or declarations specific to tree-sitter-swift.
5435
+ "actor_declaration",
5436
+ "extension_declaration",
5437
+ "protocol_declaration",
5438
+ "protocol_function_declaration",
5439
+ "init_declaration",
5440
+ "deinit_declaration",
5441
+ "subscript_declaration",
5339
5442
  // GDScript declarations whose names participate in the call graph.
5340
5443
  // `function_definition` and `class_definition` are already in the set
5341
5444
  // above (shared with Python/C/Bash and Python, respectively).
@@ -5345,6 +5448,53 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5345
5448
  "const_statement",
5346
5449
  "class_name_statement"
5347
5450
  ]);
5451
+ var C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set(["class_specifier", "struct_specifier"]);
5452
+ function isCompatibleCFamilyCallTarget(language, callType, symbolKind) {
5453
+ if (language !== "c" && language !== "cpp") return true;
5454
+ if (symbolKind === "namespace_definition") return callType === "Import";
5455
+ const isTypeSymbol = C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES.has(symbolKind);
5456
+ if (callType === "Constructor" || callType === "Inherits" || callType === "Implements") {
5457
+ return isTypeSymbol;
5458
+ }
5459
+ return !isTypeSymbol;
5460
+ }
5461
+ var EXECUTABLE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5462
+ "function_declaration",
5463
+ "function",
5464
+ "arrow_function",
5465
+ "method_definition",
5466
+ "function_definition",
5467
+ "method_declaration",
5468
+ "function_item",
5469
+ "protocol_function_declaration",
5470
+ "init_declaration",
5471
+ "deinit_declaration",
5472
+ "subscript_declaration",
5473
+ "constructor_definition",
5474
+ "trigger_declaration",
5475
+ "test_declaration"
5476
+ ]);
5477
+ function findEnclosingSymbol(symbols, line, column) {
5478
+ let best;
5479
+ for (const symbol of symbols) {
5480
+ if (line < symbol.startLine || line > symbol.endLine) continue;
5481
+ if (column !== void 0 && (line === symbol.startLine && column < symbol.startCol || line === symbol.endLine && column >= symbol.endCol)) {
5482
+ continue;
5483
+ }
5484
+ if (!best) {
5485
+ best = symbol;
5486
+ continue;
5487
+ }
5488
+ const span = symbol.endLine - symbol.startLine;
5489
+ const bestSpan = best.endLine - best.startLine;
5490
+ const isNarrowerPositionRange = column !== void 0 && span === bestSpan && symbol.startLine === best.startLine && symbol.endLine === best.endLine && symbol.startCol >= best.startCol && symbol.endCol <= best.endCol && (symbol.startCol > best.startCol || symbol.endCol < best.endCol);
5491
+ const isMoreSpecificTie = span === bestSpan && symbol.startLine === best.startLine && EXECUTABLE_SYMBOL_CHUNK_TYPES.has(symbol.kind) && !EXECUTABLE_SYMBOL_CHUNK_TYPES.has(best.kind);
5492
+ if (span < bestSpan || span === bestSpan && symbol.startLine > best.startLine || isNarrowerPositionRange || isMoreSpecificTie) {
5493
+ best = symbol;
5494
+ }
5495
+ }
5496
+ return best;
5497
+ }
5348
5498
  function float32ArrayToBuffer(arr) {
5349
5499
  const float32 = new Float32Array(arr);
5350
5500
  return Buffer.from(float32.buffer);
@@ -5429,6 +5579,8 @@ function hasBlameMetadata(metadata) {
5429
5579
  }
5430
5580
  var INDEX_METADATA_VERSION = "1";
5431
5581
  var EMBEDDING_STRATEGY_VERSION = "2";
5582
+ var SWIFT_PARSER_VERSION = "1";
5583
+ var METAL_PARSER_VERSION = "1";
5432
5584
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
5433
5585
  var RANK_HYBRID_CACHE_LIMIT = 256;
5434
5586
  function createPendingChunkStorageText(texts) {
@@ -5751,15 +5903,25 @@ function chunkTypeBoost(chunkType) {
5751
5903
  case "function_declaration":
5752
5904
  case "method":
5753
5905
  case "method_definition":
5906
+ case "method_declaration":
5907
+ case "protocol_function_declaration":
5908
+ case "init_declaration":
5909
+ case "deinit_declaration":
5910
+ case "subscript_declaration":
5754
5911
  case "class":
5755
5912
  case "class_declaration":
5913
+ case "actor_declaration":
5914
+ case "extension_declaration":
5756
5915
  return 0.2;
5757
5916
  case "interface":
5758
5917
  case "type":
5759
5918
  case "enum":
5919
+ case "enum_declaration":
5760
5920
  case "struct":
5921
+ case "struct_declaration":
5761
5922
  case "impl":
5762
5923
  case "trait":
5924
+ case "protocol_declaration":
5763
5925
  case "module":
5764
5926
  return 0.1;
5765
5927
  default:
@@ -5866,11 +6028,21 @@ function isImplementationChunkType(chunkType) {
5866
6028
  "function_declaration",
5867
6029
  "method",
5868
6030
  "method_definition",
6031
+ "method_declaration",
6032
+ "protocol_function_declaration",
6033
+ "init_declaration",
6034
+ "deinit_declaration",
6035
+ "subscript_declaration",
5869
6036
  "class",
5870
6037
  "class_declaration",
6038
+ "actor_declaration",
6039
+ "extension_declaration",
5871
6040
  "interface",
6041
+ "protocol_declaration",
5872
6042
  "type",
5873
6043
  "enum",
6044
+ "enum_declaration",
6045
+ "struct_declaration",
5874
6046
  "module"
5875
6047
  ].includes(chunkType);
5876
6048
  }
@@ -6873,6 +7045,29 @@ var Indexer = class {
6873
7045
  const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6874
7046
  return `index.forceReembed.${projectHash}`;
6875
7047
  }
7048
+ getCallGraphResolutionMetadataKey() {
7049
+ if (this.config.scope !== "global") {
7050
+ return "index.callGraphResolutionVersion";
7051
+ }
7052
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7053
+ return `index.callGraphResolutionVersion.${projectHash}`;
7054
+ }
7055
+ getSwiftParserVersionMetadataKey() {
7056
+ const key = "index.parser.swiftVersion";
7057
+ if (this.config.scope !== "global") {
7058
+ return key;
7059
+ }
7060
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7061
+ return `${key}.${projectHash}`;
7062
+ }
7063
+ getMetalParserVersionMetadataKey() {
7064
+ const key = "index.parser.metalVersion";
7065
+ if (this.config.scope !== "global") {
7066
+ return key;
7067
+ }
7068
+ const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7069
+ return `${key}.${projectHash}`;
7070
+ }
6876
7071
  hasProjectForceReembedPending() {
6877
7072
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
6878
7073
  }
@@ -8033,6 +8228,7 @@ var Indexer = class {
8033
8228
  this.database.setMetadata("index.embeddingProvider", provider.provider);
8034
8229
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
8035
8230
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8231
+ this.database.setMetadata(this.getCallGraphResolutionMetadataKey(), CALL_GRAPH_RESOLUTION_VERSION);
8036
8232
  if (this.config.scope === "global") {
8037
8233
  if (completeProjectEmbeddingStrategyReset) {
8038
8234
  this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
@@ -8220,6 +8416,20 @@ var Indexer = class {
8220
8416
  totalChunks: 0
8221
8417
  });
8222
8418
  this.loadFileHashCache();
8419
+ const swiftParserMetadataKey = this.getSwiftParserVersionMetadataKey();
8420
+ const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
8421
+ const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
8422
+ const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
8423
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
8424
+ (filePath) => path13.extname(filePath).toLowerCase() === ".swift"
8425
+ )) {
8426
+ this.logger.info("Reindexing cached Swift files for parser support");
8427
+ }
8428
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some(
8429
+ (filePath) => path13.extname(filePath).toLowerCase() === ".metal"
8430
+ )) {
8431
+ this.logger.info("Reindexing cached Metal files for parser support");
8432
+ }
8223
8433
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
8224
8434
  const { files, skipped } = await collectFiles(
8225
8435
  this.projectRoot,
@@ -8239,10 +8449,17 @@ var Indexer = class {
8239
8449
  const changedFiles = [];
8240
8450
  const unchangedFilePaths = /* @__PURE__ */ new Set();
8241
8451
  const currentFileHashes = /* @__PURE__ */ new Map();
8452
+ const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
8242
8453
  for (const f of files) {
8243
8454
  const currentHash = hashFile(f.path);
8244
8455
  currentFileHashes.set(f.path, currentHash);
8245
- if (this.fileHashCache.get(f.path) === currentHash) {
8456
+ const cachedHashMatches = this.fileHashCache.get(f.path) === currentHash;
8457
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(f.path).some(
8458
+ (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
8459
+ );
8460
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path13.extname(f.path).toLowerCase() === ".swift";
8461
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path13.extname(f.path).toLowerCase() === ".metal";
8462
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
8246
8463
  unchangedFilePaths.add(f.path);
8247
8464
  this.logger.recordCacheHit();
8248
8465
  } else {
@@ -8444,16 +8661,28 @@ var Indexer = class {
8444
8661
  const fileSymbols = [];
8445
8662
  for (const chunk of parsed.chunks) {
8446
8663
  if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
8447
- const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
8664
+ const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
8665
+ (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
8666
+ ) : void 0;
8667
+ if (existingMetalSymbol) {
8668
+ existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
8669
+ existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
8670
+ existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
8671
+ existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
8672
+ continue;
8673
+ }
8674
+ const symbolId = `sym_${hashContent(
8675
+ parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0)
8676
+ ).slice(0, 16)}`;
8448
8677
  const symbol = {
8449
8678
  id: symbolId,
8450
8679
  filePath: parsed.path,
8451
8680
  name: chunk.name,
8452
8681
  kind: chunk.chunkType,
8453
8682
  startLine: chunk.startLine,
8454
- startCol: 0,
8683
+ startCol: chunk.startCol ?? 0,
8455
8684
  endLine: chunk.endLine,
8456
- endCol: 0,
8685
+ endCol: chunk.endCol ?? 0,
8457
8686
  language: chunk.language
8458
8687
  };
8459
8688
  fileSymbols.push(symbol);
@@ -8478,8 +8707,10 @@ var Indexer = class {
8478
8707
  if (callSites.length === 0) continue;
8479
8708
  const edges = [];
8480
8709
  for (const site of callSites) {
8481
- const enclosingSymbol = fileSymbols.find(
8482
- (sym) => site.line >= sym.startLine && site.line <= sym.endLine
8710
+ const enclosingSymbol = findEnclosingSymbol(
8711
+ fileSymbols,
8712
+ site.line,
8713
+ site.column
8483
8714
  );
8484
8715
  if (!enclosingSymbol) continue;
8485
8716
  const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
@@ -8498,7 +8729,21 @@ var Indexer = class {
8498
8729
  if (edges.length > 0) {
8499
8730
  database.upsertCallEdgesBatch(edges);
8500
8731
  for (const edge of edges) {
8501
- const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8732
+ let candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8733
+ if (fileLanguage === "php" && candidates) {
8734
+ if (edge.callType === "Constructor") {
8735
+ candidates = candidates.filter(
8736
+ (candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8737
+ );
8738
+ } else if (edge.callType === "Call") {
8739
+ candidates = candidates.filter(
8740
+ (candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8741
+ );
8742
+ }
8743
+ }
8744
+ candidates = candidates?.filter(
8745
+ (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, edge.callType, symbol.kind)
8746
+ );
8502
8747
  if (candidates && candidates.length === 1) {
8503
8748
  database.resolveCallEdge(edge.id, candidates[0].id);
8504
8749
  }
@@ -8553,6 +8798,8 @@ var Indexer = class {
8553
8798
  this.saveFileHashCache();
8554
8799
  this.saveFailedBatches([]);
8555
8800
  }
8801
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8802
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8556
8803
  this.saveIndexMetadata(configuredProviderInfo);
8557
8804
  this.indexCompatibility = { compatible: true };
8558
8805
  stats.durationMs = Date.now() - startTime;
@@ -8580,6 +8827,8 @@ var Indexer = class {
8580
8827
  this.saveFileHashCache();
8581
8828
  this.saveFailedBatches([]);
8582
8829
  }
8830
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8831
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8583
8832
  this.saveIndexMetadata(configuredProviderInfo);
8584
8833
  this.indexCompatibility = { compatible: true };
8585
8834
  stats.durationMs = Date.now() - startTime;
@@ -8850,6 +9099,8 @@ var Indexer = class {
8850
9099
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
8851
9100
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8852
9101
  }
9102
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9103
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8853
9104
  this.saveIndexMetadata(configuredProviderInfo);
8854
9105
  this.indexCompatibility = { compatible: true };
8855
9106
  this.logger.recordIndexingEnd();
@@ -10510,15 +10761,6 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
10510
10761
  indexerCache.set(key, indexer);
10511
10762
  configCache.set(key, config);
10512
10763
  }
10513
- function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
10514
- const root = getProjectRoot(projectRoot, host);
10515
- const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10516
- if ((0, import_fs10.existsSync)(localIndexPath)) {
10517
- return false;
10518
- }
10519
- const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
10520
- return inheritedIndexPath !== null;
10521
- }
10522
10764
  async function searchCodebase(projectRoot, host, query, options = {}) {
10523
10765
  const indexer = getIndexerForProject(projectRoot, host);
10524
10766
  return indexer.search(query, options.limit, {
@@ -10568,9 +10810,7 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
10568
10810
  }
10569
10811
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
10570
10812
  const root = getProjectRoot(projectRoot, host);
10571
- const key = getIndexerCacheKey(root, host);
10572
- let indexer = getIndexerForProject(root, host);
10573
- const runtimeConfig = configCache.get(key);
10813
+ const indexer = getIndexerForProject(root, host);
10574
10814
  try {
10575
10815
  if (args.estimateOnly) {
10576
10816
  return { kind: "estimate", estimate: await indexer.estimateCost() };
@@ -10591,19 +10831,7 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
10591
10831
  return Promise.resolve();
10592
10832
  });
10593
10833
  };
10594
- let stats;
10595
- if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10596
- const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10597
- stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10598
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10599
- refreshIndexerForDirectory(root, host, runtimeConfig);
10600
- indexer = getIndexerForProject(root, host);
10601
- return runIndex(indexer);
10602
- }, { completeRecoveries: false });
10603
- } else {
10604
- stats = await runIndex(indexer);
10605
- }
10606
- return { kind: "stats", stats };
10834
+ return { kind: "stats", stats: await runIndex(indexer) };
10607
10835
  } catch (error) {
10608
10836
  const busyResult = getIndexBusyResult(error);
10609
10837
  if (!busyResult) throw error;