@oss-scout/mcp 0.9.0 → 0.10.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/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/mcp-server.bundle.cjs +1031 -321
- package/dist/tools.js +72 -12
- package/package.json +4 -4
|
@@ -3100,15 +3100,16 @@ var require_data = __commonJS({
|
|
|
3100
3100
|
}
|
|
3101
3101
|
});
|
|
3102
3102
|
|
|
3103
|
-
// ../../node_modules/.pnpm/fast-uri@
|
|
3103
|
+
// ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/utils.js
|
|
3104
3104
|
var require_utils = __commonJS({
|
|
3105
|
-
"../../node_modules/.pnpm/fast-uri@
|
|
3105
|
+
"../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/utils.js"(exports2, module2) {
|
|
3106
3106
|
"use strict";
|
|
3107
3107
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3108
3108
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
3109
3109
|
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
3110
3110
|
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
3111
3111
|
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
3112
|
+
var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/?]$/iu);
|
|
3112
3113
|
function stringArrayToHexStripped(input) {
|
|
3113
3114
|
let acc = "";
|
|
3114
3115
|
let code = 0;
|
|
@@ -3251,7 +3252,7 @@ var require_utils = __commonJS({
|
|
|
3251
3252
|
continue;
|
|
3252
3253
|
}
|
|
3253
3254
|
} else if (input[0] === "/") {
|
|
3254
|
-
if (input[1] === "."
|
|
3255
|
+
if (input[1] === ".") {
|
|
3255
3256
|
output.push("/");
|
|
3256
3257
|
break;
|
|
3257
3258
|
}
|
|
@@ -3333,10 +3334,30 @@ var require_utils = __commonJS({
|
|
|
3333
3334
|
}
|
|
3334
3335
|
return output;
|
|
3335
3336
|
}
|
|
3337
|
+
var BYTE_HEX = new Array(256);
|
|
3338
|
+
{
|
|
3339
|
+
const HEX_DIGITS = "0123456789ABCDEF";
|
|
3340
|
+
for (let i = 0; i < 256; i++) {
|
|
3341
|
+
BYTE_HEX[i] = "%" + HEX_DIGITS[i >> 4] + HEX_DIGITS[i & 15];
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
function isEscapeSafe(cp) {
|
|
3345
|
+
return cp >= 48 && cp <= 57 || cp >= 65 && cp <= 90 || cp >= 97 && cp <= 122 || cp === 42 || cp === 43 || cp === 45 || cp === 46 || cp === 47 || cp === 64 || cp === 95;
|
|
3346
|
+
}
|
|
3347
|
+
function percentEncodeNonAscii(cp) {
|
|
3348
|
+
if (cp < 2048) {
|
|
3349
|
+
return BYTE_HEX[192 | cp >> 6] + BYTE_HEX[128 | cp & 63];
|
|
3350
|
+
}
|
|
3351
|
+
if (cp < 65536) {
|
|
3352
|
+
return BYTE_HEX[224 | cp >> 12] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
|
|
3353
|
+
}
|
|
3354
|
+
return BYTE_HEX[240 | cp >> 18] + BYTE_HEX[128 | cp >> 12 & 63] + BYTE_HEX[128 | cp >> 6 & 63] + BYTE_HEX[128 | cp & 63];
|
|
3355
|
+
}
|
|
3336
3356
|
function normalizePathEncoding(input) {
|
|
3337
3357
|
let output = "";
|
|
3338
3358
|
for (let i = 0; i < input.length; i++) {
|
|
3339
|
-
|
|
3359
|
+
const ch = input[i];
|
|
3360
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3340
3361
|
const hex3 = input.slice(i + 1, i + 3);
|
|
3341
3362
|
if (isHexPair(hex3)) {
|
|
3342
3363
|
const normalizedHex = hex3.toUpperCase();
|
|
@@ -3350,10 +3371,66 @@ var require_utils = __commonJS({
|
|
|
3350
3371
|
continue;
|
|
3351
3372
|
}
|
|
3352
3373
|
}
|
|
3353
|
-
if (isPathCharacter(
|
|
3354
|
-
output +=
|
|
3374
|
+
if (isPathCharacter(ch)) {
|
|
3375
|
+
output += ch;
|
|
3376
|
+
} else {
|
|
3377
|
+
const code = input.charCodeAt(i);
|
|
3378
|
+
if (code < 128) {
|
|
3379
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
|
|
3380
|
+
} else if (code < 55296 || code > 57343) {
|
|
3381
|
+
output += percentEncodeNonAscii(code);
|
|
3382
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3383
|
+
const low = input.charCodeAt(i + 1);
|
|
3384
|
+
if (low >= 56320 && low <= 57343) {
|
|
3385
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3386
|
+
i++;
|
|
3387
|
+
} else {
|
|
3388
|
+
output += percentEncodeNonAscii(65533);
|
|
3389
|
+
}
|
|
3390
|
+
} else {
|
|
3391
|
+
output += percentEncodeNonAscii(65533);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
return output;
|
|
3396
|
+
}
|
|
3397
|
+
function normalizeQueryFragmentEncoding(input) {
|
|
3398
|
+
let output = "";
|
|
3399
|
+
for (let i = 0; i < input.length; i++) {
|
|
3400
|
+
const ch = input[i];
|
|
3401
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3402
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
3403
|
+
if (isHexPair(hex3)) {
|
|
3404
|
+
const normalizedHex = hex3.toUpperCase();
|
|
3405
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
3406
|
+
if (isUnreserved(decoded)) {
|
|
3407
|
+
output += decoded;
|
|
3408
|
+
} else {
|
|
3409
|
+
output += "%" + normalizedHex;
|
|
3410
|
+
}
|
|
3411
|
+
i += 2;
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (isQueryFragmentCharacter(ch)) {
|
|
3416
|
+
output += ch;
|
|
3355
3417
|
} else {
|
|
3356
|
-
|
|
3418
|
+
const code = input.charCodeAt(i);
|
|
3419
|
+
if (code < 128) {
|
|
3420
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
|
|
3421
|
+
} else if (code < 55296 || code > 57343) {
|
|
3422
|
+
output += percentEncodeNonAscii(code);
|
|
3423
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3424
|
+
const low = input.charCodeAt(i + 1);
|
|
3425
|
+
if (low >= 56320 && low <= 57343) {
|
|
3426
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3427
|
+
i++;
|
|
3428
|
+
} else {
|
|
3429
|
+
output += percentEncodeNonAscii(65533);
|
|
3430
|
+
}
|
|
3431
|
+
} else {
|
|
3432
|
+
output += percentEncodeNonAscii(65533);
|
|
3433
|
+
}
|
|
3357
3434
|
}
|
|
3358
3435
|
}
|
|
3359
3436
|
return output;
|
|
@@ -3361,7 +3438,8 @@ var require_utils = __commonJS({
|
|
|
3361
3438
|
function escapePreservingEscapes(input) {
|
|
3362
3439
|
let output = "";
|
|
3363
3440
|
for (let i = 0; i < input.length; i++) {
|
|
3364
|
-
|
|
3441
|
+
const ch = input[i];
|
|
3442
|
+
if (ch === "%" && i + 2 < input.length) {
|
|
3365
3443
|
const hex3 = input.slice(i + 1, i + 3);
|
|
3366
3444
|
if (isHexPair(hex3)) {
|
|
3367
3445
|
output += "%" + hex3.toUpperCase();
|
|
@@ -3369,7 +3447,22 @@ var require_utils = __commonJS({
|
|
|
3369
3447
|
continue;
|
|
3370
3448
|
}
|
|
3371
3449
|
}
|
|
3372
|
-
|
|
3450
|
+
const code = input.charCodeAt(i);
|
|
3451
|
+
if (code < 128) {
|
|
3452
|
+
output += isEscapeSafe(code) ? ch : BYTE_HEX[code];
|
|
3453
|
+
} else if (code < 55296 || code > 57343) {
|
|
3454
|
+
output += percentEncodeNonAscii(code);
|
|
3455
|
+
} else if (code <= 56319 && i + 1 < input.length) {
|
|
3456
|
+
const low = input.charCodeAt(i + 1);
|
|
3457
|
+
if (low >= 56320 && low <= 57343) {
|
|
3458
|
+
output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
|
|
3459
|
+
i++;
|
|
3460
|
+
} else {
|
|
3461
|
+
output += percentEncodeNonAscii(65533);
|
|
3462
|
+
}
|
|
3463
|
+
} else {
|
|
3464
|
+
output += percentEncodeNonAscii(65533);
|
|
3465
|
+
}
|
|
3373
3466
|
}
|
|
3374
3467
|
return output;
|
|
3375
3468
|
}
|
|
@@ -3403,6 +3496,7 @@ var require_utils = __commonJS({
|
|
|
3403
3496
|
reescapeHostDelimiters,
|
|
3404
3497
|
normalizePercentEncoding,
|
|
3405
3498
|
normalizePathEncoding,
|
|
3499
|
+
normalizeQueryFragmentEncoding,
|
|
3406
3500
|
escapePreservingEscapes,
|
|
3407
3501
|
removeDotSegments,
|
|
3408
3502
|
isIPv4,
|
|
@@ -3413,9 +3507,9 @@ var require_utils = __commonJS({
|
|
|
3413
3507
|
}
|
|
3414
3508
|
});
|
|
3415
3509
|
|
|
3416
|
-
// ../../node_modules/.pnpm/fast-uri@
|
|
3510
|
+
// ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/schemes.js
|
|
3417
3511
|
var require_schemes = __commonJS({
|
|
3418
|
-
"../../node_modules/.pnpm/fast-uri@
|
|
3512
|
+
"../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
|
|
3419
3513
|
"use strict";
|
|
3420
3514
|
var { isUUID } = require_utils();
|
|
3421
3515
|
var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
|
|
@@ -3623,11 +3717,11 @@ var require_schemes = __commonJS({
|
|
|
3623
3717
|
}
|
|
3624
3718
|
});
|
|
3625
3719
|
|
|
3626
|
-
// ../../node_modules/.pnpm/fast-uri@
|
|
3720
|
+
// ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/index.js
|
|
3627
3721
|
var require_fast_uri = __commonJS({
|
|
3628
|
-
"../../node_modules/.pnpm/fast-uri@
|
|
3722
|
+
"../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/index.js"(exports2, module2) {
|
|
3629
3723
|
"use strict";
|
|
3630
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3724
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3631
3725
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3632
3726
|
function normalize(uri, options) {
|
|
3633
3727
|
if (typeof uri === "string") {
|
|
@@ -3641,7 +3735,12 @@ var require_fast_uri = __commonJS({
|
|
|
3641
3735
|
}
|
|
3642
3736
|
function resolve(baseURI, relativeURI, options) {
|
|
3643
3737
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3644
|
-
const
|
|
3738
|
+
const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
|
|
3739
|
+
const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
|
|
3740
|
+
if (baseMalformed || relativeMalformed) {
|
|
3741
|
+
throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
|
|
3742
|
+
}
|
|
3743
|
+
const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
|
|
3645
3744
|
schemelessOptions.skipEscape = true;
|
|
3646
3745
|
return serialize(resolved, schemelessOptions);
|
|
3647
3746
|
}
|
|
@@ -3766,6 +3865,8 @@ var require_fast_uri = __commonJS({
|
|
|
3766
3865
|
return uriTokens.join("");
|
|
3767
3866
|
}
|
|
3768
3867
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
3868
|
+
var AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/;
|
|
3869
|
+
var AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/;
|
|
3769
3870
|
function getParseError(parsed, matches) {
|
|
3770
3871
|
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
3771
3872
|
return 'URI path must start with "/" when authority is present.';
|
|
@@ -3795,9 +3896,28 @@ var require_fast_uri = __commonJS({
|
|
|
3795
3896
|
uri = "//" + uri;
|
|
3796
3897
|
}
|
|
3797
3898
|
}
|
|
3899
|
+
const authorityMatch = uri.match(AUTHORITY_PREFIX);
|
|
3900
|
+
if (authorityMatch !== null && authorityMatch[1].indexOf("\\") !== -1) {
|
|
3901
|
+
parsed.error = "URI authority must not contain a literal backslash.";
|
|
3902
|
+
malformedAuthorityOrPort = true;
|
|
3903
|
+
}
|
|
3904
|
+
const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION);
|
|
3905
|
+
if (introducerMatch !== null) {
|
|
3906
|
+
const region = introducerMatch[1];
|
|
3907
|
+
const normalizedRegion = region.replace(/[\t\n\r]/g, "");
|
|
3908
|
+
if (normalizedRegion.length >= 2) {
|
|
3909
|
+
if (normalizedRegion.slice(0, 2) !== "//") {
|
|
3910
|
+
parsed.error = parsed.error || "URI authority must not contain a literal backslash.";
|
|
3911
|
+
malformedAuthorityOrPort = true;
|
|
3912
|
+
} else if (region.length !== normalizedRegion.length) {
|
|
3913
|
+
parsed.error = parsed.error || "URI authority introducer must not contain whitespace.";
|
|
3914
|
+
malformedAuthorityOrPort = true;
|
|
3915
|
+
}
|
|
3916
|
+
}
|
|
3917
|
+
}
|
|
3798
3918
|
const matches = uri.match(URI_PARSE);
|
|
3799
3919
|
if (matches) {
|
|
3800
|
-
parsed.scheme = matches[1];
|
|
3920
|
+
parsed.scheme = matches[1] === void 0 ? void 0 : matches[1].toLowerCase();
|
|
3801
3921
|
parsed.userinfo = matches[3];
|
|
3802
3922
|
parsed.host = matches[4];
|
|
3803
3923
|
parsed.port = parseInt(matches[5], 10);
|
|
@@ -3838,7 +3958,7 @@ var require_fast_uri = __commonJS({
|
|
|
3838
3958
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
3839
3959
|
if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
3840
3960
|
try {
|
|
3841
|
-
parsed.host = URL
|
|
3961
|
+
parsed.host = new URL("http://" + parsed.host).hostname;
|
|
3842
3962
|
} catch (e) {
|
|
3843
3963
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
3844
3964
|
}
|
|
@@ -3856,12 +3976,11 @@ var require_fast_uri = __commonJS({
|
|
|
3856
3976
|
if (parsed.path) {
|
|
3857
3977
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
3858
3978
|
}
|
|
3979
|
+
if (parsed.query) {
|
|
3980
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query);
|
|
3981
|
+
}
|
|
3859
3982
|
if (parsed.fragment) {
|
|
3860
|
-
|
|
3861
|
-
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
3862
|
-
} catch {
|
|
3863
|
-
parsed.error = parsed.error || "URI malformed";
|
|
3864
|
-
}
|
|
3983
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment);
|
|
3865
3984
|
}
|
|
3866
3985
|
}
|
|
3867
3986
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -7356,11 +7475,11 @@ var require_light = __commonJS({
|
|
|
7356
7475
|
return this._onFailure(error48, eventInfo, clearGlobalState, run, free);
|
|
7357
7476
|
}
|
|
7358
7477
|
async _onFailure(error48, eventInfo, clearGlobalState, run, free) {
|
|
7359
|
-
var
|
|
7478
|
+
var retry2, retryAfter;
|
|
7360
7479
|
if (clearGlobalState()) {
|
|
7361
|
-
|
|
7362
|
-
if (
|
|
7363
|
-
retryAfter = ~~
|
|
7480
|
+
retry2 = await this.Events.trigger("failed", error48, eventInfo);
|
|
7481
|
+
if (retry2 != null) {
|
|
7482
|
+
retryAfter = ~~retry2;
|
|
7364
7483
|
this.Events.trigger("retry", `Retrying ${this.options.id} after ${retryAfter} ms`, eventInfo);
|
|
7365
7484
|
this.retryCount++;
|
|
7366
7485
|
return run(retryAfter);
|
|
@@ -7875,7 +7994,7 @@ var require_light = __commonJS({
|
|
|
7875
7994
|
var Batcher_1 = Batcher;
|
|
7876
7995
|
var require$$4$1 = () => console.log("You must import the full version of Bottleneck in order to use this feature.");
|
|
7877
7996
|
var require$$8 = getCjsExportFromNamespace(version$2);
|
|
7878
|
-
var
|
|
7997
|
+
var Bottleneck2, DEFAULT_PRIORITY$1, Events$4, Job$1, LocalDatastore$1, NUM_PRIORITIES$1, Queues$1, RedisDatastore$1, States$1, Sync$1, parser$5, splice = [].splice;
|
|
7879
7998
|
NUM_PRIORITIES$1 = 10;
|
|
7880
7999
|
DEFAULT_PRIORITY$1 = 5;
|
|
7881
8000
|
parser$5 = parser;
|
|
@@ -7886,8 +8005,8 @@ var require_light = __commonJS({
|
|
|
7886
8005
|
Events$4 = Events_1;
|
|
7887
8006
|
States$1 = States_1;
|
|
7888
8007
|
Sync$1 = Sync_1;
|
|
7889
|
-
|
|
7890
|
-
class
|
|
8008
|
+
Bottleneck2 = (function() {
|
|
8009
|
+
class Bottleneck3 {
|
|
7891
8010
|
constructor(options = {}, ...invalid) {
|
|
7892
8011
|
var storeInstanceOptions, storeOptions;
|
|
7893
8012
|
this._addToQueue = this._addToQueue.bind(this);
|
|
@@ -7909,7 +8028,7 @@ var require_light = __commonJS({
|
|
|
7909
8028
|
storeInstanceOptions = parser$5.load(options, this.localStoreDefaults, {});
|
|
7910
8029
|
return new LocalDatastore$1(this, storeOptions, storeInstanceOptions);
|
|
7911
8030
|
} else {
|
|
7912
|
-
throw new
|
|
8031
|
+
throw new Bottleneck3.prototype.BottleneckError(`Invalid datastore type: ${this.datastore}`);
|
|
7913
8032
|
}
|
|
7914
8033
|
}).call(this);
|
|
7915
8034
|
this._queues.on("leftzero", () => {
|
|
@@ -7923,7 +8042,7 @@ var require_light = __commonJS({
|
|
|
7923
8042
|
}
|
|
7924
8043
|
_validateOptions(options, invalid) {
|
|
7925
8044
|
if (!(options != null && typeof options === "object" && invalid.length === 0)) {
|
|
7926
|
-
throw new
|
|
8045
|
+
throw new Bottleneck3.prototype.BottleneckError("Bottleneck v2 takes a single object argument. Refer to https://github.com/SGrondin/bottleneck#upgrading-to-v2 if you're upgrading from Bottleneck v1.");
|
|
7927
8046
|
}
|
|
7928
8047
|
}
|
|
7929
8048
|
ready() {
|
|
@@ -8120,10 +8239,10 @@ var require_light = __commonJS({
|
|
|
8120
8239
|
return waitForExecuting(1);
|
|
8121
8240
|
});
|
|
8122
8241
|
this._receive = function(job) {
|
|
8123
|
-
return job._reject(new
|
|
8242
|
+
return job._reject(new Bottleneck3.prototype.BottleneckError(options.enqueueErrorMessage));
|
|
8124
8243
|
};
|
|
8125
8244
|
this.stop = () => {
|
|
8126
|
-
return this.Promise.reject(new
|
|
8245
|
+
return this.Promise.reject(new Bottleneck3.prototype.BottleneckError("stop() has already been called"));
|
|
8127
8246
|
};
|
|
8128
8247
|
return done;
|
|
8129
8248
|
}
|
|
@@ -8142,11 +8261,11 @@ var require_light = __commonJS({
|
|
|
8142
8261
|
job.doDrop();
|
|
8143
8262
|
return true;
|
|
8144
8263
|
} else if (reachedHWM) {
|
|
8145
|
-
shifted = strategy ===
|
|
8264
|
+
shifted = strategy === Bottleneck3.prototype.strategy.LEAK ? this._queues.shiftLastFrom(options.priority) : strategy === Bottleneck3.prototype.strategy.OVERFLOW_PRIORITY ? this._queues.shiftLastFrom(options.priority + 1) : strategy === Bottleneck3.prototype.strategy.OVERFLOW ? job : void 0;
|
|
8146
8265
|
if (shifted != null) {
|
|
8147
8266
|
shifted.doDrop();
|
|
8148
8267
|
}
|
|
8149
|
-
if (shifted == null || strategy ===
|
|
8268
|
+
if (shifted == null || strategy === Bottleneck3.prototype.strategy.OVERFLOW) {
|
|
8150
8269
|
if (shifted == null) {
|
|
8151
8270
|
job.doDrop();
|
|
8152
8271
|
}
|
|
@@ -8160,7 +8279,7 @@ var require_light = __commonJS({
|
|
|
8160
8279
|
}
|
|
8161
8280
|
_receive(job) {
|
|
8162
8281
|
if (this._states.jobStatus(job.options.id) != null) {
|
|
8163
|
-
job._reject(new
|
|
8282
|
+
job._reject(new Bottleneck3.prototype.BottleneckError(`A job with the same id already exists (id=${job.options.id})`));
|
|
8164
8283
|
return false;
|
|
8165
8284
|
} else {
|
|
8166
8285
|
job.doReceive();
|
|
@@ -8230,31 +8349,31 @@ var require_light = __commonJS({
|
|
|
8230
8349
|
return this._store.__incrementReservoir__(incr);
|
|
8231
8350
|
}
|
|
8232
8351
|
}
|
|
8233
|
-
|
|
8234
|
-
|
|
8235
|
-
|
|
8236
|
-
|
|
8352
|
+
Bottleneck3.default = Bottleneck3;
|
|
8353
|
+
Bottleneck3.Events = Events$4;
|
|
8354
|
+
Bottleneck3.version = Bottleneck3.prototype.version = require$$8.version;
|
|
8355
|
+
Bottleneck3.strategy = Bottleneck3.prototype.strategy = {
|
|
8237
8356
|
LEAK: 1,
|
|
8238
8357
|
OVERFLOW: 2,
|
|
8239
8358
|
OVERFLOW_PRIORITY: 4,
|
|
8240
8359
|
BLOCK: 3
|
|
8241
8360
|
};
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8361
|
+
Bottleneck3.BottleneckError = Bottleneck3.prototype.BottleneckError = BottleneckError_1;
|
|
8362
|
+
Bottleneck3.Group = Bottleneck3.prototype.Group = Group_1;
|
|
8363
|
+
Bottleneck3.RedisConnection = Bottleneck3.prototype.RedisConnection = require$$2;
|
|
8364
|
+
Bottleneck3.IORedisConnection = Bottleneck3.prototype.IORedisConnection = require$$3;
|
|
8365
|
+
Bottleneck3.Batcher = Bottleneck3.prototype.Batcher = Batcher_1;
|
|
8366
|
+
Bottleneck3.prototype.jobDefaults = {
|
|
8248
8367
|
priority: DEFAULT_PRIORITY$1,
|
|
8249
8368
|
weight: 1,
|
|
8250
8369
|
expiration: null,
|
|
8251
8370
|
id: "<no-id>"
|
|
8252
8371
|
};
|
|
8253
|
-
|
|
8372
|
+
Bottleneck3.prototype.storeDefaults = {
|
|
8254
8373
|
maxConcurrent: null,
|
|
8255
8374
|
minTime: 0,
|
|
8256
8375
|
highWater: null,
|
|
8257
|
-
strategy:
|
|
8376
|
+
strategy: Bottleneck3.prototype.strategy.LEAK,
|
|
8258
8377
|
penalty: null,
|
|
8259
8378
|
reservoir: null,
|
|
8260
8379
|
reservoirRefreshInterval: null,
|
|
@@ -8263,12 +8382,12 @@ var require_light = __commonJS({
|
|
|
8263
8382
|
reservoirIncreaseAmount: null,
|
|
8264
8383
|
reservoirIncreaseMaximum: null
|
|
8265
8384
|
};
|
|
8266
|
-
|
|
8385
|
+
Bottleneck3.prototype.localStoreDefaults = {
|
|
8267
8386
|
Promise,
|
|
8268
8387
|
timeout: null,
|
|
8269
8388
|
heartbeatInterval: 250
|
|
8270
8389
|
};
|
|
8271
|
-
|
|
8390
|
+
Bottleneck3.prototype.redisStoreDefaults = {
|
|
8272
8391
|
Promise,
|
|
8273
8392
|
timeout: null,
|
|
8274
8393
|
heartbeatInterval: 5e3,
|
|
@@ -8279,7 +8398,7 @@ var require_light = __commonJS({
|
|
|
8279
8398
|
clearDatastore: false,
|
|
8280
8399
|
connection: null
|
|
8281
8400
|
};
|
|
8282
|
-
|
|
8401
|
+
Bottleneck3.prototype.instanceDefaults = {
|
|
8283
8402
|
datastore: "local",
|
|
8284
8403
|
connection: null,
|
|
8285
8404
|
id: "<no-id>",
|
|
@@ -8287,14 +8406,14 @@ var require_light = __commonJS({
|
|
|
8287
8406
|
trackDoneStatus: false,
|
|
8288
8407
|
Promise
|
|
8289
8408
|
};
|
|
8290
|
-
|
|
8409
|
+
Bottleneck3.prototype.stopDefaults = {
|
|
8291
8410
|
enqueueErrorMessage: "This limiter has been stopped and cannot accept new jobs.",
|
|
8292
8411
|
dropWaitingJobs: true,
|
|
8293
8412
|
dropErrorMessage: "This limiter has been stopped."
|
|
8294
8413
|
};
|
|
8295
|
-
return
|
|
8414
|
+
return Bottleneck3;
|
|
8296
8415
|
}).call(commonjsGlobal);
|
|
8297
|
-
var Bottleneck_1 =
|
|
8416
|
+
var Bottleneck_1 = Bottleneck2;
|
|
8298
8417
|
var lib = Bottleneck_1;
|
|
8299
8418
|
return lib;
|
|
8300
8419
|
}));
|
|
@@ -35311,30 +35430,30 @@ function routeMatcher(paths) {
|
|
|
35311
35430
|
var regex = routeMatcher(triggers_notification_paths_default);
|
|
35312
35431
|
var triggersNotification = regex.test.bind(regex);
|
|
35313
35432
|
var groups = {};
|
|
35314
|
-
var createGroups = function(
|
|
35315
|
-
groups.global = new
|
|
35433
|
+
var createGroups = function(Bottleneck2, common) {
|
|
35434
|
+
groups.global = new Bottleneck2.Group({
|
|
35316
35435
|
id: "octokit-global",
|
|
35317
35436
|
maxConcurrent: 10,
|
|
35318
35437
|
...common
|
|
35319
35438
|
});
|
|
35320
|
-
groups.auth = new
|
|
35439
|
+
groups.auth = new Bottleneck2.Group({
|
|
35321
35440
|
id: "octokit-auth",
|
|
35322
35441
|
maxConcurrent: 1,
|
|
35323
35442
|
...common
|
|
35324
35443
|
});
|
|
35325
|
-
groups.search = new
|
|
35444
|
+
groups.search = new Bottleneck2.Group({
|
|
35326
35445
|
id: "octokit-search",
|
|
35327
35446
|
maxConcurrent: 1,
|
|
35328
35447
|
minTime: 2e3,
|
|
35329
35448
|
...common
|
|
35330
35449
|
});
|
|
35331
|
-
groups.write = new
|
|
35450
|
+
groups.write = new Bottleneck2.Group({
|
|
35332
35451
|
id: "octokit-write",
|
|
35333
35452
|
maxConcurrent: 1,
|
|
35334
35453
|
minTime: 1e3,
|
|
35335
35454
|
...common
|
|
35336
35455
|
});
|
|
35337
|
-
groups.notifications = new
|
|
35456
|
+
groups.notifications = new Bottleneck2.Group({
|
|
35338
35457
|
id: "octokit-notifications",
|
|
35339
35458
|
maxConcurrent: 1,
|
|
35340
35459
|
minTime: 3e3,
|
|
@@ -35344,7 +35463,7 @@ var createGroups = function(Bottleneck, common) {
|
|
|
35344
35463
|
function throttling(octokit, octokitOptions) {
|
|
35345
35464
|
const {
|
|
35346
35465
|
enabled = true,
|
|
35347
|
-
Bottleneck = import_light.default,
|
|
35466
|
+
Bottleneck: Bottleneck2 = import_light.default,
|
|
35348
35467
|
id = "no-id",
|
|
35349
35468
|
timeout = 1e3 * 60 * 2,
|
|
35350
35469
|
// Redis TTL: 2 minutes
|
|
@@ -35358,7 +35477,7 @@ function throttling(octokit, octokitOptions) {
|
|
|
35358
35477
|
common.connection = connection;
|
|
35359
35478
|
}
|
|
35360
35479
|
if (groups.global == null) {
|
|
35361
|
-
createGroups(
|
|
35480
|
+
createGroups(Bottleneck2, common);
|
|
35362
35481
|
}
|
|
35363
35482
|
const state = Object.assign(
|
|
35364
35483
|
{
|
|
@@ -35366,7 +35485,7 @@ function throttling(octokit, octokitOptions) {
|
|
|
35366
35485
|
triggersNotification,
|
|
35367
35486
|
fallbackSecondaryRateRetryAfter: 60,
|
|
35368
35487
|
retryAfterBaseValue: 1e3,
|
|
35369
|
-
retryLimiter: new
|
|
35488
|
+
retryLimiter: new Bottleneck2(),
|
|
35370
35489
|
id,
|
|
35371
35490
|
...groups
|
|
35372
35491
|
},
|
|
@@ -35386,7 +35505,7 @@ function throttling(octokit, octokitOptions) {
|
|
|
35386
35505
|
`);
|
|
35387
35506
|
}
|
|
35388
35507
|
const events = {};
|
|
35389
|
-
const emitter = new
|
|
35508
|
+
const emitter = new Bottleneck2.Events(events);
|
|
35390
35509
|
events.on("secondary-limit", state.onSecondaryRateLimit);
|
|
35391
35510
|
events.on("rate-limit", state.onRateLimit);
|
|
35392
35511
|
events.on(
|
|
@@ -35449,6 +35568,80 @@ function throttling(octokit, octokitOptions) {
|
|
|
35449
35568
|
throttling.VERSION = VERSION9;
|
|
35450
35569
|
throttling.triggersNotification = triggersNotification;
|
|
35451
35570
|
|
|
35571
|
+
// ../../node_modules/.pnpm/@octokit+plugin-retry@8.1.0_@octokit+core@7.0.6/node_modules/@octokit/plugin-retry/dist-bundle/index.js
|
|
35572
|
+
var import_light2 = __toESM(require_light(), 1);
|
|
35573
|
+
var VERSION10 = "0.0.0-development";
|
|
35574
|
+
function isRequestError(error48) {
|
|
35575
|
+
return error48.request !== void 0;
|
|
35576
|
+
}
|
|
35577
|
+
async function errorRequest(state, octokit, error48, options) {
|
|
35578
|
+
if (!isRequestError(error48) || !error48?.request.request) {
|
|
35579
|
+
throw error48;
|
|
35580
|
+
}
|
|
35581
|
+
if (error48.status >= 400 && !state.doNotRetry.includes(error48.status)) {
|
|
35582
|
+
const retries = options.request.retries != null ? options.request.retries : state.retries;
|
|
35583
|
+
const retryAfter = Math.pow((options.request.retryCount || 0) + 1, 2);
|
|
35584
|
+
throw octokit.retry.retryRequest(error48, retries, retryAfter);
|
|
35585
|
+
}
|
|
35586
|
+
throw error48;
|
|
35587
|
+
}
|
|
35588
|
+
async function wrapRequest2(state, octokit, request2, options) {
|
|
35589
|
+
const limiter = new import_light2.default();
|
|
35590
|
+
limiter.on("failed", function(error48, info2) {
|
|
35591
|
+
const maxRetries = ~~error48.request.request?.retries;
|
|
35592
|
+
const after = ~~error48.request.request?.retryAfter;
|
|
35593
|
+
options.request.retryCount = info2.retryCount + 1;
|
|
35594
|
+
if (maxRetries > info2.retryCount) {
|
|
35595
|
+
return after * state.retryAfterBaseValue;
|
|
35596
|
+
}
|
|
35597
|
+
});
|
|
35598
|
+
return limiter.schedule(
|
|
35599
|
+
requestWithGraphqlErrorHandling.bind(null, state, octokit, request2),
|
|
35600
|
+
options
|
|
35601
|
+
);
|
|
35602
|
+
}
|
|
35603
|
+
async function requestWithGraphqlErrorHandling(state, octokit, request2, options) {
|
|
35604
|
+
const response = await request2(options);
|
|
35605
|
+
if (response.data && response.data.errors && response.data.errors.length > 0 && /Something went wrong while executing your query/.test(
|
|
35606
|
+
response.data.errors[0].message
|
|
35607
|
+
)) {
|
|
35608
|
+
const error48 = new RequestError(response.data.errors[0].message, 500, {
|
|
35609
|
+
request: options,
|
|
35610
|
+
response
|
|
35611
|
+
});
|
|
35612
|
+
return errorRequest(state, octokit, error48, options);
|
|
35613
|
+
}
|
|
35614
|
+
return response;
|
|
35615
|
+
}
|
|
35616
|
+
function retry(octokit, octokitOptions) {
|
|
35617
|
+
const state = Object.assign(
|
|
35618
|
+
{
|
|
35619
|
+
enabled: true,
|
|
35620
|
+
retryAfterBaseValue: 1e3,
|
|
35621
|
+
doNotRetry: [400, 401, 403, 404, 410, 422, 451],
|
|
35622
|
+
retries: 3
|
|
35623
|
+
},
|
|
35624
|
+
octokitOptions.retry
|
|
35625
|
+
);
|
|
35626
|
+
const retryPlugin = {
|
|
35627
|
+
retry: {
|
|
35628
|
+
retryRequest: (error48, retries, retryAfter) => {
|
|
35629
|
+
error48.request.request = Object.assign({}, error48.request.request, {
|
|
35630
|
+
retries,
|
|
35631
|
+
retryAfter
|
|
35632
|
+
});
|
|
35633
|
+
return error48;
|
|
35634
|
+
}
|
|
35635
|
+
}
|
|
35636
|
+
};
|
|
35637
|
+
if (state.enabled) {
|
|
35638
|
+
octokit.hook.error("request", errorRequest.bind(null, state, retryPlugin));
|
|
35639
|
+
octokit.hook.wrap("request", wrapRequest2.bind(null, state, retryPlugin));
|
|
35640
|
+
}
|
|
35641
|
+
return retryPlugin;
|
|
35642
|
+
}
|
|
35643
|
+
retry.VERSION = VERSION10;
|
|
35644
|
+
|
|
35452
35645
|
// ../core/dist/core/logger.js
|
|
35453
35646
|
var LEVEL_RANK = {
|
|
35454
35647
|
silent: 0,
|
|
@@ -35484,7 +35677,7 @@ function warn(module2, message, ...args) {
|
|
|
35484
35677
|
|
|
35485
35678
|
// ../core/dist/core/github.js
|
|
35486
35679
|
var MODULE = "github";
|
|
35487
|
-
var ThrottledOctokit = Octokit2.plugin(throttling);
|
|
35680
|
+
var ThrottledOctokit = Octokit2.plugin(throttling, retry);
|
|
35488
35681
|
var _octokit = null;
|
|
35489
35682
|
var _currentToken = null;
|
|
35490
35683
|
function formatResetTime(date5) {
|
|
@@ -35552,8 +35745,10 @@ var OssScoutError = class extends Error {
|
|
|
35552
35745
|
}
|
|
35553
35746
|
};
|
|
35554
35747
|
var ValidationError = class extends OssScoutError {
|
|
35555
|
-
|
|
35748
|
+
strategiesUsed;
|
|
35749
|
+
constructor(message, strategiesUsed) {
|
|
35556
35750
|
super(message, "VALIDATION_ERROR");
|
|
35751
|
+
this.strategiesUsed = strategiesUsed;
|
|
35557
35752
|
this.name = "ValidationError";
|
|
35558
35753
|
}
|
|
35559
35754
|
};
|
|
@@ -35567,6 +35762,12 @@ function getHttpStatusCode(error48) {
|
|
|
35567
35762
|
}
|
|
35568
35763
|
return void 0;
|
|
35569
35764
|
}
|
|
35765
|
+
function isAuthError(error48) {
|
|
35766
|
+
const status = getHttpStatusCode(error48);
|
|
35767
|
+
if (status === 401)
|
|
35768
|
+
return true;
|
|
35769
|
+
return status === 403 && !isRateLimitError(error48);
|
|
35770
|
+
}
|
|
35570
35771
|
function isRateLimitError(error48) {
|
|
35571
35772
|
const status = getHttpStatusCode(error48);
|
|
35572
35773
|
if (status === 429)
|
|
@@ -35702,6 +35903,13 @@ var SearchBudgetTracker = class {
|
|
|
35702
35903
|
totalCalls = 0;
|
|
35703
35904
|
/** Calls made since the last known quota reset (external accounting). */
|
|
35704
35905
|
callsSinceReset = 0;
|
|
35906
|
+
/**
|
|
35907
|
+
* Slots handed out by waitForBudget() but not yet recorded. Without this,
|
|
35908
|
+
* concurrent callers (vetting runs 3 tasks) could all pass waitForBudget()
|
|
35909
|
+
* on an effective budget of 1 before any recordCall() landed, overshooting
|
|
35910
|
+
* the external quota into real 429s (#287).
|
|
35911
|
+
*/
|
|
35912
|
+
reservedCalls = 0;
|
|
35705
35913
|
/**
|
|
35706
35914
|
* Initialize with pre-flight rate limit data from GitHub.
|
|
35707
35915
|
*/
|
|
@@ -35711,12 +35919,17 @@ var SearchBudgetTracker = class {
|
|
|
35711
35919
|
this.callTimestamps = [];
|
|
35712
35920
|
this.totalCalls = 0;
|
|
35713
35921
|
this.callsSinceReset = 0;
|
|
35922
|
+
this.reservedCalls = 0;
|
|
35714
35923
|
debug(MODULE3, `Initialized: ${remaining} remaining, resets at ${new Date(this.resetAt).toLocaleTimeString()}`);
|
|
35715
35924
|
}
|
|
35716
35925
|
/**
|
|
35717
|
-
* Record that a Search API call was just made.
|
|
35926
|
+
* Record that a Search API call was just made. Releases the reservation
|
|
35927
|
+
* taken by the preceding waitForBudget() (every call site pairs them, with
|
|
35928
|
+
* recordCall in a finally).
|
|
35718
35929
|
*/
|
|
35719
35930
|
recordCall() {
|
|
35931
|
+
if (this.reservedCalls > 0)
|
|
35932
|
+
this.reservedCalls--;
|
|
35720
35933
|
this.callTimestamps.push(Date.now());
|
|
35721
35934
|
this.totalCalls++;
|
|
35722
35935
|
this.callsSinceReset++;
|
|
@@ -35763,8 +35976,8 @@ var SearchBudgetTracker = class {
|
|
|
35763
35976
|
*/
|
|
35764
35977
|
getEffectiveBudget() {
|
|
35765
35978
|
this.refreshExternalBudget();
|
|
35766
|
-
const localBudget = EFFECTIVE_BUDGET - this.callTimestamps.length;
|
|
35767
|
-
const externalBudget = this.knownRemaining - this.callsSinceReset;
|
|
35979
|
+
const localBudget = EFFECTIVE_BUDGET - this.callTimestamps.length - this.reservedCalls;
|
|
35980
|
+
const externalBudget = this.knownRemaining - this.callsSinceReset - this.reservedCalls;
|
|
35768
35981
|
return Math.max(0, Math.min(localBudget, externalBudget));
|
|
35769
35982
|
}
|
|
35770
35983
|
/**
|
|
@@ -35775,7 +35988,9 @@ var SearchBudgetTracker = class {
|
|
|
35775
35988
|
return this.getEffectiveBudget() >= n;
|
|
35776
35989
|
}
|
|
35777
35990
|
/**
|
|
35778
|
-
* Wait if necessary to stay within the Search API rate limit
|
|
35991
|
+
* Wait if necessary to stay within the Search API rate limit, then reserve
|
|
35992
|
+
* one call slot. The reservation is released by the paired recordCall(), so
|
|
35993
|
+
* concurrent callers cannot all claim the same last remaining slot (#287).
|
|
35779
35994
|
* If the sliding window is at capacity, sleeps until the oldest
|
|
35780
35995
|
* call ages out of the window.
|
|
35781
35996
|
*/
|
|
@@ -35783,16 +35998,22 @@ var SearchBudgetTracker = class {
|
|
|
35783
35998
|
while (true) {
|
|
35784
35999
|
this.pruneOldTimestamps();
|
|
35785
36000
|
if (this.getEffectiveBudget() > 0) {
|
|
36001
|
+
this.reservedCalls++;
|
|
35786
36002
|
return;
|
|
35787
36003
|
}
|
|
35788
36004
|
const oldestInWindow = this.callTimestamps[0];
|
|
35789
36005
|
if (!oldestInWindow) {
|
|
36006
|
+
if (this.reservedCalls > 0) {
|
|
36007
|
+
await sleep(250);
|
|
36008
|
+
continue;
|
|
36009
|
+
}
|
|
35790
36010
|
const untilReset = this.resetAt - Date.now();
|
|
35791
36011
|
if (this.resetAt > 0 && untilReset > 0) {
|
|
35792
36012
|
debug(MODULE3, `External quota exhausted, waiting ${untilReset}ms for reset`);
|
|
35793
36013
|
await sleep(untilReset + 100);
|
|
35794
36014
|
continue;
|
|
35795
36015
|
}
|
|
36016
|
+
this.reservedCalls++;
|
|
35796
36017
|
return;
|
|
35797
36018
|
}
|
|
35798
36019
|
const waitUntil = oldestInWindow + SEARCH_WINDOW_MS;
|
|
@@ -35867,6 +36088,9 @@ var CONCRETE_STRATEGIES = [
|
|
|
35867
36088
|
];
|
|
35868
36089
|
var RepoSignalsSchema = external_exports3.looseObject({
|
|
35869
36090
|
hasActiveMaintainers: external_exports3.boolean(),
|
|
36091
|
+
// Retained for backward compatibility but no longer affects the repo score
|
|
36092
|
+
// (#167): nothing computes it, and hasActiveMaintainers is the live activity
|
|
36093
|
+
// proxy. Kept so old persisted state and the search JSON output still parse.
|
|
35870
36094
|
isResponsive: external_exports3.boolean(),
|
|
35871
36095
|
hasHostileComments: external_exports3.boolean()
|
|
35872
36096
|
});
|
|
@@ -36009,7 +36233,17 @@ var ScoutPreferencesSchema = external_exports3.looseObject({
|
|
|
36009
36233
|
preferLanguages: external_exports3.array(external_exports3.string()).default([]),
|
|
36010
36234
|
preferRepos: external_exports3.array(external_exports3.string()).default([]),
|
|
36011
36235
|
diversityRatio: external_exports3.number().min(0).max(1).default(0),
|
|
36012
|
-
|
|
36236
|
+
// Soft penalty (milder than the hard excludeRepos filter): candidates in
|
|
36237
|
+
// these `owner/repo` slugs are pushed down the ranking but not removed (#168).
|
|
36238
|
+
avoidRepos: external_exports3.array(external_exports3.string()).default([]),
|
|
36239
|
+
// Soft boost for candidates whose issue labels match one of these types,
|
|
36240
|
+
// case-insensitive (e.g. "bug", "good first issue") (#168).
|
|
36241
|
+
boostIssueTypes: external_exports3.array(external_exports3.string()).default([]),
|
|
36242
|
+
// Broad-phase cooldown. Now that the broad phase runs on GraphQL (points
|
|
36243
|
+
// bucket, 5000/hr) rather than the REST Search bucket (30/min), the long
|
|
36244
|
+
// secondary-rate-limit cooldown is no longer needed; defaults to 0 (no extra
|
|
36245
|
+
// pause). Retained as a knob for hosts that still want to throttle the phase.
|
|
36246
|
+
broadPhaseDelayMs: external_exports3.number().min(0).max(3e5).default(0),
|
|
36013
36247
|
/**
|
|
36014
36248
|
* Skip the expensive broad phase once this many candidates were found by
|
|
36015
36249
|
* the cheaper phases. Clamped at runtime to maxResults - 1 so it stays
|
|
@@ -36072,7 +36306,20 @@ var ScoutStateSchema = external_exports3.looseObject({
|
|
|
36072
36306
|
preferencesUpdatedAt: external_exports3.string().optional(),
|
|
36073
36307
|
lastSearchAt: external_exports3.string().optional(),
|
|
36074
36308
|
lastRunAt: external_exports3.string().default(() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
36075
|
-
gistId: external_exports3.string().optional()
|
|
36309
|
+
gistId: external_exports3.string().optional(),
|
|
36310
|
+
/**
|
|
36311
|
+
* Diversity cursor for Phase 2's broad search (#249 follow-up). Without
|
|
36312
|
+
* this, every run started the language-variant fan-out at index 0, so the
|
|
36313
|
+
* broad phase only ever queried the first configured language before
|
|
36314
|
+
* moving on. Not a rate-limit mechanism — purely rotates which language
|
|
36315
|
+
* variant leads each run. `languageOffset` advances (no modulo; the
|
|
36316
|
+
* consumer wraps at use time so a shrinking language list never needs
|
|
36317
|
+
* clamping here).
|
|
36318
|
+
*/
|
|
36319
|
+
searchRotation: external_exports3.looseObject({
|
|
36320
|
+
languageOffset: external_exports3.number().int().min(0).default(0),
|
|
36321
|
+
lastRotatedAt: external_exports3.string().optional()
|
|
36322
|
+
}).default(() => ({ languageOffset: 0 }))
|
|
36076
36323
|
});
|
|
36077
36324
|
function parseScoutState(raw) {
|
|
36078
36325
|
return ScoutStateSchema.parse(raw);
|
|
@@ -36725,16 +36972,18 @@ async function checkNoExistingPR(octokit, owner, repo, issueNumber) {
|
|
|
36725
36972
|
}
|
|
36726
36973
|
}
|
|
36727
36974
|
var MERGED_PR_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
36728
|
-
|
|
36975
|
+
function mergedPRsCacheKey(owner, repo) {
|
|
36976
|
+
return versionedCacheKey(`merged-prs:${owner}/${repo}`);
|
|
36977
|
+
}
|
|
36978
|
+
async function checkUserMergedPRsInRepo(octokit, owner, repo, tracker = getSearchBudgetTracker()) {
|
|
36729
36979
|
const cache = getHttpCache();
|
|
36730
|
-
const cacheKey =
|
|
36980
|
+
const cacheKey = mergedPRsCacheKey(owner, repo);
|
|
36731
36981
|
return withInflightDedup(cache, cacheKey, async () => {
|
|
36732
36982
|
const cached2 = cache.getIfFresh(cacheKey, MERGED_PR_CACHE_TTL_MS);
|
|
36733
36983
|
if (cached2 != null && typeof cached2 === "number") {
|
|
36734
36984
|
return cached2;
|
|
36735
36985
|
}
|
|
36736
36986
|
try {
|
|
36737
|
-
const tracker = getSearchBudgetTracker();
|
|
36738
36987
|
await tracker.waitForBudget();
|
|
36739
36988
|
try {
|
|
36740
36989
|
const { data } = await octokit.search.issuesAndPullRequests({
|
|
@@ -36773,6 +37022,17 @@ async function checkNotClaimed(octokit, owner, repo, issueNumber, commentCount)
|
|
|
36773
37022
|
});
|
|
36774
37023
|
recentComments.push(...response.data);
|
|
36775
37024
|
}
|
|
37025
|
+
const lastFetched = recentComments.length % PER_PAGE;
|
|
37026
|
+
if (recentComments.length > 0 && lastFetched === 0) {
|
|
37027
|
+
const response = await octokit.issues.listComments({
|
|
37028
|
+
owner,
|
|
37029
|
+
repo,
|
|
37030
|
+
issue_number: issueNumber,
|
|
37031
|
+
per_page: PER_PAGE,
|
|
37032
|
+
page: lastPage + 1
|
|
37033
|
+
});
|
|
37034
|
+
recentComments.push(...response.data);
|
|
37035
|
+
}
|
|
36776
37036
|
for (const comment of recentComments) {
|
|
36777
37037
|
if (commentClaimsIssue(comment.body || "")) {
|
|
36778
37038
|
return { passed: false };
|
|
@@ -36801,8 +37061,54 @@ function analyzeRequirements(body) {
|
|
|
36801
37061
|
return indicators.filter(Boolean).length >= 2;
|
|
36802
37062
|
}
|
|
36803
37063
|
|
|
37064
|
+
// ../core/dist/core/probe-repo-file.js
|
|
37065
|
+
var MODULE6 = "probe-repo-file";
|
|
37066
|
+
async function probeRepoFile(octokit, owner, repo, path5) {
|
|
37067
|
+
try {
|
|
37068
|
+
const data = await cachedRequest(getHttpCache(), `/repos/${owner}/${repo}/contents/${path5}`, (headers) => octokit.repos.getContent({ owner, repo, path: path5, headers }));
|
|
37069
|
+
if (data && typeof data === "object" && "content" in data && typeof data.content === "string") {
|
|
37070
|
+
return {
|
|
37071
|
+
text: Buffer.from(data.content, "base64").toString("utf-8"),
|
|
37072
|
+
transient: false
|
|
37073
|
+
};
|
|
37074
|
+
}
|
|
37075
|
+
return { text: null, transient: false };
|
|
37076
|
+
} catch (error48) {
|
|
37077
|
+
const status = getHttpStatusCode(error48);
|
|
37078
|
+
if (status === 404)
|
|
37079
|
+
return { text: null, transient: false };
|
|
37080
|
+
rethrowIfFatal(error48);
|
|
37081
|
+
warn(MODULE6, `Unexpected error fetching ${path5} from ${owner}/${repo}: ${errorMessage(error48)}`);
|
|
37082
|
+
return { text: null, transient: true };
|
|
37083
|
+
}
|
|
37084
|
+
}
|
|
37085
|
+
|
|
36804
37086
|
// ../core/dist/core/repo-health.js
|
|
36805
|
-
var
|
|
37087
|
+
var MODULE7 = "repo-health";
|
|
37088
|
+
var MERGE_WINDOW_DAYS = 90;
|
|
37089
|
+
async function fetchRepoMergeStats(octokit, owner, repo, tracker) {
|
|
37090
|
+
const since = new Date(Date.now() - MERGE_WINDOW_DAYS * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
|
|
37091
|
+
const count = async (qualifiers) => {
|
|
37092
|
+
await tracker.waitForBudget();
|
|
37093
|
+
try {
|
|
37094
|
+
const { data } = await octokit.search.issuesAndPullRequests({
|
|
37095
|
+
q: `repo:${owner}/${repo} ${qualifiers} closed:>=${since}`,
|
|
37096
|
+
per_page: 1
|
|
37097
|
+
// only total_count is needed
|
|
37098
|
+
});
|
|
37099
|
+
return data.total_count;
|
|
37100
|
+
} finally {
|
|
37101
|
+
tracker.recordCall();
|
|
37102
|
+
}
|
|
37103
|
+
};
|
|
37104
|
+
const merged = await count("is:pr is:merged");
|
|
37105
|
+
const closedUnmerged = await count("is:pr is:unmerged is:closed");
|
|
37106
|
+
const denom = merged + closedUnmerged;
|
|
37107
|
+
return {
|
|
37108
|
+
recentMergedPRCount: merged,
|
|
37109
|
+
recentMergeRate: denom > 0 ? merged / denom : null
|
|
37110
|
+
};
|
|
37111
|
+
}
|
|
36806
37112
|
var guidelinesCache = /* @__PURE__ */ new Map();
|
|
36807
37113
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
36808
37114
|
var HEALTH_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
@@ -36822,22 +37128,25 @@ function pruneCache() {
|
|
|
36822
37128
|
}
|
|
36823
37129
|
}
|
|
36824
37130
|
}
|
|
36825
|
-
async function checkProjectHealth(octokit, owner, repo) {
|
|
37131
|
+
async function checkProjectHealth(octokit, owner, repo, tracker = getSearchBudgetTracker()) {
|
|
36826
37132
|
const cache = getHttpCache();
|
|
36827
|
-
const healthCacheKey = `health:${owner}/${repo}
|
|
37133
|
+
const healthCacheKey = versionedCacheKey(`health:${owner}/${repo}`);
|
|
36828
37134
|
try {
|
|
36829
37135
|
return await cachedTimeBased(cache, healthCacheKey, HEALTH_CACHE_TTL_MS, async () => {
|
|
36830
37136
|
const url2 = `/repos/${owner}/${repo}`;
|
|
36831
37137
|
const repoData = await cachedRequest(cache, url2, (headers) => octokit.repos.get({ owner, repo, headers }));
|
|
36832
|
-
const
|
|
37138
|
+
const commitsUrl = `/repos/${owner}/${repo}/commits?per_page=1`;
|
|
37139
|
+
const commits = await cachedRequest(cache, commitsUrl, (headers) => octokit.repos.listCommits({
|
|
36833
37140
|
owner,
|
|
36834
37141
|
repo,
|
|
36835
|
-
per_page: 1
|
|
36836
|
-
|
|
37142
|
+
per_page: 1,
|
|
37143
|
+
headers
|
|
37144
|
+
}));
|
|
36837
37145
|
const lastCommit = commits[0];
|
|
36838
37146
|
const lastCommitAt = lastCommit?.commit?.author?.date || repoData.pushed_at;
|
|
36839
37147
|
const daysSinceLastCommit = daysBetween(new Date(lastCommitAt));
|
|
36840
37148
|
const ciStatus = "unknown";
|
|
37149
|
+
const mergeStats = await fetchRepoMergeStats(octokit, owner, repo, tracker);
|
|
36841
37150
|
return {
|
|
36842
37151
|
repo: `${owner}/${repo}`,
|
|
36843
37152
|
lastCommitAt,
|
|
@@ -36849,13 +37158,15 @@ async function checkProjectHealth(octokit, owner, repo) {
|
|
|
36849
37158
|
isActive: daysSinceLastCommit < 30,
|
|
36850
37159
|
stargazersCount: repoData.stargazers_count,
|
|
36851
37160
|
forksCount: repoData.forks_count,
|
|
36852
|
-
language: repoData.language
|
|
37161
|
+
language: repoData.language,
|
|
37162
|
+
recentMergedPRCount: mergeStats.recentMergedPRCount,
|
|
37163
|
+
recentMergeRate: mergeStats.recentMergeRate
|
|
36853
37164
|
};
|
|
36854
37165
|
});
|
|
36855
37166
|
} catch (error48) {
|
|
36856
37167
|
rethrowIfFatal(error48);
|
|
36857
37168
|
const errMsg = errorMessage(error48);
|
|
36858
|
-
warn(
|
|
37169
|
+
warn(MODULE7, `Error checking project health for ${owner}/${repo}: ${errMsg}`);
|
|
36859
37170
|
return {
|
|
36860
37171
|
repo: `${owner}/${repo}`,
|
|
36861
37172
|
checkFailed: true,
|
|
@@ -36889,12 +37200,7 @@ async function fetchContributionGuidelinesUncached(octokit, owner, repo) {
|
|
|
36889
37200
|
"docs/CONTRIBUTING.md",
|
|
36890
37201
|
"contributing.md"
|
|
36891
37202
|
];
|
|
36892
|
-
const results = await Promise.allSettled(filesToCheck.map((file2) => octokit
|
|
36893
|
-
if ("content" in data) {
|
|
36894
|
-
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
36895
|
-
}
|
|
36896
|
-
return null;
|
|
36897
|
-
})));
|
|
37203
|
+
const results = await Promise.allSettled(filesToCheck.map((file2) => probeRepoFile(octokit, owner, repo, file2)));
|
|
36898
37204
|
for (const result of results) {
|
|
36899
37205
|
if (result.status !== "rejected")
|
|
36900
37206
|
continue;
|
|
@@ -36902,20 +37208,13 @@ async function fetchContributionGuidelinesUncached(octokit, owner, repo) {
|
|
|
36902
37208
|
throw result.reason;
|
|
36903
37209
|
}
|
|
36904
37210
|
}
|
|
36905
|
-
for (
|
|
36906
|
-
|
|
36907
|
-
|
|
36908
|
-
const guidelines = parseContributionGuidelines(result.value);
|
|
37211
|
+
for (const result of results) {
|
|
37212
|
+
if (result.status === "fulfilled" && result.value.text) {
|
|
37213
|
+
const guidelines = parseContributionGuidelines(result.value.text);
|
|
36909
37214
|
guidelinesCache.set(cacheKey, { guidelines, fetchedAt: Date.now() });
|
|
36910
37215
|
pruneCache();
|
|
36911
37216
|
return guidelines;
|
|
36912
37217
|
}
|
|
36913
|
-
if (result.status === "rejected") {
|
|
36914
|
-
const status = getHttpStatusCode(result.reason);
|
|
36915
|
-
if (status !== 404) {
|
|
36916
|
-
warn(MODULE6, `Unexpected error fetching ${filesToCheck[i]} from ${owner}/${repo}: ${errorMessage(result.reason)}`);
|
|
36917
|
-
}
|
|
36918
|
-
}
|
|
36919
37218
|
}
|
|
36920
37219
|
guidelinesCache.set(cacheKey, {
|
|
36921
37220
|
guidelines: void 0,
|
|
@@ -36964,7 +37263,6 @@ function parseContributionGuidelines(content) {
|
|
|
36964
37263
|
}
|
|
36965
37264
|
|
|
36966
37265
|
// ../core/dist/core/anti-llm-policy.js
|
|
36967
|
-
var MODULE7 = "anti-llm-policy";
|
|
36968
37266
|
var POLICY_SCAN_CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
36969
37267
|
var ANTI_LLM_KEYWORDS = [
|
|
36970
37268
|
"no ai-generated",
|
|
@@ -37027,27 +37325,8 @@ var SOURCE_FILE_FAMILIES = [
|
|
|
37027
37325
|
paths: ["README.md", "readme.md", "Readme.md"]
|
|
37028
37326
|
}
|
|
37029
37327
|
];
|
|
37030
|
-
async function fetchFileText(octokit, owner, repo, path5) {
|
|
37031
|
-
try {
|
|
37032
|
-
const { data } = await octokit.repos.getContent({ owner, repo, path: path5 });
|
|
37033
|
-
if ("content" in data && typeof data.content === "string") {
|
|
37034
|
-
return {
|
|
37035
|
-
text: Buffer.from(data.content, "base64").toString("utf-8"),
|
|
37036
|
-
transient: false
|
|
37037
|
-
};
|
|
37038
|
-
}
|
|
37039
|
-
return { text: null, transient: false };
|
|
37040
|
-
} catch (error48) {
|
|
37041
|
-
const status = getHttpStatusCode(error48);
|
|
37042
|
-
if (status === 404)
|
|
37043
|
-
return { text: null, transient: false };
|
|
37044
|
-
rethrowIfFatal(error48);
|
|
37045
|
-
warn(MODULE7, `Unexpected error fetching ${path5} from ${owner}/${repo}: ${errorMessage(error48)}`);
|
|
37046
|
-
return { text: null, transient: true };
|
|
37047
|
-
}
|
|
37048
|
-
}
|
|
37049
37328
|
async function fetchFamilyText(octokit, owner, repo, paths) {
|
|
37050
|
-
const results = await Promise.allSettled(paths.map((p) =>
|
|
37329
|
+
const results = await Promise.allSettled(paths.map((p) => probeRepoFile(octokit, owner, repo, p)));
|
|
37051
37330
|
let hadTransientFailure = false;
|
|
37052
37331
|
for (const result of results) {
|
|
37053
37332
|
if (result.status === "fulfilled") {
|
|
@@ -37186,8 +37465,133 @@ ${selections.join("\n")}
|
|
|
37186
37465
|
});
|
|
37187
37466
|
return result;
|
|
37188
37467
|
}
|
|
37468
|
+
var GH_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
37469
|
+
var MERGED_PR_BATCH_SIZE = 15;
|
|
37470
|
+
async function prefetchMergedPRCounts(octokit, username, repos) {
|
|
37471
|
+
if (repos.length === 0)
|
|
37472
|
+
return;
|
|
37473
|
+
if (!GH_NAME_PATTERN.test(username)) {
|
|
37474
|
+
warn(MODULE8, `Skipping merged-PR prefetch: username failed validation (falling back to REST)`);
|
|
37475
|
+
return;
|
|
37476
|
+
}
|
|
37477
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37478
|
+
const valid = [];
|
|
37479
|
+
for (const r of repos) {
|
|
37480
|
+
const full = `${r.owner}/${r.repo}`;
|
|
37481
|
+
if (seen.has(full))
|
|
37482
|
+
continue;
|
|
37483
|
+
seen.add(full);
|
|
37484
|
+
if (!GH_NAME_PATTERN.test(r.owner) || !GH_NAME_PATTERN.test(r.repo)) {
|
|
37485
|
+
warn(MODULE8, `Skipping merged-PR prefetch for "${full}": failed validation (falling back to REST)`);
|
|
37486
|
+
continue;
|
|
37487
|
+
}
|
|
37488
|
+
valid.push(r);
|
|
37489
|
+
}
|
|
37490
|
+
if (valid.length === 0)
|
|
37491
|
+
return;
|
|
37492
|
+
const cache = getHttpCache();
|
|
37493
|
+
const batches = [];
|
|
37494
|
+
for (let i = 0; i < valid.length; i += MERGED_PR_BATCH_SIZE) {
|
|
37495
|
+
batches.push(fetchMergedPRBatch(octokit, username, valid.slice(i, i + MERGED_PR_BATCH_SIZE), cache));
|
|
37496
|
+
}
|
|
37497
|
+
await Promise.all(batches);
|
|
37498
|
+
}
|
|
37499
|
+
async function fetchMergedPRBatch(octokit, username, repos, cache) {
|
|
37500
|
+
const varDefs = [];
|
|
37501
|
+
const selections = ["viewer { login }"];
|
|
37502
|
+
const variables = {};
|
|
37503
|
+
repos.forEach((r, i) => {
|
|
37504
|
+
varDefs.push(`$q${i}: String!`);
|
|
37505
|
+
variables[`q${i}`] = `is:pr is:merged author:${username} repo:${r.owner}/${r.repo}`;
|
|
37506
|
+
selections.push(`r${i}: search(query: $q${i}, type: ISSUE, first: 1) { issueCount }`);
|
|
37507
|
+
});
|
|
37508
|
+
const query = `query batchMergedPRCounts(${varDefs.join(", ")}) {
|
|
37509
|
+
${selections.join("\n")}
|
|
37510
|
+
}`;
|
|
37511
|
+
let data;
|
|
37512
|
+
try {
|
|
37513
|
+
data = await octokit.graphql(query, variables);
|
|
37514
|
+
} catch (err) {
|
|
37515
|
+
rethrowIfFatal(err);
|
|
37516
|
+
const partial2 = err.data;
|
|
37517
|
+
if (partial2) {
|
|
37518
|
+
data = partial2;
|
|
37519
|
+
} else {
|
|
37520
|
+
warn(MODULE8, `Merged-PR prefetch batch failed, falling back to REST: ${errorMessage(err)}`);
|
|
37521
|
+
return;
|
|
37522
|
+
}
|
|
37523
|
+
}
|
|
37524
|
+
const viewerLogin = data?.viewer?.login;
|
|
37525
|
+
if (!viewerLogin || viewerLogin.toLowerCase() !== username.toLowerCase()) {
|
|
37526
|
+
if (viewerLogin) {
|
|
37527
|
+
warn(MODULE8, `Merged-PR prefetch identity mismatch (configured username != authenticated login); leaving cache cold so REST @me answers`);
|
|
37528
|
+
}
|
|
37529
|
+
return;
|
|
37530
|
+
}
|
|
37531
|
+
repos.forEach((r, i) => {
|
|
37532
|
+
const alias = data?.[`r${i}`];
|
|
37533
|
+
const count = alias && "issueCount" in alias ? alias.issueCount : void 0;
|
|
37534
|
+
if (typeof count !== "number")
|
|
37535
|
+
return;
|
|
37536
|
+
cache.set(mergedPRsCacheKey(r.owner, r.repo), "", count);
|
|
37537
|
+
});
|
|
37538
|
+
}
|
|
37539
|
+
async function graphqlSearchIssues(octokit, query, first) {
|
|
37540
|
+
const clampedFirst = Math.min(Math.max(Math.trunc(first) || 1, 1), 100);
|
|
37541
|
+
const document = `query searchIssuesBroad($q: String!, $first: Int!) {
|
|
37542
|
+
search(query: $q, type: ISSUE, first: $first) {
|
|
37543
|
+
issueCount
|
|
37544
|
+
nodes {
|
|
37545
|
+
... on Issue {
|
|
37546
|
+
url
|
|
37547
|
+
title
|
|
37548
|
+
updatedAt
|
|
37549
|
+
labels(first: 20) { nodes { name } }
|
|
37550
|
+
repository { nameWithOwner }
|
|
37551
|
+
}
|
|
37552
|
+
}
|
|
37553
|
+
}
|
|
37554
|
+
}`;
|
|
37555
|
+
let data;
|
|
37556
|
+
try {
|
|
37557
|
+
data = await octokit.graphql(document, {
|
|
37558
|
+
q: query,
|
|
37559
|
+
first: clampedFirst
|
|
37560
|
+
});
|
|
37561
|
+
} catch (err) {
|
|
37562
|
+
rethrowIfFatal(err);
|
|
37563
|
+
const partial2 = err.data;
|
|
37564
|
+
if (partial2) {
|
|
37565
|
+
data = partial2;
|
|
37566
|
+
} else {
|
|
37567
|
+
warn(MODULE8, `GraphQL broad search failed, falling back to REST: ${errorMessage(err)}`);
|
|
37568
|
+
return null;
|
|
37569
|
+
}
|
|
37570
|
+
}
|
|
37571
|
+
const search = data?.search;
|
|
37572
|
+
if (!search)
|
|
37573
|
+
return null;
|
|
37574
|
+
const items = [];
|
|
37575
|
+
for (const node of search.nodes ?? []) {
|
|
37576
|
+
if (!node?.url)
|
|
37577
|
+
continue;
|
|
37578
|
+
const nameWithOwner = node.repository?.nameWithOwner;
|
|
37579
|
+
if (!nameWithOwner)
|
|
37580
|
+
continue;
|
|
37581
|
+
items.push({
|
|
37582
|
+
html_url: node.url,
|
|
37583
|
+
// Same construction as the REST search adapter (search-phases.ts).
|
|
37584
|
+
repository_url: `https://api.github.com/repos/${nameWithOwner}`,
|
|
37585
|
+
updated_at: node.updatedAt ?? "",
|
|
37586
|
+
title: node.title,
|
|
37587
|
+
labels: node.labels?.nodes ?? []
|
|
37588
|
+
});
|
|
37589
|
+
}
|
|
37590
|
+
return { total_count: search.issueCount ?? 0, items };
|
|
37591
|
+
}
|
|
37189
37592
|
|
|
37190
37593
|
// ../core/dist/core/slm-triage.js
|
|
37594
|
+
var MODULE9 = "slm-triage";
|
|
37191
37595
|
var DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434";
|
|
37192
37596
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
37193
37597
|
var MAX_BODY_CHARS = 2e3;
|
|
@@ -37224,10 +37628,48 @@ function buildPrompt(input) {
|
|
|
37224
37628
|
`Linked PR exists: ${linkedPRExists}`
|
|
37225
37629
|
].join("\n");
|
|
37226
37630
|
}
|
|
37631
|
+
function isAllowedTriageHost(raw) {
|
|
37632
|
+
let url2;
|
|
37633
|
+
try {
|
|
37634
|
+
url2 = new URL(raw);
|
|
37635
|
+
} catch {
|
|
37636
|
+
return false;
|
|
37637
|
+
}
|
|
37638
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:")
|
|
37639
|
+
return false;
|
|
37640
|
+
const host = url2.hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
37641
|
+
if (host === "localhost" || host === "::1")
|
|
37642
|
+
return true;
|
|
37643
|
+
const v4 = host.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
37644
|
+
if (v4) {
|
|
37645
|
+
const a = Number(v4[1]);
|
|
37646
|
+
const b = Number(v4[2]);
|
|
37647
|
+
if (a === 127 || a === 10)
|
|
37648
|
+
return true;
|
|
37649
|
+
if (a === 192 && b === 168)
|
|
37650
|
+
return true;
|
|
37651
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
37652
|
+
return true;
|
|
37653
|
+
return false;
|
|
37654
|
+
}
|
|
37655
|
+
if (host.includes(":")) {
|
|
37656
|
+
return host.startsWith("fc") || host.startsWith("fd") || host.startsWith("fe80");
|
|
37657
|
+
}
|
|
37658
|
+
return !host.includes(".") || host.endsWith(".local");
|
|
37659
|
+
}
|
|
37660
|
+
function assertValidTriageHost(value) {
|
|
37661
|
+
if (value === "" || isAllowedTriageHost(value))
|
|
37662
|
+
return;
|
|
37663
|
+
throw new ValidationError(`slmTriageHost must be an http(s) URL pointing at localhost or a private/LAN address (got "${value}"). Example: http://192.168.1.20:11434`);
|
|
37664
|
+
}
|
|
37227
37665
|
async function triageWithSLM(input, options) {
|
|
37228
37666
|
if (!options.model)
|
|
37229
37667
|
return null;
|
|
37230
37668
|
const host = options.host ?? DEFAULT_OLLAMA_HOST;
|
|
37669
|
+
if (!isAllowedTriageHost(host)) {
|
|
37670
|
+
warn(MODULE9, `slmTriageHost "${host}" is not a local/private address; skipping SLM triage`);
|
|
37671
|
+
return null;
|
|
37672
|
+
}
|
|
37231
37673
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
37232
37674
|
const fetchFn = options.fetchImpl ?? fetch;
|
|
37233
37675
|
let response;
|
|
@@ -37299,15 +37741,23 @@ function isValidTriageShape(value) {
|
|
|
37299
37741
|
}
|
|
37300
37742
|
|
|
37301
37743
|
// ../core/dist/core/issue-vetting.js
|
|
37302
|
-
var
|
|
37744
|
+
var MODULE10 = "issue-vetting";
|
|
37303
37745
|
var MAX_CONCURRENT_VETTING = 3;
|
|
37304
37746
|
var VETTING_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
37747
|
+
function repoAcceptsContributionsFromHealth(projectHealth) {
|
|
37748
|
+
if (projectHealth.checkFailed || projectHealth.recentMergedPRCount == null) {
|
|
37749
|
+
return null;
|
|
37750
|
+
}
|
|
37751
|
+
if (projectHealth.recentMergedPRCount > 0)
|
|
37752
|
+
return true;
|
|
37753
|
+
return projectHealth.recentMergeRate == null ? null : false;
|
|
37754
|
+
}
|
|
37305
37755
|
function deriveRecommendation(input) {
|
|
37306
37756
|
const notes = [];
|
|
37307
37757
|
const reasonsToApprove = [];
|
|
37308
37758
|
const reasonsToSkip = [];
|
|
37309
37759
|
if (!input.noExistingPR)
|
|
37310
|
-
notes.push(input.ownPR ? "Your PR is already in flight for this issue" : "Existing PR found for this issue");
|
|
37760
|
+
notes.push(input.ownPR ? "Your PR is already in flight for this issue" : input.linkedPRMerged ? "A PR for this issue was already merged" : input.linkedPRClosed ? "A PR for this issue was closed without merging" : "Existing PR found for this issue");
|
|
37311
37761
|
if (!input.notClaimed)
|
|
37312
37762
|
notes.push("Issue appears to be claimed by someone");
|
|
37313
37763
|
if (input.existingPRInconclusive) {
|
|
@@ -37325,14 +37775,28 @@ function deriveRecommendation(input) {
|
|
|
37325
37775
|
notes.push("Issue requirements are unclear");
|
|
37326
37776
|
if (!input.contributionGuidelinesFound)
|
|
37327
37777
|
notes.push("No CONTRIBUTING.md found");
|
|
37328
|
-
if (
|
|
37329
|
-
|
|
37778
|
+
if (input.repoAcceptsContributions === false)
|
|
37779
|
+
notes.push("Repo has merged no PRs in the last 90 days");
|
|
37780
|
+
else if (input.repoAcceptsContributions === null)
|
|
37781
|
+
notes.push("Could not verify whether the repo merges PRs");
|
|
37782
|
+
if (!input.noExistingPR) {
|
|
37783
|
+
if (input.ownPR)
|
|
37784
|
+
reasonsToSkip.push("You already have a PR in flight");
|
|
37785
|
+
else if (input.linkedPRMerged)
|
|
37786
|
+
reasonsToSkip.push("Linked PR already merged");
|
|
37787
|
+
else if (input.linkedPRClosed)
|
|
37788
|
+
reasonsToSkip.push("Linked PR closed without merge");
|
|
37789
|
+
else
|
|
37790
|
+
reasonsToSkip.push("Has existing PR");
|
|
37791
|
+
}
|
|
37330
37792
|
if (!input.notClaimed)
|
|
37331
37793
|
reasonsToSkip.push("Already claimed");
|
|
37332
37794
|
if (!input.projectIsActive && !input.projectCheckFailed)
|
|
37333
37795
|
reasonsToSkip.push("Inactive project");
|
|
37334
37796
|
if (!input.clearRequirements)
|
|
37335
37797
|
reasonsToSkip.push("Unclear requirements");
|
|
37798
|
+
if (input.repoAcceptsContributions === false)
|
|
37799
|
+
reasonsToSkip.push("Repo has no recent merged PRs");
|
|
37336
37800
|
if (input.noExistingPR)
|
|
37337
37801
|
reasonsToApprove.push("No existing PR");
|
|
37338
37802
|
if (input.notClaimed)
|
|
@@ -37358,16 +37822,18 @@ function deriveRecommendation(input) {
|
|
|
37358
37822
|
let recommendation;
|
|
37359
37823
|
if (input.issueClosed) {
|
|
37360
37824
|
recommendation = "skip";
|
|
37825
|
+
} else if (input.linkedPRMerged || input.linkedPRClosed) {
|
|
37826
|
+
recommendation = "skip";
|
|
37361
37827
|
} else if (input.ownPR) {
|
|
37362
37828
|
recommendation = "skip";
|
|
37363
|
-
} else if (input.passedAllChecks) {
|
|
37829
|
+
} else if (input.passedAllChecks && input.repoAcceptsContributions !== false) {
|
|
37364
37830
|
recommendation = "approve";
|
|
37365
37831
|
} else if (reasonsToSkip.length > 2) {
|
|
37366
37832
|
recommendation = "skip";
|
|
37367
37833
|
} else {
|
|
37368
37834
|
recommendation = "needs_review";
|
|
37369
37835
|
}
|
|
37370
|
-
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive;
|
|
37836
|
+
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive || input.repoAcceptsContributions === null;
|
|
37371
37837
|
if (recommendation === "approve" && hasInconclusiveChecks) {
|
|
37372
37838
|
recommendation = "needs_review";
|
|
37373
37839
|
notes.push("Recommendation downgraded: one or more checks were inconclusive");
|
|
@@ -37377,9 +37843,18 @@ function deriveRecommendation(input) {
|
|
|
37377
37843
|
var IssueVetter = class {
|
|
37378
37844
|
octokit;
|
|
37379
37845
|
stateReader;
|
|
37380
|
-
|
|
37846
|
+
budgetTracker;
|
|
37847
|
+
/**
|
|
37848
|
+
* @param octokit - Authenticated Octokit instance
|
|
37849
|
+
* @param stateReader - Read-only scout state interface
|
|
37850
|
+
* @param budgetTracker - Search budget tracker. Defaults to the shared
|
|
37851
|
+
* singleton so existing callers behave identically; inject a per-search
|
|
37852
|
+
* instance to isolate budget accounting in a long-lived concurrent host.
|
|
37853
|
+
*/
|
|
37854
|
+
constructor(octokit, stateReader, budgetTracker = getSearchBudgetTracker()) {
|
|
37381
37855
|
this.octokit = octokit;
|
|
37382
37856
|
this.stateReader = stateReader;
|
|
37857
|
+
this.budgetTracker = budgetTracker;
|
|
37383
37858
|
}
|
|
37384
37859
|
/**
|
|
37385
37860
|
* Vet a specific issue — runs all checks and computes recommendation + viability score.
|
|
@@ -37396,7 +37871,7 @@ var IssueVetter = class {
|
|
|
37396
37871
|
const cacheKey = versionedCacheKey(`vet:${issueUrl}${sigKey}`);
|
|
37397
37872
|
const cached2 = cache.getIfFresh(cacheKey, VETTING_CACHE_TTL_MS);
|
|
37398
37873
|
if (cached2 && typeof cached2 === "object" && "issue" in cached2 && "viabilityScore" in cached2) {
|
|
37399
|
-
debug(
|
|
37874
|
+
debug(MODULE10, `Vetting cache hit for ${issueUrl}`);
|
|
37400
37875
|
return cached2;
|
|
37401
37876
|
}
|
|
37402
37877
|
const parsed = parseGitHubUrl(issueUrl);
|
|
@@ -37411,9 +37886,9 @@ var IssueVetter = class {
|
|
|
37411
37886
|
const [existingPRCheck, claimCheck, projectHealth, contributionGuidelines, userMergedPRCount] = await Promise.all([
|
|
37412
37887
|
checkNoExistingPR(this.octokit, owner, repo, number4),
|
|
37413
37888
|
checkNotClaimed(this.octokit, owner, repo, number4, core.commentCount),
|
|
37414
|
-
checkProjectHealth(this.octokit, owner, repo),
|
|
37889
|
+
checkProjectHealth(this.octokit, owner, repo, this.budgetTracker),
|
|
37415
37890
|
fetchContributionGuidelines(this.octokit, owner, repo),
|
|
37416
|
-
hasMergedPRsInRepo ? Promise.resolve(0) : checkUserMergedPRsInRepo(this.octokit, owner, repo)
|
|
37891
|
+
hasMergedPRsInRepo ? Promise.resolve(0) : checkUserMergedPRsInRepo(this.octokit, owner, repo, this.budgetTracker)
|
|
37417
37892
|
]);
|
|
37418
37893
|
const antiLLMPolicy = await fetchAndScanAntiLLMPolicy(this.octokit, owner, repo, { contributingText: contributionGuidelines?.rawContent });
|
|
37419
37894
|
const noExistingPR = existingPRCheck.passed;
|
|
@@ -37421,8 +37896,11 @@ var IssueVetter = class {
|
|
|
37421
37896
|
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
37422
37897
|
const linkedPR = existingPRCheck.linkedPR;
|
|
37423
37898
|
const ownPR = !noExistingPR && !!linkedPR && linkedPR.state === "open" && !linkedPR.merged && username !== "" && linkedPR.author.toLowerCase() === username.toLowerCase();
|
|
37899
|
+
const linkedPRMerged = !!linkedPR && linkedPR.merged;
|
|
37900
|
+
const linkedPRClosed = !!linkedPR && linkedPR.state === "closed" && !linkedPR.merged;
|
|
37424
37901
|
const clearRequirements = analyzeRequirements(core.body);
|
|
37425
37902
|
const projectActive = projectHealth.checkFailed ? true : projectHealth.isActive;
|
|
37903
|
+
const repoAcceptsContributions = repoAcceptsContributionsFromHealth(projectHealth);
|
|
37426
37904
|
const vettingResult = {
|
|
37427
37905
|
passedAllChecks: noExistingPR && notClaimed && projectActive && clearRequirements,
|
|
37428
37906
|
checks: {
|
|
@@ -37458,10 +37936,12 @@ var IssueVetter = class {
|
|
|
37458
37936
|
}
|
|
37459
37937
|
const matchesCategory = repoBelongsToCategory(repoFullName, this.stateReader.getProjectCategories());
|
|
37460
37938
|
const issueClosed = core.state === "closed";
|
|
37461
|
-
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive;
|
|
37939
|
+
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive || repoAcceptsContributions === null;
|
|
37462
37940
|
const { notes, reasonsToApprove, reasonsToSkip, recommendation } = deriveRecommendation({
|
|
37463
37941
|
noExistingPR,
|
|
37464
37942
|
ownPR,
|
|
37943
|
+
linkedPRMerged,
|
|
37944
|
+
linkedPRClosed,
|
|
37465
37945
|
notClaimed,
|
|
37466
37946
|
clearRequirements,
|
|
37467
37947
|
contributionGuidelinesFound: !!contributionGuidelines,
|
|
@@ -37478,7 +37958,8 @@ var IssueVetter = class {
|
|
|
37478
37958
|
orgHasMergedPRs,
|
|
37479
37959
|
matchesCategory,
|
|
37480
37960
|
issueClosed,
|
|
37481
|
-
passedAllChecks: vettingResult.passedAllChecks
|
|
37961
|
+
passedAllChecks: vettingResult.passedAllChecks,
|
|
37962
|
+
repoAcceptsContributions
|
|
37482
37963
|
});
|
|
37483
37964
|
vettingResult.notes = notes;
|
|
37484
37965
|
const repoQualityBonus = projectHealth.checkFailed ? 0 : calculateRepoQualityBonus(projectHealth.stargazersCount ?? 0, projectHealth.forksCount ?? 0);
|
|
@@ -37558,6 +38039,36 @@ var IssueVetter = class {
|
|
|
37558
38039
|
updatedAt: ghIssue.updated_at
|
|
37559
38040
|
};
|
|
37560
38041
|
}
|
|
38042
|
+
/**
|
|
38043
|
+
* Batch-prefetch merged-PR counts for the repos in a vetting batch (#182),
|
|
38044
|
+
* warming the cache `checkUserMergedPRsInRepo` reads so each per-repo Search
|
|
38045
|
+
* API call becomes a cache hit.
|
|
38046
|
+
*
|
|
38047
|
+
* Two repos are intentionally excluded:
|
|
38048
|
+
* - Repos already known to have the user's merged PRs
|
|
38049
|
+
* (`getReposWithMergedPRs`): `vetIssue` short-circuits those to a merged
|
|
38050
|
+
* count of 1 and never calls the Search API, so prefetching them is waste.
|
|
38051
|
+
* - Everything, when the GitHub username is unknown: the GraphQL `author:`
|
|
38052
|
+
* filter needs a concrete login and the REST path falls back to `@me`.
|
|
38053
|
+
*/
|
|
38054
|
+
async prefetchMergedPRCounts(issueRefs) {
|
|
38055
|
+
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
38056
|
+
if (!username)
|
|
38057
|
+
return;
|
|
38058
|
+
const alreadyKnown = new Set(this.stateReader.getReposWithMergedPRs());
|
|
38059
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38060
|
+
const repos = [];
|
|
38061
|
+
for (const { owner, repo } of issueRefs) {
|
|
38062
|
+
const full = `${owner}/${repo}`;
|
|
38063
|
+
if (alreadyKnown.has(full) || seen.has(full))
|
|
38064
|
+
continue;
|
|
38065
|
+
seen.add(full);
|
|
38066
|
+
repos.push({ owner, repo });
|
|
38067
|
+
}
|
|
38068
|
+
if (repos.length === 0)
|
|
38069
|
+
return;
|
|
38070
|
+
await prefetchMergedPRCounts(this.octokit, username, repos);
|
|
38071
|
+
}
|
|
37561
38072
|
/**
|
|
37562
38073
|
* Vet multiple issues in parallel with concurrency limit
|
|
37563
38074
|
*/
|
|
@@ -37569,10 +38080,14 @@ var IssueVetter = class {
|
|
|
37569
38080
|
let attemptedCount = 0;
|
|
37570
38081
|
let firstAuthError = null;
|
|
37571
38082
|
const uniqueUrls = [...new Set(urls)];
|
|
37572
|
-
const
|
|
38083
|
+
const issueRefs = uniqueUrls.flatMap((url2) => {
|
|
37573
38084
|
const p = parseGitHubUrl(url2);
|
|
37574
38085
|
return p && p.type === "issues" ? [{ owner: p.owner, repo: p.repo, number: p.number }] : [];
|
|
37575
|
-
})
|
|
38086
|
+
});
|
|
38087
|
+
const [prefetched] = await Promise.all([
|
|
38088
|
+
prefetchIssueCores(this.octokit, issueRefs),
|
|
38089
|
+
this.prefetchMergedPRCounts(issueRefs)
|
|
38090
|
+
]);
|
|
37576
38091
|
const prefetchFor = (url2) => {
|
|
37577
38092
|
const p = parseGitHubUrl(url2);
|
|
37578
38093
|
return p && p.type === "issues" ? prefetched.get(issueCoreKey(p.owner, p.repo, p.number)) : void 0;
|
|
@@ -37592,7 +38107,7 @@ var IssueVetter = class {
|
|
|
37592
38107
|
candidates.push(candidate);
|
|
37593
38108
|
}
|
|
37594
38109
|
}).catch((error48) => {
|
|
37595
|
-
if (
|
|
38110
|
+
if (isAuthError(error48)) {
|
|
37596
38111
|
firstAuthError ??= error48;
|
|
37597
38112
|
return;
|
|
37598
38113
|
}
|
|
@@ -37600,7 +38115,7 @@ var IssueVetter = class {
|
|
|
37600
38115
|
if (isRateLimitError(error48)) {
|
|
37601
38116
|
rateLimitFailures++;
|
|
37602
38117
|
}
|
|
37603
|
-
warn(
|
|
38118
|
+
warn(MODULE10, `Error vetting issue ${url2}:`, errorMessage(error48));
|
|
37604
38119
|
}).finally(() => pending.delete(url2));
|
|
37605
38120
|
pending.set(url2, task);
|
|
37606
38121
|
if (pending.size >= MAX_CONCURRENT_VETTING) {
|
|
@@ -37610,13 +38125,13 @@ var IssueVetter = class {
|
|
|
37610
38125
|
await Promise.allSettled(pending.values());
|
|
37611
38126
|
if (firstAuthError) {
|
|
37612
38127
|
if (candidates.length > 0) {
|
|
37613
|
-
warn(
|
|
38128
|
+
warn(MODULE10, `Auth failed mid-batch after ${candidates.length} successful vet(s) \u2014 discarding partial results`);
|
|
37614
38129
|
}
|
|
37615
38130
|
throw firstAuthError;
|
|
37616
38131
|
}
|
|
37617
38132
|
const allFailed = failedVettingCount === attemptedCount && attemptedCount > 0;
|
|
37618
38133
|
if (allFailed) {
|
|
37619
|
-
warn(
|
|
38134
|
+
warn(MODULE10, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37620
38135
|
}
|
|
37621
38136
|
return {
|
|
37622
38137
|
candidates: candidates.slice(0, maxResults),
|
|
@@ -37627,14 +38142,22 @@ var IssueVetter = class {
|
|
|
37627
38142
|
};
|
|
37628
38143
|
|
|
37629
38144
|
// ../core/dist/core/search-phases.js
|
|
37630
|
-
var
|
|
38145
|
+
var MODULE11 = "search-phases";
|
|
37631
38146
|
var GITHUB_MAX_BOOLEAN_OPS = 5;
|
|
37632
38147
|
var INTER_QUERY_DELAY_MS = 2e3;
|
|
38148
|
+
var GRAPHQL_INTER_QUERY_DELAY_MS = 500;
|
|
38149
|
+
var graphqlSearchQueryCount = 0;
|
|
38150
|
+
function getGraphQLSearchQueryCount() {
|
|
38151
|
+
return graphqlSearchQueryCount;
|
|
38152
|
+
}
|
|
38153
|
+
function resetGraphQLSearchQueryCount() {
|
|
38154
|
+
graphqlSearchQueryCount = 0;
|
|
38155
|
+
}
|
|
37633
38156
|
function chunkLabels(labels, reservedOps = 0) {
|
|
37634
38157
|
const maxPerChunk = GITHUB_MAX_BOOLEAN_OPS - reservedOps + 1;
|
|
37635
38158
|
if (maxPerChunk < 1) {
|
|
37636
38159
|
if (labels.length > 0) {
|
|
37637
|
-
warn(
|
|
38160
|
+
warn(MODULE11, `Label filtering disabled: ${reservedOps} repo/org ORs exceed GitHub's ${GITHUB_MAX_BOOLEAN_OPS} operator limit. All ${labels.length} label(s) dropped from query.`);
|
|
37638
38161
|
}
|
|
37639
38162
|
return [[]];
|
|
37640
38163
|
}
|
|
@@ -37644,7 +38167,7 @@ function chunkLabels(labels, reservedOps = 0) {
|
|
|
37644
38167
|
for (let i = 0; i < labels.length; i += maxPerChunk) {
|
|
37645
38168
|
chunks.push(labels.slice(i, i + maxPerChunk));
|
|
37646
38169
|
}
|
|
37647
|
-
debug(
|
|
38170
|
+
debug(MODULE11, `Split ${labels.length} labels into ${chunks.length} chunks (${reservedOps} ops reserved, max ${maxPerChunk} per chunk)`);
|
|
37648
38171
|
return chunks;
|
|
37649
38172
|
}
|
|
37650
38173
|
function buildLabelQuery(labels) {
|
|
@@ -37676,15 +38199,14 @@ function interleaveArrays(arrays) {
|
|
|
37676
38199
|
return result;
|
|
37677
38200
|
}
|
|
37678
38201
|
var SEARCH_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
37679
|
-
async function cachedSearchIssues(octokit, params) {
|
|
38202
|
+
async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTracker()) {
|
|
37680
38203
|
const cacheKey = versionedCacheKey(`search:${params.q}:${params.sort}:${params.order}:${params.per_page}`);
|
|
37681
38204
|
const cache = getHttpCache();
|
|
37682
38205
|
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
37683
38206
|
if (cached2) {
|
|
37684
|
-
debug(
|
|
38207
|
+
debug(MODULE11, `Search cache hit for query`);
|
|
37685
38208
|
return cached2;
|
|
37686
38209
|
}
|
|
37687
|
-
const tracker = getSearchBudgetTracker();
|
|
37688
38210
|
await tracker.waitForBudget();
|
|
37689
38211
|
let data;
|
|
37690
38212
|
try {
|
|
@@ -37696,10 +38218,32 @@ async function cachedSearchIssues(octokit, params) {
|
|
|
37696
38218
|
if (data.items.length > 0) {
|
|
37697
38219
|
cache.set(cacheKey, "", data);
|
|
37698
38220
|
} else {
|
|
37699
|
-
debug(
|
|
38221
|
+
debug(MODULE11, `Skipping cache for empty search result (possible rate limit artifact)`);
|
|
37700
38222
|
}
|
|
37701
38223
|
return data;
|
|
37702
38224
|
}
|
|
38225
|
+
async function searchIssuesGraphQLFirst(octokit, params, tracker = getSearchBudgetTracker()) {
|
|
38226
|
+
const cacheKey = versionedCacheKey(`gqlsearch:${params.q}:${params.sort}:${params.order}:${params.per_page}`);
|
|
38227
|
+
const cache = getHttpCache();
|
|
38228
|
+
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
38229
|
+
if (cached2) {
|
|
38230
|
+
debug(MODULE11, `GraphQL search cache hit for query`);
|
|
38231
|
+
return cached2;
|
|
38232
|
+
}
|
|
38233
|
+
const gqlQuery = `${params.q} sort:${params.sort}-${params.order}`;
|
|
38234
|
+
const gql = await graphqlSearchIssues(octokit, gqlQuery, Math.min(params.per_page, 100));
|
|
38235
|
+
if (gql) {
|
|
38236
|
+
graphqlSearchQueryCount++;
|
|
38237
|
+
if (gql.items.length > 0) {
|
|
38238
|
+
cache.set(cacheKey, "", gql);
|
|
38239
|
+
} else {
|
|
38240
|
+
debug(MODULE11, `Skipping cache for empty GraphQL search result (possible transient artifact)`);
|
|
38241
|
+
}
|
|
38242
|
+
return gql;
|
|
38243
|
+
}
|
|
38244
|
+
warn(MODULE11, `GraphQL broad search unavailable; falling back to REST search API`);
|
|
38245
|
+
return cachedSearchIssues(octokit, params, tracker);
|
|
38246
|
+
}
|
|
37703
38247
|
async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResults) {
|
|
37704
38248
|
const items = [];
|
|
37705
38249
|
for (const repoFullName of repos) {
|
|
@@ -37744,15 +38288,15 @@ async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResul
|
|
|
37744
38288
|
if (getHttpStatusCode(error48) === 401)
|
|
37745
38289
|
throw error48;
|
|
37746
38290
|
if (isRateLimitError(error48)) {
|
|
37747
|
-
warn(
|
|
38291
|
+
warn(MODULE11, `Rate limit hit fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37748
38292
|
break;
|
|
37749
38293
|
}
|
|
37750
|
-
warn(
|
|
38294
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37751
38295
|
}
|
|
37752
38296
|
}
|
|
37753
38297
|
return items;
|
|
37754
38298
|
}
|
|
37755
|
-
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn) {
|
|
38299
|
+
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn, perPage = 5) {
|
|
37756
38300
|
const candidates = [];
|
|
37757
38301
|
let failedRepos = 0;
|
|
37758
38302
|
let rateLimitFailures = 0;
|
|
@@ -37774,7 +38318,7 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37774
38318
|
state: "open",
|
|
37775
38319
|
sort: "created",
|
|
37776
38320
|
direction: "desc",
|
|
37777
|
-
per_page:
|
|
38321
|
+
per_page: perPage,
|
|
37778
38322
|
...label !== void 0 ? { labels: label } : {}
|
|
37779
38323
|
});
|
|
37780
38324
|
for (const issue2 of response.data) {
|
|
@@ -37809,30 +38353,30 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37809
38353
|
if (isRateLimitError(error48)) {
|
|
37810
38354
|
rateLimitFailures++;
|
|
37811
38355
|
}
|
|
37812
|
-
warn(
|
|
38356
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37813
38357
|
}
|
|
37814
38358
|
}
|
|
37815
38359
|
const allReposFailed = failedRepos === repos.length && repos.length > 0;
|
|
37816
38360
|
const rateLimitHit = rateLimitFailures > 0;
|
|
37817
38361
|
if (allReposFailed) {
|
|
37818
|
-
warn(
|
|
38362
|
+
warn(MODULE11, `All ${repos.length} repo(s) failed for ${priority} phase. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37819
38363
|
}
|
|
37820
38364
|
return { candidates, allReposFailed, rateLimitHit };
|
|
37821
38365
|
}
|
|
37822
|
-
async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery, perPage) {
|
|
38366
|
+
async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery, perPage, tracker = getSearchBudgetTracker()) {
|
|
37823
38367
|
const labelChunks = chunkLabels(labels, reservedOps);
|
|
37824
38368
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
37825
38369
|
const allItems = [];
|
|
37826
38370
|
for (let i = 0; i < labelChunks.length; i++) {
|
|
37827
38371
|
if (i > 0)
|
|
37828
|
-
await sleep(
|
|
38372
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
37829
38373
|
const query = buildQuery(buildLabelQuery(labelChunks[i]));
|
|
37830
|
-
const data = await
|
|
38374
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
37831
38375
|
q: query,
|
|
37832
38376
|
sort: "created",
|
|
37833
38377
|
order: "desc",
|
|
37834
38378
|
per_page: perPage
|
|
37835
|
-
});
|
|
38379
|
+
}, tracker);
|
|
37836
38380
|
for (const item of data.items) {
|
|
37837
38381
|
if (!seenUrls.has(item.html_url)) {
|
|
37838
38382
|
seenUrls.add(item.html_url);
|
|
@@ -37851,14 +38395,16 @@ function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
|
|
|
37851
38395
|
return [languages.map((l) => `language:${l}`).join(" ")];
|
|
37852
38396
|
return languages.map((l) => `language:${l}`);
|
|
37853
38397
|
}
|
|
37854
|
-
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage) {
|
|
37855
|
-
const
|
|
38398
|
+
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker(), startOffset = 0) {
|
|
38399
|
+
const variants = buildLanguageVariants(languages, isAnyLanguage, labels.length > 0);
|
|
38400
|
+
const k = variants.length > 0 ? startOffset % variants.length : 0;
|
|
38401
|
+
const langVariants = [...variants.slice(k), ...variants.slice(0, k)];
|
|
37856
38402
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
37857
38403
|
const allItems = [];
|
|
37858
38404
|
for (let i = 0; i < langVariants.length; i++) {
|
|
37859
38405
|
if (i > 0)
|
|
37860
|
-
await sleep(
|
|
37861
|
-
const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage);
|
|
38406
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
38407
|
+
const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage, tracker);
|
|
37862
38408
|
for (const item of items) {
|
|
37863
38409
|
if (!seenUrls.has(item.html_url)) {
|
|
37864
38410
|
seenUrls.add(item.html_url);
|
|
@@ -37872,7 +38418,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37872
38418
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
37873
38419
|
if (spamRepos.size > 0) {
|
|
37874
38420
|
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
37875
|
-
debug(
|
|
38421
|
+
debug(MODULE11, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
37876
38422
|
}
|
|
37877
38423
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
37878
38424
|
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
@@ -37883,7 +38429,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37883
38429
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
37884
38430
|
}).slice(0, remainingNeeded * 2);
|
|
37885
38431
|
if (itemsToVet.length === 0) {
|
|
37886
|
-
debug(
|
|
38432
|
+
debug(MODULE11, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
|
|
37887
38433
|
return { candidates: [], allVetFailed: false, rateLimitHit: false };
|
|
37888
38434
|
}
|
|
37889
38435
|
const { candidates: results, allFailed: allVetFailed, rateLimitHit } = await vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
|
|
@@ -37895,7 +38441,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37895
38441
|
});
|
|
37896
38442
|
const starFilteredCount = results.length - starFiltered.length;
|
|
37897
38443
|
if (starFilteredCount > 0) {
|
|
37898
|
-
debug(
|
|
38444
|
+
debug(MODULE11, `[STAR_FILTER] Filtered ${starFilteredCount} ${phaseLabel} candidates below ${minStars} stars`);
|
|
37899
38445
|
}
|
|
37900
38446
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
37901
38447
|
}
|
|
@@ -37903,18 +38449,27 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37903
38449
|
// ../core/dist/core/personalization.js
|
|
37904
38450
|
var REPO_BOOST = 20;
|
|
37905
38451
|
var LANGUAGE_BOOST = 10;
|
|
38452
|
+
var ISSUE_TYPE_BOOST = 10;
|
|
38453
|
+
var AVOID_PENALTY = 15;
|
|
37906
38454
|
function boostScoreOf(candidate) {
|
|
37907
38455
|
return candidate.personalization?.kind === "boosted" ? candidate.personalization.score : 0;
|
|
37908
38456
|
}
|
|
37909
|
-
function
|
|
37910
|
-
|
|
37911
|
-
|
|
37912
|
-
|
|
38457
|
+
function normalizeSet(values) {
|
|
38458
|
+
return new Set((values ?? []).map((v) => v.trim().toLowerCase()).filter(Boolean));
|
|
38459
|
+
}
|
|
38460
|
+
function annotateBoost(candidates, bias = {}) {
|
|
38461
|
+
const langSet = normalizeSet(bias.preferLanguages);
|
|
38462
|
+
const repoSet = normalizeSet(bias.preferRepos);
|
|
38463
|
+
const avoidSet = normalizeSet(bias.avoidRepos);
|
|
38464
|
+
const typeSet = normalizeSet(bias.boostIssueTypes);
|
|
38465
|
+
if (langSet.size === 0 && repoSet.size === 0 && avoidSet.size === 0 && typeSet.size === 0) {
|
|
37913
38466
|
return candidates;
|
|
38467
|
+
}
|
|
37914
38468
|
return candidates.map((c) => {
|
|
37915
38469
|
let score = 0;
|
|
37916
38470
|
const reasons = [];
|
|
37917
|
-
|
|
38471
|
+
const repoLower = c.issue.repo.toLowerCase();
|
|
38472
|
+
if (repoSet.size > 0 && repoSet.has(repoLower)) {
|
|
37918
38473
|
score += REPO_BOOST;
|
|
37919
38474
|
reasons.push(`repo affinity: ${c.issue.repo}`);
|
|
37920
38475
|
}
|
|
@@ -37923,7 +38478,18 @@ function annotateBoost(candidates, preferLanguages, preferRepos) {
|
|
|
37923
38478
|
score += LANGUAGE_BOOST;
|
|
37924
38479
|
reasons.push(`language match: ${lang}`);
|
|
37925
38480
|
}
|
|
37926
|
-
if (
|
|
38481
|
+
if (typeSet.size > 0) {
|
|
38482
|
+
const matched = c.issue.labels.find((l) => typeSet.has(l.toLowerCase()));
|
|
38483
|
+
if (matched) {
|
|
38484
|
+
score += ISSUE_TYPE_BOOST;
|
|
38485
|
+
reasons.push(`issue type: ${matched}`);
|
|
38486
|
+
}
|
|
38487
|
+
}
|
|
38488
|
+
if (avoidSet.size > 0 && avoidSet.has(repoLower)) {
|
|
38489
|
+
score -= AVOID_PENALTY;
|
|
38490
|
+
reasons.push(`avoided repo: ${c.issue.repo}`);
|
|
38491
|
+
}
|
|
38492
|
+
if (reasons.length === 0)
|
|
37927
38493
|
return c;
|
|
37928
38494
|
return { ...c, personalization: { kind: "boosted", score, reasons } };
|
|
37929
38495
|
});
|
|
@@ -37951,7 +38517,7 @@ function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
|
37951
38517
|
break;
|
|
37952
38518
|
if (seen.has(c.issue.url))
|
|
37953
38519
|
continue;
|
|
37954
|
-
if (boostScoreOf(c)
|
|
38520
|
+
if (boostScoreOf(c) !== 0)
|
|
37955
38521
|
continue;
|
|
37956
38522
|
picks.push({ ...c, personalization: { kind: "diversity" } });
|
|
37957
38523
|
seen.add(c.issue.url);
|
|
@@ -37968,9 +38534,12 @@ function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
|
37968
38534
|
}
|
|
37969
38535
|
|
|
37970
38536
|
// ../core/dist/core/issue-discovery.js
|
|
37971
|
-
var
|
|
38537
|
+
var MODULE12 = "issue-discovery";
|
|
37972
38538
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
37973
38539
|
var CRITICAL_BUDGET_THRESHOLD = 10;
|
|
38540
|
+
var PHASE0_PER_PAGE = 30;
|
|
38541
|
+
var CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
|
|
38542
|
+
var PHASE0_MAX_SHARE = 0.5;
|
|
37974
38543
|
function buildIssueFilter(config2) {
|
|
37975
38544
|
return (items) => {
|
|
37976
38545
|
return items.filter((item) => {
|
|
@@ -37992,6 +38561,8 @@ function buildIssueFilter(config2) {
|
|
|
37992
38561
|
if (config2.skippedUrls.has(item.html_url))
|
|
37993
38562
|
return false;
|
|
37994
38563
|
const updatedAt = new Date(item.updated_at);
|
|
38564
|
+
if (Number.isNaN(updatedAt.getTime()))
|
|
38565
|
+
return false;
|
|
37995
38566
|
const ageDays = daysBetween(updatedAt, config2.now);
|
|
37996
38567
|
if (ageDays > config2.maxAgeDays)
|
|
37997
38568
|
return false;
|
|
@@ -38002,9 +38573,9 @@ function buildIssueFilter(config2) {
|
|
|
38002
38573
|
};
|
|
38003
38574
|
}
|
|
38004
38575
|
async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
38005
|
-
info(
|
|
38006
|
-
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues);
|
|
38007
|
-
info(
|
|
38576
|
+
info(MODULE12, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter, ${PHASE0_PER_PAGE}/repo)...`);
|
|
38577
|
+
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues, PHASE0_PER_PAGE);
|
|
38578
|
+
info(MODULE12, `Found ${candidates.length} candidates from merged-PR repos`);
|
|
38008
38579
|
return {
|
|
38009
38580
|
candidates,
|
|
38010
38581
|
error: allReposFailed ? "All merged-PR repo fetches failed" : null,
|
|
@@ -38012,26 +38583,26 @@ async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
|
38012
38583
|
};
|
|
38013
38584
|
}
|
|
38014
38585
|
async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
|
|
38015
|
-
info(
|
|
38586
|
+
info(MODULE12, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
38016
38587
|
const phase1Labels = labels.slice(0, 3);
|
|
38017
38588
|
const reposToSearch = repos.slice(0, 10);
|
|
38018
38589
|
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
|
|
38019
|
-
info(
|
|
38590
|
+
info(MODULE12, `Found ${candidates.length} candidates from starred repos`);
|
|
38020
38591
|
return {
|
|
38021
38592
|
candidates,
|
|
38022
38593
|
error: allReposFailed ? "All starred repo fetches failed" : null,
|
|
38023
38594
|
rateLimitHit
|
|
38024
38595
|
};
|
|
38025
38596
|
}
|
|
38026
|
-
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues) {
|
|
38027
|
-
info(
|
|
38597
|
+
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues, tracker, languageRotationOffset = 0) {
|
|
38598
|
+
info(MODULE12, "Phase 2: General issue search...");
|
|
38028
38599
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38029
38600
|
const tierLabelGroups = [];
|
|
38030
38601
|
if (scopes && scopes.length > 1) {
|
|
38031
38602
|
for (const scope of scopes) {
|
|
38032
38603
|
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
38033
38604
|
if (scopeLabels.length === 0) {
|
|
38034
|
-
warn(
|
|
38605
|
+
warn(MODULE12, `Scope "${scope}" has no labels, skipping tier`);
|
|
38035
38606
|
continue;
|
|
38036
38607
|
}
|
|
38037
38608
|
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
@@ -38050,8 +38621,8 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38050
38621
|
let rateLimitHit = false;
|
|
38051
38622
|
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
38052
38623
|
try {
|
|
38053
|
-
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3);
|
|
38054
|
-
info(
|
|
38624
|
+
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3, tracker, languageRotationOffset);
|
|
38625
|
+
info(MODULE12, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
38055
38626
|
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
38056
38627
|
tierResults.push(tierCandidates);
|
|
38057
38628
|
for (const c of tierCandidates)
|
|
@@ -38062,7 +38633,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38062
38633
|
if (vetRateLimitHit) {
|
|
38063
38634
|
rateLimitHit = true;
|
|
38064
38635
|
}
|
|
38065
|
-
info(
|
|
38636
|
+
info(MODULE12, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
38066
38637
|
} catch (err) {
|
|
38067
38638
|
if (getHttpStatusCode(err) === 401)
|
|
38068
38639
|
throw err;
|
|
@@ -38071,13 +38642,13 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38071
38642
|
if (isRateLimitError(err)) {
|
|
38072
38643
|
rateLimitHit = true;
|
|
38073
38644
|
}
|
|
38074
|
-
warn(
|
|
38645
|
+
warn(MODULE12, `Error in ${tier} tier search: ${errMsg}`);
|
|
38075
38646
|
tierResults.push([]);
|
|
38076
38647
|
}
|
|
38077
38648
|
}
|
|
38078
38649
|
const interleaved = interleaveArrays(tierResults);
|
|
38079
38650
|
if (interleaved.length === 0 && error48) {
|
|
38080
|
-
warn(
|
|
38651
|
+
warn(MODULE12, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${error48}`);
|
|
38081
38652
|
}
|
|
38082
38653
|
return {
|
|
38083
38654
|
candidates: interleaved.slice(0, maxResults),
|
|
@@ -38085,18 +38656,18 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38085
38656
|
rateLimitHit
|
|
38086
38657
|
};
|
|
38087
38658
|
}
|
|
38088
|
-
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues) {
|
|
38089
|
-
info(
|
|
38659
|
+
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker) {
|
|
38660
|
+
info(MODULE12, "Phase 3: Searching actively maintained repos...");
|
|
38090
38661
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38091
38662
|
const eligibleStarred = starredRepos.filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
|
|
38092
38663
|
if (eligibleStarred.length > 0) {
|
|
38093
|
-
info(
|
|
38664
|
+
info(MODULE12, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
|
|
38094
38665
|
const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred.slice(0, 15), minStars, maxResults);
|
|
38095
38666
|
if (restItems.length > 0) {
|
|
38096
38667
|
try {
|
|
38097
38668
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
|
|
38098
38669
|
if (candidates.length > 0) {
|
|
38099
|
-
info(
|
|
38670
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo REST search`);
|
|
38100
38671
|
return {
|
|
38101
38672
|
candidates,
|
|
38102
38673
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38106,11 +38677,11 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38106
38677
|
} catch (error48) {
|
|
38107
38678
|
if (getHttpStatusCode(error48) === 401)
|
|
38108
38679
|
throw error48;
|
|
38109
|
-
warn(
|
|
38680
|
+
warn(MODULE12, `Phase 3 REST vetting failed, falling back to Search API:`, errorMessage(error48));
|
|
38110
38681
|
}
|
|
38111
38682
|
}
|
|
38112
38683
|
}
|
|
38113
|
-
info(
|
|
38684
|
+
info(MODULE12, "Phase 3: Falling back to Search API...");
|
|
38114
38685
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
38115
38686
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
38116
38687
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
@@ -38118,15 +38689,15 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38118
38689
|
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
38119
38690
|
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
38120
38691
|
try {
|
|
38121
|
-
const data = await
|
|
38692
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
38122
38693
|
q: phase3Query,
|
|
38123
38694
|
sort: "updated",
|
|
38124
38695
|
order: "desc",
|
|
38125
38696
|
per_page: maxResults * 3
|
|
38126
|
-
});
|
|
38127
|
-
info(
|
|
38697
|
+
}, tracker);
|
|
38698
|
+
info(MODULE12, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
38128
38699
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
38129
|
-
info(
|
|
38700
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
38130
38701
|
return {
|
|
38131
38702
|
candidates,
|
|
38132
38703
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38136,7 +38707,7 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38136
38707
|
if (getHttpStatusCode(error48) === 401)
|
|
38137
38708
|
throw error48;
|
|
38138
38709
|
const errMsg = errorMessage(error48);
|
|
38139
|
-
warn(
|
|
38710
|
+
warn(MODULE12, `Error in maintained-repo search: ${errMsg}`);
|
|
38140
38711
|
return {
|
|
38141
38712
|
candidates: [],
|
|
38142
38713
|
error: errMsg,
|
|
@@ -38150,19 +38721,26 @@ var IssueDiscovery = class {
|
|
|
38150
38721
|
octokit;
|
|
38151
38722
|
githubToken;
|
|
38152
38723
|
vetter;
|
|
38724
|
+
budgetTracker;
|
|
38153
38725
|
/** Set after searchIssues() runs if rate limits affected the search (low pre-flight quota or mid-search rate limit hits). */
|
|
38154
38726
|
rateLimitWarning = null;
|
|
38155
38727
|
/**
|
|
38156
38728
|
* @param githubToken - GitHub personal access token or token from `gh auth token`
|
|
38157
38729
|
* @param preferences - User's search preferences (languages, labels, scopes, etc.)
|
|
38158
38730
|
* @param stateReader - Read-only interface for accessing scout state (merged PRs, starred repos, etc.)
|
|
38731
|
+
* @param budgetTracker - Search budget tracker. Defaults to the shared
|
|
38732
|
+
* singleton so existing callers behave identically. A long-lived host
|
|
38733
|
+
* serving concurrent searches can inject a per-search instance so one
|
|
38734
|
+
* search's init() no longer resets the budget state of another (the
|
|
38735
|
+
* shared-singleton concurrency hazard, #156).
|
|
38159
38736
|
*/
|
|
38160
|
-
constructor(githubToken, preferences, stateReader) {
|
|
38737
|
+
constructor(githubToken, preferences, stateReader, budgetTracker = getSearchBudgetTracker()) {
|
|
38161
38738
|
this.preferences = preferences;
|
|
38162
38739
|
this.stateReader = stateReader;
|
|
38163
38740
|
this.githubToken = githubToken;
|
|
38164
38741
|
this.octokit = getOctokit(githubToken);
|
|
38165
|
-
this.
|
|
38742
|
+
this.budgetTracker = budgetTracker;
|
|
38743
|
+
this.vetter = new IssueVetter(this.octokit, this.stateReader, this.budgetTracker);
|
|
38166
38744
|
}
|
|
38167
38745
|
/**
|
|
38168
38746
|
* Get starred repos from the state reader.
|
|
@@ -38211,9 +38789,23 @@ var IssueDiscovery = class {
|
|
|
38211
38789
|
const allCandidates = [];
|
|
38212
38790
|
const phaseErrors = {};
|
|
38213
38791
|
let rateLimitHitDuringSearch = false;
|
|
38792
|
+
const applyInterPhaseDelay = async () => {
|
|
38793
|
+
if (interPhaseDelay > 0) {
|
|
38794
|
+
info(MODULE12, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
38795
|
+
await sleep(interPhaseDelay);
|
|
38796
|
+
}
|
|
38797
|
+
};
|
|
38798
|
+
const recordPhaseResult = (key, result) => {
|
|
38799
|
+
allCandidates.push(...result.candidates);
|
|
38800
|
+
phaseErrors[key] = result.error;
|
|
38801
|
+
if (result.rateLimitHit)
|
|
38802
|
+
rateLimitHitDuringSearch = true;
|
|
38803
|
+
};
|
|
38804
|
+
const viableCandidateCount = () => allCandidates.filter((c) => c.recommendation !== "skip").length;
|
|
38214
38805
|
this.rateLimitWarning = null;
|
|
38215
|
-
|
|
38216
|
-
|
|
38806
|
+
resetGraphQLSearchQueryCount();
|
|
38807
|
+
const tracker = this.budgetTracker;
|
|
38808
|
+
let searchBudget = CRITICAL_BUDGET_THRESHOLD - 1;
|
|
38217
38809
|
try {
|
|
38218
38810
|
const rateLimit = await checkRateLimit(this.githubToken);
|
|
38219
38811
|
searchBudget = rateLimit.remaining;
|
|
@@ -38221,22 +38813,22 @@ var IssueDiscovery = class {
|
|
|
38221
38813
|
if (rateLimit.remaining < 5) {
|
|
38222
38814
|
const resetTime = new Date(rateLimit.resetAt).toLocaleTimeString("en-US", { hour12: false });
|
|
38223
38815
|
this.rateLimitWarning = `GitHub search API quota low (${rateLimit.remaining}/${rateLimit.limit} remaining, resets at ${resetTime}). Search may be slow.`;
|
|
38224
|
-
warn(
|
|
38816
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38225
38817
|
}
|
|
38226
38818
|
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
38227
|
-
info(
|
|
38819
|
+
info(MODULE12, `Search budget critical (${searchBudget} remaining) \u2014 skipping the starred phase; broad/maintained still run on GraphQL (points bucket)`);
|
|
38228
38820
|
} else if (searchBudget < LOW_BUDGET_THRESHOLD) {
|
|
38229
|
-
info(
|
|
38821
|
+
info(MODULE12, `Search budget low (${searchBudget} remaining) \u2014 broad/maintained run on GraphQL (points bucket), unaffected by REST search quota`);
|
|
38230
38822
|
}
|
|
38231
38823
|
} catch (error48) {
|
|
38232
38824
|
if (getHttpStatusCode(error48) === 401)
|
|
38233
38825
|
throw error48;
|
|
38234
38826
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
38235
|
-
warn(
|
|
38827
|
+
warn(MODULE12, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error48));
|
|
38236
38828
|
}
|
|
38237
38829
|
if (searchBudget <= 0) {
|
|
38238
|
-
this.rateLimitWarning = "GitHub search API quota exhausted
|
|
38239
|
-
|
|
38830
|
+
this.rateLimitWarning = "GitHub search API quota exhausted \u2014 skipping the starred phase; other phases don't use it.";
|
|
38831
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38240
38832
|
}
|
|
38241
38833
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
38242
38834
|
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
@@ -38247,17 +38839,24 @@ var IssueDiscovery = class {
|
|
|
38247
38839
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
38248
38840
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist.map((r) => r.toLowerCase()));
|
|
38249
38841
|
if (aiBlocklisted.size > 0) {
|
|
38250
|
-
debug(
|
|
38842
|
+
debug(MODULE12, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
38251
38843
|
}
|
|
38252
|
-
const
|
|
38844
|
+
const baseFilterConfig = {
|
|
38253
38845
|
excludedRepos: new Set(config2.excludeRepos.map((r) => r.toLowerCase())),
|
|
38254
38846
|
excludeOrgs: new Set((config2.excludeOrgs ?? []).map((o) => o.toLowerCase())),
|
|
38255
38847
|
aiBlocklisted,
|
|
38256
38848
|
lowScoringRepos,
|
|
38257
38849
|
skippedUrls: options.skippedUrls ?? /* @__PURE__ */ new Set(),
|
|
38258
|
-
maxAgeDays: config2.maxIssueAgeDays || 90,
|
|
38259
38850
|
now: /* @__PURE__ */ new Date(),
|
|
38260
38851
|
includeDocIssues: config2.includeDocIssues ?? true
|
|
38852
|
+
};
|
|
38853
|
+
const filterIssues = buildIssueFilter({
|
|
38854
|
+
...baseFilterConfig,
|
|
38855
|
+
maxAgeDays: config2.maxIssueAgeDays || 90
|
|
38856
|
+
});
|
|
38857
|
+
const filterIssuesPhase0 = buildIssueFilter({
|
|
38858
|
+
...baseFilterConfig,
|
|
38859
|
+
maxAgeDays: CONTRIBUTED_REPO_MAX_AGE_DAYS
|
|
38261
38860
|
});
|
|
38262
38861
|
const seenPhase0 = /* @__PURE__ */ new Set();
|
|
38263
38862
|
const phase0Repos = [];
|
|
@@ -38270,31 +38869,24 @@ var IssueDiscovery = class {
|
|
|
38270
38869
|
break;
|
|
38271
38870
|
}
|
|
38272
38871
|
const phase0RepoSet = new Set(phase0Repos);
|
|
38872
|
+
const otherStrategiesCanRun = starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
|
|
38273
38873
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
38274
|
-
const
|
|
38874
|
+
const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
|
|
38875
|
+
const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
|
|
38275
38876
|
if (remaining > 0) {
|
|
38276
|
-
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining,
|
|
38277
|
-
|
|
38278
|
-
phaseErrors["0"] = result.error;
|
|
38279
|
-
if (result.rateLimitHit)
|
|
38280
|
-
rateLimitHitDuringSearch = true;
|
|
38877
|
+
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
|
|
38878
|
+
recordPhaseResult("0", result);
|
|
38281
38879
|
}
|
|
38282
38880
|
strategiesUsed.push("merged");
|
|
38283
38881
|
}
|
|
38284
38882
|
if (allCandidates.length < maxResults && starredRepos.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("starred")) {
|
|
38285
|
-
|
|
38286
|
-
info(MODULE11, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
38287
|
-
await sleep(interPhaseDelay);
|
|
38288
|
-
}
|
|
38883
|
+
await applyInterPhaseDelay();
|
|
38289
38884
|
const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
|
|
38290
38885
|
if (reposToSearch.length > 0) {
|
|
38291
38886
|
const remaining = maxResults - allCandidates.length;
|
|
38292
38887
|
if (remaining > 0) {
|
|
38293
38888
|
const result = await runPhase1(this.octokit, this.vetter, reposToSearch, labels, remaining, filterIssues);
|
|
38294
|
-
|
|
38295
|
-
phaseErrors["1"] = result.error;
|
|
38296
|
-
if (result.rateLimitHit)
|
|
38297
|
-
rateLimitHitDuringSearch = true;
|
|
38889
|
+
recordPhaseResult("1", result);
|
|
38298
38890
|
strategiesUsed.push("starred");
|
|
38299
38891
|
}
|
|
38300
38892
|
}
|
|
@@ -38302,49 +38894,39 @@ var IssueDiscovery = class {
|
|
|
38302
38894
|
const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 9e4;
|
|
38303
38895
|
const configuredSkipThreshold = config2.skipBroadWhenSufficientResults ?? 8;
|
|
38304
38896
|
const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
|
|
38305
|
-
|
|
38306
|
-
|
|
38307
|
-
|
|
38897
|
+
const viableBeforeBroad = viableCandidateCount();
|
|
38898
|
+
if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
|
|
38899
|
+
info(MODULE12, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
38900
|
+
} else if (enabledStrategies.has("broad")) {
|
|
38901
|
+
const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo)).length;
|
|
38902
|
+
if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
|
|
38903
|
+
info(MODULE12, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
|
|
38308
38904
|
} else {
|
|
38309
|
-
|
|
38310
|
-
info(MODULE11, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
38311
|
-
await sleep(interPhaseDelay);
|
|
38312
|
-
}
|
|
38905
|
+
await applyInterPhaseDelay();
|
|
38313
38906
|
if (allCandidates.length > 0 && broadDelay > 0) {
|
|
38314
|
-
info(
|
|
38907
|
+
info(MODULE12, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
|
|
38315
38908
|
await sleep(broadDelay);
|
|
38316
38909
|
} else if (allCandidates.length === 0) {
|
|
38317
|
-
info(
|
|
38910
|
+
info(MODULE12, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
38318
38911
|
}
|
|
38319
|
-
const remaining = maxResults -
|
|
38320
|
-
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues);
|
|
38321
|
-
|
|
38322
|
-
phaseErrors["2"] = result.error;
|
|
38323
|
-
if (result.rateLimitHit)
|
|
38324
|
-
rateLimitHitDuringSearch = true;
|
|
38912
|
+
const remaining = maxResults - viableBeforeBroad;
|
|
38913
|
+
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues, tracker, options.languageRotationOffset ?? 0);
|
|
38914
|
+
recordPhaseResult("2", result);
|
|
38325
38915
|
strategiesUsed.push("broad");
|
|
38326
38916
|
}
|
|
38327
38917
|
}
|
|
38328
|
-
|
|
38329
|
-
|
|
38330
|
-
|
|
38331
|
-
|
|
38332
|
-
|
|
38333
|
-
const remaining = maxResults -
|
|
38334
|
-
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues);
|
|
38335
|
-
|
|
38336
|
-
phaseErrors["3"] = result.error;
|
|
38337
|
-
if (result.rateLimitHit)
|
|
38338
|
-
rateLimitHitDuringSearch = true;
|
|
38918
|
+
const viableBeforeMaintained = viableCandidateCount();
|
|
38919
|
+
if (viableBeforeMaintained >= maxResults && enabledStrategies.has("maintained")) {
|
|
38920
|
+
info(MODULE12, `Skipping maintained search: ${viableBeforeMaintained} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
38921
|
+
} else if (enabledStrategies.has("maintained")) {
|
|
38922
|
+
await applyInterPhaseDelay();
|
|
38923
|
+
const remaining = maxResults - viableBeforeMaintained;
|
|
38924
|
+
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues, tracker);
|
|
38925
|
+
recordPhaseResult("3", result);
|
|
38339
38926
|
strategiesUsed.push("maintained");
|
|
38340
38927
|
}
|
|
38341
|
-
const phasesSkippedForBudget = searchBudget <
|
|
38342
|
-
|
|
38343
|
-
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
38344
|
-
budgetNote = ` Most search phases were skipped due to critically low API quota (${searchBudget} remaining).`;
|
|
38345
|
-
} else if (phasesSkippedForBudget) {
|
|
38346
|
-
budgetNote = ` Some search phases were skipped due to low API quota (${searchBudget} remaining).`;
|
|
38347
|
-
}
|
|
38928
|
+
const phasesSkippedForBudget = searchBudget < CRITICAL_BUDGET_THRESHOLD;
|
|
38929
|
+
const budgetNote = phasesSkippedForBudget ? ` The starred-repo phase was skipped due to critically low API quota (${searchBudget} remaining); broad and maintained phases still ran on GraphQL.` : "";
|
|
38348
38930
|
if (allCandidates.length === 0) {
|
|
38349
38931
|
const errorDetails = [
|
|
38350
38932
|
phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
|
|
@@ -38357,12 +38939,17 @@ var IssueDiscovery = class {
|
|
|
38357
38939
|
this.rateLimitWarning = `Search returned no results due to GitHub API rate limits.${details}${budgetNote} Try again after the rate limit resets.`;
|
|
38358
38940
|
return { candidates: [], strategiesUsed };
|
|
38359
38941
|
}
|
|
38360
|
-
throw new ValidationError(`No issue candidates found across all search phases.${details} Try adjusting your search criteria (languages, labels) or check your network connection
|
|
38942
|
+
throw new ValidationError(`No issue candidates found across all search phases.${details} Try adjusting your search criteria (languages, labels) or check your network connection.`, strategiesUsed);
|
|
38361
38943
|
}
|
|
38362
38944
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
38363
38945
|
this.rateLimitWarning = `Search results may be incomplete: GitHub API rate limits were hit during search.${budgetNote} Found ${allCandidates.length} candidate${allCandidates.length === 1 ? "" : "s"} but some search phases were limited. Try again after the rate limit resets for complete results.`;
|
|
38364
38946
|
}
|
|
38365
|
-
const ranked = annotateBoost(allCandidates,
|
|
38947
|
+
const ranked = annotateBoost(allCandidates, {
|
|
38948
|
+
preferLanguages: options.preferLanguages,
|
|
38949
|
+
preferRepos: options.preferRepos,
|
|
38950
|
+
avoidRepos: options.avoidRepos,
|
|
38951
|
+
boostIssueTypes: options.boostIssueTypes
|
|
38952
|
+
});
|
|
38366
38953
|
ranked.sort((a, b) => {
|
|
38367
38954
|
const priorityOrder = {
|
|
38368
38955
|
merged_pr: 0,
|
|
@@ -38381,9 +38968,13 @@ var IssueDiscovery = class {
|
|
|
38381
38968
|
return boostDiff;
|
|
38382
38969
|
return b.viabilityScore - a.viabilityScore;
|
|
38383
38970
|
});
|
|
38384
|
-
const
|
|
38971
|
+
const viableFirst = [
|
|
38972
|
+
...ranked.filter((c) => c.recommendation !== "skip"),
|
|
38973
|
+
...ranked.filter((c) => c.recommendation === "skip")
|
|
38974
|
+
];
|
|
38975
|
+
const capped = applyPerRepoCap(viableFirst, 2);
|
|
38385
38976
|
const finalPicks = applyDiversityRatio(capped, maxResults, options.diversityRatio ?? 0);
|
|
38386
|
-
info(
|
|
38977
|
+
info(MODULE12, `Search complete: ${tracker.getTotalCalls()} REST Search API call(s) + ${getGraphQLSearchQueryCount()} GraphQL broad-search query/queries used, ${finalPicks.length} candidates returned`);
|
|
38387
38978
|
return { candidates: finalPicks, strategiesUsed };
|
|
38388
38979
|
}
|
|
38389
38980
|
/**
|
|
@@ -38416,7 +39007,6 @@ var IssueDiscovery = class {
|
|
|
38416
39007
|
};
|
|
38417
39008
|
|
|
38418
39009
|
// ../core/dist/core/roadmap.js
|
|
38419
|
-
var MODULE12 = "roadmap";
|
|
38420
39010
|
var CACHE_TTL_MS2 = 60 * 60 * 1e3;
|
|
38421
39011
|
var ROADMAP_PATHS = [
|
|
38422
39012
|
"ROADMAP.md",
|
|
@@ -38436,8 +39026,8 @@ function pruneCache2() {
|
|
|
38436
39026
|
function parseRoadmapIssueRefs(content, owner, repo) {
|
|
38437
39027
|
const refs = /* @__PURE__ */ new Set();
|
|
38438
39028
|
const ownerRepoLower = `${owner}/${repo}`.toLowerCase();
|
|
38439
|
-
const
|
|
38440
|
-
const fullRefPattern = new RegExp(`\\b${
|
|
39029
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39030
|
+
const fullRefPattern = new RegExp(`\\b${escape(owner)}/${escape(repo)}#(\\d+)\\b`, "gi");
|
|
38441
39031
|
for (const m of content.matchAll(fullRefPattern)) {
|
|
38442
39032
|
const n = Number.parseInt(m[1], 10);
|
|
38443
39033
|
if (n > 0)
|
|
@@ -38484,22 +39074,13 @@ async function fetchRoadmapIssueRefs(octokit, owner, repo) {
|
|
|
38484
39074
|
var roadmapInflight = /* @__PURE__ */ new Map();
|
|
38485
39075
|
async function fetchRoadmapIssueRefsUncached(octokit, owner, repo, cacheKey) {
|
|
38486
39076
|
for (const path5 of ROADMAP_PATHS) {
|
|
38487
|
-
|
|
38488
|
-
|
|
38489
|
-
|
|
38490
|
-
|
|
38491
|
-
|
|
38492
|
-
|
|
38493
|
-
|
|
38494
|
-
pruneCache2();
|
|
38495
|
-
return refs;
|
|
38496
|
-
} catch (err) {
|
|
38497
|
-
rethrowIfFatal(err);
|
|
38498
|
-
const status = getHttpStatusCode(err);
|
|
38499
|
-
if (status === 404)
|
|
38500
|
-
continue;
|
|
38501
|
-
warn(MODULE12, `Unexpected error fetching ${path5} from ${owner}/${repo}: ${errorMessage(err)}`);
|
|
38502
|
-
}
|
|
39077
|
+
const { text } = await probeRepoFile(octokit, owner, repo, path5);
|
|
39078
|
+
if (!text)
|
|
39079
|
+
continue;
|
|
39080
|
+
const refs = parseRoadmapIssueRefs(text, owner, repo);
|
|
39081
|
+
roadmapCache.set(cacheKey, { refs, fetchedAt: Date.now() });
|
|
39082
|
+
pruneCache2();
|
|
39083
|
+
return refs;
|
|
38503
39084
|
}
|
|
38504
39085
|
const empty = /* @__PURE__ */ new Set();
|
|
38505
39086
|
roadmapCache.set(cacheKey, { refs: empty, fetchedAt: Date.now() });
|
|
@@ -38830,8 +39411,12 @@ var GistStateStore = class {
|
|
|
38830
39411
|
let toWrite = state;
|
|
38831
39412
|
try {
|
|
38832
39413
|
const remote = await this.fetchGistState(this.gistId);
|
|
38833
|
-
if (remote) {
|
|
38834
|
-
toWrite = mergeStates(state, remote);
|
|
39414
|
+
if (remote.kind === "ok") {
|
|
39415
|
+
toWrite = mergeStates(state, remote.state);
|
|
39416
|
+
} else if (remote.kind === "invalid") {
|
|
39417
|
+
this.writeCache(state);
|
|
39418
|
+
warn(MODULE14, "Remote gist content failed validation \u2014 not overwriting it. Changes saved locally; gist sync skipped.");
|
|
39419
|
+
return false;
|
|
38835
39420
|
}
|
|
38836
39421
|
} catch (err) {
|
|
38837
39422
|
rethrowIfFatal(err);
|
|
@@ -38869,9 +39454,9 @@ var GistStateStore = class {
|
|
|
38869
39454
|
debug(MODULE14, `Trying cached gist ID: ${cachedId}`);
|
|
38870
39455
|
try {
|
|
38871
39456
|
const fetched = await this.fetchGistState(cachedId);
|
|
38872
|
-
if (fetched) {
|
|
39457
|
+
if (fetched.kind === "ok") {
|
|
38873
39458
|
this.gistId = cachedId;
|
|
38874
|
-
const state = this.mergeCacheInto(fetched);
|
|
39459
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38875
39460
|
this.writeCache(state);
|
|
38876
39461
|
return { gistId: cachedId, state, created: false };
|
|
38877
39462
|
}
|
|
@@ -38888,8 +39473,8 @@ var GistStateStore = class {
|
|
|
38888
39473
|
this.saveGistId(search.id);
|
|
38889
39474
|
this.gistId = search.id;
|
|
38890
39475
|
const fetched = await this.fetchGistState(search.id);
|
|
38891
|
-
if (fetched) {
|
|
38892
|
-
const state = this.mergeCacheInto(fetched);
|
|
39476
|
+
if (fetched.kind === "ok") {
|
|
39477
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38893
39478
|
this.writeCache(state);
|
|
38894
39479
|
return { gistId: search.id, state, created: false };
|
|
38895
39480
|
}
|
|
@@ -38933,17 +39518,24 @@ var GistStateStore = class {
|
|
|
38933
39518
|
};
|
|
38934
39519
|
}
|
|
38935
39520
|
// ── Gist API operations ──────────────────────────────────────────────
|
|
39521
|
+
/**
|
|
39522
|
+
* "missing" (no file/content) and "invalid" (content that fails parse or
|
|
39523
|
+
* schema validation) are distinct outcomes: an invalid remote may be a
|
|
39524
|
+
* corrupted-but-recoverable state or a newer state version written by a
|
|
39525
|
+
* newer binary, so callers must not treat it as "nothing there" and
|
|
39526
|
+
* overwrite it (#286).
|
|
39527
|
+
*/
|
|
38936
39528
|
async fetchGistState(gistId) {
|
|
38937
39529
|
const { data } = await this.octokit.gists.get({ gist_id: gistId });
|
|
38938
39530
|
const file2 = data.files?.[GIST_FILENAME];
|
|
38939
39531
|
if (!file2?.content)
|
|
38940
|
-
return
|
|
39532
|
+
return { kind: "missing" };
|
|
38941
39533
|
try {
|
|
38942
39534
|
const parsed = JSON.parse(file2.content);
|
|
38943
|
-
return parseScoutState(parsed);
|
|
39535
|
+
return { kind: "ok", state: parseScoutState(parsed) };
|
|
38944
39536
|
} catch (err) {
|
|
38945
39537
|
warn(MODULE14, `Gist content failed validation: ${errorMessage(err)}`);
|
|
38946
|
-
return
|
|
39538
|
+
return { kind: "invalid" };
|
|
38947
39539
|
}
|
|
38948
39540
|
}
|
|
38949
39541
|
/**
|
|
@@ -39037,7 +39629,7 @@ function mergeTombstones(local, remote) {
|
|
|
39037
39629
|
}
|
|
39038
39630
|
return [...byUrl.values()].filter((t) => {
|
|
39039
39631
|
const ts = new Date(t.removedAt).getTime();
|
|
39040
|
-
return
|
|
39632
|
+
return Number.isFinite(ts) && ts >= cutoff;
|
|
39041
39633
|
});
|
|
39042
39634
|
}
|
|
39043
39635
|
function applyTombstones(items, tombstones, urlOf, touchedAtOf) {
|
|
@@ -39079,11 +39671,26 @@ function mergeStates(local, remote) {
|
|
|
39079
39671
|
skippedIssues,
|
|
39080
39672
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
39081
39673
|
lastRunAt: pickFresherTimestamp(local.lastRunAt, remote.lastRunAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
39082
|
-
gistId: remote.gistId ?? local.gistId
|
|
39674
|
+
gistId: remote.gistId ?? local.gistId,
|
|
39675
|
+
searchRotation: mergeSearchRotation(local.searchRotation, remote.searchRotation)
|
|
39083
39676
|
};
|
|
39084
39677
|
}
|
|
39678
|
+
function mergeSearchRotation(local, remote) {
|
|
39679
|
+
const localRotation = local ?? { languageOffset: 0 };
|
|
39680
|
+
const remoteRotation = remote ?? { languageOffset: 0 };
|
|
39681
|
+
const localTs = localRotation.lastRotatedAt;
|
|
39682
|
+
const remoteTs = remoteRotation.lastRotatedAt;
|
|
39683
|
+
if (localTs && remoteTs) {
|
|
39684
|
+
return localTs >= remoteTs ? localRotation : remoteRotation;
|
|
39685
|
+
}
|
|
39686
|
+
if (localTs)
|
|
39687
|
+
return localRotation;
|
|
39688
|
+
if (remoteTs)
|
|
39689
|
+
return remoteRotation;
|
|
39690
|
+
return remoteRotation.languageOffset >= localRotation.languageOffset ? remoteRotation : localRotation;
|
|
39691
|
+
}
|
|
39085
39692
|
function mergeRepoScores(local, remote) {
|
|
39086
|
-
const merged =
|
|
39693
|
+
const merged = Object.assign(/* @__PURE__ */ Object.create(null), local);
|
|
39087
39694
|
for (const [repo, remoteScore] of Object.entries(remote)) {
|
|
39088
39695
|
const localScore = merged[repo];
|
|
39089
39696
|
if (!localScore) {
|
|
@@ -39303,23 +39910,50 @@ var OssScout = class {
|
|
|
39303
39910
|
this.evictStaleCacheEntries();
|
|
39304
39911
|
this.cullExpiredSkips();
|
|
39305
39912
|
const skippedUrls = new Set((this.state.skippedIssues ?? []).map((s) => s.url));
|
|
39913
|
+
if (options?.excludeRecentlySurfaced ?? true) {
|
|
39914
|
+
const ttlDays = options?.recentlySurfacedTtlDays ?? 7;
|
|
39915
|
+
const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1e3;
|
|
39916
|
+
for (const r of this.state.savedResults ?? []) {
|
|
39917
|
+
const seen = Date.parse(r.lastSeenAt);
|
|
39918
|
+
if (!Number.isNaN(seen) && seen >= cutoff)
|
|
39919
|
+
skippedUrls.add(r.issueUrl);
|
|
39920
|
+
}
|
|
39921
|
+
}
|
|
39306
39922
|
const discovery = new IssueDiscovery(this.githubToken, this.state.preferences, this);
|
|
39307
39923
|
const prefs = this.state.preferences;
|
|
39308
39924
|
const prefLangs = prefs.preferLanguages ?? [];
|
|
39309
39925
|
const prefRepos = prefs.preferRepos ?? [];
|
|
39310
39926
|
const preferLanguages = options?.preferLanguages ?? (prefLangs.length > 0 ? prefLangs : void 0);
|
|
39311
39927
|
const preferRepos = options?.preferRepos ?? (prefRepos.length > 0 ? prefRepos : void 0);
|
|
39928
|
+
const prefAvoid = prefs.avoidRepos ?? [];
|
|
39929
|
+
const prefBoostTypes = prefs.boostIssueTypes ?? [];
|
|
39930
|
+
const avoidRepos = options?.avoidRepos ?? (prefAvoid.length > 0 ? prefAvoid : void 0);
|
|
39931
|
+
const boostIssueTypes = options?.boostIssueTypes ?? (prefBoostTypes.length > 0 ? prefBoostTypes : void 0);
|
|
39312
39932
|
const diversityRatio = options?.diversityRatio ?? prefs.diversityRatio ?? 0;
|
|
39313
|
-
const
|
|
39314
|
-
|
|
39315
|
-
|
|
39316
|
-
|
|
39317
|
-
|
|
39318
|
-
|
|
39319
|
-
|
|
39320
|
-
|
|
39321
|
-
|
|
39322
|
-
|
|
39933
|
+
const rotation = this.state.searchRotation ?? { languageOffset: 0 };
|
|
39934
|
+
let candidates;
|
|
39935
|
+
let strategiesUsed;
|
|
39936
|
+
try {
|
|
39937
|
+
({ candidates, strategiesUsed } = await discovery.searchIssues({
|
|
39938
|
+
maxResults: options?.maxResults,
|
|
39939
|
+
strategies: options?.strategies,
|
|
39940
|
+
skippedUrls,
|
|
39941
|
+
preferLanguages,
|
|
39942
|
+
preferRepos,
|
|
39943
|
+
avoidRepos,
|
|
39944
|
+
boostIssueTypes,
|
|
39945
|
+
diversityRatio,
|
|
39946
|
+
interPhaseDelayMs: options?.interPhaseDelayMs,
|
|
39947
|
+
broadPhaseDelayMs: options?.broadPhaseDelayMs,
|
|
39948
|
+
languageRotationOffset: rotation.languageOffset
|
|
39949
|
+
}));
|
|
39950
|
+
} catch (err) {
|
|
39951
|
+
if (err instanceof ValidationError) {
|
|
39952
|
+
this.advanceRotationIfBroadRan(rotation, err.strategiesUsed ?? []);
|
|
39953
|
+
}
|
|
39954
|
+
throw err;
|
|
39955
|
+
}
|
|
39956
|
+
this.advanceRotationIfBroadRan(rotation, strategiesUsed);
|
|
39323
39957
|
for (const c of candidates) {
|
|
39324
39958
|
this.updateRepoSignalsFromHealth(c.projectHealth);
|
|
39325
39959
|
}
|
|
@@ -39333,20 +39967,36 @@ var OssScout = class {
|
|
|
39333
39967
|
strategiesUsed
|
|
39334
39968
|
};
|
|
39335
39969
|
}
|
|
39970
|
+
/**
|
|
39971
|
+
* Advance the language-rotation cursor when the broad phase actually ran.
|
|
39972
|
+
* Called on both the success path and the zero-candidate ValidationError
|
|
39973
|
+
* path — a broad phase that ran and found nothing must still rotate, or the
|
|
39974
|
+
* same barren language slice would lead every subsequent run (#249
|
|
39975
|
+
* follow-up). Skipped/gated broad phases don't burn a rotation slot.
|
|
39976
|
+
*/
|
|
39977
|
+
advanceRotationIfBroadRan(rotation, strategiesUsed) {
|
|
39978
|
+
if (!strategiesUsed.includes("broad"))
|
|
39979
|
+
return;
|
|
39980
|
+
this.state.searchRotation = {
|
|
39981
|
+
...rotation,
|
|
39982
|
+
languageOffset: rotation.languageOffset + 1,
|
|
39983
|
+
lastRotatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
39984
|
+
};
|
|
39985
|
+
this.dirty = true;
|
|
39986
|
+
}
|
|
39336
39987
|
/**
|
|
39337
39988
|
* Populate the `hasActiveMaintainers` repo-score signal from a freshly
|
|
39338
39989
|
* computed projectHealth (#167). It was initialized false and never set, so
|
|
39339
39990
|
* calculateScore's +1 active-maintainers weight was inert; `isActive`
|
|
39340
39991
|
* (recent commit activity) is a real, already-computed proxy.
|
|
39341
39992
|
*
|
|
39342
|
-
* `isResponsive` and `avgResponseDays` are deliberately NOT set here
|
|
39343
|
-
* `
|
|
39344
|
-
*
|
|
39345
|
-
*
|
|
39346
|
-
*
|
|
39347
|
-
*
|
|
39348
|
-
*
|
|
39349
|
-
* neutral-default fields don't pollute the score.
|
|
39993
|
+
* `isResponsive` and `avgResponseDays` are deliberately NOT set here, and
|
|
39994
|
+
* `isResponsive` no longer carries a score weight at all (#167): real
|
|
39995
|
+
* responsiveness needs an actual response-time measurement (extra API calls)
|
|
39996
|
+
* that is out of scope, and `hasActiveMaintainers` already covers the
|
|
39997
|
+
* activity proxy. `hasHostileComments` stays a host-settable capability (it
|
|
39998
|
+
* needs comment sentiment, out of scope). A failed health check is skipped so
|
|
39999
|
+
* its neutral-default fields don't pollute the score.
|
|
39350
40000
|
*/
|
|
39351
40001
|
updateRepoSignalsFromHealth(health) {
|
|
39352
40002
|
if (health.checkFailed)
|
|
@@ -39878,6 +40528,7 @@ var OssScout = class {
|
|
|
39878
40528
|
}
|
|
39879
40529
|
if (this.persistLocal) {
|
|
39880
40530
|
try {
|
|
40531
|
+
this.state = mergeStates(this.state, loadLocalState());
|
|
39881
40532
|
saveLocalState(this.state);
|
|
39882
40533
|
} catch {
|
|
39883
40534
|
return false;
|
|
@@ -39903,16 +40554,21 @@ var OssScout = class {
|
|
|
39903
40554
|
});
|
|
39904
40555
|
}
|
|
39905
40556
|
/**
|
|
39906
|
-
* Calculate repo score
|
|
40557
|
+
* Calculate repo score from observed data.
|
|
39907
40558
|
* base 5, +1 per merged PR (max +3), -1 per closed-without-merge (max -3),
|
|
39908
|
-
* +1
|
|
40559
|
+
* +1 active maintainers, -2 hostile comments, clamped to [1, 10].
|
|
40560
|
+
*
|
|
40561
|
+
* `isResponsive` is intentionally NOT scored (#167): nothing in oss-scout
|
|
40562
|
+
* ever computes it, so awarding +1 for it was dead weight that the
|
|
40563
|
+
* now-computed `hasActiveMaintainers` signal already covers as the activity
|
|
40564
|
+
* proxy. The field is retained on RepoSignals for backward compatibility but
|
|
40565
|
+
* no longer affects the score. With the current signals the reachable range
|
|
40566
|
+
* is [1, 9]; the upper clamp stays as defensive hygiene.
|
|
39909
40567
|
*/
|
|
39910
40568
|
calculateScore(repoScore) {
|
|
39911
40569
|
let score = 5;
|
|
39912
40570
|
score += Math.min(repoScore.mergedPRCount, 3);
|
|
39913
40571
|
score -= Math.min(repoScore.closedWithoutMergeCount, 3);
|
|
39914
|
-
if (repoScore.signals.isResponsive)
|
|
39915
|
-
score += 1;
|
|
39916
40572
|
if (repoScore.signals.hasActiveMaintainers)
|
|
39917
40573
|
score += 1;
|
|
39918
40574
|
if (repoScore.signals.hasHostileComments)
|
|
@@ -39949,6 +40605,8 @@ var FIELD_CONFIGS = {
|
|
|
39949
40605
|
preferLanguages: { type: "array" },
|
|
39950
40606
|
preferRepos: { type: "array" },
|
|
39951
40607
|
diversityRatio: { type: "float" },
|
|
40608
|
+
avoidRepos: { type: "array" },
|
|
40609
|
+
boostIssueTypes: { type: "array" },
|
|
39952
40610
|
slmTriageModel: { type: "string" },
|
|
39953
40611
|
slmTriageHost: { type: "string" },
|
|
39954
40612
|
featuresAnchorThreshold: { type: "number" },
|
|
@@ -40007,6 +40665,8 @@ function applyPreferenceField(preferences, key, value) {
|
|
|
40007
40665
|
const prefs = { ...preferences };
|
|
40008
40666
|
switch (field.type) {
|
|
40009
40667
|
case "string":
|
|
40668
|
+
if (key === "slmTriageHost")
|
|
40669
|
+
assertValidTriageHost(value);
|
|
40010
40670
|
prefs[key] = value;
|
|
40011
40671
|
break;
|
|
40012
40672
|
case "boolean":
|
|
@@ -40067,6 +40727,10 @@ function validateUrl(url2) {
|
|
|
40067
40727
|
|
|
40068
40728
|
// src/tools.ts
|
|
40069
40729
|
var TOOL_TIMEOUT_MS = 3e5;
|
|
40730
|
+
var PERSIST_WARNING = "Checkpoint failed: changes were applied in memory but could not be persisted. They may be lost when the server restarts.";
|
|
40731
|
+
function withPersistWarning(payload, persisted) {
|
|
40732
|
+
return persisted ? payload : { ...payload, persistWarning: PERSIST_WARNING };
|
|
40733
|
+
}
|
|
40070
40734
|
function withTimeout(promise2, ms = TOOL_TIMEOUT_MS) {
|
|
40071
40735
|
let timeoutId;
|
|
40072
40736
|
const timer = new Promise((_, reject) => {
|
|
@@ -40082,7 +40746,7 @@ function registerTools(server, scout) {
|
|
|
40082
40746
|
"search",
|
|
40083
40747
|
"Search for open source issues matching your preferences",
|
|
40084
40748
|
{
|
|
40085
|
-
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
40749
|
+
maxResults: external_exports3.number().int().min(1).max(50).optional().describe("Maximum number of results to return (1-50, default 10)"),
|
|
40086
40750
|
strategies: external_exports3.string().optional().describe(
|
|
40087
40751
|
"Comma-separated search strategies: merged, starred, broad, maintained, all"
|
|
40088
40752
|
),
|
|
@@ -40092,6 +40756,12 @@ function registerTools(server, scout) {
|
|
|
40092
40756
|
preferRepos: external_exports3.array(external_exports3.string()).optional().describe(
|
|
40093
40757
|
"Soft-boost ranking for candidates in one of these `owner/repo` slugs. Stronger weight than language match (#1244). Not a filter; only reorders."
|
|
40094
40758
|
),
|
|
40759
|
+
avoidRepos: external_exports3.array(external_exports3.string()).optional().describe(
|
|
40760
|
+
"Soft PENALTY (milder than excludeRepos) for candidates in one of these `owner/repo` slugs (#168). Pushes them down the ranking but does not remove them; a strong boost can still outweigh it."
|
|
40761
|
+
),
|
|
40762
|
+
boostIssueTypes: external_exports3.array(external_exports3.string()).optional().describe(
|
|
40763
|
+
'Soft-boost ranking for candidates whose issue labels match one of these types, case-insensitive (e.g. "bug", "good first issue") (#168). Not a filter; only reorders.'
|
|
40764
|
+
),
|
|
40095
40765
|
diversityRatio: external_exports3.number().min(0).max(1).optional().describe(
|
|
40096
40766
|
"Diversity counterweight (#1244): fraction of result slots reserved for candidates that matched NEITHER preference list. Counters echo-chamber bias when boosts accumulate over time. 0 disables, 1 reserves every slot for diversity. Default 0."
|
|
40097
40767
|
)
|
|
@@ -40101,16 +40771,29 @@ function registerTools(server, scout) {
|
|
|
40101
40771
|
strategies,
|
|
40102
40772
|
preferLanguages,
|
|
40103
40773
|
preferRepos,
|
|
40774
|
+
avoidRepos,
|
|
40775
|
+
boostIssueTypes,
|
|
40104
40776
|
diversityRatio
|
|
40105
40777
|
}) => {
|
|
40106
40778
|
try {
|
|
40107
|
-
const parsedStrategies = strategies ? strategies.split(",").map((s) =>
|
|
40779
|
+
const parsedStrategies = strategies ? strategies.split(",").map((s) => {
|
|
40780
|
+
const trimmed = s.trim();
|
|
40781
|
+
const parsed = SearchStrategySchema.safeParse(trimmed);
|
|
40782
|
+
if (!parsed.success) {
|
|
40783
|
+
throw new Error(
|
|
40784
|
+
`Invalid strategy "${trimmed}". Valid strategies: ${SearchStrategySchema.options.join(", ")}`
|
|
40785
|
+
);
|
|
40786
|
+
}
|
|
40787
|
+
return parsed.data;
|
|
40788
|
+
}) : void 0;
|
|
40108
40789
|
const result = await withTimeout(
|
|
40109
40790
|
scout.search({
|
|
40110
40791
|
maxResults: maxResults ?? 10,
|
|
40111
40792
|
strategies: parsedStrategies,
|
|
40112
40793
|
preferLanguages,
|
|
40113
40794
|
preferRepos,
|
|
40795
|
+
avoidRepos,
|
|
40796
|
+
boostIssueTypes,
|
|
40114
40797
|
diversityRatio,
|
|
40115
40798
|
// No fixed inter-phase sleeps in the request/response MCP context;
|
|
40116
40799
|
// the budget tracker still paces the GitHub calls (#143)
|
|
@@ -40119,9 +40802,18 @@ function registerTools(server, scout) {
|
|
|
40119
40802
|
})
|
|
40120
40803
|
);
|
|
40121
40804
|
scout.saveResults(result.candidates);
|
|
40122
|
-
await scout.checkpoint();
|
|
40805
|
+
const persisted = await scout.checkpoint();
|
|
40123
40806
|
return {
|
|
40124
|
-
content: [
|
|
40807
|
+
content: [
|
|
40808
|
+
{
|
|
40809
|
+
type: "text",
|
|
40810
|
+
text: JSON.stringify(
|
|
40811
|
+
withPersistWarning(result, persisted),
|
|
40812
|
+
null,
|
|
40813
|
+
2
|
|
40814
|
+
)
|
|
40815
|
+
}
|
|
40816
|
+
]
|
|
40125
40817
|
};
|
|
40126
40818
|
} catch (err) {
|
|
40127
40819
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -40136,7 +40828,7 @@ function registerTools(server, scout) {
|
|
|
40136
40828
|
"scout-features",
|
|
40137
40829
|
"Surface feature-scoped contribution opportunities in repos where you have 3+ merged PRs",
|
|
40138
40830
|
{
|
|
40139
|
-
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
40831
|
+
maxResults: external_exports3.number().int().min(1).max(50).optional().describe("Maximum number of results to return (1-50, default 10)"),
|
|
40140
40832
|
anchorThreshold: external_exports3.number().int().min(1).max(50).optional().describe("Anchor threshold override (default 3)"),
|
|
40141
40833
|
splitRatio: external_exports3.number().min(0).max(1).optional().describe("Quick-wins/bigger-bets split ratio (default 0.6)")
|
|
40142
40834
|
},
|
|
@@ -40150,9 +40842,18 @@ function registerTools(server, scout) {
|
|
|
40150
40842
|
})
|
|
40151
40843
|
);
|
|
40152
40844
|
scout.saveResults([...result.quickWins, ...result.biggerBets]);
|
|
40153
|
-
await scout.checkpoint();
|
|
40845
|
+
const persisted = await scout.checkpoint();
|
|
40154
40846
|
return {
|
|
40155
|
-
content: [
|
|
40847
|
+
content: [
|
|
40848
|
+
{
|
|
40849
|
+
type: "text",
|
|
40850
|
+
text: JSON.stringify(
|
|
40851
|
+
withPersistWarning(result, persisted),
|
|
40852
|
+
null,
|
|
40853
|
+
2
|
|
40854
|
+
)
|
|
40855
|
+
}
|
|
40856
|
+
]
|
|
40156
40857
|
};
|
|
40157
40858
|
} catch (err) {
|
|
40158
40859
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -40324,10 +41025,19 @@ function registerTools(server, scout) {
|
|
|
40324
41025
|
scout.updatePreferences({
|
|
40325
41026
|
[key]: validated[key]
|
|
40326
41027
|
});
|
|
40327
|
-
await scout.checkpoint();
|
|
41028
|
+
const persisted = await scout.checkpoint();
|
|
40328
41029
|
const updated = scout.getPreferences();
|
|
40329
41030
|
return {
|
|
40330
|
-
content: [
|
|
41031
|
+
content: [
|
|
41032
|
+
{
|
|
41033
|
+
type: "text",
|
|
41034
|
+
text: JSON.stringify(
|
|
41035
|
+
withPersistWarning(updated, persisted),
|
|
41036
|
+
null,
|
|
41037
|
+
2
|
|
41038
|
+
)
|
|
41039
|
+
}
|
|
41040
|
+
]
|
|
40331
41041
|
};
|
|
40332
41042
|
}
|
|
40333
41043
|
);
|
|
@@ -40385,7 +41095,7 @@ function registerResources(server, scout) {
|
|
|
40385
41095
|
// src/index.ts
|
|
40386
41096
|
var SERVER_INFO = {
|
|
40387
41097
|
name: "oss-scout-mcp",
|
|
40388
|
-
version: "0.
|
|
41098
|
+
version: "0.10.1"
|
|
40389
41099
|
// x-release-please-version
|
|
40390
41100
|
};
|
|
40391
41101
|
function createServer(scout) {
|