terminalhire 0.40.7 → 0.40.8

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.
@@ -1339,6 +1339,7 @@ async function fetchRepoMeta(owner, name, token, cache, stats) {
1339
1339
  topics: r.topics ?? [],
1340
1340
  // `|| null` collapses "" → null so an empty description never crosses the wire.
1341
1341
  description: r.description || null,
1342
+ defaultBranch: r.default_branch || "main",
1342
1343
  contributors
1343
1344
  };
1344
1345
  } catch (err) {
@@ -1604,6 +1605,131 @@ async function fetchOpenExternalPRs(login, token, cache = /* @__PURE__ */ new Ma
1604
1605
  }
1605
1606
  return out;
1606
1607
  }
1608
+ async function fetchCarriedContributions(login, token, cache = /* @__PURE__ */ new Map(), gates = {
1609
+ minStars: MIN_STARS,
1610
+ minContributors: MIN_CONTRIBUTORS
1611
+ }) {
1612
+ if (!token) return [];
1613
+ const loginLc = login.toLowerCase();
1614
+ let ownedOrgs;
1615
+ try {
1616
+ ownedOrgs = await fetchPublicOrgs(login, token);
1617
+ } catch {
1618
+ return null;
1619
+ }
1620
+ let items;
1621
+ try {
1622
+ const q = encodeURIComponent(
1623
+ `type:pr is:closed is:unmerged is:public author:${login} -user:${login} sort:updated`
1624
+ );
1625
+ const res = await ghFetch(
1626
+ `/search/issues?q=${q}&per_page=${CARRIED_PR_PAGE}`,
1627
+ token
1628
+ );
1629
+ items = res.items ?? [];
1630
+ } catch (err) {
1631
+ const msg = err instanceof Error ? err.message : String(err);
1632
+ console.warn("[carried] search failed:", msg);
1633
+ return null;
1634
+ }
1635
+ const metaStats = { transient: 0 };
1636
+ const out = [];
1637
+ let probes = 0;
1638
+ for (const item of items) {
1639
+ if (probes >= MAX_CARRIED_PROBES) {
1640
+ console.warn(
1641
+ `[carried] ${login}: probe cap ${MAX_CARRIED_PROBES} reached \u2014 later closed PRs not examined`
1642
+ );
1643
+ break;
1644
+ }
1645
+ const repo = parseRepoUrl(item.repository_url);
1646
+ if (!repo) continue;
1647
+ const ownerLc = repo.owner.toLowerCase();
1648
+ if (ownerLc === loginLc) continue;
1649
+ if (ownedOrgs.has(ownerLc)) continue;
1650
+ if (isTrivialPRTitle(item.title)) continue;
1651
+ const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache, metaStats);
1652
+ if (metaStats.transient > 0) {
1653
+ console.warn(
1654
+ `[carried] ${login}: per-repo metadata transient failure (${metaStats.transient}) \u2014 returning null (keep prior)`
1655
+ );
1656
+ return null;
1657
+ }
1658
+ if (!meta) continue;
1659
+ if (meta.private) continue;
1660
+ if (meta.archived || meta.fork) continue;
1661
+ if (meta.stars < gates.minStars) continue;
1662
+ if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
1663
+ const ref = parseGitHubRef(item.html_url);
1664
+ if (!ref || ref.kind !== "pull") continue;
1665
+ probes += 1;
1666
+ let carried;
1667
+ try {
1668
+ carried = await probeCarriedPR(login, loginLc, ref, meta.defaultBranch, item, token);
1669
+ } catch (err) {
1670
+ const msg = err instanceof Error ? err.message : String(err);
1671
+ if (TRANSIENT_META_ERROR.test(msg)) {
1672
+ console.warn(`[carried] ${login}: probe transient failure \u2014 returning null (keep prior)`);
1673
+ return null;
1674
+ }
1675
+ continue;
1676
+ }
1677
+ if (carried) out.push(carried);
1678
+ }
1679
+ return out;
1680
+ }
1681
+ async function probeCarriedPR(login, loginLc, ref, defaultBranch, item, token) {
1682
+ const prCommits = await ghFetch(
1683
+ `/repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/commits?per_page=100`,
1684
+ token
1685
+ );
1686
+ const mine = prCommits.filter((c) => c.author?.login?.toLowerCase() === loginLc);
1687
+ if (mine.length === 0) return null;
1688
+ const mineShas = new Set(mine.map((c) => c.sha));
1689
+ const dates = mine.map((c) => c.commit?.author?.date).filter((d) => !!d);
1690
+ const since = dates.length > 0 ? dates.reduce((a, b) => a < b ? a : b) : void 0;
1691
+ const q = new URLSearchParams({ author: login, sha: defaultBranch, per_page: "100" });
1692
+ if (since) q.set("since", since);
1693
+ const landedList = await ghFetch(
1694
+ `/repos/${ref.owner}/${ref.repo}/commits?${q.toString()}`,
1695
+ token
1696
+ );
1697
+ const landed = landedList.filter((c) => c.author?.login?.toLowerCase() === loginLc).filter((c) => mineShas.has(c.sha));
1698
+ if (landed.length === 0) return null;
1699
+ const mergedPullsBySha = /* @__PURE__ */ new Map();
1700
+ const probeShas = landed.slice(0, CARRIED_SHA_PROBE_CAP);
1701
+ if (landed.length > probeShas.length) {
1702
+ console.warn(
1703
+ `[carried] ${ref.owner}/${ref.repo}#${ref.number}: ${landed.length} landed commits exceeds probe cap ${CARRIED_SHA_PROBE_CAP} \u2014 crediting only the probed ones`
1704
+ );
1705
+ }
1706
+ for (const c of probeShas) {
1707
+ const pulls = await ghFetch(
1708
+ `/repos/${ref.owner}/${ref.repo}/commits/${c.sha}/pulls?per_page=10`,
1709
+ token
1710
+ );
1711
+ mergedPullsBySha.set(
1712
+ c.sha,
1713
+ pulls.filter((p) => !!p.merged_at)
1714
+ );
1715
+ }
1716
+ const ownedByMergedPath = (sha) => (mergedPullsBySha.get(sha) ?? []).some((p) => p.user?.login?.toLowerCase() === loginLc);
1717
+ const credited = probeShas.filter((c) => !ownedByMergedPath(c.sha));
1718
+ if (credited.length === 0) return null;
1719
+ const landedDates = credited.map((c) => c.commit?.author?.date).filter((d) => !!d);
1720
+ const landedAt = landedDates.length > 0 ? landedDates.reduce((a, b) => a > b ? a : b) : item.created_at;
1721
+ const carrierPrUrl = credited.flatMap((c) => mergedPullsBySha.get(c.sha) ?? []).find((p) => p.html_url !== item.html_url)?.html_url;
1722
+ return {
1723
+ closedPrUrl: item.html_url,
1724
+ title: item.title,
1725
+ repoFullName: `${ref.owner}/${ref.repo}`,
1726
+ // CREDITED, not `landed`: a commit the merged accumulator already owns must not
1727
+ // reappear as this row's evidence, or the same work is counted on both paths.
1728
+ landedShas: credited.map((c) => c.sha),
1729
+ carrierPrUrl,
1730
+ landedAt
1731
+ };
1732
+ }
1607
1733
  function acceptanceCountForDomains(cred, domains) {
1608
1734
  if (cred.status !== "ok") return 0;
1609
1735
  let max = 0;
@@ -2159,7 +2285,7 @@ async function fetchPRLifecycle(prUrl, token, signal, governor) {
2159
2285
  complete
2160
2286
  };
2161
2287
  }
2162
- 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, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2288
+ var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, CARRIED_PR_PAGE, MAX_CARRIED_PROBES, CARRIED_SHA_PROBE_CAP, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR, GITHUB_GRAPHQL_URL, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2163
2289
  var init_github = __esm({
2164
2290
  "../../packages/core/src/github.ts"() {
2165
2291
  "use strict";
@@ -2175,6 +2301,9 @@ var init_github = __esm({
2175
2301
  MAX_ENRICH_PRS = 12;
2176
2302
  OPEN_PR_PAGE = 20;
2177
2303
  TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
2304
+ CARRIED_PR_PAGE = 20;
2305
+ MAX_CARRIED_PROBES = 10;
2306
+ CARRIED_SHA_PROBE_CAP = 20;
2178
2307
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
2179
2308
  RESUME_MIN_SCORE = 0.05;
2180
2309
  RECEPTIVITY_RECENCY_DAYS = 180;
@@ -10196,6 +10325,7 @@ __export(src_exports, {
10196
10325
  extractJson: () => extractJson,
10197
10326
  extractSkillTags: () => extractSkillTags,
10198
10327
  fetchAcceptanceSearchPage: () => fetchAcceptanceSearchPage,
10328
+ fetchCarriedContributions: () => fetchCarriedContributions,
10199
10329
  fetchGitHubProfile: () => fetchGitHubProfile,
10200
10330
  fetchIssueStatus: () => fetchIssueStatus,
10201
10331
  fetchOpenExternalPRs: () => fetchOpenExternalPRs,
@@ -1362,6 +1362,7 @@ async function fetchRepoMeta(owner, name, token, cache, stats) {
1362
1362
  topics: r.topics ?? [],
1363
1363
  // `|| null` collapses "" → null so an empty description never crosses the wire.
1364
1364
  description: r.description || null,
1365
+ defaultBranch: r.default_branch || "main",
1365
1366
  contributors
1366
1367
  };
1367
1368
  } catch (err) {
@@ -1627,6 +1628,131 @@ async function fetchOpenExternalPRs(login, token, cache = /* @__PURE__ */ new Ma
1627
1628
  }
1628
1629
  return out;
1629
1630
  }
1631
+ async function fetchCarriedContributions(login, token, cache = /* @__PURE__ */ new Map(), gates = {
1632
+ minStars: MIN_STARS,
1633
+ minContributors: MIN_CONTRIBUTORS
1634
+ }) {
1635
+ if (!token) return [];
1636
+ const loginLc = login.toLowerCase();
1637
+ let ownedOrgs;
1638
+ try {
1639
+ ownedOrgs = await fetchPublicOrgs(login, token);
1640
+ } catch {
1641
+ return null;
1642
+ }
1643
+ let items;
1644
+ try {
1645
+ const q = encodeURIComponent(
1646
+ `type:pr is:closed is:unmerged is:public author:${login} -user:${login} sort:updated`
1647
+ );
1648
+ const res = await ghFetch(
1649
+ `/search/issues?q=${q}&per_page=${CARRIED_PR_PAGE}`,
1650
+ token
1651
+ );
1652
+ items = res.items ?? [];
1653
+ } catch (err) {
1654
+ const msg = err instanceof Error ? err.message : String(err);
1655
+ console.warn("[carried] search failed:", msg);
1656
+ return null;
1657
+ }
1658
+ const metaStats = { transient: 0 };
1659
+ const out = [];
1660
+ let probes = 0;
1661
+ for (const item of items) {
1662
+ if (probes >= MAX_CARRIED_PROBES) {
1663
+ console.warn(
1664
+ `[carried] ${login}: probe cap ${MAX_CARRIED_PROBES} reached \u2014 later closed PRs not examined`
1665
+ );
1666
+ break;
1667
+ }
1668
+ const repo = parseRepoUrl(item.repository_url);
1669
+ if (!repo) continue;
1670
+ const ownerLc = repo.owner.toLowerCase();
1671
+ if (ownerLc === loginLc) continue;
1672
+ if (ownedOrgs.has(ownerLc)) continue;
1673
+ if (isTrivialPRTitle(item.title)) continue;
1674
+ const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache, metaStats);
1675
+ if (metaStats.transient > 0) {
1676
+ console.warn(
1677
+ `[carried] ${login}: per-repo metadata transient failure (${metaStats.transient}) \u2014 returning null (keep prior)`
1678
+ );
1679
+ return null;
1680
+ }
1681
+ if (!meta) continue;
1682
+ if (meta.private) continue;
1683
+ if (meta.archived || meta.fork) continue;
1684
+ if (meta.stars < gates.minStars) continue;
1685
+ if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
1686
+ const ref = parseGitHubRef(item.html_url);
1687
+ if (!ref || ref.kind !== "pull") continue;
1688
+ probes += 1;
1689
+ let carried;
1690
+ try {
1691
+ carried = await probeCarriedPR(login, loginLc, ref, meta.defaultBranch, item, token);
1692
+ } catch (err) {
1693
+ const msg = err instanceof Error ? err.message : String(err);
1694
+ if (TRANSIENT_META_ERROR.test(msg)) {
1695
+ console.warn(`[carried] ${login}: probe transient failure \u2014 returning null (keep prior)`);
1696
+ return null;
1697
+ }
1698
+ continue;
1699
+ }
1700
+ if (carried) out.push(carried);
1701
+ }
1702
+ return out;
1703
+ }
1704
+ async function probeCarriedPR(login, loginLc, ref, defaultBranch, item, token) {
1705
+ const prCommits = await ghFetch(
1706
+ `/repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/commits?per_page=100`,
1707
+ token
1708
+ );
1709
+ const mine = prCommits.filter((c) => c.author?.login?.toLowerCase() === loginLc);
1710
+ if (mine.length === 0) return null;
1711
+ const mineShas = new Set(mine.map((c) => c.sha));
1712
+ const dates = mine.map((c) => c.commit?.author?.date).filter((d) => !!d);
1713
+ const since = dates.length > 0 ? dates.reduce((a, b) => a < b ? a : b) : void 0;
1714
+ const q = new URLSearchParams({ author: login, sha: defaultBranch, per_page: "100" });
1715
+ if (since) q.set("since", since);
1716
+ const landedList = await ghFetch(
1717
+ `/repos/${ref.owner}/${ref.repo}/commits?${q.toString()}`,
1718
+ token
1719
+ );
1720
+ const landed = landedList.filter((c) => c.author?.login?.toLowerCase() === loginLc).filter((c) => mineShas.has(c.sha));
1721
+ if (landed.length === 0) return null;
1722
+ const mergedPullsBySha = /* @__PURE__ */ new Map();
1723
+ const probeShas = landed.slice(0, CARRIED_SHA_PROBE_CAP);
1724
+ if (landed.length > probeShas.length) {
1725
+ console.warn(
1726
+ `[carried] ${ref.owner}/${ref.repo}#${ref.number}: ${landed.length} landed commits exceeds probe cap ${CARRIED_SHA_PROBE_CAP} \u2014 crediting only the probed ones`
1727
+ );
1728
+ }
1729
+ for (const c of probeShas) {
1730
+ const pulls = await ghFetch(
1731
+ `/repos/${ref.owner}/${ref.repo}/commits/${c.sha}/pulls?per_page=10`,
1732
+ token
1733
+ );
1734
+ mergedPullsBySha.set(
1735
+ c.sha,
1736
+ pulls.filter((p) => !!p.merged_at)
1737
+ );
1738
+ }
1739
+ const ownedByMergedPath = (sha) => (mergedPullsBySha.get(sha) ?? []).some((p) => p.user?.login?.toLowerCase() === loginLc);
1740
+ const credited = probeShas.filter((c) => !ownedByMergedPath(c.sha));
1741
+ if (credited.length === 0) return null;
1742
+ const landedDates = credited.map((c) => c.commit?.author?.date).filter((d) => !!d);
1743
+ const landedAt = landedDates.length > 0 ? landedDates.reduce((a, b) => a > b ? a : b) : item.created_at;
1744
+ const carrierPrUrl = credited.flatMap((c) => mergedPullsBySha.get(c.sha) ?? []).find((p) => p.html_url !== item.html_url)?.html_url;
1745
+ return {
1746
+ closedPrUrl: item.html_url,
1747
+ title: item.title,
1748
+ repoFullName: `${ref.owner}/${ref.repo}`,
1749
+ // CREDITED, not `landed`: a commit the merged accumulator already owns must not
1750
+ // reappear as this row's evidence, or the same work is counted on both paths.
1751
+ landedShas: credited.map((c) => c.sha),
1752
+ carrierPrUrl,
1753
+ landedAt
1754
+ };
1755
+ }
1630
1756
  function acceptanceCountForDomains(cred, domains) {
1631
1757
  if (cred.status !== "ok") return 0;
1632
1758
  let max = 0;
@@ -2182,7 +2308,7 @@ async function fetchPRLifecycle(prUrl, token, signal, governor) {
2182
2308
  complete
2183
2309
  };
2184
2310
  }
2185
- 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, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2311
+ var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, CARRIED_PR_PAGE, MAX_CARRIED_PROBES, CARRIED_SHA_PROBE_CAP, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR, GITHUB_GRAPHQL_URL, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2186
2312
  var init_github = __esm({
2187
2313
  "../../packages/core/src/github.ts"() {
2188
2314
  "use strict";
@@ -2198,6 +2324,9 @@ var init_github = __esm({
2198
2324
  MAX_ENRICH_PRS = 12;
2199
2325
  OPEN_PR_PAGE = 20;
2200
2326
  TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
2327
+ CARRIED_PR_PAGE = 20;
2328
+ MAX_CARRIED_PROBES = 10;
2329
+ CARRIED_SHA_PROBE_CAP = 20;
2201
2330
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
2202
2331
  RESUME_MIN_SCORE = 0.05;
2203
2332
  RECEPTIVITY_RECENCY_DAYS = 180;
@@ -10219,6 +10348,7 @@ __export(src_exports, {
10219
10348
  extractJson: () => extractJson,
10220
10349
  extractSkillTags: () => extractSkillTags,
10221
10350
  fetchAcceptanceSearchPage: () => fetchAcceptanceSearchPage,
10351
+ fetchCarriedContributions: () => fetchCarriedContributions,
10222
10352
  fetchGitHubProfile: () => fetchGitHubProfile,
10223
10353
  fetchIssueStatus: () => fetchIssueStatus,
10224
10354
  fetchOpenExternalPRs: () => fetchOpenExternalPRs,
@@ -27960,20 +28090,64 @@ function renderRunView(run2, message) {
27960
28090
  const sha = run2.commitSha ? String(run2.commitSha).slice(0, 10) : null;
27961
28091
  lines.push(` run: ${run2.branch ?? "(no branch)"}${sha ? ` @ ${sha}` : ""}`);
27962
28092
  lines.push(
27963
- ` status: ${run2.status}${run2.conclusion ? ` \xB7 conclusion: ${run2.conclusion}` : ""}`
28093
+ ` status: ${terminalSafeInline(run2.status)}${run2.conclusion ? ` \xB7 conclusion: ${terminalSafeInline(run2.conclusion)}` : ""}`
27964
28094
  );
27965
28095
  if (Array.isArray(run2.failingJobs) && run2.failingJobs.length > 0) {
27966
- lines.push(` failing: ${run2.failingJobs.join(", ")}`);
28096
+ lines.push(` failing: ${run2.failingJobs.map(terminalSafeInline).join(", ")}`);
27967
28097
  }
27968
- if (run2.previewUrl) lines.push(` preview: ${run2.previewUrl}`);
28098
+ if (run2.previewUrl) lines.push(` preview: ${terminalSafeInline(run2.previewUrl)}`);
27969
28099
  if (run2.roundTrips != null) lines.push(` round trips: ${run2.roundTrips}`);
27970
28100
  if (run2.logTail) {
27971
28101
  lines.push(" \u2500\u2500 log tail \u2500\u2500");
27972
- for (const l of String(run2.logTail).split("\n")) lines.push(` \u2502 ${l}`);
28102
+ for (const l of terminalSafeLines(run2.logTail)) lines.push(` \u2502 ${l}`);
27973
28103
  }
27974
28104
  if (message) lines.push(` ${message}`);
27975
28105
  return lines.join("\n");
27976
28106
  }
28107
+ var CLAIM_EVENT_LABEL = {
28108
+ claimed: "you claimed this",
28109
+ claimant_approved: "the founder approved you to start",
28110
+ branch_created: "your branch was created",
28111
+ patch_submitted: "you submitted a change",
28112
+ ci_result: "checks reported",
28113
+ pr_opened: "you finished \u2014 waiting on the founder",
28114
+ feedback: "the founder sent feedback",
28115
+ accepted: "the founder accepted",
28116
+ rejected: "the founder rejected"
28117
+ };
28118
+ var LINE_BREAKS = /\r\n|[\r\n\v\f\u0085\u2028\u2029]/;
28119
+ var CONTROL_CHARS = /[\u0000-\u001F\u007F-\u009F]/g;
28120
+ function terminalSafeLines(raw) {
28121
+ if (typeof raw !== "string" || raw === "") return [];
28122
+ return raw.split(LINE_BREAKS).map((l) => l.replace(CONTROL_CHARS, ""));
28123
+ }
28124
+ function terminalSafeInline(raw) {
28125
+ return terminalSafeLines(raw).join(" ");
28126
+ }
28127
+ function shortWhen(iso) {
28128
+ const d = new Date(iso);
28129
+ if (Number.isNaN(d.getTime())) return "";
28130
+ const month = d.toLocaleString("en-US", { month: "short", timeZone: "UTC" }).toLowerCase();
28131
+ const pad = (n) => String(n).padStart(2, "0");
28132
+ return `${month} ${d.getUTCDate()} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
28133
+ }
28134
+ function renderClaimHistory(attempts, timeline) {
28135
+ const runs = Array.isArray(attempts) ? attempts : [];
28136
+ const told = (Array.isArray(timeline) ? timeline : []).filter((e) => CLAIM_EVENT_LABEL[e?.kind]);
28137
+ if (runs.length < 2 && told.length === 0) return "";
28138
+ const lines = ["", " \u2500\u2500 how this got here \u2500\u2500"];
28139
+ for (const a of runs) {
28140
+ const sha = a.commitSha ? String(a.commitSha).slice(0, 10) : "";
28141
+ const jobs = Array.isArray(a.failingJobs) && a.failingJobs.length > 0 ? ` \u2014 ${a.failingJobs.map(terminalSafeInline).join(", ")}` : "";
28142
+ const outcome = a.conclusion ? `checks ${terminalSafeInline(a.conclusion)}${jobs}` : a.status === "no-ci" ? "no checks ran" : "checks running";
28143
+ lines.push(` attempt ${a.attemptNumber} ${sha} ${outcome} ${shortWhen(a.at)}`);
28144
+ }
28145
+ for (const e of told) {
28146
+ lines.push(` ${shortWhen(e.at)} ${CLAIM_EVENT_LABEL[e.kind]}`);
28147
+ for (const l of terminalSafeLines(e.note)) lines.push(` \u2502 ${l}`);
28148
+ }
28149
+ return lines.join("\n");
28150
+ }
27977
28151
  function isTerminalRunStatus(status) {
27978
28152
  return status === "completed" || status === "no-ci";
27979
28153
  }
@@ -28177,7 +28351,13 @@ async function cmdRuns(id, flags = {}) {
28177
28351
  if (!body || body.ok !== true || !body.run) {
28178
28352
  return { kind: "refusal", status: res.status, body: { error: "malformed-response" } };
28179
28353
  }
28180
- return { kind: "ok", run: body.run, message: body.message ?? null };
28354
+ return {
28355
+ kind: "ok",
28356
+ run: body.run,
28357
+ message: body.message ?? null,
28358
+ attempts: Array.isArray(body.attempts) ? body.attempts : [],
28359
+ timeline: Array.isArray(body.timeline) ? body.timeline : []
28360
+ };
28181
28361
  };
28182
28362
  if (flags.watch) {
28183
28363
  console.log(
@@ -28199,6 +28379,8 @@ async function cmdRuns(id, flags = {}) {
28199
28379
  console.log(`
28200
28380
  ${claim.title}`);
28201
28381
  console.log(renderRunView(r.run, r.message));
28382
+ const history = renderClaimHistory(r.attempts, r.timeline);
28383
+ if (history) console.log(history);
28202
28384
  if (!isTerminalRunStatus(r.run.status)) {
28203
28385
  console.log(`
28204
28386
  Still running \u2014 poll it: terminalhire claim runs ${id} --watch`);
@@ -29326,6 +29508,7 @@ export {
29326
29508
  pickBodySource,
29327
29509
  pickExistingPr,
29328
29510
  printNextSteps,
29511
+ renderClaimHistory,
29329
29512
  renderRunView,
29330
29513
  renderServerRefusal,
29331
29514
  resolveBounty,
@@ -29340,6 +29523,8 @@ export {
29340
29523
  sliceWorkDirFor,
29341
29524
  stakeDecision,
29342
29525
  startBranchFor,
29526
+ terminalSafeInline,
29527
+ terminalSafeLines,
29343
29528
  watchRunsLoop,
29344
29529
  workDirFor,
29345
29530
  writeSliceFiles
@@ -1418,6 +1418,7 @@ async function fetchRepoMeta(owner, name, token, cache, stats) {
1418
1418
  topics: r.topics ?? [],
1419
1419
  // `|| null` collapses "" → null so an empty description never crosses the wire.
1420
1420
  description: r.description || null,
1421
+ defaultBranch: r.default_branch || "main",
1421
1422
  contributors
1422
1423
  };
1423
1424
  } catch (err) {
@@ -1683,6 +1684,131 @@ async function fetchOpenExternalPRs(login, token, cache = /* @__PURE__ */ new Ma
1683
1684
  }
1684
1685
  return out;
1685
1686
  }
1687
+ async function fetchCarriedContributions(login, token, cache = /* @__PURE__ */ new Map(), gates = {
1688
+ minStars: MIN_STARS,
1689
+ minContributors: MIN_CONTRIBUTORS
1690
+ }) {
1691
+ if (!token) return [];
1692
+ const loginLc = login.toLowerCase();
1693
+ let ownedOrgs;
1694
+ try {
1695
+ ownedOrgs = await fetchPublicOrgs(login, token);
1696
+ } catch {
1697
+ return null;
1698
+ }
1699
+ let items;
1700
+ try {
1701
+ const q = encodeURIComponent(
1702
+ `type:pr is:closed is:unmerged is:public author:${login} -user:${login} sort:updated`
1703
+ );
1704
+ const res = await ghFetch(
1705
+ `/search/issues?q=${q}&per_page=${CARRIED_PR_PAGE}`,
1706
+ token
1707
+ );
1708
+ items = res.items ?? [];
1709
+ } catch (err) {
1710
+ const msg = err instanceof Error ? err.message : String(err);
1711
+ console.warn("[carried] search failed:", msg);
1712
+ return null;
1713
+ }
1714
+ const metaStats = { transient: 0 };
1715
+ const out = [];
1716
+ let probes = 0;
1717
+ for (const item of items) {
1718
+ if (probes >= MAX_CARRIED_PROBES) {
1719
+ console.warn(
1720
+ `[carried] ${login}: probe cap ${MAX_CARRIED_PROBES} reached \u2014 later closed PRs not examined`
1721
+ );
1722
+ break;
1723
+ }
1724
+ const repo = parseRepoUrl(item.repository_url);
1725
+ if (!repo) continue;
1726
+ const ownerLc = repo.owner.toLowerCase();
1727
+ if (ownerLc === loginLc) continue;
1728
+ if (ownedOrgs.has(ownerLc)) continue;
1729
+ if (isTrivialPRTitle(item.title)) continue;
1730
+ const meta = await fetchRepoMeta(repo.owner, repo.name, token, cache, metaStats);
1731
+ if (metaStats.transient > 0) {
1732
+ console.warn(
1733
+ `[carried] ${login}: per-repo metadata transient failure (${metaStats.transient}) \u2014 returning null (keep prior)`
1734
+ );
1735
+ return null;
1736
+ }
1737
+ if (!meta) continue;
1738
+ if (meta.private) continue;
1739
+ if (meta.archived || meta.fork) continue;
1740
+ if (meta.stars < gates.minStars) continue;
1741
+ if (meta.contributors !== void 0 && meta.contributors < gates.minContributors) continue;
1742
+ const ref = parseGitHubRef(item.html_url);
1743
+ if (!ref || ref.kind !== "pull") continue;
1744
+ probes += 1;
1745
+ let carried;
1746
+ try {
1747
+ carried = await probeCarriedPR(login, loginLc, ref, meta.defaultBranch, item, token);
1748
+ } catch (err) {
1749
+ const msg = err instanceof Error ? err.message : String(err);
1750
+ if (TRANSIENT_META_ERROR.test(msg)) {
1751
+ console.warn(`[carried] ${login}: probe transient failure \u2014 returning null (keep prior)`);
1752
+ return null;
1753
+ }
1754
+ continue;
1755
+ }
1756
+ if (carried) out.push(carried);
1757
+ }
1758
+ return out;
1759
+ }
1760
+ async function probeCarriedPR(login, loginLc, ref, defaultBranch, item, token) {
1761
+ const prCommits = await ghFetch(
1762
+ `/repos/${ref.owner}/${ref.repo}/pulls/${ref.number}/commits?per_page=100`,
1763
+ token
1764
+ );
1765
+ const mine = prCommits.filter((c) => c.author?.login?.toLowerCase() === loginLc);
1766
+ if (mine.length === 0) return null;
1767
+ const mineShas = new Set(mine.map((c) => c.sha));
1768
+ const dates = mine.map((c) => c.commit?.author?.date).filter((d) => !!d);
1769
+ const since = dates.length > 0 ? dates.reduce((a, b) => a < b ? a : b) : void 0;
1770
+ const q = new URLSearchParams({ author: login, sha: defaultBranch, per_page: "100" });
1771
+ if (since) q.set("since", since);
1772
+ const landedList = await ghFetch(
1773
+ `/repos/${ref.owner}/${ref.repo}/commits?${q.toString()}`,
1774
+ token
1775
+ );
1776
+ const landed = landedList.filter((c) => c.author?.login?.toLowerCase() === loginLc).filter((c) => mineShas.has(c.sha));
1777
+ if (landed.length === 0) return null;
1778
+ const mergedPullsBySha = /* @__PURE__ */ new Map();
1779
+ const probeShas = landed.slice(0, CARRIED_SHA_PROBE_CAP);
1780
+ if (landed.length > probeShas.length) {
1781
+ console.warn(
1782
+ `[carried] ${ref.owner}/${ref.repo}#${ref.number}: ${landed.length} landed commits exceeds probe cap ${CARRIED_SHA_PROBE_CAP} \u2014 crediting only the probed ones`
1783
+ );
1784
+ }
1785
+ for (const c of probeShas) {
1786
+ const pulls = await ghFetch(
1787
+ `/repos/${ref.owner}/${ref.repo}/commits/${c.sha}/pulls?per_page=10`,
1788
+ token
1789
+ );
1790
+ mergedPullsBySha.set(
1791
+ c.sha,
1792
+ pulls.filter((p) => !!p.merged_at)
1793
+ );
1794
+ }
1795
+ const ownedByMergedPath = (sha) => (mergedPullsBySha.get(sha) ?? []).some((p) => p.user?.login?.toLowerCase() === loginLc);
1796
+ const credited = probeShas.filter((c) => !ownedByMergedPath(c.sha));
1797
+ if (credited.length === 0) return null;
1798
+ const landedDates = credited.map((c) => c.commit?.author?.date).filter((d) => !!d);
1799
+ const landedAt = landedDates.length > 0 ? landedDates.reduce((a, b) => a > b ? a : b) : item.created_at;
1800
+ const carrierPrUrl = credited.flatMap((c) => mergedPullsBySha.get(c.sha) ?? []).find((p) => p.html_url !== item.html_url)?.html_url;
1801
+ return {
1802
+ closedPrUrl: item.html_url,
1803
+ title: item.title,
1804
+ repoFullName: `${ref.owner}/${ref.repo}`,
1805
+ // CREDITED, not `landed`: a commit the merged accumulator already owns must not
1806
+ // reappear as this row's evidence, or the same work is counted on both paths.
1807
+ landedShas: credited.map((c) => c.sha),
1808
+ carrierPrUrl,
1809
+ landedAt
1810
+ };
1811
+ }
1686
1812
  function acceptanceCountForDomains(cred, domains) {
1687
1813
  if (cred.status !== "ok") return 0;
1688
1814
  let max = 0;
@@ -2238,7 +2364,7 @@ async function fetchPRLifecycle(prUrl, token, signal, governor) {
2238
2364
  complete
2239
2365
  };
2240
2366
  }
2241
- 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, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2367
+ var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, CARRIED_PR_PAGE, MAX_CARRIED_PROBES, CARRIED_SHA_PROBE_CAP, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR, GITHUB_GRAPHQL_URL, AFFILIATION_REVIEWER_CAP, LIFECYCLE_BOT_LOGINS;
2242
2368
  var init_github = __esm({
2243
2369
  "../../packages/core/src/github.ts"() {
2244
2370
  "use strict";
@@ -2254,6 +2380,9 @@ var init_github = __esm({
2254
2380
  MAX_ENRICH_PRS = 12;
2255
2381
  OPEN_PR_PAGE = 20;
2256
2382
  TRANSIENT_META_ERROR = /HTTP 403|HTTP 429|rate limit|HTTP 5\d\d|timeout|network|fetch failed/i;
2383
+ CARRIED_PR_PAGE = 20;
2384
+ MAX_CARRIED_PROBES = 10;
2385
+ CARRIED_SHA_PROBE_CAP = 20;
2257
2386
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
2258
2387
  RESUME_MIN_SCORE = 0.05;
2259
2388
  RECEPTIVITY_RECENCY_DAYS = 180;
@@ -10275,6 +10404,7 @@ __export(src_exports, {
10275
10404
  extractJson: () => extractJson,
10276
10405
  extractSkillTags: () => extractSkillTags,
10277
10406
  fetchAcceptanceSearchPage: () => fetchAcceptanceSearchPage,
10407
+ fetchCarriedContributions: () => fetchCarriedContributions,
10278
10408
  fetchGitHubProfile: () => fetchGitHubProfile,
10279
10409
  fetchIssueStatus: () => fetchIssueStatus,
10280
10410
  fetchOpenExternalPRs: () => fetchOpenExternalPRs,