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.
@@ -855,6 +855,187 @@ var init_rigor = __esm({
855
855
  }
856
856
  });
857
857
 
858
+ // ../../packages/core/src/gh-governor.ts
859
+ function readReqGapMs() {
860
+ const raw = process.env["CONTRIB_REQ_GAP_MS"];
861
+ if (raw == null) return DEFAULT_REQ_GAP_MS;
862
+ const n = Number.parseInt(raw, 10);
863
+ if (Number.isNaN(n)) return DEFAULT_REQ_GAP_MS;
864
+ return Math.min(Math.max(n, 0), 1e3);
865
+ }
866
+ function readBuildBudgetMs() {
867
+ const raw = process.env["CONTRIB_BUILD_BUDGET_MS"];
868
+ if (raw == null) return DEFAULT_BUILD_BUDGET_MS;
869
+ const n = Number.parseInt(raw, 10);
870
+ if (Number.isNaN(n)) return DEFAULT_BUILD_BUDGET_MS;
871
+ return Math.min(Math.max(n, MIN_BUILD_BUDGET_MS), MAX_BUILD_BUDGET_MS);
872
+ }
873
+ function makeDefaultGovernorConfig(o) {
874
+ return {
875
+ paceEnabled: o.paceEnabled,
876
+ gapMs: readReqGapMs(),
877
+ budgetMs: readBuildBudgetMs(),
878
+ sleep: o.sleep ?? realSleep,
879
+ now: o.now ?? Date.now,
880
+ probeTimeoutMs: o.probeTimeoutMs ?? (o.paceEnabled ? PROBE_TIMEOUT_MS : null)
881
+ };
882
+ }
883
+ function makeGitHubGovernor(fetchImpl, cfg) {
884
+ const startedAt = cfg.now();
885
+ let lastRequestAt = 0;
886
+ let pacedMs = 0;
887
+ let secondaryHits = 0;
888
+ let aborted = false;
889
+ let secondaryAborted = false;
890
+ let budgetAborted = false;
891
+ let gqlCost = 0;
892
+ let gqlRemaining = null;
893
+ let coreHealthyAtStart = false;
894
+ async function noteAndMaybeBackOff(res) {
895
+ if (res.status !== 403) return;
896
+ const remaining = res.headers.get("x-ratelimit-remaining");
897
+ const retryAfter = res.headers.get("retry-after");
898
+ const positiveSecondary = retryAfter != null || remaining != null && remaining !== "0";
899
+ const isSecondary = positiveSecondary || coreHealthyAtStart;
900
+ if (!isSecondary) return;
901
+ await recordSecondaryStrike(retryAfter);
902
+ }
903
+ async function recordSecondaryStrike(retryAfter) {
904
+ secondaryHits++;
905
+ if (secondaryHits >= 2) {
906
+ aborted = true;
907
+ secondaryAborted = true;
908
+ return;
909
+ }
910
+ if (cfg.paceEnabled) {
911
+ const trimmed = (retryAfter ?? "").trim();
912
+ const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
913
+ const sec = Number.isNaN(parsed) ? SECONDARY_BACKOFF_CAP_S : Math.min(Math.max(parsed, 0), SECONDARY_BACKOFF_CAP_S);
914
+ const remainingBudget = Math.max(0, cfg.budgetMs - (cfg.now() - startedAt));
915
+ await cfg.sleep(Math.min(sec * 1e3, remainingBudget));
916
+ }
917
+ }
918
+ async function noteGraphQLAndMaybeBackOff(res, body) {
919
+ const b = body ?? {};
920
+ const rl = b.data?.rateLimit;
921
+ if (rl) {
922
+ if (typeof rl.cost === "number") gqlCost += rl.cost;
923
+ if (typeof rl.remaining === "number") gqlRemaining = rl.remaining;
924
+ }
925
+ const remainingHdr = res.headers?.get("x-ratelimit-remaining") ?? null;
926
+ const retryAfter = res.headers?.get("retry-after") ?? null;
927
+ const headerRate = retryAfter != null || remainingHdr === "0";
928
+ const errs = Array.isArray(b.errors) ? b.errors : [];
929
+ const bodyRate = errs.some((e) => e?.type === "RATE_LIMITED" || /rate limit/i.test(String(e?.message ?? ""))) || typeof rl?.remaining === "number" && rl.remaining <= 0;
930
+ if (headerRate || bodyRate) await recordSecondaryStrike(retryAfter);
931
+ }
932
+ async function preflight() {
933
+ if (aborted) return false;
934
+ if (cfg.now() - startedAt >= cfg.budgetMs) {
935
+ aborted = true;
936
+ budgetAborted = true;
937
+ return false;
938
+ }
939
+ if (cfg.paceEnabled && cfg.gapMs > 0) {
940
+ const wait = cfg.gapMs - (cfg.now() - lastRequestAt);
941
+ if (wait > 0) {
942
+ await cfg.sleep(wait);
943
+ pacedMs += wait;
944
+ if (cfg.now() - startedAt >= cfg.budgetMs) {
945
+ aborted = true;
946
+ budgetAborted = true;
947
+ return false;
948
+ }
949
+ }
950
+ lastRequestAt = cfg.now();
951
+ }
952
+ return true;
953
+ }
954
+ async function get(url, init) {
955
+ if (!await preflight()) return null;
956
+ try {
957
+ const res = await fetchImpl(url, init);
958
+ await noteAndMaybeBackOff(res);
959
+ return res;
960
+ } catch {
961
+ return null;
962
+ }
963
+ }
964
+ async function graphql(url, init) {
965
+ if (!await preflight()) return null;
966
+ let res;
967
+ try {
968
+ res = await fetchImpl(url, init);
969
+ } catch {
970
+ return null;
971
+ }
972
+ let body;
973
+ try {
974
+ body = await res.json();
975
+ } catch {
976
+ return null;
977
+ }
978
+ await noteGraphQLAndMaybeBackOff(res, body);
979
+ if (!res.ok) return null;
980
+ return body;
981
+ }
982
+ async function probe(url, init) {
983
+ const bound = cfg.probeTimeoutMs;
984
+ let timer;
985
+ const fetchP = fetchImpl(url, init).then(
986
+ (r) => r,
987
+ () => null
988
+ );
989
+ try {
990
+ const res = bound == null ? await fetchP : await Promise.race([
991
+ fetchP,
992
+ new Promise((resolve) => {
993
+ timer = setTimeout(() => resolve(null), bound);
994
+ })
995
+ ]);
996
+ if (!res || !res.ok) return null;
997
+ return await res.json();
998
+ } catch {
999
+ return null;
1000
+ } finally {
1001
+ if (timer) clearTimeout(timer);
1002
+ }
1003
+ }
1004
+ function setSecondaryHint(coreHealthy) {
1005
+ coreHealthyAtStart = coreHealthy;
1006
+ }
1007
+ function getStats() {
1008
+ return {
1009
+ pacedMs,
1010
+ secondaryAborted: secondaryAborted ? 1 : 0,
1011
+ budgetAborted: budgetAborted ? 1 : 0,
1012
+ elapsedMs: cfg.now() - startedAt,
1013
+ gqlCost,
1014
+ gqlRemaining
1015
+ };
1016
+ }
1017
+ function tripped() {
1018
+ return secondaryAborted;
1019
+ }
1020
+ function budgetExhausted() {
1021
+ return budgetAborted || cfg.now() - startedAt >= cfg.budgetMs;
1022
+ }
1023
+ return { get, graphql, probe, setSecondaryHint, getStats, tripped, budgetExhausted };
1024
+ }
1025
+ 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;
1026
+ var init_gh_governor = __esm({
1027
+ "../../packages/core/src/gh-governor.ts"() {
1028
+ "use strict";
1029
+ DEFAULT_REQ_GAP_MS = 75;
1030
+ SECONDARY_BACKOFF_CAP_S = 30;
1031
+ DEFAULT_BUILD_BUDGET_MS = 9e4;
1032
+ MIN_BUILD_BUDGET_MS = 1e4;
1033
+ MAX_BUILD_BUDGET_MS = 9e4;
1034
+ PROBE_TIMEOUT_MS = 3e3;
1035
+ realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
1036
+ }
1037
+ });
1038
+
858
1039
  // ../../packages/core/src/github.ts
859
1040
  function ghHeaders(token) {
860
1041
  const headers = {
@@ -1335,11 +1516,9 @@ async function fetchRepoReceptivity(fullName, opts = {}) {
1335
1516
  const doFetch = opts.fetchImpl ?? fetch;
1336
1517
  const now = opts.now ?? Date.now();
1337
1518
  try {
1338
- const res = await doFetch(
1339
- `https://api.github.com/repos/${fullName}/pulls?state=closed&per_page=50&sort=updated&direction=desc`,
1340
- { headers: ghHeaders(opts.token) }
1341
- );
1342
- if (!res.ok) return null;
1519
+ const url = `https://api.github.com/repos/${fullName}/pulls?state=closed&per_page=50&sort=updated&direction=desc`;
1520
+ const res = opts.governor ? await opts.governor.get(url, { headers: ghHeaders(opts.token) }) : await doFetch(url, { headers: ghHeaders(opts.token) });
1521
+ if (!res || !res.ok) return null;
1343
1522
  const prs = await res.json();
1344
1523
  if (!Array.isArray(prs)) return null;
1345
1524
  const external = prs.filter(isExternalAuthor);
@@ -1424,25 +1603,34 @@ function parseGitHubRef(url) {
1424
1603
  if (!m) return null;
1425
1604
  return { owner: m[1], repo: m[2], number: parseInt(m[4], 10), kind: m[3] === "pull" ? "pull" : "issue" };
1426
1605
  }
1427
- async function ghGraphQL(query, variables, token, signal) {
1428
- const res = await fetch("https://api.github.com/graphql", {
1606
+ async function ghGraphQL(query, variables, token, signal, governor) {
1607
+ const init = {
1429
1608
  method: "POST",
1430
1609
  headers: { ...ghHeaders(token), "Content-Type": "application/json" },
1431
1610
  body: JSON.stringify({ query, variables }),
1432
1611
  signal
1433
- });
1612
+ };
1613
+ if (governor) {
1614
+ const json2 = await governor.graphql(GITHUB_GRAPHQL_URL, init);
1615
+ if (json2 === null) return null;
1616
+ if (json2.errors?.length) throw new Error("GitHub GraphQL errors: " + JSON.stringify(json2.errors));
1617
+ return json2;
1618
+ }
1619
+ const res = await fetch(GITHUB_GRAPHQL_URL, init);
1434
1620
  if (!res.ok) throw new Error(`GitHub GraphQL: HTTP ${res.status}`);
1435
1621
  const json = await res.json();
1436
1622
  if (json.errors?.length) throw new Error("GitHub GraphQL errors: " + JSON.stringify(json.errors));
1437
1623
  return json;
1438
1624
  }
1439
- async function resolveClosingIssues(owner, name, number, body, token, signal) {
1625
+ async function resolveClosingIssues(owner, name, number, body, token, signal, governor) {
1440
1626
  if (token) {
1441
1627
  try {
1442
- const q = `query($o:String!,$n:String!,$p:Int!){repository(owner:$o,name:$n){pullRequest(number:$p){closingIssuesReferences(first:20){nodes{number}}}}}`;
1443
- const r = await ghGraphQL(q, { o: owner, n: name, p: number }, token, signal);
1444
- const nodes = r.data?.repository?.pullRequest?.closingIssuesReferences?.nodes ?? [];
1445
- return { closesIssues: nodes.map((x) => x.number), linkageSource: "graphql" };
1628
+ 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}}`;
1629
+ const r = await ghGraphQL(q, { o: owner, n: name, p: number }, token, signal, governor);
1630
+ if (r) {
1631
+ const nodes = r.data?.repository?.pullRequest?.closingIssuesReferences?.nodes ?? [];
1632
+ return { closesIssues: nodes.map((x) => x.number), linkageSource: "graphql" };
1633
+ }
1446
1634
  } catch {
1447
1635
  }
1448
1636
  }
@@ -1452,11 +1640,19 @@ async function resolveClosingIssues(owner, name, number, body, token, signal) {
1452
1640
  while ((m = re.exec(body)) !== null) nums.add(parseInt(m[1], 10));
1453
1641
  return { closesIssues: [...nums], linkageSource: nums.size ? "body-keyword" : "none" };
1454
1642
  }
1455
- async function fetchPRScoringFacts(prUrl, token, signal) {
1643
+ function makeScoringGovernor(governor) {
1644
+ return governor ?? makeGitHubGovernor(
1645
+ ((url, init) => fetch(url, init)),
1646
+ makeDefaultGovernorConfig({ paceEnabled: false })
1647
+ );
1648
+ }
1649
+ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
1456
1650
  const ref = parseGitHubRef(prUrl);
1457
1651
  if (!ref || ref.kind !== "pull") return null;
1458
1652
  const { owner, repo, number } = ref;
1459
1653
  const sig = signal ?? AbortSignal.timeout(1e4);
1654
+ const gov = makeScoringGovernor(governor);
1655
+ if (gov.tripped() || gov.budgetExhausted()) return null;
1460
1656
  let pr;
1461
1657
  try {
1462
1658
  pr = await ghFetch(`/repos/${owner}/${repo}/pulls/${number}`, token, sig);
@@ -1464,22 +1660,26 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1464
1660
  return null;
1465
1661
  }
1466
1662
  let repoMeta = null;
1467
- try {
1468
- repoMeta = await ghFetch(`/repos/${owner}/${repo}`, token, sig);
1469
- } catch {
1663
+ if (!gov.tripped() && !gov.budgetExhausted()) {
1664
+ try {
1665
+ repoMeta = await ghFetch(`/repos/${owner}/${repo}`, token, sig);
1666
+ } catch {
1667
+ }
1470
1668
  }
1471
- const contributors = await repoContributorCount(owner, repo, token, sig);
1472
- const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig);
1669
+ const contributors = gov.tripped() || gov.budgetExhausted() ? null : await repoContributorCount(owner, repo, token, sig);
1670
+ const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig, gov);
1473
1671
  let reviewerAssociations;
1474
- try {
1475
- const reviews = await ghFetch(
1476
- `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1477
- token,
1478
- sig
1479
- );
1480
- reviewerAssociations = reviews.map((r) => r.author_association);
1481
- } catch {
1482
- reviewerAssociations = void 0;
1672
+ if (!gov.tripped() && !gov.budgetExhausted()) {
1673
+ try {
1674
+ const reviews = await ghFetch(
1675
+ `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1676
+ token,
1677
+ sig
1678
+ );
1679
+ reviewerAssociations = reviews.map((r) => r.author_association);
1680
+ } catch {
1681
+ reviewerAssociations = void 0;
1682
+ }
1483
1683
  }
1484
1684
  return {
1485
1685
  repo: `${owner}/${repo}`,
@@ -1506,7 +1706,7 @@ async function fetchPRScoringFacts(prUrl, token, signal) {
1506
1706
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1507
1707
  };
1508
1708
  }
1509
- 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;
1709
+ 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;
1510
1710
  var init_github = __esm({
1511
1711
  "../../packages/core/src/github.ts"() {
1512
1712
  "use strict";
@@ -1514,6 +1714,7 @@ var init_github = __esm({
1514
1714
  init_contribution_gate();
1515
1715
  init_contribution_gate();
1516
1716
  init_rigor();
1717
+ init_gh_governor();
1517
1718
  TRACTION_TOP_N = 6;
1518
1719
  MAINTAINER_ENRICH_MAX = 25;
1519
1720
  CANDIDATE_PR_PAGE = 50;
@@ -1524,6 +1725,7 @@ var init_github = __esm({
1524
1725
  RESUME_MIN_SCORE = 0.05;
1525
1726
  RECEPTIVITY_RECENCY_DAYS = 180;
1526
1727
  RECEPTIVITY_RECENCY_FLOOR = 0.1;
1728
+ GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
1527
1729
  }
1528
1730
  });
1529
1731
 
@@ -1604,34 +1806,45 @@ function mergeSoftCoverage(covMap, softTags, cap) {
1604
1806
  }
1605
1807
  return covMap;
1606
1808
  }
1809
+ function tagKernel(job, ctx) {
1810
+ const { expanded, covMap, maxDevScore, skillTags } = ctx;
1811
+ const details = [];
1812
+ let jobMatchScore = 0;
1813
+ let jobMaxScore = 0;
1814
+ const devCovByTag = /* @__PURE__ */ new Map();
1815
+ for (const tag of job.tags) {
1816
+ const w = backgroundIdf(tag);
1817
+ jobMaxScore += w;
1818
+ const covHit = covMap.get(tag);
1819
+ if (covHit) jobMatchScore += w * Math.pow(covHit.weight, SHARPEN);
1820
+ const fpHit = expanded.get(tag);
1821
+ if (fpHit) {
1822
+ const credit = Math.pow(fpHit.weight, SHARPEN);
1823
+ details.push({ tag, weight: fpHit.weight, via: fpHit.via });
1824
+ if (credit > (devCovByTag.get(fpHit.via) ?? 0)) devCovByTag.set(fpHit.via, credit);
1825
+ }
1826
+ }
1827
+ let devScore = 0;
1828
+ for (const t of skillTags) devScore += backgroundIdf(t) * (devCovByTag.get(t) ?? 0);
1829
+ const devCov = maxDevScore > 0 ? Math.min(1, devScore / maxDevScore) : 0;
1830
+ const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
1831
+ return { tagComponent: harmonicMean(devCov, jobCov), details };
1832
+ }
1833
+ function relevanceScore(fp, job, softTags = []) {
1834
+ const expanded = expandWeighted(fp.skillTags);
1835
+ const covMap = softTags.length > 0 ? mergeSoftCoverage(new Map(expanded), softTags, INTEREST_CAP) : expanded;
1836
+ const maxDevScore = fp.skillTags.reduce((acc, t) => acc + backgroundIdf(t), 0);
1837
+ return tagKernel(job, { expanded, covMap, maxDevScore, skillTags: fp.skillTags }).tagComponent;
1838
+ }
1607
1839
  function match(fp, jobs, limit = 5, now = Date.now(), opts = {}) {
1608
1840
  const idfOf = backgroundIdf;
1609
1841
  const expanded = expandWeighted(fp.skillTags);
1610
1842
  const covMap = opts.softTags && opts.softTags.length > 0 ? mergeSoftCoverage(new Map(expanded), opts.softTags, INTEREST_CAP) : expanded;
1611
1843
  const maxDevScore = fp.skillTags.reduce((acc, t) => acc + idfOf(t), 0);
1844
+ const ctx = { expanded, covMap, maxDevScore, skillTags: fp.skillTags };
1612
1845
  const candidates = jobs.filter((j) => passesFilters(fp, j));
1613
1846
  const scored = candidates.map((job) => {
1614
- const details = [];
1615
- let jobMatchScore = 0;
1616
- let jobMaxScore = 0;
1617
- const devCovByTag = /* @__PURE__ */ new Map();
1618
- for (const tag of job.tags) {
1619
- const w = idfOf(tag);
1620
- jobMaxScore += w;
1621
- const covHit = covMap.get(tag);
1622
- if (covHit) jobMatchScore += w * Math.pow(covHit.weight, SHARPEN);
1623
- const fpHit = expanded.get(tag);
1624
- if (fpHit) {
1625
- const credit = Math.pow(fpHit.weight, SHARPEN);
1626
- details.push({ tag, weight: fpHit.weight, via: fpHit.via });
1627
- if (credit > (devCovByTag.get(fpHit.via) ?? 0)) devCovByTag.set(fpHit.via, credit);
1628
- }
1629
- }
1630
- let devScore = 0;
1631
- for (const t of fp.skillTags) devScore += idfOf(t) * (devCovByTag.get(t) ?? 0);
1632
- const devCov = maxDevScore > 0 ? Math.min(1, devScore / maxDevScore) : 0;
1633
- const jobCov = jobMaxScore > 0 ? Math.min(1, jobMatchScore / jobMaxScore) : 0;
1634
- const tagComponent = harmonicMean(devCov, jobCov);
1847
+ const { tagComponent, details } = tagKernel(job, ctx);
1635
1848
  if (tagComponent === 0) return null;
1636
1849
  const coreTags = job.coreTags ?? coreTagsFromTitle(job.title);
1637
1850
  let coreComponent = tagComponent;
@@ -3166,6 +3379,7 @@ function hasContentSignal(title, body, labels) {
3166
3379
  ${body}`;
3167
3380
  if (CONTENT_ADD_RE.test(text)) return true;
3168
3381
  if (ADD_TO_CORPUS_RE.test(text)) return true;
3382
+ if (NUMBERED_SEED_RE.test(title)) return true;
3169
3383
  if (TRANSLATE_RE.test(text)) return true;
3170
3384
  if (TYPO_RE.test(text)) return true;
3171
3385
  return false;
@@ -3181,7 +3395,7 @@ function classifyContributionKind(input) {
3181
3395
  function looksLikeContentTask(input) {
3182
3396
  return classifyContributionKind(input) === "content";
3183
3397
  }
3184
- 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;
3398
+ 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;
3185
3399
  var init_contribution_classify = __esm({
3186
3400
  "../../packages/core/src/feeds/contribution-classify.ts"() {
3187
3401
  "use strict";
@@ -3191,7 +3405,16 @@ var init_contribution_classify = __esm({
3191
3405
  CODE_EXCEPTION_RE = /exception|stacktrace|segfault|traceback/i;
3192
3406
  CODE_FENCE_RE = /```|(?:^|\n)\s{4,}\S/;
3193
3407
  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;
3194
- 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;
3408
+ CONTENT_NOUN_STRONG = String.raw`proverbs?|words?|phrases?|sayings?|quotes?|quotations?|translations?|entry|entries|definitions?|terms?|idioms?|synonyms?|antonyms?|acronyms?|abbreviations?`;
3409
+ CONTENT_NOUN_BROAD = String.raw`trivia\s+questions?|grammar\s+points?|trivia|facts?|quiz(?:zes)?|flash\s?cards?|vocab(?:ulary)?|lessons?|kanji`;
3410
+ CONTENT_ADD_RE = new RegExp(
3411
+ String.raw`\badd(?:ing|s)?\s+(?:\w+\s+){0,4}?(?:${CONTENT_NOUN_STRONG})\b`,
3412
+ "i"
3413
+ );
3414
+ NUMBERED_SEED_RE = new RegExp(
3415
+ String.raw`\badd(?:ing|s)?\s+(?:\w+\s+){0,4}?(?:${CONTENT_NOUN_STRONG}|${CONTENT_NOUN_BROAD})\s*#?\s*\d{1,3}\s*$`,
3416
+ "i"
3417
+ );
3195
3418
  ADD_TO_CORPUS_RE = /\badd\b[\s\S]*?\bto\s+(?:the\s+)?(?:word\s?list|dictionary|glossary|phrasebook)\b/i;
3196
3419
  TRANSLATE_RE = /\b(?:translate|translating|translation|localize|localise|localization|localisation)\b/i;
3197
3420
  TYPO_RE = /\bfix(?:ing)?\s+(?:a\s+|the\s+|some\s+)?typos?\b/i;
@@ -3199,20 +3422,6 @@ var init_contribution_classify = __esm({
3199
3422
  });
3200
3423
 
3201
3424
  // ../../packages/core/src/feeds/contributions.ts
3202
- function readReqGapMs() {
3203
- const raw = process.env["CONTRIB_REQ_GAP_MS"];
3204
- if (raw == null) return DEFAULT_REQ_GAP_MS;
3205
- const n = Number.parseInt(raw, 10);
3206
- if (Number.isNaN(n)) return DEFAULT_REQ_GAP_MS;
3207
- return Math.min(Math.max(n, 0), 1e3);
3208
- }
3209
- function readBuildBudgetMs() {
3210
- const raw = process.env["CONTRIB_BUILD_BUDGET_MS"];
3211
- if (raw == null) return DEFAULT_BUILD_BUDGET_MS;
3212
- const n = Number.parseInt(raw, 10);
3213
- if (Number.isNaN(n)) return DEFAULT_BUILD_BUDGET_MS;
3214
- return Math.min(Math.max(n, MIN_BUILD_BUDGET_MS), MAX_BUILD_BUDGET_MS);
3215
- }
3216
3425
  function authHeaders2() {
3217
3426
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
3218
3427
  const h = {
@@ -3234,57 +3443,9 @@ function repoFullNameFromApiUrl2(url) {
3234
3443
  return m ? `${m[1]}/${m[2]}` : null;
3235
3444
  }
3236
3445
  function makeClient(fetchImpl, cfg) {
3237
- const startedAt = cfg.now();
3238
- let lastRequestAt = 0;
3239
- let pacedMs = 0;
3240
- let secondaryHits = 0;
3241
- let aborted = false;
3242
- let secondaryAborted = false;
3243
- let budgetAborted = false;
3244
- let coreHealthyAtStart = false;
3245
- async function noteAndMaybeBackOff(res) {
3246
- if (res.status !== 403) return;
3247
- const remaining = res.headers.get("x-ratelimit-remaining");
3248
- const retryAfter = res.headers.get("retry-after");
3249
- const positiveSecondary = retryAfter != null || remaining != null && remaining !== "0";
3250
- const isSecondary = positiveSecondary || coreHealthyAtStart;
3251
- if (!isSecondary) return;
3252
- secondaryHits++;
3253
- if (secondaryHits >= 2) {
3254
- aborted = true;
3255
- secondaryAborted = true;
3256
- return;
3257
- }
3258
- if (cfg.paceEnabled) {
3259
- const trimmed = (retryAfter ?? "").trim();
3260
- const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
3261
- const sec = Number.isNaN(parsed) ? SECONDARY_BACKOFF_CAP_S : Math.min(Math.max(parsed, 0), SECONDARY_BACKOFF_CAP_S);
3262
- const remaining2 = Math.max(0, cfg.budgetMs - (cfg.now() - startedAt));
3263
- await cfg.sleep(Math.min(sec * 1e3, remaining2));
3264
- }
3265
- }
3446
+ const gov = makeGitHubGovernor(fetchImpl, cfg);
3266
3447
  async function raw(path) {
3267
- if (aborted) return null;
3268
- if (cfg.now() - startedAt > cfg.budgetMs) {
3269
- aborted = true;
3270
- budgetAborted = true;
3271
- return null;
3272
- }
3273
- if (cfg.paceEnabled && cfg.gapMs > 0) {
3274
- const wait = cfg.gapMs - (cfg.now() - lastRequestAt);
3275
- if (wait > 0) {
3276
- await cfg.sleep(wait);
3277
- pacedMs += wait;
3278
- }
3279
- lastRequestAt = cfg.now();
3280
- }
3281
- try {
3282
- const res = await fetchImpl(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3283
- await noteAndMaybeBackOff(res);
3284
- return res;
3285
- } catch {
3286
- return null;
3287
- }
3448
+ return gov.get(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3288
3449
  }
3289
3450
  async function json(path) {
3290
3451
  const res = await raw(path);
@@ -3298,41 +3459,9 @@ function makeClient(fetchImpl, cfg) {
3298
3459
  }
3299
3460
  }
3300
3461
  async function probe(path) {
3301
- const bound = cfg.probeTimeoutMs;
3302
- let timer;
3303
- const fetchP = fetchImpl(`${GITHUB_API2}${path}`, {
3304
- headers: authHeaders2()
3305
- }).then(
3306
- (r) => r,
3307
- () => null
3308
- );
3309
- try {
3310
- const res = bound == null ? await fetchP : await Promise.race([
3311
- fetchP,
3312
- new Promise((resolve) => {
3313
- timer = setTimeout(() => resolve(null), bound);
3314
- })
3315
- ]);
3316
- if (!res || !res.ok) return null;
3317
- return await res.json();
3318
- } catch {
3319
- return null;
3320
- } finally {
3321
- if (timer) clearTimeout(timer);
3322
- }
3323
- }
3324
- function setSecondaryHint(coreHealthy) {
3325
- coreHealthyAtStart = coreHealthy;
3462
+ return gov.probe(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3326
3463
  }
3327
- function getStats() {
3328
- return {
3329
- pacedMs,
3330
- secondaryAborted: secondaryAborted ? 1 : 0,
3331
- budgetAborted: budgetAborted ? 1 : 0,
3332
- elapsedMs: cfg.now() - startedAt
3333
- };
3334
- }
3335
- return { raw, json, probe, setSecondaryHint, getStats };
3464
+ return { raw, json, probe, setSecondaryHint: gov.setSecondaryHint, getStats: gov.getStats };
3336
3465
  }
3337
3466
  async function contributorCount(client, fullName) {
3338
3467
  const res = await client.raw(`/repos/${fullName}/contributors?per_page=1&anon=false`);
@@ -3431,10 +3560,63 @@ async function aggregateContributions(opts = {}) {
3431
3560
  const repoCache = /* @__PURE__ */ new Map();
3432
3561
  const contribCache = /* @__PURE__ */ new Map();
3433
3562
  const prRefsCache = /* @__PURE__ */ new Map();
3563
+ const xbuild = opts.repoMetaCache;
3564
+ const servedFromXbuild = /* @__PURE__ */ new Set();
3565
+ const persistedXbuild = /* @__PURE__ */ new Set();
3566
+ const xbuildTried = /* @__PURE__ */ new Set();
3567
+ async function primeFromXbuild(key, fullName) {
3568
+ if (!xbuild || xbuildTried.has(key)) return;
3569
+ xbuildTried.add(key);
3570
+ if (repoCache.has(key) && contribCache.has(key)) return;
3571
+ let cached = null;
3572
+ try {
3573
+ cached = await xbuild.get(key);
3574
+ } catch {
3575
+ return;
3576
+ }
3577
+ if (!cached) return;
3578
+ if (!repoCache.has(key)) {
3579
+ const owner = fullName.split("/")[0] ?? "";
3580
+ repoCache.set(key, {
3581
+ full_name: fullName,
3582
+ stargazers_count: cached.stars,
3583
+ archived: cached.archived,
3584
+ disabled: cached.disabled,
3585
+ fork: cached.fork,
3586
+ language: cached.language,
3587
+ owner: { login: owner }
3588
+ });
3589
+ }
3590
+ if (!contribCache.has(key)) {
3591
+ contribCache.set(key, cached.contributors);
3592
+ servedFromXbuild.add(key);
3593
+ }
3594
+ }
3595
+ async function persistRepoMeta(fullName, repo, contributors) {
3596
+ if (!xbuild) return;
3597
+ const key = repoKey(fullName);
3598
+ if (servedFromXbuild.has(key) || persistedXbuild.has(key)) return;
3599
+ if (contributors === void 0) return;
3600
+ persistedXbuild.add(key);
3601
+ try {
3602
+ await xbuild.set(key, {
3603
+ stars: repo.stargazers_count,
3604
+ contributors,
3605
+ language: repo.language,
3606
+ archived: repo.archived,
3607
+ fork: repo.fork,
3608
+ disabled: repo.disabled
3609
+ });
3610
+ } catch {
3611
+ }
3612
+ }
3434
3613
  async function repoMeta(fullName) {
3435
3614
  const key = repoKey(fullName);
3436
3615
  const hit = repoCache.get(key);
3437
3616
  if (hit !== void 0) return hit;
3617
+ await primeFromXbuild(key, fullName);
3618
+ const primed = repoCache.get(key);
3619
+ if (primed !== void 0) return primed;
3438
3620
  const r = await client.json(`/repos/${fullName}`) ?? null;
3439
3621
  repoCache.set(key, r);
3440
3622
  return r;
@@ -3442,6 +3624,8 @@ async function aggregateContributions(opts = {}) {
3442
3624
  async function repoContribCount(fullName) {
3443
3625
  const key = repoKey(fullName);
3444
3626
  if (contribCache.has(key)) return contribCache.get(key);
3627
+ await primeFromXbuild(key, fullName);
3628
+ if (contribCache.has(key)) return contribCache.get(key);
3445
3629
  const n = await contributorCount(client, fullName);
3446
3630
  contribCache.set(key, n);
3447
3631
  return n;
@@ -3468,14 +3652,18 @@ async function aggregateContributions(opts = {}) {
3468
3652
  if (isDenylistedRepo(fullName)) continue;
3469
3653
  if (isAssigned(issue)) continue;
3470
3654
  if ((perRepo.get(repoKey(fullName)) ?? 0) >= MAX_BOUNTIES_PER_DISCOVERED_REPO) continue;
3655
+ const title = decodeEntities(issue.title).trim();
3656
+ const body = issue.body ? decodeEntities(issue.body) : "";
3657
+ const labels = labelNames2(issue.labels);
3658
+ if (looksLikeContentTask({ title, body, labels })) continue;
3471
3659
  const repo = await repoMeta(fullName);
3472
3660
  if (!repo) {
3473
3661
  metaNull++;
3474
3662
  continue;
3475
3663
  }
3476
- const title = decodeEntities(issue.title).trim();
3477
3664
  const contributors = await repoContribCount(fullName);
3478
3665
  if (contributors === void 0) contribUndefined++;
3666
+ await persistRepoMeta(fullName, repo, contributors);
3479
3667
  if (!passesContributionGate({
3480
3668
  fullName,
3481
3669
  stars: repo.stargazers_count,
@@ -3487,13 +3675,9 @@ async function aggregateContributions(opts = {}) {
3487
3675
  continue;
3488
3676
  }
3489
3677
  if (repo.disabled) continue;
3490
- const body = issue.body ? decodeEntities(issue.body) : "";
3491
- const labels = labelNames2(issue.labels);
3492
- if (looksLikeContentTask({ title, body, labels })) continue;
3493
3678
  const prRefs = await repoPRRefs(fullName);
3494
3679
  if (prRefs === null) prRefsNull++;
3495
- if (prRefs && prRefs.has(issue.number)) continue;
3496
- const openPRsAtDiscovery = prRefs ? 0 : void 0;
3680
+ const openPRsAtDiscovery = prRefs ? prRefs.has(issue.number) ? 1 : 0 : void 0;
3497
3681
  const tags = normalize(
3498
3682
  tokenize4([title, repo.language ?? "", labels.join(" "), body.slice(0, 2e3)].join(" "))
3499
3683
  );
@@ -3584,6 +3768,7 @@ async function aggregateContributions(opts = {}) {
3584
3768
  if (repo.disabled) continue;
3585
3769
  const contributors = await repoContribCount(fullName);
3586
3770
  if (contributors === void 0) contribUndefined++;
3771
+ await persistRepoMeta(fullName, repo, contributors);
3587
3772
  if (repo.archived || repo.fork || repo.stargazers_count < MIN_STARS || contributors === void 0 || contributors < MIN_CONTRIBUTORS) {
3588
3773
  continue;
3589
3774
  }
@@ -3616,8 +3801,7 @@ async function aggregateContributions(opts = {}) {
3616
3801
  if (looksLikeContentTask({ title, body, labels })) continue;
3617
3802
  const prRefs = await repoPRRefs(fullName);
3618
3803
  if (prRefs === null) prRefsNull++;
3619
- if (prRefs && prRefs.has(issue.number)) continue;
3620
- const openPRsAtDiscovery = prRefs ? 0 : void 0;
3804
+ const openPRsAtDiscovery = prRefs ? prRefs.has(issue.number) ? 1 : 0 : void 0;
3621
3805
  const tags = normalize(
3622
3806
  tokenize4([title, repo.language ?? "", labels.join(" "), body.slice(0, 2e3)].join(" "))
3623
3807
  );
@@ -3648,9 +3832,9 @@ async function aggregateContributions(opts = {}) {
3648
3832
  const core = rl?.core ? `${rl.core.remaining}/${rl.core.limit}` : "n/a";
3649
3833
  const search = rl?.search ? `${rl.search.remaining}/${rl.search.limit}` : "n/a";
3650
3834
  const noToken = !(process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"]);
3651
- const { pacedMs, secondaryAborted, budgetAborted, elapsedMs } = client.getStats();
3835
+ const { pacedMs, secondaryAborted, budgetAborted, elapsedMs, gqlCost, gqlRemaining } = client.getStats();
3652
3836
  console.info(
3653
- `[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)" : "")
3837
+ `[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)" : "")
3654
3838
  );
3655
3839
  if (discoveryBudgetStopped) {
3656
3840
  console.warn(
@@ -3660,7 +3844,7 @@ async function aggregateContributions(opts = {}) {
3660
3844
  }
3661
3845
  return jobs;
3662
3846
  }
3663
- 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;
3847
+ 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;
3664
3848
  var init_contributions = __esm({
3665
3849
  "../../packages/core/src/feeds/contributions.ts"() {
3666
3850
  "use strict";
@@ -3671,14 +3855,8 @@ var init_contributions = __esm({
3671
3855
  init_contribution_classify();
3672
3856
  init_github_bounties();
3673
3857
  init_http();
3858
+ init_gh_governor();
3674
3859
  GITHUB_API2 = "https://api.github.com";
3675
- DEFAULT_REQ_GAP_MS = 75;
3676
- SECONDARY_BACKOFF_CAP_S = 30;
3677
- DEFAULT_BUILD_BUDGET_MS = 9e4;
3678
- MIN_BUILD_BUDGET_MS = 1e4;
3679
- MAX_BUILD_BUDGET_MS = 9e4;
3680
- PROBE_TIMEOUT_MS = 3e3;
3681
- realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
3682
3860
  CONTRIB_LABEL_QUERIES = [
3683
3861
  'label:"good first issue" type:issue state:open',
3684
3862
  'label:"good-first-issue" type:issue state:open',
@@ -3896,12 +4074,22 @@ async function enrichWinnability(jobs, contribute, w) {
3896
4074
  const repos = [...byRepo.keys()].sort((a, b) => starsOf(b) - starsOf(a));
3897
4075
  const scored = repos.slice(0, maxRepos);
3898
4076
  const cappedOut = repos.length - scored.length;
4077
+ const gov = w.governor ?? makeGitHubGovernor(
4078
+ w.fetchImpl ?? fetch,
4079
+ makeDefaultGovernorConfig({ paceEnabled: !w.fetchImpl })
4080
+ );
4081
+ let stoppedEarly = false;
3899
4082
  for (const repo of scored) {
4083
+ if (gov.tripped() || gov.budgetExhausted()) {
4084
+ stoppedEarly = true;
4085
+ break;
4086
+ }
3900
4087
  const targets = byRepo.get(repo) ?? [];
3901
4088
  const receptivity = await fetchRepoReceptivity(repo, {
3902
4089
  token: w.token,
3903
4090
  fetchImpl: w.fetchImpl,
3904
- now
4091
+ now,
4092
+ governor: gov
3905
4093
  });
3906
4094
  const stars = starsOf(repo);
3907
4095
  const prev = priorStars[repo];
@@ -3930,6 +4118,12 @@ async function enrichWinnability(jobs, contribute, w) {
3930
4118
  }
3931
4119
  }
3932
4120
  }
4121
+ if (stoppedEarly) {
4122
+ const reason = gov.tripped() ? "secondary-abuse breaker" : "wall-clock budget";
4123
+ console.warn(
4124
+ `[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.`
4125
+ );
4126
+ }
3933
4127
  if (cappedOut > 0) {
3934
4128
  console.warn(
3935
4129
  `[winnability] receptivity cap hit \u2014 scored ${scored.length}/${repos.length} repos (${cappedOut} beyond maxRepos=${maxRepos} left un-scored, legacy ranking)`
@@ -3972,6 +4166,7 @@ var init_indexer = __esm({
3972
4166
  init_contributions();
3973
4167
  init_partners();
3974
4168
  init_github();
4169
+ init_gh_governor();
3975
4170
  init_winnability();
3976
4171
  }
3977
4172
  });
@@ -7567,6 +7762,7 @@ __export(src_exports, {
7567
7762
  MERGE_PROBABILITY: () => MERGE_PROBABILITY,
7568
7763
  MIN_CONTRIBUTORS: () => MIN_CONTRIBUTORS,
7569
7764
  MIN_STARS: () => MIN_STARS,
7765
+ PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
7570
7766
  RIGOR: () => RIGOR,
7571
7767
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
7572
7768
  SYNONYMS: () => SYNONYMS,
@@ -7638,6 +7834,9 @@ __export(src_exports, {
7638
7834
  lever: () => lever,
7639
7835
  loadPartnerRoles: () => loadPartnerRoles,
7640
7836
  looksLikeEngRole: () => looksLikeEngRole,
7837
+ makeDefaultGovernorConfig: () => makeDefaultGovernorConfig,
7838
+ makeGitHubGovernor: () => makeGitHubGovernor,
7839
+ makeScoringGovernor: () => makeScoringGovernor,
7641
7840
  match: () => match,
7642
7841
  mergeProbability: () => mergeProbability,
7643
7842
  mmrRerank: () => mmrRerank,
@@ -7650,8 +7849,12 @@ __export(src_exports, {
7650
7849
  passesMaturityGate: () => passesMaturityGate,
7651
7850
  personCardToJob: () => personCardToJob,
7652
7851
  projectCardToJob: () => projectCardToJob,
7852
+ readBuildBudgetMs: () => readBuildBudgetMs,
7853
+ readReqGapMs: () => readReqGapMs,
7854
+ realSleep: () => realSleep,
7653
7855
  recordClick: () => recordClick,
7654
7856
  rejectExtraIntroFields: () => rejectExtraIntroFields,
7857
+ relevanceScore: () => relevanceScore,
7655
7858
  revealIntroContacts: () => revealIntroContacts,
7656
7859
  safetyNumber: () => safetyNumber,
7657
7860
  sameLogin: () => sameLogin,
@@ -7678,6 +7881,7 @@ var init_src = __esm({
7678
7881
  init_winnability();
7679
7882
  init_partners();
7680
7883
  init_github();
7884
+ init_gh_governor();
7681
7885
  init_credit();
7682
7886
  init_intro();
7683
7887
  init_directoryThreshold();
@@ -7821,9 +8025,9 @@ var init_keytar = __esm({
7821
8025
  }
7822
8026
  });
7823
8027
 
7824
- // node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a4d8b1364e2f66adc/node_modules/keytar/build/Release/keytar.node
8028
+ // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
7825
8029
  var require_keytar = __commonJS({
7826
- "node-file:/Users/ericgang/job-placement-inline/.claude/worktrees/agent-a4d8b1364e2f66adc/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8030
+ "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
7827
8031
  "use strict";
7828
8032
  init_keytar();
7829
8033
  try {
@@ -8589,6 +8793,12 @@ function findClaimableInCache(id) {
8589
8793
  function looksLikeShortRef(arg) {
8590
8794
  return typeof arg === "string" && /^[A-Za-z0-9_-]{8}$/.test(arg);
8591
8795
  }
8796
+ function isVerblessShortRefClaim(verb, positional) {
8797
+ return looksLikeShortRef(verb) && positional.length === 0;
8798
+ }
8799
+ function isStrayArgShortRefClaim(verb, positional) {
8800
+ return looksLikeShortRef(verb) && positional.length > 0;
8801
+ }
8592
8802
  function findClaimableByShortRef(ref) {
8593
8803
  try {
8594
8804
  return findByShortRefInPool(readClaimablePool(), ref);
@@ -8628,9 +8838,12 @@ function extractClaimableFields(job) {
8628
8838
  issueUrl: c.issueUrl ?? job.url ?? "",
8629
8839
  amountUSD: null,
8630
8840
  source: "contribute",
8631
- // Fix 2: the aggregator proved 0 open PRs at discovery (it only admits an
8632
- // uncontested issue). Carry it through so resolveBounty can prefer it over
8633
- // a live re-count that degrades to null under a rate limit / >100-PR page.
8841
+ // Fix 2: the aggregator persists a discovery-time open-PR presence flag
8842
+ // (0 = verified-uncontested, 1 = contested kept and flagged, not dropped,
8843
+ // undefined = the PR check couldn't run). Carry it through so resolveBounty
8844
+ // can prefer it over a live re-count that degrades to null under a rate limit
8845
+ // / >100-PR page. Display/fallback ONLY — the contention gate still forces a
8846
+ // LIVE recount (see resolveBounty); the snapshot never decides contention.
8634
8847
  openPRsAtDiscovery: c.openPRsAtDiscovery
8635
8848
  };
8636
8849
  }
@@ -9740,6 +9953,16 @@ async function run() {
9740
9953
  else await cmdPush({ keepUpdated: Boolean(flags["keep-updated"]) });
9741
9954
  return;
9742
9955
  }
9956
+ if (isVerblessShortRefClaim(verb, positional)) {
9957
+ await cmdRecord(verb, flags);
9958
+ return;
9959
+ }
9960
+ if (isStrayArgShortRefClaim(verb, positional)) {
9961
+ console.error(
9962
+ `terminalhire claim: '${verb}' is a short ref and takes no extra arguments (got: ${positional.join(" ")}). Did you mean \`terminalhire claim ${verb}\`?`
9963
+ );
9964
+ process.exit(1);
9965
+ }
9743
9966
  try {
9744
9967
  switch (verb) {
9745
9968
  case "preview":
@@ -9788,6 +10011,8 @@ export {
9788
10011
  fmtAge,
9789
10012
  fmtContestedWarning,
9790
10013
  isContested,
10014
+ isStrayArgShortRefClaim,
10015
+ isVerblessShortRefClaim,
9791
10016
  listOpenPRsReferencingIssue,
9792
10017
  matchReferencingPrs,
9793
10018
  pickBodySource,