@oss-scout/mcp 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3100,16 +3100,18 @@ var require_data = __commonJS({
3100
3100
  }
3101
3101
  });
3102
3102
 
3103
- // ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/utils.js
3103
+ // ../../node_modules/.pnpm/fast-uri@4.1.5/node_modules/fast-uri/lib/utils.js
3104
3104
  var require_utils = __commonJS({
3105
- "../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/utils.js"(exports2, module2) {
3105
+ "../../node_modules/.pnpm/fast-uri@4.1.5/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
+ var isPort = RegExp.prototype.test.bind(/^\d*$/u);
3109
3110
  var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
3110
3111
  var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
3111
- var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
3112
- var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/?]$/iu);
3112
+ var isPathCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/]$/u);
3113
+ var isQueryFragmentCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:@/?]$/u);
3114
+ var isUserinfoCharacter = RegExp.prototype.test.bind(/^[A-Za-z0-9\-._~!$&'()*+,;=:]$/u);
3113
3115
  function stringArrayToHexStripped(input) {
3114
3116
  let acc = "";
3115
3117
  let code = 0;
@@ -3134,91 +3136,110 @@ var require_utils = __commonJS({
3134
3136
  }
3135
3137
  return acc;
3136
3138
  }
3139
+ var isHextet = RegExp.prototype.test.bind(/^[\dA-Fa-f]{1,4}$/);
3140
+ var isIPvFuture = RegExp.prototype.test.bind(/^[vV][\dA-Fa-f]+\.[A-Za-z\d\-._~!$&'()*+,;=:]+$/);
3141
+ var isZoneCharacter = RegExp.prototype.test.bind(/^[A-Za-z\d\-._~]$/);
3137
3142
  var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
3138
- function consumeIsZone(buffer) {
3139
- buffer.length = 0;
3140
- return true;
3141
- }
3142
- function consumeHextets(buffer, address, output) {
3143
- if (buffer.length) {
3144
- const hex3 = stringArrayToHexStripped(buffer);
3145
- if (hex3 !== "") {
3146
- address.push(hex3);
3147
- } else {
3148
- output.error = true;
3149
- return false;
3143
+ var nonSimpleDomainChars = RegExp.prototype.test.bind(/[^\d!"$&'()*+,\-.;=_`a-z{}~]/u);
3144
+ var numericLastLabel = RegExp.prototype.test.bind(/(?:^|\.)\d+$/u);
3145
+ function nonSimpleMailtoDomain(domain2) {
3146
+ return nonSimpleDomainChars(domain2) || numericLastLabel(domain2);
3147
+ }
3148
+ function isZoneIdentifier(zone) {
3149
+ if (zone.length === 0) return false;
3150
+ for (let i = 0; i < zone.length; i++) {
3151
+ if (isZoneCharacter(zone[i])) continue;
3152
+ if (zone[i] === "%" && i + 2 < zone.length && isHexPair(zone.slice(i + 1, i + 3))) {
3153
+ i += 2;
3154
+ continue;
3150
3155
  }
3151
- buffer.length = 0;
3156
+ return false;
3152
3157
  }
3153
3158
  return true;
3154
3159
  }
3155
- function getIPV6(input) {
3156
- let tokenCount = 0;
3157
- const output = { error: false, address: "", zone: "" };
3158
- const address = [];
3159
- const buffer = [];
3160
- let endipv6Encountered = false;
3161
- let endIpv6 = false;
3162
- let consume = consumeHextets;
3163
- for (let i = 0; i < input.length; i++) {
3164
- const cursor = input[i];
3165
- if (cursor === "[" || cursor === "]") {
3166
- continue;
3167
- }
3168
- if (cursor === ":") {
3169
- if (endipv6Encountered === true) {
3170
- endIpv6 = true;
3171
- }
3172
- if (!consume(buffer, address, output)) {
3173
- break;
3174
- }
3175
- if (++tokenCount > 7) {
3176
- output.error = true;
3177
- break;
3178
- }
3179
- if (i > 0 && input[i - 1] === ":") {
3180
- endipv6Encountered = true;
3181
- }
3182
- address.push(":");
3183
- continue;
3184
- } else if (cursor === "%") {
3185
- if (!consume(buffer, address, output)) {
3186
- break;
3160
+ function compressIPv6ZeroRun(hextets) {
3161
+ let bestStart = -1;
3162
+ let bestLength = 0;
3163
+ let runStart = -1;
3164
+ let runLength = 0;
3165
+ for (let i = 0; i < hextets.length; i++) {
3166
+ if (hextets[i] === "0") {
3167
+ if (runStart === -1) runStart = i;
3168
+ runLength++;
3169
+ if (runLength > bestLength) {
3170
+ bestLength = runLength;
3171
+ bestStart = runStart;
3187
3172
  }
3188
- consume = consumeIsZone;
3189
3173
  } else {
3190
- buffer.push(cursor);
3174
+ runStart = -1;
3175
+ runLength = 0;
3176
+ }
3177
+ }
3178
+ if (bestLength < 2) return hextets.join(":");
3179
+ const head = hextets.slice(0, bestStart).join(":");
3180
+ const tail = hextets.slice(bestStart + bestLength).join(":");
3181
+ return head + "::" + tail;
3182
+ }
3183
+ function normalizeIPv6Address(input) {
3184
+ const compression = input.indexOf("::");
3185
+ if (compression !== -1 && input.indexOf("::", compression + 1) !== -1) return void 0;
3186
+ const left = compression === -1 ? input.split(":") : input.slice(0, compression).split(":");
3187
+ const right = compression === -1 ? [] : input.slice(compression + 2).split(":");
3188
+ if (compression !== -1) {
3189
+ if (left.length === 1 && left[0] === "") left.length = 0;
3190
+ if (right.length === 1 && right[0] === "") right.length = 0;
3191
+ }
3192
+ const parts = left.concat(right);
3193
+ let hextetCount = 0;
3194
+ for (let i = 0; i < parts.length; i++) {
3195
+ const part = parts[i];
3196
+ if (part === "") return void 0;
3197
+ if (part.indexOf(".") !== -1) {
3198
+ if (i !== parts.length - 1 || compression !== -1 && right.length === 0 || !isIPv4(part)) return void 0;
3199
+ hextetCount += 2;
3191
3200
  continue;
3192
3201
  }
3202
+ if (!isHextet(part)) return void 0;
3203
+ parts[i] = parseInt(part, 16).toString(16);
3204
+ hextetCount++;
3193
3205
  }
3194
- if (buffer.length) {
3195
- if (consume === consumeIsZone) {
3196
- output.zone = buffer.join("");
3197
- } else if (endIpv6) {
3198
- address.push(buffer.join(""));
3199
- } else {
3200
- address.push(stringArrayToHexStripped(buffer));
3201
- }
3206
+ if (compression === -1) {
3207
+ if (hextetCount !== 8) return void 0;
3208
+ return compressIPv6ZeroRun(parts);
3202
3209
  }
3203
- output.address = address.join("");
3204
- return output;
3210
+ if (hextetCount >= 8) return void 0;
3211
+ const expanded = parts.slice(0, left.length);
3212
+ for (let i = hextetCount; i < 8; i++) expanded.push("0");
3213
+ for (let i = left.length; i < parts.length; i++) expanded.push(parts[i]);
3214
+ return compressIPv6ZeroRun(expanded);
3205
3215
  }
3206
3216
  function normalizeIPv6(host) {
3207
- if (findToken(host, ":") < 2) {
3208
- return { host, isIPV6: false };
3209
- }
3210
- const ipv63 = getIPV6(host);
3211
- if (!ipv63.error) {
3212
- let newHost = ipv63.address;
3213
- let escapedHost = ipv63.address;
3214
- if (ipv63.zone) {
3215
- newHost += "%" + ipv63.zone;
3216
- escapedHost += "%25" + ipv63.zone;
3217
- }
3218
- return { host: newHost, isIPV6: true, escapedHost };
3219
- } else {
3220
- return { host, isIPV6: false };
3221
- }
3217
+ const bracketed = host[0] === "[" && host[host.length - 1] === "]";
3218
+ const hasBracket = host[0] === "[" || host[host.length - 1] === "]";
3219
+ if (hasBracket && !bracketed) return { host, isIPV6: false, error: true };
3220
+ let input = bracketed ? host.slice(1, -1) : host;
3221
+ if (bracketed && isIPvFuture(input)) {
3222
+ input = input.toLowerCase();
3223
+ return { host: `[${input}]`, escapedHost: input, isIPV6: false, isIPVFuture: true };
3224
+ }
3225
+ if (findToken(input, ":") < 2) {
3226
+ return { host, isIPV6: false, error: bracketed };
3227
+ }
3228
+ let zoneIdentifier = "";
3229
+ const zoneSeparator = input.indexOf("%");
3230
+ if (zoneSeparator !== -1) {
3231
+ const separatorLength = input.slice(zoneSeparator, zoneSeparator + 3).toLowerCase() === "%25" ? 3 : 1;
3232
+ zoneIdentifier = input.slice(zoneSeparator + separatorLength);
3233
+ if (!isZoneIdentifier(zoneIdentifier)) return { host, isIPV6: false, error: true };
3234
+ input = input.slice(0, zoneSeparator);
3235
+ }
3236
+ const address = normalizeIPv6Address(input);
3237
+ if (address === void 0) return { host, isIPV6: false, error: true };
3238
+ return {
3239
+ host: address + (zoneIdentifier ? "%" + zoneIdentifier : ""),
3240
+ escapedHost: address + (zoneIdentifier ? "%25" + zoneIdentifier : ""),
3241
+ isIPV6: true
3242
+ };
3222
3243
  }
3223
3244
  function findToken(str, token) {
3224
3245
  let ind = 0;
@@ -3394,6 +3415,45 @@ var require_utils = __commonJS({
3394
3415
  }
3395
3416
  return output;
3396
3417
  }
3418
+ function serializePathEncoding(input, pathNoScheme = false) {
3419
+ let output = "";
3420
+ let firstSegment = pathNoScheme && input[0] !== "/";
3421
+ for (let i = 0; i < input.length; i++) {
3422
+ const ch = input[i];
3423
+ if (ch === "%" && i + 2 < input.length) {
3424
+ const hex3 = input.slice(i + 1, i + 3);
3425
+ if (isHexPair(hex3)) {
3426
+ output += "%" + hex3.toUpperCase();
3427
+ i += 2;
3428
+ continue;
3429
+ }
3430
+ }
3431
+ if (ch === "/") {
3432
+ firstSegment = false;
3433
+ }
3434
+ if (isPathCharacter(ch) && (ch !== ":" || !firstSegment)) {
3435
+ output += ch;
3436
+ } else {
3437
+ const code = input.charCodeAt(i);
3438
+ if (code < 128) {
3439
+ output += BYTE_HEX[code];
3440
+ } else if (code < 55296 || code > 57343) {
3441
+ output += percentEncodeNonAscii(code);
3442
+ } else if (code <= 56319 && i + 1 < input.length) {
3443
+ const low = input.charCodeAt(i + 1);
3444
+ if (low >= 56320 && low <= 57343) {
3445
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3446
+ i++;
3447
+ } else {
3448
+ output += percentEncodeNonAscii(65533);
3449
+ }
3450
+ } else {
3451
+ output += percentEncodeNonAscii(65533);
3452
+ }
3453
+ }
3454
+ }
3455
+ return output;
3456
+ }
3397
3457
  function normalizeQueryFragmentEncoding(input) {
3398
3458
  let output = "";
3399
3459
  for (let i = 0; i < input.length; i++) {
@@ -3435,6 +3495,50 @@ var require_utils = __commonJS({
3435
3495
  }
3436
3496
  return output;
3437
3497
  }
3498
+ function encodeComponent(input, isAllowed) {
3499
+ let output = "";
3500
+ for (let i = 0; i < input.length; i++) {
3501
+ const ch = input[i];
3502
+ if (ch === "%" && i + 2 < input.length) {
3503
+ const hex3 = input.slice(i + 1, i + 3);
3504
+ if (isHexPair(hex3)) {
3505
+ output += "%" + hex3.toUpperCase();
3506
+ i += 2;
3507
+ continue;
3508
+ }
3509
+ }
3510
+ if (isAllowed(ch)) {
3511
+ output += ch;
3512
+ } else {
3513
+ const code = input.charCodeAt(i);
3514
+ if (code < 128) {
3515
+ output += BYTE_HEX[code];
3516
+ } else if (code < 55296 || code > 57343) {
3517
+ output += percentEncodeNonAscii(code);
3518
+ } else if (code <= 56319 && i + 1 < input.length) {
3519
+ const low = input.charCodeAt(i + 1);
3520
+ if (low >= 56320 && low <= 57343) {
3521
+ output += percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3522
+ i++;
3523
+ } else {
3524
+ output += percentEncodeNonAscii(65533);
3525
+ }
3526
+ } else {
3527
+ output += percentEncodeNonAscii(65533);
3528
+ }
3529
+ }
3530
+ }
3531
+ return output;
3532
+ }
3533
+ function encodeUserinfo(input) {
3534
+ return encodeComponent(input, isUserinfoCharacter);
3535
+ }
3536
+ function encodeQuery(input) {
3537
+ return encodeComponent(input, isQueryFragmentCharacter);
3538
+ }
3539
+ function encodeFragment(input) {
3540
+ return encodeComponent(input, isQueryFragmentCharacter);
3541
+ }
3438
3542
  function escapePreservingEscapes(input) {
3439
3543
  let output = "";
3440
3544
  for (let i = 0; i < input.length; i++) {
@@ -3469,14 +3573,18 @@ var require_utils = __commonJS({
3469
3573
  function recomposeAuthority(component) {
3470
3574
  const uriTokens = [];
3471
3575
  if (component.userinfo !== void 0) {
3472
- uriTokens.push(component.userinfo);
3576
+ uriTokens.push(encodeUserinfo(component.userinfo));
3473
3577
  uriTokens.push("@");
3474
3578
  }
3475
3579
  if (component.host !== void 0) {
3476
- let host = unescape(component.host);
3580
+ let host = component.host;
3477
3581
  if (!isIPv4(host)) {
3478
- const ipV6res = normalizeIPv6(host);
3479
- if (ipV6res.isIPV6 === true) {
3582
+ let ipV6res = normalizeIPv6(host);
3583
+ if (ipV6res.isIPV6 !== true && ipV6res.isIPVFuture !== true) {
3584
+ host = normalizePercentEncoding(host, true);
3585
+ ipV6res = normalizeIPv6(host);
3586
+ }
3587
+ if (ipV6res.isIPV6 === true || ipV6res.isIPVFuture === true) {
3480
3588
  host = `[${ipV6res.escapedHost}]`;
3481
3589
  } else {
3482
3590
  host = reescapeHostDelimiters(host, false);
@@ -3485,18 +3593,29 @@ var require_utils = __commonJS({
3485
3593
  uriTokens.push(host);
3486
3594
  }
3487
3595
  if (typeof component.port === "number" || typeof component.port === "string") {
3596
+ const port = String(component.port);
3597
+ if (!isPort(port)) {
3598
+ throw new TypeError("URI port is malformed.");
3599
+ }
3488
3600
  uriTokens.push(":");
3489
- uriTokens.push(String(component.port));
3601
+ uriTokens.push(port);
3490
3602
  }
3491
3603
  return uriTokens.length ? uriTokens.join("") : void 0;
3492
3604
  }
3493
3605
  module2.exports = {
3606
+ BYTE_HEX,
3607
+ percentEncodeNonAscii,
3494
3608
  nonSimpleDomain,
3609
+ nonSimpleMailtoDomain,
3495
3610
  recomposeAuthority,
3496
3611
  reescapeHostDelimiters,
3497
3612
  normalizePercentEncoding,
3498
3613
  normalizePathEncoding,
3614
+ serializePathEncoding,
3499
3615
  normalizeQueryFragmentEncoding,
3616
+ encodeUserinfo,
3617
+ encodeQuery,
3618
+ encodeFragment,
3500
3619
  escapePreservingEscapes,
3501
3620
  removeDotSegments,
3502
3621
  isIPv4,
@@ -3507,12 +3626,12 @@ var require_utils = __commonJS({
3507
3626
  }
3508
3627
  });
3509
3628
 
3510
- // ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/schemes.js
3629
+ // ../../node_modules/.pnpm/fast-uri@4.1.5/node_modules/fast-uri/lib/schemes.js
3511
3630
  var require_schemes = __commonJS({
3512
- "../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
3631
+ "../../node_modules/.pnpm/fast-uri@4.1.5/node_modules/fast-uri/lib/schemes.js"(exports2, module2) {
3513
3632
  "use strict";
3514
- var { isUUID } = require_utils();
3515
- var URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu;
3633
+ var { isUUID, BYTE_HEX, percentEncodeNonAscii, nonSimpleMailtoDomain } = require_utils();
3634
+ var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
3516
3635
  var supportedSchemeNames = (
3517
3636
  /** @type {const} */
3518
3637
  [
@@ -3521,7 +3640,8 @@ var require_schemes = __commonJS({
3521
3640
  "ws",
3522
3641
  "wss",
3523
3642
  "urn",
3524
- "urn:uuid"
3643
+ "urn:uuid",
3644
+ "mailto"
3525
3645
  ]
3526
3646
  );
3527
3647
  function isValidSchemeName(name) {
@@ -3573,9 +3693,10 @@ var require_schemes = __commonJS({
3573
3693
  wsComponent.secure = void 0;
3574
3694
  }
3575
3695
  if (wsComponent.resourceName) {
3576
- const [path5, query] = wsComponent.resourceName.split("?");
3696
+ const queryIndex = wsComponent.resourceName.indexOf("?");
3697
+ const path5 = queryIndex === -1 ? wsComponent.resourceName : wsComponent.resourceName.slice(0, queryIndex);
3577
3698
  wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
3578
- wsComponent.query = query;
3699
+ wsComponent.query = queryIndex === -1 ? void 0 : wsComponent.resourceName.slice(queryIndex + 1);
3579
3700
  wsComponent.resourceName = void 0;
3580
3701
  }
3581
3702
  wsComponent.fragment = void 0;
@@ -3587,7 +3708,7 @@ var require_schemes = __commonJS({
3587
3708
  return urnComponent;
3588
3709
  }
3589
3710
  const matches = urnComponent.path.match(URN_REG);
3590
- if (matches) {
3711
+ if (matches && matches[0] === urnComponent.path) {
3591
3712
  const scheme = options.scheme || urnComponent.scheme || "urn";
3592
3713
  urnComponent.nid = matches[1].toLowerCase();
3593
3714
  urnComponent.nss = matches[2];
@@ -3687,6 +3808,242 @@ var require_schemes = __commonJS({
3687
3808
  skipNormalize: true
3688
3809
  }
3689
3810
  );
3811
+ var LOCAL_PART_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+=";
3812
+ var HFNAME_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+,;:@";
3813
+ var DTEXT_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~!$'()*+;:";
3814
+ function allowSet(chars, allowNonAscii = false) {
3815
+ const table = new Uint8Array(128);
3816
+ for (let i = 0; i < chars.length; i++) {
3817
+ table[chars.charCodeAt(i)] = 1;
3818
+ }
3819
+ const escaped = chars.replace(/[\\\]^-]/gu, "\\$&");
3820
+ const cls = allowNonAscii ? `[${escaped}]|[^\\0-\\x7F\\uD800-\\uDFFF]` : `[${escaped}]`;
3821
+ return { all: new RegExp(`^(?:${cls})*$`, "u"), table, allowNonAscii };
3822
+ }
3823
+ var LOCAL_PART = allowSet(LOCAL_PART_ALLOWED);
3824
+ var HFNAME = allowSet(HFNAME_ALLOWED);
3825
+ var DTEXT = allowSet(DTEXT_ALLOWED);
3826
+ var DTEXT_IRI = allowSet(DTEXT_ALLOWED, true);
3827
+ var HEX_PAIR = /^[\da-f]{2}$/iu;
3828
+ var MAILTO_DOMAIN_LITERAL = /^\[[\x21-\x5A\x5E-\x7E]*\]$/u;
3829
+ var MAILTO_DOMAIN_ERROR = "URI mailto has an invalid recipient domain.";
3830
+ var HAS_SURROGATE = /[\uD800-\uDFFF]/u;
3831
+ function decodeHex(str) {
3832
+ if (typeof str !== "string" || str.indexOf("%") === -1) {
3833
+ return str;
3834
+ }
3835
+ try {
3836
+ return decodeURIComponent(str);
3837
+ } catch {
3838
+ return str;
3839
+ }
3840
+ }
3841
+ function replaceLoneSurrogates(input) {
3842
+ let result = "";
3843
+ for (let i = 0; i < input.length; i++) {
3844
+ const code = input.charCodeAt(i);
3845
+ if (code >= 55296 && code <= 56319 && i + 1 < input.length) {
3846
+ const low = input.charCodeAt(i + 1);
3847
+ if (low >= 56320 && low <= 57343) {
3848
+ result += input[i] + input[i + 1];
3849
+ i++;
3850
+ continue;
3851
+ }
3852
+ }
3853
+ result += code >= 55296 && code <= 57343 ? "\uFFFD" : input[i];
3854
+ }
3855
+ return result;
3856
+ }
3857
+ function encodeWithAllow(input, set2) {
3858
+ if (set2.all.test(input)) {
3859
+ return input;
3860
+ }
3861
+ const table = set2.table;
3862
+ let result = "";
3863
+ for (let i = 0; i < input.length; i++) {
3864
+ const code = input.charCodeAt(i);
3865
+ if (code < 128) {
3866
+ if (table[code] === 1) {
3867
+ result += input[i];
3868
+ } else if (code === 37 && i + 2 < input.length && HEX_PAIR.test(input.slice(i + 1, i + 3))) {
3869
+ result += "%" + input.slice(i + 1, i + 3).toUpperCase();
3870
+ i += 2;
3871
+ } else {
3872
+ result += BYTE_HEX[code];
3873
+ }
3874
+ continue;
3875
+ }
3876
+ if (code < 55296 || code > 57343) {
3877
+ result += set2.allowNonAscii ? input[i] : percentEncodeNonAscii(code);
3878
+ continue;
3879
+ }
3880
+ if (code <= 56319 && i + 1 < input.length) {
3881
+ const low = input.charCodeAt(i + 1);
3882
+ if (low >= 56320 && low <= 57343) {
3883
+ result += set2.allowNonAscii ? input[i] + input[i + 1] : percentEncodeNonAscii(65536 + (code - 55296 << 10) + (low - 56320));
3884
+ i++;
3885
+ continue;
3886
+ }
3887
+ }
3888
+ result += set2.allowNonAscii ? "\uFFFD" : percentEncodeNonAscii(65533);
3889
+ }
3890
+ return result;
3891
+ }
3892
+ function mailtoNormalizeDomain(domain2, options, component) {
3893
+ const normalizedDomain = String(domain2).toLowerCase();
3894
+ if (MAILTO_DOMAIN_LITERAL.test(normalizedDomain)) {
3895
+ return normalizedDomain;
3896
+ }
3897
+ if (normalizedDomain !== "" && !nonSimpleMailtoDomain(normalizedDomain)) {
3898
+ return normalizedDomain;
3899
+ }
3900
+ try {
3901
+ const parsedDomain = new URL("http://" + normalizedDomain);
3902
+ if (parsedDomain.username || parsedDomain.password || parsedDomain.port || parsedDomain.pathname !== "/" || parsedDomain.search || parsedDomain.hash || !parsedDomain.hostname) {
3903
+ throw new Error(MAILTO_DOMAIN_ERROR);
3904
+ }
3905
+ return options && options.unicodeSupport ? normalizedDomain : parsedDomain.hostname;
3906
+ } catch {
3907
+ if (component) {
3908
+ component.error = component.error || MAILTO_DOMAIN_ERROR;
3909
+ }
3910
+ return normalizedDomain;
3911
+ }
3912
+ }
3913
+ function mailtoEncodeDomain(domain2, options) {
3914
+ const set2 = options && options.unicodeSupport ? DTEXT_IRI : DTEXT;
3915
+ if (domain2.length > 1 && domain2[0] === "[" && domain2[domain2.length - 1] === "]") {
3916
+ return "[" + encodeWithAllow(domain2.slice(1, -1), set2) + "]";
3917
+ }
3918
+ return encodeWithAllow(domain2, set2);
3919
+ }
3920
+ function mailtoParse(component, options) {
3921
+ const mailtoComponent = component;
3922
+ let rawPath = mailtoComponent.path;
3923
+ let rawQuery = mailtoComponent.query;
3924
+ if (rawPath && HAS_SURROGATE.test(rawPath)) rawPath = replaceLoneSurrogates(rawPath);
3925
+ if (rawQuery && HAS_SURROGATE.test(rawQuery)) rawQuery = replaceLoneSurrogates(rawQuery);
3926
+ const to = rawPath ? rawPath.indexOf(",") === -1 ? [rawPath] : rawPath.split(",") : [];
3927
+ mailtoComponent.path = void 0;
3928
+ if (rawQuery) {
3929
+ let headers = null;
3930
+ let start = 0;
3931
+ while (start <= rawQuery.length) {
3932
+ let end = rawQuery.indexOf("&", start);
3933
+ if (end === -1) end = rawQuery.length;
3934
+ const token = rawQuery.slice(start, end);
3935
+ start = end + 1;
3936
+ const eqIdx = token.indexOf("=");
3937
+ if (eqIdx !== token.lastIndexOf("=")) {
3938
+ mailtoComponent.error = mailtoComponent.error || "URI mailto has malformed header fields.";
3939
+ continue;
3940
+ }
3941
+ const name = decodeHex(eqIdx === -1 ? token : token.slice(0, eqIdx));
3942
+ const normalizedName = name.toLowerCase();
3943
+ const value = eqIdx === -1 ? "" : token.slice(eqIdx + 1);
3944
+ if (normalizedName === "to") {
3945
+ const addrs = value.split(",");
3946
+ for (let j = 0; j < addrs.length; j++) to.push(addrs[j]);
3947
+ continue;
3948
+ }
3949
+ if (normalizedName === "subject") {
3950
+ mailtoComponent.subject = decodeHex(value);
3951
+ continue;
3952
+ }
3953
+ if (normalizedName === "body") {
3954
+ mailtoComponent.body = decodeHex(value);
3955
+ continue;
3956
+ }
3957
+ if (headers === null) headers = /** @type {Record<string,string>} */
3958
+ /* @__PURE__ */ Object.create(null);
3959
+ headers[name] = decodeHex(value);
3960
+ }
3961
+ if (headers !== null) mailtoComponent.headers = headers;
3962
+ }
3963
+ mailtoComponent.query = void 0;
3964
+ for (let i = 0; i < to.length; i++) {
3965
+ const rawAddr = to[i];
3966
+ const atIdx = rawAddr.lastIndexOf("@");
3967
+ if (atIdx < 0) {
3968
+ mailtoComponent.error = mailtoComponent.error || MAILTO_DOMAIN_ERROR;
3969
+ to[i] = decodeHex(rawAddr);
3970
+ continue;
3971
+ }
3972
+ const local = decodeHex(rawAddr.slice(0, atIdx));
3973
+ const domain2 = mailtoNormalizeDomain(decodeHex(rawAddr.slice(atIdx + 1)), options, mailtoComponent);
3974
+ to[i] = local + "@" + domain2;
3975
+ }
3976
+ if (to.length) mailtoComponent.to = to;
3977
+ return mailtoComponent;
3978
+ }
3979
+ function mailtoEncodeHeaderName(name) {
3980
+ return encodeWithAllow(name.replace(/%/gu, "%25"), HFNAME);
3981
+ }
3982
+ function mailtoSerialize(component, options) {
3983
+ const mailtoComponent = component;
3984
+ const to = Array.isArray(mailtoComponent.to) ? mailtoComponent.to.slice() : [];
3985
+ const sourceHeaders = mailtoComponent.headers && typeof mailtoComponent.headers === "object" ? Object.assign(/* @__PURE__ */ Object.create(null), mailtoComponent.headers) : /* @__PURE__ */ Object.create(null);
3986
+ const headers = /* @__PURE__ */ Object.create(null);
3987
+ for (const name in sourceHeaders) {
3988
+ const normalizedName = name.toLowerCase();
3989
+ if (normalizedName === "to") {
3990
+ const addrs = String(sourceHeaders[name]).split(",");
3991
+ for (let i = 0; i < addrs.length; i++) to.push(addrs[i]);
3992
+ } else if (normalizedName === "subject" || normalizedName === "body") {
3993
+ headers[normalizedName] = sourceHeaders[name];
3994
+ } else {
3995
+ headers[name] = sourceHeaders[name];
3996
+ }
3997
+ }
3998
+ if (mailtoComponent.subject !== void 0) headers.subject = mailtoComponent.subject;
3999
+ if (mailtoComponent.body !== void 0) headers.body = mailtoComponent.body;
4000
+ mailtoComponent.headers = headers;
4001
+ if (to.length) {
4002
+ for (let i = 0; i < to.length; i++) {
4003
+ const addr = String(to[i]);
4004
+ const atIdx = addr.lastIndexOf("@");
4005
+ const rawLocal = atIdx >= 0 ? addr.slice(0, atIdx) : addr;
4006
+ const rawDomain = atIdx >= 0 ? addr.slice(atIdx + 1) : "";
4007
+ const local = encodeWithAllow(rawLocal, LOCAL_PART);
4008
+ const decodedDomain = decodeHex(rawDomain);
4009
+ const normalizedDomain = mailtoNormalizeDomain(decodedDomain, options);
4010
+ to[i] = local + "@" + mailtoEncodeDomain(normalizedDomain, options);
4011
+ }
4012
+ mailtoComponent.path = to.join(",");
4013
+ options.skipEscape = true;
4014
+ } else {
4015
+ mailtoComponent.path = void 0;
4016
+ }
4017
+ let query = "";
4018
+ let count = 0;
4019
+ for (const name in headers) {
4020
+ if (count++ !== 0) query += "&";
4021
+ query += mailtoEncodeHeaderName(name) + "=" + encodeWithAllow(String(headers[name]), HFNAME);
4022
+ }
4023
+ if (count !== 0) {
4024
+ mailtoComponent.query = query;
4025
+ } else {
4026
+ mailtoComponent.headers = void 0;
4027
+ }
4028
+ return mailtoComponent;
4029
+ }
4030
+ var mailto = (
4031
+ /** @type {SchemeHandler} */
4032
+ {
4033
+ scheme: "mailto",
4034
+ parse: mailtoParse,
4035
+ serialize: mailtoSerialize,
4036
+ domainHost: false,
4037
+ unicodeSupport: true,
4038
+ // `mailtoParse` re-derives every component from the raw path/query via
4039
+ // `decodeHex`, which subsumes the generic normalizers, so running them first
4040
+ // is wasted work (~28% of parsing a URI with header fields).
4041
+ skipNormalize: true,
4042
+ // A recipient list has no dot segments to remove, and `removeDotSegments`
4043
+ // would rewrite a "./"-prefixed local part.
4044
+ absolutePath: true
4045
+ }
4046
+ );
3690
4047
  var SCHEMES = (
3691
4048
  /** @type {Record<SchemeName, SchemeHandler>} */
3692
4049
  {
@@ -3695,7 +4052,8 @@ var require_schemes = __commonJS({
3695
4052
  ws,
3696
4053
  wss,
3697
4054
  urn,
3698
- "urn:uuid": urnuuid
4055
+ "urn:uuid": urnuuid,
4056
+ mailto
3699
4057
  }
3700
4058
  );
3701
4059
  Object.setPrototypeOf(SCHEMES, null);
@@ -3717,12 +4075,21 @@ var require_schemes = __commonJS({
3717
4075
  }
3718
4076
  });
3719
4077
 
3720
- // ../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/index.js
4078
+ // ../../node_modules/.pnpm/fast-uri@4.1.5/node_modules/fast-uri/index.js
3721
4079
  var require_fast_uri = __commonJS({
3722
- "../../node_modules/.pnpm/fast-uri@4.1.2/node_modules/fast-uri/index.js"(exports2, module2) {
4080
+ "../../node_modules/.pnpm/fast-uri@4.1.5/node_modules/fast-uri/index.js"(exports2, module2) {
3723
4081
  "use strict";
3724
- var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, normalizeQueryFragmentEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
4082
+ var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3725
4083
  var { SCHEMES, getSchemeHandler } = require_schemes();
4084
+ var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
4085
+ var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
4086
+ function decodeValidScheme(scheme) {
4087
+ const decodedScheme = unescape(String(scheme));
4088
+ if (!VALID_SCHEME.test(decodedScheme)) {
4089
+ throw new TypeError(MALFORMED_SCHEME_ERROR);
4090
+ }
4091
+ return decodedScheme;
4092
+ }
3726
4093
  function normalize(uri, options) {
3727
4094
  if (typeof uri === "string") {
3728
4095
  uri = /** @type {T} */
@@ -3735,12 +4102,34 @@ var require_fast_uri = __commonJS({
3735
4102
  }
3736
4103
  function resolve(baseURI, relativeURI, options) {
3737
4104
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
3738
- const { parsed: baseParsed, malformedAuthorityOrPort: baseMalformed } = parseWithStatus(baseURI, schemelessOptions);
3739
- const { parsed: relativeParsed, malformedAuthorityOrPort: relativeMalformed } = parseWithStatus(relativeURI, schemelessOptions);
3740
- if (baseMalformed || relativeMalformed) {
4105
+ const {
4106
+ parsed: baseParsed,
4107
+ malformedAuthorityOrPort: baseMalformed,
4108
+ malformedPercentEncoding: baseMalformedPercentEncoding,
4109
+ malformedSchemeSpecific: baseMalformedSchemeSpecific,
4110
+ malformedHost: baseMalformedHost,
4111
+ malformedScheme: baseMalformedScheme
4112
+ } = parseWithStatus(baseURI, schemelessOptions);
4113
+ const {
4114
+ parsed: relativeParsed,
4115
+ malformedAuthorityOrPort: relativeMalformed,
4116
+ malformedPercentEncoding: relativeMalformedPercentEncoding,
4117
+ malformedSchemeSpecific: relativeMalformedSchemeSpecific,
4118
+ malformedHost: relativeMalformedHost,
4119
+ malformedScheme: relativeMalformedScheme
4120
+ } = parseWithStatus(relativeURI, schemelessOptions);
4121
+ if (baseMalformed || relativeMalformed || baseMalformedPercentEncoding || relativeMalformedPercentEncoding || baseMalformedSchemeSpecific || relativeMalformedSchemeSpecific || baseMalformedHost || relativeMalformedHost || baseMalformedScheme || relativeMalformedScheme) {
3741
4122
  throw new Error(baseParsed.error || relativeParsed.error || "URI is malformed.");
3742
4123
  }
3743
4124
  const resolved = resolveComponent(baseParsed, relativeParsed, schemelessOptions, true);
4125
+ const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
4126
+ const resolvedHost = resolved.host;
4127
+ const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
4128
+ canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
4129
+ const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
4130
+ if (resolved.error && !encodedASCIIHost) {
4131
+ throw new Error(resolved.error);
4132
+ }
3744
4133
  schemelessOptions.skipEscape = true;
3745
4134
  return serialize(resolved, schemelessOptions);
3746
4135
  }
@@ -3800,7 +4189,7 @@ var require_fast_uri = __commonJS({
3800
4189
  function equal(uriA, uriB, options) {
3801
4190
  const normalizedA = normalizeComparableURI(uriA, options);
3802
4191
  const normalizedB = normalizeComparableURI(uriB, options);
3803
- return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
4192
+ return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA === normalizedB;
3804
4193
  }
3805
4194
  function serialize(cmpts, opts) {
3806
4195
  const component = {
@@ -3817,23 +4206,30 @@ var require_fast_uri = __commonJS({
3817
4206
  reference: cmpts.reference,
3818
4207
  resourceName: cmpts.resourceName,
3819
4208
  secure: cmpts.secure,
4209
+ to: cmpts.to,
4210
+ subject: cmpts.subject,
4211
+ body: cmpts.body,
4212
+ headers: cmpts.headers,
3820
4213
  error: ""
3821
4214
  };
3822
4215
  const options = Object.assign({}, opts);
3823
4216
  const uriTokens = [];
4217
+ if (component.scheme) {
4218
+ component.scheme = decodeValidScheme(component.scheme);
4219
+ }
3824
4220
  const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
3825
4221
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
4222
+ const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
4223
+ const pathNoScheme = !options.skipEscape && component.scheme === void 0 && !hasAuthority;
3826
4224
  if (component.path !== void 0) {
3827
4225
  if (!options.skipEscape) {
3828
- component.path = escapePreservingEscapes(component.path);
3829
- if (component.scheme !== void 0) {
3830
- component.path = component.path.split("%3A").join(":");
3831
- }
4226
+ component.path = serializePathEncoding(component.path, pathNoScheme);
3832
4227
  } else {
3833
4228
  component.path = normalizePercentEncoding(component.path);
3834
4229
  }
3835
4230
  }
3836
4231
  if (options.reference !== "suffix" && component.scheme) {
4232
+ component.scheme = decodeValidScheme(component.scheme);
3837
4233
  uriTokens.push(component.scheme, ":");
3838
4234
  }
3839
4235
  const authority = recomposeAuthority(component);
@@ -3851,16 +4247,19 @@ var require_fast_uri = __commonJS({
3851
4247
  if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
3852
4248
  s = removeDotSegments(s);
3853
4249
  }
4250
+ if (pathNoScheme) {
4251
+ s = serializePathEncoding(s, true);
4252
+ }
3854
4253
  if (authority === void 0 && s[0] === "/" && s[1] === "/") {
3855
4254
  s = "/%2F" + s.slice(2);
3856
4255
  }
3857
4256
  uriTokens.push(s);
3858
4257
  }
3859
4258
  if (component.query !== void 0) {
3860
- uriTokens.push("?", component.query);
4259
+ uriTokens.push("?", encodeQuery(component.query));
3861
4260
  }
3862
4261
  if (component.fragment !== void 0) {
3863
- uriTokens.push("#", component.fragment);
4262
+ uriTokens.push("#", encodeFragment(component.fragment));
3864
4263
  }
3865
4264
  return uriTokens.join("");
3866
4265
  }
@@ -3876,6 +4275,36 @@ var require_fast_uri = __commonJS({
3876
4275
  }
3877
4276
  return void 0;
3878
4277
  }
4278
+ function hasMalformedPercentEncoding(component) {
4279
+ if (component === void 0) return false;
4280
+ let percent = component.indexOf("%");
4281
+ while (percent !== -1) {
4282
+ if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
4283
+ return true;
4284
+ }
4285
+ percent = component.indexOf("%", percent + 3);
4286
+ }
4287
+ return false;
4288
+ }
4289
+ function isIPLiteral(host) {
4290
+ return host[0] === "[" && host[host.length - 1] === "]";
4291
+ }
4292
+ function hasMalformedComponentPercentEncoding(matches) {
4293
+ const host = matches[4];
4294
+ return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
4295
+ }
4296
+ function canonicalizeHost(parsed, options, schemeHandler, isIP) {
4297
+ const bracketedIPLiteral = parsed.host && parsed.host[0] === "[" && parsed.host[parsed.host.length - 1] === "]";
4298
+ if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && !bracketedIPLiteral && nonSimpleDomain(parsed.host)) {
4299
+ try {
4300
+ parsed.host = new URL("http://" + parsed.host).hostname;
4301
+ } catch (e) {
4302
+ parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
4303
+ return true;
4304
+ }
4305
+ }
4306
+ return false;
4307
+ }
3879
4308
  function parseWithStatus(uri, opts) {
3880
4309
  const options = Object.assign({}, opts);
3881
4310
  const parsed = {
@@ -3888,6 +4317,11 @@ var require_fast_uri = __commonJS({
3888
4317
  fragment: void 0
3889
4318
  };
3890
4319
  let malformedAuthorityOrPort = false;
4320
+ let malformedPercentEncoding = false;
4321
+ let malformedSchemeSpecific = false;
4322
+ let malformedHost = false;
4323
+ let malformedIPLiteral = false;
4324
+ let malformedScheme = false;
3891
4325
  let isIP = false;
3892
4326
  if (options.reference === "suffix") {
3893
4327
  if (options.scheme) {
@@ -3917,13 +4351,26 @@ var require_fast_uri = __commonJS({
3917
4351
  }
3918
4352
  const matches = uri.match(URI_PARSE);
3919
4353
  if (matches) {
3920
- parsed.scheme = matches[1] === void 0 ? void 0 : matches[1].toLowerCase();
4354
+ parsed.scheme = matches[1];
3921
4355
  parsed.userinfo = matches[3];
3922
4356
  parsed.host = matches[4];
3923
4357
  parsed.port = parseInt(matches[5], 10);
3924
4358
  parsed.path = matches[6] || "";
3925
4359
  parsed.query = matches[7];
3926
4360
  parsed.fragment = matches[8];
4361
+ if (parsed.scheme !== void 0) {
4362
+ const decodedScheme = unescape(parsed.scheme);
4363
+ if (VALID_SCHEME.test(decodedScheme)) {
4364
+ parsed.scheme = decodedScheme.toLowerCase();
4365
+ } else {
4366
+ parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
4367
+ malformedScheme = true;
4368
+ }
4369
+ }
4370
+ malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches);
4371
+ if (malformedPercentEncoding) {
4372
+ parsed.error = parsed.error || "URI contains malformed percent-encoding.";
4373
+ }
3927
4374
  if (isNaN(parsed.port)) {
3928
4375
  parsed.port = matches[5];
3929
4376
  }
@@ -3935,9 +4382,16 @@ var require_fast_uri = __commonJS({
3935
4382
  if (parsed.host) {
3936
4383
  const ipv4result = isIPv4(parsed.host);
3937
4384
  if (ipv4result === false) {
4385
+ const bracketedIPLiteral = isIPLiteral(parsed.host);
4386
+ const hasIPLiteralBracket = parsed.host.indexOf("[") !== -1 || parsed.host.indexOf("]") !== -1;
3938
4387
  const ipv6result = normalizeIPv6(parsed.host);
3939
- parsed.host = ipv6result.host.toLowerCase();
3940
- isIP = ipv6result.isIPV6;
4388
+ isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true;
4389
+ malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
4390
+ parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
4391
+ if (malformedIPLiteral) {
4392
+ parsed.error = parsed.error || "URI host is malformed.";
4393
+ malformedAuthorityOrPort = true;
4394
+ }
3941
4395
  } else {
3942
4396
  isIP = true;
3943
4397
  }
@@ -3955,24 +4409,17 @@ var require_fast_uri = __commonJS({
3955
4409
  parsed.error = parsed.error || "URI is not a " + options.reference + " reference.";
3956
4410
  }
3957
4411
  const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme);
3958
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
3959
- if (parsed.host && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
3960
- try {
3961
- parsed.host = new URL("http://" + parsed.host).hostname;
3962
- } catch (e) {
3963
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
3964
- }
4412
+ if (!malformedIPLiteral) {
4413
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4414
+ }
4415
+ if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
4416
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4417
+ if (!isIP) {
4418
+ host = normalizePercentEncoding(host.toLowerCase());
3965
4419
  }
4420
+ parsed.host = reescapeHostDelimiters(host, isIP);
3966
4421
  }
3967
4422
  if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
3968
- if (uri.indexOf("%") !== -1) {
3969
- if (parsed.scheme !== void 0) {
3970
- parsed.scheme = unescape(parsed.scheme);
3971
- }
3972
- if (parsed.host !== void 0) {
3973
- parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
3974
- }
3975
- }
3976
4423
  if (parsed.path) {
3977
4424
  parsed.path = normalizePathEncoding(parsed.path);
3978
4425
  }
@@ -3985,11 +4432,14 @@ var require_fast_uri = __commonJS({
3985
4432
  }
3986
4433
  if (schemeHandler && schemeHandler.parse) {
3987
4434
  schemeHandler.parse(parsed, options);
4435
+ if (schemeHandler === SCHEMES.urn && parsed.nid === void 0) {
4436
+ malformedSchemeSpecific = true;
4437
+ }
3988
4438
  }
3989
4439
  } else {
3990
4440
  parsed.error = parsed.error || "URI can not be parsed.";
3991
4441
  }
3992
- return { parsed, malformedAuthorityOrPort };
4442
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme };
3993
4443
  }
3994
4444
  function parse4(uri, opts) {
3995
4445
  return parseWithStatus(uri, opts).parsed;
@@ -3998,20 +4448,28 @@ var require_fast_uri = __commonJS({
3998
4448
  return normalizeStringWithStatus(uri, opts).normalized;
3999
4449
  }
4000
4450
  function normalizeStringWithStatus(uri, opts) {
4001
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
4451
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts);
4002
4452
  return {
4003
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
4004
- malformedAuthorityOrPort
4453
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
4454
+ malformedAuthorityOrPort,
4455
+ malformedPercentEncoding,
4456
+ malformedSchemeSpecific,
4457
+ malformedHost,
4458
+ malformedScheme
4005
4459
  };
4006
4460
  }
4007
4461
  function normalizeComparableURI(uri, opts) {
4008
- if (typeof uri === "string") {
4009
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
4010
- return malformedAuthorityOrPort ? void 0 : normalized;
4462
+ if (typeof uri !== "string" && typeof uri !== "object") {
4463
+ return void 0;
4011
4464
  }
4012
- if (typeof uri === "object") {
4013
- return serialize(uri, opts);
4465
+ let value;
4466
+ try {
4467
+ value = typeof uri === "string" ? uri : serialize(uri, opts);
4468
+ } catch {
4469
+ return void 0;
4014
4470
  }
4471
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts);
4472
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? void 0 : normalized;
4015
4473
  }
4016
4474
  var fastUri = {
4017
4475
  SCHEMES,
@@ -36183,7 +36641,9 @@ var SkippedIssueSchema = external_exports3.looseObject({
36183
36641
  repo: external_exports3.string(),
36184
36642
  number: external_exports3.number(),
36185
36643
  title: external_exports3.string(),
36186
- skippedAt: external_exports3.string()
36644
+ skippedAt: external_exports3.string(),
36645
+ /** Why it was skipped: the vetter's reasons, or absent for a manual skip (#343). */
36646
+ reason: external_exports3.string().optional()
36187
36647
  });
36188
36648
  var HorizonSchema = external_exports3.enum(["quick-win", "bigger-bet"]);
36189
36649
  var SavedCandidateSchema = external_exports3.looseObject({
@@ -36226,7 +36686,11 @@ var ScoutPreferencesSchema = external_exports3.looseObject({
36226
36686
  maxIssueAgeDays: external_exports3.number().default(90),
36227
36687
  includeDocIssues: external_exports3.boolean().default(true),
36228
36688
  minRepoScoreThreshold: external_exports3.number().default(4),
36229
- interPhaseDelayMs: external_exports3.number().min(0).max(12e4).default(3e4),
36689
+ // Fixed pause before a search moves on to the next strategy. Defaults to
36690
+ // 0 (#334): SearchBudgetTracker already paces every search call against
36691
+ // the 30/min window and the pre-flight quota, so the old 30s pin only
36692
+ // added wall clock. Retained as a knob for hosts that want spacing.
36693
+ interPhaseDelayMs: external_exports3.number().min(0).max(12e4).default(0),
36230
36694
  persistence: PersistenceModeSchema.default("local"),
36231
36695
  defaultStrategy: external_exports3.array(SearchStrategySchema).optional(),
36232
36696
  /**
@@ -36320,11 +36784,30 @@ var ScoutStateSchema = external_exports3.looseObject({
36320
36784
  * variant leads each run. `languageOffset` advances (no modulo; the
36321
36785
  * consumer wraps at use time so a shrinking language list never needs
36322
36786
  * clamping here).
36787
+ *
36788
+ * `phase0Offset`, `starredOffset` and `maintainedOffset` do the same for the
36789
+ * repo lists those phases cap per run (#324, #333): each run searches the
36790
+ * next window of the list instead of always the first N, so every
36791
+ * contributed/starred repo gets searched within a few runs.
36792
+ *
36793
+ * `strategyOffset` is the round-robin cursor (#336): a position in
36794
+ * CONCRETE_STRATEGIES. A search runs the first usable strategy from there,
36795
+ * and the cursor moves to just after the strategy that led.
36323
36796
  */
36324
36797
  searchRotation: external_exports3.looseObject({
36325
36798
  languageOffset: external_exports3.number().int().min(0).default(0),
36799
+ phase0Offset: external_exports3.number().int().min(0).default(0),
36800
+ starredOffset: external_exports3.number().int().min(0).default(0),
36801
+ maintainedOffset: external_exports3.number().int().min(0).default(0),
36802
+ strategyOffset: external_exports3.number().int().min(0).default(0),
36326
36803
  lastRotatedAt: external_exports3.string().optional()
36327
- }).default(() => ({ languageOffset: 0 }))
36804
+ }).default(() => ({
36805
+ languageOffset: 0,
36806
+ phase0Offset: 0,
36807
+ starredOffset: 0,
36808
+ maintainedOffset: 0,
36809
+ strategyOffset: 0
36810
+ }))
36328
36811
  });
36329
36812
  function parseScoutState(raw) {
36330
36813
  return ScoutStateSchema.parse(raw);
@@ -36346,6 +36829,18 @@ function isDocOnlyIssue(item) {
36346
36829
  return false;
36347
36830
  return nonEmptyLabels.every((n) => DOC_ONLY_LABELS.has(n));
36348
36831
  }
36832
+ var NON_ACTIONABLE_LABELS = /* @__PURE__ */ new Set(["question", "discussion", "support"]);
36833
+ function isNonActionableIssue(item) {
36834
+ const labelNames = (item.labels ?? []).map((l) => (typeof l === "string" ? l : l.name || "").toLowerCase());
36835
+ if (labelNames.some((n) => NON_ACTIONABLE_LABELS.has(n)))
36836
+ return true;
36837
+ const title = (item.title ?? "").trim();
36838
+ if (title.endsWith("?"))
36839
+ return true;
36840
+ if (/^(how|why|what|when|where|which|who|is|are|does|do|can|could|should|would|will|has|have)\b/i.test(title))
36841
+ return true;
36842
+ return title.split(/\s+/).filter(Boolean).length < 3;
36843
+ }
36349
36844
  var BEGINNER_LABELS = /* @__PURE__ */ new Set([
36350
36845
  "good first issue",
36351
36846
  "hacktoberfest",
@@ -37054,16 +37549,10 @@ async function checkNotClaimed(octokit, owner, repo, issueNumber, commentCount)
37054
37549
  function analyzeRequirements(body) {
37055
37550
  if (!body || body.length < 50)
37056
37551
  return false;
37057
- const hasSteps = /\d\.|[-*]\s/.test(body);
37058
- const hasCodeBlock = /```/.test(body);
37059
- const hasExpectedBehavior = /expect|should|must|want/i.test(body);
37060
- const indicators = [
37061
- hasSteps,
37062
- hasCodeBlock,
37063
- hasExpectedBehavior,
37064
- body.length > 200
37065
- ];
37066
- return indicators.filter(Boolean).length >= 2;
37552
+ const hasSteps = /(^|\n)\s*\d+[.)]\s/.test(body) || /steps?\s+to\s+reproduce|\brepro(duction)?\b/i.test(body);
37553
+ const hasExpectedVsActual = /\b(expected|should)\b/i.test(body) && /\b(actual(ly)?|instead|but|currently|observed|got)\b/i.test(body);
37554
+ const hasCodeRef = /```|`[^`\n]+`/.test(body) || /\b[\w/.-]+\.(ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|java|kt|swift|c|cc|cpp|h|hpp|cs|php|sh|vue|svelte)\b/.test(body) || /^\s+at\s+\S+\s+\(/m.test(body);
37555
+ return hasCodeRef || hasSteps && hasExpectedVsActual;
37067
37556
  }
37068
37557
 
37069
37558
  // ../core/dist/core/probe-repo-file.js
@@ -38173,8 +38662,8 @@ var IssueVetter = class {
38173
38662
 
38174
38663
  // ../core/dist/core/search-phases.js
38175
38664
  var MODULE11 = "search-phases";
38176
- var GITHUB_MAX_BOOLEAN_OPS = 5;
38177
38665
  var INTER_QUERY_DELAY_MS = 2e3;
38666
+ var KNOWN_REPO_PER_REPO_CAP = 2;
38178
38667
  var GRAPHQL_INTER_QUERY_DELAY_MS = 500;
38179
38668
  var graphqlSearchQueryCount = 0;
38180
38669
  function getGraphQLSearchQueryCount() {
@@ -38183,29 +38672,10 @@ function getGraphQLSearchQueryCount() {
38183
38672
  function resetGraphQLSearchQueryCount() {
38184
38673
  graphqlSearchQueryCount = 0;
38185
38674
  }
38186
- function chunkLabels(labels, reservedOps = 0) {
38187
- const maxPerChunk = GITHUB_MAX_BOOLEAN_OPS - reservedOps + 1;
38188
- if (maxPerChunk < 1) {
38189
- if (labels.length > 0) {
38190
- 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.`);
38191
- }
38192
- return [[]];
38193
- }
38194
- if (labels.length <= maxPerChunk)
38195
- return [labels];
38196
- const chunks = [];
38197
- for (let i = 0; i < labels.length; i += maxPerChunk) {
38198
- chunks.push(labels.slice(i, i + maxPerChunk));
38199
- }
38200
- debug(MODULE11, `Split ${labels.length} labels into ${chunks.length} chunks (${reservedOps} ops reserved, max ${maxPerChunk} per chunk)`);
38201
- return chunks;
38202
- }
38203
38675
  function buildLabelQuery(labels) {
38204
38676
  if (labels.length === 0)
38205
38677
  return "";
38206
- if (labels.length === 1)
38207
- return `label:"${labels[0]}"`;
38208
- return `(${labels.map((l) => `label:"${l}"`).join(" OR ")})`;
38678
+ return `label:${labels.map((l) => JSON.stringify(l)).join(",")}`;
38209
38679
  }
38210
38680
  function buildEffectiveLabels(scopes, customLabels) {
38211
38681
  const labels = /* @__PURE__ */ new Set();
@@ -38369,9 +38839,9 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
38369
38839
  if (mapped.length > 0) {
38370
38840
  const filtered = filterFn(mapped);
38371
38841
  if (filtered.length > 0) {
38372
- const remainingNeeded = maxResults - candidates.length;
38842
+ const remainingNeeded = Math.min(KNOWN_REPO_PER_REPO_CAP, maxResults - candidates.length);
38373
38843
  const { candidates: vetted, rateLimitHit: vetRateLimitHit } = await vetter.vetIssuesParallel(filtered.slice(0, remainingNeeded * 2).map((item) => item.html_url), remainingNeeded, priority);
38374
- candidates.push(...vetted);
38844
+ candidates.push(...vetted.slice(0, remainingNeeded));
38375
38845
  if (vetRateLimitHit)
38376
38846
  rateLimitFailures++;
38377
38847
  }
@@ -38393,28 +38863,14 @@ async function fetchIssuesFromKnownRepos(octokit, vetter, repos, labels, maxResu
38393
38863
  }
38394
38864
  return { candidates, allReposFailed, rateLimitHit };
38395
38865
  }
38396
- async function searchWithChunkedLabels(octokit, labels, reservedOps, buildQuery, perPage, tracker = getSearchBudgetTracker()) {
38397
- const labelChunks = chunkLabels(labels, reservedOps);
38398
- const seenUrls = /* @__PURE__ */ new Set();
38399
- const allItems = [];
38400
- for (let i = 0; i < labelChunks.length; i++) {
38401
- if (i > 0)
38402
- await sleep(GRAPHQL_INTER_QUERY_DELAY_MS);
38403
- const query = buildQuery(buildLabelQuery(labelChunks[i]));
38404
- const data = await searchIssuesGraphQLFirst(octokit, {
38405
- q: query,
38406
- sort: "created",
38407
- order: "desc",
38408
- per_page: perPage
38409
- }, tracker);
38410
- for (const item of data.items) {
38411
- if (!seenUrls.has(item.html_url)) {
38412
- seenUrls.add(item.html_url);
38413
- allItems.push(item);
38414
- }
38415
- }
38416
- }
38417
- return allItems;
38866
+ async function searchWithChunkedLabels(octokit, labels, _reservedOps, buildQuery, perPage, tracker = getSearchBudgetTracker()) {
38867
+ const data = await searchIssuesGraphQLFirst(octokit, {
38868
+ q: buildQuery(buildLabelQuery(labels)),
38869
+ sort: "created",
38870
+ order: "desc",
38871
+ per_page: perPage
38872
+ }, tracker);
38873
+ return data.items;
38418
38874
  }
38419
38875
  function buildLanguageVariants(languages, isAnyLanguage, hasLabels) {
38420
38876
  if (isAnyLanguage || languages.length === 0)
@@ -38570,6 +39026,15 @@ var CRITICAL_BUDGET_THRESHOLD = 10;
38570
39026
  var PHASE0_PER_PAGE = 30;
38571
39027
  var CONTRIBUTED_REPO_MAX_AGE_DAYS = 365;
38572
39028
  var PHASE0_MAX_SHARE = 0.5;
39029
+ var PHASE0_REPOS_PER_RUN = 10;
39030
+ var STARRED_REPOS_PER_RUN = 10;
39031
+ var MAINTAINED_REPOS_PER_RUN = 15;
39032
+ function rotatingWindow(items, size, offset) {
39033
+ if (items.length <= size)
39034
+ return [...items];
39035
+ const start = offset * size % items.length;
39036
+ return Array.from({ length: size }, (_, i) => items[(start + i) % items.length]);
39037
+ }
38573
39038
  function buildIssueFilter(config2) {
38574
39039
  return (items) => {
38575
39040
  return items.filter((item) => {
@@ -38598,6 +39063,8 @@ function buildIssueFilter(config2) {
38598
39063
  return false;
38599
39064
  if (!config2.includeDocIssues && isDocOnlyIssue(item))
38600
39065
  return false;
39066
+ if (isNonActionableIssue(item))
39067
+ return false;
38601
39068
  return true;
38602
39069
  });
38603
39070
  };
@@ -38644,7 +39111,7 @@ async function runPhaseOrgs(octokit, vetter, orgs, languages, isAnyLanguage, lab
38644
39111
  async function runPhase1(octokit, vetter, repos, labels, maxResults, filterIssues) {
38645
39112
  info(MODULE12, `Phase 1: Searching issues in ${repos.length} starred repos...`);
38646
39113
  const phase1Labels = labels.slice(0, 3);
38647
- const reposToSearch = repos.slice(0, 10);
39114
+ const reposToSearch = repos.slice(0, STARRED_REPOS_PER_RUN);
38648
39115
  const { candidates, allReposFailed, rateLimitHit } = await fetchIssuesFromKnownRepos(octokit, vetter, reposToSearch, phase1Labels, maxResults, "starred", filterIssues);
38649
39116
  info(MODULE12, `Found ${candidates.length} candidates from starred repos`);
38650
39117
  return {
@@ -38715,13 +39182,13 @@ async function runPhase2(octokit, vetter, scopes, labels, configLabels, language
38715
39182
  rateLimitHit
38716
39183
  };
38717
39184
  }
38718
- async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker) {
39185
+ async function runPhase3(octokit, vetter, langQuery, minStars, projectCategories, maxResults, phase0RepoSet, starredRepoSet, starredRepos, existingCandidates, filterIssues, tracker, rotationOffset = 0) {
38719
39186
  info(MODULE12, "Phase 3: Searching actively maintained repos...");
38720
39187
  const seenRepos = new Set(existingCandidates.map((c) => c.issue.repo));
38721
- const eligibleStarred = starredRepos.filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
39188
+ const eligibleStarred = rotatingWindow(starredRepos, MAINTAINED_REPOS_PER_RUN, rotationOffset).filter((r) => !phase0RepoSet.has(r) && !seenRepos.has(r));
38722
39189
  if (eligibleStarred.length > 0) {
38723
39190
  info(MODULE12, `Phase 3: Checking ${eligibleStarred.length} starred repos via REST API...`);
38724
- const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred.slice(0, 15), minStars, maxResults);
39191
+ const restItems = await fetchIssuesFromMaintainedRepos(octokit, eligibleStarred, minStars, maxResults);
38725
39192
  if (restItems.length > 0) {
38726
39193
  try {
38727
39194
  const { candidates, allVetFailed, rateLimitHit: vetRateLimitHit } = await filterVetAndScore(vetter, restItems, filterIssues, [phase0RepoSet, seenRepos], maxResults, minStars, "Phase 3 (REST)");
@@ -38839,7 +39306,7 @@ var IssueDiscovery = class {
38839
39306
  const labels = options.labels || (scopes ? buildEffectiveLabels(scopes, config2.labels) : config2.labels);
38840
39307
  const maxResults = options.maxResults || 10;
38841
39308
  const minStars = config2.minStars ?? 50;
38842
- const interPhaseDelay = options.interPhaseDelayMs ?? config2.interPhaseDelayMs ?? 3e4;
39309
+ const interPhaseDelay = options.interPhaseDelayMs ?? config2.interPhaseDelayMs ?? 0;
38843
39310
  const ALL_STRATEGIES = CONCRETE_STRATEGIES;
38844
39311
  const pickStrategies = (...candidates) => candidates.find((c) => c && c.length > 0) ?? ["all"];
38845
39312
  const rawStrategies = pickStrategies(options.strategies, config2.defaultStrategy);
@@ -38919,85 +39386,110 @@ var IssueDiscovery = class {
38919
39386
  ...baseFilterConfig,
38920
39387
  maxAgeDays: CONTRIBUTED_REPO_MAX_AGE_DAYS
38921
39388
  });
38922
- const seenPhase0 = /* @__PURE__ */ new Set();
38923
- const phase0Repos = [];
38924
- for (const repo of [...mergedPRRepos, ...openPRRepos]) {
38925
- if (seenPhase0.has(repo))
38926
- continue;
38927
- seenPhase0.add(repo);
38928
- phase0Repos.push(repo);
38929
- if (phase0Repos.length >= 10)
38930
- break;
38931
- }
39389
+ const repoRotation = options.repoRotationOffsets ?? {};
39390
+ const phase0Repos = rotatingWindow([.../* @__PURE__ */ new Set([...mergedPRRepos, ...openPRRepos])], PHASE0_REPOS_PER_RUN, repoRotation.merged ?? 0);
38932
39391
  const phase0RepoSet = new Set(phase0Repos);
38933
39392
  const preferredOrgs = config2.preferredOrgs ?? [];
38934
- const orgsPhaseEnabled = preferredOrgs.length > 0 && enabledStrategies.has("orgs");
38935
- const otherStrategiesCanRun = orgsPhaseEnabled || starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
38936
- if (phase0Repos.length > 0 && enabledStrategies.has("merged")) {
38937
- const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
38938
- const remaining = Math.min(maxResults - allCandidates.length, phase0Cap);
38939
- if (remaining > 0) {
38940
- const result = await runPhase0(this.octokit, this.vetter, phase0Repos, remaining, filterIssuesPhase0);
38941
- recordPhaseResult("0", result);
38942
- }
38943
- strategiesUsed.push("merged");
38944
- }
38945
- if (allCandidates.length < maxResults && orgsPhaseEnabled) {
38946
- await applyInterPhaseDelay();
38947
- const remaining = maxResults - allCandidates.length;
38948
- const result = await runPhaseOrgs(this.octokit, this.vetter, preferredOrgs, languages, isAnyLanguage, labels, remaining, minStars, phase0RepoSet, starredRepoSet, filterIssues, tracker);
38949
- recordPhaseResult("orgs", result);
38950
- strategiesUsed.push("orgs");
38951
- }
38952
- if (allCandidates.length < maxResults && starredRepos.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("starred")) {
38953
- await applyInterPhaseDelay();
38954
- const reposToSearch = starredRepos.filter((r) => !phase0RepoSet.has(r));
38955
- if (reposToSearch.length > 0) {
38956
- const remaining = maxResults - allCandidates.length;
38957
- if (remaining > 0) {
38958
- const result = await runPhase1(this.octokit, this.vetter, reposToSearch, labels, remaining, filterIssues);
38959
- recordPhaseResult("1", result);
38960
- strategiesUsed.push("starred");
38961
- }
38962
- }
38963
- }
38964
- const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 9e4;
38965
- const configuredSkipThreshold = config2.skipBroadWhenSufficientResults ?? 8;
38966
- const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
38967
- const viableBeforeBroad = viableCandidateCount();
38968
- if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
38969
- info(MODULE12, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
38970
- } else if (enabledStrategies.has("broad")) {
38971
- const preferredOrgSet = new Set(preferredOrgs.map((o) => o.toLowerCase()));
38972
- const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo) && !preferredOrgSet.has(c.issue.repo.split("/")[0]?.toLowerCase() ?? "")).length;
38973
- if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
38974
- info(MODULE12, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
38975
- } else {
38976
- await applyInterPhaseDelay();
38977
- if (allCandidates.length > 0 && broadDelay > 0) {
38978
- info(MODULE12, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
38979
- await sleep(broadDelay);
38980
- } else if (allCandidates.length === 0) {
38981
- info(MODULE12, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
38982
- }
38983
- const remaining = maxResults - viableBeforeBroad;
38984
- const result = await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, remaining, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues, tracker, options.languageRotationOffset ?? 0);
38985
- recordPhaseResult("2", result);
39393
+ const starredToSearch = rotatingWindow(starredRepos, STARRED_REPOS_PER_RUN, repoRotation.starred ?? 0).filter((r) => !phase0RepoSet.has(r));
39394
+ const canRun = {
39395
+ all: false,
39396
+ merged: phase0Repos.length > 0,
39397
+ orgs: preferredOrgs.length > 0,
39398
+ starred: starredToSearch.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD,
39399
+ broad: true,
39400
+ maintained: true
39401
+ };
39402
+ const broadDelay = options.broadPhaseDelayMs ?? config2.broadPhaseDelayMs ?? 0;
39403
+ const runStrategy = {
39404
+ merged: async (wanted) => {
39405
+ recordPhaseResult("0", await runPhase0(this.octokit, this.vetter, phase0Repos, wanted, filterIssuesPhase0));
39406
+ strategiesUsed.push("merged");
39407
+ },
39408
+ orgs: async (wanted) => {
39409
+ recordPhaseResult("orgs", await runPhaseOrgs(this.octokit, this.vetter, preferredOrgs, languages, isAnyLanguage, labels, wanted, minStars, phase0RepoSet, starredRepoSet, filterIssues, tracker));
39410
+ strategiesUsed.push("orgs");
39411
+ },
39412
+ starred: async (wanted) => {
39413
+ recordPhaseResult("1", await runPhase1(this.octokit, this.vetter, starredToSearch, labels, wanted, filterIssues));
39414
+ strategiesUsed.push("starred");
39415
+ },
39416
+ broad: async (wanted) => {
39417
+ recordPhaseResult("2", await runPhase2(this.octokit, this.vetter, scopes, labels, config2.labels, languages, isAnyLanguage, wanted, minStars, phase0RepoSet, starredRepoSet, allCandidates, filterIssues, tracker, options.languageRotationOffset ?? 0));
38986
39418
  strategiesUsed.push("broad");
39419
+ },
39420
+ maintained: async (wanted) => {
39421
+ recordPhaseResult("3", await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], wanted, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues, tracker, repoRotation.maintained ?? 0));
39422
+ strategiesUsed.push("maintained");
39423
+ }
39424
+ };
39425
+ let starredSkippedForBudget = searchBudget < CRITICAL_BUDGET_THRESHOLD;
39426
+ if (options.strategyRotationOffset !== void 0) {
39427
+ const cursor = options.strategyRotationOffset;
39428
+ const n = CONCRETE_STRATEGIES.length;
39429
+ const order = CONCRETE_STRATEGIES.map((_, i) => CONCRETE_STRATEGIES[(cursor + i) % n]).filter((s) => enabledStrategies.has(s));
39430
+ starredSkippedForBudget = false;
39431
+ for (const strategy of order) {
39432
+ if (!canRun[strategy]) {
39433
+ if (strategy === "starred" && starredToSearch.length > 0) {
39434
+ starredSkippedForBudget = true;
39435
+ }
39436
+ continue;
39437
+ }
39438
+ if (strategiesUsed.length > 0)
39439
+ await applyInterPhaseDelay();
39440
+ info(MODULE12, `Round-robin: running the ${strategy} strategy`);
39441
+ await runStrategy[strategy](maxResults - viableCandidateCount());
39442
+ if (viableCandidateCount() > 0)
39443
+ break;
39444
+ }
39445
+ } else {
39446
+ const orgsPhaseEnabled = canRun.orgs && enabledStrategies.has("orgs");
39447
+ const otherStrategiesCanRun = orgsPhaseEnabled || starredRepos.length > 0 && enabledStrategies.has("starred") || enabledStrategies.has("broad") || enabledStrategies.has("maintained");
39448
+ if (canRun.merged && enabledStrategies.has("merged")) {
39449
+ const phase0Cap = otherStrategiesCanRun ? Math.max(1, Math.ceil(maxResults * PHASE0_MAX_SHARE)) : maxResults;
39450
+ await runStrategy.merged(Math.min(maxResults - allCandidates.length, phase0Cap));
39451
+ }
39452
+ if (allCandidates.length < maxResults && orgsPhaseEnabled) {
39453
+ await applyInterPhaseDelay();
39454
+ await runStrategy.orgs(maxResults - allCandidates.length);
39455
+ }
39456
+ if (allCandidates.length < maxResults && starredRepos.length > 0 && searchBudget >= CRITICAL_BUDGET_THRESHOLD && enabledStrategies.has("starred")) {
39457
+ await applyInterPhaseDelay();
39458
+ if (starredToSearch.length > 0) {
39459
+ await runStrategy.starred(maxResults - allCandidates.length);
39460
+ }
39461
+ }
39462
+ const configuredSkipThreshold = config2.skipBroadWhenSufficientResults ?? 8;
39463
+ const skipThreshold = configuredSkipThreshold > 0 ? Math.min(configuredSkipThreshold, maxResults - 1) : 0;
39464
+ const viableBeforeBroad = viableCandidateCount();
39465
+ if (viableBeforeBroad >= maxResults && enabledStrategies.has("broad")) {
39466
+ info(MODULE12, `Skipping broad search: ${viableBeforeBroad} viable candidate(s) already meet maxResults (${maxResults})`);
39467
+ } else if (enabledStrategies.has("broad")) {
39468
+ const preferredOrgSet = new Set(preferredOrgs.map((o) => o.toLowerCase()));
39469
+ const newRepoCandidateCount = allCandidates.filter((c) => c.recommendation !== "skip" && !phase0RepoSet.has(c.issue.repo) && !starredRepoSet.has(c.issue.repo) && !preferredOrgSet.has(c.issue.repo.split("/")[0]?.toLowerCase() ?? "")).length;
39470
+ if (skipThreshold > 0 && newRepoCandidateCount >= skipThreshold) {
39471
+ info(MODULE12, `Skipping broad search: already found ${newRepoCandidateCount} viable candidate(s) from new repos (threshold: ${skipThreshold})`);
39472
+ } else {
39473
+ await applyInterPhaseDelay();
39474
+ if (allCandidates.length > 0 && broadDelay > 0) {
39475
+ info(MODULE12, `Waiting ${(broadDelay / 1e3).toFixed(0)}s for rate limit cooldown before broad search...`);
39476
+ await sleep(broadDelay);
39477
+ } else if (allCandidates.length === 0) {
39478
+ info(MODULE12, `Skipping broad phase delay: no results from previous phases, proceeding immediately`);
39479
+ }
39480
+ await runStrategy.broad(maxResults - viableBeforeBroad);
39481
+ }
39482
+ }
39483
+ const viableBeforeMaintained = viableCandidateCount();
39484
+ if (viableBeforeMaintained >= maxResults && enabledStrategies.has("maintained")) {
39485
+ info(MODULE12, `Skipping maintained search: ${viableBeforeMaintained} viable candidate(s) already meet maxResults (${maxResults})`);
39486
+ } else if (enabledStrategies.has("maintained")) {
39487
+ await applyInterPhaseDelay();
39488
+ await runStrategy.maintained(maxResults - viableBeforeMaintained);
38987
39489
  }
38988
39490
  }
38989
- const viableBeforeMaintained = viableCandidateCount();
38990
- if (viableBeforeMaintained >= maxResults && enabledStrategies.has("maintained")) {
38991
- info(MODULE12, `Skipping maintained search: ${viableBeforeMaintained} viable candidate(s) already meet maxResults (${maxResults})`);
38992
- } else if (enabledStrategies.has("maintained")) {
38993
- await applyInterPhaseDelay();
38994
- const remaining = maxResults - viableBeforeMaintained;
38995
- const result = await runPhase3(this.octokit, this.vetter, langQuery, minStars, config2.projectCategories ?? [], remaining, phase0RepoSet, starredRepoSet, starredRepos, allCandidates, filterIssues, tracker);
38996
- recordPhaseResult("3", result);
38997
- strategiesUsed.push("maintained");
38998
- }
38999
- const phasesSkippedForBudget = searchBudget < CRITICAL_BUDGET_THRESHOLD;
39000
- 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.` : "";
39491
+ const phasesSkippedForBudget = starredSkippedForBudget;
39492
+ const budgetNote = phasesSkippedForBudget ? preflightFailed ? " The starred-repo phase was skipped as a precaution because the rate-limit check failed (quota unknown); the other strategies don't use that quota." : ` The starred-repo phase was skipped due to critically low API quota (${searchBudget} remaining); the other strategies don't use that quota.` : "";
39001
39493
  if (allCandidates.length === 0) {
39002
39494
  const errorDetails = [
39003
39495
  phaseErrors["0"] ? `Phase 0 (merged-PR repos): ${phaseErrors["0"]}` : null,
@@ -39360,8 +39852,8 @@ var NO_BROAD_RESULTS_MESSAGE = "No open feature opportunities matched your filte
39360
39852
  var DEFAULT_BROAD_MAX_TO_VET = 30;
39361
39853
  function buildBroadFeatureSearchQuery(opts) {
39362
39854
  const parts = ["is:issue", "is:open", "no:assignee"];
39363
- const labelClause = FEATURE_LABELS.map((l) => `label:"${l}"`).join(" OR ");
39364
- parts.push(`(${labelClause})`);
39855
+ const labelClause = `label:${FEATURE_LABELS.map((l) => JSON.stringify(l)).join(",")}`;
39856
+ parts.push(labelClause);
39365
39857
  for (const excl of FEATURE_EXCLUSION_LABELS) {
39366
39858
  parts.push(`-label:"${excl}"`);
39367
39859
  }
@@ -40007,11 +40499,10 @@ var OssScout = class {
40007
40499
  }
40008
40500
  /**
40009
40501
  * Multi-strategy issue search. Returns scored, sorted candidates.
40010
- * Automatically culls expired skip entries and filters skipped issues.
40502
+ * Skipped issues are excluded; the skip list is permanent (#343).
40011
40503
  */
40012
40504
  async search(options) {
40013
40505
  this.evictStaleCacheEntries();
40014
- this.cullExpiredSkips();
40015
40506
  const skippedUrls = new Set((this.state.skippedIssues ?? []).map((s) => s.url));
40016
40507
  if (options?.excludeRecentlySurfaced ?? true) {
40017
40508
  const ttlDays = options?.recentlySurfacedTtlDays ?? 7;
@@ -40048,15 +40539,21 @@ var OssScout = class {
40048
40539
  diversityRatio,
40049
40540
  interPhaseDelayMs: options?.interPhaseDelayMs,
40050
40541
  broadPhaseDelayMs: options?.broadPhaseDelayMs,
40051
- languageRotationOffset: rotation.languageOffset
40542
+ languageRotationOffset: rotation.languageOffset,
40543
+ repoRotationOffsets: {
40544
+ merged: rotation.phase0Offset ?? 0,
40545
+ starred: rotation.starredOffset ?? 0,
40546
+ maintained: rotation.maintainedOffset ?? 0
40547
+ },
40548
+ strategyRotationOffset: rotation.strategyOffset ?? 0
40052
40549
  }));
40053
40550
  } catch (err) {
40054
40551
  if (err instanceof ValidationError) {
40055
- this.advanceRotationIfBroadRan(rotation, err.strategiesUsed ?? []);
40552
+ this.advanceRotation(rotation, err.strategiesUsed ?? []);
40056
40553
  }
40057
40554
  throw err;
40058
40555
  }
40059
- this.advanceRotationIfBroadRan(rotation, strategiesUsed);
40556
+ this.advanceRotation(rotation, strategiesUsed);
40060
40557
  for (const c of candidates) {
40061
40558
  this.updateRepoSignalsFromHealth(c.projectHealth);
40062
40559
  }
@@ -40071,18 +40568,26 @@ var OssScout = class {
40071
40568
  };
40072
40569
  }
40073
40570
  /**
40074
- * Advance the language-rotation cursor when the broad phase actually ran.
40075
- * Called on both the success path and the zero-candidate ValidationError
40076
- * path — a broad phase that ran and found nothing must still rotate, or the
40077
- * same barren language slice would lead every subsequent run (#249
40078
- * follow-up). Skipped/gated broad phases don't burn a rotation slot.
40571
+ * Advance the rotation cursor of each phase that actually ran: the language
40572
+ * cursor for broad (#249 follow-up), the repo-window cursors for
40573
+ * merged/starred/maintained (#324, #333). Called on both the success path
40574
+ * and the zero-candidate ValidationError path — a phase that ran and found
40575
+ * nothing must still rotate, or the same barren slice would lead every
40576
+ * subsequent run. Skipped/gated phases don't burn a rotation slot. The
40577
+ * round-robin strategy cursor (#336) moves to just after the strategy that
40578
+ * led (the first one used), so the next search starts with the next one.
40079
40579
  */
40080
- advanceRotationIfBroadRan(rotation, strategiesUsed) {
40081
- if (!strategiesUsed.includes("broad"))
40580
+ advanceRotation(rotation, strategiesUsed) {
40581
+ const ran = (strategy) => strategiesUsed.includes(strategy) ? 1 : 0;
40582
+ if (strategiesUsed.length === 0)
40082
40583
  return;
40083
40584
  this.state.searchRotation = {
40084
40585
  ...rotation,
40085
- languageOffset: rotation.languageOffset + 1,
40586
+ languageOffset: rotation.languageOffset + ran("broad"),
40587
+ phase0Offset: (rotation.phase0Offset ?? 0) + ran("merged"),
40588
+ starredOffset: (rotation.starredOffset ?? 0) + ran("starred"),
40589
+ maintainedOffset: (rotation.maintainedOffset ?? 0) + ran("maintained"),
40590
+ strategyOffset: CONCRETE_STRATEGIES.indexOf(strategiesUsed[0]) + 1,
40086
40591
  lastRotatedAt: (/* @__PURE__ */ new Date()).toISOString()
40087
40592
  };
40088
40593
  this.dirty = true;
@@ -40498,6 +41003,16 @@ var OssScout = class {
40498
41003
  const now = (/* @__PURE__ */ new Date()).toISOString();
40499
41004
  const existing = new Map((this.state.savedResults ?? []).map((r) => [r.issueUrl, r]));
40500
41005
  for (const c of candidates) {
41006
+ if (c.recommendation === "skip") {
41007
+ existing.delete(c.issue.url);
41008
+ this.skipIssue(c.issue.url, {
41009
+ repo: c.issue.repo,
41010
+ number: c.issue.number,
41011
+ title: c.issue.title,
41012
+ reason: c.reasonsToSkip.join("; ") || "vetter: skip"
41013
+ });
41014
+ continue;
41015
+ }
40501
41016
  const prev = existing.get(c.issue.url);
40502
41017
  existing.set(c.issue.url, {
40503
41018
  issueUrl: c.issue.url,
@@ -40548,7 +41063,7 @@ var OssScout = class {
40548
41063
  }
40549
41064
  // ── Skip List ───────────────────────────────────────────────────────
40550
41065
  /**
40551
- * Skip an issue — excludes it from future searches. Auto-culled after 90 days.
41066
+ * Skip an issue — excludes it from future searches permanently (#343).
40552
41067
  */
40553
41068
  skipIssue(url2, metadata) {
40554
41069
  const existing = this.state.skippedIssues ?? [];
@@ -40561,7 +41076,8 @@ var OssScout = class {
40561
41076
  repo: metadata?.repo ?? "",
40562
41077
  number: metadata?.number ?? 0,
40563
41078
  title: metadata?.title ?? "",
40564
- skippedAt: (/* @__PURE__ */ new Date()).toISOString()
41079
+ skippedAt: (/* @__PURE__ */ new Date()).toISOString(),
41080
+ ...metadata?.reason ? { reason: metadata.reason } : {}
40565
41081
  }
40566
41082
  ];
40567
41083
  if (this.state.savedResults) {
@@ -40594,7 +41110,8 @@ var OssScout = class {
40594
41110
  this.dirty = true;
40595
41111
  }
40596
41112
  /**
40597
- * Remove skipped issues older than maxDays (default 90). Called automatically during search.
41113
+ * Remove skipped issues older than maxDays (default 90). Not called by
41114
+ * search since #343 (skips are permanent); available for an explicit prune.
40598
41115
  * @returns The number of expired entries that were removed.
40599
41116
  */
40600
41117
  cullExpiredSkips(maxDays = 90) {
@@ -41025,7 +41542,7 @@ function registerTools(server, scout) {
41025
41542
  );
41026
41543
  server.tool(
41027
41544
  "skip",
41028
- "Skip, unskip, list, or clear skipped issues. Skipped issues are excluded from future searches and auto-expire after 90 days.",
41545
+ "Skip, unskip, list, or clear skipped issues. Skipped issues are excluded from future searches permanently.",
41029
41546
  {
41030
41547
  action: external_exports3.enum(["add", "remove", "list", "clear"]).default("add").describe("Action to perform (default: add)"),
41031
41548
  issueUrl: external_exports3.string().optional().describe("GitHub issue URL (required for add/remove actions)")
@@ -41203,7 +41720,7 @@ function registerResources(server, scout) {
41203
41720
  // src/index.ts
41204
41721
  var SERVER_INFO = {
41205
41722
  name: "oss-scout-mcp",
41206
- version: "0.11.0"
41723
+ version: "0.12.0"
41207
41724
  // x-release-please-version
41208
41725
  };
41209
41726
  function createServer(scout) {