terminalhire 0.26.0 → 0.26.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.
@@ -826,6 +826,187 @@ var init_rigor = __esm({
826
826
  }
827
827
  });
828
828
 
829
+ // ../../packages/core/src/gh-governor.ts
830
+ function readReqGapMs() {
831
+ const raw = process.env["CONTRIB_REQ_GAP_MS"];
832
+ if (raw == null) return DEFAULT_REQ_GAP_MS;
833
+ const n = Number.parseInt(raw, 10);
834
+ if (Number.isNaN(n)) return DEFAULT_REQ_GAP_MS;
835
+ return Math.min(Math.max(n, 0), 1e3);
836
+ }
837
+ function readBuildBudgetMs() {
838
+ const raw = process.env["CONTRIB_BUILD_BUDGET_MS"];
839
+ if (raw == null) return DEFAULT_BUILD_BUDGET_MS;
840
+ const n = Number.parseInt(raw, 10);
841
+ if (Number.isNaN(n)) return DEFAULT_BUILD_BUDGET_MS;
842
+ return Math.min(Math.max(n, MIN_BUILD_BUDGET_MS), MAX_BUILD_BUDGET_MS);
843
+ }
844
+ function makeDefaultGovernorConfig(o) {
845
+ return {
846
+ paceEnabled: o.paceEnabled,
847
+ gapMs: readReqGapMs(),
848
+ budgetMs: readBuildBudgetMs(),
849
+ sleep: o.sleep ?? realSleep,
850
+ now: o.now ?? Date.now,
851
+ probeTimeoutMs: o.probeTimeoutMs ?? (o.paceEnabled ? PROBE_TIMEOUT_MS : null)
852
+ };
853
+ }
854
+ function makeGitHubGovernor(fetchImpl, cfg) {
855
+ const startedAt = cfg.now();
856
+ let lastRequestAt = 0;
857
+ let pacedMs = 0;
858
+ let secondaryHits = 0;
859
+ let aborted = false;
860
+ let secondaryAborted = false;
861
+ let budgetAborted = false;
862
+ let gqlCost = 0;
863
+ let gqlRemaining = null;
864
+ let coreHealthyAtStart = false;
865
+ async function noteAndMaybeBackOff(res) {
866
+ if (res.status !== 403) return;
867
+ const remaining = res.headers.get("x-ratelimit-remaining");
868
+ const retryAfter = res.headers.get("retry-after");
869
+ const positiveSecondary = retryAfter != null || remaining != null && remaining !== "0";
870
+ const isSecondary = positiveSecondary || coreHealthyAtStart;
871
+ if (!isSecondary) return;
872
+ await recordSecondaryStrike(retryAfter);
873
+ }
874
+ async function recordSecondaryStrike(retryAfter) {
875
+ secondaryHits++;
876
+ if (secondaryHits >= 2) {
877
+ aborted = true;
878
+ secondaryAborted = true;
879
+ return;
880
+ }
881
+ if (cfg.paceEnabled) {
882
+ const trimmed = (retryAfter ?? "").trim();
883
+ const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
884
+ const sec = Number.isNaN(parsed) ? SECONDARY_BACKOFF_CAP_S : Math.min(Math.max(parsed, 0), SECONDARY_BACKOFF_CAP_S);
885
+ const remainingBudget = Math.max(0, cfg.budgetMs - (cfg.now() - startedAt));
886
+ await cfg.sleep(Math.min(sec * 1e3, remainingBudget));
887
+ }
888
+ }
889
+ async function noteGraphQLAndMaybeBackOff(res, body) {
890
+ const b = body ?? {};
891
+ const rl = b.data?.rateLimit;
892
+ if (rl) {
893
+ if (typeof rl.cost === "number") gqlCost += rl.cost;
894
+ if (typeof rl.remaining === "number") gqlRemaining = rl.remaining;
895
+ }
896
+ const remainingHdr = res.headers?.get("x-ratelimit-remaining") ?? null;
897
+ const retryAfter = res.headers?.get("retry-after") ?? null;
898
+ const headerRate = retryAfter != null || remainingHdr === "0";
899
+ const errs = Array.isArray(b.errors) ? b.errors : [];
900
+ const bodyRate = errs.some((e) => e?.type === "RATE_LIMITED" || /rate limit/i.test(String(e?.message ?? ""))) || typeof rl?.remaining === "number" && rl.remaining <= 0;
901
+ if (headerRate || bodyRate) await recordSecondaryStrike(retryAfter);
902
+ }
903
+ async function preflight() {
904
+ if (aborted) return false;
905
+ if (cfg.now() - startedAt >= cfg.budgetMs) {
906
+ aborted = true;
907
+ budgetAborted = true;
908
+ return false;
909
+ }
910
+ if (cfg.paceEnabled && cfg.gapMs > 0) {
911
+ const wait = cfg.gapMs - (cfg.now() - lastRequestAt);
912
+ if (wait > 0) {
913
+ await cfg.sleep(wait);
914
+ pacedMs += wait;
915
+ if (cfg.now() - startedAt >= cfg.budgetMs) {
916
+ aborted = true;
917
+ budgetAborted = true;
918
+ return false;
919
+ }
920
+ }
921
+ lastRequestAt = cfg.now();
922
+ }
923
+ return true;
924
+ }
925
+ async function get(url, init) {
926
+ if (!await preflight()) return null;
927
+ try {
928
+ const res = await fetchImpl(url, init);
929
+ await noteAndMaybeBackOff(res);
930
+ return res;
931
+ } catch {
932
+ return null;
933
+ }
934
+ }
935
+ async function graphql(url, init) {
936
+ if (!await preflight()) return null;
937
+ let res;
938
+ try {
939
+ res = await fetchImpl(url, init);
940
+ } catch {
941
+ return null;
942
+ }
943
+ let body;
944
+ try {
945
+ body = await res.json();
946
+ } catch {
947
+ return null;
948
+ }
949
+ await noteGraphQLAndMaybeBackOff(res, body);
950
+ if (!res.ok) return null;
951
+ return body;
952
+ }
953
+ async function probe(url, init) {
954
+ const bound = cfg.probeTimeoutMs;
955
+ let timer;
956
+ const fetchP = fetchImpl(url, init).then(
957
+ (r) => r,
958
+ () => null
959
+ );
960
+ try {
961
+ const res = bound == null ? await fetchP : await Promise.race([
962
+ fetchP,
963
+ new Promise((resolve) => {
964
+ timer = setTimeout(() => resolve(null), bound);
965
+ })
966
+ ]);
967
+ if (!res || !res.ok) return null;
968
+ return await res.json();
969
+ } catch {
970
+ return null;
971
+ } finally {
972
+ if (timer) clearTimeout(timer);
973
+ }
974
+ }
975
+ function setSecondaryHint(coreHealthy) {
976
+ coreHealthyAtStart = coreHealthy;
977
+ }
978
+ function getStats() {
979
+ return {
980
+ pacedMs,
981
+ secondaryAborted: secondaryAborted ? 1 : 0,
982
+ budgetAborted: budgetAborted ? 1 : 0,
983
+ elapsedMs: cfg.now() - startedAt,
984
+ gqlCost,
985
+ gqlRemaining
986
+ };
987
+ }
988
+ function tripped() {
989
+ return secondaryAborted;
990
+ }
991
+ function budgetExhausted() {
992
+ return budgetAborted || cfg.now() - startedAt >= cfg.budgetMs;
993
+ }
994
+ return { get, graphql, probe, setSecondaryHint, getStats, tripped, budgetExhausted };
995
+ }
996
+ var DEFAULT_REQ_GAP_MS, SECONDARY_BACKOFF_CAP_S, DEFAULT_BUILD_BUDGET_MS, MIN_BUILD_BUDGET_MS, MAX_BUILD_BUDGET_MS, PROBE_TIMEOUT_MS, realSleep;
997
+ var init_gh_governor = __esm({
998
+ "../../packages/core/src/gh-governor.ts"() {
999
+ "use strict";
1000
+ DEFAULT_REQ_GAP_MS = 75;
1001
+ SECONDARY_BACKOFF_CAP_S = 30;
1002
+ DEFAULT_BUILD_BUDGET_MS = 9e4;
1003
+ MIN_BUILD_BUDGET_MS = 1e4;
1004
+ MAX_BUILD_BUDGET_MS = 9e4;
1005
+ PROBE_TIMEOUT_MS = 3e3;
1006
+ realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
1007
+ }
1008
+ });
1009
+
829
1010
  // ../../packages/core/src/github.ts
830
1011
  function ghHeaders(token) {
831
1012
  const headers = {
@@ -1306,11 +1487,9 @@ async function fetchRepoReceptivity(fullName, opts = {}) {
1306
1487
  const doFetch = opts.fetchImpl ?? fetch;
1307
1488
  const now = opts.now ?? Date.now();
1308
1489
  try {
1309
- const res = await doFetch(
1310
- `https://api.github.com/repos/${fullName}/pulls?state=closed&per_page=50&sort=updated&direction=desc`,
1311
- { headers: ghHeaders(opts.token) }
1312
- );
1313
- if (!res.ok) return null;
1490
+ const url = `https://api.github.com/repos/${fullName}/pulls?state=closed&per_page=50&sort=updated&direction=desc`;
1491
+ const res = opts.governor ? await opts.governor.get(url, { headers: ghHeaders(opts.token) }) : await doFetch(url, { headers: ghHeaders(opts.token) });
1492
+ if (!res || !res.ok) return null;
1314
1493
  const prs = await res.json();
1315
1494
  if (!Array.isArray(prs)) return null;
1316
1495
  const external = prs.filter(isExternalAuthor);
@@ -1395,25 +1574,34 @@ function parseGitHubRef(url) {
1395
1574
  if (!m) return null;
1396
1575
  return { owner: m[1], repo: m[2], number: parseInt(m[4], 10), kind: m[3] === "pull" ? "pull" : "issue" };
1397
1576
  }
1398
- async function ghGraphQL(query, variables, token, signal) {
1399
- const res = await fetch("https://api.github.com/graphql", {
1577
+ async function ghGraphQL(query, variables, token, signal, governor) {
1578
+ const init = {
1400
1579
  method: "POST",
1401
1580
  headers: { ...ghHeaders(token), "Content-Type": "application/json" },
1402
1581
  body: JSON.stringify({ query, variables }),
1403
1582
  signal
1404
- });
1583
+ };
1584
+ if (governor) {
1585
+ const json2 = await governor.graphql(GITHUB_GRAPHQL_URL, init);
1586
+ if (json2 === null) return null;
1587
+ if (json2.errors?.length) throw new Error("GitHub GraphQL errors: " + JSON.stringify(json2.errors));
1588
+ return json2;
1589
+ }
1590
+ const res = await fetch(GITHUB_GRAPHQL_URL, init);
1405
1591
  if (!res.ok) throw new Error(`GitHub GraphQL: HTTP ${res.status}`);
1406
1592
  const json = await res.json();
1407
1593
  if (json.errors?.length) throw new Error("GitHub GraphQL errors: " + JSON.stringify(json.errors));
1408
1594
  return json;
1409
1595
  }
1410
- async function resolveClosingIssues(owner, name, number, body, token, signal) {
1596
+ async function resolveClosingIssues(owner, name, number, body, token, signal, governor) {
1411
1597
  if (token) {
1412
1598
  try {
1413
- const q = `query($o:String!,$n:String!,$p:Int!){repository(owner:$o,name:$n){pullRequest(number:$p){closingIssuesReferences(first:20){nodes{number}}}}}`;
1414
- const r = await ghGraphQL(q, { o: owner, n: name, p: number }, token, signal);
1415
- const nodes = r.data?.repository?.pullRequest?.closingIssuesReferences?.nodes ?? [];
1416
- return { closesIssues: nodes.map((x) => x.number), linkageSource: "graphql" };
1599
+ const q = `query($o:String!,$n:String!,$p:Int!){repository(owner:$o,name:$n){pullRequest(number:$p){closingIssuesReferences(first:20){nodes{number}}}}rateLimit{cost remaining}}`;
1600
+ const r = await ghGraphQL(q, { o: owner, n: name, p: number }, token, signal, governor);
1601
+ if (r) {
1602
+ const nodes = r.data?.repository?.pullRequest?.closingIssuesReferences?.nodes ?? [];
1603
+ return { closesIssues: nodes.map((x) => x.number), linkageSource: "graphql" };
1604
+ }
1417
1605
  } catch {
1418
1606
  }
1419
1607
  }
@@ -1423,11 +1611,19 @@ async function resolveClosingIssues(owner, name, number, body, token, signal) {
1423
1611
  while ((m = re.exec(body)) !== null) nums.add(parseInt(m[1], 10));
1424
1612
  return { closesIssues: [...nums], linkageSource: nums.size ? "body-keyword" : "none" };
1425
1613
  }
1426
- async function fetchPRScoringFacts(prUrl, token, signal) {
1614
+ function makeScoringGovernor(governor) {
1615
+ return governor ?? makeGitHubGovernor(
1616
+ ((url, init) => fetch(url, init)),
1617
+ makeDefaultGovernorConfig({ paceEnabled: false })
1618
+ );
1619
+ }
1620
+ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
1427
1621
  const ref = parseGitHubRef(prUrl);
1428
1622
  if (!ref || ref.kind !== "pull") return null;
1429
1623
  const { owner, repo, number } = ref;
1430
1624
  const sig = signal ?? AbortSignal.timeout(1e4);
1625
+ const gov = makeScoringGovernor(governor);
1626
+ if (gov.tripped() || gov.budgetExhausted()) return null;
1431
1627
  let pr;
1432
1628
  try {
1433
1629
  pr = await ghFetch(`/repos/${owner}/${repo}/pulls/${number}`, token, sig);
@@ -1435,22 +1631,26 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1435
1631
  return null;
1436
1632
  }
1437
1633
  let repoMeta = null;
1438
- try {
1439
- repoMeta = await ghFetch(`/repos/${owner}/${repo}`, token, sig);
1440
- } catch {
1634
+ if (!gov.tripped() && !gov.budgetExhausted()) {
1635
+ try {
1636
+ repoMeta = await ghFetch(`/repos/${owner}/${repo}`, token, sig);
1637
+ } catch {
1638
+ }
1441
1639
  }
1442
- const contributors = await repoContributorCount(owner, repo, token, sig);
1443
- const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig);
1640
+ const contributors = gov.tripped() || gov.budgetExhausted() ? null : await repoContributorCount(owner, repo, token, sig);
1641
+ const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig, gov);
1444
1642
  let reviewerAssociations;
1445
- try {
1446
- const reviews = await ghFetch(
1447
- `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1448
- token,
1449
- sig
1450
- );
1451
- reviewerAssociations = reviews.map((r) => r.author_association);
1452
- } catch {
1453
- reviewerAssociations = void 0;
1643
+ if (!gov.tripped() && !gov.budgetExhausted()) {
1644
+ try {
1645
+ const reviews = await ghFetch(
1646
+ `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1647
+ token,
1648
+ sig
1649
+ );
1650
+ reviewerAssociations = reviews.map((r) => r.author_association);
1651
+ } catch {
1652
+ reviewerAssociations = void 0;
1653
+ }
1454
1654
  }
1455
1655
  return {
1456
1656
  repo: `${owner}/${repo}`,
@@ -1477,7 +1677,7 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1477
1677
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1478
1678
  };
1479
1679
  }
1480
- var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR;
1680
+ var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR, GITHUB_GRAPHQL_URL;
1481
1681
  var init_github = __esm({
1482
1682
  "../../packages/core/src/github.ts"() {
1483
1683
  "use strict";
@@ -1485,6 +1685,7 @@ var init_github = __esm({
1485
1685
  init_contribution_gate();
1486
1686
  init_contribution_gate();
1487
1687
  init_rigor();
1688
+ init_gh_governor();
1488
1689
  TRACTION_TOP_N = 6;
1489
1690
  MAINTAINER_ENRICH_MAX = 25;
1490
1691
  CANDIDATE_PR_PAGE = 50;
@@ -1495,6 +1696,7 @@ var init_github = __esm({
1495
1696
  RESUME_MIN_SCORE = 0.05;
1496
1697
  RECEPTIVITY_RECENCY_DAYS = 180;
1497
1698
  RECEPTIVITY_RECENCY_FLOOR = 0.1;
1699
+ GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
1498
1700
  }
1499
1701
  });
1500
1702
 
@@ -1575,34 +1777,45 @@ function mergeSoftCoverage(covMap, softTags, cap) {
1575
1777
  }
1576
1778
  return covMap;
1577
1779
  }
1780
+ function tagKernel(job, ctx) {
1781
+ const { expanded, covMap, maxDevScore, skillTags } = ctx;
1782
+ const details = [];
1783
+ let jobMatchScore = 0;
1784
+ let jobMaxScore = 0;
1785
+ const devCovByTag = /* @__PURE__ */ new Map();
1786
+ for (const tag of job.tags) {
1787
+ const w = backgroundIdf(tag);
1788
+ jobMaxScore += w;
1789
+ const covHit = covMap.get(tag);
1790
+ if (covHit) jobMatchScore += w * Math.pow(covHit.weight, SHARPEN);
1791
+ const fpHit = expanded.get(tag);
1792
+ if (fpHit) {
1793
+ const credit = Math.pow(fpHit.weight, SHARPEN);
1794
+ details.push({ tag, weight: fpHit.weight, via: fpHit.via });
1795
+ if (credit > (devCovByTag.get(fpHit.via) ?? 0)) devCovByTag.set(fpHit.via, credit);
1796
+ }
1797
+ }
1798
+ let devScore = 0;
1799
+ for (const t of skillTags) devScore += backgroundIdf(t) * (devCovByTag.get(t) ?? 0);
1800
+ const devCov = maxDevScore > 0 ? Math.min(1, devScore / maxDevScore) : 0;
1801
+ const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
1802
+ return { tagComponent: harmonicMean(devCov, jobCov), details };
1803
+ }
1804
+ function relevanceScore(fp, job, softTags = []) {
1805
+ const expanded = expandWeighted(fp.skillTags);
1806
+ const covMap = softTags.length > 0 ? mergeSoftCoverage(new Map(expanded), softTags, INTEREST_CAP) : expanded;
1807
+ const maxDevScore = fp.skillTags.reduce((acc, t) => acc + backgroundIdf(t), 0);
1808
+ return tagKernel(job, { expanded, covMap, maxDevScore, skillTags: fp.skillTags }).tagComponent;
1809
+ }
1578
1810
  function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
1579
1811
  const idfOf = backgroundIdf;
1580
1812
  const expanded = expandWeighted(fp.skillTags);
1581
1813
  const covMap = opts.softTags && opts.softTags.length > 0 ? mergeSoftCoverage(new Map(expanded), opts.softTags, INTEREST_CAP) : expanded;
1582
1814
  const maxDevScore = fp.skillTags.reduce((acc, t) => acc + idfOf(t), 0);
1815
+ const ctx = { expanded, covMap, maxDevScore, skillTags: fp.skillTags };
1583
1816
  const candidates = jobs.filter((j) => passesFilters(fp, j));
1584
1817
  const scored = candidates.map((job) => {
1585
- const details = [];
1586
- let jobMatchScore = 0;
1587
- let jobMaxScore = 0;
1588
- const devCovByTag = /* @__PURE__ */ new Map();
1589
- for (const tag of job.tags) {
1590
- const w = idfOf(tag);
1591
- jobMaxScore += w;
1592
- const covHit = covMap.get(tag);
1593
- if (covHit) jobMatchScore += w * Math.pow(covHit.weight, SHARPEN);
1594
- const fpHit = expanded.get(tag);
1595
- if (fpHit) {
1596
- const credit = Math.pow(fpHit.weight, SHARPEN);
1597
- details.push({ tag, weight: fpHit.weight, via: fpHit.via });
1598
- if (credit > (devCovByTag.get(fpHit.via) ?? 0)) devCovByTag.set(fpHit.via, credit);
1599
- }
1600
- }
1601
- let devScore = 0;
1602
- for (const t of fp.skillTags) devScore += idfOf(t) * (devCovByTag.get(t) ?? 0);
1603
- const devCov = maxDevScore > 0 ? Math.min(1, devScore / maxDevScore) : 0;
1604
- const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
1605
- const tagComponent = harmonicMean(devCov, jobCov);
1818
+ const { tagComponent, details } = tagKernel(job, ctx);
1606
1819
  if (tagComponent === 0) return null;
1607
1820
  const coreTags = job.coreTags ?? coreTagsFromTitle(job.title);
1608
1821
  let coreComponent = tagComponent;
@@ -3137,6 +3350,7 @@ function hasContentSignal(title, body, labels) {
3137
3350
  ${body}`;
3138
3351
  if (CONTENT_ADD_RE.test(text)) return true;
3139
3352
  if (ADD_TO_CORPUS_RE.test(text)) return true;
3353
+ if (NUMBERED_SEED_RE.test(title)) return true;
3140
3354
  if (TRANSLATE_RE.test(text)) return true;
3141
3355
  if (TYPO_RE.test(text)) return true;
3142
3356
  return false;
@@ -3152,7 +3366,7 @@ function classifyContributionKind(input) {
3152
3366
  function looksLikeContentTask(input) {
3153
3367
  return classifyContributionKind(input) === "content";
3154
3368
  }
3155
- var CONTENT_LABEL_RE, CODE_LABEL_RE, CODE_TERM_RE, CODE_EXCEPTION_RE, CODE_FENCE_RE, FILE_PATH_RE, CONTENT_ADD_RE, ADD_TO_CORPUS_RE, TRANSLATE_RE, TYPO_RE;
3369
+ var CONTENT_LABEL_RE, CODE_LABEL_RE, CODE_TERM_RE, CODE_EXCEPTION_RE, CODE_FENCE_RE, FILE_PATH_RE, CONTENT_NOUN_STRONG, CONTENT_NOUN_BROAD, CONTENT_ADD_RE, NUMBERED_SEED_RE, ADD_TO_CORPUS_RE, TRANSLATE_RE, TYPO_RE;
3156
3370
  var init_contribution_classify = __esm({
3157
3371
  "../../packages/core/src/feeds/contribution-classify.ts"() {
3158
3372
  "use strict";
@@ -3162,7 +3376,16 @@ var init_contribution_classify = __esm({
3162
3376
  CODE_EXCEPTION_RE = /exception|stacktrace|segfault|traceback/i;
3163
3377
  CODE_FENCE_RE = /```|(?:^|\n)\s{4,}\S/;
3164
3378
  FILE_PATH_RE = /\b[\w./-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|java|kt|scala|c|cc|cpp|cxx|h|hpp|cs|php|swift|m|mm|sh|bash|zsh|sql|graphql|proto|css|scss|sass|less|vue|svelte|toml|ini|gradle|dockerfile)\b/i;
3165
- CONTENT_ADD_RE = /\badd(?:ing|s)?\s+(?:\w+\s+){0,4}?(?:proverbs?|words?|phrases?|sayings?|quotes?|quotations?|translations?|entry|entries|definitions?|terms?|idioms?|synonyms?|antonyms?|acronyms?|abbreviations?)\b/i;
3379
+ CONTENT_NOUN_STRONG = String.raw`proverbs?|words?|phrases?|sayings?|quotes?|quotations?|translations?|entry|entries|definitions?|terms?|idioms?|synonyms?|antonyms?|acronyms?|abbreviations?`;
3380
+ CONTENT_NOUN_BROAD = String.raw`trivia\s+questions?|grammar\s+points?|trivia|facts?|quiz(?:zes)?|flash\s?cards?|vocab(?:ulary)?|lessons?|kanji`;
3381
+ CONTENT_ADD_RE = new RegExp(
3382
+ String.raw`\badd(?:ing|s)?\s+(?:\w+\s+){0,4}?(?:${CONTENT_NOUN_STRONG})\b`,
3383
+ "i"
3384
+ );
3385
+ NUMBERED_SEED_RE = new RegExp(
3386
+ String.raw`\badd(?:ing|s)?\s+(?:\w+\s+){0,4}?(?:${CONTENT_NOUN_STRONG}|${CONTENT_NOUN_BROAD})\s*#?\s*\d{1,3}\s*$`,
3387
+ "i"
3388
+ );
3166
3389
  ADD_TO_CORPUS_RE = /\badd\b[\s\S]*?\bto\s+(?:the\s+)?(?:word\s?list|dictionary|glossary|phrasebook)\b/i;
3167
3390
  TRANSLATE_RE = /\b(?:translate|translating|translation|localize|localise|localization|localisation)\b/i;
3168
3391
  TYPO_RE = /\bfix(?:ing)?\s+(?:a\s+|the\s+|some\s+)?typos?\b/i;
@@ -3170,20 +3393,6 @@ var init_contribution_classify = __esm({
3170
3393
  });
3171
3394
 
3172
3395
  // ../../packages/core/src/feeds/contributions.ts
3173
- function readReqGapMs() {
3174
- const raw = process.env["CONTRIB_REQ_GAP_MS"];
3175
- if (raw == null) return DEFAULT_REQ_GAP_MS;
3176
- const n = Number.parseInt(raw, 10);
3177
- if (Number.isNaN(n)) return DEFAULT_REQ_GAP_MS;
3178
- return Math.min(Math.max(n, 0), 1e3);
3179
- }
3180
- function readBuildBudgetMs() {
3181
- const raw = process.env["CONTRIB_BUILD_BUDGET_MS"];
3182
- if (raw == null) return DEFAULT_BUILD_BUDGET_MS;
3183
- const n = Number.parseInt(raw, 10);
3184
- if (Number.isNaN(n)) return DEFAULT_BUILD_BUDGET_MS;
3185
- return Math.min(Math.max(n, MIN_BUILD_BUDGET_MS), MAX_BUILD_BUDGET_MS);
3186
- }
3187
3396
  function authHeaders2() {
3188
3397
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
3189
3398
  const h = {
@@ -3205,57 +3414,9 @@ function repoFullNameFromApiUrl2(url) {
3205
3414
  return m ? `${m[1]}/${m[2]}` : null;
3206
3415
  }
3207
3416
  function makeClient(fetchImpl, cfg) {
3208
- const startedAt = cfg.now();
3209
- let lastRequestAt = 0;
3210
- let pacedMs = 0;
3211
- let secondaryHits = 0;
3212
- let aborted = false;
3213
- let secondaryAborted = false;
3214
- let budgetAborted = false;
3215
- let coreHealthyAtStart = false;
3216
- async function noteAndMaybeBackOff(res) {
3217
- if (res.status !== 403) return;
3218
- const remaining = res.headers.get("x-ratelimit-remaining");
3219
- const retryAfter = res.headers.get("retry-after");
3220
- const positiveSecondary = retryAfter != null || remaining != null && remaining !== "0";
3221
- const isSecondary = positiveSecondary || coreHealthyAtStart;
3222
- if (!isSecondary) return;
3223
- secondaryHits++;
3224
- if (secondaryHits >= 2) {
3225
- aborted = true;
3226
- secondaryAborted = true;
3227
- return;
3228
- }
3229
- if (cfg.paceEnabled) {
3230
- const trimmed = (retryAfter ?? "").trim();
3231
- const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
3232
- const sec = Number.isNaN(parsed) ? SECONDARY_BACKOFF_CAP_S : Math.min(Math.max(parsed, 0), SECONDARY_BACKOFF_CAP_S);
3233
- const remaining2 = Math.max(0, cfg.budgetMs - (cfg.now() - startedAt));
3234
- await cfg.sleep(Math.min(sec * 1e3, remaining2));
3235
- }
3236
- }
3417
+ const gov = makeGitHubGovernor(fetchImpl, cfg);
3237
3418
  async function raw(path) {
3238
- if (aborted) return null;
3239
- if (cfg.now() - startedAt > cfg.budgetMs) {
3240
- aborted = true;
3241
- budgetAborted = true;
3242
- return null;
3243
- }
3244
- if (cfg.paceEnabled && cfg.gapMs > 0) {
3245
- const wait = cfg.gapMs - (cfg.now() - lastRequestAt);
3246
- if (wait > 0) {
3247
- await cfg.sleep(wait);
3248
- pacedMs += wait;
3249
- }
3250
- lastRequestAt = cfg.now();
3251
- }
3252
- try {
3253
- const res = await fetchImpl(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3254
- await noteAndMaybeBackOff(res);
3255
- return res;
3256
- } catch {
3257
- return null;
3258
- }
3419
+ return gov.get(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3259
3420
  }
3260
3421
  async function json(path) {
3261
3422
  const res = await raw(path);
@@ -3269,41 +3430,9 @@ function makeClient(fetchImpl, cfg) {
3269
3430
  }
3270
3431
  }
3271
3432
  async function probe(path) {
3272
- const bound = cfg.probeTimeoutMs;
3273
- let timer;
3274
- const fetchP = fetchImpl(`${GITHUB_API2}${path}`, {
3275
- headers: authHeaders2()
3276
- }).then(
3277
- (r) => r,
3278
- () => null
3279
- );
3280
- try {
3281
- const res = bound == null ? await fetchP : await Promise.race([
3282
- fetchP,
3283
- new Promise((resolve) => {
3284
- timer = setTimeout(() => resolve(null), bound);
3285
- })
3286
- ]);
3287
- if (!res || !res.ok) return null;
3288
- return await res.json();
3289
- } catch {
3290
- return null;
3291
- } finally {
3292
- if (timer) clearTimeout(timer);
3293
- }
3433
+ return gov.probe(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3294
3434
  }
3295
- function setSecondaryHint(coreHealthy) {
3296
- coreHealthyAtStart = coreHealthy;
3297
- }
3298
- function getStats() {
3299
- return {
3300
- pacedMs,
3301
- secondaryAborted: secondaryAborted ? 1 : 0,
3302
- budgetAborted: budgetAborted ? 1 : 0,
3303
- elapsedMs: cfg.now() - startedAt
3304
- };
3305
- }
3306
- return { raw, json, probe, setSecondaryHint, getStats };
3435
+ return { raw, json, probe, setSecondaryHint: gov.setSecondaryHint, getStats: gov.getStats };
3307
3436
  }
3308
3437
  async function contributorCount(client, fullName) {
3309
3438
  const res = await client.raw(`/repos/${fullName}/contributors?per_page=1&anon=false`);
@@ -3402,10 +3531,63 @@ async function aggregateContributions(opts = {}) {
3402
3531
  const repoCache = /* @__PURE__ */ new Map();
3403
3532
  const contribCache = /* @__PURE__ */ new Map();
3404
3533
  const prRefsCache = /* @__PURE__ */ new Map();
3534
+ const xbuild = opts.repoMetaCache;
3535
+ const servedFromXbuild = /* @__PURE__ */ new Set();
3536
+ const persistedXbuild = /* @__PURE__ */ new Set();
3537
+ const xbuildTried = /* @__PURE__ */ new Set();
3538
+ async function primeFromXbuild(key, fullName) {
3539
+ if (!xbuild || xbuildTried.has(key)) return;
3540
+ xbuildTried.add(key);
3541
+ if (repoCache.has(key) && contribCache.has(key)) return;
3542
+ let cached = null;
3543
+ try {
3544
+ cached = await xbuild.get(key);
3545
+ } catch {
3546
+ return;
3547
+ }
3548
+ if (!cached) return;
3549
+ if (!repoCache.has(key)) {
3550
+ const owner = fullName.split("/")[0] ?? "";
3551
+ repoCache.set(key, {
3552
+ full_name: fullName,
3553
+ stargazers_count: cached.stars,
3554
+ archived: cached.archived,
3555
+ disabled: cached.disabled,
3556
+ fork: cached.fork,
3557
+ language: cached.language,
3558
+ owner: { login: owner }
3559
+ });
3560
+ }
3561
+ if (!contribCache.has(key)) {
3562
+ contribCache.set(key, cached.contributors);
3563
+ servedFromXbuild.add(key);
3564
+ }
3565
+ }
3566
+ async function persistRepoMeta(fullName, repo, contributors) {
3567
+ if (!xbuild) return;
3568
+ const key = repoKey(fullName);
3569
+ if (servedFromXbuild.has(key) || persistedXbuild.has(key)) return;
3570
+ if (contributors === void 0) return;
3571
+ persistedXbuild.add(key);
3572
+ try {
3573
+ await xbuild.set(key, {
3574
+ stars: repo.stargazers_count,
3575
+ contributors,
3576
+ language: repo.language,
3577
+ archived: repo.archived,
3578
+ fork: repo.fork,
3579
+ disabled: repo.disabled
3580
+ });
3581
+ } catch {
3582
+ }
3583
+ }
3405
3584
  async function repoMeta(fullName) {
3406
3585
  const key = repoKey(fullName);
3407
3586
  const hit = repoCache.get(key);
3408
3587
  if (hit !== void 0) return hit;
3588
+ await primeFromXbuild(key, fullName);
3589
+ const primed = repoCache.get(key);
3590
+ if (primed !== void 0) return primed;
3409
3591
  const r = await client.json(`/repos/${fullName}`) ?? null;
3410
3592
  repoCache.set(key, r);
3411
3593
  return r;
@@ -3413,6 +3595,8 @@ async function aggregateContributions(opts = {}) {
3413
3595
  async function repoContribCount(fullName) {
3414
3596
  const key = repoKey(fullName);
3415
3597
  if (contribCache.has(key)) return contribCache.get(key);
3598
+ await primeFromXbuild(key, fullName);
3599
+ if (contribCache.has(key)) return contribCache.get(key);
3416
3600
  const n = await contributorCount(client, fullName);
3417
3601
  contribCache.set(key, n);
3418
3602
  return n;
@@ -3439,14 +3623,18 @@ async function aggregateContributions(opts = {}) {
3439
3623
  if (isDenylistedRepo(fullName)) continue;
3440
3624
  if (isAssigned(issue)) continue;
3441
3625
  if ((perRepo.get(repoKey(fullName)) ?? 0) >= MAX_BOUNTIES_PER_DISCOVERED_REPO) continue;
3626
+ const title = decodeEntities(issue.title).trim();
3627
+ const body = issue.body ? decodeEntities(issue.body) : "";
3628
+ const labels = labelNames2(issue.labels);
3629
+ if (looksLikeContentTask({ title, body, labels })) continue;
3442
3630
  const repo = await repoMeta(fullName);
3443
3631
  if (!repo) {
3444
3632
  metaNull++;
3445
3633
  continue;
3446
3634
  }
3447
- const title = decodeEntities(issue.title).trim();
3448
3635
  const contributors = await repoContribCount(fullName);
3449
3636
  if (contributors === void 0) contribUndefined++;
3637
+ await persistRepoMeta(fullName, repo, contributors);
3450
3638
  if (!passesContributionGate({
3451
3639
  fullName,
3452
3640
  stars: repo.stargazers_count,
@@ -3458,13 +3646,9 @@ async function aggregateContributions(opts = {}) {
3458
3646
  continue;
3459
3647
  }
3460
3648
  if (repo.disabled) continue;
3461
- const body = issue.body ? decodeEntities(issue.body) : "";
3462
- const labels = labelNames2(issue.labels);
3463
- if (looksLikeContentTask({ title, body, labels })) continue;
3464
3649
  const prRefs = await repoPRRefs(fullName);
3465
3650
  if (prRefs === null) prRefsNull++;
3466
- if (prRefs && prRefs.has(issue.number)) continue;
3467
- const openPRsAtDiscovery = prRefs ? 0 : void 0;
3651
+ const openPRsAtDiscovery = prRefs ? prRefs.has(issue.number) ? 1 : 0 : void 0;
3468
3652
  const tags = normalize(
3469
3653
  tokenize4([title, repo.language ?? "", labels.join(" "), body.slice(0, 2e3)].join(" "))
3470
3654
  );
@@ -3555,6 +3739,7 @@ async function aggregateContributions(opts = {}) {
3555
3739
  if (repo.disabled) continue;
3556
3740
  const contributors = await repoContribCount(fullName);
3557
3741
  if (contributors === void 0) contribUndefined++;
3742
+ await persistRepoMeta(fullName, repo, contributors);
3558
3743
  if (repo.archived || repo.fork || repo.stargazers_count < MIN_STARS || contributors === void 0 || contributors < MIN_CONTRIBUTORS) {
3559
3744
  continue;
3560
3745
  }
@@ -3587,8 +3772,7 @@ async function aggregateContributions(opts = {}) {
3587
3772
  if (looksLikeContentTask({ title, body, labels })) continue;
3588
3773
  const prRefs = await repoPRRefs(fullName);
3589
3774
  if (prRefs === null) prRefsNull++;
3590
- if (prRefs && prRefs.has(issue.number)) continue;
3591
- const openPRsAtDiscovery = prRefs ? 0 : void 0;
3775
+ const openPRsAtDiscovery = prRefs ? prRefs.has(issue.number) ? 1 : 0 : void 0;
3592
3776
  const tags = normalize(
3593
3777
  tokenize4([title, repo.language ?? "", labels.join(" "), body.slice(0, 2e3)].join(" "))
3594
3778
  );
@@ -3619,9 +3803,9 @@ async function aggregateContributions(opts = {}) {
3619
3803
  const core = rl?.core ? `${rl.core.remaining}/${rl.core.limit}` : "n/a";
3620
3804
  const search = rl?.search ? `${rl.search.remaining}/${rl.search.limit}` : "n/a";
3621
3805
  const noToken = !(process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"]);
3622
- const { pacedMs, secondaryAborted, budgetAborted, elapsedMs } = client.getStats();
3806
+ const { pacedMs, secondaryAborted, budgetAborted, elapsedMs, gqlCost, gqlRemaining } = client.getStats();
3623
3807
  console.info(
3624
- `[contribute] build metrics \u2014 scanned=${issues.length} reposDistinct=${repoCache.size} emitted=${jobs.length} discovered=${discoveredEmitted} metaNull=${metaNull} contribUndefined=${contribUndefined} prRefsNull=${prRefsNull} paced=${pacedMs} secondaryAborted=${secondaryAborted} budgetAborted=${budgetAborted} core=${core} search=${search} elapsed=${elapsedMs}` + (noToken ? " (NO TOKEN \u2192 60/hr)" : "")
3808
+ `[contribute] build metrics \u2014 scanned=${issues.length} reposDistinct=${repoCache.size} emitted=${jobs.length} discovered=${discoveredEmitted} metaNull=${metaNull} contribUndefined=${contribUndefined} prRefsNull=${prRefsNull} paced=${pacedMs} secondaryAborted=${secondaryAborted} budgetAborted=${budgetAborted} core=${core} search=${search} gqlCost=${gqlCost} gqlRemaining=${gqlRemaining ?? "n/a"} elapsed=${elapsedMs}` + (noToken ? " (NO TOKEN \u2192 60/hr)" : "")
3625
3809
  );
3626
3810
  if (discoveryBudgetStopped) {
3627
3811
  console.warn(
@@ -3631,7 +3815,7 @@ async function aggregateContributions(opts = {}) {
3631
3815
  }
3632
3816
  return jobs;
3633
3817
  }
3634
- var GITHUB_API2, DEFAULT_REQ_GAP_MS, SECONDARY_BACKOFF_CAP_S, DEFAULT_BUILD_BUDGET_MS, MIN_BUILD_BUDGET_MS, MAX_BUILD_BUDGET_MS, PROBE_TIMEOUT_MS, realSleep, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED, MAX_DISCOVERED_REPOS, MAX_ISSUES_PER_DISCOVERED_REPO, DISCOVERY_REPOS_PER_TERM, DISCOVERY_VOCAB_TERMS, DISCOVERY_ISSUE_LABELS, repoKey;
3818
+ var GITHUB_API2, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED, MAX_DISCOVERED_REPOS, MAX_ISSUES_PER_DISCOVERED_REPO, DISCOVERY_REPOS_PER_TERM, DISCOVERY_VOCAB_TERMS, DISCOVERY_ISSUE_LABELS, repoKey;
3635
3819
  var init_contributions = __esm({
3636
3820
  "../../packages/core/src/feeds/contributions.ts"() {
3637
3821
  "use strict";
@@ -3642,14 +3826,8 @@ var init_contributions = __esm({
3642
3826
  init_contribution_classify();
3643
3827
  init_github_bounties();
3644
3828
  init_http();
3829
+ init_gh_governor();
3645
3830
  GITHUB_API2 = "https://api.github.com";
3646
- DEFAULT_REQ_GAP_MS = 75;
3647
- SECONDARY_BACKOFF_CAP_S = 30;
3648
- DEFAULT_BUILD_BUDGET_MS = 9e4;
3649
- MIN_BUILD_BUDGET_MS = 1e4;
3650
- MAX_BUILD_BUDGET_MS = 9e4;
3651
- PROBE_TIMEOUT_MS = 3e3;
3652
- realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
3653
3831
  CONTRIB_LABEL_QUERIES = [
3654
3832
  'label:"good first issue" type:issue state:open',
3655
3833
  'label:"good-first-issue" type:issue state:open',
@@ -3867,12 +4045,22 @@ async function enrichWinnability(jobs, contribute, w) {
3867
4045
  const repos = [...byRepo.keys()].sort((a, b) => starsOf(b) - starsOf(a));
3868
4046
  const scored = repos.slice(0, maxRepos);
3869
4047
  const cappedOut = repos.length - scored.length;
4048
+ const gov = w.governor ?? makeGitHubGovernor(
4049
+ w.fetchImpl ?? fetch,
4050
+ makeDefaultGovernorConfig({ paceEnabled: !w.fetchImpl })
4051
+ );
4052
+ let stoppedEarly = false;
3870
4053
  for (const repo of scored) {
4054
+ if (gov.tripped() || gov.budgetExhausted()) {
4055
+ stoppedEarly = true;
4056
+ break;
4057
+ }
3871
4058
  const targets = byRepo.get(repo) ?? [];
3872
4059
  const receptivity = await fetchRepoReceptivity(repo, {
3873
4060
  token: w.token,
3874
4061
  fetchImpl: w.fetchImpl,
3875
- now
4062
+ now,
4063
+ governor: gov
3876
4064
  });
3877
4065
  const stars = starsOf(repo);
3878
4066
  const prev = priorStars[repo];
@@ -3901,6 +4089,12 @@ async function enrichWinnability(jobs, contribute, w) {
3901
4089
  }
3902
4090
  }
3903
4091
  }
4092
+ if (stoppedEarly) {
4093
+ const reason = gov.tripped() ? "secondary-abuse breaker" : "wall-clock budget";
4094
+ console.warn(
4095
+ `[winnability] receptivity governor stopped the loop \u2014 ${reason} tripped; remaining repos left un-scored (legacy ranking). Governed egress halted to avoid prolonging the GitHub rate-limit window.`
4096
+ );
4097
+ }
3904
4098
  if (cappedOut > 0) {
3905
4099
  console.warn(
3906
4100
  `[winnability] receptivity cap hit \u2014 scored ${scored.length}/${repos.length} repos (${cappedOut} beyond maxRepos=${maxRepos} left un-scored, legacy ranking)`
@@ -3943,6 +4137,7 @@ var init_indexer = __esm({
3943
4137
  init_contributions();
3944
4138
  init_partners();
3945
4139
  init_github();
4140
+ init_gh_governor();
3946
4141
  init_winnability();
3947
4142
  }
3948
4143
  });
@@ -7538,6 +7733,7 @@ __export(src_exports, {
7538
7733
  MERGE_PROBABILITY: () => MERGE_PROBABILITY,
7539
7734
  MIN_CONTRIBUTORS: () => MIN_CONTRIBUTORS,
7540
7735
  MIN_STARS: () => MIN_STARS,
7736
+ PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
7541
7737
  RIGOR: () => RIGOR,
7542
7738
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
7543
7739
  SYNONYMS: () => SYNONYMS,
@@ -7609,6 +7805,9 @@ __export(src_exports, {
7609
7805
  lever: () => lever,
7610
7806
  loadPartnerRoles: () => loadPartnerRoles,
7611
7807
  looksLikeEngRole: () => looksLikeEngRole,
7808
+ makeDefaultGovernorConfig: () => makeDefaultGovernorConfig,
7809
+ makeGitHubGovernor: () => makeGitHubGovernor,
7810
+ makeScoringGovernor: () => makeScoringGovernor,
7612
7811
  match: () => match,
7613
7812
  mergeProbability: () => mergeProbability,
7614
7813
  mmrRerank: () => mmrRerank,
@@ -7621,8 +7820,12 @@ __export(src_exports, {
7621
7820
  passesMaturityGate: () => passesMaturityGate,
7622
7821
  personCardToJob: () => personCardToJob,
7623
7822
  projectCardToJob: () => projectCardToJob,
7823
+ readBuildBudgetMs: () => readBuildBudgetMs,
7824
+ readReqGapMs: () => readReqGapMs,
7825
+ realSleep: () => realSleep,
7624
7826
  recordClick: () => recordClick,
7625
7827
  rejectExtraIntroFields: () => rejectExtraIntroFields,
7828
+ relevanceScore: () => relevanceScore,
7626
7829
  revealIntroContacts: () => revealIntroContacts,
7627
7830
  safetyNumber: () => safetyNumber,
7628
7831
  sameLogin: () => sameLogin,
@@ -7649,6 +7852,7 @@ var init_src = __esm({
7649
7852
  init_winnability();
7650
7853
  init_partners();
7651
7854
  init_github();
7855
+ init_gh_governor();
7652
7856
  init_credit();
7653
7857
  init_intro();
7654
7858
  init_directoryThreshold();