terminalhire 0.35.0 → 0.35.1

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.
@@ -45,9 +45,9 @@ var init_keytar = __esm({
45
45
  }
46
46
  });
47
47
 
48
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
48
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
49
49
  var require_keytar = __commonJS({
50
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
50
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
51
51
  "use strict";
52
52
  init_keytar();
53
53
  try {
@@ -3487,7 +3487,7 @@ function firstNumber(xs) {
3487
3487
  function isWinnableIssue(issue) {
3488
3488
  const contested = (issue.contribution.openPRsAtDiscovery ?? 0) > 0;
3489
3489
  if (contested) return false;
3490
- if (issue.winnabilityScore !== void 0 && issue.winnabilityScore <= 0) return false;
3490
+ if (issue.winnabilityScore != null && issue.winnabilityScore <= 0) return false;
3491
3491
  return true;
3492
3492
  }
3493
3493
  function issueRecency(postedAt, now) {
@@ -3504,6 +3504,7 @@ function issueRecency(postedAt, now) {
3504
3504
  function curateProjects(issues, opts = {}) {
3505
3505
  const now = opts.now ?? Date.now();
3506
3506
  const vocabSet = new Set(opts.vocabTerms ?? []);
3507
+ const applyFloor = opts.applySkillFloor ?? SKILL_FLOOR_ENABLED;
3507
3508
  const groups = /* @__PURE__ */ new Map();
3508
3509
  for (const issue of issues) {
3509
3510
  const key = repoKeyOf(issue.contribution.repoFullName);
@@ -3531,15 +3532,21 @@ function curateProjects(issues, opts = {}) {
3531
3532
  }
3532
3533
  if (topTags.length >= 4) break;
3533
3534
  }
3535
+ const repoLanguageRaw = firstNonEmptyString(
3536
+ winnableIssues.map((i) => i.contribution.language ?? "")
3537
+ );
3538
+ const languageIds = new Set(repoLanguageRaw ? normalize(tokenize(repoLanguageRaw)) : []);
3539
+ const skillTagUnion = /* @__PURE__ */ new Set();
3540
+ for (const iss of winnableIssues) for (const t of iss.tags ?? []) skillTagUnion.add(t);
3541
+ let distinctNonLanguageSkillTags = 0;
3542
+ for (const t of skillTagUnion) if (!languageIds.has(t)) distinctNonLanguageSkillTags++;
3543
+ if (applyFloor && distinctNonLanguageSkillTags < SKILL_FLOOR_MIN) continue;
3544
+ const skillDensity = clamp012(distinctNonLanguageSkillTags / SKILL_DENSITY_SATURATION);
3534
3545
  let vocabRelevance = 0;
3535
- if (vocabSet.size > 0) {
3536
- const repoTags = /* @__PURE__ */ new Set();
3537
- for (const iss of winnableIssues) for (const t of iss.tags ?? []) repoTags.add(t);
3538
- if (repoTags.size > 0) {
3539
- let matched = 0;
3540
- for (const t of repoTags) if (vocabSet.has(t)) matched++;
3541
- vocabRelevance = matched / repoTags.size;
3542
- }
3546
+ if (vocabSet.size > 0 && skillTagUnion.size > 0) {
3547
+ let matched = 0;
3548
+ for (const t of skillTagUnion) if (vocabSet.has(t)) matched++;
3549
+ vocabRelevance = matched / skillTagUnion.size;
3543
3550
  }
3544
3551
  const cadence = clamp012((commitCadence ?? 0) / CURATION_NORM.commitCadence);
3545
3552
  let recency = 0;
@@ -3558,6 +3565,8 @@ function curateProjects(issues, opts = {}) {
3558
3565
  repoContributors: repoContributors ?? null,
3559
3566
  topTags,
3560
3567
  vocabRelevance,
3568
+ distinctNonLanguageSkillTags,
3569
+ skillDensity,
3561
3570
  freshness,
3562
3571
  mergeVelocity,
3563
3572
  popularity
@@ -3567,7 +3576,7 @@ function curateProjects(issues, opts = {}) {
3567
3576
  const cards = partials.map((p) => {
3568
3577
  const winnableCount = p.winnableIssues.length;
3569
3578
  const winnableCountNorm = maxCount > 0 ? winnableCount / maxCount : 0;
3570
- const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3579
+ const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.skillDensity * p.skillDensity + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3571
3580
  return {
3572
3581
  repoKey: p.repoKey,
3573
3582
  description: p.description,
@@ -3580,6 +3589,8 @@ function curateProjects(issues, opts = {}) {
3580
3589
  winnableCount,
3581
3590
  winnableCountNorm,
3582
3591
  vocabRelevance: p.vocabRelevance,
3592
+ distinctNonLanguageSkillTags: p.distinctNonLanguageSkillTags,
3593
+ skillDensity: p.skillDensity,
3583
3594
  freshness: p.freshness,
3584
3595
  mergeVelocity: p.mergeVelocity,
3585
3596
  popularity: p.popularity
@@ -3591,18 +3602,32 @@ function curateProjects(issues, opts = {}) {
3591
3602
  );
3592
3603
  return cards;
3593
3604
  }
3594
- var CURATION_WEIGHTS, CURATION_NORM, repoKeyOf;
3605
+ function rosterActiveFromContribution(issues) {
3606
+ return curateProjects(issues).map((c) => ({ repoKey: c.repoKey, topTags: c.topTags }));
3607
+ }
3608
+ var CURATION_WEIGHTS, SKILL_DENSITY_SATURATION, SKILL_FLOOR_ENABLED, SKILL_FLOOR_MIN, CURATION_NORM, repoKeyOf;
3595
3609
  var init_projectCuration = __esm({
3596
3610
  "../../packages/core/src/feeds/projectCuration.ts"() {
3597
3611
  "use strict";
3612
+ init_vocab();
3598
3613
  init_winnability();
3599
3614
  CURATION_WEIGHTS = {
3600
3615
  winnableCount: 0.45,
3601
- vocabRelevance: 0.2,
3616
+ vocabRelevance: 0.05,
3617
+ skillDensity: 0.15,
3602
3618
  freshness: 0.15,
3603
3619
  mergeVelocity: 0.15,
3604
3620
  popularity: 0.05
3605
3621
  };
3622
+ SKILL_DENSITY_SATURATION = 3;
3623
+ SKILL_FLOOR_ENABLED = false;
3624
+ SKILL_FLOOR_MIN = 1;
3625
+ {
3626
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
3627
+ if (Math.abs(_sum - 1) > 1e-9) {
3628
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
3629
+ }
3630
+ }
3606
3631
  CURATION_NORM = {
3607
3632
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
3608
3633
  commitCadence: 60,
@@ -3877,7 +3902,7 @@ var init_feeds = __esm({
3877
3902
  DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
3878
3903
  DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
3879
3904
  DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
3880
- MAX_JOBS_PER_COMPANY = 3;
3905
+ MAX_JOBS_PER_COMPANY = 10;
3881
3906
  BIGCO_SLUGS_BY_SOURCE = {
3882
3907
  greenhouse: new Set(GREENHOUSE_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
3883
3908
  ashby: new Set(ASHBY_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
@@ -3999,7 +4024,11 @@ function buildContributionJob(a) {
3999
4024
  // verified-empty/non-matching set; a failed check leaves it undefined (never a
4000
4025
  // fabricated 0), so the claim path falls through to a live re-count.
4001
4026
  openPRsAtDiscovery: a.openPRsAtDiscovery,
4002
- repoDescription: a.repo.description || null
4027
+ repoDescription: a.repo.description || null,
4028
+ // TERM-27: persist the repo's primary language so project curation can
4029
+ // exclude the repo's OWN language id (folded into every issue's tags) from
4030
+ // the distinct-skill signal. Same `repo` used by the tokenize() tag build.
4031
+ language: a.repo.language ?? null
4003
4032
  },
4004
4033
  // Provenance: repo-first discovered items only (label-first omits the field).
4005
4034
  ...a.discovered ? { discovered: true } : {},
@@ -8214,6 +8243,9 @@ __export(src_exports, {
8214
8243
  MIN_STARS: () => MIN_STARS,
8215
8244
  PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
8216
8245
  RIGOR: () => RIGOR,
8246
+ SKILL_DENSITY_SATURATION: () => SKILL_DENSITY_SATURATION,
8247
+ SKILL_FLOOR_ENABLED: () => SKILL_FLOOR_ENABLED,
8248
+ SKILL_FLOOR_MIN: () => SKILL_FLOOR_MIN,
8217
8249
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
8218
8250
  SYNONYMS: () => SYNONYMS,
8219
8251
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
@@ -8314,6 +8346,7 @@ __export(src_exports, {
8314
8346
  rejectExtraIntroFields: () => rejectExtraIntroFields,
8315
8347
  relevanceScore: () => relevanceScore,
8316
8348
  revealIntroContacts: () => revealIntroContacts,
8349
+ rosterActiveFromContribution: () => rosterActiveFromContribution,
8317
8350
  safetyNumber: () => safetyNumber,
8318
8351
  sameLogin: () => sameLogin,
8319
8352
  setStatus: () => setStatus,
@@ -8361,9 +8394,9 @@ var init_keytar = __esm({
8361
8394
  }
8362
8395
  });
8363
8396
 
8364
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
8397
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
8365
8398
  var require_keytar = __commonJS({
8366
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8399
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8367
8400
  "use strict";
8368
8401
  init_keytar();
8369
8402
  try {
@@ -707,11 +707,26 @@ var init_winnability = __esm({
707
707
  });
708
708
 
709
709
  // ../../packages/core/src/feeds/projectCuration.ts
710
- var CURATION_NORM;
710
+ var CURATION_WEIGHTS, CURATION_NORM;
711
711
  var init_projectCuration = __esm({
712
712
  "../../packages/core/src/feeds/projectCuration.ts"() {
713
713
  "use strict";
714
+ init_vocab();
714
715
  init_winnability();
716
+ CURATION_WEIGHTS = {
717
+ winnableCount: 0.45,
718
+ vocabRelevance: 0.05,
719
+ skillDensity: 0.15,
720
+ freshness: 0.15,
721
+ mergeVelocity: 0.15,
722
+ popularity: 0.05
723
+ };
724
+ {
725
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
726
+ if (Math.abs(_sum - 1) > 1e-9) {
727
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
728
+ }
729
+ }
715
730
  CURATION_NORM = {
716
731
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
717
732
  commitCadence: 60,
@@ -4105,9 +4120,9 @@ var init_keytar = __esm({
4105
4120
  }
4106
4121
  });
4107
4122
 
4108
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
4123
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
4109
4124
  var require_keytar = __commonJS({
4110
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4125
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4111
4126
  "use strict";
4112
4127
  init_keytar();
4113
4128
  try {
@@ -724,11 +724,26 @@ var init_winnability = __esm({
724
724
  });
725
725
 
726
726
  // ../../packages/core/src/feeds/projectCuration.ts
727
- var CURATION_NORM;
727
+ var CURATION_WEIGHTS, CURATION_NORM;
728
728
  var init_projectCuration = __esm({
729
729
  "../../packages/core/src/feeds/projectCuration.ts"() {
730
730
  "use strict";
731
+ init_vocab();
731
732
  init_winnability();
733
+ CURATION_WEIGHTS = {
734
+ winnableCount: 0.45,
735
+ vocabRelevance: 0.05,
736
+ skillDensity: 0.15,
737
+ freshness: 0.15,
738
+ mergeVelocity: 0.15,
739
+ popularity: 0.05
740
+ };
741
+ {
742
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
743
+ if (Math.abs(_sum - 1) > 1e-9) {
744
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
745
+ }
746
+ }
732
747
  CURATION_NORM = {
733
748
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
734
749
  commitCadence: 60,
@@ -4133,9 +4148,9 @@ var init_keytar = __esm({
4133
4148
  }
4134
4149
  });
4135
4150
 
4136
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
4151
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
4137
4152
  var require_keytar = __commonJS({
4138
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4153
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
4139
4154
  "use strict";
4140
4155
  init_keytar();
4141
4156
  try {
@@ -3487,7 +3487,7 @@ function firstNumber(xs) {
3487
3487
  function isWinnableIssue(issue) {
3488
3488
  const contested = (issue.contribution.openPRsAtDiscovery ?? 0) > 0;
3489
3489
  if (contested) return false;
3490
- if (issue.winnabilityScore !== void 0 && issue.winnabilityScore <= 0) return false;
3490
+ if (issue.winnabilityScore != null && issue.winnabilityScore <= 0) return false;
3491
3491
  return true;
3492
3492
  }
3493
3493
  function issueRecency(postedAt, now) {
@@ -3504,6 +3504,7 @@ function issueRecency(postedAt, now) {
3504
3504
  function curateProjects(issues, opts = {}) {
3505
3505
  const now = opts.now ?? Date.now();
3506
3506
  const vocabSet = new Set(opts.vocabTerms ?? []);
3507
+ const applyFloor = opts.applySkillFloor ?? SKILL_FLOOR_ENABLED;
3507
3508
  const groups = /* @__PURE__ */ new Map();
3508
3509
  for (const issue of issues) {
3509
3510
  const key = repoKeyOf(issue.contribution.repoFullName);
@@ -3531,15 +3532,21 @@ function curateProjects(issues, opts = {}) {
3531
3532
  }
3532
3533
  if (topTags.length >= 4) break;
3533
3534
  }
3535
+ const repoLanguageRaw = firstNonEmptyString(
3536
+ winnableIssues.map((i) => i.contribution.language ?? "")
3537
+ );
3538
+ const languageIds = new Set(repoLanguageRaw ? normalize(tokenize(repoLanguageRaw)) : []);
3539
+ const skillTagUnion = /* @__PURE__ */ new Set();
3540
+ for (const iss of winnableIssues) for (const t of iss.tags ?? []) skillTagUnion.add(t);
3541
+ let distinctNonLanguageSkillTags = 0;
3542
+ for (const t of skillTagUnion) if (!languageIds.has(t)) distinctNonLanguageSkillTags++;
3543
+ if (applyFloor && distinctNonLanguageSkillTags < SKILL_FLOOR_MIN) continue;
3544
+ const skillDensity = clamp012(distinctNonLanguageSkillTags / SKILL_DENSITY_SATURATION);
3534
3545
  let vocabRelevance = 0;
3535
- if (vocabSet.size > 0) {
3536
- const repoTags = /* @__PURE__ */ new Set();
3537
- for (const iss of winnableIssues) for (const t of iss.tags ?? []) repoTags.add(t);
3538
- if (repoTags.size > 0) {
3539
- let matched = 0;
3540
- for (const t of repoTags) if (vocabSet.has(t)) matched++;
3541
- vocabRelevance = matched / repoTags.size;
3542
- }
3546
+ if (vocabSet.size > 0 && skillTagUnion.size > 0) {
3547
+ let matched = 0;
3548
+ for (const t of skillTagUnion) if (vocabSet.has(t)) matched++;
3549
+ vocabRelevance = matched / skillTagUnion.size;
3543
3550
  }
3544
3551
  const cadence = clamp012((commitCadence ?? 0) / CURATION_NORM.commitCadence);
3545
3552
  let recency = 0;
@@ -3558,6 +3565,8 @@ function curateProjects(issues, opts = {}) {
3558
3565
  repoContributors: repoContributors ?? null,
3559
3566
  topTags,
3560
3567
  vocabRelevance,
3568
+ distinctNonLanguageSkillTags,
3569
+ skillDensity,
3561
3570
  freshness,
3562
3571
  mergeVelocity,
3563
3572
  popularity
@@ -3567,7 +3576,7 @@ function curateProjects(issues, opts = {}) {
3567
3576
  const cards = partials.map((p) => {
3568
3577
  const winnableCount = p.winnableIssues.length;
3569
3578
  const winnableCountNorm = maxCount > 0 ? winnableCount / maxCount : 0;
3570
- const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3579
+ const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.skillDensity * p.skillDensity + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3571
3580
  return {
3572
3581
  repoKey: p.repoKey,
3573
3582
  description: p.description,
@@ -3580,6 +3589,8 @@ function curateProjects(issues, opts = {}) {
3580
3589
  winnableCount,
3581
3590
  winnableCountNorm,
3582
3591
  vocabRelevance: p.vocabRelevance,
3592
+ distinctNonLanguageSkillTags: p.distinctNonLanguageSkillTags,
3593
+ skillDensity: p.skillDensity,
3583
3594
  freshness: p.freshness,
3584
3595
  mergeVelocity: p.mergeVelocity,
3585
3596
  popularity: p.popularity
@@ -3591,18 +3602,32 @@ function curateProjects(issues, opts = {}) {
3591
3602
  );
3592
3603
  return cards;
3593
3604
  }
3594
- var CURATION_WEIGHTS, CURATION_NORM, repoKeyOf;
3605
+ function rosterActiveFromContribution(issues) {
3606
+ return curateProjects(issues).map((c) => ({ repoKey: c.repoKey, topTags: c.topTags }));
3607
+ }
3608
+ var CURATION_WEIGHTS, SKILL_DENSITY_SATURATION, SKILL_FLOOR_ENABLED, SKILL_FLOOR_MIN, CURATION_NORM, repoKeyOf;
3595
3609
  var init_projectCuration = __esm({
3596
3610
  "../../packages/core/src/feeds/projectCuration.ts"() {
3597
3611
  "use strict";
3612
+ init_vocab();
3598
3613
  init_winnability();
3599
3614
  CURATION_WEIGHTS = {
3600
3615
  winnableCount: 0.45,
3601
- vocabRelevance: 0.2,
3616
+ vocabRelevance: 0.05,
3617
+ skillDensity: 0.15,
3602
3618
  freshness: 0.15,
3603
3619
  mergeVelocity: 0.15,
3604
3620
  popularity: 0.05
3605
3621
  };
3622
+ SKILL_DENSITY_SATURATION = 3;
3623
+ SKILL_FLOOR_ENABLED = false;
3624
+ SKILL_FLOOR_MIN = 1;
3625
+ {
3626
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
3627
+ if (Math.abs(_sum - 1) > 1e-9) {
3628
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
3629
+ }
3630
+ }
3606
3631
  CURATION_NORM = {
3607
3632
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
3608
3633
  commitCadence: 60,
@@ -3877,7 +3902,7 @@ var init_feeds = __esm({
3877
3902
  DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
3878
3903
  DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
3879
3904
  DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
3880
- MAX_JOBS_PER_COMPANY = 3;
3905
+ MAX_JOBS_PER_COMPANY = 10;
3881
3906
  BIGCO_SLUGS_BY_SOURCE = {
3882
3907
  greenhouse: new Set(GREENHOUSE_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
3883
3908
  ashby: new Set(ASHBY_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
@@ -3999,7 +4024,11 @@ function buildContributionJob(a) {
3999
4024
  // verified-empty/non-matching set; a failed check leaves it undefined (never a
4000
4025
  // fabricated 0), so the claim path falls through to a live re-count.
4001
4026
  openPRsAtDiscovery: a.openPRsAtDiscovery,
4002
- repoDescription: a.repo.description || null
4027
+ repoDescription: a.repo.description || null,
4028
+ // TERM-27: persist the repo's primary language so project curation can
4029
+ // exclude the repo's OWN language id (folded into every issue's tags) from
4030
+ // the distinct-skill signal. Same `repo` used by the tokenize() tag build.
4031
+ language: a.repo.language ?? null
4003
4032
  },
4004
4033
  // Provenance: repo-first discovered items only (label-first omits the field).
4005
4034
  ...a.discovered ? { discovered: true } : {},
@@ -8214,6 +8243,9 @@ __export(src_exports, {
8214
8243
  MIN_STARS: () => MIN_STARS,
8215
8244
  PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
8216
8245
  RIGOR: () => RIGOR,
8246
+ SKILL_DENSITY_SATURATION: () => SKILL_DENSITY_SATURATION,
8247
+ SKILL_FLOOR_ENABLED: () => SKILL_FLOOR_ENABLED,
8248
+ SKILL_FLOOR_MIN: () => SKILL_FLOOR_MIN,
8217
8249
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
8218
8250
  SYNONYMS: () => SYNONYMS,
8219
8251
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
@@ -8314,6 +8346,7 @@ __export(src_exports, {
8314
8346
  rejectExtraIntroFields: () => rejectExtraIntroFields,
8315
8347
  relevanceScore: () => relevanceScore,
8316
8348
  revealIntroContacts: () => revealIntroContacts,
8349
+ rosterActiveFromContribution: () => rosterActiveFromContribution,
8317
8350
  safetyNumber: () => safetyNumber,
8318
8351
  sameLogin: () => sameLogin,
8319
8352
  setStatus: () => setStatus,
@@ -8544,9 +8577,9 @@ var init_keytar = __esm({
8544
8577
  }
8545
8578
  });
8546
8579
 
8547
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
8580
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
8548
8581
  var require_keytar = __commonJS({
8549
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8582
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8550
8583
  "use strict";
8551
8584
  init_keytar();
8552
8585
  try {
@@ -1095,11 +1095,26 @@ var init_winnability = __esm({
1095
1095
  });
1096
1096
 
1097
1097
  // ../../packages/core/src/feeds/projectCuration.ts
1098
- var CURATION_NORM;
1098
+ var CURATION_WEIGHTS, CURATION_NORM;
1099
1099
  var init_projectCuration = __esm({
1100
1100
  "../../packages/core/src/feeds/projectCuration.ts"() {
1101
1101
  "use strict";
1102
+ init_vocab();
1102
1103
  init_winnability();
1104
+ CURATION_WEIGHTS = {
1105
+ winnableCount: 0.45,
1106
+ vocabRelevance: 0.05,
1107
+ skillDensity: 0.15,
1108
+ freshness: 0.15,
1109
+ mergeVelocity: 0.15,
1110
+ popularity: 0.05
1111
+ };
1112
+ {
1113
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
1114
+ if (Math.abs(_sum - 1) > 1e-9) {
1115
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
1116
+ }
1117
+ }
1103
1118
  CURATION_NORM = {
1104
1119
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
1105
1120
  commitCadence: 60,
@@ -1475,9 +1490,9 @@ var init_keytar = __esm({
1475
1490
  }
1476
1491
  });
1477
1492
 
1478
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
1493
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
1479
1494
  var require_keytar = __commonJS({
1480
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1495
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
1481
1496
  "use strict";
1482
1497
  init_keytar();
1483
1498
  try {
@@ -3487,7 +3487,7 @@ function firstNumber(xs) {
3487
3487
  function isWinnableIssue(issue) {
3488
3488
  const contested = (issue.contribution.openPRsAtDiscovery ?? 0) > 0;
3489
3489
  if (contested) return false;
3490
- if (issue.winnabilityScore !== void 0 && issue.winnabilityScore <= 0) return false;
3490
+ if (issue.winnabilityScore != null && issue.winnabilityScore <= 0) return false;
3491
3491
  return true;
3492
3492
  }
3493
3493
  function issueRecency(postedAt, now) {
@@ -3504,6 +3504,7 @@ function issueRecency(postedAt, now) {
3504
3504
  function curateProjects(issues, opts = {}) {
3505
3505
  const now = opts.now ?? Date.now();
3506
3506
  const vocabSet = new Set(opts.vocabTerms ?? []);
3507
+ const applyFloor = opts.applySkillFloor ?? SKILL_FLOOR_ENABLED;
3507
3508
  const groups = /* @__PURE__ */ new Map();
3508
3509
  for (const issue of issues) {
3509
3510
  const key = repoKeyOf(issue.contribution.repoFullName);
@@ -3531,15 +3532,21 @@ function curateProjects(issues, opts = {}) {
3531
3532
  }
3532
3533
  if (topTags.length >= 4) break;
3533
3534
  }
3535
+ const repoLanguageRaw = firstNonEmptyString(
3536
+ winnableIssues.map((i) => i.contribution.language ?? "")
3537
+ );
3538
+ const languageIds = new Set(repoLanguageRaw ? normalize(tokenize(repoLanguageRaw)) : []);
3539
+ const skillTagUnion = /* @__PURE__ */ new Set();
3540
+ for (const iss of winnableIssues) for (const t of iss.tags ?? []) skillTagUnion.add(t);
3541
+ let distinctNonLanguageSkillTags = 0;
3542
+ for (const t of skillTagUnion) if (!languageIds.has(t)) distinctNonLanguageSkillTags++;
3543
+ if (applyFloor && distinctNonLanguageSkillTags < SKILL_FLOOR_MIN) continue;
3544
+ const skillDensity = clamp012(distinctNonLanguageSkillTags / SKILL_DENSITY_SATURATION);
3534
3545
  let vocabRelevance = 0;
3535
- if (vocabSet.size > 0) {
3536
- const repoTags = /* @__PURE__ */ new Set();
3537
- for (const iss of winnableIssues) for (const t of iss.tags ?? []) repoTags.add(t);
3538
- if (repoTags.size > 0) {
3539
- let matched = 0;
3540
- for (const t of repoTags) if (vocabSet.has(t)) matched++;
3541
- vocabRelevance = matched / repoTags.size;
3542
- }
3546
+ if (vocabSet.size > 0 && skillTagUnion.size > 0) {
3547
+ let matched = 0;
3548
+ for (const t of skillTagUnion) if (vocabSet.has(t)) matched++;
3549
+ vocabRelevance = matched / skillTagUnion.size;
3543
3550
  }
3544
3551
  const cadence = clamp012((commitCadence ?? 0) / CURATION_NORM.commitCadence);
3545
3552
  let recency = 0;
@@ -3558,6 +3565,8 @@ function curateProjects(issues, opts = {}) {
3558
3565
  repoContributors: repoContributors ?? null,
3559
3566
  topTags,
3560
3567
  vocabRelevance,
3568
+ distinctNonLanguageSkillTags,
3569
+ skillDensity,
3561
3570
  freshness,
3562
3571
  mergeVelocity,
3563
3572
  popularity
@@ -3567,7 +3576,7 @@ function curateProjects(issues, opts = {}) {
3567
3576
  const cards = partials.map((p) => {
3568
3577
  const winnableCount = p.winnableIssues.length;
3569
3578
  const winnableCountNorm = maxCount > 0 ? winnableCount / maxCount : 0;
3570
- const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3579
+ const score = CURATION_WEIGHTS.winnableCount * winnableCountNorm + CURATION_WEIGHTS.vocabRelevance * p.vocabRelevance + CURATION_WEIGHTS.skillDensity * p.skillDensity + CURATION_WEIGHTS.freshness * p.freshness + CURATION_WEIGHTS.mergeVelocity * p.mergeVelocity + CURATION_WEIGHTS.popularity * p.popularity;
3571
3580
  return {
3572
3581
  repoKey: p.repoKey,
3573
3582
  description: p.description,
@@ -3580,6 +3589,8 @@ function curateProjects(issues, opts = {}) {
3580
3589
  winnableCount,
3581
3590
  winnableCountNorm,
3582
3591
  vocabRelevance: p.vocabRelevance,
3592
+ distinctNonLanguageSkillTags: p.distinctNonLanguageSkillTags,
3593
+ skillDensity: p.skillDensity,
3583
3594
  freshness: p.freshness,
3584
3595
  mergeVelocity: p.mergeVelocity,
3585
3596
  popularity: p.popularity
@@ -3591,18 +3602,32 @@ function curateProjects(issues, opts = {}) {
3591
3602
  );
3592
3603
  return cards;
3593
3604
  }
3594
- var CURATION_WEIGHTS, CURATION_NORM, repoKeyOf;
3605
+ function rosterActiveFromContribution(issues) {
3606
+ return curateProjects(issues).map((c) => ({ repoKey: c.repoKey, topTags: c.topTags }));
3607
+ }
3608
+ var CURATION_WEIGHTS, SKILL_DENSITY_SATURATION, SKILL_FLOOR_ENABLED, SKILL_FLOOR_MIN, CURATION_NORM, repoKeyOf;
3595
3609
  var init_projectCuration = __esm({
3596
3610
  "../../packages/core/src/feeds/projectCuration.ts"() {
3597
3611
  "use strict";
3612
+ init_vocab();
3598
3613
  init_winnability();
3599
3614
  CURATION_WEIGHTS = {
3600
3615
  winnableCount: 0.45,
3601
- vocabRelevance: 0.2,
3616
+ vocabRelevance: 0.05,
3617
+ skillDensity: 0.15,
3602
3618
  freshness: 0.15,
3603
3619
  mergeVelocity: 0.15,
3604
3620
  popularity: 0.05
3605
3621
  };
3622
+ SKILL_DENSITY_SATURATION = 3;
3623
+ SKILL_FLOOR_ENABLED = false;
3624
+ SKILL_FLOOR_MIN = 1;
3625
+ {
3626
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
3627
+ if (Math.abs(_sum - 1) > 1e-9) {
3628
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
3629
+ }
3630
+ }
3606
3631
  CURATION_NORM = {
3607
3632
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
3608
3633
  commitCadence: 60,
@@ -3877,7 +3902,7 @@ var init_feeds = __esm({
3877
3902
  DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
3878
3903
  DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
3879
3904
  DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
3880
- MAX_JOBS_PER_COMPANY = 3;
3905
+ MAX_JOBS_PER_COMPANY = 10;
3881
3906
  BIGCO_SLUGS_BY_SOURCE = {
3882
3907
  greenhouse: new Set(GREENHOUSE_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
3883
3908
  ashby: new Set(ASHBY_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
@@ -3999,7 +4024,11 @@ function buildContributionJob(a) {
3999
4024
  // verified-empty/non-matching set; a failed check leaves it undefined (never a
4000
4025
  // fabricated 0), so the claim path falls through to a live re-count.
4001
4026
  openPRsAtDiscovery: a.openPRsAtDiscovery,
4002
- repoDescription: a.repo.description || null
4027
+ repoDescription: a.repo.description || null,
4028
+ // TERM-27: persist the repo's primary language so project curation can
4029
+ // exclude the repo's OWN language id (folded into every issue's tags) from
4030
+ // the distinct-skill signal. Same `repo` used by the tokenize() tag build.
4031
+ language: a.repo.language ?? null
4003
4032
  },
4004
4033
  // Provenance: repo-first discovered items only (label-first omits the field).
4005
4034
  ...a.discovered ? { discovered: true } : {},
@@ -8214,6 +8243,9 @@ __export(src_exports, {
8214
8243
  MIN_STARS: () => MIN_STARS,
8215
8244
  PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
8216
8245
  RIGOR: () => RIGOR,
8246
+ SKILL_DENSITY_SATURATION: () => SKILL_DENSITY_SATURATION,
8247
+ SKILL_FLOOR_ENABLED: () => SKILL_FLOOR_ENABLED,
8248
+ SKILL_FLOOR_MIN: () => SKILL_FLOOR_MIN,
8217
8249
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
8218
8250
  SYNONYMS: () => SYNONYMS,
8219
8251
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
@@ -8314,6 +8346,7 @@ __export(src_exports, {
8314
8346
  rejectExtraIntroFields: () => rejectExtraIntroFields,
8315
8347
  relevanceScore: () => relevanceScore,
8316
8348
  revealIntroContacts: () => revealIntroContacts,
8349
+ rosterActiveFromContribution: () => rosterActiveFromContribution,
8317
8350
  safetyNumber: () => safetyNumber,
8318
8351
  sameLogin: () => sameLogin,
8319
8352
  setStatus: () => setStatus,
@@ -8361,9 +8394,9 @@ var init_keytar = __esm({
8361
8394
  }
8362
8395
  });
8363
8396
 
8364
- // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node
8397
+ // node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node
8365
8398
  var require_keytar = __commonJS({
8366
- "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/wt-cli-update/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8399
+ "node-file:/private/tmp/claude-501/-Users-ericgang-job-placement-inline/9716ff9c-0531-4844-adf4-286763cf8ab8/scratchpad/deeplink-wt/node_modules/keytar/build/Release/keytar.node"(exports, module) {
8367
8400
  "use strict";
8368
8401
  init_keytar();
8369
8402
  try {