opencode-codebase-index 0.20.1 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -651,7 +651,7 @@ var require_ignore = __commonJS({
651
651
  }
652
652
  });
653
653
 
654
- // src/cli.ts
654
+ // src/adapters/mcp/cli.ts
655
655
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
656
656
  import { realpathSync as realpathSync6, writeFileSync as writeFileSync6 } from "fs";
657
657
  import * as os8 from "os";
@@ -4140,23 +4140,61 @@ import * as os2 from "os";
4140
4140
  import * as path5 from "path";
4141
4141
  import * as module from "module";
4142
4142
  import { fileURLToPath } from "url";
4143
- function getNativeBinding() {
4144
- const platform2 = os2.platform();
4145
- const arch2 = os2.arch();
4146
- let bindingName;
4143
+
4144
+ // src/identity-catalog.json
4145
+ var identity_catalog_default = {
4146
+ product: {
4147
+ current: {
4148
+ productName: "opencode-codebase-index",
4149
+ packageName: "opencode-codebase-index",
4150
+ repository: "https://github.com/Helweg/opencode-codebase-index",
4151
+ mcpBinary: "opencode-codebase-index-mcp",
4152
+ mcpServerName: "opencode-codebase-index"
4153
+ },
4154
+ future: {
4155
+ productName: "open-codebase-index",
4156
+ packageName: "open-codebase-index",
4157
+ repository: "https://github.com/Helweg/open-codebase-index",
4158
+ mcpBinary: "open-codebase-index-mcp",
4159
+ mcpServerName: "open-codebase-index"
4160
+ }
4161
+ },
4162
+ native: {
4163
+ binaryName: "codebase-index-native"
4164
+ }
4165
+ };
4166
+
4167
+ // src/identity-catalog.ts
4168
+ var IDENTITY_CATALOG = identity_catalog_default;
4169
+ var CURRENT_PRODUCT = IDENTITY_CATALOG.product.current;
4170
+ var FUTURE_PRODUCT = IDENTITY_CATALOG.product.future;
4171
+ var MCP_SERVER_CURRENT_NAME = CURRENT_PRODUCT.mcpServerName;
4172
+ var MCP_BINARY_CURRENT_NAME = CURRENT_PRODUCT.mcpBinary;
4173
+ var STABLE_NATIVE_BINARY_NAME = IDENTITY_CATALOG.native.binaryName;
4174
+
4175
+ // src/native/binding.ts
4176
+ function getNativeBindingFilename(platform2 = os2.platform(), arch2 = os2.arch()) {
4147
4177
  if (platform2 === "darwin" && arch2 === "arm64") {
4148
- bindingName = "codebase-index-native.darwin-arm64.node";
4149
- } else if (platform2 === "darwin" && arch2 === "x64") {
4150
- bindingName = "codebase-index-native.darwin-x64.node";
4151
- } else if (platform2 === "linux" && arch2 === "x64") {
4152
- bindingName = "codebase-index-native.linux-x64-gnu.node";
4153
- } else if (platform2 === "linux" && arch2 === "arm64") {
4154
- bindingName = "codebase-index-native.linux-arm64-gnu.node";
4155
- } else if (platform2 === "win32" && arch2 === "x64") {
4156
- bindingName = "codebase-index-native.win32-x64-msvc.node";
4157
- } else {
4158
- throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
4178
+ return `${STABLE_NATIVE_BINARY_NAME}.darwin-arm64.node`;
4179
+ }
4180
+ if (platform2 === "darwin" && arch2 === "x64") {
4181
+ return `${STABLE_NATIVE_BINARY_NAME}.darwin-x64.node`;
4182
+ }
4183
+ if (platform2 === "linux" && arch2 === "x64") {
4184
+ return `${STABLE_NATIVE_BINARY_NAME}.linux-x64-gnu.node`;
4185
+ }
4186
+ if (platform2 === "linux" && arch2 === "arm64") {
4187
+ return `${STABLE_NATIVE_BINARY_NAME}.linux-arm64-gnu.node`;
4159
4188
  }
4189
+ if (platform2 === "win32" && arch2 === "x64") {
4190
+ return `${STABLE_NATIVE_BINARY_NAME}.win32-x64-msvc.node`;
4191
+ }
4192
+ throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
4193
+ }
4194
+ function resolveNativeBindingPath(packageRoot, platform2 = os2.platform(), arch2 = os2.arch()) {
4195
+ return path5.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
4196
+ }
4197
+ function getNativeBinding() {
4160
4198
  let currentDir;
4161
4199
  let requireTarget;
4162
4200
  if (typeof import.meta !== "undefined" && import.meta.url) {
@@ -4172,7 +4210,7 @@ function getNativeBinding() {
4172
4210
  const normalizedDir = currentDir.replace(/\\/g, "/");
4173
4211
  const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path5.join("src", "native"));
4174
4212
  const packageRoot = isDevMode ? path5.resolve(currentDir, "../..") : path5.resolve(currentDir, "..");
4175
- const nativePath = path5.join(packageRoot, "native", bindingName);
4213
+ const nativePath = resolveNativeBindingPath(packageRoot);
4176
4214
  const require2 = module.createRequire(requireTarget);
4177
4215
  return require2(nativePath);
4178
4216
  }
@@ -5502,8 +5540,37 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
5502
5540
  const fallbackPath = path10.join(mainRepoRoot, relativePath);
5503
5541
  return existsSync5(fallbackPath) ? fallbackPath : null;
5504
5542
  }
5505
- function getHostProjectIndexRelativePath(host) {
5506
- return getProjectIndexRelativePath(host);
5543
+ function getProjectConfigCandidatePaths(projectRoot, host) {
5544
+ const candidates = [path10.join(projectRoot, getProjectConfigRelativePath(host))];
5545
+ if (host !== "opencode") {
5546
+ candidates.push(path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH));
5547
+ }
5548
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5549
+ if (mainRepoRoot) {
5550
+ candidates.push(path10.join(mainRepoRoot, getProjectConfigRelativePath(host)));
5551
+ if (host !== "opencode") {
5552
+ candidates.push(path10.join(mainRepoRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH));
5553
+ }
5554
+ }
5555
+ return [...new Set(candidates)];
5556
+ }
5557
+ function isProjectIndexPathOwnedByProject(projectRoot, indexPath, host) {
5558
+ const projectRoots = [projectRoot];
5559
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5560
+ if (mainRepoRoot) {
5561
+ projectRoots.push(mainRepoRoot);
5562
+ }
5563
+ const ownedIndexPaths = projectRoots.flatMap((root) => {
5564
+ const indexPaths = [path10.join(root, getProjectIndexRelativePath(host))];
5565
+ if (host !== "opencode") {
5566
+ indexPaths.push(path10.join(root, OPENCODE_PROJECT_INDEX_RELATIVE_PATH));
5567
+ }
5568
+ return indexPaths;
5569
+ });
5570
+ const canonicalIndexPath = canonicalizePathForComparison(indexPath);
5571
+ return ownedIndexPaths.some(
5572
+ (ownedPath) => canonicalizePathForComparison(ownedPath) === canonicalIndexPath
5573
+ );
5507
5574
  }
5508
5575
  function hasHostProjectConfig(projectRoot, host) {
5509
5576
  return existsSync5(path10.join(projectRoot, getProjectConfigRelativePath(host)));
@@ -5511,7 +5578,7 @@ function hasHostProjectConfig(projectRoot, host) {
5511
5578
  function hasHostGlobalConfig(host) {
5512
5579
  return existsSync5(getGlobalConfigPath(host));
5513
5580
  }
5514
- function getGlobalIndexPath(host = "opencode") {
5581
+ function getGlobalIndexPath(host) {
5515
5582
  switch (host) {
5516
5583
  case "opencode":
5517
5584
  return path10.join(os4.homedir(), ".opencode", "global-index");
@@ -5521,7 +5588,7 @@ function getGlobalIndexPath(host = "opencode") {
5521
5588
  return path10.join(os4.homedir(), ".codebase-index", "global-index");
5522
5589
  }
5523
5590
  }
5524
- function getGlobalConfigPath(host = "opencode") {
5591
+ function getGlobalConfigPath(host) {
5525
5592
  switch (host) {
5526
5593
  case "opencode":
5527
5594
  return path10.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
@@ -5531,7 +5598,7 @@ function getGlobalConfigPath(host = "opencode") {
5531
5598
  return path10.join(os4.homedir(), ".config", "codebase-index", "config.json");
5532
5599
  }
5533
5600
  }
5534
- function resolveGlobalConfigPath(host = "opencode") {
5601
+ function resolveGlobalConfigPath(host) {
5535
5602
  const hostConfigPath = getGlobalConfigPath(host);
5536
5603
  if (existsSync5(hostConfigPath)) {
5537
5604
  return hostConfigPath;
@@ -5544,7 +5611,7 @@ function resolveGlobalConfigPath(host = "opencode") {
5544
5611
  }
5545
5612
  return hostConfigPath;
5546
5613
  }
5547
- function resolveGlobalIndexPath(host = "opencode") {
5614
+ function resolveGlobalIndexPath(host) {
5548
5615
  const hostIndexPath = getGlobalIndexPath(host);
5549
5616
  if (existsSync5(hostIndexPath)) {
5550
5617
  return hostIndexPath;
@@ -5560,37 +5627,35 @@ function resolveGlobalIndexPath(host = "opencode") {
5560
5627
  }
5561
5628
  return hostIndexPath;
5562
5629
  }
5563
- function resolveProjectConfigPath(projectRoot, host = "opencode") {
5564
- const hostConfigPath = path10.join(projectRoot, getProjectConfigRelativePath(host));
5565
- if (existsSync5(hostConfigPath)) {
5566
- return hostConfigPath;
5567
- }
5568
- if (host !== "opencode") {
5569
- const legacyConfigPath = path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5570
- if (existsSync5(legacyConfigPath)) {
5571
- return legacyConfigPath;
5572
- }
5573
- }
5574
- const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectConfigRelativePath(host));
5575
- if (hostFallback) {
5576
- return hostFallback;
5577
- }
5578
- if (host !== "opencode") {
5579
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5580
- if (legacyFallback) {
5581
- return legacyFallback;
5582
- }
5583
- }
5584
- return hostConfigPath;
5585
- }
5586
- function resolveWritableProjectConfigPath(projectRoot, host = "opencode") {
5587
- return path10.join(projectRoot, getProjectConfigRelativePath(host));
5630
+ function resolveProjectConfigPath(projectRoot, host) {
5631
+ const candidates = getProjectConfigCandidatePaths(projectRoot, host);
5632
+ return candidates.find((candidate) => existsSync5(candidate)) ?? path10.join(projectRoot, getProjectConfigRelativePath(host));
5588
5633
  }
5589
- function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
5634
+ function resolveProjectIndexPath(projectRoot, scope, host) {
5590
5635
  if (scope === "global") {
5591
5636
  return resolveGlobalIndexPath(host);
5592
5637
  }
5593
5638
  const localIndexPath = path10.join(projectRoot, getProjectIndexRelativePath(host));
5639
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5640
+ if (mainRepoRoot) {
5641
+ if (hasHostProjectConfig(projectRoot, host)) {
5642
+ return localIndexPath;
5643
+ }
5644
+ if (host !== "opencode") {
5645
+ const localLegacyConfigPath = path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5646
+ if (existsSync5(localLegacyConfigPath)) {
5647
+ return path10.join(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
5648
+ }
5649
+ const mainHostConfigPath = path10.join(mainRepoRoot, getProjectConfigRelativePath(host));
5650
+ const mainHostIndexPath = path10.join(mainRepoRoot, getProjectIndexRelativePath(host));
5651
+ const mainLegacyConfigPath = path10.join(mainRepoRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5652
+ const mainLegacyIndexPath = path10.join(mainRepoRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
5653
+ if (!existsSync5(mainHostConfigPath) && !existsSync5(mainHostIndexPath) && (existsSync5(mainLegacyConfigPath) || existsSync5(mainLegacyIndexPath))) {
5654
+ return mainLegacyIndexPath;
5655
+ }
5656
+ }
5657
+ return path10.join(mainRepoRoot, getProjectIndexRelativePath(host));
5658
+ }
5594
5659
  if (existsSync5(localIndexPath)) {
5595
5660
  return localIndexPath;
5596
5661
  }
@@ -5603,9 +5668,6 @@ function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
5603
5668
  if (hasHostProjectConfig(projectRoot, host)) {
5604
5669
  return localIndexPath;
5605
5670
  }
5606
- if (resolveWorktreeMainRepoRoot(projectRoot)) {
5607
- return localIndexPath;
5608
- }
5609
5671
  const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
5610
5672
  if (hostFallback) {
5611
5673
  return hostFallback;
@@ -7682,6 +7744,8 @@ function hasBlameMetadata(metadata) {
7682
7744
  return blameFromMetadata(metadata) !== void 0;
7683
7745
  }
7684
7746
  var INDEX_METADATA_VERSION = "1";
7747
+ var PROJECT_PATH_STORAGE_VERSION = "2";
7748
+ var GLOBAL_PATH_STORAGE_VERSION = "1";
7685
7749
  var EMBEDDING_STRATEGY_VERSION = "2";
7686
7750
  var SWIFT_PARSER_VERSION = "1";
7687
7751
  var METAL_PARSER_VERSION = "1";
@@ -8012,18 +8076,20 @@ function selectIndexableChunks(chunks, limit, semanticOnly) {
8012
8076
  const indexableChunks = semanticOnly ? chunks.filter((chunk) => chunk.chunkType !== "other") : chunks;
8013
8077
  return selectChunksWithFileCoverage(indexableChunks, limit);
8014
8078
  }
8015
- function matchesHardSearchFilters(candidate, options) {
8079
+ function matchesHardSearchFilters(candidate, options, projectRoot) {
8016
8080
  if (options?.fileType) {
8017
8081
  const ext = candidate.metadata.filePath.split(".").pop()?.toLowerCase();
8018
8082
  const requestedExtension = options.fileType.trim().toLowerCase().replace(/^\./, "");
8019
8083
  if (ext !== requestedExtension) return false;
8020
8084
  }
8021
8085
  if (options?.directory) {
8022
- const normalizedPath = candidate.metadata.filePath.replace(/\\/g, "/");
8023
- const normalizedDir = options.directory.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
8024
- const isAbsoluteDirectory = normalizedDir.startsWith("/");
8025
- const matchesDirectory = isAbsoluteDirectory ? normalizedPath === normalizedDir || normalizedPath.startsWith(`${normalizedDir}/`) : normalizedPath === normalizedDir || normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath.includes(`/${normalizedDir}/`) || normalizedPath.endsWith(`/${normalizedDir}`);
8026
- if (!matchesDirectory) return false;
8086
+ const candidatePath = canonicalizePathForComparison(
8087
+ path14.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path14.sep))
8088
+ );
8089
+ const directoryPath = canonicalizePathForComparison(
8090
+ path14.resolve(projectRoot, options.directory.trim().replace(/\\/g, path14.sep))
8091
+ );
8092
+ if (!isPathWithinRoot2(candidatePath, directoryPath)) return false;
8027
8093
  }
8028
8094
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
8029
8095
  return false;
@@ -8045,8 +8111,8 @@ function matchesHardSearchFilters(candidate, options) {
8045
8111
  }
8046
8112
  return true;
8047
8113
  }
8048
- function matchesSearchFilters(candidate, options, minScore) {
8049
- return candidate.score >= minScore && matchesHardSearchFilters(candidate, options);
8114
+ function matchesSearchFilters(candidate, options, minScore, projectRoot) {
8115
+ return candidate.score >= minScore && matchesHardSearchFilters(candidate, options, projectRoot);
8050
8116
  }
8051
8117
  function unionCandidates(semanticCandidates, keywordCandidates) {
8052
8118
  const byId = /* @__PURE__ */ new Map();
@@ -8097,7 +8163,7 @@ var Indexer = class _Indexer {
8097
8163
  readerArtifactFingerprint = null;
8098
8164
  writerArtifactFingerprint = null;
8099
8165
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
8100
- constructor(projectRoot, config, host = "opencode", runtimeOptions = {}) {
8166
+ constructor(projectRoot, config, host, runtimeOptions = {}) {
8101
8167
  this.projectRoot = projectRoot;
8102
8168
  this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8103
8169
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
@@ -8110,37 +8176,90 @@ var Indexer = class _Indexer {
8110
8176
  this.indexPathOverride = runtimeOptions.indexPath;
8111
8177
  this.config = config;
8112
8178
  this.host = host;
8179
+ if (isGitRepo(this.materializedProjectRoot)) {
8180
+ this.currentBranch = this.branchNameOverride ?? getBranchOrDefault(this.materializedProjectRoot);
8181
+ this.baseBranch = getBaseBranch(this.materializedProjectRoot);
8182
+ } else {
8183
+ this.currentBranch = "default";
8184
+ this.baseBranch = "default";
8185
+ }
8113
8186
  this.indexPath = this.getIndexPath();
8114
- this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8115
- this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8187
+ this.refreshRuntimeArtifactPaths();
8116
8188
  this.logger = initializeLogger(config.debug);
8117
8189
  }
8118
8190
  getIndexPath() {
8119
8191
  return this.indexPathOverride ?? resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
8120
8192
  }
8121
8193
  toCanonicalFilePath(filePath) {
8194
+ if (!path14.isAbsolute(filePath)) {
8195
+ return this.resolveStoredFilePath(filePath, this.projectRoot);
8196
+ }
8122
8197
  if (path14.resolve(this.materializedProjectRoot) === path14.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
8123
8198
  return filePath;
8124
8199
  }
8125
8200
  return path14.resolve(this.projectRoot, path14.relative(this.materializedProjectRoot, filePath));
8126
8201
  }
8127
- toMaterializedFilePath(filePath) {
8128
- if (path14.resolve(this.materializedProjectRoot) === path14.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.projectRoot)) {
8202
+ toStoredFilePath(filePath) {
8203
+ const canonicalFilePath = this.toCanonicalFilePath(filePath);
8204
+ if (this.config.scope !== "project" || !isPathWithinRoot2(canonicalFilePath, this.projectRoot)) {
8205
+ return canonicalFilePath;
8206
+ }
8207
+ return path14.relative(this.projectRoot, canonicalFilePath).split(path14.sep).join("/");
8208
+ }
8209
+ resolveStoredFilePath(filePath, rootPath = this.projectRoot) {
8210
+ if (path14.isAbsolute(filePath)) {
8129
8211
  return filePath;
8130
8212
  }
8131
- return path14.resolve(this.materializedProjectRoot, path14.relative(this.projectRoot, filePath));
8213
+ const resolvedPath = path14.resolve(rootPath, ...filePath.split("/"));
8214
+ if (!isPathWithinRoot2(resolvedPath, rootPath)) {
8215
+ throw new Error(`Stored project path escapes project root: ${JSON.stringify(filePath)}`);
8216
+ }
8217
+ return resolvedPath;
8218
+ }
8219
+ getCanonicalStoredFilePath(filePath) {
8220
+ return this.getCanonicalPath(this.resolveStoredFilePath(filePath));
8221
+ }
8222
+ resolveFilePathRecord(record) {
8223
+ return {
8224
+ ...record,
8225
+ filePath: this.resolveStoredFilePath(record.filePath)
8226
+ };
8227
+ }
8228
+ resolveCallEdgeFilePath(edge) {
8229
+ if (!edge.fromSymbolFilePath) return edge;
8230
+ return {
8231
+ ...edge,
8232
+ fromSymbolFilePath: this.resolveStoredFilePath(edge.fromSymbolFilePath)
8233
+ };
8234
+ }
8235
+ toMaterializedFilePath(filePath) {
8236
+ const storedFilePath = this.toStoredFilePath(filePath);
8237
+ if (path14.isAbsolute(storedFilePath)) {
8238
+ return storedFilePath;
8239
+ }
8240
+ return this.resolveStoredFilePath(storedFilePath, this.materializedProjectRoot);
8132
8241
  }
8133
8242
  getPreparedBranchNamespace() {
8134
8243
  if (!this.branchNameOverride && !this.catalogIdentityOverride) return null;
8135
8244
  return hashContent(this.getBranchCatalogKey()).slice(0, 16);
8136
8245
  }
8246
+ getRuntimeArtifactNamespace() {
8247
+ if (this.config.scope !== "project" || this.getBranchCatalogIdentity() === "default") {
8248
+ return this.getPreparedBranchNamespace();
8249
+ }
8250
+ return hashContent(this.getBranchCatalogKey()).slice(0, 16);
8251
+ }
8137
8252
  getRuntimeArtifactPath(fileName) {
8138
- const namespace = this.getPreparedBranchNamespace();
8253
+ const namespace = this.getRuntimeArtifactNamespace();
8139
8254
  if (!namespace) return path14.join(this.indexPath, fileName);
8140
8255
  const extension = path14.extname(fileName);
8141
8256
  const baseName = fileName.slice(0, fileName.length - extension.length);
8142
8257
  return path14.join(this.indexPath, `${baseName}.${namespace}${extension}`);
8143
8258
  }
8259
+ refreshRuntimeArtifactPaths() {
8260
+ this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8261
+ this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8262
+ }
8144
8263
  getPreparedChunkId(chunkId) {
8145
8264
  const namespace = this.getPreparedBranchNamespace();
8146
8265
  return namespace ? `${chunkId}_${namespace}` : chunkId;
@@ -8166,19 +8285,8 @@ var Indexer = class _Indexer {
8166
8285
  return path14.resolve(targetPath);
8167
8286
  }
8168
8287
  }
8169
- isLocalProjectIndexPath() {
8170
- const localProjectIndexPaths = [path14.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8171
- if (this.host !== "opencode") {
8172
- localProjectIndexPaths.push(path14.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8173
- }
8174
- return localProjectIndexPaths.some((localPath) => {
8175
- if (!existsSync7(localPath) || !existsSync7(this.indexPath)) {
8176
- return path14.resolve(this.indexPath) === path14.resolve(localPath);
8177
- }
8178
- const indexStats = statSync4(this.indexPath);
8179
- const localStats = statSync4(localPath);
8180
- return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
8181
- });
8288
+ isProjectOwnedIndexPath() {
8289
+ return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
8182
8290
  }
8183
8291
  resetLoadedIndexState(retireDatabase = false) {
8184
8292
  if (this.database) {
@@ -8213,10 +8321,10 @@ var Indexer = class _Indexer {
8213
8321
  this.readerArtifactRetryAfter.clear();
8214
8322
  }
8215
8323
  async withIndexMutationLease(operation, callback) {
8324
+ this.refreshBranchInfo();
8216
8325
  const lease = acquireIndexLock(this.indexPath, operation);
8217
8326
  this.indexPath = lease.canonicalIndexPath;
8218
- this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8219
- this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8327
+ this.refreshRuntimeArtifactPaths();
8220
8328
  this.activeIndexLease = lease;
8221
8329
  let result;
8222
8330
  let callbackError;
@@ -8388,29 +8496,24 @@ var Indexer = class _Indexer {
8388
8496
  getProjectForceReembedMetadataKey() {
8389
8497
  return `index.forceReembed.${this.projectIdentityHash}`;
8390
8498
  }
8391
- getCallGraphResolutionMetadataKey() {
8392
- if (this.config.scope !== "global") {
8393
- return "index.callGraphResolutionVersion";
8394
- }
8395
- return `index.callGraphResolutionVersion.${this.projectIdentityHash}`;
8499
+ getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
8500
+ const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
8501
+ return `${prefix}.${hashContent(branchKey).slice(0, 24)}`;
8396
8502
  }
8397
- getSwiftParserVersionMetadataKey() {
8398
- const key = "index.parser.swiftVersion";
8399
- if (this.config.scope !== "global") {
8400
- return key;
8401
- }
8402
- return `${key}.${this.projectIdentityHash}`;
8503
+ getCallGraphResolutionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8504
+ return this.getBranchMigrationMetadataKey("index.callGraphResolutionVersion", catalogIdentity);
8403
8505
  }
8404
- getMetalParserVersionMetadataKey() {
8405
- const key = "index.parser.metalVersion";
8406
- if (this.config.scope !== "global") {
8407
- return key;
8408
- }
8409
- return `${key}.${this.projectIdentityHash}`;
8506
+ getSwiftParserVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8507
+ return this.getBranchMigrationMetadataKey("index.parser.swiftVersion", catalogIdentity);
8508
+ }
8509
+ getMetalParserVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8510
+ return this.getBranchMigrationMetadataKey("index.parser.metalVersion", catalogIdentity);
8410
8511
  }
8411
8512
  getSymbolExtractorVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8412
- const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
8413
- return `index.symbolExtractorVersion.${hashContent(branchKey).slice(0, 24)}`;
8513
+ return this.getBranchMigrationMetadataKey("index.symbolExtractorVersion", catalogIdentity);
8514
+ }
8515
+ areBranchMigrationVersionsCurrent(database, catalogIdentity = this.getBranchCatalogIdentity()) {
8516
+ return database.getMetadata(this.getCallGraphResolutionMetadataKey(catalogIdentity)) === CALL_GRAPH_RESOLUTION_VERSION && database.getMetadata(this.getSwiftParserVersionMetadataKey(catalogIdentity)) === SWIFT_PARSER_VERSION && database.getMetadata(this.getMetalParserVersionMetadataKey(catalogIdentity)) === METAL_PARSER_VERSION && database.getMetadata(this.getSymbolExtractorVersionMetadataKey(catalogIdentity)) === SYMBOL_EXTRACTOR_VERSION;
8414
8517
  }
8415
8518
  hasProjectForceReembedPending() {
8416
8519
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -8545,13 +8648,14 @@ var Indexer = class _Indexer {
8545
8648
  return Array.from(keys);
8546
8649
  }
8547
8650
  isFileInCurrentScope(filePath, roots) {
8548
- if (roots.some((root) => isPathWithinRoot2(filePath, root))) return true;
8549
- const canonicalFilePath = this.getCanonicalPath(filePath);
8651
+ const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
8550
8652
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
8551
8653
  }
8552
8654
  isFileInProjectRoot(filePath) {
8553
- if (isPathWithinRoot2(filePath, this.projectRoot)) return true;
8554
- return isPathWithinRoot2(this.getCanonicalPath(filePath), this.getCanonicalPath(this.projectRoot));
8655
+ return isPathWithinRoot2(
8656
+ this.getCanonicalStoredFilePath(filePath),
8657
+ this.getCanonicalPath(this.projectRoot)
8658
+ );
8555
8659
  }
8556
8660
  clearScopedFileHashCache(roots) {
8557
8661
  for (const filePath of Array.from(this.fileHashCache.keys())) {
@@ -8952,7 +9056,10 @@ var Indexer = class _Indexer {
8952
9056
  const intent = isLikelyImplementationPath2(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
8953
9057
  parts.push(`intent_hint: ${intent}`);
8954
9058
  try {
8955
- const fileContent = await fsPromises3.readFile(candidate.metadata.filePath, "utf-8");
9059
+ const fileContent = await fsPromises3.readFile(
9060
+ this.toMaterializedFilePath(candidate.metadata.filePath),
9061
+ "utf-8"
9062
+ );
8956
9063
  const lines = fileContent.split("\n");
8957
9064
  const snippetStartLine = Math.max(1, candidate.metadata.startLine);
8958
9065
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine);
@@ -9021,7 +9128,7 @@ var Indexer = class _Indexer {
9021
9128
  if (this.config.scope === "global") {
9022
9129
  return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
9023
9130
  }
9024
- if (!this.isLocalProjectIndexPath()) {
9131
+ if (!this.isProjectOwnedIndexPath()) {
9025
9132
  return "Vector index could not be read from an inherited project index. Restore or fingerprint it from the checkout that owns the index; do not remove or rebuild it from this worktree.";
9026
9133
  }
9027
9134
  return "Vector index could not be read. Run index_codebase after the active writer finishes to fingerprint a structurally valid legacy pair, or remove this checkout's local index directory and run index_codebase to rebuild it.";
@@ -9030,7 +9137,7 @@ var Indexer = class _Indexer {
9030
9137
  if (this.config.scope === "global") {
9031
9138
  return "Shared keyword index could not be read; semantic search remains available. Restore or repair the shared keyword artifact; automatic reset is disabled for global scope.";
9032
9139
  }
9033
- if (!this.isLocalProjectIndexPath()) {
9140
+ if (!this.isProjectOwnedIndexPath()) {
9034
9141
  return "Keyword index could not be read from an inherited project index; semantic search remains available. Restore or repair it from the checkout that owns the index; do not rebuild it from this worktree.";
9035
9142
  }
9036
9143
  return "Keyword index could not be read; semantic search remains available. Restore a readable published keyword index, or run index_codebase with force=true after the active writer finishes.";
@@ -9039,10 +9146,10 @@ var Indexer = class _Indexer {
9039
9146
  if (this.config.scope === "global") {
9040
9147
  return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
9041
9148
  }
9042
- if (!this.isLocalProjectIndexPath()) {
9149
+ if (!this.isProjectOwnedIndexPath()) {
9043
9150
  return "Index database could not be read from an inherited project index. Restore or repair it from the checkout that owns the index; do not migrate or rebuild it from this worktree.";
9044
9151
  }
9045
- return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
9152
+ return "Index database could not be read. Run index_codebase with force=true to rebuild a legacy absolute-path schema, or repair the database after the active writer finishes.";
9046
9153
  }
9047
9154
  getReaderFileFingerprint(filePath, identityOnly = false) {
9048
9155
  try {
@@ -9222,7 +9329,7 @@ var Indexer = class _Indexer {
9222
9329
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
9223
9330
  if (mode === "writer") {
9224
9331
  await fsPromises3.mkdir(this.indexPath, { recursive: true });
9225
- if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
9332
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isProjectOwnedIndexPath()) {
9226
9333
  throw new Error(
9227
9334
  "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
9228
9335
  );
@@ -9323,6 +9430,7 @@ var Indexer = class _Indexer {
9323
9430
  this.baseBranch = "default";
9324
9431
  this.logger.branch("debug", "Not a git repository, using default branch");
9325
9432
  }
9433
+ this.refreshRuntimeArtifactPaths();
9326
9434
  if (mode === "writer" && recoveredOwners.length > 0) {
9327
9435
  await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
9328
9436
  }
@@ -9478,6 +9586,14 @@ var Indexer = class _Indexer {
9478
9586
  }
9479
9587
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
9480
9588
  }
9589
+ async removeProjectRuntimeStateArtifacts() {
9590
+ if (!existsSync7(this.indexPath)) return;
9591
+ const names = await fsPromises3.readdir(this.indexPath);
9592
+ const runtimeStatePattern = /^(?:file-hashes|failed-batches)(?:\.[a-f0-9]{16})?\.json$/;
9593
+ await Promise.all(
9594
+ names.filter((name) => runtimeStatePattern.test(name)).map((name) => fsPromises3.rm(path14.join(this.indexPath, name), { force: true }))
9595
+ );
9596
+ }
9481
9597
  async resetLocalIndexArtifacts() {
9482
9598
  this.store = null;
9483
9599
  this.invertedIndex = null;
@@ -9497,11 +9613,10 @@ var Indexer = class _Indexer {
9497
9613
  path14.join(this.indexPath, "vectors"),
9498
9614
  path14.join(this.indexPath, "vectors.usearch"),
9499
9615
  path14.join(this.indexPath, "vectors.meta.json"),
9500
- path14.join(this.indexPath, "inverted-index.json"),
9501
- path14.join(this.indexPath, "file-hashes.json"),
9502
- path14.join(this.indexPath, "failed-batches.json")
9616
+ path14.join(this.indexPath, "inverted-index.json")
9503
9617
  ];
9504
9618
  await Promise.all(resetPaths.map((targetPath) => fsPromises3.rm(targetPath, { recursive: true, force: true })));
9619
+ await this.removeProjectRuntimeStateArtifacts();
9505
9620
  await fsPromises3.mkdir(this.indexPath, { recursive: true });
9506
9621
  }
9507
9622
  async tryResetCorruptedIndex(stage, error) {
@@ -9551,12 +9666,20 @@ var Indexer = class _Indexer {
9551
9666
  }
9552
9667
  this.database.addChunksToBranchBatch(this.getBranchCatalogKey(), chunkIds);
9553
9668
  }
9669
+ getExpectedPathStorageVersion() {
9670
+ return this.config.scope === "project" ? PROJECT_PATH_STORAGE_VERSION : GLOBAL_PATH_STORAGE_VERSION;
9671
+ }
9672
+ hasStoredIndexData() {
9673
+ const stats = this.database?.getStats();
9674
+ return (this.store?.count() ?? 0) > 0 || (stats?.chunkCount ?? 0) > 0 || (stats?.symbolCount ?? 0) > 0 || this.fileHashCache.size > 0;
9675
+ }
9554
9676
  loadIndexMetadata() {
9555
9677
  if (!this.database) return null;
9556
9678
  const version = this.database.getMetadata("index.version");
9557
9679
  if (!version) return null;
9558
9680
  return {
9559
9681
  indexVersion: version,
9682
+ pathStorageVersion: this.database.getMetadata("index.pathStorageVersion") ?? GLOBAL_PATH_STORAGE_VERSION,
9560
9683
  embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
9561
9684
  embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
9562
9685
  embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
@@ -9571,6 +9694,7 @@ var Indexer = class _Indexer {
9571
9694
  const existingCreatedAt = this.database.getMetadata("index.createdAt");
9572
9695
  const completeProjectEmbeddingStrategyReset = !this.hasProjectForceReembedPending();
9573
9696
  this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
9697
+ this.database.setMetadata("index.pathStorageVersion", this.getExpectedPathStorageVersion());
9574
9698
  this.database.setMetadata("index.embeddingProvider", provider.provider);
9575
9699
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
9576
9700
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
@@ -9593,6 +9717,16 @@ var Indexer = class _Indexer {
9593
9717
  }
9594
9718
  validateIndexCompatibility(provider) {
9595
9719
  const storedMetadata = this.loadIndexMetadata();
9720
+ const storedPathStorageVersion = this.database?.getMetadata("index.pathStorageVersion") ?? GLOBAL_PATH_STORAGE_VERSION;
9721
+ const expectedPathStorageVersion = this.getExpectedPathStorageVersion();
9722
+ if (this.hasStoredIndexData() && storedPathStorageVersion !== expectedPathStorageVersion) {
9723
+ return {
9724
+ compatible: false,
9725
+ code: "PATH_STORAGE_MISMATCH" /* PATH_STORAGE_MISMATCH */,
9726
+ reason: `Path storage format mismatch: index uses v${storedPathStorageVersion} checkout-absolute paths, but this project requires portable v${expectedPathStorageVersion} paths. Run index_codebase with force=true to rebuild the shared project index once.`,
9727
+ storedMetadata: storedMetadata ?? void 0
9728
+ };
9729
+ }
9596
9730
  if (!storedMetadata) {
9597
9731
  return { compatible: true };
9598
9732
  }
@@ -9644,6 +9778,7 @@ var Indexer = class _Indexer {
9644
9778
  return this.indexCompatibility;
9645
9779
  }
9646
9780
  async ensureInitialized() {
9781
+ this.refreshBranchInfo();
9647
9782
  let initializedReader = false;
9648
9783
  while (true) {
9649
9784
  if (this.initializationPromise) {
@@ -9751,8 +9886,8 @@ var Indexer = class _Indexer {
9751
9886
  }
9752
9887
  const branchKey = this.getBranchCatalogKey();
9753
9888
  const alreadyIndexed = database.getBranchChunkIds(branchKey).length > 0 && database.getBranchSymbolIds(branchKey).length > 0;
9754
- const symbolsCurrent = database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) === SYMBOL_EXTRACTOR_VERSION;
9755
- if (alreadyIndexed && symbolsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9889
+ const migrationsCurrent = this.areBranchMigrationVersionsCurrent(database);
9890
+ if (alreadyIndexed && migrationsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9756
9891
  return { prepared: false };
9757
9892
  }
9758
9893
  const stats = await this.indexUnlocked(onProgress, [], true);
@@ -9846,21 +9981,21 @@ var Indexer = class _Indexer {
9846
9981
  const currentFileHashes = /* @__PURE__ */ new Map();
9847
9982
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
9848
9983
  for (const f of files) {
9849
- const canonicalPath = this.toCanonicalFilePath(f.path);
9984
+ const storedPath = this.toStoredFilePath(f.path);
9850
9985
  const currentHash = hashFile(f.path);
9851
- currentFileHashes.set(canonicalPath, currentHash);
9852
- const cachedHashMatches = this.fileHashCache.get(canonicalPath) === currentHash;
9853
- const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(canonicalPath).some(
9986
+ currentFileHashes.set(storedPath, currentHash);
9987
+ const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
9988
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
9854
9989
  (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
9855
9990
  );
9856
- const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path14.extname(canonicalPath).toLowerCase() === ".swift";
9857
- const requiresMetalParserUpgrade = reparseCachedMetalFiles && path14.extname(canonicalPath).toLowerCase() === ".metal";
9991
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path14.extname(storedPath).toLowerCase() === ".swift";
9992
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path14.extname(storedPath).toLowerCase() === ".metal";
9858
9993
  if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
9859
- unchangedFilePaths.add(canonicalPath);
9994
+ unchangedFilePaths.add(storedPath);
9860
9995
  this.logger.recordCacheHit();
9861
9996
  } else {
9862
9997
  const content = await fsPromises3.readFile(f.path, "utf-8");
9863
- changedFiles.push({ path: canonicalPath, content, hash: currentHash });
9998
+ changedFiles.push({ path: storedPath, content, hash: currentHash });
9864
9999
  this.logger.recordCacheMiss();
9865
10000
  }
9866
10001
  }
@@ -9965,8 +10100,7 @@ var Indexer = class _Indexer {
9965
10100
  for (const parsed of parsedFiles) {
9966
10101
  currentFilePaths.add(parsed.path);
9967
10102
  if (parsed.chunks.length === 0) {
9968
- const relativePath = path14.relative(this.projectRoot, parsed.path);
9969
- stats.parseFailures.push(relativePath);
10103
+ stats.parseFailures.push(path14.isAbsolute(parsed.path) ? path14.relative(this.projectRoot, parsed.path) : parsed.path);
9970
10104
  }
9971
10105
  let chunksToProcess = parsed.chunks;
9972
10106
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
@@ -10607,6 +10741,43 @@ var Indexer = class _Indexer {
10607
10741
  const union = a.size + b.size - intersection;
10608
10742
  return intersection / union;
10609
10743
  }
10744
+ getBranchPrefilterState(database, branchChunkIds) {
10745
+ const hasInitializedBranchCatalog = branchChunkIds !== null && database.getAllBranches().length > 0;
10746
+ return {
10747
+ hasInitializedBranchCatalog,
10748
+ shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
10749
+ };
10750
+ }
10751
+ searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
10752
+ const normalizedLimit = Math.max(0, Math.floor(initialLimit));
10753
+ if (normalizedLimit === 0) return [];
10754
+ if (!shouldPrefilterByBranch || !branchChunkIds) {
10755
+ return search(normalizedLimit);
10756
+ }
10757
+ const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
10758
+ if (targetCount === 0 || totalCount === 0) return [];
10759
+ let requestedLimit = Math.min(normalizedLimit, totalCount);
10760
+ while (true) {
10761
+ const results = search(requestedLimit);
10762
+ const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
10763
+ if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
10764
+ return branchResults;
10765
+ }
10766
+ const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
10767
+ if (nextLimit === requestedLimit) return branchResults;
10768
+ requestedLimit = nextLimit;
10769
+ }
10770
+ }
10771
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
10772
+ return this.searchCandidatesWithBranchPrefilter(
10773
+ initialLimit,
10774
+ store.count(),
10775
+ branchChunkIds,
10776
+ shouldPrefilterByBranch,
10777
+ (requestedLimit) => store.search(embedding, requestedLimit),
10778
+ (candidate) => candidate.id
10779
+ );
10780
+ }
10610
10781
  async search(query, limit, options) {
10611
10782
  const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
10612
10783
  this.requireReadableComponents(readIssues, "vectors", "database");
@@ -10651,12 +10822,7 @@ var Indexer = class _Indexer {
10651
10822
  });
10652
10823
  }
10653
10824
  const embeddingMs = performance2.now() - embeddingStartTime;
10654
- const vectorStartTime = performance2.now();
10655
- const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
10656
- const vectorMs = performance2.now() - vectorStartTime;
10657
- const keywordStartTime = performance2.now();
10658
- const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
10659
- const keywordMs = performance2.now() - keywordStartTime;
10825
+ const prefilterStartTime = performance2.now();
10660
10826
  let branchChunkIds = null;
10661
10827
  let branchSymbolIds = null;
10662
10828
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -10664,35 +10830,38 @@ var Indexer = class _Indexer {
10664
10830
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
10665
10831
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
10666
10832
  }
10667
- const prefilterStartTime = performance2.now();
10668
- const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
10669
- const allowBranchPrefilterFallback = this.config.scope !== "global";
10670
- const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
10671
- const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
10672
- const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
10673
- const keywordCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
10833
+ const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
10834
+ const prefilterMs = performance2.now() - prefilterStartTime;
10835
+ const vectorStartTime = performance2.now();
10836
+ const semanticCandidates = embedding ? this.searchSemanticCandidates(
10837
+ store,
10838
+ embedding,
10839
+ maxResults * 4,
10840
+ branchChunkIds,
10841
+ shouldPrefilterByBranch
10842
+ ) : [];
10843
+ const vectorMs = performance2.now() - vectorStartTime;
10844
+ const keywordStartTime = performance2.now();
10845
+ const keywordCandidates = await this.keywordSearch(
10846
+ query,
10847
+ maxResults * 4,
10848
+ store,
10849
+ invertedIndex,
10850
+ branchChunkIds,
10851
+ shouldPrefilterByBranch
10852
+ );
10853
+ const keywordMs = performance2.now() - keywordStartTime;
10674
10854
  const scopedSemanticCandidates = semanticCandidates.filter(
10675
- (candidate) => matchesHardSearchFilters(candidate, options)
10855
+ (candidate) => matchesHardSearchFilters(candidate, options, this.projectRoot)
10676
10856
  );
10677
10857
  const scopedKeywordCandidates = keywordCandidates.filter(
10678
- (candidate) => matchesHardSearchFilters(candidate, options)
10858
+ (candidate) => matchesHardSearchFilters(candidate, options, this.projectRoot)
10679
10859
  );
10680
- const prefilterMs = performance2.now() - prefilterStartTime;
10681
- if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
10860
+ if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
10682
10861
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
10683
10862
  branch: this.currentBranch
10684
10863
  });
10685
10864
  }
10686
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
10687
- this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
10688
- branch: this.currentBranch
10689
- });
10690
- }
10691
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
10692
- this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
10693
- branch: this.currentBranch
10694
- });
10695
- }
10696
10865
  const fusionStartTime = performance2.now();
10697
10866
  const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
10698
10867
  const combined = rankHybridResults(query, scopedSemanticCandidates, scopedKeywordCandidates, {
@@ -10743,12 +10912,14 @@ var Indexer = class _Indexer {
10743
10912
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
10744
10913
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
10745
10914
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
10746
- const baseFiltered = tiered.filter((r) => matchesSearchFilters(r, options, this.config.search.minScore));
10915
+ const baseFiltered = tiered.filter(
10916
+ (r) => matchesSearchFilters(r, options, this.config.search.minScore, this.projectRoot)
10917
+ );
10747
10918
  const implementationOnly = baseFiltered.filter(
10748
10919
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
10749
10920
  );
10750
10921
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
10751
- const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore)).slice(0, maxResults) : [];
10922
+ const identifierFallback = !options?.definitionIntent && filtered.length === 0 && identifierHints.length > 0 ? buildSymbolDefinitionLane(query, database, branchChunkIds, branchSymbolIds, maxResults, union, true).filter((r) => matchesSearchFilters(r, options, this.config.search.minScore, this.projectRoot)).slice(0, maxResults) : [];
10752
10923
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
10753
10924
  const totalSearchMs = performance2.now() - searchStartTime;
10754
10925
  this.logger.recordSearch(totalSearchMs, {
@@ -10773,10 +10944,11 @@ var Indexer = class _Indexer {
10773
10944
  let content = "";
10774
10945
  let contextStartLine = r.metadata.startLine;
10775
10946
  let contextEndLine = r.metadata.endLine;
10947
+ const resolvedFilePath = this.resolveStoredFilePath(r.metadata.filePath);
10776
10948
  if (!metadataOnly && this.config.search.includeContext) {
10777
10949
  try {
10778
10950
  const fileContent = await fsPromises3.readFile(
10779
- r.metadata.filePath,
10951
+ resolvedFilePath,
10780
10952
  "utf-8"
10781
10953
  );
10782
10954
  const lines = fileContent.split("\n");
@@ -10789,7 +10961,7 @@ var Indexer = class _Indexer {
10789
10961
  }
10790
10962
  }
10791
10963
  return {
10792
- filePath: r.metadata.filePath,
10964
+ filePath: resolvedFilePath,
10793
10965
  startLine: contextStartLine,
10794
10966
  endLine: contextEndLine,
10795
10967
  content,
@@ -10801,8 +10973,18 @@ var Indexer = class _Indexer {
10801
10973
  })
10802
10974
  );
10803
10975
  }
10804
- async keywordSearch(query, limit, store, invertedIndex) {
10805
- const scores = invertedIndex.search(query);
10976
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
10977
+ const normalizedLimit = Math.max(0, Math.floor(limit));
10978
+ if (normalizedLimit === 0) return [];
10979
+ const scoreEntries = this.searchCandidatesWithBranchPrefilter(
10980
+ normalizedLimit,
10981
+ invertedIndex.getDocumentCount(),
10982
+ branchChunkIds,
10983
+ shouldPrefilterByBranch,
10984
+ (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
10985
+ ([chunkId]) => chunkId
10986
+ );
10987
+ const scores = new Map(scoreEntries);
10806
10988
  if (scores.size === 0) {
10807
10989
  return [];
10808
10990
  }
@@ -10816,7 +10998,7 @@ var Indexer = class _Indexer {
10816
10998
  }
10817
10999
  }
10818
11000
  results.sort((a, b) => b.score - a.score);
10819
- return results.slice(0, limit);
11001
+ return results.slice(0, normalizedLimit);
10820
11002
  }
10821
11003
  async getStatus() {
10822
11004
  const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
@@ -10883,7 +11065,7 @@ var Indexer = class _Indexer {
10883
11065
  );
10884
11066
  const currentFileHashes = /* @__PURE__ */ new Map();
10885
11067
  for (const file of files) {
10886
- currentFileHashes.set(this.toCanonicalFilePath(file.path), hashFile(file.path));
11068
+ currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
10887
11069
  }
10888
11070
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
10889
11071
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -10895,17 +11077,7 @@ var Indexer = class _Indexer {
10895
11077
  return { readable: true, current: false, reason: "files-changed" };
10896
11078
  }
10897
11079
  }
10898
- const hasSwiftFiles = Array.from(currentFileHashes.keys()).some(
10899
- (filePath) => path14.extname(filePath).toLowerCase() === ".swift"
10900
- );
10901
- const hasMetalFiles = Array.from(currentFileHashes.keys()).some(
10902
- (filePath) => path14.extname(filePath).toLowerCase() === ".metal"
10903
- );
10904
- const hasCallGraphMigrationFiles = Array.from(currentFileHashes.keys()).some((filePath) => {
10905
- const extension = path14.extname(filePath).toLowerCase();
10906
- return extension === ".php" || extension === ".c" || extension === ".cc" || extension === ".cpp" || extension === ".cxx";
10907
- });
10908
- if (hasSwiftFiles && database.getMetadata(this.getSwiftParserVersionMetadataKey()) !== SWIFT_PARSER_VERSION || hasMetalFiles && database.getMetadata(this.getMetalParserVersionMetadataKey()) !== METAL_PARSER_VERSION || database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) !== SYMBOL_EXTRACTOR_VERSION || hasCallGraphMigrationFiles && database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION) {
11080
+ if (!this.areBranchMigrationVersionsCurrent(database)) {
10909
11081
  return { readable: true, current: false, reason: "migration-required" };
10910
11082
  }
10911
11083
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -10965,6 +11137,7 @@ var Indexer = class _Indexer {
10965
11137
  this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
10966
11138
  this.saveFailedBatches([]);
10967
11139
  database.deleteMetadata("index.version");
11140
+ database.deleteMetadata("index.pathStorageVersion");
10968
11141
  database.deleteMetadata("index.embeddingProvider");
10969
11142
  database.deleteMetadata("index.embeddingModel");
10970
11143
  database.deleteMetadata("index.embeddingDimensions");
@@ -10983,7 +11156,7 @@ var Indexer = class _Indexer {
10983
11156
  this.indexCompatibility = compatibility;
10984
11157
  return;
10985
11158
  }
10986
- if (!this.isLocalProjectIndexPath()) {
11159
+ if (!this.isProjectOwnedIndexPath()) {
10987
11160
  throw new Error(
10988
11161
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
10989
11162
  );
@@ -10994,11 +11167,11 @@ var Indexer = class _Indexer {
10994
11167
  invertedIndex.clear();
10995
11168
  this.saveInvertedIndex(invertedIndex);
10996
11169
  this.fileHashCache.clear();
10997
- this.saveFileHashCache();
11170
+ await this.removeProjectRuntimeStateArtifacts();
10998
11171
  database.clearAllIndexedData();
10999
11172
  this.deleteBranchCommitMetadata(database, clearedBranchKeys);
11000
- this.saveFailedBatches([]);
11001
11173
  database.deleteMetadata("index.version");
11174
+ database.deleteMetadata("index.pathStorageVersion");
11002
11175
  database.deleteMetadata("index.embeddingProvider");
11003
11176
  database.deleteMetadata("index.embeddingModel");
11004
11177
  database.deleteMetadata("index.embeddingDimensions");
@@ -11026,32 +11199,46 @@ var Indexer = class _Indexer {
11026
11199
  existing.push(key);
11027
11200
  filePathsToChunkKeys.set(metadata.filePath, existing);
11028
11201
  }
11029
- const removedFilePaths = [];
11030
- const removedChunkKeys = [];
11202
+ const missingStoredFilePaths = [];
11203
+ const missingChunkKeys = [];
11031
11204
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
11032
11205
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
11033
- if (!existsSync7(filePath)) {
11206
+ if (!existsSync7(this.toMaterializedFilePath(filePath))) {
11034
11207
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
11035
11208
  for (const key of chunkKeys) {
11036
- removedChunkKeys.push(key);
11209
+ missingChunkKeys.push(key);
11037
11210
  }
11038
- removedFilePaths.push(filePath);
11211
+ missingStoredFilePaths.push(filePath);
11039
11212
  }
11040
11213
  }
11214
+ const branchCatalogKeys = this.getBranchCatalogKeys();
11215
+ for (const branchKey of branchCatalogKeys) {
11216
+ database.deleteBranchChunksForBranch(branchKey, missingChunkKeys);
11217
+ }
11218
+ const referencedChunkKeys = new Set(database.getReferencedChunkIds(missingChunkKeys));
11219
+ const removedChunkKeys = missingChunkKeys.filter((key) => !referencedChunkKeys.has(key));
11041
11220
  if (removedChunkKeys.length > 0) {
11042
11221
  this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkKeys);
11043
11222
  for (const key of removedChunkKeys) {
11044
11223
  invertedIndex.removeChunk(key);
11045
11224
  }
11225
+ database.deleteChunksByIds(removedChunkKeys);
11046
11226
  }
11047
- for (const filePath of removedFilePaths) {
11048
- const fileChunkKeys = chunkKeysByRemovedFile.get(filePath) ?? [];
11049
- if (fileChunkKeys.length > 0) {
11050
- database.deleteChunksByIds(fileChunkKeys);
11051
- }
11052
- database.deleteCallEdgesByFile(filePath);
11053
- database.deleteSymbolsByFile(filePath);
11054
- }
11227
+ const missingSymbolIds = Array.from(new Set(
11228
+ missingStoredFilePaths.flatMap(
11229
+ (filePath) => database.getSymbolsByFile(filePath).map((symbol) => symbol.id)
11230
+ )
11231
+ ));
11232
+ for (const branchKey of branchCatalogKeys) {
11233
+ database.deleteBranchSymbolsForBranch(branchKey, missingSymbolIds);
11234
+ }
11235
+ const referencedSymbolIds = new Set(database.getReferencedSymbolIds(missingSymbolIds));
11236
+ const removedSymbolIds = missingSymbolIds.filter((symbolId) => !referencedSymbolIds.has(symbolId));
11237
+ database.clearCallEdgeTargetsForSymbols(removedSymbolIds);
11238
+ const removedChunkKeySet = new Set(removedChunkKeys);
11239
+ const removedStoredFilePaths = missingStoredFilePaths.filter(
11240
+ (filePath) => (chunkKeysByRemovedFile.get(filePath) ?? []).some((key) => removedChunkKeySet.has(key))
11241
+ );
11055
11242
  const removedCount = removedChunkKeys.length;
11056
11243
  if (removedCount > 0) {
11057
11244
  store.save();
@@ -11087,9 +11274,16 @@ var Indexer = class _Indexer {
11087
11274
  removedStale: removedCount,
11088
11275
  orphanEmbeddings: gcOrphanEmbeddings,
11089
11276
  orphanChunks: gcOrphanChunks,
11090
- removedFiles: removedFilePaths.length
11277
+ removedFiles: removedStoredFilePaths.length
11091
11278
  });
11092
- return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
11279
+ return {
11280
+ removed: removedCount,
11281
+ filePaths: removedStoredFilePaths.map((filePath) => this.resolveStoredFilePath(filePath)),
11282
+ gcOrphanEmbeddings,
11283
+ gcOrphanChunks,
11284
+ gcOrphanSymbols,
11285
+ gcOrphanCallEdges
11286
+ };
11093
11287
  }
11094
11288
  async retryFailedBatches() {
11095
11289
  return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
@@ -11284,9 +11478,18 @@ var Indexer = class _Indexer {
11284
11478
  return this.baseBranch;
11285
11479
  }
11286
11480
  refreshBranchInfo() {
11481
+ const previousBranch = this.currentBranch;
11287
11482
  if (isGitRepo(this.materializedProjectRoot)) {
11288
11483
  this.currentBranch = this.branchNameOverride ?? getBranchOrDefault(this.materializedProjectRoot);
11289
11484
  this.baseBranch = getBaseBranch(this.materializedProjectRoot);
11485
+ } else {
11486
+ this.currentBranch = "default";
11487
+ this.baseBranch = "default";
11488
+ }
11489
+ if (this.currentBranch !== previousBranch) {
11490
+ this.refreshRuntimeArtifactPaths();
11491
+ this.fileHashCache.clear();
11492
+ this.loadFileHashCache();
11290
11493
  }
11291
11494
  }
11292
11495
  async getDatabaseStats() {
@@ -11311,6 +11514,7 @@ var Indexer = class _Indexer {
11311
11514
  return [];
11312
11515
  }
11313
11516
  const filterByBranch = options?.filterByBranch ?? true;
11517
+ const excludedStoredFile = options?.excludeFile ? this.toStoredFilePath(options.excludeFile) : void 0;
11314
11518
  this.logger.search("debug", "Starting find similar", {
11315
11519
  codeLength: code.length,
11316
11520
  limit,
@@ -11320,31 +11524,29 @@ var Indexer = class _Indexer {
11320
11524
  const { embedding, tokensUsed } = await provider.embedDocument(code);
11321
11525
  const embeddingMs = performance2.now() - embeddingStartTime;
11322
11526
  this.logger.recordEmbeddingApiCall(tokensUsed);
11323
- const vectorStartTime = performance2.now();
11324
- const semanticResults = store.search(embedding, limit * 2);
11325
- const vectorMs = performance2.now() - vectorStartTime;
11527
+ const prefilterStartTime = performance2.now();
11326
11528
  let branchChunkIds = null;
11327
11529
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
11328
11530
  branchChunkIds = new Set(
11329
11531
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
11330
11532
  );
11331
11533
  }
11332
- const prefilterStartTime = performance2.now();
11333
- const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
11334
- const allowBranchPrefilterFallback = this.config.scope !== "global";
11335
- const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
11336
- const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
11534
+ const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
11337
11535
  const prefilterMs = performance2.now() - prefilterStartTime;
11338
- if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
11536
+ const vectorStartTime = performance2.now();
11537
+ const semanticCandidates = this.searchSemanticCandidates(
11538
+ store,
11539
+ embedding,
11540
+ limit * 2,
11541
+ branchChunkIds,
11542
+ shouldPrefilterByBranch
11543
+ );
11544
+ const vectorMs = performance2.now() - vectorStartTime;
11545
+ if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
11339
11546
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
11340
11547
  branch: this.currentBranch
11341
11548
  });
11342
11549
  }
11343
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
11344
- this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
11345
- branch: this.currentBranch
11346
- });
11347
- }
11348
11550
  const rerankTopN = this.config.search.rerankTopN;
11349
11551
  const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
11350
11552
  rerankTopN,
@@ -11353,21 +11555,10 @@ var Indexer = class _Indexer {
11353
11555
  });
11354
11556
  const filtered = ranked.filter((r) => {
11355
11557
  if (r.score < this.config.search.minScore) return false;
11356
- if (options?.excludeFile) {
11357
- if (r.metadata.filePath === options.excludeFile) return false;
11358
- }
11359
- if (options?.fileType) {
11360
- const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
11361
- if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
11558
+ if (excludedStoredFile) {
11559
+ if (r.metadata.filePath === excludedStoredFile) return false;
11362
11560
  }
11363
- if (options?.directory) {
11364
- const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
11365
- if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
11366
- }
11367
- if (options?.chunkType) {
11368
- if (r.metadata.chunkType !== options.chunkType) return false;
11369
- }
11370
- return true;
11561
+ return matchesHardSearchFilters(r, options, this.projectRoot);
11371
11562
  }).slice(0, limit);
11372
11563
  const totalSearchMs = performance2.now() - searchStartTime;
11373
11564
  this.logger.recordSearch(totalSearchMs, {
@@ -11387,10 +11578,11 @@ var Indexer = class _Indexer {
11387
11578
  return Promise.all(
11388
11579
  filtered.map(async (r) => {
11389
11580
  let content = "";
11581
+ const resolvedFilePath = this.resolveStoredFilePath(r.metadata.filePath);
11390
11582
  if (this.config.search.includeContext) {
11391
11583
  try {
11392
11584
  const fileContent = await fsPromises3.readFile(
11393
- r.metadata.filePath,
11585
+ resolvedFilePath,
11394
11586
  "utf-8"
11395
11587
  );
11396
11588
  const lines = fileContent.split("\n");
@@ -11400,7 +11592,7 @@ var Indexer = class _Indexer {
11400
11592
  }
11401
11593
  }
11402
11594
  return {
11403
- filePath: r.metadata.filePath,
11595
+ filePath: resolvedFilePath,
11404
11596
  startLine: r.metadata.startLine,
11405
11597
  endLine: r.metadata.endLine,
11406
11598
  content,
@@ -11421,7 +11613,7 @@ var Indexer = class _Indexer {
11421
11613
  for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
11422
11614
  if (!seen.has(edge.id)) {
11423
11615
  seen.add(edge.id);
11424
- results.push(edge);
11616
+ results.push(this.resolveCallEdgeFilePath(edge));
11425
11617
  }
11426
11618
  }
11427
11619
  }
@@ -11440,7 +11632,7 @@ var Indexer = class _Indexer {
11440
11632
  const safelyMatchesUnresolvedSymbol = includeUnresolved && !edge.toSymbolId;
11441
11633
  if (!matchesResolvedSymbol && !safelyMatchesUnresolvedSymbol || seen.has(edge.id)) continue;
11442
11634
  seen.add(edge.id);
11443
- results.push(edge);
11635
+ results.push(this.resolveCallEdgeFilePath(edge));
11444
11636
  }
11445
11637
  }
11446
11638
  return results;
@@ -11454,7 +11646,7 @@ var Indexer = class _Indexer {
11454
11646
  for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
11455
11647
  if (!seen.has(edge.id)) {
11456
11648
  seen.add(edge.id);
11457
- results.push(edge);
11649
+ results.push(this.resolveCallEdgeFilePath(edge));
11458
11650
  }
11459
11651
  }
11460
11652
  }
@@ -11470,7 +11662,7 @@ var Indexer = class _Indexer {
11470
11662
  shortest = path30;
11471
11663
  }
11472
11664
  }
11473
- return shortest;
11665
+ return shortest.map((hop) => this.resolveFilePathRecord(hop));
11474
11666
  }
11475
11667
  async findCallPathBySymbolIds(fromSymbolId, toSymbolId, maxDepth = 10) {
11476
11668
  const { database, readIssues } = await this.ensureInitialized();
@@ -11536,7 +11728,7 @@ var Indexer = class _Indexer {
11536
11728
  shortest = path30;
11537
11729
  }
11538
11730
  }
11539
- return shortest;
11731
+ return shortest.map((hop) => this.resolveFilePathRecord(hop));
11540
11732
  }
11541
11733
  async getCallGraphSymbols() {
11542
11734
  const { database, readIssues } = await this.ensureInitialized();
@@ -11544,7 +11736,7 @@ var Indexer = class _Indexer {
11544
11736
  const symbols = /* @__PURE__ */ new Map();
11545
11737
  for (const branchKey of this.getBranchCatalogKeys()) {
11546
11738
  for (const symbol of database.getSymbolsForBranch(branchKey)) {
11547
- symbols.set(symbol.id, symbol);
11739
+ symbols.set(symbol.id, this.resolveFilePathRecord(symbol));
11548
11740
  }
11549
11741
  }
11550
11742
  return [...symbols.values()];
@@ -11553,31 +11745,32 @@ var Indexer = class _Indexer {
11553
11745
  const { database, readIssues } = await this.ensureInitialized();
11554
11746
  this.requireReadableComponents(readIssues, "database");
11555
11747
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11556
- return database.getSymbolsForBranch(resolvedBranch);
11748
+ return database.getSymbolsForBranch(resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
11557
11749
  }
11558
11750
  async getSymbolsForFiles(filePaths, branch) {
11559
11751
  const { database, readIssues } = await this.ensureInitialized();
11560
11752
  this.requireReadableComponents(readIssues, "database");
11561
11753
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11562
- return database.getSymbolsForFiles(filePaths, resolvedBranch);
11754
+ const storedFilePaths = filePaths.map((filePath) => this.toStoredFilePath(filePath));
11755
+ return database.getSymbolsForFiles(storedFilePaths, resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
11563
11756
  }
11564
11757
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
11565
11758
  const { database, readIssues } = await this.ensureInitialized();
11566
11759
  this.requireReadableComponents(readIssues, "database");
11567
11760
  const branch = this.getBranchCatalogKey();
11568
- return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
11761
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth).map((entry) => this.resolveFilePathRecord(entry));
11569
11762
  }
11570
11763
  async detectCommunities(branch, symbolIds) {
11571
11764
  const { database, readIssues } = await this.ensureInitialized();
11572
11765
  this.requireReadableComponents(readIssues, "database");
11573
11766
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11574
- return database.detectCommunities(resolvedBranch, symbolIds);
11767
+ return database.detectCommunities(resolvedBranch, symbolIds).map((entry) => this.resolveFilePathRecord(entry));
11575
11768
  }
11576
11769
  async computeCentrality(branch) {
11577
11770
  const { database, readIssues } = await this.ensureInitialized();
11578
11771
  this.requireReadableComponents(readIssues, "database");
11579
11772
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11580
- return database.computeCentrality(resolvedBranch);
11773
+ return database.computeCentrality(resolvedBranch).map((entry) => this.resolveFilePathRecord(entry));
11581
11774
  }
11582
11775
  async getPrImpact(opts, onPreparationProgress) {
11583
11776
  const initialState = await this.ensureInitialized();
@@ -11613,10 +11806,8 @@ var Indexer = class _Indexer {
11613
11806
  const requestedRef = opts.pr !== void 0 ? expectedCommit : headRefName ?? resolvedBranch;
11614
11807
  const storedCommit = this.getStoredBranchCommit(database, catalogIdentity);
11615
11808
  const catalogIdentityMatches = storedCommit === expectedCommit;
11616
- const symbolsCurrent = database.getMetadata(
11617
- this.getSymbolExtractorVersionMetadataKey(catalogIdentity)
11618
- ) === SYMBOL_EXTRACTOR_VERSION;
11619
- if (branchSymbols.length === 0 || !catalogIdentityMatches || !symbolsCurrent) {
11809
+ const migrationsCurrent = this.areBranchMigrationVersionsCurrent(database, catalogIdentity);
11810
+ if (branchSymbols.length === 0 || !catalogIdentityMatches || !migrationsCurrent) {
11620
11811
  if (!resolvedBranch || resolvedBranch === "default") {
11621
11812
  throw new Error("Run index_codebase first to build the call graph and symbol index for this project.");
11622
11813
  }
@@ -11672,8 +11863,9 @@ var Indexer = class _Indexer {
11672
11863
  );
11673
11864
  }
11674
11865
  }
11675
- const absoluteChangedFiles = changedFiles.map((f) => path14.resolve(this.projectRoot, f));
11676
- const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
11866
+ const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path14.resolve(this.projectRoot, filePath)));
11867
+ const storedChangedFiles = toStoredChangedFiles(changedFiles);
11868
+ const directSymbols = database.getSymbolsForFiles(storedChangedFiles, branchKey);
11677
11869
  const directIds = directSymbols.map((s) => s.id);
11678
11870
  const direction = opts.direction ?? "both";
11679
11871
  const maxDepth = opts.maxDepth ?? 5;
@@ -11715,7 +11907,7 @@ var Indexer = class _Indexer {
11715
11907
  id: c.symbolId,
11716
11908
  name: c.symbolName,
11717
11909
  callerCount: c.callerCount,
11718
- filePath: c.filePath
11910
+ filePath: this.resolveStoredFilePath(c.filePath)
11719
11911
  }));
11720
11912
  const totalAffected = allAffectedIds.length;
11721
11913
  let riskLevel;
@@ -11755,9 +11947,9 @@ var Indexer = class _Indexer {
11755
11947
  projectRoot: this.projectRoot,
11756
11948
  baseBranch: this.baseBranch
11757
11949
  });
11758
- const otherAbsolute = otherChanged.files.map((f) => path14.resolve(this.projectRoot, f));
11950
+ const otherStored = toStoredChangedFiles(otherChanged.files);
11759
11951
  const prBranchKey = this.getBranchCatalogKeyFor(otherChanged.catalogIdentity);
11760
- const otherSymbols = database.getSymbolsForFiles(otherAbsolute, prBranchKey);
11952
+ const otherSymbols = database.getSymbolsForFiles(otherStored, prBranchKey);
11761
11953
  const otherLabels = /* @__PURE__ */ new Set();
11762
11954
  for (const sym of otherSymbols) {
11763
11955
  const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
@@ -11788,12 +11980,12 @@ var Indexer = class _Indexer {
11788
11980
  id: s.id,
11789
11981
  name: s.name,
11790
11982
  kind: s.kind,
11791
- filePath: s.filePath
11983
+ filePath: this.resolveStoredFilePath(s.filePath)
11792
11984
  })),
11793
11985
  transitiveCallers: transitiveCallers.map((c) => ({
11794
11986
  id: c.symbolId,
11795
11987
  name: c.symbolName,
11796
- filePath: c.filePath,
11988
+ filePath: this.resolveStoredFilePath(c.filePath),
11797
11989
  depth: c.depth
11798
11990
  })),
11799
11991
  totalAffected,
@@ -11823,7 +12015,7 @@ var Indexer = class _Indexer {
11823
12015
  const absoluteDirectoryFilter = directory ? path14.resolve(this.projectRoot, directory) : void 0;
11824
12016
  for (const filePath of filePaths) {
11825
12017
  if (directory) {
11826
- const absoluteFilePath = path14.resolve(filePath);
12018
+ const absoluteFilePath = this.resolveStoredFilePath(filePath);
11827
12019
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
11828
12020
  const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path14.sep));
11829
12021
  if (!matchesRelative && !matchesProjectRelative) {
@@ -11832,14 +12024,14 @@ var Indexer = class _Indexer {
11832
12024
  }
11833
12025
  for (const sym of database.getSymbolsByFile(filePath)) {
11834
12026
  if (symbolIdSet.has(sym.id) && !seenSymbols.has(sym.id)) {
11835
- seenSymbols.set(sym.id, sym);
12027
+ seenSymbols.set(sym.id, this.resolveFilePathRecord(sym));
11836
12028
  }
11837
12029
  }
11838
12030
  }
11839
12031
  for (const symbolId of seenSymbols.keys()) {
11840
12032
  for (const edge of database.getCallees(symbolId, branchKey)) {
11841
12033
  if (!seenEdges.has(edge.id)) {
11842
- seenEdges.set(edge.id, edge);
12034
+ seenEdges.set(edge.id, this.resolveCallEdgeFilePath(edge));
11843
12035
  }
11844
12036
  }
11845
12037
  }
@@ -12111,11 +12303,11 @@ function formatIndexStats(stats, verbose = false) {
12111
12303
  } else if (stats.indexedChunks === 0) {
12112
12304
  lines.push(`${stats.totalFiles} files, removed ${stats.removedChunks} stale chunks, ${stats.existingChunks} chunks remain.`);
12113
12305
  } else {
12114
- let main2 = `${stats.totalFiles} files processed, ${stats.indexedChunks} new chunks embedded.`;
12306
+ let main = `${stats.totalFiles} files processed, ${stats.indexedChunks} new chunks embedded.`;
12115
12307
  if (stats.existingChunks > 0) {
12116
- main2 += ` ${stats.existingChunks} unchanged chunks skipped.`;
12308
+ main += ` ${stats.existingChunks} unchanged chunks skipped.`;
12117
12309
  }
12118
- lines.push(main2);
12310
+ lines.push(main);
12119
12311
  if (stats.removedChunks > 0) {
12120
12312
  lines.push(`Removed ${stats.removedChunks} stale chunks.`);
12121
12313
  }
@@ -13289,10 +13481,10 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
13289
13481
  }
13290
13482
  coordinatorKeysByProject.set(projectKey, key);
13291
13483
  }
13292
- function startAutoIndex(projectRoot, host = "opencode", source = "startup") {
13484
+ function startAutoIndex(projectRoot, host, source = "startup") {
13293
13485
  return getCoordinator(projectRoot, host)?.start(source) ?? null;
13294
13486
  }
13295
- function requestBackgroundIndex(projectRoot, host = "opencode") {
13487
+ function requestBackgroundIndex(projectRoot, host) {
13296
13488
  return getCoordinator(projectRoot, host)?.request({
13297
13489
  checkFreshness: false,
13298
13490
  force: false,
@@ -13307,14 +13499,14 @@ function runCoordinatedIndex(projectRoot, host, force, onProgress) {
13307
13499
  source: "manual"
13308
13500
  }) ?? null;
13309
13501
  }
13310
- function getAutoIndexStatus(projectRoot, host = "opencode") {
13502
+ function getAutoIndexStatus(projectRoot, host) {
13311
13503
  return getCoordinator(projectRoot, host)?.snapshot() ?? {
13312
13504
  enabled: false,
13313
13505
  state: "idle",
13314
13506
  updatedAt: now()
13315
13507
  };
13316
13508
  }
13317
- async function waitForAutoIndexForRetrieval(projectRoot, host = "opencode") {
13509
+ async function waitForAutoIndexForRetrieval(projectRoot, host) {
13318
13510
  const coordinator = getCoordinator(projectRoot, host);
13319
13511
  if (!coordinator) return { ready: true };
13320
13512
  const initial = coordinator.snapshot();
@@ -13361,7 +13553,7 @@ async function waitForAutoIndexForRetrieval(projectRoot, host = "opencode") {
13361
13553
  text: `Automatic indexing is ${status.state}. Retry shortly or call index_status for progress. You can also run index_codebase explicitly.`
13362
13554
  };
13363
13555
  }
13364
- async function stopAutoIndex(projectRoot, host = "opencode") {
13556
+ async function stopAutoIndex(projectRoot, host) {
13365
13557
  await getCoordinator(projectRoot, host)?.stop();
13366
13558
  }
13367
13559
  async function hasReadableCurrentIndex(coordinator) {
@@ -13508,7 +13700,7 @@ function loadJsonFile(filePath) {
13508
13700
  function loadConfigFile(filePath) {
13509
13701
  return loadJsonFile(filePath);
13510
13702
  }
13511
- function loadProjectConfigLayer(projectRoot, host = "opencode") {
13703
+ function loadProjectConfigLayer(projectRoot, host) {
13512
13704
  const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
13513
13705
  const projectConfig = loadJsonFile(projectConfigPath);
13514
13706
  if (!projectConfig) {
@@ -13525,7 +13717,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
13525
13717
  }
13526
13718
  return normalizedConfig;
13527
13719
  }
13528
- function loadMergedConfig(projectRoot, host = "opencode") {
13720
+ function loadMergedConfig(projectRoot, host) {
13529
13721
  const globalConfigPath = resolveGlobalConfigPath(host);
13530
13722
  const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
13531
13723
  let globalConfig = null;
@@ -13594,7 +13786,7 @@ function toConfigRecord(rawConfig) {
13594
13786
  }
13595
13787
  return { ...rawConfig };
13596
13788
  }
13597
- function loadRuntimeConfig(projectRoot, host = "opencode") {
13789
+ function loadRuntimeConfig(projectRoot, host) {
13598
13790
  return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot, host)), projectRoot);
13599
13791
  }
13600
13792
 
@@ -13677,14 +13869,14 @@ function getOrCreateIndexer(projectRoot, host) {
13677
13869
  configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
13678
13870
  return indexer;
13679
13871
  }
13680
- function initializeTools(projectRoot, config, host = "opencode") {
13872
+ function initializeTools(projectRoot, config, host) {
13681
13873
  defaultProjectRoots.set(host, projectRoot);
13682
13874
  const key = getIndexerCacheKey(projectRoot, host);
13683
13875
  configCache.set(key, config);
13684
13876
  indexerCache.set(key, new Indexer(projectRoot, config, host));
13685
13877
  configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
13686
13878
  }
13687
- function getIndexerForProject(projectRoot, host = "opencode") {
13879
+ function getIndexerForProject(projectRoot, host) {
13688
13880
  const root = getProjectRoot(projectRoot, host);
13689
13881
  return getOrCreateIndexer(root, host);
13690
13882
  }
@@ -13692,7 +13884,7 @@ function recordToolEffectiveness(projectRoot, host, event) {
13692
13884
  if (!isToolEffectivenessEnabled(projectRoot, host)) return;
13693
13885
  safelyRecordToolEffectiveness(event);
13694
13886
  }
13695
- function refreshIndexerForDirectory(projectRoot, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot, host))) {
13887
+ function refreshIndexerForDirectory(projectRoot, host, config = parseConfig(loadRuntimeConfig(projectRoot, host))) {
13696
13888
  const key = getIndexerCacheKey(projectRoot, host);
13697
13889
  configCache.set(key, config);
13698
13890
  indexerCache.set(key, new Indexer(projectRoot, config, host));
@@ -15226,7 +15418,7 @@ function loadRawConfig(projectRoot, configPath) {
15226
15418
  fromPath
15227
15419
  );
15228
15420
  }
15229
- const projectConfig = resolveProjectConfigPath(projectRoot);
15421
+ const projectConfig = resolveProjectConfigPath(projectRoot, "opencode");
15230
15422
  if (existsSync12(projectConfig)) {
15231
15423
  return normalizeEvalConfigKnowledgeBases(
15232
15424
  parseJsonConfigFile(projectConfig),
@@ -15241,7 +15433,7 @@ function loadRawConfig(projectRoot, configPath) {
15241
15433
  return {};
15242
15434
  }
15243
15435
  function getIndexRootPath(projectRoot, scope) {
15244
- return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
15436
+ return scope === "global" ? getGlobalIndexPath("opencode") : resolveProjectIndexPath(projectRoot, scope, "opencode");
15245
15437
  }
15246
15438
  function getLocalProjectIndexRoot(projectRoot) {
15247
15439
  return path21.join(projectRoot, ".opencode", "index");
@@ -15257,7 +15449,7 @@ function clearIndexRoot(projectRoot, scope) {
15257
15449
  }
15258
15450
  function ensureLocalEvalProjectConfig(projectRoot, configPath) {
15259
15451
  const localConfigPath = getLocalProjectConfigPath(projectRoot);
15260
- const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
15452
+ const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot, "opencode");
15261
15453
  if (!configPath && existsSync12(localConfigPath)) {
15262
15454
  return localConfigPath;
15263
15455
  }
@@ -15692,7 +15884,7 @@ async function runEvaluation(options) {
15692
15884
  if (options.reindex) {
15693
15885
  clearIndexRoot(options.projectRoot, effectiveConfig.scope);
15694
15886
  }
15695
- const indexer = new Indexer(options.projectRoot, effectiveConfig);
15887
+ const indexer = new Indexer(options.projectRoot, effectiveConfig, "opencode");
15696
15888
  try {
15697
15889
  await indexer.index();
15698
15890
  const perQuery = [];
@@ -15895,9 +16087,9 @@ import * as path23 from "path";
15895
16087
  function printUsage() {
15896
16088
  console.log(`
15897
16089
  Usage:
15898
- opencode-codebase-index-mcp eval run [options]
15899
- opencode-codebase-index-mcp eval compare --against <summary.json> [options]
15900
- opencode-codebase-index-mcp eval diff --current <summary.json> --against <summary.json> [options]
16090
+ ${MCP_BINARY_CURRENT_NAME} eval run [options]
16091
+ ${MCP_BINARY_CURRENT_NAME} eval compare --against <summary.json> [options]
16092
+ ${MCP_BINARY_CURRENT_NAME} eval diff --current <summary.json> --against <summary.json> [options]
15901
16093
 
15902
16094
  Options:
15903
16095
  --project <path> Project root (default: cwd)
@@ -16186,11 +16378,20 @@ async function handleEvalCommand(args, cwd) {
16186
16378
  throw new Error(`Unknown eval subcommand: ${subcommand}`);
16187
16379
  }
16188
16380
 
16189
- // src/mcp-server.ts
16381
+ // src/adapters/mcp/server.ts
16190
16382
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
16383
+
16384
+ // src/package-metadata.ts
16191
16385
  import { readFileSync as readFileSync11 } from "fs";
16386
+ function getPackageVersion() {
16387
+ const raw = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
16388
+ if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
16389
+ return raw.version;
16390
+ }
16391
+ return "0.0.0";
16392
+ }
16192
16393
 
16193
- // src/mcp-server/register-prompts.ts
16394
+ // src/adapters/mcp/register-prompts.ts
16194
16395
  import { z } from "zod";
16195
16396
  function registerMcpPrompts(server) {
16196
16397
  server.prompt(
@@ -16280,7 +16481,7 @@ Use the implementation_lookup tool to find where this symbol is defined. This pr
16280
16481
  );
16281
16482
  }
16282
16483
 
16283
- // src/mcp-server/register-tools.ts
16484
+ // src/adapters/mcp/register-tools.ts
16284
16485
  import { z as z2 } from "zod";
16285
16486
 
16286
16487
  // src/tools/contracts.ts
@@ -16420,13 +16621,89 @@ function formatPrImpact(result) {
16420
16621
  return lines.join("\n");
16421
16622
  }
16422
16623
 
16423
- // src/mcp-server/register-tools.ts
16624
+ // src/tools/tool-names.ts
16625
+ var TOOL_NAME = {
16626
+ CODEBASE_CONTEXT: "codebase_context",
16627
+ CODEBASE_SEARCH: "codebase_search",
16628
+ CODEBASE_PEEK: "codebase_peek",
16629
+ FIND_SIMILAR: "find_similar",
16630
+ IMPLEMENTATION_LOOKUP: "implementation_lookup",
16631
+ INDEX_CODEBASE: "index_codebase",
16632
+ INDEX_STATUS: "index_status",
16633
+ INDEX_HEALTH_CHECK: "index_health_check",
16634
+ INDEX_METRICS: "index_metrics",
16635
+ INDEX_LOGS: "index_logs",
16636
+ CALL_GRAPH: "call_graph",
16637
+ CALL_GRAPH_PATH: "call_graph_path",
16638
+ PR_IMPACT: "pr_impact",
16639
+ ADD_KNOWLEDGE_BASE: "add_knowledge_base",
16640
+ LIST_KNOWLEDGE_BASES: "list_knowledge_bases",
16641
+ REMOVE_KNOWLEDGE_BASE: "remove_knowledge_base",
16642
+ PI_KNOWLEDGE_BASE_ADD: "knowledge_base_add",
16643
+ PI_KNOWLEDGE_BASE_LIST: "knowledge_base_list",
16644
+ PI_KNOWLEDGE_BASE_REMOVE: "knowledge_base_remove",
16645
+ INDEX_VISUALIZE: "index_visualize"
16646
+ };
16647
+ var PORTABLE_TOOL_NAMES = [
16648
+ TOOL_NAME.CODEBASE_CONTEXT,
16649
+ TOOL_NAME.CODEBASE_SEARCH,
16650
+ TOOL_NAME.CODEBASE_PEEK,
16651
+ TOOL_NAME.INDEX_CODEBASE,
16652
+ TOOL_NAME.INDEX_STATUS,
16653
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16654
+ TOOL_NAME.INDEX_METRICS,
16655
+ TOOL_NAME.INDEX_LOGS,
16656
+ TOOL_NAME.FIND_SIMILAR,
16657
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16658
+ TOOL_NAME.CALL_GRAPH,
16659
+ TOOL_NAME.CALL_GRAPH_PATH,
16660
+ TOOL_NAME.PR_IMPACT
16661
+ ];
16662
+ var OPENCODE_TOOL_NAMES = [
16663
+ TOOL_NAME.CODEBASE_CONTEXT,
16664
+ TOOL_NAME.CODEBASE_SEARCH,
16665
+ TOOL_NAME.CODEBASE_PEEK,
16666
+ TOOL_NAME.INDEX_CODEBASE,
16667
+ TOOL_NAME.INDEX_STATUS,
16668
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16669
+ TOOL_NAME.INDEX_METRICS,
16670
+ TOOL_NAME.INDEX_LOGS,
16671
+ TOOL_NAME.FIND_SIMILAR,
16672
+ TOOL_NAME.CALL_GRAPH,
16673
+ TOOL_NAME.CALL_GRAPH_PATH,
16674
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16675
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
16676
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
16677
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE,
16678
+ TOOL_NAME.PR_IMPACT,
16679
+ TOOL_NAME.INDEX_VISUALIZE
16680
+ ];
16681
+ var PI_TOOL_NAMES = [
16682
+ TOOL_NAME.CODEBASE_CONTEXT,
16683
+ TOOL_NAME.CODEBASE_SEARCH,
16684
+ TOOL_NAME.CODEBASE_PEEK,
16685
+ TOOL_NAME.FIND_SIMILAR,
16686
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16687
+ TOOL_NAME.INDEX_CODEBASE,
16688
+ TOOL_NAME.INDEX_STATUS,
16689
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16690
+ TOOL_NAME.INDEX_METRICS,
16691
+ TOOL_NAME.INDEX_LOGS,
16692
+ TOOL_NAME.CALL_GRAPH,
16693
+ TOOL_NAME.CALL_GRAPH_PATH,
16694
+ TOOL_NAME.PR_IMPACT,
16695
+ TOOL_NAME.PI_KNOWLEDGE_BASE_LIST,
16696
+ TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
16697
+ TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
16698
+ ];
16699
+
16700
+ // src/adapters/mcp/register-tools.ts
16424
16701
  function allowNullAsUndefined(schema) {
16425
16702
  return z2.preprocess((value) => value === null ? void 0 : value, schema);
16426
16703
  }
16427
16704
  function registerMcpTools(server, runtime) {
16428
16705
  server.tool(
16429
- "codebase_context",
16706
+ TOOL_NAME.CODEBASE_CONTEXT,
16430
16707
  "PREFERRED FIRST TOOL for any question about this repository. Returns a deduplicated, file-diverse evidence pack within tokenBudget. Use before built-in code search, grep, shell search, or broad file reads. Provide from+to for a dependency path, with optional fromFilePath/toFilePath when names are ambiguous; provide symbol for a definition; or provide only query for low-token conceptual discovery. Use call_graph directly for callers or callees.",
16431
16708
  {
16432
16709
  query: z2.string().describe("The codebase question or behavior to locate. Always provide the user's repository question here."),
@@ -16453,7 +16730,7 @@ function registerMcpTools(server, runtime) {
16453
16730
  }
16454
16731
  );
16455
16732
  server.tool(
16456
- "codebase_search",
16733
+ TOOL_NAME.CODEBASE_SEARCH,
16457
16734
  "FULL-CONTENT semantic retrieval. Use after codebase_peek when you need implementation text, not as the default first step. For exact identifiers or exhaustive matches use grep instead.",
16458
16735
  {
16459
16736
  query: z2.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
@@ -16485,7 +16762,7 @@ ${formatSearchResults(results, "score")}`;
16485
16762
  }
16486
16763
  );
16487
16764
  server.tool(
16488
- "codebase_peek",
16765
+ TOOL_NAME.CODEBASE_PEEK,
16489
16766
  "DIRECT LOW-TOKEN semantic location lookup for unfamiliar-code discovery. Prefer codebase_context when the request may involve definitions or graph navigation; use this specialized tool when you only need conceptual locations.",
16490
16767
  {
16491
16768
  query: z2.string().describe("Natural language description of what code you're looking for."),
@@ -16516,7 +16793,7 @@ ${formatCodebasePeek(results)}`;
16516
16793
  }
16517
16794
  );
16518
16795
  server.tool(
16519
- "index_codebase",
16796
+ TOOL_NAME.INDEX_CODEBASE,
16520
16797
  "Create or refresh the semantic index. Call index_status first when readiness is unknown, then use this tool only if the index is missing, stale, or incompatible. Incremental by default; force=true rebuilds everything.",
16521
16798
  {
16522
16799
  force: allowNullAsUndefined(z2.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
@@ -16529,7 +16806,7 @@ ${formatCodebasePeek(results)}`;
16529
16806
  }
16530
16807
  );
16531
16808
  server.tool(
16532
- "index_status",
16809
+ TOOL_NAME.INDEX_STATUS,
16533
16810
  "START HERE once per repository task when index readiness or freshness is unknown. Reports whether semantic retrieval is ready, chunk counts, compatibility, and the embedding provider. If ready, continue with codebase_peek or implementation_lookup; otherwise run index_codebase.",
16534
16811
  {},
16535
16812
  async () => {
@@ -16538,7 +16815,7 @@ ${formatCodebasePeek(results)}`;
16538
16815
  }
16539
16816
  );
16540
16817
  server.tool(
16541
- "index_health_check",
16818
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16542
16819
  "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
16543
16820
  {},
16544
16821
  async () => {
@@ -16547,7 +16824,7 @@ ${formatCodebasePeek(results)}`;
16547
16824
  }
16548
16825
  );
16549
16826
  server.tool(
16550
- "index_metrics",
16827
+ TOOL_NAME.INDEX_METRICS,
16551
16828
  "Get operational metrics plus opt-in privacy-safe repository-tool effectiveness counters. Metrics are memory-only. Set reset=true to clear them before reading. Operational metrics require debug.enabled=true and debug.metrics=true. Privacy-safe aggregates require only effectivenessMetrics.enabled=true.",
16552
16829
  {
16553
16830
  reset: z2.boolean().optional().default(false).describe("Reset in-memory operational and effectiveness metrics before returning the snapshot")
@@ -16558,7 +16835,7 @@ ${formatCodebasePeek(results)}`;
16558
16835
  }
16559
16836
  );
16560
16837
  server.tool(
16561
- "index_logs",
16838
+ TOOL_NAME.INDEX_LOGS,
16562
16839
  "Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
16563
16840
  {
16564
16841
  limit: allowNullAsUndefined(z2.number().optional().default(20)).describe("Maximum number of log entries to return"),
@@ -16575,7 +16852,7 @@ ${formatCodebasePeek(results)}`;
16575
16852
  }
16576
16853
  );
16577
16854
  server.tool(
16578
- "find_similar",
16855
+ TOOL_NAME.FIND_SIMILAR,
16579
16856
  "Use when you already have a code snippet and need analogous implementations, duplicates, patterns, or refactoring candidates. For a natural-language concept without example code, start with codebase_peek instead.",
16580
16857
  {
16581
16858
  code: z2.string().describe("The code snippet to find similar code for"),
@@ -16602,7 +16879,7 @@ ${formatSearchResults(results)}` }] };
16602
16879
  }
16603
16880
  );
16604
16881
  server.tool(
16605
- "implementation_lookup",
16882
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16606
16883
  "FIRST TOOL only for known-symbol definition questions. Returns authoritative source locations and prefers implementations over tests, docs, examples, and fixtures. Do not use for callers, callees, dependency paths, or code flow; use codebase_context with direction or from/to for those questions.",
16607
16884
  {
16608
16885
  query: z2.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
@@ -16616,7 +16893,7 @@ ${formatSearchResults(results)}` }] };
16616
16893
  }
16617
16894
  );
16618
16895
  server.tool(
16619
- "call_graph",
16896
+ TOOL_NAME.CALL_GRAPH,
16620
16897
  "Find direct callers or callees by function or method name. Unique names resolve automatically; when duplicate names are reported, retry with filePath. Supports relationship types: Call, MethodCall, Constructor, Import, Inherits, Implements.",
16621
16898
  {
16622
16899
  name: z2.string().describe("Function or method name to query"),
@@ -16635,7 +16912,7 @@ ${formatSearchResults(results)}` }] };
16635
16912
  }
16636
16913
  );
16637
16914
  server.tool(
16638
- "call_graph_path",
16915
+ TOOL_NAME.CALL_GRAPH_PATH,
16639
16916
  "Find the shortest known call path between two named functions or methods. Unique names resolve automatically; when duplicate endpoints are reported, retry with fromFilePath or toFilePath.",
16640
16917
  {
16641
16918
  from: z2.string().describe("Source function/method name (starting point)"),
@@ -16650,7 +16927,7 @@ ${formatSearchResults(results)}` }] };
16650
16927
  }
16651
16928
  );
16652
16929
  server.tool(
16653
- "pr_impact",
16930
+ TOOL_NAME.PR_IMPACT,
16654
16931
  "FIRST TOOL for pull-request or branch blast-radius questions. Analyzes changed files, affected symbols, transitive dependencies, communities, hub nodes, conflicts, and risk before merging.",
16655
16932
  {
16656
16933
  pr: allowNullAsUndefined(z2.number().optional()).describe("Pull request number to analyze"),
@@ -16681,21 +16958,14 @@ ${formatSearchResults(results)}` }] };
16681
16958
  );
16682
16959
  }
16683
16960
 
16684
- // src/mcp-server.ts
16961
+ // src/adapters/mcp/server.ts
16685
16962
  function getServerInstructions(host) {
16686
16963
  const hostText = `host ${host}`;
16687
16964
  return `This MCP server is the preferred codebase-understanding path for ${hostText}. Start a repository task with index_status when index readiness or freshness is unknown. Use codebase_context as the preferred first entry point because it returns a token-budgeted location pack and routes to definitions or call-graph helpers when symbol intent is present. Keep the default tokenBudget for normal discovery, then use implementation_lookup, codebase_search, or a targeted file read only for selected locations that need source content. Use codebase_peek for direct conceptual location lookup. For exact identifiers or exhaustive matches, use grep. After identifying symbols, use call_graph or call_graph_path to trace dependencies. If the index is unavailable, run index_codebase, then retry the retrieval tool.`;
16688
16965
  }
16689
- function getPackageVersion() {
16690
- const raw = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
16691
- if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
16692
- return raw.version;
16693
- }
16694
- return "0.0.0";
16695
- }
16696
- function createMcpServer(projectRoot, config, host = "opencode") {
16966
+ function createMcpServer(projectRoot, config, host) {
16697
16967
  const server = new McpServer({
16698
- name: "opencode-codebase-index",
16968
+ name: MCP_SERVER_CURRENT_NAME,
16699
16969
  version: getPackageVersion()
16700
16970
  }, {
16701
16971
  instructions: getServerInstructions(host)
@@ -16730,6 +17000,9 @@ function createMcpServer(projectRoot, config, host = "opencode") {
16730
17000
  return server;
16731
17001
  }
16732
17002
 
17003
+ // src/watcher/file-watcher.ts
17004
+ import { existsSync as existsSync14 } from "fs";
17005
+
16733
17006
  // node_modules/chokidar/index.js
16734
17007
  import { EventEmitter as EventEmitter2 } from "events";
16735
17008
  import { stat as statcb, Stats } from "fs";
@@ -17274,10 +17547,10 @@ var foreach = (val, fn) => {
17274
17547
  fn(val);
17275
17548
  }
17276
17549
  };
17277
- var addAndConvert = (main2, prop, item) => {
17278
- let container = main2[prop];
17550
+ var addAndConvert = (main, prop, item) => {
17551
+ let container = main[prop];
17279
17552
  if (!(container instanceof Set)) {
17280
- main2[prop] = container = /* @__PURE__ */ new Set([container]);
17553
+ main[prop] = container = /* @__PURE__ */ new Set([container]);
17281
17554
  }
17282
17555
  container.add(item);
17283
17556
  };
@@ -17289,12 +17562,12 @@ var clearItem = (cont) => (key) => {
17289
17562
  delete cont[key];
17290
17563
  }
17291
17564
  };
17292
- var delFromSet = (main2, prop, item) => {
17293
- const container = main2[prop];
17565
+ var delFromSet = (main, prop, item) => {
17566
+ const container = main[prop];
17294
17567
  if (container instanceof Set) {
17295
17568
  container.delete(item);
17296
17569
  } else if (container === item) {
17297
- delete main2[prop];
17570
+ delete main[prop];
17298
17571
  }
17299
17572
  };
17300
17573
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
@@ -17475,9 +17748,9 @@ var NodeFsHandler = class {
17475
17748
  if (this.fsw.closed) {
17476
17749
  return;
17477
17750
  }
17478
- const dirname13 = sp.dirname(file);
17751
+ const dirname14 = sp.dirname(file);
17479
17752
  const basename7 = sp.basename(file);
17480
- const parent = this.fsw._getWatchedDir(dirname13);
17753
+ const parent = this.fsw._getWatchedDir(dirname14);
17481
17754
  let prevStats = stats;
17482
17755
  if (parent.has(basename7))
17483
17756
  return;
@@ -17504,7 +17777,7 @@ var NodeFsHandler = class {
17504
17777
  prevStats = newStats2;
17505
17778
  }
17506
17779
  } catch (error) {
17507
- this.fsw._remove(dirname13, basename7);
17780
+ this.fsw._remove(dirname14, basename7);
17508
17781
  }
17509
17782
  } else if (parent.has(basename7)) {
17510
17783
  const at = newStats.atimeMs;
@@ -18468,8 +18741,8 @@ var FileWatcher = class {
18468
18741
  watcher = null;
18469
18742
  projectRoot;
18470
18743
  config;
18471
- host;
18472
18744
  configPath;
18745
+ projectConfigPaths;
18473
18746
  pendingChanges = /* @__PURE__ */ new Map();
18474
18747
  debounceTimer = null;
18475
18748
  debounceMs = 1e3;
@@ -18478,11 +18751,11 @@ var FileWatcher = class {
18478
18751
  resolveReady = null;
18479
18752
  pollingFallbackAttempted = false;
18480
18753
  pendingClose = null;
18481
- constructor(projectRoot, config, host = "opencode", options = {}) {
18754
+ constructor(projectRoot, config, host, options = {}) {
18482
18755
  this.projectRoot = projectRoot;
18483
18756
  this.config = config;
18484
- this.host = host;
18485
18757
  this.configPath = options.configPath;
18758
+ this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
18486
18759
  }
18487
18760
  start(handler) {
18488
18761
  if (this.watcher) {
@@ -18500,7 +18773,19 @@ var FileWatcher = class {
18500
18773
  }
18501
18774
  createWatcher(usePolling = false) {
18502
18775
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
18503
- const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
18776
+ let watchTargets = this.projectRoot;
18777
+ if (this.configPath) {
18778
+ watchTargets = [this.projectRoot, this.configPath];
18779
+ } else {
18780
+ const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
18781
+ const relativeConfigPath = path25.relative(this.projectRoot, projectConfigPath);
18782
+ return this.isOutsideProjectPath(relativeConfigPath);
18783
+ }).map((projectConfigPath) => existsSync14(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path25.dirname(projectConfigPath)));
18784
+ const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
18785
+ if (uniqueExternalConfigTargets.length > 0) {
18786
+ watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
18787
+ }
18788
+ }
18504
18789
  const watcherOptions = {
18505
18790
  ignored: (filePath) => {
18506
18791
  const relativePath = path25.relative(this.projectRoot, filePath);
@@ -18508,6 +18793,9 @@ var FileWatcher = class {
18508
18793
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
18509
18794
  return false;
18510
18795
  }
18796
+ if (this.isOutsideProjectPath(relativePath)) {
18797
+ return true;
18798
+ }
18511
18799
  if (hasFilteredPathSegment(relativePath, path25.sep)) {
18512
18800
  return true;
18513
18801
  }
@@ -18610,14 +18898,22 @@ var FileWatcher = class {
18610
18898
  (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path25.sep}`)
18611
18899
  );
18612
18900
  }
18613
- getProjectConfigRelativePaths() {
18614
- if (this.configPath) {
18615
- return [path25.normalize(path25.relative(this.projectRoot, this.configPath))];
18901
+ isOutsideProjectPath(relativePath) {
18902
+ return relativePath === ".." || relativePath.startsWith(`..${path25.sep}`) || path25.isAbsolute(relativePath);
18903
+ }
18904
+ getNearestExistingDirectory(directoryPath) {
18905
+ let candidate = directoryPath;
18906
+ while (!existsSync14(candidate)) {
18907
+ const parent = path25.dirname(candidate);
18908
+ if (parent === candidate) break;
18909
+ candidate = parent;
18616
18910
  }
18617
- return [
18618
- resolveProjectConfigPath(this.projectRoot, this.host),
18619
- resolveWritableProjectConfigPath(this.projectRoot, this.host)
18620
- ].map((configPath) => path25.normalize(path25.relative(this.projectRoot, configPath)));
18911
+ return candidate;
18912
+ }
18913
+ getProjectConfigRelativePaths() {
18914
+ return this.projectConfigPaths.map(
18915
+ (configPath) => path25.normalize(path25.relative(this.projectRoot, configPath))
18916
+ );
18621
18917
  }
18622
18918
  scheduleFlush() {
18623
18919
  if (this.debounceTimer) {
@@ -18744,8 +19040,9 @@ var GitHeadWatcher = class {
18744
19040
  };
18745
19041
 
18746
19042
  // src/watcher/index.ts
18747
- function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "opencode", options = {}) {
19043
+ function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options = {}) {
18748
19044
  const fileWatcher = new FileWatcher(projectRoot, config, host, options);
19045
+ const configPaths = getConfigPaths(projectRoot, host, options);
18749
19046
  configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer);
18750
19047
  let stopped = false;
18751
19048
  const requestReindex = () => {
@@ -18762,7 +19059,6 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
18762
19059
  );
18763
19060
  const hasDelete = changes.some((c) => c.type === "unlink");
18764
19061
  if (hasAddOrChange || hasDelete) {
18765
- const configPaths = getConfigPaths(projectRoot, host, options);
18766
19062
  if (changes.some((change) => configPaths.includes(pathNormalize(change.path)))) {
18767
19063
  const parsedConfig = options.configPath ? parseConfig(loadConfigFile(options.configPath)) : void 0;
18768
19064
  const refreshedConfig = refreshIndexerForDirectory(projectRoot, host, parsedConfig);
@@ -18777,7 +19073,9 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
18777
19073
  if (isGitRepo(projectRoot)) {
18778
19074
  gitWatcher = new GitHeadWatcher(projectRoot);
18779
19075
  gitWatcher.start(async (oldBranch, newBranch) => {
18780
- getIndexer().getLogger().branch("info", "Branch changed", {
19076
+ const indexer = getIndexer();
19077
+ indexer.refreshBranchInfo();
19078
+ indexer.getLogger().branch("info", "Branch changed", {
18781
19079
  oldBranch,
18782
19080
  newBranch
18783
19081
  });
@@ -18803,10 +19101,9 @@ function getConfigPaths(projectRoot, host, options) {
18803
19101
  if (options.configPath) {
18804
19102
  return [pathNormalize(options.configPath)];
18805
19103
  }
18806
- return [
18807
- resolveProjectConfigPath(projectRoot, host),
18808
- resolveWritableProjectConfigPath(projectRoot, host)
18809
- ].map((configPath) => pathNormalize(configPath));
19104
+ return getProjectConfigCandidatePaths(projectRoot, host).map(
19105
+ (configPath) => pathNormalize(configPath)
19106
+ );
18810
19107
  }
18811
19108
 
18812
19109
  // src/tools/visualize/activity.ts
@@ -19512,7 +19809,7 @@ function transformForVisualization(symbols, edges, options = {}) {
19512
19809
  };
19513
19810
  }
19514
19811
 
19515
- // src/cli.ts
19812
+ // src/adapters/mcp/cli.ts
19516
19813
  function parseArgs(argv) {
19517
19814
  let project = process.cwd();
19518
19815
  let config;
@@ -19563,8 +19860,8 @@ function parseVisualizeArgs(argv, cwd) {
19563
19860
  async function handleVisualizeCommand(argv, cwd) {
19564
19861
  try {
19565
19862
  const args = parseVisualizeArgs(argv, cwd);
19566
- const config = parseConfig(loadMergedConfig(args.project));
19567
- const indexer = new Indexer(args.project, config);
19863
+ const config = parseConfig(loadMergedConfig(args.project, "opencode"));
19864
+ const indexer = new Indexer(args.project, config, "opencode");
19568
19865
  const rawData = await indexer.getVisualizationData({
19569
19866
  directory: args.directory
19570
19867
  });
@@ -19595,16 +19892,16 @@ async function handleVisualizeCommand(argv, cwd) {
19595
19892
  return 1;
19596
19893
  }
19597
19894
  }
19598
- async function main() {
19599
- if (process.argv[2] === "eval") {
19600
- const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
19895
+ async function runMcpCli(argv) {
19896
+ if (argv[2] === "eval") {
19897
+ const exitCode = await handleEvalCommand(argv.slice(3), process.cwd());
19601
19898
  process.exit(exitCode);
19602
19899
  }
19603
- if (process.argv[2] === "visualize") {
19604
- const exitCode = await handleVisualizeCommand(process.argv.slice(3), process.cwd());
19900
+ if (argv[2] === "visualize") {
19901
+ const exitCode = await handleVisualizeCommand(argv.slice(3), process.cwd());
19605
19902
  process.exit(exitCode);
19606
19903
  }
19607
- const args = parseArgs(process.argv);
19904
+ const args = parseArgs(argv);
19608
19905
  const rawConfig = loadCliRawConfig(args);
19609
19906
  const config = parseConfig(rawConfig);
19610
19907
  const server = createMcpServer(args.project, config, args.host);
@@ -19655,8 +19952,10 @@ function handleMainError(error) {
19655
19952
  console.error("Fatal: failed to start MCP server");
19656
19953
  process.exit(1);
19657
19954
  }
19955
+
19956
+ // src/cli.ts
19658
19957
  if (isCliEntrypoint(import.meta.url, process.argv[1])) {
19659
- main().catch(handleMainError);
19958
+ runMcpCli(process.argv).catch(handleMainError);
19660
19959
  }
19661
19960
  export {
19662
19961
  isCliEntrypoint,