terminalhire 0.29.0 → 0.30.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.
- package/dist/bin/jpi-bounties.js +161 -1
- package/dist/bin/jpi-claim.js +402 -5
- package/dist/bin/jpi-devs.js +161 -1
- package/dist/bin/jpi-dispatch.js +402 -5
- package/dist/bin/jpi-hub.js +161 -1
- package/dist/bin/jpi-init.js +161 -1
- package/dist/bin/jpi-jobs.js +161 -1
- package/dist/bin/jpi-login.js +161 -1
- package/dist/bin/jpi-project.js +161 -1
- package/dist/bin/jpi-refresh.js +161 -1
- package/dist/src/reputation/audit.js +84 -0
- package/dist/src/reputation/fetch.js +1042 -0
- package/dist/src/reputation/identity.js +19 -0
- package/package.json +1 -1
package/dist/bin/jpi-bounties.js
CHANGED
|
@@ -1706,7 +1706,152 @@ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
|
|
|
1706
1706
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1707
1707
|
};
|
|
1708
1708
|
}
|
|
1709
|
-
|
|
1709
|
+
function isLifecycleBot(actor) {
|
|
1710
|
+
if (!actor) return false;
|
|
1711
|
+
if (actor.type === "Bot") return true;
|
|
1712
|
+
const login = String(actor.login ?? "");
|
|
1713
|
+
if (/\[bot\]$/i.test(login)) return true;
|
|
1714
|
+
return LIFECYCLE_BOT_LOGINS.has(login.toLowerCase());
|
|
1715
|
+
}
|
|
1716
|
+
async function fetchPRLifecycle(prUrl, token, signal, governor) {
|
|
1717
|
+
const ref = parseGitHubRef(prUrl);
|
|
1718
|
+
if (!ref || ref.kind !== "pull") return null;
|
|
1719
|
+
const { owner, repo, number } = ref;
|
|
1720
|
+
const sig = signal ?? AbortSignal.timeout(1e4);
|
|
1721
|
+
const gov = makeScoringGovernor(governor);
|
|
1722
|
+
if (gov.tripped() || gov.budgetExhausted()) return null;
|
|
1723
|
+
let pr;
|
|
1724
|
+
try {
|
|
1725
|
+
pr = await ghFetch(`/repos/${owner}/${repo}/pulls/${number}`, token, sig);
|
|
1726
|
+
} catch {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
const authorId = pr.user?.id ?? null;
|
|
1730
|
+
const events = [];
|
|
1731
|
+
const complete = { reviews: false, comments: false, commits: false };
|
|
1732
|
+
if (pr.created_at) {
|
|
1733
|
+
events.push({
|
|
1734
|
+
event_type: "pr_opened",
|
|
1735
|
+
source_id: `pr-${number}`,
|
|
1736
|
+
actor_id: authorId ?? 0,
|
|
1737
|
+
actor_assoc: "",
|
|
1738
|
+
is_author: true,
|
|
1739
|
+
is_bot: false,
|
|
1740
|
+
occurred_at: pr.created_at
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1744
|
+
try {
|
|
1745
|
+
const reviews = await ghFetch(
|
|
1746
|
+
`/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
|
|
1747
|
+
token,
|
|
1748
|
+
sig
|
|
1749
|
+
);
|
|
1750
|
+
for (const r of reviews) {
|
|
1751
|
+
if (r.state === "PENDING" || !r.submitted_at) continue;
|
|
1752
|
+
const aid = r.user?.id ?? 0;
|
|
1753
|
+
events.push({
|
|
1754
|
+
event_type: "review_submitted",
|
|
1755
|
+
source_id: `review-${r.id}`,
|
|
1756
|
+
actor_id: aid,
|
|
1757
|
+
actor_assoc: r.author_association ?? "",
|
|
1758
|
+
is_author: authorId != null && aid === authorId,
|
|
1759
|
+
is_bot: isLifecycleBot(r.user),
|
|
1760
|
+
occurred_at: r.submitted_at
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
complete.reviews = true;
|
|
1764
|
+
} catch {
|
|
1765
|
+
complete.reviews = false;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1769
|
+
try {
|
|
1770
|
+
const comments = await ghFetch(
|
|
1771
|
+
`/repos/${owner}/${repo}/issues/${number}/comments?per_page=100`,
|
|
1772
|
+
token,
|
|
1773
|
+
sig
|
|
1774
|
+
);
|
|
1775
|
+
for (const c of comments) {
|
|
1776
|
+
if (!c.created_at) continue;
|
|
1777
|
+
const aid = c.user?.id ?? 0;
|
|
1778
|
+
events.push({
|
|
1779
|
+
event_type: "issue_comment",
|
|
1780
|
+
source_id: `comment-${c.id}`,
|
|
1781
|
+
actor_id: aid,
|
|
1782
|
+
actor_assoc: c.author_association ?? "",
|
|
1783
|
+
is_author: authorId != null && aid === authorId,
|
|
1784
|
+
is_bot: isLifecycleBot(c.user),
|
|
1785
|
+
occurred_at: c.created_at
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
complete.comments = true;
|
|
1789
|
+
} catch {
|
|
1790
|
+
complete.comments = false;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1794
|
+
try {
|
|
1795
|
+
const commits = await ghFetch(
|
|
1796
|
+
`/repos/${owner}/${repo}/pulls/${number}/commits?per_page=100`,
|
|
1797
|
+
token,
|
|
1798
|
+
sig
|
|
1799
|
+
);
|
|
1800
|
+
for (const cm of commits) {
|
|
1801
|
+
const when = cm.commit?.author?.date ?? null;
|
|
1802
|
+
if (!when) continue;
|
|
1803
|
+
const aid = cm.author?.id ?? authorId ?? 0;
|
|
1804
|
+
events.push({
|
|
1805
|
+
event_type: "commit_pushed",
|
|
1806
|
+
source_id: `commit-${cm.sha}`,
|
|
1807
|
+
actor_id: aid,
|
|
1808
|
+
actor_assoc: "",
|
|
1809
|
+
is_author: authorId != null && aid === authorId,
|
|
1810
|
+
is_bot: isLifecycleBot(cm.author),
|
|
1811
|
+
occurred_at: when
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
complete.commits = true;
|
|
1815
|
+
} catch {
|
|
1816
|
+
complete.commits = false;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
if (pr.merged && pr.merged_at) {
|
|
1820
|
+
events.push({
|
|
1821
|
+
event_type: "pr_merged",
|
|
1822
|
+
source_id: `merged-${number}`,
|
|
1823
|
+
actor_id: pr.merged_by?.id ?? 0,
|
|
1824
|
+
actor_assoc: "",
|
|
1825
|
+
is_author: authorId != null && pr.merged_by?.id === authorId,
|
|
1826
|
+
// A bot merger (mergify[bot], a merge queue, etc.) must NOT count as an
|
|
1827
|
+
// independent human counterparty. Classify it like every other actor (§4b V4).
|
|
1828
|
+
is_bot: isLifecycleBot(pr.merged_by),
|
|
1829
|
+
occurred_at: pr.merged_at
|
|
1830
|
+
});
|
|
1831
|
+
} else if (pr.state === "closed" && pr.closed_at) {
|
|
1832
|
+
events.push({
|
|
1833
|
+
event_type: "pr_closed_unmerged",
|
|
1834
|
+
source_id: `closed-${number}`,
|
|
1835
|
+
actor_id: 0,
|
|
1836
|
+
actor_assoc: "",
|
|
1837
|
+
is_author: false,
|
|
1838
|
+
is_bot: false,
|
|
1839
|
+
occurred_at: pr.closed_at
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
return {
|
|
1843
|
+
prNumber: number,
|
|
1844
|
+
prUrl: pr.html_url,
|
|
1845
|
+
openedAt: pr.created_at ?? null,
|
|
1846
|
+
merged: pr.merged === true,
|
|
1847
|
+
mergedAt: pr.merged_at ?? null,
|
|
1848
|
+
closedUnmergedAt: !pr.merged && pr.state === "closed" ? pr.closed_at ?? null : null,
|
|
1849
|
+
authorId,
|
|
1850
|
+
events,
|
|
1851
|
+
complete
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
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, LIFECYCLE_BOT_LOGINS;
|
|
1710
1855
|
var init_github = __esm({
|
|
1711
1856
|
"../../packages/core/src/github.ts"() {
|
|
1712
1857
|
"use strict";
|
|
@@ -1726,6 +1871,20 @@ var init_github = __esm({
|
|
|
1726
1871
|
RECEPTIVITY_RECENCY_DAYS = 180;
|
|
1727
1872
|
RECEPTIVITY_RECENCY_FLOOR = 0.1;
|
|
1728
1873
|
GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
|
|
1874
|
+
LIFECYCLE_BOT_LOGINS = /* @__PURE__ */ new Set([
|
|
1875
|
+
"mergify",
|
|
1876
|
+
"mergify[bot]",
|
|
1877
|
+
"bors",
|
|
1878
|
+
"bors[bot]",
|
|
1879
|
+
"kodiakhq",
|
|
1880
|
+
"kodiakhq[bot]",
|
|
1881
|
+
"dependabot",
|
|
1882
|
+
"dependabot[bot]",
|
|
1883
|
+
"renovate",
|
|
1884
|
+
"renovate[bot]",
|
|
1885
|
+
"github-actions",
|
|
1886
|
+
"github-actions[bot]"
|
|
1887
|
+
]);
|
|
1729
1888
|
}
|
|
1730
1889
|
});
|
|
1731
1890
|
|
|
@@ -7807,6 +7966,7 @@ __export(src_exports, {
|
|
|
7807
7966
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
7808
7967
|
fetchOpenExternalPRs: () => fetchOpenExternalPRs,
|
|
7809
7968
|
fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
|
|
7969
|
+
fetchPRLifecycle: () => fetchPRLifecycle,
|
|
7810
7970
|
fetchPRScoringFacts: () => fetchPRScoringFacts,
|
|
7811
7971
|
fetchRepoRecency: () => fetchRepoRecency,
|
|
7812
7972
|
fetchRepoReceptivity: () => fetchRepoReceptivity,
|
package/dist/bin/jpi-claim.js
CHANGED
|
@@ -1706,7 +1706,152 @@ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
|
|
|
1706
1706
|
fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1707
1707
|
};
|
|
1708
1708
|
}
|
|
1709
|
-
|
|
1709
|
+
function isLifecycleBot(actor) {
|
|
1710
|
+
if (!actor) return false;
|
|
1711
|
+
if (actor.type === "Bot") return true;
|
|
1712
|
+
const login = String(actor.login ?? "");
|
|
1713
|
+
if (/\[bot\]$/i.test(login)) return true;
|
|
1714
|
+
return LIFECYCLE_BOT_LOGINS.has(login.toLowerCase());
|
|
1715
|
+
}
|
|
1716
|
+
async function fetchPRLifecycle(prUrl, token, signal, governor) {
|
|
1717
|
+
const ref = parseGitHubRef(prUrl);
|
|
1718
|
+
if (!ref || ref.kind !== "pull") return null;
|
|
1719
|
+
const { owner, repo, number } = ref;
|
|
1720
|
+
const sig = signal ?? AbortSignal.timeout(1e4);
|
|
1721
|
+
const gov = makeScoringGovernor(governor);
|
|
1722
|
+
if (gov.tripped() || gov.budgetExhausted()) return null;
|
|
1723
|
+
let pr;
|
|
1724
|
+
try {
|
|
1725
|
+
pr = await ghFetch(`/repos/${owner}/${repo}/pulls/${number}`, token, sig);
|
|
1726
|
+
} catch {
|
|
1727
|
+
return null;
|
|
1728
|
+
}
|
|
1729
|
+
const authorId = pr.user?.id ?? null;
|
|
1730
|
+
const events = [];
|
|
1731
|
+
const complete = { reviews: false, comments: false, commits: false };
|
|
1732
|
+
if (pr.created_at) {
|
|
1733
|
+
events.push({
|
|
1734
|
+
event_type: "pr_opened",
|
|
1735
|
+
source_id: `pr-${number}`,
|
|
1736
|
+
actor_id: authorId ?? 0,
|
|
1737
|
+
actor_assoc: "",
|
|
1738
|
+
is_author: true,
|
|
1739
|
+
is_bot: false,
|
|
1740
|
+
occurred_at: pr.created_at
|
|
1741
|
+
});
|
|
1742
|
+
}
|
|
1743
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1744
|
+
try {
|
|
1745
|
+
const reviews = await ghFetch(
|
|
1746
|
+
`/repos/${owner}/${repo}/pulls/${number}/reviews?per_page=100`,
|
|
1747
|
+
token,
|
|
1748
|
+
sig
|
|
1749
|
+
);
|
|
1750
|
+
for (const r of reviews) {
|
|
1751
|
+
if (r.state === "PENDING" || !r.submitted_at) continue;
|
|
1752
|
+
const aid = r.user?.id ?? 0;
|
|
1753
|
+
events.push({
|
|
1754
|
+
event_type: "review_submitted",
|
|
1755
|
+
source_id: `review-${r.id}`,
|
|
1756
|
+
actor_id: aid,
|
|
1757
|
+
actor_assoc: r.author_association ?? "",
|
|
1758
|
+
is_author: authorId != null && aid === authorId,
|
|
1759
|
+
is_bot: isLifecycleBot(r.user),
|
|
1760
|
+
occurred_at: r.submitted_at
|
|
1761
|
+
});
|
|
1762
|
+
}
|
|
1763
|
+
complete.reviews = true;
|
|
1764
|
+
} catch {
|
|
1765
|
+
complete.reviews = false;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1769
|
+
try {
|
|
1770
|
+
const comments = await ghFetch(
|
|
1771
|
+
`/repos/${owner}/${repo}/issues/${number}/comments?per_page=100`,
|
|
1772
|
+
token,
|
|
1773
|
+
sig
|
|
1774
|
+
);
|
|
1775
|
+
for (const c of comments) {
|
|
1776
|
+
if (!c.created_at) continue;
|
|
1777
|
+
const aid = c.user?.id ?? 0;
|
|
1778
|
+
events.push({
|
|
1779
|
+
event_type: "issue_comment",
|
|
1780
|
+
source_id: `comment-${c.id}`,
|
|
1781
|
+
actor_id: aid,
|
|
1782
|
+
actor_assoc: c.author_association ?? "",
|
|
1783
|
+
is_author: authorId != null && aid === authorId,
|
|
1784
|
+
is_bot: isLifecycleBot(c.user),
|
|
1785
|
+
occurred_at: c.created_at
|
|
1786
|
+
});
|
|
1787
|
+
}
|
|
1788
|
+
complete.comments = true;
|
|
1789
|
+
} catch {
|
|
1790
|
+
complete.comments = false;
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (!gov.tripped() && !gov.budgetExhausted()) {
|
|
1794
|
+
try {
|
|
1795
|
+
const commits = await ghFetch(
|
|
1796
|
+
`/repos/${owner}/${repo}/pulls/${number}/commits?per_page=100`,
|
|
1797
|
+
token,
|
|
1798
|
+
sig
|
|
1799
|
+
);
|
|
1800
|
+
for (const cm of commits) {
|
|
1801
|
+
const when = cm.commit?.author?.date ?? null;
|
|
1802
|
+
if (!when) continue;
|
|
1803
|
+
const aid = cm.author?.id ?? authorId ?? 0;
|
|
1804
|
+
events.push({
|
|
1805
|
+
event_type: "commit_pushed",
|
|
1806
|
+
source_id: `commit-${cm.sha}`,
|
|
1807
|
+
actor_id: aid,
|
|
1808
|
+
actor_assoc: "",
|
|
1809
|
+
is_author: authorId != null && aid === authorId,
|
|
1810
|
+
is_bot: isLifecycleBot(cm.author),
|
|
1811
|
+
occurred_at: when
|
|
1812
|
+
});
|
|
1813
|
+
}
|
|
1814
|
+
complete.commits = true;
|
|
1815
|
+
} catch {
|
|
1816
|
+
complete.commits = false;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
if (pr.merged && pr.merged_at) {
|
|
1820
|
+
events.push({
|
|
1821
|
+
event_type: "pr_merged",
|
|
1822
|
+
source_id: `merged-${number}`,
|
|
1823
|
+
actor_id: pr.merged_by?.id ?? 0,
|
|
1824
|
+
actor_assoc: "",
|
|
1825
|
+
is_author: authorId != null && pr.merged_by?.id === authorId,
|
|
1826
|
+
// A bot merger (mergify[bot], a merge queue, etc.) must NOT count as an
|
|
1827
|
+
// independent human counterparty. Classify it like every other actor (§4b V4).
|
|
1828
|
+
is_bot: isLifecycleBot(pr.merged_by),
|
|
1829
|
+
occurred_at: pr.merged_at
|
|
1830
|
+
});
|
|
1831
|
+
} else if (pr.state === "closed" && pr.closed_at) {
|
|
1832
|
+
events.push({
|
|
1833
|
+
event_type: "pr_closed_unmerged",
|
|
1834
|
+
source_id: `closed-${number}`,
|
|
1835
|
+
actor_id: 0,
|
|
1836
|
+
actor_assoc: "",
|
|
1837
|
+
is_author: false,
|
|
1838
|
+
is_bot: false,
|
|
1839
|
+
occurred_at: pr.closed_at
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
return {
|
|
1843
|
+
prNumber: number,
|
|
1844
|
+
prUrl: pr.html_url,
|
|
1845
|
+
openedAt: pr.created_at ?? null,
|
|
1846
|
+
merged: pr.merged === true,
|
|
1847
|
+
mergedAt: pr.merged_at ?? null,
|
|
1848
|
+
closedUnmergedAt: !pr.merged && pr.state === "closed" ? pr.closed_at ?? null : null,
|
|
1849
|
+
authorId,
|
|
1850
|
+
events,
|
|
1851
|
+
complete
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
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, LIFECYCLE_BOT_LOGINS;
|
|
1710
1855
|
var init_github = __esm({
|
|
1711
1856
|
"../../packages/core/src/github.ts"() {
|
|
1712
1857
|
"use strict";
|
|
@@ -1726,6 +1871,20 @@ var init_github = __esm({
|
|
|
1726
1871
|
RECEPTIVITY_RECENCY_DAYS = 180;
|
|
1727
1872
|
RECEPTIVITY_RECENCY_FLOOR = 0.1;
|
|
1728
1873
|
GITHUB_GRAPHQL_URL = "https://api.github.com/graphql";
|
|
1874
|
+
LIFECYCLE_BOT_LOGINS = /* @__PURE__ */ new Set([
|
|
1875
|
+
"mergify",
|
|
1876
|
+
"mergify[bot]",
|
|
1877
|
+
"bors",
|
|
1878
|
+
"bors[bot]",
|
|
1879
|
+
"kodiakhq",
|
|
1880
|
+
"kodiakhq[bot]",
|
|
1881
|
+
"dependabot",
|
|
1882
|
+
"dependabot[bot]",
|
|
1883
|
+
"renovate",
|
|
1884
|
+
"renovate[bot]",
|
|
1885
|
+
"github-actions",
|
|
1886
|
+
"github-actions[bot]"
|
|
1887
|
+
]);
|
|
1729
1888
|
}
|
|
1730
1889
|
});
|
|
1731
1890
|
|
|
@@ -7807,6 +7966,7 @@ __export(src_exports, {
|
|
|
7807
7966
|
fetchGitHubProfile: () => fetchGitHubProfile,
|
|
7808
7967
|
fetchOpenExternalPRs: () => fetchOpenExternalPRs,
|
|
7809
7968
|
fetchOwnedRepoTraction: () => fetchOwnedRepoTraction,
|
|
7969
|
+
fetchPRLifecycle: () => fetchPRLifecycle,
|
|
7810
7970
|
fetchPRScoringFacts: () => fetchPRScoringFacts,
|
|
7811
7971
|
fetchRepoRecency: () => fetchRepoRecency,
|
|
7812
7972
|
fetchRepoReceptivity: () => fetchRepoReceptivity,
|
|
@@ -8523,13 +8683,153 @@ var init_profile = __esm({
|
|
|
8523
8683
|
}
|
|
8524
8684
|
});
|
|
8525
8685
|
|
|
8686
|
+
// src/reputation/identity.ts
|
|
8687
|
+
var identity_exports = {};
|
|
8688
|
+
__export(identity_exports, {
|
|
8689
|
+
resolveCallerIdentity: () => resolveCallerIdentity
|
|
8690
|
+
});
|
|
8691
|
+
import { execFile } from "child_process";
|
|
8692
|
+
import { promisify } from "util";
|
|
8693
|
+
async function resolveCallerIdentity() {
|
|
8694
|
+
try {
|
|
8695
|
+
const { stdout } = await execFileAsync("gh", ["api", "user"], { timeout: 1e4 });
|
|
8696
|
+
const u = JSON.parse(stdout);
|
|
8697
|
+
if (typeof u.id === "number" && typeof u.login === "string" && u.login.length > 0) {
|
|
8698
|
+
return { id: u.id, login: u.login };
|
|
8699
|
+
}
|
|
8700
|
+
return null;
|
|
8701
|
+
} catch {
|
|
8702
|
+
return null;
|
|
8703
|
+
}
|
|
8704
|
+
}
|
|
8705
|
+
var execFileAsync;
|
|
8706
|
+
var init_identity = __esm({
|
|
8707
|
+
"src/reputation/identity.ts"() {
|
|
8708
|
+
"use strict";
|
|
8709
|
+
execFileAsync = promisify(execFile);
|
|
8710
|
+
}
|
|
8711
|
+
});
|
|
8712
|
+
|
|
8713
|
+
// src/reputation/fetch.ts
|
|
8714
|
+
var fetch_exports = {};
|
|
8715
|
+
__export(fetch_exports, {
|
|
8716
|
+
gatherAudit: () => gatherAudit
|
|
8717
|
+
});
|
|
8718
|
+
async function gatherAudit(prUrl, token) {
|
|
8719
|
+
const signal = AbortSignal.timeout(2e4);
|
|
8720
|
+
const governor = makeScoringGovernor();
|
|
8721
|
+
const [lifecycle, facts] = await Promise.all([
|
|
8722
|
+
fetchPRLifecycle(prUrl, token, signal, governor),
|
|
8723
|
+
fetchPRScoringFacts(prUrl, token, signal, governor)
|
|
8724
|
+
]);
|
|
8725
|
+
return { lifecycle, facts };
|
|
8726
|
+
}
|
|
8727
|
+
var init_fetch = __esm({
|
|
8728
|
+
"src/reputation/fetch.ts"() {
|
|
8729
|
+
"use strict";
|
|
8730
|
+
init_src();
|
|
8731
|
+
}
|
|
8732
|
+
});
|
|
8733
|
+
|
|
8734
|
+
// src/reputation/audit.ts
|
|
8735
|
+
var audit_exports = {};
|
|
8736
|
+
__export(audit_exports, {
|
|
8737
|
+
buildAuditView: () => buildAuditView
|
|
8738
|
+
});
|
|
8739
|
+
function roleOf(e) {
|
|
8740
|
+
if (e.is_bot) return "bot";
|
|
8741
|
+
if (e.is_author) return "you";
|
|
8742
|
+
const a = String(e.actor_assoc || "").toUpperCase();
|
|
8743
|
+
if (MAINTAINER_ASSOC.has(a)) return "maintainer";
|
|
8744
|
+
if (a === "CONTRIBUTOR") return "contributor";
|
|
8745
|
+
if (a === "NONE") return "outside";
|
|
8746
|
+
return "unknown";
|
|
8747
|
+
}
|
|
8748
|
+
function isMaintainerResponse(e) {
|
|
8749
|
+
return !e.is_author && !e.is_bot && MAINTAINER_ASSOC.has(String(e.actor_assoc || "").toUpperCase());
|
|
8750
|
+
}
|
|
8751
|
+
function byTime(a, b) {
|
|
8752
|
+
if (a.occurred_at < b.occurred_at) return -1;
|
|
8753
|
+
if (a.occurred_at > b.occurred_at) return 1;
|
|
8754
|
+
if (a.source_id < b.source_id) return -1;
|
|
8755
|
+
if (a.source_id > b.source_id) return 1;
|
|
8756
|
+
return 0;
|
|
8757
|
+
}
|
|
8758
|
+
function hoursBetween(a, b) {
|
|
8759
|
+
if (!a || !b) return null;
|
|
8760
|
+
const ta = Date.parse(a);
|
|
8761
|
+
const tb = Date.parse(b);
|
|
8762
|
+
if (Number.isNaN(ta) || Number.isNaN(tb)) return null;
|
|
8763
|
+
return Math.round((tb - ta) / 36e5 * 10) / 10;
|
|
8764
|
+
}
|
|
8765
|
+
function buildAuditView(lifecycle, facts) {
|
|
8766
|
+
const events = [...lifecycle.events].sort(byTime);
|
|
8767
|
+
const counterpartyIds = /* @__PURE__ */ new Set();
|
|
8768
|
+
for (const e of events) {
|
|
8769
|
+
if (!e.is_author && !e.is_bot && e.actor_id !== 0) counterpartyIds.add(e.actor_id);
|
|
8770
|
+
}
|
|
8771
|
+
const firstResponse = events.find(isMaintainerResponse) ?? null;
|
|
8772
|
+
const firstResponseAt = firstResponse?.occurred_at ?? null;
|
|
8773
|
+
const maintainerReviews = events.filter(
|
|
8774
|
+
(e) => e.event_type === "review_submitted" && isMaintainerResponse(e)
|
|
8775
|
+
).length;
|
|
8776
|
+
const iterationsAfterFirstResponse = firstResponseAt ? events.filter(
|
|
8777
|
+
(e) => e.event_type === "commit_pushed" && e.is_author && e.occurred_at > firstResponseAt
|
|
8778
|
+
).length : 0;
|
|
8779
|
+
const outcome = lifecycle.merged ? "merged" : lifecycle.closedUnmergedAt ? "closed_unmerged" : "open";
|
|
8780
|
+
const signals = {
|
|
8781
|
+
distinctCounterparties: counterpartyIds.size,
|
|
8782
|
+
maintainerReviews,
|
|
8783
|
+
iterationsAfterFirstResponse,
|
|
8784
|
+
hoursToFirstResponse: hoursBetween(lifecycle.openedAt, firstResponseAt),
|
|
8785
|
+
hoursToMerge: hoursBetween(lifecycle.openedAt, lifecycle.mergedAt)
|
|
8786
|
+
};
|
|
8787
|
+
const timeline = events.map((e) => ({
|
|
8788
|
+
at: e.occurred_at,
|
|
8789
|
+
event: e.event_type,
|
|
8790
|
+
role: roleOf(e)
|
|
8791
|
+
}));
|
|
8792
|
+
const notes = [];
|
|
8793
|
+
if (!lifecycle.complete.reviews) notes.push("Review data incomplete \u2014 review counts may undercount.");
|
|
8794
|
+
if (!lifecycle.complete.comments) notes.push("Comment data incomplete \u2014 response detection may be partial.");
|
|
8795
|
+
if (!lifecycle.complete.commits) notes.push("Commit data incomplete \u2014 iteration count may undercount.");
|
|
8796
|
+
if (lifecycle.authorId == null) notes.push("PR author identity unavailable \u2014 author/counterparty split may be imprecise.");
|
|
8797
|
+
return {
|
|
8798
|
+
repo: facts?.repo ?? "",
|
|
8799
|
+
prNumber: lifecycle.prNumber,
|
|
8800
|
+
prUrl: lifecycle.prUrl,
|
|
8801
|
+
openedAt: lifecycle.openedAt,
|
|
8802
|
+
outcome,
|
|
8803
|
+
mergedAt: lifecycle.mergedAt,
|
|
8804
|
+
closedUnmergedAt: lifecycle.closedUnmergedAt,
|
|
8805
|
+
mergedByLogin: facts?.mergedByLogin ?? null,
|
|
8806
|
+
signals,
|
|
8807
|
+
timeline,
|
|
8808
|
+
completeness: { ...lifecycle.complete },
|
|
8809
|
+
notes,
|
|
8810
|
+
limitations: [...AUDIT_LIMITATIONS]
|
|
8811
|
+
};
|
|
8812
|
+
}
|
|
8813
|
+
var MAINTAINER_ASSOC, AUDIT_LIMITATIONS;
|
|
8814
|
+
var init_audit = __esm({
|
|
8815
|
+
"src/reputation/audit.ts"() {
|
|
8816
|
+
"use strict";
|
|
8817
|
+
MAINTAINER_ASSOC = /* @__PURE__ */ new Set(["OWNER", "MEMBER", "COLLABORATOR"]);
|
|
8818
|
+
AUDIT_LIMITATIONS = [
|
|
8819
|
+
"Post-merge bug/revert rate \u2014 longitudinal; it only shows up after the merge this audit stops at.",
|
|
8820
|
+
"Downstream adoption / real-world impact \u2014 a repo-level, slow signal not visible in one PR timeline.",
|
|
8821
|
+
"Reviewer satisfaction and collaboration quality \u2014 not observable from public timeline events."
|
|
8822
|
+
];
|
|
8823
|
+
}
|
|
8824
|
+
});
|
|
8825
|
+
|
|
8526
8826
|
// bin/jpi-claim.js
|
|
8527
8827
|
init_src();
|
|
8528
8828
|
import { readFileSync as readFileSync7, writeFileSync as writeFileSync6, mkdirSync as mkdirSync6, existsSync as existsSync6, rmSync as rmSync3 } from "fs";
|
|
8529
8829
|
import { join as join7 } from "path";
|
|
8530
8830
|
import { homedir as homedir6, hostname as osHostname } from "os";
|
|
8531
|
-
import { execFile } from "child_process";
|
|
8532
|
-
import { promisify } from "util";
|
|
8831
|
+
import { execFile as execFile2 } from "child_process";
|
|
8832
|
+
import { promisify as promisify2 } from "util";
|
|
8533
8833
|
import { createInterface } from "readline";
|
|
8534
8834
|
|
|
8535
8835
|
// src/open-url.js
|
|
@@ -8722,7 +9022,7 @@ function printPolicySection(policy) {
|
|
|
8722
9022
|
console.log(" policy: no AI-assistance policy language detected in CONTRIBUTING/PR-template/AGENTS docs");
|
|
8723
9023
|
}
|
|
8724
9024
|
}
|
|
8725
|
-
var pExecFile =
|
|
9025
|
+
var pExecFile = promisify2(execFile2);
|
|
8726
9026
|
async function sh(cmd, args, opts = {}) {
|
|
8727
9027
|
const { stdout } = await pExecFile(cmd, args, { ...opts, shell: false, maxBuffer: 16 * 1024 * 1024 });
|
|
8728
9028
|
return String(stdout).trim();
|
|
@@ -10253,6 +10553,100 @@ async function cmdRevoke() {
|
|
|
10253
10553
|
console.log("\n \u2713 Pushed claims deleted and local marker cleared.\n");
|
|
10254
10554
|
console.log(" Background updates (if any) have been stopped.\n");
|
|
10255
10555
|
}
|
|
10556
|
+
function renderAudit(view) {
|
|
10557
|
+
const roleLabel = {
|
|
10558
|
+
you: "you",
|
|
10559
|
+
maintainer: "maintainer",
|
|
10560
|
+
contributor: "contributor",
|
|
10561
|
+
outside: "outside",
|
|
10562
|
+
bot: "bot",
|
|
10563
|
+
unknown: "\u2014"
|
|
10564
|
+
};
|
|
10565
|
+
const eventLabel = {
|
|
10566
|
+
pr_opened: "opened PR",
|
|
10567
|
+
review_submitted: "reviewed",
|
|
10568
|
+
issue_comment: "commented",
|
|
10569
|
+
commit_pushed: "pushed commit",
|
|
10570
|
+
pr_merged: "merged",
|
|
10571
|
+
pr_closed_unmerged: "closed (unmerged)"
|
|
10572
|
+
};
|
|
10573
|
+
const outcomeLabel = {
|
|
10574
|
+
merged: "\u2714 merged",
|
|
10575
|
+
closed_unmerged: "\u2715 closed without merge",
|
|
10576
|
+
open: "\u2026 still open"
|
|
10577
|
+
};
|
|
10578
|
+
const hrs = (h) => h == null ? "\u2014" : `${h}h`;
|
|
10579
|
+
console.log(`
|
|
10580
|
+
Audit \u2014 ${view.repo || "repo"} #${view.prNumber}`);
|
|
10581
|
+
console.log(` ${view.prUrl}`);
|
|
10582
|
+
console.log(` Outcome: ${outcomeLabel[view.outcome] ?? view.outcome}` + (view.mergedByLogin ? ` (merged by @${view.mergedByLogin})` : ""));
|
|
10583
|
+
console.log("\n Engagement (raw counts \u2014 not a score):");
|
|
10584
|
+
console.log(` Independent counterparties engaged : ${view.signals.distinctCounterparties}`);
|
|
10585
|
+
console.log(` Maintainer reviews : ${view.signals.maintainerReviews}`);
|
|
10586
|
+
console.log(` Your commits after first response : ${view.signals.iterationsAfterFirstResponse}`);
|
|
10587
|
+
console.log(` Time to first maintainer response : ${hrs(view.signals.hoursToFirstResponse)}`);
|
|
10588
|
+
console.log(` Time to merge : ${hrs(view.signals.hoursToMerge)}`);
|
|
10589
|
+
console.log("\n Timeline:");
|
|
10590
|
+
if (view.timeline.length === 0) {
|
|
10591
|
+
console.log(" (no events captured)");
|
|
10592
|
+
} else {
|
|
10593
|
+
for (const e of view.timeline) {
|
|
10594
|
+
console.log(` ${e.at} ${roleLabel[e.role] ?? e.role} ${eventLabel[e.event] ?? e.event}`);
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
if (view.notes.length > 0) {
|
|
10598
|
+
console.log("\n Notes:");
|
|
10599
|
+
for (const n of view.notes) console.log(` \u26A0 ${n}`);
|
|
10600
|
+
}
|
|
10601
|
+
console.log("\n What this does NOT measure:");
|
|
10602
|
+
for (const l of view.limitations || []) console.log(` \xB7 ${l}`);
|
|
10603
|
+
console.log("");
|
|
10604
|
+
}
|
|
10605
|
+
async function cmdAudit(id, flags = {}) {
|
|
10606
|
+
if (!id) {
|
|
10607
|
+
console.error("terminalhire claim audit: needs a claim id. Try `terminalhire claim list`.");
|
|
10608
|
+
process.exit(1);
|
|
10609
|
+
}
|
|
10610
|
+
const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
|
|
10611
|
+
const claim = claims.findClaim(id);
|
|
10612
|
+
if (!claim) {
|
|
10613
|
+
console.error(`No claim with id '${id}'.`);
|
|
10614
|
+
process.exit(1);
|
|
10615
|
+
}
|
|
10616
|
+
if (!claim.prUrl) {
|
|
10617
|
+
console.log(`Claim '${id}' has no submitted PR yet \u2014 nothing to audit. Submit first: terminalhire claim submit ${id}`);
|
|
10618
|
+
return;
|
|
10619
|
+
}
|
|
10620
|
+
const { resolveCallerIdentity: resolveCallerIdentity2 } = await Promise.resolve().then(() => (init_identity(), identity_exports));
|
|
10621
|
+
const identity = await resolveCallerIdentity2();
|
|
10622
|
+
if (!identity) {
|
|
10623
|
+
console.error("terminalhire claim audit: could not verify your GitHub identity (`gh api user`). Run `gh auth login` and retry.");
|
|
10624
|
+
process.exit(1);
|
|
10625
|
+
}
|
|
10626
|
+
let token;
|
|
10627
|
+
try {
|
|
10628
|
+
token = await sh("gh", ["auth", "token"]);
|
|
10629
|
+
} catch {
|
|
10630
|
+
token = void 0;
|
|
10631
|
+
}
|
|
10632
|
+
const { gatherAudit: gatherAudit2 } = await Promise.resolve().then(() => (init_fetch(), fetch_exports));
|
|
10633
|
+
const { lifecycle, facts } = await gatherAudit2(claim.prUrl, token || void 0);
|
|
10634
|
+
if (!lifecycle) {
|
|
10635
|
+
console.error(`terminalhire claim audit: could not fetch the PR lifecycle for ${claim.prUrl} (private, deleted, rate-limited, or not a PR).`);
|
|
10636
|
+
process.exit(1);
|
|
10637
|
+
}
|
|
10638
|
+
if (lifecycle.authorId !== identity.id) {
|
|
10639
|
+
console.error(`terminalhire claim audit: PR ${claim.prUrl} was not authored by you (@${identity.login}); the audit only covers your own PRs.`);
|
|
10640
|
+
process.exit(1);
|
|
10641
|
+
}
|
|
10642
|
+
const { buildAuditView: buildAuditView2 } = await Promise.resolve().then(() => (init_audit(), audit_exports));
|
|
10643
|
+
const view = buildAuditView2(lifecycle, facts);
|
|
10644
|
+
if (flags.json) {
|
|
10645
|
+
console.log(JSON.stringify(view, null, 2));
|
|
10646
|
+
return;
|
|
10647
|
+
}
|
|
10648
|
+
renderAudit(view);
|
|
10649
|
+
}
|
|
10256
10650
|
async function run() {
|
|
10257
10651
|
const verb = process.argv[2];
|
|
10258
10652
|
const rawArgs = process.argv.slice(3).map((a) => a === "-y" ? "--yes" : a);
|
|
@@ -10303,8 +10697,11 @@ async function run() {
|
|
|
10303
10697
|
case "release":
|
|
10304
10698
|
await cmdRelease(positional[0]);
|
|
10305
10699
|
break;
|
|
10700
|
+
case "audit":
|
|
10701
|
+
await cmdAudit(positional[0], flags);
|
|
10702
|
+
break;
|
|
10306
10703
|
default:
|
|
10307
|
-
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | release`);
|
|
10704
|
+
console.error(`terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | list | status | update | submit | audit | release`);
|
|
10308
10705
|
process.exit(1);
|
|
10309
10706
|
}
|
|
10310
10707
|
} catch (err) {
|