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.
- package/dist/bin/jpi-bounties.js +131 -1
- package/dist/bin/jpi-claim.js +191 -6
- package/dist/bin/jpi-devs.js +131 -1
- package/dist/bin/jpi-dispatch.js +192 -7
- package/dist/bin/jpi-hub.js +131 -1
- package/dist/bin/jpi-init.js +131 -1
- package/dist/bin/jpi-jobs.js +131 -1
- package/dist/bin/jpi-login.js +131 -1
- package/dist/bin/jpi-mcp.js +192 -7
- package/dist/bin/jpi-project.js +131 -1
- package/dist/bin/jpi-refresh.js +131 -1
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -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,
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -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
|
|
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 {
|
|
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
|
package/dist/bin/jpi-devs.js
CHANGED
|
@@ -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,
|