opencode-codebase-index 0.5.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -7,13 +7,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
8
  var __getProtoOf = Object.getPrototypeOf;
9
9
  var __hasOwnProp = Object.prototype.hasOwnProperty;
10
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
11
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
12
- }) : x)(function(x) {
13
- if (typeof require !== "undefined") return require.apply(this, arguments);
14
- throw Error('Dynamic require of "' + x + '" is not supported');
15
- });
16
- var __commonJS = (cb, mod) => function __require2() {
10
+ var __commonJS = (cb, mod) => function __require() {
17
11
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
18
12
  };
19
13
  var __copyProps = (to, from, except, desc) => {
@@ -35,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
35
29
 
36
30
  // node_modules/eventemitter3/index.js
37
31
  var require_eventemitter3 = __commonJS({
38
- "node_modules/eventemitter3/index.js"(exports, module) {
32
+ "node_modules/eventemitter3/index.js"(exports, module2) {
39
33
  "use strict";
40
34
  var has = Object.prototype.hasOwnProperty;
41
35
  var prefix = "~";
@@ -189,15 +183,15 @@ var require_eventemitter3 = __commonJS({
189
183
  EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
190
184
  EventEmitter2.prefixed = prefix;
191
185
  EventEmitter2.EventEmitter = EventEmitter2;
192
- if ("undefined" !== typeof module) {
193
- module.exports = EventEmitter2;
186
+ if ("undefined" !== typeof module2) {
187
+ module2.exports = EventEmitter2;
194
188
  }
195
189
  }
196
190
  });
197
191
 
198
192
  // node_modules/ignore/index.js
199
193
  var require_ignore = __commonJS({
200
- "node_modules/ignore/index.js"(exports, module) {
194
+ "node_modules/ignore/index.js"(exports, module2) {
201
195
  "use strict";
202
196
  function makeArray(subject) {
203
197
  return Array.isArray(subject) ? subject : [subject];
@@ -493,7 +487,7 @@ var require_ignore = __commonJS({
493
487
  // path matching.
494
488
  // - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
495
489
  // @returns {TestResult} true if a file is ignored
496
- test(path7, checkUnignored, mode) {
490
+ test(path10, checkUnignored, mode) {
497
491
  let ignored = false;
498
492
  let unignored = false;
499
493
  let matchedRule;
@@ -502,7 +496,7 @@ var require_ignore = __commonJS({
502
496
  if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
503
497
  return;
504
498
  }
505
- const matched = rule[mode].test(path7);
499
+ const matched = rule[mode].test(path10);
506
500
  if (!matched) {
507
501
  return;
508
502
  }
@@ -523,17 +517,17 @@ var require_ignore = __commonJS({
523
517
  var throwError = (message, Ctor) => {
524
518
  throw new Ctor(message);
525
519
  };
526
- var checkPath = (path7, originalPath, doThrow) => {
527
- if (!isString(path7)) {
520
+ var checkPath = (path10, originalPath, doThrow) => {
521
+ if (!isString(path10)) {
528
522
  return doThrow(
529
523
  `path must be a string, but got \`${originalPath}\``,
530
524
  TypeError
531
525
  );
532
526
  }
533
- if (!path7) {
527
+ if (!path10) {
534
528
  return doThrow(`path must not be empty`, TypeError);
535
529
  }
536
- if (checkPath.isNotRelative(path7)) {
530
+ if (checkPath.isNotRelative(path10)) {
537
531
  const r = "`path.relative()`d";
538
532
  return doThrow(
539
533
  `path should be a ${r} string, but got "${originalPath}"`,
@@ -542,7 +536,7 @@ var require_ignore = __commonJS({
542
536
  }
543
537
  return true;
544
538
  };
545
- var isNotRelative = (path7) => REGEX_TEST_INVALID_PATH.test(path7);
539
+ var isNotRelative = (path10) => REGEX_TEST_INVALID_PATH.test(path10);
546
540
  checkPath.isNotRelative = isNotRelative;
547
541
  checkPath.convert = (p) => p;
548
542
  var Ignore2 = class {
@@ -572,19 +566,19 @@ var require_ignore = __commonJS({
572
566
  }
573
567
  // @returns {TestResult}
574
568
  _test(originalPath, cache, checkUnignored, slices) {
575
- const path7 = originalPath && checkPath.convert(originalPath);
569
+ const path10 = originalPath && checkPath.convert(originalPath);
576
570
  checkPath(
577
- path7,
571
+ path10,
578
572
  originalPath,
579
573
  this._strictPathCheck ? throwError : RETURN_FALSE
580
574
  );
581
- return this._t(path7, cache, checkUnignored, slices);
575
+ return this._t(path10, cache, checkUnignored, slices);
582
576
  }
583
- checkIgnore(path7) {
584
- if (!REGEX_TEST_TRAILING_SLASH.test(path7)) {
585
- return this.test(path7);
577
+ checkIgnore(path10) {
578
+ if (!REGEX_TEST_TRAILING_SLASH.test(path10)) {
579
+ return this.test(path10);
586
580
  }
587
- const slices = path7.split(SLASH).filter(Boolean);
581
+ const slices = path10.split(SLASH).filter(Boolean);
588
582
  slices.pop();
589
583
  if (slices.length) {
590
584
  const parent = this._t(
@@ -597,18 +591,18 @@ var require_ignore = __commonJS({
597
591
  return parent;
598
592
  }
599
593
  }
600
- return this._rules.test(path7, false, MODE_CHECK_IGNORE);
594
+ return this._rules.test(path10, false, MODE_CHECK_IGNORE);
601
595
  }
602
- _t(path7, cache, checkUnignored, slices) {
603
- if (path7 in cache) {
604
- return cache[path7];
596
+ _t(path10, cache, checkUnignored, slices) {
597
+ if (path10 in cache) {
598
+ return cache[path10];
605
599
  }
606
600
  if (!slices) {
607
- slices = path7.split(SLASH).filter(Boolean);
601
+ slices = path10.split(SLASH).filter(Boolean);
608
602
  }
609
603
  slices.pop();
610
604
  if (!slices.length) {
611
- return cache[path7] = this._rules.test(path7, checkUnignored, MODE_IGNORE);
605
+ return cache[path10] = this._rules.test(path10, checkUnignored, MODE_IGNORE);
612
606
  }
613
607
  const parent = this._t(
614
608
  slices.join(SLASH) + SLASH,
@@ -616,29 +610,29 @@ var require_ignore = __commonJS({
616
610
  checkUnignored,
617
611
  slices
618
612
  );
619
- return cache[path7] = parent.ignored ? parent : this._rules.test(path7, checkUnignored, MODE_IGNORE);
613
+ return cache[path10] = parent.ignored ? parent : this._rules.test(path10, checkUnignored, MODE_IGNORE);
620
614
  }
621
- ignores(path7) {
622
- return this._test(path7, this._ignoreCache, false).ignored;
615
+ ignores(path10) {
616
+ return this._test(path10, this._ignoreCache, false).ignored;
623
617
  }
624
618
  createFilter() {
625
- return (path7) => !this.ignores(path7);
619
+ return (path10) => !this.ignores(path10);
626
620
  }
627
621
  filter(paths) {
628
622
  return makeArray(paths).filter(this.createFilter());
629
623
  }
630
624
  // @returns {TestResult}
631
- test(path7) {
632
- return this._test(path7, this._testCache, true);
625
+ test(path10) {
626
+ return this._test(path10, this._testCache, true);
633
627
  }
634
628
  };
635
629
  var factory = (options) => new Ignore2(options);
636
- var isPathValid = (path7) => checkPath(path7 && checkPath.convert(path7), path7, RETURN_FALSE);
630
+ var isPathValid = (path10) => checkPath(path10 && checkPath.convert(path10), path10, RETURN_FALSE);
637
631
  var setupWindows = () => {
638
632
  const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
639
633
  checkPath.convert = makePosix;
640
634
  const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
641
- checkPath.isNotRelative = (path7) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path7) || isNotRelative(path7);
635
+ checkPath.isNotRelative = (path10) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path10) || isNotRelative(path10);
642
636
  };
643
637
  if (
644
638
  // Detect `process` so that it can run in browsers.
@@ -646,18 +640,18 @@ var require_ignore = __commonJS({
646
640
  ) {
647
641
  setupWindows();
648
642
  }
649
- module.exports = factory;
643
+ module2.exports = factory;
650
644
  factory.default = factory;
651
- module.exports.isPathValid = isPathValid;
652
- define(module.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
645
+ module2.exports.isPathValid = isPathValid;
646
+ define(module2.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
653
647
  }
654
648
  });
655
649
 
656
650
  // src/cli.ts
657
651
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
658
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
659
- import * as path6 from "path";
660
- import * as os3 from "os";
652
+ import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
653
+ import * as path9 from "path";
654
+ import * as os4 from "os";
661
655
 
662
656
  // src/config/constants.ts
663
657
  var DEFAULT_INCLUDE = [
@@ -665,7 +659,7 @@ var DEFAULT_INCLUDE = [
665
659
  "**/*.{py,pyi}",
666
660
  "**/*.{go,rs,java,kt,scala}",
667
661
  "**/*.{c,cpp,cc,h,hpp}",
668
- "**/*.{rb,php,swift}",
662
+ "**/*.{rb,php,inc,swift}",
669
663
  "**/*.{vue,svelte,astro}",
670
664
  "**/*.{sql,graphql,proto}",
671
665
  "**/*.{yaml,yml,toml}",
@@ -915,13 +909,197 @@ function getDefaultModelForProvider(provider) {
915
909
  }
916
910
  var availableProviders = Object.keys(EMBEDDING_MODELS);
917
911
 
918
- // src/mcp-server.ts
919
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
920
- import { z } from "zod";
912
+ // src/eval/cli.ts
913
+ import * as path8 from "path";
914
+
915
+ // src/eval/compare.ts
916
+ function metricDelta(current, baseline) {
917
+ const absolute = current - baseline;
918
+ const relativePct = baseline === 0 ? current === 0 ? 0 : 100 : absolute / baseline * 100;
919
+ return {
920
+ current,
921
+ baseline,
922
+ absolute,
923
+ relativePct
924
+ };
925
+ }
926
+ function compareSummaries(current, baseline, againstPath) {
927
+ return {
928
+ againstPath,
929
+ deltas: {
930
+ hitAt1: metricDelta(current.metrics.hitAt1, baseline.metrics.hitAt1),
931
+ hitAt3: metricDelta(current.metrics.hitAt3, baseline.metrics.hitAt3),
932
+ hitAt5: metricDelta(current.metrics.hitAt5, baseline.metrics.hitAt5),
933
+ hitAt10: metricDelta(current.metrics.hitAt10, baseline.metrics.hitAt10),
934
+ mrrAt10: metricDelta(current.metrics.mrrAt10, baseline.metrics.mrrAt10),
935
+ ndcgAt10: metricDelta(current.metrics.ndcgAt10, baseline.metrics.ndcgAt10),
936
+ latencyP50Ms: metricDelta(current.metrics.latencyMs.p50, baseline.metrics.latencyMs.p50),
937
+ latencyP95Ms: metricDelta(current.metrics.latencyMs.p95, baseline.metrics.latencyMs.p95),
938
+ latencyP99Ms: metricDelta(current.metrics.latencyMs.p99, baseline.metrics.latencyMs.p99),
939
+ embeddingCallCount: metricDelta(
940
+ current.metrics.embedding.callCount,
941
+ baseline.metrics.embedding.callCount
942
+ ),
943
+ estimatedCostUsd: metricDelta(
944
+ current.metrics.embedding.estimatedCostUsd,
945
+ baseline.metrics.embedding.estimatedCostUsd
946
+ )
947
+ }
948
+ };
949
+ }
950
+
951
+ // src/eval/reports.ts
952
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
953
+ import * as path from "path";
954
+ function formatPct(value) {
955
+ return `${(value * 100).toFixed(2)}%`;
956
+ }
957
+ function formatMs(value) {
958
+ return `${value.toFixed(3)}ms`;
959
+ }
960
+ function formatUsd(value) {
961
+ return `$${value.toFixed(6)}`;
962
+ }
963
+ function signed(value, digits = 4) {
964
+ const formatted = value.toFixed(digits);
965
+ return value > 0 ? `+${formatted}` : formatted;
966
+ }
967
+ function loadSummary(summaryPath) {
968
+ const raw = readFileSync(summaryPath, "utf-8");
969
+ return JSON.parse(raw);
970
+ }
971
+ function createRunDirectory(outputRoot, timestampOverride) {
972
+ const timestamp = (timestampOverride ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
973
+ const dir = path.join(outputRoot, timestamp);
974
+ mkdirSync(dir, { recursive: true });
975
+ return dir;
976
+ }
977
+ function writeJson(filePath, value) {
978
+ writeFileSync(filePath, JSON.stringify(value, null, 2), "utf-8");
979
+ }
980
+ function writeText(filePath, value) {
981
+ writeFileSync(filePath, value, "utf-8");
982
+ }
983
+ function createSummaryMarkdown(summary, comparison, gate, sweep) {
984
+ const lines = [];
985
+ lines.push("# Evaluation Summary");
986
+ lines.push("");
987
+ lines.push(`- Generated: ${summary.generatedAt}`);
988
+ lines.push(`- Dataset: ${summary.datasetName} (v${summary.datasetVersion})`);
989
+ lines.push(`- Query count: ${summary.queryCount}`);
990
+ lines.push(
991
+ `- Search config: fusion=${summary.searchConfig.fusionStrategy}, hybridWeight=${summary.searchConfig.hybridWeight}, rrfK=${summary.searchConfig.rrfK}, rerankTopN=${summary.searchConfig.rerankTopN}`
992
+ );
993
+ lines.push("");
994
+ lines.push("## Metrics");
995
+ lines.push("");
996
+ lines.push("| Metric | Value |");
997
+ lines.push("|---|---:|");
998
+ lines.push(`| Hit@1 | ${formatPct(summary.metrics.hitAt1)} |`);
999
+ lines.push(`| Hit@3 | ${formatPct(summary.metrics.hitAt3)} |`);
1000
+ lines.push(`| Hit@5 | ${formatPct(summary.metrics.hitAt5)} |`);
1001
+ lines.push(`| Hit@10 | ${formatPct(summary.metrics.hitAt10)} |`);
1002
+ lines.push(`| MRR@10 | ${summary.metrics.mrrAt10.toFixed(4)} |`);
1003
+ lines.push(`| nDCG@10 | ${summary.metrics.ndcgAt10.toFixed(4)} |`);
1004
+ lines.push(`| Latency p50 | ${formatMs(summary.metrics.latencyMs.p50)} |`);
1005
+ lines.push(`| Latency p95 | ${formatMs(summary.metrics.latencyMs.p95)} |`);
1006
+ lines.push(`| Latency p99 | ${formatMs(summary.metrics.latencyMs.p99)} |`);
1007
+ lines.push(`| Embedding calls | ${summary.metrics.embedding.callCount} |`);
1008
+ lines.push(`| Embedding tokens | ${summary.metrics.tokenEstimate.embeddingTokensUsed} |`);
1009
+ lines.push(`| Estimated embedding cost | ${formatUsd(summary.metrics.embedding.estimatedCostUsd)} |`);
1010
+ lines.push("");
1011
+ lines.push("## Failure Buckets");
1012
+ lines.push("");
1013
+ lines.push("| Bucket | Count |");
1014
+ lines.push("|---|---:|");
1015
+ lines.push(
1016
+ `| wrong-file | ${summary.metrics.failureBuckets["wrong-file"]} |`
1017
+ );
1018
+ lines.push(
1019
+ `| wrong-symbol | ${summary.metrics.failureBuckets["wrong-symbol"]} |`
1020
+ );
1021
+ lines.push(
1022
+ `| docs/tests outranking source | ${summary.metrics.failureBuckets["docs-tests-outranking-source"]} |`
1023
+ );
1024
+ lines.push(
1025
+ `| no relevant hit in top-k | ${summary.metrics.failureBuckets["no-relevant-hit-top-k"]} |`
1026
+ );
1027
+ lines.push("");
1028
+ if (comparison) {
1029
+ lines.push("## Comparison vs Baseline");
1030
+ lines.push("");
1031
+ lines.push(`- Against: ${comparison.againstPath}`);
1032
+ lines.push("");
1033
+ lines.push("| Metric | Baseline | Current | Delta |");
1034
+ lines.push("|---|---:|---:|---:|");
1035
+ lines.push(
1036
+ `| Hit@5 | ${formatPct(comparison.deltas.hitAt5.baseline)} | ${formatPct(comparison.deltas.hitAt5.current)} | ${signed(comparison.deltas.hitAt5.absolute)} |`
1037
+ );
1038
+ lines.push(
1039
+ `| MRR@10 | ${comparison.deltas.mrrAt10.baseline.toFixed(4)} | ${comparison.deltas.mrrAt10.current.toFixed(4)} | ${signed(comparison.deltas.mrrAt10.absolute)} |`
1040
+ );
1041
+ lines.push(
1042
+ `| nDCG@10 | ${comparison.deltas.ndcgAt10.baseline.toFixed(4)} | ${comparison.deltas.ndcgAt10.current.toFixed(4)} | ${signed(comparison.deltas.ndcgAt10.absolute)} |`
1043
+ );
1044
+ lines.push(
1045
+ `| p95 latency (ms) | ${comparison.deltas.latencyP95Ms.baseline.toFixed(3)} | ${comparison.deltas.latencyP95Ms.current.toFixed(3)} | ${signed(comparison.deltas.latencyP95Ms.absolute, 3)} |`
1046
+ );
1047
+ lines.push("");
1048
+ }
1049
+ if (gate) {
1050
+ lines.push("## CI Gate");
1051
+ lines.push("");
1052
+ lines.push(`- Result: ${gate.passed ? "PASS \u2705" : "FAIL \u274C"}`);
1053
+ if (gate.violations.length > 0) {
1054
+ lines.push("- Violations:");
1055
+ for (const violation of gate.violations) {
1056
+ lines.push(` - ${violation.metric}: ${violation.message}`);
1057
+ }
1058
+ }
1059
+ lines.push("");
1060
+ }
1061
+ if (sweep) {
1062
+ lines.push("## Parameter Sweep");
1063
+ lines.push("");
1064
+ lines.push(`- Run count: ${sweep.runCount}`);
1065
+ if (sweep.bestByHitAt5) {
1066
+ lines.push(
1067
+ `- Best Hit@5: ${formatPct(sweep.bestByHitAt5.summary.metrics.hitAt5)} with fusion=${sweep.bestByHitAt5.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByHitAt5.searchConfig.hybridWeight}, rrfK=${sweep.bestByHitAt5.searchConfig.rrfK}, rerankTopN=${sweep.bestByHitAt5.searchConfig.rerankTopN}`
1068
+ );
1069
+ }
1070
+ if (sweep.bestByMrrAt10) {
1071
+ lines.push(
1072
+ `- Best MRR@10: ${sweep.bestByMrrAt10.summary.metrics.mrrAt10.toFixed(4)} with fusion=${sweep.bestByMrrAt10.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByMrrAt10.searchConfig.hybridWeight}, rrfK=${sweep.bestByMrrAt10.searchConfig.rrfK}, rerankTopN=${sweep.bestByMrrAt10.searchConfig.rerankTopN}`
1073
+ );
1074
+ }
1075
+ if (sweep.bestByP95Latency) {
1076
+ lines.push(
1077
+ `- Best p95 latency: ${formatMs(sweep.bestByP95Latency.summary.metrics.latencyMs.p95)} with fusion=${sweep.bestByP95Latency.searchConfig.fusionStrategy}, hybridWeight=${sweep.bestByP95Latency.searchConfig.hybridWeight}, rrfK=${sweep.bestByP95Latency.searchConfig.rrfK}, rerankTopN=${sweep.bestByP95Latency.searchConfig.rerankTopN}`
1078
+ );
1079
+ }
1080
+ lines.push("");
1081
+ }
1082
+ return `${lines.join("\n")}
1083
+ `;
1084
+ }
1085
+ function buildPerQueryArtifact(perQuery) {
1086
+ return {
1087
+ queryCount: perQuery.length,
1088
+ queries: [...perQuery].sort((a, b) => a.id.localeCompare(b.id))
1089
+ };
1090
+ }
1091
+
1092
+ // src/eval/runner.ts
1093
+ import { existsSync as existsSync5 } from "fs";
1094
+ import { readFileSync as readFileSync7 } from "fs";
1095
+ import { rmSync } from "fs";
1096
+ import * as os3 from "os";
1097
+ import * as path7 from "path";
1098
+ import { performance as performance3 } from "perf_hooks";
921
1099
 
922
1100
  // src/indexer/index.ts
923
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
924
- import * as path5 from "path";
1101
+ import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync2, renameSync, unlinkSync, promises as fsPromises2 } from "fs";
1102
+ import * as path6 from "path";
925
1103
  import { performance as performance2 } from "perf_hooks";
926
1104
 
927
1105
  // node_modules/eventemitter3/index.mjs
@@ -946,7 +1124,7 @@ function pTimeout(promise, options) {
946
1124
  } = options;
947
1125
  let timer;
948
1126
  let abortHandler;
949
- const wrappedPromise = new Promise((resolve4, reject) => {
1127
+ const wrappedPromise = new Promise((resolve5, reject) => {
950
1128
  if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
951
1129
  throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
952
1130
  }
@@ -960,7 +1138,7 @@ function pTimeout(promise, options) {
960
1138
  };
961
1139
  signal.addEventListener("abort", abortHandler, { once: true });
962
1140
  }
963
- promise.then(resolve4, reject);
1141
+ promise.then(resolve5, reject);
964
1142
  if (milliseconds === Number.POSITIVE_INFINITY) {
965
1143
  return;
966
1144
  }
@@ -968,7 +1146,7 @@ function pTimeout(promise, options) {
968
1146
  timer = customTimers.setTimeout.call(void 0, () => {
969
1147
  if (fallback) {
970
1148
  try {
971
- resolve4(fallback());
1149
+ resolve5(fallback());
972
1150
  } catch (error) {
973
1151
  reject(error);
974
1152
  }
@@ -978,7 +1156,7 @@ function pTimeout(promise, options) {
978
1156
  promise.cancel();
979
1157
  }
980
1158
  if (message === false) {
981
- resolve4();
1159
+ resolve5();
982
1160
  } else if (message instanceof Error) {
983
1161
  reject(message);
984
1162
  } else {
@@ -1368,7 +1546,7 @@ var PQueue = class extends import_index.default {
1368
1546
  // Assign unique ID if not provided
1369
1547
  id: options.id ?? (this.#idAssigner++).toString()
1370
1548
  };
1371
- return new Promise((resolve4, reject) => {
1549
+ return new Promise((resolve5, reject) => {
1372
1550
  const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
1373
1551
  this.#queue.enqueue(async () => {
1374
1552
  this.#pending++;
@@ -1406,7 +1584,7 @@ var PQueue = class extends import_index.default {
1406
1584
  })]);
1407
1585
  }
1408
1586
  const result = await operation;
1409
- resolve4(result);
1587
+ resolve5(result);
1410
1588
  this.emit("completed", result);
1411
1589
  } catch (error) {
1412
1590
  reject(error);
@@ -1563,13 +1741,13 @@ var PQueue = class extends import_index.default {
1563
1741
  });
1564
1742
  }
1565
1743
  async #onEvent(event, filter) {
1566
- return new Promise((resolve4) => {
1744
+ return new Promise((resolve5) => {
1567
1745
  const listener = () => {
1568
1746
  if (filter && !filter()) {
1569
1747
  return;
1570
1748
  }
1571
1749
  this.off(event, listener);
1572
- resolve4();
1750
+ resolve5();
1573
1751
  };
1574
1752
  this.on(event, listener);
1575
1753
  });
@@ -1854,7 +2032,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
1854
2032
  const finalDelay = Math.min(delayTime, remainingTime);
1855
2033
  options.signal?.throwIfAborted();
1856
2034
  if (finalDelay > 0) {
1857
- await new Promise((resolve4, reject) => {
2035
+ await new Promise((resolve5, reject) => {
1858
2036
  const onAbort = () => {
1859
2037
  clearTimeout(timeoutToken);
1860
2038
  options.signal?.removeEventListener("abort", onAbort);
@@ -1862,7 +2040,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
1862
2040
  };
1863
2041
  const timeoutToken = setTimeout(() => {
1864
2042
  options.signal?.removeEventListener("abort", onAbort);
1865
- resolve4();
2043
+ resolve5();
1866
2044
  }, finalDelay);
1867
2045
  if (options.unref) {
1868
2046
  timeoutToken.unref?.();
@@ -1923,17 +2101,17 @@ async function pRetry(input, options = {}) {
1923
2101
  }
1924
2102
 
1925
2103
  // src/embeddings/detector.ts
1926
- import { existsSync, readFileSync } from "fs";
1927
- import * as path from "path";
2104
+ import { existsSync, readFileSync as readFileSync2 } from "fs";
2105
+ import * as path2 from "path";
1928
2106
  import * as os from "os";
1929
2107
  function getOpenCodeAuthPath() {
1930
- return path.join(os.homedir(), ".local", "share", "opencode", "auth.json");
2108
+ return path2.join(os.homedir(), ".local", "share", "opencode", "auth.json");
1931
2109
  }
1932
2110
  function loadOpenCodeAuth() {
1933
2111
  const authPath = getOpenCodeAuthPath();
1934
2112
  try {
1935
2113
  if (existsSync(authPath)) {
1936
- return JSON.parse(readFileSync(authPath, "utf-8"));
2114
+ return JSON.parse(readFileSync2(authPath, "utf-8"));
1937
2115
  }
1938
2116
  } catch {
1939
2117
  }
@@ -2435,8 +2613,8 @@ var CustomEmbeddingProvider = class {
2435
2613
 
2436
2614
  // src/utils/files.ts
2437
2615
  var import_ignore = __toESM(require_ignore(), 1);
2438
- import { existsSync as existsSync2, readFileSync as readFileSync2, promises as fsPromises } from "fs";
2439
- import * as path2 from "path";
2616
+ import { existsSync as existsSync2, readFileSync as readFileSync3, promises as fsPromises } from "fs";
2617
+ import * as path3 from "path";
2440
2618
  function createIgnoreFilter(projectRoot) {
2441
2619
  const ig = (0, import_ignore.default)();
2442
2620
  const defaultIgnores = [
@@ -2453,9 +2631,9 @@ function createIgnoreFilter(projectRoot) {
2453
2631
  ".opencode"
2454
2632
  ];
2455
2633
  ig.add(defaultIgnores);
2456
- const gitignorePath = path2.join(projectRoot, ".gitignore");
2634
+ const gitignorePath = path3.join(projectRoot, ".gitignore");
2457
2635
  if (existsSync2(gitignorePath)) {
2458
- const gitignoreContent = readFileSync2(gitignorePath, "utf-8");
2636
+ const gitignoreContent = readFileSync3(gitignorePath, "utf-8");
2459
2637
  ig.add(gitignoreContent);
2460
2638
  }
2461
2639
  return ig;
@@ -2471,8 +2649,8 @@ function matchGlob(filePath, pattern) {
2471
2649
  async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped) {
2472
2650
  const entries = await fsPromises.readdir(dir, { withFileTypes: true });
2473
2651
  for (const entry of entries) {
2474
- const fullPath = path2.join(dir, entry.name);
2475
- const relativePath = path2.relative(projectRoot, fullPath);
2652
+ const fullPath = path3.join(dir, entry.name);
2653
+ const relativePath = path3.relative(projectRoot, fullPath);
2476
2654
  if (ignoreFilter.ignores(relativePath)) {
2477
2655
  if (entry.isFile()) {
2478
2656
  skipped.push({ path: relativePath, reason: "gitignore" });
@@ -2533,6 +2711,9 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
2533
2711
  }
2534
2712
 
2535
2713
  // src/utils/cost.ts
2714
+ function estimateTokens(text) {
2715
+ return Math.ceil(text.length / 4);
2716
+ }
2536
2717
  function estimateChunksFromFiles(files) {
2537
2718
  let totalChunks = 0;
2538
2719
  for (const file of files) {
@@ -2887,8 +3068,9 @@ function initializeLogger(config) {
2887
3068
  }
2888
3069
 
2889
3070
  // src/native/index.ts
2890
- import * as path3 from "path";
3071
+ import * as path4 from "path";
2891
3072
  import * as os2 from "os";
3073
+ import * as module from "module";
2892
3074
  import { fileURLToPath } from "url";
2893
3075
  function getNativeBinding() {
2894
3076
  const platform2 = os2.platform();
@@ -2908,17 +3090,22 @@ function getNativeBinding() {
2908
3090
  throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
2909
3091
  }
2910
3092
  let currentDir;
3093
+ let requireTarget;
2911
3094
  if (typeof import.meta !== "undefined" && import.meta.url) {
2912
- currentDir = path3.dirname(fileURLToPath(import.meta.url));
3095
+ currentDir = path4.dirname(fileURLToPath(import.meta.url));
3096
+ requireTarget = import.meta.url;
2913
3097
  } else if (typeof __dirname !== "undefined") {
2914
3098
  currentDir = __dirname;
3099
+ requireTarget = __filename;
2915
3100
  } else {
2916
3101
  currentDir = process.cwd();
3102
+ requireTarget = path4.join(currentDir, "index.js");
2917
3103
  }
2918
3104
  const isDevMode = currentDir.includes("/src/native");
2919
- const packageRoot = isDevMode ? path3.resolve(currentDir, "../..") : path3.resolve(currentDir, "..");
2920
- const nativePath = path3.join(packageRoot, "native", bindingName);
2921
- return __require(nativePath);
3105
+ const packageRoot = isDevMode ? path4.resolve(currentDir, "../..") : path4.resolve(currentDir, "..");
3106
+ const nativePath = path4.join(packageRoot, "native", bindingName);
3107
+ const require2 = module.createRequire(requireTarget);
3108
+ return require2(nativePath);
2922
3109
  }
2923
3110
  var native = getNativeBinding();
2924
3111
  function parseFiles(files) {
@@ -3036,7 +3223,7 @@ var VectorStore = class {
3036
3223
  var CHARS_PER_TOKEN = 4;
3037
3224
  var MAX_BATCH_TOKENS = 7500;
3038
3225
  var MAX_SINGLE_CHUNK_TOKENS = 2e3;
3039
- function estimateTokens(text) {
3226
+ function estimateTokens2(text) {
3040
3227
  return Math.ceil(text.length / CHARS_PER_TOKEN);
3041
3228
  }
3042
3229
  function createEmbeddingText(chunk, filePath) {
@@ -3101,7 +3288,7 @@ function createDynamicBatches(chunks) {
3101
3288
  let currentBatch = [];
3102
3289
  let currentTokens = 0;
3103
3290
  for (const chunk of chunks) {
3104
- const chunkTokens = estimateTokens(chunk.text);
3291
+ const chunkTokens = estimateTokens2(chunk.text);
3105
3292
  if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
3106
3293
  batches.push(currentBatch);
3107
3294
  currentBatch = [];
@@ -3398,11 +3585,40 @@ var Database = class {
3398
3585
  };
3399
3586
 
3400
3587
  // src/git/index.ts
3401
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync } from "fs";
3402
- import * as path4 from "path";
3403
- import { execSync } from "child_process";
3588
+ import { existsSync as existsSync3, readFileSync as readFileSync4, readdirSync, statSync } from "fs";
3589
+ import * as path5 from "path";
3590
+ function readPackedRefs(gitDir) {
3591
+ const packedRefsPath = path5.join(gitDir, "packed-refs");
3592
+ if (!existsSync3(packedRefsPath)) {
3593
+ return [];
3594
+ }
3595
+ try {
3596
+ return readFileSync4(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
3597
+ } catch {
3598
+ return [];
3599
+ }
3600
+ }
3601
+ function resolveCommonGitDir(gitDir) {
3602
+ const commonDirPath = path5.join(gitDir, "commondir");
3603
+ if (!existsSync3(commonDirPath)) {
3604
+ return gitDir;
3605
+ }
3606
+ try {
3607
+ const raw = readFileSync4(commonDirPath, "utf-8").trim();
3608
+ if (!raw) {
3609
+ return gitDir;
3610
+ }
3611
+ const resolved = path5.isAbsolute(raw) ? raw : path5.resolve(gitDir, raw);
3612
+ if (existsSync3(resolved)) {
3613
+ return resolved;
3614
+ }
3615
+ } catch {
3616
+ return gitDir;
3617
+ }
3618
+ return gitDir;
3619
+ }
3404
3620
  function resolveGitDir(repoRoot) {
3405
- const gitPath = path4.join(repoRoot, ".git");
3621
+ const gitPath = path5.join(repoRoot, ".git");
3406
3622
  if (!existsSync3(gitPath)) {
3407
3623
  return null;
3408
3624
  }
@@ -3412,11 +3628,11 @@ function resolveGitDir(repoRoot) {
3412
3628
  return gitPath;
3413
3629
  }
3414
3630
  if (stat.isFile()) {
3415
- const content = readFileSync3(gitPath, "utf-8").trim();
3631
+ const content = readFileSync4(gitPath, "utf-8").trim();
3416
3632
  const match = content.match(/^gitdir:\s*(.+)$/);
3417
3633
  if (match) {
3418
3634
  const gitdir = match[1];
3419
- const resolvedPath = path4.isAbsolute(gitdir) ? gitdir : path4.resolve(repoRoot, gitdir);
3635
+ const resolvedPath = path5.isAbsolute(gitdir) ? gitdir : path5.resolve(repoRoot, gitdir);
3420
3636
  if (existsSync3(resolvedPath)) {
3421
3637
  return resolvedPath;
3422
3638
  }
@@ -3434,12 +3650,12 @@ function getCurrentBranch(repoRoot) {
3434
3650
  if (!gitDir) {
3435
3651
  return null;
3436
3652
  }
3437
- const headPath = path4.join(gitDir, "HEAD");
3653
+ const headPath = path5.join(gitDir, "HEAD");
3438
3654
  if (!existsSync3(headPath)) {
3439
3655
  return null;
3440
3656
  }
3441
3657
  try {
3442
- const headContent = readFileSync3(headPath, "utf-8").trim();
3658
+ const headContent = readFileSync4(headPath, "utf-8").trim();
3443
3659
  const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
3444
3660
  if (match) {
3445
3661
  return match[1];
@@ -3454,37 +3670,20 @@ function getCurrentBranch(repoRoot) {
3454
3670
  }
3455
3671
  function getBaseBranch(repoRoot) {
3456
3672
  const gitDir = resolveGitDir(repoRoot);
3673
+ const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
3457
3674
  const candidates = ["main", "master", "develop", "trunk"];
3458
- if (gitDir) {
3675
+ if (refStoreDir) {
3459
3676
  for (const candidate of candidates) {
3460
- const refPath = path4.join(gitDir, "refs", "heads", candidate);
3677
+ const refPath = path5.join(refStoreDir, "refs", "heads", candidate);
3461
3678
  if (existsSync3(refPath)) {
3462
3679
  return candidate;
3463
3680
  }
3464
- const packedRefsPath = path4.join(gitDir, "packed-refs");
3465
- if (existsSync3(packedRefsPath)) {
3466
- try {
3467
- const content = readFileSync3(packedRefsPath, "utf-8");
3468
- if (content.includes(`refs/heads/${candidate}`)) {
3469
- return candidate;
3470
- }
3471
- } catch {
3472
- }
3681
+ const packedRefs = readPackedRefs(refStoreDir);
3682
+ if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
3683
+ return candidate;
3473
3684
  }
3474
3685
  }
3475
3686
  }
3476
- try {
3477
- const result = execSync("git remote show origin", {
3478
- cwd: repoRoot,
3479
- encoding: "utf-8",
3480
- stdio: ["pipe", "pipe", "pipe"]
3481
- });
3482
- const match = result.match(/HEAD branch: (.+)/);
3483
- if (match) {
3484
- return match[1].trim();
3485
- }
3486
- } catch {
3487
- }
3488
3687
  return getCurrentBranch(repoRoot) ?? "main";
3489
3688
  }
3490
3689
  function getBranchOrDefault(repoRoot) {
@@ -3495,7 +3694,7 @@ function getBranchOrDefault(repoRoot) {
3495
3694
  }
3496
3695
 
3497
3696
  // src/indexer/index.ts
3498
- var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust"]);
3697
+ var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
3499
3698
  var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
3500
3699
  "function_declaration",
3501
3700
  "function",
@@ -3516,7 +3715,8 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
3516
3715
  "struct_item",
3517
3716
  "enum_item",
3518
3717
  "trait_item",
3519
- "mod_item"
3718
+ "mod_item",
3719
+ "trait_declaration"
3520
3720
  ]);
3521
3721
  function float32ArrayToBuffer(arr) {
3522
3722
  const float32 = new Float32Array(arr);
@@ -3898,8 +4098,8 @@ function stripFilePathHint(query) {
3898
4098
  const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
3899
4099
  return stripped.length > 0 ? stripped : query;
3900
4100
  }
3901
- function buildDeterministicIdentifierPass(query, candidates, limit) {
3902
- if (classifyQueryIntentRaw(query) !== "source") {
4101
+ function buildDeterministicIdentifierPass(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
4102
+ if (!prioritizeSourcePaths) {
3903
4103
  return [];
3904
4104
  }
3905
4105
  const primary = extractPrimaryIdentifierQueryHint(query);
@@ -4115,9 +4315,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
4115
4315
  const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
4116
4316
  const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
4117
4317
  const rerankPool = fused.slice(0, rerankPoolLimit);
4118
- const intent = classifyQueryIntentRaw(query);
4119
4318
  return rerankResults(query, rerankPool, options.rerankTopN, {
4120
- prioritizeSourcePaths: intent === "source"
4319
+ prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
4121
4320
  });
4122
4321
  }
4123
4322
  function rankSemanticOnlyResults(query, semanticResults, options) {
@@ -4127,11 +4326,11 @@ function rankSemanticOnlyResults(query, semanticResults, options) {
4127
4326
  prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
4128
4327
  });
4129
4328
  }
4130
- function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds) {
4329
+ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
4131
4330
  if (combined.length === 0) {
4132
4331
  return combined;
4133
4332
  }
4134
- if (classifyQueryIntentRaw(query) !== "source") {
4333
+ if (!prioritizeSourcePaths) {
4135
4334
  return combined;
4136
4335
  }
4137
4336
  const identifierHints = extractIdentifierHints(query);
@@ -4221,8 +4420,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
4221
4420
  const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
4222
4421
  return [...promoted, ...remainder];
4223
4422
  }
4224
- function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates) {
4225
- if (classifyQueryIntentRaw(query) !== "source") {
4423
+ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
4424
+ if (!prioritizeSourcePaths) {
4226
4425
  return [];
4227
4426
  }
4228
4427
  const identifierHints = extractIdentifierHints(query);
@@ -4367,8 +4566,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
4367
4566
  const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
4368
4567
  return withFallback.slice(0, Math.max(limit * 2, limit));
4369
4568
  }
4370
- function buildIdentifierDefinitionLane(query, candidates, limit) {
4371
- if (classifyQueryIntentRaw(query) !== "source") {
4569
+ function buildIdentifierDefinitionLane(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
4570
+ if (!prioritizeSourcePaths) {
4372
4571
  return [];
4373
4572
  }
4374
4573
  const primaryHint = extractPrimaryIdentifierQueryHint(query);
@@ -4453,22 +4652,22 @@ var Indexer = class {
4453
4652
  this.projectRoot = projectRoot;
4454
4653
  this.config = config;
4455
4654
  this.indexPath = this.getIndexPath();
4456
- this.fileHashCachePath = path5.join(this.indexPath, "file-hashes.json");
4457
- this.failedBatchesPath = path5.join(this.indexPath, "failed-batches.json");
4458
- this.indexingLockPath = path5.join(this.indexPath, "indexing.lock");
4655
+ this.fileHashCachePath = path6.join(this.indexPath, "file-hashes.json");
4656
+ this.failedBatchesPath = path6.join(this.indexPath, "failed-batches.json");
4657
+ this.indexingLockPath = path6.join(this.indexPath, "indexing.lock");
4459
4658
  this.logger = initializeLogger(config.debug);
4460
4659
  }
4461
4660
  getIndexPath() {
4462
4661
  if (this.config.scope === "global") {
4463
4662
  const homeDir = process.env.HOME || process.env.USERPROFILE || "";
4464
- return path5.join(homeDir, ".opencode", "global-index");
4663
+ return path6.join(homeDir, ".opencode", "global-index");
4465
4664
  }
4466
- return path5.join(this.projectRoot, ".opencode", "index");
4665
+ return path6.join(this.projectRoot, ".opencode", "index");
4467
4666
  }
4468
4667
  loadFileHashCache() {
4469
4668
  try {
4470
4669
  if (existsSync4(this.fileHashCachePath)) {
4471
- const data = readFileSync4(this.fileHashCachePath, "utf-8");
4670
+ const data = readFileSync5(this.fileHashCachePath, "utf-8");
4472
4671
  const parsed = JSON.parse(data);
4473
4672
  this.fileHashCache = new Map(Object.entries(parsed));
4474
4673
  }
@@ -4485,7 +4684,7 @@ var Indexer = class {
4485
4684
  }
4486
4685
  atomicWriteSync(targetPath, data) {
4487
4686
  const tempPath = `${targetPath}.tmp`;
4488
- writeFileSync(tempPath, data);
4687
+ writeFileSync2(tempPath, data);
4489
4688
  renameSync(tempPath, targetPath);
4490
4689
  }
4491
4690
  checkForInterruptedIndexing() {
@@ -4496,7 +4695,7 @@ var Indexer = class {
4496
4695
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
4497
4696
  pid: process.pid
4498
4697
  };
4499
- writeFileSync(this.indexingLockPath, JSON.stringify(lockData));
4698
+ writeFileSync2(this.indexingLockPath, JSON.stringify(lockData));
4500
4699
  }
4501
4700
  releaseIndexingLock() {
4502
4701
  if (existsSync4(this.indexingLockPath)) {
@@ -4515,7 +4714,7 @@ var Indexer = class {
4515
4714
  loadFailedBatches() {
4516
4715
  try {
4517
4716
  if (existsSync4(this.failedBatchesPath)) {
4518
- const data = readFileSync4(this.failedBatchesPath, "utf-8");
4717
+ const data = readFileSync5(this.failedBatchesPath, "utf-8");
4519
4718
  return JSON.parse(data);
4520
4719
  }
4521
4720
  } catch {
@@ -4531,7 +4730,7 @@ var Indexer = class {
4531
4730
  }
4532
4731
  return;
4533
4732
  }
4534
- writeFileSync(this.failedBatchesPath, JSON.stringify(batches, null, 2));
4733
+ writeFileSync2(this.failedBatchesPath, JSON.stringify(batches, null, 2));
4535
4734
  }
4536
4735
  addFailedBatch(batch, error) {
4537
4736
  const existing = this.loadFailedBatches();
@@ -4590,13 +4789,13 @@ var Indexer = class {
4590
4789
  this.provider = createEmbeddingProvider(this.configuredProviderInfo);
4591
4790
  await fsPromises2.mkdir(this.indexPath, { recursive: true });
4592
4791
  const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
4593
- const storePath = path5.join(this.indexPath, "vectors");
4792
+ const storePath = path6.join(this.indexPath, "vectors");
4594
4793
  this.store = new VectorStore(storePath, dimensions);
4595
- const indexFilePath = path5.join(this.indexPath, "vectors.usearch");
4794
+ const indexFilePath = path6.join(this.indexPath, "vectors.usearch");
4596
4795
  if (existsSync4(indexFilePath)) {
4597
4796
  this.store.load();
4598
4797
  }
4599
- const invertedIndexPath = path5.join(this.indexPath, "inverted-index.json");
4798
+ const invertedIndexPath = path6.join(this.indexPath, "inverted-index.json");
4600
4799
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
4601
4800
  try {
4602
4801
  this.invertedIndex.load();
@@ -4606,7 +4805,7 @@ var Indexer = class {
4606
4805
  }
4607
4806
  this.invertedIndex = new InvertedIndex(invertedIndexPath);
4608
4807
  }
4609
- const dbPath = path5.join(this.indexPath, "codebase.db");
4808
+ const dbPath = path6.join(this.indexPath, "codebase.db");
4610
4809
  const dbIsNew = !existsSync4(dbPath);
4611
4810
  this.database = new Database(dbPath);
4612
4811
  if (this.checkForInterruptedIndexing()) {
@@ -4885,7 +5084,7 @@ var Indexer = class {
4885
5084
  for (const parsed of parsedFiles) {
4886
5085
  currentFilePaths.add(parsed.path);
4887
5086
  if (parsed.chunks.length === 0) {
4888
- const relativePath = path5.relative(this.projectRoot, parsed.path);
5087
+ const relativePath = path6.relative(this.projectRoot, parsed.path);
4889
5088
  stats.parseFailures.push(relativePath);
4890
5089
  }
4891
5090
  let fileChunkCount = 0;
@@ -5096,7 +5295,7 @@ var Indexer = class {
5096
5295
  for (const batch of dynamicBatches) {
5097
5296
  queue.add(async () => {
5098
5297
  if (rateLimitBackoffMs > 0) {
5099
- await new Promise((resolve4) => setTimeout(resolve4, rateLimitBackoffMs));
5298
+ await new Promise((resolve5) => setTimeout(resolve5, rateLimitBackoffMs));
5100
5299
  }
5101
5300
  try {
5102
5301
  const result = await pRetry(
@@ -5300,6 +5499,7 @@ var Indexer = class {
5300
5499
  const rrfK = this.config.search.rrfK;
5301
5500
  const rerankTopN = this.config.search.rerankTopN;
5302
5501
  const filterByBranch = options?.filterByBranch ?? true;
5502
+ const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
5303
5503
  this.logger.search("debug", "Starting search", {
5304
5504
  query,
5305
5505
  maxResults,
@@ -5351,7 +5551,8 @@ var Indexer = class {
5351
5551
  rrfK,
5352
5552
  rerankTopN,
5353
5553
  limit: maxResults,
5354
- hybridWeight
5554
+ hybridWeight,
5555
+ prioritizeSourcePaths: sourceIntent
5355
5556
  });
5356
5557
  const fusionMs = performance2.now() - fusionStartTime;
5357
5558
  const rescued = promoteIdentifierMatches(
@@ -5360,30 +5561,33 @@ var Indexer = class {
5360
5561
  semanticCandidates,
5361
5562
  keywordCandidates,
5362
5563
  database,
5363
- branchChunkIds
5564
+ branchChunkIds,
5565
+ sourceIntent
5364
5566
  );
5365
5567
  const union = unionCandidates(semanticCandidates, keywordCandidates);
5366
5568
  const deterministicIdentifierLane = buildDeterministicIdentifierPass(
5367
5569
  query,
5368
5570
  union,
5369
- maxResults
5571
+ maxResults,
5572
+ sourceIntent
5370
5573
  );
5371
5574
  const identifierLane = buildIdentifierDefinitionLane(
5372
5575
  query,
5373
5576
  union,
5374
- maxResults
5577
+ maxResults,
5578
+ sourceIntent
5375
5579
  );
5376
5580
  const symbolLane = buildSymbolDefinitionLane(
5377
5581
  query,
5378
5582
  database,
5379
5583
  branchChunkIds,
5380
5584
  maxResults,
5381
- union
5585
+ union,
5586
+ sourceIntent
5382
5587
  );
5383
5588
  const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
5384
5589
  const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
5385
5590
  const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
5386
- const sourceIntent = classifyQueryIntentRaw(query) === "source";
5387
5591
  const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
5388
5592
  const baseFiltered = tiered.filter((r) => {
5389
5593
  if (r.score < this.config.search.minScore) return false;
@@ -5740,7 +5944,896 @@ var Indexer = class {
5740
5944
  }
5741
5945
  };
5742
5946
 
5947
+ // src/eval/budget.ts
5948
+ function evaluateBudgetGate(budget, summary, comparison) {
5949
+ const BASELINE_P95_EPSILON_MS = 1e-3;
5950
+ const violations = [];
5951
+ const { thresholds } = budget;
5952
+ if (thresholds.minHitAt5 !== void 0 && summary.metrics.hitAt5 < thresholds.minHitAt5) {
5953
+ violations.push({
5954
+ metric: "minHitAt5",
5955
+ message: `Hit@5 ${summary.metrics.hitAt5.toFixed(4)} is below minimum ${thresholds.minHitAt5.toFixed(4)}`
5956
+ });
5957
+ }
5958
+ if (thresholds.minMrrAt10 !== void 0 && summary.metrics.mrrAt10 < thresholds.minMrrAt10) {
5959
+ violations.push({
5960
+ metric: "minMrrAt10",
5961
+ message: `MRR@10 ${summary.metrics.mrrAt10.toFixed(4)} is below minimum ${thresholds.minMrrAt10.toFixed(4)}`
5962
+ });
5963
+ }
5964
+ if (comparison) {
5965
+ if (thresholds.hitAt5MaxDrop !== void 0 && comparison.deltas.hitAt5.absolute < -thresholds.hitAt5MaxDrop) {
5966
+ violations.push({
5967
+ metric: "hitAt5MaxDrop",
5968
+ message: `Hit@5 drop ${comparison.deltas.hitAt5.absolute.toFixed(4)} exceeds allowed -${thresholds.hitAt5MaxDrop.toFixed(4)}`
5969
+ });
5970
+ }
5971
+ if (thresholds.mrrAt10MaxDrop !== void 0 && comparison.deltas.mrrAt10.absolute < -thresholds.mrrAt10MaxDrop) {
5972
+ violations.push({
5973
+ metric: "mrrAt10MaxDrop",
5974
+ message: `MRR@10 drop ${comparison.deltas.mrrAt10.absolute.toFixed(4)} exceeds allowed -${thresholds.mrrAt10MaxDrop.toFixed(4)}`
5975
+ });
5976
+ }
5977
+ if (thresholds.p95LatencyMaxMultiplier !== void 0) {
5978
+ const baselineP95 = comparison.deltas.latencyP95Ms.baseline;
5979
+ if (baselineP95 > BASELINE_P95_EPSILON_MS) {
5980
+ const allowed = baselineP95 * thresholds.p95LatencyMaxMultiplier;
5981
+ if (summary.metrics.latencyMs.p95 > allowed) {
5982
+ violations.push({
5983
+ metric: "p95LatencyMaxMultiplier",
5984
+ message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds allowed ${allowed.toFixed(3)}ms (${thresholds.p95LatencyMaxMultiplier.toFixed(2)}x baseline)`
5985
+ });
5986
+ }
5987
+ }
5988
+ }
5989
+ }
5990
+ if (thresholds.p95LatencyMaxAbsoluteMs !== void 0 && summary.metrics.latencyMs.p95 > thresholds.p95LatencyMaxAbsoluteMs) {
5991
+ violations.push({
5992
+ metric: "p95LatencyMaxAbsoluteMs",
5993
+ message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds absolute maximum ${thresholds.p95LatencyMaxAbsoluteMs.toFixed(3)}ms`
5994
+ });
5995
+ }
5996
+ return {
5997
+ passed: violations.length === 0,
5998
+ budgetName: budget.name,
5999
+ violations
6000
+ };
6001
+ }
6002
+
6003
+ // src/eval/metrics.ts
6004
+ function percentile(values, p) {
6005
+ if (values.length === 0) return 0;
6006
+ if (values.length === 1) return values[0];
6007
+ const sorted = [...values].sort((a, b) => a - b);
6008
+ const x = p * (sorted.length - 1);
6009
+ const lowerIndex = Math.floor(x);
6010
+ const upperIndex = Math.ceil(x);
6011
+ if (lowerIndex === upperIndex) {
6012
+ return sorted[lowerIndex];
6013
+ }
6014
+ const fraction = x - lowerIndex;
6015
+ return sorted[lowerIndex] + fraction * (sorted[upperIndex] - sorted[lowerIndex]);
6016
+ }
6017
+ function normalizePath(input) {
6018
+ return input.replace(/\\/g, "/");
6019
+ }
6020
+ function uniqueResultsByPath(results) {
6021
+ const seen = /* @__PURE__ */ new Set();
6022
+ const unique = [];
6023
+ for (const result of results) {
6024
+ const normalized = normalizePath(result.filePath);
6025
+ if (seen.has(normalized)) continue;
6026
+ seen.add(normalized);
6027
+ unique.push(result);
6028
+ }
6029
+ return unique;
6030
+ }
6031
+ function pathMatchesExpected(actualPath, expectedPath) {
6032
+ const actual = normalizePath(actualPath);
6033
+ const expected = normalizePath(expectedPath);
6034
+ if (actual === expected) return true;
6035
+ return actual.endsWith(`/${expected}`) || expected.endsWith(`/${actual}`);
6036
+ }
6037
+ function getRelevantPaths(query) {
6038
+ const fromExact = query.expected.filePath ? [query.expected.filePath] : [];
6039
+ const fromAcceptable = query.expected.acceptableFiles ?? [];
6040
+ return Array.from(/* @__PURE__ */ new Set([...fromExact, ...fromAcceptable]));
6041
+ }
6042
+ function isRelevantResult(filePath, relevantPaths) {
6043
+ return relevantPaths.some((expected) => pathMatchesExpected(filePath, expected));
6044
+ }
6045
+ function reciprocalRankAtK(results, relevantPaths, k) {
6046
+ const top = uniqueResultsByPath(results).slice(0, k);
6047
+ for (let i = 0; i < top.length; i += 1) {
6048
+ if (isRelevantResult(top[i].filePath, relevantPaths)) {
6049
+ return 1 / (i + 1);
6050
+ }
6051
+ }
6052
+ return 0;
6053
+ }
6054
+ function ndcgAtK(results, relevantPaths, k) {
6055
+ const top = uniqueResultsByPath(results).slice(0, k);
6056
+ const dcg = top.reduce((sum, result, i) => {
6057
+ const rel = isRelevantResult(result.filePath, relevantPaths) ? 1 : 0;
6058
+ return sum + rel / Math.log2(i + 2);
6059
+ }, 0);
6060
+ const idealLen = Math.min(k, relevantPaths.length);
6061
+ const idcg = Array.from({ length: idealLen }, (_, i) => 1 / Math.log2(i + 2)).reduce(
6062
+ (sum, value) => sum + value,
6063
+ 0
6064
+ );
6065
+ return idcg === 0 ? 0 : dcg / idcg;
6066
+ }
6067
+ function isDocsOrTestsPath(filePath) {
6068
+ const lowered = normalizePath(filePath).toLowerCase();
6069
+ return lowered.includes("/docs/") || lowered.includes("/test/") || lowered.includes("/tests/") || lowered.includes("readme") || lowered.includes("/benchmarks/");
6070
+ }
6071
+ function classifyFailureBucket(query, results, k) {
6072
+ const relevantPaths = getRelevantPaths(query);
6073
+ const top = uniqueResultsByPath(results).slice(0, k);
6074
+ const hasRelevantTopK = top.some((result) => isRelevantResult(result.filePath, relevantPaths));
6075
+ if (!hasRelevantTopK) {
6076
+ return "no-relevant-hit-top-k";
6077
+ }
6078
+ if (query.expected.symbol) {
6079
+ const hasSymbol = top.some(
6080
+ (result) => isRelevantResult(result.filePath, relevantPaths) && result.name === query.expected.symbol
6081
+ );
6082
+ if (!hasSymbol) return "wrong-symbol";
6083
+ }
6084
+ const top1 = top[0];
6085
+ if (top1 && !isRelevantResult(top1.filePath, relevantPaths) && isDocsOrTestsPath(top1.filePath)) {
6086
+ return "docs-tests-outranking-source";
6087
+ }
6088
+ if (top1 && !isRelevantResult(top1.filePath, relevantPaths)) {
6089
+ return "wrong-file";
6090
+ }
6091
+ return void 0;
6092
+ }
6093
+ function buildPerQueryResult(query, results, latencyMs, k) {
6094
+ const relevantPaths = getRelevantPaths(query);
6095
+ const deduped = uniqueResultsByPath(results);
6096
+ const hitAt = (cutoff) => deduped.slice(0, cutoff).some((result) => isRelevantResult(result.filePath, relevantPaths));
6097
+ const perQuery = {
6098
+ id: query.id,
6099
+ query: query.query,
6100
+ queryType: query.queryType,
6101
+ latencyMs,
6102
+ hitAt1: hitAt(1),
6103
+ hitAt3: hitAt(3),
6104
+ hitAt5: hitAt(5),
6105
+ hitAt10: hitAt(10),
6106
+ reciprocalRankAt10: reciprocalRankAtK(deduped, relevantPaths, 10),
6107
+ ndcgAt10: ndcgAtK(deduped, relevantPaths, 10),
6108
+ failureBucket: classifyFailureBucket(query, results, k),
6109
+ results: deduped
6110
+ };
6111
+ return perQuery;
6112
+ }
6113
+ function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingTokensUsed, costPer1MTokensUsd) {
6114
+ const count = perQuery.length;
6115
+ const safeDiv = (value) => count === 0 ? 0 : value / count;
6116
+ const sum = {
6117
+ hitAt1: 0,
6118
+ hitAt3: 0,
6119
+ hitAt5: 0,
6120
+ hitAt10: 0,
6121
+ mrrAt10: 0,
6122
+ ndcgAt10: 0
6123
+ };
6124
+ const failureBuckets = {
6125
+ "wrong-file": 0,
6126
+ "wrong-symbol": 0,
6127
+ "docs-tests-outranking-source": 0,
6128
+ "no-relevant-hit-top-k": 0
6129
+ };
6130
+ const latencies = perQuery.map((item) => item.latencyMs);
6131
+ for (const query of perQuery) {
6132
+ if (query.hitAt1) sum.hitAt1 += 1;
6133
+ if (query.hitAt3) sum.hitAt3 += 1;
6134
+ if (query.hitAt5) sum.hitAt5 += 1;
6135
+ if (query.hitAt10) sum.hitAt10 += 1;
6136
+ sum.mrrAt10 += query.reciprocalRankAt10;
6137
+ sum.ndcgAt10 += query.ndcgAt10;
6138
+ if (query.failureBucket) {
6139
+ failureBuckets[query.failureBucket] += 1;
6140
+ }
6141
+ }
6142
+ const queryTokens = queries.reduce((acc, q) => acc + estimateTokens(q.query), 0);
6143
+ return {
6144
+ hitAt1: safeDiv(sum.hitAt1),
6145
+ hitAt3: safeDiv(sum.hitAt3),
6146
+ hitAt5: safeDiv(sum.hitAt5),
6147
+ hitAt10: safeDiv(sum.hitAt10),
6148
+ mrrAt10: safeDiv(sum.mrrAt10),
6149
+ ndcgAt10: safeDiv(sum.ndcgAt10),
6150
+ latencyMs: {
6151
+ p50: percentile(latencies, 0.5),
6152
+ p95: percentile(latencies, 0.95),
6153
+ p99: percentile(latencies, 0.99)
6154
+ },
6155
+ tokenEstimate: {
6156
+ queryTokens,
6157
+ embeddingTokensUsed
6158
+ },
6159
+ embedding: {
6160
+ callCount: embeddingCallCount,
6161
+ estimatedCostUsd: embeddingTokensUsed / 1e6 * costPer1MTokensUsd,
6162
+ costPer1MTokensUsd
6163
+ },
6164
+ failureBuckets
6165
+ };
6166
+ }
6167
+
6168
+ // src/eval/schema.ts
6169
+ import { readFileSync as readFileSync6 } from "fs";
6170
+ function parseJsonFile(filePath) {
6171
+ const content = readFileSync6(filePath, "utf-8");
6172
+ return JSON.parse(content);
6173
+ }
6174
+ function isRecord(value) {
6175
+ return typeof value === "object" && value !== null;
6176
+ }
6177
+ function isStringArray2(value) {
6178
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
6179
+ }
6180
+ function asPositiveNumber(value, path10) {
6181
+ if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
6182
+ throw new Error(`${path10} must be a non-negative number`);
6183
+ }
6184
+ return value;
6185
+ }
6186
+ function parseQueryType(value, path10) {
6187
+ if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
6188
+ return value;
6189
+ }
6190
+ throw new Error(
6191
+ `${path10} must be one of: definition, implementation-intent, similarity, keyword-heavy`
6192
+ );
6193
+ }
6194
+ function parseExpected(input, path10) {
6195
+ if (!isRecord(input)) {
6196
+ throw new Error(`${path10} must be an object`);
6197
+ }
6198
+ const filePathRaw = input.filePath;
6199
+ const acceptableFilesRaw = input.acceptableFiles;
6200
+ const symbolRaw = input.symbol;
6201
+ const branchRaw = input.branch;
6202
+ const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
6203
+ const acceptableFiles = isStringArray2(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
6204
+ if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
6205
+ throw new Error(`${path10} must include either expected.filePath or expected.acceptableFiles`);
6206
+ }
6207
+ if (acceptableFilesRaw !== void 0 && !isStringArray2(acceptableFilesRaw)) {
6208
+ throw new Error(`${path10}.acceptableFiles must be an array of strings`);
6209
+ }
6210
+ if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
6211
+ throw new Error(`${path10}.symbol must be a string when provided`);
6212
+ }
6213
+ if (branchRaw !== void 0 && typeof branchRaw !== "string") {
6214
+ throw new Error(`${path10}.branch must be a string when provided`);
6215
+ }
6216
+ return {
6217
+ filePath,
6218
+ acceptableFiles,
6219
+ symbol: typeof symbolRaw === "string" ? symbolRaw : void 0,
6220
+ branch: typeof branchRaw === "string" ? branchRaw : void 0
6221
+ };
6222
+ }
6223
+ function parseQuery(input, index) {
6224
+ const path10 = `queries[${index}]`;
6225
+ if (!isRecord(input)) {
6226
+ throw new Error(`${path10} must be an object`);
6227
+ }
6228
+ const id = input.id;
6229
+ const query = input.query;
6230
+ const queryType = input.queryType;
6231
+ const expected = input.expected;
6232
+ if (typeof id !== "string" || id.trim().length === 0) {
6233
+ throw new Error(`${path10}.id must be a non-empty string`);
6234
+ }
6235
+ if (typeof query !== "string" || query.trim().length === 0) {
6236
+ throw new Error(`${path10}.query must be a non-empty string`);
6237
+ }
6238
+ return {
6239
+ id,
6240
+ query,
6241
+ queryType: parseQueryType(queryType, `${path10}.queryType`),
6242
+ expected: parseExpected(expected, `${path10}.expected`)
6243
+ };
6244
+ }
6245
+ function parseGoldenDataset(raw, sourceLabel) {
6246
+ if (!isRecord(raw)) {
6247
+ throw new Error(`${sourceLabel} must be a JSON object`);
6248
+ }
6249
+ const version = raw.version;
6250
+ const name = raw.name;
6251
+ const description = raw.description;
6252
+ const queriesRaw = raw.queries;
6253
+ if (typeof version !== "string" || version.trim().length === 0) {
6254
+ throw new Error(`${sourceLabel}.version must be a non-empty string`);
6255
+ }
6256
+ if (typeof name !== "string" || name.trim().length === 0) {
6257
+ throw new Error(`${sourceLabel}.name must be a non-empty string`);
6258
+ }
6259
+ if (description !== void 0 && typeof description !== "string") {
6260
+ throw new Error(`${sourceLabel}.description must be a string when provided`);
6261
+ }
6262
+ if (!Array.isArray(queriesRaw)) {
6263
+ throw new Error(`${sourceLabel}.queries must be an array`);
6264
+ }
6265
+ if (queriesRaw.length === 0) {
6266
+ throw new Error(`${sourceLabel}.queries must contain at least one query`);
6267
+ }
6268
+ const queries = queriesRaw.map((query, idx) => parseQuery(query, idx));
6269
+ const idSet = /* @__PURE__ */ new Set();
6270
+ for (const query of queries) {
6271
+ if (idSet.has(query.id)) {
6272
+ throw new Error(`${sourceLabel}.queries has duplicate id: ${query.id}`);
6273
+ }
6274
+ idSet.add(query.id);
6275
+ }
6276
+ return {
6277
+ version,
6278
+ name,
6279
+ description: typeof description === "string" ? description : void 0,
6280
+ queries
6281
+ };
6282
+ }
6283
+ function loadGoldenDataset(datasetPath) {
6284
+ const parsed = parseJsonFile(datasetPath);
6285
+ return parseGoldenDataset(parsed, datasetPath);
6286
+ }
6287
+ function parseBudget(raw, sourceLabel) {
6288
+ if (!isRecord(raw)) {
6289
+ throw new Error(`${sourceLabel} must be a JSON object`);
6290
+ }
6291
+ const name = raw.name;
6292
+ const baselinePath = raw.baselinePath;
6293
+ const failOnMissingBaseline = raw.failOnMissingBaseline;
6294
+ const thresholds = raw.thresholds;
6295
+ if (typeof name !== "string" || name.trim().length === 0) {
6296
+ throw new Error(`${sourceLabel}.name must be a non-empty string`);
6297
+ }
6298
+ if (baselinePath !== void 0 && typeof baselinePath !== "string") {
6299
+ throw new Error(`${sourceLabel}.baselinePath must be a string when provided`);
6300
+ }
6301
+ if (!isRecord(thresholds)) {
6302
+ throw new Error(`${sourceLabel}.thresholds must be an object`);
6303
+ }
6304
+ return {
6305
+ name,
6306
+ baselinePath: typeof baselinePath === "string" ? baselinePath : void 0,
6307
+ failOnMissingBaseline: typeof failOnMissingBaseline === "boolean" ? failOnMissingBaseline : true,
6308
+ thresholds: {
6309
+ hitAt5MaxDrop: thresholds.hitAt5MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.hitAt5MaxDrop, `${sourceLabel}.thresholds.hitAt5MaxDrop`),
6310
+ mrrAt10MaxDrop: thresholds.mrrAt10MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.mrrAt10MaxDrop, `${sourceLabel}.thresholds.mrrAt10MaxDrop`),
6311
+ p95LatencyMaxMultiplier: thresholds.p95LatencyMaxMultiplier === void 0 ? void 0 : asPositiveNumber(
6312
+ thresholds.p95LatencyMaxMultiplier,
6313
+ `${sourceLabel}.thresholds.p95LatencyMaxMultiplier`
6314
+ ),
6315
+ p95LatencyMaxAbsoluteMs: thresholds.p95LatencyMaxAbsoluteMs === void 0 ? void 0 : asPositiveNumber(
6316
+ thresholds.p95LatencyMaxAbsoluteMs,
6317
+ `${sourceLabel}.thresholds.p95LatencyMaxAbsoluteMs`
6318
+ ),
6319
+ minHitAt5: thresholds.minHitAt5 === void 0 ? void 0 : asPositiveNumber(thresholds.minHitAt5, `${sourceLabel}.thresholds.minHitAt5`),
6320
+ minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`)
6321
+ }
6322
+ };
6323
+ }
6324
+ function loadBudget(budgetPath) {
6325
+ const parsed = parseJsonFile(budgetPath);
6326
+ return parseBudget(parsed, budgetPath);
6327
+ }
6328
+
6329
+ // src/eval/runner.ts
6330
+ function toAbsolute(projectRoot, maybeRelative) {
6331
+ return path7.isAbsolute(maybeRelative) ? maybeRelative : path7.join(projectRoot, maybeRelative);
6332
+ }
6333
+ function loadRawConfig(projectRoot, configPath) {
6334
+ const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
6335
+ if (fromPath && existsSync5(fromPath)) {
6336
+ return JSON.parse(readFileSync7(fromPath, "utf-8"));
6337
+ }
6338
+ const projectConfig = path7.join(projectRoot, ".opencode", "codebase-index.json");
6339
+ if (existsSync5(projectConfig)) {
6340
+ return JSON.parse(readFileSync7(projectConfig, "utf-8"));
6341
+ }
6342
+ const globalConfig = path7.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
6343
+ if (existsSync5(globalConfig)) {
6344
+ return JSON.parse(readFileSync7(globalConfig, "utf-8"));
6345
+ }
6346
+ return {};
6347
+ }
6348
+ function getIndexRootPath(projectRoot, scope) {
6349
+ if (scope === "global") {
6350
+ return path7.join(os3.homedir(), ".opencode", "global-index");
6351
+ }
6352
+ return path7.join(projectRoot, ".opencode", "index");
6353
+ }
6354
+ function clearIndexRoot(projectRoot, scope) {
6355
+ const indexRoot = getIndexRootPath(projectRoot, scope);
6356
+ if (existsSync5(indexRoot)) {
6357
+ rmSync(indexRoot, { recursive: true, force: true });
6358
+ }
6359
+ }
6360
+ function loadParsedConfig(projectRoot, configPath) {
6361
+ const raw = loadRawConfig(projectRoot, configPath);
6362
+ return parseConfig(raw);
6363
+ }
6364
+ function resolveSearchConfig(parsedConfig, overrides) {
6365
+ const nextSearch = {
6366
+ ...parsedConfig.search
6367
+ };
6368
+ if (overrides?.fusionStrategy !== void 0) {
6369
+ nextSearch.fusionStrategy = overrides.fusionStrategy;
6370
+ }
6371
+ if (overrides?.hybridWeight !== void 0) {
6372
+ nextSearch.hybridWeight = overrides.hybridWeight;
6373
+ }
6374
+ if (overrides?.rrfK !== void 0) {
6375
+ nextSearch.rrfK = overrides.rrfK;
6376
+ }
6377
+ if (overrides?.rerankTopN !== void 0) {
6378
+ nextSearch.rerankTopN = overrides.rerankTopN;
6379
+ }
6380
+ return {
6381
+ ...parsedConfig,
6382
+ search: nextSearch
6383
+ };
6384
+ }
6385
+ async function runEvaluation(options) {
6386
+ const datasetPath = toAbsolute(options.projectRoot, options.datasetPath);
6387
+ const againstPath = options.againstPath ? toAbsolute(options.projectRoot, options.againstPath) : void 0;
6388
+ const budgetPath = options.budgetPath ? toAbsolute(options.projectRoot, options.budgetPath) : void 0;
6389
+ const dataset = loadGoldenDataset(datasetPath);
6390
+ const parsedConfig = loadParsedConfig(options.projectRoot, options.configPath);
6391
+ const effectiveConfig = resolveSearchConfig(parsedConfig, options.searchOverrides);
6392
+ if (options.reindex) {
6393
+ clearIndexRoot(options.projectRoot, effectiveConfig.scope);
6394
+ }
6395
+ const indexer = new Indexer(options.projectRoot, effectiveConfig);
6396
+ await indexer.index();
6397
+ const perQuery = [];
6398
+ for (const query of dataset.queries) {
6399
+ if (query.expected.branch && query.expected.branch !== indexer.getCurrentBranch()) {
6400
+ throw new Error(
6401
+ `Query '${query.id}' expects branch '${query.expected.branch}', but current branch is '${indexer.getCurrentBranch()}'. Switch branch before running this dataset.`
6402
+ );
6403
+ }
6404
+ const start = performance3.now();
6405
+ const result = await indexer.search(query.query, 10, {
6406
+ metadataOnly: true,
6407
+ filterByBranch: query.expected.branch ? true : false
6408
+ });
6409
+ const elapsed = performance3.now() - start;
6410
+ const materialized = result.map((item) => ({
6411
+ filePath: item.filePath,
6412
+ startLine: item.startLine,
6413
+ endLine: item.endLine,
6414
+ score: item.score,
6415
+ chunkType: item.chunkType,
6416
+ name: item.name
6417
+ }));
6418
+ perQuery.push(buildPerQueryResult(query, materialized, elapsed, 10));
6419
+ }
6420
+ const logger = indexer.getLogger();
6421
+ const metricSnapshot = logger.getMetrics();
6422
+ const costPer1MTokensUsd = effectiveConfig.embeddingProvider === "custom" || effectiveConfig.embeddingProvider === "auto" ? 0 : getDefaultModelForProvider(effectiveConfig.embeddingProvider).costPer1MTokens;
6423
+ const summary = {
6424
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6425
+ projectRoot: options.projectRoot,
6426
+ datasetPath,
6427
+ datasetName: dataset.name,
6428
+ datasetVersion: dataset.version,
6429
+ queryCount: dataset.queries.length,
6430
+ topK: 10,
6431
+ searchConfig: {
6432
+ fusionStrategy: effectiveConfig.search.fusionStrategy,
6433
+ hybridWeight: effectiveConfig.search.hybridWeight,
6434
+ rrfK: effectiveConfig.search.rrfK,
6435
+ rerankTopN: effectiveConfig.search.rerankTopN
6436
+ },
6437
+ metrics: computeEvalMetrics(
6438
+ dataset.queries,
6439
+ perQuery,
6440
+ metricSnapshot.embeddingApiCalls,
6441
+ metricSnapshot.embeddingTokensUsed,
6442
+ costPer1MTokensUsd
6443
+ )
6444
+ };
6445
+ const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
6446
+ const perQueryArtifact = buildPerQueryArtifact(perQuery);
6447
+ writeJson(path7.join(outputDir, "summary.json"), summary);
6448
+ writeJson(path7.join(outputDir, "per-query.json"), perQueryArtifact);
6449
+ let comparison;
6450
+ if (againstPath) {
6451
+ const baseline = loadSummary(againstPath);
6452
+ comparison = compareSummaries(summary, baseline, againstPath);
6453
+ writeJson(path7.join(outputDir, "compare.json"), comparison);
6454
+ }
6455
+ let gate;
6456
+ if (options.ciMode) {
6457
+ if (!budgetPath) {
6458
+ throw new Error("CI mode requires --budget path");
6459
+ }
6460
+ const budget = loadBudget(budgetPath);
6461
+ if (!comparison && budget.baselinePath) {
6462
+ const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
6463
+ if (existsSync5(resolvedBaseline)) {
6464
+ const baselineSummary = loadSummary(resolvedBaseline);
6465
+ comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
6466
+ writeJson(path7.join(outputDir, "compare.json"), comparison);
6467
+ } else if (budget.failOnMissingBaseline) {
6468
+ throw new Error(
6469
+ `Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
6470
+ );
6471
+ }
6472
+ }
6473
+ gate = evaluateBudgetGate(budget, summary, comparison);
6474
+ }
6475
+ const markdown = createSummaryMarkdown(summary, comparison, gate);
6476
+ writeText(path7.join(outputDir, "summary.md"), markdown);
6477
+ return { outputDir, summary, perQuery, comparison, gate };
6478
+ }
6479
+ async function runSweep(options, sweep) {
6480
+ const fusionValues = sweep.fusionStrategy && sweep.fusionStrategy.length > 0 ? [...sweep.fusionStrategy] : [void 0];
6481
+ const weightValues = sweep.hybridWeight && sweep.hybridWeight.length > 0 ? [...sweep.hybridWeight] : [void 0];
6482
+ const rrfValues = sweep.rrfK && sweep.rrfK.length > 0 ? [...sweep.rrfK] : [void 0];
6483
+ const rerankValues = sweep.rerankTopN && sweep.rerankTopN.length > 0 ? [...sweep.rerankTopN] : [void 0];
6484
+ const runs = [];
6485
+ for (const fusion of fusionValues) {
6486
+ for (const hybridWeight of weightValues) {
6487
+ for (const rrfK of rrfValues) {
6488
+ for (const rerankTopN of rerankValues) {
6489
+ const run = await runEvaluation({
6490
+ ...options,
6491
+ searchOverrides: {
6492
+ ...fusion !== void 0 ? { fusionStrategy: fusion } : {},
6493
+ ...hybridWeight !== void 0 ? { hybridWeight } : {},
6494
+ ...rrfK !== void 0 ? { rrfK } : {},
6495
+ ...rerankTopN !== void 0 ? { rerankTopN } : {}
6496
+ }
6497
+ });
6498
+ runs.push({
6499
+ searchConfig: run.summary.searchConfig,
6500
+ summary: run.summary,
6501
+ comparison: run.comparison,
6502
+ gate: run.gate
6503
+ });
6504
+ }
6505
+ }
6506
+ }
6507
+ }
6508
+ const bestByHitAt5 = [...runs].sort(
6509
+ (a, b) => b.summary.metrics.hitAt5 - a.summary.metrics.hitAt5
6510
+ )[0];
6511
+ const bestByMrrAt10 = [...runs].sort(
6512
+ (a, b) => b.summary.metrics.mrrAt10 - a.summary.metrics.mrrAt10
6513
+ )[0];
6514
+ const bestByP95Latency = [...runs].sort(
6515
+ (a, b) => a.summary.metrics.latencyMs.p95 - b.summary.metrics.latencyMs.p95
6516
+ )[0];
6517
+ const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
6518
+ const failedGateRuns = runs.filter((run) => run.gate && !run.gate.passed).length;
6519
+ const gatePassed = failedGateRuns === 0;
6520
+ const aggregate = {
6521
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6522
+ againstPath: options.againstPath,
6523
+ runCount: runs.length,
6524
+ runs,
6525
+ gatePassed,
6526
+ failedGateRuns,
6527
+ bestByHitAt5,
6528
+ bestByMrrAt10,
6529
+ bestByP95Latency
6530
+ };
6531
+ writeJson(path7.join(outputDir, "compare.json"), aggregate);
6532
+ const md = createSummaryMarkdown(
6533
+ bestByHitAt5?.summary ?? runs[0].summary,
6534
+ bestByHitAt5?.comparison,
6535
+ void 0,
6536
+ aggregate
6537
+ );
6538
+ writeText(path7.join(outputDir, "summary.md"), md);
6539
+ writeJson(path7.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
6540
+ return { outputDir, aggregate };
6541
+ }
6542
+
6543
+ // src/eval/cli.ts
6544
+ function printUsage() {
6545
+ console.log(`
6546
+ Usage:
6547
+ opencode-codebase-index-mcp eval run [options]
6548
+ opencode-codebase-index-mcp eval compare --against <summary.json> [options]
6549
+ opencode-codebase-index-mcp eval diff --current <summary.json> --against <summary.json> [options]
6550
+
6551
+ Options:
6552
+ --project <path> Project root (default: cwd)
6553
+ --config <path> Config JSON path
6554
+ --dataset <path> Golden dataset path (default: benchmarks/golden/small.json)
6555
+ --current <path> Current summary.json path (required for eval diff)
6556
+ --output <path> Output root dir (default: benchmarks/results)
6557
+ --against <path> Baseline summary.json to compare against
6558
+ --budget <path> Budget file for CI mode (default: benchmarks/budgets/default.json)
6559
+ --ci Enable CI gate mode
6560
+ --reindex Force reindex before eval
6561
+
6562
+ Search overrides:
6563
+ --fusionStrategy <rrf|weighted>
6564
+ --hybridWeight <0-1>
6565
+ --rrfK <number>
6566
+ --rerankTopN <number>
6567
+
6568
+ Sweep options (comma-separated values):
6569
+ --sweepFusionStrategy <rrf,weighted>
6570
+ --sweepHybridWeight <0.3,0.5,0.7>
6571
+ --sweepRrfK <30,60,90>
6572
+ --sweepRerankTopN <10,20,40>
6573
+ `);
6574
+ }
6575
+ function parseNumber(value, flag) {
6576
+ const parsed = Number(value);
6577
+ if (Number.isNaN(parsed)) {
6578
+ throw new Error(`${flag} must be a number`);
6579
+ }
6580
+ return parsed;
6581
+ }
6582
+ function parseCsvNumbers(value, flag) {
6583
+ return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0).map((item) => parseNumber(item, flag));
6584
+ }
6585
+ function parseCsvFusion(value) {
6586
+ const values = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
6587
+ const parsed = [];
6588
+ for (const candidate of values) {
6589
+ if (candidate !== "rrf" && candidate !== "weighted") {
6590
+ throw new Error("--sweepFusionStrategy accepts only rrf,weighted");
6591
+ }
6592
+ parsed.push(candidate);
6593
+ }
6594
+ return parsed;
6595
+ }
6596
+ function hasSweepOptions(sweep) {
6597
+ return Boolean(
6598
+ sweep.fusionStrategy && sweep.fusionStrategy.length > 0 || sweep.hybridWeight && sweep.hybridWeight.length > 0 || sweep.rrfK && sweep.rrfK.length > 0 || sweep.rerankTopN && sweep.rerankTopN.length > 0
6599
+ );
6600
+ }
6601
+ function parseEvalArgs(argv, cwd) {
6602
+ const parsed = {
6603
+ projectRoot: cwd,
6604
+ datasetPath: "benchmarks/golden/small.json",
6605
+ outputRoot: "benchmarks/results",
6606
+ budgetPath: "benchmarks/budgets/default.json",
6607
+ ciMode: false,
6608
+ reindex: false,
6609
+ sweep: {}
6610
+ };
6611
+ for (let i = 0; i < argv.length; i += 1) {
6612
+ const arg = argv[i];
6613
+ const next = argv[i + 1];
6614
+ if (arg === "--project" && next) {
6615
+ parsed.projectRoot = path8.resolve(cwd, next);
6616
+ i += 1;
6617
+ continue;
6618
+ }
6619
+ if (arg === "--config" && next) {
6620
+ parsed.configPath = path8.resolve(cwd, next);
6621
+ i += 1;
6622
+ continue;
6623
+ }
6624
+ if (arg === "--dataset" && next) {
6625
+ parsed.datasetPath = next;
6626
+ i += 1;
6627
+ continue;
6628
+ }
6629
+ if (arg === "--current" && next) {
6630
+ parsed.currentPath = next;
6631
+ i += 1;
6632
+ continue;
6633
+ }
6634
+ if (arg === "--output" && next) {
6635
+ parsed.outputRoot = next;
6636
+ i += 1;
6637
+ continue;
6638
+ }
6639
+ if (arg === "--against" && next) {
6640
+ parsed.againstPath = next;
6641
+ i += 1;
6642
+ continue;
6643
+ }
6644
+ if (arg === "--budget" && next) {
6645
+ parsed.budgetPath = next;
6646
+ i += 1;
6647
+ continue;
6648
+ }
6649
+ if (arg === "--ci") {
6650
+ parsed.ciMode = true;
6651
+ continue;
6652
+ }
6653
+ if (arg === "--reindex") {
6654
+ parsed.reindex = true;
6655
+ continue;
6656
+ }
6657
+ if (arg === "--fusionStrategy" && next) {
6658
+ if (next !== "rrf" && next !== "weighted") {
6659
+ throw new Error("--fusionStrategy must be rrf or weighted");
6660
+ }
6661
+ parsed.fusionStrategy = next;
6662
+ i += 1;
6663
+ continue;
6664
+ }
6665
+ if (arg === "--hybridWeight" && next) {
6666
+ parsed.hybridWeight = parseNumber(next, "--hybridWeight");
6667
+ i += 1;
6668
+ continue;
6669
+ }
6670
+ if (arg === "--rrfK" && next) {
6671
+ parsed.rrfK = parseNumber(next, "--rrfK");
6672
+ i += 1;
6673
+ continue;
6674
+ }
6675
+ if (arg === "--rerankTopN" && next) {
6676
+ parsed.rerankTopN = parseNumber(next, "--rerankTopN");
6677
+ i += 1;
6678
+ continue;
6679
+ }
6680
+ if (arg === "--sweepFusionStrategy" && next) {
6681
+ parsed.sweep.fusionStrategy = parseCsvFusion(next);
6682
+ i += 1;
6683
+ continue;
6684
+ }
6685
+ if (arg === "--sweepHybridWeight" && next) {
6686
+ parsed.sweep.hybridWeight = parseCsvNumbers(next, "--sweepHybridWeight");
6687
+ i += 1;
6688
+ continue;
6689
+ }
6690
+ if (arg === "--sweepRrfK" && next) {
6691
+ parsed.sweep.rrfK = parseCsvNumbers(next, "--sweepRrfK");
6692
+ i += 1;
6693
+ continue;
6694
+ }
6695
+ if (arg === "--sweepRerankTopN" && next) {
6696
+ parsed.sweep.rerankTopN = parseCsvNumbers(next, "--sweepRerankTopN");
6697
+ i += 1;
6698
+ continue;
6699
+ }
6700
+ }
6701
+ return parsed;
6702
+ }
6703
+ function parseEvalSubcommandOptions(argv, cwd) {
6704
+ let explicitAgainst;
6705
+ const filtered = [];
6706
+ for (let i = 0; i < argv.length; i += 1) {
6707
+ const current = argv[i];
6708
+ const next = argv[i + 1];
6709
+ if (current === "--against" && next) {
6710
+ explicitAgainst = next;
6711
+ i += 1;
6712
+ continue;
6713
+ }
6714
+ filtered.push(current);
6715
+ }
6716
+ return {
6717
+ parsed: parseEvalArgs(filtered, cwd),
6718
+ explicitAgainst
6719
+ };
6720
+ }
6721
+ function toRunOptions(parsed) {
6722
+ return {
6723
+ projectRoot: parsed.projectRoot,
6724
+ configPath: parsed.configPath,
6725
+ datasetPath: parsed.datasetPath,
6726
+ outputRoot: parsed.outputRoot,
6727
+ againstPath: parsed.againstPath,
6728
+ budgetPath: parsed.budgetPath,
6729
+ ciMode: parsed.ciMode,
6730
+ reindex: parsed.reindex,
6731
+ searchOverrides: {
6732
+ ...parsed.fusionStrategy !== void 0 ? { fusionStrategy: parsed.fusionStrategy } : {},
6733
+ ...parsed.hybridWeight !== void 0 ? { hybridWeight: parsed.hybridWeight } : {},
6734
+ ...parsed.rrfK !== void 0 ? { rrfK: parsed.rrfK } : {},
6735
+ ...parsed.rerankTopN !== void 0 ? { rerankTopN: parsed.rerankTopN } : {}
6736
+ }
6737
+ };
6738
+ }
6739
+ async function handleEvalCommand(args, cwd) {
6740
+ const subcommand = args[0];
6741
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
6742
+ printUsage();
6743
+ return 0;
6744
+ }
6745
+ if (subcommand === "run") {
6746
+ const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
6747
+ if (explicitAgainst) {
6748
+ parsed.againstPath = explicitAgainst;
6749
+ }
6750
+ const runOptions = toRunOptions(parsed);
6751
+ if (hasSweepOptions(parsed.sweep)) {
6752
+ const sweep = await runSweep(runOptions, parsed.sweep);
6753
+ console.log(`Eval sweep complete. Artifacts: ${sweep.outputDir}`);
6754
+ console.log(`Sweep runs: ${sweep.aggregate.runCount}`);
6755
+ if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
6756
+ console.error(
6757
+ `[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
6758
+ );
6759
+ return 1;
6760
+ }
6761
+ return 0;
6762
+ }
6763
+ const result = await runEvaluation(runOptions);
6764
+ console.log(`Eval run complete. Artifacts: ${result.outputDir}`);
6765
+ console.log(
6766
+ `Hit@5=${(result.summary.metrics.hitAt5 * 100).toFixed(2)}% MRR@10=${result.summary.metrics.mrrAt10.toFixed(4)} p95=${result.summary.metrics.latencyMs.p95.toFixed(3)}ms`
6767
+ );
6768
+ if (result.gate && !result.gate.passed) {
6769
+ for (const violation of result.gate.violations) {
6770
+ console.error(`[CI-GATE] ${violation.metric}: ${violation.message}`);
6771
+ }
6772
+ return 1;
6773
+ }
6774
+ return 0;
6775
+ }
6776
+ if (subcommand === "compare") {
6777
+ const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
6778
+ if (!explicitAgainst) {
6779
+ throw new Error("eval compare requires --against <baseline summary.json>");
6780
+ }
6781
+ parsed.againstPath = explicitAgainst;
6782
+ const runOptions = toRunOptions(parsed);
6783
+ if (hasSweepOptions(parsed.sweep)) {
6784
+ const sweep = await runSweep(runOptions, parsed.sweep);
6785
+ console.log(`Eval compare sweep complete. Artifacts: ${sweep.outputDir}`);
6786
+ if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
6787
+ console.error(
6788
+ `[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
6789
+ );
6790
+ return 1;
6791
+ }
6792
+ return 0;
6793
+ }
6794
+ const result = await runEvaluation(runOptions);
6795
+ console.log(`Eval compare complete. Artifacts: ${result.outputDir}`);
6796
+ return 0;
6797
+ }
6798
+ if (subcommand === "diff") {
6799
+ const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
6800
+ if (!explicitAgainst) {
6801
+ throw new Error("eval diff requires --against <baseline summary.json>");
6802
+ }
6803
+ if (!parsed.currentPath) {
6804
+ throw new Error("eval diff requires --current <current summary.json>");
6805
+ }
6806
+ parsed.againstPath = explicitAgainst;
6807
+ const currentPath = parsed.currentPath;
6808
+ if (!currentPath.endsWith(".json")) {
6809
+ throw new Error("eval diff --current must point to a summary JSON file");
6810
+ }
6811
+ if (!parsed.againstPath.endsWith(".json")) {
6812
+ throw new Error("eval diff --against must point to a summary JSON file");
6813
+ }
6814
+ const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath));
6815
+ const baselineSummary = loadSummary(path8.resolve(parsed.projectRoot, parsed.againstPath));
6816
+ const comparison = compareSummaries(
6817
+ currentSummary,
6818
+ baselineSummary,
6819
+ path8.resolve(parsed.projectRoot, parsed.againstPath)
6820
+ );
6821
+ const outputDir = createRunDirectory(path8.resolve(parsed.projectRoot, parsed.outputRoot));
6822
+ const summaryMd = createSummaryMarkdown(currentSummary, comparison);
6823
+ writeJson(path8.join(outputDir, "compare.json"), comparison);
6824
+ writeText(path8.join(outputDir, "summary.md"), summaryMd);
6825
+ writeJson(path8.join(outputDir, "summary.json"), currentSummary);
6826
+ console.log(`Eval diff complete. Artifacts: ${outputDir}`);
6827
+ return 0;
6828
+ }
6829
+ throw new Error(`Unknown eval subcommand: ${subcommand}`);
6830
+ }
6831
+
5743
6832
  // src/mcp-server.ts
6833
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6834
+ import { z } from "zod";
6835
+
6836
+ // src/tools/utils.ts
5744
6837
  var MAX_CONTENT_LINES = 30;
5745
6838
  function truncateContent(content) {
5746
6839
  const lines = content.split("\n");
@@ -5748,6 +6841,31 @@ function truncateContent(content) {
5748
6841
  return lines.slice(0, MAX_CONTENT_LINES).join("\n") + `
5749
6842
  // ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
5750
6843
  }
6844
+ function formatDefinitionLookup(results, query) {
6845
+ if (results.length === 0) {
6846
+ return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
6847
+ }
6848
+ const formatted = results.map((r, idx) => {
6849
+ const header2 = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
6850
+ return `${header2} (score: ${r.score.toFixed(2)})
6851
+ \`\`\`
6852
+ ${truncateContent(r.content)}
6853
+ \`\`\``;
6854
+ });
6855
+ const header = results.length === 1 ? `Definition found for "${query}":` : `Found ${results.length} definition candidates for "${query}":`;
6856
+ return `${header}
6857
+
6858
+ ${formatted.join("\n\n")}`;
6859
+ }
6860
+
6861
+ // src/mcp-server.ts
6862
+ var MAX_CONTENT_LINES2 = 30;
6863
+ function truncateContent2(content) {
6864
+ const lines = content.split("\n");
6865
+ if (lines.length <= MAX_CONTENT_LINES2) return content;
6866
+ return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
6867
+ // ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
6868
+ }
5751
6869
  function formatIndexStats(stats, verbose = false) {
5752
6870
  const lines = [];
5753
6871
  if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
@@ -5861,7 +6979,7 @@ function createMcpServer(projectRoot, config) {
5861
6979
  const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
5862
6980
  return `${header} (score: ${r.score.toFixed(2)})
5863
6981
  \`\`\`
5864
- ${truncateContent(r.content)}
6982
+ ${truncateContent2(r.content)}
5865
6983
  \`\`\``;
5866
6984
  });
5867
6985
  return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
@@ -6039,7 +7157,7 @@ Use Read tool to examine specific files.` }] };
6039
7157
  const header = r.name ? `[${idx + 1}] ${r.chunkType} "${r.name}" in ${r.filePath}:${r.startLine}-${r.endLine}` : `[${idx + 1}] ${r.chunkType} in ${r.filePath}:${r.startLine}-${r.endLine}`;
6040
7158
  return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
6041
7159
  \`\`\`
6042
- ${truncateContent(r.content)}
7160
+ ${truncateContent2(r.content)}
6043
7161
  \`\`\``;
6044
7162
  });
6045
7163
  return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
@@ -6047,6 +7165,25 @@ ${truncateContent(r.content)}
6047
7165
  ${formatted.join("\n\n")}` }] };
6048
7166
  }
6049
7167
  );
7168
+ server.tool(
7169
+ "implementation_lookup",
7170
+ "Jump to symbol definition. Find WHERE something is defined. Returns the authoritative source location(s). Prefers real implementation files over tests, docs, examples, and fixtures.",
7171
+ {
7172
+ query: z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
7173
+ limit: z.number().optional().default(5).describe("Maximum number of results"),
7174
+ fileType: z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
7175
+ directory: z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
7176
+ },
7177
+ async (args) => {
7178
+ await ensureInitialized();
7179
+ const results = await indexer.search(args.query, args.limit ?? 5, {
7180
+ fileType: args.fileType,
7181
+ directory: args.directory,
7182
+ definitionIntent: true
7183
+ });
7184
+ return { content: [{ type: "text", text: formatDefinitionLookup(results, args.query) }] };
7185
+ }
7186
+ );
6050
7187
  server.tool(
6051
7188
  "call_graph",
6052
7189
  "Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
@@ -6153,14 +7290,30 @@ Use a hybrid approach:
6153
7290
  }]
6154
7291
  })
6155
7292
  );
7293
+ server.prompt(
7294
+ "definition",
7295
+ "Find where a symbol is defined in the codebase",
7296
+ { query: z.string().describe("Symbol name or description to find the definition of") },
7297
+ (args) => ({
7298
+ messages: [{
7299
+ role: "user",
7300
+ content: {
7301
+ type: "text",
7302
+ text: `Find the definition of: "${args.query}"
7303
+
7304
+ Use the implementation_lookup tool to find where this symbol is defined. This prioritizes real implementation files over tests, docs, and examples. If no definition is found, fall back to codebase_search for broader discovery.`
7305
+ }
7306
+ }]
7307
+ })
7308
+ );
6156
7309
  return server;
6157
7310
  }
6158
7311
 
6159
7312
  // src/cli.ts
6160
7313
  function loadJsonFile(filePath) {
6161
7314
  try {
6162
- if (existsSync5(filePath)) {
6163
- const content = readFileSync5(filePath, "utf-8");
7315
+ if (existsSync6(filePath)) {
7316
+ const content = readFileSync8(filePath, "utf-8");
6164
7317
  return JSON.parse(content);
6165
7318
  }
6166
7319
  } catch {
@@ -6172,9 +7325,9 @@ function loadPluginConfig(projectRoot, configPath) {
6172
7325
  const config = loadJsonFile(configPath);
6173
7326
  if (config) return config;
6174
7327
  }
6175
- const projectConfig = loadJsonFile(path6.join(projectRoot, ".opencode", "codebase-index.json"));
7328
+ const projectConfig = loadJsonFile(path9.join(projectRoot, ".opencode", "codebase-index.json"));
6176
7329
  if (projectConfig) return projectConfig;
6177
- const globalConfig = loadJsonFile(path6.join(os3.homedir(), ".config", "opencode", "codebase-index.json"));
7330
+ const globalConfig = loadJsonFile(path9.join(os4.homedir(), ".config", "opencode", "codebase-index.json"));
6178
7331
  if (globalConfig) return globalConfig;
6179
7332
  return {};
6180
7333
  }
@@ -6183,14 +7336,18 @@ function parseArgs(argv) {
6183
7336
  let config;
6184
7337
  for (let i = 2; i < argv.length; i++) {
6185
7338
  if (argv[i] === "--project" && argv[i + 1]) {
6186
- project = path6.resolve(argv[++i]);
7339
+ project = path9.resolve(argv[++i]);
6187
7340
  } else if (argv[i] === "--config" && argv[i + 1]) {
6188
- config = path6.resolve(argv[++i]);
7341
+ config = path9.resolve(argv[++i]);
6189
7342
  }
6190
7343
  }
6191
7344
  return { project, config };
6192
7345
  }
6193
7346
  async function main() {
7347
+ if (process.argv[2] === "eval") {
7348
+ const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
7349
+ process.exit(exitCode);
7350
+ }
6194
7351
  const args = parseArgs(process.argv);
6195
7352
  const rawConfig = loadPluginConfig(args.project, args.config);
6196
7353
  const config = parseConfig(rawConfig);