@oss-scout/mcp 0.10.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 +867 -229
- package/dist/tools.js +27 -5
- 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;
|
|
3355
3376
|
} else {
|
|
3356
|
-
|
|
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;
|
|
3417
|
+
} else {
|
|
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;
|
|
@@ -36018,7 +36239,11 @@ var ScoutPreferencesSchema = external_exports3.looseObject({
|
|
|
36018
36239
|
// Soft boost for candidates whose issue labels match one of these types,
|
|
36019
36240
|
// case-insensitive (e.g. "bug", "good first issue") (#168).
|
|
36020
36241
|
boostIssueTypes: external_exports3.array(external_exports3.string()).default([]),
|
|
36021
|
-
|
|
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),
|
|
36022
36247
|
/**
|
|
36023
36248
|
* Skip the expensive broad phase once this many candidates were found by
|
|
36024
36249
|
* the cheaper phases. Clamped at runtime to maxResults - 1 so it stays
|
|
@@ -36081,7 +36306,20 @@ var ScoutStateSchema = external_exports3.looseObject({
|
|
|
36081
36306
|
preferencesUpdatedAt: external_exports3.string().optional(),
|
|
36082
36307
|
lastSearchAt: external_exports3.string().optional(),
|
|
36083
36308
|
lastRunAt: external_exports3.string().default(() => (/* @__PURE__ */ new Date()).toISOString()),
|
|
36084
|
-
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 }))
|
|
36085
36323
|
});
|
|
36086
36324
|
function parseScoutState(raw) {
|
|
36087
36325
|
return ScoutStateSchema.parse(raw);
|
|
@@ -36734,9 +36972,12 @@ async function checkNoExistingPR(octokit, owner, repo, issueNumber) {
|
|
|
36734
36972
|
}
|
|
36735
36973
|
}
|
|
36736
36974
|
var MERGED_PR_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
36975
|
+
function mergedPRsCacheKey(owner, repo) {
|
|
36976
|
+
return versionedCacheKey(`merged-prs:${owner}/${repo}`);
|
|
36977
|
+
}
|
|
36737
36978
|
async function checkUserMergedPRsInRepo(octokit, owner, repo, tracker = getSearchBudgetTracker()) {
|
|
36738
36979
|
const cache = getHttpCache();
|
|
36739
|
-
const cacheKey =
|
|
36980
|
+
const cacheKey = mergedPRsCacheKey(owner, repo);
|
|
36740
36981
|
return withInflightDedup(cache, cacheKey, async () => {
|
|
36741
36982
|
const cached2 = cache.getIfFresh(cacheKey, MERGED_PR_CACHE_TTL_MS);
|
|
36742
36983
|
if (cached2 != null && typeof cached2 === "number") {
|
|
@@ -36781,6 +37022,17 @@ async function checkNotClaimed(octokit, owner, repo, issueNumber, commentCount)
|
|
|
36781
37022
|
});
|
|
36782
37023
|
recentComments.push(...response.data);
|
|
36783
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
|
+
}
|
|
36784
37036
|
for (const comment of recentComments) {
|
|
36785
37037
|
if (commentClaimsIssue(comment.body || "")) {
|
|
36786
37038
|
return { passed: false };
|
|
@@ -36813,7 +37065,7 @@ function analyzeRequirements(body) {
|
|
|
36813
37065
|
var MODULE6 = "probe-repo-file";
|
|
36814
37066
|
async function probeRepoFile(octokit, owner, repo, path5) {
|
|
36815
37067
|
try {
|
|
36816
|
-
const
|
|
37068
|
+
const data = await cachedRequest(getHttpCache(), `/repos/${owner}/${repo}/contents/${path5}`, (headers) => octokit.repos.getContent({ owner, repo, path: path5, headers }));
|
|
36817
37069
|
if (data && typeof data === "object" && "content" in data && typeof data.content === "string") {
|
|
36818
37070
|
return {
|
|
36819
37071
|
text: Buffer.from(data.content, "base64").toString("utf-8"),
|
|
@@ -36833,6 +37085,30 @@ async function probeRepoFile(octokit, owner, repo, path5) {
|
|
|
36833
37085
|
|
|
36834
37086
|
// ../core/dist/core/repo-health.js
|
|
36835
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
|
+
}
|
|
36836
37112
|
var guidelinesCache = /* @__PURE__ */ new Map();
|
|
36837
37113
|
var CACHE_TTL_MS = 60 * 60 * 1e3;
|
|
36838
37114
|
var HEALTH_CACHE_TTL_MS = 4 * 60 * 60 * 1e3;
|
|
@@ -36852,22 +37128,25 @@ function pruneCache() {
|
|
|
36852
37128
|
}
|
|
36853
37129
|
}
|
|
36854
37130
|
}
|
|
36855
|
-
async function checkProjectHealth(octokit, owner, repo) {
|
|
37131
|
+
async function checkProjectHealth(octokit, owner, repo, tracker = getSearchBudgetTracker()) {
|
|
36856
37132
|
const cache = getHttpCache();
|
|
36857
|
-
const healthCacheKey = `health:${owner}/${repo}
|
|
37133
|
+
const healthCacheKey = versionedCacheKey(`health:${owner}/${repo}`);
|
|
36858
37134
|
try {
|
|
36859
37135
|
return await cachedTimeBased(cache, healthCacheKey, HEALTH_CACHE_TTL_MS, async () => {
|
|
36860
37136
|
const url2 = `/repos/${owner}/${repo}`;
|
|
36861
37137
|
const repoData = await cachedRequest(cache, url2, (headers) => octokit.repos.get({ owner, repo, headers }));
|
|
36862
|
-
const
|
|
37138
|
+
const commitsUrl = `/repos/${owner}/${repo}/commits?per_page=1`;
|
|
37139
|
+
const commits = await cachedRequest(cache, commitsUrl, (headers) => octokit.repos.listCommits({
|
|
36863
37140
|
owner,
|
|
36864
37141
|
repo,
|
|
36865
|
-
per_page: 1
|
|
36866
|
-
|
|
37142
|
+
per_page: 1,
|
|
37143
|
+
headers
|
|
37144
|
+
}));
|
|
36867
37145
|
const lastCommit = commits[0];
|
|
36868
37146
|
const lastCommitAt = lastCommit?.commit?.author?.date || repoData.pushed_at;
|
|
36869
37147
|
const daysSinceLastCommit = daysBetween(new Date(lastCommitAt));
|
|
36870
37148
|
const ciStatus = "unknown";
|
|
37149
|
+
const mergeStats = await fetchRepoMergeStats(octokit, owner, repo, tracker);
|
|
36871
37150
|
return {
|
|
36872
37151
|
repo: `${owner}/${repo}`,
|
|
36873
37152
|
lastCommitAt,
|
|
@@ -36879,7 +37158,9 @@ async function checkProjectHealth(octokit, owner, repo) {
|
|
|
36879
37158
|
isActive: daysSinceLastCommit < 30,
|
|
36880
37159
|
stargazersCount: repoData.stargazers_count,
|
|
36881
37160
|
forksCount: repoData.forks_count,
|
|
36882
|
-
language: repoData.language
|
|
37161
|
+
language: repoData.language,
|
|
37162
|
+
recentMergedPRCount: mergeStats.recentMergedPRCount,
|
|
37163
|
+
recentMergeRate: mergeStats.recentMergeRate
|
|
36883
37164
|
};
|
|
36884
37165
|
});
|
|
36885
37166
|
} catch (error48) {
|
|
@@ -37184,8 +37465,133 @@ ${selections.join("\n")}
|
|
|
37184
37465
|
});
|
|
37185
37466
|
return result;
|
|
37186
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
|
+
}
|
|
37187
37592
|
|
|
37188
37593
|
// ../core/dist/core/slm-triage.js
|
|
37594
|
+
var MODULE9 = "slm-triage";
|
|
37189
37595
|
var DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434";
|
|
37190
37596
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
37191
37597
|
var MAX_BODY_CHARS = 2e3;
|
|
@@ -37222,10 +37628,48 @@ function buildPrompt(input) {
|
|
|
37222
37628
|
`Linked PR exists: ${linkedPRExists}`
|
|
37223
37629
|
].join("\n");
|
|
37224
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
|
+
}
|
|
37225
37665
|
async function triageWithSLM(input, options) {
|
|
37226
37666
|
if (!options.model)
|
|
37227
37667
|
return null;
|
|
37228
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
|
+
}
|
|
37229
37673
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
37230
37674
|
const fetchFn = options.fetchImpl ?? fetch;
|
|
37231
37675
|
let response;
|
|
@@ -37297,15 +37741,23 @@ function isValidTriageShape(value) {
|
|
|
37297
37741
|
}
|
|
37298
37742
|
|
|
37299
37743
|
// ../core/dist/core/issue-vetting.js
|
|
37300
|
-
var
|
|
37744
|
+
var MODULE10 = "issue-vetting";
|
|
37301
37745
|
var MAX_CONCURRENT_VETTING = 3;
|
|
37302
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
|
+
}
|
|
37303
37755
|
function deriveRecommendation(input) {
|
|
37304
37756
|
const notes = [];
|
|
37305
37757
|
const reasonsToApprove = [];
|
|
37306
37758
|
const reasonsToSkip = [];
|
|
37307
37759
|
if (!input.noExistingPR)
|
|
37308
|
-
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");
|
|
37309
37761
|
if (!input.notClaimed)
|
|
37310
37762
|
notes.push("Issue appears to be claimed by someone");
|
|
37311
37763
|
if (input.existingPRInconclusive) {
|
|
@@ -37323,14 +37775,28 @@ function deriveRecommendation(input) {
|
|
|
37323
37775
|
notes.push("Issue requirements are unclear");
|
|
37324
37776
|
if (!input.contributionGuidelinesFound)
|
|
37325
37777
|
notes.push("No CONTRIBUTING.md found");
|
|
37326
|
-
if (
|
|
37327
|
-
|
|
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
|
+
}
|
|
37328
37792
|
if (!input.notClaimed)
|
|
37329
37793
|
reasonsToSkip.push("Already claimed");
|
|
37330
37794
|
if (!input.projectIsActive && !input.projectCheckFailed)
|
|
37331
37795
|
reasonsToSkip.push("Inactive project");
|
|
37332
37796
|
if (!input.clearRequirements)
|
|
37333
37797
|
reasonsToSkip.push("Unclear requirements");
|
|
37798
|
+
if (input.repoAcceptsContributions === false)
|
|
37799
|
+
reasonsToSkip.push("Repo has no recent merged PRs");
|
|
37334
37800
|
if (input.noExistingPR)
|
|
37335
37801
|
reasonsToApprove.push("No existing PR");
|
|
37336
37802
|
if (input.notClaimed)
|
|
@@ -37356,16 +37822,18 @@ function deriveRecommendation(input) {
|
|
|
37356
37822
|
let recommendation;
|
|
37357
37823
|
if (input.issueClosed) {
|
|
37358
37824
|
recommendation = "skip";
|
|
37825
|
+
} else if (input.linkedPRMerged || input.linkedPRClosed) {
|
|
37826
|
+
recommendation = "skip";
|
|
37359
37827
|
} else if (input.ownPR) {
|
|
37360
37828
|
recommendation = "skip";
|
|
37361
|
-
} else if (input.passedAllChecks) {
|
|
37829
|
+
} else if (input.passedAllChecks && input.repoAcceptsContributions !== false) {
|
|
37362
37830
|
recommendation = "approve";
|
|
37363
37831
|
} else if (reasonsToSkip.length > 2) {
|
|
37364
37832
|
recommendation = "skip";
|
|
37365
37833
|
} else {
|
|
37366
37834
|
recommendation = "needs_review";
|
|
37367
37835
|
}
|
|
37368
|
-
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive;
|
|
37836
|
+
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive || input.repoAcceptsContributions === null;
|
|
37369
37837
|
if (recommendation === "approve" && hasInconclusiveChecks) {
|
|
37370
37838
|
recommendation = "needs_review";
|
|
37371
37839
|
notes.push("Recommendation downgraded: one or more checks were inconclusive");
|
|
@@ -37403,7 +37871,7 @@ var IssueVetter = class {
|
|
|
37403
37871
|
const cacheKey = versionedCacheKey(`vet:${issueUrl}${sigKey}`);
|
|
37404
37872
|
const cached2 = cache.getIfFresh(cacheKey, VETTING_CACHE_TTL_MS);
|
|
37405
37873
|
if (cached2 && typeof cached2 === "object" && "issue" in cached2 && "viabilityScore" in cached2) {
|
|
37406
|
-
debug(
|
|
37874
|
+
debug(MODULE10, `Vetting cache hit for ${issueUrl}`);
|
|
37407
37875
|
return cached2;
|
|
37408
37876
|
}
|
|
37409
37877
|
const parsed = parseGitHubUrl(issueUrl);
|
|
@@ -37418,7 +37886,7 @@ var IssueVetter = class {
|
|
|
37418
37886
|
const [existingPRCheck, claimCheck, projectHealth, contributionGuidelines, userMergedPRCount] = await Promise.all([
|
|
37419
37887
|
checkNoExistingPR(this.octokit, owner, repo, number4),
|
|
37420
37888
|
checkNotClaimed(this.octokit, owner, repo, number4, core.commentCount),
|
|
37421
|
-
checkProjectHealth(this.octokit, owner, repo),
|
|
37889
|
+
checkProjectHealth(this.octokit, owner, repo, this.budgetTracker),
|
|
37422
37890
|
fetchContributionGuidelines(this.octokit, owner, repo),
|
|
37423
37891
|
hasMergedPRsInRepo ? Promise.resolve(0) : checkUserMergedPRsInRepo(this.octokit, owner, repo, this.budgetTracker)
|
|
37424
37892
|
]);
|
|
@@ -37428,8 +37896,11 @@ var IssueVetter = class {
|
|
|
37428
37896
|
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
37429
37897
|
const linkedPR = existingPRCheck.linkedPR;
|
|
37430
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;
|
|
37431
37901
|
const clearRequirements = analyzeRequirements(core.body);
|
|
37432
37902
|
const projectActive = projectHealth.checkFailed ? true : projectHealth.isActive;
|
|
37903
|
+
const repoAcceptsContributions = repoAcceptsContributionsFromHealth(projectHealth);
|
|
37433
37904
|
const vettingResult = {
|
|
37434
37905
|
passedAllChecks: noExistingPR && notClaimed && projectActive && clearRequirements,
|
|
37435
37906
|
checks: {
|
|
@@ -37465,10 +37936,12 @@ var IssueVetter = class {
|
|
|
37465
37936
|
}
|
|
37466
37937
|
const matchesCategory = repoBelongsToCategory(repoFullName, this.stateReader.getProjectCategories());
|
|
37467
37938
|
const issueClosed = core.state === "closed";
|
|
37468
|
-
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive;
|
|
37939
|
+
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive || repoAcceptsContributions === null;
|
|
37469
37940
|
const { notes, reasonsToApprove, reasonsToSkip, recommendation } = deriveRecommendation({
|
|
37470
37941
|
noExistingPR,
|
|
37471
37942
|
ownPR,
|
|
37943
|
+
linkedPRMerged,
|
|
37944
|
+
linkedPRClosed,
|
|
37472
37945
|
notClaimed,
|
|
37473
37946
|
clearRequirements,
|
|
37474
37947
|
contributionGuidelinesFound: !!contributionGuidelines,
|
|
@@ -37485,7 +37958,8 @@ var IssueVetter = class {
|
|
|
37485
37958
|
orgHasMergedPRs,
|
|
37486
37959
|
matchesCategory,
|
|
37487
37960
|
issueClosed,
|
|
37488
|
-
passedAllChecks: vettingResult.passedAllChecks
|
|
37961
|
+
passedAllChecks: vettingResult.passedAllChecks,
|
|
37962
|
+
repoAcceptsContributions
|
|
37489
37963
|
});
|
|
37490
37964
|
vettingResult.notes = notes;
|
|
37491
37965
|
const repoQualityBonus = projectHealth.checkFailed ? 0 : calculateRepoQualityBonus(projectHealth.stargazersCount ?? 0, projectHealth.forksCount ?? 0);
|
|
@@ -37565,6 +38039,36 @@ var IssueVetter = class {
|
|
|
37565
38039
|
updatedAt: ghIssue.updated_at
|
|
37566
38040
|
};
|
|
37567
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
|
+
}
|
|
37568
38072
|
/**
|
|
37569
38073
|
* Vet multiple issues in parallel with concurrency limit
|
|
37570
38074
|
*/
|
|
@@ -37576,10 +38080,14 @@ var IssueVetter = class {
|
|
|
37576
38080
|
let attemptedCount = 0;
|
|
37577
38081
|
let firstAuthError = null;
|
|
37578
38082
|
const uniqueUrls = [...new Set(urls)];
|
|
37579
|
-
const
|
|
38083
|
+
const issueRefs = uniqueUrls.flatMap((url2) => {
|
|
37580
38084
|
const p = parseGitHubUrl(url2);
|
|
37581
38085
|
return p && p.type === "issues" ? [{ owner: p.owner, repo: p.repo, number: p.number }] : [];
|
|
37582
|
-
})
|
|
38086
|
+
});
|
|
38087
|
+
const [prefetched] = await Promise.all([
|
|
38088
|
+
prefetchIssueCores(this.octokit, issueRefs),
|
|
38089
|
+
this.prefetchMergedPRCounts(issueRefs)
|
|
38090
|
+
]);
|
|
37583
38091
|
const prefetchFor = (url2) => {
|
|
37584
38092
|
const p = parseGitHubUrl(url2);
|
|
37585
38093
|
return p && p.type === "issues" ? prefetched.get(issueCoreKey(p.owner, p.repo, p.number)) : void 0;
|
|
@@ -37599,7 +38107,7 @@ var IssueVetter = class {
|
|
|
37599
38107
|
candidates.push(candidate);
|
|
37600
38108
|
}
|
|
37601
38109
|
}).catch((error48) => {
|
|
37602
|
-
if (
|
|
38110
|
+
if (isAuthError(error48)) {
|
|
37603
38111
|
firstAuthError ??= error48;
|
|
37604
38112
|
return;
|
|
37605
38113
|
}
|
|
@@ -37607,7 +38115,7 @@ var IssueVetter = class {
|
|
|
37607
38115
|
if (isRateLimitError(error48)) {
|
|
37608
38116
|
rateLimitFailures++;
|
|
37609
38117
|
}
|
|
37610
|
-
warn(
|
|
38118
|
+
warn(MODULE10, `Error vetting issue ${url2}:`, errorMessage(error48));
|
|
37611
38119
|
}).finally(() => pending.delete(url2));
|
|
37612
38120
|
pending.set(url2, task);
|
|
37613
38121
|
if (pending.size >= MAX_CONCURRENT_VETTING) {
|
|
@@ -37617,13 +38125,13 @@ var IssueVetter = class {
|
|
|
37617
38125
|
await Promise.allSettled(pending.values());
|
|
37618
38126
|
if (firstAuthError) {
|
|
37619
38127
|
if (candidates.length > 0) {
|
|
37620
|
-
warn(
|
|
38128
|
+
warn(MODULE10, `Auth failed mid-batch after ${candidates.length} successful vet(s) \u2014 discarding partial results`);
|
|
37621
38129
|
}
|
|
37622
38130
|
throw firstAuthError;
|
|
37623
38131
|
}
|
|
37624
38132
|
const allFailed = failedVettingCount === attemptedCount && attemptedCount > 0;
|
|
37625
38133
|
if (allFailed) {
|
|
37626
|
-
warn(
|
|
38134
|
+
warn(MODULE10, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37627
38135
|
}
|
|
37628
38136
|
return {
|
|
37629
38137
|
candidates: candidates.slice(0, maxResults),
|
|
@@ -37634,14 +38142,22 @@ var IssueVetter = class {
|
|
|
37634
38142
|
};
|
|
37635
38143
|
|
|
37636
38144
|
// ../core/dist/core/search-phases.js
|
|
37637
|
-
var
|
|
38145
|
+
var MODULE11 = "search-phases";
|
|
37638
38146
|
var GITHUB_MAX_BOOLEAN_OPS = 5;
|
|
37639
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
|
+
}
|
|
37640
38156
|
function chunkLabels(labels, reservedOps = 0) {
|
|
37641
38157
|
const maxPerChunk = GITHUB_MAX_BOOLEAN_OPS - reservedOps + 1;
|
|
37642
38158
|
if (maxPerChunk < 1) {
|
|
37643
38159
|
if (labels.length > 0) {
|
|
37644
|
-
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.`);
|
|
37645
38161
|
}
|
|
37646
38162
|
return [[]];
|
|
37647
38163
|
}
|
|
@@ -37651,7 +38167,7 @@ function chunkLabels(labels, reservedOps = 0) {
|
|
|
37651
38167
|
for (let i = 0; i < labels.length; i += maxPerChunk) {
|
|
37652
38168
|
chunks.push(labels.slice(i, i + maxPerChunk));
|
|
37653
38169
|
}
|
|
37654
|
-
debug(
|
|
38170
|
+
debug(MODULE11, `Split ${labels.length} labels into ${chunks.length} chunks (${reservedOps} ops reserved, max ${maxPerChunk} per chunk)`);
|
|
37655
38171
|
return chunks;
|
|
37656
38172
|
}
|
|
37657
38173
|
function buildLabelQuery(labels) {
|
|
@@ -37688,7 +38204,7 @@ async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTrac
|
|
|
37688
38204
|
const cache = getHttpCache();
|
|
37689
38205
|
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
37690
38206
|
if (cached2) {
|
|
37691
|
-
debug(
|
|
38207
|
+
debug(MODULE11, `Search cache hit for query`);
|
|
37692
38208
|
return cached2;
|
|
37693
38209
|
}
|
|
37694
38210
|
await tracker.waitForBudget();
|
|
@@ -37702,10 +38218,32 @@ async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTrac
|
|
|
37702
38218
|
if (data.items.length > 0) {
|
|
37703
38219
|
cache.set(cacheKey, "", data);
|
|
37704
38220
|
} else {
|
|
37705
|
-
debug(
|
|
38221
|
+
debug(MODULE11, `Skipping cache for empty search result (possible rate limit artifact)`);
|
|
37706
38222
|
}
|
|
37707
38223
|
return data;
|
|
37708
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
|
+
}
|
|
37709
38247
|
async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResults) {
|
|
37710
38248
|
const items = [];
|
|
37711
38249
|
for (const repoFullName of repos) {
|
|
@@ -37750,15 +38288,15 @@ async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResul
|
|
|
37750
38288
|
if (getHttpStatusCode(error48) === 401)
|
|
37751
38289
|
throw error48;
|
|
37752
38290
|
if (isRateLimitError(error48)) {
|
|
37753
|
-
warn(
|
|
38291
|
+
warn(MODULE11, `Rate limit hit fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37754
38292
|
break;
|
|
37755
38293
|
}
|
|
37756
|
-
warn(
|
|
38294
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37757
38295
|
}
|
|
37758
38296
|
}
|
|
37759
38297
|
return items;
|
|
37760
38298
|
}
|
|
37761
|
-
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn) {
|
|
38299
|
+
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn, perPage = 5) {
|
|
37762
38300
|
const candidates = [];
|
|
37763
38301
|
let failedRepos = 0;
|
|
37764
38302
|
let rateLimitFailures = 0;
|
|
@@ -37780,7 +38318,7 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37780
38318
|
state: "open",
|
|
37781
38319
|
sort: "created",
|
|
37782
38320
|
direction: "desc",
|
|
37783
|
-
per_page:
|
|
38321
|
+
per_page: perPage,
|
|
37784
38322
|
...label !== void 0 ? { labels: label } : {}
|
|
37785
38323
|
});
|
|
37786
38324
|
for (const issue2 of response.data) {
|
|
@@ -37815,13 +38353,13 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37815
38353
|
if (isRateLimitError(error48)) {
|
|
37816
38354
|
rateLimitFailures++;
|
|
37817
38355
|
}
|
|
37818
|
-
warn(
|
|
38356
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37819
38357
|
}
|
|
37820
38358
|
}
|
|
37821
38359
|
const allReposFailed = failedRepos === repos.length && repos.length > 0;
|
|
37822
38360
|
const rateLimitHit = rateLimitFailures > 0;
|
|
37823
38361
|
if (allReposFailed) {
|
|
37824
|
-
warn(
|
|
38362
|
+
warn(MODULE11, `All ${repos.length} repo(s) failed for ${priority} phase. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37825
38363
|
}
|
|
37826
38364
|
return { candidates, allReposFailed, rateLimitHit };
|
|
37827
38365
|
}
|
|
@@ -37831,9 +38369,9 @@ async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery,
|
|
|
37831
38369
|
const allItems = [];
|
|
37832
38370
|
for (let i = 0; i < labelChunks.length; i++) {
|
|
37833
38371
|
if (i > 0)
|
|
37834
|
-
await sleep(
|
|
38372
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
37835
38373
|
const query = buildQuery(buildLabelQuery(labelChunks[i]));
|
|
37836
|
-
const data = await
|
|
38374
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
37837
38375
|
q: query,
|
|
37838
38376
|
sort: "created",
|
|
37839
38377
|
order: "desc",
|
|
@@ -37857,13 +38395,15 @@ function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
|
|
|
37857
38395
|
return [languages.map((l) => `language:${l}`).join(" ")];
|
|
37858
38396
|
return languages.map((l) => `language:${l}`);
|
|
37859
38397
|
}
|
|
37860
|
-
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker()) {
|
|
37861
|
-
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)];
|
|
37862
38402
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
37863
38403
|
const allItems = [];
|
|
37864
38404
|
for (let i = 0; i < langVariants.length; i++) {
|
|
37865
38405
|
if (i > 0)
|
|
37866
|
-
await sleep(
|
|
38406
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
37867
38407
|
const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage, tracker);
|
|
37868
38408
|
for (const item of items) {
|
|
37869
38409
|
if (!seenUrls.has(item.html_url)) {
|
|
@@ -37878,7 +38418,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37878
38418
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
37879
38419
|
if (spamRepos.size > 0) {
|
|
37880
38420
|
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
37881
|
-
debug(
|
|
38421
|
+
debug(MODULE11, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
37882
38422
|
}
|
|
37883
38423
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
37884
38424
|
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
@@ -37889,7 +38429,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37889
38429
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
37890
38430
|
}).slice(0, remainingNeeded * 2);
|
|
37891
38431
|
if (itemsToVet.length === 0) {
|
|
37892
|
-
debug(
|
|
38432
|
+
debug(MODULE11, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
|
|
37893
38433
|
return { candidates: [], allVetFailed: false, rateLimitHit: false };
|
|
37894
38434
|
}
|
|
37895
38435
|
const { candidates: results, allFailed: allVetFailed, rateLimitHit } = await vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
|
|
@@ -37901,7 +38441,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37901
38441
|
});
|
|
37902
38442
|
const starFilteredCount = results.length - starFiltered.length;
|
|
37903
38443
|
if (starFilteredCount > 0) {
|
|
37904
|
-
debug(
|
|
38444
|
+
debug(MODULE11, `[STAR_FILTER] Filtered ${starFilteredCount} ${phaseLabel} candidates below ${minStars} stars`);
|
|
37905
38445
|
}
|
|
37906
38446
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
37907
38447
|
}
|
|
@@ -37994,9 +38534,12 @@ function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
|
37994
38534
|
}
|
|
37995
38535
|
|
|
37996
38536
|
// ../core/dist/core/issue-discovery.js
|
|
37997
|
-
var
|
|
38537
|
+
var MODULE12 = "issue-discovery";
|
|
37998
38538
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
37999
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;
|
|
38000
38543
|
function buildIssueFilter(config2) {
|
|
38001
38544
|
return (items) => {
|
|
38002
38545
|
return items.filter((item) => {
|
|
@@ -38018,6 +38561,8 @@ function buildIssueFilter(config2) {
|
|
|
38018
38561
|
if (config2.skippedUrls.has(item.html_url))
|
|
38019
38562
|
return false;
|
|
38020
38563
|
const updatedAt = new Date(item.updated_at);
|
|
38564
|
+
if (Number.isNaN(updatedAt.getTime()))
|
|
38565
|
+
return false;
|
|
38021
38566
|
const ageDays = daysBetween(updatedAt, config2.now);
|
|
38022
38567
|
if (ageDays > config2.maxAgeDays)
|
|
38023
38568
|
return false;
|
|
@@ -38028,9 +38573,9 @@ function buildIssueFilter(config2) {
|
|
|
38028
38573
|
};
|
|
38029
38574
|
}
|
|
38030
38575
|
async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
38031
|
-
info(
|
|
38032
|
-
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues);
|
|
38033
|
-
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`);
|
|
38034
38579
|
return {
|
|
38035
38580
|
candidates,
|
|
38036
38581
|
error: allReposFailed ? "All merged-PR repo fetches failed" : null,
|
|
@@ -38038,26 +38583,26 @@ async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
|
38038
38583
|
};
|
|
38039
38584
|
}
|
|
38040
38585
|
async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
|
|
38041
|
-
info(
|
|
38586
|
+
info(MODULE12, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
38042
38587
|
const phase1Labels = labels.slice(0, 3);
|
|
38043
38588
|
const reposToSearch = repos.slice(0, 10);
|
|
38044
38589
|
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
|
|
38045
|
-
info(
|
|
38590
|
+
info(MODULE12, `Found ${candidates.length} candidates from starred repos`);
|
|
38046
38591
|
return {
|
|
38047
38592
|
candidates,
|
|
38048
38593
|
error: allReposFailed ? "All starred repo fetches failed" : null,
|
|
38049
38594
|
rateLimitHit
|
|
38050
38595
|
};
|
|
38051
38596
|
}
|
|
38052
|
-
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues, tracker) {
|
|
38053
|
-
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...");
|
|
38054
38599
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38055
38600
|
const tierLabelGroups = [];
|
|
38056
38601
|
if (scopes && scopes.length > 1) {
|
|
38057
38602
|
for (const scope of scopes) {
|
|
38058
38603
|
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
38059
38604
|
if (scopeLabels.length === 0) {
|
|
38060
|
-
warn(
|
|
38605
|
+
warn(MODULE12, `Scope "${scope}" has no labels, skipping tier`);
|
|
38061
38606
|
continue;
|
|
38062
38607
|
}
|
|
38063
38608
|
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
@@ -38076,8 +38621,8 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38076
38621
|
let rateLimitHit = false;
|
|
38077
38622
|
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
38078
38623
|
try {
|
|
38079
|
-
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3, tracker);
|
|
38080
|
-
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...`);
|
|
38081
38626
|
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
38082
38627
|
tierResults.push(tierCandidates);
|
|
38083
38628
|
for (const c of tierCandidates)
|
|
@@ -38088,7 +38633,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38088
38633
|
if (vetRateLimitHit) {
|
|
38089
38634
|
rateLimitHit = true;
|
|
38090
38635
|
}
|
|
38091
|
-
info(
|
|
38636
|
+
info(MODULE12, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
38092
38637
|
} catch (err) {
|
|
38093
38638
|
if (getHttpStatusCode(err) === 401)
|
|
38094
38639
|
throw err;
|
|
@@ -38097,13 +38642,13 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38097
38642
|
if (isRateLimitError(err)) {
|
|
38098
38643
|
rateLimitHit = true;
|
|
38099
38644
|
}
|
|
38100
|
-
warn(
|
|
38645
|
+
warn(MODULE12, `Error in ${tier} tier search: ${errMsg}`);
|
|
38101
38646
|
tierResults.push([]);
|
|
38102
38647
|
}
|
|
38103
38648
|
}
|
|
38104
38649
|
const interleaved = interleaveArrays(tierResults);
|
|
38105
38650
|
if (interleaved.length === 0 && error48) {
|
|
38106
|
-
warn(
|
|
38651
|
+
warn(MODULE12, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${error48}`);
|
|
38107
38652
|
}
|
|
38108
38653
|
return {
|
|
38109
38654
|
candidates: interleaved.slice(0, maxResults),
|
|
@@ -38112,17 +38657,17 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38112
38657
|
};
|
|
38113
38658
|
}
|
|
38114
38659
|
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker) {
|
|
38115
|
-
info(
|
|
38660
|
+
info(MODULE12, "Phase 3: Searching actively maintained repos...");
|
|
38116
38661
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38117
38662
|
const eligibleStarred = starredRepos.filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
|
|
38118
38663
|
if (eligibleStarred.length > 0) {
|
|
38119
|
-
info(
|
|
38664
|
+
info(MODULE12, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
|
|
38120
38665
|
const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred.slice(0, 15), minStars, maxResults);
|
|
38121
38666
|
if (restItems.length > 0) {
|
|
38122
38667
|
try {
|
|
38123
38668
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
|
|
38124
38669
|
if (candidates.length > 0) {
|
|
38125
|
-
info(
|
|
38670
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo REST search`);
|
|
38126
38671
|
return {
|
|
38127
38672
|
candidates,
|
|
38128
38673
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38132,11 +38677,11 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38132
38677
|
} catch (error48) {
|
|
38133
38678
|
if (getHttpStatusCode(error48) === 401)
|
|
38134
38679
|
throw error48;
|
|
38135
|
-
warn(
|
|
38680
|
+
warn(MODULE12, `Phase 3 REST vetting failed, falling back to Search API:`, errorMessage(error48));
|
|
38136
38681
|
}
|
|
38137
38682
|
}
|
|
38138
38683
|
}
|
|
38139
|
-
info(
|
|
38684
|
+
info(MODULE12, "Phase 3: Falling back to Search API...");
|
|
38140
38685
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
38141
38686
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
38142
38687
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
@@ -38144,15 +38689,15 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38144
38689
|
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
38145
38690
|
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
38146
38691
|
try {
|
|
38147
|
-
const data = await
|
|
38692
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
38148
38693
|
q: phase3Query,
|
|
38149
38694
|
sort: "updated",
|
|
38150
38695
|
order: "desc",
|
|
38151
38696
|
per_page: maxResults * 3
|
|
38152
38697
|
}, tracker);
|
|
38153
|
-
info(
|
|
38698
|
+
info(MODULE12, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
38154
38699
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
38155
|
-
info(
|
|
38700
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
38156
38701
|
return {
|
|
38157
38702
|
candidates,
|
|
38158
38703
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38162,7 +38707,7 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38162
38707
|
if (getHttpStatusCode(error48) === 401)
|
|
38163
38708
|
throw error48;
|
|
38164
38709
|
const errMsg = errorMessage(error48);
|
|
38165
|
-
warn(
|
|
38710
|
+
warn(MODULE12, `Error in maintained-repo search: ${errMsg}`);
|
|
38166
38711
|
return {
|
|
38167
38712
|
candidates: [],
|
|
38168
38713
|
error: errMsg,
|
|
@@ -38246,7 +38791,7 @@ var IssueDiscovery = class {
|
|
|
38246
38791
|
let rateLimitHitDuringSearch = false;
|
|
38247
38792
|
const applyInterPhaseDelay = async () => {
|
|
38248
38793
|
if (interPhaseDelay > 0) {
|
|
38249
|
-
info(
|
|
38794
|
+
info(MODULE12, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
38250
38795
|
await sleep(interPhaseDelay);
|
|
38251
38796
|
}
|
|
38252
38797
|
};
|
|
@@ -38256,9 +38801,11 @@ var IssueDiscovery = class {
|
|
|
38256
38801
|
if (result.rateLimitHit)
|
|
38257
38802
|
rateLimitHitDuringSearch = true;
|
|
38258
38803
|
};
|
|
38804
|
+
const viableCandidateCount = () => allCandidates.filter((c) => c.recommendation !== "skip").length;
|
|
38259
38805
|
this.rateLimitWarning = null;
|
|
38806
|
+
resetGraphQLSearchQueryCount();
|
|
38260
38807
|
const tracker = this.budgetTracker;
|
|
38261
|
-
let searchBudget =
|
|
38808
|
+
let searchBudget = CRITICAL_BUDGET_THRESHOLD - 1;
|
|
38262
38809
|
try {
|
|
38263
38810
|
const rateLimit = await checkRateLimit(this.githubToken);
|
|
38264
38811
|
searchBudget = rateLimit.remaining;
|
|
@@ -38266,22 +38813,22 @@ var IssueDiscovery = class {
|
|
|
38266
38813
|
if (rateLimit.remaining < 5) {
|
|
38267
38814
|
const resetTime = new Date(rateLimit.resetAt).toLocaleTimeString("en-US", { hour12: false });
|
|
38268
38815
|
this.rateLimitWarning = `GitHub search API quota low (${rateLimit.remaining}/${rateLimit.limit} remaining, resets at ${resetTime}). Search may be slow.`;
|
|
38269
|
-
warn(
|
|
38816
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38270
38817
|
}
|
|
38271
38818
|
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
38272
|
-
info(
|
|
38819
|
+
info(MODULE12, `Search budget critical (${searchBudget} remaining) \u2014 skipping the starred phase; broad/maintained still run on GraphQL (points bucket)`);
|
|
38273
38820
|
} else if (searchBudget < LOW_BUDGET_THRESHOLD) {
|
|
38274
|
-
info(
|
|
38821
|
+
info(MODULE12, `Search budget low (${searchBudget} remaining) \u2014 broad/maintained run on GraphQL (points bucket), unaffected by REST search quota`);
|
|
38275
38822
|
}
|
|
38276
38823
|
} catch (error48) {
|
|
38277
38824
|
if (getHttpStatusCode(error48) === 401)
|
|
38278
38825
|
throw error48;
|
|
38279
38826
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
38280
|
-
warn(
|
|
38827
|
+
warn(MODULE12, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error48));
|
|
38281
38828
|
}
|
|
38282
38829
|
if (searchBudget <= 0) {
|
|
38283
|
-
this.rateLimitWarning = "GitHub search API quota exhausted
|
|
38284
|
-
|
|
38830
|
+
this.rateLimitWarning = "GitHub search API quota exhausted \u2014 skipping the starred phase; other phases don't use it.";
|
|
38831
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38285
38832
|
}
|
|
38286
38833
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
38287
38834
|
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
@@ -38292,17 +38839,24 @@ var IssueDiscovery = class {
|
|
|
38292
38839
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
38293
38840
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist.map((r) => r.toLowerCase()));
|
|
38294
38841
|
if (aiBlocklisted.size > 0) {
|
|
38295
|
-
debug(
|
|
38842
|
+
debug(MODULE12, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
38296
38843
|
}
|
|
38297
|
-
const
|
|
38844
|
+
const baseFilterConfig = {
|
|
38298
38845
|
excludedRepos: new Set(config2.excludeRepos.map((r) => r.toLowerCase())),
|
|
38299
38846
|
excludeOrgs: new Set((config2.excludeOrgs ?? []).map((o) => o.toLowerCase())),
|
|
38300
38847
|
aiBlocklisted,
|
|
38301
38848
|
lowScoringRepos,
|
|
38302
38849
|
skippedUrls: options.skippedUrls ?? /* @__PURE__ */ new Set(),
|
|
38303
|
-
maxAgeDays: config2.maxIssueAgeDays || 90,
|
|
38304
38850
|
now: /* @__PURE__ */ new Date(),
|
|
38305
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
|
|
38306
38860
|
});
|
|
38307
38861
|
const seenPhase0 = /* @__PURE__ */ new Set();
|
|
38308
38862
|
const phase0Repos = [];
|
|
@@ -38315,10 +38869,12 @@ var IssueDiscovery = class {
|
|
|
38315
38869
|
break;
|
|
38316
38870
|
}
|
|
38317
38871
|
const phase0RepoSet = new Set(phase0Repos);
|
|
38872
|
+
const otherStrategiesCanRun = starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
|
|
38318
38873
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
38319
|
-
const
|
|
38874
|
+
const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
|
|
38875
|
+
const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
|
|
38320
38876
|
if (remaining > 0) {
|
|
38321
|
-
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining,
|
|
38877
|
+
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
|
|
38322
38878
|
recordPhaseResult("0", result);
|
|
38323
38879
|
}
|
|
38324
38880
|
strategiesUsed.push("merged");
|
|
@@ -38338,37 +38894,39 @@ var IssueDiscovery = class {
|
|
|
38338
38894
|
const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 9e4;
|
|
38339
38895
|
const configuredSkipThreshold = config2.skipBroadWhenSufficientResults ?? 8;
|
|
38340
38896
|
const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
|
|
38341
|
-
|
|
38342
|
-
|
|
38343
|
-
|
|
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})`);
|
|
38344
38904
|
} else {
|
|
38345
38905
|
await applyInterPhaseDelay();
|
|
38346
38906
|
if (allCandidates.length > 0 && broadDelay > 0) {
|
|
38347
|
-
info(
|
|
38907
|
+
info(MODULE12, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
|
|
38348
38908
|
await sleep(broadDelay);
|
|
38349
38909
|
} else if (allCandidates.length === 0) {
|
|
38350
|
-
info(
|
|
38910
|
+
info(MODULE12, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
38351
38911
|
}
|
|
38352
|
-
const remaining = maxResults -
|
|
38353
|
-
const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues, tracker);
|
|
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);
|
|
38354
38914
|
recordPhaseResult("2", result);
|
|
38355
38915
|
strategiesUsed.push("broad");
|
|
38356
38916
|
}
|
|
38357
38917
|
}
|
|
38358
|
-
|
|
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")) {
|
|
38359
38922
|
await applyInterPhaseDelay();
|
|
38360
|
-
const remaining = maxResults -
|
|
38923
|
+
const remaining = maxResults - viableBeforeMaintained;
|
|
38361
38924
|
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues, tracker);
|
|
38362
38925
|
recordPhaseResult("3", result);
|
|
38363
38926
|
strategiesUsed.push("maintained");
|
|
38364
38927
|
}
|
|
38365
|
-
const phasesSkippedForBudget = searchBudget <
|
|
38366
|
-
|
|
38367
|
-
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
38368
|
-
budgetNote = ` Most search phases were skipped due to critically low API quota (${searchBudget} remaining).`;
|
|
38369
|
-
} else if (phasesSkippedForBudget) {
|
|
38370
|
-
budgetNote = ` Some search phases were skipped due to low API quota (${searchBudget} remaining).`;
|
|
38371
|
-
}
|
|
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.` : "";
|
|
38372
38930
|
if (allCandidates.length === 0) {
|
|
38373
38931
|
const errorDetails = [
|
|
38374
38932
|
phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
|
|
@@ -38381,7 +38939,7 @@ var IssueDiscovery = class {
|
|
|
38381
38939
|
this.rateLimitWarning = `Search returned no results due to GitHub API rate limits.${details}${budgetNote} Try again after the rate limit resets.`;
|
|
38382
38940
|
return { candidates: [], strategiesUsed };
|
|
38383
38941
|
}
|
|
38384
|
-
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);
|
|
38385
38943
|
}
|
|
38386
38944
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
38387
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.`;
|
|
@@ -38410,9 +38968,13 @@ var IssueDiscovery = class {
|
|
|
38410
38968
|
return boostDiff;
|
|
38411
38969
|
return b.viabilityScore - a.viabilityScore;
|
|
38412
38970
|
});
|
|
38413
|
-
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);
|
|
38414
38976
|
const finalPicks = applyDiversityRatio(capped, maxResults, options.diversityRatio ?? 0);
|
|
38415
|
-
info(
|
|
38977
|
+
info(MODULE12, `Search complete: ${tracker.getTotalCalls()} REST Search API call(s) + ${getGraphQLSearchQueryCount()} GraphQL broad-search query/queries used, ${finalPicks.length} candidates returned`);
|
|
38416
38978
|
return { candidates: finalPicks, strategiesUsed };
|
|
38417
38979
|
}
|
|
38418
38980
|
/**
|
|
@@ -38464,8 +39026,8 @@ function pruneCache2() {
|
|
|
38464
39026
|
function parseRoadmapIssueRefs(content, owner, repo) {
|
|
38465
39027
|
const refs = /* @__PURE__ */ new Set();
|
|
38466
39028
|
const ownerRepoLower = `${owner}/${repo}`.toLowerCase();
|
|
38467
|
-
const
|
|
38468
|
-
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");
|
|
38469
39031
|
for (const m of content.matchAll(fullRefPattern)) {
|
|
38470
39032
|
const n = Number.parseInt(m[1], 10);
|
|
38471
39033
|
if (n > 0)
|
|
@@ -38527,7 +39089,7 @@ async function fetchRoadmapIssueRefsUncached(octokit, owner, repo, cacheKey) {
|
|
|
38527
39089
|
}
|
|
38528
39090
|
|
|
38529
39091
|
// ../core/dist/core/feature-discovery.js
|
|
38530
|
-
var
|
|
39092
|
+
var MODULE13 = "feature-discovery";
|
|
38531
39093
|
var INTER_REPO_DELAY_MS = 2e3;
|
|
38532
39094
|
var MIN_VIABILITY_SCORE = 40;
|
|
38533
39095
|
var ANCHOR_THRESHOLD = 3;
|
|
@@ -38640,7 +39202,7 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
|
38640
39202
|
});
|
|
38641
39203
|
} catch (err) {
|
|
38642
39204
|
rethrowIfFatal(err);
|
|
38643
|
-
warn(
|
|
39205
|
+
warn(MODULE13, `vet failed for ${item.html_url}: ${errorMessage(err)}`);
|
|
38644
39206
|
return null;
|
|
38645
39207
|
}
|
|
38646
39208
|
const horizon = classifyHorizon({
|
|
@@ -38683,7 +39245,7 @@ async function discoverFeatures(opts) {
|
|
|
38683
39245
|
roadmapRefs = refs;
|
|
38684
39246
|
} catch (err) {
|
|
38685
39247
|
rethrowIfFatal(err);
|
|
38686
|
-
warn(
|
|
39248
|
+
warn(MODULE13, `failed to list issues for ${anchorRepos[i]}: ${errorMessage(err)}`);
|
|
38687
39249
|
continue;
|
|
38688
39250
|
}
|
|
38689
39251
|
const items = response.data.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it));
|
|
@@ -38761,7 +39323,7 @@ async function discoverFeaturesBroad(opts) {
|
|
|
38761
39323
|
items = merged.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it)).slice(0, maxToVet);
|
|
38762
39324
|
} catch (err) {
|
|
38763
39325
|
rethrowIfFatal(err);
|
|
38764
|
-
warn(
|
|
39326
|
+
warn(MODULE13, `broad feature search failed: ${errorMessage(err)}`);
|
|
38765
39327
|
return {
|
|
38766
39328
|
quickWins: [],
|
|
38767
39329
|
biggerBets: [],
|
|
@@ -38788,7 +39350,7 @@ async function discoverFeaturesBroad(opts) {
|
|
|
38788
39350
|
// ../core/dist/core/gist-state-store.js
|
|
38789
39351
|
var fs3 = __toESM(require("fs"), 1);
|
|
38790
39352
|
var path3 = __toESM(require("path"), 1);
|
|
38791
|
-
var
|
|
39353
|
+
var MODULE14 = "gist-state";
|
|
38792
39354
|
var GIST_DESCRIPTION = "oss-scout-state";
|
|
38793
39355
|
var GIST_FILENAME = "state.json";
|
|
38794
39356
|
var GIST_ID_FILE = "gist-id";
|
|
@@ -38833,7 +39395,7 @@ var GistStateStore = class {
|
|
|
38833
39395
|
} catch (err) {
|
|
38834
39396
|
if (getHttpStatusCode(err) === 401)
|
|
38835
39397
|
throw err;
|
|
38836
|
-
warn(
|
|
39398
|
+
warn(MODULE14, `API bootstrap failed: ${errorMessage(err)}`);
|
|
38837
39399
|
return this.bootstrapFromCache(classifyDegradedReason(err));
|
|
38838
39400
|
}
|
|
38839
39401
|
}
|
|
@@ -38842,24 +39404,28 @@ var GistStateStore = class {
|
|
|
38842
39404
|
*/
|
|
38843
39405
|
async push(state) {
|
|
38844
39406
|
if (!this.gistId) {
|
|
38845
|
-
warn(
|
|
39407
|
+
warn(MODULE14, "No gist ID \u2014 cannot push");
|
|
38846
39408
|
this.writeCache(state);
|
|
38847
39409
|
return false;
|
|
38848
39410
|
}
|
|
38849
39411
|
let toWrite = state;
|
|
38850
39412
|
try {
|
|
38851
39413
|
const remote = await this.fetchGistState(this.gistId);
|
|
38852
|
-
if (remote) {
|
|
38853
|
-
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;
|
|
38854
39420
|
}
|
|
38855
39421
|
} catch (err) {
|
|
38856
39422
|
rethrowIfFatal(err);
|
|
38857
|
-
warn(
|
|
39423
|
+
warn(MODULE14, `Could not fetch gist before push, writing local snapshot: ${errorMessage(err)}`);
|
|
38858
39424
|
}
|
|
38859
39425
|
this.writeCache(toWrite);
|
|
38860
39426
|
const json2 = JSON.stringify(toWrite, null, 2);
|
|
38861
39427
|
if (json2.length > 9e5) {
|
|
38862
|
-
warn(
|
|
39428
|
+
warn(MODULE14, `State too large for gist (${Math.round(json2.length / 1024)}KB). Consider clearing old results with 'oss-scout results clear'.`);
|
|
38863
39429
|
return false;
|
|
38864
39430
|
}
|
|
38865
39431
|
try {
|
|
@@ -38869,11 +39435,11 @@ var GistStateStore = class {
|
|
|
38869
39435
|
[GIST_FILENAME]: { content: json2 }
|
|
38870
39436
|
}
|
|
38871
39437
|
});
|
|
38872
|
-
debug(
|
|
39438
|
+
debug(MODULE14, "State pushed to gist");
|
|
38873
39439
|
return true;
|
|
38874
39440
|
} catch (err) {
|
|
38875
39441
|
rethrowIfFatal(err);
|
|
38876
|
-
warn(
|
|
39442
|
+
warn(MODULE14, `Failed to push: ${errorMessage(err)}`);
|
|
38877
39443
|
return false;
|
|
38878
39444
|
}
|
|
38879
39445
|
}
|
|
@@ -38885,40 +39451,40 @@ var GistStateStore = class {
|
|
|
38885
39451
|
async bootstrapFromApi() {
|
|
38886
39452
|
const cachedId = this.readCachedGistId();
|
|
38887
39453
|
if (cachedId) {
|
|
38888
|
-
debug(
|
|
39454
|
+
debug(MODULE14, `Trying cached gist ID: ${cachedId}`);
|
|
38889
39455
|
try {
|
|
38890
39456
|
const fetched = await this.fetchGistState(cachedId);
|
|
38891
|
-
if (fetched) {
|
|
39457
|
+
if (fetched.kind === "ok") {
|
|
38892
39458
|
this.gistId = cachedId;
|
|
38893
|
-
const state = this.mergeCacheInto(fetched);
|
|
39459
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38894
39460
|
this.writeCache(state);
|
|
38895
39461
|
return { gistId: cachedId, state, created: false };
|
|
38896
39462
|
}
|
|
38897
39463
|
} catch (err) {
|
|
38898
39464
|
if (getHttpStatusCode(err) !== 404)
|
|
38899
39465
|
throw err;
|
|
38900
|
-
debug(
|
|
39466
|
+
debug(MODULE14, `Cached gist gone (404): ${errorMessage(err)}`);
|
|
38901
39467
|
}
|
|
38902
|
-
debug(
|
|
39468
|
+
debug(MODULE14, "Cached gist ID invalid, searching...");
|
|
38903
39469
|
}
|
|
38904
39470
|
const search = await this.searchForGist();
|
|
38905
39471
|
if (search.id) {
|
|
38906
|
-
debug(
|
|
39472
|
+
debug(MODULE14, `Found gist via search: ${search.id}`);
|
|
38907
39473
|
this.saveGistId(search.id);
|
|
38908
39474
|
this.gistId = search.id;
|
|
38909
39475
|
const fetched = await this.fetchGistState(search.id);
|
|
38910
|
-
if (fetched) {
|
|
38911
|
-
const state = this.mergeCacheInto(fetched);
|
|
39476
|
+
if (fetched.kind === "ok") {
|
|
39477
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38912
39478
|
this.writeCache(state);
|
|
38913
39479
|
return { gistId: search.id, state, created: false };
|
|
38914
39480
|
}
|
|
38915
|
-
warn(
|
|
39481
|
+
warn(MODULE14, `Found existing gist ${search.id} but content failed validation. Using local cache to avoid data loss.`);
|
|
38916
39482
|
return this.bootstrapFromCache("unknown");
|
|
38917
39483
|
}
|
|
38918
39484
|
if (!search.exhaustive) {
|
|
38919
|
-
warn(
|
|
39485
|
+
warn(MODULE14, `Scanned the first ${SEARCH_MAX_PAGES * 100} gists without finding an oss-scout state gist, but the account has more. Creating a new state gist; if one already exists, copy its id into the gist-id file in the oss-scout data directory to avoid a duplicate.`);
|
|
38920
39486
|
}
|
|
38921
|
-
debug(
|
|
39487
|
+
debug(MODULE14, "No existing gist found, creating new one");
|
|
38922
39488
|
const freshState = ScoutStateSchema.parse({ version: 1 });
|
|
38923
39489
|
const newId = await this.createGist(freshState);
|
|
38924
39490
|
this.saveGistId(newId);
|
|
@@ -38929,7 +39495,7 @@ var GistStateStore = class {
|
|
|
38929
39495
|
bootstrapFromCache(reason) {
|
|
38930
39496
|
const cached2 = this.readCache();
|
|
38931
39497
|
if (cached2) {
|
|
38932
|
-
debug(
|
|
39498
|
+
debug(MODULE14, "Bootstrapped from local cache (degraded mode)");
|
|
38933
39499
|
const cachedId = this.readCachedGistId();
|
|
38934
39500
|
if (cachedId)
|
|
38935
39501
|
this.gistId = cachedId;
|
|
@@ -38941,7 +39507,7 @@ var GistStateStore = class {
|
|
|
38941
39507
|
degradedReason: reason
|
|
38942
39508
|
};
|
|
38943
39509
|
}
|
|
38944
|
-
debug(
|
|
39510
|
+
debug(MODULE14, "No cache available, using fresh state (degraded mode)");
|
|
38945
39511
|
const fresh = ScoutStateSchema.parse({ version: 1 });
|
|
38946
39512
|
return {
|
|
38947
39513
|
gistId: "",
|
|
@@ -38952,17 +39518,24 @@ var GistStateStore = class {
|
|
|
38952
39518
|
};
|
|
38953
39519
|
}
|
|
38954
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
|
+
*/
|
|
38955
39528
|
async fetchGistState(gistId) {
|
|
38956
39529
|
const { data } = await this.octokit.gists.get({ gist_id: gistId });
|
|
38957
39530
|
const file2 = data.files?.[GIST_FILENAME];
|
|
38958
39531
|
if (!file2?.content)
|
|
38959
|
-
return
|
|
39532
|
+
return { kind: "missing" };
|
|
38960
39533
|
try {
|
|
38961
39534
|
const parsed = JSON.parse(file2.content);
|
|
38962
|
-
return parseScoutState(parsed);
|
|
39535
|
+
return { kind: "ok", state: parseScoutState(parsed) };
|
|
38963
39536
|
} catch (err) {
|
|
38964
|
-
warn(
|
|
38965
|
-
return
|
|
39537
|
+
warn(MODULE14, `Gist content failed validation: ${errorMessage(err)}`);
|
|
39538
|
+
return { kind: "invalid" };
|
|
38966
39539
|
}
|
|
38967
39540
|
}
|
|
38968
39541
|
/**
|
|
@@ -39004,7 +39577,7 @@ var GistStateStore = class {
|
|
|
39004
39577
|
} catch (err) {
|
|
39005
39578
|
const code = err?.code;
|
|
39006
39579
|
if (code !== "ENOENT") {
|
|
39007
|
-
warn(
|
|
39580
|
+
warn(MODULE14, `Failed to read cached gist ID: ${errorMessage(err)}`);
|
|
39008
39581
|
}
|
|
39009
39582
|
return null;
|
|
39010
39583
|
}
|
|
@@ -39030,7 +39603,7 @@ var GistStateStore = class {
|
|
|
39030
39603
|
} catch (err) {
|
|
39031
39604
|
const code = err?.code;
|
|
39032
39605
|
if (code !== "ENOENT") {
|
|
39033
|
-
warn(
|
|
39606
|
+
warn(MODULE14, `Failed to read state cache: ${errorMessage(err)}`);
|
|
39034
39607
|
}
|
|
39035
39608
|
return null;
|
|
39036
39609
|
}
|
|
@@ -39041,7 +39614,7 @@ var GistStateStore = class {
|
|
|
39041
39614
|
mode: 384
|
|
39042
39615
|
});
|
|
39043
39616
|
} catch (err) {
|
|
39044
|
-
warn(
|
|
39617
|
+
warn(MODULE14, `Failed to write cache: ${errorMessage(err)}`);
|
|
39045
39618
|
}
|
|
39046
39619
|
}
|
|
39047
39620
|
};
|
|
@@ -39056,7 +39629,7 @@ function mergeTombstones(local, remote) {
|
|
|
39056
39629
|
}
|
|
39057
39630
|
return [...byUrl.values()].filter((t) => {
|
|
39058
39631
|
const ts = new Date(t.removedAt).getTime();
|
|
39059
|
-
return
|
|
39632
|
+
return Number.isFinite(ts) && ts >= cutoff;
|
|
39060
39633
|
});
|
|
39061
39634
|
}
|
|
39062
39635
|
function applyTombstones(items, tombstones, urlOf, touchedAtOf) {
|
|
@@ -39098,11 +39671,26 @@ function mergeStates(local, remote) {
|
|
|
39098
39671
|
skippedIssues,
|
|
39099
39672
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
39100
39673
|
lastRunAt: pickFresherTimestamp(local.lastRunAt, remote.lastRunAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
39101
|
-
gistId: remote.gistId ?? local.gistId
|
|
39674
|
+
gistId: remote.gistId ?? local.gistId,
|
|
39675
|
+
searchRotation: mergeSearchRotation(local.searchRotation, remote.searchRotation)
|
|
39102
39676
|
};
|
|
39103
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
|
+
}
|
|
39104
39692
|
function mergeRepoScores(local, remote) {
|
|
39105
|
-
const merged =
|
|
39693
|
+
const merged = Object.assign(/* @__PURE__ */ Object.create(null), local);
|
|
39106
39694
|
for (const [repo, remoteScore] of Object.entries(remote)) {
|
|
39107
39695
|
const localScore = merged[repo];
|
|
39108
39696
|
if (!localScore) {
|
|
@@ -39169,7 +39757,7 @@ function pickFresherTimestamp(a, b) {
|
|
|
39169
39757
|
// ../core/dist/core/local-state.js
|
|
39170
39758
|
var fs4 = __toESM(require("fs"), 1);
|
|
39171
39759
|
var path4 = __toESM(require("path"), 1);
|
|
39172
|
-
var
|
|
39760
|
+
var MODULE15 = "local-state";
|
|
39173
39761
|
function getStatePath() {
|
|
39174
39762
|
return path4.join(getDataDir(), "state.json");
|
|
39175
39763
|
}
|
|
@@ -39183,13 +39771,13 @@ function loadLocalState() {
|
|
|
39183
39771
|
if (code === "ENOENT") {
|
|
39184
39772
|
return ScoutStateSchema.parse({ version: 1 });
|
|
39185
39773
|
}
|
|
39186
|
-
warn(
|
|
39774
|
+
warn(MODULE15, `Failed to load state from ${statePath}: ${errorMessage(err)}. Using defaults.`);
|
|
39187
39775
|
try {
|
|
39188
39776
|
const backupPath = `${statePath}.corrupt.${Date.now()}`;
|
|
39189
39777
|
fs4.copyFileSync(statePath, backupPath);
|
|
39190
|
-
warn(
|
|
39778
|
+
warn(MODULE15, `Corrupt state backed up to ${backupPath}`);
|
|
39191
39779
|
} catch (backupErr) {
|
|
39192
|
-
warn(
|
|
39780
|
+
warn(MODULE15, `Failed to back up corrupt state: ${errorMessage(backupErr)}`);
|
|
39193
39781
|
}
|
|
39194
39782
|
return ScoutStateSchema.parse({ version: 1 });
|
|
39195
39783
|
}
|
|
@@ -39209,7 +39797,7 @@ function saveLocalState(state) {
|
|
|
39209
39797
|
}
|
|
39210
39798
|
throw err;
|
|
39211
39799
|
}
|
|
39212
|
-
debug(
|
|
39800
|
+
debug(MODULE15, "State saved");
|
|
39213
39801
|
}
|
|
39214
39802
|
|
|
39215
39803
|
// ../core/dist/scout.js
|
|
@@ -39322,6 +39910,15 @@ var OssScout = class {
|
|
|
39322
39910
|
this.evictStaleCacheEntries();
|
|
39323
39911
|
this.cullExpiredSkips();
|
|
39324
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
|
+
}
|
|
39325
39922
|
const discovery = new IssueDiscovery(this.githubToken, this.state.preferences, this);
|
|
39326
39923
|
const prefs = this.state.preferences;
|
|
39327
39924
|
const prefLangs = prefs.preferLanguages ?? [];
|
|
@@ -39333,18 +39930,30 @@ var OssScout = class {
|
|
|
39333
39930
|
const avoidRepos = options?.avoidRepos ?? (prefAvoid.length > 0 ? prefAvoid : void 0);
|
|
39334
39931
|
const boostIssueTypes = options?.boostIssueTypes ?? (prefBoostTypes.length > 0 ? prefBoostTypes : void 0);
|
|
39335
39932
|
const diversityRatio = options?.diversityRatio ?? prefs.diversityRatio ?? 0;
|
|
39336
|
-
const
|
|
39337
|
-
|
|
39338
|
-
|
|
39339
|
-
|
|
39340
|
-
|
|
39341
|
-
|
|
39342
|
-
|
|
39343
|
-
|
|
39344
|
-
|
|
39345
|
-
|
|
39346
|
-
|
|
39347
|
-
|
|
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);
|
|
39348
39957
|
for (const c of candidates) {
|
|
39349
39958
|
this.updateRepoSignalsFromHealth(c.projectHealth);
|
|
39350
39959
|
}
|
|
@@ -39358,6 +39967,23 @@ var OssScout = class {
|
|
|
39358
39967
|
strategiesUsed
|
|
39359
39968
|
};
|
|
39360
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
|
+
}
|
|
39361
39987
|
/**
|
|
39362
39988
|
* Populate the `hasActiveMaintainers` repo-score signal from a freshly
|
|
39363
39989
|
* computed projectHealth (#167). It was initialized false and never set, so
|
|
@@ -39902,6 +40528,7 @@ var OssScout = class {
|
|
|
39902
40528
|
}
|
|
39903
40529
|
if (this.persistLocal) {
|
|
39904
40530
|
try {
|
|
40531
|
+
this.state = mergeStates(this.state, loadLocalState());
|
|
39905
40532
|
saveLocalState(this.state);
|
|
39906
40533
|
} catch {
|
|
39907
40534
|
return false;
|
|
@@ -40038,6 +40665,8 @@ function applyPreferenceField(preferences, key, value) {
|
|
|
40038
40665
|
const prefs = { ...preferences };
|
|
40039
40666
|
switch (field.type) {
|
|
40040
40667
|
case "string":
|
|
40668
|
+
if (key === "slmTriageHost")
|
|
40669
|
+
assertValidTriageHost(value);
|
|
40041
40670
|
prefs[key] = value;
|
|
40042
40671
|
break;
|
|
40043
40672
|
case "boolean":
|
|
@@ -40117,7 +40746,7 @@ function registerTools(server, scout) {
|
|
|
40117
40746
|
"search",
|
|
40118
40747
|
"Search for open source issues matching your preferences",
|
|
40119
40748
|
{
|
|
40120
|
-
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)"),
|
|
40121
40750
|
strategies: external_exports3.string().optional().describe(
|
|
40122
40751
|
"Comma-separated search strategies: merged, starred, broad, maintained, all"
|
|
40123
40752
|
),
|
|
@@ -40147,7 +40776,16 @@ function registerTools(server, scout) {
|
|
|
40147
40776
|
diversityRatio
|
|
40148
40777
|
}) => {
|
|
40149
40778
|
try {
|
|
40150
|
-
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;
|
|
40151
40789
|
const result = await withTimeout(
|
|
40152
40790
|
scout.search({
|
|
40153
40791
|
maxResults: maxResults ?? 10,
|
|
@@ -40190,7 +40828,7 @@ function registerTools(server, scout) {
|
|
|
40190
40828
|
"scout-features",
|
|
40191
40829
|
"Surface feature-scoped contribution opportunities in repos where you have 3+ merged PRs",
|
|
40192
40830
|
{
|
|
40193
|
-
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)"),
|
|
40194
40832
|
anchorThreshold: external_exports3.number().int().min(1).max(50).optional().describe("Anchor threshold override (default 3)"),
|
|
40195
40833
|
splitRatio: external_exports3.number().min(0).max(1).optional().describe("Quick-wins/bigger-bets split ratio (default 0.6)")
|
|
40196
40834
|
},
|
|
@@ -40457,7 +41095,7 @@ function registerResources(server, scout) {
|
|
|
40457
41095
|
// src/index.ts
|
|
40458
41096
|
var SERVER_INFO = {
|
|
40459
41097
|
name: "oss-scout-mcp",
|
|
40460
|
-
version: "0.10.
|
|
41098
|
+
version: "0.10.1"
|
|
40461
41099
|
// x-release-please-version
|
|
40462
41100
|
};
|
|
40463
41101
|
function createServer(scout) {
|