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.
@@ -678,7 +678,8 @@ var DEFAULT_INCLUDE = [
678
678
  "**/*.{sh,bash,zsh}",
679
679
  "**/*.{txt,html,htm}",
680
680
  "**/*.zig",
681
- "**/*.gd"
681
+ "**/*.gd",
682
+ "**/*.metal"
682
683
  ];
683
684
  var DEFAULT_EXCLUDE = [
684
685
  "**/node_modules/**",
@@ -869,6 +870,9 @@ function isValidProvider(value) {
869
870
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS).includes(value);
870
871
  }
871
872
  function isValidModel(value, provider) {
873
+ if (typeof value === "string" && provider === "ollama" && value.trim().length > 0) {
874
+ return true;
875
+ }
872
876
  return typeof value === "string" && Object.keys(EMBEDDING_MODELS[provider]).includes(value);
873
877
  }
874
878
  function isValidScope(value) {
@@ -1068,6 +1072,9 @@ function loadOpenCodeAuth() {
1068
1072
  return {};
1069
1073
  }
1070
1074
  async function detectEmbeddingProvider(preferredProvider, model) {
1075
+ if (preferredProvider === "ollama") {
1076
+ return detectOllamaProvider(model);
1077
+ }
1071
1078
  const credentials = await getProviderCredentials(preferredProvider);
1072
1079
  if (credentials) {
1073
1080
  if (!model) {
@@ -1083,10 +1090,16 @@ async function detectEmbeddingProvider(preferredProvider, model) {
1083
1090
  );
1084
1091
  }
1085
1092
  const providerModels = EMBEDDING_MODELS[preferredProvider];
1093
+ const modelInfo = Object.values(providerModels).find((candidate) => candidate.model === model);
1094
+ if (!modelInfo) {
1095
+ throw new Error(
1096
+ `Model '${model}' is not supported by provider '${preferredProvider}'`
1097
+ );
1098
+ }
1086
1099
  return {
1087
1100
  provider: preferredProvider,
1088
1101
  credentials,
1089
- modelInfo: providerModels[model]
1102
+ modelInfo
1090
1103
  };
1091
1104
  }
1092
1105
  throw new Error(
@@ -1095,6 +1108,13 @@ async function detectEmbeddingProvider(preferredProvider, model) {
1095
1108
  }
1096
1109
  async function tryDetectProvider() {
1097
1110
  for (const provider of autoDetectProviders) {
1111
+ if (provider === "ollama") {
1112
+ const ollamaProvider = await tryDetectOllamaProvider();
1113
+ if (ollamaProvider) {
1114
+ return ollamaProvider;
1115
+ }
1116
+ continue;
1117
+ }
1098
1118
  const credentials = await getProviderCredentials(provider);
1099
1119
  if (credentials) {
1100
1120
  return {
@@ -1162,32 +1182,105 @@ function getGoogleCredentials() {
1162
1182
  }
1163
1183
  return null;
1164
1184
  }
1165
- async function getOllamaCredentials() {
1166
- const baseUrl = process.env.OLLAMA_HOST || "http://localhost:11434";
1185
+ async function fetchOllama(url, init) {
1186
+ const controller = new AbortController();
1187
+ const timeoutId = setTimeout(() => controller.abort(), 2e3);
1167
1188
  try {
1168
- const controller = new AbortController();
1169
- const timeoutId = setTimeout(() => controller.abort(), 2e3);
1170
- const response = await fetch(`${baseUrl}/api/tags`, {
1171
- signal: controller.signal
1172
- });
1189
+ return await fetch(url, { ...init, signal: controller.signal });
1190
+ } finally {
1173
1191
  clearTimeout(timeoutId);
1192
+ }
1193
+ }
1194
+ async function getOllamaCredentials() {
1195
+ const baseUrl = (process.env.OLLAMA_HOST || "http://localhost:11434").replace(/\/+$/, "");
1196
+ try {
1197
+ const response = await fetchOllama(`${baseUrl}/api/tags`);
1174
1198
  if (response.ok) {
1175
- const data = await response.json();
1176
- const hasEmbeddingModel = data.models?.some(
1177
- (m) => m.name.includes("nomic-embed") || m.name.includes("mxbai-embed") || m.name.includes("all-minilm")
1178
- );
1179
- if (hasEmbeddingModel) {
1180
- return {
1181
- provider: "ollama",
1182
- baseUrl
1183
- };
1184
- }
1199
+ return {
1200
+ provider: "ollama",
1201
+ baseUrl
1202
+ };
1185
1203
  }
1186
1204
  } catch {
1187
1205
  return null;
1188
1206
  }
1189
1207
  return null;
1190
1208
  }
1209
+ function findCatalogOllamaModel(model) {
1210
+ const stableName = model.endsWith(":latest") ? model.slice(0, -":latest".length) : model;
1211
+ return Object.values(EMBEDDING_MODELS.ollama).find((candidate) => candidate.model === stableName) ?? null;
1212
+ }
1213
+ function getPositiveIntegerMetadata(modelInfo, suffix) {
1214
+ const values = Object.entries(modelInfo).filter(([key]) => key.endsWith(suffix)).map(([, value]) => value).filter((value) => typeof value === "number" && Number.isInteger(value) && value > 0);
1215
+ return values.length === 1 ? values[0] : null;
1216
+ }
1217
+ async function fetchOllamaModelInfo(credentials, model) {
1218
+ const response = await fetchOllama(`${credentials.baseUrl}/api/show`, {
1219
+ method: "POST",
1220
+ headers: { "Content-Type": "application/json" },
1221
+ body: JSON.stringify({ model })
1222
+ });
1223
+ if (!response.ok) {
1224
+ return null;
1225
+ }
1226
+ const data = await response.json();
1227
+ if (!data.capabilities?.includes("embedding") || !data.model_info) {
1228
+ return null;
1229
+ }
1230
+ const dimensions = getPositiveIntegerMetadata(data.model_info, ".embedding_length");
1231
+ const maxTokens = getPositiveIntegerMetadata(data.model_info, ".context_length");
1232
+ if (!dimensions || !maxTokens) {
1233
+ return null;
1234
+ }
1235
+ return {
1236
+ provider: "ollama",
1237
+ model,
1238
+ dimensions,
1239
+ maxTokens,
1240
+ costPer1MTokens: 0
1241
+ };
1242
+ }
1243
+ async function listOllamaModels(credentials) {
1244
+ const response = await fetchOllama(`${credentials.baseUrl}/api/tags`);
1245
+ if (!response.ok) {
1246
+ return [];
1247
+ }
1248
+ const data = await response.json();
1249
+ return [...new Set((data.models ?? []).map((entry) => entry.name ?? entry.model).filter((name) => typeof name === "string" && name.trim().length > 0))];
1250
+ }
1251
+ async function detectOllamaProvider(model) {
1252
+ const credentials = await getOllamaCredentials();
1253
+ if (!credentials) {
1254
+ throw new Error("Preferred provider 'ollama' is not configured or authenticated");
1255
+ }
1256
+ const requestedModel = model?.trim();
1257
+ if (requestedModel) {
1258
+ const catalogModel = findCatalogOllamaModel(requestedModel);
1259
+ if (catalogModel) {
1260
+ return { provider: "ollama", credentials, modelInfo: catalogModel };
1261
+ }
1262
+ }
1263
+ const candidates = requestedModel ? [requestedModel] : await listOllamaModels(credentials);
1264
+ for (const candidate of candidates) {
1265
+ const modelInfo = await fetchOllamaModelInfo(credentials, candidate);
1266
+ if (modelInfo) {
1267
+ return {
1268
+ provider: "ollama",
1269
+ credentials,
1270
+ modelInfo: findCatalogOllamaModel(candidate) ?? modelInfo
1271
+ };
1272
+ }
1273
+ }
1274
+ const detail = model ? `Model '${model}' is not installed or is not embedding-capable` : "No installed embedding-capable Ollama model was found";
1275
+ throw new Error(detail);
1276
+ }
1277
+ async function tryDetectOllamaProvider() {
1278
+ try {
1279
+ return await detectOllamaProvider();
1280
+ } catch {
1281
+ return null;
1282
+ }
1283
+ }
1191
1284
  function getProviderDisplayName(provider) {
1192
1285
  switch (provider) {
1193
1286
  case "github-copilot":
@@ -1340,10 +1433,6 @@ function formatPrImpact(result) {
1340
1433
  var import_fs10 = require("fs");
1341
1434
  var path16 = __toESM(require("path"), 1);
1342
1435
 
1343
- // src/config/merger.ts
1344
- var import_fs5 = require("fs");
1345
- var path7 = __toESM(require("path"), 1);
1346
-
1347
1436
  // src/config/paths.ts
1348
1437
  var import_fs4 = require("fs");
1349
1438
  var os2 = __toESM(require("os"), 1);
@@ -1516,16 +1605,6 @@ function resolveWorktreeFallbackPath(projectRoot3, relativePath) {
1516
1605
  const fallbackPath = path4.join(mainRepoRoot, relativePath);
1517
1606
  return (0, import_fs4.existsSync)(fallbackPath) ? fallbackPath : null;
1518
1607
  }
1519
- function resolveWorktreeFallbackProjectIndexPath(projectRoot3, host) {
1520
- const inheritedHostPath = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1521
- if (inheritedHostPath) {
1522
- return inheritedHostPath;
1523
- }
1524
- if (host !== "opencode") {
1525
- return resolveWorktreeFallbackPath(projectRoot3, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
1526
- }
1527
- return null;
1528
- }
1529
1608
  function getHostProjectConfigRelativePath(host) {
1530
1609
  return getProjectConfigRelativePath(host);
1531
1610
  }
@@ -1630,6 +1709,9 @@ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1630
1709
  if (hasHostProjectConfig(projectRoot3, host)) {
1631
1710
  return localIndexPath;
1632
1711
  }
1712
+ if (resolveWorktreeMainRepoRoot(projectRoot3)) {
1713
+ return localIndexPath;
1714
+ }
1633
1715
  const hostFallback = resolveWorktreeFallbackPath(projectRoot3, getProjectIndexRelativePath(host));
1634
1716
  if (hostFallback) {
1635
1717
  return hostFallback;
@@ -1643,210 +1725,9 @@ function resolveProjectIndexPath(projectRoot3, scope, host = "opencode") {
1643
1725
  return localIndexPath;
1644
1726
  }
1645
1727
 
1646
- // src/config/rebase.ts
1647
- var path6 = __toESM(require("path"), 1);
1648
-
1649
- // src/utils/paths.ts
1650
- var path5 = __toESM(require("path"), 1);
1651
- function normalizePathSeparators(value) {
1652
- return value.replace(/\\/g, "/");
1653
- }
1654
- function isHiddenPathSegment(part) {
1655
- return part.startsWith(".") && part !== "." && part !== "..";
1656
- }
1657
- function isBuildPathSegment(part) {
1658
- return part.toLowerCase().includes("build");
1659
- }
1660
-
1661
- // src/config/rebase.ts
1662
- function isWithinRoot(rootDir, targetPath) {
1663
- const relativePath = path6.relative(rootDir, targetPath);
1664
- return relativePath === "" || !relativePath.startsWith("..") && !path6.isAbsolute(relativePath);
1665
- }
1666
- function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
1667
- if (!Array.isArray(values)) {
1668
- return [];
1669
- }
1670
- return values.filter((value) => typeof value === "string").map((value) => {
1671
- const trimmed = value.trim();
1672
- if (!trimmed) {
1673
- return trimmed;
1674
- }
1675
- if (path6.isAbsolute(trimmed)) {
1676
- if (isWithinRoot(sourceRoot, trimmed)) {
1677
- return normalizePathSeparators(path6.normalize(path6.relative(sourceRoot, trimmed) || "."));
1678
- }
1679
- return path6.normalize(trimmed);
1680
- }
1681
- const resolvedFromSource = path6.resolve(sourceRoot, trimmed);
1682
- if (isWithinRoot(sourceRoot, resolvedFromSource)) {
1683
- return normalizePathSeparators(path6.normalize(trimmed));
1684
- }
1685
- return normalizePathSeparators(path6.normalize(path6.relative(targetRoot, resolvedFromSource)));
1686
- }).filter(Boolean);
1687
- }
1688
-
1689
- // src/config/merger.ts
1690
- var PROJECT_OVERRIDE_KEYS = [
1691
- "embeddingProvider",
1692
- "customProvider",
1693
- "embeddingModel",
1694
- "reranker",
1695
- "include",
1696
- "exclude",
1697
- "indexing",
1698
- "search",
1699
- "debug",
1700
- "scope"
1701
- ];
1702
- var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
1703
- function isRecord(value) {
1704
- return typeof value === "object" && value !== null && !Array.isArray(value);
1705
- }
1706
- function isStringArray2(value) {
1707
- return Array.isArray(value) && value.every((item) => typeof item === "string");
1708
- }
1709
- function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
1710
- if (key in normalizedProjectConfig) {
1711
- merged[key] = normalizedProjectConfig[key];
1712
- return;
1713
- }
1714
- if (key in globalConfig) {
1715
- merged[key] = globalConfig[key];
1716
- }
1717
- }
1718
- function mergeUniqueStringArray(values) {
1719
- return [...new Set(values.map((value) => String(value).trim()))];
1720
- }
1721
- function normalizeKnowledgeBasePath(value) {
1722
- let normalized = path7.normalize(String(value).trim());
1723
- const root = path7.parse(normalized).root;
1724
- while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
1725
- normalized = normalized.slice(0, -1);
1726
- }
1727
- return normalized;
1728
- }
1729
- function mergeKnowledgeBasePaths(values) {
1730
- return [...new Set(values.map((value) => normalizeKnowledgeBasePath(value)).filter((value) => value.length > 0))];
1731
- }
1732
- function validateConfigLayerShape(rawConfig, filePath) {
1733
- if (!isRecord(rawConfig)) {
1734
- throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
1735
- }
1736
- if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
1737
- throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
1738
- }
1739
- if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
1740
- throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
1741
- }
1742
- if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
1743
- throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
1744
- }
1745
- if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
1746
- throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
1747
- }
1748
- for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
1749
- const value = rawConfig[section];
1750
- if (value !== void 0 && !isRecord(value)) {
1751
- throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
1752
- }
1753
- }
1754
- return rawConfig;
1755
- }
1756
- function loadJsonFile(filePath) {
1757
- if (!(0, import_fs5.existsSync)(filePath)) {
1758
- return null;
1759
- }
1760
- try {
1761
- const content = (0, import_fs5.readFileSync)(filePath, "utf-8");
1762
- return validateConfigLayerShape(JSON.parse(content), filePath);
1763
- } catch (error) {
1764
- if (error instanceof Error && error.message.startsWith("Config file ")) {
1765
- throw error;
1766
- }
1767
- const message = error instanceof Error ? error.message : String(error);
1768
- throw new Error(`Failed to load config file ${filePath}: ${message}`);
1769
- }
1770
- }
1771
- function materializeLocalProjectConfig(projectRoot3, config, host = "opencode") {
1772
- const localConfigPath = resolveWritableProjectConfigPath(projectRoot3, host);
1773
- (0, import_fs5.mkdirSync)(path7.dirname(localConfigPath), { recursive: true });
1774
- (0, import_fs5.writeFileSync)(localConfigPath, JSON.stringify(config, null, 2), "utf-8");
1775
- return localConfigPath;
1776
- }
1777
- function loadProjectConfigLayer(projectRoot3, host = "opencode") {
1778
- const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1779
- const projectConfig = loadJsonFile(projectConfigPath);
1780
- if (!projectConfig) {
1781
- return {};
1782
- }
1783
- const normalizedConfig = { ...projectConfig };
1784
- const projectConfigBaseDir = path7.dirname(path7.dirname(projectConfigPath));
1785
- if (Array.isArray(normalizedConfig.knowledgeBases)) {
1786
- normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
1787
- normalizedConfig.knowledgeBases,
1788
- projectConfigBaseDir,
1789
- projectRoot3
1790
- );
1791
- }
1792
- return normalizedConfig;
1793
- }
1794
- function loadMergedConfig(projectRoot3, host = "opencode") {
1795
- const globalConfigPath = resolveGlobalConfigPath(host);
1796
- const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
1797
- let globalConfig = null;
1798
- let globalConfigError = null;
1799
- try {
1800
- globalConfig = loadJsonFile(globalConfigPath);
1801
- } catch (error) {
1802
- globalConfigError = error instanceof Error ? error : new Error(String(error));
1803
- }
1804
- const projectConfig = loadJsonFile(projectConfigPath);
1805
- const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
1806
- if (globalConfigError) {
1807
- if (!projectConfig) {
1808
- throw globalConfigError;
1809
- }
1810
- globalConfig = null;
1811
- }
1812
- if (!globalConfig && !projectConfig) {
1813
- return {};
1814
- }
1815
- if (!projectConfig && globalConfig) {
1816
- return globalConfig;
1817
- }
1818
- if (!globalConfig && projectConfig) {
1819
- return normalizedProjectConfig;
1820
- }
1821
- if (!globalConfig || !projectConfig) {
1822
- return globalConfig ?? normalizedProjectConfig;
1823
- }
1824
- const merged = { ...globalConfig };
1825
- for (const key of PROJECT_OVERRIDE_KEYS) {
1826
- applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
1827
- }
1828
- if (projectConfig) {
1829
- for (const key of Object.keys(projectConfig)) {
1830
- if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
1831
- continue;
1832
- }
1833
- merged[key] = normalizedProjectConfig[key];
1834
- }
1835
- }
1836
- const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
1837
- const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
1838
- const allKbs = [...globalKbs, ...projectKbs];
1839
- merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
1840
- const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
1841
- const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
1842
- const allAdditional = [...globalAdditional, ...projectAdditional];
1843
- merged.additionalInclude = mergeUniqueStringArray(allAdditional);
1844
- return merged;
1845
- }
1846
-
1847
1728
  // src/indexer/index.ts
1848
- var import_fs8 = require("fs");
1849
- var path13 = __toESM(require("path"), 1);
1729
+ var import_fs7 = require("fs");
1730
+ var path11 = __toESM(require("path"), 1);
1850
1731
  var import_perf_hooks = require("perf_hooks");
1851
1732
  var import_child_process3 = require("child_process");
1852
1733
  var import_util3 = require("util");
@@ -3197,6 +3078,7 @@ var GoogleEmbeddingProvider = class _GoogleEmbeddingProvider extends BaseEmbeddi
3197
3078
  // src/embeddings/providers/ollama.ts
3198
3079
  var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddingProvider {
3199
3080
  static MIN_TRUNCATION_CHARS = 512;
3081
+ static REQUEST_TIMEOUT_MS = 12e4;
3200
3082
  constructor(credentials, modelInfo) {
3201
3083
  super(credentials, modelInfo);
3202
3084
  }
@@ -3271,22 +3153,45 @@ var OllamaEmbeddingProvider = class _OllamaEmbeddingProvider extends BaseEmbeddi
3271
3153
  }
3272
3154
  }
3273
3155
  async embedSingle(text3) {
3274
- const response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3275
- method: "POST",
3276
- headers: {
3277
- "Content-Type": "application/json"
3278
- },
3279
- body: JSON.stringify({
3280
- model: this.modelInfo.model,
3281
- prompt: text3,
3282
- truncate: false
3283
- })
3284
- });
3156
+ const controller = new AbortController();
3157
+ const timeout = setTimeout(
3158
+ () => controller.abort(),
3159
+ _OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS
3160
+ );
3161
+ let response;
3162
+ try {
3163
+ response = await fetch(`${this.credentials.baseUrl}/api/embeddings`, {
3164
+ method: "POST",
3165
+ headers: {
3166
+ "Content-Type": "application/json"
3167
+ },
3168
+ body: JSON.stringify({
3169
+ model: this.modelInfo.model,
3170
+ prompt: text3,
3171
+ truncate: false
3172
+ }),
3173
+ signal: controller.signal
3174
+ });
3175
+ } catch (error) {
3176
+ if (error instanceof Error && error.name === "AbortError") {
3177
+ throw new Error(
3178
+ `Ollama embedding request timed out after ${_OllamaEmbeddingProvider.REQUEST_TIMEOUT_MS}ms`
3179
+ );
3180
+ }
3181
+ throw error;
3182
+ } finally {
3183
+ clearTimeout(timeout);
3184
+ }
3285
3185
  if (!response.ok) {
3286
3186
  const error = (await response.text()).slice(0, 500);
3287
3187
  throw new Error(`Ollama embedding API error: ${response.status} - ${error}`);
3288
3188
  }
3289
3189
  const data = await response.json();
3190
+ if (!Array.isArray(data.embedding) || data.embedding.length !== this.modelInfo.dimensions || data.embedding.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
3191
+ throw new Error(
3192
+ `Ollama returned an invalid embedding; expected ${this.modelInfo.dimensions} finite dimensions`
3193
+ );
3194
+ }
3290
3195
  return {
3291
3196
  embedding: data.embedding,
3292
3197
  tokensUsed: this.estimateTokens(text3)
@@ -3433,9 +3338,23 @@ var SiliconFlowReranker = class {
3433
3338
  };
3434
3339
 
3435
3340
  // src/utils/files.ts
3436
- var import_ignore = __toESM(require_ignore(), 1);
3437
- var import_fs6 = require("fs");
3438
- var path8 = __toESM(require("path"), 1);
3341
+ var import_ignore = __toESM(require_ignore(), 1);
3342
+ var import_fs5 = require("fs");
3343
+ var path6 = __toESM(require("path"), 1);
3344
+
3345
+ // src/utils/paths.ts
3346
+ var path5 = __toESM(require("path"), 1);
3347
+ function normalizePathSeparators(value) {
3348
+ return value.replace(/\\/g, "/");
3349
+ }
3350
+ function isHiddenPathSegment(part) {
3351
+ return part.startsWith(".") && part !== "." && part !== "..";
3352
+ }
3353
+ function isBuildPathSegment(part) {
3354
+ return part.toLowerCase().includes("build");
3355
+ }
3356
+
3357
+ // src/utils/files.ts
3439
3358
  function createIgnoreFilter(projectRoot3) {
3440
3359
  const ig = (0, import_ignore.default)();
3441
3360
  const defaultIgnores = [
@@ -3457,9 +3376,9 @@ function createIgnoreFilter(projectRoot3) {
3457
3376
  "**/*build*/**"
3458
3377
  ];
3459
3378
  ig.add(defaultIgnores);
3460
- const gitignorePath = path8.join(projectRoot3, ".gitignore");
3461
- if ((0, import_fs6.existsSync)(gitignorePath)) {
3462
- const gitignoreContent = (0, import_fs6.readFileSync)(gitignorePath, "utf-8");
3379
+ const gitignorePath = path6.join(projectRoot3, ".gitignore");
3380
+ if ((0, import_fs5.existsSync)(gitignorePath)) {
3381
+ const gitignoreContent = (0, import_fs5.readFileSync)(gitignorePath, "utf-8");
3463
3382
  ig.add(gitignoreContent);
3464
3383
  }
3465
3384
  return ig;
@@ -3480,12 +3399,12 @@ function matchGlob(filePath, pattern) {
3480
3399
  return regex.test(filePath);
3481
3400
  }
3482
3401
  async function* walkDirectory(dir, projectRoot3, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped, options, currentDepth = 0) {
3483
- const entries = await import_fs6.promises.readdir(dir, { withFileTypes: true });
3402
+ const entries = await import_fs5.promises.readdir(dir, { withFileTypes: true });
3484
3403
  const filesInDir = [];
3485
3404
  const subdirs = [];
3486
3405
  for (const entry of entries) {
3487
- const fullPath = path8.join(dir, entry.name);
3488
- const relativePath = path8.relative(projectRoot3, fullPath);
3406
+ const fullPath = path6.join(dir, entry.name);
3407
+ const relativePath = path6.relative(projectRoot3, fullPath);
3489
3408
  if (isHiddenPathSegment(entry.name)) {
3490
3409
  if (entry.isDirectory()) {
3491
3410
  skipped.push({ path: relativePath, reason: "excluded" });
@@ -3505,7 +3424,7 @@ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePattern
3505
3424
  if (entry.isDirectory()) {
3506
3425
  subdirs.push({ fullPath, relativePath });
3507
3426
  } else if (entry.isFile()) {
3508
- const stat = await import_fs6.promises.stat(fullPath);
3427
+ const stat = await import_fs5.promises.stat(fullPath);
3509
3428
  if (stat.size > maxFileSize) {
3510
3429
  skipped.push({ path: relativePath, reason: "too_large" });
3511
3430
  continue;
@@ -3534,7 +3453,7 @@ async function* walkDirectory(dir, projectRoot3, includePatterns, excludePattern
3534
3453
  yield f;
3535
3454
  }
3536
3455
  for (let i = options.maxFilesPerDirectory; i < filesInDir.length; i++) {
3537
- skipped.push({ path: path8.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3456
+ skipped.push({ path: path6.relative(projectRoot3, filesInDir[i].path), reason: "excluded" });
3538
3457
  }
3539
3458
  const canRecurse = options.maxDepth === -1 || currentDepth < options.maxDepth;
3540
3459
  if (canRecurse) {
@@ -3574,14 +3493,14 @@ async function collectFiles(projectRoot3, includePatterns, excludePatterns, maxF
3574
3493
  if (additionalRoots && additionalRoots.length > 0) {
3575
3494
  const normalizedRoots = /* @__PURE__ */ new Set();
3576
3495
  for (const kbRoot of additionalRoots) {
3577
- const resolved = path8.normalize(
3578
- path8.isAbsolute(kbRoot) ? kbRoot : path8.resolve(projectRoot3, kbRoot)
3496
+ const resolved = path6.normalize(
3497
+ path6.isAbsolute(kbRoot) ? kbRoot : path6.resolve(projectRoot3, kbRoot)
3579
3498
  );
3580
3499
  normalizedRoots.add(resolved);
3581
3500
  }
3582
3501
  for (const resolvedKbRoot of normalizedRoots) {
3583
3502
  try {
3584
- const stat = await import_fs6.promises.stat(resolvedKbRoot);
3503
+ const stat = await import_fs5.promises.stat(resolvedKbRoot);
3585
3504
  if (!stat.isDirectory()) {
3586
3505
  skipped.push({ path: resolvedKbRoot, reason: "excluded" });
3587
3506
  continue;
@@ -3922,7 +3841,7 @@ function initializeLogger(config) {
3922
3841
  }
3923
3842
 
3924
3843
  // src/native/index.ts
3925
- var path9 = __toESM(require("path"), 1);
3844
+ var path7 = __toESM(require("path"), 1);
3926
3845
  var os3 = __toESM(require("os"), 1);
3927
3846
  var module2 = __toESM(require("module"), 1);
3928
3847
  var import_url = require("url");
@@ -3947,19 +3866,19 @@ function getNativeBinding() {
3947
3866
  let currentDir;
3948
3867
  let requireTarget;
3949
3868
  if (typeof import_meta !== "undefined" && import_meta.url) {
3950
- currentDir = path9.dirname((0, import_url.fileURLToPath)(import_meta.url));
3869
+ currentDir = path7.dirname((0, import_url.fileURLToPath)(import_meta.url));
3951
3870
  requireTarget = import_meta.url;
3952
3871
  } else if (typeof __dirname !== "undefined") {
3953
3872
  currentDir = __dirname;
3954
3873
  requireTarget = __filename;
3955
3874
  } else {
3956
3875
  currentDir = process.cwd();
3957
- requireTarget = path9.join(currentDir, "index.js");
3876
+ requireTarget = path7.join(currentDir, "index.js");
3958
3877
  }
3959
3878
  const normalizedDir = currentDir.replace(/\\/g, "/");
3960
- const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path9.join("src", "native"));
3961
- const packageRoot = isDevMode ? path9.resolve(currentDir, "../..") : path9.resolve(currentDir, "..");
3962
- const nativePath = path9.join(packageRoot, "native", bindingName);
3879
+ const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path7.join("src", "native"));
3880
+ const packageRoot = isDevMode ? path7.resolve(currentDir, "../..") : path7.resolve(currentDir, "..");
3881
+ const nativePath = path7.join(packageRoot, "native", bindingName);
3963
3882
  const require2 = module2.createRequire(requireTarget);
3964
3883
  return require2(nativePath);
3965
3884
  }
@@ -4045,7 +3964,9 @@ function mapChunk(c) {
4045
3964
  return {
4046
3965
  content: c.content,
4047
3966
  startLine: c.startLine ?? c.start_line,
3967
+ startCol: c.startCol ?? c.start_col,
4048
3968
  endLine: c.endLine ?? c.end_line,
3969
+ endCol: c.endCol ?? c.end_col,
4049
3970
  chunkType: c.chunkType ?? c.chunk_type,
4050
3971
  name: c.name ?? void 0,
4051
3972
  language: c.language
@@ -4166,6 +4087,7 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4166
4087
  javascript: "JavaScript",
4167
4088
  python: "Python",
4168
4089
  rust: "Rust",
4090
+ swift: "Swift",
4169
4091
  go: "Go",
4170
4092
  java: "Java"
4171
4093
  };
@@ -4174,7 +4096,16 @@ function getEmbeddingHeaderParts(chunk, filePath) {
4174
4096
  function: "function",
4175
4097
  arrow_function: "arrow function",
4176
4098
  method_definition: "method",
4099
+ method_declaration: "method",
4100
+ protocol_function_declaration: "protocol requirement",
4101
+ init_declaration: "initializer",
4102
+ deinit_declaration: "deinitializer",
4103
+ subscript_declaration: "subscript",
4177
4104
  class_declaration: "class",
4105
+ actor_declaration: "actor",
4106
+ extension_declaration: "extension",
4107
+ protocol_declaration: "protocol",
4108
+ struct_declaration: "struct",
4178
4109
  interface_declaration: "interface",
4179
4110
  type_alias_declaration: "type alias",
4180
4111
  enum_declaration: "enum",
@@ -4692,7 +4623,7 @@ var Database = class _Database {
4692
4623
  };
4693
4624
 
4694
4625
  // src/tools/changed-files.ts
4695
- var path10 = __toESM(require("path"), 1);
4626
+ var path8 = __toESM(require("path"), 1);
4696
4627
  var import_child_process = require("child_process");
4697
4628
  var import_util = require("util");
4698
4629
  var execFileAsync = (0, import_util.promisify)(import_child_process.execFile);
@@ -4780,8 +4711,8 @@ function normalizeFiles(rawFiles, projectRoot3) {
4780
4711
  for (const raw of rawFiles) {
4781
4712
  const trimmed = raw.trim();
4782
4713
  if (!trimmed) continue;
4783
- const absolute = path10.resolve(projectRoot3, trimmed);
4784
- const relative7 = path10.relative(projectRoot3, absolute);
4714
+ const absolute = path8.resolve(projectRoot3, trimmed);
4715
+ const relative7 = path8.relative(projectRoot3, absolute);
4785
4716
  const cleaned = relative7.startsWith("./") ? relative7.slice(2) : relative7;
4786
4717
  if (!seen.has(cleaned)) {
4787
4718
  seen.add(cleaned);
@@ -4793,7 +4724,7 @@ function normalizeFiles(rawFiles, projectRoot3) {
4793
4724
 
4794
4725
  // src/indexer/git-blame.ts
4795
4726
  var import_child_process2 = require("child_process");
4796
- var path11 = __toESM(require("path"), 1);
4727
+ var path9 = __toESM(require("path"), 1);
4797
4728
  var import_util2 = require("util");
4798
4729
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process2.execFile);
4799
4730
  function parseGitBlamePorcelain(output) {
@@ -4831,7 +4762,7 @@ function parseGitBlamePorcelain(output) {
4831
4762
  return Array.from(commits.values()).filter((commit) => commit.lines > 0).sort((a, b) => b.lines - a.lines || b.committedAt - a.committedAt)[0];
4832
4763
  }
4833
4764
  async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4834
- const relativePath = path11.relative(projectRoot3, filePath);
4765
+ const relativePath = path9.relative(projectRoot3, filePath);
4835
4766
  try {
4836
4767
  const { stdout } = await execFileAsync2(
4837
4768
  "git",
@@ -4846,9 +4777,9 @@ async function getChunkGitBlame(projectRoot3, filePath, startLine, endLine) {
4846
4777
 
4847
4778
  // src/indexer/index-lock.ts
4848
4779
  var import_crypto = require("crypto");
4849
- var import_fs7 = require("fs");
4780
+ var import_fs6 = require("fs");
4850
4781
  var os4 = __toESM(require("os"), 1);
4851
- var path12 = __toESM(require("path"), 1);
4782
+ var path10 = __toESM(require("path"), 1);
4852
4783
  var OWNER_FILE_NAME = "owner.json";
4853
4784
  var RECLAIM_DIRECTORY_NAME = "reclaiming";
4854
4785
  var RECOVERY_MARKER_PREFIX = "indexing.lock.recovery.";
@@ -4905,7 +4836,7 @@ function parseReclaimOwner(value) {
4905
4836
  }
4906
4837
  function readJsonDirectory(directoryPath, parser) {
4907
4838
  try {
4908
- return parser(JSON.parse((0, import_fs7.readFileSync)(path12.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4839
+ return parser(JSON.parse((0, import_fs6.readFileSync)(path10.join(directoryPath, OWNER_FILE_NAME), "utf-8")));
4909
4840
  } catch {
4910
4841
  return null;
4911
4842
  }
@@ -4921,7 +4852,7 @@ function readRecoveryOwner(markerPath) {
4921
4852
  }
4922
4853
  function readLegacyOwner(lockPath) {
4923
4854
  try {
4924
- const parsed = JSON.parse((0, import_fs7.readFileSync)(lockPath, "utf-8"));
4855
+ const parsed = JSON.parse((0, import_fs6.readFileSync)(lockPath, "utf-8"));
4925
4856
  if (!Number.isInteger(parsed.pid) || Number(parsed.pid) <= 0) return null;
4926
4857
  if (typeof parsed.startedAt !== "string" || Number.isNaN(Date.parse(parsed.startedAt))) return null;
4927
4858
  return {
@@ -4956,23 +4887,23 @@ function sameReclaimOwner(left, right) {
4956
4887
  function publishJsonDirectory(finalPath, value) {
4957
4888
  const candidatePath = `${finalPath}.candidate.${process.pid}.${(0, import_crypto.randomUUID)()}`;
4958
4889
  try {
4959
- (0, import_fs7.mkdirSync)(candidatePath, { mode: 448 });
4890
+ (0, import_fs6.mkdirSync)(candidatePath, { mode: 448 });
4960
4891
  } catch (error) {
4961
4892
  if (getErrorCode(error) === "ENOENT") return false;
4962
4893
  throw error;
4963
4894
  }
4964
4895
  try {
4965
- (0, import_fs7.writeFileSync)(path12.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4896
+ (0, import_fs6.writeFileSync)(path10.join(candidatePath, OWNER_FILE_NAME), JSON.stringify(value), {
4966
4897
  encoding: "utf-8",
4967
4898
  flag: "wx",
4968
4899
  mode: 384
4969
4900
  });
4970
- if ((0, import_fs7.existsSync)(finalPath)) return false;
4901
+ if ((0, import_fs6.existsSync)(finalPath)) return false;
4971
4902
  try {
4972
- (0, import_fs7.renameSync)(candidatePath, finalPath);
4903
+ (0, import_fs6.renameSync)(candidatePath, finalPath);
4973
4904
  return true;
4974
4905
  } catch (error) {
4975
- if ((0, import_fs7.existsSync)(finalPath)) return false;
4906
+ if ((0, import_fs6.existsSync)(finalPath)) return false;
4976
4907
  if (getErrorCode(error) === "ENOENT") return false;
4977
4908
  throw error;
4978
4909
  }
@@ -4980,7 +4911,7 @@ function publishJsonDirectory(finalPath, value) {
4980
4911
  if (getErrorCode(error) === "ENOENT") return false;
4981
4912
  throw error;
4982
4913
  } finally {
4983
- if ((0, import_fs7.existsSync)(candidatePath)) (0, import_fs7.rmSync)(candidatePath, { recursive: true, force: true });
4914
+ if ((0, import_fs6.existsSync)(candidatePath)) (0, import_fs6.rmSync)(candidatePath, { recursive: true, force: true });
4984
4915
  }
4985
4916
  }
4986
4917
  function createOwner(operation) {
@@ -4993,7 +4924,7 @@ function createOwner(operation) {
4993
4924
  };
4994
4925
  }
4995
4926
  function recoveryMarkerPath(indexPath, owner) {
4996
- return path12.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4927
+ return path10.join(indexPath, `${RECOVERY_MARKER_PREFIX}${owner.token}`);
4997
4928
  }
4998
4929
  function publishRecoveryMarker(indexPath, owner) {
4999
4930
  const markerPath = recoveryMarkerPath(indexPath, owner);
@@ -5007,16 +4938,16 @@ function publishRecoveryMarker(indexPath, owner) {
5007
4938
  }
5008
4939
  function getPendingRecoveries(indexPath) {
5009
4940
  const recoveries = [];
5010
- const markerNames = (0, import_fs7.readdirSync)(indexPath).filter((name) => {
4941
+ const markerNames = (0, import_fs6.readdirSync)(indexPath).filter((name) => {
5011
4942
  if (!name.startsWith(RECOVERY_MARKER_PREFIX)) return false;
5012
4943
  return UUID_PATTERN.test(name.slice(RECOVERY_MARKER_PREFIX.length));
5013
4944
  }).sort();
5014
4945
  for (const markerName of markerNames) {
5015
- const markerPath = path12.join(indexPath, markerName);
4946
+ const markerPath = path10.join(indexPath, markerName);
5016
4947
  const markerToken = markerName.slice(RECOVERY_MARKER_PREFIX.length);
5017
4948
  let markerStats;
5018
4949
  try {
5019
- markerStats = (0, import_fs7.lstatSync)(markerPath);
4950
+ markerStats = (0, import_fs6.lstatSync)(markerPath);
5020
4951
  } catch (error) {
5021
4952
  if (getErrorCode(error) === "ENOENT") continue;
5022
4953
  throw error;
@@ -5038,17 +4969,17 @@ function getPendingRecoveries(indexPath) {
5038
4969
  }
5039
4970
  function cleanupDeadPublicationCandidates(indexPath) {
5040
4971
  const candidatePattern = /^indexing\.lock(?:\.recovery\.[0-9a-f-]{36})?\.candidate\.(\d+)\.[0-9a-f-]{36}$/i;
5041
- for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
4972
+ for (const entry of (0, import_fs6.readdirSync)(indexPath)) {
5042
4973
  const match = candidatePattern.exec(entry);
5043
4974
  if (!match) continue;
5044
4975
  const pid = Number(match[1]);
5045
4976
  if (!Number.isInteger(pid) || pid <= 0) continue;
5046
4977
  if (getOwnerLiveness({ pid, hostname: os4.hostname() }) !== "dead") continue;
5047
- (0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
4978
+ (0, import_fs6.rmSync)(path10.join(indexPath, entry), { recursive: true, force: true });
5048
4979
  }
5049
4980
  }
5050
4981
  function removeDeadReclaimMarker(lockPath, expectedOwner) {
5051
- const markerPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
4982
+ const markerPath = path10.join(lockPath, RECLAIM_DIRECTORY_NAME);
5052
4983
  const marker = readReclaimOwner(markerPath);
5053
4984
  if (!marker || marker.expectedOwnerToken !== expectedOwner.token || getOwnerLiveness(marker) !== "dead") {
5054
4985
  return false;
@@ -5059,7 +4990,7 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5059
4990
  }
5060
4991
  const claimedMarkerPath = `${markerPath}.stale.${marker.pid}.${marker.token}.${(0, import_crypto.randomUUID)()}`;
5061
4992
  try {
5062
- (0, import_fs7.renameSync)(markerPath, claimedMarkerPath);
4993
+ (0, import_fs6.renameSync)(markerPath, claimedMarkerPath);
5063
4994
  } catch (error) {
5064
4995
  if (getErrorCode(error) === "ENOENT") return false;
5065
4996
  throw error;
@@ -5067,16 +4998,16 @@ function removeDeadReclaimMarker(lockPath, expectedOwner) {
5067
4998
  const claimedMarker = readReclaimOwner(claimedMarkerPath);
5068
4999
  const ownerAfterClaim = readDirectoryOwner(lockPath);
5069
5000
  if (!claimedMarker || !sameReclaimOwner(claimedMarker, marker) || !ownerAfterClaim || !sameOwner(ownerAfterClaim, expectedOwner) || getOwnerLiveness(ownerAfterClaim) !== "dead") {
5070
- if (!(0, import_fs7.existsSync)(markerPath) && (0, import_fs7.existsSync)(claimedMarkerPath)) {
5071
- (0, import_fs7.renameSync)(claimedMarkerPath, markerPath);
5001
+ if (!(0, import_fs6.existsSync)(markerPath) && (0, import_fs6.existsSync)(claimedMarkerPath)) {
5002
+ (0, import_fs6.renameSync)(claimedMarkerPath, markerPath);
5072
5003
  }
5073
5004
  return false;
5074
5005
  }
5075
- (0, import_fs7.rmSync)(claimedMarkerPath, { recursive: true, force: true });
5006
+ (0, import_fs6.rmSync)(claimedMarkerPath, { recursive: true, force: true });
5076
5007
  return true;
5077
5008
  }
5078
5009
  function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5079
- const reclaimPath = path12.join(lockPath, RECLAIM_DIRECTORY_NAME);
5010
+ const reclaimPath = path10.join(lockPath, RECLAIM_DIRECTORY_NAME);
5080
5011
  const reclaimOwner = {
5081
5012
  pid: process.pid,
5082
5013
  hostname: os4.hostname(),
@@ -5102,8 +5033,8 @@ function reclaimDeadOwner(indexPath, lockPath, expectedOwner) {
5102
5033
  return false;
5103
5034
  }
5104
5035
  const quarantinePath = `${lockPath}.stale.${expectedOwner.token}.${reclaimOwner.token}`;
5105
- (0, import_fs7.renameSync)(lockPath, quarantinePath);
5106
- (0, import_fs7.rmSync)(quarantinePath, { recursive: true, force: true });
5036
+ (0, import_fs6.renameSync)(lockPath, quarantinePath);
5037
+ (0, import_fs6.rmSync)(quarantinePath, { recursive: true, force: true });
5107
5038
  return true;
5108
5039
  } catch (error) {
5109
5040
  if (getErrorCode(error) === "ENOENT") return false;
@@ -5128,9 +5059,9 @@ function isIndexLockContentionError(error) {
5128
5059
  return error instanceof IndexLockContentionError || typeof error === "object" && error !== null && "code" in error && error.code === "INDEX_BUSY";
5129
5060
  }
5130
5061
  function acquireIndexLock(indexPath, operation) {
5131
- (0, import_fs7.mkdirSync)(indexPath, { recursive: true });
5132
- const canonicalIndexPath = import_fs7.realpathSync.native(indexPath);
5133
- const lockPath = path12.join(canonicalIndexPath, "indexing.lock");
5062
+ (0, import_fs6.mkdirSync)(indexPath, { recursive: true });
5063
+ const canonicalIndexPath = import_fs6.realpathSync.native(indexPath);
5064
+ const lockPath = path10.join(canonicalIndexPath, "indexing.lock");
5134
5065
  cleanupDeadPublicationCandidates(canonicalIndexPath);
5135
5066
  for (let attempt = 0; attempt < 6; attempt += 1) {
5136
5067
  const owner = createOwner(operation);
@@ -5151,7 +5082,7 @@ function acquireIndexLock(indexPath, operation) {
5151
5082
  }
5152
5083
  let stats;
5153
5084
  try {
5154
- stats = (0, import_fs7.lstatSync)(lockPath);
5085
+ stats = (0, import_fs6.lstatSync)(lockPath);
5155
5086
  } catch (error) {
5156
5087
  if (getErrorCode(error) === "ENOENT") continue;
5157
5088
  throw error;
@@ -5179,78 +5110,44 @@ function releaseIndexLock(lease) {
5179
5110
  if (!currentOwner || !sameOwner(currentOwner, lease.owner)) return false;
5180
5111
  const releasePath = `${lease.lockPath}.release.${lease.owner.pid}.${lease.owner.token}`;
5181
5112
  try {
5182
- retryTransientFilesystemOperation(() => (0, import_fs7.renameSync)(lease.lockPath, releasePath));
5113
+ retryTransientFilesystemOperation(() => (0, import_fs6.renameSync)(lease.lockPath, releasePath));
5183
5114
  } catch (error) {
5184
5115
  if (getErrorCode(error) === "ENOENT") return false;
5185
5116
  throw error;
5186
5117
  }
5187
5118
  const claimedOwner = readDirectoryOwner(releasePath);
5188
5119
  if (!claimedOwner || !sameOwner(claimedOwner, lease.owner)) {
5189
- if (!(0, import_fs7.existsSync)(lease.lockPath) && (0, import_fs7.existsSync)(releasePath)) {
5190
- (0, import_fs7.renameSync)(releasePath, lease.lockPath);
5120
+ if (!(0, import_fs6.existsSync)(lease.lockPath) && (0, import_fs6.existsSync)(releasePath)) {
5121
+ (0, import_fs6.renameSync)(releasePath, lease.lockPath);
5191
5122
  }
5192
5123
  return false;
5193
5124
  }
5194
5125
  try {
5195
- retryTransientFilesystemOperation(() => (0, import_fs7.rmSync)(releasePath, { recursive: true, force: true }));
5126
+ retryTransientFilesystemOperation(() => (0, import_fs6.rmSync)(releasePath, { recursive: true, force: true }));
5196
5127
  } catch (error) {
5197
5128
  console.error(`[codebase-index] Lease released but tombstone cleanup failed: ${releasePath}`, error);
5198
5129
  }
5199
5130
  return true;
5200
5131
  }
5201
- async function withIndexLock(indexPath, operation, callback, options = {}) {
5202
- const lease = acquireIndexLock(indexPath, operation);
5203
- let result;
5204
- let callbackError;
5205
- let callbackFailed = false;
5206
- try {
5207
- result = await callback(lease);
5208
- } catch (error) {
5209
- callbackFailed = true;
5210
- callbackError = error;
5211
- }
5212
- if (!callbackFailed && options.completeRecoveries !== false) {
5213
- try {
5214
- completeLeaseRecovery(lease);
5215
- } catch (error) {
5216
- callbackFailed = true;
5217
- callbackError = error;
5218
- }
5219
- }
5220
- let releaseError;
5221
- try {
5222
- if (!releaseIndexLock(lease)) {
5223
- releaseError = new Error(`Lost ownership of index mutation lease ${lease.owner.token}`);
5224
- }
5225
- } catch (error) {
5226
- releaseError = error;
5227
- }
5228
- if (releaseError !== void 0) {
5229
- if (callbackFailed) throw new AggregateError([callbackError, releaseError], "Index mutation and lease release both failed");
5230
- throw releaseError;
5231
- }
5232
- if (callbackFailed) throw callbackError;
5233
- return result;
5234
- }
5235
5132
  function createLeaseTemporaryPath(targetPath, owner, kind = "tmp") {
5236
5133
  if (kind === "bak") return `${targetPath}.bak.${owner.pid}.${owner.token}`;
5237
5134
  temporaryCounter += 1;
5238
5135
  return `${targetPath}.tmp.${owner.pid}.${owner.token}.${temporaryCounter}`;
5239
5136
  }
5240
5137
  function removeLeaseTemporaryPath(temporaryPath) {
5241
- if ((0, import_fs7.existsSync)(temporaryPath)) (0, import_fs7.rmSync)(temporaryPath, { recursive: true, force: true });
5138
+ if ((0, import_fs6.existsSync)(temporaryPath)) (0, import_fs6.rmSync)(temporaryPath, { recursive: true, force: true });
5242
5139
  }
5243
5140
  function recoverLeaseArtifacts(indexPath, owner, backupTargets) {
5244
5141
  for (const targetPath of backupTargets) {
5245
5142
  const backupPath = createLeaseTemporaryPath(targetPath, owner, "bak");
5246
- if (!(0, import_fs7.existsSync)(backupPath)) continue;
5247
- if ((0, import_fs7.existsSync)(targetPath)) (0, import_fs7.rmSync)(targetPath, { recursive: true, force: true });
5248
- (0, import_fs7.renameSync)(backupPath, targetPath);
5143
+ if (!(0, import_fs6.existsSync)(backupPath)) continue;
5144
+ if ((0, import_fs6.existsSync)(targetPath)) (0, import_fs6.rmSync)(targetPath, { recursive: true, force: true });
5145
+ (0, import_fs6.renameSync)(backupPath, targetPath);
5249
5146
  }
5250
5147
  const temporaryOwnerMarker = `.tmp.${owner.pid}.${owner.token}.`;
5251
- for (const entry of (0, import_fs7.readdirSync)(indexPath)) {
5148
+ for (const entry of (0, import_fs6.readdirSync)(indexPath)) {
5252
5149
  if (!entry.includes(temporaryOwnerMarker)) continue;
5253
- (0, import_fs7.rmSync)(path12.join(indexPath, entry), { recursive: true, force: true });
5150
+ (0, import_fs6.rmSync)(path10.join(indexPath, entry), { recursive: true, force: true });
5254
5151
  }
5255
5152
  }
5256
5153
  function completeLeaseRecovery(lease) {
@@ -5261,13 +5158,23 @@ function completeLeaseRecovery(lease) {
5261
5158
  }
5262
5159
  }
5263
5160
  for (const recovery of lease.recoveries) {
5264
- (0, import_fs7.rmSync)(recovery.markerPath, { recursive: true, force: true });
5161
+ (0, import_fs6.rmSync)(recovery.markerPath, { recursive: true, force: true });
5265
5162
  }
5266
5163
  }
5267
5164
 
5268
5165
  // src/indexer/index.ts
5269
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php", "apex", "zig", "gdscript", "matlab", "bash"]);
5270
- var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex"]);
5166
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "swift", "php", "apex", "zig", "gdscript", "matlab", "bash", "c", "cpp", "metal"]);
5167
+ var CASE_INSENSITIVE_LANGUAGES = /* @__PURE__ */ new Set(["apex", "php"]);
5168
+ var CALL_GRAPH_RESOLUTION_VERSION = "4";
5169
+ var PHP_FUNCTION_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5170
+ "function_declaration",
5171
+ "function",
5172
+ "function_definition"
5173
+ ]);
5174
+ var PHP_CLASS_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5175
+ "class_declaration",
5176
+ "class_definition"
5177
+ ]);
5271
5178
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5272
5179
  "function_declaration",
5273
5180
  "function",
@@ -5280,6 +5187,9 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5280
5187
  "enum_declaration",
5281
5188
  "function_definition",
5282
5189
  "class_definition",
5190
+ "class_specifier",
5191
+ "struct_specifier",
5192
+ "namespace_definition",
5283
5193
  "decorated_definition",
5284
5194
  "method_declaration",
5285
5195
  "type_declaration",
@@ -5295,6 +5205,14 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5295
5205
  "test_declaration",
5296
5206
  "struct_declaration",
5297
5207
  "union_declaration",
5208
+ // Synthetic Swift declarations or declarations specific to tree-sitter-swift.
5209
+ "actor_declaration",
5210
+ "extension_declaration",
5211
+ "protocol_declaration",
5212
+ "protocol_function_declaration",
5213
+ "init_declaration",
5214
+ "deinit_declaration",
5215
+ "subscript_declaration",
5298
5216
  // GDScript declarations whose names participate in the call graph.
5299
5217
  // `function_definition` and `class_definition` are already in the set
5300
5218
  // above (shared with Python/C/Bash and Python, respectively).
@@ -5304,6 +5222,53 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5304
5222
  "const_statement",
5305
5223
  "class_name_statement"
5306
5224
  ]);
5225
+ var C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set(["class_specifier", "struct_specifier"]);
5226
+ function isCompatibleCFamilyCallTarget(language, callType, symbolKind) {
5227
+ if (language !== "c" && language !== "cpp") return true;
5228
+ if (symbolKind === "namespace_definition") return callType === "Import";
5229
+ const isTypeSymbol = C_FAMILY_TYPE_SYMBOL_CHUNK_TYPES.has(symbolKind);
5230
+ if (callType === "Constructor" || callType === "Inherits" || callType === "Implements") {
5231
+ return isTypeSymbol;
5232
+ }
5233
+ return !isTypeSymbol;
5234
+ }
5235
+ var EXECUTABLE_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
5236
+ "function_declaration",
5237
+ "function",
5238
+ "arrow_function",
5239
+ "method_definition",
5240
+ "function_definition",
5241
+ "method_declaration",
5242
+ "function_item",
5243
+ "protocol_function_declaration",
5244
+ "init_declaration",
5245
+ "deinit_declaration",
5246
+ "subscript_declaration",
5247
+ "constructor_definition",
5248
+ "trigger_declaration",
5249
+ "test_declaration"
5250
+ ]);
5251
+ function findEnclosingSymbol(symbols, line, column) {
5252
+ let best;
5253
+ for (const symbol of symbols) {
5254
+ if (line < symbol.startLine || line > symbol.endLine) continue;
5255
+ if (column !== void 0 && (line === symbol.startLine && column < symbol.startCol || line === symbol.endLine && column >= symbol.endCol)) {
5256
+ continue;
5257
+ }
5258
+ if (!best) {
5259
+ best = symbol;
5260
+ continue;
5261
+ }
5262
+ const span = symbol.endLine - symbol.startLine;
5263
+ const bestSpan = best.endLine - best.startLine;
5264
+ 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);
5265
+ const isMoreSpecificTie = span === bestSpan && symbol.startLine === best.startLine && EXECUTABLE_SYMBOL_CHUNK_TYPES.has(symbol.kind) && !EXECUTABLE_SYMBOL_CHUNK_TYPES.has(best.kind);
5266
+ if (span < bestSpan || span === bestSpan && symbol.startLine > best.startLine || isNarrowerPositionRange || isMoreSpecificTie) {
5267
+ best = symbol;
5268
+ }
5269
+ }
5270
+ return best;
5271
+ }
5307
5272
  function float32ArrayToBuffer(arr) {
5308
5273
  const float32 = new Float32Array(arr);
5309
5274
  return Buffer.from(float32.buffer);
@@ -5388,6 +5353,8 @@ function hasBlameMetadata(metadata) {
5388
5353
  }
5389
5354
  var INDEX_METADATA_VERSION = "1";
5390
5355
  var EMBEDDING_STRATEGY_VERSION = "2";
5356
+ var SWIFT_PARSER_VERSION = "1";
5357
+ var METAL_PARSER_VERSION = "1";
5391
5358
  var RANKING_TOKEN_CACHE_LIMIT = 4096;
5392
5359
  var RANK_HYBRID_CACHE_LIMIT = 256;
5393
5360
  function createPendingChunkStorageText(texts) {
@@ -5544,9 +5511,9 @@ function hasAllEmbeddingParts(parts, expectedPartCount) {
5544
5511
  return true;
5545
5512
  }
5546
5513
  function isPathWithinRoot(filePath, rootPath) {
5547
- const normalizedFilePath = path13.resolve(filePath);
5548
- const normalizedRoot = path13.resolve(rootPath);
5549
- return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path13.sep}`);
5514
+ const normalizedFilePath = path11.resolve(filePath);
5515
+ const normalizedRoot = path11.resolve(rootPath);
5516
+ return normalizedFilePath === normalizedRoot || normalizedFilePath.startsWith(`${normalizedRoot}${path11.sep}`);
5550
5517
  }
5551
5518
  var rankingQueryTokenCache = /* @__PURE__ */ new Map();
5552
5519
  var rankingNameTokenCache = /* @__PURE__ */ new Map();
@@ -5710,15 +5677,25 @@ function chunkTypeBoost(chunkType) {
5710
5677
  case "function_declaration":
5711
5678
  case "method":
5712
5679
  case "method_definition":
5680
+ case "method_declaration":
5681
+ case "protocol_function_declaration":
5682
+ case "init_declaration":
5683
+ case "deinit_declaration":
5684
+ case "subscript_declaration":
5713
5685
  case "class":
5714
5686
  case "class_declaration":
5687
+ case "actor_declaration":
5688
+ case "extension_declaration":
5715
5689
  return 0.2;
5716
5690
  case "interface":
5717
5691
  case "type":
5718
5692
  case "enum":
5693
+ case "enum_declaration":
5719
5694
  case "struct":
5695
+ case "struct_declaration":
5720
5696
  case "impl":
5721
5697
  case "trait":
5698
+ case "protocol_declaration":
5722
5699
  case "module":
5723
5700
  return 0.1;
5724
5701
  default:
@@ -5825,11 +5802,21 @@ function isImplementationChunkType(chunkType) {
5825
5802
  "function_declaration",
5826
5803
  "method",
5827
5804
  "method_definition",
5805
+ "method_declaration",
5806
+ "protocol_function_declaration",
5807
+ "init_declaration",
5808
+ "deinit_declaration",
5809
+ "subscript_declaration",
5828
5810
  "class",
5829
5811
  "class_declaration",
5812
+ "actor_declaration",
5813
+ "extension_declaration",
5830
5814
  "interface",
5815
+ "protocol_declaration",
5831
5816
  "type",
5832
5817
  "enum",
5818
+ "enum_declaration",
5819
+ "struct_declaration",
5833
5820
  "module"
5834
5821
  ].includes(chunkType);
5835
5822
  }
@@ -6646,24 +6633,24 @@ var Indexer = class {
6646
6633
  this.config = config;
6647
6634
  this.host = host;
6648
6635
  this.indexPath = this.getIndexPath();
6649
- this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6650
- this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6636
+ this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6637
+ this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6651
6638
  this.logger = initializeLogger(config.debug);
6652
6639
  }
6653
6640
  getIndexPath() {
6654
6641
  return resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
6655
6642
  }
6656
6643
  isLocalProjectIndexPath() {
6657
- const localProjectIndexPaths = [path13.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6644
+ const localProjectIndexPaths = [path11.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
6658
6645
  if (this.host !== "opencode") {
6659
- localProjectIndexPaths.push(path13.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6646
+ localProjectIndexPaths.push(path11.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
6660
6647
  }
6661
6648
  return localProjectIndexPaths.some((localPath) => {
6662
- if (!(0, import_fs8.existsSync)(localPath) || !(0, import_fs8.existsSync)(this.indexPath)) {
6663
- return path13.resolve(this.indexPath) === path13.resolve(localPath);
6649
+ if (!(0, import_fs7.existsSync)(localPath) || !(0, import_fs7.existsSync)(this.indexPath)) {
6650
+ return path11.resolve(this.indexPath) === path11.resolve(localPath);
6664
6651
  }
6665
- const indexStats = (0, import_fs8.statSync)(this.indexPath);
6666
- const localStats = (0, import_fs8.statSync)(localPath);
6652
+ const indexStats = (0, import_fs7.statSync)(this.indexPath);
6653
+ const localStats = (0, import_fs7.statSync)(localPath);
6667
6654
  return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
6668
6655
  });
6669
6656
  }
@@ -6702,8 +6689,8 @@ var Indexer = class {
6702
6689
  async withIndexMutationLease(operation, callback) {
6703
6690
  const lease = acquireIndexLock(this.indexPath, operation);
6704
6691
  this.indexPath = lease.canonicalIndexPath;
6705
- this.fileHashCachePath = path13.join(this.indexPath, "file-hashes.json");
6706
- this.failedBatchesPath = path13.join(this.indexPath, "failed-batches.json");
6692
+ this.fileHashCachePath = path11.join(this.indexPath, "file-hashes.json");
6693
+ this.failedBatchesPath = path11.join(this.indexPath, "failed-batches.json");
6707
6694
  this.activeIndexLease = lease;
6708
6695
  let result;
6709
6696
  let callbackError;
@@ -6737,7 +6724,7 @@ var Indexer = class {
6737
6724
  } catch (error) {
6738
6725
  releaseError = error;
6739
6726
  this.writerArtifactFingerprint = null;
6740
- if (!(0, import_fs8.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6727
+ if (!(0, import_fs7.existsSync)(lease.lockPath) && this.activeIndexLease?.owner.token === lease.owner.token) {
6741
6728
  this.activeIndexLease = null;
6742
6729
  }
6743
6730
  }
@@ -6755,11 +6742,11 @@ var Indexer = class {
6755
6742
  return this.activeIndexLease;
6756
6743
  }
6757
6744
  loadFileHashCache() {
6758
- if (!(0, import_fs8.existsSync)(this.fileHashCachePath)) {
6745
+ if (!(0, import_fs7.existsSync)(this.fileHashCachePath)) {
6759
6746
  return;
6760
6747
  }
6761
6748
  try {
6762
- const data = (0, import_fs8.readFileSync)(this.fileHashCachePath, "utf-8");
6749
+ const data = (0, import_fs7.readFileSync)(this.fileHashCachePath, "utf-8");
6763
6750
  const parsed = JSON.parse(data);
6764
6751
  this.fileHashCache = new Map(Object.entries(parsed));
6765
6752
  } catch (error) {
@@ -6781,24 +6768,24 @@ var Indexer = class {
6781
6768
  atomicWriteSync(targetPath, data) {
6782
6769
  const lease = this.requireActiveLease();
6783
6770
  const tempPath = createLeaseTemporaryPath(targetPath, lease.owner, "tmp");
6784
- (0, import_fs8.mkdirSync)(path13.dirname(targetPath), { recursive: true });
6771
+ (0, import_fs7.mkdirSync)(path11.dirname(targetPath), { recursive: true });
6785
6772
  try {
6786
- (0, import_fs8.writeFileSync)(tempPath, data);
6787
- (0, import_fs8.renameSync)(tempPath, targetPath);
6773
+ (0, import_fs7.writeFileSync)(tempPath, data);
6774
+ (0, import_fs7.renameSync)(tempPath, targetPath);
6788
6775
  } finally {
6789
6776
  removeLeaseTemporaryPath(tempPath);
6790
6777
  }
6791
6778
  }
6792
6779
  saveInvertedIndex(invertedIndex) {
6793
6780
  this.atomicWriteSync(
6794
- path13.join(this.indexPath, "inverted-index.json"),
6781
+ path11.join(this.indexPath, "inverted-index.json"),
6795
6782
  invertedIndex.serialize()
6796
6783
  );
6797
6784
  }
6798
6785
  getScopedRoots() {
6799
- const roots = /* @__PURE__ */ new Set([path13.resolve(this.projectRoot)]);
6786
+ const roots = /* @__PURE__ */ new Set([path11.resolve(this.projectRoot)]);
6800
6787
  for (const kbRoot of this.config.knowledgeBases) {
6801
- roots.add(path13.resolve(this.projectRoot, kbRoot));
6788
+ roots.add(path11.resolve(this.projectRoot, kbRoot));
6802
6789
  }
6803
6790
  return Array.from(roots);
6804
6791
  }
@@ -6807,31 +6794,54 @@ var Indexer = class {
6807
6794
  if (this.config.scope !== "global") {
6808
6795
  return branchName;
6809
6796
  }
6810
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6797
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6811
6798
  return `${projectHash}:${branchName}`;
6812
6799
  }
6813
6800
  getBranchCatalogKeyFor(branchName) {
6814
6801
  if (this.config.scope !== "global") {
6815
6802
  return branchName;
6816
6803
  }
6817
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6804
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6818
6805
  return `${projectHash}:${branchName}`;
6819
6806
  }
6820
6807
  getLegacyBranchCatalogKey() {
6821
6808
  return this.currentBranch || "default";
6822
6809
  }
6823
6810
  getLegacyMigrationMetadataKey() {
6824
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6811
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6825
6812
  return `index.globalBranchMigration.${projectHash}`;
6826
6813
  }
6827
6814
  getProjectEmbeddingStrategyMetadataKey() {
6828
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6815
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6829
6816
  return `index.embeddingStrategyVersion.${projectHash}`;
6830
6817
  }
6831
6818
  getProjectForceReembedMetadataKey() {
6832
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6819
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6833
6820
  return `index.forceReembed.${projectHash}`;
6834
6821
  }
6822
+ getCallGraphResolutionMetadataKey() {
6823
+ if (this.config.scope !== "global") {
6824
+ return "index.callGraphResolutionVersion";
6825
+ }
6826
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6827
+ return `index.callGraphResolutionVersion.${projectHash}`;
6828
+ }
6829
+ getSwiftParserVersionMetadataKey() {
6830
+ const key = "index.parser.swiftVersion";
6831
+ if (this.config.scope !== "global") {
6832
+ return key;
6833
+ }
6834
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6835
+ return `${key}.${projectHash}`;
6836
+ }
6837
+ getMetalParserVersionMetadataKey() {
6838
+ const key = "index.parser.metalVersion";
6839
+ if (this.config.scope !== "global") {
6840
+ return key;
6841
+ }
6842
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6843
+ return `${key}.${projectHash}`;
6844
+ }
6835
6845
  hasProjectForceReembedPending() {
6836
6846
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
6837
6847
  }
@@ -6923,7 +6933,7 @@ var Indexer = class {
6923
6933
  if (!this.database) {
6924
6934
  return { chunkIds, symbolIds };
6925
6935
  }
6926
- const projectRootPath = path13.resolve(this.projectRoot);
6936
+ const projectRootPath = path11.resolve(this.projectRoot);
6927
6937
  const projectLocalFilePaths = /* @__PURE__ */ new Set([
6928
6938
  ...Array.from(this.fileHashCache.keys()).filter(
6929
6939
  (filePath) => this.isFileInCurrentScope(filePath, roots) && isPathWithinRoot(filePath, projectRootPath)
@@ -6946,7 +6956,7 @@ var Indexer = class {
6946
6956
  if (this.config.scope !== "global") {
6947
6957
  return this.getBranchCatalogCleanupKeys();
6948
6958
  }
6949
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
6959
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
6950
6960
  const keys = /* @__PURE__ */ new Set();
6951
6961
  const projectChunkIdSet = new Set(projectChunkIds);
6952
6962
  const projectSymbolIdSet = new Set(projectSymbolIds);
@@ -7030,7 +7040,7 @@ var Indexer = class {
7030
7040
  if (!this.database || this.config.scope !== "global") {
7031
7041
  return false;
7032
7042
  }
7033
- const projectHash = hashContent(path13.resolve(this.projectRoot)).slice(0, 16);
7043
+ const projectHash = hashContent(path11.resolve(this.projectRoot)).slice(0, 16);
7034
7044
  const roots = this.getScopedRoots();
7035
7045
  const { chunkIds: projectLocalChunkIds, symbolIds: projectLocalSymbolIds } = this.getProjectLocalScopedOwnershipIds(roots);
7036
7046
  return this.database.getAllBranches().some(
@@ -7064,7 +7074,7 @@ var Indexer = class {
7064
7074
  ...Array.from(this.fileHashCache.keys()).filter((filePath) => this.isFileInCurrentScope(filePath, roots)),
7065
7075
  ...scopedEntries.map(({ metadata }) => metadata.filePath)
7066
7076
  ]);
7067
- const projectRootPath = path13.resolve(this.projectRoot);
7077
+ const projectRootPath = path11.resolve(this.projectRoot);
7068
7078
  const projectLocalFilePaths = new Set(
7069
7079
  Array.from(filePaths).filter((filePath) => isPathWithinRoot(filePath, projectRootPath))
7070
7080
  );
@@ -7142,8 +7152,8 @@ var Indexer = class {
7142
7152
  });
7143
7153
  }
7144
7154
  if (this.config.scope === "global") {
7145
- if ((0, import_fs8.existsSync)(this.fileHashCachePath)) {
7146
- (0, import_fs8.unlinkSync)(this.fileHashCachePath);
7155
+ if ((0, import_fs7.existsSync)(this.fileHashCachePath)) {
7156
+ (0, import_fs7.unlinkSync)(this.fileHashCachePath);
7147
7157
  }
7148
7158
  await this.healthCheckUnlocked();
7149
7159
  }
@@ -7162,10 +7172,10 @@ var Indexer = class {
7162
7172
  }
7163
7173
  }
7164
7174
  loadSerializedFailedBatches() {
7165
- if (!(0, import_fs8.existsSync)(this.failedBatchesPath)) {
7175
+ if (!(0, import_fs7.existsSync)(this.failedBatchesPath)) {
7166
7176
  return [];
7167
7177
  }
7168
- const data = (0, import_fs8.readFileSync)(this.failedBatchesPath, "utf-8");
7178
+ const data = (0, import_fs7.readFileSync)(this.failedBatchesPath, "utf-8");
7169
7179
  const parsed = JSON.parse(data);
7170
7180
  return parsed.map((batch) => {
7171
7181
  const chunks = Array.isArray(batch.chunks) ? batch.chunks : [];
@@ -7182,9 +7192,9 @@ var Indexer = class {
7182
7192
  }
7183
7193
  saveFailedBatches(batches) {
7184
7194
  if (batches.length === 0) {
7185
- if ((0, import_fs8.existsSync)(this.failedBatchesPath)) {
7195
+ if ((0, import_fs7.existsSync)(this.failedBatchesPath)) {
7186
7196
  try {
7187
- (0, import_fs8.unlinkSync)(this.failedBatchesPath);
7197
+ (0, import_fs7.unlinkSync)(this.failedBatchesPath);
7188
7198
  } catch {
7189
7199
  }
7190
7200
  }
@@ -7370,7 +7380,7 @@ var Indexer = class {
7370
7380
  const intent = isLikelyImplementationPath(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
7371
7381
  parts.push(`intent_hint: ${intent}`);
7372
7382
  try {
7373
- const fileContent = await import_fs8.promises.readFile(candidate.metadata.filePath, "utf-8");
7383
+ const fileContent = await import_fs7.promises.readFile(candidate.metadata.filePath, "utf-8");
7374
7384
  const lines = fileContent.split("\n");
7375
7385
  const snippetStartLine = Math.max(1, candidate.metadata.startLine - 2);
7376
7386
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine + 2);
@@ -7464,7 +7474,7 @@ var Indexer = class {
7464
7474
  }
7465
7475
  getReaderFileFingerprint(filePath, identityOnly = false) {
7466
7476
  try {
7467
- const stats = (0, import_fs8.statSync)(filePath);
7477
+ const stats = (0, import_fs7.statSync)(filePath);
7468
7478
  if (identityOnly) {
7469
7479
  return `${stats.dev}:${stats.ino}`;
7470
7480
  }
@@ -7474,12 +7484,12 @@ var Indexer = class {
7474
7484
  }
7475
7485
  }
7476
7486
  captureReaderArtifactFingerprint() {
7477
- const storePath = path13.join(this.indexPath, "vectors");
7487
+ const storePath = path11.join(this.indexPath, "vectors");
7478
7488
  return {
7479
7489
  vectors: `${this.getReaderFileFingerprint(storePath)}|${this.getReaderFileFingerprint(`${storePath}.meta.json`)}`,
7480
- keyword: this.getReaderFileFingerprint(path13.join(this.indexPath, "inverted-index.json")),
7481
- database: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db")),
7482
- databaseIdentity: this.getReaderFileFingerprint(path13.join(this.indexPath, "codebase.db"), true)
7490
+ keyword: this.getReaderFileFingerprint(path11.join(this.indexPath, "inverted-index.json")),
7491
+ database: this.getReaderFileFingerprint(path11.join(this.indexPath, "codebase.db")),
7492
+ databaseIdentity: this.getReaderFileFingerprint(path11.join(this.indexPath, "codebase.db"), true)
7483
7493
  };
7484
7494
  }
7485
7495
  refreshReaderArtifacts() {
@@ -7504,13 +7514,13 @@ var Indexer = class {
7504
7514
  issues.set(component, this.createReadIssue(component, message));
7505
7515
  this.readerArtifactRetryAfter.set(component, Date.now() + READER_ARTIFACT_RETRY_INTERVAL_MS);
7506
7516
  };
7507
- const storePath = path13.join(this.indexPath, "vectors");
7517
+ const storePath = path11.join(this.indexPath, "vectors");
7508
7518
  const vectorMetadataPath = `${storePath}.meta.json`;
7509
- const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7510
- const dbPath = path13.join(this.indexPath, "codebase.db");
7519
+ const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
7520
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7511
7521
  if (vectorsChanged || retryDue("vectors")) {
7512
- const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7513
- const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7522
+ const vectorStoreExists = (0, import_fs7.existsSync)(storePath);
7523
+ const vectorMetadataExists = (0, import_fs7.existsSync)(vectorMetadataPath);
7514
7524
  if (vectorStoreExists && vectorMetadataExists) {
7515
7525
  try {
7516
7526
  const store = new VectorStore(storePath, this.configuredProviderInfo.modelInfo.dimensions);
@@ -7525,8 +7535,8 @@ var Indexer = class {
7525
7535
  setIssue("vectors", this.getVectorReadIssueMessage());
7526
7536
  }
7527
7537
  }
7528
- if (keywordChanged || retryDue("keyword") || !(0, import_fs8.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7529
- if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7538
+ if (keywordChanged || retryDue("keyword") || !(0, import_fs7.existsSync)(invertedIndexPath) && (this.store?.count() ?? 0) > 0) {
7539
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
7530
7540
  try {
7531
7541
  const invertedIndex = new InvertedIndex(invertedIndexPath);
7532
7542
  invertedIndex.load();
@@ -7541,7 +7551,7 @@ var Indexer = class {
7541
7551
  }
7542
7552
  }
7543
7553
  if (databaseReplaced || databaseChanged && issues.has("database") || retryDue("database")) {
7544
- if ((0, import_fs8.existsSync)(dbPath)) {
7554
+ if ((0, import_fs7.existsSync)(dbPath)) {
7545
7555
  try {
7546
7556
  const database = Database.openReadOnly(dbPath);
7547
7557
  if (this.database) {
@@ -7632,14 +7642,14 @@ var Indexer = class {
7632
7642
  }
7633
7643
  }
7634
7644
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
7635
- const storePath = path13.join(this.indexPath, "vectors");
7645
+ const storePath = path11.join(this.indexPath, "vectors");
7636
7646
  const vectorMetadataPath = `${storePath}.meta.json`;
7637
- const invertedIndexPath = path13.join(this.indexPath, "inverted-index.json");
7638
- const dbPath = path13.join(this.indexPath, "codebase.db");
7639
- let dbIsNew = !(0, import_fs8.existsSync)(dbPath);
7647
+ const invertedIndexPath = path11.join(this.indexPath, "inverted-index.json");
7648
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7649
+ let dbIsNew = !(0, import_fs7.existsSync)(dbPath);
7640
7650
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
7641
7651
  if (mode === "writer") {
7642
- await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7652
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
7643
7653
  if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
7644
7654
  throw new Error(
7645
7655
  "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
@@ -7655,15 +7665,15 @@ var Indexer = class {
7655
7665
  await this.resetLocalIndexArtifacts();
7656
7666
  }
7657
7667
  this.store = new VectorStore(storePath, dimensions);
7658
- if ((0, import_fs8.existsSync)(storePath) || (0, import_fs8.existsSync)(vectorMetadataPath)) {
7668
+ if ((0, import_fs7.existsSync)(storePath) || (0, import_fs7.existsSync)(vectorMetadataPath)) {
7659
7669
  this.store.load();
7660
7670
  }
7661
7671
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7662
7672
  try {
7663
7673
  this.invertedIndex.load();
7664
7674
  } catch {
7665
- if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7666
- await import_fs8.promises.unlink(invertedIndexPath);
7675
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
7676
+ await import_fs7.promises.unlink(invertedIndexPath);
7667
7677
  }
7668
7678
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7669
7679
  }
@@ -7680,8 +7690,8 @@ var Indexer = class {
7680
7690
  }
7681
7691
  } else {
7682
7692
  this.store = new VectorStore(storePath, dimensions);
7683
- const vectorStoreExists = (0, import_fs8.existsSync)(storePath);
7684
- const vectorMetadataExists = (0, import_fs8.existsSync)(vectorMetadataPath);
7693
+ const vectorStoreExists = (0, import_fs7.existsSync)(storePath);
7694
+ const vectorMetadataExists = (0, import_fs7.existsSync)(vectorMetadataPath);
7685
7695
  const vectorReadFailureMessage = this.getVectorReadIssueMessage();
7686
7696
  if (vectorStoreExists !== vectorMetadataExists) {
7687
7697
  this.recordReadIssue("vectors", vectorReadFailureMessage);
@@ -7694,7 +7704,7 @@ var Indexer = class {
7694
7704
  }
7695
7705
  }
7696
7706
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
7697
- if ((0, import_fs8.existsSync)(invertedIndexPath)) {
7707
+ if ((0, import_fs7.existsSync)(invertedIndexPath)) {
7698
7708
  try {
7699
7709
  this.invertedIndex.load();
7700
7710
  } catch (error) {
@@ -7708,7 +7718,7 @@ var Indexer = class {
7708
7718
  } else if (this.store.count() > 0) {
7709
7719
  this.recordReadIssue("keyword", this.getKeywordReadIssueMessage());
7710
7720
  }
7711
- if ((0, import_fs8.existsSync)(dbPath)) {
7721
+ if ((0, import_fs7.existsSync)(dbPath)) {
7712
7722
  try {
7713
7723
  this.database = Database.openReadOnly(dbPath);
7714
7724
  } catch (error) {
@@ -7799,7 +7809,7 @@ var Indexer = class {
7799
7809
  if (await this.tryResetCorruptedIndex("running automatic orphan garbage collection", error)) {
7800
7810
  return {
7801
7811
  resetCorruptedIndex: true,
7802
- warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
7812
+ warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
7803
7813
  };
7804
7814
  }
7805
7815
  throw error;
@@ -7814,7 +7824,7 @@ var Indexer = class {
7814
7824
  return;
7815
7825
  }
7816
7826
  const retainedEntries = store.getAllMetadata().filter(({ key }) => !excludedSet.has(key));
7817
- const storeBasePath = path13.join(this.indexPath, "vectors");
7827
+ const storeBasePath = path11.join(this.indexPath, "vectors");
7818
7828
  const storeIndexPath = storeBasePath;
7819
7829
  const storeMetadataPath = `${storeBasePath}.meta.json`;
7820
7830
  const lease = this.requireActiveLease();
@@ -7824,19 +7834,19 @@ var Indexer = class {
7824
7834
  let backedUpMetadata = false;
7825
7835
  let rebuiltCount = 0;
7826
7836
  let skippedCount = 0;
7827
- if ((0, import_fs8.existsSync)(backupIndexPath)) {
7828
- (0, import_fs8.unlinkSync)(backupIndexPath);
7837
+ if ((0, import_fs7.existsSync)(backupIndexPath)) {
7838
+ (0, import_fs7.unlinkSync)(backupIndexPath);
7829
7839
  }
7830
- if ((0, import_fs8.existsSync)(backupMetadataPath)) {
7831
- (0, import_fs8.unlinkSync)(backupMetadataPath);
7840
+ if ((0, import_fs7.existsSync)(backupMetadataPath)) {
7841
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
7832
7842
  }
7833
7843
  try {
7834
- if ((0, import_fs8.existsSync)(storeIndexPath)) {
7835
- (0, import_fs8.renameSync)(storeIndexPath, backupIndexPath);
7844
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
7845
+ (0, import_fs7.renameSync)(storeIndexPath, backupIndexPath);
7836
7846
  backedUpIndex = true;
7837
7847
  }
7838
- if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7839
- (0, import_fs8.renameSync)(storeMetadataPath, backupMetadataPath);
7848
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7849
+ (0, import_fs7.renameSync)(storeMetadataPath, backupMetadataPath);
7840
7850
  backedUpMetadata = true;
7841
7851
  }
7842
7852
  store.clear();
@@ -7856,11 +7866,11 @@ var Indexer = class {
7856
7866
  rebuiltCount += 1;
7857
7867
  }
7858
7868
  store.save();
7859
- if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7860
- (0, import_fs8.unlinkSync)(backupIndexPath);
7869
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7870
+ (0, import_fs7.unlinkSync)(backupIndexPath);
7861
7871
  }
7862
- if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7863
- (0, import_fs8.unlinkSync)(backupMetadataPath);
7872
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7873
+ (0, import_fs7.unlinkSync)(backupMetadataPath);
7864
7874
  }
7865
7875
  this.logger.gc("info", "Rebuilt vector store to avoid native remove", {
7866
7876
  excludedChunks: excludedSet.size,
@@ -7872,17 +7882,17 @@ var Indexer = class {
7872
7882
  store.clear();
7873
7883
  } catch {
7874
7884
  }
7875
- if ((0, import_fs8.existsSync)(storeIndexPath)) {
7876
- (0, import_fs8.unlinkSync)(storeIndexPath);
7885
+ if ((0, import_fs7.existsSync)(storeIndexPath)) {
7886
+ (0, import_fs7.unlinkSync)(storeIndexPath);
7877
7887
  }
7878
- if ((0, import_fs8.existsSync)(storeMetadataPath)) {
7879
- (0, import_fs8.unlinkSync)(storeMetadataPath);
7888
+ if ((0, import_fs7.existsSync)(storeMetadataPath)) {
7889
+ (0, import_fs7.unlinkSync)(storeMetadataPath);
7880
7890
  }
7881
- if (backedUpIndex && (0, import_fs8.existsSync)(backupIndexPath)) {
7882
- (0, import_fs8.renameSync)(backupIndexPath, storeIndexPath);
7891
+ if (backedUpIndex && (0, import_fs7.existsSync)(backupIndexPath)) {
7892
+ (0, import_fs7.renameSync)(backupIndexPath, storeIndexPath);
7883
7893
  }
7884
- if (backedUpMetadata && (0, import_fs8.existsSync)(backupMetadataPath)) {
7885
- (0, import_fs8.renameSync)(backupMetadataPath, storeMetadataPath);
7894
+ if (backedUpMetadata && (0, import_fs7.existsSync)(backupMetadataPath)) {
7895
+ (0, import_fs7.renameSync)(backupMetadataPath, storeMetadataPath);
7886
7896
  }
7887
7897
  if (backedUpIndex || backedUpMetadata) {
7888
7898
  store.load();
@@ -7909,24 +7919,24 @@ var Indexer = class {
7909
7919
  this.readerArtifactRetryAfter.clear();
7910
7920
  this.fileHashCache.clear();
7911
7921
  const resetPaths = [
7912
- path13.join(this.indexPath, "codebase.db"),
7913
- path13.join(this.indexPath, "codebase.db-shm"),
7914
- path13.join(this.indexPath, "codebase.db-wal"),
7915
- path13.join(this.indexPath, "vectors"),
7916
- path13.join(this.indexPath, "vectors.usearch"),
7917
- path13.join(this.indexPath, "vectors.meta.json"),
7918
- path13.join(this.indexPath, "inverted-index.json"),
7919
- path13.join(this.indexPath, "file-hashes.json"),
7920
- path13.join(this.indexPath, "failed-batches.json")
7922
+ path11.join(this.indexPath, "codebase.db"),
7923
+ path11.join(this.indexPath, "codebase.db-shm"),
7924
+ path11.join(this.indexPath, "codebase.db-wal"),
7925
+ path11.join(this.indexPath, "vectors"),
7926
+ path11.join(this.indexPath, "vectors.usearch"),
7927
+ path11.join(this.indexPath, "vectors.meta.json"),
7928
+ path11.join(this.indexPath, "inverted-index.json"),
7929
+ path11.join(this.indexPath, "file-hashes.json"),
7930
+ path11.join(this.indexPath, "failed-batches.json")
7921
7931
  ];
7922
- await Promise.all(resetPaths.map((targetPath) => import_fs8.promises.rm(targetPath, { recursive: true, force: true })));
7923
- await import_fs8.promises.mkdir(this.indexPath, { recursive: true });
7932
+ await Promise.all(resetPaths.map((targetPath) => import_fs7.promises.rm(targetPath, { recursive: true, force: true })));
7933
+ await import_fs7.promises.mkdir(this.indexPath, { recursive: true });
7924
7934
  }
7925
7935
  async tryResetCorruptedIndex(stage, error) {
7926
7936
  if (!isSqliteCorruptionError(error)) {
7927
7937
  return false;
7928
7938
  }
7929
- const dbPath = path13.join(this.indexPath, "codebase.db");
7939
+ const dbPath = path11.join(this.indexPath, "codebase.db");
7930
7940
  const warning = this.getCorruptedIndexWarning(dbPath);
7931
7941
  const errorMessage = getErrorMessage2(error);
7932
7942
  if (this.config.scope === "global") {
@@ -7992,6 +8002,7 @@ var Indexer = class {
7992
8002
  this.database.setMetadata("index.embeddingProvider", provider.provider);
7993
8003
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
7994
8004
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
8005
+ this.database.setMetadata(this.getCallGraphResolutionMetadataKey(), CALL_GRAPH_RESOLUTION_VERSION);
7995
8006
  if (this.config.scope === "global") {
7996
8007
  if (completeProjectEmbeddingStrategyReset) {
7997
8008
  this.database.setMetadata(this.getProjectEmbeddingStrategyMetadataKey(), EMBEDDING_STRATEGY_VERSION);
@@ -8179,6 +8190,20 @@ var Indexer = class {
8179
8190
  totalChunks: 0
8180
8191
  });
8181
8192
  this.loadFileHashCache();
8193
+ const swiftParserMetadataKey = this.getSwiftParserVersionMetadataKey();
8194
+ const reparseCachedSwiftFiles = database.getMetadata(swiftParserMetadataKey) !== SWIFT_PARSER_VERSION;
8195
+ const metalParserMetadataKey = this.getMetalParserVersionMetadataKey();
8196
+ const reparseCachedMetalFiles = database.getMetadata(metalParserMetadataKey) !== METAL_PARSER_VERSION;
8197
+ if (reparseCachedSwiftFiles && Array.from(this.fileHashCache.keys()).some(
8198
+ (filePath) => path11.extname(filePath).toLowerCase() === ".swift"
8199
+ )) {
8200
+ this.logger.info("Reindexing cached Swift files for parser support");
8201
+ }
8202
+ if (reparseCachedMetalFiles && Array.from(this.fileHashCache.keys()).some(
8203
+ (filePath) => path11.extname(filePath).toLowerCase() === ".metal"
8204
+ )) {
8205
+ this.logger.info("Reindexing cached Metal files for parser support");
8206
+ }
8182
8207
  const includePatterns = [...this.config.include, ...this.config.additionalInclude];
8183
8208
  const { files, skipped } = await collectFiles(
8184
8209
  this.projectRoot,
@@ -8198,14 +8223,21 @@ var Indexer = class {
8198
8223
  const changedFiles = [];
8199
8224
  const unchangedFilePaths = /* @__PURE__ */ new Set();
8200
8225
  const currentFileHashes = /* @__PURE__ */ new Map();
8226
+ const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
8201
8227
  for (const f of files) {
8202
8228
  const currentHash = hashFile(f.path);
8203
8229
  currentFileHashes.set(f.path, currentHash);
8204
- if (this.fileHashCache.get(f.path) === currentHash) {
8230
+ const cachedHashMatches = this.fileHashCache.get(f.path) === currentHash;
8231
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(f.path).some(
8232
+ (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
8233
+ );
8234
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path11.extname(f.path).toLowerCase() === ".swift";
8235
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path11.extname(f.path).toLowerCase() === ".metal";
8236
+ if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade) {
8205
8237
  unchangedFilePaths.add(f.path);
8206
8238
  this.logger.recordCacheHit();
8207
8239
  } else {
8208
- const content = await import_fs8.promises.readFile(f.path, "utf-8");
8240
+ const content = await import_fs7.promises.readFile(f.path, "utf-8");
8209
8241
  changedFiles.push({ path: f.path, content, hash: currentHash });
8210
8242
  this.logger.recordCacheMiss();
8211
8243
  }
@@ -8303,7 +8335,7 @@ var Indexer = class {
8303
8335
  for (const parsed of parsedFiles) {
8304
8336
  currentFilePaths.add(parsed.path);
8305
8337
  if (parsed.chunks.length === 0) {
8306
- const relativePath = path13.relative(this.projectRoot, parsed.path);
8338
+ const relativePath = path11.relative(this.projectRoot, parsed.path);
8307
8339
  stats.parseFailures.push(relativePath);
8308
8340
  }
8309
8341
  let chunksToProcess = parsed.chunks;
@@ -8403,16 +8435,28 @@ var Indexer = class {
8403
8435
  const fileSymbols = [];
8404
8436
  for (const chunk of parsed.chunks) {
8405
8437
  if (!chunk.name || !CALL_GRAPH_SYMBOL_CHUNK_TYPES.has(chunk.chunkType)) continue;
8406
- const symbolId = `sym_${hashContent(parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine).slice(0, 16)}`;
8438
+ const existingMetalSymbol = chunk.language === "metal" ? fileSymbols.find(
8439
+ (symbol2) => symbol2.name === chunk.name && symbol2.kind === chunk.chunkType && symbol2.startLine <= chunk.endLine && chunk.startLine <= symbol2.endLine
8440
+ ) : void 0;
8441
+ if (existingMetalSymbol) {
8442
+ existingMetalSymbol.startLine = Math.min(existingMetalSymbol.startLine, chunk.startLine);
8443
+ existingMetalSymbol.endLine = Math.max(existingMetalSymbol.endLine, chunk.endLine);
8444
+ existingMetalSymbol.startCol = Math.min(existingMetalSymbol.startCol, chunk.startCol ?? 0);
8445
+ existingMetalSymbol.endCol = Math.max(existingMetalSymbol.endCol, chunk.endCol ?? 0);
8446
+ continue;
8447
+ }
8448
+ const symbolId = `sym_${hashContent(
8449
+ parsed.path + ":" + chunk.name + ":" + chunk.chunkType + ":" + chunk.startLine + ":" + (chunk.startCol ?? 0)
8450
+ ).slice(0, 16)}`;
8407
8451
  const symbol = {
8408
8452
  id: symbolId,
8409
8453
  filePath: parsed.path,
8410
8454
  name: chunk.name,
8411
8455
  kind: chunk.chunkType,
8412
8456
  startLine: chunk.startLine,
8413
- startCol: 0,
8457
+ startCol: chunk.startCol ?? 0,
8414
8458
  endLine: chunk.endLine,
8415
- endCol: 0,
8459
+ endCol: chunk.endCol ?? 0,
8416
8460
  language: chunk.language
8417
8461
  };
8418
8462
  fileSymbols.push(symbol);
@@ -8437,8 +8481,10 @@ var Indexer = class {
8437
8481
  if (callSites.length === 0) continue;
8438
8482
  const edges = [];
8439
8483
  for (const site of callSites) {
8440
- const enclosingSymbol = fileSymbols.find(
8441
- (sym) => site.line >= sym.startLine && site.line <= sym.endLine
8484
+ const enclosingSymbol = findEnclosingSymbol(
8485
+ fileSymbols,
8486
+ site.line,
8487
+ site.column
8442
8488
  );
8443
8489
  if (!enclosingSymbol) continue;
8444
8490
  const edgeId = `edge_${hashContent(enclosingSymbol.id + ":" + site.calleeName + ":" + site.line + ":" + site.column).slice(0, 16)}`;
@@ -8457,7 +8503,21 @@ var Indexer = class {
8457
8503
  if (edges.length > 0) {
8458
8504
  database.upsertCallEdgesBatch(edges);
8459
8505
  for (const edge of edges) {
8460
- const candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8506
+ let candidates = symbolsByName.get(normalizeSymbolKey(edge.targetName));
8507
+ if (fileLanguage === "php" && candidates) {
8508
+ if (edge.callType === "Constructor") {
8509
+ candidates = candidates.filter(
8510
+ (candidate) => PHP_CLASS_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8511
+ );
8512
+ } else if (edge.callType === "Call") {
8513
+ candidates = candidates.filter(
8514
+ (candidate) => PHP_FUNCTION_SYMBOL_CHUNK_TYPES.has(candidate.kind)
8515
+ );
8516
+ }
8517
+ }
8518
+ candidates = candidates?.filter(
8519
+ (symbol) => isCompatibleCFamilyCallTarget(fileLanguage, edge.callType, symbol.kind)
8520
+ );
8461
8521
  if (candidates && candidates.length === 1) {
8462
8522
  database.resolveCallEdge(edge.id, candidates[0].id);
8463
8523
  }
@@ -8499,8 +8559,8 @@ var Indexer = class {
8499
8559
  database.addChunksToBranchBatch(branchCatalogKey, Array.from(currentChunkIds));
8500
8560
  database.clearBranchSymbols(branchCatalogKey);
8501
8561
  database.addSymbolsToBranchBatch(branchCatalogKey, Array.from(allSymbolIds));
8502
- const vectorPath = path13.join(this.indexPath, "vectors");
8503
- const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs8.existsSync)(vectorPath) && (0, import_fs8.existsSync)(`${vectorPath}.meta.json`);
8562
+ const vectorPath = path11.join(this.indexPath, "vectors");
8563
+ const shouldFingerprintLegacyPair = !store.hasFingerprint() && (0, import_fs7.existsSync)(vectorPath) && (0, import_fs7.existsSync)(`${vectorPath}.meta.json`);
8504
8564
  if (backfilledBlameMetadata || shouldFingerprintLegacyPair) {
8505
8565
  store.save();
8506
8566
  }
@@ -8512,6 +8572,8 @@ var Indexer = class {
8512
8572
  this.saveFileHashCache();
8513
8573
  this.saveFailedBatches([]);
8514
8574
  }
8575
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8576
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8515
8577
  this.saveIndexMetadata(configuredProviderInfo);
8516
8578
  this.indexCompatibility = { compatible: true };
8517
8579
  stats.durationMs = Date.now() - startTime;
@@ -8539,6 +8601,8 @@ var Indexer = class {
8539
8601
  this.saveFileHashCache();
8540
8602
  this.saveFailedBatches([]);
8541
8603
  }
8604
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8605
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8542
8606
  this.saveIndexMetadata(configuredProviderInfo);
8543
8607
  this.indexCompatibility = { compatible: true };
8544
8608
  stats.durationMs = Date.now() - startTime;
@@ -8809,6 +8873,8 @@ var Indexer = class {
8809
8873
  if (forceScopedReembed && failedForcedChunkIds.size === 0) {
8810
8874
  database.deleteMetadata(this.getProjectForceReembedMetadataKey());
8811
8875
  }
8876
+ database.setMetadata(swiftParserMetadataKey, SWIFT_PARSER_VERSION);
8877
+ database.setMetadata(metalParserMetadataKey, METAL_PARSER_VERSION);
8812
8878
  this.saveIndexMetadata(configuredProviderInfo);
8813
8879
  this.indexCompatibility = { compatible: true };
8814
8880
  this.logger.recordIndexingEnd();
@@ -9055,7 +9121,7 @@ var Indexer = class {
9055
9121
  let contextEndLine = r.metadata.endLine;
9056
9122
  if (!metadataOnly && this.config.search.includeContext) {
9057
9123
  try {
9058
- const fileContent = await import_fs8.promises.readFile(
9124
+ const fileContent = await import_fs7.promises.readFile(
9059
9125
  r.metadata.filePath,
9060
9126
  "utf-8"
9061
9127
  );
@@ -9242,7 +9308,7 @@ var Indexer = class {
9242
9308
  const removedChunkKeys = [];
9243
9309
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
9244
9310
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
9245
- if (!(0, import_fs8.existsSync)(filePath)) {
9311
+ if (!(0, import_fs7.existsSync)(filePath)) {
9246
9312
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
9247
9313
  for (const key of chunkKeys) {
9248
9314
  removedChunkKeys.push(key);
@@ -9291,7 +9357,7 @@ var Indexer = class {
9291
9357
  gcOrphanSymbols: 0,
9292
9358
  gcOrphanCallEdges: 0,
9293
9359
  resetCorruptedIndex: true,
9294
- warning: this.getCorruptedIndexWarning(path13.join(this.indexPath, "codebase.db"))
9360
+ warning: this.getCorruptedIndexWarning(path11.join(this.indexPath, "codebase.db"))
9295
9361
  };
9296
9362
  }
9297
9363
  this.logger.recordGc(removedCount, gcOrphanChunks, gcOrphanEmbeddings);
@@ -9601,7 +9667,7 @@ var Indexer = class {
9601
9667
  let content = "";
9602
9668
  if (this.config.search.includeContext) {
9603
9669
  try {
9604
- const fileContent = await import_fs8.promises.readFile(
9670
+ const fileContent = await import_fs7.promises.readFile(
9605
9671
  r.metadata.filePath,
9606
9672
  "utf-8"
9607
9673
  );
@@ -9721,7 +9787,7 @@ var Indexer = class {
9721
9787
  "Run index_codebase first to build the call graph and symbol index for this branch."
9722
9788
  );
9723
9789
  }
9724
- const absoluteChangedFiles = changedFiles.map((f) => path13.resolve(this.projectRoot, f));
9790
+ const absoluteChangedFiles = changedFiles.map((f) => path11.resolve(this.projectRoot, f));
9725
9791
  const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
9726
9792
  const directIds = directSymbols.map((s) => s.id);
9727
9793
  const direction = opts.direction ?? "both";
@@ -9804,7 +9870,7 @@ var Indexer = class {
9804
9870
  projectRoot: this.projectRoot,
9805
9871
  baseBranch: this.baseBranch
9806
9872
  });
9807
- const otherAbsolute = otherChanged.files.map((f) => path13.resolve(this.projectRoot, f));
9873
+ const otherAbsolute = otherChanged.files.map((f) => path11.resolve(this.projectRoot, f));
9808
9874
  const otherBranchKey = this.getBranchCatalogKeyFor(openPr.headRefName);
9809
9875
  const otherSymbols = database.getSymbolsForFiles(otherAbsolute, otherBranchKey);
9810
9876
  const otherLabels = /* @__PURE__ */ new Set();
@@ -9868,12 +9934,12 @@ var Indexer = class {
9868
9934
  if (meta.filePath) filePaths.add(meta.filePath);
9869
9935
  }
9870
9936
  const directory = options?.directory?.replace(/\/$/, "");
9871
- const absoluteDirectoryFilter = directory ? path13.resolve(this.projectRoot, directory) : void 0;
9937
+ const absoluteDirectoryFilter = directory ? path11.resolve(this.projectRoot, directory) : void 0;
9872
9938
  for (const filePath of filePaths) {
9873
9939
  if (directory) {
9874
- const absoluteFilePath = path13.resolve(filePath);
9940
+ const absoluteFilePath = path11.resolve(filePath);
9875
9941
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
9876
- const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path13.sep));
9942
+ const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path11.sep));
9877
9943
  if (!matchesRelative && !matchesProjectRelative) {
9878
9944
  continue;
9879
9945
  }
@@ -9916,43 +9982,43 @@ var Indexer = class {
9916
9982
  };
9917
9983
 
9918
9984
  // src/tools/knowledge-base-paths.ts
9919
- var path14 = __toESM(require("path"), 1);
9985
+ var path12 = __toESM(require("path"), 1);
9920
9986
  function resolveConfigPathValue(value, baseDir) {
9921
9987
  const trimmed = value.trim();
9922
9988
  if (!trimmed) {
9923
9989
  return trimmed;
9924
9990
  }
9925
- const absolutePath = path14.isAbsolute(trimmed) ? trimmed : path14.resolve(baseDir, trimmed);
9926
- return path14.normalize(absolutePath);
9991
+ const absolutePath = path12.isAbsolute(trimmed) ? trimmed : path12.resolve(baseDir, trimmed);
9992
+ return path12.normalize(absolutePath);
9927
9993
  }
9928
9994
  function serializeConfigPathValue(value, baseDir) {
9929
9995
  const trimmed = value.trim();
9930
9996
  if (!trimmed) {
9931
9997
  return trimmed;
9932
9998
  }
9933
- if (!path14.isAbsolute(trimmed)) {
9934
- return normalizePathSeparators(path14.normalize(trimmed));
9999
+ if (!path12.isAbsolute(trimmed)) {
10000
+ return normalizePathSeparators(path12.normalize(trimmed));
9935
10001
  }
9936
- const relativePath = path14.relative(baseDir, trimmed);
9937
- if (!relativePath || !relativePath.startsWith("..") && !path14.isAbsolute(relativePath)) {
9938
- return normalizePathSeparators(path14.normalize(relativePath || "."));
10002
+ const relativePath = path12.relative(baseDir, trimmed);
10003
+ if (!relativePath || !relativePath.startsWith("..") && !path12.isAbsolute(relativePath)) {
10004
+ return normalizePathSeparators(path12.normalize(relativePath || "."));
9939
10005
  }
9940
- return path14.normalize(trimmed);
10006
+ return path12.normalize(trimmed);
9941
10007
  }
9942
10008
  function resolveKnowledgeBasePath(value, projectRoot3) {
9943
- return path14.isAbsolute(value) ? value : path14.resolve(projectRoot3, value);
10009
+ return path12.isAbsolute(value) ? value : path12.resolve(projectRoot3, value);
9944
10010
  }
9945
- function normalizeKnowledgeBasePath2(value, projectRoot3) {
9946
- return path14.normalize(resolveKnowledgeBasePath(value, projectRoot3));
10011
+ function normalizeKnowledgeBasePath(value, projectRoot3) {
10012
+ return path12.normalize(resolveKnowledgeBasePath(value, projectRoot3));
9947
10013
  }
9948
10014
  function hasMatchingKnowledgeBasePath(knowledgeBases, inputPath, projectRoot3) {
9949
- const normalizedInput = path14.normalize(inputPath);
9950
- return knowledgeBases.some((kb) => normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput);
10015
+ const normalizedInput = path12.normalize(inputPath);
10016
+ return knowledgeBases.some((kb) => normalizeKnowledgeBasePath(kb, projectRoot3) === normalizedInput);
9951
10017
  }
9952
10018
  function findKnowledgeBasePathIndex(knowledgeBases, inputPath, projectRoot3) {
9953
- const normalizedInput = path14.normalize(inputPath);
10019
+ const normalizedInput = path12.normalize(inputPath);
9954
10020
  return knowledgeBases.findIndex(
9955
- (kb) => path14.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath2(kb, projectRoot3) === normalizedInput
10021
+ (kb) => path12.normalize(kb) === normalizedInput || normalizeKnowledgeBasePath(kb, projectRoot3) === normalizedInput
9956
10022
  );
9957
10023
  }
9958
10024
 
@@ -10354,6 +10420,193 @@ ${truncateContent(r.content)}
10354
10420
  // src/tools/config-state.ts
10355
10421
  var import_fs9 = require("fs");
10356
10422
  var path15 = __toESM(require("path"), 1);
10423
+
10424
+ // src/config/merger.ts
10425
+ var import_fs8 = require("fs");
10426
+ var path14 = __toESM(require("path"), 1);
10427
+
10428
+ // src/config/rebase.ts
10429
+ var path13 = __toESM(require("path"), 1);
10430
+ function isWithinRoot(rootDir, targetPath) {
10431
+ const relativePath = path13.relative(rootDir, targetPath);
10432
+ return relativePath === "" || !relativePath.startsWith("..") && !path13.isAbsolute(relativePath);
10433
+ }
10434
+ function resolveInheritedKnowledgeBaseEntries(values, sourceRoot, targetRoot) {
10435
+ if (!Array.isArray(values)) {
10436
+ return [];
10437
+ }
10438
+ return values.filter((value) => typeof value === "string").map((value) => {
10439
+ const trimmed = value.trim();
10440
+ if (!trimmed) {
10441
+ return trimmed;
10442
+ }
10443
+ if (path13.isAbsolute(trimmed)) {
10444
+ if (isWithinRoot(sourceRoot, trimmed)) {
10445
+ return normalizePathSeparators(path13.normalize(path13.relative(sourceRoot, trimmed) || "."));
10446
+ }
10447
+ return path13.normalize(trimmed);
10448
+ }
10449
+ const resolvedFromSource = path13.resolve(sourceRoot, trimmed);
10450
+ if (isWithinRoot(sourceRoot, resolvedFromSource)) {
10451
+ return normalizePathSeparators(path13.normalize(trimmed));
10452
+ }
10453
+ return normalizePathSeparators(path13.normalize(path13.relative(targetRoot, resolvedFromSource)));
10454
+ }).filter(Boolean);
10455
+ }
10456
+
10457
+ // src/config/merger.ts
10458
+ var PROJECT_OVERRIDE_KEYS = [
10459
+ "embeddingProvider",
10460
+ "customProvider",
10461
+ "embeddingModel",
10462
+ "reranker",
10463
+ "include",
10464
+ "exclude",
10465
+ "indexing",
10466
+ "search",
10467
+ "debug",
10468
+ "scope"
10469
+ ];
10470
+ var MERGE_ARRAY_KEYS = ["knowledgeBases", "additionalInclude"];
10471
+ function isRecord(value) {
10472
+ return typeof value === "object" && value !== null && !Array.isArray(value);
10473
+ }
10474
+ function isStringArray2(value) {
10475
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
10476
+ }
10477
+ function applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key) {
10478
+ if (key in normalizedProjectConfig) {
10479
+ merged[key] = normalizedProjectConfig[key];
10480
+ return;
10481
+ }
10482
+ if (key in globalConfig) {
10483
+ merged[key] = globalConfig[key];
10484
+ }
10485
+ }
10486
+ function mergeUniqueStringArray(values) {
10487
+ return [...new Set(values.map((value) => String(value).trim()))];
10488
+ }
10489
+ function normalizeKnowledgeBasePath2(value) {
10490
+ let normalized = path14.normalize(String(value).trim());
10491
+ const root = path14.parse(normalized).root;
10492
+ while (normalized.length > root.length && /[\\/]$/.test(normalized)) {
10493
+ normalized = normalized.slice(0, -1);
10494
+ }
10495
+ return normalized;
10496
+ }
10497
+ function mergeKnowledgeBasePaths(values) {
10498
+ return [...new Set(values.map((value) => normalizeKnowledgeBasePath2(value)).filter((value) => value.length > 0))];
10499
+ }
10500
+ function validateConfigLayerShape(rawConfig, filePath) {
10501
+ if (!isRecord(rawConfig)) {
10502
+ throw new Error(`Config file ${filePath} must contain a JSON object at the root.`);
10503
+ }
10504
+ if (rawConfig.knowledgeBases !== void 0 && !isStringArray2(rawConfig.knowledgeBases)) {
10505
+ throw new Error(`Config file ${filePath} field 'knowledgeBases' must be an array of strings.`);
10506
+ }
10507
+ if (rawConfig.additionalInclude !== void 0 && !isStringArray2(rawConfig.additionalInclude)) {
10508
+ throw new Error(`Config file ${filePath} field 'additionalInclude' must be an array of strings.`);
10509
+ }
10510
+ if (rawConfig.include !== void 0 && !isStringArray2(rawConfig.include)) {
10511
+ throw new Error(`Config file ${filePath} field 'include' must be an array of strings.`);
10512
+ }
10513
+ if (rawConfig.exclude !== void 0 && !isStringArray2(rawConfig.exclude)) {
10514
+ throw new Error(`Config file ${filePath} field 'exclude' must be an array of strings.`);
10515
+ }
10516
+ for (const section of ["customProvider", "indexing", "search", "debug", "reranker"]) {
10517
+ const value = rawConfig[section];
10518
+ if (value !== void 0 && !isRecord(value)) {
10519
+ throw new Error(`Config file ${filePath} field '${section}' must be an object.`);
10520
+ }
10521
+ }
10522
+ return rawConfig;
10523
+ }
10524
+ function loadJsonFile(filePath) {
10525
+ if (!(0, import_fs8.existsSync)(filePath)) {
10526
+ return null;
10527
+ }
10528
+ try {
10529
+ const content = (0, import_fs8.readFileSync)(filePath, "utf-8");
10530
+ return validateConfigLayerShape(JSON.parse(content), filePath);
10531
+ } catch (error) {
10532
+ if (error instanceof Error && error.message.startsWith("Config file ")) {
10533
+ throw error;
10534
+ }
10535
+ const message = error instanceof Error ? error.message : String(error);
10536
+ throw new Error(`Failed to load config file ${filePath}: ${message}`);
10537
+ }
10538
+ }
10539
+ function loadProjectConfigLayer(projectRoot3, host = "opencode") {
10540
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
10541
+ const projectConfig = loadJsonFile(projectConfigPath);
10542
+ if (!projectConfig) {
10543
+ return {};
10544
+ }
10545
+ const normalizedConfig = { ...projectConfig };
10546
+ const projectConfigBaseDir = path14.dirname(path14.dirname(projectConfigPath));
10547
+ if (Array.isArray(normalizedConfig.knowledgeBases)) {
10548
+ normalizedConfig.knowledgeBases = resolveInheritedKnowledgeBaseEntries(
10549
+ normalizedConfig.knowledgeBases,
10550
+ projectConfigBaseDir,
10551
+ projectRoot3
10552
+ );
10553
+ }
10554
+ return normalizedConfig;
10555
+ }
10556
+ function loadMergedConfig(projectRoot3, host = "opencode") {
10557
+ const globalConfigPath = resolveGlobalConfigPath(host);
10558
+ const projectConfigPath = resolveProjectConfigPath(projectRoot3, host);
10559
+ let globalConfig = null;
10560
+ let globalConfigError = null;
10561
+ try {
10562
+ globalConfig = loadJsonFile(globalConfigPath);
10563
+ } catch (error) {
10564
+ globalConfigError = error instanceof Error ? error : new Error(String(error));
10565
+ }
10566
+ const projectConfig = loadJsonFile(projectConfigPath);
10567
+ const normalizedProjectConfig = loadProjectConfigLayer(projectRoot3, host);
10568
+ if (globalConfigError) {
10569
+ if (!projectConfig) {
10570
+ throw globalConfigError;
10571
+ }
10572
+ globalConfig = null;
10573
+ }
10574
+ if (!globalConfig && !projectConfig) {
10575
+ return {};
10576
+ }
10577
+ if (!projectConfig && globalConfig) {
10578
+ return globalConfig;
10579
+ }
10580
+ if (!globalConfig && projectConfig) {
10581
+ return normalizedProjectConfig;
10582
+ }
10583
+ if (!globalConfig || !projectConfig) {
10584
+ return globalConfig ?? normalizedProjectConfig;
10585
+ }
10586
+ const merged = { ...globalConfig };
10587
+ for (const key of PROJECT_OVERRIDE_KEYS) {
10588
+ applyProjectOverride(merged, normalizedProjectConfig, globalConfig, key);
10589
+ }
10590
+ if (projectConfig) {
10591
+ for (const key of Object.keys(projectConfig)) {
10592
+ if (PROJECT_OVERRIDE_KEYS.includes(key) || MERGE_ARRAY_KEYS.includes(key)) {
10593
+ continue;
10594
+ }
10595
+ merged[key] = normalizedProjectConfig[key];
10596
+ }
10597
+ }
10598
+ const globalKbs = globalConfig && Array.isArray(globalConfig.knowledgeBases) ? globalConfig.knowledgeBases : [];
10599
+ const projectKbs = projectConfig ? Array.isArray(normalizedProjectConfig.knowledgeBases) ? normalizedProjectConfig.knowledgeBases : [] : [];
10600
+ const allKbs = [...globalKbs, ...projectKbs];
10601
+ merged.knowledgeBases = mergeKnowledgeBasePaths(allKbs);
10602
+ const globalAdditional = globalConfig && Array.isArray(globalConfig.additionalInclude) ? globalConfig.additionalInclude : [];
10603
+ const projectAdditional = projectConfig && Array.isArray(projectConfig.additionalInclude) ? projectConfig.additionalInclude : [];
10604
+ const allAdditional = [...globalAdditional, ...projectAdditional];
10605
+ merged.additionalInclude = mergeUniqueStringArray(allAdditional);
10606
+ return merged;
10607
+ }
10608
+
10609
+ // src/tools/config-state.ts
10357
10610
  function normalizeKnowledgeBasePaths(config, projectRoot3) {
10358
10611
  const normalized = { ...config };
10359
10612
  if (Array.isArray(normalized.knowledgeBases)) {
@@ -10451,15 +10704,6 @@ function refreshIndexerForDirectory(projectRoot3, host = "opencode", config = pa
10451
10704
  indexerCache.set(key, indexer);
10452
10705
  configCache.set(key, config);
10453
10706
  }
10454
- function shouldForceLocalizeProjectIndex(projectRoot3, host = "opencode") {
10455
- const root = getProjectRoot(projectRoot3, host);
10456
- const localIndexPath = path16.join(root, getHostProjectIndexRelativePath(host));
10457
- if ((0, import_fs10.existsSync)(localIndexPath)) {
10458
- return false;
10459
- }
10460
- const inheritedIndexPath = resolveWorktreeFallbackProjectIndexPath(root, host);
10461
- return inheritedIndexPath !== null;
10462
- }
10463
10707
  async function searchCodebase(projectRoot3, host, query, options = {}) {
10464
10708
  const indexer = getIndexerForProject(projectRoot3, host);
10465
10709
  return indexer.search(query, options.limit, {
@@ -10509,9 +10753,7 @@ async function getCallGraphPath(projectRoot3, host, from, to, maxDepth) {
10509
10753
  }
10510
10754
  async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10511
10755
  const root = getProjectRoot(projectRoot3, host);
10512
- const key = getIndexerCacheKey(root, host);
10513
- let indexer = getIndexerForProject(root, host);
10514
- const runtimeConfig = configCache.get(key);
10756
+ const indexer = getIndexerForProject(root, host);
10515
10757
  try {
10516
10758
  if (args.estimateOnly) {
10517
10759
  return { kind: "estimate", estimate: await indexer.estimateCost() };
@@ -10532,19 +10774,7 @@ async function runIndexCodebase(projectRoot3, host, args, onProgress) {
10532
10774
  return Promise.resolve();
10533
10775
  });
10534
10776
  };
10535
- let stats;
10536
- if (args.force && shouldForceLocalizeProjectIndex(root, host)) {
10537
- const inheritedIndexPath = resolveProjectIndexPath(root, runtimeConfig.scope, host);
10538
- stats = await withIndexLock(inheritedIndexPath, "force-index", async () => {
10539
- materializeLocalProjectConfig(root, loadProjectConfigLayer(root, host), host);
10540
- refreshIndexerForDirectory(root, host, runtimeConfig);
10541
- indexer = getIndexerForProject(root, host);
10542
- return runIndex(indexer);
10543
- }, { completeRecoveries: false });
10544
- } else {
10545
- stats = await runIndex(indexer);
10546
- }
10547
- return { kind: "stats", stats };
10777
+ return { kind: "stats", stats: await runIndex(indexer) };
10548
10778
  } catch (error) {
10549
10779
  const busyResult = getIndexBusyResult(error);
10550
10780
  if (!busyResult) throw error;