terminalhire 0.12.0 → 0.13.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.
@@ -13,6 +13,9 @@ var __export = (target, all) => {
13
13
  function isBounty(job) {
14
14
  return job.source === "bounty" && job.bounty != null;
15
15
  }
16
+ function isContribution(job) {
17
+ return job.source === "contribute" && job.contribution != null;
18
+ }
16
19
  var init_types = __esm({
17
20
  "../../packages/core/src/types.ts"() {
18
21
  "use strict";
@@ -587,6 +590,21 @@ var init_vocabulary = __esm({
587
590
  }
588
591
  });
589
592
 
593
+ // ../../packages/core/src/feeds/contribution-gate.ts
594
+ function passesContributionGate(input) {
595
+ if (input.contributors === void 0) return false;
596
+ return input.stars >= MIN_STARS && input.contributors >= MIN_CONTRIBUTORS && !TRIVIAL_PR_TITLE.test(input.title) && !input.archived && !input.fork;
597
+ }
598
+ var MIN_STARS, MIN_CONTRIBUTORS, TRIVIAL_PR_TITLE;
599
+ var init_contribution_gate = __esm({
600
+ "../../packages/core/src/feeds/contribution-gate.ts"() {
601
+ "use strict";
602
+ MIN_STARS = 50;
603
+ MIN_CONTRIBUTORS = 10;
604
+ TRIVIAL_PR_TITLE = /^\s*(fix\s+typo|typo\b|update\s+readme|readme\b|docs?:|docs?\(|chore:|chore\(|style:|ci:|build:|bump\b|update\s+dependenc)/i;
605
+ }
606
+ });
607
+
590
608
  // ../../packages/core/src/github.ts
591
609
  function ghHeaders(token) {
592
610
  const headers = {
@@ -934,16 +952,14 @@ function deriveResumeTrend(cred, repoRecency, now = Date.now()) {
934
952
  }
935
953
  return scored.sort((a, b) => b.weight - a.weight).slice(0, 12).map((s) => s.t);
936
954
  }
937
- var TRACTION_TOP_N, MIN_STARS, MIN_CONTRIBUTORS, CANDIDATE_PR_PAGE, TRIVIAL_PR_TITLE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
955
+ var TRACTION_TOP_N, CANDIDATE_PR_PAGE, RESUME_DECAY_HALF_LIFE_MS, RESUME_MIN_SCORE;
938
956
  var init_github = __esm({
939
957
  "../../packages/core/src/github.ts"() {
940
958
  "use strict";
941
959
  init_vocabulary();
960
+ init_contribution_gate();
942
961
  TRACTION_TOP_N = 6;
943
- MIN_STARS = 50;
944
- MIN_CONTRIBUTORS = 10;
945
962
  CANDIDATE_PR_PAGE = 50;
946
- TRIVIAL_PR_TITLE = /^\s*(fix\s+typo|typo\b|update\s+readme|readme\b|docs?:|docs?\(|chore:|chore\(|style:|ci:|build:|bump\b|update\s+dependenc)/i;
947
963
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
948
964
  RESUME_MIN_SCORE = 0.05;
949
965
  }
@@ -1670,13 +1686,18 @@ var init_bounty_gate = __esm({
1670
1686
  "aragon/hack",
1671
1687
  "spacemeshos/app",
1672
1688
  "archestra-ai/archestra",
1673
- "boundlessfi/boundless",
1674
1689
  "ucfopen/Obojobo",
1675
- "widgetti/ipyvolume",
1676
1690
  "moorcheh-ai/memanto",
1677
1691
  "PrismarineJS/mineflayer"
1678
1692
  ];
1679
- BOUNTY_REPO_DENYLIST = ["SecureBananaLabs/bug-bounty"];
1693
+ BOUNTY_REPO_DENYLIST = [
1694
+ "SecureBananaLabs/bug-bounty",
1695
+ // Meta-farm: a bounty PLATFORM whose own issues are an assignment-gated
1696
+ // contributor queue ("please assign me, my chief") — an unsolicited PR won't
1697
+ // merge, so it's not a real claimable bounty. Not structurally derivable from
1698
+ // any fetched field, so it's a manual entry (also dropped from the allowlist).
1699
+ "boundlessfi/boundless"
1700
+ ];
1680
1701
  DENYLIST_LC = new Set(BOUNTY_REPO_DENYLIST.map((r) => r.toLowerCase()));
1681
1702
  MAX_BOUNTIES_PER_REPO = 10;
1682
1703
  MAX_BOUNTIES_PER_DISCOVERED_REPO = 3;
@@ -1746,6 +1767,9 @@ function isBountyIssue(issue) {
1746
1767
  if (labels.some((n) => BOUNTY_LABEL_RE.test(n))) return true;
1747
1768
  return /bounty/i.test(issue.title) && parseAmountUSD(issue.title) != null;
1748
1769
  }
1770
+ function isAssigned(issue) {
1771
+ return !!issue.assignee || (issue.assignees?.length ?? 0) > 0;
1772
+ }
1749
1773
  async function ghJson(path) {
1750
1774
  let res;
1751
1775
  try {
@@ -1798,7 +1822,7 @@ async function fetchRepoBounties(repoFullName) {
1798
1822
  }
1799
1823
  const issues = await ghJson(`/repos/${repoFullName}/issues?state=open&per_page=100`);
1800
1824
  if (!issues) return [];
1801
- const bounties = issues.filter(isBountyIssue).slice(0, MAX_BOUNTIES_PER_REPO);
1825
+ const bounties = issues.filter((i) => isBountyIssue(i) && !isAssigned(i)).slice(0, MAX_BOUNTIES_PER_REPO);
1802
1826
  const owner = repo.owner.login;
1803
1827
  return mapWithConcurrency(bounties, BOUNTY_FETCH_CONCURRENCY, async (issue) => {
1804
1828
  const title = decodeEntities(issue.title).trim();
@@ -1921,6 +1945,7 @@ async function fetchSearchBounties() {
1921
1945
  if (jobs.length >= MAX_SEARCH_BOUNTIES) break;
1922
1946
  const fullName = repoFullNameFromApiUrl(issue.repository_url);
1923
1947
  if (!fullName) continue;
1948
+ if (isAssigned(issue)) continue;
1924
1949
  if ((perRepo.get(fullName) ?? 0) >= MAX_BOUNTIES_PER_REPO) continue;
1925
1950
  const repo = await repoMetaCached(fullName);
1926
1951
  if (!repo) continue;
@@ -2432,6 +2457,7 @@ var init_feeds = __esm({
2432
2457
  init_directory();
2433
2458
  init_bounty_gate();
2434
2459
  init_bounty_gate();
2460
+ init_contribution_gate();
2435
2461
  FEEDS = [greenhouse, ashby, lever, workable, himalayas, wwr, hn];
2436
2462
  GREENHOUSE_SLUGS_BY_TIER = {
2437
2463
  bigco: [
@@ -2542,6 +2568,202 @@ var init_feeds = __esm({
2542
2568
  }
2543
2569
  });
2544
2570
 
2571
+ // ../../packages/core/src/feeds/contributions.ts
2572
+ function authHeaders2() {
2573
+ const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
2574
+ const h = {
2575
+ Accept: "application/vnd.github+json",
2576
+ "User-Agent": "terminalhire",
2577
+ "X-GitHub-Api-Version": "2022-11-28"
2578
+ };
2579
+ if (token) h["Authorization"] = `Bearer ${token}`;
2580
+ return h;
2581
+ }
2582
+ function tokenize4(text) {
2583
+ return text.toLowerCase().replace(/[^a-z0-9.\-+#]/g, " ").split(/\s+/).filter(Boolean);
2584
+ }
2585
+ function labelNames2(labels) {
2586
+ return (labels ?? []).map((l) => typeof l === "string" ? l : l.name ?? "").filter(Boolean);
2587
+ }
2588
+ function repoFullNameFromApiUrl2(url) {
2589
+ const m = url.match(/\/repos\/([^/]+)\/([^/]+)\/?$/);
2590
+ return m ? `${m[1]}/${m[2]}` : null;
2591
+ }
2592
+ function makeClient(fetchImpl) {
2593
+ async function raw(path) {
2594
+ try {
2595
+ return await fetchImpl(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
2596
+ } catch {
2597
+ return null;
2598
+ }
2599
+ }
2600
+ async function json(path) {
2601
+ const res = await raw(path);
2602
+ if (!res) return null;
2603
+ if (res.status === 403 && res.headers.get("x-ratelimit-remaining") === "0") return null;
2604
+ if (!res.ok) return null;
2605
+ try {
2606
+ return await res.json();
2607
+ } catch {
2608
+ return null;
2609
+ }
2610
+ }
2611
+ return { raw, json };
2612
+ }
2613
+ async function contributorCount(client, fullName) {
2614
+ const res = await client.raw(`/repos/${fullName}/contributors?per_page=1&anon=false`);
2615
+ if (!res || !res.ok) return void 0;
2616
+ const link = res.headers.get("link");
2617
+ const m = link?.match(/[?&]page=(\d+)>;\s*rel="last"/);
2618
+ if (m) return Number(m[1]);
2619
+ try {
2620
+ const body = await res.json();
2621
+ return Array.isArray(body) ? body.length : 0;
2622
+ } catch {
2623
+ return void 0;
2624
+ }
2625
+ }
2626
+ async function openPRIssueRefs(client, fullName) {
2627
+ const refs = /* @__PURE__ */ new Set();
2628
+ const prs = await client.json(
2629
+ `/repos/${fullName}/pulls?state=open&per_page=100`
2630
+ );
2631
+ if (!Array.isArray(prs)) return refs;
2632
+ for (const pr of prs) {
2633
+ for (const m of `${pr.title ?? ""}
2634
+ ${pr.body ?? ""}`.matchAll(/#(\d+)\b/g)) {
2635
+ refs.add(Number(m[1]));
2636
+ }
2637
+ }
2638
+ return refs;
2639
+ }
2640
+ async function searchContribIssues(client, queries) {
2641
+ const byUrl = /* @__PURE__ */ new Map();
2642
+ for (const q of queries) {
2643
+ const res = await client.json(
2644
+ `/search/issues?q=${encodeURIComponent(q)}&sort=created&order=desc&per_page=${SEARCH_PER_PAGE2}`
2645
+ );
2646
+ for (const it of res?.items ?? []) {
2647
+ if (it.pull_request) continue;
2648
+ if (!byUrl.has(it.html_url)) byUrl.set(it.html_url, it);
2649
+ }
2650
+ }
2651
+ return [...byUrl.values()].sort(
2652
+ (a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
2653
+ );
2654
+ }
2655
+ async function aggregateContributions(opts = {}) {
2656
+ const client = makeClient(opts.fetchImpl ?? fetchWithTimeout);
2657
+ const queries = opts.queries ?? CONTRIB_SEARCH_QUERIES;
2658
+ const issues = (await searchContribIssues(client, queries)).slice(0, MAX_CONTRIB_ISSUES_SCANNED);
2659
+ const repoCache = /* @__PURE__ */ new Map();
2660
+ const contribCache = /* @__PURE__ */ new Map();
2661
+ const prRefsCache = /* @__PURE__ */ new Map();
2662
+ async function repoMeta(fullName) {
2663
+ const hit = repoCache.get(fullName);
2664
+ if (hit !== void 0) return hit;
2665
+ const r = await client.json(`/repos/${fullName}`) ?? null;
2666
+ repoCache.set(fullName, r);
2667
+ return r;
2668
+ }
2669
+ async function repoContribCount(fullName) {
2670
+ if (contribCache.has(fullName)) return contribCache.get(fullName);
2671
+ const n = await contributorCount(client, fullName);
2672
+ contribCache.set(fullName, n);
2673
+ return n;
2674
+ }
2675
+ async function repoPRRefs(fullName) {
2676
+ const hit = prRefsCache.get(fullName);
2677
+ if (hit !== void 0) return hit;
2678
+ const refs = await openPRIssueRefs(client, fullName);
2679
+ prRefsCache.set(fullName, refs);
2680
+ return refs;
2681
+ }
2682
+ const jobs = [];
2683
+ const seen = /* @__PURE__ */ new Set();
2684
+ const perRepo = /* @__PURE__ */ new Map();
2685
+ for (const issue of issues) {
2686
+ if (jobs.length >= MAX_CONTRIB_ITEMS) break;
2687
+ const fullName = repoFullNameFromApiUrl2(issue.repository_url);
2688
+ if (!fullName) continue;
2689
+ const id = `contribute:${fullName}#${issue.number}`;
2690
+ if (seen.has(id)) continue;
2691
+ if (isDenylistedRepo(fullName)) continue;
2692
+ if (isAssigned(issue)) continue;
2693
+ if ((perRepo.get(fullName) ?? 0) >= MAX_BOUNTIES_PER_DISCOVERED_REPO) continue;
2694
+ const repo = await repoMeta(fullName);
2695
+ if (!repo) continue;
2696
+ const title = decodeEntities(issue.title).trim();
2697
+ const contributors = await repoContribCount(fullName);
2698
+ if (!passesContributionGate({
2699
+ stars: repo.stargazers_count,
2700
+ contributors,
2701
+ title,
2702
+ archived: repo.archived,
2703
+ fork: repo.fork
2704
+ })) {
2705
+ continue;
2706
+ }
2707
+ if (repo.disabled) continue;
2708
+ const prRefs = await repoPRRefs(fullName);
2709
+ if (prRefs.has(issue.number)) continue;
2710
+ const body = issue.body ? decodeEntities(issue.body) : "";
2711
+ const labels = labelNames2(issue.labels);
2712
+ const tags = normalize(
2713
+ tokenize4([title, repo.language ?? "", labels.join(" "), body.slice(0, 2e3)].join(" "))
2714
+ );
2715
+ seen.add(id);
2716
+ perRepo.set(fullName, (perRepo.get(fullName) ?? 0) + 1);
2717
+ jobs.push({
2718
+ id,
2719
+ source: "contribute",
2720
+ title,
2721
+ company: repo.owner.login,
2722
+ url: issue.html_url,
2723
+ remote: true,
2724
+ location: "Remote",
2725
+ tags,
2726
+ roleType: "freelance",
2727
+ postedAt: issue.created_at,
2728
+ applyMode: "direct",
2729
+ contribution: {
2730
+ repoFullName: fullName,
2731
+ repoStars: repo.stargazers_count,
2732
+ repoContributors: contributors,
2733
+ // gate guarantees a number here
2734
+ issueNumber: issue.number,
2735
+ labels,
2736
+ issueUrl: issue.html_url,
2737
+ issueBody: body.slice(0, 1e3) || void 0
2738
+ },
2739
+ raw: issue
2740
+ });
2741
+ }
2742
+ return jobs;
2743
+ }
2744
+ var GITHUB_API2, CONTRIB_SEARCH_QUERIES, SEARCH_PER_PAGE2, MAX_CONTRIB_ITEMS, MAX_CONTRIB_ISSUES_SCANNED;
2745
+ var init_contributions = __esm({
2746
+ "../../packages/core/src/feeds/contributions.ts"() {
2747
+ "use strict";
2748
+ init_vocabulary();
2749
+ init_entities();
2750
+ init_bounty_gate();
2751
+ init_contribution_gate();
2752
+ init_github_bounties();
2753
+ init_http();
2754
+ GITHUB_API2 = "https://api.github.com";
2755
+ CONTRIB_SEARCH_QUERIES = [
2756
+ 'label:"good first issue" type:issue state:open',
2757
+ 'label:"help wanted" type:issue state:open',
2758
+ 'label:"good-first-issue" type:issue state:open',
2759
+ 'label:"help-wanted" type:issue state:open'
2760
+ ];
2761
+ SEARCH_PER_PAGE2 = 100;
2762
+ MAX_CONTRIB_ITEMS = 150;
2763
+ MAX_CONTRIB_ISSUES_SCANNED = 300;
2764
+ }
2765
+ });
2766
+
2545
2767
  // ../../packages/core/src/partners.ts
2546
2768
  import { readFileSync } from "fs";
2547
2769
  import { join } from "path";
@@ -2617,15 +2839,21 @@ async function buildIndex(opts) {
2617
2839
  }
2618
2840
  }
2619
2841
  const jobs = allJobs.map(({ raw: _raw, ...rest }) => rest);
2620
- return {
2842
+ const index = {
2621
2843
  builtAt: (/* @__PURE__ */ new Date()).toISOString(),
2622
2844
  jobs
2623
2845
  };
2846
+ if (opts?.includeContribute) {
2847
+ const contributions = await aggregateContributions(opts.contributeOpts);
2848
+ index.contribute = contributions.map(({ raw: _raw, ...rest }) => rest);
2849
+ }
2850
+ return index;
2624
2851
  }
2625
2852
  var init_indexer = __esm({
2626
2853
  "../../packages/core/src/indexer.ts"() {
2627
2854
  "use strict";
2628
2855
  init_feeds();
2856
+ init_contributions();
2629
2857
  init_partners();
2630
2858
  }
2631
2859
  });
@@ -5887,6 +6115,179 @@ var init_job_status = __esm({
5887
6115
  }
5888
6116
  });
5889
6117
 
6118
+ // ../../packages/core/src/episodes/schema.ts
6119
+ var init_schema = __esm({
6120
+ "../../packages/core/src/episodes/schema.ts"() {
6121
+ "use strict";
6122
+ }
6123
+ });
6124
+
6125
+ // ../../packages/core/src/episodes/doors.ts
6126
+ var init_doors = __esm({
6127
+ "../../packages/core/src/episodes/doors.ts"() {
6128
+ "use strict";
6129
+ init_schema();
6130
+ }
6131
+ });
6132
+
6133
+ // ../../packages/core/src/episodes/node-model.ts
6134
+ var init_node_model = __esm({
6135
+ "../../packages/core/src/episodes/node-model.ts"() {
6136
+ "use strict";
6137
+ }
6138
+ });
6139
+
6140
+ // ../../packages/core/src/episodes/derivers/signals.ts
6141
+ var init_signals = __esm({
6142
+ "../../packages/core/src/episodes/derivers/signals.ts"() {
6143
+ "use strict";
6144
+ init_node_model();
6145
+ }
6146
+ });
6147
+
6148
+ // ../../packages/core/src/episodes/derivers/recency-split.ts
6149
+ var DORMANT_THRESHOLD_DAYS;
6150
+ var init_recency_split = __esm({
6151
+ "../../packages/core/src/episodes/derivers/recency-split.ts"() {
6152
+ "use strict";
6153
+ init_doors();
6154
+ init_signals();
6155
+ DORMANT_THRESHOLD_DAYS = 90;
6156
+ }
6157
+ });
6158
+
6159
+ // ../../packages/core/src/credential/legible.ts
6160
+ function labelFor(id) {
6161
+ return DISPLAY_LABEL[id] ?? id.split("-").map((w) => w.length > 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w).join(" ");
6162
+ }
6163
+ function capitalize(s) {
6164
+ return s.length > 0 ? s.charAt(0).toUpperCase() + s.slice(1) : s;
6165
+ }
6166
+ function deriveLegibleProfile(credential, recency, traction, seniorityBand) {
6167
+ const ok = credential.status === "ok";
6168
+ const domains = ok ? credential.byDomain : {};
6169
+ const chips = Object.entries(domains).map(([rawDomain, d]) => {
6170
+ const canon = normalize([rawDomain])[0];
6171
+ return canon ? { domain: canon, mergedPRs: d.mergedPRs } : null;
6172
+ }).filter((c) => c !== null).sort((a, b) => b.mergedPRs - a.mergedPRs || (a.domain < b.domain ? -1 : 1));
6173
+ const dominant = chips.length > 0 ? chips[0].domain : void 0;
6174
+ const role = dominant ? ROLE_BY_DOMAIN[dominant] ?? GENERIC_ROLE : GENERIC_ROLE;
6175
+ const stackChips = chips.filter((c) => !(c.domain in CONCEPT_TAGS));
6176
+ const conceptChips = chips.filter((c) => c.domain in CONCEPT_TAGS && c.domain !== dominant);
6177
+ const topStacks = stackChips.slice(0, 3).map((c) => labelFor(c.domain)).join(", ");
6178
+ const specialization = dominant && dominant in CONCEPT_TAGS ? CONCEPT_TAGS[dominant] : conceptChips.length > 0 ? CONCEPT_TAGS[conceptChips[0].domain] : "";
6179
+ const sen = seniorityBand ? capitalize(seniorityBand) : "";
6180
+ let headline;
6181
+ if (chips.length > 0) {
6182
+ const roleSeg = sen ? `${sen} ${role}` : role;
6183
+ const segs = [roleSeg];
6184
+ if (specialization) segs.push(specialization);
6185
+ if (topStacks) segs.push(topStacks);
6186
+ headline = segs.join(" \xB7 ");
6187
+ } else {
6188
+ const roleSeg = sen ? `${sen} ${GENERIC_ROLE}` : GENERIC_ROLE;
6189
+ headline = traction.status === "ok" && traction.totalStars > 0 ? `${roleSeg} \xB7 ${traction.totalStars}\u2605 across owned repos` : roleSeg;
6190
+ }
6191
+ const mergedDates = Object.values(domains).map((d) => d.lastMergedAt).filter((s) => typeof s === "string" && s.length > 0).sort();
6192
+ const mostRecent = mergedDates.length > 0 ? mergedDates[mergedDates.length - 1] : void 0;
6193
+ const thresholdDays = Number.isFinite(recency.value.thresholdDays) && recency.value.thresholdDays > 0 ? recency.value.thresholdDays : DORMANT_THRESHOLD_DAYS;
6194
+ const nowIso = recency.value.now && recency.value.now.length > 0 ? recency.value.now : credential.computedAt || (/* @__PURE__ */ new Date()).toISOString();
6195
+ const nowMs = Date.parse(nowIso);
6196
+ let recencyBadge = null;
6197
+ let daysAgo;
6198
+ if (mostRecent) {
6199
+ const ageDays2 = (nowMs - Date.parse(mostRecent)) / DAY_MS;
6200
+ daysAgo = Math.max(0, Math.round(ageDays2));
6201
+ recencyBadge = { lastMergedAt: mostRecent, state: ageDays2 <= thresholdDays ? "live" : "dormant" };
6202
+ }
6203
+ const orgCount = Object.values(domains).reduce((m, d) => Math.max(m, d.distinctOrgs), 0);
6204
+ let proofSentence;
6205
+ if (!ok) {
6206
+ proofSentence = "Contribution credential unavailable \u2014 could not verify.";
6207
+ } else {
6208
+ const prs = credential.qualifyingTotal;
6209
+ let s = `${prs} substantive PR${prs === 1 ? "" : "s"} merged into at least ${orgCount} external org${orgCount === 1 ? "" : "s"} (\u2265${MIN_STARS}\u2605, \u2265${MIN_CONTRIBUTORS} contributors)`;
6210
+ if (daysAgo !== void 0) s += ` \u2014 most recent ${daysAgo}d ago`;
6211
+ proofSentence = `${s}.`;
6212
+ }
6213
+ const auditableBadge = ok ? {
6214
+ mergedTotal: credential.qualifyingTotal,
6215
+ distinctOrgs: orgCount,
6216
+ thresholds: { stars: MIN_STARS, contributors: MIN_CONTRIBUTORS }
6217
+ } : null;
6218
+ const profile = {
6219
+ headline,
6220
+ verifiedSkillChips: chips,
6221
+ recencyBadge,
6222
+ proofSentence,
6223
+ auditableBadge
6224
+ };
6225
+ if (seniorityBand) profile.seniority = seniorityBand;
6226
+ return profile;
6227
+ }
6228
+ var DAY_MS, GENERIC_ROLE, ROLE_BY_DOMAIN, CONCEPT_TAGS, DISPLAY_LABEL;
6229
+ var init_legible = __esm({
6230
+ "../../packages/core/src/credential/legible.ts"() {
6231
+ "use strict";
6232
+ init_contribution_gate();
6233
+ init_vocabulary();
6234
+ init_recency_split();
6235
+ DAY_MS = 864e5;
6236
+ GENERIC_ROLE = "Software Engineer";
6237
+ ROLE_BY_DOMAIN = {
6238
+ backend: "Backend Engineer",
6239
+ go: "Backend Engineer",
6240
+ rust: "Backend Engineer",
6241
+ java: "Backend Engineer",
6242
+ python: "Backend Engineer",
6243
+ nodejs: "Backend Engineer",
6244
+ "api-design": "Backend Engineer",
6245
+ microservices: "Backend Engineer",
6246
+ postgresql: "Backend Engineer",
6247
+ frontend: "Frontend Engineer",
6248
+ react: "Frontend Engineer",
6249
+ vue: "Frontend Engineer",
6250
+ css: "Frontend Engineer",
6251
+ ml: "ML Engineer",
6252
+ pytorch: "ML Engineer",
6253
+ llm: "ML Engineer",
6254
+ "computer-vision": "ML Engineer",
6255
+ devops: "Platform Engineer",
6256
+ kubernetes: "Platform Engineer",
6257
+ terraform: "Platform Engineer",
6258
+ docker: "Platform Engineer",
6259
+ aws: "Platform Engineer",
6260
+ ios: "Mobile Engineer",
6261
+ android: "Mobile Engineer"
6262
+ };
6263
+ CONCEPT_TAGS = {
6264
+ "distributed-systems": "Distributed systems",
6265
+ microservices: "Microservices",
6266
+ security: "Security",
6267
+ payments: "Payments",
6268
+ ml: "Machine learning",
6269
+ llm: "LLM systems",
6270
+ "computer-vision": "Computer vision",
6271
+ recsys: "Recommendation systems",
6272
+ "api-design": "API design"
6273
+ };
6274
+ DISPLAY_LABEL = {
6275
+ postgresql: "Postgres",
6276
+ nodejs: "Node.js",
6277
+ ml: "ML",
6278
+ llm: "LLM",
6279
+ "api-design": "API design",
6280
+ "ci-cd": "CI/CD",
6281
+ "computer-vision": "Computer vision",
6282
+ "distributed-systems": "Distributed systems",
6283
+ ios: "iOS",
6284
+ css: "CSS",
6285
+ graphql: "GraphQL",
6286
+ aws: "AWS"
6287
+ };
6288
+ }
6289
+ });
6290
+
5890
6291
  // ../../packages/core/src/index.ts
5891
6292
  var src_exports = {};
5892
6293
  __export(src_exports, {
@@ -5906,8 +6307,11 @@ __export(src_exports, {
5906
6307
  INTRO_ALLOWED_FIELDS: () => INTRO_ALLOWED_FIELDS,
5907
6308
  INTRO_PENDING_TTL_MS: () => INTRO_PENDING_TTL_MS,
5908
6309
  LEVER_SLUGS_BY_TIER: () => LEVER_SLUGS_BY_TIER,
6310
+ MIN_CONTRIBUTORS: () => MIN_CONTRIBUTORS,
6311
+ MIN_STARS: () => MIN_STARS,
5909
6312
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
5910
6313
  SYNONYMS: () => SYNONYMS,
6314
+ TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
5911
6315
  VOCABULARY: () => VOCABULARY,
5912
6316
  VOCAB_NODES: () => VOCAB_NODES,
5913
6317
  acceptanceCountForDomains: () => acceptanceCountForDomains,
@@ -5930,6 +6334,7 @@ __export(src_exports, {
5930
6334
  coreTagsFromTitle: () => coreTagsFromTitle,
5931
6335
  decorate: () => decorate,
5932
6336
  decryptMessage: () => decryptMessage,
6337
+ deriveLegibleProfile: () => deriveLegibleProfile,
5933
6338
  deriveResumeTrend: () => deriveResumeTrend,
5934
6339
  deriveSharedKey: () => deriveSharedKey,
5935
6340
  encryptMessage: () => encryptMessage,
@@ -5951,6 +6356,7 @@ __export(src_exports, {
5951
6356
  introRateLimitCheck: () => introRateLimitCheck,
5952
6357
  introRetentionAction: () => introRetentionAction,
5953
6358
  isBounty: () => isBounty,
6359
+ isContribution: () => isContribution,
5954
6360
  isOverIntroLimit: () => isOverIntroLimit,
5955
6361
  lever: () => lever,
5956
6362
  loadPartnerRoles: () => loadPartnerRoles,
@@ -5959,6 +6365,7 @@ __export(src_exports, {
5959
6365
  normalize: () => normalize,
5960
6366
  opire: () => opire,
5961
6367
  pageMatches: () => pageMatches,
6368
+ passesContributionGate: () => passesContributionGate,
5962
6369
  passesMaturityGate: () => passesMaturityGate,
5963
6370
  personCardToJob: () => personCardToJob,
5964
6371
  projectCardToJob: () => projectCardToJob,
@@ -5988,6 +6395,7 @@ var init_src = __esm({
5988
6395
  init_directoryThreshold();
5989
6396
  init_chatCrypto();
5990
6397
  init_job_status();
6398
+ init_legible();
5991
6399
  }
5992
6400
  });
5993
6401
 
@@ -392,12 +392,20 @@ var init_vocabulary = __esm({
392
392
  }
393
393
  });
394
394
 
395
+ // ../../packages/core/src/feeds/contribution-gate.ts
396
+ var init_contribution_gate = __esm({
397
+ "../../packages/core/src/feeds/contribution-gate.ts"() {
398
+ "use strict";
399
+ }
400
+ });
401
+
395
402
  // ../../packages/core/src/github.ts
396
403
  var RESUME_DECAY_HALF_LIFE_MS;
397
404
  var init_github = __esm({
398
405
  "../../packages/core/src/github.ts"() {
399
406
  "use strict";
400
407
  init_vocabulary();
408
+ init_contribution_gate();
401
409
  RESUME_DECAY_HALF_LIFE_MS = 30 * 24 * 60 * 60 * 1e3;
402
410
  }
403
411
  });
@@ -486,7 +494,14 @@ var BOUNTY_REPO_DENYLIST, DENYLIST_LC;
486
494
  var init_bounty_gate = __esm({
487
495
  "../../packages/core/src/feeds/bounty-gate.ts"() {
488
496
  "use strict";
489
- BOUNTY_REPO_DENYLIST = ["SecureBananaLabs/bug-bounty"];
497
+ BOUNTY_REPO_DENYLIST = [
498
+ "SecureBananaLabs/bug-bounty",
499
+ // Meta-farm: a bounty PLATFORM whose own issues are an assignment-gated
500
+ // contributor queue ("please assign me, my chief") — an unsolicited PR won't
501
+ // merge, so it's not a real claimable bounty. Not structurally derivable from
502
+ // any fetched field, so it's a manual entry (also dropped from the allowlist).
503
+ "boundlessfi/boundless"
504
+ ];
490
505
  DENYLIST_LC = new Set(BOUNTY_REPO_DENYLIST.map((r) => r.toLowerCase()));
491
506
  }
492
507
  });
@@ -557,6 +572,7 @@ var init_feeds = __esm({
557
572
  init_directory();
558
573
  init_bounty_gate();
559
574
  init_bounty_gate();
575
+ init_contribution_gate();
560
576
  GREENHOUSE_SLUGS_BY_TIER = {
561
577
  bigco: [
562
578
  "stripe",
@@ -665,6 +681,19 @@ var init_feeds = __esm({
665
681
  }
666
682
  });
667
683
 
684
+ // ../../packages/core/src/feeds/contributions.ts
685
+ var init_contributions = __esm({
686
+ "../../packages/core/src/feeds/contributions.ts"() {
687
+ "use strict";
688
+ init_vocabulary();
689
+ init_entities();
690
+ init_bounty_gate();
691
+ init_contribution_gate();
692
+ init_github_bounties();
693
+ init_http();
694
+ }
695
+ });
696
+
668
697
  // ../../packages/core/src/partners.ts
669
698
  import { readFileSync } from "fs";
670
699
  import { join } from "path";
@@ -689,6 +718,7 @@ var init_indexer = __esm({
689
718
  "../../packages/core/src/indexer.ts"() {
690
719
  "use strict";
691
720
  init_feeds();
721
+ init_contributions();
692
722
  init_partners();
693
723
  }
694
724
  });
@@ -3756,6 +3786,55 @@ var init_job_status = __esm({
3756
3786
  }
3757
3787
  });
3758
3788
 
3789
+ // ../../packages/core/src/episodes/schema.ts
3790
+ var init_schema = __esm({
3791
+ "../../packages/core/src/episodes/schema.ts"() {
3792
+ "use strict";
3793
+ }
3794
+ });
3795
+
3796
+ // ../../packages/core/src/episodes/doors.ts
3797
+ var init_doors = __esm({
3798
+ "../../packages/core/src/episodes/doors.ts"() {
3799
+ "use strict";
3800
+ init_schema();
3801
+ }
3802
+ });
3803
+
3804
+ // ../../packages/core/src/episodes/node-model.ts
3805
+ var init_node_model = __esm({
3806
+ "../../packages/core/src/episodes/node-model.ts"() {
3807
+ "use strict";
3808
+ }
3809
+ });
3810
+
3811
+ // ../../packages/core/src/episodes/derivers/signals.ts
3812
+ var init_signals = __esm({
3813
+ "../../packages/core/src/episodes/derivers/signals.ts"() {
3814
+ "use strict";
3815
+ init_node_model();
3816
+ }
3817
+ });
3818
+
3819
+ // ../../packages/core/src/episodes/derivers/recency-split.ts
3820
+ var init_recency_split = __esm({
3821
+ "../../packages/core/src/episodes/derivers/recency-split.ts"() {
3822
+ "use strict";
3823
+ init_doors();
3824
+ init_signals();
3825
+ }
3826
+ });
3827
+
3828
+ // ../../packages/core/src/credential/legible.ts
3829
+ var init_legible = __esm({
3830
+ "../../packages/core/src/credential/legible.ts"() {
3831
+ "use strict";
3832
+ init_contribution_gate();
3833
+ init_vocabulary();
3834
+ init_recency_split();
3835
+ }
3836
+ });
3837
+
3759
3838
  // ../../packages/core/src/index.ts
3760
3839
  var init_src = __esm({
3761
3840
  "../../packages/core/src/index.ts"() {
@@ -3771,6 +3850,7 @@ var init_src = __esm({
3771
3850
  init_directoryThreshold();
3772
3851
  init_chatCrypto();
3773
3852
  init_job_status();
3853
+ init_legible();
3774
3854
  }
3775
3855
  });
3776
3856
 
@@ -4176,7 +4256,9 @@ var init_config = __esm({
4176
4256
  chatDisclosureAck: false,
4177
4257
  chatShareActivity: false,
4178
4258
  inboundNudgeMuted: false,
4179
- inboundNudgeDisclosed: false
4259
+ inboundNudgeDisclosed: false,
4260
+ contributeEnabled: false,
4261
+ contributePrompted: false
4180
4262
  };
4181
4263
  }
4182
4264
  });