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.cjs CHANGED
@@ -664,8 +664,10 @@ __export(cli_exports, {
664
664
  parseArgs: () => parseArgs
665
665
  });
666
666
  module.exports = __toCommonJS(cli_exports);
667
+
668
+ // src/adapters/mcp/cli.ts
667
669
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
668
- var import_fs19 = require("fs");
670
+ var import_fs20 = require("fs");
669
671
  var os8 = __toESM(require("os"), 1);
670
672
  var path29 = __toESM(require("path"), 1);
671
673
  var import_url = require("url");
@@ -4152,24 +4154,62 @@ var os2 = __toESM(require("os"), 1);
4152
4154
  var path5 = __toESM(require("path"), 1);
4153
4155
  var module2 = __toESM(require("module"), 1);
4154
4156
  var import_node_url = require("url");
4157
+
4158
+ // src/identity-catalog.json
4159
+ var identity_catalog_default = {
4160
+ product: {
4161
+ current: {
4162
+ productName: "opencode-codebase-index",
4163
+ packageName: "opencode-codebase-index",
4164
+ repository: "https://github.com/Helweg/opencode-codebase-index",
4165
+ mcpBinary: "opencode-codebase-index-mcp",
4166
+ mcpServerName: "opencode-codebase-index"
4167
+ },
4168
+ future: {
4169
+ productName: "open-codebase-index",
4170
+ packageName: "open-codebase-index",
4171
+ repository: "https://github.com/Helweg/open-codebase-index",
4172
+ mcpBinary: "open-codebase-index-mcp",
4173
+ mcpServerName: "open-codebase-index"
4174
+ }
4175
+ },
4176
+ native: {
4177
+ binaryName: "codebase-index-native"
4178
+ }
4179
+ };
4180
+
4181
+ // src/identity-catalog.ts
4182
+ var IDENTITY_CATALOG = identity_catalog_default;
4183
+ var CURRENT_PRODUCT = IDENTITY_CATALOG.product.current;
4184
+ var FUTURE_PRODUCT = IDENTITY_CATALOG.product.future;
4185
+ var MCP_SERVER_CURRENT_NAME = CURRENT_PRODUCT.mcpServerName;
4186
+ var MCP_BINARY_CURRENT_NAME = CURRENT_PRODUCT.mcpBinary;
4187
+ var STABLE_NATIVE_BINARY_NAME = IDENTITY_CATALOG.native.binaryName;
4188
+
4189
+ // src/native/binding.ts
4155
4190
  var import_meta = {};
4156
- function getNativeBinding() {
4157
- const platform2 = os2.platform();
4158
- const arch2 = os2.arch();
4159
- let bindingName;
4191
+ function getNativeBindingFilename(platform2 = os2.platform(), arch2 = os2.arch()) {
4160
4192
  if (platform2 === "darwin" && arch2 === "arm64") {
4161
- bindingName = "codebase-index-native.darwin-arm64.node";
4162
- } else if (platform2 === "darwin" && arch2 === "x64") {
4163
- bindingName = "codebase-index-native.darwin-x64.node";
4164
- } else if (platform2 === "linux" && arch2 === "x64") {
4165
- bindingName = "codebase-index-native.linux-x64-gnu.node";
4166
- } else if (platform2 === "linux" && arch2 === "arm64") {
4167
- bindingName = "codebase-index-native.linux-arm64-gnu.node";
4168
- } else if (platform2 === "win32" && arch2 === "x64") {
4169
- bindingName = "codebase-index-native.win32-x64-msvc.node";
4170
- } else {
4171
- throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
4193
+ return `${STABLE_NATIVE_BINARY_NAME}.darwin-arm64.node`;
4194
+ }
4195
+ if (platform2 === "darwin" && arch2 === "x64") {
4196
+ return `${STABLE_NATIVE_BINARY_NAME}.darwin-x64.node`;
4197
+ }
4198
+ if (platform2 === "linux" && arch2 === "x64") {
4199
+ return `${STABLE_NATIVE_BINARY_NAME}.linux-x64-gnu.node`;
4172
4200
  }
4201
+ if (platform2 === "linux" && arch2 === "arm64") {
4202
+ return `${STABLE_NATIVE_BINARY_NAME}.linux-arm64-gnu.node`;
4203
+ }
4204
+ if (platform2 === "win32" && arch2 === "x64") {
4205
+ return `${STABLE_NATIVE_BINARY_NAME}.win32-x64-msvc.node`;
4206
+ }
4207
+ throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
4208
+ }
4209
+ function resolveNativeBindingPath(packageRoot, platform2 = os2.platform(), arch2 = os2.arch()) {
4210
+ return path5.join(packageRoot, "native", getNativeBindingFilename(platform2, arch2));
4211
+ }
4212
+ function getNativeBinding() {
4173
4213
  let currentDir;
4174
4214
  let requireTarget;
4175
4215
  if (typeof import_meta !== "undefined" && import_meta.url) {
@@ -4185,7 +4225,7 @@ function getNativeBinding() {
4185
4225
  const normalizedDir = currentDir.replace(/\\/g, "/");
4186
4226
  const isDevMode = normalizedDir.includes("/src/native") || currentDir.includes(path5.join("src", "native"));
4187
4227
  const packageRoot = isDevMode ? path5.resolve(currentDir, "../..") : path5.resolve(currentDir, "..");
4188
- const nativePath = path5.join(packageRoot, "native", bindingName);
4228
+ const nativePath = resolveNativeBindingPath(packageRoot);
4189
4229
  const require2 = module2.createRequire(requireTarget);
4190
4230
  return require2(nativePath);
4191
4231
  }
@@ -5515,8 +5555,37 @@ function resolveWorktreeFallbackPath(projectRoot, relativePath) {
5515
5555
  const fallbackPath = path10.join(mainRepoRoot, relativePath);
5516
5556
  return (0, import_fs7.existsSync)(fallbackPath) ? fallbackPath : null;
5517
5557
  }
5518
- function getHostProjectIndexRelativePath(host) {
5519
- return getProjectIndexRelativePath(host);
5558
+ function getProjectConfigCandidatePaths(projectRoot, host) {
5559
+ const candidates = [path10.join(projectRoot, getProjectConfigRelativePath(host))];
5560
+ if (host !== "opencode") {
5561
+ candidates.push(path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH));
5562
+ }
5563
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5564
+ if (mainRepoRoot) {
5565
+ candidates.push(path10.join(mainRepoRoot, getProjectConfigRelativePath(host)));
5566
+ if (host !== "opencode") {
5567
+ candidates.push(path10.join(mainRepoRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH));
5568
+ }
5569
+ }
5570
+ return [...new Set(candidates)];
5571
+ }
5572
+ function isProjectIndexPathOwnedByProject(projectRoot, indexPath, host) {
5573
+ const projectRoots = [projectRoot];
5574
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5575
+ if (mainRepoRoot) {
5576
+ projectRoots.push(mainRepoRoot);
5577
+ }
5578
+ const ownedIndexPaths = projectRoots.flatMap((root) => {
5579
+ const indexPaths = [path10.join(root, getProjectIndexRelativePath(host))];
5580
+ if (host !== "opencode") {
5581
+ indexPaths.push(path10.join(root, OPENCODE_PROJECT_INDEX_RELATIVE_PATH));
5582
+ }
5583
+ return indexPaths;
5584
+ });
5585
+ const canonicalIndexPath = canonicalizePathForComparison(indexPath);
5586
+ return ownedIndexPaths.some(
5587
+ (ownedPath) => canonicalizePathForComparison(ownedPath) === canonicalIndexPath
5588
+ );
5520
5589
  }
5521
5590
  function hasHostProjectConfig(projectRoot, host) {
5522
5591
  return (0, import_fs7.existsSync)(path10.join(projectRoot, getProjectConfigRelativePath(host)));
@@ -5524,7 +5593,7 @@ function hasHostProjectConfig(projectRoot, host) {
5524
5593
  function hasHostGlobalConfig(host) {
5525
5594
  return (0, import_fs7.existsSync)(getGlobalConfigPath(host));
5526
5595
  }
5527
- function getGlobalIndexPath(host = "opencode") {
5596
+ function getGlobalIndexPath(host) {
5528
5597
  switch (host) {
5529
5598
  case "opencode":
5530
5599
  return path10.join(os4.homedir(), ".opencode", "global-index");
@@ -5534,7 +5603,7 @@ function getGlobalIndexPath(host = "opencode") {
5534
5603
  return path10.join(os4.homedir(), ".codebase-index", "global-index");
5535
5604
  }
5536
5605
  }
5537
- function getGlobalConfigPath(host = "opencode") {
5606
+ function getGlobalConfigPath(host) {
5538
5607
  switch (host) {
5539
5608
  case "opencode":
5540
5609
  return path10.join(os4.homedir(), ".config", "opencode", "codebase-index.json");
@@ -5544,7 +5613,7 @@ function getGlobalConfigPath(host = "opencode") {
5544
5613
  return path10.join(os4.homedir(), ".config", "codebase-index", "config.json");
5545
5614
  }
5546
5615
  }
5547
- function resolveGlobalConfigPath(host = "opencode") {
5616
+ function resolveGlobalConfigPath(host) {
5548
5617
  const hostConfigPath = getGlobalConfigPath(host);
5549
5618
  if ((0, import_fs7.existsSync)(hostConfigPath)) {
5550
5619
  return hostConfigPath;
@@ -5557,7 +5626,7 @@ function resolveGlobalConfigPath(host = "opencode") {
5557
5626
  }
5558
5627
  return hostConfigPath;
5559
5628
  }
5560
- function resolveGlobalIndexPath(host = "opencode") {
5629
+ function resolveGlobalIndexPath(host) {
5561
5630
  const hostIndexPath = getGlobalIndexPath(host);
5562
5631
  if ((0, import_fs7.existsSync)(hostIndexPath)) {
5563
5632
  return hostIndexPath;
@@ -5573,37 +5642,35 @@ function resolveGlobalIndexPath(host = "opencode") {
5573
5642
  }
5574
5643
  return hostIndexPath;
5575
5644
  }
5576
- function resolveProjectConfigPath(projectRoot, host = "opencode") {
5577
- const hostConfigPath = path10.join(projectRoot, getProjectConfigRelativePath(host));
5578
- if ((0, import_fs7.existsSync)(hostConfigPath)) {
5579
- return hostConfigPath;
5580
- }
5581
- if (host !== "opencode") {
5582
- const legacyConfigPath = path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5583
- if ((0, import_fs7.existsSync)(legacyConfigPath)) {
5584
- return legacyConfigPath;
5585
- }
5586
- }
5587
- const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectConfigRelativePath(host));
5588
- if (hostFallback) {
5589
- return hostFallback;
5590
- }
5591
- if (host !== "opencode") {
5592
- const legacyFallback = resolveWorktreeFallbackPath(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5593
- if (legacyFallback) {
5594
- return legacyFallback;
5595
- }
5596
- }
5597
- return hostConfigPath;
5598
- }
5599
- function resolveWritableProjectConfigPath(projectRoot, host = "opencode") {
5600
- return path10.join(projectRoot, getProjectConfigRelativePath(host));
5645
+ function resolveProjectConfigPath(projectRoot, host) {
5646
+ const candidates = getProjectConfigCandidatePaths(projectRoot, host);
5647
+ return candidates.find((candidate) => (0, import_fs7.existsSync)(candidate)) ?? path10.join(projectRoot, getProjectConfigRelativePath(host));
5601
5648
  }
5602
- function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
5649
+ function resolveProjectIndexPath(projectRoot, scope, host) {
5603
5650
  if (scope === "global") {
5604
5651
  return resolveGlobalIndexPath(host);
5605
5652
  }
5606
5653
  const localIndexPath = path10.join(projectRoot, getProjectIndexRelativePath(host));
5654
+ const mainRepoRoot = resolveWorktreeMainRepoRoot(projectRoot);
5655
+ if (mainRepoRoot) {
5656
+ if (hasHostProjectConfig(projectRoot, host)) {
5657
+ return localIndexPath;
5658
+ }
5659
+ if (host !== "opencode") {
5660
+ const localLegacyConfigPath = path10.join(projectRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5661
+ if ((0, import_fs7.existsSync)(localLegacyConfigPath)) {
5662
+ return path10.join(projectRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
5663
+ }
5664
+ const mainHostConfigPath = path10.join(mainRepoRoot, getProjectConfigRelativePath(host));
5665
+ const mainHostIndexPath = path10.join(mainRepoRoot, getProjectIndexRelativePath(host));
5666
+ const mainLegacyConfigPath = path10.join(mainRepoRoot, OPENCODE_PROJECT_CONFIG_RELATIVE_PATH);
5667
+ const mainLegacyIndexPath = path10.join(mainRepoRoot, OPENCODE_PROJECT_INDEX_RELATIVE_PATH);
5668
+ if (!(0, import_fs7.existsSync)(mainHostConfigPath) && !(0, import_fs7.existsSync)(mainHostIndexPath) && ((0, import_fs7.existsSync)(mainLegacyConfigPath) || (0, import_fs7.existsSync)(mainLegacyIndexPath))) {
5669
+ return mainLegacyIndexPath;
5670
+ }
5671
+ }
5672
+ return path10.join(mainRepoRoot, getProjectIndexRelativePath(host));
5673
+ }
5607
5674
  if ((0, import_fs7.existsSync)(localIndexPath)) {
5608
5675
  return localIndexPath;
5609
5676
  }
@@ -5616,9 +5683,6 @@ function resolveProjectIndexPath(projectRoot, scope, host = "opencode") {
5616
5683
  if (hasHostProjectConfig(projectRoot, host)) {
5617
5684
  return localIndexPath;
5618
5685
  }
5619
- if (resolveWorktreeMainRepoRoot(projectRoot)) {
5620
- return localIndexPath;
5621
- }
5622
5686
  const hostFallback = resolveWorktreeFallbackPath(projectRoot, getProjectIndexRelativePath(host));
5623
5687
  if (hostFallback) {
5624
5688
  return hostFallback;
@@ -7685,6 +7749,8 @@ function hasBlameMetadata(metadata) {
7685
7749
  return blameFromMetadata(metadata) !== void 0;
7686
7750
  }
7687
7751
  var INDEX_METADATA_VERSION = "1";
7752
+ var PROJECT_PATH_STORAGE_VERSION = "2";
7753
+ var GLOBAL_PATH_STORAGE_VERSION = "1";
7688
7754
  var EMBEDDING_STRATEGY_VERSION = "2";
7689
7755
  var SWIFT_PARSER_VERSION = "1";
7690
7756
  var METAL_PARSER_VERSION = "1";
@@ -8015,18 +8081,20 @@ function selectIndexableChunks(chunks, limit, semanticOnly) {
8015
8081
  const indexableChunks = semanticOnly ? chunks.filter((chunk) => chunk.chunkType !== "other") : chunks;
8016
8082
  return selectChunksWithFileCoverage(indexableChunks, limit);
8017
8083
  }
8018
- function matchesHardSearchFilters(candidate, options) {
8084
+ function matchesHardSearchFilters(candidate, options, projectRoot) {
8019
8085
  if (options?.fileType) {
8020
8086
  const ext = candidate.metadata.filePath.split(".").pop()?.toLowerCase();
8021
8087
  const requestedExtension = options.fileType.trim().toLowerCase().replace(/^\./, "");
8022
8088
  if (ext !== requestedExtension) return false;
8023
8089
  }
8024
8090
  if (options?.directory) {
8025
- const normalizedPath = candidate.metadata.filePath.replace(/\\/g, "/");
8026
- const normalizedDir = options.directory.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/$/, "");
8027
- const isAbsoluteDirectory = normalizedDir.startsWith("/");
8028
- const matchesDirectory = isAbsoluteDirectory ? normalizedPath === normalizedDir || normalizedPath.startsWith(`${normalizedDir}/`) : normalizedPath === normalizedDir || normalizedPath.startsWith(`${normalizedDir}/`) || normalizedPath.includes(`/${normalizedDir}/`) || normalizedPath.endsWith(`/${normalizedDir}`);
8029
- if (!matchesDirectory) return false;
8091
+ const candidatePath = canonicalizePathForComparison(
8092
+ path14.resolve(projectRoot, candidate.metadata.filePath.replace(/\\/g, path14.sep))
8093
+ );
8094
+ const directoryPath = canonicalizePathForComparison(
8095
+ path14.resolve(projectRoot, options.directory.trim().replace(/\\/g, path14.sep))
8096
+ );
8097
+ if (!isPathWithinRoot2(candidatePath, directoryPath)) return false;
8030
8098
  }
8031
8099
  if (options?.chunkType && candidate.metadata.chunkType !== options.chunkType) {
8032
8100
  return false;
@@ -8048,8 +8116,8 @@ function matchesHardSearchFilters(candidate, options) {
8048
8116
  }
8049
8117
  return true;
8050
8118
  }
8051
- function matchesSearchFilters(candidate, options, minScore) {
8052
- return candidate.score >= minScore && matchesHardSearchFilters(candidate, options);
8119
+ function matchesSearchFilters(candidate, options, minScore, projectRoot) {
8120
+ return candidate.score >= minScore && matchesHardSearchFilters(candidate, options, projectRoot);
8053
8121
  }
8054
8122
  function unionCandidates(semanticCandidates, keywordCandidates) {
8055
8123
  const byId = /* @__PURE__ */ new Map();
@@ -8100,7 +8168,7 @@ var Indexer = class _Indexer {
8100
8168
  readerArtifactFingerprint = null;
8101
8169
  writerArtifactFingerprint = null;
8102
8170
  readerArtifactRetryAfter = /* @__PURE__ */ new Map();
8103
- constructor(projectRoot, config, host = "opencode", runtimeOptions = {}) {
8171
+ constructor(projectRoot, config, host, runtimeOptions = {}) {
8104
8172
  this.projectRoot = projectRoot;
8105
8173
  this.projectIdentityHash = hashContent(this.getCanonicalPath(projectRoot)).slice(0, 16);
8106
8174
  this.materializedProjectRoot = runtimeOptions.materializedProjectRoot ?? projectRoot;
@@ -8113,37 +8181,90 @@ var Indexer = class _Indexer {
8113
8181
  this.indexPathOverride = runtimeOptions.indexPath;
8114
8182
  this.config = config;
8115
8183
  this.host = host;
8184
+ if (isGitRepo(this.materializedProjectRoot)) {
8185
+ this.currentBranch = this.branchNameOverride ?? getBranchOrDefault(this.materializedProjectRoot);
8186
+ this.baseBranch = getBaseBranch(this.materializedProjectRoot);
8187
+ } else {
8188
+ this.currentBranch = "default";
8189
+ this.baseBranch = "default";
8190
+ }
8116
8191
  this.indexPath = this.getIndexPath();
8117
- this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8118
- this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8192
+ this.refreshRuntimeArtifactPaths();
8119
8193
  this.logger = initializeLogger(config.debug);
8120
8194
  }
8121
8195
  getIndexPath() {
8122
8196
  return this.indexPathOverride ?? resolveProjectIndexPath(this.projectRoot, this.config.scope, this.host);
8123
8197
  }
8124
8198
  toCanonicalFilePath(filePath) {
8199
+ if (!path14.isAbsolute(filePath)) {
8200
+ return this.resolveStoredFilePath(filePath, this.projectRoot);
8201
+ }
8125
8202
  if (path14.resolve(this.materializedProjectRoot) === path14.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.materializedProjectRoot)) {
8126
8203
  return filePath;
8127
8204
  }
8128
8205
  return path14.resolve(this.projectRoot, path14.relative(this.materializedProjectRoot, filePath));
8129
8206
  }
8130
- toMaterializedFilePath(filePath) {
8131
- if (path14.resolve(this.materializedProjectRoot) === path14.resolve(this.projectRoot) || !isPathWithinRoot2(filePath, this.projectRoot)) {
8207
+ toStoredFilePath(filePath) {
8208
+ const canonicalFilePath = this.toCanonicalFilePath(filePath);
8209
+ if (this.config.scope !== "project" || !isPathWithinRoot2(canonicalFilePath, this.projectRoot)) {
8210
+ return canonicalFilePath;
8211
+ }
8212
+ return path14.relative(this.projectRoot, canonicalFilePath).split(path14.sep).join("/");
8213
+ }
8214
+ resolveStoredFilePath(filePath, rootPath = this.projectRoot) {
8215
+ if (path14.isAbsolute(filePath)) {
8132
8216
  return filePath;
8133
8217
  }
8134
- return path14.resolve(this.materializedProjectRoot, path14.relative(this.projectRoot, filePath));
8218
+ const resolvedPath = path14.resolve(rootPath, ...filePath.split("/"));
8219
+ if (!isPathWithinRoot2(resolvedPath, rootPath)) {
8220
+ throw new Error(`Stored project path escapes project root: ${JSON.stringify(filePath)}`);
8221
+ }
8222
+ return resolvedPath;
8223
+ }
8224
+ getCanonicalStoredFilePath(filePath) {
8225
+ return this.getCanonicalPath(this.resolveStoredFilePath(filePath));
8226
+ }
8227
+ resolveFilePathRecord(record) {
8228
+ return {
8229
+ ...record,
8230
+ filePath: this.resolveStoredFilePath(record.filePath)
8231
+ };
8232
+ }
8233
+ resolveCallEdgeFilePath(edge) {
8234
+ if (!edge.fromSymbolFilePath) return edge;
8235
+ return {
8236
+ ...edge,
8237
+ fromSymbolFilePath: this.resolveStoredFilePath(edge.fromSymbolFilePath)
8238
+ };
8239
+ }
8240
+ toMaterializedFilePath(filePath) {
8241
+ const storedFilePath = this.toStoredFilePath(filePath);
8242
+ if (path14.isAbsolute(storedFilePath)) {
8243
+ return storedFilePath;
8244
+ }
8245
+ return this.resolveStoredFilePath(storedFilePath, this.materializedProjectRoot);
8135
8246
  }
8136
8247
  getPreparedBranchNamespace() {
8137
8248
  if (!this.branchNameOverride && !this.catalogIdentityOverride) return null;
8138
8249
  return hashContent(this.getBranchCatalogKey()).slice(0, 16);
8139
8250
  }
8251
+ getRuntimeArtifactNamespace() {
8252
+ if (this.config.scope !== "project" || this.getBranchCatalogIdentity() === "default") {
8253
+ return this.getPreparedBranchNamespace();
8254
+ }
8255
+ return hashContent(this.getBranchCatalogKey()).slice(0, 16);
8256
+ }
8140
8257
  getRuntimeArtifactPath(fileName) {
8141
- const namespace = this.getPreparedBranchNamespace();
8258
+ const namespace = this.getRuntimeArtifactNamespace();
8142
8259
  if (!namespace) return path14.join(this.indexPath, fileName);
8143
8260
  const extension = path14.extname(fileName);
8144
8261
  const baseName = fileName.slice(0, fileName.length - extension.length);
8145
8262
  return path14.join(this.indexPath, `${baseName}.${namespace}${extension}`);
8146
8263
  }
8264
+ refreshRuntimeArtifactPaths() {
8265
+ this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8266
+ this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8267
+ }
8147
8268
  getPreparedChunkId(chunkId) {
8148
8269
  const namespace = this.getPreparedBranchNamespace();
8149
8270
  return namespace ? `${chunkId}_${namespace}` : chunkId;
@@ -8169,19 +8290,8 @@ var Indexer = class _Indexer {
8169
8290
  return path14.resolve(targetPath);
8170
8291
  }
8171
8292
  }
8172
- isLocalProjectIndexPath() {
8173
- const localProjectIndexPaths = [path14.join(this.projectRoot, getHostProjectIndexRelativePath(this.host))];
8174
- if (this.host !== "opencode") {
8175
- localProjectIndexPaths.push(path14.join(this.projectRoot, getHostProjectIndexRelativePath("opencode")));
8176
- }
8177
- return localProjectIndexPaths.some((localPath) => {
8178
- if (!(0, import_fs10.existsSync)(localPath) || !(0, import_fs10.existsSync)(this.indexPath)) {
8179
- return path14.resolve(this.indexPath) === path14.resolve(localPath);
8180
- }
8181
- const indexStats = (0, import_fs10.statSync)(this.indexPath);
8182
- const localStats = (0, import_fs10.statSync)(localPath);
8183
- return indexStats.dev === localStats.dev && indexStats.ino === localStats.ino;
8184
- });
8293
+ isProjectOwnedIndexPath() {
8294
+ return isProjectIndexPathOwnedByProject(this.projectRoot, this.indexPath, this.host);
8185
8295
  }
8186
8296
  resetLoadedIndexState(retireDatabase = false) {
8187
8297
  if (this.database) {
@@ -8216,10 +8326,10 @@ var Indexer = class _Indexer {
8216
8326
  this.readerArtifactRetryAfter.clear();
8217
8327
  }
8218
8328
  async withIndexMutationLease(operation, callback) {
8329
+ this.refreshBranchInfo();
8219
8330
  const lease = acquireIndexLock(this.indexPath, operation);
8220
8331
  this.indexPath = lease.canonicalIndexPath;
8221
- this.fileHashCachePath = this.getRuntimeArtifactPath("file-hashes.json");
8222
- this.failedBatchesPath = this.getRuntimeArtifactPath("failed-batches.json");
8332
+ this.refreshRuntimeArtifactPaths();
8223
8333
  this.activeIndexLease = lease;
8224
8334
  let result;
8225
8335
  let callbackError;
@@ -8391,29 +8501,24 @@ var Indexer = class _Indexer {
8391
8501
  getProjectForceReembedMetadataKey() {
8392
8502
  return `index.forceReembed.${this.projectIdentityHash}`;
8393
8503
  }
8394
- getCallGraphResolutionMetadataKey() {
8395
- if (this.config.scope !== "global") {
8396
- return "index.callGraphResolutionVersion";
8397
- }
8398
- return `index.callGraphResolutionVersion.${this.projectIdentityHash}`;
8504
+ getBranchMigrationMetadataKey(prefix, catalogIdentity = this.getBranchCatalogIdentity()) {
8505
+ const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
8506
+ return `${prefix}.${hashContent(branchKey).slice(0, 24)}`;
8399
8507
  }
8400
- getSwiftParserVersionMetadataKey() {
8401
- const key = "index.parser.swiftVersion";
8402
- if (this.config.scope !== "global") {
8403
- return key;
8404
- }
8405
- return `${key}.${this.projectIdentityHash}`;
8508
+ getCallGraphResolutionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8509
+ return this.getBranchMigrationMetadataKey("index.callGraphResolutionVersion", catalogIdentity);
8406
8510
  }
8407
- getMetalParserVersionMetadataKey() {
8408
- const key = "index.parser.metalVersion";
8409
- if (this.config.scope !== "global") {
8410
- return key;
8411
- }
8412
- return `${key}.${this.projectIdentityHash}`;
8511
+ getSwiftParserVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8512
+ return this.getBranchMigrationMetadataKey("index.parser.swiftVersion", catalogIdentity);
8513
+ }
8514
+ getMetalParserVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8515
+ return this.getBranchMigrationMetadataKey("index.parser.metalVersion", catalogIdentity);
8413
8516
  }
8414
8517
  getSymbolExtractorVersionMetadataKey(catalogIdentity = this.getBranchCatalogIdentity()) {
8415
- const branchKey = this.getBranchCatalogKeyFor(catalogIdentity);
8416
- return `index.symbolExtractorVersion.${hashContent(branchKey).slice(0, 24)}`;
8518
+ return this.getBranchMigrationMetadataKey("index.symbolExtractorVersion", catalogIdentity);
8519
+ }
8520
+ areBranchMigrationVersionsCurrent(database, catalogIdentity = this.getBranchCatalogIdentity()) {
8521
+ 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;
8417
8522
  }
8418
8523
  hasProjectForceReembedPending() {
8419
8524
  return this.config.scope === "global" && this.database?.getMetadata(this.getProjectForceReembedMetadataKey()) === "true";
@@ -8548,13 +8653,14 @@ var Indexer = class _Indexer {
8548
8653
  return Array.from(keys);
8549
8654
  }
8550
8655
  isFileInCurrentScope(filePath, roots) {
8551
- if (roots.some((root) => isPathWithinRoot2(filePath, root))) return true;
8552
- const canonicalFilePath = this.getCanonicalPath(filePath);
8656
+ const canonicalFilePath = this.getCanonicalStoredFilePath(filePath);
8553
8657
  return roots.some((root) => isPathWithinRoot2(canonicalFilePath, root));
8554
8658
  }
8555
8659
  isFileInProjectRoot(filePath) {
8556
- if (isPathWithinRoot2(filePath, this.projectRoot)) return true;
8557
- return isPathWithinRoot2(this.getCanonicalPath(filePath), this.getCanonicalPath(this.projectRoot));
8660
+ return isPathWithinRoot2(
8661
+ this.getCanonicalStoredFilePath(filePath),
8662
+ this.getCanonicalPath(this.projectRoot)
8663
+ );
8558
8664
  }
8559
8665
  clearScopedFileHashCache(roots) {
8560
8666
  for (const filePath of Array.from(this.fileHashCache.keys())) {
@@ -8955,7 +9061,10 @@ var Indexer = class _Indexer {
8955
9061
  const intent = isLikelyImplementationPath2(candidate.metadata.filePath) ? "implementation" : "doc_or_test";
8956
9062
  parts.push(`intent_hint: ${intent}`);
8957
9063
  try {
8958
- const fileContent = await import_fs10.promises.readFile(candidate.metadata.filePath, "utf-8");
9064
+ const fileContent = await import_fs10.promises.readFile(
9065
+ this.toMaterializedFilePath(candidate.metadata.filePath),
9066
+ "utf-8"
9067
+ );
8959
9068
  const lines = fileContent.split("\n");
8960
9069
  const snippetStartLine = Math.max(1, candidate.metadata.startLine);
8961
9070
  const snippetEndLine = Math.min(lines.length, candidate.metadata.endLine);
@@ -9024,7 +9133,7 @@ var Indexer = class _Indexer {
9024
9133
  if (this.config.scope === "global") {
9025
9134
  return "Shared vector index could not be read. Restore or repair the complete fingerprinted shared vector artifacts; automatic reset is disabled for global scope.";
9026
9135
  }
9027
- if (!this.isLocalProjectIndexPath()) {
9136
+ if (!this.isProjectOwnedIndexPath()) {
9028
9137
  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.";
9029
9138
  }
9030
9139
  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.";
@@ -9033,7 +9142,7 @@ var Indexer = class _Indexer {
9033
9142
  if (this.config.scope === "global") {
9034
9143
  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.";
9035
9144
  }
9036
- if (!this.isLocalProjectIndexPath()) {
9145
+ if (!this.isProjectOwnedIndexPath()) {
9037
9146
  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.";
9038
9147
  }
9039
9148
  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.";
@@ -9042,10 +9151,10 @@ var Indexer = class _Indexer {
9042
9151
  if (this.config.scope === "global") {
9043
9152
  return "Shared index database could not be read. Restore or repair the shared SQLite database; automatic reset is disabled for global scope.";
9044
9153
  }
9045
- if (!this.isLocalProjectIndexPath()) {
9154
+ if (!this.isProjectOwnedIndexPath()) {
9046
9155
  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.";
9047
9156
  }
9048
- return "Index database could not be read. Run index_codebase after the active writer finishes to repair or migrate it under the writer lease.";
9157
+ 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.";
9049
9158
  }
9050
9159
  getReaderFileFingerprint(filePath, identityOnly = false) {
9051
9160
  try {
@@ -9225,7 +9334,7 @@ var Indexer = class _Indexer {
9225
9334
  const readerArtifactFingerprint = mode === "reader" ? this.captureReaderArtifactFingerprint() : null;
9226
9335
  if (mode === "writer") {
9227
9336
  await import_fs10.promises.mkdir(this.indexPath, { recursive: true });
9228
- if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isLocalProjectIndexPath()) {
9337
+ if (recoveredOwners.length > 0 && this.config.scope === "project" && !this.isProjectOwnedIndexPath()) {
9229
9338
  throw new Error(
9230
9339
  "Interrupted indexing recovery is unsafe while using an inherited worktree index. Run index_codebase with force=true to create a local project index boundary."
9231
9340
  );
@@ -9326,6 +9435,7 @@ var Indexer = class _Indexer {
9326
9435
  this.baseBranch = "default";
9327
9436
  this.logger.branch("debug", "Not a git repository, using default branch");
9328
9437
  }
9438
+ this.refreshRuntimeArtifactPaths();
9329
9439
  if (mode === "writer" && recoveredOwners.length > 0) {
9330
9440
  await this.recoverFromInterruptedIndexingUnlocked(recoveredOwners);
9331
9441
  }
@@ -9481,6 +9591,14 @@ var Indexer = class _Indexer {
9481
9591
  }
9482
9592
  return `Detected a corrupted local SQLite index at ${dbPath} and reset the local index. Run index_codebase to rebuild search data.`;
9483
9593
  }
9594
+ async removeProjectRuntimeStateArtifacts() {
9595
+ if (!(0, import_fs10.existsSync)(this.indexPath)) return;
9596
+ const names = await import_fs10.promises.readdir(this.indexPath);
9597
+ const runtimeStatePattern = /^(?:file-hashes|failed-batches)(?:\.[a-f0-9]{16})?\.json$/;
9598
+ await Promise.all(
9599
+ names.filter((name) => runtimeStatePattern.test(name)).map((name) => import_fs10.promises.rm(path14.join(this.indexPath, name), { force: true }))
9600
+ );
9601
+ }
9484
9602
  async resetLocalIndexArtifacts() {
9485
9603
  this.store = null;
9486
9604
  this.invertedIndex = null;
@@ -9500,11 +9618,10 @@ var Indexer = class _Indexer {
9500
9618
  path14.join(this.indexPath, "vectors"),
9501
9619
  path14.join(this.indexPath, "vectors.usearch"),
9502
9620
  path14.join(this.indexPath, "vectors.meta.json"),
9503
- path14.join(this.indexPath, "inverted-index.json"),
9504
- path14.join(this.indexPath, "file-hashes.json"),
9505
- path14.join(this.indexPath, "failed-batches.json")
9621
+ path14.join(this.indexPath, "inverted-index.json")
9506
9622
  ];
9507
9623
  await Promise.all(resetPaths.map((targetPath) => import_fs10.promises.rm(targetPath, { recursive: true, force: true })));
9624
+ await this.removeProjectRuntimeStateArtifacts();
9508
9625
  await import_fs10.promises.mkdir(this.indexPath, { recursive: true });
9509
9626
  }
9510
9627
  async tryResetCorruptedIndex(stage, error) {
@@ -9554,12 +9671,20 @@ var Indexer = class _Indexer {
9554
9671
  }
9555
9672
  this.database.addChunksToBranchBatch(this.getBranchCatalogKey(), chunkIds);
9556
9673
  }
9674
+ getExpectedPathStorageVersion() {
9675
+ return this.config.scope === "project" ? PROJECT_PATH_STORAGE_VERSION : GLOBAL_PATH_STORAGE_VERSION;
9676
+ }
9677
+ hasStoredIndexData() {
9678
+ const stats = this.database?.getStats();
9679
+ return (this.store?.count() ?? 0) > 0 || (stats?.chunkCount ?? 0) > 0 || (stats?.symbolCount ?? 0) > 0 || this.fileHashCache.size > 0;
9680
+ }
9557
9681
  loadIndexMetadata() {
9558
9682
  if (!this.database) return null;
9559
9683
  const version = this.database.getMetadata("index.version");
9560
9684
  if (!version) return null;
9561
9685
  return {
9562
9686
  indexVersion: version,
9687
+ pathStorageVersion: this.database.getMetadata("index.pathStorageVersion") ?? GLOBAL_PATH_STORAGE_VERSION,
9563
9688
  embeddingProvider: this.database.getMetadata("index.embeddingProvider") ?? "",
9564
9689
  embeddingModel: this.database.getMetadata("index.embeddingModel") ?? "",
9565
9690
  embeddingDimensions: parseInt(this.database.getMetadata("index.embeddingDimensions") ?? "0", 10),
@@ -9574,6 +9699,7 @@ var Indexer = class _Indexer {
9574
9699
  const existingCreatedAt = this.database.getMetadata("index.createdAt");
9575
9700
  const completeProjectEmbeddingStrategyReset = !this.hasProjectForceReembedPending();
9576
9701
  this.database.setMetadata("index.version", INDEX_METADATA_VERSION);
9702
+ this.database.setMetadata("index.pathStorageVersion", this.getExpectedPathStorageVersion());
9577
9703
  this.database.setMetadata("index.embeddingProvider", provider.provider);
9578
9704
  this.database.setMetadata("index.embeddingModel", provider.modelInfo.model);
9579
9705
  this.database.setMetadata("index.embeddingDimensions", provider.modelInfo.dimensions.toString());
@@ -9596,6 +9722,16 @@ var Indexer = class _Indexer {
9596
9722
  }
9597
9723
  validateIndexCompatibility(provider) {
9598
9724
  const storedMetadata = this.loadIndexMetadata();
9725
+ const storedPathStorageVersion = this.database?.getMetadata("index.pathStorageVersion") ?? GLOBAL_PATH_STORAGE_VERSION;
9726
+ const expectedPathStorageVersion = this.getExpectedPathStorageVersion();
9727
+ if (this.hasStoredIndexData() && storedPathStorageVersion !== expectedPathStorageVersion) {
9728
+ return {
9729
+ compatible: false,
9730
+ code: "PATH_STORAGE_MISMATCH" /* PATH_STORAGE_MISMATCH */,
9731
+ 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.`,
9732
+ storedMetadata: storedMetadata ?? void 0
9733
+ };
9734
+ }
9599
9735
  if (!storedMetadata) {
9600
9736
  return { compatible: true };
9601
9737
  }
@@ -9647,6 +9783,7 @@ var Indexer = class _Indexer {
9647
9783
  return this.indexCompatibility;
9648
9784
  }
9649
9785
  async ensureInitialized() {
9786
+ this.refreshBranchInfo();
9650
9787
  let initializedReader = false;
9651
9788
  while (true) {
9652
9789
  if (this.initializationPromise) {
@@ -9754,8 +9891,8 @@ var Indexer = class _Indexer {
9754
9891
  }
9755
9892
  const branchKey = this.getBranchCatalogKey();
9756
9893
  const alreadyIndexed = database.getBranchChunkIds(branchKey).length > 0 && database.getBranchSymbolIds(branchKey).length > 0;
9757
- const symbolsCurrent = database.getMetadata(this.getSymbolExtractorVersionMetadataKey()) === SYMBOL_EXTRACTOR_VERSION;
9758
- if (alreadyIndexed && symbolsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9894
+ const migrationsCurrent = this.areBranchMigrationVersionsCurrent(database);
9895
+ if (alreadyIndexed && migrationsCurrent && this.getStoredBranchCommit(database) === normalizedCommit) {
9759
9896
  return { prepared: false };
9760
9897
  }
9761
9898
  const stats = await this.indexUnlocked(onProgress, [], true);
@@ -9849,21 +9986,21 @@ var Indexer = class _Indexer {
9849
9986
  const currentFileHashes = /* @__PURE__ */ new Map();
9850
9987
  const needsCallGraphResolutionMigration = database.getMetadata(this.getCallGraphResolutionMetadataKey()) !== CALL_GRAPH_RESOLUTION_VERSION;
9851
9988
  for (const f of files) {
9852
- const canonicalPath = this.toCanonicalFilePath(f.path);
9989
+ const storedPath = this.toStoredFilePath(f.path);
9853
9990
  const currentHash = hashFile(f.path);
9854
- currentFileHashes.set(canonicalPath, currentHash);
9855
- const cachedHashMatches = this.fileHashCache.get(canonicalPath) === currentHash;
9856
- const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(canonicalPath).some(
9991
+ currentFileHashes.set(storedPath, currentHash);
9992
+ const cachedHashMatches = this.fileHashCache.get(storedPath) === currentHash;
9993
+ const needsCallGraphRefresh = cachedHashMatches && needsCallGraphResolutionMigration && database.getChunksByFile(storedPath).some(
9857
9994
  (chunk) => chunk.language === "php" || chunk.language === "c" || chunk.language === "cpp"
9858
9995
  );
9859
- const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path14.extname(canonicalPath).toLowerCase() === ".swift";
9860
- const requiresMetalParserUpgrade = reparseCachedMetalFiles && path14.extname(canonicalPath).toLowerCase() === ".metal";
9996
+ const requiresSwiftParserUpgrade = reparseCachedSwiftFiles && path14.extname(storedPath).toLowerCase() === ".swift";
9997
+ const requiresMetalParserUpgrade = reparseCachedMetalFiles && path14.extname(storedPath).toLowerCase() === ".metal";
9861
9998
  if (cachedHashMatches && !needsCallGraphRefresh && !requiresSwiftParserUpgrade && !requiresMetalParserUpgrade && !refreshCachedSymbols) {
9862
- unchangedFilePaths.add(canonicalPath);
9999
+ unchangedFilePaths.add(storedPath);
9863
10000
  this.logger.recordCacheHit();
9864
10001
  } else {
9865
10002
  const content = await import_fs10.promises.readFile(f.path, "utf-8");
9866
- changedFiles.push({ path: canonicalPath, content, hash: currentHash });
10003
+ changedFiles.push({ path: storedPath, content, hash: currentHash });
9867
10004
  this.logger.recordCacheMiss();
9868
10005
  }
9869
10006
  }
@@ -9968,8 +10105,7 @@ var Indexer = class _Indexer {
9968
10105
  for (const parsed of parsedFiles) {
9969
10106
  currentFilePaths.add(parsed.path);
9970
10107
  if (parsed.chunks.length === 0) {
9971
- const relativePath = path14.relative(this.projectRoot, parsed.path);
9972
- stats.parseFailures.push(relativePath);
10108
+ stats.parseFailures.push(path14.isAbsolute(parsed.path) ? path14.relative(this.projectRoot, parsed.path) : parsed.path);
9973
10109
  }
9974
10110
  let chunksToProcess = parsed.chunks;
9975
10111
  if (this.config.indexing.fallbackToTextOnMaxChunks && chunksToProcess.length > this.config.indexing.maxChunksPerFile) {
@@ -10610,6 +10746,43 @@ var Indexer = class _Indexer {
10610
10746
  const union = a.size + b.size - intersection;
10611
10747
  return intersection / union;
10612
10748
  }
10749
+ getBranchPrefilterState(database, branchChunkIds) {
10750
+ const hasInitializedBranchCatalog = branchChunkIds !== null && database.getAllBranches().length > 0;
10751
+ return {
10752
+ hasInitializedBranchCatalog,
10753
+ shouldPrefilterByBranch: branchChunkIds !== null && (this.config.scope === "global" || hasInitializedBranchCatalog)
10754
+ };
10755
+ }
10756
+ searchCandidatesWithBranchPrefilter(initialLimit, totalCount, branchChunkIds, shouldPrefilterByBranch, search, getChunkId) {
10757
+ const normalizedLimit = Math.max(0, Math.floor(initialLimit));
10758
+ if (normalizedLimit === 0) return [];
10759
+ if (!shouldPrefilterByBranch || !branchChunkIds) {
10760
+ return search(normalizedLimit);
10761
+ }
10762
+ const targetCount = Math.min(normalizedLimit, branchChunkIds.size);
10763
+ if (targetCount === 0 || totalCount === 0) return [];
10764
+ let requestedLimit = Math.min(normalizedLimit, totalCount);
10765
+ while (true) {
10766
+ const results = search(requestedLimit);
10767
+ const branchResults = results.filter((candidate) => branchChunkIds.has(getChunkId(candidate)));
10768
+ if (branchResults.length >= targetCount || results.length < requestedLimit || requestedLimit >= totalCount) {
10769
+ return branchResults;
10770
+ }
10771
+ const nextLimit = Math.min(totalCount, Math.max(requestedLimit + 1, requestedLimit * 2));
10772
+ if (nextLimit === requestedLimit) return branchResults;
10773
+ requestedLimit = nextLimit;
10774
+ }
10775
+ }
10776
+ searchSemanticCandidates(store, embedding, initialLimit, branchChunkIds, shouldPrefilterByBranch) {
10777
+ return this.searchCandidatesWithBranchPrefilter(
10778
+ initialLimit,
10779
+ store.count(),
10780
+ branchChunkIds,
10781
+ shouldPrefilterByBranch,
10782
+ (requestedLimit) => store.search(embedding, requestedLimit),
10783
+ (candidate) => candidate.id
10784
+ );
10785
+ }
10613
10786
  async search(query, limit, options) {
10614
10787
  const { store, provider, invertedIndex, database, readIssues, compatibility } = await this.ensureInitialized();
10615
10788
  this.requireReadableComponents(readIssues, "vectors", "database");
@@ -10654,12 +10827,7 @@ var Indexer = class _Indexer {
10654
10827
  });
10655
10828
  }
10656
10829
  const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
10657
- const vectorStartTime = import_perf_hooks.performance.now();
10658
- const semanticResults = embedding ? store.search(embedding, maxResults * 4) : [];
10659
- const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
10660
- const keywordStartTime = import_perf_hooks.performance.now();
10661
- const keywordResults = await this.keywordSearch(query, maxResults * 4, store, invertedIndex);
10662
- const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
10830
+ const prefilterStartTime = import_perf_hooks.performance.now();
10663
10831
  let branchChunkIds = null;
10664
10832
  let branchSymbolIds = null;
10665
10833
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
@@ -10667,35 +10835,38 @@ var Indexer = class _Indexer {
10667
10835
  branchChunkIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchChunkIds(branchKey)));
10668
10836
  branchSymbolIds = new Set(branchCatalogKeys.flatMap((branchKey) => database.getBranchSymbolIds(branchKey)));
10669
10837
  }
10670
- const prefilterStartTime = import_perf_hooks.performance.now();
10671
- const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
10672
- const allowBranchPrefilterFallback = this.config.scope !== "global";
10673
- const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
10674
- const prefilteredKeyword = shouldPrefilterByBranch && branchChunkIds ? keywordResults.filter((r) => branchChunkIds.has(r.id)) : keywordResults;
10675
- const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
10676
- const keywordCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0 ? keywordResults : prefilteredKeyword;
10838
+ const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
10839
+ const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
10840
+ const vectorStartTime = import_perf_hooks.performance.now();
10841
+ const semanticCandidates = embedding ? this.searchSemanticCandidates(
10842
+ store,
10843
+ embedding,
10844
+ maxResults * 4,
10845
+ branchChunkIds,
10846
+ shouldPrefilterByBranch
10847
+ ) : [];
10848
+ const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
10849
+ const keywordStartTime = import_perf_hooks.performance.now();
10850
+ const keywordCandidates = await this.keywordSearch(
10851
+ query,
10852
+ maxResults * 4,
10853
+ store,
10854
+ invertedIndex,
10855
+ branchChunkIds,
10856
+ shouldPrefilterByBranch
10857
+ );
10858
+ const keywordMs = import_perf_hooks.performance.now() - keywordStartTime;
10677
10859
  const scopedSemanticCandidates = semanticCandidates.filter(
10678
- (candidate) => matchesHardSearchFilters(candidate, options)
10860
+ (candidate) => matchesHardSearchFilters(candidate, options, this.projectRoot)
10679
10861
  );
10680
10862
  const scopedKeywordCandidates = keywordCandidates.filter(
10681
- (candidate) => matchesHardSearchFilters(candidate, options)
10863
+ (candidate) => matchesHardSearchFilters(candidate, options, this.projectRoot)
10682
10864
  );
10683
- const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
10684
- if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
10865
+ if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
10685
10866
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
10686
10867
  branch: this.currentBranch
10687
10868
  });
10688
10869
  }
10689
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
10690
- this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
10691
- branch: this.currentBranch
10692
- });
10693
- }
10694
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && keywordResults.length > 0 && prefilteredKeyword.length === 0) {
10695
- this.logger.search("warn", "Branch prefilter produced no keyword overlap, using unfiltered keyword candidates", {
10696
- branch: this.currentBranch
10697
- });
10698
- }
10699
10870
  const fusionStartTime = import_perf_hooks.performance.now();
10700
10871
  const rankingHybridWeight = embedding === void 0 && fusionStrategy === "weighted" ? 1 : effectiveHybridWeight;
10701
10872
  const combined = rankHybridResults(query, scopedSemanticCandidates, scopedKeywordCandidates, {
@@ -10746,12 +10917,14 @@ var Indexer = class _Indexer {
10746
10917
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
10747
10918
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
10748
10919
  const hasCodeHints = extractCodeTermHints(query).length > 0 || identifierHints.length > 0;
10749
- const baseFiltered = tiered.filter((r) => matchesSearchFilters(r, options, this.config.search.minScore));
10920
+ const baseFiltered = tiered.filter(
10921
+ (r) => matchesSearchFilters(r, options, this.config.search.minScore, this.projectRoot)
10922
+ );
10750
10923
  const implementationOnly = baseFiltered.filter(
10751
10924
  (r) => isLikelyImplementationPath2(r.metadata.filePath) && isImplementationChunkType(r.metadata.chunkType)
10752
10925
  );
10753
10926
  const filtered = (sourceIntent && hasCodeHints && implementationOnly.length > 0 ? implementationOnly : baseFiltered).slice(0, maxResults);
10754
- 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) : [];
10927
+ 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) : [];
10755
10928
  const finalResults = filtered.length > 0 ? filtered : identifierFallback;
10756
10929
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
10757
10930
  this.logger.recordSearch(totalSearchMs, {
@@ -10776,10 +10949,11 @@ var Indexer = class _Indexer {
10776
10949
  let content = "";
10777
10950
  let contextStartLine = r.metadata.startLine;
10778
10951
  let contextEndLine = r.metadata.endLine;
10952
+ const resolvedFilePath = this.resolveStoredFilePath(r.metadata.filePath);
10779
10953
  if (!metadataOnly && this.config.search.includeContext) {
10780
10954
  try {
10781
10955
  const fileContent = await import_fs10.promises.readFile(
10782
- r.metadata.filePath,
10956
+ resolvedFilePath,
10783
10957
  "utf-8"
10784
10958
  );
10785
10959
  const lines = fileContent.split("\n");
@@ -10792,7 +10966,7 @@ var Indexer = class _Indexer {
10792
10966
  }
10793
10967
  }
10794
10968
  return {
10795
- filePath: r.metadata.filePath,
10969
+ filePath: resolvedFilePath,
10796
10970
  startLine: contextStartLine,
10797
10971
  endLine: contextEndLine,
10798
10972
  content,
@@ -10804,8 +10978,18 @@ var Indexer = class _Indexer {
10804
10978
  })
10805
10979
  );
10806
10980
  }
10807
- async keywordSearch(query, limit, store, invertedIndex) {
10808
- const scores = invertedIndex.search(query);
10981
+ async keywordSearch(query, limit, store, invertedIndex, branchChunkIds = null, shouldPrefilterByBranch = false) {
10982
+ const normalizedLimit = Math.max(0, Math.floor(limit));
10983
+ if (normalizedLimit === 0) return [];
10984
+ const scoreEntries = this.searchCandidatesWithBranchPrefilter(
10985
+ normalizedLimit,
10986
+ invertedIndex.getDocumentCount(),
10987
+ branchChunkIds,
10988
+ shouldPrefilterByBranch,
10989
+ (requestedLimit) => Array.from(invertedIndex.search(query, requestedLimit)),
10990
+ ([chunkId]) => chunkId
10991
+ );
10992
+ const scores = new Map(scoreEntries);
10809
10993
  if (scores.size === 0) {
10810
10994
  return [];
10811
10995
  }
@@ -10819,7 +11003,7 @@ var Indexer = class _Indexer {
10819
11003
  }
10820
11004
  }
10821
11005
  results.sort((a, b) => b.score - a.score);
10822
- return results.slice(0, limit);
11006
+ return results.slice(0, normalizedLimit);
10823
11007
  }
10824
11008
  async getStatus() {
10825
11009
  const { store, configuredProviderInfo, database, readIssues, compatibility } = await this.ensureInitialized();
@@ -10886,7 +11070,7 @@ var Indexer = class _Indexer {
10886
11070
  );
10887
11071
  const currentFileHashes = /* @__PURE__ */ new Map();
10888
11072
  for (const file of files) {
10889
- currentFileHashes.set(this.toCanonicalFilePath(file.path), hashFile(file.path));
11073
+ currentFileHashes.set(this.toStoredFilePath(file.path), hashFile(file.path));
10890
11074
  }
10891
11075
  const scopedRoots = this.config.scope === "global" ? this.getScopedRoots() : null;
10892
11076
  const cachedFileHashes = scopedRoots ? new Map(Array.from(this.fileHashCache).filter(([filePath]) => this.isFileInCurrentScope(filePath, scopedRoots))) : this.fileHashCache;
@@ -10898,17 +11082,7 @@ var Indexer = class _Indexer {
10898
11082
  return { readable: true, current: false, reason: "files-changed" };
10899
11083
  }
10900
11084
  }
10901
- const hasSwiftFiles = Array.from(currentFileHashes.keys()).some(
10902
- (filePath) => path14.extname(filePath).toLowerCase() === ".swift"
10903
- );
10904
- const hasMetalFiles = Array.from(currentFileHashes.keys()).some(
10905
- (filePath) => path14.extname(filePath).toLowerCase() === ".metal"
10906
- );
10907
- const hasCallGraphMigrationFiles = Array.from(currentFileHashes.keys()).some((filePath) => {
10908
- const extension = path14.extname(filePath).toLowerCase();
10909
- return extension === ".php" || extension === ".c" || extension === ".cc" || extension === ".cpp" || extension === ".cxx";
10910
- });
10911
- 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) {
11085
+ if (!this.areBranchMigrationVersionsCurrent(database)) {
10912
11086
  return { readable: true, current: false, reason: "migration-required" };
10913
11087
  }
10914
11088
  if (isGitRepo(this.materializedProjectRoot)) {
@@ -10968,6 +11142,7 @@ var Indexer = class _Indexer {
10968
11142
  this.deleteBranchCommitMetadata(database, clearedBranchKeys2);
10969
11143
  this.saveFailedBatches([]);
10970
11144
  database.deleteMetadata("index.version");
11145
+ database.deleteMetadata("index.pathStorageVersion");
10971
11146
  database.deleteMetadata("index.embeddingProvider");
10972
11147
  database.deleteMetadata("index.embeddingModel");
10973
11148
  database.deleteMetadata("index.embeddingDimensions");
@@ -10986,7 +11161,7 @@ var Indexer = class _Indexer {
10986
11161
  this.indexCompatibility = compatibility;
10987
11162
  return;
10988
11163
  }
10989
- if (!this.isLocalProjectIndexPath()) {
11164
+ if (!this.isProjectOwnedIndexPath()) {
10990
11165
  throw new Error(
10991
11166
  "Project-scoped force rebuild is unsafe while using an inherited worktree index. Create a local project config boundary before clearing the index."
10992
11167
  );
@@ -10997,11 +11172,11 @@ var Indexer = class _Indexer {
10997
11172
  invertedIndex.clear();
10998
11173
  this.saveInvertedIndex(invertedIndex);
10999
11174
  this.fileHashCache.clear();
11000
- this.saveFileHashCache();
11175
+ await this.removeProjectRuntimeStateArtifacts();
11001
11176
  database.clearAllIndexedData();
11002
11177
  this.deleteBranchCommitMetadata(database, clearedBranchKeys);
11003
- this.saveFailedBatches([]);
11004
11178
  database.deleteMetadata("index.version");
11179
+ database.deleteMetadata("index.pathStorageVersion");
11005
11180
  database.deleteMetadata("index.embeddingProvider");
11006
11181
  database.deleteMetadata("index.embeddingModel");
11007
11182
  database.deleteMetadata("index.embeddingDimensions");
@@ -11029,32 +11204,46 @@ var Indexer = class _Indexer {
11029
11204
  existing.push(key);
11030
11205
  filePathsToChunkKeys.set(metadata.filePath, existing);
11031
11206
  }
11032
- const removedFilePaths = [];
11033
- const removedChunkKeys = [];
11207
+ const missingStoredFilePaths = [];
11208
+ const missingChunkKeys = [];
11034
11209
  const chunkKeysByRemovedFile = /* @__PURE__ */ new Map();
11035
11210
  for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
11036
- if (!(0, import_fs10.existsSync)(filePath)) {
11211
+ if (!(0, import_fs10.existsSync)(this.toMaterializedFilePath(filePath))) {
11037
11212
  chunkKeysByRemovedFile.set(filePath, chunkKeys);
11038
11213
  for (const key of chunkKeys) {
11039
- removedChunkKeys.push(key);
11214
+ missingChunkKeys.push(key);
11040
11215
  }
11041
- removedFilePaths.push(filePath);
11216
+ missingStoredFilePaths.push(filePath);
11042
11217
  }
11043
11218
  }
11219
+ const branchCatalogKeys = this.getBranchCatalogKeys();
11220
+ for (const branchKey of branchCatalogKeys) {
11221
+ database.deleteBranchChunksForBranch(branchKey, missingChunkKeys);
11222
+ }
11223
+ const referencedChunkKeys = new Set(database.getReferencedChunkIds(missingChunkKeys));
11224
+ const removedChunkKeys = missingChunkKeys.filter((key) => !referencedChunkKeys.has(key));
11044
11225
  if (removedChunkKeys.length > 0) {
11045
11226
  this.rebuildVectorStoreExcludingChunkIds(store, database, removedChunkKeys);
11046
11227
  for (const key of removedChunkKeys) {
11047
11228
  invertedIndex.removeChunk(key);
11048
11229
  }
11230
+ database.deleteChunksByIds(removedChunkKeys);
11049
11231
  }
11050
- for (const filePath of removedFilePaths) {
11051
- const fileChunkKeys = chunkKeysByRemovedFile.get(filePath) ?? [];
11052
- if (fileChunkKeys.length > 0) {
11053
- database.deleteChunksByIds(fileChunkKeys);
11054
- }
11055
- database.deleteCallEdgesByFile(filePath);
11056
- database.deleteSymbolsByFile(filePath);
11057
- }
11232
+ const missingSymbolIds = Array.from(new Set(
11233
+ missingStoredFilePaths.flatMap(
11234
+ (filePath) => database.getSymbolsByFile(filePath).map((symbol) => symbol.id)
11235
+ )
11236
+ ));
11237
+ for (const branchKey of branchCatalogKeys) {
11238
+ database.deleteBranchSymbolsForBranch(branchKey, missingSymbolIds);
11239
+ }
11240
+ const referencedSymbolIds = new Set(database.getReferencedSymbolIds(missingSymbolIds));
11241
+ const removedSymbolIds = missingSymbolIds.filter((symbolId) => !referencedSymbolIds.has(symbolId));
11242
+ database.clearCallEdgeTargetsForSymbols(removedSymbolIds);
11243
+ const removedChunkKeySet = new Set(removedChunkKeys);
11244
+ const removedStoredFilePaths = missingStoredFilePaths.filter(
11245
+ (filePath) => (chunkKeysByRemovedFile.get(filePath) ?? []).some((key) => removedChunkKeySet.has(key))
11246
+ );
11058
11247
  const removedCount = removedChunkKeys.length;
11059
11248
  if (removedCount > 0) {
11060
11249
  store.save();
@@ -11090,9 +11279,16 @@ var Indexer = class _Indexer {
11090
11279
  removedStale: removedCount,
11091
11280
  orphanEmbeddings: gcOrphanEmbeddings,
11092
11281
  orphanChunks: gcOrphanChunks,
11093
- removedFiles: removedFilePaths.length
11282
+ removedFiles: removedStoredFilePaths.length
11094
11283
  });
11095
- return { removed: removedCount, filePaths: removedFilePaths, gcOrphanEmbeddings, gcOrphanChunks, gcOrphanSymbols, gcOrphanCallEdges };
11284
+ return {
11285
+ removed: removedCount,
11286
+ filePaths: removedStoredFilePaths.map((filePath) => this.resolveStoredFilePath(filePath)),
11287
+ gcOrphanEmbeddings,
11288
+ gcOrphanChunks,
11289
+ gcOrphanSymbols,
11290
+ gcOrphanCallEdges
11291
+ };
11096
11292
  }
11097
11293
  async retryFailedBatches() {
11098
11294
  return this.withIndexMutationLease("retry-failed-batches", async (recoveredOwners) => {
@@ -11287,9 +11483,18 @@ var Indexer = class _Indexer {
11287
11483
  return this.baseBranch;
11288
11484
  }
11289
11485
  refreshBranchInfo() {
11486
+ const previousBranch = this.currentBranch;
11290
11487
  if (isGitRepo(this.materializedProjectRoot)) {
11291
11488
  this.currentBranch = this.branchNameOverride ?? getBranchOrDefault(this.materializedProjectRoot);
11292
11489
  this.baseBranch = getBaseBranch(this.materializedProjectRoot);
11490
+ } else {
11491
+ this.currentBranch = "default";
11492
+ this.baseBranch = "default";
11493
+ }
11494
+ if (this.currentBranch !== previousBranch) {
11495
+ this.refreshRuntimeArtifactPaths();
11496
+ this.fileHashCache.clear();
11497
+ this.loadFileHashCache();
11293
11498
  }
11294
11499
  }
11295
11500
  async getDatabaseStats() {
@@ -11314,6 +11519,7 @@ var Indexer = class _Indexer {
11314
11519
  return [];
11315
11520
  }
11316
11521
  const filterByBranch = options?.filterByBranch ?? true;
11522
+ const excludedStoredFile = options?.excludeFile ? this.toStoredFilePath(options.excludeFile) : void 0;
11317
11523
  this.logger.search("debug", "Starting find similar", {
11318
11524
  codeLength: code.length,
11319
11525
  limit,
@@ -11323,31 +11529,29 @@ var Indexer = class _Indexer {
11323
11529
  const { embedding, tokensUsed } = await provider.embedDocument(code);
11324
11530
  const embeddingMs = import_perf_hooks.performance.now() - embeddingStartTime;
11325
11531
  this.logger.recordEmbeddingApiCall(tokensUsed);
11326
- const vectorStartTime = import_perf_hooks.performance.now();
11327
- const semanticResults = store.search(embedding, limit * 2);
11328
- const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
11532
+ const prefilterStartTime = import_perf_hooks.performance.now();
11329
11533
  let branchChunkIds = null;
11330
11534
  if (filterByBranch && (this.config.scope === "global" || this.currentBranch !== "default")) {
11331
11535
  branchChunkIds = new Set(
11332
11536
  this.getBranchCatalogKeys().flatMap((branchKey) => database.getBranchChunkIds(branchKey))
11333
11537
  );
11334
11538
  }
11335
- const prefilterStartTime = import_perf_hooks.performance.now();
11336
- const shouldPrefilterByBranch = branchChunkIds !== null && (this.config.scope === "global" || branchChunkIds.size > 0);
11337
- const allowBranchPrefilterFallback = this.config.scope !== "global";
11338
- const prefilteredSemantic = shouldPrefilterByBranch && branchChunkIds ? semanticResults.filter((r) => branchChunkIds.has(r.id)) : semanticResults;
11339
- const semanticCandidates = allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0 ? semanticResults : prefilteredSemantic;
11539
+ const { hasInitializedBranchCatalog, shouldPrefilterByBranch } = this.getBranchPrefilterState(database, branchChunkIds);
11340
11540
  const prefilterMs = import_perf_hooks.performance.now() - prefilterStartTime;
11341
- if (this.config.scope !== "global" && branchChunkIds && branchChunkIds.size === 0) {
11541
+ const vectorStartTime = import_perf_hooks.performance.now();
11542
+ const semanticCandidates = this.searchSemanticCandidates(
11543
+ store,
11544
+ embedding,
11545
+ limit * 2,
11546
+ branchChunkIds,
11547
+ shouldPrefilterByBranch
11548
+ );
11549
+ const vectorMs = import_perf_hooks.performance.now() - vectorStartTime;
11550
+ if (this.config.scope !== "global" && branchChunkIds && !hasInitializedBranchCatalog) {
11342
11551
  this.logger.search("warn", "Branch prefilter skipped because branch catalog is empty", {
11343
11552
  branch: this.currentBranch
11344
11553
  });
11345
11554
  }
11346
- if (allowBranchPrefilterFallback && shouldPrefilterByBranch && semanticResults.length > 0 && prefilteredSemantic.length === 0) {
11347
- this.logger.search("warn", "Branch prefilter produced no semantic overlap, using unfiltered semantic candidates", {
11348
- branch: this.currentBranch
11349
- });
11350
- }
11351
11555
  const rerankTopN = this.config.search.rerankTopN;
11352
11556
  const ranked = rankSemanticOnlyResults(code, semanticCandidates, {
11353
11557
  rerankTopN,
@@ -11356,21 +11560,10 @@ var Indexer = class _Indexer {
11356
11560
  });
11357
11561
  const filtered = ranked.filter((r) => {
11358
11562
  if (r.score < this.config.search.minScore) return false;
11359
- if (options?.excludeFile) {
11360
- if (r.metadata.filePath === options.excludeFile) return false;
11563
+ if (excludedStoredFile) {
11564
+ if (r.metadata.filePath === excludedStoredFile) return false;
11361
11565
  }
11362
- if (options?.fileType) {
11363
- const ext = r.metadata.filePath.split(".").pop()?.toLowerCase();
11364
- if (ext !== options.fileType.toLowerCase().replace(/^\./, "")) return false;
11365
- }
11366
- if (options?.directory) {
11367
- const normalizedDir = options.directory.replace(/^\/|\/$/g, "");
11368
- if (!r.metadata.filePath.includes(`/${normalizedDir}/`) && !r.metadata.filePath.includes(`${normalizedDir}/`)) return false;
11369
- }
11370
- if (options?.chunkType) {
11371
- if (r.metadata.chunkType !== options.chunkType) return false;
11372
- }
11373
- return true;
11566
+ return matchesHardSearchFilters(r, options, this.projectRoot);
11374
11567
  }).slice(0, limit);
11375
11568
  const totalSearchMs = import_perf_hooks.performance.now() - searchStartTime;
11376
11569
  this.logger.recordSearch(totalSearchMs, {
@@ -11390,10 +11583,11 @@ var Indexer = class _Indexer {
11390
11583
  return Promise.all(
11391
11584
  filtered.map(async (r) => {
11392
11585
  let content = "";
11586
+ const resolvedFilePath = this.resolveStoredFilePath(r.metadata.filePath);
11393
11587
  if (this.config.search.includeContext) {
11394
11588
  try {
11395
11589
  const fileContent = await import_fs10.promises.readFile(
11396
- r.metadata.filePath,
11590
+ resolvedFilePath,
11397
11591
  "utf-8"
11398
11592
  );
11399
11593
  const lines = fileContent.split("\n");
@@ -11403,7 +11597,7 @@ var Indexer = class _Indexer {
11403
11597
  }
11404
11598
  }
11405
11599
  return {
11406
- filePath: r.metadata.filePath,
11600
+ filePath: resolvedFilePath,
11407
11601
  startLine: r.metadata.startLine,
11408
11602
  endLine: r.metadata.endLine,
11409
11603
  content,
@@ -11424,7 +11618,7 @@ var Indexer = class _Indexer {
11424
11618
  for (const edge of database.getCallersWithContext(targetName, branchKey, callTypeFilter)) {
11425
11619
  if (!seen.has(edge.id)) {
11426
11620
  seen.add(edge.id);
11427
- results.push(edge);
11621
+ results.push(this.resolveCallEdgeFilePath(edge));
11428
11622
  }
11429
11623
  }
11430
11624
  }
@@ -11443,7 +11637,7 @@ var Indexer = class _Indexer {
11443
11637
  const safelyMatchesUnresolvedSymbol = includeUnresolved && !edge.toSymbolId;
11444
11638
  if (!matchesResolvedSymbol && !safelyMatchesUnresolvedSymbol || seen.has(edge.id)) continue;
11445
11639
  seen.add(edge.id);
11446
- results.push(edge);
11640
+ results.push(this.resolveCallEdgeFilePath(edge));
11447
11641
  }
11448
11642
  }
11449
11643
  return results;
@@ -11457,7 +11651,7 @@ var Indexer = class _Indexer {
11457
11651
  for (const edge of database.getCallees(symbolId, branchKey, callTypeFilter)) {
11458
11652
  if (!seen.has(edge.id)) {
11459
11653
  seen.add(edge.id);
11460
- results.push(edge);
11654
+ results.push(this.resolveCallEdgeFilePath(edge));
11461
11655
  }
11462
11656
  }
11463
11657
  }
@@ -11473,7 +11667,7 @@ var Indexer = class _Indexer {
11473
11667
  shortest = path30;
11474
11668
  }
11475
11669
  }
11476
- return shortest;
11670
+ return shortest.map((hop) => this.resolveFilePathRecord(hop));
11477
11671
  }
11478
11672
  async findCallPathBySymbolIds(fromSymbolId, toSymbolId, maxDepth = 10) {
11479
11673
  const { database, readIssues } = await this.ensureInitialized();
@@ -11539,7 +11733,7 @@ var Indexer = class _Indexer {
11539
11733
  shortest = path30;
11540
11734
  }
11541
11735
  }
11542
- return shortest;
11736
+ return shortest.map((hop) => this.resolveFilePathRecord(hop));
11543
11737
  }
11544
11738
  async getCallGraphSymbols() {
11545
11739
  const { database, readIssues } = await this.ensureInitialized();
@@ -11547,7 +11741,7 @@ var Indexer = class _Indexer {
11547
11741
  const symbols = /* @__PURE__ */ new Map();
11548
11742
  for (const branchKey of this.getBranchCatalogKeys()) {
11549
11743
  for (const symbol of database.getSymbolsForBranch(branchKey)) {
11550
- symbols.set(symbol.id, symbol);
11744
+ symbols.set(symbol.id, this.resolveFilePathRecord(symbol));
11551
11745
  }
11552
11746
  }
11553
11747
  return [...symbols.values()];
@@ -11556,31 +11750,32 @@ var Indexer = class _Indexer {
11556
11750
  const { database, readIssues } = await this.ensureInitialized();
11557
11751
  this.requireReadableComponents(readIssues, "database");
11558
11752
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11559
- return database.getSymbolsForBranch(resolvedBranch);
11753
+ return database.getSymbolsForBranch(resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
11560
11754
  }
11561
11755
  async getSymbolsForFiles(filePaths, branch) {
11562
11756
  const { database, readIssues } = await this.ensureInitialized();
11563
11757
  this.requireReadableComponents(readIssues, "database");
11564
11758
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11565
- return database.getSymbolsForFiles(filePaths, resolvedBranch);
11759
+ const storedFilePaths = filePaths.map((filePath) => this.toStoredFilePath(filePath));
11760
+ return database.getSymbolsForFiles(storedFilePaths, resolvedBranch).map((symbol) => this.resolveFilePathRecord(symbol));
11566
11761
  }
11567
11762
  async getTransitiveReachability(rootSymbolIds, direction, maxDepth) {
11568
11763
  const { database, readIssues } = await this.ensureInitialized();
11569
11764
  this.requireReadableComponents(readIssues, "database");
11570
11765
  const branch = this.getBranchCatalogKey();
11571
- return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth);
11766
+ return database.getTransitiveReachability(rootSymbolIds, branch, direction, maxDepth).map((entry) => this.resolveFilePathRecord(entry));
11572
11767
  }
11573
11768
  async detectCommunities(branch, symbolIds) {
11574
11769
  const { database, readIssues } = await this.ensureInitialized();
11575
11770
  this.requireReadableComponents(readIssues, "database");
11576
11771
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11577
- return database.detectCommunities(resolvedBranch, symbolIds);
11772
+ return database.detectCommunities(resolvedBranch, symbolIds).map((entry) => this.resolveFilePathRecord(entry));
11578
11773
  }
11579
11774
  async computeCentrality(branch) {
11580
11775
  const { database, readIssues } = await this.ensureInitialized();
11581
11776
  this.requireReadableComponents(readIssues, "database");
11582
11777
  const resolvedBranch = branch ?? this.getBranchCatalogKey();
11583
- return database.computeCentrality(resolvedBranch);
11778
+ return database.computeCentrality(resolvedBranch).map((entry) => this.resolveFilePathRecord(entry));
11584
11779
  }
11585
11780
  async getPrImpact(opts, onPreparationProgress) {
11586
11781
  const initialState = await this.ensureInitialized();
@@ -11616,10 +11811,8 @@ var Indexer = class _Indexer {
11616
11811
  const requestedRef = opts.pr !== void 0 ? expectedCommit : headRefName ?? resolvedBranch;
11617
11812
  const storedCommit = this.getStoredBranchCommit(database, catalogIdentity);
11618
11813
  const catalogIdentityMatches = storedCommit === expectedCommit;
11619
- const symbolsCurrent = database.getMetadata(
11620
- this.getSymbolExtractorVersionMetadataKey(catalogIdentity)
11621
- ) === SYMBOL_EXTRACTOR_VERSION;
11622
- if (branchSymbols.length === 0 || !catalogIdentityMatches || !symbolsCurrent) {
11814
+ const migrationsCurrent = this.areBranchMigrationVersionsCurrent(database, catalogIdentity);
11815
+ if (branchSymbols.length === 0 || !catalogIdentityMatches || !migrationsCurrent) {
11623
11816
  if (!resolvedBranch || resolvedBranch === "default") {
11624
11817
  throw new Error("Run index_codebase first to build the call graph and symbol index for this project.");
11625
11818
  }
@@ -11675,8 +11868,9 @@ var Indexer = class _Indexer {
11675
11868
  );
11676
11869
  }
11677
11870
  }
11678
- const absoluteChangedFiles = changedFiles.map((f) => path14.resolve(this.projectRoot, f));
11679
- const directSymbols = database.getSymbolsForFiles(absoluteChangedFiles, branchKey);
11871
+ const toStoredChangedFiles = (filePaths) => filePaths.map((filePath) => this.toStoredFilePath(path14.resolve(this.projectRoot, filePath)));
11872
+ const storedChangedFiles = toStoredChangedFiles(changedFiles);
11873
+ const directSymbols = database.getSymbolsForFiles(storedChangedFiles, branchKey);
11680
11874
  const directIds = directSymbols.map((s) => s.id);
11681
11875
  const direction = opts.direction ?? "both";
11682
11876
  const maxDepth = opts.maxDepth ?? 5;
@@ -11718,7 +11912,7 @@ var Indexer = class _Indexer {
11718
11912
  id: c.symbolId,
11719
11913
  name: c.symbolName,
11720
11914
  callerCount: c.callerCount,
11721
- filePath: c.filePath
11915
+ filePath: this.resolveStoredFilePath(c.filePath)
11722
11916
  }));
11723
11917
  const totalAffected = allAffectedIds.length;
11724
11918
  let riskLevel;
@@ -11758,9 +11952,9 @@ var Indexer = class _Indexer {
11758
11952
  projectRoot: this.projectRoot,
11759
11953
  baseBranch: this.baseBranch
11760
11954
  });
11761
- const otherAbsolute = otherChanged.files.map((f) => path14.resolve(this.projectRoot, f));
11955
+ const otherStored = toStoredChangedFiles(otherChanged.files);
11762
11956
  const prBranchKey = this.getBranchCatalogKeyFor(otherChanged.catalogIdentity);
11763
- const otherSymbols = database.getSymbolsForFiles(otherAbsolute, prBranchKey);
11957
+ const otherSymbols = database.getSymbolsForFiles(otherStored, prBranchKey);
11764
11958
  const otherLabels = /* @__PURE__ */ new Set();
11765
11959
  for (const sym of otherSymbols) {
11766
11960
  const label = symbolToCommunity.get(structuralKey(sym.filePath, sym.name));
@@ -11791,12 +11985,12 @@ var Indexer = class _Indexer {
11791
11985
  id: s.id,
11792
11986
  name: s.name,
11793
11987
  kind: s.kind,
11794
- filePath: s.filePath
11988
+ filePath: this.resolveStoredFilePath(s.filePath)
11795
11989
  })),
11796
11990
  transitiveCallers: transitiveCallers.map((c) => ({
11797
11991
  id: c.symbolId,
11798
11992
  name: c.symbolName,
11799
- filePath: c.filePath,
11993
+ filePath: this.resolveStoredFilePath(c.filePath),
11800
11994
  depth: c.depth
11801
11995
  })),
11802
11996
  totalAffected,
@@ -11826,7 +12020,7 @@ var Indexer = class _Indexer {
11826
12020
  const absoluteDirectoryFilter = directory ? path14.resolve(this.projectRoot, directory) : void 0;
11827
12021
  for (const filePath of filePaths) {
11828
12022
  if (directory) {
11829
- const absoluteFilePath = path14.resolve(filePath);
12023
+ const absoluteFilePath = this.resolveStoredFilePath(filePath);
11830
12024
  const matchesRelative = filePath === directory || filePath.startsWith(directory + "/");
11831
12025
  const matchesProjectRelative = absoluteDirectoryFilter !== void 0 && (absoluteFilePath === absoluteDirectoryFilter || absoluteFilePath.startsWith(absoluteDirectoryFilter + path14.sep));
11832
12026
  if (!matchesRelative && !matchesProjectRelative) {
@@ -11835,14 +12029,14 @@ var Indexer = class _Indexer {
11835
12029
  }
11836
12030
  for (const sym of database.getSymbolsByFile(filePath)) {
11837
12031
  if (symbolIdSet.has(sym.id) && !seenSymbols.has(sym.id)) {
11838
- seenSymbols.set(sym.id, sym);
12032
+ seenSymbols.set(sym.id, this.resolveFilePathRecord(sym));
11839
12033
  }
11840
12034
  }
11841
12035
  }
11842
12036
  for (const symbolId of seenSymbols.keys()) {
11843
12037
  for (const edge of database.getCallees(symbolId, branchKey)) {
11844
12038
  if (!seenEdges.has(edge.id)) {
11845
- seenEdges.set(edge.id, edge);
12039
+ seenEdges.set(edge.id, this.resolveCallEdgeFilePath(edge));
11846
12040
  }
11847
12041
  }
11848
12042
  }
@@ -12114,11 +12308,11 @@ function formatIndexStats(stats, verbose = false) {
12114
12308
  } else if (stats.indexedChunks === 0) {
12115
12309
  lines.push(`${stats.totalFiles} files, removed ${stats.removedChunks} stale chunks, ${stats.existingChunks} chunks remain.`);
12116
12310
  } else {
12117
- let main2 = `${stats.totalFiles} files processed, ${stats.indexedChunks} new chunks embedded.`;
12311
+ let main = `${stats.totalFiles} files processed, ${stats.indexedChunks} new chunks embedded.`;
12118
12312
  if (stats.existingChunks > 0) {
12119
- main2 += ` ${stats.existingChunks} unchanged chunks skipped.`;
12313
+ main += ` ${stats.existingChunks} unchanged chunks skipped.`;
12120
12314
  }
12121
- lines.push(main2);
12315
+ lines.push(main);
12122
12316
  if (stats.removedChunks > 0) {
12123
12317
  lines.push(`Removed ${stats.removedChunks} stale chunks.`);
12124
12318
  }
@@ -13292,10 +13486,10 @@ function configureAutoIndex(projectRoot, host, config, getIndexer) {
13292
13486
  }
13293
13487
  coordinatorKeysByProject.set(projectKey, key);
13294
13488
  }
13295
- function startAutoIndex(projectRoot, host = "opencode", source = "startup") {
13489
+ function startAutoIndex(projectRoot, host, source = "startup") {
13296
13490
  return getCoordinator(projectRoot, host)?.start(source) ?? null;
13297
13491
  }
13298
- function requestBackgroundIndex(projectRoot, host = "opencode") {
13492
+ function requestBackgroundIndex(projectRoot, host) {
13299
13493
  return getCoordinator(projectRoot, host)?.request({
13300
13494
  checkFreshness: false,
13301
13495
  force: false,
@@ -13310,14 +13504,14 @@ function runCoordinatedIndex(projectRoot, host, force, onProgress) {
13310
13504
  source: "manual"
13311
13505
  }) ?? null;
13312
13506
  }
13313
- function getAutoIndexStatus(projectRoot, host = "opencode") {
13507
+ function getAutoIndexStatus(projectRoot, host) {
13314
13508
  return getCoordinator(projectRoot, host)?.snapshot() ?? {
13315
13509
  enabled: false,
13316
13510
  state: "idle",
13317
13511
  updatedAt: now()
13318
13512
  };
13319
13513
  }
13320
- async function waitForAutoIndexForRetrieval(projectRoot, host = "opencode") {
13514
+ async function waitForAutoIndexForRetrieval(projectRoot, host) {
13321
13515
  const coordinator = getCoordinator(projectRoot, host);
13322
13516
  if (!coordinator) return { ready: true };
13323
13517
  const initial = coordinator.snapshot();
@@ -13364,7 +13558,7 @@ async function waitForAutoIndexForRetrieval(projectRoot, host = "opencode") {
13364
13558
  text: `Automatic indexing is ${status.state}. Retry shortly or call index_status for progress. You can also run index_codebase explicitly.`
13365
13559
  };
13366
13560
  }
13367
- async function stopAutoIndex(projectRoot, host = "opencode") {
13561
+ async function stopAutoIndex(projectRoot, host) {
13368
13562
  await getCoordinator(projectRoot, host)?.stop();
13369
13563
  }
13370
13564
  async function hasReadableCurrentIndex(coordinator) {
@@ -13511,7 +13705,7 @@ function loadJsonFile(filePath) {
13511
13705
  function loadConfigFile(filePath) {
13512
13706
  return loadJsonFile(filePath);
13513
13707
  }
13514
- function loadProjectConfigLayer(projectRoot, host = "opencode") {
13708
+ function loadProjectConfigLayer(projectRoot, host) {
13515
13709
  const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
13516
13710
  const projectConfig = loadJsonFile(projectConfigPath);
13517
13711
  if (!projectConfig) {
@@ -13528,7 +13722,7 @@ function loadProjectConfigLayer(projectRoot, host = "opencode") {
13528
13722
  }
13529
13723
  return normalizedConfig;
13530
13724
  }
13531
- function loadMergedConfig(projectRoot, host = "opencode") {
13725
+ function loadMergedConfig(projectRoot, host) {
13532
13726
  const globalConfigPath = resolveGlobalConfigPath(host);
13533
13727
  const projectConfigPath = resolveProjectConfigPath(projectRoot, host);
13534
13728
  let globalConfig = null;
@@ -13597,7 +13791,7 @@ function toConfigRecord(rawConfig) {
13597
13791
  }
13598
13792
  return { ...rawConfig };
13599
13793
  }
13600
- function loadRuntimeConfig(projectRoot, host = "opencode") {
13794
+ function loadRuntimeConfig(projectRoot, host) {
13601
13795
  return normalizeKnowledgeBasePaths(toConfigRecord(loadMergedConfig(projectRoot, host)), projectRoot);
13602
13796
  }
13603
13797
 
@@ -13680,14 +13874,14 @@ function getOrCreateIndexer(projectRoot, host) {
13680
13874
  configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
13681
13875
  return indexer;
13682
13876
  }
13683
- function initializeTools(projectRoot, config, host = "opencode") {
13877
+ function initializeTools(projectRoot, config, host) {
13684
13878
  defaultProjectRoots.set(host, projectRoot);
13685
13879
  const key = getIndexerCacheKey(projectRoot, host);
13686
13880
  configCache.set(key, config);
13687
13881
  indexerCache.set(key, new Indexer(projectRoot, config, host));
13688
13882
  configureAutoIndex(projectRoot, host, config, () => getOrCreateIndexer(projectRoot, host));
13689
13883
  }
13690
- function getIndexerForProject(projectRoot, host = "opencode") {
13884
+ function getIndexerForProject(projectRoot, host) {
13691
13885
  const root = getProjectRoot(projectRoot, host);
13692
13886
  return getOrCreateIndexer(root, host);
13693
13887
  }
@@ -13695,7 +13889,7 @@ function recordToolEffectiveness(projectRoot, host, event) {
13695
13889
  if (!isToolEffectivenessEnabled(projectRoot, host)) return;
13696
13890
  safelyRecordToolEffectiveness(event);
13697
13891
  }
13698
- function refreshIndexerForDirectory(projectRoot, host = "opencode", config = parseConfig(loadRuntimeConfig(projectRoot, host))) {
13892
+ function refreshIndexerForDirectory(projectRoot, host, config = parseConfig(loadRuntimeConfig(projectRoot, host))) {
13699
13893
  const key = getIndexerCacheKey(projectRoot, host);
13700
13894
  configCache.set(key, config);
13701
13895
  indexerCache.set(key, new Indexer(projectRoot, config, host));
@@ -15229,7 +15423,7 @@ function loadRawConfig(projectRoot, configPath) {
15229
15423
  fromPath
15230
15424
  );
15231
15425
  }
15232
- const projectConfig = resolveProjectConfigPath(projectRoot);
15426
+ const projectConfig = resolveProjectConfigPath(projectRoot, "opencode");
15233
15427
  if ((0, import_fs15.existsSync)(projectConfig)) {
15234
15428
  return normalizeEvalConfigKnowledgeBases(
15235
15429
  parseJsonConfigFile(projectConfig),
@@ -15244,7 +15438,7 @@ function loadRawConfig(projectRoot, configPath) {
15244
15438
  return {};
15245
15439
  }
15246
15440
  function getIndexRootPath(projectRoot, scope) {
15247
- return scope === "global" ? getGlobalIndexPath() : resolveProjectIndexPath(projectRoot, scope);
15441
+ return scope === "global" ? getGlobalIndexPath("opencode") : resolveProjectIndexPath(projectRoot, scope, "opencode");
15248
15442
  }
15249
15443
  function getLocalProjectIndexRoot(projectRoot) {
15250
15444
  return path21.join(projectRoot, ".opencode", "index");
@@ -15260,7 +15454,7 @@ function clearIndexRoot(projectRoot, scope) {
15260
15454
  }
15261
15455
  function ensureLocalEvalProjectConfig(projectRoot, configPath) {
15262
15456
  const localConfigPath = getLocalProjectConfigPath(projectRoot);
15263
- const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot);
15457
+ const resolvedConfigPath = configPath ? toAbsolute(projectRoot, configPath) : resolveProjectConfigPath(projectRoot, "opencode");
15264
15458
  if (!configPath && (0, import_fs15.existsSync)(localConfigPath)) {
15265
15459
  return localConfigPath;
15266
15460
  }
@@ -15695,7 +15889,7 @@ async function runEvaluation(options) {
15695
15889
  if (options.reindex) {
15696
15890
  clearIndexRoot(options.projectRoot, effectiveConfig.scope);
15697
15891
  }
15698
- const indexer = new Indexer(options.projectRoot, effectiveConfig);
15892
+ const indexer = new Indexer(options.projectRoot, effectiveConfig, "opencode");
15699
15893
  try {
15700
15894
  await indexer.index();
15701
15895
  const perQuery = [];
@@ -15898,9 +16092,9 @@ var path23 = __toESM(require("path"), 1);
15898
16092
  function printUsage() {
15899
16093
  console.log(`
15900
16094
  Usage:
15901
- opencode-codebase-index-mcp eval run [options]
15902
- opencode-codebase-index-mcp eval compare --against <summary.json> [options]
15903
- opencode-codebase-index-mcp eval diff --current <summary.json> --against <summary.json> [options]
16095
+ ${MCP_BINARY_CURRENT_NAME} eval run [options]
16096
+ ${MCP_BINARY_CURRENT_NAME} eval compare --against <summary.json> [options]
16097
+ ${MCP_BINARY_CURRENT_NAME} eval diff --current <summary.json> --against <summary.json> [options]
15904
16098
 
15905
16099
  Options:
15906
16100
  --project <path> Project root (default: cwd)
@@ -16189,11 +16383,21 @@ async function handleEvalCommand(args, cwd) {
16189
16383
  throw new Error(`Unknown eval subcommand: ${subcommand}`);
16190
16384
  }
16191
16385
 
16192
- // src/mcp-server.ts
16386
+ // src/adapters/mcp/server.ts
16193
16387
  var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
16388
+
16389
+ // src/package-metadata.ts
16194
16390
  var import_fs18 = require("fs");
16391
+ var import_meta2 = {};
16392
+ function getPackageVersion() {
16393
+ const raw = JSON.parse((0, import_fs18.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
16394
+ if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
16395
+ return raw.version;
16396
+ }
16397
+ return "0.0.0";
16398
+ }
16195
16399
 
16196
- // src/mcp-server/register-prompts.ts
16400
+ // src/adapters/mcp/register-prompts.ts
16197
16401
  var import_zod = require("zod");
16198
16402
  function registerMcpPrompts(server) {
16199
16403
  server.prompt(
@@ -16283,7 +16487,7 @@ Use the implementation_lookup tool to find where this symbol is defined. This pr
16283
16487
  );
16284
16488
  }
16285
16489
 
16286
- // src/mcp-server/register-tools.ts
16490
+ // src/adapters/mcp/register-tools.ts
16287
16491
  var import_zod2 = require("zod");
16288
16492
 
16289
16493
  // src/tools/contracts.ts
@@ -16423,13 +16627,89 @@ function formatPrImpact(result) {
16423
16627
  return lines.join("\n");
16424
16628
  }
16425
16629
 
16426
- // src/mcp-server/register-tools.ts
16630
+ // src/tools/tool-names.ts
16631
+ var TOOL_NAME = {
16632
+ CODEBASE_CONTEXT: "codebase_context",
16633
+ CODEBASE_SEARCH: "codebase_search",
16634
+ CODEBASE_PEEK: "codebase_peek",
16635
+ FIND_SIMILAR: "find_similar",
16636
+ IMPLEMENTATION_LOOKUP: "implementation_lookup",
16637
+ INDEX_CODEBASE: "index_codebase",
16638
+ INDEX_STATUS: "index_status",
16639
+ INDEX_HEALTH_CHECK: "index_health_check",
16640
+ INDEX_METRICS: "index_metrics",
16641
+ INDEX_LOGS: "index_logs",
16642
+ CALL_GRAPH: "call_graph",
16643
+ CALL_GRAPH_PATH: "call_graph_path",
16644
+ PR_IMPACT: "pr_impact",
16645
+ ADD_KNOWLEDGE_BASE: "add_knowledge_base",
16646
+ LIST_KNOWLEDGE_BASES: "list_knowledge_bases",
16647
+ REMOVE_KNOWLEDGE_BASE: "remove_knowledge_base",
16648
+ PI_KNOWLEDGE_BASE_ADD: "knowledge_base_add",
16649
+ PI_KNOWLEDGE_BASE_LIST: "knowledge_base_list",
16650
+ PI_KNOWLEDGE_BASE_REMOVE: "knowledge_base_remove",
16651
+ INDEX_VISUALIZE: "index_visualize"
16652
+ };
16653
+ var PORTABLE_TOOL_NAMES = [
16654
+ TOOL_NAME.CODEBASE_CONTEXT,
16655
+ TOOL_NAME.CODEBASE_SEARCH,
16656
+ TOOL_NAME.CODEBASE_PEEK,
16657
+ TOOL_NAME.INDEX_CODEBASE,
16658
+ TOOL_NAME.INDEX_STATUS,
16659
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16660
+ TOOL_NAME.INDEX_METRICS,
16661
+ TOOL_NAME.INDEX_LOGS,
16662
+ TOOL_NAME.FIND_SIMILAR,
16663
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16664
+ TOOL_NAME.CALL_GRAPH,
16665
+ TOOL_NAME.CALL_GRAPH_PATH,
16666
+ TOOL_NAME.PR_IMPACT
16667
+ ];
16668
+ var OPENCODE_TOOL_NAMES = [
16669
+ TOOL_NAME.CODEBASE_CONTEXT,
16670
+ TOOL_NAME.CODEBASE_SEARCH,
16671
+ TOOL_NAME.CODEBASE_PEEK,
16672
+ TOOL_NAME.INDEX_CODEBASE,
16673
+ TOOL_NAME.INDEX_STATUS,
16674
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16675
+ TOOL_NAME.INDEX_METRICS,
16676
+ TOOL_NAME.INDEX_LOGS,
16677
+ TOOL_NAME.FIND_SIMILAR,
16678
+ TOOL_NAME.CALL_GRAPH,
16679
+ TOOL_NAME.CALL_GRAPH_PATH,
16680
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16681
+ TOOL_NAME.ADD_KNOWLEDGE_BASE,
16682
+ TOOL_NAME.LIST_KNOWLEDGE_BASES,
16683
+ TOOL_NAME.REMOVE_KNOWLEDGE_BASE,
16684
+ TOOL_NAME.PR_IMPACT,
16685
+ TOOL_NAME.INDEX_VISUALIZE
16686
+ ];
16687
+ var PI_TOOL_NAMES = [
16688
+ TOOL_NAME.CODEBASE_CONTEXT,
16689
+ TOOL_NAME.CODEBASE_SEARCH,
16690
+ TOOL_NAME.CODEBASE_PEEK,
16691
+ TOOL_NAME.FIND_SIMILAR,
16692
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16693
+ TOOL_NAME.INDEX_CODEBASE,
16694
+ TOOL_NAME.INDEX_STATUS,
16695
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16696
+ TOOL_NAME.INDEX_METRICS,
16697
+ TOOL_NAME.INDEX_LOGS,
16698
+ TOOL_NAME.CALL_GRAPH,
16699
+ TOOL_NAME.CALL_GRAPH_PATH,
16700
+ TOOL_NAME.PR_IMPACT,
16701
+ TOOL_NAME.PI_KNOWLEDGE_BASE_LIST,
16702
+ TOOL_NAME.PI_KNOWLEDGE_BASE_ADD,
16703
+ TOOL_NAME.PI_KNOWLEDGE_BASE_REMOVE
16704
+ ];
16705
+
16706
+ // src/adapters/mcp/register-tools.ts
16427
16707
  function allowNullAsUndefined(schema) {
16428
16708
  return import_zod2.z.preprocess((value) => value === null ? void 0 : value, schema);
16429
16709
  }
16430
16710
  function registerMcpTools(server, runtime) {
16431
16711
  server.tool(
16432
- "codebase_context",
16712
+ TOOL_NAME.CODEBASE_CONTEXT,
16433
16713
  "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.",
16434
16714
  {
16435
16715
  query: import_zod2.z.string().describe("The codebase question or behavior to locate. Always provide the user's repository question here."),
@@ -16456,7 +16736,7 @@ function registerMcpTools(server, runtime) {
16456
16736
  }
16457
16737
  );
16458
16738
  server.tool(
16459
- "codebase_search",
16739
+ TOOL_NAME.CODEBASE_SEARCH,
16460
16740
  "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.",
16461
16741
  {
16462
16742
  query: import_zod2.z.string().describe("Natural language description of what code you're looking for. Describe behavior, not syntax."),
@@ -16488,7 +16768,7 @@ ${formatSearchResults(results, "score")}`;
16488
16768
  }
16489
16769
  );
16490
16770
  server.tool(
16491
- "codebase_peek",
16771
+ TOOL_NAME.CODEBASE_PEEK,
16492
16772
  "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.",
16493
16773
  {
16494
16774
  query: import_zod2.z.string().describe("Natural language description of what code you're looking for."),
@@ -16519,7 +16799,7 @@ ${formatCodebasePeek(results)}`;
16519
16799
  }
16520
16800
  );
16521
16801
  server.tool(
16522
- "index_codebase",
16802
+ TOOL_NAME.INDEX_CODEBASE,
16523
16803
  "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.",
16524
16804
  {
16525
16805
  force: allowNullAsUndefined(import_zod2.z.boolean().optional().default(false)).describe("Force reindex even if already indexed"),
@@ -16532,7 +16812,7 @@ ${formatCodebasePeek(results)}`;
16532
16812
  }
16533
16813
  );
16534
16814
  server.tool(
16535
- "index_status",
16815
+ TOOL_NAME.INDEX_STATUS,
16536
16816
  "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.",
16537
16817
  {},
16538
16818
  async () => {
@@ -16541,7 +16821,7 @@ ${formatCodebasePeek(results)}`;
16541
16821
  }
16542
16822
  );
16543
16823
  server.tool(
16544
- "index_health_check",
16824
+ TOOL_NAME.INDEX_HEALTH_CHECK,
16545
16825
  "Check index health and remove stale entries from deleted files. Run this to clean up the index after files have been deleted.",
16546
16826
  {},
16547
16827
  async () => {
@@ -16550,7 +16830,7 @@ ${formatCodebasePeek(results)}`;
16550
16830
  }
16551
16831
  );
16552
16832
  server.tool(
16553
- "index_metrics",
16833
+ TOOL_NAME.INDEX_METRICS,
16554
16834
  "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.",
16555
16835
  {
16556
16836
  reset: import_zod2.z.boolean().optional().default(false).describe("Reset in-memory operational and effectiveness metrics before returning the snapshot")
@@ -16561,7 +16841,7 @@ ${formatCodebasePeek(results)}`;
16561
16841
  }
16562
16842
  );
16563
16843
  server.tool(
16564
- "index_logs",
16844
+ TOOL_NAME.INDEX_LOGS,
16565
16845
  "Get recent debug logs from the codebase indexer. Requires debug.enabled=true in config.",
16566
16846
  {
16567
16847
  limit: allowNullAsUndefined(import_zod2.z.number().optional().default(20)).describe("Maximum number of log entries to return"),
@@ -16578,7 +16858,7 @@ ${formatCodebasePeek(results)}`;
16578
16858
  }
16579
16859
  );
16580
16860
  server.tool(
16581
- "find_similar",
16861
+ TOOL_NAME.FIND_SIMILAR,
16582
16862
  "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.",
16583
16863
  {
16584
16864
  code: import_zod2.z.string().describe("The code snippet to find similar code for"),
@@ -16605,7 +16885,7 @@ ${formatSearchResults(results)}` }] };
16605
16885
  }
16606
16886
  );
16607
16887
  server.tool(
16608
- "implementation_lookup",
16888
+ TOOL_NAME.IMPLEMENTATION_LOOKUP,
16609
16889
  "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.",
16610
16890
  {
16611
16891
  query: import_zod2.z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
@@ -16619,7 +16899,7 @@ ${formatSearchResults(results)}` }] };
16619
16899
  }
16620
16900
  );
16621
16901
  server.tool(
16622
- "call_graph",
16902
+ TOOL_NAME.CALL_GRAPH,
16623
16903
  "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.",
16624
16904
  {
16625
16905
  name: import_zod2.z.string().describe("Function or method name to query"),
@@ -16638,7 +16918,7 @@ ${formatSearchResults(results)}` }] };
16638
16918
  }
16639
16919
  );
16640
16920
  server.tool(
16641
- "call_graph_path",
16921
+ TOOL_NAME.CALL_GRAPH_PATH,
16642
16922
  "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.",
16643
16923
  {
16644
16924
  from: import_zod2.z.string().describe("Source function/method name (starting point)"),
@@ -16653,7 +16933,7 @@ ${formatSearchResults(results)}` }] };
16653
16933
  }
16654
16934
  );
16655
16935
  server.tool(
16656
- "pr_impact",
16936
+ TOOL_NAME.PR_IMPACT,
16657
16937
  "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.",
16658
16938
  {
16659
16939
  pr: allowNullAsUndefined(import_zod2.z.number().optional()).describe("Pull request number to analyze"),
@@ -16684,22 +16964,14 @@ ${formatSearchResults(results)}` }] };
16684
16964
  );
16685
16965
  }
16686
16966
 
16687
- // src/mcp-server.ts
16688
- var import_meta2 = {};
16967
+ // src/adapters/mcp/server.ts
16689
16968
  function getServerInstructions(host) {
16690
16969
  const hostText = `host ${host}`;
16691
16970
  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.`;
16692
16971
  }
16693
- function getPackageVersion() {
16694
- const raw = JSON.parse((0, import_fs18.readFileSync)(new URL("../package.json", import_meta2.url), "utf-8"));
16695
- if (raw && typeof raw === "object" && "version" in raw && typeof raw.version === "string") {
16696
- return raw.version;
16697
- }
16698
- return "0.0.0";
16699
- }
16700
- function createMcpServer(projectRoot, config, host = "opencode") {
16972
+ function createMcpServer(projectRoot, config, host) {
16701
16973
  const server = new import_mcp.McpServer({
16702
- name: "opencode-codebase-index",
16974
+ name: MCP_SERVER_CURRENT_NAME,
16703
16975
  version: getPackageVersion()
16704
16976
  }, {
16705
16977
  instructions: getServerInstructions(host)
@@ -16734,6 +17006,9 @@ function createMcpServer(projectRoot, config, host = "opencode") {
16734
17006
  return server;
16735
17007
  }
16736
17008
 
17009
+ // src/watcher/file-watcher.ts
17010
+ var import_fs19 = require("fs");
17011
+
16737
17012
  // node_modules/chokidar/index.js
16738
17013
  var import_node_events = require("events");
16739
17014
  var import_node_fs2 = require("fs");
@@ -17278,10 +17553,10 @@ var foreach = (val, fn) => {
17278
17553
  fn(val);
17279
17554
  }
17280
17555
  };
17281
- var addAndConvert = (main2, prop, item) => {
17282
- let container = main2[prop];
17556
+ var addAndConvert = (main, prop, item) => {
17557
+ let container = main[prop];
17283
17558
  if (!(container instanceof Set)) {
17284
- main2[prop] = container = /* @__PURE__ */ new Set([container]);
17559
+ main[prop] = container = /* @__PURE__ */ new Set([container]);
17285
17560
  }
17286
17561
  container.add(item);
17287
17562
  };
@@ -17293,12 +17568,12 @@ var clearItem = (cont) => (key) => {
17293
17568
  delete cont[key];
17294
17569
  }
17295
17570
  };
17296
- var delFromSet = (main2, prop, item) => {
17297
- const container = main2[prop];
17571
+ var delFromSet = (main, prop, item) => {
17572
+ const container = main[prop];
17298
17573
  if (container instanceof Set) {
17299
17574
  container.delete(item);
17300
17575
  } else if (container === item) {
17301
- delete main2[prop];
17576
+ delete main[prop];
17302
17577
  }
17303
17578
  };
17304
17579
  var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
@@ -17479,9 +17754,9 @@ var NodeFsHandler = class {
17479
17754
  if (this.fsw.closed) {
17480
17755
  return;
17481
17756
  }
17482
- const dirname13 = sp.dirname(file);
17757
+ const dirname14 = sp.dirname(file);
17483
17758
  const basename7 = sp.basename(file);
17484
- const parent = this.fsw._getWatchedDir(dirname13);
17759
+ const parent = this.fsw._getWatchedDir(dirname14);
17485
17760
  let prevStats = stats;
17486
17761
  if (parent.has(basename7))
17487
17762
  return;
@@ -17508,7 +17783,7 @@ var NodeFsHandler = class {
17508
17783
  prevStats = newStats2;
17509
17784
  }
17510
17785
  } catch (error) {
17511
- this.fsw._remove(dirname13, basename7);
17786
+ this.fsw._remove(dirname14, basename7);
17512
17787
  }
17513
17788
  } else if (parent.has(basename7)) {
17514
17789
  const at = newStats.atimeMs;
@@ -18472,8 +18747,8 @@ var FileWatcher = class {
18472
18747
  watcher = null;
18473
18748
  projectRoot;
18474
18749
  config;
18475
- host;
18476
18750
  configPath;
18751
+ projectConfigPaths;
18477
18752
  pendingChanges = /* @__PURE__ */ new Map();
18478
18753
  debounceTimer = null;
18479
18754
  debounceMs = 1e3;
@@ -18482,11 +18757,11 @@ var FileWatcher = class {
18482
18757
  resolveReady = null;
18483
18758
  pollingFallbackAttempted = false;
18484
18759
  pendingClose = null;
18485
- constructor(projectRoot, config, host = "opencode", options = {}) {
18760
+ constructor(projectRoot, config, host, options = {}) {
18486
18761
  this.projectRoot = projectRoot;
18487
18762
  this.config = config;
18488
- this.host = host;
18489
18763
  this.configPath = options.configPath;
18764
+ this.projectConfigPaths = options.configPath ? [options.configPath] : getProjectConfigCandidatePaths(projectRoot, host);
18490
18765
  }
18491
18766
  start(handler) {
18492
18767
  if (this.watcher) {
@@ -18504,7 +18779,19 @@ var FileWatcher = class {
18504
18779
  }
18505
18780
  createWatcher(usePolling = false) {
18506
18781
  const ignoreFilter = createIgnoreFilter(this.projectRoot);
18507
- const watchTargets = this.configPath ? [this.projectRoot, this.configPath] : this.projectRoot;
18782
+ let watchTargets = this.projectRoot;
18783
+ if (this.configPath) {
18784
+ watchTargets = [this.projectRoot, this.configPath];
18785
+ } else {
18786
+ const externalConfigTargets = this.projectConfigPaths.filter((projectConfigPath) => {
18787
+ const relativeConfigPath = path25.relative(this.projectRoot, projectConfigPath);
18788
+ return this.isOutsideProjectPath(relativeConfigPath);
18789
+ }).map((projectConfigPath) => (0, import_fs19.existsSync)(projectConfigPath) ? projectConfigPath : this.getNearestExistingDirectory(path25.dirname(projectConfigPath)));
18790
+ const uniqueExternalConfigTargets = [...new Set(externalConfigTargets)];
18791
+ if (uniqueExternalConfigTargets.length > 0) {
18792
+ watchTargets = [this.projectRoot, ...uniqueExternalConfigTargets];
18793
+ }
18794
+ }
18508
18795
  const watcherOptions = {
18509
18796
  ignored: (filePath) => {
18510
18797
  const relativePath = path25.relative(this.projectRoot, filePath);
@@ -18512,6 +18799,9 @@ var FileWatcher = class {
18512
18799
  if (this.isProjectConfigPathOrAncestor(relativePath)) {
18513
18800
  return false;
18514
18801
  }
18802
+ if (this.isOutsideProjectPath(relativePath)) {
18803
+ return true;
18804
+ }
18515
18805
  if (hasFilteredPathSegment(relativePath, path25.sep)) {
18516
18806
  return true;
18517
18807
  }
@@ -18614,14 +18904,22 @@ var FileWatcher = class {
18614
18904
  (configPath) => configPath === normalizedRelativePath || configPath.startsWith(`${normalizedRelativePath}${path25.sep}`)
18615
18905
  );
18616
18906
  }
18617
- getProjectConfigRelativePaths() {
18618
- if (this.configPath) {
18619
- return [path25.normalize(path25.relative(this.projectRoot, this.configPath))];
18907
+ isOutsideProjectPath(relativePath) {
18908
+ return relativePath === ".." || relativePath.startsWith(`..${path25.sep}`) || path25.isAbsolute(relativePath);
18909
+ }
18910
+ getNearestExistingDirectory(directoryPath) {
18911
+ let candidate = directoryPath;
18912
+ while (!(0, import_fs19.existsSync)(candidate)) {
18913
+ const parent = path25.dirname(candidate);
18914
+ if (parent === candidate) break;
18915
+ candidate = parent;
18620
18916
  }
18621
- return [
18622
- resolveProjectConfigPath(this.projectRoot, this.host),
18623
- resolveWritableProjectConfigPath(this.projectRoot, this.host)
18624
- ].map((configPath) => path25.normalize(path25.relative(this.projectRoot, configPath)));
18917
+ return candidate;
18918
+ }
18919
+ getProjectConfigRelativePaths() {
18920
+ return this.projectConfigPaths.map(
18921
+ (configPath) => path25.normalize(path25.relative(this.projectRoot, configPath))
18922
+ );
18625
18923
  }
18626
18924
  scheduleFlush() {
18627
18925
  if (this.debounceTimer) {
@@ -18748,8 +19046,9 @@ var GitHeadWatcher = class {
18748
19046
  };
18749
19047
 
18750
19048
  // src/watcher/index.ts
18751
- function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "opencode", options = {}) {
19049
+ function createWatcherWithIndexer(getIndexer, projectRoot, config, host, options = {}) {
18752
19050
  const fileWatcher = new FileWatcher(projectRoot, config, host, options);
19051
+ const configPaths = getConfigPaths(projectRoot, host, options);
18753
19052
  configureAutoIndex(projectRoot, host, parseConfig(config), getIndexer);
18754
19053
  let stopped = false;
18755
19054
  const requestReindex = () => {
@@ -18766,7 +19065,6 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
18766
19065
  );
18767
19066
  const hasDelete = changes.some((c) => c.type === "unlink");
18768
19067
  if (hasAddOrChange || hasDelete) {
18769
- const configPaths = getConfigPaths(projectRoot, host, options);
18770
19068
  if (changes.some((change) => configPaths.includes(pathNormalize(change.path)))) {
18771
19069
  const parsedConfig = options.configPath ? parseConfig(loadConfigFile(options.configPath)) : void 0;
18772
19070
  const refreshedConfig = refreshIndexerForDirectory(projectRoot, host, parsedConfig);
@@ -18781,7 +19079,9 @@ function createWatcherWithIndexer(getIndexer, projectRoot, config, host = "openc
18781
19079
  if (isGitRepo(projectRoot)) {
18782
19080
  gitWatcher = new GitHeadWatcher(projectRoot);
18783
19081
  gitWatcher.start(async (oldBranch, newBranch) => {
18784
- getIndexer().getLogger().branch("info", "Branch changed", {
19082
+ const indexer = getIndexer();
19083
+ indexer.refreshBranchInfo();
19084
+ indexer.getLogger().branch("info", "Branch changed", {
18785
19085
  oldBranch,
18786
19086
  newBranch
18787
19087
  });
@@ -18807,10 +19107,9 @@ function getConfigPaths(projectRoot, host, options) {
18807
19107
  if (options.configPath) {
18808
19108
  return [pathNormalize(options.configPath)];
18809
19109
  }
18810
- return [
18811
- resolveProjectConfigPath(projectRoot, host),
18812
- resolveWritableProjectConfigPath(projectRoot, host)
18813
- ].map((configPath) => pathNormalize(configPath));
19110
+ return getProjectConfigCandidatePaths(projectRoot, host).map(
19111
+ (configPath) => pathNormalize(configPath)
19112
+ );
18814
19113
  }
18815
19114
 
18816
19115
  // src/tools/visualize/activity.ts
@@ -19516,8 +19815,7 @@ function transformForVisualization(symbols, edges, options = {}) {
19516
19815
  };
19517
19816
  }
19518
19817
 
19519
- // src/cli.ts
19520
- var import_meta3 = {};
19818
+ // src/adapters/mcp/cli.ts
19521
19819
  function parseArgs(argv) {
19522
19820
  let project = process.cwd();
19523
19821
  let config;
@@ -19539,7 +19837,7 @@ function loadCliRawConfig(args) {
19539
19837
  return args.config ? loadConfigFile(args.config) : loadMergedConfig(args.project, args.host);
19540
19838
  }
19541
19839
  function isCliEntrypoint(moduleUrl, argvPath) {
19542
- return argvPath !== void 0 && (0, import_fs19.realpathSync)((0, import_url.fileURLToPath)(moduleUrl)) === (0, import_fs19.realpathSync)(argvPath);
19840
+ return argvPath !== void 0 && (0, import_fs20.realpathSync)((0, import_url.fileURLToPath)(moduleUrl)) === (0, import_fs20.realpathSync)(argvPath);
19543
19841
  }
19544
19842
  function parseVisualizeArgs(argv, cwd) {
19545
19843
  let project = cwd;
@@ -19568,8 +19866,8 @@ function parseVisualizeArgs(argv, cwd) {
19568
19866
  async function handleVisualizeCommand(argv, cwd) {
19569
19867
  try {
19570
19868
  const args = parseVisualizeArgs(argv, cwd);
19571
- const config = parseConfig(loadMergedConfig(args.project));
19572
- const indexer = new Indexer(args.project, config);
19869
+ const config = parseConfig(loadMergedConfig(args.project, "opencode"));
19870
+ const indexer = new Indexer(args.project, config, "opencode");
19573
19871
  const rawData = await indexer.getVisualizationData({
19574
19872
  directory: args.directory
19575
19873
  });
@@ -19587,7 +19885,7 @@ async function handleVisualizeCommand(argv, cwd) {
19587
19885
  return 1;
19588
19886
  }
19589
19887
  const outputPath = path29.join(os8.tmpdir(), `call-graph-${Date.now()}.html`);
19590
- (0, import_fs19.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
19888
+ (0, import_fs20.writeFileSync)(outputPath, generateVisualizationHtml(vizData), "utf-8");
19591
19889
  console.log(`Temporal call graph visualization generated: ${outputPath}`);
19592
19890
  console.log(`Nodes: ${vizData.nodes.length} | Edges: ${vizData.edges.length}`);
19593
19891
  console.log(`Recent change lenses: ${vizData.changes?.length ?? 0}`);
@@ -19600,16 +19898,16 @@ async function handleVisualizeCommand(argv, cwd) {
19600
19898
  return 1;
19601
19899
  }
19602
19900
  }
19603
- async function main() {
19604
- if (process.argv[2] === "eval") {
19605
- const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
19901
+ async function runMcpCli(argv) {
19902
+ if (argv[2] === "eval") {
19903
+ const exitCode = await handleEvalCommand(argv.slice(3), process.cwd());
19606
19904
  process.exit(exitCode);
19607
19905
  }
19608
- if (process.argv[2] === "visualize") {
19609
- const exitCode = await handleVisualizeCommand(process.argv.slice(3), process.cwd());
19906
+ if (argv[2] === "visualize") {
19907
+ const exitCode = await handleVisualizeCommand(argv.slice(3), process.cwd());
19610
19908
  process.exit(exitCode);
19611
19909
  }
19612
- const args = parseArgs(process.argv);
19910
+ const args = parseArgs(argv);
19613
19911
  const rawConfig = loadCliRawConfig(args);
19614
19912
  const config = parseConfig(rawConfig);
19615
19913
  const server = createMcpServer(args.project, config, args.host);
@@ -19660,8 +19958,11 @@ function handleMainError(error) {
19660
19958
  console.error("Fatal: failed to start MCP server");
19661
19959
  process.exit(1);
19662
19960
  }
19961
+
19962
+ // src/cli.ts
19963
+ var import_meta3 = {};
19663
19964
  if (isCliEntrypoint(import_meta3.url, process.argv[1])) {
19664
- main().catch(handleMainError);
19965
+ runMcpCli(process.argv).catch(handleMainError);
19665
19966
  }
19666
19967
  // Annotate the CommonJS export names for ESM import in node:
19667
19968
  0 && (module.exports = {