terminalhire 0.21.0 → 0.23.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.
@@ -45,9 +45,9 @@ var init_keytar = __esm({
45
45
  }
46
46
  });
47
47
 
48
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
48
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node
49
49
  var require_keytar = __commonJS({
50
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
50
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node"(exports, module) {
51
51
  "use strict";
52
52
  init_keytar();
53
53
  try {
@@ -749,6 +749,30 @@ var init_contribution_gate = __esm({
749
749
  }
750
750
  });
751
751
 
752
+ // ../../packages/core/src/credential/rigor.ts
753
+ function deriveRigorTiers(input) {
754
+ const tiers = {};
755
+ if (input.reviewerAssociations !== void 0) {
756
+ tiers.maintainerReviewed = input.reviewerAssociations.some(
757
+ (a) => MAINTAINER_SET.has(String(a).toUpperCase())
758
+ );
759
+ }
760
+ return tiers;
761
+ }
762
+ var RIGOR, MAINTAINER_SET;
763
+ var init_rigor = __esm({
764
+ "../../packages/core/src/credential/rigor.ts"() {
765
+ "use strict";
766
+ RIGOR = {
767
+ /** `authorAssociation` values that count as a maintainer review. */
768
+ MAINTAINER_ASSOCIATIONS: ["OWNER", "MEMBER", "COLLABORATOR"]
769
+ };
770
+ MAINTAINER_SET = new Set(
771
+ RIGOR.MAINTAINER_ASSOCIATIONS.map((a) => a.toUpperCase())
772
+ );
773
+ }
774
+ });
775
+
752
776
  // ../../packages/core/src/github.ts
753
777
  function ghHeaders(token) {
754
778
  const headers = {
@@ -761,9 +785,9 @@ function ghHeaders(token) {
761
785
  if (token) headers["Authorization"] = `Bearer ${token}`;
762
786
  return headers;
763
787
  }
764
- async function ghFetch(path, token) {
788
+ async function ghFetch(path, token, signal) {
765
789
  const url = `https://api.github.com${path}`;
766
- const res = await fetch(url, { headers: ghHeaders(token) });
790
+ const res = await fetch(url, { headers: ghHeaders(token), signal });
767
791
  if (!res.ok) {
768
792
  throw new Error(`GitHub API ${path}: HTTP ${res.status} ${res.statusText}`);
769
793
  }
@@ -812,6 +836,7 @@ async function fetchGitHubProfile(login, token) {
812
836
  } catch {
813
837
  }
814
838
  return {
839
+ id: user.id,
815
840
  login: user.login,
816
841
  name: user.name ?? void 0,
817
842
  publicEmail: user.email ?? void 0,
@@ -888,8 +913,8 @@ async function fetchOwnedRepoTraction(login, token) {
888
913
  computedAt
889
914
  };
890
915
  }
891
- async function ghFetchRaw(path, token) {
892
- return fetch(`https://api.github.com${path}`, { headers: ghHeaders(token) });
916
+ async function ghFetchRaw(path, token, signal) {
917
+ return fetch(`https://api.github.com${path}`, { headers: ghHeaders(token), signal });
893
918
  }
894
919
  function parseRepoUrl(repoUrl) {
895
920
  const m = repoUrl.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
@@ -908,11 +933,12 @@ async function fetchOwnedOrgs(token) {
908
933
  return /* @__PURE__ */ new Set();
909
934
  }
910
935
  }
911
- async function repoContributorCount(owner, name, token) {
936
+ async function repoContributorCount(owner, name, token, signal) {
912
937
  try {
913
938
  const res = await ghFetchRaw(
914
939
  `/repos/${owner}/${name}/contributors?per_page=1&anon=false`,
915
- token
940
+ token,
941
+ signal
916
942
  );
917
943
  if (!res.ok) return void 0;
918
944
  const link = res.headers.get("link");
@@ -1025,6 +1051,33 @@ async function computeAcceptanceFromSearch(login, token, ownedOrgs, cache, gates
1025
1051
  if (mergedAt > b.lastMergedAt) b.lastMergedAt = mergedAt;
1026
1052
  }
1027
1053
  }
1054
+ if (token) {
1055
+ const enrichStats = { transient: 0 };
1056
+ const enrichCount = Math.min(qualifyingPRs.length, MAX_ENRICH_PRS);
1057
+ for (let i = 0; i < enrichCount; i++) {
1058
+ const pr = qualifyingPRs[i];
1059
+ const ref = parseGitHubRef(pr.url);
1060
+ if (!ref || ref.kind !== "pull") continue;
1061
+ try {
1062
+ const reviews = await ghFetch(
1063
+ `/repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/reviews?per_page=100`,
1064
+ token
1065
+ );
1066
+ const reviewerAssociations = reviews.map((r) => r.author_association);
1067
+ const tiers = deriveRigorTiers({ reviewerAssociations });
1068
+ if (tiers.maintainerReviewed !== void 0) pr.maintainerReviewed = tiers.maintainerReviewed;
1069
+ } catch (err) {
1070
+ const msg = err instanceof Error ? err.message : String(err);
1071
+ if (TRANSIENT_META_ERROR.test(msg)) {
1072
+ enrichStats.transient += 1;
1073
+ console.warn(
1074
+ `[acceptance] ${login}: per-PR rigor enrichment transient failure (${enrichStats.transient}) \u2014 leaving remaining tiers undefined rather than fabricating a false`
1075
+ );
1076
+ break;
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1028
1081
  const finalDomains = {};
1029
1082
  for (const [d, b] of Object.entries(byDomain)) {
1030
1083
  finalDomains[d] = {
@@ -1180,14 +1233,103 @@ function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
1180
1233
  }
1181
1234
  return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
1182
1235
  }
1183
- var TRACTION_TOP_N, CANDIDATE_PR_PAGE, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1236
+ function parseGitHubRef(url) {
1237
+ const m = String(url ?? "").match(/github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/);
1238
+ if (!m) return null;
1239
+ return { owner: m[1], repo: m[2], number: parseInt(m[4], 10), kind: m[3] === "pull" ? "pull" : "issue" };
1240
+ }
1241
+ async function ghGraphQL(query, variables, token, signal) {
1242
+ const res = await fetch("https://api.github.com/graphql", {
1243
+ method: "POST",
1244
+ headers: { ...ghHeaders(token), "Content-Type": "application/json" },
1245
+ body: JSON.stringify({ query, variables }),
1246
+ signal
1247
+ });
1248
+ if (!res.ok) throw new Error(`GitHub GraphQL: HTTP ${res.status}`);
1249
+ const json = await res.json();
1250
+ if (json.errors?.length) throw new Error("GitHub GraphQL errors: " + JSON.stringify(json.errors));
1251
+ return json;
1252
+ }
1253
+ async function resolveClosingIssues(owner, name, number, body, token, signal) {
1254
+ if (token) {
1255
+ try {
1256
+ const q = `query($o:String!,$n:String!,$p:Int!){repository(owner:$o,name:$n){pullRequest(number:$p){closingIssuesReferences(first:20){nodes{number}}}}}`;
1257
+ const r = await ghGraphQL(q, { o: owner, n: name, p: number }, token, signal);
1258
+ const nodes = r.data?.repository?.pullRequest?.closingIssuesReferences?.nodes ?? [];
1259
+ return { closesIssues: nodes.map((x) => x.number), linkageSource: "graphql" };
1260
+ } catch {
1261
+ }
1262
+ }
1263
+ const nums = /* @__PURE__ */ new Set();
1264
+ const re = /\b(?:clos(?:e|es|ed)|fix(?:es|ed)?|resolv(?:e|es|ed))\s+#(\d+)/gi;
1265
+ let m;
1266
+ while ((m = re.exec(body)) !== null) nums.add(parseInt(m[1], 10));
1267
+ return { closesIssues: [...nums], linkageSource: nums.size ? "body-keyword" : "none" };
1268
+ }
1269
+ async function fetchPRScoringFacts(prUrl, token, signal) {
1270
+ const ref = parseGitHubRef(prUrl);
1271
+ if (!ref || ref.kind !== "pull") return null;
1272
+ const { owner, repo, number } = ref;
1273
+ const sig = signal ?? AbortSignal.timeout(1e4);
1274
+ let pr;
1275
+ try {
1276
+ pr = await ghFetch(`/repos/${owner}/${repo}/pulls/${number}`, token, sig);
1277
+ } catch {
1278
+ return null;
1279
+ }
1280
+ let repoMeta = null;
1281
+ try {
1282
+ repoMeta = await ghFetch(`/repos/${owner}/${repo}`, token, sig);
1283
+ } catch {
1284
+ }
1285
+ const contributors = await repoContributorCount(owner, repo, token, sig);
1286
+ const { closesIssues, linkageSource } = await resolveClosingIssues(owner, repo, number, pr.body ?? "", token, sig);
1287
+ let reviewerAssociations;
1288
+ try {
1289
+ const reviews = await ghFetch(
1290
+ `/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
1291
+ token,
1292
+ sig
1293
+ );
1294
+ reviewerAssociations = reviews.map((r) => r.author_association);
1295
+ } catch {
1296
+ reviewerAssociations = void 0;
1297
+ }
1298
+ return {
1299
+ repo: `${owner}/${repo}`,
1300
+ prNumber: number,
1301
+ prUrl: pr.html_url,
1302
+ merged: pr.merged === true,
1303
+ mergedAt: pr.merged_at ?? null,
1304
+ authorId: pr.user?.id ?? null,
1305
+ authorLogin: pr.user?.login ?? null,
1306
+ mergedById: pr.merged_by?.id ?? null,
1307
+ mergedByLogin: pr.merged_by?.login ?? null,
1308
+ closesIssues,
1309
+ linkageSource,
1310
+ repoStars: repoMeta?.stargazers_count ?? null,
1311
+ repoContributors: contributors ?? null,
1312
+ repoArchived: !!repoMeta?.archived,
1313
+ repoFork: !!repoMeta?.fork,
1314
+ repoPrivate: !!repoMeta?.private,
1315
+ additions: pr.additions ?? null,
1316
+ deletions: pr.deletions ?? null,
1317
+ changedFiles: pr.changed_files ?? null,
1318
+ repoForks: repoMeta?.forks_count ?? null,
1319
+ reviewerAssociations,
1320
+ fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1321
+ };
1322
+ }
1323
+ var TRACTION_TOP_N, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
1184
1324
  var init_github = __esm({
1185
1325
  "../../packages/core/src/github.ts"() {
1186
1326
  "use strict";
1187
1327
  init_vocabulary();
1188
1328
  init_contribution_gate();
1329
+ init_rigor();
1189
1330
  TRACTION_TOP_N = 6;
1190
1331
  CANDIDATE_PR_PAGE = 50;
1332
+ MAX_ENRICH_PRS = 12;
1191
1333
  OPEN_PR_PAGE = 20;
1192
1334
  TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
1193
1335
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -2867,6 +3009,20 @@ var init_contribution_classify = __esm({
2867
3009
  });
2868
3010
 
2869
3011
  // ../../packages/core/src/feeds/contributions.ts
3012
+ function readReqGapMs() {
3013
+ const raw = process.env["CONTRIB_REQ_GAP_MS"];
3014
+ if (raw == null) return DEFAULT_REQ_GAP_MS;
3015
+ const n = Number.parseInt(raw, 10);
3016
+ if (Number.isNaN(n)) return DEFAULT_REQ_GAP_MS;
3017
+ return Math.min(Math.max(n, 0), 1e3);
3018
+ }
3019
+ function readBuildBudgetMs() {
3020
+ const raw = process.env["CONTRIB_BUILD_BUDGET_MS"];
3021
+ if (raw == null) return DEFAULT_BUILD_BUDGET_MS;
3022
+ const n = Number.parseInt(raw, 10);
3023
+ if (Number.isNaN(n)) return DEFAULT_BUILD_BUDGET_MS;
3024
+ return Math.min(Math.max(n, MIN_BUILD_BUDGET_MS), MAX_BUILD_BUDGET_MS);
3025
+ }
2870
3026
  function authHeaders2() {
2871
3027
  const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
2872
3028
  const h = {
@@ -2887,10 +3043,55 @@ function repoFullNameFromApiUrl2(url) {
2887
3043
  const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
2888
3044
  return m ? `${m[1]}/${m[2]}` : null;
2889
3045
  }
2890
- function makeClient(fetchImpl) {
3046
+ function makeClient(fetchImpl, cfg) {
3047
+ const startedAt = cfg.now();
3048
+ let lastRequestAt = 0;
3049
+ let pacedMs = 0;
3050
+ let secondaryHits = 0;
3051
+ let aborted = false;
3052
+ let secondaryAborted = false;
3053
+ let budgetAborted = false;
3054
+ let coreHealthyAtStart = false;
3055
+ async function noteAndMaybeBackOff(res) {
3056
+ if (res.status !== 403) return;
3057
+ const remaining = res.headers.get("x-ratelimit-remaining");
3058
+ const retryAfter = res.headers.get("retry-after");
3059
+ const positiveSecondary = retryAfter != null || remaining != null && remaining !== "0";
3060
+ const isSecondary = positiveSecondary || coreHealthyAtStart;
3061
+ if (!isSecondary) return;
3062
+ secondaryHits++;
3063
+ if (secondaryHits >= 2) {
3064
+ aborted = true;
3065
+ secondaryAborted = true;
3066
+ return;
3067
+ }
3068
+ if (cfg.paceEnabled) {
3069
+ const trimmed = (retryAfter ?? "").trim();
3070
+ const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
3071
+ const sec = Number.isNaN(parsed) ? SECONDARY_BACKOFF_CAP_S : Math.min(Math.max(parsed, 0), SECONDARY_BACKOFF_CAP_S);
3072
+ const remaining2 = Math.max(0, cfg.budgetMs - (cfg.now() - startedAt));
3073
+ await cfg.sleep(Math.min(sec * 1e3, remaining2));
3074
+ }
3075
+ }
2891
3076
  async function raw(path) {
3077
+ if (aborted) return null;
3078
+ if (cfg.now() - startedAt > cfg.budgetMs) {
3079
+ aborted = true;
3080
+ budgetAborted = true;
3081
+ return null;
3082
+ }
3083
+ if (cfg.paceEnabled && cfg.gapMs > 0) {
3084
+ const wait = cfg.gapMs - (cfg.now() - lastRequestAt);
3085
+ if (wait > 0) {
3086
+ await cfg.sleep(wait);
3087
+ pacedMs += wait;
3088
+ }
3089
+ lastRequestAt = cfg.now();
3090
+ }
2892
3091
  try {
2893
- return await fetchImpl(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3092
+ const res = await fetchImpl(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3093
+ await noteAndMaybeBackOff(res);
3094
+ return res;
2894
3095
  } catch {
2895
3096
  return null;
2896
3097
  }
@@ -2906,7 +3107,42 @@ function makeClient(fetchImpl) {
2906
3107
  return null;
2907
3108
  }
2908
3109
  }
2909
- return { raw, json };
3110
+ async function probe(path) {
3111
+ const bound = cfg.probeTimeoutMs;
3112
+ let timer;
3113
+ const fetchP = fetchImpl(`${GITHUB_API2}${path}`, {
3114
+ headers: authHeaders2()
3115
+ }).then(
3116
+ (r) => r,
3117
+ () => null
3118
+ );
3119
+ try {
3120
+ const res = bound == null ? await fetchP : await Promise.race([
3121
+ fetchP,
3122
+ new Promise((resolve) => {
3123
+ timer = setTimeout(() => resolve(null), bound);
3124
+ })
3125
+ ]);
3126
+ if (!res || !res.ok) return null;
3127
+ return await res.json();
3128
+ } catch {
3129
+ return null;
3130
+ } finally {
3131
+ if (timer) clearTimeout(timer);
3132
+ }
3133
+ }
3134
+ function setSecondaryHint(coreHealthy) {
3135
+ coreHealthyAtStart = coreHealthy;
3136
+ }
3137
+ function getStats() {
3138
+ return {
3139
+ pacedMs,
3140
+ secondaryAborted: secondaryAborted ? 1 : 0,
3141
+ budgetAborted: budgetAborted ? 1 : 0,
3142
+ elapsedMs: cfg.now() - startedAt
3143
+ };
3144
+ }
3145
+ return { raw, json, probe, setSecondaryHint, getStats };
2910
3146
  }
2911
3147
  async function contributorCount(client, fullName) {
2912
3148
  const res = await client.raw(`/repos/${fullName}/contributors?per_page=1&anon=false`);
@@ -2936,7 +3172,7 @@ ${pr.body ?? ""}`.matchAll(/#(\d+)\b/g)) {
2936
3172
  return refs;
2937
3173
  }
2938
3174
  async function fetchRateLimit(client) {
2939
- const r = await client.json("/rate_limit");
3175
+ const r = await client.probe("/rate_limit");
2940
3176
  return r?.resources ?? null;
2941
3177
  }
2942
3178
  async function searchContribIssues(client, queries) {
@@ -2955,8 +3191,21 @@ async function searchContribIssues(client, queries) {
2955
3191
  );
2956
3192
  }
2957
3193
  async function aggregateContributions(opts = {}) {
2958
- const client = makeClient(opts.fetchImpl ?? fetchWithTimeout);
3194
+ const paceEnabled = opts.paceEnabled ?? !opts.fetchImpl;
3195
+ const client = makeClient(opts.fetchImpl ?? fetchWithTimeout, {
3196
+ paceEnabled,
3197
+ gapMs: readReqGapMs(),
3198
+ budgetMs: readBuildBudgetMs(),
3199
+ sleep: opts.sleepImpl ?? realSleep,
3200
+ now: opts.nowImpl ?? Date.now,
3201
+ // Bound the unguarded probe on the REAL network only; injected-fetch tests get
3202
+ // null so the probe stays deterministic (and its shared spy sleeper untouched).
3203
+ probeTimeoutMs: opts.fetchImpl ? null : PROBE_TIMEOUT_MS
3204
+ });
2959
3205
  const queries = opts.queries ?? CONTRIB_SEARCH_QUERIES;
3206
+ const startRl = await fetchRateLimit(client);
3207
+ const coreHealthyAtStart = (startRl?.core?.remaining ?? 0) >= 500;
3208
+ client.setSecondaryHint(coreHealthyAtStart);
2960
3209
  const issues = (await searchContribIssues(client, queries)).slice(0, MAX_CONTRIB_ISSUES_SCANNED);
2961
3210
  const repoCache = /* @__PURE__ */ new Map();
2962
3211
  const contribCache = /* @__PURE__ */ new Map();
@@ -3063,13 +3312,14 @@ async function aggregateContributions(opts = {}) {
3063
3312
  const core = rl?.core ? `${rl.core.remaining}/${rl.core.limit}` : "n/a";
3064
3313
  const search = rl?.search ? `${rl.search.remaining}/${rl.search.limit}` : "n/a";
3065
3314
  const noToken = !(process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"]);
3315
+ const { pacedMs, secondaryAborted, budgetAborted, elapsedMs } = client.getStats();
3066
3316
  console.info(
3067
- `[contribute] build metrics \u2014 scanned=${issues.length} reposDistinct=${repoCache.size} emitted=${jobs.length} metaNull=${metaNull} contribUndefined=${contribUndefined} prRefsNull=${prRefsNull} core=${core} search=${search}` + (noToken ? " (NO TOKEN \u2192 60/hr)" : "")
3317
+ `[contribute] build metrics \u2014 scanned=${issues.length} reposDistinct=${repoCache.size} emitted=${jobs.length} metaNull=${metaNull} contribUndefined=${contribUndefined} prRefsNull=${prRefsNull} paced=${pacedMs} secondaryAborted=${secondaryAborted} budgetAborted=${budgetAborted} core=${core} search=${search} elapsed=${elapsedMs}` + (noToken ? " (NO TOKEN \u2192 60/hr)" : "")
3068
3318
  );
3069
3319
  }
3070
3320
  return jobs;
3071
3321
  }
3072
- var GITHUB_API2, CONTRIB_LABEL_QUERIES, CONTRIB_LANGUAGE_QUERIES, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED;
3322
+ 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;
3073
3323
  var init_contributions = __esm({
3074
3324
  "../../packages/core/src/feeds/contributions.ts"() {
3075
3325
  "use strict";
@@ -3081,6 +3331,13 @@ var init_contributions = __esm({
3081
3331
  init_github_bounties();
3082
3332
  init_http();
3083
3333
  GITHUB_API2 = "https://api.github.com";
3334
+ DEFAULT_REQ_GAP_MS = 75;
3335
+ SECONDARY_BACKOFF_CAP_S = 30;
3336
+ DEFAULT_BUILD_BUDGET_MS = 9e4;
3337
+ MIN_BUILD_BUDGET_MS = 1e4;
3338
+ MAX_BUILD_BUDGET_MS = 9e4;
3339
+ PROBE_TIMEOUT_MS = 3e3;
3340
+ realSleep = (ms) => new Promise((r) => setTimeout(r, ms));
3084
3341
  CONTRIB_LABEL_QUERIES = [
3085
3342
  'label:"good first issue" type:issue state:open',
3086
3343
  'label:"good-first-issue" type:issue state:open',
@@ -3197,6 +3454,36 @@ var init_indexer = __esm({
3197
3454
  }
3198
3455
  });
3199
3456
 
3457
+ // ../../packages/core/src/credit.ts
3458
+ function verifyClaimCredit(claim, facts) {
3459
+ const reasons = [];
3460
+ const fail = (code, message) => reasons.push({ code, message });
3461
+ const norm = (r) => r.trim().toLowerCase();
3462
+ if (norm(facts.repo) !== norm(claim.repo))
3463
+ fail("repo-mismatch", `PR is in ${facts.repo}, claim is against ${claim.repo}`);
3464
+ if (!facts.merged) fail("not-merged", `PR #${facts.prNumber} is not merged`);
3465
+ if (facts.authorId == null || facts.authorId !== claim.claimantId)
3466
+ fail("author-mismatch", `PR author id ${facts.authorId} !== claimant id ${claim.claimantId}`);
3467
+ if (facts.merged && facts.mergedById != null && facts.authorId != null && facts.mergedById === facts.authorId)
3468
+ fail("self-merged", `PR was merged by its own author (id ${facts.authorId})`);
3469
+ if (claim.claimedIssueNumber != null) {
3470
+ if (facts.closesIssues.length === 0)
3471
+ fail("issue-linkage-missing", `PR closes no issue; claim names #${claim.claimedIssueNumber}`);
3472
+ else if (!facts.closesIssues.includes(claim.claimedIssueNumber))
3473
+ fail(
3474
+ "issue-linkage-mismatch",
3475
+ `PR closes ${facts.closesIssues.map((n) => "#" + n).join(", ")}; claim names #${claim.claimedIssueNumber}`
3476
+ );
3477
+ }
3478
+ if (reasons.length === 0) reasons.push({ code: "ok", message: "all credit predicates hold" });
3479
+ return { ok: reasons.every((r) => r.code === "ok"), reasons };
3480
+ }
3481
+ var init_credit = __esm({
3482
+ "../../packages/core/src/credit.ts"() {
3483
+ "use strict";
3484
+ }
3485
+ });
3486
+
3200
3487
  // ../../packages/core/src/intro.ts
3201
3488
  function buildIntroPayload(input) {
3202
3489
  const payload = {
@@ -6554,10 +6841,13 @@ function deriveLegibleProfile(credential, recency, traction, seniorityBand) {
6554
6841
  if (daysAgo !== void 0) s += ` \u2014 most recent ${daysAgo}d ago`;
6555
6842
  proofSentence = `${s}.`;
6556
6843
  }
6844
+ const enrichedPRs = ok ? credential.qualifyingPRs ?? [] : [];
6845
+ const maintainerReviewedCount = enrichedPRs.some((p) => p.maintainerReviewed !== void 0) ? enrichedPRs.filter((p) => p.maintainerReviewed === true).length : void 0;
6557
6846
  const auditableBadge = ok ? {
6558
6847
  mergedTotal: credential.qualifyingTotal,
6559
6848
  distinctOrgs: orgCount,
6560
- thresholds: { stars: MIN_STARS, contributors: MIN_CONTRIBUTORS }
6849
+ thresholds: { stars: MIN_STARS, contributors: MIN_CONTRIBUTORS },
6850
+ ...maintainerReviewedCount !== void 0 ? { maintainerReviewedCount } : {}
6561
6851
  } : null;
6562
6852
  const profile = {
6563
6853
  headline,
@@ -6753,6 +7043,7 @@ __export(src_exports, {
6753
7043
  MENTION_DELTA: () => MENTION_DELTA,
6754
7044
  MIN_CONTRIBUTORS: () => MIN_CONTRIBUTORS,
6755
7045
  MIN_STARS: () => MIN_STARS,
7046
+ RIGOR: () => RIGOR,
6756
7047
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
6757
7048
  SYNONYMS: () => SYNONYMS,
6758
7049
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
@@ -6781,6 +7072,7 @@ __export(src_exports, {
6781
7072
  decryptMessage: () => decryptMessage,
6782
7073
  deriveLegibleProfile: () => deriveLegibleProfile,
6783
7074
  deriveResumeTrend: () => deriveResumeTrend,
7075
+ deriveRigorTiers: () => deriveRigorTiers,
6784
7076
  deriveSharedKey: () => deriveSharedKey,
6785
7077
  deriveTrajectoryNarrative: () => deriveTrajectoryNarrative,
6786
7078
  displayableDrift: () => displayableDrift,
@@ -6790,6 +7082,7 @@ __export(src_exports, {
6790
7082
  fetchGitHubProfile: () => fetchGitHubProfile,
6791
7083
  fetchOpenExternalPRs: () => fetchOpenExternalPRs,
6792
7084
  fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
7085
+ fetchPRScoringFacts: () => fetchPRScoringFacts,
6793
7086
  fetchRepoRecency: () => fetchRepoRecency,
6794
7087
  flattenTiers: () => flattenTiers,
6795
7088
  funnelCounts: () => funnelCounts,
@@ -6820,6 +7113,7 @@ __export(src_exports, {
6820
7113
  opire: () => opire,
6821
7114
  opportunityShortToken: () => opportunityShortToken,
6822
7115
  pageMatches: () => pageMatches,
7116
+ parseGitHubRef: () => parseGitHubRef,
6823
7117
  passesContributionGate: () => passesContributionGate,
6824
7118
  passesMaturityGate: () => passesMaturityGate,
6825
7119
  personCardToJob: () => personCardToJob,
@@ -6836,6 +7130,7 @@ __export(src_exports, {
6836
7130
  validateGraph: () => validateGraph,
6837
7131
  validateIntroPayload: () => validateIntroPayload,
6838
7132
  validateTargetContact: () => validateTargetContact,
7133
+ verifyClaimCredit: () => verifyClaimCredit,
6839
7134
  workable: () => workable,
6840
7135
  wwr: () => wwr
6841
7136
  });
@@ -6850,12 +7145,14 @@ var init_src = __esm({
6850
7145
  init_indexer();
6851
7146
  init_partners();
6852
7147
  init_github();
7148
+ init_credit();
6853
7149
  init_intro();
6854
7150
  init_directoryThreshold();
6855
7151
  init_chatCrypto();
6856
7152
  init_job_status();
6857
7153
  init_legible();
6858
7154
  init_legible_trajectory();
7155
+ init_rigor();
6859
7156
  init_short_token();
6860
7157
  }
6861
7158
  });
@@ -6868,9 +7165,9 @@ var init_keytar = __esm({
6868
7165
  }
6869
7166
  });
6870
7167
 
6871
- // node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node
7168
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node
6872
7169
  var require_keytar = __commonJS({
6873
- "node-file:/Users/ericgang/job-placement-inline/node_modules/keytar/build/Release/keytar.node"(exports, module) {
7170
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/91995792-9cff-48f4-ae9c-dee9e36fc319/scratchpad/release-v0230/node_modules/keytar/build/Release/keytar.node"(exports, module) {
6874
7171
  "use strict";
6875
7172
  init_keytar();
6876
7173
  try {
@@ -7290,36 +7587,46 @@ ${i + 1}. ${linkTitle(job.title, job.url)} [${ref}]`);
7290
7587
  console.log(` Claim: ${sanitizeText(b.claimUrl ?? job.url)}`);
7291
7588
  console.log(` \u2192 terminalhire claim ${ref}`);
7292
7589
  }
7590
+ async function getBounties({ quiet = false, offline = false } = {}) {
7591
+ if (!quiet) console.log(`Fetching bounty index from ${API_URL}/api/index...`);
7592
+ const index = offline ? readIndexCache() : await fetchIndex();
7593
+ if (offline && !index) return { status: "no-cache" };
7594
+ let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
7595
+ if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
7596
+ if (WINNABLE_ONLY) bounties = bounties.filter((j) => (j.bounty?.competingOpenPRs ?? 0) === 0);
7597
+ if (bounties.length === 0) {
7598
+ return { status: "empty" };
7599
+ }
7600
+ const ranked = /* @__PURE__ */ new Map();
7601
+ try {
7602
+ const { readProfile: readProfile2, profileToFingerprint: profileToFingerprint2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
7603
+ const { match: match2 } = await Promise.resolve().then(() => (init_src(), src_exports));
7604
+ const profile = await readProfile2();
7605
+ if (profile.skillTags.length > 0) {
7606
+ const fp = profileToFingerprint2(profile);
7607
+ for (const r of match2(fp, bounties, bounties.length)) {
7608
+ ranked.set(r.job.id, { score: r.score, reason: r.reason, matchedTags: r.matchedTags });
7609
+ }
7610
+ }
7611
+ } catch {
7612
+ }
7613
+ const score = (j) => ranked.get(j.id)?.score ?? 0;
7614
+ const amt = (j) => j.bounty?.amountUSD ?? -1;
7615
+ const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
7616
+ bounties.sort((a, b) => contested(a) - contested(b) || score(b) - score(a) || amt(b) - amt(a));
7617
+ const matchedCount = bounties.filter((j) => score(j) > 0).length;
7618
+ return { status: "ok", bounties, ranked, matchedCount };
7619
+ }
7293
7620
  async function run() {
7294
7621
  try {
7295
- console.log(`Fetching bounty index from ${API_URL}/api/index...`);
7296
- const index = await fetchIndex();
7297
- let bounties = (index.jobs ?? []).filter((j) => j.source === "bounty");
7298
- if (PRICED_ONLY) bounties = bounties.filter((j) => j.bounty?.amountUSD != null);
7299
- if (WINNABLE_ONLY) bounties = bounties.filter((j) => (j.bounty?.competingOpenPRs ?? 0) === 0);
7300
- if (bounties.length === 0) {
7622
+ const result = await getBounties();
7623
+ if (result.status === "empty") {
7301
7624
  console.log("\nNo bounties available right now. Try again later \u2014 supply refreshes through the day.");
7302
7625
  return;
7303
7626
  }
7304
- const ranked = /* @__PURE__ */ new Map();
7305
- try {
7306
- const { readProfile: readProfile2, profileToFingerprint: profileToFingerprint2 } = await Promise.resolve().then(() => (init_profile(), profile_exports));
7307
- const { match: match2 } = await Promise.resolve().then(() => (init_src(), src_exports));
7308
- const profile = await readProfile2();
7309
- if (profile.skillTags.length > 0) {
7310
- const fp = profileToFingerprint2(profile);
7311
- for (const r of match2(fp, bounties, bounties.length)) {
7312
- ranked.set(r.job.id, { score: r.score, reason: r.reason, matchedTags: r.matchedTags });
7313
- }
7314
- }
7315
- } catch {
7316
- }
7317
- const score = (j) => ranked.get(j.id)?.score ?? 0;
7318
- const amt = (j) => j.bounty?.amountUSD ?? -1;
7319
- const contested = (j) => (j.bounty?.competingOpenPRs ?? 0) > 0 ? 1 : 0;
7320
- bounties.sort((a, b) => contested(a) - contested(b) || score(b) - score(a) || amt(b) - amt(a));
7627
+ if (result.status !== "ok") return;
7628
+ const { bounties, ranked, matchedCount } = result;
7321
7629
  const shown = SHOW_ALL ? bounties : bounties.slice(0, LIMIT);
7322
- const matchedCount = bounties.filter((j) => score(j) > 0).length;
7323
7630
  console.log(
7324
7631
  `
7325
7632
  \u26A1 ${bounties.length} bount${bounties.length === 1 ? "y" : "ies"} you could knock out` + (matchedCount ? ` \u2014 ${matchedCount} matched to your profile` : "") + ` (local rank \u2014 no data sent)
@@ -7351,6 +7658,7 @@ Open this to claim/work the bounty (you go straight to the source \u2014 we neve
7351
7658
  }
7352
7659
  }
7353
7660
  export {
7661
+ getBounties,
7354
7662
  run
7355
7663
  };
7356
7664
  /*! Bundled license information: