terminalhire 0.35.0 → 0.35.3

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.
Files changed (46) hide show
  1. package/dist/bin/claim-push-bg.js +10 -58
  2. package/dist/bin/jpi-bounties.js +124 -77
  3. package/dist/bin/jpi-chat-read.js +29 -60
  4. package/dist/bin/jpi-chat.js +31 -62
  5. package/dist/bin/jpi-claim.js +125 -78
  6. package/dist/bin/jpi-config.js +51 -1
  7. package/dist/bin/jpi-contribute.js +30 -61
  8. package/dist/bin/jpi-devs.js +124 -77
  9. package/dist/bin/jpi-dispatch.js +682 -6167
  10. package/dist/bin/jpi-hub.js +127 -79
  11. package/dist/bin/jpi-inbox.js +31 -62
  12. package/dist/bin/jpi-init.js +113 -18
  13. package/dist/bin/jpi-intro.js +28 -60
  14. package/dist/bin/jpi-jobs.js +126 -78
  15. package/dist/bin/jpi-learn.js +28 -60
  16. package/dist/bin/jpi-link.js +2 -1
  17. package/dist/bin/jpi-login.js +127 -79
  18. package/dist/bin/jpi-mcp.js +148 -5827
  19. package/dist/bin/jpi-profile.js +28 -60
  20. package/dist/bin/jpi-project.js +113 -18
  21. package/dist/bin/jpi-protocol.js +73 -5
  22. package/dist/bin/jpi-refresh.js +158 -83
  23. package/dist/bin/jpi-repo.js +27 -59
  24. package/dist/bin/jpi-save.js +28 -60
  25. package/dist/bin/jpi-spinner.js +14 -0
  26. package/dist/bin/jpi-sync.js +28 -60
  27. package/dist/bin/jpi-trajectory.js +28 -60
  28. package/dist/bin/jpi-update.js +487 -5
  29. package/dist/bin/peer-connect-prompt.js +2 -1
  30. package/dist/bin/pulse-prompt.js +2 -1
  31. package/dist/bin/spinner.js +18 -1
  32. package/dist/src/chat-client.js +24 -61
  33. package/dist/src/chat-keystore.js +24 -61
  34. package/dist/src/config.js +17 -1
  35. package/dist/src/crypto-store.js +11 -62
  36. package/dist/src/github-auth.js +10 -61
  37. package/dist/src/intro.js +28 -60
  38. package/dist/src/link.js +2 -1
  39. package/dist/src/profile.js +25 -62
  40. package/dist/src/protocol.js +72 -4
  41. package/dist/src/repo-experience.js +24 -61
  42. package/dist/src/reputation/fetch.js +14 -0
  43. package/dist/src/signal.js +14 -0
  44. package/dist/src/trajectory.js +28 -60
  45. package/package.json +1 -1
  46. package/dist/keytar-KOAAH267.node +0 -0
@@ -5,16 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
8
  var __esm = (fn, res) => function __init() {
15
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
10
  };
17
- var __commonJS = (cb, mod2) => function __require2() {
11
+ var __commonJS = (cb, mod2) => function __require() {
18
12
  return mod2 || (0, cb[__getOwnPropNames(cb)[0]])((mod2 = { exports: {} }).exports, mod2), mod2.exports;
19
13
  };
20
14
  var __export = (target, all) => {
@@ -724,11 +718,26 @@ var init_winnability = __esm({
724
718
  });
725
719
 
726
720
  // ../../packages/core/src/feeds/projectCuration.ts
727
- var CURATION_NORM;
721
+ var CURATION_WEIGHTS, CURATION_NORM;
728
722
  var init_projectCuration = __esm({
729
723
  "../../packages/core/src/feeds/projectCuration.ts"() {
730
724
  "use strict";
725
+ init_vocab();
731
726
  init_winnability();
727
+ CURATION_WEIGHTS = {
728
+ winnableCount: 0.45,
729
+ vocabRelevance: 0.05,
730
+ skillDensity: 0.15,
731
+ freshness: 0.15,
732
+ mergeVelocity: 0.15,
733
+ popularity: 0.05
734
+ };
735
+ {
736
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
737
+ if (Math.abs(_sum - 1) > 1e-9) {
738
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
739
+ }
740
+ }
732
741
  CURATION_NORM = {
733
742
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
734
743
  commitCadence: 60,
@@ -892,6 +901,7 @@ var init_contributions = __esm({
892
901
  init_contribution_gate();
893
902
  init_contribution_classify();
894
903
  init_github_bounties();
904
+ init_github();
895
905
  init_http();
896
906
  init_gh_governor();
897
907
  CONTRIB_LABEL_QUERIES = [
@@ -4125,61 +4135,19 @@ var init_src = __esm({
4125
4135
  }
4126
4136
  });
4127
4137
 
4128
- // ../../node_modules/keytar/build/Release/keytar.node
4129
- var keytar_default;
4130
- var init_keytar = __esm({
4131
- "../../node_modules/keytar/build/Release/keytar.node"() {
4132
- keytar_default = "../keytar-KOAAH267.node";
4133
- }
4134
- });
4135
-
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
4137
- 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) {
4139
- "use strict";
4140
- init_keytar();
4141
- try {
4142
- module.exports = __require(keytar_default);
4143
- } catch {
4144
- }
4145
- }
4146
- });
4147
-
4148
4138
  // ../../node_modules/keytar/lib/keytar.js
4149
- var require_keytar2 = __commonJS({
4139
+ var require_keytar = __commonJS({
4150
4140
  "../../node_modules/keytar/lib/keytar.js"(exports, module) {
4151
4141
  "use strict";
4152
- var keytar = require_keytar();
4153
- function checkRequired(val, name) {
4154
- if (!val || val.length <= 0) {
4155
- throw new Error(name + " is required.");
4156
- }
4142
+ function disabled() {
4143
+ throw new Error("keytar disabled in this dev checkout (keychain popup guard) \u2014 key-file fallback expected");
4157
4144
  }
4158
4145
  module.exports = {
4159
- getPassword: function(service, account) {
4160
- checkRequired(service, "Service");
4161
- checkRequired(account, "Account");
4162
- return keytar.getPassword(service, account);
4163
- },
4164
- setPassword: function(service, account, password) {
4165
- checkRequired(service, "Service");
4166
- checkRequired(account, "Account");
4167
- checkRequired(password, "Password");
4168
- return keytar.setPassword(service, account, password);
4169
- },
4170
- deletePassword: function(service, account) {
4171
- checkRequired(service, "Service");
4172
- checkRequired(account, "Account");
4173
- return keytar.deletePassword(service, account);
4174
- },
4175
- findPassword: function(service) {
4176
- checkRequired(service, "Service");
4177
- return keytar.findPassword(service);
4178
- },
4179
- findCredentials: function(service) {
4180
- checkRequired(service, "Service");
4181
- return keytar.findCredentials(service);
4182
- }
4146
+ getPassword: disabled,
4147
+ setPassword: disabled,
4148
+ deletePassword: disabled,
4149
+ findPassword: disabled,
4150
+ findCredentials: disabled
4183
4151
  };
4184
4152
  }
4185
4153
  });
@@ -4206,7 +4174,7 @@ function skipKeychain() {
4206
4174
  async function loadKey() {
4207
4175
  if (!skipKeychain()) {
4208
4176
  try {
4209
- const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
4177
+ const kt = await Promise.resolve().then(() => __toESM(require_keytar(), 1));
4210
4178
  const stored = await kt.getPassword("terminalhire", "profile-key");
4211
4179
  if (stored) return Buffer.from(stored, "hex");
4212
4180
  const key2 = randomBytes3(KEY_BYTES);
@@ -4598,7 +4566,8 @@ var init_config = __esm({
4598
4566
  betaOptIn: false,
4599
4567
  lastFullFeedbackAt: null,
4600
4568
  lastPulseAskAt: null,
4601
- pulseDisclosed: false
4569
+ pulseDisclosed: false,
4570
+ mix: "balanced"
4602
4571
  };
4603
4572
  }
4604
4573
  });
@@ -5104,7 +5073,7 @@ function skipKeychain2() {
5104
5073
  async function tryLoadFromKeytar(policy) {
5105
5074
  if (forceKeytarUnavailableForTests || skipKeychain2()) return null;
5106
5075
  try {
5107
- const kt = policy === "keychain-required" ? createRequire(import.meta.url)("keytar") : await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
5076
+ const kt = policy === "keychain-required" ? createRequire(import.meta.url)("keytar") : await Promise.resolve().then(() => __toESM(require_keytar(), 1));
5108
5077
  const stored = await kt.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
5109
5078
  if (stored) {
5110
5079
  return Buffer.from(stored, "hex");
@@ -5145,7 +5114,7 @@ async function deleteKey() {
5145
5114
  }
5146
5115
  if (!forceKeytarUnavailableForTests && !skipKeychain2()) {
5147
5116
  try {
5148
- const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
5117
+ const kt = await Promise.resolve().then(() => __toESM(require_keytar(), 1));
5149
5118
  await kt.deletePassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
5150
5119
  } catch {
5151
5120
  }
@@ -5,16 +5,10 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __getProtoOf = Object.getPrototypeOf;
7
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
8
  var __esm = (fn, res) => function __init() {
15
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
10
  };
17
- var __commonJS = (cb, mod2) => function __require2() {
11
+ var __commonJS = (cb, mod2) => function __require() {
18
12
  return mod2 || (0, cb[__getOwnPropNames(cb)[0]])((mod2 = { exports: {} }).exports, mod2), mod2.exports;
19
13
  };
20
14
  var __export = (target, all) => {
@@ -1735,6 +1729,25 @@ async function resolveClosingIssues(owner, name, number, body, token, signal, go
1735
1729
  while ((m = re.exec(body)) !== null) nums.add(parseInt(m[1], 10));
1736
1730
  return { closesIssues: [...nums], linkageSource: nums.size ? "body-keyword" : "none" };
1737
1731
  }
1732
+ async function openPRClosingRefs(owner, name, token, signal, governor) {
1733
+ const q = `query($o:String!,$n:String!){repository(owner:$o,name:$n){pullRequests(states:OPEN,first:100){totalCount nodes{number closingIssuesReferences(first:20){nodes{number}}}}}rateLimit{cost remaining}}`;
1734
+ try {
1735
+ const r = await ghGraphQL(q, { o: owner, n: name }, token, signal, governor);
1736
+ if (r === null) return null;
1737
+ if (!r.data?.repository) return null;
1738
+ const prs = r.data.repository.pullRequests;
1739
+ if (prs && prs.nodes !== void 0 && !Array.isArray(prs.nodes)) return null;
1740
+ const nodes = prs?.nodes ?? [];
1741
+ const refs = /* @__PURE__ */ new Set();
1742
+ for (const node of nodes) {
1743
+ for (const ref of node.closingIssuesReferences?.nodes ?? []) refs.add(ref.number);
1744
+ }
1745
+ const totalCount = prs?.totalCount ?? nodes.length;
1746
+ return { refs, capHit: totalCount > nodes.length, totalCount };
1747
+ } catch {
1748
+ return null;
1749
+ }
1750
+ }
1738
1751
  function makeScoringGovernor(governor) {
1739
1752
  return governor ?? makeGitHubGovernor(
1740
1753
  ((url, init) => fetch(url, init)),
@@ -3487,7 +3500,7 @@ function firstNumber(xs) {
3487
3500
  function isWinnableIssue(issue) {
3488
3501
  const contested = (issue.contribution.openPRsAtDiscovery ?? 0) > 0;
3489
3502
  if (contested) return false;
3490
- if (issue.winnabilityScore !== void 0 && issue.winnabilityScore <= 0) return false;
3503
+ if (issue.winnabilityScore != null && issue.winnabilityScore <= 0) return false;
3491
3504
  return true;
3492
3505
  }
3493
3506
  function issueRecency(postedAt, now) {
@@ -3504,6 +3517,7 @@ function issueRecency(postedAt, now) {
3504
3517
  function curateProjects(issues, opts = {}) {
3505
3518
  const now = opts.now ?? Date.now();
3506
3519
  const vocabSet = new Set(opts.vocabTerms ?? []);
3520
+ const applyFloor = opts.applySkillFloor ?? SKILL_FLOOR_ENABLED;
3507
3521
  const groups = /* @__PURE__ */ new Map();
3508
3522
  for (const issue of issues) {
3509
3523
  const key = repoKeyOf(issue.contribution.repoFullName);
@@ -3531,15 +3545,21 @@ function curateProjects(issues, opts = {}) {
3531
3545
  }
3532
3546
  if (topTags.length >= 4) break;
3533
3547
  }
3548
+ const repoLanguageRaw = firstNonEmptyString(
3549
+ winnableIssues.map((i) => i.contribution.language ?? "")
3550
+ );
3551
+ const languageIds = new Set(repoLanguageRaw ? normalize(tokenize(repoLanguageRaw)) : []);
3552
+ const skillTagUnion = /* @__PURE__ */ new Set();
3553
+ for (const iss of winnableIssues) for (const t of iss.tags ?? []) skillTagUnion.add(t);
3554
+ let distinctNonLanguageSkillTags = 0;
3555
+ for (const t of skillTagUnion) if (!languageIds.has(t)) distinctNonLanguageSkillTags++;
3556
+ if (applyFloor && distinctNonLanguageSkillTags < SKILL_FLOOR_MIN) continue;
3557
+ const skillDensity = clamp012(distinctNonLanguageSkillTags / SKILL_DENSITY_SATURATION);
3534
3558
  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
- }
3559
+ if (vocabSet.size > 0 && skillTagUnion.size > 0) {
3560
+ let matched = 0;
3561
+ for (const t of skillTagUnion) if (vocabSet.has(t)) matched++;
3562
+ vocabRelevance = matched / skillTagUnion.size;
3543
3563
  }
3544
3564
  const cadence = clamp012((commitCadence ?? 0) / CURATION_NORM.commitCadence);
3545
3565
  let recency = 0;
@@ -3558,6 +3578,8 @@ function curateProjects(issues, opts = {}) {
3558
3578
  repoContributors: repoContributors ?? null,
3559
3579
  topTags,
3560
3580
  vocabRelevance,
3581
+ distinctNonLanguageSkillTags,
3582
+ skillDensity,
3561
3583
  freshness,
3562
3584
  mergeVelocity,
3563
3585
  popularity
@@ -3567,7 +3589,7 @@ function curateProjects(issues, opts = {}) {
3567
3589
  const cards = partials.map((p) => {
3568
3590
  const winnableCount = p.winnableIssues.length;
3569
3591
  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;
3592
+ 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
3593
  return {
3572
3594
  repoKey: p.repoKey,
3573
3595
  description: p.description,
@@ -3580,6 +3602,8 @@ function curateProjects(issues, opts = {}) {
3580
3602
  winnableCount,
3581
3603
  winnableCountNorm,
3582
3604
  vocabRelevance: p.vocabRelevance,
3605
+ distinctNonLanguageSkillTags: p.distinctNonLanguageSkillTags,
3606
+ skillDensity: p.skillDensity,
3583
3607
  freshness: p.freshness,
3584
3608
  mergeVelocity: p.mergeVelocity,
3585
3609
  popularity: p.popularity
@@ -3591,18 +3615,32 @@ function curateProjects(issues, opts = {}) {
3591
3615
  );
3592
3616
  return cards;
3593
3617
  }
3594
- var CURATION_WEIGHTS, CURATION_NORM, repoKeyOf;
3618
+ function rosterActiveFromContribution(issues) {
3619
+ return curateProjects(issues).map((c) => ({ repoKey: c.repoKey, topTags: c.topTags }));
3620
+ }
3621
+ var CURATION_WEIGHTS, SKILL_DENSITY_SATURATION, SKILL_FLOOR_ENABLED, SKILL_FLOOR_MIN, CURATION_NORM, repoKeyOf;
3595
3622
  var init_projectCuration = __esm({
3596
3623
  "../../packages/core/src/feeds/projectCuration.ts"() {
3597
3624
  "use strict";
3625
+ init_vocab();
3598
3626
  init_winnability();
3599
3627
  CURATION_WEIGHTS = {
3600
3628
  winnableCount: 0.45,
3601
- vocabRelevance: 0.2,
3629
+ vocabRelevance: 0.05,
3630
+ skillDensity: 0.15,
3602
3631
  freshness: 0.15,
3603
3632
  mergeVelocity: 0.15,
3604
3633
  popularity: 0.05
3605
3634
  };
3635
+ SKILL_DENSITY_SATURATION = 3;
3636
+ SKILL_FLOOR_ENABLED = false;
3637
+ SKILL_FLOOR_MIN = 1;
3638
+ {
3639
+ const _sum = Object.values(CURATION_WEIGHTS).reduce((a, b) => a + b, 0);
3640
+ if (Math.abs(_sum - 1) > 1e-9) {
3641
+ throw new Error(`CURATION_WEIGHTS must sum to 1.0, got ${_sum}`);
3642
+ }
3643
+ }
3606
3644
  CURATION_NORM = {
3607
3645
  /** ~60 commits in the last ~30d is treated as "maxed" commit-cadence freshness. */
3608
3646
  commitCadence: 60,
@@ -3877,7 +3915,7 @@ var init_feeds = __esm({
3877
3915
  DEFAULT_ASHBY_SLUGS = flattenTiers(ASHBY_SLUGS_BY_TIER);
3878
3916
  DEFAULT_LEVER_SLUGS = flattenTiers(LEVER_SLUGS_BY_TIER);
3879
3917
  DEFAULT_WORKABLE_SLUGS = ["zego", "workmotion"];
3880
- MAX_JOBS_PER_COMPANY = 3;
3918
+ MAX_JOBS_PER_COMPANY = 10;
3881
3919
  BIGCO_SLUGS_BY_SOURCE = {
3882
3920
  greenhouse: new Set(GREENHOUSE_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
3883
3921
  ashby: new Set(ASHBY_SLUGS_BY_TIER.bigco.map((s) => s.toLowerCase())),
@@ -3926,7 +3964,7 @@ function makeClient(fetchImpl, cfg) {
3926
3964
  async function probe(path) {
3927
3965
  return gov.probe(`${GITHUB_API2}${path}`, { headers: authHeaders2() });
3928
3966
  }
3929
- return { raw, json, probe, setSecondaryHint: gov.setSecondaryHint, getStats: gov.getStats };
3967
+ return { raw, json, probe, governor: gov, setSecondaryHint: gov.setSecondaryHint, getStats: gov.getStats };
3930
3968
  }
3931
3969
  async function contributorCount(client, fullName) {
3932
3970
  const res = await client.raw(`/repos/${fullName}/contributors?per_page=1&anon=false`);
@@ -3942,14 +3980,27 @@ async function contributorCount(client, fullName) {
3942
3980
  }
3943
3981
  }
3944
3982
  async function openPRIssueRefs(client, fullName) {
3983
+ const token = process.env["GITHUB_TOKEN"] ?? process.env["GH_TOKEN"];
3984
+ const [owner, name] = fullName.split("/");
3985
+ if (token && owner && name) {
3986
+ const res = await openPRClosingRefs(owner, name, token, void 0, client.governor);
3987
+ if (res === null) return null;
3988
+ if (res.capHit) {
3989
+ console.warn(
3990
+ `[contribute] open-PR closing-ref scan capped at 100/${res.totalCount} open PRs for ${fullName} (closing refs beyond the first 100 open PRs not scanned)`
3991
+ );
3992
+ }
3993
+ return res.refs;
3994
+ }
3945
3995
  const prs = await client.json(
3946
3996
  `/repos/${fullName}/pulls?state=open&per_page=100`
3947
3997
  );
3948
3998
  if (!Array.isArray(prs)) return null;
3949
3999
  const refs = /* @__PURE__ */ new Set();
3950
4000
  for (const pr of prs) {
3951
- for (const m of `${pr.title ?? ""}
3952
- ${pr.body ?? ""}`.matchAll(/#(\d+)\b/g)) {
4001
+ const text = `${pr.title ?? ""}
4002
+ ${pr.body ?? ""}`;
4003
+ for (const m of text.matchAll(/\b(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi)) {
3953
4004
  refs.add(Number(m[1]));
3954
4005
  }
3955
4006
  }
@@ -3999,7 +4050,11 @@ function buildContributionJob(a) {
3999
4050
  // verified-empty/non-matching set; a failed check leaves it undefined (never a
4000
4051
  // fabricated 0), so the claim path falls through to a live re-count.
4001
4052
  openPRsAtDiscovery: a.openPRsAtDiscovery,
4002
- repoDescription: a.repo.description || null
4053
+ repoDescription: a.repo.description || null,
4054
+ // TERM-27: persist the repo's primary language so project curation can
4055
+ // exclude the repo's OWN language id (folded into every issue's tags) from
4056
+ // the distinct-skill signal. Same `repo` used by the tokenize() tag build.
4057
+ language: a.repo.language ?? null
4003
4058
  },
4004
4059
  // Provenance: repo-first discovered items only (label-first omits the field).
4005
4060
  ...a.discovered ? { discovered: true } : {},
@@ -4323,6 +4378,7 @@ var init_contributions = __esm({
4323
4378
  init_contribution_gate();
4324
4379
  init_contribution_classify();
4325
4380
  init_github_bounties();
4381
+ init_github();
4326
4382
  init_http();
4327
4383
  init_gh_governor();
4328
4384
  GITHUB_API2 = "https://api.github.com";
@@ -8171,11 +8227,35 @@ import { createHash as createHash2 } from "crypto";
8171
8227
  function opportunityShortToken(id) {
8172
8228
  return createHash2("sha256").update(id, "utf8").digest("base64url").slice(0, 8);
8173
8229
  }
8174
- var contributeShortToken;
8230
+ function jobShortToken(id) {
8231
+ return createHash2("sha256").update(`job:${id}`, "utf8").digest("base64url").slice(0, 8);
8232
+ }
8233
+ function jobTokenMap(index) {
8234
+ const cached = jobTokenMaps.get(index);
8235
+ if (cached) return cached;
8236
+ const map = /* @__PURE__ */ new Map();
8237
+ for (const job of index.jobs) {
8238
+ const token = jobShortToken(job.id);
8239
+ map.set(token, map.has(token) ? AMBIGUOUS_JOB_TOKEN : job);
8240
+ }
8241
+ jobTokenMaps.set(index, map);
8242
+ return map;
8243
+ }
8244
+ function resolveJobToken(index, token) {
8245
+ const hit = jobTokenMap(index).get(token);
8246
+ return hit && hit !== AMBIGUOUS_JOB_TOKEN ? hit : null;
8247
+ }
8248
+ function _jobTokenMapForTests(index) {
8249
+ return jobTokenMap(index);
8250
+ }
8251
+ var contributeShortToken, AMBIGUOUS_JOB_TOKEN, jobTokenMaps, _AMBIGUOUS_JOB_TOKEN_FOR_TESTS;
8175
8252
  var init_short_token = __esm({
8176
8253
  "../../packages/core/src/short-token.ts"() {
8177
8254
  "use strict";
8178
8255
  contributeShortToken = opportunityShortToken;
8256
+ AMBIGUOUS_JOB_TOKEN = /* @__PURE__ */ Symbol("ambiguous-job-token");
8257
+ jobTokenMaps = /* @__PURE__ */ new WeakMap();
8258
+ _AMBIGUOUS_JOB_TOKEN_FOR_TESTS = AMBIGUOUS_JOB_TOKEN;
8179
8259
  }
8180
8260
  });
8181
8261
 
@@ -8214,12 +8294,17 @@ __export(src_exports, {
8214
8294
  MIN_STARS: () => MIN_STARS,
8215
8295
  PROBE_TIMEOUT_MS: () => PROBE_TIMEOUT_MS,
8216
8296
  RIGOR: () => RIGOR,
8297
+ SKILL_DENSITY_SATURATION: () => SKILL_DENSITY_SATURATION,
8298
+ SKILL_FLOOR_ENABLED: () => SKILL_FLOOR_ENABLED,
8299
+ SKILL_FLOOR_MIN: () => SKILL_FLOOR_MIN,
8217
8300
  STRONG_MATCH_THRESHOLD: () => STRONG_MATCH_THRESHOLD,
8218
8301
  SYNONYMS: () => SYNONYMS,
8219
8302
  TRIVIAL_PR_TITLE: () => TRIVIAL_PR_TITLE,
8220
8303
  VOCABULARY: () => VOCABULARY,
8221
8304
  VOCAB_NODES: () => VOCAB_NODES,
8222
8305
  WINNABILITY_NORM: () => WINNABILITY_NORM,
8306
+ _AMBIGUOUS_JOB_TOKEN_FOR_TESTS: () => _AMBIGUOUS_JOB_TOKEN_FOR_TESTS,
8307
+ _jobTokenMapForTests: () => _jobTokenMapForTests,
8223
8308
  acceptanceCountForDomains: () => acceptanceCountForDomains,
8224
8309
  aggregate: () => aggregate,
8225
8310
  aggregateBounties: () => aggregateBounties,
@@ -8286,6 +8371,7 @@ __export(src_exports, {
8286
8371
  isOverIntroLimit: () => isOverIntroLimit,
8287
8372
  isTrivialPRTitle: () => isTrivialPRTitle,
8288
8373
  isWinnableIssue: () => isWinnableIssue,
8374
+ jobShortToken: () => jobShortToken,
8289
8375
  joinLabels: () => joinLabels,
8290
8376
  labelFor: () => labelFor,
8291
8377
  lever: () => lever,
@@ -8299,6 +8385,7 @@ __export(src_exports, {
8299
8385
  mergeProbability: () => mergeProbability,
8300
8386
  mmrRerank: () => mmrRerank,
8301
8387
  normalize: () => normalize,
8388
+ openPRClosingRefs: () => openPRClosingRefs,
8302
8389
  opire: () => opire,
8303
8390
  opportunityShortToken: () => opportunityShortToken,
8304
8391
  pageMatches: () => pageMatches,
@@ -8313,7 +8400,9 @@ __export(src_exports, {
8313
8400
  recordClick: () => recordClick,
8314
8401
  rejectExtraIntroFields: () => rejectExtraIntroFields,
8315
8402
  relevanceScore: () => relevanceScore,
8403
+ resolveJobToken: () => resolveJobToken,
8316
8404
  revealIntroContacts: () => revealIntroContacts,
8405
+ rosterActiveFromContribution: () => rosterActiveFromContribution,
8317
8406
  safetyNumber: () => safetyNumber,
8318
8407
  sameLogin: () => sameLogin,
8319
8408
  setStatus: () => setStatus,
@@ -8536,61 +8625,19 @@ var init_claims = __esm({
8536
8625
  }
8537
8626
  });
8538
8627
 
8539
- // ../../node_modules/keytar/build/Release/keytar.node
8540
- var keytar_default;
8541
- var init_keytar = __esm({
8542
- "../../node_modules/keytar/build/Release/keytar.node"() {
8543
- keytar_default = "../keytar-KOAAH267.node";
8544
- }
8545
- });
8546
-
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
8548
- 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) {
8550
- "use strict";
8551
- init_keytar();
8552
- try {
8553
- module.exports = __require(keytar_default);
8554
- } catch {
8555
- }
8556
- }
8557
- });
8558
-
8559
8628
  // ../../node_modules/keytar/lib/keytar.js
8560
- var require_keytar2 = __commonJS({
8629
+ var require_keytar = __commonJS({
8561
8630
  "../../node_modules/keytar/lib/keytar.js"(exports, module) {
8562
8631
  "use strict";
8563
- var keytar = require_keytar();
8564
- function checkRequired(val, name) {
8565
- if (!val || val.length <= 0) {
8566
- throw new Error(name + " is required.");
8567
- }
8632
+ function disabled() {
8633
+ throw new Error("keytar disabled in this dev checkout (keychain popup guard) \u2014 key-file fallback expected");
8568
8634
  }
8569
8635
  module.exports = {
8570
- getPassword: function(service, account) {
8571
- checkRequired(service, "Service");
8572
- checkRequired(account, "Account");
8573
- return keytar.getPassword(service, account);
8574
- },
8575
- setPassword: function(service, account, password) {
8576
- checkRequired(service, "Service");
8577
- checkRequired(account, "Account");
8578
- checkRequired(password, "Password");
8579
- return keytar.setPassword(service, account, password);
8580
- },
8581
- deletePassword: function(service, account) {
8582
- checkRequired(service, "Service");
8583
- checkRequired(account, "Account");
8584
- return keytar.deletePassword(service, account);
8585
- },
8586
- findPassword: function(service) {
8587
- checkRequired(service, "Service");
8588
- return keytar.findPassword(service);
8589
- },
8590
- findCredentials: function(service) {
8591
- checkRequired(service, "Service");
8592
- return keytar.findCredentials(service);
8593
- }
8636
+ getPassword: disabled,
8637
+ setPassword: disabled,
8638
+ deletePassword: disabled,
8639
+ findPassword: disabled,
8640
+ findCredentials: disabled
8594
8641
  };
8595
8642
  }
8596
8643
  });
@@ -8798,7 +8845,7 @@ function skipKeychain2() {
8798
8845
  async function tryLoadFromKeytar(policy) {
8799
8846
  if (forceKeytarUnavailableForTests || skipKeychain2()) return null;
8800
8847
  try {
8801
- const kt = policy === "keychain-required" ? createRequire(import.meta.url)("keytar") : await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
8848
+ const kt = policy === "keychain-required" ? createRequire(import.meta.url)("keytar") : await Promise.resolve().then(() => __toESM(require_keytar(), 1));
8802
8849
  const stored = await kt.getPassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
8803
8850
  if (stored) {
8804
8851
  return Buffer.from(stored, "hex");
@@ -8839,7 +8886,7 @@ async function deleteKey() {
8839
8886
  }
8840
8887
  if (!forceKeytarUnavailableForTests && !skipKeychain2()) {
8841
8888
  try {
8842
- const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
8889
+ const kt = await Promise.resolve().then(() => __toESM(require_keytar(), 1));
8843
8890
  await kt.deletePassword(KEYTAR_SERVICE, KEYTAR_ACCOUNT);
8844
8891
  } catch {
8845
8892
  }
@@ -9691,7 +9738,7 @@ function skipKeychain() {
9691
9738
  async function loadKey() {
9692
9739
  if (!skipKeychain()) {
9693
9740
  try {
9694
- const kt = await Promise.resolve().then(() => __toESM(require_keytar2(), 1));
9741
+ const kt = await Promise.resolve().then(() => __toESM(require_keytar(), 1));
9695
9742
  const stored = await kt.getPassword("terminalhire", "profile-key");
9696
9743
  if (stored) return Buffer.from(stored, "hex");
9697
9744
  const key2 = randomBytes4(KEY_BYTES);
@@ -24,7 +24,8 @@ var DEFAULT_CONFIG = {
24
24
  betaOptIn: false,
25
25
  lastFullFeedbackAt: null,
26
26
  lastPulseAskAt: null,
27
- pulseDisclosed: false
27
+ pulseDisclosed: false,
28
+ mix: "balanced"
28
29
  };
29
30
  function readConfig() {
30
31
  try {
@@ -42,6 +43,10 @@ function writeConfig(config) {
42
43
  const merged = { ...current, ...config };
43
44
  writeFileSync(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
44
45
  }
46
+ function parseSurfaceMix(raw) {
47
+ if (raw === "jobs" || raw === "balanced" || raw === "credential") return raw;
48
+ return null;
49
+ }
45
50
 
46
51
  // bin/jpi-config.js
47
52
  var TERMINALHIRE_DIR2 = process.env.TERMINALHIRE_DIR || join2(homedir2(), ".terminalhire");
@@ -52,9 +57,44 @@ function parseNudgeMode(raw) {
52
57
  if (m && parseInt(m[1], 10) >= 1) return raw;
53
58
  return null;
54
59
  }
60
+ function printMixValues() {
61
+ console.log(" Valid mix values (roles vs. contribution items on the ambient surface):");
62
+ console.log(" jobs \u2014 more roles, fewer contributions (contribute 5, roles ~15)");
63
+ console.log(" balanced \u2014 rebalanced default (contribute 8, roles ~12)");
64
+ console.log(" credential \u2014 contribution-forward (contribute 12, roles ~8)");
65
+ }
55
66
  async function run() {
56
67
  const args = process.argv.slice(2);
57
68
  const filtered = args[0] === "config" ? args.slice(1) : args;
69
+ if (filtered[0] === "get" && filtered[1] === "mix") {
70
+ const cfg = readConfig();
71
+ const envOverride = process.env["TH_MIX"];
72
+ console.log("");
73
+ console.log(` mix: ${cfg.mix}`);
74
+ if (envOverride) {
75
+ console.log(` (overridden by TH_MIX=${envOverride} at runtime)`);
76
+ }
77
+ console.log("");
78
+ printMixValues();
79
+ console.log("");
80
+ return;
81
+ }
82
+ if (filtered[0] === "set" && filtered[1] === "mix") {
83
+ const value = filtered[2];
84
+ if (!value) {
85
+ console.error("Error: config set mix requires a value: jobs | balanced | credential");
86
+ process.exit(1);
87
+ }
88
+ const parsed = parseSurfaceMix(value);
89
+ if (!parsed) {
90
+ console.error(`Error: invalid mix value "${value}". Valid: jobs | balanced | credential`);
91
+ process.exit(1);
92
+ }
93
+ writeConfig({ mix: parsed });
94
+ console.log(` mix set to: ${parsed}`);
95
+ console.log(` (saved to ${CONFIG_FILE2})`);
96
+ return;
97
+ }
58
98
  if (filtered.includes("--show") || filtered.length === 0) {
59
99
  const cfg = readConfig();
60
100
  const envOverride = process.env["TERMINALHIRE_NUDGE"];
@@ -66,6 +106,11 @@ async function run() {
66
106
  console.log(` (overridden by TERMINALHIRE_NUDGE=${envOverride} at runtime)`);
67
107
  }
68
108
  console.log(` peer-connect: ${cfg.peerConnect ? "on" : "off"} (ambient peer & founder surfacing; default off)`);
109
+ const mixEnv = process.env["TH_MIX"];
110
+ console.log(` mix: ${cfg.mix} (roles vs. contributions on the ambient surface; default balanced)`);
111
+ if (mixEnv) {
112
+ console.log(` (mix overridden by TH_MIX=${mixEnv} at runtime)`);
113
+ }
69
114
  console.log(` config file: ${CONFIG_FILE2}`);
70
115
  console.log("");
71
116
  console.log(" Valid nudge values:");
@@ -73,6 +118,9 @@ async function run() {
73
118
  console.log(" always \u2014 print every statusLine render when matches exist");
74
119
  console.log(" every:N \u2014 print every Nth render (e.g. every:3)");
75
120
  console.log("");
121
+ printMixValues();
122
+ console.log(" (set with: config set mix <value> \xB7 read with: config get mix)");
123
+ console.log("");
76
124
  console.log(" Peer-connect (--connect on|off):");
77
125
  console.log(" on \u2014 surface peers & founders in the spinner + send an anonymous matched signal");
78
126
  console.log(" off \u2014 no peer matching, no directory fetch, no signal (default)");
@@ -110,6 +158,8 @@ async function run() {
110
158
  }
111
159
  console.error("Usage: terminalhire config --nudge <session|always|every:N>");
112
160
  console.error(" terminalhire config --connect <on|off>");
161
+ console.error(" terminalhire config set mix <jobs|balanced|credential>");
162
+ console.error(" terminalhire config get mix");
113
163
  console.error(" terminalhire config --show");
114
164
  process.exit(1);
115
165
  }