opencode-codebase-index 0.17.1 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -653,7 +653,7 @@ var require_ignore = __commonJS({
653
653
 
654
654
  // src/cli.ts
655
655
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
656
- import { realpathSync as realpathSync3, writeFileSync as writeFileSync7 } from "fs";
656
+ import { realpathSync as realpathSync3, writeFileSync as writeFileSync6 } from "fs";
657
657
  import * as os6 from "os";
658
658
  import * as path26 from "path";
659
659
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -673,7 +673,8 @@ var DEFAULT_INCLUDE = [
673
673
  "**/*.{sh,bash,zsh}",
674
674
  "**/*.{txt,html,htm}",
675
675
  "**/*.zig",
676
- "**/*.gd"
676
+ "**/*.gd",
677
+ "**/*.metal"
677
678
  ];
678
679
  var DEFAULT_EXCLUDE = [
679
680
  "**/node_modules/**",
@@ -864,6 +865,9 @@ function isValidProvider(value) {
864
865
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
865
866
  }
866
867
  function isValidModel(value, provider) {
868
+ if (typeof value === "string" && provider === "ollama" && value.trim().length > 0) {
869
+ return true;
870
+ }
867
871
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS[provider]).includes(value);
868
872
  }
869
873
  function isValidScope(value) {
@@ -2446,6 +2450,9 @@ function loadOpenCodeAuth() {
2446
2450
  return {};
2447
2451
  }
2448
2452
  async function detectEmbeddingProvider(preferredProvider, model) {
2453
+ if (preferredProvider === "ollama") {
2454
+ return detectOllamaProvider(model);
2455
+ }
2449
2456
  const credentials = await getProviderCredentials(preferredProvider);
2450
2457
  if (credentials) {
2451
2458
  if (!model) {
@@ -2461,10 +2468,16 @@ async function detectEmbeddingProvider(preferredProvider, model) {
2461
2468
  );
2462
2469
  }
2463
2470
  const providerModels = EMBEDDING_MODELS[preferredProvider];
2471
+ const modelInfo = Object.values(providerModels).find((candidate) => candidate.model === model);
2472
+ if (!modelInfo) {
2473
+ throw new Error(
2474
+ `Model '${model}' is not supported by provider '${preferredProvider}'`
2475
+ );
2476
+ }
2464
2477
  return {
2465
2478
  provider: preferredProvider,
2466
2479
  credentials,
2467
- modelInfo: providerModels[model]
2480
+ modelInfo
2468
2481
  };
2469
2482
  }
2470
2483
  throw new Error(
@@ -2473,6 +2486,13 @@ async function detectEmbeddingProvider(preferredProvider, model) {
2473
2486
  }
2474
2487
  async function tryDetectProvider() {
2475
2488
  for (const provider of autoDetectProviders) {
2489
+ if (provider === "ollama") {
2490
+ const ollamaProvider = await tryDetectOllamaProvider();
2491
+ if (ollamaProvider) {
2492
+ return ollamaProvider;
2493
+ }
2494
+ continue;
2495
+ }
2476
2496
  const credentials = await getProviderCredentials(provider);
2477
2497
  if (credentials) {
2478
2498
  return {
@@ -2540,32 +2560,105 @@ function getGoogleCredentials() {
2540
2560
  }
2541
2561
  return null;
2542
2562
  }
2543
- async function getOllamaCredentials() {
2544
- const baseUrl = process.env.OLLAMA_HOST || "http://localhost:11434";
2563
+ async function fetchOllama(url, init) {
2564
+ const controller = new AbortController();
2565
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
2545
2566
  try {
2546
- const controller = new AbortController();
2547
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
2548
- const response = await fetch(`${baseUrl}/api/tags`, {
2549
- signal: controller.signal
2550
- });
2567
+ return await fetch(url, { ...init, signal: controller.signal });
2568
+ } finally {
2551
2569
  clearTimeout(timeoutId);
2570
+ }
2571
+ }
2572
+ async function getOllamaCredentials() {
2573
+ const baseUrl = (process.env.OLLAMA_HOST || "http://localhost:11434").replace(/\/+$/, "");
2574
+ try {
2575
+ const response = await fetchOllama(`${baseUrl}/api/tags`);
2552
2576
  if (response.ok) {
2553
- const data = await response.json();
2554
- const hasEmbeddingModel = data.models?.some(
2555
- (m) => m.name.includes("nomic-embed") || m.name.includes("mxbai-embed") || m.name.includes("all-minilm")
2556
- );
2557
- if (hasEmbeddingModel) {
2558
- return {
2559
- provider: "ollama",
2560
- baseUrl
2561
- };
2562
- }
2577
+ return {
2578
+ provider: "ollama",
2579
+ baseUrl
2580
+ };
2563
2581
  }
2564
2582
  } catch {
2565
2583
  return null;
2566
2584
  }
2567
2585
  return null;
2568
2586
  }
2587
+ function findCatalogOllamaModel(model) {
2588
+ const stableName = model.endsWith(":latest") ? model.slice(0, -":latest".length) : model;
2589
+ return Object.values(EMBEDDING_MODELS.ollama).find((candidate) => candidate.model === stableName) ?? null;
2590
+ }
2591
+ function getPositiveIntegerMetadata(modelInfo, suffix) {
2592
+ const values = Object.entries(modelInfo).filter(([key]) => key.endsWith(suffix)).map(([, value]) => value).filter((value) => typeof value === "number" && Number.isInteger(value) && value > 0);
2593
+ return values.length === 1 ? values[0] : null;
2594
+ }
2595
+ async function fetchOllamaModelInfo(credentials, model) {
2596
+ const response = await fetchOllama(`${credentials.baseUrl}/api/show`, {
2597
+ method: "POST",
2598
+ headers: { "Content-Type": "application/json" },
2599
+ body: JSON.stringify({ model })
2600
+ });
2601
+ if (!response.ok) {
2602
+ return null;
2603
+ }
2604
+ const data = await response.json();
2605
+ if (!data.capabilities?.includes("embedding") || !data.model_info) {
2606
+ return null;
2607
+ }
2608
+ const dimensions = getPositiveIntegerMetadata(data.model_info, ".embedding_length");
2609
+ const maxTokens = getPositiveIntegerMetadata(data.model_info, ".context_length");
2610
+ if (!dimensions || !maxTokens) {
2611
+ return null;
2612
+ }
2613
+ return {
2614
+ provider: "ollama",
2615
+ model,
2616
+ dimensions,
2617
+ maxTokens,
2618
+ costPer1MTokens: 0
2619
+ };
2620
+ }
2621
+ async function listOllamaModels(credentials) {
2622
+ const response = await fetchOllama(`${credentials.baseUrl}/api/tags`);
2623
+ if (!response.ok) {
2624
+ return [];
2625
+ }
2626
+ const data = await response.json();
2627
+ return [...new Set((data.models ?? []).map((entry) => entry.name ?? entry.model).filter((name) => typeof name === "string" && name.trim().length > 0))];
2628
+ }
2629
+ async function detectOllamaProvider(model) {
2630
+ const credentials = await getOllamaCredentials();
2631
+ if (!credentials) {
2632
+ throw new Error("Preferred provider 'ollama' is not configured or authenticated");
2633
+ }
2634
+ const requestedModel = model?.trim();
2635
+ if (requestedModel) {
2636
+ const catalogModel = findCatalogOllamaModel(requestedModel);
2637
+ if (catalogModel) {
2638
+ return { provider: "ollama", credentials, modelInfo: catalogModel };
2639
+ }
2640
+ }
2641
+ const candidates = requestedModel ? [requestedModel] : await listOllamaModels(credentials);
2642
+ for (const candidate of candidates) {
2643
+ const modelInfo = await fetchOllamaModelInfo(credentials, candidate);
2644
+ if (modelInfo) {
2645
+ return {
2646
+ provider: "ollama",
2647
+ credentials,
2648
+ modelInfo: findCatalogOllamaModel(candidate) ?? modelInfo
2649
+ };
2650
+ }
2651
+ }
2652
+ const detail = model ? `Model '${model}' is not installed or is not embedding-capable` : "No installed embedding-capable Ollama model was found";
2653
+ throw new Error(detail);
2654
+ }
2655
+ async function tryDetectOllamaProvider() {
2656
+ try {
2657
+ return await detectOllamaProvider();
2658
+ } catch {
2659
+ return null;
2660
+ }
2661
+ }
2569
2662
  function getProviderDisplayName(provider) {
2570
2663
  switch (provider) {
2571
2664
  case "github-copilot":
@@ -2905,6 +2998,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
2905
2998
  // src/embeddings/providers/ollama.ts
2906
2999
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
2907
3000
  static MIN_TRUNCATION_CHARS = 512;
3001
+ static REQUEST_TIMEOUT_MS = 12e4;
2908
3002
  constructor(credentials, modelInfo) {
2909
3003
  super(credentials, modelInfo);
2910
3004
  }
@@ -2979,22 +3073,45 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
2979
3073
  }
2980
3074
  }
2981
3075
  async embedSingle(text) {
2982
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
2983
- method: "POST",
2984
- headers: {
2985
- "Content-Type": "application/json"
2986
- },
2987
- body: JSON.stringify({
2988
- model: this.modelInfo.model,
2989
- prompt: text,
2990
- truncate: false
2991
- })
2992
- });
3076
+ const controller = new AbortController();
3077
+ const timeout = setTimeout(
3078
+ () => controller.abort(),
3079
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3080
+ );
3081
+ let response;
3082
+ try {
3083
+ response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3084
+ method: "POST",
3085
+ headers: {
3086
+ "Content-Type": "application/json"
3087
+ },
3088
+ body: JSON.stringify({
3089
+ model: this.modelInfo.model,
3090
+ prompt: text,
3091
+ truncate: false
3092
+ }),
3093
+ signal: controller.signal
3094
+ });
3095
+ } catch (error) {
3096
+ if (error instanceof Error && error.name === "AbortError") {
3097
+ throw new Error(
3098
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3099
+ );
3100
+ }
3101
+ throw error;
3102
+ } finally {
3103
+ clearTimeout(timeout);
3104
+ }
2993
3105
  if (!response.ok) {
2994
3106
  const error = (await response.text()).slice(0, 500);
2995
3107
  throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
2996
3108
  }
2997
3109
  const data = await response.json();
3110
+ if (!Array.isArray(data.embedding) || data.embedding.length !== this.modelInfo.dimensions || data.embedding.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
3111
+ throw new Error(
3112
+ `Ollama returned an invalid embedding; expected ${this.modelInfo.dimensions} finite dimensions`
3113
+ );
3114
+ }
2998
3115
  return {
2999
3116
  embedding: data.embedding,
3000
3117
  tokensUsed: this.estimateTokens(text)
@@ -3905,7 +4022,9 @@ function mapChunk(c) {
3905
4022
  return {
3906
4023
  content: c.content,
3907
4024
  startLine: c.startLine ?? c.start_line,
4025
+ startCol: c.startCol ?? c.start_col,
3908
4026
  endLine: c.endLine ?? c.end_line,
4027
+ endCol: c.endCol ?? c.end_col,
3909
4028
  chunkType: c.chunkType ?? c.chunk_type,
3910
4029
  name: c.name ?? void 0,
3911
4030
  language: c.language
@@ -4026,6 +4145,7 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4026
4145
  javascript: "JavaScript",
4027
4146
  python: "Python",
4028
4147
  rust: "Rust",
4148
+ swift: "Swift",
4029
4149
  go: "Go",
4030
4150
  java: "Java"
4031
4151
  };
@@ -4034,7 +4154,16 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4034
4154
  function: "function",
4035
4155
  arrow_function: "arrow function",
4036
4156
  method_definition: "method",
4157
+ method_declaration: "method",
4158
+ protocol_function_declaration: "protocol requirement",
4159
+ init_declaration: "initializer",
4160
+ deinit_declaration: "deinitializer",
4161
+ subscript_declaration: "subscript",
4037
4162
  class_declaration: "class",
4163
+ actor_declaration: "actor",
4164
+ extension_declaration: "extension",
4165
+ protocol_declaration: "protocol",
4166
+ struct_declaration: "struct",
4038
4167
  interface_declaration: "interface",
4039
4168
  type_alias_declaration: "type alias",
4040
4169
  enum_declaration: "enum",
@@ -4728,16 +4857,6 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
4728
4857
  const fallbackPath = path8.join(mainRepoRoot, relativePath);
4729
4858
  return existsSync5(fallbackPath) ? fallbackPath : null;
4730
4859
  }
4731
- function resolveWorktreeFallbackProjectIndexPath(projectRoot, host) {
4732
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
4733
- if (inheritedHostPath) {
4734
- return inheritedHostPath;
4735
- }
4736
- if (host !== "opencode") {
4737
- return resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
4738
- }
4739
- return null;
4740
- }
4741
4860
  function getHostProjectIndexRelativePath(host) {
4742
4861
  return getProjectIndexRelativePath(host);
4743
4862
  }
@@ -4839,6 +4958,9 @@ function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
4839
4958
  if (hasHostProjectConfig(projectRoot, host)) {
4840
4959
  return localIndexPath;
4841
4960
  }
4961
+ if (resolveWorktreeMainRepoRoot(projectRoot)) {
4962
+ return localIndexPath;
4963
+ }
4842
4964
  const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
4843
4965
  if (hostFallback) {
4844
4966
  return hostFallback;
@@ -5373,40 +5495,6 @@ function releaseIndexLock(lease) {
5373
5495
  }
5374
5496
  return true;
5375
5497
  }
5376
- async function withIndexLock(indexPath, operation, callback, options = {}) {
5377
- const lease = acquireIndexLock(indexPath, operation);
5378
- let result;
5379
- let callbackError;
5380
- let callbackFailed = false;
5381
- try {
5382
- result = await callback(lease);
5383
- } catch (error) {
5384
- callbackFailed = true;
5385
- callbackError = error;
5386
- }
5387
- if (!callbackFailed && options.completeRecoveries !== false) {
5388
- try {
5389
- completeLeaseRecovery(lease);
5390
- } catch (error) {
5391
- callbackFailed = true;
5392
- callbackError = error;
5393
- }
5394
- }
5395
- let releaseError;
5396
- try {
5397
- if (!releaseIndexLock(lease)) {
5398
- releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5399
- }
5400
- } catch (error) {
5401
- releaseError = error;
5402
- }
5403
- if (releaseError !== void 0) {
5404
- if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5405
- throw releaseError;
5406
- }
5407
- if (callbackFailed) throw callbackError;
5408
- return result;
5409
- }
5410
5498
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5411
5499
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5412
5500
  temporaryCounter += 1;
@@ -5441,8 +5529,18 @@ function completeLeaseRecovery(lease) {
5441
5529
  }
5442
5530
 
5443
5531
  // src/indexer/index.ts
5444
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
5445
- var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5532
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "swift", "php", "apex", "zig", "gdscript", "matlab", "bash", "c", "cpp", "metal"]);
5533
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex", "php"]);
5534
+ var CALL_GRAPH_RESOLUTION_VERSION = "4";
5535
+ var PHP_FUNCTION_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5536
+ "function_declaration",
5537
+ "function",
5538
+ "function_definition"
5539
+ ]);
5540
+ var PHP_CLASS_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5541
+ "class_declaration",
5542
+ "class_definition"
5543
+ ]);
5446
5544
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5447
5545
  "function_declaration",
5448
5546
  "function",
@@ -5455,6 +5553,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5455
5553
  "enum_declaration",
5456
5554
  "function_definition",
5457
5555
  "class_definition",
5556
+ "class_specifier",
5557
+ "struct_specifier",
5558
+ "namespace_definition",
5458
5559
  "decorated_definition",
5459
5560
  "method_declaration",
5460
5561
  "type_declaration",
@@ -5470,6 +5571,14 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5470
5571
  "test_declaration",
5471
5572
  "struct_declaration",
5472
5573
  "union_declaration",
5574
+ // Synthetic Swift declarations or declarations specific to tree-sitter-swift.
5575
+ "actor_declaration",
5576
+ "extension_declaration",
5577
+ "protocol_declaration",
5578
+ "protocol_function_declaration",
5579
+ "init_declaration",
5580
+ "deinit_declaration",
5581
+ "subscript_declaration",
5473
5582
  // GDScript declarations whose names participate in the call graph.
5474
5583
  // `function_definition` and `class_definition` are already in the set
5475
5584
  // above (shared with Python/C/Bash and Python, respectively).
@@ -5479,6 +5588,53 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5479
5588
  "const_statement",
5480
5589
  "class_name_statement"
5481
5590
  ]);
5591
+ var C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set(["class_specifier", "struct_specifier"]);
5592
+ function isCompatibleCFamilyCallTarget(language, callType, symbolKind) {
5593
+ if (language !== "c" && language !== "cpp") return true;
5594
+ if (symbolKind === "namespace_definition") return callType === "Import";
5595
+ const isTypeSymbol = C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES.has(symbolKind);
5596
+ if (callType === "Constructor" || callType === "Inherits" || callType === "Implements") {
5597
+ return isTypeSymbol;
5598
+ }
5599
+ return !isTypeSymbol;
5600
+ }
5601
+ var EXECUTABLE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5602
+ "function_declaration",
5603
+ "function",
5604
+ "arrow_function",
5605
+ "method_definition",
5606
+ "function_definition",
5607
+ "method_declaration",
5608
+ "function_item",
5609
+ "protocol_function_declaration",
5610
+ "init_declaration",
5611
+ "deinit_declaration",
5612
+ "subscript_declaration",
5613
+ "constructor_definition",
5614
+ "trigger_declaration",
5615
+ "test_declaration"
5616
+ ]);
5617
+ function findEnclosingSymbol(symbols, line, column) {
5618
+ let best;
5619
+ for (const symbol of symbols) {
5620
+ if (line < symbol.startLine || line > symbol.endLine) continue;
5621
+ if (column !== void 0 && (line === symbol.startLine && column < symbol.startCol || line === symbol.endLine && column >= symbol.endCol)) {
5622
+ continue;
5623
+ }
5624
+ if (!best) {
5625
+ best = symbol;
5626
+ continue;
5627
+ }
5628
+ const span = symbol.endLine - symbol.startLine;
5629
+ const bestSpan = best.endLine - best.startLine;
5630
+ 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);
5631
+ const isMoreSpecificTie = span === bestSpan && symbol.startLine === best.startLine && EXECUTABLE_SYMBOL_CHUNK_TYPES.has(symbol.kind) && !EXECUTABLE_SYMBOL_CHUNK_TYPES.has(best.kind);
5632
+ if (span < bestSpan || span === bestSpan && symbol.startLine > best.startLine || isNarrowerPositionRange || isMoreSpecificTie) {
5633
+ best = symbol;
5634
+ }
5635
+ }
5636
+ return best;
5637
+ }
5482
5638
  function float32ArrayToBuffer(arr) {
5483
5639
  const float32 = new Float32Array(arr);
5484
5640
  return Buffer.from(float32.buffer);
@@ -5563,6 +5719,8 @@ function hasBlameMetadata(metadata) {
5563
5719
  }
5564
5720
  var INDEX_METADATA_VERSION = "1";
5565
5721
  var EMBEDDING_STRATEGY_VERSION = "2";
5722
+ var SWIFT_PARSER_VERSION = "1";
5723
+ var METAL_PARSER_VERSION = "1";
5566
5724
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
5567
5725
  var RANK_HYBRID_CACHE_LIMIT = 256;
5568
5726
  function createPendingChunkStorageText(texts) {
@@ -5885,15 +6043,25 @@ function chunkTypeBoost(chunkType) {
5885
6043
  case "function_declaration":
5886
6044
  case "method":
5887
6045
  case "method_definition":
6046
+ case "method_declaration":
6047
+ case "protocol_function_declaration":
6048
+ case "init_declaration":
6049
+ case "deinit_declaration":
6050
+ case "subscript_declaration":
5888
6051
  case "class":
5889
6052
  case "class_declaration":
6053
+ case "actor_declaration":
6054
+ case "extension_declaration":
5890
6055
  return 0.2;
5891
6056
  case "interface":
5892
6057
  case "type":
5893
6058
  case "enum":
6059
+ case "enum_declaration":
5894
6060
  case "struct":
6061
+ case "struct_declaration":
5895
6062
  case "impl":
5896
6063
  case "trait":
6064
+ case "protocol_declaration":
5897
6065
  case "module":
5898
6066
  return 0.1;
5899
6067
  default:
@@ -6000,11 +6168,21 @@ function isImplementationChunkType(chunkType) {
6000
6168
  "function_declaration",
6001
6169
  "method",
6002
6170
  "method_definition",
6171
+ "method_declaration",
6172
+ "protocol_function_declaration",
6173
+ "init_declaration",
6174
+ "deinit_declaration",
6175
+ "subscript_declaration",
6003
6176
  "class",
6004
6177
  "class_declaration",
6178
+ "actor_declaration",
6179
+ "extension_declaration",
6005
6180
  "interface",
6181
+ "protocol_declaration",
6006
6182
  "type",
6007
6183
  "enum",
6184
+ "enum_declaration",
6185
+ "struct_declaration",
6008
6186
  "module"
6009
6187
  ].includes(chunkType);
6010
6188
  }
@@ -7007,6 +7185,29 @@ var Indexer = class {
7007
7185
  const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7008
7186
  return `index.forceReembed.${projectHash}`;
7009
7187
  }
7188
+ getCallGraphResolutionMetadataKey() {
7189
+ if (this.config.scope !== "global") {
7190
+ return "index.callGraphResolutionVersion";
7191
+ }
7192
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7193
+ return `index.callGraphResolutionVersion.${projectHash}`;
7194
+ }
7195
+ getSwiftParserVersionMetadataKey() {
7196
+ const key = "index.parser.swiftVersion";
7197
+ if (this.config.scope !== "global") {
7198
+ return key;
7199
+ }
7200
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7201
+ return `${key}.${projectHash}`;
7202
+ }
7203
+ getMetalParserVersionMetadataKey() {
7204
+ const key = "index.parser.metalVersion";
7205
+ if (this.config.scope !== "global") {
7206
+ return key;
7207
+ }
7208
+ const projectHash = hashContent(path12.resolve(this.projectRoot)).slice(0, 16);
7209
+ return `${key}.${projectHash}`;
7210
+ }
7010
7211
  hasProjectForceReembedPending() {
7011
7212
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
7012
7213
  }
@@ -8167,6 +8368,7 @@ var Indexer = class {
8167
8368
  this.database.setMetadata("index.embeddingProvider", provider.provider);
8168
8369
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
8169
8370
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8371
+ this.database.setMetadata(this.getCallGraphResolutionMetadataKey(), CALL_GRAPH_RESOLUTION_VERSION);
8170
8372
  if (this.config.scope === "global") {
8171
8373
  if (completeProjectEmbeddingStrategyReset) {
8172
8374
  this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
@@ -8354,6 +8556,20 @@ var Indexer = class {
8354
8556
  totalChunks: 0
8355
8557
  });
8356
8558
  this.loadFileHashCache();
8559
+ const swiftParserMetadataKey = this.getSwiftParserVersionMetadataKey();
8560
+ const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
8561
+ const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
8562
+ const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
8563
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
8564
+ (filePath) => path12.extname(filePath).toLowerCase() === ".swift"
8565
+ )) {
8566
+ this.logger.info("Reindexing cached Swift files for parser support");
8567
+ }
8568
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some(
8569
+ (filePath) => path12.extname(filePath).toLowerCase() === ".metal"
8570
+ )) {
8571
+ this.logger.info("Reindexing cached Metal files for parser support");
8572
+ }
8357
8573
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
8358
8574
  const { files, skipped } = await collectFiles(
8359
8575
  this.projectRoot,
@@ -8373,10 +8589,17 @@ var Indexer = class {
8373
8589
  const changedFiles = [];
8374
8590
  const unchangedFilePaths = /* @__PURE__ */ new Set();
8375
8591
  const currentFileHashes = /* @__PURE__ */ new Map();
8592
+ const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
8376
8593
  for (const f of files) {
8377
8594
  const currentHash = hashFile(f.path);
8378
8595
  currentFileHashes.set(f.path, currentHash);
8379
- if (this.fileHashCache.get(f.path) === currentHash) {
8596
+ const cachedHashMatches = this.fileHashCache.get(f.path) === currentHash;
8597
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(f.path).some(
8598
+ (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
8599
+ );
8600
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path12.extname(f.path).toLowerCase() === ".swift";
8601
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path12.extname(f.path).toLowerCase() === ".metal";
8602
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
8380
8603
  unchangedFilePaths.add(f.path);
8381
8604
  this.logger.recordCacheHit();
8382
8605
  } else {
@@ -8578,16 +8801,28 @@ var Indexer = class {
8578
8801
  const fileSymbols = [];
8579
8802
  for (const chunk of parsed.chunks) {
8580
8803
  if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
8581
- const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
8804
+ const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
8805
+ (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
8806
+ ) : void 0;
8807
+ if (existingMetalSymbol) {
8808
+ existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
8809
+ existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
8810
+ existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
8811
+ existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
8812
+ continue;
8813
+ }
8814
+ const symbolId = `sym_${hashContent(
8815
+ parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0)
8816
+ ).slice(0, 16)}`;
8582
8817
  const symbol = {
8583
8818
  id: symbolId,
8584
8819
  filePath: parsed.path,
8585
8820
  name: chunk.name,
8586
8821
  kind: chunk.chunkType,
8587
8822
  startLine: chunk.startLine,
8588
- startCol: 0,
8823
+ startCol: chunk.startCol ?? 0,
8589
8824
  endLine: chunk.endLine,
8590
- endCol: 0,
8825
+ endCol: chunk.endCol ?? 0,
8591
8826
  language: chunk.language
8592
8827
  };
8593
8828
  fileSymbols.push(symbol);
@@ -8612,8 +8847,10 @@ var Indexer = class {
8612
8847
  if (callSites.length === 0) continue;
8613
8848
  const edges = [];
8614
8849
  for (const site of callSites) {
8615
- const enclosingSymbol = fileSymbols.find(
8616
- (sym) => site.line >= sym.startLine && site.line <= sym.endLine
8850
+ const enclosingSymbol = findEnclosingSymbol(
8851
+ fileSymbols,
8852
+ site.line,
8853
+ site.column
8617
8854
  );
8618
8855
  if (!enclosingSymbol) continue;
8619
8856
  const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
@@ -8632,7 +8869,21 @@ var Indexer = class {
8632
8869
  if (edges.length > 0) {
8633
8870
  database.upsertCallEdgesBatch(edges);
8634
8871
  for (const edge of edges) {
8635
- const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8872
+ let candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8873
+ if (fileLanguage === "php" && candidates) {
8874
+ if (edge.callType === "Constructor") {
8875
+ candidates = candidates.filter(
8876
+ (candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8877
+ );
8878
+ } else if (edge.callType === "Call") {
8879
+ candidates = candidates.filter(
8880
+ (candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8881
+ );
8882
+ }
8883
+ }
8884
+ candidates = candidates?.filter(
8885
+ (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, edge.callType, symbol.kind)
8886
+ );
8636
8887
  if (candidates && candidates.length === 1) {
8637
8888
  database.resolveCallEdge(edge.id, candidates[0].id);
8638
8889
  }
@@ -8687,6 +8938,8 @@ var Indexer = class {
8687
8938
  this.saveFileHashCache();
8688
8939
  this.saveFailedBatches([]);
8689
8940
  }
8941
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8942
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8690
8943
  this.saveIndexMetadata(configuredProviderInfo);
8691
8944
  this.indexCompatibility = { compatible: true };
8692
8945
  stats.durationMs = Date.now() - startTime;
@@ -8714,6 +8967,8 @@ var Indexer = class {
8714
8967
  this.saveFileHashCache();
8715
8968
  this.saveFailedBatches([]);
8716
8969
  }
8970
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8971
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8717
8972
  this.saveIndexMetadata(configuredProviderInfo);
8718
8973
  this.indexCompatibility = { compatible: true };
8719
8974
  stats.durationMs = Date.now() - startTime;
@@ -8984,6 +9239,8 @@ var Indexer = class {
8984
9239
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
8985
9240
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8986
9241
  }
9242
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
9243
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8987
9244
  this.saveIndexMetadata(configuredProviderInfo);
8988
9245
  this.indexCompatibility = { compatible: true };
8989
9246
  this.logger.recordIndexingEnd();
@@ -10094,251 +10351,45 @@ var Indexer = class {
10094
10351
  import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
10095
10352
  import * as path17 from "path";
10096
10353
 
10097
- // src/config/merger.ts
10098
- import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
10099
- import * as path14 from "path";
10100
-
10101
- // src/config/rebase.ts
10354
+ // src/tools/knowledge-base-paths.ts
10102
10355
  import * as path13 from "path";
10103
- function isWithinRoot(rootDir, targetPath) {
10104
- const relativePath = path13.relative(rootDir, targetPath);
10105
- return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
10106
- }
10107
- function rebasePathEntries(values, fromDir, toDir) {
10108
- if (!Array.isArray(values)) {
10109
- return [];
10110
- }
10111
- return values.filter((value) => typeof value === "string").map((value) => {
10112
- const trimmed = value.trim();
10113
- if (!trimmed || path13.isAbsolute(trimmed)) {
10114
- return trimmed;
10115
- }
10116
- return normalizePathSeparators(path13.normalize(path13.relative(toDir, path13.resolve(fromDir, trimmed))));
10117
- }).filter(Boolean);
10118
- }
10119
- function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
10120
- if (!Array.isArray(values)) {
10121
- return [];
10356
+ function resolveConfigPathValue(value, baseDir) {
10357
+ const trimmed = value.trim();
10358
+ if (!trimmed) {
10359
+ return trimmed;
10122
10360
  }
10123
- return values.filter((value) => typeof value === "string").map((value) => {
10124
- const trimmed = value.trim();
10125
- if (!trimmed) {
10126
- return trimmed;
10127
- }
10128
- if (path13.isAbsolute(trimmed)) {
10129
- if (isWithinRoot(sourceRoot, trimmed)) {
10130
- return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
10131
- }
10132
- return path13.normalize(trimmed);
10133
- }
10134
- const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
10135
- if (isWithinRoot(sourceRoot, resolvedFromSource)) {
10136
- return normalizePathSeparators(path13.normalize(trimmed));
10137
- }
10138
- return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
10139
- }).filter(Boolean);
10361
+ const absolutePath = path13.isAbsolute(trimmed) ? trimmed : path13.resolve(baseDir, trimmed);
10362
+ return path13.normalize(absolutePath);
10140
10363
  }
10141
10364
 
10142
- // src/config/merger.ts
10143
- var PROJECT_OVERRIDE_KEYS = [
10144
- "embeddingProvider",
10145
- "customProvider",
10146
- "embeddingModel",
10147
- "reranker",
10148
- "include",
10149
- "exclude",
10150
- "indexing",
10151
- "search",
10152
- "debug",
10153
- "scope"
10154
- ];
10155
- var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
10156
- function isRecord(value) {
10157
- return typeof value === "object" && value !== null && !Array.isArray(value);
10158
- }
10159
- function isStringArray2(value) {
10160
- return Array.isArray(value) && value.every((item) => typeof item === "string");
10161
- }
10162
- function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
10163
- if (key in normalizedProjectConfig) {
10164
- merged[key] = normalizedProjectConfig[key];
10165
- return;
10166
- }
10167
- if (key in globalConfig) {
10168
- merged[key] = globalConfig[key];
10365
+ // src/tools/utils.ts
10366
+ import { get_encoding } from "tiktoken";
10367
+ var MIN_CONTEXT_PACK_TOKEN_BUDGET = 128;
10368
+ var MAX_CONTEXT_PACK_TOKEN_BUDGET = 4e3;
10369
+ var DEFAULT_CONTEXT_PACK_TOKEN_BUDGET = 1200;
10370
+ var CONTEXT_TOKENIZER = get_encoding("cl100k_base");
10371
+ function clampContextPackTokenBudget(tokenBudget) {
10372
+ if (tokenBudget === void 0 || !Number.isFinite(tokenBudget)) {
10373
+ return DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
10169
10374
  }
10375
+ return Math.min(
10376
+ MAX_CONTEXT_PACK_TOKEN_BUDGET,
10377
+ Math.max(MIN_CONTEXT_PACK_TOKEN_BUDGET, Math.floor(tokenBudget))
10378
+ );
10170
10379
  }
10171
- function mergeUniqueStringArray(values) {
10172
- return [...new Set(values.map((value) => String(value).trim()))];
10380
+ function countContextTokens(text) {
10381
+ return CONTEXT_TOKENIZER.encode(text).length;
10173
10382
  }
10174
- function normalizeKnowledgeBasePath(value) {
10175
- let normalized = path14.normalize(String(value).trim());
10176
- const root = path14.parse(normalized).root;
10177
- while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10178
- normalized = normalized.slice(0, -1);
10179
- }
10180
- return normalized;
10181
- }
10182
- function mergeKnowledgeBasePaths(values) {
10183
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
10184
- }
10185
- function validateConfigLayerShape(rawConfig, filePath) {
10186
- if (!isRecord(rawConfig)) {
10187
- throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
10188
- }
10189
- if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
10190
- throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
10191
- }
10192
- if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
10193
- throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
10194
- }
10195
- if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
10196
- throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
10197
- }
10198
- if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
10199
- throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
10200
- }
10201
- for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
10202
- const value = rawConfig[section];
10203
- if (value !== void 0 && !isRecord(value)) {
10204
- throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
10205
- }
10206
- }
10207
- return rawConfig;
10208
- }
10209
- function loadJsonFile(filePath) {
10210
- if (!existsSync8(filePath)) {
10211
- return null;
10212
- }
10213
- try {
10214
- const content = readFileSync8(filePath, "utf-8");
10215
- return validateConfigLayerShape(JSON.parse(content), filePath);
10216
- } catch (error) {
10217
- if (error instanceof Error && error.message.startsWith("Config file ")) {
10218
- throw error;
10219
- }
10220
- const message = error instanceof Error ? error.message : String(error);
10221
- throw new Error(`Failed to load config file ${filePath}: ${message}`);
10222
- }
10223
- }
10224
- function loadConfigFile(filePath) {
10225
- return loadJsonFile(filePath);
10226
- }
10227
- function materializeLocalProjectConfig(projectRoot, config, host = "opencode") {
10228
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot, host);
10229
- mkdirSync4(path14.dirname(localConfigPath), { recursive: true });
10230
- writeFileSync4(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
10231
- return localConfigPath;
10232
- }
10233
- function loadProjectConfigLayer(projectRoot, host = "opencode") {
10234
- const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10235
- const projectConfig = loadJsonFile(projectConfigPath);
10236
- if (!projectConfig) {
10237
- return {};
10238
- }
10239
- const normalizedConfig = { ...projectConfig };
10240
- const projectConfigBaseDir = path14.dirname(path14.dirname(projectConfigPath));
10241
- if (Array.isArray(normalizedConfig.knowledgeBases)) {
10242
- normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10243
- normalizedConfig.knowledgeBases,
10244
- projectConfigBaseDir,
10245
- projectRoot
10246
- );
10247
- }
10248
- return normalizedConfig;
10249
- }
10250
- function loadMergedConfig(projectRoot, host = "opencode") {
10251
- const globalConfigPath = resolveGlobalConfigPath(host);
10252
- const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10253
- let globalConfig = null;
10254
- let globalConfigError = null;
10255
- try {
10256
- globalConfig = loadJsonFile(globalConfigPath);
10257
- } catch (error) {
10258
- globalConfigError = error instanceof Error ? error : new Error(String(error));
10259
- }
10260
- const projectConfig = loadJsonFile(projectConfigPath);
10261
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot, host);
10262
- if (globalConfigError) {
10263
- if (!projectConfig) {
10264
- throw globalConfigError;
10265
- }
10266
- globalConfig = null;
10267
- }
10268
- if (!globalConfig && !projectConfig) {
10269
- return {};
10270
- }
10271
- if (!projectConfig && globalConfig) {
10272
- return globalConfig;
10273
- }
10274
- if (!globalConfig && projectConfig) {
10275
- return normalizedProjectConfig;
10276
- }
10277
- if (!globalConfig || !projectConfig) {
10278
- return globalConfig ?? normalizedProjectConfig;
10279
- }
10280
- const merged = { ...globalConfig };
10281
- for (const key of PROJECT_OVERRIDE_KEYS) {
10282
- applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
10283
- }
10284
- if (projectConfig) {
10285
- for (const key of Object.keys(projectConfig)) {
10286
- if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
10287
- continue;
10288
- }
10289
- merged[key] = normalizedProjectConfig[key];
10290
- }
10291
- }
10292
- const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
10293
- const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
10294
- const allKbs = [...globalKbs, ...projectKbs];
10295
- merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
10296
- const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
10297
- const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
10298
- const allAdditional = [...globalAdditional, ...projectAdditional];
10299
- merged.additionalInclude = mergeUniqueStringArray(allAdditional);
10300
- return merged;
10301
- }
10302
-
10303
- // src/tools/knowledge-base-paths.ts
10304
- import * as path15 from "path";
10305
- function resolveConfigPathValue(value, baseDir) {
10306
- const trimmed = value.trim();
10307
- if (!trimmed) {
10308
- return trimmed;
10309
- }
10310
- const absolutePath = path15.isAbsolute(trimmed) ? trimmed : path15.resolve(baseDir, trimmed);
10311
- return path15.normalize(absolutePath);
10312
- }
10313
-
10314
- // src/tools/utils.ts
10315
- import { get_encoding } from "tiktoken";
10316
- var MIN_CONTEXT_PACK_TOKEN_BUDGET = 128;
10317
- var MAX_CONTEXT_PACK_TOKEN_BUDGET = 4e3;
10318
- var DEFAULT_CONTEXT_PACK_TOKEN_BUDGET = 1200;
10319
- var CONTEXT_TOKENIZER = get_encoding("cl100k_base");
10320
- function clampContextPackTokenBudget(tokenBudget) {
10321
- if (tokenBudget === void 0 || !Number.isFinite(tokenBudget)) {
10322
- return DEFAULT_CONTEXT_PACK_TOKEN_BUDGET;
10323
- }
10324
- return Math.min(
10325
- MAX_CONTEXT_PACK_TOKEN_BUDGET,
10326
- Math.max(MIN_CONTEXT_PACK_TOKEN_BUDGET, Math.floor(tokenBudget))
10327
- );
10328
- }
10329
- function countContextTokens(text) {
10330
- return CONTEXT_TOKENIZER.encode(text).length;
10331
- }
10332
- function fitTextToContextBudget(text, tokenBudget) {
10333
- const normalizedBudget = clampContextPackTokenBudget(tokenBudget);
10334
- const tokenEstimate = countContextTokens(text);
10335
- if (tokenEstimate <= normalizedBudget) {
10336
- return {
10337
- text,
10338
- tokenBudget: normalizedBudget,
10339
- tokenEstimate,
10340
- truncated: false
10341
- };
10383
+ function fitTextToContextBudget(text, tokenBudget) {
10384
+ const normalizedBudget = clampContextPackTokenBudget(tokenBudget);
10385
+ const tokenEstimate = countContextTokens(text);
10386
+ if (tokenEstimate <= normalizedBudget) {
10387
+ return {
10388
+ text,
10389
+ tokenBudget: normalizedBudget,
10390
+ tokenEstimate,
10391
+ truncated: false
10392
+ };
10342
10393
  }
10343
10394
  const suffix = "\n...[truncated to context token budget]";
10344
10395
  const codePoints = Array.from(text);
@@ -10718,8 +10769,210 @@ ${truncateContent(r.content)}
10718
10769
  }
10719
10770
 
10720
10771
  // src/tools/config-state.ts
10721
- import { existsSync as existsSync9, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5 } from "fs";
10772
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10722
10773
  import * as path16 from "path";
10774
+
10775
+ // src/config/merger.ts
10776
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
10777
+ import * as path15 from "path";
10778
+
10779
+ // src/config/rebase.ts
10780
+ import * as path14 from "path";
10781
+ function isWithinRoot(rootDir, targetPath) {
10782
+ const relativePath = path14.relative(rootDir, targetPath);
10783
+ return relativePath === "" || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath);
10784
+ }
10785
+ function rebasePathEntries(values, fromDir, toDir) {
10786
+ if (!Array.isArray(values)) {
10787
+ return [];
10788
+ }
10789
+ return values.filter((value) => typeof value === "string").map((value) => {
10790
+ const trimmed = value.trim();
10791
+ if (!trimmed || path14.isAbsolute(trimmed)) {
10792
+ return trimmed;
10793
+ }
10794
+ return normalizePathSeparators(path14.normalize(path14.relative(toDir, path14.resolve(fromDir, trimmed))));
10795
+ }).filter(Boolean);
10796
+ }
10797
+ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
10798
+ if (!Array.isArray(values)) {
10799
+ return [];
10800
+ }
10801
+ return values.filter((value) => typeof value === "string").map((value) => {
10802
+ const trimmed = value.trim();
10803
+ if (!trimmed) {
10804
+ return trimmed;
10805
+ }
10806
+ if (path14.isAbsolute(trimmed)) {
10807
+ if (isWithinRoot(sourceRoot, trimmed)) {
10808
+ return normalizePathSeparators(path14.normalize(path14.relative(sourceRoot, trimmed) || "."));
10809
+ }
10810
+ return path14.normalize(trimmed);
10811
+ }
10812
+ const resolvedFromSource = path14.resolve(sourceRoot, trimmed);
10813
+ if (isWithinRoot(sourceRoot, resolvedFromSource)) {
10814
+ return normalizePathSeparators(path14.normalize(trimmed));
10815
+ }
10816
+ return normalizePathSeparators(path14.normalize(path14.relative(targetRoot, resolvedFromSource)));
10817
+ }).filter(Boolean);
10818
+ }
10819
+
10820
+ // src/config/merger.ts
10821
+ var PROJECT_OVERRIDE_KEYS = [
10822
+ "embeddingProvider",
10823
+ "customProvider",
10824
+ "embeddingModel",
10825
+ "reranker",
10826
+ "include",
10827
+ "exclude",
10828
+ "indexing",
10829
+ "search",
10830
+ "debug",
10831
+ "scope"
10832
+ ];
10833
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
10834
+ function isRecord(value) {
10835
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10836
+ }
10837
+ function isStringArray2(value) {
10838
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
10839
+ }
10840
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
10841
+ if (key in normalizedProjectConfig) {
10842
+ merged[key] = normalizedProjectConfig[key];
10843
+ return;
10844
+ }
10845
+ if (key in globalConfig) {
10846
+ merged[key] = globalConfig[key];
10847
+ }
10848
+ }
10849
+ function mergeUniqueStringArray(values) {
10850
+ return [...new Set(values.map((value) => String(value).trim()))];
10851
+ }
10852
+ function normalizeKnowledgeBasePath(value) {
10853
+ let normalized = path15.normalize(String(value).trim());
10854
+ const root = path15.parse(normalized).root;
10855
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10856
+ normalized = normalized.slice(0, -1);
10857
+ }
10858
+ return normalized;
10859
+ }
10860
+ function mergeKnowledgeBasePaths(values) {
10861
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
10862
+ }
10863
+ function validateConfigLayerShape(rawConfig, filePath) {
10864
+ if (!isRecord(rawConfig)) {
10865
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
10866
+ }
10867
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
10868
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
10869
+ }
10870
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
10871
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
10872
+ }
10873
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
10874
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
10875
+ }
10876
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
10877
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
10878
+ }
10879
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
10880
+ const value = rawConfig[section];
10881
+ if (value !== void 0 && !isRecord(value)) {
10882
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
10883
+ }
10884
+ }
10885
+ return rawConfig;
10886
+ }
10887
+ function loadJsonFile(filePath) {
10888
+ if (!existsSync8(filePath)) {
10889
+ return null;
10890
+ }
10891
+ try {
10892
+ const content = readFileSync8(filePath, "utf-8");
10893
+ return validateConfigLayerShape(JSON.parse(content), filePath);
10894
+ } catch (error) {
10895
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
10896
+ throw error;
10897
+ }
10898
+ const message = error instanceof Error ? error.message : String(error);
10899
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
10900
+ }
10901
+ }
10902
+ function loadConfigFile(filePath) {
10903
+ return loadJsonFile(filePath);
10904
+ }
10905
+ function loadProjectConfigLayer(projectRoot, host = "opencode") {
10906
+ const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10907
+ const projectConfig = loadJsonFile(projectConfigPath);
10908
+ if (!projectConfig) {
10909
+ return {};
10910
+ }
10911
+ const normalizedConfig = { ...projectConfig };
10912
+ const projectConfigBaseDir = path15.dirname(path15.dirname(projectConfigPath));
10913
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
10914
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10915
+ normalizedConfig.knowledgeBases,
10916
+ projectConfigBaseDir,
10917
+ projectRoot
10918
+ );
10919
+ }
10920
+ return normalizedConfig;
10921
+ }
10922
+ function loadMergedConfig(projectRoot, host = "opencode") {
10923
+ const globalConfigPath = resolveGlobalConfigPath(host);
10924
+ const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
10925
+ let globalConfig = null;
10926
+ let globalConfigError = null;
10927
+ try {
10928
+ globalConfig = loadJsonFile(globalConfigPath);
10929
+ } catch (error) {
10930
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
10931
+ }
10932
+ const projectConfig = loadJsonFile(projectConfigPath);
10933
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot, host);
10934
+ if (globalConfigError) {
10935
+ if (!projectConfig) {
10936
+ throw globalConfigError;
10937
+ }
10938
+ globalConfig = null;
10939
+ }
10940
+ if (!globalConfig && !projectConfig) {
10941
+ return {};
10942
+ }
10943
+ if (!projectConfig && globalConfig) {
10944
+ return globalConfig;
10945
+ }
10946
+ if (!globalConfig && projectConfig) {
10947
+ return normalizedProjectConfig;
10948
+ }
10949
+ if (!globalConfig || !projectConfig) {
10950
+ return globalConfig ?? normalizedProjectConfig;
10951
+ }
10952
+ const merged = { ...globalConfig };
10953
+ for (const key of PROJECT_OVERRIDE_KEYS) {
10954
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
10955
+ }
10956
+ if (projectConfig) {
10957
+ for (const key of Object.keys(projectConfig)) {
10958
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
10959
+ continue;
10960
+ }
10961
+ merged[key] = normalizedProjectConfig[key];
10962
+ }
10963
+ }
10964
+ const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
10965
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
10966
+ const allKbs = [...globalKbs, ...projectKbs];
10967
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
10968
+ const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
10969
+ const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
10970
+ const allAdditional = [...globalAdditional, ...projectAdditional];
10971
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
10972
+ return merged;
10973
+ }
10974
+
10975
+ // src/tools/config-state.ts
10723
10976
  function normalizeKnowledgeBasePaths(config, projectRoot) {
10724
10977
  const normalized = { ...config };
10725
10978
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10803,15 +11056,6 @@ function refreshIndexerForDirectory(projectRoot, host = "opencode", config = par
10803
11056
  indexerCache.set(key, indexer);
10804
11057
  configCache.set(key, config);
10805
11058
  }
10806
- function shouldForceLocalizeProjectIndex(projectRoot, host = "opencode") {
10807
- const root = getProjectRoot(projectRoot, host);
10808
- const localIndexPath = path17.join(root, getHostProjectIndexRelativePath(host));
10809
- if (existsSync10(localIndexPath)) {
10810
- return false;
10811
- }
10812
- const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
10813
- return inheritedIndexPath !== null;
10814
- }
10815
11059
  async function searchCodebase(projectRoot, host, query, options = {}) {
10816
11060
  const indexer = getIndexerForProject(projectRoot, host);
10817
11061
  return indexer.search(query, options.limit, {
@@ -10861,9 +11105,7 @@ async function getCallGraphPath(projectRoot, host, from, to, maxDepth) {
10861
11105
  }
10862
11106
  async function runIndexCodebase(projectRoot, host, args, onProgress) {
10863
11107
  const root = getProjectRoot(projectRoot, host);
10864
- const key = getIndexerCacheKey(root, host);
10865
- let indexer = getIndexerForProject(root, host);
10866
- const runtimeConfig = configCache.get(key);
11108
+ const indexer = getIndexerForProject(root, host);
10867
11109
  try {
10868
11110
  if (args.estimateOnly) {
10869
11111
  return { kind: "estimate", estimate: await indexer.estimateCost() };
@@ -10884,19 +11126,7 @@ async function runIndexCodebase(projectRoot, host, args, onProgress) {
10884
11126
  return Promise.resolve();
10885
11127
  });
10886
11128
  };
10887
- let stats;
10888
- if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10889
- const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10890
- stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10891
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10892
- refreshIndexerForDirectory(root, host, runtimeConfig);
10893
- indexer = getIndexerForProject(root, host);
10894
- return runIndex(indexer);
10895
- }, { completeRecoveries: false });
10896
- } else {
10897
- stats = await runIndex(indexer);
10898
- }
10899
- return { kind: "stats", stats };
11129
+ return { kind: "stats", stats: await runIndex(indexer) };
10900
11130
  } catch (error) {
10901
11131
  const busyResult = getIndexBusyResult(error);
10902
11132
  if (!busyResult) throw error;
@@ -11554,7 +11784,7 @@ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingToke
11554
11784
  }
11555
11785
 
11556
11786
  // src/eval/runner-config.ts
11557
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
11787
+ import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "fs";
11558
11788
  import * as os5 from "os";
11559
11789
  import * as path18 from "path";
11560
11790
  function isRecord2(value) {
@@ -11676,8 +11906,8 @@ function ensureLocalEvalProjectConfig(projectRoot, configPath) {
11676
11906
  projectRoot,
11677
11907
  resolvedConfigPath
11678
11908
  );
11679
- mkdirSync6(path18.dirname(localConfigPath), { recursive: true });
11680
- writeFileSync6(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
11909
+ mkdirSync5(path18.dirname(localConfigPath), { recursive: true });
11910
+ writeFileSync5(localConfigPath, JSON.stringify(sourceConfig, null, 2), "utf-8");
11681
11911
  return localConfigPath;
11682
11912
  }
11683
11913
  function loadParsedConfig(projectRoot, configPath) {
@@ -15790,7 +16020,7 @@ async function handleVisualizeCommand(argv, cwd) {
15790
16020
  return 1;
15791
16021
  }
15792
16022
  const outputPath = path26.join(os6.tmpdir(), `call-graph-${Date.now()}.html`);
15793
- writeFileSync7(outputPath, generateVisualizationHtml(vizData), "utf-8");
16023
+ writeFileSync6(outputPath, generateVisualizationHtml(vizData), "utf-8");
15794
16024
  console.log(`Temporal call graph visualization generated: ${outputPath}`);
15795
16025
  console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
15796
16026
  console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);