opencode-codebase-index 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +208 -105
- package/commands/definition.md +24 -0
- package/dist/cli.cjs +1442 -200
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1415 -179
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +217 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -87
- package/dist/index.js.map +1 -1
- package/native/codebase-index-native.darwin-arm64.node +0 -0
- package/native/codebase-index-native.darwin-x64.node +0 -0
- package/native/codebase-index-native.linux-arm64-gnu.node +0 -0
- package/native/codebase-index-native.linux-x64-gnu.node +0 -0
- package/native/codebase-index-native.win32-x64-msvc.node +0 -0
- package/package.json +8 -1
package/dist/cli.cjs
CHANGED
|
@@ -29,7 +29,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
29
29
|
|
|
30
30
|
// node_modules/eventemitter3/index.js
|
|
31
31
|
var require_eventemitter3 = __commonJS({
|
|
32
|
-
"node_modules/eventemitter3/index.js"(exports2,
|
|
32
|
+
"node_modules/eventemitter3/index.js"(exports2, module3) {
|
|
33
33
|
"use strict";
|
|
34
34
|
var has = Object.prototype.hasOwnProperty;
|
|
35
35
|
var prefix = "~";
|
|
@@ -183,15 +183,15 @@ var require_eventemitter3 = __commonJS({
|
|
|
183
183
|
EventEmitter2.prototype.addListener = EventEmitter2.prototype.on;
|
|
184
184
|
EventEmitter2.prefixed = prefix;
|
|
185
185
|
EventEmitter2.EventEmitter = EventEmitter2;
|
|
186
|
-
if ("undefined" !== typeof
|
|
187
|
-
|
|
186
|
+
if ("undefined" !== typeof module3) {
|
|
187
|
+
module3.exports = EventEmitter2;
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// node_modules/ignore/index.js
|
|
193
193
|
var require_ignore = __commonJS({
|
|
194
|
-
"node_modules/ignore/index.js"(exports2,
|
|
194
|
+
"node_modules/ignore/index.js"(exports2, module3) {
|
|
195
195
|
"use strict";
|
|
196
196
|
function makeArray(subject) {
|
|
197
197
|
return Array.isArray(subject) ? subject : [subject];
|
|
@@ -487,7 +487,7 @@ var require_ignore = __commonJS({
|
|
|
487
487
|
// path matching.
|
|
488
488
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
489
489
|
// @returns {TestResult} true if a file is ignored
|
|
490
|
-
test(
|
|
490
|
+
test(path10, checkUnignored, mode) {
|
|
491
491
|
let ignored = false;
|
|
492
492
|
let unignored = false;
|
|
493
493
|
let matchedRule;
|
|
@@ -496,7 +496,7 @@ var require_ignore = __commonJS({
|
|
|
496
496
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
497
497
|
return;
|
|
498
498
|
}
|
|
499
|
-
const matched = rule[mode].test(
|
|
499
|
+
const matched = rule[mode].test(path10);
|
|
500
500
|
if (!matched) {
|
|
501
501
|
return;
|
|
502
502
|
}
|
|
@@ -517,17 +517,17 @@ var require_ignore = __commonJS({
|
|
|
517
517
|
var throwError = (message, Ctor) => {
|
|
518
518
|
throw new Ctor(message);
|
|
519
519
|
};
|
|
520
|
-
var checkPath = (
|
|
521
|
-
if (!isString(
|
|
520
|
+
var checkPath = (path10, originalPath, doThrow) => {
|
|
521
|
+
if (!isString(path10)) {
|
|
522
522
|
return doThrow(
|
|
523
523
|
`path must be a string, but got \`${originalPath}\``,
|
|
524
524
|
TypeError
|
|
525
525
|
);
|
|
526
526
|
}
|
|
527
|
-
if (!
|
|
527
|
+
if (!path10) {
|
|
528
528
|
return doThrow(`path must not be empty`, TypeError);
|
|
529
529
|
}
|
|
530
|
-
if (checkPath.isNotRelative(
|
|
530
|
+
if (checkPath.isNotRelative(path10)) {
|
|
531
531
|
const r = "`path.relative()`d";
|
|
532
532
|
return doThrow(
|
|
533
533
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -536,7 +536,7 @@ var require_ignore = __commonJS({
|
|
|
536
536
|
}
|
|
537
537
|
return true;
|
|
538
538
|
};
|
|
539
|
-
var isNotRelative = (
|
|
539
|
+
var isNotRelative = (path10) => REGEX_TEST_INVALID_PATH.test(path10);
|
|
540
540
|
checkPath.isNotRelative = isNotRelative;
|
|
541
541
|
checkPath.convert = (p) => p;
|
|
542
542
|
var Ignore2 = class {
|
|
@@ -566,19 +566,19 @@ var require_ignore = __commonJS({
|
|
|
566
566
|
}
|
|
567
567
|
// @returns {TestResult}
|
|
568
568
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
569
|
-
const
|
|
569
|
+
const path10 = originalPath && checkPath.convert(originalPath);
|
|
570
570
|
checkPath(
|
|
571
|
-
|
|
571
|
+
path10,
|
|
572
572
|
originalPath,
|
|
573
573
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
574
574
|
);
|
|
575
|
-
return this._t(
|
|
575
|
+
return this._t(path10, cache, checkUnignored, slices);
|
|
576
576
|
}
|
|
577
|
-
checkIgnore(
|
|
578
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
579
|
-
return this.test(
|
|
577
|
+
checkIgnore(path10) {
|
|
578
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path10)) {
|
|
579
|
+
return this.test(path10);
|
|
580
580
|
}
|
|
581
|
-
const slices =
|
|
581
|
+
const slices = path10.split(SLASH).filter(Boolean);
|
|
582
582
|
slices.pop();
|
|
583
583
|
if (slices.length) {
|
|
584
584
|
const parent = this._t(
|
|
@@ -591,18 +591,18 @@ var require_ignore = __commonJS({
|
|
|
591
591
|
return parent;
|
|
592
592
|
}
|
|
593
593
|
}
|
|
594
|
-
return this._rules.test(
|
|
594
|
+
return this._rules.test(path10, false, MODE_CHECK_IGNORE);
|
|
595
595
|
}
|
|
596
|
-
_t(
|
|
597
|
-
if (
|
|
598
|
-
return cache[
|
|
596
|
+
_t(path10, cache, checkUnignored, slices) {
|
|
597
|
+
if (path10 in cache) {
|
|
598
|
+
return cache[path10];
|
|
599
599
|
}
|
|
600
600
|
if (!slices) {
|
|
601
|
-
slices =
|
|
601
|
+
slices = path10.split(SLASH).filter(Boolean);
|
|
602
602
|
}
|
|
603
603
|
slices.pop();
|
|
604
604
|
if (!slices.length) {
|
|
605
|
-
return cache[
|
|
605
|
+
return cache[path10] = this._rules.test(path10, checkUnignored, MODE_IGNORE);
|
|
606
606
|
}
|
|
607
607
|
const parent = this._t(
|
|
608
608
|
slices.join(SLASH) + SLASH,
|
|
@@ -610,29 +610,29 @@ var require_ignore = __commonJS({
|
|
|
610
610
|
checkUnignored,
|
|
611
611
|
slices
|
|
612
612
|
);
|
|
613
|
-
return cache[
|
|
613
|
+
return cache[path10] = parent.ignored ? parent : this._rules.test(path10, checkUnignored, MODE_IGNORE);
|
|
614
614
|
}
|
|
615
|
-
ignores(
|
|
616
|
-
return this._test(
|
|
615
|
+
ignores(path10) {
|
|
616
|
+
return this._test(path10, this._ignoreCache, false).ignored;
|
|
617
617
|
}
|
|
618
618
|
createFilter() {
|
|
619
|
-
return (
|
|
619
|
+
return (path10) => !this.ignores(path10);
|
|
620
620
|
}
|
|
621
621
|
filter(paths) {
|
|
622
622
|
return makeArray(paths).filter(this.createFilter());
|
|
623
623
|
}
|
|
624
624
|
// @returns {TestResult}
|
|
625
|
-
test(
|
|
626
|
-
return this._test(
|
|
625
|
+
test(path10) {
|
|
626
|
+
return this._test(path10, this._testCache, true);
|
|
627
627
|
}
|
|
628
628
|
};
|
|
629
629
|
var factory = (options) => new Ignore2(options);
|
|
630
|
-
var isPathValid = (
|
|
630
|
+
var isPathValid = (path10) => checkPath(path10 && checkPath.convert(path10), path10, RETURN_FALSE);
|
|
631
631
|
var setupWindows = () => {
|
|
632
632
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
633
633
|
checkPath.convert = makePosix;
|
|
634
634
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
635
|
-
checkPath.isNotRelative = (
|
|
635
|
+
checkPath.isNotRelative = (path10) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path10) || isNotRelative(path10);
|
|
636
636
|
};
|
|
637
637
|
if (
|
|
638
638
|
// Detect `process` so that it can run in browsers.
|
|
@@ -640,18 +640,18 @@ var require_ignore = __commonJS({
|
|
|
640
640
|
) {
|
|
641
641
|
setupWindows();
|
|
642
642
|
}
|
|
643
|
-
|
|
643
|
+
module3.exports = factory;
|
|
644
644
|
factory.default = factory;
|
|
645
|
-
|
|
646
|
-
define(
|
|
645
|
+
module3.exports.isPathValid = isPathValid;
|
|
646
|
+
define(module3.exports, /* @__PURE__ */ Symbol.for("setupWindows"), setupWindows);
|
|
647
647
|
}
|
|
648
648
|
});
|
|
649
649
|
|
|
650
650
|
// src/cli.ts
|
|
651
651
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
652
|
-
var
|
|
653
|
-
var
|
|
654
|
-
var
|
|
652
|
+
var import_fs10 = require("fs");
|
|
653
|
+
var path9 = __toESM(require("path"), 1);
|
|
654
|
+
var os4 = __toESM(require("os"), 1);
|
|
655
655
|
|
|
656
656
|
// src/config/constants.ts
|
|
657
657
|
var DEFAULT_INCLUDE = [
|
|
@@ -659,7 +659,7 @@ var DEFAULT_INCLUDE = [
|
|
|
659
659
|
"**/*.{py,pyi}",
|
|
660
660
|
"**/*.{go,rs,java,kt,scala}",
|
|
661
661
|
"**/*.{c,cpp,cc,h,hpp}",
|
|
662
|
-
"**/*.{rb,php,swift}",
|
|
662
|
+
"**/*.{rb,php,inc,swift}",
|
|
663
663
|
"**/*.{vue,svelte,astro}",
|
|
664
664
|
"**/*.{sql,graphql,proto}",
|
|
665
665
|
"**/*.{yaml,yml,toml}",
|
|
@@ -754,6 +754,27 @@ var DEFAULT_PROVIDER_MODELS = {
|
|
|
754
754
|
"ollama": "nomic-embed-text"
|
|
755
755
|
};
|
|
756
756
|
|
|
757
|
+
// src/config/env-substitution.ts
|
|
758
|
+
var ENV_REFERENCE_PATTERN = /^\{env:([A-Z_][A-Z0-9_]*)\}$/;
|
|
759
|
+
var ENV_REFERENCE_LIKE_PATTERN = /\{env:[^}]+\}/;
|
|
760
|
+
function substituteEnvString(value, keyPath) {
|
|
761
|
+
const match = value.match(ENV_REFERENCE_PATTERN);
|
|
762
|
+
if (!match) {
|
|
763
|
+
if (ENV_REFERENCE_LIKE_PATTERN.test(value)) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
`Invalid environment variable reference at '${keyPath}'. Expected the entire string to match '{env:VAR_NAME}' with VAR_NAME matching [A-Z_][A-Z0-9_]*.`
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
return value;
|
|
769
|
+
}
|
|
770
|
+
const variableName = match[1];
|
|
771
|
+
const envValue = process.env[variableName];
|
|
772
|
+
if (envValue === void 0) {
|
|
773
|
+
throw new Error(`Missing environment variable '${variableName}' referenced by config at '${keyPath}'.`);
|
|
774
|
+
}
|
|
775
|
+
return envValue;
|
|
776
|
+
}
|
|
777
|
+
|
|
757
778
|
// src/config/schema.ts
|
|
758
779
|
function getDefaultIndexingConfig() {
|
|
759
780
|
return {
|
|
@@ -811,11 +832,27 @@ function isValidScope(value) {
|
|
|
811
832
|
function isStringArray(value) {
|
|
812
833
|
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
813
834
|
}
|
|
835
|
+
function getResolvedString(value, keyPath) {
|
|
836
|
+
if (typeof value !== "string") {
|
|
837
|
+
return void 0;
|
|
838
|
+
}
|
|
839
|
+
return substituteEnvString(value, keyPath);
|
|
840
|
+
}
|
|
841
|
+
function getResolvedStringArray(value, keyPath) {
|
|
842
|
+
if (!isStringArray(value)) {
|
|
843
|
+
return void 0;
|
|
844
|
+
}
|
|
845
|
+
return value.map((item, index) => substituteEnvString(item, `${keyPath}[${index}]`));
|
|
846
|
+
}
|
|
814
847
|
function isValidLogLevel(value) {
|
|
815
848
|
return typeof value === "string" && VALID_LOG_LEVELS.includes(value);
|
|
816
849
|
}
|
|
817
850
|
function parseConfig(raw) {
|
|
818
851
|
const input = raw && typeof raw === "object" ? raw : {};
|
|
852
|
+
const embeddingProviderValue = getResolvedString(input.embeddingProvider, "$root.embeddingProvider");
|
|
853
|
+
const scopeValue = getResolvedString(input.scope, "$root.scope");
|
|
854
|
+
const includeValue = getResolvedStringArray(input.include, "$root.include");
|
|
855
|
+
const excludeValue = getResolvedStringArray(input.exclude, "$root.exclude");
|
|
819
856
|
const defaultIndexing = getDefaultIndexingConfig();
|
|
820
857
|
const defaultSearch = getDefaultSearchConfig();
|
|
821
858
|
const defaultDebug = getDefaultDebugConfig();
|
|
@@ -858,19 +895,23 @@ function parseConfig(raw) {
|
|
|
858
895
|
let embeddingProvider;
|
|
859
896
|
let embeddingModel = void 0;
|
|
860
897
|
let customProvider = void 0;
|
|
861
|
-
if (
|
|
898
|
+
if (embeddingProviderValue === "custom") {
|
|
862
899
|
embeddingProvider = "custom";
|
|
863
900
|
const rawCustom = input.customProvider && typeof input.customProvider === "object" ? input.customProvider : null;
|
|
864
|
-
|
|
901
|
+
const baseUrlValue = getResolvedString(rawCustom?.baseUrl, "$root.customProvider.baseUrl");
|
|
902
|
+
const modelValue = getResolvedString(rawCustom?.model, "$root.customProvider.model");
|
|
903
|
+
const apiKeyValue = getResolvedString(rawCustom?.apiKey, "$root.customProvider.apiKey");
|
|
904
|
+
if (rawCustom && typeof baseUrlValue === "string" && baseUrlValue.trim().length > 0 && typeof modelValue === "string" && modelValue.trim().length > 0 && typeof rawCustom.dimensions === "number" && Number.isInteger(rawCustom.dimensions) && rawCustom.dimensions > 0) {
|
|
865
905
|
customProvider = {
|
|
866
|
-
baseUrl:
|
|
867
|
-
model:
|
|
906
|
+
baseUrl: baseUrlValue.trim().replace(/\/+$/, ""),
|
|
907
|
+
model: modelValue,
|
|
868
908
|
dimensions: rawCustom.dimensions,
|
|
869
|
-
apiKey:
|
|
909
|
+
apiKey: apiKeyValue,
|
|
870
910
|
maxTokens: typeof rawCustom.maxTokens === "number" ? rawCustom.maxTokens : void 0,
|
|
871
911
|
timeoutMs: typeof rawCustom.timeoutMs === "number" ? Math.max(1e3, rawCustom.timeoutMs) : void 0,
|
|
872
912
|
concurrency: typeof rawCustom.concurrency === "number" ? Math.max(1, Math.floor(rawCustom.concurrency)) : void 0,
|
|
873
|
-
requestIntervalMs: typeof rawCustom.requestIntervalMs === "number" ? Math.max(0, Math.floor(rawCustom.requestIntervalMs)) : void 0
|
|
913
|
+
requestIntervalMs: typeof rawCustom.requestIntervalMs === "number" ? Math.max(0, Math.floor(rawCustom.requestIntervalMs)) : void 0,
|
|
914
|
+
maxBatchSize: typeof rawCustom.maxBatchSize === "number" ? Math.max(1, Math.floor(rawCustom.maxBatchSize)) : typeof rawCustom.max_batch_size === "number" ? Math.max(1, Math.floor(rawCustom.max_batch_size)) : void 0
|
|
874
915
|
};
|
|
875
916
|
if (!/\/v\d+\/?$/.test(customProvider.baseUrl)) {
|
|
876
917
|
console.warn(
|
|
@@ -882,10 +923,16 @@ function parseConfig(raw) {
|
|
|
882
923
|
"embeddingProvider is 'custom' but customProvider config is missing or invalid. Required fields: baseUrl (string), model (string), dimensions (positive integer)."
|
|
883
924
|
);
|
|
884
925
|
}
|
|
885
|
-
} else if (isValidProvider(
|
|
886
|
-
embeddingProvider =
|
|
887
|
-
|
|
888
|
-
|
|
926
|
+
} else if (isValidProvider(embeddingProviderValue)) {
|
|
927
|
+
embeddingProvider = embeddingProviderValue;
|
|
928
|
+
const rawEmbeddingModel = input.embeddingModel;
|
|
929
|
+
if (typeof rawEmbeddingModel === "string") {
|
|
930
|
+
const embeddingModelValue = substituteEnvString(rawEmbeddingModel, "$root.embeddingModel");
|
|
931
|
+
if (embeddingModelValue) {
|
|
932
|
+
embeddingModel = isValidModel(embeddingModelValue, embeddingProvider) ? embeddingModelValue : DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
933
|
+
}
|
|
934
|
+
} else if (rawEmbeddingModel) {
|
|
935
|
+
embeddingModel = DEFAULT_PROVIDER_MODELS[embeddingProvider];
|
|
889
936
|
}
|
|
890
937
|
} else {
|
|
891
938
|
embeddingProvider = "auto";
|
|
@@ -894,9 +941,9 @@ function parseConfig(raw) {
|
|
|
894
941
|
embeddingProvider,
|
|
895
942
|
embeddingModel,
|
|
896
943
|
customProvider,
|
|
897
|
-
scope: isValidScope(
|
|
898
|
-
include:
|
|
899
|
-
exclude:
|
|
944
|
+
scope: isValidScope(scopeValue) ? scopeValue : "project",
|
|
945
|
+
include: includeValue ?? DEFAULT_INCLUDE,
|
|
946
|
+
exclude: excludeValue ?? DEFAULT_EXCLUDE,
|
|
900
947
|
indexing,
|
|
901
948
|
search,
|
|
902
949
|
debug
|
|
@@ -909,13 +956,197 @@ function getDefaultModelForProvider(provider) {
|
|
|
909
956
|
}
|
|
910
957
|
var availableProviders = Object.keys(EMBEDDING_MODELS);
|
|
911
958
|
|
|
912
|
-
// src/
|
|
913
|
-
var
|
|
914
|
-
|
|
959
|
+
// src/eval/cli.ts
|
|
960
|
+
var path8 = __toESM(require("path"), 1);
|
|
961
|
+
|
|
962
|
+
// src/eval/compare.ts
|
|
963
|
+
function metricDelta(current, baseline) {
|
|
964
|
+
const absolute = current - baseline;
|
|
965
|
+
const relativePct = baseline === 0 ? current === 0 ? 0 : 100 : absolute / baseline * 100;
|
|
966
|
+
return {
|
|
967
|
+
current,
|
|
968
|
+
baseline,
|
|
969
|
+
absolute,
|
|
970
|
+
relativePct
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function compareSummaries(current, baseline, againstPath) {
|
|
974
|
+
return {
|
|
975
|
+
againstPath,
|
|
976
|
+
deltas: {
|
|
977
|
+
hitAt1: metricDelta(current.metrics.hitAt1, baseline.metrics.hitAt1),
|
|
978
|
+
hitAt3: metricDelta(current.metrics.hitAt3, baseline.metrics.hitAt3),
|
|
979
|
+
hitAt5: metricDelta(current.metrics.hitAt5, baseline.metrics.hitAt5),
|
|
980
|
+
hitAt10: metricDelta(current.metrics.hitAt10, baseline.metrics.hitAt10),
|
|
981
|
+
mrrAt10: metricDelta(current.metrics.mrrAt10, baseline.metrics.mrrAt10),
|
|
982
|
+
ndcgAt10: metricDelta(current.metrics.ndcgAt10, baseline.metrics.ndcgAt10),
|
|
983
|
+
latencyP50Ms: metricDelta(current.metrics.latencyMs.p50, baseline.metrics.latencyMs.p50),
|
|
984
|
+
latencyP95Ms: metricDelta(current.metrics.latencyMs.p95, baseline.metrics.latencyMs.p95),
|
|
985
|
+
latencyP99Ms: metricDelta(current.metrics.latencyMs.p99, baseline.metrics.latencyMs.p99),
|
|
986
|
+
embeddingCallCount: metricDelta(
|
|
987
|
+
current.metrics.embedding.callCount,
|
|
988
|
+
baseline.metrics.embedding.callCount
|
|
989
|
+
),
|
|
990
|
+
estimatedCostUsd: metricDelta(
|
|
991
|
+
current.metrics.embedding.estimatedCostUsd,
|
|
992
|
+
baseline.metrics.embedding.estimatedCostUsd
|
|
993
|
+
)
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// src/eval/reports.ts
|
|
999
|
+
var import_fs = require("fs");
|
|
1000
|
+
var path = __toESM(require("path"), 1);
|
|
1001
|
+
function formatPct(value) {
|
|
1002
|
+
return `${(value * 100).toFixed(2)}%`;
|
|
1003
|
+
}
|
|
1004
|
+
function formatMs(value) {
|
|
1005
|
+
return `${value.toFixed(3)}ms`;
|
|
1006
|
+
}
|
|
1007
|
+
function formatUsd(value) {
|
|
1008
|
+
return `$${value.toFixed(6)}`;
|
|
1009
|
+
}
|
|
1010
|
+
function signed(value, digits = 4) {
|
|
1011
|
+
const formatted = value.toFixed(digits);
|
|
1012
|
+
return value > 0 ? `+${formatted}` : formatted;
|
|
1013
|
+
}
|
|
1014
|
+
function loadSummary(summaryPath) {
|
|
1015
|
+
const raw = (0, import_fs.readFileSync)(summaryPath, "utf-8");
|
|
1016
|
+
return JSON.parse(raw);
|
|
1017
|
+
}
|
|
1018
|
+
function createRunDirectory(outputRoot, timestampOverride) {
|
|
1019
|
+
const timestamp = (timestampOverride ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
|
|
1020
|
+
const dir = path.join(outputRoot, timestamp);
|
|
1021
|
+
(0, import_fs.mkdirSync)(dir, { recursive: true });
|
|
1022
|
+
return dir;
|
|
1023
|
+
}
|
|
1024
|
+
function writeJson(filePath, value) {
|
|
1025
|
+
(0, import_fs.writeFileSync)(filePath, JSON.stringify(value, null, 2), "utf-8");
|
|
1026
|
+
}
|
|
1027
|
+
function writeText(filePath, value) {
|
|
1028
|
+
(0, import_fs.writeFileSync)(filePath, value, "utf-8");
|
|
1029
|
+
}
|
|
1030
|
+
function createSummaryMarkdown(summary, comparison, gate, sweep) {
|
|
1031
|
+
const lines = [];
|
|
1032
|
+
lines.push("# Evaluation Summary");
|
|
1033
|
+
lines.push("");
|
|
1034
|
+
lines.push(`- Generated: ${summary.generatedAt}`);
|
|
1035
|
+
lines.push(`- Dataset: ${summary.datasetName} (v${summary.datasetVersion})`);
|
|
1036
|
+
lines.push(`- Query count: ${summary.queryCount}`);
|
|
1037
|
+
lines.push(
|
|
1038
|
+
`- Search config: fusion=${summary.searchConfig.fusionStrategy}, hybridWeight=${summary.searchConfig.hybridWeight}, rrfK=${summary.searchConfig.rrfK}, rerankTopN=${summary.searchConfig.rerankTopN}`
|
|
1039
|
+
);
|
|
1040
|
+
lines.push("");
|
|
1041
|
+
lines.push("## Metrics");
|
|
1042
|
+
lines.push("");
|
|
1043
|
+
lines.push("| Metric | Value |");
|
|
1044
|
+
lines.push("|---|---:|");
|
|
1045
|
+
lines.push(`| Hit@1 | ${formatPct(summary.metrics.hitAt1)} |`);
|
|
1046
|
+
lines.push(`| Hit@3 | ${formatPct(summary.metrics.hitAt3)} |`);
|
|
1047
|
+
lines.push(`| Hit@5 | ${formatPct(summary.metrics.hitAt5)} |`);
|
|
1048
|
+
lines.push(`| Hit@10 | ${formatPct(summary.metrics.hitAt10)} |`);
|
|
1049
|
+
lines.push(`| MRR@10 | ${summary.metrics.mrrAt10.toFixed(4)} |`);
|
|
1050
|
+
lines.push(`| nDCG@10 | ${summary.metrics.ndcgAt10.toFixed(4)} |`);
|
|
1051
|
+
lines.push(`| Latency p50 | ${formatMs(summary.metrics.latencyMs.p50)} |`);
|
|
1052
|
+
lines.push(`| Latency p95 | ${formatMs(summary.metrics.latencyMs.p95)} |`);
|
|
1053
|
+
lines.push(`| Latency p99 | ${formatMs(summary.metrics.latencyMs.p99)} |`);
|
|
1054
|
+
lines.push(`| Embedding calls | ${summary.metrics.embedding.callCount} |`);
|
|
1055
|
+
lines.push(`| Embedding tokens | ${summary.metrics.tokenEstimate.embeddingTokensUsed} |`);
|
|
1056
|
+
lines.push(`| Estimated embedding cost | ${formatUsd(summary.metrics.embedding.estimatedCostUsd)} |`);
|
|
1057
|
+
lines.push("");
|
|
1058
|
+
lines.push("## Failure Buckets");
|
|
1059
|
+
lines.push("");
|
|
1060
|
+
lines.push("| Bucket | Count |");
|
|
1061
|
+
lines.push("|---|---:|");
|
|
1062
|
+
lines.push(
|
|
1063
|
+
`| wrong-file | ${summary.metrics.failureBuckets["wrong-file"]} |`
|
|
1064
|
+
);
|
|
1065
|
+
lines.push(
|
|
1066
|
+
`| wrong-symbol | ${summary.metrics.failureBuckets["wrong-symbol"]} |`
|
|
1067
|
+
);
|
|
1068
|
+
lines.push(
|
|
1069
|
+
`| docs/tests outranking source | ${summary.metrics.failureBuckets["docs-tests-outranking-source"]} |`
|
|
1070
|
+
);
|
|
1071
|
+
lines.push(
|
|
1072
|
+
`| no relevant hit in top-k | ${summary.metrics.failureBuckets["no-relevant-hit-top-k"]} |`
|
|
1073
|
+
);
|
|
1074
|
+
lines.push("");
|
|
1075
|
+
if (comparison) {
|
|
1076
|
+
lines.push("## Comparison vs Baseline");
|
|
1077
|
+
lines.push("");
|
|
1078
|
+
lines.push(`- Against: ${comparison.againstPath}`);
|
|
1079
|
+
lines.push("");
|
|
1080
|
+
lines.push("| Metric | Baseline | Current | Delta |");
|
|
1081
|
+
lines.push("|---|---:|---:|---:|");
|
|
1082
|
+
lines.push(
|
|
1083
|
+
`| Hit@5 | ${formatPct(comparison.deltas.hitAt5.baseline)} | ${formatPct(comparison.deltas.hitAt5.current)} | ${signed(comparison.deltas.hitAt5.absolute)} |`
|
|
1084
|
+
);
|
|
1085
|
+
lines.push(
|
|
1086
|
+
`| MRR@10 | ${comparison.deltas.mrrAt10.baseline.toFixed(4)} | ${comparison.deltas.mrrAt10.current.toFixed(4)} | ${signed(comparison.deltas.mrrAt10.absolute)} |`
|
|
1087
|
+
);
|
|
1088
|
+
lines.push(
|
|
1089
|
+
`| nDCG@10 | ${comparison.deltas.ndcgAt10.baseline.toFixed(4)} | ${comparison.deltas.ndcgAt10.current.toFixed(4)} | ${signed(comparison.deltas.ndcgAt10.absolute)} |`
|
|
1090
|
+
);
|
|
1091
|
+
lines.push(
|
|
1092
|
+
`| p95 latency (ms) | ${comparison.deltas.latencyP95Ms.baseline.toFixed(3)} | ${comparison.deltas.latencyP95Ms.current.toFixed(3)} | ${signed(comparison.deltas.latencyP95Ms.absolute, 3)} |`
|
|
1093
|
+
);
|
|
1094
|
+
lines.push("");
|
|
1095
|
+
}
|
|
1096
|
+
if (gate) {
|
|
1097
|
+
lines.push("## CI Gate");
|
|
1098
|
+
lines.push("");
|
|
1099
|
+
lines.push(`- Result: ${gate.passed ? "PASS \u2705" : "FAIL \u274C"}`);
|
|
1100
|
+
if (gate.violations.length > 0) {
|
|
1101
|
+
lines.push("- Violations:");
|
|
1102
|
+
for (const violation of gate.violations) {
|
|
1103
|
+
lines.push(` - ${violation.metric}: ${violation.message}`);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
lines.push("");
|
|
1107
|
+
}
|
|
1108
|
+
if (sweep) {
|
|
1109
|
+
lines.push("## Parameter Sweep");
|
|
1110
|
+
lines.push("");
|
|
1111
|
+
lines.push(`- Run count: ${sweep.runCount}`);
|
|
1112
|
+
if (sweep.bestByHitAt5) {
|
|
1113
|
+
lines.push(
|
|
1114
|
+
`- 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}`
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
if (sweep.bestByMrrAt10) {
|
|
1118
|
+
lines.push(
|
|
1119
|
+
`- 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}`
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
if (sweep.bestByP95Latency) {
|
|
1123
|
+
lines.push(
|
|
1124
|
+
`- 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}`
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
lines.push("");
|
|
1128
|
+
}
|
|
1129
|
+
return `${lines.join("\n")}
|
|
1130
|
+
`;
|
|
1131
|
+
}
|
|
1132
|
+
function buildPerQueryArtifact(perQuery) {
|
|
1133
|
+
return {
|
|
1134
|
+
queryCount: perQuery.length,
|
|
1135
|
+
queries: [...perQuery].sort((a, b) => a.id.localeCompare(b.id))
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/eval/runner.ts
|
|
1140
|
+
var import_fs7 = require("fs");
|
|
1141
|
+
var import_fs8 = require("fs");
|
|
1142
|
+
var import_fs9 = require("fs");
|
|
1143
|
+
var os3 = __toESM(require("os"), 1);
|
|
1144
|
+
var path7 = __toESM(require("path"), 1);
|
|
1145
|
+
var import_perf_hooks2 = require("perf_hooks");
|
|
915
1146
|
|
|
916
1147
|
// src/indexer/index.ts
|
|
917
|
-
var
|
|
918
|
-
var
|
|
1148
|
+
var import_fs5 = require("fs");
|
|
1149
|
+
var path6 = __toESM(require("path"), 1);
|
|
919
1150
|
var import_perf_hooks = require("perf_hooks");
|
|
920
1151
|
|
|
921
1152
|
// node_modules/eventemitter3/index.mjs
|
|
@@ -940,7 +1171,7 @@ function pTimeout(promise, options) {
|
|
|
940
1171
|
} = options;
|
|
941
1172
|
let timer;
|
|
942
1173
|
let abortHandler;
|
|
943
|
-
const wrappedPromise = new Promise((
|
|
1174
|
+
const wrappedPromise = new Promise((resolve5, reject) => {
|
|
944
1175
|
if (typeof milliseconds !== "number" || Math.sign(milliseconds) !== 1) {
|
|
945
1176
|
throw new TypeError(`Expected \`milliseconds\` to be a positive number, got \`${milliseconds}\``);
|
|
946
1177
|
}
|
|
@@ -954,7 +1185,7 @@ function pTimeout(promise, options) {
|
|
|
954
1185
|
};
|
|
955
1186
|
signal.addEventListener("abort", abortHandler, { once: true });
|
|
956
1187
|
}
|
|
957
|
-
promise.then(
|
|
1188
|
+
promise.then(resolve5, reject);
|
|
958
1189
|
if (milliseconds === Number.POSITIVE_INFINITY) {
|
|
959
1190
|
return;
|
|
960
1191
|
}
|
|
@@ -962,7 +1193,7 @@ function pTimeout(promise, options) {
|
|
|
962
1193
|
timer = customTimers.setTimeout.call(void 0, () => {
|
|
963
1194
|
if (fallback) {
|
|
964
1195
|
try {
|
|
965
|
-
|
|
1196
|
+
resolve5(fallback());
|
|
966
1197
|
} catch (error) {
|
|
967
1198
|
reject(error);
|
|
968
1199
|
}
|
|
@@ -972,7 +1203,7 @@ function pTimeout(promise, options) {
|
|
|
972
1203
|
promise.cancel();
|
|
973
1204
|
}
|
|
974
1205
|
if (message === false) {
|
|
975
|
-
|
|
1206
|
+
resolve5();
|
|
976
1207
|
} else if (message instanceof Error) {
|
|
977
1208
|
reject(message);
|
|
978
1209
|
} else {
|
|
@@ -1362,7 +1593,7 @@ var PQueue = class extends import_index.default {
|
|
|
1362
1593
|
// Assign unique ID if not provided
|
|
1363
1594
|
id: options.id ?? (this.#idAssigner++).toString()
|
|
1364
1595
|
};
|
|
1365
|
-
return new Promise((
|
|
1596
|
+
return new Promise((resolve5, reject) => {
|
|
1366
1597
|
const taskSymbol = /* @__PURE__ */ Symbol(`task-${options.id}`);
|
|
1367
1598
|
this.#queue.enqueue(async () => {
|
|
1368
1599
|
this.#pending++;
|
|
@@ -1400,7 +1631,7 @@ var PQueue = class extends import_index.default {
|
|
|
1400
1631
|
})]);
|
|
1401
1632
|
}
|
|
1402
1633
|
const result = await operation;
|
|
1403
|
-
|
|
1634
|
+
resolve5(result);
|
|
1404
1635
|
this.emit("completed", result);
|
|
1405
1636
|
} catch (error) {
|
|
1406
1637
|
reject(error);
|
|
@@ -1557,13 +1788,13 @@ var PQueue = class extends import_index.default {
|
|
|
1557
1788
|
});
|
|
1558
1789
|
}
|
|
1559
1790
|
async #onEvent(event, filter) {
|
|
1560
|
-
return new Promise((
|
|
1791
|
+
return new Promise((resolve5) => {
|
|
1561
1792
|
const listener = () => {
|
|
1562
1793
|
if (filter && !filter()) {
|
|
1563
1794
|
return;
|
|
1564
1795
|
}
|
|
1565
1796
|
this.off(event, listener);
|
|
1566
|
-
|
|
1797
|
+
resolve5();
|
|
1567
1798
|
};
|
|
1568
1799
|
this.on(event, listener);
|
|
1569
1800
|
});
|
|
@@ -1848,7 +2079,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
1848
2079
|
const finalDelay = Math.min(delayTime, remainingTime);
|
|
1849
2080
|
options.signal?.throwIfAborted();
|
|
1850
2081
|
if (finalDelay > 0) {
|
|
1851
|
-
await new Promise((
|
|
2082
|
+
await new Promise((resolve5, reject) => {
|
|
1852
2083
|
const onAbort = () => {
|
|
1853
2084
|
clearTimeout(timeoutToken);
|
|
1854
2085
|
options.signal?.removeEventListener("abort", onAbort);
|
|
@@ -1856,7 +2087,7 @@ async function onAttemptFailure({ error, attemptNumber, retriesConsumed, startTi
|
|
|
1856
2087
|
};
|
|
1857
2088
|
const timeoutToken = setTimeout(() => {
|
|
1858
2089
|
options.signal?.removeEventListener("abort", onAbort);
|
|
1859
|
-
|
|
2090
|
+
resolve5();
|
|
1860
2091
|
}, finalDelay);
|
|
1861
2092
|
if (options.unref) {
|
|
1862
2093
|
timeoutToken.unref?.();
|
|
@@ -1917,17 +2148,17 @@ async function pRetry(input, options = {}) {
|
|
|
1917
2148
|
}
|
|
1918
2149
|
|
|
1919
2150
|
// src/embeddings/detector.ts
|
|
1920
|
-
var
|
|
1921
|
-
var
|
|
2151
|
+
var import_fs2 = require("fs");
|
|
2152
|
+
var path2 = __toESM(require("path"), 1);
|
|
1922
2153
|
var os = __toESM(require("os"), 1);
|
|
1923
2154
|
function getOpenCodeAuthPath() {
|
|
1924
|
-
return
|
|
2155
|
+
return path2.join(os.homedir(), ".local", "share", "opencode", "auth.json");
|
|
1925
2156
|
}
|
|
1926
2157
|
function loadOpenCodeAuth() {
|
|
1927
2158
|
const authPath = getOpenCodeAuthPath();
|
|
1928
2159
|
try {
|
|
1929
|
-
if ((0,
|
|
1930
|
-
return JSON.parse((0,
|
|
2160
|
+
if ((0, import_fs2.existsSync)(authPath)) {
|
|
2161
|
+
return JSON.parse((0, import_fs2.readFileSync)(authPath, "utf-8"));
|
|
1931
2162
|
}
|
|
1932
2163
|
} catch {
|
|
1933
2164
|
}
|
|
@@ -2084,7 +2315,8 @@ function createCustomProviderInfo(config) {
|
|
|
2084
2315
|
dimensions: config.dimensions,
|
|
2085
2316
|
maxTokens: config.maxTokens ?? 8192,
|
|
2086
2317
|
costPer1MTokens: 0,
|
|
2087
|
-
timeoutMs: config.timeoutMs ?? 3e4
|
|
2318
|
+
timeoutMs: config.timeoutMs ?? 3e4,
|
|
2319
|
+
maxBatchSize: config.maxBatchSize
|
|
2088
2320
|
}
|
|
2089
2321
|
};
|
|
2090
2322
|
}
|
|
@@ -2347,21 +2579,24 @@ var CustomEmbeddingProvider = class {
|
|
|
2347
2579
|
this.credentials = credentials;
|
|
2348
2580
|
this.modelInfo = modelInfo;
|
|
2349
2581
|
}
|
|
2350
|
-
|
|
2351
|
-
const
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
return
|
|
2360
|
-
embedding: result.embeddings[0],
|
|
2361
|
-
tokensUsed: result.totalTokensUsed
|
|
2362
|
-
};
|
|
2582
|
+
splitIntoRequestBatches(texts) {
|
|
2583
|
+
const maxBatchSize = this.modelInfo.maxBatchSize;
|
|
2584
|
+
if (!maxBatchSize || texts.length <= maxBatchSize) {
|
|
2585
|
+
return [texts];
|
|
2586
|
+
}
|
|
2587
|
+
const batches = [];
|
|
2588
|
+
for (let i = 0; i < texts.length; i += maxBatchSize) {
|
|
2589
|
+
batches.push(texts.slice(i, i + maxBatchSize));
|
|
2590
|
+
}
|
|
2591
|
+
return batches;
|
|
2363
2592
|
}
|
|
2364
|
-
async
|
|
2593
|
+
async embedRequest(texts) {
|
|
2594
|
+
if (texts.length === 0) {
|
|
2595
|
+
return {
|
|
2596
|
+
embeddings: [],
|
|
2597
|
+
totalTokensUsed: 0
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2365
2600
|
const headers = {
|
|
2366
2601
|
"Content-Type": "application/json"
|
|
2367
2602
|
};
|
|
@@ -2422,6 +2657,34 @@ var CustomEmbeddingProvider = class {
|
|
|
2422
2657
|
}
|
|
2423
2658
|
throw new Error("Custom embedding API returned unexpected response format. Expected OpenAI-compatible format with data[].embedding.");
|
|
2424
2659
|
}
|
|
2660
|
+
async embedQuery(query) {
|
|
2661
|
+
const result = await this.embedBatch([query]);
|
|
2662
|
+
return {
|
|
2663
|
+
embedding: result.embeddings[0],
|
|
2664
|
+
tokensUsed: result.totalTokensUsed
|
|
2665
|
+
};
|
|
2666
|
+
}
|
|
2667
|
+
async embedDocument(document) {
|
|
2668
|
+
const result = await this.embedBatch([document]);
|
|
2669
|
+
return {
|
|
2670
|
+
embedding: result.embeddings[0],
|
|
2671
|
+
tokensUsed: result.totalTokensUsed
|
|
2672
|
+
};
|
|
2673
|
+
}
|
|
2674
|
+
async embedBatch(texts) {
|
|
2675
|
+
const requestBatches = this.splitIntoRequestBatches(texts);
|
|
2676
|
+
const embeddings = [];
|
|
2677
|
+
let totalTokensUsed = 0;
|
|
2678
|
+
for (const batch of requestBatches) {
|
|
2679
|
+
const result = await this.embedRequest(batch);
|
|
2680
|
+
embeddings.push(...result.embeddings);
|
|
2681
|
+
totalTokensUsed += result.totalTokensUsed;
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
embeddings,
|
|
2685
|
+
totalTokensUsed
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2425
2688
|
getModelInfo() {
|
|
2426
2689
|
return this.modelInfo;
|
|
2427
2690
|
}
|
|
@@ -2429,8 +2692,8 @@ var CustomEmbeddingProvider = class {
|
|
|
2429
2692
|
|
|
2430
2693
|
// src/utils/files.ts
|
|
2431
2694
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
2432
|
-
var
|
|
2433
|
-
var
|
|
2695
|
+
var import_fs3 = require("fs");
|
|
2696
|
+
var path3 = __toESM(require("path"), 1);
|
|
2434
2697
|
function createIgnoreFilter(projectRoot) {
|
|
2435
2698
|
const ig = (0, import_ignore.default)();
|
|
2436
2699
|
const defaultIgnores = [
|
|
@@ -2447,9 +2710,9 @@ function createIgnoreFilter(projectRoot) {
|
|
|
2447
2710
|
".opencode"
|
|
2448
2711
|
];
|
|
2449
2712
|
ig.add(defaultIgnores);
|
|
2450
|
-
const gitignorePath =
|
|
2451
|
-
if ((0,
|
|
2452
|
-
const gitignoreContent = (0,
|
|
2713
|
+
const gitignorePath = path3.join(projectRoot, ".gitignore");
|
|
2714
|
+
if ((0, import_fs3.existsSync)(gitignorePath)) {
|
|
2715
|
+
const gitignoreContent = (0, import_fs3.readFileSync)(gitignorePath, "utf-8");
|
|
2453
2716
|
ig.add(gitignoreContent);
|
|
2454
2717
|
}
|
|
2455
2718
|
return ig;
|
|
@@ -2463,10 +2726,10 @@ function matchGlob(filePath, pattern) {
|
|
|
2463
2726
|
return regex.test(filePath);
|
|
2464
2727
|
}
|
|
2465
2728
|
async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns, ignoreFilter, maxFileSize, skipped) {
|
|
2466
|
-
const entries = await
|
|
2729
|
+
const entries = await import_fs3.promises.readdir(dir, { withFileTypes: true });
|
|
2467
2730
|
for (const entry of entries) {
|
|
2468
|
-
const fullPath =
|
|
2469
|
-
const relativePath =
|
|
2731
|
+
const fullPath = path3.join(dir, entry.name);
|
|
2732
|
+
const relativePath = path3.relative(projectRoot, fullPath);
|
|
2470
2733
|
if (ignoreFilter.ignores(relativePath)) {
|
|
2471
2734
|
if (entry.isFile()) {
|
|
2472
2735
|
skipped.push({ path: relativePath, reason: "gitignore" });
|
|
@@ -2484,7 +2747,7 @@ async function* walkDirectory(dir, projectRoot, includePatterns, excludePatterns
|
|
|
2484
2747
|
skipped
|
|
2485
2748
|
);
|
|
2486
2749
|
} else if (entry.isFile()) {
|
|
2487
|
-
const stat = await
|
|
2750
|
+
const stat = await import_fs3.promises.stat(fullPath);
|
|
2488
2751
|
if (stat.size > maxFileSize) {
|
|
2489
2752
|
skipped.push({ path: relativePath, reason: "too_large" });
|
|
2490
2753
|
continue;
|
|
@@ -2527,6 +2790,9 @@ async function collectFiles(projectRoot, includePatterns, excludePatterns, maxFi
|
|
|
2527
2790
|
}
|
|
2528
2791
|
|
|
2529
2792
|
// src/utils/cost.ts
|
|
2793
|
+
function estimateTokens(text) {
|
|
2794
|
+
return Math.ceil(text.length / 4);
|
|
2795
|
+
}
|
|
2530
2796
|
function estimateChunksFromFiles(files) {
|
|
2531
2797
|
let totalChunks = 0;
|
|
2532
2798
|
for (const file of files) {
|
|
@@ -2881,8 +3147,9 @@ function initializeLogger(config) {
|
|
|
2881
3147
|
}
|
|
2882
3148
|
|
|
2883
3149
|
// src/native/index.ts
|
|
2884
|
-
var
|
|
3150
|
+
var path4 = __toESM(require("path"), 1);
|
|
2885
3151
|
var os2 = __toESM(require("os"), 1);
|
|
3152
|
+
var module2 = __toESM(require("module"), 1);
|
|
2886
3153
|
var import_url = require("url");
|
|
2887
3154
|
var import_meta = {};
|
|
2888
3155
|
function getNativeBinding() {
|
|
@@ -2903,17 +3170,22 @@ function getNativeBinding() {
|
|
|
2903
3170
|
throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
|
|
2904
3171
|
}
|
|
2905
3172
|
let currentDir;
|
|
3173
|
+
let requireTarget;
|
|
2906
3174
|
if (typeof import_meta !== "undefined" && import_meta.url) {
|
|
2907
|
-
currentDir =
|
|
3175
|
+
currentDir = path4.dirname((0, import_url.fileURLToPath)(import_meta.url));
|
|
3176
|
+
requireTarget = import_meta.url;
|
|
2908
3177
|
} else if (typeof __dirname !== "undefined") {
|
|
2909
3178
|
currentDir = __dirname;
|
|
3179
|
+
requireTarget = __filename;
|
|
2910
3180
|
} else {
|
|
2911
3181
|
currentDir = process.cwd();
|
|
3182
|
+
requireTarget = path4.join(currentDir, "index.js");
|
|
2912
3183
|
}
|
|
2913
3184
|
const isDevMode = currentDir.includes("/src/native");
|
|
2914
|
-
const packageRoot = isDevMode ?
|
|
2915
|
-
const nativePath =
|
|
2916
|
-
|
|
3185
|
+
const packageRoot = isDevMode ? path4.resolve(currentDir, "../..") : path4.resolve(currentDir, "..");
|
|
3186
|
+
const nativePath = path4.join(packageRoot, "native", bindingName);
|
|
3187
|
+
const require2 = module2.createRequire(requireTarget);
|
|
3188
|
+
return require2(nativePath);
|
|
2917
3189
|
}
|
|
2918
3190
|
var native = getNativeBinding();
|
|
2919
3191
|
function parseFiles(files) {
|
|
@@ -3031,7 +3303,7 @@ var VectorStore = class {
|
|
|
3031
3303
|
var CHARS_PER_TOKEN = 4;
|
|
3032
3304
|
var MAX_BATCH_TOKENS = 7500;
|
|
3033
3305
|
var MAX_SINGLE_CHUNK_TOKENS = 2e3;
|
|
3034
|
-
function
|
|
3306
|
+
function estimateTokens2(text) {
|
|
3035
3307
|
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3036
3308
|
}
|
|
3037
3309
|
function createEmbeddingText(chunk, filePath) {
|
|
@@ -3096,7 +3368,7 @@ function createDynamicBatches(chunks) {
|
|
|
3096
3368
|
let currentBatch = [];
|
|
3097
3369
|
let currentTokens = 0;
|
|
3098
3370
|
for (const chunk of chunks) {
|
|
3099
|
-
const chunkTokens =
|
|
3371
|
+
const chunkTokens = estimateTokens2(chunk.text);
|
|
3100
3372
|
if (currentBatch.length > 0 && currentTokens + chunkTokens > MAX_BATCH_TOKENS) {
|
|
3101
3373
|
batches.push(currentBatch);
|
|
3102
3374
|
currentBatch = [];
|
|
@@ -3393,26 +3665,55 @@ var Database = class {
|
|
|
3393
3665
|
};
|
|
3394
3666
|
|
|
3395
3667
|
// src/git/index.ts
|
|
3396
|
-
var
|
|
3397
|
-
var
|
|
3398
|
-
|
|
3668
|
+
var import_fs4 = require("fs");
|
|
3669
|
+
var path5 = __toESM(require("path"), 1);
|
|
3670
|
+
function readPackedRefs(gitDir) {
|
|
3671
|
+
const packedRefsPath = path5.join(gitDir, "packed-refs");
|
|
3672
|
+
if (!(0, import_fs4.existsSync)(packedRefsPath)) {
|
|
3673
|
+
return [];
|
|
3674
|
+
}
|
|
3675
|
+
try {
|
|
3676
|
+
return (0, import_fs4.readFileSync)(packedRefsPath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#") && !line.startsWith("^"));
|
|
3677
|
+
} catch {
|
|
3678
|
+
return [];
|
|
3679
|
+
}
|
|
3680
|
+
}
|
|
3681
|
+
function resolveCommonGitDir(gitDir) {
|
|
3682
|
+
const commonDirPath = path5.join(gitDir, "commondir");
|
|
3683
|
+
if (!(0, import_fs4.existsSync)(commonDirPath)) {
|
|
3684
|
+
return gitDir;
|
|
3685
|
+
}
|
|
3686
|
+
try {
|
|
3687
|
+
const raw = (0, import_fs4.readFileSync)(commonDirPath, "utf-8").trim();
|
|
3688
|
+
if (!raw) {
|
|
3689
|
+
return gitDir;
|
|
3690
|
+
}
|
|
3691
|
+
const resolved = path5.isAbsolute(raw) ? raw : path5.resolve(gitDir, raw);
|
|
3692
|
+
if ((0, import_fs4.existsSync)(resolved)) {
|
|
3693
|
+
return resolved;
|
|
3694
|
+
}
|
|
3695
|
+
} catch {
|
|
3696
|
+
return gitDir;
|
|
3697
|
+
}
|
|
3698
|
+
return gitDir;
|
|
3699
|
+
}
|
|
3399
3700
|
function resolveGitDir(repoRoot) {
|
|
3400
|
-
const gitPath =
|
|
3401
|
-
if (!(0,
|
|
3701
|
+
const gitPath = path5.join(repoRoot, ".git");
|
|
3702
|
+
if (!(0, import_fs4.existsSync)(gitPath)) {
|
|
3402
3703
|
return null;
|
|
3403
3704
|
}
|
|
3404
3705
|
try {
|
|
3405
|
-
const stat = (0,
|
|
3706
|
+
const stat = (0, import_fs4.statSync)(gitPath);
|
|
3406
3707
|
if (stat.isDirectory()) {
|
|
3407
3708
|
return gitPath;
|
|
3408
3709
|
}
|
|
3409
3710
|
if (stat.isFile()) {
|
|
3410
|
-
const content = (0,
|
|
3711
|
+
const content = (0, import_fs4.readFileSync)(gitPath, "utf-8").trim();
|
|
3411
3712
|
const match = content.match(/^gitdir:\s*(.+)$/);
|
|
3412
3713
|
if (match) {
|
|
3413
3714
|
const gitdir = match[1];
|
|
3414
|
-
const resolvedPath =
|
|
3415
|
-
if ((0,
|
|
3715
|
+
const resolvedPath = path5.isAbsolute(gitdir) ? gitdir : path5.resolve(repoRoot, gitdir);
|
|
3716
|
+
if ((0, import_fs4.existsSync)(resolvedPath)) {
|
|
3416
3717
|
return resolvedPath;
|
|
3417
3718
|
}
|
|
3418
3719
|
}
|
|
@@ -3429,12 +3730,12 @@ function getCurrentBranch(repoRoot) {
|
|
|
3429
3730
|
if (!gitDir) {
|
|
3430
3731
|
return null;
|
|
3431
3732
|
}
|
|
3432
|
-
const headPath =
|
|
3433
|
-
if (!(0,
|
|
3733
|
+
const headPath = path5.join(gitDir, "HEAD");
|
|
3734
|
+
if (!(0, import_fs4.existsSync)(headPath)) {
|
|
3434
3735
|
return null;
|
|
3435
3736
|
}
|
|
3436
3737
|
try {
|
|
3437
|
-
const headContent = (0,
|
|
3738
|
+
const headContent = (0, import_fs4.readFileSync)(headPath, "utf-8").trim();
|
|
3438
3739
|
const match = headContent.match(/^ref: refs\/heads\/(.+)$/);
|
|
3439
3740
|
if (match) {
|
|
3440
3741
|
return match[1];
|
|
@@ -3449,37 +3750,20 @@ function getCurrentBranch(repoRoot) {
|
|
|
3449
3750
|
}
|
|
3450
3751
|
function getBaseBranch(repoRoot) {
|
|
3451
3752
|
const gitDir = resolveGitDir(repoRoot);
|
|
3753
|
+
const refStoreDir = gitDir ? resolveCommonGitDir(gitDir) : null;
|
|
3452
3754
|
const candidates = ["main", "master", "develop", "trunk"];
|
|
3453
|
-
if (
|
|
3755
|
+
if (refStoreDir) {
|
|
3454
3756
|
for (const candidate of candidates) {
|
|
3455
|
-
const refPath =
|
|
3456
|
-
if ((0,
|
|
3757
|
+
const refPath = path5.join(refStoreDir, "refs", "heads", candidate);
|
|
3758
|
+
if ((0, import_fs4.existsSync)(refPath)) {
|
|
3457
3759
|
return candidate;
|
|
3458
3760
|
}
|
|
3459
|
-
const
|
|
3460
|
-
if ((
|
|
3461
|
-
|
|
3462
|
-
const content = (0, import_fs3.readFileSync)(packedRefsPath, "utf-8");
|
|
3463
|
-
if (content.includes(`refs/heads/${candidate}`)) {
|
|
3464
|
-
return candidate;
|
|
3465
|
-
}
|
|
3466
|
-
} catch {
|
|
3467
|
-
}
|
|
3761
|
+
const packedRefs = readPackedRefs(refStoreDir);
|
|
3762
|
+
if (packedRefs.some((line) => line.endsWith(` refs/heads/${candidate}`))) {
|
|
3763
|
+
return candidate;
|
|
3468
3764
|
}
|
|
3469
3765
|
}
|
|
3470
3766
|
}
|
|
3471
|
-
try {
|
|
3472
|
-
const result = (0, import_child_process.execSync)("git remote show origin", {
|
|
3473
|
-
cwd: repoRoot,
|
|
3474
|
-
encoding: "utf-8",
|
|
3475
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3476
|
-
});
|
|
3477
|
-
const match = result.match(/HEAD branch: (.+)/);
|
|
3478
|
-
if (match) {
|
|
3479
|
-
return match[1].trim();
|
|
3480
|
-
}
|
|
3481
|
-
} catch {
|
|
3482
|
-
}
|
|
3483
3767
|
return getCurrentBranch(repoRoot) ?? "main";
|
|
3484
3768
|
}
|
|
3485
3769
|
function getBranchOrDefault(repoRoot) {
|
|
@@ -3490,7 +3774,7 @@ function getBranchOrDefault(repoRoot) {
|
|
|
3490
3774
|
}
|
|
3491
3775
|
|
|
3492
3776
|
// src/indexer/index.ts
|
|
3493
|
-
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust"]);
|
|
3777
|
+
var CALL_GRAPH_LANGUAGES = /* @__PURE__ */ new Set(["typescript", "tsx", "javascript", "jsx", "python", "go", "rust", "php"]);
|
|
3494
3778
|
var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
3495
3779
|
"function_declaration",
|
|
3496
3780
|
"function",
|
|
@@ -3511,7 +3795,8 @@ var CALL_GRAPH_SYMBOL_CHUNK_TYPES = /* @__PURE__ */ new Set([
|
|
|
3511
3795
|
"struct_item",
|
|
3512
3796
|
"enum_item",
|
|
3513
3797
|
"trait_item",
|
|
3514
|
-
"mod_item"
|
|
3798
|
+
"mod_item",
|
|
3799
|
+
"trait_declaration"
|
|
3515
3800
|
]);
|
|
3516
3801
|
function float32ArrayToBuffer(arr) {
|
|
3517
3802
|
const float32 = new Float32Array(arr);
|
|
@@ -3893,8 +4178,8 @@ function stripFilePathHint(query) {
|
|
|
3893
4178
|
const stripped = query.replace(FILE_PATH_HINT_SUFFIX_REGEX, "").trim();
|
|
3894
4179
|
return stripped.length > 0 ? stripped : query;
|
|
3895
4180
|
}
|
|
3896
|
-
function buildDeterministicIdentifierPass(query, candidates, limit) {
|
|
3897
|
-
if (
|
|
4181
|
+
function buildDeterministicIdentifierPass(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4182
|
+
if (!prioritizeSourcePaths) {
|
|
3898
4183
|
return [];
|
|
3899
4184
|
}
|
|
3900
4185
|
const primary = extractPrimaryIdentifierQueryHint(query);
|
|
@@ -4110,9 +4395,8 @@ function rankHybridResults(query, semanticResults, keywordResults, options) {
|
|
|
4110
4395
|
const fused = options.fusionStrategy === "rrf" ? fuseResultsRrf(semanticResults, keywordResults, options.rrfK, overfetchLimit) : fuseResultsWeighted(semanticResults, keywordResults, options.hybridWeight, overfetchLimit);
|
|
4111
4396
|
const rerankPoolLimit = Math.max(overfetchLimit, options.rerankTopN * 3, options.limit * 6);
|
|
4112
4397
|
const rerankPool = fused.slice(0, rerankPoolLimit);
|
|
4113
|
-
const intent = classifyQueryIntentRaw(query);
|
|
4114
4398
|
return rerankResults(query, rerankPool, options.rerankTopN, {
|
|
4115
|
-
prioritizeSourcePaths:
|
|
4399
|
+
prioritizeSourcePaths: options.prioritizeSourcePaths ?? classifyQueryIntentRaw(query) === "source"
|
|
4116
4400
|
});
|
|
4117
4401
|
}
|
|
4118
4402
|
function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
@@ -4122,11 +4406,11 @@ function rankSemanticOnlyResults(query, semanticResults, options) {
|
|
|
4122
4406
|
prioritizeSourcePaths: options.prioritizeSourcePaths ?? false
|
|
4123
4407
|
});
|
|
4124
4408
|
}
|
|
4125
|
-
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds) {
|
|
4409
|
+
function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCandidates, database, branchChunkIds, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4126
4410
|
if (combined.length === 0) {
|
|
4127
4411
|
return combined;
|
|
4128
4412
|
}
|
|
4129
|
-
if (
|
|
4413
|
+
if (!prioritizeSourcePaths) {
|
|
4130
4414
|
return combined;
|
|
4131
4415
|
}
|
|
4132
4416
|
const identifierHints = extractIdentifierHints(query);
|
|
@@ -4216,8 +4500,8 @@ function promoteIdentifierMatches(query, combined, semanticCandidates, keywordCa
|
|
|
4216
4500
|
const remainder = combined.filter((candidate) => !promotedIds.has(candidate.id));
|
|
4217
4501
|
return [...promoted, ...remainder];
|
|
4218
4502
|
}
|
|
4219
|
-
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates) {
|
|
4220
|
-
if (
|
|
4503
|
+
function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallbackCandidates, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4504
|
+
if (!prioritizeSourcePaths) {
|
|
4221
4505
|
return [];
|
|
4222
4506
|
}
|
|
4223
4507
|
const identifierHints = extractIdentifierHints(query);
|
|
@@ -4362,8 +4646,8 @@ function buildSymbolDefinitionLane(query, database, branchChunkIds, limit, fallb
|
|
|
4362
4646
|
const withFallback = Array.from(symbolCandidates.values()).sort((a, b) => b.score - a.score || a.id.localeCompare(b.id));
|
|
4363
4647
|
return withFallback.slice(0, Math.max(limit * 2, limit));
|
|
4364
4648
|
}
|
|
4365
|
-
function buildIdentifierDefinitionLane(query, candidates, limit) {
|
|
4366
|
-
if (
|
|
4649
|
+
function buildIdentifierDefinitionLane(query, candidates, limit, prioritizeSourcePaths = classifyQueryIntentRaw(query) === "source") {
|
|
4650
|
+
if (!prioritizeSourcePaths) {
|
|
4367
4651
|
return [];
|
|
4368
4652
|
}
|
|
4369
4653
|
const primaryHint = extractPrimaryIdentifierQueryHint(query);
|
|
@@ -4448,22 +4732,22 @@ var Indexer = class {
|
|
|
4448
4732
|
this.projectRoot = projectRoot;
|
|
4449
4733
|
this.config = config;
|
|
4450
4734
|
this.indexPath = this.getIndexPath();
|
|
4451
|
-
this.fileHashCachePath =
|
|
4452
|
-
this.failedBatchesPath =
|
|
4453
|
-
this.indexingLockPath =
|
|
4735
|
+
this.fileHashCachePath = path6.join(this.indexPath, "file-hashes.json");
|
|
4736
|
+
this.failedBatchesPath = path6.join(this.indexPath, "failed-batches.json");
|
|
4737
|
+
this.indexingLockPath = path6.join(this.indexPath, "indexing.lock");
|
|
4454
4738
|
this.logger = initializeLogger(config.debug);
|
|
4455
4739
|
}
|
|
4456
4740
|
getIndexPath() {
|
|
4457
4741
|
if (this.config.scope === "global") {
|
|
4458
4742
|
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
4459
|
-
return
|
|
4743
|
+
return path6.join(homeDir, ".opencode", "global-index");
|
|
4460
4744
|
}
|
|
4461
|
-
return
|
|
4745
|
+
return path6.join(this.projectRoot, ".opencode", "index");
|
|
4462
4746
|
}
|
|
4463
4747
|
loadFileHashCache() {
|
|
4464
4748
|
try {
|
|
4465
|
-
if ((0,
|
|
4466
|
-
const data = (0,
|
|
4749
|
+
if ((0, import_fs5.existsSync)(this.fileHashCachePath)) {
|
|
4750
|
+
const data = (0, import_fs5.readFileSync)(this.fileHashCachePath, "utf-8");
|
|
4467
4751
|
const parsed = JSON.parse(data);
|
|
4468
4752
|
this.fileHashCache = new Map(Object.entries(parsed));
|
|
4469
4753
|
}
|
|
@@ -4480,28 +4764,28 @@ var Indexer = class {
|
|
|
4480
4764
|
}
|
|
4481
4765
|
atomicWriteSync(targetPath, data) {
|
|
4482
4766
|
const tempPath = `${targetPath}.tmp`;
|
|
4483
|
-
(0,
|
|
4484
|
-
(0,
|
|
4767
|
+
(0, import_fs5.writeFileSync)(tempPath, data);
|
|
4768
|
+
(0, import_fs5.renameSync)(tempPath, targetPath);
|
|
4485
4769
|
}
|
|
4486
4770
|
checkForInterruptedIndexing() {
|
|
4487
|
-
return (0,
|
|
4771
|
+
return (0, import_fs5.existsSync)(this.indexingLockPath);
|
|
4488
4772
|
}
|
|
4489
4773
|
acquireIndexingLock() {
|
|
4490
4774
|
const lockData = {
|
|
4491
4775
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4492
4776
|
pid: process.pid
|
|
4493
4777
|
};
|
|
4494
|
-
(0,
|
|
4778
|
+
(0, import_fs5.writeFileSync)(this.indexingLockPath, JSON.stringify(lockData));
|
|
4495
4779
|
}
|
|
4496
4780
|
releaseIndexingLock() {
|
|
4497
|
-
if ((0,
|
|
4498
|
-
(0,
|
|
4781
|
+
if ((0, import_fs5.existsSync)(this.indexingLockPath)) {
|
|
4782
|
+
(0, import_fs5.unlinkSync)(this.indexingLockPath);
|
|
4499
4783
|
}
|
|
4500
4784
|
}
|
|
4501
4785
|
async recoverFromInterruptedIndexing() {
|
|
4502
4786
|
this.logger.warn("Detected interrupted indexing session, recovering...");
|
|
4503
|
-
if ((0,
|
|
4504
|
-
(0,
|
|
4787
|
+
if ((0, import_fs5.existsSync)(this.fileHashCachePath)) {
|
|
4788
|
+
(0, import_fs5.unlinkSync)(this.fileHashCachePath);
|
|
4505
4789
|
}
|
|
4506
4790
|
await this.healthCheck();
|
|
4507
4791
|
this.releaseIndexingLock();
|
|
@@ -4509,8 +4793,8 @@ var Indexer = class {
|
|
|
4509
4793
|
}
|
|
4510
4794
|
loadFailedBatches() {
|
|
4511
4795
|
try {
|
|
4512
|
-
if ((0,
|
|
4513
|
-
const data = (0,
|
|
4796
|
+
if ((0, import_fs5.existsSync)(this.failedBatchesPath)) {
|
|
4797
|
+
const data = (0, import_fs5.readFileSync)(this.failedBatchesPath, "utf-8");
|
|
4514
4798
|
return JSON.parse(data);
|
|
4515
4799
|
}
|
|
4516
4800
|
} catch {
|
|
@@ -4520,13 +4804,13 @@ var Indexer = class {
|
|
|
4520
4804
|
}
|
|
4521
4805
|
saveFailedBatches(batches) {
|
|
4522
4806
|
if (batches.length === 0) {
|
|
4523
|
-
if ((0,
|
|
4524
|
-
|
|
4807
|
+
if ((0, import_fs5.existsSync)(this.failedBatchesPath)) {
|
|
4808
|
+
import_fs5.promises.unlink(this.failedBatchesPath).catch(() => {
|
|
4525
4809
|
});
|
|
4526
4810
|
}
|
|
4527
4811
|
return;
|
|
4528
4812
|
}
|
|
4529
|
-
(0,
|
|
4813
|
+
(0, import_fs5.writeFileSync)(this.failedBatchesPath, JSON.stringify(batches, null, 2));
|
|
4530
4814
|
}
|
|
4531
4815
|
addFailedBatch(batch, error) {
|
|
4532
4816
|
const existing = this.loadFailedBatches();
|
|
@@ -4583,26 +4867,26 @@ var Indexer = class {
|
|
|
4583
4867
|
scope: this.config.scope
|
|
4584
4868
|
});
|
|
4585
4869
|
this.provider = createEmbeddingProvider(this.configuredProviderInfo);
|
|
4586
|
-
await
|
|
4870
|
+
await import_fs5.promises.mkdir(this.indexPath, { recursive: true });
|
|
4587
4871
|
const dimensions = this.configuredProviderInfo.modelInfo.dimensions;
|
|
4588
|
-
const storePath =
|
|
4872
|
+
const storePath = path6.join(this.indexPath, "vectors");
|
|
4589
4873
|
this.store = new VectorStore(storePath, dimensions);
|
|
4590
|
-
const indexFilePath =
|
|
4591
|
-
if ((0,
|
|
4874
|
+
const indexFilePath = path6.join(this.indexPath, "vectors.usearch");
|
|
4875
|
+
if ((0, import_fs5.existsSync)(indexFilePath)) {
|
|
4592
4876
|
this.store.load();
|
|
4593
4877
|
}
|
|
4594
|
-
const invertedIndexPath =
|
|
4878
|
+
const invertedIndexPath = path6.join(this.indexPath, "inverted-index.json");
|
|
4595
4879
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
4596
4880
|
try {
|
|
4597
4881
|
this.invertedIndex.load();
|
|
4598
4882
|
} catch {
|
|
4599
|
-
if ((0,
|
|
4600
|
-
await
|
|
4883
|
+
if ((0, import_fs5.existsSync)(invertedIndexPath)) {
|
|
4884
|
+
await import_fs5.promises.unlink(invertedIndexPath);
|
|
4601
4885
|
}
|
|
4602
4886
|
this.invertedIndex = new InvertedIndex(invertedIndexPath);
|
|
4603
4887
|
}
|
|
4604
|
-
const dbPath =
|
|
4605
|
-
const dbIsNew = !(0,
|
|
4888
|
+
const dbPath = path6.join(this.indexPath, "codebase.db");
|
|
4889
|
+
const dbIsNew = !(0, import_fs5.existsSync)(dbPath);
|
|
4606
4890
|
this.database = new Database(dbPath);
|
|
4607
4891
|
if (this.checkForInterruptedIndexing()) {
|
|
4608
4892
|
await this.recoverFromInterruptedIndexing();
|
|
@@ -4834,7 +5118,7 @@ var Indexer = class {
|
|
|
4834
5118
|
unchangedFilePaths.add(f.path);
|
|
4835
5119
|
this.logger.recordCacheHit();
|
|
4836
5120
|
} else {
|
|
4837
|
-
const content = await
|
|
5121
|
+
const content = await import_fs5.promises.readFile(f.path, "utf-8");
|
|
4838
5122
|
changedFiles.push({ path: f.path, content, hash: currentHash });
|
|
4839
5123
|
this.logger.recordCacheMiss();
|
|
4840
5124
|
}
|
|
@@ -4880,7 +5164,7 @@ var Indexer = class {
|
|
|
4880
5164
|
for (const parsed of parsedFiles) {
|
|
4881
5165
|
currentFilePaths.add(parsed.path);
|
|
4882
5166
|
if (parsed.chunks.length === 0) {
|
|
4883
|
-
const relativePath =
|
|
5167
|
+
const relativePath = path6.relative(this.projectRoot, parsed.path);
|
|
4884
5168
|
stats.parseFailures.push(relativePath);
|
|
4885
5169
|
}
|
|
4886
5170
|
let fileChunkCount = 0;
|
|
@@ -5091,7 +5375,7 @@ var Indexer = class {
|
|
|
5091
5375
|
for (const batch of dynamicBatches) {
|
|
5092
5376
|
queue.add(async () => {
|
|
5093
5377
|
if (rateLimitBackoffMs > 0) {
|
|
5094
|
-
await new Promise((
|
|
5378
|
+
await new Promise((resolve5) => setTimeout(resolve5, rateLimitBackoffMs));
|
|
5095
5379
|
}
|
|
5096
5380
|
try {
|
|
5097
5381
|
const result = await pRetry(
|
|
@@ -5295,6 +5579,7 @@ var Indexer = class {
|
|
|
5295
5579
|
const rrfK = this.config.search.rrfK;
|
|
5296
5580
|
const rerankTopN = this.config.search.rerankTopN;
|
|
5297
5581
|
const filterByBranch = options?.filterByBranch ?? true;
|
|
5582
|
+
const sourceIntent = options?.definitionIntent === true || classifyQueryIntentRaw(query) === "source";
|
|
5298
5583
|
this.logger.search("debug", "Starting search", {
|
|
5299
5584
|
query,
|
|
5300
5585
|
maxResults,
|
|
@@ -5346,7 +5631,8 @@ var Indexer = class {
|
|
|
5346
5631
|
rrfK,
|
|
5347
5632
|
rerankTopN,
|
|
5348
5633
|
limit: maxResults,
|
|
5349
|
-
hybridWeight
|
|
5634
|
+
hybridWeight,
|
|
5635
|
+
prioritizeSourcePaths: sourceIntent
|
|
5350
5636
|
});
|
|
5351
5637
|
const fusionMs = import_perf_hooks.performance.now() - fusionStartTime;
|
|
5352
5638
|
const rescued = promoteIdentifierMatches(
|
|
@@ -5355,30 +5641,33 @@ var Indexer = class {
|
|
|
5355
5641
|
semanticCandidates,
|
|
5356
5642
|
keywordCandidates,
|
|
5357
5643
|
database,
|
|
5358
|
-
branchChunkIds
|
|
5644
|
+
branchChunkIds,
|
|
5645
|
+
sourceIntent
|
|
5359
5646
|
);
|
|
5360
5647
|
const union = unionCandidates(semanticCandidates, keywordCandidates);
|
|
5361
5648
|
const deterministicIdentifierLane = buildDeterministicIdentifierPass(
|
|
5362
5649
|
query,
|
|
5363
5650
|
union,
|
|
5364
|
-
maxResults
|
|
5651
|
+
maxResults,
|
|
5652
|
+
sourceIntent
|
|
5365
5653
|
);
|
|
5366
5654
|
const identifierLane = buildIdentifierDefinitionLane(
|
|
5367
5655
|
query,
|
|
5368
5656
|
union,
|
|
5369
|
-
maxResults
|
|
5657
|
+
maxResults,
|
|
5658
|
+
sourceIntent
|
|
5370
5659
|
);
|
|
5371
5660
|
const symbolLane = buildSymbolDefinitionLane(
|
|
5372
5661
|
query,
|
|
5373
5662
|
database,
|
|
5374
5663
|
branchChunkIds,
|
|
5375
5664
|
maxResults,
|
|
5376
|
-
union
|
|
5665
|
+
union,
|
|
5666
|
+
sourceIntent
|
|
5377
5667
|
);
|
|
5378
5668
|
const prePrimaryLane = mergeTieredResults(deterministicIdentifierLane, identifierLane, maxResults * 4);
|
|
5379
5669
|
const primaryLane = mergeTieredResults(prePrimaryLane, symbolLane, maxResults * 4);
|
|
5380
5670
|
const tiered = mergeTieredResults(primaryLane, rescued, maxResults * 4);
|
|
5381
|
-
const sourceIntent = classifyQueryIntentRaw(query) === "source";
|
|
5382
5671
|
const hasCodeHints = extractCodeTermHints(query).length > 0 || extractIdentifierHints(query).length > 0;
|
|
5383
5672
|
const baseFiltered = tiered.filter((r) => {
|
|
5384
5673
|
if (r.score < this.config.search.minScore) return false;
|
|
@@ -5424,7 +5713,7 @@ var Indexer = class {
|
|
|
5424
5713
|
let contextEndLine = r.metadata.endLine;
|
|
5425
5714
|
if (!metadataOnly && this.config.search.includeContext) {
|
|
5426
5715
|
try {
|
|
5427
|
-
const fileContent = await
|
|
5716
|
+
const fileContent = await import_fs5.promises.readFile(
|
|
5428
5717
|
r.metadata.filePath,
|
|
5429
5718
|
"utf-8"
|
|
5430
5719
|
);
|
|
@@ -5511,7 +5800,7 @@ var Indexer = class {
|
|
|
5511
5800
|
const removedFilePaths = [];
|
|
5512
5801
|
let removedCount = 0;
|
|
5513
5802
|
for (const [filePath, chunkKeys] of filePathsToChunkKeys) {
|
|
5514
|
-
if (!(0,
|
|
5803
|
+
if (!(0, import_fs5.existsSync)(filePath)) {
|
|
5515
5804
|
for (const key of chunkKeys) {
|
|
5516
5805
|
store.remove(key);
|
|
5517
5806
|
invertedIndex.removeChunk(key);
|
|
@@ -5703,7 +5992,7 @@ var Indexer = class {
|
|
|
5703
5992
|
let content = "";
|
|
5704
5993
|
if (this.config.search.includeContext) {
|
|
5705
5994
|
try {
|
|
5706
|
-
const fileContent = await
|
|
5995
|
+
const fileContent = await import_fs5.promises.readFile(
|
|
5707
5996
|
r.metadata.filePath,
|
|
5708
5997
|
"utf-8"
|
|
5709
5998
|
);
|
|
@@ -5735,7 +6024,896 @@ var Indexer = class {
|
|
|
5735
6024
|
}
|
|
5736
6025
|
};
|
|
5737
6026
|
|
|
6027
|
+
// src/eval/budget.ts
|
|
6028
|
+
function evaluateBudgetGate(budget, summary, comparison) {
|
|
6029
|
+
const BASELINE_P95_EPSILON_MS = 1e-3;
|
|
6030
|
+
const violations = [];
|
|
6031
|
+
const { thresholds } = budget;
|
|
6032
|
+
if (thresholds.minHitAt5 !== void 0 && summary.metrics.hitAt5 < thresholds.minHitAt5) {
|
|
6033
|
+
violations.push({
|
|
6034
|
+
metric: "minHitAt5",
|
|
6035
|
+
message: `Hit@5 ${summary.metrics.hitAt5.toFixed(4)} is below minimum ${thresholds.minHitAt5.toFixed(4)}`
|
|
6036
|
+
});
|
|
6037
|
+
}
|
|
6038
|
+
if (thresholds.minMrrAt10 !== void 0 && summary.metrics.mrrAt10 < thresholds.minMrrAt10) {
|
|
6039
|
+
violations.push({
|
|
6040
|
+
metric: "minMrrAt10",
|
|
6041
|
+
message: `MRR@10 ${summary.metrics.mrrAt10.toFixed(4)} is below minimum ${thresholds.minMrrAt10.toFixed(4)}`
|
|
6042
|
+
});
|
|
6043
|
+
}
|
|
6044
|
+
if (comparison) {
|
|
6045
|
+
if (thresholds.hitAt5MaxDrop !== void 0 && comparison.deltas.hitAt5.absolute < -thresholds.hitAt5MaxDrop) {
|
|
6046
|
+
violations.push({
|
|
6047
|
+
metric: "hitAt5MaxDrop",
|
|
6048
|
+
message: `Hit@5 drop ${comparison.deltas.hitAt5.absolute.toFixed(4)} exceeds allowed -${thresholds.hitAt5MaxDrop.toFixed(4)}`
|
|
6049
|
+
});
|
|
6050
|
+
}
|
|
6051
|
+
if (thresholds.mrrAt10MaxDrop !== void 0 && comparison.deltas.mrrAt10.absolute < -thresholds.mrrAt10MaxDrop) {
|
|
6052
|
+
violations.push({
|
|
6053
|
+
metric: "mrrAt10MaxDrop",
|
|
6054
|
+
message: `MRR@10 drop ${comparison.deltas.mrrAt10.absolute.toFixed(4)} exceeds allowed -${thresholds.mrrAt10MaxDrop.toFixed(4)}`
|
|
6055
|
+
});
|
|
6056
|
+
}
|
|
6057
|
+
if (thresholds.p95LatencyMaxMultiplier !== void 0) {
|
|
6058
|
+
const baselineP95 = comparison.deltas.latencyP95Ms.baseline;
|
|
6059
|
+
if (baselineP95 > BASELINE_P95_EPSILON_MS) {
|
|
6060
|
+
const allowed = baselineP95 * thresholds.p95LatencyMaxMultiplier;
|
|
6061
|
+
if (summary.metrics.latencyMs.p95 > allowed) {
|
|
6062
|
+
violations.push({
|
|
6063
|
+
metric: "p95LatencyMaxMultiplier",
|
|
6064
|
+
message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds allowed ${allowed.toFixed(3)}ms (${thresholds.p95LatencyMaxMultiplier.toFixed(2)}x baseline)`
|
|
6065
|
+
});
|
|
6066
|
+
}
|
|
6067
|
+
}
|
|
6068
|
+
}
|
|
6069
|
+
}
|
|
6070
|
+
if (thresholds.p95LatencyMaxAbsoluteMs !== void 0 && summary.metrics.latencyMs.p95 > thresholds.p95LatencyMaxAbsoluteMs) {
|
|
6071
|
+
violations.push({
|
|
6072
|
+
metric: "p95LatencyMaxAbsoluteMs",
|
|
6073
|
+
message: `p95 latency ${summary.metrics.latencyMs.p95.toFixed(3)}ms exceeds absolute maximum ${thresholds.p95LatencyMaxAbsoluteMs.toFixed(3)}ms`
|
|
6074
|
+
});
|
|
6075
|
+
}
|
|
6076
|
+
return {
|
|
6077
|
+
passed: violations.length === 0,
|
|
6078
|
+
budgetName: budget.name,
|
|
6079
|
+
violations
|
|
6080
|
+
};
|
|
6081
|
+
}
|
|
6082
|
+
|
|
6083
|
+
// src/eval/metrics.ts
|
|
6084
|
+
function percentile(values, p) {
|
|
6085
|
+
if (values.length === 0) return 0;
|
|
6086
|
+
if (values.length === 1) return values[0];
|
|
6087
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
6088
|
+
const x = p * (sorted.length - 1);
|
|
6089
|
+
const lowerIndex = Math.floor(x);
|
|
6090
|
+
const upperIndex = Math.ceil(x);
|
|
6091
|
+
if (lowerIndex === upperIndex) {
|
|
6092
|
+
return sorted[lowerIndex];
|
|
6093
|
+
}
|
|
6094
|
+
const fraction = x - lowerIndex;
|
|
6095
|
+
return sorted[lowerIndex] + fraction * (sorted[upperIndex] - sorted[lowerIndex]);
|
|
6096
|
+
}
|
|
6097
|
+
function normalizePath(input) {
|
|
6098
|
+
return input.replace(/\\/g, "/");
|
|
6099
|
+
}
|
|
6100
|
+
function uniqueResultsByPath(results) {
|
|
6101
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6102
|
+
const unique = [];
|
|
6103
|
+
for (const result of results) {
|
|
6104
|
+
const normalized = normalizePath(result.filePath);
|
|
6105
|
+
if (seen.has(normalized)) continue;
|
|
6106
|
+
seen.add(normalized);
|
|
6107
|
+
unique.push(result);
|
|
6108
|
+
}
|
|
6109
|
+
return unique;
|
|
6110
|
+
}
|
|
6111
|
+
function pathMatchesExpected(actualPath, expectedPath) {
|
|
6112
|
+
const actual = normalizePath(actualPath);
|
|
6113
|
+
const expected = normalizePath(expectedPath);
|
|
6114
|
+
if (actual === expected) return true;
|
|
6115
|
+
return actual.endsWith(`/${expected}`) || expected.endsWith(`/${actual}`);
|
|
6116
|
+
}
|
|
6117
|
+
function getRelevantPaths(query) {
|
|
6118
|
+
const fromExact = query.expected.filePath ? [query.expected.filePath] : [];
|
|
6119
|
+
const fromAcceptable = query.expected.acceptableFiles ?? [];
|
|
6120
|
+
return Array.from(/* @__PURE__ */ new Set([...fromExact, ...fromAcceptable]));
|
|
6121
|
+
}
|
|
6122
|
+
function isRelevantResult(filePath, relevantPaths) {
|
|
6123
|
+
return relevantPaths.some((expected) => pathMatchesExpected(filePath, expected));
|
|
6124
|
+
}
|
|
6125
|
+
function reciprocalRankAtK(results, relevantPaths, k) {
|
|
6126
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6127
|
+
for (let i = 0; i < top.length; i += 1) {
|
|
6128
|
+
if (isRelevantResult(top[i].filePath, relevantPaths)) {
|
|
6129
|
+
return 1 / (i + 1);
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
6132
|
+
return 0;
|
|
6133
|
+
}
|
|
6134
|
+
function ndcgAtK(results, relevantPaths, k) {
|
|
6135
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6136
|
+
const dcg = top.reduce((sum, result, i) => {
|
|
6137
|
+
const rel = isRelevantResult(result.filePath, relevantPaths) ? 1 : 0;
|
|
6138
|
+
return sum + rel / Math.log2(i + 2);
|
|
6139
|
+
}, 0);
|
|
6140
|
+
const idealLen = Math.min(k, relevantPaths.length);
|
|
6141
|
+
const idcg = Array.from({ length: idealLen }, (_, i) => 1 / Math.log2(i + 2)).reduce(
|
|
6142
|
+
(sum, value) => sum + value,
|
|
6143
|
+
0
|
|
6144
|
+
);
|
|
6145
|
+
return idcg === 0 ? 0 : dcg / idcg;
|
|
6146
|
+
}
|
|
6147
|
+
function isDocsOrTestsPath(filePath) {
|
|
6148
|
+
const lowered = normalizePath(filePath).toLowerCase();
|
|
6149
|
+
return lowered.includes("/docs/") || lowered.includes("/test/") || lowered.includes("/tests/") || lowered.includes("readme") || lowered.includes("/benchmarks/");
|
|
6150
|
+
}
|
|
6151
|
+
function classifyFailureBucket(query, results, k) {
|
|
6152
|
+
const relevantPaths = getRelevantPaths(query);
|
|
6153
|
+
const top = uniqueResultsByPath(results).slice(0, k);
|
|
6154
|
+
const hasRelevantTopK = top.some((result) => isRelevantResult(result.filePath, relevantPaths));
|
|
6155
|
+
if (!hasRelevantTopK) {
|
|
6156
|
+
return "no-relevant-hit-top-k";
|
|
6157
|
+
}
|
|
6158
|
+
if (query.expected.symbol) {
|
|
6159
|
+
const hasSymbol = top.some(
|
|
6160
|
+
(result) => isRelevantResult(result.filePath, relevantPaths) && result.name === query.expected.symbol
|
|
6161
|
+
);
|
|
6162
|
+
if (!hasSymbol) return "wrong-symbol";
|
|
6163
|
+
}
|
|
6164
|
+
const top1 = top[0];
|
|
6165
|
+
if (top1 && !isRelevantResult(top1.filePath, relevantPaths) && isDocsOrTestsPath(top1.filePath)) {
|
|
6166
|
+
return "docs-tests-outranking-source";
|
|
6167
|
+
}
|
|
6168
|
+
if (top1 && !isRelevantResult(top1.filePath, relevantPaths)) {
|
|
6169
|
+
return "wrong-file";
|
|
6170
|
+
}
|
|
6171
|
+
return void 0;
|
|
6172
|
+
}
|
|
6173
|
+
function buildPerQueryResult(query, results, latencyMs, k) {
|
|
6174
|
+
const relevantPaths = getRelevantPaths(query);
|
|
6175
|
+
const deduped = uniqueResultsByPath(results);
|
|
6176
|
+
const hitAt = (cutoff) => deduped.slice(0, cutoff).some((result) => isRelevantResult(result.filePath, relevantPaths));
|
|
6177
|
+
const perQuery = {
|
|
6178
|
+
id: query.id,
|
|
6179
|
+
query: query.query,
|
|
6180
|
+
queryType: query.queryType,
|
|
6181
|
+
latencyMs,
|
|
6182
|
+
hitAt1: hitAt(1),
|
|
6183
|
+
hitAt3: hitAt(3),
|
|
6184
|
+
hitAt5: hitAt(5),
|
|
6185
|
+
hitAt10: hitAt(10),
|
|
6186
|
+
reciprocalRankAt10: reciprocalRankAtK(deduped, relevantPaths, 10),
|
|
6187
|
+
ndcgAt10: ndcgAtK(deduped, relevantPaths, 10),
|
|
6188
|
+
failureBucket: classifyFailureBucket(query, results, k),
|
|
6189
|
+
results: deduped
|
|
6190
|
+
};
|
|
6191
|
+
return perQuery;
|
|
6192
|
+
}
|
|
6193
|
+
function computeEvalMetrics(queries, perQuery, embeddingCallCount, embeddingTokensUsed, costPer1MTokensUsd) {
|
|
6194
|
+
const count = perQuery.length;
|
|
6195
|
+
const safeDiv = (value) => count === 0 ? 0 : value / count;
|
|
6196
|
+
const sum = {
|
|
6197
|
+
hitAt1: 0,
|
|
6198
|
+
hitAt3: 0,
|
|
6199
|
+
hitAt5: 0,
|
|
6200
|
+
hitAt10: 0,
|
|
6201
|
+
mrrAt10: 0,
|
|
6202
|
+
ndcgAt10: 0
|
|
6203
|
+
};
|
|
6204
|
+
const failureBuckets = {
|
|
6205
|
+
"wrong-file": 0,
|
|
6206
|
+
"wrong-symbol": 0,
|
|
6207
|
+
"docs-tests-outranking-source": 0,
|
|
6208
|
+
"no-relevant-hit-top-k": 0
|
|
6209
|
+
};
|
|
6210
|
+
const latencies = perQuery.map((item) => item.latencyMs);
|
|
6211
|
+
for (const query of perQuery) {
|
|
6212
|
+
if (query.hitAt1) sum.hitAt1 += 1;
|
|
6213
|
+
if (query.hitAt3) sum.hitAt3 += 1;
|
|
6214
|
+
if (query.hitAt5) sum.hitAt5 += 1;
|
|
6215
|
+
if (query.hitAt10) sum.hitAt10 += 1;
|
|
6216
|
+
sum.mrrAt10 += query.reciprocalRankAt10;
|
|
6217
|
+
sum.ndcgAt10 += query.ndcgAt10;
|
|
6218
|
+
if (query.failureBucket) {
|
|
6219
|
+
failureBuckets[query.failureBucket] += 1;
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
const queryTokens = queries.reduce((acc, q) => acc + estimateTokens(q.query), 0);
|
|
6223
|
+
return {
|
|
6224
|
+
hitAt1: safeDiv(sum.hitAt1),
|
|
6225
|
+
hitAt3: safeDiv(sum.hitAt3),
|
|
6226
|
+
hitAt5: safeDiv(sum.hitAt5),
|
|
6227
|
+
hitAt10: safeDiv(sum.hitAt10),
|
|
6228
|
+
mrrAt10: safeDiv(sum.mrrAt10),
|
|
6229
|
+
ndcgAt10: safeDiv(sum.ndcgAt10),
|
|
6230
|
+
latencyMs: {
|
|
6231
|
+
p50: percentile(latencies, 0.5),
|
|
6232
|
+
p95: percentile(latencies, 0.95),
|
|
6233
|
+
p99: percentile(latencies, 0.99)
|
|
6234
|
+
},
|
|
6235
|
+
tokenEstimate: {
|
|
6236
|
+
queryTokens,
|
|
6237
|
+
embeddingTokensUsed
|
|
6238
|
+
},
|
|
6239
|
+
embedding: {
|
|
6240
|
+
callCount: embeddingCallCount,
|
|
6241
|
+
estimatedCostUsd: embeddingTokensUsed / 1e6 * costPer1MTokensUsd,
|
|
6242
|
+
costPer1MTokensUsd
|
|
6243
|
+
},
|
|
6244
|
+
failureBuckets
|
|
6245
|
+
};
|
|
6246
|
+
}
|
|
6247
|
+
|
|
6248
|
+
// src/eval/schema.ts
|
|
6249
|
+
var import_fs6 = require("fs");
|
|
6250
|
+
function parseJsonFile(filePath) {
|
|
6251
|
+
const content = (0, import_fs6.readFileSync)(filePath, "utf-8");
|
|
6252
|
+
return JSON.parse(content);
|
|
6253
|
+
}
|
|
6254
|
+
function isRecord(value) {
|
|
6255
|
+
return typeof value === "object" && value !== null;
|
|
6256
|
+
}
|
|
6257
|
+
function isStringArray2(value) {
|
|
6258
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
6259
|
+
}
|
|
6260
|
+
function asPositiveNumber(value, path10) {
|
|
6261
|
+
if (typeof value !== "number" || Number.isNaN(value) || value < 0) {
|
|
6262
|
+
throw new Error(`${path10} must be a non-negative number`);
|
|
6263
|
+
}
|
|
6264
|
+
return value;
|
|
6265
|
+
}
|
|
6266
|
+
function parseQueryType(value, path10) {
|
|
6267
|
+
if (value === "definition" || value === "implementation-intent" || value === "similarity" || value === "keyword-heavy") {
|
|
6268
|
+
return value;
|
|
6269
|
+
}
|
|
6270
|
+
throw new Error(
|
|
6271
|
+
`${path10} must be one of: definition, implementation-intent, similarity, keyword-heavy`
|
|
6272
|
+
);
|
|
6273
|
+
}
|
|
6274
|
+
function parseExpected(input, path10) {
|
|
6275
|
+
if (!isRecord(input)) {
|
|
6276
|
+
throw new Error(`${path10} must be an object`);
|
|
6277
|
+
}
|
|
6278
|
+
const filePathRaw = input.filePath;
|
|
6279
|
+
const acceptableFilesRaw = input.acceptableFiles;
|
|
6280
|
+
const symbolRaw = input.symbol;
|
|
6281
|
+
const branchRaw = input.branch;
|
|
6282
|
+
const filePath = typeof filePathRaw === "string" ? filePathRaw : void 0;
|
|
6283
|
+
const acceptableFiles = isStringArray2(acceptableFilesRaw) ? acceptableFilesRaw : void 0;
|
|
6284
|
+
if (!filePath && (!acceptableFiles || acceptableFiles.length === 0)) {
|
|
6285
|
+
throw new Error(`${path10} must include either expected.filePath or expected.acceptableFiles`);
|
|
6286
|
+
}
|
|
6287
|
+
if (acceptableFilesRaw !== void 0 && !isStringArray2(acceptableFilesRaw)) {
|
|
6288
|
+
throw new Error(`${path10}.acceptableFiles must be an array of strings`);
|
|
6289
|
+
}
|
|
6290
|
+
if (symbolRaw !== void 0 && typeof symbolRaw !== "string") {
|
|
6291
|
+
throw new Error(`${path10}.symbol must be a string when provided`);
|
|
6292
|
+
}
|
|
6293
|
+
if (branchRaw !== void 0 && typeof branchRaw !== "string") {
|
|
6294
|
+
throw new Error(`${path10}.branch must be a string when provided`);
|
|
6295
|
+
}
|
|
6296
|
+
return {
|
|
6297
|
+
filePath,
|
|
6298
|
+
acceptableFiles,
|
|
6299
|
+
symbol: typeof symbolRaw === "string" ? symbolRaw : void 0,
|
|
6300
|
+
branch: typeof branchRaw === "string" ? branchRaw : void 0
|
|
6301
|
+
};
|
|
6302
|
+
}
|
|
6303
|
+
function parseQuery(input, index) {
|
|
6304
|
+
const path10 = `queries[${index}]`;
|
|
6305
|
+
if (!isRecord(input)) {
|
|
6306
|
+
throw new Error(`${path10} must be an object`);
|
|
6307
|
+
}
|
|
6308
|
+
const id = input.id;
|
|
6309
|
+
const query = input.query;
|
|
6310
|
+
const queryType = input.queryType;
|
|
6311
|
+
const expected = input.expected;
|
|
6312
|
+
if (typeof id !== "string" || id.trim().length === 0) {
|
|
6313
|
+
throw new Error(`${path10}.id must be a non-empty string`);
|
|
6314
|
+
}
|
|
6315
|
+
if (typeof query !== "string" || query.trim().length === 0) {
|
|
6316
|
+
throw new Error(`${path10}.query must be a non-empty string`);
|
|
6317
|
+
}
|
|
6318
|
+
return {
|
|
6319
|
+
id,
|
|
6320
|
+
query,
|
|
6321
|
+
queryType: parseQueryType(queryType, `${path10}.queryType`),
|
|
6322
|
+
expected: parseExpected(expected, `${path10}.expected`)
|
|
6323
|
+
};
|
|
6324
|
+
}
|
|
6325
|
+
function parseGoldenDataset(raw, sourceLabel) {
|
|
6326
|
+
if (!isRecord(raw)) {
|
|
6327
|
+
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
6328
|
+
}
|
|
6329
|
+
const version = raw.version;
|
|
6330
|
+
const name = raw.name;
|
|
6331
|
+
const description = raw.description;
|
|
6332
|
+
const queriesRaw = raw.queries;
|
|
6333
|
+
if (typeof version !== "string" || version.trim().length === 0) {
|
|
6334
|
+
throw new Error(`${sourceLabel}.version must be a non-empty string`);
|
|
6335
|
+
}
|
|
6336
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
6337
|
+
throw new Error(`${sourceLabel}.name must be a non-empty string`);
|
|
6338
|
+
}
|
|
6339
|
+
if (description !== void 0 && typeof description !== "string") {
|
|
6340
|
+
throw new Error(`${sourceLabel}.description must be a string when provided`);
|
|
6341
|
+
}
|
|
6342
|
+
if (!Array.isArray(queriesRaw)) {
|
|
6343
|
+
throw new Error(`${sourceLabel}.queries must be an array`);
|
|
6344
|
+
}
|
|
6345
|
+
if (queriesRaw.length === 0) {
|
|
6346
|
+
throw new Error(`${sourceLabel}.queries must contain at least one query`);
|
|
6347
|
+
}
|
|
6348
|
+
const queries = queriesRaw.map((query, idx) => parseQuery(query, idx));
|
|
6349
|
+
const idSet = /* @__PURE__ */ new Set();
|
|
6350
|
+
for (const query of queries) {
|
|
6351
|
+
if (idSet.has(query.id)) {
|
|
6352
|
+
throw new Error(`${sourceLabel}.queries has duplicate id: ${query.id}`);
|
|
6353
|
+
}
|
|
6354
|
+
idSet.add(query.id);
|
|
6355
|
+
}
|
|
6356
|
+
return {
|
|
6357
|
+
version,
|
|
6358
|
+
name,
|
|
6359
|
+
description: typeof description === "string" ? description : void 0,
|
|
6360
|
+
queries
|
|
6361
|
+
};
|
|
6362
|
+
}
|
|
6363
|
+
function loadGoldenDataset(datasetPath) {
|
|
6364
|
+
const parsed = parseJsonFile(datasetPath);
|
|
6365
|
+
return parseGoldenDataset(parsed, datasetPath);
|
|
6366
|
+
}
|
|
6367
|
+
function parseBudget(raw, sourceLabel) {
|
|
6368
|
+
if (!isRecord(raw)) {
|
|
6369
|
+
throw new Error(`${sourceLabel} must be a JSON object`);
|
|
6370
|
+
}
|
|
6371
|
+
const name = raw.name;
|
|
6372
|
+
const baselinePath = raw.baselinePath;
|
|
6373
|
+
const failOnMissingBaseline = raw.failOnMissingBaseline;
|
|
6374
|
+
const thresholds = raw.thresholds;
|
|
6375
|
+
if (typeof name !== "string" || name.trim().length === 0) {
|
|
6376
|
+
throw new Error(`${sourceLabel}.name must be a non-empty string`);
|
|
6377
|
+
}
|
|
6378
|
+
if (baselinePath !== void 0 && typeof baselinePath !== "string") {
|
|
6379
|
+
throw new Error(`${sourceLabel}.baselinePath must be a string when provided`);
|
|
6380
|
+
}
|
|
6381
|
+
if (!isRecord(thresholds)) {
|
|
6382
|
+
throw new Error(`${sourceLabel}.thresholds must be an object`);
|
|
6383
|
+
}
|
|
6384
|
+
return {
|
|
6385
|
+
name,
|
|
6386
|
+
baselinePath: typeof baselinePath === "string" ? baselinePath : void 0,
|
|
6387
|
+
failOnMissingBaseline: typeof failOnMissingBaseline === "boolean" ? failOnMissingBaseline : true,
|
|
6388
|
+
thresholds: {
|
|
6389
|
+
hitAt5MaxDrop: thresholds.hitAt5MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.hitAt5MaxDrop, `${sourceLabel}.thresholds.hitAt5MaxDrop`),
|
|
6390
|
+
mrrAt10MaxDrop: thresholds.mrrAt10MaxDrop === void 0 ? void 0 : asPositiveNumber(thresholds.mrrAt10MaxDrop, `${sourceLabel}.thresholds.mrrAt10MaxDrop`),
|
|
6391
|
+
p95LatencyMaxMultiplier: thresholds.p95LatencyMaxMultiplier === void 0 ? void 0 : asPositiveNumber(
|
|
6392
|
+
thresholds.p95LatencyMaxMultiplier,
|
|
6393
|
+
`${sourceLabel}.thresholds.p95LatencyMaxMultiplier`
|
|
6394
|
+
),
|
|
6395
|
+
p95LatencyMaxAbsoluteMs: thresholds.p95LatencyMaxAbsoluteMs === void 0 ? void 0 : asPositiveNumber(
|
|
6396
|
+
thresholds.p95LatencyMaxAbsoluteMs,
|
|
6397
|
+
`${sourceLabel}.thresholds.p95LatencyMaxAbsoluteMs`
|
|
6398
|
+
),
|
|
6399
|
+
minHitAt5: thresholds.minHitAt5 === void 0 ? void 0 : asPositiveNumber(thresholds.minHitAt5, `${sourceLabel}.thresholds.minHitAt5`),
|
|
6400
|
+
minMrrAt10: thresholds.minMrrAt10 === void 0 ? void 0 : asPositiveNumber(thresholds.minMrrAt10, `${sourceLabel}.thresholds.minMrrAt10`)
|
|
6401
|
+
}
|
|
6402
|
+
};
|
|
6403
|
+
}
|
|
6404
|
+
function loadBudget(budgetPath) {
|
|
6405
|
+
const parsed = parseJsonFile(budgetPath);
|
|
6406
|
+
return parseBudget(parsed, budgetPath);
|
|
6407
|
+
}
|
|
6408
|
+
|
|
6409
|
+
// src/eval/runner.ts
|
|
6410
|
+
function toAbsolute(projectRoot, maybeRelative) {
|
|
6411
|
+
return path7.isAbsolute(maybeRelative) ? maybeRelative : path7.join(projectRoot, maybeRelative);
|
|
6412
|
+
}
|
|
6413
|
+
function loadRawConfig(projectRoot, configPath) {
|
|
6414
|
+
const fromPath = configPath ? toAbsolute(projectRoot, configPath) : null;
|
|
6415
|
+
if (fromPath && (0, import_fs7.existsSync)(fromPath)) {
|
|
6416
|
+
return JSON.parse((0, import_fs8.readFileSync)(fromPath, "utf-8"));
|
|
6417
|
+
}
|
|
6418
|
+
const projectConfig = path7.join(projectRoot, ".opencode", "codebase-index.json");
|
|
6419
|
+
if ((0, import_fs7.existsSync)(projectConfig)) {
|
|
6420
|
+
return JSON.parse((0, import_fs8.readFileSync)(projectConfig, "utf-8"));
|
|
6421
|
+
}
|
|
6422
|
+
const globalConfig = path7.join(os3.homedir(), ".config", "opencode", "codebase-index.json");
|
|
6423
|
+
if ((0, import_fs7.existsSync)(globalConfig)) {
|
|
6424
|
+
return JSON.parse((0, import_fs8.readFileSync)(globalConfig, "utf-8"));
|
|
6425
|
+
}
|
|
6426
|
+
return {};
|
|
6427
|
+
}
|
|
6428
|
+
function getIndexRootPath(projectRoot, scope) {
|
|
6429
|
+
if (scope === "global") {
|
|
6430
|
+
return path7.join(os3.homedir(), ".opencode", "global-index");
|
|
6431
|
+
}
|
|
6432
|
+
return path7.join(projectRoot, ".opencode", "index");
|
|
6433
|
+
}
|
|
6434
|
+
function clearIndexRoot(projectRoot, scope) {
|
|
6435
|
+
const indexRoot = getIndexRootPath(projectRoot, scope);
|
|
6436
|
+
if ((0, import_fs7.existsSync)(indexRoot)) {
|
|
6437
|
+
(0, import_fs9.rmSync)(indexRoot, { recursive: true, force: true });
|
|
6438
|
+
}
|
|
6439
|
+
}
|
|
6440
|
+
function loadParsedConfig(projectRoot, configPath) {
|
|
6441
|
+
const raw = loadRawConfig(projectRoot, configPath);
|
|
6442
|
+
return parseConfig(raw);
|
|
6443
|
+
}
|
|
6444
|
+
function resolveSearchConfig(parsedConfig, overrides) {
|
|
6445
|
+
const nextSearch = {
|
|
6446
|
+
...parsedConfig.search
|
|
6447
|
+
};
|
|
6448
|
+
if (overrides?.fusionStrategy !== void 0) {
|
|
6449
|
+
nextSearch.fusionStrategy = overrides.fusionStrategy;
|
|
6450
|
+
}
|
|
6451
|
+
if (overrides?.hybridWeight !== void 0) {
|
|
6452
|
+
nextSearch.hybridWeight = overrides.hybridWeight;
|
|
6453
|
+
}
|
|
6454
|
+
if (overrides?.rrfK !== void 0) {
|
|
6455
|
+
nextSearch.rrfK = overrides.rrfK;
|
|
6456
|
+
}
|
|
6457
|
+
if (overrides?.rerankTopN !== void 0) {
|
|
6458
|
+
nextSearch.rerankTopN = overrides.rerankTopN;
|
|
6459
|
+
}
|
|
6460
|
+
return {
|
|
6461
|
+
...parsedConfig,
|
|
6462
|
+
search: nextSearch
|
|
6463
|
+
};
|
|
6464
|
+
}
|
|
6465
|
+
async function runEvaluation(options) {
|
|
6466
|
+
const datasetPath = toAbsolute(options.projectRoot, options.datasetPath);
|
|
6467
|
+
const againstPath = options.againstPath ? toAbsolute(options.projectRoot, options.againstPath) : void 0;
|
|
6468
|
+
const budgetPath = options.budgetPath ? toAbsolute(options.projectRoot, options.budgetPath) : void 0;
|
|
6469
|
+
const dataset = loadGoldenDataset(datasetPath);
|
|
6470
|
+
const parsedConfig = loadParsedConfig(options.projectRoot, options.configPath);
|
|
6471
|
+
const effectiveConfig = resolveSearchConfig(parsedConfig, options.searchOverrides);
|
|
6472
|
+
if (options.reindex) {
|
|
6473
|
+
clearIndexRoot(options.projectRoot, effectiveConfig.scope);
|
|
6474
|
+
}
|
|
6475
|
+
const indexer = new Indexer(options.projectRoot, effectiveConfig);
|
|
6476
|
+
await indexer.index();
|
|
6477
|
+
const perQuery = [];
|
|
6478
|
+
for (const query of dataset.queries) {
|
|
6479
|
+
if (query.expected.branch && query.expected.branch !== indexer.getCurrentBranch()) {
|
|
6480
|
+
throw new Error(
|
|
6481
|
+
`Query '${query.id}' expects branch '${query.expected.branch}', but current branch is '${indexer.getCurrentBranch()}'. Switch branch before running this dataset.`
|
|
6482
|
+
);
|
|
6483
|
+
}
|
|
6484
|
+
const start = import_perf_hooks2.performance.now();
|
|
6485
|
+
const result = await indexer.search(query.query, 10, {
|
|
6486
|
+
metadataOnly: true,
|
|
6487
|
+
filterByBranch: query.expected.branch ? true : false
|
|
6488
|
+
});
|
|
6489
|
+
const elapsed = import_perf_hooks2.performance.now() - start;
|
|
6490
|
+
const materialized = result.map((item) => ({
|
|
6491
|
+
filePath: item.filePath,
|
|
6492
|
+
startLine: item.startLine,
|
|
6493
|
+
endLine: item.endLine,
|
|
6494
|
+
score: item.score,
|
|
6495
|
+
chunkType: item.chunkType,
|
|
6496
|
+
name: item.name
|
|
6497
|
+
}));
|
|
6498
|
+
perQuery.push(buildPerQueryResult(query, materialized, elapsed, 10));
|
|
6499
|
+
}
|
|
6500
|
+
const logger = indexer.getLogger();
|
|
6501
|
+
const metricSnapshot = logger.getMetrics();
|
|
6502
|
+
const costPer1MTokensUsd = effectiveConfig.embeddingProvider === "custom" || effectiveConfig.embeddingProvider === "auto" ? 0 : getDefaultModelForProvider(effectiveConfig.embeddingProvider).costPer1MTokens;
|
|
6503
|
+
const summary = {
|
|
6504
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6505
|
+
projectRoot: options.projectRoot,
|
|
6506
|
+
datasetPath,
|
|
6507
|
+
datasetName: dataset.name,
|
|
6508
|
+
datasetVersion: dataset.version,
|
|
6509
|
+
queryCount: dataset.queries.length,
|
|
6510
|
+
topK: 10,
|
|
6511
|
+
searchConfig: {
|
|
6512
|
+
fusionStrategy: effectiveConfig.search.fusionStrategy,
|
|
6513
|
+
hybridWeight: effectiveConfig.search.hybridWeight,
|
|
6514
|
+
rrfK: effectiveConfig.search.rrfK,
|
|
6515
|
+
rerankTopN: effectiveConfig.search.rerankTopN
|
|
6516
|
+
},
|
|
6517
|
+
metrics: computeEvalMetrics(
|
|
6518
|
+
dataset.queries,
|
|
6519
|
+
perQuery,
|
|
6520
|
+
metricSnapshot.embeddingApiCalls,
|
|
6521
|
+
metricSnapshot.embeddingTokensUsed,
|
|
6522
|
+
costPer1MTokensUsd
|
|
6523
|
+
)
|
|
6524
|
+
};
|
|
6525
|
+
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
6526
|
+
const perQueryArtifact = buildPerQueryArtifact(perQuery);
|
|
6527
|
+
writeJson(path7.join(outputDir, "summary.json"), summary);
|
|
6528
|
+
writeJson(path7.join(outputDir, "per-query.json"), perQueryArtifact);
|
|
6529
|
+
let comparison;
|
|
6530
|
+
if (againstPath) {
|
|
6531
|
+
const baseline = loadSummary(againstPath);
|
|
6532
|
+
comparison = compareSummaries(summary, baseline, againstPath);
|
|
6533
|
+
writeJson(path7.join(outputDir, "compare.json"), comparison);
|
|
6534
|
+
}
|
|
6535
|
+
let gate;
|
|
6536
|
+
if (options.ciMode) {
|
|
6537
|
+
if (!budgetPath) {
|
|
6538
|
+
throw new Error("CI mode requires --budget path");
|
|
6539
|
+
}
|
|
6540
|
+
const budget = loadBudget(budgetPath);
|
|
6541
|
+
if (!comparison && budget.baselinePath) {
|
|
6542
|
+
const resolvedBaseline = toAbsolute(options.projectRoot, budget.baselinePath);
|
|
6543
|
+
if ((0, import_fs7.existsSync)(resolvedBaseline)) {
|
|
6544
|
+
const baselineSummary = loadSummary(resolvedBaseline);
|
|
6545
|
+
comparison = compareSummaries(summary, baselineSummary, resolvedBaseline);
|
|
6546
|
+
writeJson(path7.join(outputDir, "compare.json"), comparison);
|
|
6547
|
+
} else if (budget.failOnMissingBaseline) {
|
|
6548
|
+
throw new Error(
|
|
6549
|
+
`Budget baseline is missing: ${resolvedBaseline}. Set failOnMissingBaseline=false to allow CI run without baseline.`
|
|
6550
|
+
);
|
|
6551
|
+
}
|
|
6552
|
+
}
|
|
6553
|
+
gate = evaluateBudgetGate(budget, summary, comparison);
|
|
6554
|
+
}
|
|
6555
|
+
const markdown = createSummaryMarkdown(summary, comparison, gate);
|
|
6556
|
+
writeText(path7.join(outputDir, "summary.md"), markdown);
|
|
6557
|
+
return { outputDir, summary, perQuery, comparison, gate };
|
|
6558
|
+
}
|
|
6559
|
+
async function runSweep(options, sweep) {
|
|
6560
|
+
const fusionValues = sweep.fusionStrategy && sweep.fusionStrategy.length > 0 ? [...sweep.fusionStrategy] : [void 0];
|
|
6561
|
+
const weightValues = sweep.hybridWeight && sweep.hybridWeight.length > 0 ? [...sweep.hybridWeight] : [void 0];
|
|
6562
|
+
const rrfValues = sweep.rrfK && sweep.rrfK.length > 0 ? [...sweep.rrfK] : [void 0];
|
|
6563
|
+
const rerankValues = sweep.rerankTopN && sweep.rerankTopN.length > 0 ? [...sweep.rerankTopN] : [void 0];
|
|
6564
|
+
const runs = [];
|
|
6565
|
+
for (const fusion of fusionValues) {
|
|
6566
|
+
for (const hybridWeight of weightValues) {
|
|
6567
|
+
for (const rrfK of rrfValues) {
|
|
6568
|
+
for (const rerankTopN of rerankValues) {
|
|
6569
|
+
const run = await runEvaluation({
|
|
6570
|
+
...options,
|
|
6571
|
+
searchOverrides: {
|
|
6572
|
+
...fusion !== void 0 ? { fusionStrategy: fusion } : {},
|
|
6573
|
+
...hybridWeight !== void 0 ? { hybridWeight } : {},
|
|
6574
|
+
...rrfK !== void 0 ? { rrfK } : {},
|
|
6575
|
+
...rerankTopN !== void 0 ? { rerankTopN } : {}
|
|
6576
|
+
}
|
|
6577
|
+
});
|
|
6578
|
+
runs.push({
|
|
6579
|
+
searchConfig: run.summary.searchConfig,
|
|
6580
|
+
summary: run.summary,
|
|
6581
|
+
comparison: run.comparison,
|
|
6582
|
+
gate: run.gate
|
|
6583
|
+
});
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
}
|
|
6588
|
+
const bestByHitAt5 = [...runs].sort(
|
|
6589
|
+
(a, b) => b.summary.metrics.hitAt5 - a.summary.metrics.hitAt5
|
|
6590
|
+
)[0];
|
|
6591
|
+
const bestByMrrAt10 = [...runs].sort(
|
|
6592
|
+
(a, b) => b.summary.metrics.mrrAt10 - a.summary.metrics.mrrAt10
|
|
6593
|
+
)[0];
|
|
6594
|
+
const bestByP95Latency = [...runs].sort(
|
|
6595
|
+
(a, b) => a.summary.metrics.latencyMs.p95 - b.summary.metrics.latencyMs.p95
|
|
6596
|
+
)[0];
|
|
6597
|
+
const outputDir = createRunDirectory(toAbsolute(options.projectRoot, options.outputRoot));
|
|
6598
|
+
const failedGateRuns = runs.filter((run) => run.gate && !run.gate.passed).length;
|
|
6599
|
+
const gatePassed = failedGateRuns === 0;
|
|
6600
|
+
const aggregate = {
|
|
6601
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6602
|
+
againstPath: options.againstPath,
|
|
6603
|
+
runCount: runs.length,
|
|
6604
|
+
runs,
|
|
6605
|
+
gatePassed,
|
|
6606
|
+
failedGateRuns,
|
|
6607
|
+
bestByHitAt5,
|
|
6608
|
+
bestByMrrAt10,
|
|
6609
|
+
bestByP95Latency
|
|
6610
|
+
};
|
|
6611
|
+
writeJson(path7.join(outputDir, "compare.json"), aggregate);
|
|
6612
|
+
const md = createSummaryMarkdown(
|
|
6613
|
+
bestByHitAt5?.summary ?? runs[0].summary,
|
|
6614
|
+
bestByHitAt5?.comparison,
|
|
6615
|
+
void 0,
|
|
6616
|
+
aggregate
|
|
6617
|
+
);
|
|
6618
|
+
writeText(path7.join(outputDir, "summary.md"), md);
|
|
6619
|
+
writeJson(path7.join(outputDir, "summary.json"), bestByHitAt5?.summary ?? runs[0].summary);
|
|
6620
|
+
return { outputDir, aggregate };
|
|
6621
|
+
}
|
|
6622
|
+
|
|
6623
|
+
// src/eval/cli.ts
|
|
6624
|
+
function printUsage() {
|
|
6625
|
+
console.log(`
|
|
6626
|
+
Usage:
|
|
6627
|
+
opencode-codebase-index-mcp eval run [options]
|
|
6628
|
+
opencode-codebase-index-mcp eval compare --against <summary.json> [options]
|
|
6629
|
+
opencode-codebase-index-mcp eval diff --current <summary.json> --against <summary.json> [options]
|
|
6630
|
+
|
|
6631
|
+
Options:
|
|
6632
|
+
--project <path> Project root (default: cwd)
|
|
6633
|
+
--config <path> Config JSON path
|
|
6634
|
+
--dataset <path> Golden dataset path (default: benchmarks/golden/small.json)
|
|
6635
|
+
--current <path> Current summary.json path (required for eval diff)
|
|
6636
|
+
--output <path> Output root dir (default: benchmarks/results)
|
|
6637
|
+
--against <path> Baseline summary.json to compare against
|
|
6638
|
+
--budget <path> Budget file for CI mode (default: benchmarks/budgets/default.json)
|
|
6639
|
+
--ci Enable CI gate mode
|
|
6640
|
+
--reindex Force reindex before eval
|
|
6641
|
+
|
|
6642
|
+
Search overrides:
|
|
6643
|
+
--fusionStrategy <rrf|weighted>
|
|
6644
|
+
--hybridWeight <0-1>
|
|
6645
|
+
--rrfK <number>
|
|
6646
|
+
--rerankTopN <number>
|
|
6647
|
+
|
|
6648
|
+
Sweep options (comma-separated values):
|
|
6649
|
+
--sweepFusionStrategy <rrf,weighted>
|
|
6650
|
+
--sweepHybridWeight <0.3,0.5,0.7>
|
|
6651
|
+
--sweepRrfK <30,60,90>
|
|
6652
|
+
--sweepRerankTopN <10,20,40>
|
|
6653
|
+
`);
|
|
6654
|
+
}
|
|
6655
|
+
function parseNumber(value, flag) {
|
|
6656
|
+
const parsed = Number(value);
|
|
6657
|
+
if (Number.isNaN(parsed)) {
|
|
6658
|
+
throw new Error(`${flag} must be a number`);
|
|
6659
|
+
}
|
|
6660
|
+
return parsed;
|
|
6661
|
+
}
|
|
6662
|
+
function parseCsvNumbers(value, flag) {
|
|
6663
|
+
return value.split(",").map((item) => item.trim()).filter((item) => item.length > 0).map((item) => parseNumber(item, flag));
|
|
6664
|
+
}
|
|
6665
|
+
function parseCsvFusion(value) {
|
|
6666
|
+
const values = value.split(",").map((item) => item.trim()).filter((item) => item.length > 0);
|
|
6667
|
+
const parsed = [];
|
|
6668
|
+
for (const candidate of values) {
|
|
6669
|
+
if (candidate !== "rrf" && candidate !== "weighted") {
|
|
6670
|
+
throw new Error("--sweepFusionStrategy accepts only rrf,weighted");
|
|
6671
|
+
}
|
|
6672
|
+
parsed.push(candidate);
|
|
6673
|
+
}
|
|
6674
|
+
return parsed;
|
|
6675
|
+
}
|
|
6676
|
+
function hasSweepOptions(sweep) {
|
|
6677
|
+
return Boolean(
|
|
6678
|
+
sweep.fusionStrategy && sweep.fusionStrategy.length > 0 || sweep.hybridWeight && sweep.hybridWeight.length > 0 || sweep.rrfK && sweep.rrfK.length > 0 || sweep.rerankTopN && sweep.rerankTopN.length > 0
|
|
6679
|
+
);
|
|
6680
|
+
}
|
|
6681
|
+
function parseEvalArgs(argv, cwd) {
|
|
6682
|
+
const parsed = {
|
|
6683
|
+
projectRoot: cwd,
|
|
6684
|
+
datasetPath: "benchmarks/golden/small.json",
|
|
6685
|
+
outputRoot: "benchmarks/results",
|
|
6686
|
+
budgetPath: "benchmarks/budgets/default.json",
|
|
6687
|
+
ciMode: false,
|
|
6688
|
+
reindex: false,
|
|
6689
|
+
sweep: {}
|
|
6690
|
+
};
|
|
6691
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6692
|
+
const arg = argv[i];
|
|
6693
|
+
const next = argv[i + 1];
|
|
6694
|
+
if (arg === "--project" && next) {
|
|
6695
|
+
parsed.projectRoot = path8.resolve(cwd, next);
|
|
6696
|
+
i += 1;
|
|
6697
|
+
continue;
|
|
6698
|
+
}
|
|
6699
|
+
if (arg === "--config" && next) {
|
|
6700
|
+
parsed.configPath = path8.resolve(cwd, next);
|
|
6701
|
+
i += 1;
|
|
6702
|
+
continue;
|
|
6703
|
+
}
|
|
6704
|
+
if (arg === "--dataset" && next) {
|
|
6705
|
+
parsed.datasetPath = next;
|
|
6706
|
+
i += 1;
|
|
6707
|
+
continue;
|
|
6708
|
+
}
|
|
6709
|
+
if (arg === "--current" && next) {
|
|
6710
|
+
parsed.currentPath = next;
|
|
6711
|
+
i += 1;
|
|
6712
|
+
continue;
|
|
6713
|
+
}
|
|
6714
|
+
if (arg === "--output" && next) {
|
|
6715
|
+
parsed.outputRoot = next;
|
|
6716
|
+
i += 1;
|
|
6717
|
+
continue;
|
|
6718
|
+
}
|
|
6719
|
+
if (arg === "--against" && next) {
|
|
6720
|
+
parsed.againstPath = next;
|
|
6721
|
+
i += 1;
|
|
6722
|
+
continue;
|
|
6723
|
+
}
|
|
6724
|
+
if (arg === "--budget" && next) {
|
|
6725
|
+
parsed.budgetPath = next;
|
|
6726
|
+
i += 1;
|
|
6727
|
+
continue;
|
|
6728
|
+
}
|
|
6729
|
+
if (arg === "--ci") {
|
|
6730
|
+
parsed.ciMode = true;
|
|
6731
|
+
continue;
|
|
6732
|
+
}
|
|
6733
|
+
if (arg === "--reindex") {
|
|
6734
|
+
parsed.reindex = true;
|
|
6735
|
+
continue;
|
|
6736
|
+
}
|
|
6737
|
+
if (arg === "--fusionStrategy" && next) {
|
|
6738
|
+
if (next !== "rrf" && next !== "weighted") {
|
|
6739
|
+
throw new Error("--fusionStrategy must be rrf or weighted");
|
|
6740
|
+
}
|
|
6741
|
+
parsed.fusionStrategy = next;
|
|
6742
|
+
i += 1;
|
|
6743
|
+
continue;
|
|
6744
|
+
}
|
|
6745
|
+
if (arg === "--hybridWeight" && next) {
|
|
6746
|
+
parsed.hybridWeight = parseNumber(next, "--hybridWeight");
|
|
6747
|
+
i += 1;
|
|
6748
|
+
continue;
|
|
6749
|
+
}
|
|
6750
|
+
if (arg === "--rrfK" && next) {
|
|
6751
|
+
parsed.rrfK = parseNumber(next, "--rrfK");
|
|
6752
|
+
i += 1;
|
|
6753
|
+
continue;
|
|
6754
|
+
}
|
|
6755
|
+
if (arg === "--rerankTopN" && next) {
|
|
6756
|
+
parsed.rerankTopN = parseNumber(next, "--rerankTopN");
|
|
6757
|
+
i += 1;
|
|
6758
|
+
continue;
|
|
6759
|
+
}
|
|
6760
|
+
if (arg === "--sweepFusionStrategy" && next) {
|
|
6761
|
+
parsed.sweep.fusionStrategy = parseCsvFusion(next);
|
|
6762
|
+
i += 1;
|
|
6763
|
+
continue;
|
|
6764
|
+
}
|
|
6765
|
+
if (arg === "--sweepHybridWeight" && next) {
|
|
6766
|
+
parsed.sweep.hybridWeight = parseCsvNumbers(next, "--sweepHybridWeight");
|
|
6767
|
+
i += 1;
|
|
6768
|
+
continue;
|
|
6769
|
+
}
|
|
6770
|
+
if (arg === "--sweepRrfK" && next) {
|
|
6771
|
+
parsed.sweep.rrfK = parseCsvNumbers(next, "--sweepRrfK");
|
|
6772
|
+
i += 1;
|
|
6773
|
+
continue;
|
|
6774
|
+
}
|
|
6775
|
+
if (arg === "--sweepRerankTopN" && next) {
|
|
6776
|
+
parsed.sweep.rerankTopN = parseCsvNumbers(next, "--sweepRerankTopN");
|
|
6777
|
+
i += 1;
|
|
6778
|
+
continue;
|
|
6779
|
+
}
|
|
6780
|
+
}
|
|
6781
|
+
return parsed;
|
|
6782
|
+
}
|
|
6783
|
+
function parseEvalSubcommandOptions(argv, cwd) {
|
|
6784
|
+
let explicitAgainst;
|
|
6785
|
+
const filtered = [];
|
|
6786
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
6787
|
+
const current = argv[i];
|
|
6788
|
+
const next = argv[i + 1];
|
|
6789
|
+
if (current === "--against" && next) {
|
|
6790
|
+
explicitAgainst = next;
|
|
6791
|
+
i += 1;
|
|
6792
|
+
continue;
|
|
6793
|
+
}
|
|
6794
|
+
filtered.push(current);
|
|
6795
|
+
}
|
|
6796
|
+
return {
|
|
6797
|
+
parsed: parseEvalArgs(filtered, cwd),
|
|
6798
|
+
explicitAgainst
|
|
6799
|
+
};
|
|
6800
|
+
}
|
|
6801
|
+
function toRunOptions(parsed) {
|
|
6802
|
+
return {
|
|
6803
|
+
projectRoot: parsed.projectRoot,
|
|
6804
|
+
configPath: parsed.configPath,
|
|
6805
|
+
datasetPath: parsed.datasetPath,
|
|
6806
|
+
outputRoot: parsed.outputRoot,
|
|
6807
|
+
againstPath: parsed.againstPath,
|
|
6808
|
+
budgetPath: parsed.budgetPath,
|
|
6809
|
+
ciMode: parsed.ciMode,
|
|
6810
|
+
reindex: parsed.reindex,
|
|
6811
|
+
searchOverrides: {
|
|
6812
|
+
...parsed.fusionStrategy !== void 0 ? { fusionStrategy: parsed.fusionStrategy } : {},
|
|
6813
|
+
...parsed.hybridWeight !== void 0 ? { hybridWeight: parsed.hybridWeight } : {},
|
|
6814
|
+
...parsed.rrfK !== void 0 ? { rrfK: parsed.rrfK } : {},
|
|
6815
|
+
...parsed.rerankTopN !== void 0 ? { rerankTopN: parsed.rerankTopN } : {}
|
|
6816
|
+
}
|
|
6817
|
+
};
|
|
6818
|
+
}
|
|
6819
|
+
async function handleEvalCommand(args, cwd) {
|
|
6820
|
+
const subcommand = args[0];
|
|
6821
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h") {
|
|
6822
|
+
printUsage();
|
|
6823
|
+
return 0;
|
|
6824
|
+
}
|
|
6825
|
+
if (subcommand === "run") {
|
|
6826
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6827
|
+
if (explicitAgainst) {
|
|
6828
|
+
parsed.againstPath = explicitAgainst;
|
|
6829
|
+
}
|
|
6830
|
+
const runOptions = toRunOptions(parsed);
|
|
6831
|
+
if (hasSweepOptions(parsed.sweep)) {
|
|
6832
|
+
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
6833
|
+
console.log(`Eval sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
6834
|
+
console.log(`Sweep runs: ${sweep.aggregate.runCount}`);
|
|
6835
|
+
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
6836
|
+
console.error(
|
|
6837
|
+
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
6838
|
+
);
|
|
6839
|
+
return 1;
|
|
6840
|
+
}
|
|
6841
|
+
return 0;
|
|
6842
|
+
}
|
|
6843
|
+
const result = await runEvaluation(runOptions);
|
|
6844
|
+
console.log(`Eval run complete. Artifacts: ${result.outputDir}`);
|
|
6845
|
+
console.log(
|
|
6846
|
+
`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`
|
|
6847
|
+
);
|
|
6848
|
+
if (result.gate && !result.gate.passed) {
|
|
6849
|
+
for (const violation of result.gate.violations) {
|
|
6850
|
+
console.error(`[CI-GATE] ${violation.metric}: ${violation.message}`);
|
|
6851
|
+
}
|
|
6852
|
+
return 1;
|
|
6853
|
+
}
|
|
6854
|
+
return 0;
|
|
6855
|
+
}
|
|
6856
|
+
if (subcommand === "compare") {
|
|
6857
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6858
|
+
if (!explicitAgainst) {
|
|
6859
|
+
throw new Error("eval compare requires --against <baseline summary.json>");
|
|
6860
|
+
}
|
|
6861
|
+
parsed.againstPath = explicitAgainst;
|
|
6862
|
+
const runOptions = toRunOptions(parsed);
|
|
6863
|
+
if (hasSweepOptions(parsed.sweep)) {
|
|
6864
|
+
const sweep = await runSweep(runOptions, parsed.sweep);
|
|
6865
|
+
console.log(`Eval compare sweep complete. Artifacts: ${sweep.outputDir}`);
|
|
6866
|
+
if (parsed.ciMode && sweep.aggregate.gatePassed === false) {
|
|
6867
|
+
console.error(
|
|
6868
|
+
`[CI-GATE] Sweep failed: ${sweep.aggregate.failedGateRuns ?? 0} run(s) violated budget/baseline gates`
|
|
6869
|
+
);
|
|
6870
|
+
return 1;
|
|
6871
|
+
}
|
|
6872
|
+
return 0;
|
|
6873
|
+
}
|
|
6874
|
+
const result = await runEvaluation(runOptions);
|
|
6875
|
+
console.log(`Eval compare complete. Artifacts: ${result.outputDir}`);
|
|
6876
|
+
return 0;
|
|
6877
|
+
}
|
|
6878
|
+
if (subcommand === "diff") {
|
|
6879
|
+
const { parsed, explicitAgainst } = parseEvalSubcommandOptions(args.slice(1), cwd);
|
|
6880
|
+
if (!explicitAgainst) {
|
|
6881
|
+
throw new Error("eval diff requires --against <baseline summary.json>");
|
|
6882
|
+
}
|
|
6883
|
+
if (!parsed.currentPath) {
|
|
6884
|
+
throw new Error("eval diff requires --current <current summary.json>");
|
|
6885
|
+
}
|
|
6886
|
+
parsed.againstPath = explicitAgainst;
|
|
6887
|
+
const currentPath = parsed.currentPath;
|
|
6888
|
+
if (!currentPath.endsWith(".json")) {
|
|
6889
|
+
throw new Error("eval diff --current must point to a summary JSON file");
|
|
6890
|
+
}
|
|
6891
|
+
if (!parsed.againstPath.endsWith(".json")) {
|
|
6892
|
+
throw new Error("eval diff --against must point to a summary JSON file");
|
|
6893
|
+
}
|
|
6894
|
+
const currentSummary = loadSummary(path8.resolve(parsed.projectRoot, currentPath));
|
|
6895
|
+
const baselineSummary = loadSummary(path8.resolve(parsed.projectRoot, parsed.againstPath));
|
|
6896
|
+
const comparison = compareSummaries(
|
|
6897
|
+
currentSummary,
|
|
6898
|
+
baselineSummary,
|
|
6899
|
+
path8.resolve(parsed.projectRoot, parsed.againstPath)
|
|
6900
|
+
);
|
|
6901
|
+
const outputDir = createRunDirectory(path8.resolve(parsed.projectRoot, parsed.outputRoot));
|
|
6902
|
+
const summaryMd = createSummaryMarkdown(currentSummary, comparison);
|
|
6903
|
+
writeJson(path8.join(outputDir, "compare.json"), comparison);
|
|
6904
|
+
writeText(path8.join(outputDir, "summary.md"), summaryMd);
|
|
6905
|
+
writeJson(path8.join(outputDir, "summary.json"), currentSummary);
|
|
6906
|
+
console.log(`Eval diff complete. Artifacts: ${outputDir}`);
|
|
6907
|
+
return 0;
|
|
6908
|
+
}
|
|
6909
|
+
throw new Error(`Unknown eval subcommand: ${subcommand}`);
|
|
6910
|
+
}
|
|
6911
|
+
|
|
5738
6912
|
// src/mcp-server.ts
|
|
6913
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6914
|
+
var import_zod = require("zod");
|
|
6915
|
+
|
|
6916
|
+
// src/tools/utils.ts
|
|
5739
6917
|
var MAX_CONTENT_LINES = 30;
|
|
5740
6918
|
function truncateContent(content) {
|
|
5741
6919
|
const lines = content.split("\n");
|
|
@@ -5743,6 +6921,31 @@ function truncateContent(content) {
|
|
|
5743
6921
|
return lines.slice(0, MAX_CONTENT_LINES).join("\n") + `
|
|
5744
6922
|
// ... (${lines.length - MAX_CONTENT_LINES} more lines)`;
|
|
5745
6923
|
}
|
|
6924
|
+
function formatDefinitionLookup(results, query) {
|
|
6925
|
+
if (results.length === 0) {
|
|
6926
|
+
return `No definition found for "${query}". Try codebase_search for broader discovery, or verify the symbol name.`;
|
|
6927
|
+
}
|
|
6928
|
+
const formatted = results.map((r, idx) => {
|
|
6929
|
+
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}`;
|
|
6930
|
+
return `${header2} (score: ${r.score.toFixed(2)})
|
|
6931
|
+
\`\`\`
|
|
6932
|
+
${truncateContent(r.content)}
|
|
6933
|
+
\`\`\``;
|
|
6934
|
+
});
|
|
6935
|
+
const header = results.length === 1 ? `Definition found for "${query}":` : `Found ${results.length} definition candidates for "${query}":`;
|
|
6936
|
+
return `${header}
|
|
6937
|
+
|
|
6938
|
+
${formatted.join("\n\n")}`;
|
|
6939
|
+
}
|
|
6940
|
+
|
|
6941
|
+
// src/mcp-server.ts
|
|
6942
|
+
var MAX_CONTENT_LINES2 = 30;
|
|
6943
|
+
function truncateContent2(content) {
|
|
6944
|
+
const lines = content.split("\n");
|
|
6945
|
+
if (lines.length <= MAX_CONTENT_LINES2) return content;
|
|
6946
|
+
return lines.slice(0, MAX_CONTENT_LINES2).join("\n") + `
|
|
6947
|
+
// ... (${lines.length - MAX_CONTENT_LINES2} more lines)`;
|
|
6948
|
+
}
|
|
5746
6949
|
function formatIndexStats(stats, verbose = false) {
|
|
5747
6950
|
const lines = [];
|
|
5748
6951
|
if (stats.indexedChunks === 0 && stats.removedChunks === 0) {
|
|
@@ -5856,7 +7059,7 @@ function createMcpServer(projectRoot, config) {
|
|
|
5856
7059
|
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}`;
|
|
5857
7060
|
return `${header} (score: ${r.score.toFixed(2)})
|
|
5858
7061
|
\`\`\`
|
|
5859
|
-
${
|
|
7062
|
+
${truncateContent2(r.content)}
|
|
5860
7063
|
\`\`\``;
|
|
5861
7064
|
});
|
|
5862
7065
|
return { content: [{ type: "text", text: `Found ${results.length} results for "${args.query}":
|
|
@@ -6034,7 +7237,7 @@ Use Read tool to examine specific files.` }] };
|
|
|
6034
7237
|
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}`;
|
|
6035
7238
|
return `${header} (similarity: ${(r.score * 100).toFixed(1)}%)
|
|
6036
7239
|
\`\`\`
|
|
6037
|
-
${
|
|
7240
|
+
${truncateContent2(r.content)}
|
|
6038
7241
|
\`\`\``;
|
|
6039
7242
|
});
|
|
6040
7243
|
return { content: [{ type: "text", text: `Found ${results.length} similar code blocks:
|
|
@@ -6042,6 +7245,25 @@ ${truncateContent(r.content)}
|
|
|
6042
7245
|
${formatted.join("\n\n")}` }] };
|
|
6043
7246
|
}
|
|
6044
7247
|
);
|
|
7248
|
+
server.tool(
|
|
7249
|
+
"implementation_lookup",
|
|
7250
|
+
"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.",
|
|
7251
|
+
{
|
|
7252
|
+
query: import_zod.z.string().describe("Symbol name or natural language description (e.g., 'validateToken', 'where is the payment handler defined')"),
|
|
7253
|
+
limit: import_zod.z.number().optional().default(5).describe("Maximum number of results"),
|
|
7254
|
+
fileType: import_zod.z.string().optional().describe("Filter by file extension (e.g., 'ts', 'py')"),
|
|
7255
|
+
directory: import_zod.z.string().optional().describe("Filter by directory path (e.g., 'src/utils')")
|
|
7256
|
+
},
|
|
7257
|
+
async (args) => {
|
|
7258
|
+
await ensureInitialized();
|
|
7259
|
+
const results = await indexer.search(args.query, args.limit ?? 5, {
|
|
7260
|
+
fileType: args.fileType,
|
|
7261
|
+
directory: args.directory,
|
|
7262
|
+
definitionIntent: true
|
|
7263
|
+
});
|
|
7264
|
+
return { content: [{ type: "text", text: formatDefinitionLookup(results, args.query) }] };
|
|
7265
|
+
}
|
|
7266
|
+
);
|
|
6045
7267
|
server.tool(
|
|
6046
7268
|
"call_graph",
|
|
6047
7269
|
"Query the call graph to find callers or callees of a function/method. Use to understand code flow and dependencies.",
|
|
@@ -6148,14 +7370,30 @@ Use a hybrid approach:
|
|
|
6148
7370
|
}]
|
|
6149
7371
|
})
|
|
6150
7372
|
);
|
|
7373
|
+
server.prompt(
|
|
7374
|
+
"definition",
|
|
7375
|
+
"Find where a symbol is defined in the codebase",
|
|
7376
|
+
{ query: import_zod.z.string().describe("Symbol name or description to find the definition of") },
|
|
7377
|
+
(args) => ({
|
|
7378
|
+
messages: [{
|
|
7379
|
+
role: "user",
|
|
7380
|
+
content: {
|
|
7381
|
+
type: "text",
|
|
7382
|
+
text: `Find the definition of: "${args.query}"
|
|
7383
|
+
|
|
7384
|
+
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.`
|
|
7385
|
+
}
|
|
7386
|
+
}]
|
|
7387
|
+
})
|
|
7388
|
+
);
|
|
6151
7389
|
return server;
|
|
6152
7390
|
}
|
|
6153
7391
|
|
|
6154
7392
|
// src/cli.ts
|
|
6155
7393
|
function loadJsonFile(filePath) {
|
|
6156
7394
|
try {
|
|
6157
|
-
if ((0,
|
|
6158
|
-
const content = (0,
|
|
7395
|
+
if ((0, import_fs10.existsSync)(filePath)) {
|
|
7396
|
+
const content = (0, import_fs10.readFileSync)(filePath, "utf-8");
|
|
6159
7397
|
return JSON.parse(content);
|
|
6160
7398
|
}
|
|
6161
7399
|
} catch {
|
|
@@ -6167,9 +7405,9 @@ function loadPluginConfig(projectRoot, configPath) {
|
|
|
6167
7405
|
const config = loadJsonFile(configPath);
|
|
6168
7406
|
if (config) return config;
|
|
6169
7407
|
}
|
|
6170
|
-
const projectConfig = loadJsonFile(
|
|
7408
|
+
const projectConfig = loadJsonFile(path9.join(projectRoot, ".opencode", "codebase-index.json"));
|
|
6171
7409
|
if (projectConfig) return projectConfig;
|
|
6172
|
-
const globalConfig = loadJsonFile(
|
|
7410
|
+
const globalConfig = loadJsonFile(path9.join(os4.homedir(), ".config", "opencode", "codebase-index.json"));
|
|
6173
7411
|
if (globalConfig) return globalConfig;
|
|
6174
7412
|
return {};
|
|
6175
7413
|
}
|
|
@@ -6178,14 +7416,18 @@ function parseArgs(argv) {
|
|
|
6178
7416
|
let config;
|
|
6179
7417
|
for (let i = 2; i < argv.length; i++) {
|
|
6180
7418
|
if (argv[i] === "--project" && argv[i + 1]) {
|
|
6181
|
-
project =
|
|
7419
|
+
project = path9.resolve(argv[++i]);
|
|
6182
7420
|
} else if (argv[i] === "--config" && argv[i + 1]) {
|
|
6183
|
-
config =
|
|
7421
|
+
config = path9.resolve(argv[++i]);
|
|
6184
7422
|
}
|
|
6185
7423
|
}
|
|
6186
7424
|
return { project, config };
|
|
6187
7425
|
}
|
|
6188
7426
|
async function main() {
|
|
7427
|
+
if (process.argv[2] === "eval") {
|
|
7428
|
+
const exitCode = await handleEvalCommand(process.argv.slice(3), process.cwd());
|
|
7429
|
+
process.exit(exitCode);
|
|
7430
|
+
}
|
|
6189
7431
|
const args = parseArgs(process.argv);
|
|
6190
7432
|
const rawConfig = loadPluginConfig(args.project, args.config);
|
|
6191
7433
|
const config = parseConfig(rawConfig);
|