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.
@@ -668,7 +668,8 @@ var DEFAULT_INCLUDE = [
668
668
  "**/*.{sh,bash,zsh}",
669
669
  "**/*.{txt,html,htm}",
670
670
  "**/*.zig",
671
- "**/*.gd"
671
+ "**/*.gd",
672
+ "**/*.metal"
672
673
  ];
673
674
  var DEFAULT_EXCLUDE = [
674
675
  "**/node_modules/**",
@@ -859,6 +860,9 @@ function isValidProvider(value) {
859
860
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
860
861
  }
861
862
  function isValidModel(value, provider) {
863
+ if (typeof value === "string" && provider === "ollama" && value.trim().length > 0) {
864
+ return true;
865
+ }
862
866
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS[provider]).includes(value);
863
867
  }
864
868
  function isValidScope(value) {
@@ -1058,6 +1062,9 @@ function loadOpenCodeAuth() {
1058
1062
  return {};
1059
1063
  }
1060
1064
  async function detectEmbeddingProvider(preferredProvider, model) {
1065
+ if (preferredProvider === "ollama") {
1066
+ return detectOllamaProvider(model);
1067
+ }
1061
1068
  const credentials = await getProviderCredentials(preferredProvider);
1062
1069
  if (credentials) {
1063
1070
  if (!model) {
@@ -1073,10 +1080,16 @@ async function detectEmbeddingProvider(preferredProvider, model) {
1073
1080
  );
1074
1081
  }
1075
1082
  const providerModels = EMBEDDING_MODELS[preferredProvider];
1083
+ const modelInfo = Object.values(providerModels).find((candidate) => candidate.model === model);
1084
+ if (!modelInfo) {
1085
+ throw new Error(
1086
+ `Model '${model}' is not supported by provider '${preferredProvider}'`
1087
+ );
1088
+ }
1076
1089
  return {
1077
1090
  provider: preferredProvider,
1078
1091
  credentials,
1079
- modelInfo: providerModels[model]
1092
+ modelInfo
1080
1093
  };
1081
1094
  }
1082
1095
  throw new Error(
@@ -1085,6 +1098,13 @@ async function detectEmbeddingProvider(preferredProvider, model) {
1085
1098
  }
1086
1099
  async function tryDetectProvider() {
1087
1100
  for (const provider of autoDetectProviders) {
1101
+ if (provider === "ollama") {
1102
+ const ollamaProvider = await tryDetectOllamaProvider();
1103
+ if (ollamaProvider) {
1104
+ return ollamaProvider;
1105
+ }
1106
+ continue;
1107
+ }
1088
1108
  const credentials = await getProviderCredentials(provider);
1089
1109
  if (credentials) {
1090
1110
  return {
@@ -1152,32 +1172,105 @@ function getGoogleCredentials() {
1152
1172
  }
1153
1173
  return null;
1154
1174
  }
1155
- async function getOllamaCredentials() {
1156
- const baseUrl = process.env.OLLAMA_HOST || "http://localhost:11434";
1175
+ async function fetchOllama(url, init) {
1176
+ const controller = new AbortController();
1177
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
1157
1178
  try {
1158
- const controller = new AbortController();
1159
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
1160
- const response = await fetch(`${baseUrl}/api/tags`, {
1161
- signal: controller.signal
1162
- });
1179
+ return await fetch(url, { ...init, signal: controller.signal });
1180
+ } finally {
1163
1181
  clearTimeout(timeoutId);
1182
+ }
1183
+ }
1184
+ async function getOllamaCredentials() {
1185
+ const baseUrl = (process.env.OLLAMA_HOST || "http://localhost:11434").replace(/\/+$/, "");
1186
+ try {
1187
+ const response = await fetchOllama(`${baseUrl}/api/tags`);
1164
1188
  if (response.ok) {
1165
- const data = await response.json();
1166
- const hasEmbeddingModel = data.models?.some(
1167
- (m) => m.name.includes("nomic-embed") || m.name.includes("mxbai-embed") || m.name.includes("all-minilm")
1168
- );
1169
- if (hasEmbeddingModel) {
1170
- return {
1171
- provider: "ollama",
1172
- baseUrl
1173
- };
1174
- }
1189
+ return {
1190
+ provider: "ollama",
1191
+ baseUrl
1192
+ };
1175
1193
  }
1176
1194
  } catch {
1177
1195
  return null;
1178
1196
  }
1179
1197
  return null;
1180
1198
  }
1199
+ function findCatalogOllamaModel(model) {
1200
+ const stableName = model.endsWith(":latest") ? model.slice(0, -":latest".length) : model;
1201
+ return Object.values(EMBEDDING_MODELS.ollama).find((candidate) => candidate.model === stableName) ?? null;
1202
+ }
1203
+ function getPositiveIntegerMetadata(modelInfo, suffix) {
1204
+ const values = Object.entries(modelInfo).filter(([key]) => key.endsWith(suffix)).map(([, value]) => value).filter((value) => typeof value === "number" && Number.isInteger(value) && value > 0);
1205
+ return values.length === 1 ? values[0] : null;
1206
+ }
1207
+ async function fetchOllamaModelInfo(credentials, model) {
1208
+ const response = await fetchOllama(`${credentials.baseUrl}/api/show`, {
1209
+ method: "POST",
1210
+ headers: { "Content-Type": "application/json" },
1211
+ body: JSON.stringify({ model })
1212
+ });
1213
+ if (!response.ok) {
1214
+ return null;
1215
+ }
1216
+ const data = await response.json();
1217
+ if (!data.capabilities?.includes("embedding") || !data.model_info) {
1218
+ return null;
1219
+ }
1220
+ const dimensions = getPositiveIntegerMetadata(data.model_info, ".embedding_length");
1221
+ const maxTokens = getPositiveIntegerMetadata(data.model_info, ".context_length");
1222
+ if (!dimensions || !maxTokens) {
1223
+ return null;
1224
+ }
1225
+ return {
1226
+ provider: "ollama",
1227
+ model,
1228
+ dimensions,
1229
+ maxTokens,
1230
+ costPer1MTokens: 0
1231
+ };
1232
+ }
1233
+ async function listOllamaModels(credentials) {
1234
+ const response = await fetchOllama(`${credentials.baseUrl}/api/tags`);
1235
+ if (!response.ok) {
1236
+ return [];
1237
+ }
1238
+ const data = await response.json();
1239
+ return [...new Set((data.models ?? []).map((entry) => entry.name ?? entry.model).filter((name) => typeof name === "string" && name.trim().length > 0))];
1240
+ }
1241
+ async function detectOllamaProvider(model) {
1242
+ const credentials = await getOllamaCredentials();
1243
+ if (!credentials) {
1244
+ throw new Error("Preferred provider 'ollama' is not configured or authenticated");
1245
+ }
1246
+ const requestedModel = model?.trim();
1247
+ if (requestedModel) {
1248
+ const catalogModel = findCatalogOllamaModel(requestedModel);
1249
+ if (catalogModel) {
1250
+ return { provider: "ollama", credentials, modelInfo: catalogModel };
1251
+ }
1252
+ }
1253
+ const candidates = requestedModel ? [requestedModel] : await listOllamaModels(credentials);
1254
+ for (const candidate of candidates) {
1255
+ const modelInfo = await fetchOllamaModelInfo(credentials, candidate);
1256
+ if (modelInfo) {
1257
+ return {
1258
+ provider: "ollama",
1259
+ credentials,
1260
+ modelInfo: findCatalogOllamaModel(candidate) ?? modelInfo
1261
+ };
1262
+ }
1263
+ }
1264
+ const detail = model ? `Model '${model}' is not installed or is not embedding-capable` : "No installed embedding-capable Ollama model was found";
1265
+ throw new Error(detail);
1266
+ }
1267
+ async function tryDetectOllamaProvider() {
1268
+ try {
1269
+ return await detectOllamaProvider();
1270
+ } catch {
1271
+ return null;
1272
+ }
1273
+ }
1181
1274
  function getProviderDisplayName(provider) {
1182
1275
  switch (provider) {
1183
1276
  case "github-copilot":
@@ -1330,10 +1423,6 @@ function formatPrImpact(result) {
1330
1423
  import { existsSync as existsSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
1331
1424
  import * as path16 from "path";
1332
1425
 
1333
- // src/config/merger.ts
1334
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync } from "fs";
1335
- import * as path7 from "path";
1336
-
1337
1426
  // src/config/paths.ts
1338
1427
  import { existsSync as existsSync4 } from "fs";
1339
1428
  import * as os2 from "os";
@@ -1506,16 +1595,6 @@ function resolveWorktreeFallbackPath(projectRoot3, relativePath) {
1506
1595
  const fallbackPath = path4.join(mainRepoRoot, relativePath);
1507
1596
  return existsSync4(fallbackPath) ? fallbackPath : null;
1508
1597
  }
1509
- function resolveWorktreeFallbackProjectIndexPath(projectRoot3, host) {
1510
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1511
- if (inheritedHostPath) {
1512
- return inheritedHostPath;
1513
- }
1514
- if (host !== "opencode") {
1515
- return resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1516
- }
1517
- return null;
1518
- }
1519
1598
  function getHostProjectConfigRelativePath(host) {
1520
1599
  return getProjectConfigRelativePath(host);
1521
1600
  }
@@ -1620,6 +1699,9 @@ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1620
1699
  if (hasHostProjectConfig(projectRoot3, host)) {
1621
1700
  return localIndexPath;
1622
1701
  }
1702
+ if (resolveWorktreeMainRepoRoot(projectRoot3)) {
1703
+ return localIndexPath;
1704
+ }
1623
1705
  const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1624
1706
  if (hostFallback) {
1625
1707
  return hostFallback;
@@ -1633,210 +1715,9 @@ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1633
1715
  return localIndexPath;
1634
1716
  }
1635
1717
 
1636
- // src/config/rebase.ts
1637
- import * as path6 from "path";
1638
-
1639
- // src/utils/paths.ts
1640
- import * as path5 from "path";
1641
- function normalizePathSeparators(value) {
1642
- return value.replace(/\\/g, "/");
1643
- }
1644
- function isHiddenPathSegment(part) {
1645
- return part.startsWith(".") && part !== "." && part !== "..";
1646
- }
1647
- function isBuildPathSegment(part) {
1648
- return part.toLowerCase().includes("build");
1649
- }
1650
-
1651
- // src/config/rebase.ts
1652
- function isWithinRoot(rootDir, targetPath) {
1653
- const relativePath = path6.relative(rootDir, targetPath);
1654
- return relativePath === "" || !relativePath.startsWith("..") && !path6.isAbsolute(relativePath);
1655
- }
1656
- function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1657
- if (!Array.isArray(values)) {
1658
- return [];
1659
- }
1660
- return values.filter((value) => typeof value === "string").map((value) => {
1661
- const trimmed = value.trim();
1662
- if (!trimmed) {
1663
- return trimmed;
1664
- }
1665
- if (path6.isAbsolute(trimmed)) {
1666
- if (isWithinRoot(sourceRoot, trimmed)) {
1667
- return normalizePathSeparators(path6.normalize(path6.relative(sourceRoot, trimmed) || "."));
1668
- }
1669
- return path6.normalize(trimmed);
1670
- }
1671
- const resolvedFromSource = path6.resolve(sourceRoot, trimmed);
1672
- if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1673
- return normalizePathSeparators(path6.normalize(trimmed));
1674
- }
1675
- return normalizePathSeparators(path6.normalize(path6.relative(targetRoot, resolvedFromSource)));
1676
- }).filter(Boolean);
1677
- }
1678
-
1679
- // src/config/merger.ts
1680
- var PROJECT_OVERRIDE_KEYS = [
1681
- "embeddingProvider",
1682
- "customProvider",
1683
- "embeddingModel",
1684
- "reranker",
1685
- "include",
1686
- "exclude",
1687
- "indexing",
1688
- "search",
1689
- "debug",
1690
- "scope"
1691
- ];
1692
- var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1693
- function isRecord(value) {
1694
- return typeof value === "object" && value !== null && !Array.isArray(value);
1695
- }
1696
- function isStringArray2(value) {
1697
- return Array.isArray(value) && value.every((item) => typeof item === "string");
1698
- }
1699
- function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1700
- if (key in normalizedProjectConfig) {
1701
- merged[key] = normalizedProjectConfig[key];
1702
- return;
1703
- }
1704
- if (key in globalConfig) {
1705
- merged[key] = globalConfig[key];
1706
- }
1707
- }
1708
- function mergeUniqueStringArray(values) {
1709
- return [...new Set(values.map((value) => String(value).trim()))];
1710
- }
1711
- function normalizeKnowledgeBasePath(value) {
1712
- let normalized = path7.normalize(String(value).trim());
1713
- const root = path7.parse(normalized).root;
1714
- while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1715
- normalized = normalized.slice(0, -1);
1716
- }
1717
- return normalized;
1718
- }
1719
- function mergeKnowledgeBasePaths(values) {
1720
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1721
- }
1722
- function validateConfigLayerShape(rawConfig, filePath) {
1723
- if (!isRecord(rawConfig)) {
1724
- throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1725
- }
1726
- if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1727
- throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1728
- }
1729
- if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1730
- throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1731
- }
1732
- if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1733
- throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1734
- }
1735
- if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1736
- throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1737
- }
1738
- for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1739
- const value = rawConfig[section];
1740
- if (value !== void 0 && !isRecord(value)) {
1741
- throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1742
- }
1743
- }
1744
- return rawConfig;
1745
- }
1746
- function loadJsonFile(filePath) {
1747
- if (!existsSync5(filePath)) {
1748
- return null;
1749
- }
1750
- try {
1751
- const content = readFileSync4(filePath, "utf-8");
1752
- return validateConfigLayerShape(JSON.parse(content), filePath);
1753
- } catch (error) {
1754
- if (error instanceof Error && error.message.startsWith("Config file ")) {
1755
- throw error;
1756
- }
1757
- const message = error instanceof Error ? error.message : String(error);
1758
- throw new Error(`Failed to load config file ${filePath}: ${message}`);
1759
- }
1760
- }
1761
- function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
1762
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
1763
- mkdirSync(path7.dirname(localConfigPath), { recursive: true });
1764
- writeFileSync(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1765
- return localConfigPath;
1766
- }
1767
- function loadProjectConfigLayer(projectRoot3, host = "opencode") {
1768
- const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1769
- const projectConfig = loadJsonFile(projectConfigPath);
1770
- if (!projectConfig) {
1771
- return {};
1772
- }
1773
- const normalizedConfig = { ...projectConfig };
1774
- const projectConfigBaseDir = path7.dirname(path7.dirname(projectConfigPath));
1775
- if (Array.isArray(normalizedConfig.knowledgeBases)) {
1776
- normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1777
- normalizedConfig.knowledgeBases,
1778
- projectConfigBaseDir,
1779
- projectRoot3
1780
- );
1781
- }
1782
- return normalizedConfig;
1783
- }
1784
- function loadMergedConfig(projectRoot3, host = "opencode") {
1785
- const globalConfigPath = resolveGlobalConfigPath(host);
1786
- const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1787
- let globalConfig = null;
1788
- let globalConfigError = null;
1789
- try {
1790
- globalConfig = loadJsonFile(globalConfigPath);
1791
- } catch (error) {
1792
- globalConfigError = error instanceof Error ? error : new Error(String(error));
1793
- }
1794
- const projectConfig = loadJsonFile(projectConfigPath);
1795
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
1796
- if (globalConfigError) {
1797
- if (!projectConfig) {
1798
- throw globalConfigError;
1799
- }
1800
- globalConfig = null;
1801
- }
1802
- if (!globalConfig && !projectConfig) {
1803
- return {};
1804
- }
1805
- if (!projectConfig && globalConfig) {
1806
- return globalConfig;
1807
- }
1808
- if (!globalConfig && projectConfig) {
1809
- return normalizedProjectConfig;
1810
- }
1811
- if (!globalConfig || !projectConfig) {
1812
- return globalConfig ?? normalizedProjectConfig;
1813
- }
1814
- const merged = { ...globalConfig };
1815
- for (const key of PROJECT_OVERRIDE_KEYS) {
1816
- applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1817
- }
1818
- if (projectConfig) {
1819
- for (const key of Object.keys(projectConfig)) {
1820
- if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1821
- continue;
1822
- }
1823
- merged[key] = normalizedProjectConfig[key];
1824
- }
1825
- }
1826
- const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1827
- const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1828
- const allKbs = [...globalKbs, ...projectKbs];
1829
- merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1830
- const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1831
- const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1832
- const allAdditional = [...globalAdditional, ...projectAdditional];
1833
- merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1834
- return merged;
1835
- }
1836
-
1837
1718
  // src/indexer/index.ts
1838
- import { existsSync as existsSync8, readFileSync as readFileSync7, statSync as statSync3, writeFileSync as writeFileSync3, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync3, promises as fsPromises2 } from "fs";
1839
- import * as path13 from "path";
1719
+ import { existsSync as existsSync7, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync2, renameSync as renameSync2, unlinkSync, mkdirSync as mkdirSync2, promises as fsPromises2 } from "fs";
1720
+ import * as path11 from "path";
1840
1721
  import { performance as performance2 } from "perf_hooks";
1841
1722
  import { execFile as execFile3 } from "child_process";
1842
1723
  import { promisify as promisify3 } from "util";
@@ -3187,6 +3068,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3187
3068
  // src/embeddings/providers/ollama.ts
3188
3069
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
3189
3070
  static MIN_TRUNCATION_CHARS = 512;
3071
+ static REQUEST_TIMEOUT_MS = 12e4;
3190
3072
  constructor(credentials, modelInfo) {
3191
3073
  super(credentials, modelInfo);
3192
3074
  }
@@ -3261,22 +3143,45 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3261
3143
  }
3262
3144
  }
3263
3145
  async embedSingle(text3) {
3264
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3265
- method: "POST",
3266
- headers: {
3267
- "Content-Type": "application/json"
3268
- },
3269
- body: JSON.stringify({
3270
- model: this.modelInfo.model,
3271
- prompt: text3,
3272
- truncate: false
3273
- })
3274
- });
3146
+ const controller = new AbortController();
3147
+ const timeout = setTimeout(
3148
+ () => controller.abort(),
3149
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3150
+ );
3151
+ let response;
3152
+ try {
3153
+ response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3154
+ method: "POST",
3155
+ headers: {
3156
+ "Content-Type": "application/json"
3157
+ },
3158
+ body: JSON.stringify({
3159
+ model: this.modelInfo.model,
3160
+ prompt: text3,
3161
+ truncate: false
3162
+ }),
3163
+ signal: controller.signal
3164
+ });
3165
+ } catch (error) {
3166
+ if (error instanceof Error && error.name === "AbortError") {
3167
+ throw new Error(
3168
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3169
+ );
3170
+ }
3171
+ throw error;
3172
+ } finally {
3173
+ clearTimeout(timeout);
3174
+ }
3275
3175
  if (!response.ok) {
3276
3176
  const error = (await response.text()).slice(0, 500);
3277
3177
  throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
3278
3178
  }
3279
3179
  const data = await response.json();
3180
+ if (!Array.isArray(data.embedding) || data.embedding.length !== this.modelInfo.dimensions || data.embedding.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
3181
+ throw new Error(
3182
+ `Ollama returned an invalid embedding; expected ${this.modelInfo.dimensions} finite dimensions`
3183
+ );
3184
+ }
3280
3185
  return {
3281
3186
  embedding: data.embedding,
3282
3187
  tokensUsed: this.estimateTokens(text3)
@@ -3423,9 +3328,23 @@ var SiliconFlowReranker = class {
3423
3328
  };
3424
3329
 
3425
3330
  // src/utils/files.ts
3426
- var import_ignore = __toESM(require_ignore(), 1);
3427
- import { existsSync as existsSync6, readFileSync as readFileSync5, promises as fsPromises } from "fs";
3428
- import * as path8 from "path";
3331
+ var import_ignore = __toESM(require_ignore(), 1);
3332
+ import { existsSync as existsSync5, readFileSync as readFileSync4, promises as fsPromises } from "fs";
3333
+ import * as path6 from "path";
3334
+
3335
+ // src/utils/paths.ts
3336
+ import * as path5 from "path";
3337
+ function normalizePathSeparators(value) {
3338
+ return value.replace(/\\/g, "/");
3339
+ }
3340
+ function isHiddenPathSegment(part) {
3341
+ return part.startsWith(".") && part !== "." && part !== "..";
3342
+ }
3343
+ function isBuildPathSegment(part) {
3344
+ return part.toLowerCase().includes("build");
3345
+ }
3346
+
3347
+ // src/utils/files.ts
3429
3348
  function createIgnoreFilter(projectRoot3) {
3430
3349
  const ig = (0, import_ignore.default)();
3431
3350
  const defaultIgnores = [
@@ -3447,9 +3366,9 @@ function createIgnoreFilter(projectRoot3) {
3447
3366
  "**/*build*/**"
3448
3367
  ];
3449
3368
  ig.add(defaultIgnores);
3450
- const gitignorePath = path8.join(projectRoot3, ".gitignore");
3451
- if (existsSync6(gitignorePath)) {
3452
- const gitignoreContent = readFileSync5(gitignorePath, "utf-8");
3369
+ const gitignorePath = path6.join(projectRoot3, ".gitignore");
3370
+ if (existsSync5(gitignorePath)) {
3371
+ const gitignoreContent = readFileSync4(gitignorePath, "utf-8");
3453
3372
  ig.add(gitignoreContent);
3454
3373
  }
3455
3374
  return ig;
@@ -3474,8 +3393,8 @@ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePattern
3474
3393
  const filesInDir = [];
3475
3394
  const subdirs = [];
3476
3395
  for (const entry of entries) {
3477
- const fullPath = path8.join(dir, entry.name);
3478
- const relativePath = path8.relative(projectRoot3, fullPath);
3396
+ const fullPath = path6.join(dir, entry.name);
3397
+ const relativePath = path6.relative(projectRoot3, fullPath);
3479
3398
  if (isHiddenPathSegment(entry.name)) {
3480
3399
  if (entry.isDirectory()) {
3481
3400
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3524,7 +3443,7 @@ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePattern
3524
3443
  yield f;
3525
3444
  }
3526
3445
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3527
- skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3446
+ skipped.push({ path: path6.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3528
3447
  }
3529
3448
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3530
3449
  if (canRecurse) {
@@ -3564,8 +3483,8 @@ async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxF
3564
3483
  if (additionalRoots && additionalRoots.length > 0) {
3565
3484
  const normalizedRoots = /* @__PURE__ */ new Set();
3566
3485
  for (const kbRoot of additionalRoots) {
3567
- const resolved = path8.normalize(
3568
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
3486
+ const resolved = path6.normalize(
3487
+ path6.isAbsolute(kbRoot) ? kbRoot : path6.resolve(projectRoot3, kbRoot)
3569
3488
  );
3570
3489
  normalizedRoots.add(resolved);
3571
3490
  }
@@ -3912,7 +3831,7 @@ function initializeLogger(config) {
3912
3831
  }
3913
3832
 
3914
3833
  // src/native/index.ts
3915
- import * as path9 from "path";
3834
+ import * as path7 from "path";
3916
3835
  import * as os3 from "os";
3917
3836
  import * as module from "module";
3918
3837
  import { fileURLToPath } from "url";
@@ -3936,19 +3855,19 @@ function getNativeBinding() {
3936
3855
  let currentDir;
3937
3856
  let requireTarget;
3938
3857
  if (typeof import.meta !== "undefined" && import.meta.url) {
3939
- currentDir = path9.dirname(fileURLToPath(import.meta.url));
3858
+ currentDir = path7.dirname(fileURLToPath(import.meta.url));
3940
3859
  requireTarget = import.meta.url;
3941
3860
  } else if (typeof __dirname !== "undefined") {
3942
3861
  currentDir = __dirname;
3943
3862
  requireTarget = __filename;
3944
3863
  } else {
3945
3864
  currentDir = process.cwd();
3946
- requireTarget = path9.join(currentDir, "index.js");
3865
+ requireTarget = path7.join(currentDir, "index.js");
3947
3866
  }
3948
3867
  const normalizedDir = currentDir.replace(/\\/g, "/");
3949
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path9.join("src", "native"));
3950
- const packageRoot = isDevMode ? path9.resolve(currentDir, "../..") : path9.resolve(currentDir, "..");
3951
- const nativePath = path9.join(packageRoot, "native", bindingName);
3868
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
3869
+ const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
3870
+ const nativePath = path7.join(packageRoot, "native", bindingName);
3952
3871
  const require2 = module.createRequire(requireTarget);
3953
3872
  return require2(nativePath);
3954
3873
  }
@@ -4034,7 +3953,9 @@ function mapChunk(c) {
4034
3953
  return {
4035
3954
  content: c.content,
4036
3955
  startLine: c.startLine ?? c.start_line,
3956
+ startCol: c.startCol ?? c.start_col,
4037
3957
  endLine: c.endLine ?? c.end_line,
3958
+ endCol: c.endCol ?? c.end_col,
4038
3959
  chunkType: c.chunkType ?? c.chunk_type,
4039
3960
  name: c.name ?? void 0,
4040
3961
  language: c.language
@@ -4155,6 +4076,7 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4155
4076
  javascript: "JavaScript",
4156
4077
  python: "Python",
4157
4078
  rust: "Rust",
4079
+ swift: "Swift",
4158
4080
  go: "Go",
4159
4081
  java: "Java"
4160
4082
  };
@@ -4163,7 +4085,16 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4163
4085
  function: "function",
4164
4086
  arrow_function: "arrow function",
4165
4087
  method_definition: "method",
4088
+ method_declaration: "method",
4089
+ protocol_function_declaration: "protocol requirement",
4090
+ init_declaration: "initializer",
4091
+ deinit_declaration: "deinitializer",
4092
+ subscript_declaration: "subscript",
4166
4093
  class_declaration: "class",
4094
+ actor_declaration: "actor",
4095
+ extension_declaration: "extension",
4096
+ protocol_declaration: "protocol",
4097
+ struct_declaration: "struct",
4167
4098
  interface_declaration: "interface",
4168
4099
  type_alias_declaration: "type alias",
4169
4100
  enum_declaration: "enum",
@@ -4681,7 +4612,7 @@ var Database = class _Database {
4681
4612
  };
4682
4613
 
4683
4614
  // src/tools/changed-files.ts
4684
- import * as path10 from "path";
4615
+ import * as path8 from "path";
4685
4616
  import { execFile } from "child_process";
4686
4617
  import { promisify } from "util";
4687
4618
  var execFileAsync = promisify(execFile);
@@ -4769,8 +4700,8 @@ function normalizeFiles(rawFiles, projectRoot3) {
4769
4700
  for (const raw of rawFiles) {
4770
4701
  const trimmed = raw.trim();
4771
4702
  if (!trimmed) continue;
4772
- const absolute = path10.resolve(projectRoot3, trimmed);
4773
- const relative7 = path10.relative(projectRoot3, absolute);
4703
+ const absolute = path8.resolve(projectRoot3, trimmed);
4704
+ const relative7 = path8.relative(projectRoot3, absolute);
4774
4705
  const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
4775
4706
  if (!seen.has(cleaned)) {
4776
4707
  seen.add(cleaned);
@@ -4782,7 +4713,7 @@ function normalizeFiles(rawFiles, projectRoot3) {
4782
4713
 
4783
4714
  // src/indexer/git-blame.ts
4784
4715
  import { execFile as execFile2 } from "child_process";
4785
- import * as path11 from "path";
4716
+ import * as path9 from "path";
4786
4717
  import { promisify as promisify2 } from "util";
4787
4718
  var execFileAsync2 = promisify2(execFile2);
4788
4719
  function parseGitBlamePorcelain(output) {
@@ -4820,7 +4751,7 @@ function parseGitBlamePorcelain(output) {
4820
4751
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4821
4752
  }
4822
4753
  async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4823
- const relativePath = path11.relative(projectRoot3, filePath);
4754
+ const relativePath = path9.relative(projectRoot3, filePath);
4824
4755
  try {
4825
4756
  const { stdout } = await execFileAsync2(
4826
4757
  "git",
@@ -4836,18 +4767,18 @@ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4836
4767
  // src/indexer/index-lock.ts
4837
4768
  import { randomUUID } from "crypto";
4838
4769
  import {
4839
- existsSync as existsSync7,
4770
+ existsSync as existsSync6,
4840
4771
  lstatSync,
4841
- mkdirSync as mkdirSync2,
4842
- readFileSync as readFileSync6,
4772
+ mkdirSync,
4773
+ readFileSync as readFileSync5,
4843
4774
  readdirSync as readdirSync2,
4844
4775
  realpathSync,
4845
4776
  renameSync,
4846
4777
  rmSync,
4847
- writeFileSync as writeFileSync2
4778
+ writeFileSync
4848
4779
  } from "fs";
4849
4780
  import * as os4 from "os";
4850
- import * as path12 from "path";
4781
+ import * as path10 from "path";
4851
4782
  var OWNER_FILE_NAME = "owner.json";
4852
4783
  var RECLAIM_DIRECTORY_NAME = "reclaiming";
4853
4784
  var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
@@ -4904,7 +4835,7 @@ function parseReclaimOwner(value) {
4904
4835
  }
4905
4836
  function readJsonDirectory(directoryPath, parser) {
4906
4837
  try {
4907
- return parser(JSON.parse(readFileSync6(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4838
+ return parser(JSON.parse(readFileSync5(path10.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4908
4839
  } catch {
4909
4840
  return null;
4910
4841
  }
@@ -4920,7 +4851,7 @@ function readRecoveryOwner(markerPath) {
4920
4851
  }
4921
4852
  function readLegacyOwner(lockPath) {
4922
4853
  try {
4923
- const parsed = JSON.parse(readFileSync6(lockPath, "utf-8"));
4854
+ const parsed = JSON.parse(readFileSync5(lockPath, "utf-8"));
4924
4855
  if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
4925
4856
  if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
4926
4857
  return {
@@ -4955,23 +4886,23 @@ function sameReclaimOwner(left, right) {
4955
4886
  function publishJsonDirectory(finalPath, value) {
4956
4887
  const candidatePath = `${finalPath}.candidate.${process.pid}.${randomUUID()}`;
4957
4888
  try {
4958
- mkdirSync2(candidatePath, { mode: 448 });
4889
+ mkdirSync(candidatePath, { mode: 448 });
4959
4890
  } catch (error) {
4960
4891
  if (getErrorCode(error) === "ENOENT") return false;
4961
4892
  throw error;
4962
4893
  }
4963
4894
  try {
4964
- writeFileSync2(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4895
+ writeFileSync(path10.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4965
4896
  encoding: "utf-8",
4966
4897
  flag: "wx",
4967
4898
  mode: 384
4968
4899
  });
4969
- if (existsSync7(finalPath)) return false;
4900
+ if (existsSync6(finalPath)) return false;
4970
4901
  try {
4971
4902
  renameSync(candidatePath, finalPath);
4972
4903
  return true;
4973
4904
  } catch (error) {
4974
- if (existsSync7(finalPath)) return false;
4905
+ if (existsSync6(finalPath)) return false;
4975
4906
  if (getErrorCode(error) === "ENOENT") return false;
4976
4907
  throw error;
4977
4908
  }
@@ -4979,7 +4910,7 @@ function publishJsonDirectory(finalPath, value) {
4979
4910
  if (getErrorCode(error) === "ENOENT") return false;
4980
4911
  throw error;
4981
4912
  } finally {
4982
- if (existsSync7(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
4913
+ if (existsSync6(candidatePath)) rmSync(candidatePath, { recursive: true, force: true });
4983
4914
  }
4984
4915
  }
4985
4916
  function createOwner(operation) {
@@ -4992,7 +4923,7 @@ function createOwner(operation) {
4992
4923
  };
4993
4924
  }
4994
4925
  function recoveryMarkerPath(indexPath, owner) {
4995
- return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4926
+ return path10.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4996
4927
  }
4997
4928
  function publishRecoveryMarker(indexPath, owner) {
4998
4929
  const markerPath = recoveryMarkerPath(indexPath, owner);
@@ -5011,7 +4942,7 @@ function getPendingRecoveries(indexPath) {
5011
4942
  return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
5012
4943
  }).sort();
5013
4944
  for (const markerName of markerNames) {
5014
- const markerPath = path12.join(indexPath, markerName);
4945
+ const markerPath = path10.join(indexPath, markerName);
5015
4946
  const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
5016
4947
  let markerStats;
5017
4948
  try {
@@ -5043,11 +4974,11 @@ function cleanupDeadPublicationCandidates(indexPath) {
5043
4974
  const pid = Number(match[1]);
5044
4975
  if (!Number.isInteger(pid) || pid <= 0) continue;
5045
4976
  if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
5046
- rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
4977
+ rmSync(path10.join(indexPath, entry), { recursive: true, force: true });
5047
4978
  }
5048
4979
  }
5049
4980
  function removeDeadReclaimMarker(lockPath, expectedOwner) {
5050
- const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
4981
+ const markerPath = path10.join(lockPath, RECLAIM_DIRECTORY_NAME);
5051
4982
  const marker = readReclaimOwner(markerPath);
5052
4983
  if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
5053
4984
  return false;
@@ -5066,7 +4997,7 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5066
4997
  const claimedMarker = readReclaimOwner(claimedMarkerPath);
5067
4998
  const ownerAfterClaim = readDirectoryOwner(lockPath);
5068
4999
  if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
5069
- if (!existsSync7(markerPath) && existsSync7(claimedMarkerPath)) {
5000
+ if (!existsSync6(markerPath) && existsSync6(claimedMarkerPath)) {
5070
5001
  renameSync(claimedMarkerPath, markerPath);
5071
5002
  }
5072
5003
  return false;
@@ -5075,7 +5006,7 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5075
5006
  return true;
5076
5007
  }
5077
5008
  function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5078
- const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5009
+ const reclaimPath = path10.join(lockPath, RECLAIM_DIRECTORY_NAME);
5079
5010
  const reclaimOwner = {
5080
5011
  pid: process.pid,
5081
5012
  hostname: os4.hostname(),
@@ -5127,9 +5058,9 @@ function isIndexLockContentionError(error) {
5127
5058
  return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
5128
5059
  }
5129
5060
  function acquireIndexLock(indexPath, operation) {
5130
- mkdirSync2(indexPath, { recursive: true });
5061
+ mkdirSync(indexPath, { recursive: true });
5131
5062
  const canonicalIndexPath = realpathSync.native(indexPath);
5132
- const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
5063
+ const lockPath = path10.join(canonicalIndexPath, "indexing.lock");
5133
5064
  cleanupDeadPublicationCandidates(canonicalIndexPath);
5134
5065
  for (let attempt = 0; attempt < 6; attempt += 1) {
5135
5066
  const owner = createOwner(operation);
@@ -5185,7 +5116,7 @@ function releaseIndexLock(lease) {
5185
5116
  }
5186
5117
  const claimedOwner = readDirectoryOwner(releasePath);
5187
5118
  if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
5188
- if (!existsSync7(lease.lockPath) && existsSync7(releasePath)) {
5119
+ if (!existsSync6(lease.lockPath) && existsSync6(releasePath)) {
5189
5120
  renameSync(releasePath, lease.lockPath);
5190
5121
  }
5191
5122
  return false;
@@ -5197,59 +5128,25 @@ function releaseIndexLock(lease) {
5197
5128
  }
5198
5129
  return true;
5199
5130
  }
5200
- async function withIndexLock(indexPath, operation, callback, options = {}) {
5201
- const lease = acquireIndexLock(indexPath, operation);
5202
- let result;
5203
- let callbackError;
5204
- let callbackFailed = false;
5205
- try {
5206
- result = await callback(lease);
5207
- } catch (error) {
5208
- callbackFailed = true;
5209
- callbackError = error;
5210
- }
5211
- if (!callbackFailed && options.completeRecoveries !== false) {
5212
- try {
5213
- completeLeaseRecovery(lease);
5214
- } catch (error) {
5215
- callbackFailed = true;
5216
- callbackError = error;
5217
- }
5218
- }
5219
- let releaseError;
5220
- try {
5221
- if (!releaseIndexLock(lease)) {
5222
- releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5223
- }
5224
- } catch (error) {
5225
- releaseError = error;
5226
- }
5227
- if (releaseError !== void 0) {
5228
- if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5229
- throw releaseError;
5230
- }
5231
- if (callbackFailed) throw callbackError;
5232
- return result;
5233
- }
5234
5131
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5235
5132
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5236
5133
  temporaryCounter += 1;
5237
5134
  return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
5238
5135
  }
5239
5136
  function removeLeaseTemporaryPath(temporaryPath) {
5240
- if (existsSync7(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
5137
+ if (existsSync6(temporaryPath)) rmSync(temporaryPath, { recursive: true, force: true });
5241
5138
  }
5242
5139
  function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
5243
5140
  for (const targetPath of backupTargets) {
5244
5141
  const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
5245
- if (!existsSync7(backupPath)) continue;
5246
- if (existsSync7(targetPath)) rmSync(targetPath, { recursive: true, force: true });
5142
+ if (!existsSync6(backupPath)) continue;
5143
+ if (existsSync6(targetPath)) rmSync(targetPath, { recursive: true, force: true });
5247
5144
  renameSync(backupPath, targetPath);
5248
5145
  }
5249
5146
  const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
5250
5147
  for (const entry of readdirSync2(indexPath)) {
5251
5148
  if (!entry.includes(temporaryOwnerMarker)) continue;
5252
- rmSync(path12.join(indexPath, entry), { recursive: true, force: true });
5149
+ rmSync(path10.join(indexPath, entry), { recursive: true, force: true });
5253
5150
  }
5254
5151
  }
5255
5152
  function completeLeaseRecovery(lease) {
@@ -5265,8 +5162,18 @@ function completeLeaseRecovery(lease) {
5265
5162
  }
5266
5163
 
5267
5164
  // src/indexer/index.ts
5268
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
5269
- var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5165
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "swift", "php", "apex", "zig", "gdscript", "matlab", "bash", "c", "cpp", "metal"]);
5166
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex", "php"]);
5167
+ var CALL_GRAPH_RESOLUTION_VERSION = "4";
5168
+ var PHP_FUNCTION_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5169
+ "function_declaration",
5170
+ "function",
5171
+ "function_definition"
5172
+ ]);
5173
+ var PHP_CLASS_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5174
+ "class_declaration",
5175
+ "class_definition"
5176
+ ]);
5270
5177
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5271
5178
  "function_declaration",
5272
5179
  "function",
@@ -5279,6 +5186,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5279
5186
  "enum_declaration",
5280
5187
  "function_definition",
5281
5188
  "class_definition",
5189
+ "class_specifier",
5190
+ "struct_specifier",
5191
+ "namespace_definition",
5282
5192
  "decorated_definition",
5283
5193
  "method_declaration",
5284
5194
  "type_declaration",
@@ -5294,6 +5204,14 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5294
5204
  "test_declaration",
5295
5205
  "struct_declaration",
5296
5206
  "union_declaration",
5207
+ // Synthetic Swift declarations or declarations specific to tree-sitter-swift.
5208
+ "actor_declaration",
5209
+ "extension_declaration",
5210
+ "protocol_declaration",
5211
+ "protocol_function_declaration",
5212
+ "init_declaration",
5213
+ "deinit_declaration",
5214
+ "subscript_declaration",
5297
5215
  // GDScript declarations whose names participate in the call graph.
5298
5216
  // `function_definition` and `class_definition` are already in the set
5299
5217
  // above (shared with Python/C/Bash and Python, respectively).
@@ -5303,6 +5221,53 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5303
5221
  "const_statement",
5304
5222
  "class_name_statement"
5305
5223
  ]);
5224
+ var C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set(["class_specifier", "struct_specifier"]);
5225
+ function isCompatibleCFamilyCallTarget(language, callType, symbolKind) {
5226
+ if (language !== "c" && language !== "cpp") return true;
5227
+ if (symbolKind === "namespace_definition") return callType === "Import";
5228
+ const isTypeSymbol = C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES.has(symbolKind);
5229
+ if (callType === "Constructor" || callType === "Inherits" || callType === "Implements") {
5230
+ return isTypeSymbol;
5231
+ }
5232
+ return !isTypeSymbol;
5233
+ }
5234
+ var EXECUTABLE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5235
+ "function_declaration",
5236
+ "function",
5237
+ "arrow_function",
5238
+ "method_definition",
5239
+ "function_definition",
5240
+ "method_declaration",
5241
+ "function_item",
5242
+ "protocol_function_declaration",
5243
+ "init_declaration",
5244
+ "deinit_declaration",
5245
+ "subscript_declaration",
5246
+ "constructor_definition",
5247
+ "trigger_declaration",
5248
+ "test_declaration"
5249
+ ]);
5250
+ function findEnclosingSymbol(symbols, line, column) {
5251
+ let best;
5252
+ for (const symbol of symbols) {
5253
+ if (line < symbol.startLine || line > symbol.endLine) continue;
5254
+ if (column !== void 0 && (line === symbol.startLine && column < symbol.startCol || line === symbol.endLine && column >= symbol.endCol)) {
5255
+ continue;
5256
+ }
5257
+ if (!best) {
5258
+ best = symbol;
5259
+ continue;
5260
+ }
5261
+ const span = symbol.endLine - symbol.startLine;
5262
+ const bestSpan = best.endLine - best.startLine;
5263
+ 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);
5264
+ const isMoreSpecificTie = span === bestSpan && symbol.startLine === best.startLine && EXECUTABLE_SYMBOL_CHUNK_TYPES.has(symbol.kind) && !EXECUTABLE_SYMBOL_CHUNK_TYPES.has(best.kind);
5265
+ if (span < bestSpan || span === bestSpan && symbol.startLine > best.startLine || isNarrowerPositionRange || isMoreSpecificTie) {
5266
+ best = symbol;
5267
+ }
5268
+ }
5269
+ return best;
5270
+ }
5306
5271
  function float32ArrayToBuffer(arr) {
5307
5272
  const float32 = new Float32Array(arr);
5308
5273
  return Buffer.from(float32.buffer);
@@ -5387,6 +5352,8 @@ function hasBlameMetadata(metadata) {
5387
5352
  }
5388
5353
  var INDEX_METADATA_VERSION = "1";
5389
5354
  var EMBEDDING_STRATEGY_VERSION = "2";
5355
+ var SWIFT_PARSER_VERSION = "1";
5356
+ var METAL_PARSER_VERSION = "1";
5390
5357
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
5391
5358
  var RANK_HYBRID_CACHE_LIMIT = 256;
5392
5359
  function createPendingChunkStorageText(texts) {
@@ -5543,9 +5510,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5543
5510
  return true;
5544
5511
  }
5545
5512
  function isPathWithinRoot(filePath, rootPath) {
5546
- const normalizedFilePath = path13.resolve(filePath);
5547
- const normalizedRoot = path13.resolve(rootPath);
5548
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5513
+ const normalizedFilePath = path11.resolve(filePath);
5514
+ const normalizedRoot = path11.resolve(rootPath);
5515
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5549
5516
  }
5550
5517
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5551
5518
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5709,15 +5676,25 @@ function chunkTypeBoost(chunkType) {
5709
5676
  case "function_declaration":
5710
5677
  case "method":
5711
5678
  case "method_definition":
5679
+ case "method_declaration":
5680
+ case "protocol_function_declaration":
5681
+ case "init_declaration":
5682
+ case "deinit_declaration":
5683
+ case "subscript_declaration":
5712
5684
  case "class":
5713
5685
  case "class_declaration":
5686
+ case "actor_declaration":
5687
+ case "extension_declaration":
5714
5688
  return 0.2;
5715
5689
  case "interface":
5716
5690
  case "type":
5717
5691
  case "enum":
5692
+ case "enum_declaration":
5718
5693
  case "struct":
5694
+ case "struct_declaration":
5719
5695
  case "impl":
5720
5696
  case "trait":
5697
+ case "protocol_declaration":
5721
5698
  case "module":
5722
5699
  return 0.1;
5723
5700
  default:
@@ -5824,11 +5801,21 @@ function isImplementationChunkType(chunkType) {
5824
5801
  "function_declaration",
5825
5802
  "method",
5826
5803
  "method_definition",
5804
+ "method_declaration",
5805
+ "protocol_function_declaration",
5806
+ "init_declaration",
5807
+ "deinit_declaration",
5808
+ "subscript_declaration",
5827
5809
  "class",
5828
5810
  "class_declaration",
5811
+ "actor_declaration",
5812
+ "extension_declaration",
5829
5813
  "interface",
5814
+ "protocol_declaration",
5830
5815
  "type",
5831
5816
  "enum",
5817
+ "enum_declaration",
5818
+ "struct_declaration",
5832
5819
  "module"
5833
5820
  ].includes(chunkType);
5834
5821
  }
@@ -6645,21 +6632,21 @@ var Indexer = class {
6645
6632
  this.config = config;
6646
6633
  this.host = host;
6647
6634
  this.indexPath = this.getIndexPath();
6648
- this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6649
- this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6635
+ this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6636
+ this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6650
6637
  this.logger = initializeLogger(config.debug);
6651
6638
  }
6652
6639
  getIndexPath() {
6653
6640
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6654
6641
  }
6655
6642
  isLocalProjectIndexPath() {
6656
- const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6643
+ const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6657
6644
  if (this.host !== "opencode") {
6658
- localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6645
+ localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6659
6646
  }
6660
6647
  return localProjectIndexPaths.some((localPath) => {
6661
- if (!existsSync8(localPath) || !existsSync8(this.indexPath)) {
6662
- return path13.resolve(this.indexPath) === path13.resolve(localPath);
6648
+ if (!existsSync7(localPath) || !existsSync7(this.indexPath)) {
6649
+ return path11.resolve(this.indexPath) === path11.resolve(localPath);
6663
6650
  }
6664
6651
  const indexStats = statSync3(this.indexPath);
6665
6652
  const localStats = statSync3(localPath);
@@ -6701,8 +6688,8 @@ var Indexer = class {
6701
6688
  async withIndexMutationLease(operation, callback) {
6702
6689
  const lease = acquireIndexLock(this.indexPath, operation);
6703
6690
  this.indexPath = lease.canonicalIndexPath;
6704
- this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6705
- this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6691
+ this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6692
+ this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6706
6693
  this.activeIndexLease = lease;
6707
6694
  let result;
6708
6695
  let callbackError;
@@ -6736,7 +6723,7 @@ var Indexer = class {
6736
6723
  } catch (error) {
6737
6724
  releaseError = error;
6738
6725
  this.writerArtifactFingerprint = null;
6739
- if (!existsSync8(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6726
+ if (!existsSync7(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6740
6727
  this.activeIndexLease = null;
6741
6728
  }
6742
6729
  }
@@ -6754,11 +6741,11 @@ var Indexer = class {
6754
6741
  return this.activeIndexLease;
6755
6742
  }
6756
6743
  loadFileHashCache() {
6757
- if (!existsSync8(this.fileHashCachePath)) {
6744
+ if (!existsSync7(this.fileHashCachePath)) {
6758
6745
  return;
6759
6746
  }
6760
6747
  try {
6761
- const data = readFileSync7(this.fileHashCachePath, "utf-8");
6748
+ const data = readFileSync6(this.fileHashCachePath, "utf-8");
6762
6749
  const parsed = JSON.parse(data);
6763
6750
  this.fileHashCache = new Map(Object.entries(parsed));
6764
6751
  } catch (error) {
@@ -6780,9 +6767,9 @@ var Indexer = class {
6780
6767
  atomicWriteSync(targetPath, data) {
6781
6768
  const lease = this.requireActiveLease();
6782
6769
  const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6783
- mkdirSync3(path13.dirname(targetPath), { recursive: true });
6770
+ mkdirSync2(path11.dirname(targetPath), { recursive: true });
6784
6771
  try {
6785
- writeFileSync3(tempPath, data);
6772
+ writeFileSync2(tempPath, data);
6786
6773
  renameSync2(tempPath, targetPath);
6787
6774
  } finally {
6788
6775
  removeLeaseTemporaryPath(tempPath);
@@ -6790,14 +6777,14 @@ var Indexer = class {
6790
6777
  }
6791
6778
  saveInvertedIndex(invertedIndex) {
6792
6779
  this.atomicWriteSync(
6793
- path13.join(this.indexPath, "inverted-index.json"),
6780
+ path11.join(this.indexPath, "inverted-index.json"),
6794
6781
  invertedIndex.serialize()
6795
6782
  );
6796
6783
  }
6797
6784
  getScopedRoots() {
6798
- const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6785
+ const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6799
6786
  for (const kbRoot of this.config.knowledgeBases) {
6800
- roots.add(path13.resolve(this.projectRoot, kbRoot));
6787
+ roots.add(path11.resolve(this.projectRoot, kbRoot));
6801
6788
  }
6802
6789
  return Array.from(roots);
6803
6790
  }
@@ -6806,31 +6793,54 @@ var Indexer = class {
6806
6793
  if (this.config.scope !== "global") {
6807
6794
  return branchName;
6808
6795
  }
6809
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6796
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6810
6797
  return `${projectHash}:${branchName}`;
6811
6798
  }
6812
6799
  getBranchCatalogKeyFor(branchName) {
6813
6800
  if (this.config.scope !== "global") {
6814
6801
  return branchName;
6815
6802
  }
6816
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6803
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6817
6804
  return `${projectHash}:${branchName}`;
6818
6805
  }
6819
6806
  getLegacyBranchCatalogKey() {
6820
6807
  return this.currentBranch || "default";
6821
6808
  }
6822
6809
  getLegacyMigrationMetadataKey() {
6823
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6810
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6824
6811
  return `index.globalBranchMigration.${projectHash}`;
6825
6812
  }
6826
6813
  getProjectEmbeddingStrategyMetadataKey() {
6827
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6814
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6828
6815
  return `index.embeddingStrategyVersion.${projectHash}`;
6829
6816
  }
6830
6817
  getProjectForceReembedMetadataKey() {
6831
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6818
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6832
6819
  return `index.forceReembed.${projectHash}`;
6833
6820
  }
6821
+ getCallGraphResolutionMetadataKey() {
6822
+ if (this.config.scope !== "global") {
6823
+ return "index.callGraphResolutionVersion";
6824
+ }
6825
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6826
+ return `index.callGraphResolutionVersion.${projectHash}`;
6827
+ }
6828
+ getSwiftParserVersionMetadataKey() {
6829
+ const key = "index.parser.swiftVersion";
6830
+ if (this.config.scope !== "global") {
6831
+ return key;
6832
+ }
6833
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6834
+ return `${key}.${projectHash}`;
6835
+ }
6836
+ getMetalParserVersionMetadataKey() {
6837
+ const key = "index.parser.metalVersion";
6838
+ if (this.config.scope !== "global") {
6839
+ return key;
6840
+ }
6841
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6842
+ return `${key}.${projectHash}`;
6843
+ }
6834
6844
  hasProjectForceReembedPending() {
6835
6845
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
6836
6846
  }
@@ -6922,7 +6932,7 @@ var Indexer = class {
6922
6932
  if (!this.database) {
6923
6933
  return { chunkIds, symbolIds };
6924
6934
  }
6925
- const projectRootPath = path13.resolve(this.projectRoot);
6935
+ const projectRootPath = path11.resolve(this.projectRoot);
6926
6936
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6927
6937
  ...Array.from(this.fileHashCache.keys()).filter(
6928
6938
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6945,7 +6955,7 @@ var Indexer = class {
6945
6955
  if (this.config.scope !== "global") {
6946
6956
  return this.getBranchCatalogCleanupKeys();
6947
6957
  }
6948
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6958
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6949
6959
  const keys = /* @__PURE__ */ new Set();
6950
6960
  const projectChunkIdSet = new Set(projectChunkIds);
6951
6961
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7029,7 +7039,7 @@ var Indexer = class {
7029
7039
  if (!this.database || this.config.scope !== "global") {
7030
7040
  return false;
7031
7041
  }
7032
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7042
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7033
7043
  const roots = this.getScopedRoots();
7034
7044
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7035
7045
  return this.database.getAllBranches().some(
@@ -7063,7 +7073,7 @@ var Indexer = class {
7063
7073
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7064
7074
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7065
7075
  ]);
7066
- const projectRootPath = path13.resolve(this.projectRoot);
7076
+ const projectRootPath = path11.resolve(this.projectRoot);
7067
7077
  const projectLocalFilePaths = new Set(
7068
7078
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7069
7079
  );
@@ -7141,7 +7151,7 @@ var Indexer = class {
7141
7151
  });
7142
7152
  }
7143
7153
  if (this.config.scope === "global") {
7144
- if (existsSync8(this.fileHashCachePath)) {
7154
+ if (existsSync7(this.fileHashCachePath)) {
7145
7155
  unlinkSync(this.fileHashCachePath);
7146
7156
  }
7147
7157
  await this.healthCheckUnlocked();
@@ -7161,10 +7171,10 @@ var Indexer = class {
7161
7171
  }
7162
7172
  }
7163
7173
  loadSerializedFailedBatches() {
7164
- if (!existsSync8(this.failedBatchesPath)) {
7174
+ if (!existsSync7(this.failedBatchesPath)) {
7165
7175
  return [];
7166
7176
  }
7167
- const data = readFileSync7(this.failedBatchesPath, "utf-8");
7177
+ const data = readFileSync6(this.failedBatchesPath, "utf-8");
7168
7178
  const parsed = JSON.parse(data);
7169
7179
  return parsed.map((batch) => {
7170
7180
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -7181,7 +7191,7 @@ var Indexer = class {
7181
7191
  }
7182
7192
  saveFailedBatches(batches) {
7183
7193
  if (batches.length === 0) {
7184
- if (existsSync8(this.failedBatchesPath)) {
7194
+ if (existsSync7(this.failedBatchesPath)) {
7185
7195
  try {
7186
7196
  unlinkSync(this.failedBatchesPath);
7187
7197
  } catch {
@@ -7473,12 +7483,12 @@ var Indexer = class {
7473
7483
  }
7474
7484
  }
7475
7485
  captureReaderArtifactFingerprint() {
7476
- const storePath = path13.join(this.indexPath, "vectors");
7486
+ const storePath = path11.join(this.indexPath, "vectors");
7477
7487
  return {
7478
7488
  vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7479
- keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7480
- database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7481
- databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7489
+ keyword: this.getReaderFileFingerprint(path11.join(this.indexPath, "inverted-index.json")),
7490
+ database: this.getReaderFileFingerprint(path11.join(this.indexPath, "codebase.db")),
7491
+ databaseIdentity: this.getReaderFileFingerprint(path11.join(this.indexPath, "codebase.db"), true)
7482
7492
  };
7483
7493
  }
7484
7494
  refreshReaderArtifacts() {
@@ -7503,13 +7513,13 @@ var Indexer = class {
7503
7513
  issues.set(component, this.createReadIssue(component, message));
7504
7514
  this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7505
7515
  };
7506
- const storePath = path13.join(this.indexPath, "vectors");
7516
+ const storePath = path11.join(this.indexPath, "vectors");
7507
7517
  const vectorMetadataPath = `${storePath}.meta.json`;
7508
- const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7509
- const dbPath = path13.join(this.indexPath, "codebase.db");
7518
+ const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
7519
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7510
7520
  if (vectorsChanged || retryDue("vectors")) {
7511
- const vectorStoreExists = existsSync8(storePath);
7512
- const vectorMetadataExists = existsSync8(vectorMetadataPath);
7521
+ const vectorStoreExists = existsSync7(storePath);
7522
+ const vectorMetadataExists = existsSync7(vectorMetadataPath);
7513
7523
  if (vectorStoreExists && vectorMetadataExists) {
7514
7524
  try {
7515
7525
  const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
@@ -7524,8 +7534,8 @@ var Indexer = class {
7524
7534
  setIssue("vectors", this.getVectorReadIssueMessage());
7525
7535
  }
7526
7536
  }
7527
- if (keywordChanged || retryDue("keyword") || !existsSync8(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7528
- if (existsSync8(invertedIndexPath)) {
7537
+ if (keywordChanged || retryDue("keyword") || !existsSync7(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7538
+ if (existsSync7(invertedIndexPath)) {
7529
7539
  try {
7530
7540
  const invertedIndex = new InvertedIndex(invertedIndexPath);
7531
7541
  invertedIndex.load();
@@ -7540,7 +7550,7 @@ var Indexer = class {
7540
7550
  }
7541
7551
  }
7542
7552
  if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7543
- if (existsSync8(dbPath)) {
7553
+ if (existsSync7(dbPath)) {
7544
7554
  try {
7545
7555
  const database = Database.openReadOnly(dbPath);
7546
7556
  if (this.database) {
@@ -7631,11 +7641,11 @@ var Indexer = class {
7631
7641
  }
7632
7642
  }
7633
7643
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7634
- const storePath = path13.join(this.indexPath, "vectors");
7644
+ const storePath = path11.join(this.indexPath, "vectors");
7635
7645
  const vectorMetadataPath = `${storePath}.meta.json`;
7636
- const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7637
- const dbPath = path13.join(this.indexPath, "codebase.db");
7638
- let dbIsNew = !existsSync8(dbPath);
7646
+ const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
7647
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7648
+ let dbIsNew = !existsSync7(dbPath);
7639
7649
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7640
7650
  if (mode === "writer") {
7641
7651
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
@@ -7654,14 +7664,14 @@ var Indexer = class {
7654
7664
  await this.resetLocalIndexArtifacts();
7655
7665
  }
7656
7666
  this.store = new VectorStore(storePath, dimensions);
7657
- if (existsSync8(storePath) || existsSync8(vectorMetadataPath)) {
7667
+ if (existsSync7(storePath) || existsSync7(vectorMetadataPath)) {
7658
7668
  this.store.load();
7659
7669
  }
7660
7670
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7661
7671
  try {
7662
7672
  this.invertedIndex.load();
7663
7673
  } catch {
7664
- if (existsSync8(invertedIndexPath)) {
7674
+ if (existsSync7(invertedIndexPath)) {
7665
7675
  await fsPromises2.unlink(invertedIndexPath);
7666
7676
  }
7667
7677
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
@@ -7679,8 +7689,8 @@ var Indexer = class {
7679
7689
  }
7680
7690
  } else {
7681
7691
  this.store = new VectorStore(storePath, dimensions);
7682
- const vectorStoreExists = existsSync8(storePath);
7683
- const vectorMetadataExists = existsSync8(vectorMetadataPath);
7692
+ const vectorStoreExists = existsSync7(storePath);
7693
+ const vectorMetadataExists = existsSync7(vectorMetadataPath);
7684
7694
  const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7685
7695
  if (vectorStoreExists !== vectorMetadataExists) {
7686
7696
  this.recordReadIssue("vectors", vectorReadFailureMessage);
@@ -7693,7 +7703,7 @@ var Indexer = class {
7693
7703
  }
7694
7704
  }
7695
7705
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7696
- if (existsSync8(invertedIndexPath)) {
7706
+ if (existsSync7(invertedIndexPath)) {
7697
7707
  try {
7698
7708
  this.invertedIndex.load();
7699
7709
  } catch (error) {
@@ -7707,7 +7717,7 @@ var Indexer = class {
7707
7717
  } else if (this.store.count() > 0) {
7708
7718
  this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7709
7719
  }
7710
- if (existsSync8(dbPath)) {
7720
+ if (existsSync7(dbPath)) {
7711
7721
  try {
7712
7722
  this.database = Database.openReadOnly(dbPath);
7713
7723
  } catch (error) {
@@ -7798,7 +7808,7 @@ var Indexer = class {
7798
7808
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
7799
7809
  return {
7800
7810
  resetCorruptedIndex: true,
7801
- warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
7811
+ warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7802
7812
  };
7803
7813
  }
7804
7814
  throw error;
@@ -7813,7 +7823,7 @@ var Indexer = class {
7813
7823
  return;
7814
7824
  }
7815
7825
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
7816
- const storeBasePath = path13.join(this.indexPath, "vectors");
7826
+ const storeBasePath = path11.join(this.indexPath, "vectors");
7817
7827
  const storeIndexPath = storeBasePath;
7818
7828
  const storeMetadataPath = `${storeBasePath}.meta.json`;
7819
7829
  const lease = this.requireActiveLease();
@@ -7823,18 +7833,18 @@ var Indexer = class {
7823
7833
  let backedUpMetadata = false;
7824
7834
  let rebuiltCount = 0;
7825
7835
  let skippedCount = 0;
7826
- if (existsSync8(backupIndexPath)) {
7836
+ if (existsSync7(backupIndexPath)) {
7827
7837
  unlinkSync(backupIndexPath);
7828
7838
  }
7829
- if (existsSync8(backupMetadataPath)) {
7839
+ if (existsSync7(backupMetadataPath)) {
7830
7840
  unlinkSync(backupMetadataPath);
7831
7841
  }
7832
7842
  try {
7833
- if (existsSync8(storeIndexPath)) {
7843
+ if (existsSync7(storeIndexPath)) {
7834
7844
  renameSync2(storeIndexPath, backupIndexPath);
7835
7845
  backedUpIndex = true;
7836
7846
  }
7837
- if (existsSync8(storeMetadataPath)) {
7847
+ if (existsSync7(storeMetadataPath)) {
7838
7848
  renameSync2(storeMetadataPath, backupMetadataPath);
7839
7849
  backedUpMetadata = true;
7840
7850
  }
@@ -7855,10 +7865,10 @@ var Indexer = class {
7855
7865
  rebuiltCount += 1;
7856
7866
  }
7857
7867
  store.save();
7858
- if (backedUpIndex && existsSync8(backupIndexPath)) {
7868
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
7859
7869
  unlinkSync(backupIndexPath);
7860
7870
  }
7861
- if (backedUpMetadata && existsSync8(backupMetadataPath)) {
7871
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
7862
7872
  unlinkSync(backupMetadataPath);
7863
7873
  }
7864
7874
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
@@ -7871,16 +7881,16 @@ var Indexer = class {
7871
7881
  store.clear();
7872
7882
  } catch {
7873
7883
  }
7874
- if (existsSync8(storeIndexPath)) {
7884
+ if (existsSync7(storeIndexPath)) {
7875
7885
  unlinkSync(storeIndexPath);
7876
7886
  }
7877
- if (existsSync8(storeMetadataPath)) {
7887
+ if (existsSync7(storeMetadataPath)) {
7878
7888
  unlinkSync(storeMetadataPath);
7879
7889
  }
7880
- if (backedUpIndex && existsSync8(backupIndexPath)) {
7890
+ if (backedUpIndex && existsSync7(backupIndexPath)) {
7881
7891
  renameSync2(backupIndexPath, storeIndexPath);
7882
7892
  }
7883
- if (backedUpMetadata && existsSync8(backupMetadataPath)) {
7893
+ if (backedUpMetadata && existsSync7(backupMetadataPath)) {
7884
7894
  renameSync2(backupMetadataPath, storeMetadataPath);
7885
7895
  }
7886
7896
  if (backedUpIndex || backedUpMetadata) {
@@ -7908,15 +7918,15 @@ var Indexer = class {
7908
7918
  this.readerArtifactRetryAfter.clear();
7909
7919
  this.fileHashCache.clear();
7910
7920
  const resetPaths = [
7911
- path13.join(this.indexPath, "codebase.db"),
7912
- path13.join(this.indexPath, "codebase.db-shm"),
7913
- path13.join(this.indexPath, "codebase.db-wal"),
7914
- path13.join(this.indexPath, "vectors"),
7915
- path13.join(this.indexPath, "vectors.usearch"),
7916
- path13.join(this.indexPath, "vectors.meta.json"),
7917
- path13.join(this.indexPath, "inverted-index.json"),
7918
- path13.join(this.indexPath, "file-hashes.json"),
7919
- path13.join(this.indexPath, "failed-batches.json")
7921
+ path11.join(this.indexPath, "codebase.db"),
7922
+ path11.join(this.indexPath, "codebase.db-shm"),
7923
+ path11.join(this.indexPath, "codebase.db-wal"),
7924
+ path11.join(this.indexPath, "vectors"),
7925
+ path11.join(this.indexPath, "vectors.usearch"),
7926
+ path11.join(this.indexPath, "vectors.meta.json"),
7927
+ path11.join(this.indexPath, "inverted-index.json"),
7928
+ path11.join(this.indexPath, "file-hashes.json"),
7929
+ path11.join(this.indexPath, "failed-batches.json")
7920
7930
  ];
7921
7931
  await Promise.all(resetPaths.map((targetPath) => fsPromises2.rm(targetPath, { recursive: true, force: true })));
7922
7932
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
@@ -7925,7 +7935,7 @@ var Indexer = class {
7925
7935
  if (!isSqliteCorruptionError(error)) {
7926
7936
  return false;
7927
7937
  }
7928
- const dbPath = path13.join(this.indexPath, "codebase.db");
7938
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7929
7939
  const warning = this.getCorruptedIndexWarning(dbPath);
7930
7940
  const errorMessage = getErrorMessage2(error);
7931
7941
  if (this.config.scope === "global") {
@@ -7991,6 +8001,7 @@ var Indexer = class {
7991
8001
  this.database.setMetadata("index.embeddingProvider", provider.provider);
7992
8002
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
7993
8003
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8004
+ this.database.setMetadata(this.getCallGraphResolutionMetadataKey(), CALL_GRAPH_RESOLUTION_VERSION);
7994
8005
  if (this.config.scope === "global") {
7995
8006
  if (completeProjectEmbeddingStrategyReset) {
7996
8007
  this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
@@ -8178,6 +8189,20 @@ var Indexer = class {
8178
8189
  totalChunks: 0
8179
8190
  });
8180
8191
  this.loadFileHashCache();
8192
+ const swiftParserMetadataKey = this.getSwiftParserVersionMetadataKey();
8193
+ const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
8194
+ const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
8195
+ const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
8196
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
8197
+ (filePath) => path11.extname(filePath).toLowerCase() === ".swift"
8198
+ )) {
8199
+ this.logger.info("Reindexing cached Swift files for parser support");
8200
+ }
8201
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some(
8202
+ (filePath) => path11.extname(filePath).toLowerCase() === ".metal"
8203
+ )) {
8204
+ this.logger.info("Reindexing cached Metal files for parser support");
8205
+ }
8181
8206
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
8182
8207
  const { files, skipped } = await collectFiles(
8183
8208
  this.projectRoot,
@@ -8197,10 +8222,17 @@ var Indexer = class {
8197
8222
  const changedFiles = [];
8198
8223
  const unchangedFilePaths = /* @__PURE__ */ new Set();
8199
8224
  const currentFileHashes = /* @__PURE__ */ new Map();
8225
+ const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
8200
8226
  for (const f of files) {
8201
8227
  const currentHash = hashFile(f.path);
8202
8228
  currentFileHashes.set(f.path, currentHash);
8203
- if (this.fileHashCache.get(f.path) === currentHash) {
8229
+ const cachedHashMatches = this.fileHashCache.get(f.path) === currentHash;
8230
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(f.path).some(
8231
+ (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
8232
+ );
8233
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path11.extname(f.path).toLowerCase() === ".swift";
8234
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path11.extname(f.path).toLowerCase() === ".metal";
8235
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
8204
8236
  unchangedFilePaths.add(f.path);
8205
8237
  this.logger.recordCacheHit();
8206
8238
  } else {
@@ -8302,7 +8334,7 @@ var Indexer = class {
8302
8334
  for (const parsed of parsedFiles) {
8303
8335
  currentFilePaths.add(parsed.path);
8304
8336
  if (parsed.chunks.length === 0) {
8305
- const relativePath = path13.relative(this.projectRoot, parsed.path);
8337
+ const relativePath = path11.relative(this.projectRoot, parsed.path);
8306
8338
  stats.parseFailures.push(relativePath);
8307
8339
  }
8308
8340
  let chunksToProcess = parsed.chunks;
@@ -8402,16 +8434,28 @@ var Indexer = class {
8402
8434
  const fileSymbols = [];
8403
8435
  for (const chunk of parsed.chunks) {
8404
8436
  if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
8405
- const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
8437
+ const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
8438
+ (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
8439
+ ) : void 0;
8440
+ if (existingMetalSymbol) {
8441
+ existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
8442
+ existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
8443
+ existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
8444
+ existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
8445
+ continue;
8446
+ }
8447
+ const symbolId = `sym_${hashContent(
8448
+ parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0)
8449
+ ).slice(0, 16)}`;
8406
8450
  const symbol = {
8407
8451
  id: symbolId,
8408
8452
  filePath: parsed.path,
8409
8453
  name: chunk.name,
8410
8454
  kind: chunk.chunkType,
8411
8455
  startLine: chunk.startLine,
8412
- startCol: 0,
8456
+ startCol: chunk.startCol ?? 0,
8413
8457
  endLine: chunk.endLine,
8414
- endCol: 0,
8458
+ endCol: chunk.endCol ?? 0,
8415
8459
  language: chunk.language
8416
8460
  };
8417
8461
  fileSymbols.push(symbol);
@@ -8436,8 +8480,10 @@ var Indexer = class {
8436
8480
  if (callSites.length === 0) continue;
8437
8481
  const edges = [];
8438
8482
  for (const site of callSites) {
8439
- const enclosingSymbol = fileSymbols.find(
8440
- (sym) => site.line >= sym.startLine && site.line <= sym.endLine
8483
+ const enclosingSymbol = findEnclosingSymbol(
8484
+ fileSymbols,
8485
+ site.line,
8486
+ site.column
8441
8487
  );
8442
8488
  if (!enclosingSymbol) continue;
8443
8489
  const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
@@ -8456,7 +8502,21 @@ var Indexer = class {
8456
8502
  if (edges.length > 0) {
8457
8503
  database.upsertCallEdgesBatch(edges);
8458
8504
  for (const edge of edges) {
8459
- const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8505
+ let candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8506
+ if (fileLanguage === "php" && candidates) {
8507
+ if (edge.callType === "Constructor") {
8508
+ candidates = candidates.filter(
8509
+ (candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8510
+ );
8511
+ } else if (edge.callType === "Call") {
8512
+ candidates = candidates.filter(
8513
+ (candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8514
+ );
8515
+ }
8516
+ }
8517
+ candidates = candidates?.filter(
8518
+ (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, edge.callType, symbol.kind)
8519
+ );
8460
8520
  if (candidates && candidates.length === 1) {
8461
8521
  database.resolveCallEdge(edge.id, candidates[0].id);
8462
8522
  }
@@ -8498,8 +8558,8 @@ var Indexer = class {
8498
8558
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8499
8559
  database.clearBranchSymbols(branchCatalogKey);
8500
8560
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8501
- const vectorPath = path13.join(this.indexPath, "vectors");
8502
- const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync8(vectorPath) && existsSync8(`${vectorPath}.meta.json`);
8561
+ const vectorPath = path11.join(this.indexPath, "vectors");
8562
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && existsSync7(vectorPath) && existsSync7(`${vectorPath}.meta.json`);
8503
8563
  if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
8504
8564
  store.save();
8505
8565
  }
@@ -8511,6 +8571,8 @@ var Indexer = class {
8511
8571
  this.saveFileHashCache();
8512
8572
  this.saveFailedBatches([]);
8513
8573
  }
8574
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8575
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8514
8576
  this.saveIndexMetadata(configuredProviderInfo);
8515
8577
  this.indexCompatibility = { compatible: true };
8516
8578
  stats.durationMs = Date.now() - startTime;
@@ -8538,6 +8600,8 @@ var Indexer = class {
8538
8600
  this.saveFileHashCache();
8539
8601
  this.saveFailedBatches([]);
8540
8602
  }
8603
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8604
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8541
8605
  this.saveIndexMetadata(configuredProviderInfo);
8542
8606
  this.indexCompatibility = { compatible: true };
8543
8607
  stats.durationMs = Date.now() - startTime;
@@ -8808,6 +8872,8 @@ var Indexer = class {
8808
8872
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
8809
8873
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8810
8874
  }
8875
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8876
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8811
8877
  this.saveIndexMetadata(configuredProviderInfo);
8812
8878
  this.indexCompatibility = { compatible: true };
8813
8879
  this.logger.recordIndexingEnd();
@@ -9241,7 +9307,7 @@ var Indexer = class {
9241
9307
  const removedChunkKeys = [];
9242
9308
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9243
9309
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9244
- if (!existsSync8(filePath)) {
9310
+ if (!existsSync7(filePath)) {
9245
9311
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9246
9312
  for (const key of chunkKeys) {
9247
9313
  removedChunkKeys.push(key);
@@ -9290,7 +9356,7 @@ var Indexer = class {
9290
9356
  gcOrphanSymbols: 0,
9291
9357
  gcOrphanCallEdges: 0,
9292
9358
  resetCorruptedIndex: true,
9293
- warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
9359
+ warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9294
9360
  };
9295
9361
  }
9296
9362
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -9720,7 +9786,7 @@ var Indexer = class {
9720
9786
  "Run index_codebase first to build the call graph and symbol index for this branch."
9721
9787
  );
9722
9788
  }
9723
- const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
9789
+ const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9724
9790
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
9725
9791
  const directIds = directSymbols.map((s) => s.id);
9726
9792
  const direction = opts.direction ?? "both";
@@ -9803,7 +9869,7 @@ var Indexer = class {
9803
9869
  projectRoot: this.projectRoot,
9804
9870
  baseBranch: this.baseBranch
9805
9871
  });
9806
- const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
9872
+ const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9807
9873
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
9808
9874
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
9809
9875
  const otherLabels = /* @__PURE__ */ new Set();
@@ -9867,12 +9933,12 @@ var Indexer = class {
9867
9933
  if (meta.filePath) filePaths.add(meta.filePath);
9868
9934
  }
9869
9935
  const directory = options?.directory?.replace(/\/$/, "");
9870
- const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
9936
+ const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9871
9937
  for (const filePath of filePaths) {
9872
9938
  if (directory) {
9873
- const absoluteFilePath = path13.resolve(filePath);
9939
+ const absoluteFilePath = path11.resolve(filePath);
9874
9940
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
9875
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
9941
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9876
9942
  if (!matchesRelative && !matchesProjectRelative) {
9877
9943
  continue;
9878
9944
  }
@@ -9915,43 +9981,43 @@ var Indexer = class {
9915
9981
  };
9916
9982
 
9917
9983
  // src/tools/knowledge-base-paths.ts
9918
- import * as path14 from "path";
9984
+ import * as path12 from "path";
9919
9985
  function resolveConfigPathValue(value, baseDir) {
9920
9986
  const trimmed = value.trim();
9921
9987
  if (!trimmed) {
9922
9988
  return trimmed;
9923
9989
  }
9924
- const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9925
- return path14.normalize(absolutePath);
9990
+ const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
9991
+ return path12.normalize(absolutePath);
9926
9992
  }
9927
9993
  function serializeConfigPathValue(value, baseDir) {
9928
9994
  const trimmed = value.trim();
9929
9995
  if (!trimmed) {
9930
9996
  return trimmed;
9931
9997
  }
9932
- if (!path14.isAbsolute(trimmed)) {
9933
- return normalizePathSeparators(path14.normalize(trimmed));
9998
+ if (!path12.isAbsolute(trimmed)) {
9999
+ return normalizePathSeparators(path12.normalize(trimmed));
9934
10000
  }
9935
- const relativePath = path14.relative(baseDir, trimmed);
9936
- if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9937
- return normalizePathSeparators(path14.normalize(relativePath || "."));
10001
+ const relativePath = path12.relative(baseDir, trimmed);
10002
+ if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
10003
+ return normalizePathSeparators(path12.normalize(relativePath || "."));
9938
10004
  }
9939
- return path14.normalize(trimmed);
10005
+ return path12.normalize(trimmed);
9940
10006
  }
9941
10007
  function resolveKnowledgeBasePath(value, projectRoot3) {
9942
- return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
10008
+ return path12.isAbsolute(value) ? value : path12.resolve(projectRoot3, value);
9943
10009
  }
9944
- function normalizeKnowledgeBasePath2(value, projectRoot3) {
9945
- return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
10010
+ function normalizeKnowledgeBasePath(value, projectRoot3) {
10011
+ return path12.normalize(resolveKnowledgeBasePath(value, projectRoot3));
9946
10012
  }
9947
10013
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
9948
- const normalizedInput = path14.normalize(inputPath);
9949
- return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
10014
+ const normalizedInput = path12.normalize(inputPath);
10015
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath(kb, projectRoot3) === normalizedInput);
9950
10016
  }
9951
10017
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
9952
- const normalizedInput = path14.normalize(inputPath);
10018
+ const normalizedInput = path12.normalize(inputPath);
9953
10019
  return knowledgeBases.findIndex(
9954
- (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
10020
+ (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath(kb, projectRoot3) === normalizedInput
9955
10021
  );
9956
10022
  }
9957
10023
 
@@ -10351,8 +10417,195 @@ ${truncateContent(r.content)}
10351
10417
  }
10352
10418
 
10353
10419
  // src/tools/config-state.ts
10354
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, writeFileSync as writeFileSync4 } from "fs";
10420
+ import { existsSync as existsSync9, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
10355
10421
  import * as path15 from "path";
10422
+
10423
+ // src/config/merger.ts
10424
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
10425
+ import * as path14 from "path";
10426
+
10427
+ // src/config/rebase.ts
10428
+ import * as path13 from "path";
10429
+ function isWithinRoot(rootDir, targetPath) {
10430
+ const relativePath = path13.relative(rootDir, targetPath);
10431
+ return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
10432
+ }
10433
+ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
10434
+ if (!Array.isArray(values)) {
10435
+ return [];
10436
+ }
10437
+ return values.filter((value) => typeof value === "string").map((value) => {
10438
+ const trimmed = value.trim();
10439
+ if (!trimmed) {
10440
+ return trimmed;
10441
+ }
10442
+ if (path13.isAbsolute(trimmed)) {
10443
+ if (isWithinRoot(sourceRoot, trimmed)) {
10444
+ return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
10445
+ }
10446
+ return path13.normalize(trimmed);
10447
+ }
10448
+ const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
10449
+ if (isWithinRoot(sourceRoot, resolvedFromSource)) {
10450
+ return normalizePathSeparators(path13.normalize(trimmed));
10451
+ }
10452
+ return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
10453
+ }).filter(Boolean);
10454
+ }
10455
+
10456
+ // src/config/merger.ts
10457
+ var PROJECT_OVERRIDE_KEYS = [
10458
+ "embeddingProvider",
10459
+ "customProvider",
10460
+ "embeddingModel",
10461
+ "reranker",
10462
+ "include",
10463
+ "exclude",
10464
+ "indexing",
10465
+ "search",
10466
+ "debug",
10467
+ "scope"
10468
+ ];
10469
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
10470
+ function isRecord(value) {
10471
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10472
+ }
10473
+ function isStringArray2(value) {
10474
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
10475
+ }
10476
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
10477
+ if (key in normalizedProjectConfig) {
10478
+ merged[key] = normalizedProjectConfig[key];
10479
+ return;
10480
+ }
10481
+ if (key in globalConfig) {
10482
+ merged[key] = globalConfig[key];
10483
+ }
10484
+ }
10485
+ function mergeUniqueStringArray(values) {
10486
+ return [...new Set(values.map((value) => String(value).trim()))];
10487
+ }
10488
+ function normalizeKnowledgeBasePath2(value) {
10489
+ let normalized = path14.normalize(String(value).trim());
10490
+ const root = path14.parse(normalized).root;
10491
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10492
+ normalized = normalized.slice(0, -1);
10493
+ }
10494
+ return normalized;
10495
+ }
10496
+ function mergeKnowledgeBasePaths(values) {
10497
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath2(value)).filter((value) => value.length > 0))];
10498
+ }
10499
+ function validateConfigLayerShape(rawConfig, filePath) {
10500
+ if (!isRecord(rawConfig)) {
10501
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
10502
+ }
10503
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
10504
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
10505
+ }
10506
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
10507
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
10508
+ }
10509
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
10510
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
10511
+ }
10512
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
10513
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
10514
+ }
10515
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
10516
+ const value = rawConfig[section];
10517
+ if (value !== void 0 && !isRecord(value)) {
10518
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
10519
+ }
10520
+ }
10521
+ return rawConfig;
10522
+ }
10523
+ function loadJsonFile(filePath) {
10524
+ if (!existsSync8(filePath)) {
10525
+ return null;
10526
+ }
10527
+ try {
10528
+ const content = readFileSync7(filePath, "utf-8");
10529
+ return validateConfigLayerShape(JSON.parse(content), filePath);
10530
+ } catch (error) {
10531
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
10532
+ throw error;
10533
+ }
10534
+ const message = error instanceof Error ? error.message : String(error);
10535
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
10536
+ }
10537
+ }
10538
+ function loadProjectConfigLayer(projectRoot3, host = "opencode") {
10539
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
10540
+ const projectConfig = loadJsonFile(projectConfigPath);
10541
+ if (!projectConfig) {
10542
+ return {};
10543
+ }
10544
+ const normalizedConfig = { ...projectConfig };
10545
+ const projectConfigBaseDir = path14.dirname(path14.dirname(projectConfigPath));
10546
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
10547
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10548
+ normalizedConfig.knowledgeBases,
10549
+ projectConfigBaseDir,
10550
+ projectRoot3
10551
+ );
10552
+ }
10553
+ return normalizedConfig;
10554
+ }
10555
+ function loadMergedConfig(projectRoot3, host = "opencode") {
10556
+ const globalConfigPath = resolveGlobalConfigPath(host);
10557
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
10558
+ let globalConfig = null;
10559
+ let globalConfigError = null;
10560
+ try {
10561
+ globalConfig = loadJsonFile(globalConfigPath);
10562
+ } catch (error) {
10563
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
10564
+ }
10565
+ const projectConfig = loadJsonFile(projectConfigPath);
10566
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
10567
+ if (globalConfigError) {
10568
+ if (!projectConfig) {
10569
+ throw globalConfigError;
10570
+ }
10571
+ globalConfig = null;
10572
+ }
10573
+ if (!globalConfig && !projectConfig) {
10574
+ return {};
10575
+ }
10576
+ if (!projectConfig && globalConfig) {
10577
+ return globalConfig;
10578
+ }
10579
+ if (!globalConfig && projectConfig) {
10580
+ return normalizedProjectConfig;
10581
+ }
10582
+ if (!globalConfig || !projectConfig) {
10583
+ return globalConfig ?? normalizedProjectConfig;
10584
+ }
10585
+ const merged = { ...globalConfig };
10586
+ for (const key of PROJECT_OVERRIDE_KEYS) {
10587
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
10588
+ }
10589
+ if (projectConfig) {
10590
+ for (const key of Object.keys(projectConfig)) {
10591
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
10592
+ continue;
10593
+ }
10594
+ merged[key] = normalizedProjectConfig[key];
10595
+ }
10596
+ }
10597
+ const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
10598
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
10599
+ const allKbs = [...globalKbs, ...projectKbs];
10600
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
10601
+ const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
10602
+ const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
10603
+ const allAdditional = [...globalAdditional, ...projectAdditional];
10604
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
10605
+ return merged;
10606
+ }
10607
+
10608
+ // src/tools/config-state.ts
10356
10609
  function normalizeKnowledgeBasePaths(config, projectRoot3) {
10357
10610
  const normalized = { ...config };
10358
10611
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10382,7 +10635,7 @@ function saveConfig(projectRoot3, config, host = "opencode") {
10382
10635
  const configDir = path15.dirname(configPath);
10383
10636
  const configBaseDir = path15.dirname(configDir);
10384
10637
  if (!existsSync9(configDir)) {
10385
- mkdirSync4(configDir, { recursive: true });
10638
+ mkdirSync3(configDir, { recursive: true });
10386
10639
  }
10387
10640
  const serializableConfig = { ...config };
10388
10641
  if (Array.isArray(serializableConfig.knowledgeBases)) {
@@ -10390,7 +10643,7 @@ function saveConfig(projectRoot3, config, host = "opencode") {
10390
10643
  (kb) => serializeConfigPathValue(kb, configBaseDir)
10391
10644
  );
10392
10645
  }
10393
- writeFileSync4(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10646
+ writeFileSync3(configPath, JSON.stringify(serializableConfig, null, 2) + "\n", "utf-8");
10394
10647
  }
10395
10648
 
10396
10649
  // src/tools/operations.ts
@@ -10450,15 +10703,6 @@ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = pa
10450
10703
  indexerCache.set(key, indexer);
10451
10704
  configCache.set(key, config);
10452
10705
  }
10453
- function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
10454
- const root = getProjectRoot(projectRoot3, host);
10455
- const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10456
- if (existsSync10(localIndexPath)) {
10457
- return false;
10458
- }
10459
- const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
10460
- return inheritedIndexPath !== null;
10461
- }
10462
10706
  async function searchCodebase(projectRoot3, host, query, options = {}) {
10463
10707
  const indexer = getIndexerForProject(projectRoot3, host);
10464
10708
  return indexer.search(query, options.limit, {
@@ -10508,9 +10752,7 @@ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
10508
10752
  }
10509
10753
  async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10510
10754
  const root = getProjectRoot(projectRoot3, host);
10511
- const key = getIndexerCacheKey(root, host);
10512
- let indexer = getIndexerForProject(root, host);
10513
- const runtimeConfig = configCache.get(key);
10755
+ const indexer = getIndexerForProject(root, host);
10514
10756
  try {
10515
10757
  if (args.estimateOnly) {
10516
10758
  return { kind: "estimate", estimate: await indexer.estimateCost() };
@@ -10531,19 +10773,7 @@ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10531
10773
  return Promise.resolve();
10532
10774
  });
10533
10775
  };
10534
- let stats;
10535
- if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10536
- const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10537
- stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10538
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10539
- refreshIndexerForDirectory(root, host, runtimeConfig);
10540
- indexer = getIndexerForProject(root, host);
10541
- return runIndex(indexer);
10542
- }, { completeRecoveries: false });
10543
- } else {
10544
- stats = await runIndex(indexer);
10545
- }
10546
- return { kind: "stats", stats };
10776
+ return { kind: "stats", stats: await runIndex(indexer) };
10547
10777
  } catch (error) {
10548
10778
  const busyResult = getIndexBusyResult(error);
10549
10779
  if (!busyResult) throw error;