terminalhire 0.28.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.
@@ -285,6 +285,7 @@ function decrypt(blob, key) {
285
285
  var TERMINALHIRE_DIR3 = process.env.TERMINALHIRE_DIR || join3(homedir3(), ".terminalhire");
286
286
  var CLAIM_PUSH_AUTO_MARKER = join3(TERMINALHIRE_DIR3, "claim-push-auto.json");
287
287
  var CLAIM_PUSH_TOKEN_FILE = join3(TERMINALHIRE_DIR3, "claim-push-token.enc");
288
+ var CLAIM_PUSH_MANUAL_MARKER = join3(TERMINALHIRE_DIR3, "claim-push.json");
288
289
  var CLAIM_SYNC_BASE = "https://terminalhire.com";
289
290
  var AUTO_CONSENT_VERSION = 2;
290
291
  var AUTO_PUSH_THROTTLE_MS = 24 * 60 * 60 * 1e3;
@@ -352,6 +353,43 @@ function backgroundPushGate(params) {
352
353
  }
353
354
  return { push: true, reason: "ok" };
354
355
  }
356
+ function unpushedNudgeGate(params) {
357
+ const {
358
+ autoMarkerExists,
359
+ tokenFileExists,
360
+ manualMarkerExists,
361
+ lastSnapshotHash,
362
+ currentHash,
363
+ claimCount
364
+ } = params;
365
+ if (autoMarkerExists && tokenFileExists) return false;
366
+ if (!manualMarkerExists) return false;
367
+ if (!(claimCount > 0)) return false;
368
+ return lastSnapshotHash !== currentHash;
369
+ }
370
+ async function shouldNudgeUnpushed() {
371
+ try {
372
+ const { listClaims: listClaims2, toPushedClaim: toPushedClaim2 } = await Promise.resolve().then(() => (init_claims(), claims_exports));
373
+ const pushed = listClaims2().map((c) => toPushedClaim2(c));
374
+ const currentHash = computeSnapshotHash(pushed);
375
+ let manual = null;
376
+ try {
377
+ manual = existsSync3(CLAIM_PUSH_MANUAL_MARKER) ? JSON.parse(readFileSync3(CLAIM_PUSH_MANUAL_MARKER, "utf8")) : null;
378
+ } catch {
379
+ manual = null;
380
+ }
381
+ return unpushedNudgeGate({
382
+ autoMarkerExists: existsSync3(CLAIM_PUSH_AUTO_MARKER),
383
+ tokenFileExists: existsSync3(CLAIM_PUSH_TOKEN_FILE),
384
+ manualMarkerExists: !!manual,
385
+ lastSnapshotHash: manual?.lastSnapshotHash ?? null,
386
+ currentHash,
387
+ claimCount: pushed.length
388
+ });
389
+ } catch {
390
+ return false;
391
+ }
392
+ }
355
393
  async function runBackgroundClaimPush({ now = Date.now() } = {}) {
356
394
  try {
357
395
  if (!existsSync3(CLAIM_PUSH_AUTO_MARKER) || !existsSync3(CLAIM_PUSH_TOKEN_FILE)) return;
@@ -396,6 +434,7 @@ export {
396
434
  AUTO_CONSENT_VERSION,
397
435
  AUTO_PUSH_THROTTLE_MS,
398
436
  CLAIM_PUSH_AUTO_MARKER,
437
+ CLAIM_PUSH_MANUAL_MARKER,
399
438
  CLAIM_PUSH_TOKEN_FILE,
400
439
  backgroundPushGate,
401
440
  clearAutoMarker,
@@ -404,6 +443,8 @@ export {
404
443
  readAutoMarker,
405
444
  readPushTokenEnc,
406
445
  runBackgroundClaimPush,
446
+ shouldNudgeUnpushed,
447
+ unpushedNudgeGate,
407
448
  writeAutoMarker,
408
449
  writePushTokenEnc
409
450
  };
@@ -1706,7 +1706,152 @@ async function fetchPRScoringFacts(prUrl, token, signal, governor) {
1706
1706
  fetchedAt: (/* @__PURE__ */ new Date()).toISOString()
1707
1707
  };
1708
1708
  }
1709
- var TRACTION_TOP_N, MAINTAINER_ENRICH_MAX, CANDIDATE_PR_PAGE, MAX_ENRICH_PRS, OPEN_PR_PAGE, TRANSIENT_META_ERROR, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE, RECEPTIVITY_RECENCY_DAYS, RECEPTIVITY_RECENCY_FLOOR, GITHUB_GRAPHQL_URL;
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,