@oss-scout/mcp 0.10.0 → 0.10.2
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 +936 -236
- package/dist/tools.js +30 -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;
|
|
@@ -37240,7 +37684,11 @@ async function triageWithSLM(input, options) {
|
|
|
37240
37684
|
format: SLM_TRIAGE_SCHEMA,
|
|
37241
37685
|
options: { temperature: 0.1 }
|
|
37242
37686
|
}),
|
|
37243
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
37687
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
37688
|
+
// No redirect following: an allowed private host could otherwise
|
|
37689
|
+
// 307/308-redirect the issue-text POST body to a public origin (#310).
|
|
37690
|
+
// Ollama never redirects, so this costs nothing.
|
|
37691
|
+
redirect: "error"
|
|
37244
37692
|
});
|
|
37245
37693
|
} catch {
|
|
37246
37694
|
return null;
|
|
@@ -37297,15 +37745,23 @@ function isValidTriageShape(value) {
|
|
|
37297
37745
|
}
|
|
37298
37746
|
|
|
37299
37747
|
// ../core/dist/core/issue-vetting.js
|
|
37300
|
-
var
|
|
37748
|
+
var MODULE10 = "issue-vetting";
|
|
37301
37749
|
var MAX_CONCURRENT_VETTING = 3;
|
|
37302
37750
|
var VETTING_CACHE_TTL_MS = 15 * 60 * 1e3;
|
|
37751
|
+
function repoAcceptsContributionsFromHealth(projectHealth) {
|
|
37752
|
+
if (projectHealth.checkFailed || projectHealth.recentMergedPRCount == null) {
|
|
37753
|
+
return null;
|
|
37754
|
+
}
|
|
37755
|
+
if (projectHealth.recentMergedPRCount > 0)
|
|
37756
|
+
return true;
|
|
37757
|
+
return projectHealth.recentMergeRate == null ? null : false;
|
|
37758
|
+
}
|
|
37303
37759
|
function deriveRecommendation(input) {
|
|
37304
37760
|
const notes = [];
|
|
37305
37761
|
const reasonsToApprove = [];
|
|
37306
37762
|
const reasonsToSkip = [];
|
|
37307
37763
|
if (!input.noExistingPR)
|
|
37308
|
-
notes.push(input.ownPR ? "Your PR is already in flight for this issue" : "Existing PR found for this issue");
|
|
37764
|
+
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
37765
|
if (!input.notClaimed)
|
|
37310
37766
|
notes.push("Issue appears to be claimed by someone");
|
|
37311
37767
|
if (input.existingPRInconclusive) {
|
|
@@ -37323,14 +37779,28 @@ function deriveRecommendation(input) {
|
|
|
37323
37779
|
notes.push("Issue requirements are unclear");
|
|
37324
37780
|
if (!input.contributionGuidelinesFound)
|
|
37325
37781
|
notes.push("No CONTRIBUTING.md found");
|
|
37326
|
-
if (
|
|
37327
|
-
|
|
37782
|
+
if (input.repoAcceptsContributions === false)
|
|
37783
|
+
notes.push("Repo has merged no PRs in the last 90 days");
|
|
37784
|
+
else if (input.repoAcceptsContributions === null)
|
|
37785
|
+
notes.push("Could not verify whether the repo merges PRs");
|
|
37786
|
+
if (!input.noExistingPR) {
|
|
37787
|
+
if (input.ownPR)
|
|
37788
|
+
reasonsToSkip.push("You already have a PR in flight");
|
|
37789
|
+
else if (input.linkedPRMerged)
|
|
37790
|
+
reasonsToSkip.push("Linked PR already merged");
|
|
37791
|
+
else if (input.linkedPRClosed)
|
|
37792
|
+
reasonsToSkip.push("Linked PR closed without merge");
|
|
37793
|
+
else
|
|
37794
|
+
reasonsToSkip.push("Has existing PR");
|
|
37795
|
+
}
|
|
37328
37796
|
if (!input.notClaimed)
|
|
37329
37797
|
reasonsToSkip.push("Already claimed");
|
|
37330
37798
|
if (!input.projectIsActive && !input.projectCheckFailed)
|
|
37331
37799
|
reasonsToSkip.push("Inactive project");
|
|
37332
37800
|
if (!input.clearRequirements)
|
|
37333
37801
|
reasonsToSkip.push("Unclear requirements");
|
|
37802
|
+
if (input.repoAcceptsContributions === false)
|
|
37803
|
+
reasonsToSkip.push("Repo has no recent merged PRs");
|
|
37334
37804
|
if (input.noExistingPR)
|
|
37335
37805
|
reasonsToApprove.push("No existing PR");
|
|
37336
37806
|
if (input.notClaimed)
|
|
@@ -37356,16 +37826,18 @@ function deriveRecommendation(input) {
|
|
|
37356
37826
|
let recommendation;
|
|
37357
37827
|
if (input.issueClosed) {
|
|
37358
37828
|
recommendation = "skip";
|
|
37829
|
+
} else if (input.linkedPRMerged || input.linkedPRClosed) {
|
|
37830
|
+
recommendation = "skip";
|
|
37359
37831
|
} else if (input.ownPR) {
|
|
37360
37832
|
recommendation = "skip";
|
|
37361
|
-
} else if (input.passedAllChecks) {
|
|
37833
|
+
} else if (input.passedAllChecks && input.repoAcceptsContributions !== false) {
|
|
37362
37834
|
recommendation = "approve";
|
|
37363
37835
|
} else if (reasonsToSkip.length > 2) {
|
|
37364
37836
|
recommendation = "skip";
|
|
37365
37837
|
} else {
|
|
37366
37838
|
recommendation = "needs_review";
|
|
37367
37839
|
}
|
|
37368
|
-
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive;
|
|
37840
|
+
const hasInconclusiveChecks = input.projectCheckFailed || input.existingPRInconclusive || input.claimInconclusive || input.mergedCountInconclusive || input.repoAcceptsContributions === null;
|
|
37369
37841
|
if (recommendation === "approve" && hasInconclusiveChecks) {
|
|
37370
37842
|
recommendation = "needs_review";
|
|
37371
37843
|
notes.push("Recommendation downgraded: one or more checks were inconclusive");
|
|
@@ -37403,7 +37875,7 @@ var IssueVetter = class {
|
|
|
37403
37875
|
const cacheKey = versionedCacheKey(`vet:${issueUrl}${sigKey}`);
|
|
37404
37876
|
const cached2 = cache.getIfFresh(cacheKey, VETTING_CACHE_TTL_MS);
|
|
37405
37877
|
if (cached2 && typeof cached2 === "object" && "issue" in cached2 && "viabilityScore" in cached2) {
|
|
37406
|
-
debug(
|
|
37878
|
+
debug(MODULE10, `Vetting cache hit for ${issueUrl}`);
|
|
37407
37879
|
return cached2;
|
|
37408
37880
|
}
|
|
37409
37881
|
const parsed = parseGitHubUrl(issueUrl);
|
|
@@ -37418,7 +37890,7 @@ var IssueVetter = class {
|
|
|
37418
37890
|
const [existingPRCheck, claimCheck, projectHealth, contributionGuidelines, userMergedPRCount] = await Promise.all([
|
|
37419
37891
|
checkNoExistingPR(this.octokit, owner, repo, number4),
|
|
37420
37892
|
checkNotClaimed(this.octokit, owner, repo, number4, core.commentCount),
|
|
37421
|
-
checkProjectHealth(this.octokit, owner, repo),
|
|
37893
|
+
checkProjectHealth(this.octokit, owner, repo, this.budgetTracker),
|
|
37422
37894
|
fetchContributionGuidelines(this.octokit, owner, repo),
|
|
37423
37895
|
hasMergedPRsInRepo ? Promise.resolve(0) : checkUserMergedPRsInRepo(this.octokit, owner, repo, this.budgetTracker)
|
|
37424
37896
|
]);
|
|
@@ -37428,8 +37900,11 @@ var IssueVetter = class {
|
|
|
37428
37900
|
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
37429
37901
|
const linkedPR = existingPRCheck.linkedPR;
|
|
37430
37902
|
const ownPR = !noExistingPR && !!linkedPR && linkedPR.state === "open" && !linkedPR.merged && username !== "" && linkedPR.author.toLowerCase() === username.toLowerCase();
|
|
37903
|
+
const linkedPRMerged = !!linkedPR && linkedPR.merged;
|
|
37904
|
+
const linkedPRClosed = !!linkedPR && linkedPR.state === "closed" && !linkedPR.merged;
|
|
37431
37905
|
const clearRequirements = analyzeRequirements(core.body);
|
|
37432
37906
|
const projectActive = projectHealth.checkFailed ? true : projectHealth.isActive;
|
|
37907
|
+
const repoAcceptsContributions = repoAcceptsContributionsFromHealth(projectHealth);
|
|
37433
37908
|
const vettingResult = {
|
|
37434
37909
|
passedAllChecks: noExistingPR && notClaimed && projectActive && clearRequirements,
|
|
37435
37910
|
checks: {
|
|
@@ -37465,10 +37940,12 @@ var IssueVetter = class {
|
|
|
37465
37940
|
}
|
|
37466
37941
|
const matchesCategory = repoBelongsToCategory(repoFullName, this.stateReader.getProjectCategories());
|
|
37467
37942
|
const issueClosed = core.state === "closed";
|
|
37468
|
-
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive;
|
|
37943
|
+
const hasInconclusiveChecks = projectHealth.checkFailed || !!existingPRCheck.inconclusive || !!claimCheck.inconclusive || mergedCountInconclusive || repoAcceptsContributions === null;
|
|
37469
37944
|
const { notes, reasonsToApprove, reasonsToSkip, recommendation } = deriveRecommendation({
|
|
37470
37945
|
noExistingPR,
|
|
37471
37946
|
ownPR,
|
|
37947
|
+
linkedPRMerged,
|
|
37948
|
+
linkedPRClosed,
|
|
37472
37949
|
notClaimed,
|
|
37473
37950
|
clearRequirements,
|
|
37474
37951
|
contributionGuidelinesFound: !!contributionGuidelines,
|
|
@@ -37485,7 +37962,8 @@ var IssueVetter = class {
|
|
|
37485
37962
|
orgHasMergedPRs,
|
|
37486
37963
|
matchesCategory,
|
|
37487
37964
|
issueClosed,
|
|
37488
|
-
passedAllChecks: vettingResult.passedAllChecks
|
|
37965
|
+
passedAllChecks: vettingResult.passedAllChecks,
|
|
37966
|
+
repoAcceptsContributions
|
|
37489
37967
|
});
|
|
37490
37968
|
vettingResult.notes = notes;
|
|
37491
37969
|
const repoQualityBonus = projectHealth.checkFailed ? 0 : calculateRepoQualityBonus(projectHealth.stargazersCount ?? 0, projectHealth.forksCount ?? 0);
|
|
@@ -37565,6 +38043,36 @@ var IssueVetter = class {
|
|
|
37565
38043
|
updatedAt: ghIssue.updated_at
|
|
37566
38044
|
};
|
|
37567
38045
|
}
|
|
38046
|
+
/**
|
|
38047
|
+
* Batch-prefetch merged-PR counts for the repos in a vetting batch (#182),
|
|
38048
|
+
* warming the cache `checkUserMergedPRsInRepo` reads so each per-repo Search
|
|
38049
|
+
* API call becomes a cache hit.
|
|
38050
|
+
*
|
|
38051
|
+
* Two repos are intentionally excluded:
|
|
38052
|
+
* - Repos already known to have the user's merged PRs
|
|
38053
|
+
* (`getReposWithMergedPRs`): `vetIssue` short-circuits those to a merged
|
|
38054
|
+
* count of 1 and never calls the Search API, so prefetching them is waste.
|
|
38055
|
+
* - Everything, when the GitHub username is unknown: the GraphQL `author:`
|
|
38056
|
+
* filter needs a concrete login and the REST path falls back to `@me`.
|
|
38057
|
+
*/
|
|
38058
|
+
async prefetchMergedPRCounts(issueRefs) {
|
|
38059
|
+
const username = this.stateReader.getGitHubUsername?.() ?? "";
|
|
38060
|
+
if (!username)
|
|
38061
|
+
return;
|
|
38062
|
+
const alreadyKnown = new Set(this.stateReader.getReposWithMergedPRs());
|
|
38063
|
+
const seen = /* @__PURE__ */ new Set();
|
|
38064
|
+
const repos = [];
|
|
38065
|
+
for (const { owner, repo } of issueRefs) {
|
|
38066
|
+
const full = `${owner}/${repo}`;
|
|
38067
|
+
if (alreadyKnown.has(full) || seen.has(full))
|
|
38068
|
+
continue;
|
|
38069
|
+
seen.add(full);
|
|
38070
|
+
repos.push({ owner, repo });
|
|
38071
|
+
}
|
|
38072
|
+
if (repos.length === 0)
|
|
38073
|
+
return;
|
|
38074
|
+
await prefetchMergedPRCounts(this.octokit, username, repos);
|
|
38075
|
+
}
|
|
37568
38076
|
/**
|
|
37569
38077
|
* Vet multiple issues in parallel with concurrency limit
|
|
37570
38078
|
*/
|
|
@@ -37575,11 +38083,18 @@ var IssueVetter = class {
|
|
|
37575
38083
|
let rateLimitFailures = 0;
|
|
37576
38084
|
let attemptedCount = 0;
|
|
37577
38085
|
let firstAuthError = null;
|
|
38086
|
+
let firstForbiddenError = null;
|
|
38087
|
+
let forbiddenCount = 0;
|
|
38088
|
+
const forbiddenRepos = /* @__PURE__ */ new Set();
|
|
37578
38089
|
const uniqueUrls = [...new Set(urls)];
|
|
37579
|
-
const
|
|
38090
|
+
const issueRefs = uniqueUrls.flatMap((url2) => {
|
|
37580
38091
|
const p = parseGitHubUrl(url2);
|
|
37581
38092
|
return p && p.type === "issues" ? [{ owner: p.owner, repo: p.repo, number: p.number }] : [];
|
|
37582
|
-
})
|
|
38093
|
+
});
|
|
38094
|
+
const [prefetched] = await Promise.all([
|
|
38095
|
+
prefetchIssueCores(this.octokit, issueRefs),
|
|
38096
|
+
this.prefetchMergedPRCounts(issueRefs)
|
|
38097
|
+
]);
|
|
37583
38098
|
const prefetchFor = (url2) => {
|
|
37584
38099
|
const p = parseGitHubUrl(url2);
|
|
37585
38100
|
return p && p.type === "issues" ? prefetched.get(issueCoreKey(p.owner, p.repo, p.number)) : void 0;
|
|
@@ -37589,6 +38104,12 @@ var IssueVetter = class {
|
|
|
37589
38104
|
break;
|
|
37590
38105
|
if (firstAuthError)
|
|
37591
38106
|
break;
|
|
38107
|
+
const parsedUrl = parseGitHubUrl(url2);
|
|
38108
|
+
const repoKey = parsedUrl ? `${parsedUrl.owner}/${parsedUrl.repo}` : null;
|
|
38109
|
+
if (repoKey && forbiddenRepos.has(repoKey)) {
|
|
38110
|
+
debug(MODULE10, `Skipping ${url2}: repo already returned 403`);
|
|
38111
|
+
continue;
|
|
38112
|
+
}
|
|
37592
38113
|
attemptedCount++;
|
|
37593
38114
|
const core = prefetchFor(url2);
|
|
37594
38115
|
const task = this.vetIssue(url2, core ? { prefetched: core } : void 0).then((candidate) => {
|
|
@@ -37603,11 +38124,20 @@ var IssueVetter = class {
|
|
|
37603
38124
|
firstAuthError ??= error48;
|
|
37604
38125
|
return;
|
|
37605
38126
|
}
|
|
38127
|
+
if (isAuthError(error48)) {
|
|
38128
|
+
firstForbiddenError ??= error48;
|
|
38129
|
+
forbiddenCount++;
|
|
38130
|
+
failedVettingCount++;
|
|
38131
|
+
if (repoKey)
|
|
38132
|
+
forbiddenRepos.add(repoKey);
|
|
38133
|
+
warn(MODULE10, `Access forbidden for ${url2} (SAML/token scope?) \u2014 skipping this repo:`, errorMessage(error48));
|
|
38134
|
+
return;
|
|
38135
|
+
}
|
|
37606
38136
|
failedVettingCount++;
|
|
37607
38137
|
if (isRateLimitError(error48)) {
|
|
37608
38138
|
rateLimitFailures++;
|
|
37609
38139
|
}
|
|
37610
|
-
warn(
|
|
38140
|
+
warn(MODULE10, `Error vetting issue ${url2}:`, errorMessage(error48));
|
|
37611
38141
|
}).finally(() => pending.delete(url2));
|
|
37612
38142
|
pending.set(url2, task);
|
|
37613
38143
|
if (pending.size >= MAX_CONCURRENT_VETTING) {
|
|
@@ -37617,13 +38147,16 @@ var IssueVetter = class {
|
|
|
37617
38147
|
await Promise.allSettled(pending.values());
|
|
37618
38148
|
if (firstAuthError) {
|
|
37619
38149
|
if (candidates.length > 0) {
|
|
37620
|
-
warn(
|
|
38150
|
+
warn(MODULE10, `Auth failed mid-batch after ${candidates.length} successful vet(s) \u2014 discarding partial results`);
|
|
37621
38151
|
}
|
|
37622
38152
|
throw firstAuthError;
|
|
37623
38153
|
}
|
|
38154
|
+
if (firstForbiddenError && attemptedCount > 0 && forbiddenCount === attemptedCount) {
|
|
38155
|
+
throw firstForbiddenError;
|
|
38156
|
+
}
|
|
37624
38157
|
const allFailed = failedVettingCount === attemptedCount && attemptedCount > 0;
|
|
37625
38158
|
if (allFailed) {
|
|
37626
|
-
warn(
|
|
38159
|
+
warn(MODULE10, `All ${attemptedCount} issue(s) failed vetting. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37627
38160
|
}
|
|
37628
38161
|
return {
|
|
37629
38162
|
candidates: candidates.slice(0, maxResults),
|
|
@@ -37634,14 +38167,22 @@ var IssueVetter = class {
|
|
|
37634
38167
|
};
|
|
37635
38168
|
|
|
37636
38169
|
// ../core/dist/core/search-phases.js
|
|
37637
|
-
var
|
|
38170
|
+
var MODULE11 = "search-phases";
|
|
37638
38171
|
var GITHUB_MAX_BOOLEAN_OPS = 5;
|
|
37639
38172
|
var INTER_QUERY_DELAY_MS = 2e3;
|
|
38173
|
+
var GRAPHQL_INTER_QUERY_DELAY_MS = 500;
|
|
38174
|
+
var graphqlSearchQueryCount = 0;
|
|
38175
|
+
function getGraphQLSearchQueryCount() {
|
|
38176
|
+
return graphqlSearchQueryCount;
|
|
38177
|
+
}
|
|
38178
|
+
function resetGraphQLSearchQueryCount() {
|
|
38179
|
+
graphqlSearchQueryCount = 0;
|
|
38180
|
+
}
|
|
37640
38181
|
function chunkLabels(labels, reservedOps = 0) {
|
|
37641
38182
|
const maxPerChunk = GITHUB_MAX_BOOLEAN_OPS - reservedOps + 1;
|
|
37642
38183
|
if (maxPerChunk < 1) {
|
|
37643
38184
|
if (labels.length > 0) {
|
|
37644
|
-
warn(
|
|
38185
|
+
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
38186
|
}
|
|
37646
38187
|
return [[]];
|
|
37647
38188
|
}
|
|
@@ -37651,7 +38192,7 @@ function chunkLabels(labels, reservedOps = 0) {
|
|
|
37651
38192
|
for (let i = 0; i < labels.length; i += maxPerChunk) {
|
|
37652
38193
|
chunks.push(labels.slice(i, i + maxPerChunk));
|
|
37653
38194
|
}
|
|
37654
|
-
debug(
|
|
38195
|
+
debug(MODULE11, `Split ${labels.length} labels into ${chunks.length} chunks (${reservedOps} ops reserved, max ${maxPerChunk} per chunk)`);
|
|
37655
38196
|
return chunks;
|
|
37656
38197
|
}
|
|
37657
38198
|
function buildLabelQuery(labels) {
|
|
@@ -37688,7 +38229,7 @@ async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTrac
|
|
|
37688
38229
|
const cache = getHttpCache();
|
|
37689
38230
|
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
37690
38231
|
if (cached2) {
|
|
37691
|
-
debug(
|
|
38232
|
+
debug(MODULE11, `Search cache hit for query`);
|
|
37692
38233
|
return cached2;
|
|
37693
38234
|
}
|
|
37694
38235
|
await tracker.waitForBudget();
|
|
@@ -37702,10 +38243,32 @@ async function cachedSearchIssues(octokit, params, tracker = getSearchBudgetTrac
|
|
|
37702
38243
|
if (data.items.length > 0) {
|
|
37703
38244
|
cache.set(cacheKey, "", data);
|
|
37704
38245
|
} else {
|
|
37705
|
-
debug(
|
|
38246
|
+
debug(MODULE11, `Skipping cache for empty search result (possible rate limit artifact)`);
|
|
37706
38247
|
}
|
|
37707
38248
|
return data;
|
|
37708
38249
|
}
|
|
38250
|
+
async function searchIssuesGraphQLFirst(octokit, params, tracker = getSearchBudgetTracker()) {
|
|
38251
|
+
const cacheKey = versionedCacheKey(`gqlsearch:${params.q}:${params.sort}:${params.order}:${params.per_page}`);
|
|
38252
|
+
const cache = getHttpCache();
|
|
38253
|
+
const cached2 = cache.getIfFresh(cacheKey, SEARCH_CACHE_TTL_MS);
|
|
38254
|
+
if (cached2) {
|
|
38255
|
+
debug(MODULE11, `GraphQL search cache hit for query`);
|
|
38256
|
+
return cached2;
|
|
38257
|
+
}
|
|
38258
|
+
const gqlQuery = `${params.q} sort:${params.sort}-${params.order}`;
|
|
38259
|
+
const gql = await graphqlSearchIssues(octokit, gqlQuery, Math.min(params.per_page, 100));
|
|
38260
|
+
if (gql) {
|
|
38261
|
+
graphqlSearchQueryCount++;
|
|
38262
|
+
if (gql.items.length > 0) {
|
|
38263
|
+
cache.set(cacheKey, "", gql);
|
|
38264
|
+
} else {
|
|
38265
|
+
debug(MODULE11, `Skipping cache for empty GraphQL search result (possible transient artifact)`);
|
|
38266
|
+
}
|
|
38267
|
+
return gql;
|
|
38268
|
+
}
|
|
38269
|
+
warn(MODULE11, `GraphQL broad search unavailable; falling back to REST search API`);
|
|
38270
|
+
return cachedSearchIssues(octokit, params, tracker);
|
|
38271
|
+
}
|
|
37709
38272
|
async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResults) {
|
|
37710
38273
|
const items = [];
|
|
37711
38274
|
for (const repoFullName of repos) {
|
|
@@ -37750,15 +38313,15 @@ async function fetchIssuesFromMaintainedRepos(octokit, repos, minStars, maxResul
|
|
|
37750
38313
|
if (getHttpStatusCode(error48) === 401)
|
|
37751
38314
|
throw error48;
|
|
37752
38315
|
if (isRateLimitError(error48)) {
|
|
37753
|
-
warn(
|
|
38316
|
+
warn(MODULE11, `Rate limit hit fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37754
38317
|
break;
|
|
37755
38318
|
}
|
|
37756
|
-
warn(
|
|
38319
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37757
38320
|
}
|
|
37758
38321
|
}
|
|
37759
38322
|
return items;
|
|
37760
38323
|
}
|
|
37761
|
-
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn) {
|
|
38324
|
+
async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResults, priority, filterFn, perPage = 5) {
|
|
37762
38325
|
const candidates = [];
|
|
37763
38326
|
let failedRepos = 0;
|
|
37764
38327
|
let rateLimitFailures = 0;
|
|
@@ -37780,7 +38343,7 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37780
38343
|
state: "open",
|
|
37781
38344
|
sort: "created",
|
|
37782
38345
|
direction: "desc",
|
|
37783
|
-
per_page:
|
|
38346
|
+
per_page: perPage,
|
|
37784
38347
|
...label !== void 0 ? { labels: label } : {}
|
|
37785
38348
|
});
|
|
37786
38349
|
for (const issue2 of response.data) {
|
|
@@ -37815,13 +38378,13 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
|
|
|
37815
38378
|
if (isRateLimitError(error48)) {
|
|
37816
38379
|
rateLimitFailures++;
|
|
37817
38380
|
}
|
|
37818
|
-
warn(
|
|
38381
|
+
warn(MODULE11, `Error fetching issues from ${repoFullName}:`, errorMessage(error48));
|
|
37819
38382
|
}
|
|
37820
38383
|
}
|
|
37821
38384
|
const allReposFailed = failedRepos === repos.length && repos.length > 0;
|
|
37822
38385
|
const rateLimitHit = rateLimitFailures > 0;
|
|
37823
38386
|
if (allReposFailed) {
|
|
37824
|
-
warn(
|
|
38387
|
+
warn(MODULE11, `All ${repos.length} repo(s) failed for ${priority} phase. This may indicate a systemic issue (rate limit, auth, network).`);
|
|
37825
38388
|
}
|
|
37826
38389
|
return { candidates, allReposFailed, rateLimitHit };
|
|
37827
38390
|
}
|
|
@@ -37831,9 +38394,9 @@ async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery,
|
|
|
37831
38394
|
const allItems = [];
|
|
37832
38395
|
for (let i = 0; i < labelChunks.length; i++) {
|
|
37833
38396
|
if (i > 0)
|
|
37834
|
-
await sleep(
|
|
38397
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
37835
38398
|
const query = buildQuery(buildLabelQuery(labelChunks[i]));
|
|
37836
|
-
const data = await
|
|
38399
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
37837
38400
|
q: query,
|
|
37838
38401
|
sort: "created",
|
|
37839
38402
|
order: "desc",
|
|
@@ -37857,13 +38420,15 @@ function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
|
|
|
37857
38420
|
return [languages.map((l) => `language:${l}`).join(" ")];
|
|
37858
38421
|
return languages.map((l) => `language:${l}`);
|
|
37859
38422
|
}
|
|
37860
|
-
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker()) {
|
|
37861
|
-
const
|
|
38423
|
+
async function searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, labels, buildBaseQuery, perPage, tracker = getSearchBudgetTracker(), startOffset = 0) {
|
|
38424
|
+
const variants = buildLanguageVariants(languages, isAnyLanguage, labels.length > 0);
|
|
38425
|
+
const k = variants.length > 0 ? startOffset % variants.length : 0;
|
|
38426
|
+
const langVariants = [...variants.slice(k), ...variants.slice(0, k)];
|
|
37862
38427
|
const seenUrls = /* @__PURE__ */ new Set();
|
|
37863
38428
|
const allItems = [];
|
|
37864
38429
|
for (let i = 0; i < langVariants.length; i++) {
|
|
37865
38430
|
if (i > 0)
|
|
37866
|
-
await sleep(
|
|
38431
|
+
await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
|
|
37867
38432
|
const items = await searchWithChunkedLabels(octokit, labels, 0, (labelQ) => `${buildBaseQuery(langVariants[i])} ${labelQ}`.replace(/ +/g, " ").trim(), perPage, tracker);
|
|
37868
38433
|
for (const item of items) {
|
|
37869
38434
|
if (!seenUrls.has(item.html_url)) {
|
|
@@ -37878,7 +38443,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37878
38443
|
const spamRepos = detectLabelFarmingRepos(items);
|
|
37879
38444
|
if (spamRepos.size > 0) {
|
|
37880
38445
|
const spamCount = items.filter((i) => spamRepos.has(extractRepoFromUrl(i.repository_url) ?? "")).length;
|
|
37881
|
-
debug(
|
|
38446
|
+
debug(MODULE11, `[SPAM_FILTER] Filtered ${spamCount} issues from ${spamRepos.size} label-farming repos: ${[...spamRepos].join(", ")}`);
|
|
37882
38447
|
}
|
|
37883
38448
|
const itemsToVet = filterIssues(items).filter((item) => {
|
|
37884
38449
|
const repoFullName = extractRepoFromUrl(item.repository_url);
|
|
@@ -37889,7 +38454,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37889
38454
|
return excludedRepoSets.every((s) => !s.has(repoFullName));
|
|
37890
38455
|
}).slice(0, remainingNeeded * 2);
|
|
37891
38456
|
if (itemsToVet.length === 0) {
|
|
37892
|
-
debug(
|
|
38457
|
+
debug(MODULE11, `[${phaseLabel}] All ${items.length} items filtered before vetting`);
|
|
37893
38458
|
return { candidates: [], allVetFailed: false, rateLimitHit: false };
|
|
37894
38459
|
}
|
|
37895
38460
|
const { candidates: results, allFailed: allVetFailed, rateLimitHit } = await vetter.vetIssuesParallel(itemsToVet.map((i) => i.html_url), remainingNeeded, "normal");
|
|
@@ -37901,7 +38466,7 @@ async function filterVetAndScore(vetter, items, filterIssues, excludedRepoSets,
|
|
|
37901
38466
|
});
|
|
37902
38467
|
const starFilteredCount = results.length - starFiltered.length;
|
|
37903
38468
|
if (starFilteredCount > 0) {
|
|
37904
|
-
debug(
|
|
38469
|
+
debug(MODULE11, `[STAR_FILTER] Filtered ${starFilteredCount} ${phaseLabel} candidates below ${minStars} stars`);
|
|
37905
38470
|
}
|
|
37906
38471
|
return { candidates: starFiltered, allVetFailed, rateLimitHit };
|
|
37907
38472
|
}
|
|
@@ -37994,9 +38559,12 @@ function applyDiversityRatio(candidates, maxResults, diversityRatio) {
|
|
|
37994
38559
|
}
|
|
37995
38560
|
|
|
37996
38561
|
// ../core/dist/core/issue-discovery.js
|
|
37997
|
-
var
|
|
38562
|
+
var MODULE12 = "issue-discovery";
|
|
37998
38563
|
var LOW_BUDGET_THRESHOLD = 20;
|
|
37999
38564
|
var CRITICAL_BUDGET_THRESHOLD = 10;
|
|
38565
|
+
var PHASE0_PER_PAGE = 30;
|
|
38566
|
+
var CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
|
|
38567
|
+
var PHASE0_MAX_SHARE = 0.5;
|
|
38000
38568
|
function buildIssueFilter(config2) {
|
|
38001
38569
|
return (items) => {
|
|
38002
38570
|
return items.filter((item) => {
|
|
@@ -38018,6 +38586,8 @@ function buildIssueFilter(config2) {
|
|
|
38018
38586
|
if (config2.skippedUrls.has(item.html_url))
|
|
38019
38587
|
return false;
|
|
38020
38588
|
const updatedAt = new Date(item.updated_at);
|
|
38589
|
+
if (Number.isNaN(updatedAt.getTime()))
|
|
38590
|
+
return false;
|
|
38021
38591
|
const ageDays = daysBetween(updatedAt, config2.now);
|
|
38022
38592
|
if (ageDays > config2.maxAgeDays)
|
|
38023
38593
|
return false;
|
|
@@ -38028,9 +38598,9 @@ function buildIssueFilter(config2) {
|
|
|
38028
38598
|
};
|
|
38029
38599
|
}
|
|
38030
38600
|
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(
|
|
38601
|
+
info(MODULE12, `Phase 0: Searching issues in ${repos.length} merged-PR repos (no label filter, ${PHASE0_PER_PAGE}/repo)...`);
|
|
38602
|
+
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, repos, [], maxResults, "merged_pr", filterIssues, PHASE0_PER_PAGE);
|
|
38603
|
+
info(MODULE12, `Found ${candidates.length} candidates from merged-PR repos`);
|
|
38034
38604
|
return {
|
|
38035
38605
|
candidates,
|
|
38036
38606
|
error: allReposFailed ? "All merged-PR repo fetches failed" : null,
|
|
@@ -38038,26 +38608,26 @@ async function runPhase0(octokit, vetter, repos, maxResults, filterIssues) {
|
|
|
38038
38608
|
};
|
|
38039
38609
|
}
|
|
38040
38610
|
async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
|
|
38041
|
-
info(
|
|
38611
|
+
info(MODULE12, `Phase 1: Searching issues in ${repos.length} starred repos...`);
|
|
38042
38612
|
const phase1Labels = labels.slice(0, 3);
|
|
38043
38613
|
const reposToSearch = repos.slice(0, 10);
|
|
38044
38614
|
const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
|
|
38045
|
-
info(
|
|
38615
|
+
info(MODULE12, `Found ${candidates.length} candidates from starred repos`);
|
|
38046
38616
|
return {
|
|
38047
38617
|
candidates,
|
|
38048
38618
|
error: allReposFailed ? "All starred repo fetches failed" : null,
|
|
38049
38619
|
rateLimitHit
|
|
38050
38620
|
};
|
|
38051
38621
|
}
|
|
38052
|
-
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues, tracker) {
|
|
38053
|
-
info(
|
|
38622
|
+
async function runPhase2(octokit, vetter, scopes, labels, configLabels, languages, isAnyLanguage, maxResults, minStars, phase0RepoSet, starredRepoSet, existingCandidates, filterIssues, tracker, languageRotationOffset = 0) {
|
|
38623
|
+
info(MODULE12, "Phase 2: General issue search...");
|
|
38054
38624
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38055
38625
|
const tierLabelGroups = [];
|
|
38056
38626
|
if (scopes && scopes.length > 1) {
|
|
38057
38627
|
for (const scope of scopes) {
|
|
38058
38628
|
const scopeLabels = SCOPE_LABELS[scope] ?? [];
|
|
38059
38629
|
if (scopeLabels.length === 0) {
|
|
38060
|
-
warn(
|
|
38630
|
+
warn(MODULE12, `Scope "${scope}" has no labels, skipping tier`);
|
|
38061
38631
|
continue;
|
|
38062
38632
|
}
|
|
38063
38633
|
tierLabelGroups.push({ tier: scope, tierLabels: scopeLabels });
|
|
@@ -38076,8 +38646,8 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38076
38646
|
let rateLimitHit = false;
|
|
38077
38647
|
for (const { tier, tierLabels } of tierLabelGroups) {
|
|
38078
38648
|
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(
|
|
38649
|
+
const allItems = await searchAcrossLanguagesAndLabels(octokit, languages, isAnyLanguage, tierLabels, (langQ) => `is:issue is:open ${langQ} no:assignee`.replace(/ +/g, " ").trim(), budgetPerTier * 3, tracker, languageRotationOffset);
|
|
38650
|
+
info(MODULE12, `Phase 2 [${tier}]: processing ${allItems.length} items...`);
|
|
38081
38651
|
const { candidates: tierCandidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, allItems, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], budgetPerTier, minStars, `Phase 2 [${tier}]`);
|
|
38082
38652
|
tierResults.push(tierCandidates);
|
|
38083
38653
|
for (const c of tierCandidates)
|
|
@@ -38088,7 +38658,7 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38088
38658
|
if (vetRateLimitHit) {
|
|
38089
38659
|
rateLimitHit = true;
|
|
38090
38660
|
}
|
|
38091
|
-
info(
|
|
38661
|
+
info(MODULE12, `Found ${tierCandidates.length} candidates from ${tier} tier`);
|
|
38092
38662
|
} catch (err) {
|
|
38093
38663
|
if (getHttpStatusCode(err) === 401)
|
|
38094
38664
|
throw err;
|
|
@@ -38097,13 +38667,13 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38097
38667
|
if (isRateLimitError(err)) {
|
|
38098
38668
|
rateLimitHit = true;
|
|
38099
38669
|
}
|
|
38100
|
-
warn(
|
|
38670
|
+
warn(MODULE12, `Error in ${tier} tier search: ${errMsg}`);
|
|
38101
38671
|
tierResults.push([]);
|
|
38102
38672
|
}
|
|
38103
38673
|
}
|
|
38104
38674
|
const interleaved = interleaveArrays(tierResults);
|
|
38105
38675
|
if (interleaved.length === 0 && error48) {
|
|
38106
|
-
warn(
|
|
38676
|
+
warn(MODULE12, `All ${tierLabelGroups.length} scope tiers failed in Phase 2: ${error48}`);
|
|
38107
38677
|
}
|
|
38108
38678
|
return {
|
|
38109
38679
|
candidates: interleaved.slice(0, maxResults),
|
|
@@ -38112,17 +38682,17 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
|
|
|
38112
38682
|
};
|
|
38113
38683
|
}
|
|
38114
38684
|
async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker) {
|
|
38115
|
-
info(
|
|
38685
|
+
info(MODULE12, "Phase 3: Searching actively maintained repos...");
|
|
38116
38686
|
const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
|
|
38117
38687
|
const eligibleStarred = starredRepos.filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
|
|
38118
38688
|
if (eligibleStarred.length > 0) {
|
|
38119
|
-
info(
|
|
38689
|
+
info(MODULE12, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
|
|
38120
38690
|
const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred.slice(0, 15), minStars, maxResults);
|
|
38121
38691
|
if (restItems.length > 0) {
|
|
38122
38692
|
try {
|
|
38123
38693
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
|
|
38124
38694
|
if (candidates.length > 0) {
|
|
38125
|
-
info(
|
|
38695
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo REST search`);
|
|
38126
38696
|
return {
|
|
38127
38697
|
candidates,
|
|
38128
38698
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38132,11 +38702,11 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38132
38702
|
} catch (error48) {
|
|
38133
38703
|
if (getHttpStatusCode(error48) === 401)
|
|
38134
38704
|
throw error48;
|
|
38135
|
-
warn(
|
|
38705
|
+
warn(MODULE12, `Phase 3 REST vetting failed, falling back to Search API:`, errorMessage(error48));
|
|
38136
38706
|
}
|
|
38137
38707
|
}
|
|
38138
38708
|
}
|
|
38139
|
-
info(
|
|
38709
|
+
info(MODULE12, "Phase 3: Falling back to Search API...");
|
|
38140
38710
|
const thirtyDaysAgo = /* @__PURE__ */ new Date();
|
|
38141
38711
|
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
|
|
38142
38712
|
const pushedSince = thirtyDaysAgo.toISOString().split("T")[0];
|
|
@@ -38144,15 +38714,15 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38144
38714
|
const topicQuery = categoryTopics.length > 0 ? `topic:${categoryTopics[0]}` : "";
|
|
38145
38715
|
const phase3Query = `is:issue is:open no:assignee ${langQuery} ${topicQuery} stars:>=${minStars} pushed:>=${pushedSince} archived:false`.replace(/ +/g, " ").trim();
|
|
38146
38716
|
try {
|
|
38147
|
-
const data = await
|
|
38717
|
+
const data = await searchIssuesGraphQLFirst(octokit, {
|
|
38148
38718
|
q: phase3Query,
|
|
38149
38719
|
sort: "updated",
|
|
38150
38720
|
order: "desc",
|
|
38151
38721
|
per_page: maxResults * 3
|
|
38152
38722
|
}, tracker);
|
|
38153
|
-
info(
|
|
38723
|
+
info(MODULE12, `Found ${data.total_count} issues in maintained-repo search, processing top ${data.items.length}...`);
|
|
38154
38724
|
const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, data.items, filterIssues, [phase0RepoSet, starredRepoSet, seenRepos], maxResults, minStars, "Phase 3");
|
|
38155
|
-
info(
|
|
38725
|
+
info(MODULE12, `Found ${candidates.length} candidates from maintained-repo search`);
|
|
38156
38726
|
return {
|
|
38157
38727
|
candidates,
|
|
38158
38728
|
error: allVetFailed ? "all vetting failed" : null,
|
|
@@ -38162,7 +38732,7 @@ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories
|
|
|
38162
38732
|
if (getHttpStatusCode(error48) === 401)
|
|
38163
38733
|
throw error48;
|
|
38164
38734
|
const errMsg = errorMessage(error48);
|
|
38165
|
-
warn(
|
|
38735
|
+
warn(MODULE12, `Error in maintained-repo search: ${errMsg}`);
|
|
38166
38736
|
return {
|
|
38167
38737
|
candidates: [],
|
|
38168
38738
|
error: errMsg,
|
|
@@ -38246,7 +38816,7 @@ var IssueDiscovery = class {
|
|
|
38246
38816
|
let rateLimitHitDuringSearch = false;
|
|
38247
38817
|
const applyInterPhaseDelay = async () => {
|
|
38248
38818
|
if (interPhaseDelay > 0) {
|
|
38249
|
-
info(
|
|
38819
|
+
info(MODULE12, `Waiting ${(interPhaseDelay / 1e3).toFixed(0)}s between phases for rate limit management...`);
|
|
38250
38820
|
await sleep(interPhaseDelay);
|
|
38251
38821
|
}
|
|
38252
38822
|
};
|
|
@@ -38256,9 +38826,12 @@ var IssueDiscovery = class {
|
|
|
38256
38826
|
if (result.rateLimitHit)
|
|
38257
38827
|
rateLimitHitDuringSearch = true;
|
|
38258
38828
|
};
|
|
38829
|
+
const viableCandidateCount = () => allCandidates.filter((c) => c.recommendation !== "skip").length;
|
|
38259
38830
|
this.rateLimitWarning = null;
|
|
38831
|
+
resetGraphQLSearchQueryCount();
|
|
38260
38832
|
const tracker = this.budgetTracker;
|
|
38261
|
-
let searchBudget =
|
|
38833
|
+
let searchBudget = CRITICAL_BUDGET_THRESHOLD - 1;
|
|
38834
|
+
let preflightFailed = false;
|
|
38262
38835
|
try {
|
|
38263
38836
|
const rateLimit = await checkRateLimit(this.githubToken);
|
|
38264
38837
|
searchBudget = rateLimit.remaining;
|
|
@@ -38266,22 +38839,23 @@ var IssueDiscovery = class {
|
|
|
38266
38839
|
if (rateLimit.remaining < 5) {
|
|
38267
38840
|
const resetTime = new Date(rateLimit.resetAt).toLocaleTimeString("en-US", { hour12: false });
|
|
38268
38841
|
this.rateLimitWarning = `GitHub search API quota low (${rateLimit.remaining}/${rateLimit.limit} remaining, resets at ${resetTime}). Search may be slow.`;
|
|
38269
|
-
warn(
|
|
38842
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38270
38843
|
}
|
|
38271
38844
|
if (searchBudget < CRITICAL_BUDGET_THRESHOLD) {
|
|
38272
|
-
info(
|
|
38845
|
+
info(MODULE12, `Search budget critical (${searchBudget} remaining) \u2014 skipping the starred phase; broad/maintained still run on GraphQL (points bucket)`);
|
|
38273
38846
|
} else if (searchBudget < LOW_BUDGET_THRESHOLD) {
|
|
38274
|
-
info(
|
|
38847
|
+
info(MODULE12, `Search budget low (${searchBudget} remaining) \u2014 broad/maintained run on GraphQL (points bucket), unaffected by REST search quota`);
|
|
38275
38848
|
}
|
|
38276
38849
|
} catch (error48) {
|
|
38277
38850
|
if (getHttpStatusCode(error48) === 401)
|
|
38278
38851
|
throw error48;
|
|
38852
|
+
preflightFailed = true;
|
|
38279
38853
|
tracker.init(CRITICAL_BUDGET_THRESHOLD, new Date(Date.now() + 6e4).toISOString());
|
|
38280
|
-
warn(
|
|
38854
|
+
warn(MODULE12, "Could not check rate limit \u2014 using conservative budget, skipping the starred phase:", errorMessage(error48));
|
|
38281
38855
|
}
|
|
38282
38856
|
if (searchBudget <= 0) {
|
|
38283
|
-
this.rateLimitWarning = "GitHub search API quota exhausted
|
|
38284
|
-
|
|
38857
|
+
this.rateLimitWarning = "GitHub search API quota exhausted \u2014 skipping the starred phase; other phases don't use it.";
|
|
38858
|
+
warn(MODULE12, this.rateLimitWarning);
|
|
38285
38859
|
}
|
|
38286
38860
|
const mergedPRRepos = this.stateReader.getReposWithMergedPRs();
|
|
38287
38861
|
const openPRRepos = this.stateReader.getReposWithOpenPRs();
|
|
@@ -38292,17 +38866,24 @@ var IssueDiscovery = class {
|
|
|
38292
38866
|
const langQuery = isAnyLanguage ? "" : languages.map((l) => `language:${l}`).join(" ");
|
|
38293
38867
|
const aiBlocklisted = new Set(config2.aiPolicyBlocklist.map((r) => r.toLowerCase()));
|
|
38294
38868
|
if (aiBlocklisted.size > 0) {
|
|
38295
|
-
debug(
|
|
38869
|
+
debug(MODULE12, `[AI_POLICY_FILTER] Filtering issues from ${aiBlocklisted.size} blocklisted repo(s): ${[...aiBlocklisted].join(", ")}`);
|
|
38296
38870
|
}
|
|
38297
|
-
const
|
|
38871
|
+
const baseFilterConfig = {
|
|
38298
38872
|
excludedRepos: new Set(config2.excludeRepos.map((r) => r.toLowerCase())),
|
|
38299
38873
|
excludeOrgs: new Set((config2.excludeOrgs ?? []).map((o) => o.toLowerCase())),
|
|
38300
38874
|
aiBlocklisted,
|
|
38301
38875
|
lowScoringRepos,
|
|
38302
38876
|
skippedUrls: options.skippedUrls ?? /* @__PURE__ */ new Set(),
|
|
38303
|
-
maxAgeDays: config2.maxIssueAgeDays || 90,
|
|
38304
38877
|
now: /* @__PURE__ */ new Date(),
|
|
38305
38878
|
includeDocIssues: config2.includeDocIssues ?? true
|
|
38879
|
+
};
|
|
38880
|
+
const filterIssues = buildIssueFilter({
|
|
38881
|
+
...baseFilterConfig,
|
|
38882
|
+
maxAgeDays: config2.maxIssueAgeDays || 90
|
|
38883
|
+
});
|
|
38884
|
+
const filterIssuesPhase0 = buildIssueFilter({
|
|
38885
|
+
...baseFilterConfig,
|
|
38886
|
+
maxAgeDays: CONTRIBUTED_REPO_MAX_AGE_DAYS
|
|
38306
38887
|
});
|
|
38307
38888
|
const seenPhase0 = /* @__PURE__ */ new Set();
|
|
38308
38889
|
const phase0Repos = [];
|
|
@@ -38315,10 +38896,12 @@ var IssueDiscovery = class {
|
|
|
38315
38896
|
break;
|
|
38316
38897
|
}
|
|
38317
38898
|
const phase0RepoSet = new Set(phase0Repos);
|
|
38899
|
+
const otherStrategiesCanRun = starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
|
|
38318
38900
|
if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
|
|
38319
|
-
const
|
|
38901
|
+
const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
|
|
38902
|
+
const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
|
|
38320
38903
|
if (remaining > 0) {
|
|
38321
|
-
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining,
|
|
38904
|
+
const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
|
|
38322
38905
|
recordPhaseResult("0", result);
|
|
38323
38906
|
}
|
|
38324
38907
|
strategiesUsed.push("merged");
|
|
@@ -38338,37 +38921,39 @@ var IssueDiscovery = class {
|
|
|
38338
38921
|
const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 9e4;
|
|
38339
38922
|
const configuredSkipThreshold = config2.skipBroadWhenSufficientResults ?? 8;
|
|
38340
38923
|
const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
|
|
38341
|
-
|
|
38342
|
-
|
|
38343
|
-
|
|
38924
|
+
const viableBeforeBroad = viableCandidateCount();
|
|
38925
|
+
if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
|
|
38926
|
+
info(MODULE12, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
38927
|
+
} else if (enabledStrategies.has("broad")) {
|
|
38928
|
+
const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo)).length;
|
|
38929
|
+
if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
|
|
38930
|
+
info(MODULE12, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
|
|
38344
38931
|
} else {
|
|
38345
38932
|
await applyInterPhaseDelay();
|
|
38346
38933
|
if (allCandidates.length > 0 && broadDelay > 0) {
|
|
38347
|
-
info(
|
|
38934
|
+
info(MODULE12, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
|
|
38348
38935
|
await sleep(broadDelay);
|
|
38349
38936
|
} else if (allCandidates.length === 0) {
|
|
38350
|
-
info(
|
|
38937
|
+
info(MODULE12, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
|
|
38351
38938
|
}
|
|
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);
|
|
38939
|
+
const remaining = maxResults - viableBeforeBroad;
|
|
38940
|
+
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
38941
|
recordPhaseResult("2", result);
|
|
38355
38942
|
strategiesUsed.push("broad");
|
|
38356
38943
|
}
|
|
38357
38944
|
}
|
|
38358
|
-
|
|
38945
|
+
const viableBeforeMaintained = viableCandidateCount();
|
|
38946
|
+
if (viableBeforeMaintained >= maxResults && enabledStrategies.has("maintained")) {
|
|
38947
|
+
info(MODULE12, `Skipping maintained search: ${viableBeforeMaintained} viable candidate(s) already meet maxResults (${maxResults})`);
|
|
38948
|
+
} else if (enabledStrategies.has("maintained")) {
|
|
38359
38949
|
await applyInterPhaseDelay();
|
|
38360
|
-
const remaining = maxResults -
|
|
38950
|
+
const remaining = maxResults - viableBeforeMaintained;
|
|
38361
38951
|
const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues, tracker);
|
|
38362
38952
|
recordPhaseResult("3", result);
|
|
38363
38953
|
strategiesUsed.push("maintained");
|
|
38364
38954
|
}
|
|
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
|
-
}
|
|
38955
|
+
const phasesSkippedForBudget = searchBudget < CRITICAL_BUDGET_THRESHOLD;
|
|
38956
|
+
const budgetNote = phasesSkippedForBudget ? preflightFailed ? " The starred-repo phase was skipped as a precaution because the rate-limit check failed (quota unknown); broad and maintained phases still ran on GraphQL." : ` The starred-repo phase was skipped due to critically low API quota (${searchBudget} remaining); broad and maintained phases still ran on GraphQL.` : "";
|
|
38372
38957
|
if (allCandidates.length === 0) {
|
|
38373
38958
|
const errorDetails = [
|
|
38374
38959
|
phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
|
|
@@ -38381,7 +38966,7 @@ var IssueDiscovery = class {
|
|
|
38381
38966
|
this.rateLimitWarning = `Search returned no results due to GitHub API rate limits.${details}${budgetNote} Try again after the rate limit resets.`;
|
|
38382
38967
|
return { candidates: [], strategiesUsed };
|
|
38383
38968
|
}
|
|
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
|
|
38969
|
+
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
38970
|
}
|
|
38386
38971
|
if (rateLimitHitDuringSearch || phasesSkippedForBudget) {
|
|
38387
38972
|
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 +38995,13 @@ var IssueDiscovery = class {
|
|
|
38410
38995
|
return boostDiff;
|
|
38411
38996
|
return b.viabilityScore - a.viabilityScore;
|
|
38412
38997
|
});
|
|
38413
|
-
const
|
|
38998
|
+
const viableFirst = [
|
|
38999
|
+
...ranked.filter((c) => c.recommendation !== "skip"),
|
|
39000
|
+
...ranked.filter((c) => c.recommendation === "skip")
|
|
39001
|
+
];
|
|
39002
|
+
const capped = applyPerRepoCap(viableFirst, 2);
|
|
38414
39003
|
const finalPicks = applyDiversityRatio(capped, maxResults, options.diversityRatio ?? 0);
|
|
38415
|
-
info(
|
|
39004
|
+
info(MODULE12, `Search complete: ${tracker.getTotalCalls()} REST Search API call(s) + ${getGraphQLSearchQueryCount()} GraphQL broad-search query/queries used, ${finalPicks.length} candidates returned`);
|
|
38416
39005
|
return { candidates: finalPicks, strategiesUsed };
|
|
38417
39006
|
}
|
|
38418
39007
|
/**
|
|
@@ -38464,18 +39053,25 @@ function pruneCache2() {
|
|
|
38464
39053
|
function parseRoadmapIssueRefs(content, owner, repo) {
|
|
38465
39054
|
const refs = /* @__PURE__ */ new Set();
|
|
38466
39055
|
const ownerRepoLower = `${owner}/${repo}`.toLowerCase();
|
|
38467
|
-
const
|
|
38468
|
-
const fullRefPattern = new RegExp(`\\b${
|
|
39056
|
+
const escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39057
|
+
const fullRefPattern = new RegExp(`\\b${escape(owner)}/${escape(repo)}#(\\d+)\\b`, "gi");
|
|
38469
39058
|
for (const m of content.matchAll(fullRefPattern)) {
|
|
38470
39059
|
const n = Number.parseInt(m[1], 10);
|
|
38471
39060
|
if (n > 0)
|
|
38472
39061
|
refs.add(n);
|
|
38473
39062
|
}
|
|
38474
39063
|
const fullRefStripPattern = /\b[\w.-]+\/[\w.-]+#\d+\b/gi;
|
|
39064
|
+
let inFence = false;
|
|
38475
39065
|
for (const line of content.split("\n")) {
|
|
39066
|
+
if (/^\s*(```|~~~)/.test(line)) {
|
|
39067
|
+
inFence = !inFence;
|
|
39068
|
+
continue;
|
|
39069
|
+
}
|
|
39070
|
+
if (inFence)
|
|
39071
|
+
continue;
|
|
38476
39072
|
if (/^\s*#+\s/.test(line))
|
|
38477
39073
|
continue;
|
|
38478
|
-
const stripped = line.replace(fullRefStripPattern, "");
|
|
39074
|
+
const stripped = line.replace(fullRefStripPattern, "").replace(/`[^`]*`/g, "");
|
|
38479
39075
|
for (const m of stripped.matchAll(/(?:^|[^&\w])#(\d+)\b/g)) {
|
|
38480
39076
|
const n = Number.parseInt(m[1], 10);
|
|
38481
39077
|
if (n > 0)
|
|
@@ -38527,7 +39123,7 @@ async function fetchRoadmapIssueRefsUncached(octokit, owner, repo, cacheKey) {
|
|
|
38527
39123
|
}
|
|
38528
39124
|
|
|
38529
39125
|
// ../core/dist/core/feature-discovery.js
|
|
38530
|
-
var
|
|
39126
|
+
var MODULE13 = "feature-discovery";
|
|
38531
39127
|
var INTER_REPO_DELAY_MS = 2e3;
|
|
38532
39128
|
var MIN_VIABILITY_SCORE = 40;
|
|
38533
39129
|
var ANCHOR_THRESHOLD = 3;
|
|
@@ -38552,7 +39148,8 @@ function classifyHorizon(input) {
|
|
|
38552
39148
|
function splitByHorizon(candidates, count, ratio = DEFAULT_SPLIT_RATIO) {
|
|
38553
39149
|
const allQuick = candidates.filter((c) => c.horizon === "quick-win").sort((a, b) => b.viabilityScore - a.viabilityScore);
|
|
38554
39150
|
const allBigger = candidates.filter((c) => c.horizon === "bigger-bet").sort((a, b) => b.viabilityScore - a.viabilityScore);
|
|
38555
|
-
const
|
|
39151
|
+
const clampedRatio = Math.max(0, Math.min(1, ratio));
|
|
39152
|
+
const targetQuick = Math.round(count * clampedRatio);
|
|
38556
39153
|
const targetBigger = count - targetQuick;
|
|
38557
39154
|
const quickTaken = Math.min(allQuick.length, targetQuick);
|
|
38558
39155
|
const biggerTaken = Math.min(allBigger.length, targetBigger);
|
|
@@ -38606,6 +39203,17 @@ function detectWontfixNoContributor(input) {
|
|
|
38606
39203
|
}
|
|
38607
39204
|
var NO_ANCHORS_MESSAGE = "No anchor repos yet (need 3+ merged PRs in a repo). Try `scout search` to build relationships first.";
|
|
38608
39205
|
var NO_RESULTS_MESSAGE = "No open feature opportunities in your anchor repos right now. Check back next week, or try `scout search` for fix-mode work.";
|
|
39206
|
+
function buildRepoExclusionFilter(opts) {
|
|
39207
|
+
const excluded = new Set([...opts.excludeRepos ?? [], ...opts.aiPolicyBlocklist ?? []].map((r) => r.toLowerCase()));
|
|
39208
|
+
const excludedOrgs = new Set((opts.excludeOrgs ?? []).map((o) => o.toLowerCase()));
|
|
39209
|
+
return (repoFullName) => {
|
|
39210
|
+
const lower = repoFullName.toLowerCase();
|
|
39211
|
+
if (excluded.has(lower))
|
|
39212
|
+
return false;
|
|
39213
|
+
const org = lower.split("/")[0];
|
|
39214
|
+
return !(org && excludedOrgs.has(org));
|
|
39215
|
+
};
|
|
39216
|
+
}
|
|
38609
39217
|
function extractLabels(item) {
|
|
38610
39218
|
if (!Array.isArray(item.labels))
|
|
38611
39219
|
return [];
|
|
@@ -38640,7 +39248,7 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
|
38640
39248
|
});
|
|
38641
39249
|
} catch (err) {
|
|
38642
39250
|
rethrowIfFatal(err);
|
|
38643
|
-
warn(
|
|
39251
|
+
warn(MODULE13, `vet failed for ${item.html_url}: ${errorMessage(err)}`);
|
|
38644
39252
|
return null;
|
|
38645
39253
|
}
|
|
38646
39254
|
const horizon = classifyHorizon({
|
|
@@ -38651,7 +39259,8 @@ async function vetAndClassify(item, vetter, roadmapRefs) {
|
|
|
38651
39259
|
return { ...candidate, horizon };
|
|
38652
39260
|
}
|
|
38653
39261
|
async function discoverFeatures(opts) {
|
|
38654
|
-
const
|
|
39262
|
+
const includeRepo = buildRepoExclusionFilter(opts);
|
|
39263
|
+
const anchorRepos = resolveAnchorRepos(opts.repoScores, opts.anchorThreshold).filter(includeRepo);
|
|
38655
39264
|
if (anchorRepos.length === 0) {
|
|
38656
39265
|
return {
|
|
38657
39266
|
quickWins: [],
|
|
@@ -38683,7 +39292,7 @@ async function discoverFeatures(opts) {
|
|
|
38683
39292
|
roadmapRefs = refs;
|
|
38684
39293
|
} catch (err) {
|
|
38685
39294
|
rethrowIfFatal(err);
|
|
38686
|
-
warn(
|
|
39295
|
+
warn(MODULE13, `failed to list issues for ${anchorRepos[i]}: ${errorMessage(err)}`);
|
|
38687
39296
|
continue;
|
|
38688
39297
|
}
|
|
38689
39298
|
const items = response.data.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it));
|
|
@@ -38758,10 +39367,14 @@ async function discoverFeaturesBroad(opts) {
|
|
|
38758
39367
|
merged.push(raw);
|
|
38759
39368
|
}
|
|
38760
39369
|
}
|
|
38761
|
-
|
|
39370
|
+
const includeRepo = buildRepoExclusionFilter(opts);
|
|
39371
|
+
items = merged.filter((it) => !it.pull_request && !it.assignee && isFeatureIssue(it)).filter((it) => {
|
|
39372
|
+
const repo = extractRepoFromUrl(it.html_url);
|
|
39373
|
+
return repo === null || includeRepo(repo);
|
|
39374
|
+
}).slice(0, maxToVet);
|
|
38762
39375
|
} catch (err) {
|
|
38763
39376
|
rethrowIfFatal(err);
|
|
38764
|
-
warn(
|
|
39377
|
+
warn(MODULE13, `broad feature search failed: ${errorMessage(err)}`);
|
|
38765
39378
|
return {
|
|
38766
39379
|
quickWins: [],
|
|
38767
39380
|
biggerBets: [],
|
|
@@ -38788,7 +39401,7 @@ async function discoverFeaturesBroad(opts) {
|
|
|
38788
39401
|
// ../core/dist/core/gist-state-store.js
|
|
38789
39402
|
var fs3 = __toESM(require("fs"), 1);
|
|
38790
39403
|
var path3 = __toESM(require("path"), 1);
|
|
38791
|
-
var
|
|
39404
|
+
var MODULE14 = "gist-state";
|
|
38792
39405
|
var GIST_DESCRIPTION = "oss-scout-state";
|
|
38793
39406
|
var GIST_FILENAME = "state.json";
|
|
38794
39407
|
var GIST_ID_FILE = "gist-id";
|
|
@@ -38833,7 +39446,7 @@ var GistStateStore = class {
|
|
|
38833
39446
|
} catch (err) {
|
|
38834
39447
|
if (getHttpStatusCode(err) === 401)
|
|
38835
39448
|
throw err;
|
|
38836
|
-
warn(
|
|
39449
|
+
warn(MODULE14, `API bootstrap failed: ${errorMessage(err)}`);
|
|
38837
39450
|
return this.bootstrapFromCache(classifyDegradedReason(err));
|
|
38838
39451
|
}
|
|
38839
39452
|
}
|
|
@@ -38842,24 +39455,28 @@ var GistStateStore = class {
|
|
|
38842
39455
|
*/
|
|
38843
39456
|
async push(state) {
|
|
38844
39457
|
if (!this.gistId) {
|
|
38845
|
-
warn(
|
|
39458
|
+
warn(MODULE14, "No gist ID \u2014 cannot push");
|
|
38846
39459
|
this.writeCache(state);
|
|
38847
39460
|
return false;
|
|
38848
39461
|
}
|
|
38849
39462
|
let toWrite = state;
|
|
38850
39463
|
try {
|
|
38851
39464
|
const remote = await this.fetchGistState(this.gistId);
|
|
38852
|
-
if (remote) {
|
|
38853
|
-
toWrite = mergeStates(state, remote);
|
|
39465
|
+
if (remote.kind === "ok") {
|
|
39466
|
+
toWrite = mergeStates(state, remote.state);
|
|
39467
|
+
} else if (remote.kind === "invalid") {
|
|
39468
|
+
this.writeCache(state);
|
|
39469
|
+
warn(MODULE14, "Remote gist content failed validation \u2014 not overwriting it. Changes saved locally; gist sync skipped.");
|
|
39470
|
+
return false;
|
|
38854
39471
|
}
|
|
38855
39472
|
} catch (err) {
|
|
38856
39473
|
rethrowIfFatal(err);
|
|
38857
|
-
warn(
|
|
39474
|
+
warn(MODULE14, `Could not fetch gist before push, writing local snapshot: ${errorMessage(err)}`);
|
|
38858
39475
|
}
|
|
38859
39476
|
this.writeCache(toWrite);
|
|
38860
39477
|
const json2 = JSON.stringify(toWrite, null, 2);
|
|
38861
39478
|
if (json2.length > 9e5) {
|
|
38862
|
-
warn(
|
|
39479
|
+
warn(MODULE14, `State too large for gist (${Math.round(json2.length / 1024)}KB). Consider clearing old results with 'oss-scout results clear'.`);
|
|
38863
39480
|
return false;
|
|
38864
39481
|
}
|
|
38865
39482
|
try {
|
|
@@ -38869,11 +39486,11 @@ var GistStateStore = class {
|
|
|
38869
39486
|
[GIST_FILENAME]: { content: json2 }
|
|
38870
39487
|
}
|
|
38871
39488
|
});
|
|
38872
|
-
debug(
|
|
39489
|
+
debug(MODULE14, "State pushed to gist");
|
|
38873
39490
|
return true;
|
|
38874
39491
|
} catch (err) {
|
|
38875
39492
|
rethrowIfFatal(err);
|
|
38876
|
-
warn(
|
|
39493
|
+
warn(MODULE14, `Failed to push: ${errorMessage(err)}`);
|
|
38877
39494
|
return false;
|
|
38878
39495
|
}
|
|
38879
39496
|
}
|
|
@@ -38885,40 +39502,40 @@ var GistStateStore = class {
|
|
|
38885
39502
|
async bootstrapFromApi() {
|
|
38886
39503
|
const cachedId = this.readCachedGistId();
|
|
38887
39504
|
if (cachedId) {
|
|
38888
|
-
debug(
|
|
39505
|
+
debug(MODULE14, `Trying cached gist ID: ${cachedId}`);
|
|
38889
39506
|
try {
|
|
38890
39507
|
const fetched = await this.fetchGistState(cachedId);
|
|
38891
|
-
if (fetched) {
|
|
39508
|
+
if (fetched.kind === "ok") {
|
|
38892
39509
|
this.gistId = cachedId;
|
|
38893
|
-
const state = this.mergeCacheInto(fetched);
|
|
39510
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38894
39511
|
this.writeCache(state);
|
|
38895
39512
|
return { gistId: cachedId, state, created: false };
|
|
38896
39513
|
}
|
|
38897
39514
|
} catch (err) {
|
|
38898
39515
|
if (getHttpStatusCode(err) !== 404)
|
|
38899
39516
|
throw err;
|
|
38900
|
-
debug(
|
|
39517
|
+
debug(MODULE14, `Cached gist gone (404): ${errorMessage(err)}`);
|
|
38901
39518
|
}
|
|
38902
|
-
debug(
|
|
39519
|
+
debug(MODULE14, "Cached gist ID invalid, searching...");
|
|
38903
39520
|
}
|
|
38904
39521
|
const search = await this.searchForGist();
|
|
38905
39522
|
if (search.id) {
|
|
38906
|
-
debug(
|
|
39523
|
+
debug(MODULE14, `Found gist via search: ${search.id}`);
|
|
38907
39524
|
this.saveGistId(search.id);
|
|
38908
39525
|
this.gistId = search.id;
|
|
38909
39526
|
const fetched = await this.fetchGistState(search.id);
|
|
38910
|
-
if (fetched) {
|
|
38911
|
-
const state = this.mergeCacheInto(fetched);
|
|
39527
|
+
if (fetched.kind === "ok") {
|
|
39528
|
+
const state = this.mergeCacheInto(fetched.state);
|
|
38912
39529
|
this.writeCache(state);
|
|
38913
39530
|
return { gistId: search.id, state, created: false };
|
|
38914
39531
|
}
|
|
38915
|
-
warn(
|
|
39532
|
+
warn(MODULE14, `Found existing gist ${search.id} but content failed validation. Using local cache to avoid data loss.`);
|
|
38916
39533
|
return this.bootstrapFromCache("unknown");
|
|
38917
39534
|
}
|
|
38918
39535
|
if (!search.exhaustive) {
|
|
38919
|
-
warn(
|
|
39536
|
+
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
39537
|
}
|
|
38921
|
-
debug(
|
|
39538
|
+
debug(MODULE14, "No existing gist found, creating new one");
|
|
38922
39539
|
const freshState = ScoutStateSchema.parse({ version: 1 });
|
|
38923
39540
|
const newId = await this.createGist(freshState);
|
|
38924
39541
|
this.saveGistId(newId);
|
|
@@ -38929,7 +39546,7 @@ var GistStateStore = class {
|
|
|
38929
39546
|
bootstrapFromCache(reason) {
|
|
38930
39547
|
const cached2 = this.readCache();
|
|
38931
39548
|
if (cached2) {
|
|
38932
|
-
debug(
|
|
39549
|
+
debug(MODULE14, "Bootstrapped from local cache (degraded mode)");
|
|
38933
39550
|
const cachedId = this.readCachedGistId();
|
|
38934
39551
|
if (cachedId)
|
|
38935
39552
|
this.gistId = cachedId;
|
|
@@ -38941,7 +39558,7 @@ var GistStateStore = class {
|
|
|
38941
39558
|
degradedReason: reason
|
|
38942
39559
|
};
|
|
38943
39560
|
}
|
|
38944
|
-
debug(
|
|
39561
|
+
debug(MODULE14, "No cache available, using fresh state (degraded mode)");
|
|
38945
39562
|
const fresh = ScoutStateSchema.parse({ version: 1 });
|
|
38946
39563
|
return {
|
|
38947
39564
|
gistId: "",
|
|
@@ -38952,17 +39569,24 @@ var GistStateStore = class {
|
|
|
38952
39569
|
};
|
|
38953
39570
|
}
|
|
38954
39571
|
// ── Gist API operations ──────────────────────────────────────────────
|
|
39572
|
+
/**
|
|
39573
|
+
* "missing" (no file/content) and "invalid" (content that fails parse or
|
|
39574
|
+
* schema validation) are distinct outcomes: an invalid remote may be a
|
|
39575
|
+
* corrupted-but-recoverable state or a newer state version written by a
|
|
39576
|
+
* newer binary, so callers must not treat it as "nothing there" and
|
|
39577
|
+
* overwrite it (#286).
|
|
39578
|
+
*/
|
|
38955
39579
|
async fetchGistState(gistId) {
|
|
38956
39580
|
const { data } = await this.octokit.gists.get({ gist_id: gistId });
|
|
38957
39581
|
const file2 = data.files?.[GIST_FILENAME];
|
|
38958
39582
|
if (!file2?.content)
|
|
38959
|
-
return
|
|
39583
|
+
return { kind: "missing" };
|
|
38960
39584
|
try {
|
|
38961
39585
|
const parsed = JSON.parse(file2.content);
|
|
38962
|
-
return parseScoutState(parsed);
|
|
39586
|
+
return { kind: "ok", state: parseScoutState(parsed) };
|
|
38963
39587
|
} catch (err) {
|
|
38964
|
-
warn(
|
|
38965
|
-
return
|
|
39588
|
+
warn(MODULE14, `Gist content failed validation: ${errorMessage(err)}`);
|
|
39589
|
+
return { kind: "invalid" };
|
|
38966
39590
|
}
|
|
38967
39591
|
}
|
|
38968
39592
|
/**
|
|
@@ -39004,7 +39628,7 @@ var GistStateStore = class {
|
|
|
39004
39628
|
} catch (err) {
|
|
39005
39629
|
const code = err?.code;
|
|
39006
39630
|
if (code !== "ENOENT") {
|
|
39007
|
-
warn(
|
|
39631
|
+
warn(MODULE14, `Failed to read cached gist ID: ${errorMessage(err)}`);
|
|
39008
39632
|
}
|
|
39009
39633
|
return null;
|
|
39010
39634
|
}
|
|
@@ -39030,7 +39654,7 @@ var GistStateStore = class {
|
|
|
39030
39654
|
} catch (err) {
|
|
39031
39655
|
const code = err?.code;
|
|
39032
39656
|
if (code !== "ENOENT") {
|
|
39033
|
-
warn(
|
|
39657
|
+
warn(MODULE14, `Failed to read state cache: ${errorMessage(err)}`);
|
|
39034
39658
|
}
|
|
39035
39659
|
return null;
|
|
39036
39660
|
}
|
|
@@ -39041,7 +39665,7 @@ var GistStateStore = class {
|
|
|
39041
39665
|
mode: 384
|
|
39042
39666
|
});
|
|
39043
39667
|
} catch (err) {
|
|
39044
|
-
warn(
|
|
39668
|
+
warn(MODULE14, `Failed to write cache: ${errorMessage(err)}`);
|
|
39045
39669
|
}
|
|
39046
39670
|
}
|
|
39047
39671
|
};
|
|
@@ -39056,7 +39680,7 @@ function mergeTombstones(local, remote) {
|
|
|
39056
39680
|
}
|
|
39057
39681
|
return [...byUrl.values()].filter((t) => {
|
|
39058
39682
|
const ts = new Date(t.removedAt).getTime();
|
|
39059
|
-
return
|
|
39683
|
+
return Number.isFinite(ts) && ts >= cutoff;
|
|
39060
39684
|
});
|
|
39061
39685
|
}
|
|
39062
39686
|
function applyTombstones(items, tombstones, urlOf, touchedAtOf) {
|
|
@@ -39081,6 +39705,13 @@ function mergeStates(local, remote) {
|
|
|
39081
39705
|
const localPrefsWin = localPrefsTs !== void 0 && (remotePrefsTs === void 0 || localPrefsTs > remotePrefsTs);
|
|
39082
39706
|
const preferences = localPrefsWin ? local.preferences : remote.preferences;
|
|
39083
39707
|
const preferencesUpdatedAt = pickFresherTimestamp(localPrefsTs, remotePrefsTs);
|
|
39708
|
+
const mergedPRsFinal = unionByUrl(local.mergedPRs, remote.mergedPRs);
|
|
39709
|
+
const closedPRsFinal = unionByUrl(local.closedPRs, remote.closedPRs);
|
|
39710
|
+
const resolvedUrls = /* @__PURE__ */ new Set([
|
|
39711
|
+
...mergedPRsFinal.map((p) => p.url),
|
|
39712
|
+
...closedPRsFinal.map((p) => p.url)
|
|
39713
|
+
]);
|
|
39714
|
+
const openPRsFinal = unionByUrl(local.openPRs ?? [], remote.openPRs ?? []).filter((p) => !resolvedUrls.has(p.url));
|
|
39084
39715
|
return {
|
|
39085
39716
|
...local,
|
|
39086
39717
|
...remote,
|
|
@@ -39091,18 +39722,33 @@ function mergeStates(local, remote) {
|
|
|
39091
39722
|
repoScores: mergeRepoScores(local.repoScores, remote.repoScores),
|
|
39092
39723
|
starredRepos: mergeStarredRepos(local, remote),
|
|
39093
39724
|
starredReposLastFetched: pickFresherTimestamp(local.starredReposLastFetched, remote.starredReposLastFetched),
|
|
39094
|
-
mergedPRs:
|
|
39095
|
-
closedPRs:
|
|
39096
|
-
openPRs:
|
|
39725
|
+
mergedPRs: mergedPRsFinal,
|
|
39726
|
+
closedPRs: closedPRsFinal,
|
|
39727
|
+
openPRs: openPRsFinal,
|
|
39097
39728
|
savedResults: savedResultsFinal,
|
|
39098
39729
|
skippedIssues,
|
|
39099
39730
|
lastSearchAt: pickFresherTimestamp(local.lastSearchAt, remote.lastSearchAt),
|
|
39100
39731
|
lastRunAt: pickFresherTimestamp(local.lastRunAt, remote.lastRunAt) ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
39101
|
-
gistId: remote.gistId ?? local.gistId
|
|
39732
|
+
gistId: remote.gistId ?? local.gistId,
|
|
39733
|
+
searchRotation: mergeSearchRotation(local.searchRotation, remote.searchRotation)
|
|
39102
39734
|
};
|
|
39103
39735
|
}
|
|
39736
|
+
function mergeSearchRotation(local, remote) {
|
|
39737
|
+
const localRotation = local ?? { languageOffset: 0 };
|
|
39738
|
+
const remoteRotation = remote ?? { languageOffset: 0 };
|
|
39739
|
+
const localTs = localRotation.lastRotatedAt;
|
|
39740
|
+
const remoteTs = remoteRotation.lastRotatedAt;
|
|
39741
|
+
if (localTs && remoteTs) {
|
|
39742
|
+
return localTs >= remoteTs ? localRotation : remoteRotation;
|
|
39743
|
+
}
|
|
39744
|
+
if (localTs)
|
|
39745
|
+
return localRotation;
|
|
39746
|
+
if (remoteTs)
|
|
39747
|
+
return remoteRotation;
|
|
39748
|
+
return remoteRotation.languageOffset >= localRotation.languageOffset ? remoteRotation : localRotation;
|
|
39749
|
+
}
|
|
39104
39750
|
function mergeRepoScores(local, remote) {
|
|
39105
|
-
const merged =
|
|
39751
|
+
const merged = Object.assign(/* @__PURE__ */ Object.create(null), local);
|
|
39106
39752
|
for (const [repo, remoteScore] of Object.entries(remote)) {
|
|
39107
39753
|
const localScore = merged[repo];
|
|
39108
39754
|
if (!localScore) {
|
|
@@ -39169,7 +39815,7 @@ function pickFresherTimestamp(a, b) {
|
|
|
39169
39815
|
// ../core/dist/core/local-state.js
|
|
39170
39816
|
var fs4 = __toESM(require("fs"), 1);
|
|
39171
39817
|
var path4 = __toESM(require("path"), 1);
|
|
39172
|
-
var
|
|
39818
|
+
var MODULE15 = "local-state";
|
|
39173
39819
|
function getStatePath() {
|
|
39174
39820
|
return path4.join(getDataDir(), "state.json");
|
|
39175
39821
|
}
|
|
@@ -39183,13 +39829,13 @@ function loadLocalState() {
|
|
|
39183
39829
|
if (code === "ENOENT") {
|
|
39184
39830
|
return ScoutStateSchema.parse({ version: 1 });
|
|
39185
39831
|
}
|
|
39186
|
-
warn(
|
|
39832
|
+
warn(MODULE15, `Failed to load state from ${statePath}: ${errorMessage(err)}. Using defaults.`);
|
|
39187
39833
|
try {
|
|
39188
39834
|
const backupPath = `${statePath}.corrupt.${Date.now()}`;
|
|
39189
39835
|
fs4.copyFileSync(statePath, backupPath);
|
|
39190
|
-
warn(
|
|
39836
|
+
warn(MODULE15, `Corrupt state backed up to ${backupPath}`);
|
|
39191
39837
|
} catch (backupErr) {
|
|
39192
|
-
warn(
|
|
39838
|
+
warn(MODULE15, `Failed to back up corrupt state: ${errorMessage(backupErr)}`);
|
|
39193
39839
|
}
|
|
39194
39840
|
return ScoutStateSchema.parse({ version: 1 });
|
|
39195
39841
|
}
|
|
@@ -39209,7 +39855,7 @@ function saveLocalState(state) {
|
|
|
39209
39855
|
}
|
|
39210
39856
|
throw err;
|
|
39211
39857
|
}
|
|
39212
|
-
debug(
|
|
39858
|
+
debug(MODULE15, "State saved");
|
|
39213
39859
|
}
|
|
39214
39860
|
|
|
39215
39861
|
// ../core/dist/scout.js
|
|
@@ -39322,6 +39968,15 @@ var OssScout = class {
|
|
|
39322
39968
|
this.evictStaleCacheEntries();
|
|
39323
39969
|
this.cullExpiredSkips();
|
|
39324
39970
|
const skippedUrls = new Set((this.state.skippedIssues ?? []).map((s) => s.url));
|
|
39971
|
+
if (options?.excludeRecentlySurfaced ?? true) {
|
|
39972
|
+
const ttlDays = options?.recentlySurfacedTtlDays ?? 7;
|
|
39973
|
+
const cutoff = Date.now() - ttlDays * 24 * 60 * 60 * 1e3;
|
|
39974
|
+
for (const r of this.state.savedResults ?? []) {
|
|
39975
|
+
const seen = Date.parse(r.lastSeenAt);
|
|
39976
|
+
if (!Number.isNaN(seen) && seen >= cutoff)
|
|
39977
|
+
skippedUrls.add(r.issueUrl);
|
|
39978
|
+
}
|
|
39979
|
+
}
|
|
39325
39980
|
const discovery = new IssueDiscovery(this.githubToken, this.state.preferences, this);
|
|
39326
39981
|
const prefs = this.state.preferences;
|
|
39327
39982
|
const prefLangs = prefs.preferLanguages ?? [];
|
|
@@ -39333,18 +39988,30 @@ var OssScout = class {
|
|
|
39333
39988
|
const avoidRepos = options?.avoidRepos ?? (prefAvoid.length > 0 ? prefAvoid : void 0);
|
|
39334
39989
|
const boostIssueTypes = options?.boostIssueTypes ?? (prefBoostTypes.length > 0 ? prefBoostTypes : void 0);
|
|
39335
39990
|
const diversityRatio = options?.diversityRatio ?? prefs.diversityRatio ?? 0;
|
|
39336
|
-
const
|
|
39337
|
-
|
|
39338
|
-
|
|
39339
|
-
|
|
39340
|
-
|
|
39341
|
-
|
|
39342
|
-
|
|
39343
|
-
|
|
39344
|
-
|
|
39345
|
-
|
|
39346
|
-
|
|
39347
|
-
|
|
39991
|
+
const rotation = this.state.searchRotation ?? { languageOffset: 0 };
|
|
39992
|
+
let candidates;
|
|
39993
|
+
let strategiesUsed;
|
|
39994
|
+
try {
|
|
39995
|
+
({ candidates, strategiesUsed } = await discovery.searchIssues({
|
|
39996
|
+
maxResults: options?.maxResults,
|
|
39997
|
+
strategies: options?.strategies,
|
|
39998
|
+
skippedUrls,
|
|
39999
|
+
preferLanguages,
|
|
40000
|
+
preferRepos,
|
|
40001
|
+
avoidRepos,
|
|
40002
|
+
boostIssueTypes,
|
|
40003
|
+
diversityRatio,
|
|
40004
|
+
interPhaseDelayMs: options?.interPhaseDelayMs,
|
|
40005
|
+
broadPhaseDelayMs: options?.broadPhaseDelayMs,
|
|
40006
|
+
languageRotationOffset: rotation.languageOffset
|
|
40007
|
+
}));
|
|
40008
|
+
} catch (err) {
|
|
40009
|
+
if (err instanceof ValidationError) {
|
|
40010
|
+
this.advanceRotationIfBroadRan(rotation, err.strategiesUsed ?? []);
|
|
40011
|
+
}
|
|
40012
|
+
throw err;
|
|
40013
|
+
}
|
|
40014
|
+
this.advanceRotationIfBroadRan(rotation, strategiesUsed);
|
|
39348
40015
|
for (const c of candidates) {
|
|
39349
40016
|
this.updateRepoSignalsFromHealth(c.projectHealth);
|
|
39350
40017
|
}
|
|
@@ -39358,6 +40025,23 @@ var OssScout = class {
|
|
|
39358
40025
|
strategiesUsed
|
|
39359
40026
|
};
|
|
39360
40027
|
}
|
|
40028
|
+
/**
|
|
40029
|
+
* Advance the language-rotation cursor when the broad phase actually ran.
|
|
40030
|
+
* Called on both the success path and the zero-candidate ValidationError
|
|
40031
|
+
* path — a broad phase that ran and found nothing must still rotate, or the
|
|
40032
|
+
* same barren language slice would lead every subsequent run (#249
|
|
40033
|
+
* follow-up). Skipped/gated broad phases don't burn a rotation slot.
|
|
40034
|
+
*/
|
|
40035
|
+
advanceRotationIfBroadRan(rotation, strategiesUsed) {
|
|
40036
|
+
if (!strategiesUsed.includes("broad"))
|
|
40037
|
+
return;
|
|
40038
|
+
this.state.searchRotation = {
|
|
40039
|
+
...rotation,
|
|
40040
|
+
languageOffset: rotation.languageOffset + 1,
|
|
40041
|
+
lastRotatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
40042
|
+
};
|
|
40043
|
+
this.dirty = true;
|
|
40044
|
+
}
|
|
39361
40045
|
/**
|
|
39362
40046
|
* Populate the `hasActiveMaintainers` repo-score signal from a freshly
|
|
39363
40047
|
* computed projectHealth (#167). It was initialized false and never set, so
|
|
@@ -39412,12 +40096,16 @@ var OssScout = class {
|
|
|
39412
40096
|
languages: this.state.preferences.languages,
|
|
39413
40097
|
excludeRepos: this.state.preferences.excludeRepos,
|
|
39414
40098
|
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
40099
|
+
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
39415
40100
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
39416
40101
|
}) : await discoverFeatures({
|
|
39417
40102
|
octokit,
|
|
39418
40103
|
vetter,
|
|
39419
40104
|
repoScores: this.state.repoScores ?? {},
|
|
39420
40105
|
count,
|
|
40106
|
+
excludeRepos: this.state.preferences.excludeRepos,
|
|
40107
|
+
excludeOrgs: this.state.preferences.excludeOrgs,
|
|
40108
|
+
aiPolicyBlocklist: this.state.preferences.aiPolicyBlocklist,
|
|
39421
40109
|
anchorThreshold: options?.anchorThreshold ?? this.state.preferences.featuresAnchorThreshold,
|
|
39422
40110
|
splitRatio: options?.splitRatio ?? this.state.preferences.featuresSplitRatio
|
|
39423
40111
|
});
|
|
@@ -39902,6 +40590,7 @@ var OssScout = class {
|
|
|
39902
40590
|
}
|
|
39903
40591
|
if (this.persistLocal) {
|
|
39904
40592
|
try {
|
|
40593
|
+
this.state = mergeStates(this.state, loadLocalState());
|
|
39905
40594
|
saveLocalState(this.state);
|
|
39906
40595
|
} catch {
|
|
39907
40596
|
return false;
|
|
@@ -40038,6 +40727,8 @@ function applyPreferenceField(preferences, key, value) {
|
|
|
40038
40727
|
const prefs = { ...preferences };
|
|
40039
40728
|
switch (field.type) {
|
|
40040
40729
|
case "string":
|
|
40730
|
+
if (key === "slmTriageHost")
|
|
40731
|
+
assertValidTriageHost(value);
|
|
40041
40732
|
prefs[key] = value;
|
|
40042
40733
|
break;
|
|
40043
40734
|
case "boolean":
|
|
@@ -40117,7 +40808,7 @@ function registerTools(server, scout) {
|
|
|
40117
40808
|
"search",
|
|
40118
40809
|
"Search for open source issues matching your preferences",
|
|
40119
40810
|
{
|
|
40120
|
-
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
40811
|
+
maxResults: external_exports3.number().int().min(1).max(100).optional().describe("Maximum number of results to return (1-100, default 10)"),
|
|
40121
40812
|
strategies: external_exports3.string().optional().describe(
|
|
40122
40813
|
"Comma-separated search strategies: merged, starred, broad, maintained, all"
|
|
40123
40814
|
),
|
|
@@ -40147,7 +40838,16 @@ function registerTools(server, scout) {
|
|
|
40147
40838
|
diversityRatio
|
|
40148
40839
|
}) => {
|
|
40149
40840
|
try {
|
|
40150
|
-
const parsedStrategies = strategies ? strategies.split(",").map((s) =>
|
|
40841
|
+
const parsedStrategies = strategies ? strategies.split(",").map((s) => {
|
|
40842
|
+
const trimmed = s.trim();
|
|
40843
|
+
const parsed = SearchStrategySchema.safeParse(trimmed);
|
|
40844
|
+
if (!parsed.success) {
|
|
40845
|
+
throw new Error(
|
|
40846
|
+
`Invalid strategy "${trimmed}". Valid strategies: ${SearchStrategySchema.options.join(", ")}`
|
|
40847
|
+
);
|
|
40848
|
+
}
|
|
40849
|
+
return parsed.data;
|
|
40850
|
+
}) : void 0;
|
|
40151
40851
|
const result = await withTimeout(
|
|
40152
40852
|
scout.search({
|
|
40153
40853
|
maxResults: maxResults ?? 10,
|
|
@@ -40190,7 +40890,7 @@ function registerTools(server, scout) {
|
|
|
40190
40890
|
"scout-features",
|
|
40191
40891
|
"Surface feature-scoped contribution opportunities in repos where you have 3+ merged PRs",
|
|
40192
40892
|
{
|
|
40193
|
-
maxResults: external_exports3.number().optional().describe("Maximum number of results to return (default 10)"),
|
|
40893
|
+
maxResults: external_exports3.number().int().min(1).max(50).optional().describe("Maximum number of results to return (1-50, default 10)"),
|
|
40194
40894
|
anchorThreshold: external_exports3.number().int().min(1).max(50).optional().describe("Anchor threshold override (default 3)"),
|
|
40195
40895
|
splitRatio: external_exports3.number().min(0).max(1).optional().describe("Quick-wins/bigger-bets split ratio (default 0.6)")
|
|
40196
40896
|
},
|
|
@@ -40457,7 +41157,7 @@ function registerResources(server, scout) {
|
|
|
40457
41157
|
// src/index.ts
|
|
40458
41158
|
var SERVER_INFO = {
|
|
40459
41159
|
name: "oss-scout-mcp",
|
|
40460
|
-
version: "0.10.
|
|
41160
|
+
version: "0.10.2"
|
|
40461
41161
|
// x-release-please-version
|
|
40462
41162
|
};
|
|
40463
41163
|
function createServer(scout) {
|