terminalhire 0.42.17 → 0.42.18

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.
@@ -91,10 +91,14 @@ var init_state_dir = __esm({
91
91
  var claims_exports = {};
92
92
  __export(claims_exports, {
93
93
  CLAIM_STATES: () => CLAIM_STATES,
94
+ CLAIM_TURN_LABEL_WIDTH: () => CLAIM_TURN_LABEL_WIDTH,
94
95
  PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
95
96
  acceptedPRRate: () => acceptedPRRate,
97
+ claimTurn: () => claimTurn,
98
+ claimTurnLabel: () => claimTurnLabel,
96
99
  countAwaitingFounderApproval: () => countAwaitingFounderApproval,
97
100
  findClaim: () => findClaim,
101
+ formatAcceptedPRRate: () => formatAcceptedPRRate,
98
102
  listClaims: () => listClaims,
99
103
  nextPolledState: () => nextPolledState,
100
104
  readClaims: () => readClaims,
@@ -149,6 +153,12 @@ function withClaimsLock(fn) {
149
153
  rmSync2(LOCK_DIR, { recursive: true, force: true });
150
154
  }
151
155
  }
156
+ function claimTurn(state) {
157
+ return CLAIM_TURN_BY_STATE[state] ?? "you";
158
+ }
159
+ function claimTurnLabel(state) {
160
+ return CLAIM_TURN_LABEL[claimTurn(state)];
161
+ }
152
162
  function toPushedClaim(claim) {
153
163
  return {
154
164
  kind: claim.kind,
@@ -303,9 +313,18 @@ function countAwaitingFounderApproval(claims = readClaims()) {
303
313
  function acceptedPRRate(claims = readClaims()) {
304
314
  const total = claims.length;
305
315
  const merged = claims.filter((c) => c.state === "merged").length;
306
- return { merged, total, rate: total === 0 ? 0 : merged / total };
316
+ const decided = claims.filter((c) => DECIDED_STATES.has(c.state)).length;
317
+ return { merged, decided, inFlight: total - decided, total, rate: decided === 0 ? 0 : merged / decided };
318
+ }
319
+ function formatAcceptedPRRate(rate) {
320
+ const inFlight = rate.inFlight > 0 ? ` \xB7 ${rate.inFlight} in flight` : "";
321
+ if (rate.decided === 0) {
322
+ return `Accepted-PR rate: no claims decided yet${inFlight}`;
323
+ }
324
+ const pct = Math.round(rate.rate * 100);
325
+ return `Accepted-PR rate: ${rate.merged}/${rate.decided} decided claims merged (${pct}%)${inFlight}`;
307
326
  }
308
- var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS;
327
+ var TERMINALHIRE_DIR3, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, CLAIM_TURN_BY_STATE, CLAIM_TURN_LABEL, CLAIM_TURN_LABEL_WIDTH, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS, CONTROL_CHARS, DECIDED_STATES;
309
328
  var init_claims = __esm({
310
329
  "src/claims.ts"() {
311
330
  "use strict";
@@ -332,6 +351,24 @@ var init_claims = __esm({
332
351
  "abandoned"
333
352
  // released, or PR closed unmerged
334
353
  ]);
354
+ CLAIM_TURN_BY_STATE = Object.freeze({
355
+ claimed: "you",
356
+ working: "you",
357
+ "in-review": "you",
358
+ ready: "you",
359
+ submitted: "poster",
360
+ merged: "merged",
361
+ abandoned: "closed"
362
+ });
363
+ CLAIM_TURN_LABEL = Object.freeze({
364
+ you: "waiting on you",
365
+ poster: "waiting on poster",
366
+ merged: "merged",
367
+ closed: "closed"
368
+ });
369
+ CLAIM_TURN_LABEL_WIDTH = Math.max(
370
+ ...Object.values(CLAIM_TURN_LABEL).map((label) => label.length)
371
+ );
335
372
  PUSHED_CLAIM_FIELDS = [
336
373
  "kind",
337
374
  "repoFullName",
@@ -363,6 +400,7 @@ var init_claims = __esm({
363
400
  };
364
401
  WHITESPACE_CONTROLS = /[\t\n\v\f\r]+/g;
365
402
  CONTROL_CHARS = /[\x00-\x1f\x7f-\x9f]/g;
403
+ DECIDED_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
366
404
  }
367
405
  });
368
406
 
@@ -11254,10 +11254,14 @@ var init_repo_experience = __esm({
11254
11254
  var claims_exports = {};
11255
11255
  __export(claims_exports, {
11256
11256
  CLAIM_STATES: () => CLAIM_STATES,
11257
+ CLAIM_TURN_LABEL_WIDTH: () => CLAIM_TURN_LABEL_WIDTH,
11257
11258
  PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
11258
11259
  acceptedPRRate: () => acceptedPRRate,
11260
+ claimTurn: () => claimTurn,
11261
+ claimTurnLabel: () => claimTurnLabel,
11259
11262
  countAwaitingFounderApproval: () => countAwaitingFounderApproval,
11260
11263
  findClaim: () => findClaim,
11264
+ formatAcceptedPRRate: () => formatAcceptedPRRate,
11261
11265
  listClaims: () => listClaims,
11262
11266
  nextPolledState: () => nextPolledState,
11263
11267
  readClaims: () => readClaims,
@@ -11312,6 +11316,12 @@ function withClaimsLock(fn) {
11312
11316
  rmSync2(LOCK_DIR, { recursive: true, force: true });
11313
11317
  }
11314
11318
  }
11319
+ function claimTurn(state) {
11320
+ return CLAIM_TURN_BY_STATE[state] ?? "you";
11321
+ }
11322
+ function claimTurnLabel(state) {
11323
+ return CLAIM_TURN_LABEL[claimTurn(state)];
11324
+ }
11315
11325
  function toPushedClaim(claim) {
11316
11326
  return {
11317
11327
  kind: claim.kind,
@@ -11466,9 +11476,18 @@ function countAwaitingFounderApproval(claims = readClaims()) {
11466
11476
  function acceptedPRRate(claims = readClaims()) {
11467
11477
  const total = claims.length;
11468
11478
  const merged = claims.filter((c) => c.state === "merged").length;
11469
- return { merged, total, rate: total === 0 ? 0 : merged / total };
11479
+ const decided = claims.filter((c) => DECIDED_STATES.has(c.state)).length;
11480
+ return { merged, decided, inFlight: total - decided, total, rate: decided === 0 ? 0 : merged / decided };
11481
+ }
11482
+ function formatAcceptedPRRate(rate) {
11483
+ const inFlight = rate.inFlight > 0 ? ` \xB7 ${rate.inFlight} in flight` : "";
11484
+ if (rate.decided === 0) {
11485
+ return `Accepted-PR rate: no claims decided yet${inFlight}`;
11486
+ }
11487
+ const pct = Math.round(rate.rate * 100);
11488
+ return `Accepted-PR rate: ${rate.merged}/${rate.decided} decided claims merged (${pct}%)${inFlight}`;
11470
11489
  }
11471
- var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
11490
+ var TERMINALHIRE_DIR5, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, CLAIM_TURN_BY_STATE, CLAIM_TURN_LABEL, CLAIM_TURN_LABEL_WIDTH, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2, DECIDED_STATES;
11472
11491
  var init_claims = __esm({
11473
11492
  "src/claims.ts"() {
11474
11493
  "use strict";
@@ -11495,6 +11514,24 @@ var init_claims = __esm({
11495
11514
  "abandoned"
11496
11515
  // released, or PR closed unmerged
11497
11516
  ]);
11517
+ CLAIM_TURN_BY_STATE = Object.freeze({
11518
+ claimed: "you",
11519
+ working: "you",
11520
+ "in-review": "you",
11521
+ ready: "you",
11522
+ submitted: "poster",
11523
+ merged: "merged",
11524
+ abandoned: "closed"
11525
+ });
11526
+ CLAIM_TURN_LABEL = Object.freeze({
11527
+ you: "waiting on you",
11528
+ poster: "waiting on poster",
11529
+ merged: "merged",
11530
+ closed: "closed"
11531
+ });
11532
+ CLAIM_TURN_LABEL_WIDTH = Math.max(
11533
+ ...Object.values(CLAIM_TURN_LABEL).map((label) => label.length)
11534
+ );
11498
11535
  PUSHED_CLAIM_FIELDS = [
11499
11536
  "kind",
11500
11537
  "repoFullName",
@@ -11526,6 +11563,7 @@ var init_claims = __esm({
11526
11563
  };
11527
11564
  WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
11528
11565
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
11566
+ DECIDED_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
11529
11567
  }
11530
11568
  });
11531
11569
 
@@ -10563,10 +10563,14 @@ var init_state_dir = __esm({
10563
10563
  var claims_exports = {};
10564
10564
  __export(claims_exports, {
10565
10565
  CLAIM_STATES: () => CLAIM_STATES,
10566
+ CLAIM_TURN_LABEL_WIDTH: () => CLAIM_TURN_LABEL_WIDTH,
10566
10567
  PUSHED_CLAIM_FIELDS: () => PUSHED_CLAIM_FIELDS,
10567
10568
  acceptedPRRate: () => acceptedPRRate,
10569
+ claimTurn: () => claimTurn,
10570
+ claimTurnLabel: () => claimTurnLabel,
10568
10571
  countAwaitingFounderApproval: () => countAwaitingFounderApproval,
10569
10572
  findClaim: () => findClaim,
10573
+ formatAcceptedPRRate: () => formatAcceptedPRRate,
10570
10574
  listClaims: () => listClaims,
10571
10575
  nextPolledState: () => nextPolledState,
10572
10576
  readClaims: () => readClaims,
@@ -10621,6 +10625,12 @@ function withClaimsLock(fn) {
10621
10625
  rmSync(LOCK_DIR, { recursive: true, force: true });
10622
10626
  }
10623
10627
  }
10628
+ function claimTurn(state) {
10629
+ return CLAIM_TURN_BY_STATE[state] ?? "you";
10630
+ }
10631
+ function claimTurnLabel(state) {
10632
+ return CLAIM_TURN_LABEL[claimTurn(state)];
10633
+ }
10624
10634
  function toPushedClaim(claim) {
10625
10635
  return {
10626
10636
  kind: claim.kind,
@@ -10775,9 +10785,18 @@ function countAwaitingFounderApproval(claims = readClaims()) {
10775
10785
  function acceptedPRRate(claims = readClaims()) {
10776
10786
  const total = claims.length;
10777
10787
  const merged = claims.filter((c) => c.state === "merged").length;
10778
- return { merged, total, rate: total === 0 ? 0 : merged / total };
10788
+ const decided = claims.filter((c) => DECIDED_STATES.has(c.state)).length;
10789
+ return { merged, decided, inFlight: total - decided, total, rate: decided === 0 ? 0 : merged / decided };
10790
+ }
10791
+ function formatAcceptedPRRate(rate) {
10792
+ const inFlight = rate.inFlight > 0 ? ` \xB7 ${rate.inFlight} in flight` : "";
10793
+ if (rate.decided === 0) {
10794
+ return `Accepted-PR rate: no claims decided yet${inFlight}`;
10795
+ }
10796
+ const pct = Math.round(rate.rate * 100);
10797
+ return `Accepted-PR rate: ${rate.merged}/${rate.decided} decided claims merged (${pct}%)${inFlight}`;
10779
10798
  }
10780
- var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2;
10799
+ var TERMINALHIRE_DIR2, CLAIMS_FILE, LOCK_DIR, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, CLAIM_STATES, CLAIM_TURN_BY_STATE, CLAIM_TURN_LABEL, CLAIM_TURN_LABEL_WIDTH, PUSHED_CLAIM_FIELDS, TERMINAL_STATES, POLL_TRANSITIONS, WHITESPACE_CONTROLS2, CONTROL_CHARS2, DECIDED_STATES;
10781
10800
  var init_claims = __esm({
10782
10801
  "src/claims.ts"() {
10783
10802
  "use strict";
@@ -10804,6 +10823,24 @@ var init_claims = __esm({
10804
10823
  "abandoned"
10805
10824
  // released, or PR closed unmerged
10806
10825
  ]);
10826
+ CLAIM_TURN_BY_STATE = Object.freeze({
10827
+ claimed: "you",
10828
+ working: "you",
10829
+ "in-review": "you",
10830
+ ready: "you",
10831
+ submitted: "poster",
10832
+ merged: "merged",
10833
+ abandoned: "closed"
10834
+ });
10835
+ CLAIM_TURN_LABEL = Object.freeze({
10836
+ you: "waiting on you",
10837
+ poster: "waiting on poster",
10838
+ merged: "merged",
10839
+ closed: "closed"
10840
+ });
10841
+ CLAIM_TURN_LABEL_WIDTH = Math.max(
10842
+ ...Object.values(CLAIM_TURN_LABEL).map((label) => label.length)
10843
+ );
10807
10844
  PUSHED_CLAIM_FIELDS = [
10808
10845
  "kind",
10809
10846
  "repoFullName",
@@ -10835,6 +10872,7 @@ var init_claims = __esm({
10835
10872
  };
10836
10873
  WHITESPACE_CONTROLS2 = /[\t\n\v\f\r]+/g;
10837
10874
  CONTROL_CHARS2 = /[\x00-\x1f\x7f-\x9f]/g;
10875
+ DECIDED_STATES = /* @__PURE__ */ new Set(["merged", "abandoned"]);
10838
10876
  }
10839
10877
  });
10840
10878
 
@@ -34740,7 +34778,9 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
34740
34778
  "intent",
34741
34779
  "eta",
34742
34780
  "dir",
34743
- "open"
34781
+ "open",
34782
+ "reason",
34783
+ "note"
34744
34784
  ]);
34745
34785
  function parseArgs(argv) {
34746
34786
  const flags = {};
@@ -35307,9 +35347,8 @@ function fmtClaimAmount(c) {
35307
35347
  return c.kind === "contribution" ? "contribution" : fmtAmount(c.amountUSD);
35308
35348
  }
35309
35349
  function printMetric(rate) {
35310
- const pct = Math.round(rate.rate * 100);
35311
35350
  console.log(`
35312
- \u{1F4CA} Accepted-PR rate: ${rate.merged}/${rate.total} claims merged (${pct}%)`);
35351
+ \u{1F4CA} ${formatAcceptedPRRate(rate)}`);
35313
35352
  }
35314
35353
  var SUBMIT_ACCEPTS = Object.freeze(["working", "ready"]);
35315
35354
  var REVISE_RECOVERY_STATES = Object.freeze(["working", "in-review", "ready"]);
@@ -36417,11 +36456,12 @@ ${list.length} ${active ? "active " : ""}claim${list.length === 1 ? "" : "s"}:
36417
36456
  `);
36418
36457
  for (const c of list) {
36419
36458
  const pr = c.prUrl ? ` \xB7 ${c.prUrl}` : "";
36420
- console.log(` [${c.state}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
36421
- console.log(` id: ${c.id}${pr}`);
36459
+ console.log(` [${claimTurnLabel(c.state)}] ${fmtClaimAmount(c)} \xB7 ${c.title}`);
36460
+ console.log(` id: ${c.id} \xB7 ${c.state}${pr}`);
36422
36461
  }
36423
36462
  printMetric(claims.acceptedPRRate());
36424
36463
  printNextSteps(list);
36464
+ printResolveHint(list, console.log);
36425
36465
  try {
36426
36466
  if (await shouldNudgeUnpushed()) {
36427
36467
  console.log(
@@ -36525,23 +36565,24 @@ async function syncFounderApprovals(claimsModule, targets) {
36525
36565
  return { pushToken, approvalsChecked, approvalsUnavailable };
36526
36566
  }
36527
36567
  function founderClaimStanding(claim, approvalsChecked) {
36568
+ const turn = claimTurnLabel(claim.state);
36528
36569
  switch (claim.state) {
36529
36570
  case "merged":
36530
- return "accepted";
36571
+ return `${turn} \u2014 accepted by the poster`;
36531
36572
  case "abandoned":
36532
- return "rejected or closed locally";
36573
+ return `${turn} \u2014 rejected, or released locally`;
36533
36574
  case "submitted":
36534
- return `submitted \u2014 CI and the poster's verdict: ${nextStep(`terminalhire claim runs ${claim.id}`)}`;
36575
+ return `${turn} \u2014 CI and the verdict: ${nextStep(`terminalhire claim runs ${claim.id}`)}`;
36535
36576
  case "working":
36536
36577
  case "in-review":
36537
36578
  case "ready":
36538
- return `approved, slice received \u2014 ${claim.state}`;
36579
+ return `${turn} \u2014 approved, slice received (${claim.state})`;
36539
36580
  default:
36540
36581
  break;
36541
36582
  }
36542
- if (claim.approval?.mode === "open") return "registered \u2014 slice ready (no approval required)";
36543
- if (claim.approval?.state === "granted") return "approved, slice ready";
36544
- return approvalsChecked ? "registered \u2014 awaiting the poster's approval" : "registered locally";
36583
+ if (claim.approval?.mode === "open") return `${turn} \u2014 slice ready (no approval required)`;
36584
+ if (claim.approval?.state === "granted") return `${turn} \u2014 approved, slice ready`;
36585
+ return approvalsChecked ? "waiting on poster \u2014 approval not granted yet" : `${turn} \u2014 registered locally, approval not checked`;
36545
36586
  }
36546
36587
  async function cmdStatus(id) {
36547
36588
  const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
@@ -36719,8 +36760,26 @@ async function cmdUpdate(id, state, prUrl) {
36719
36760
  console.log(`Updated ${id} \u2192 ${state}${prUrl ? ` (PR: ${prUrl})` : ""}`);
36720
36761
  await beatFounderPresence(updated);
36721
36762
  }
36722
- async function cmdRelease(id, flags = {}) {
36723
- const claims = await Promise.resolve().then(() => (init_claims(), claims_exports));
36763
+ function printReleaseIsLocalOnly(id, claim, log, standDownOutcome = null) {
36764
+ log("");
36765
+ log(" Release updates this machine \u2014 terminalhire is not told, so the poster hears");
36766
+ log(" nothing about it.");
36767
+ if (standDownOutcome === "posted") {
36768
+ log(" Your stand-down comment did go to the issue on GitHub.");
36769
+ } else if (standDownOutcome === "failed") {
36770
+ log(" Your stand-down comment was attempted and errored, so whether it reached the");
36771
+ log(" issue is unknown. Check it there.");
36772
+ }
36773
+ if (claim?.approval?.claimId) {
36774
+ log(" To tell the poster why you are handing it back, run this first:");
36775
+ log(` terminalhire claim resolve ${id} --reason <reason>`);
36776
+ }
36777
+ log("");
36778
+ }
36779
+ async function cmdRelease(id, flags = {}, deps = {}) {
36780
+ const claims = deps.claimsModule ?? await Promise.resolve().then(() => (init_claims(), claims_exports));
36781
+ const log = deps.log ?? console.log;
36782
+ const standDown = deps.standDown ?? offerStandDown;
36724
36783
  if (!id) {
36725
36784
  console.error("Usage: terminalhire claim release <id> [--force]");
36726
36785
  process.exit(1);
@@ -36739,24 +36798,39 @@ async function cmdRelease(id, flags = {}) {
36739
36798
  }
36740
36799
  let standDownOutcome = null;
36741
36800
  if (target && target.stake && target.stake.branch === "contested") {
36742
- const outcome = await offerStandDown(target);
36801
+ const outcome = await standDown(target);
36743
36802
  standDownOutcome = outcome;
36744
- if (outcome === "skipped" || outcome === "failed") {
36803
+ if (outcome === "skipped" || outcome === "failed" || outcome === "unchecked") {
36745
36804
  if (!flags.force) {
36746
- console.log(
36747
- `
36748
- Not released \u2014 ${id} still has a public stake on the issue and nothing has retracted it.`
36749
- );
36750
- console.log(" Retract it, then release:");
36751
- console.log(` terminalhire claim release ${id} # at a real terminal`);
36752
- console.log(
36753
- ` terminalhire claim release ${id} --force # release anyway, stake stays up`
36805
+ log("");
36806
+ if (outcome === "failed") {
36807
+ log(` Not released \u2014 whether your stand-down comment on ${id} reached the issue is`);
36808
+ log(" unknown: the post errored after it went out.");
36809
+ log(" Read the issue before writing one by hand \u2014 it may already be there.");
36810
+ log(" Once the issue reads as stood down, release:");
36811
+ } else if (outcome === "unchecked") {
36812
+ log(` Not released \u2014 whether your stake comment on ${id} ever reached the issue`);
36813
+ log(" is unknown: the post errored, and the issue could not be read to check.");
36814
+ log(" Read the issue. If your comment is there, reply that you are standing");
36815
+ log(" down; then release:");
36816
+ } else {
36817
+ log(
36818
+ ` Not released \u2014 ${id} still has a public stake on the issue and nothing has retracted it.`
36819
+ );
36820
+ log(" Retract it, then release:");
36821
+ }
36822
+ log(` terminalhire claim release ${id} # at a real terminal`);
36823
+ log(
36824
+ outcome === "failed" ? ` terminalhire claim release ${id} --force # release anyway; delivery stays unknown` : outcome === "unchecked" ? ` terminalhire claim release ${id} --force # release anyway; standing unknown` : ` terminalhire claim release ${id} --force # release anyway, stake stays up`
36754
36825
  );
36755
36826
  return;
36756
36827
  }
36757
- console.log(" (--force \u2014 releasing with the public stake still standing)");
36828
+ log(
36829
+ outcome === "failed" ? " (--force \u2014 releasing without knowing whether your stand-down comment landed)" : outcome === "unchecked" ? " (--force \u2014 releasing; whether your stake comment ever landed is unknown)" : " (--force \u2014 releasing with the public stake still standing)"
36830
+ );
36758
36831
  }
36759
36832
  }
36833
+ if (target) printReleaseIsLocalOnly(id, target, log, standDownOutcome);
36760
36834
  const expectedStake = standDownOutcome === "absent" ? null : target?.stake?.postedAt ?? null;
36761
36835
  const removed = claims.removeClaimIfStakeMatches(id, expectedStake);
36762
36836
  if (!removed && claims.findClaim(id)) {
@@ -36798,7 +36872,7 @@ async function offerStandDown(claim) {
36798
36872
  console.log(
36799
36873
  ` (couldn't reach ${repoFullName}#${number} to check whether your comment posted \u2014 check it manually; the claim is kept so the stake isn't lost)`
36800
36874
  );
36801
- return "skipped";
36875
+ return "unchecked";
36802
36876
  }
36803
36877
  }
36804
36878
  if (!process.stdin.isTTY) {
@@ -36820,7 +36894,7 @@ async function offerStandDown(claim) {
36820
36894
  return "posted";
36821
36895
  } catch (err) {
36822
36896
  console.log(
36823
- ` (could not post the stand-down: ${err.stderr || err.message || err} \u2014 do it manually)`
36897
+ ` (the stand-down post errored: ${err.stderr || err.message || err}. It may still have landed \u2014 read ${repoFullName}#${number} before writing one by hand.)`
36824
36898
  );
36825
36899
  return "failed";
36826
36900
  }
@@ -36969,7 +37043,13 @@ async function cmdStart(id, flags = {}) {
36969
37043
  if (!claim) {
36970
37044
  const b = await resolveBounty(id);
36971
37045
  if (!b) {
36972
- const { recovered, reason, id: recoveredId, linkedHost, currentHost } = await recoverHeldClaim(id, flags);
37046
+ const {
37047
+ recovered,
37048
+ reason,
37049
+ id: recoveredId,
37050
+ linkedHost,
37051
+ currentHost
37052
+ } = await recoverHeldClaim(id, flags);
36973
37053
  if (recovered) {
36974
37054
  claim = recovered;
36975
37055
  id = recoveredId;
@@ -37034,7 +37114,9 @@ async function cmdStart(id, flags = {}) {
37034
37114
  if (!chainedFromRecord) {
37035
37115
  console.log(`
37036
37116
  ${sanitizeText(claim.title)}`);
37037
- console.log("\n No fork was attempted \u2014 terminalhire postings are never forked or cloned. Your");
37117
+ console.log(
37118
+ "\n No fork was attempted \u2014 terminalhire postings are never forked or cloned. Your"
37119
+ );
37038
37120
  console.log(" work slice is delivered through terminalhire, and your patch goes back the");
37039
37121
  console.log(" same way.");
37040
37122
  }
@@ -39315,6 +39397,162 @@ async function acknowledgeNotesQuietly(notes) {
39315
39397
  } catch {
39316
39398
  }
39317
39399
  }
39400
+ var CLAIM_RESOLUTION_REASONS = [
39401
+ "not-my-stack",
39402
+ "out-of-time",
39403
+ "already-implemented",
39404
+ "repo-does-not-build",
39405
+ "brief-insufficient"
39406
+ ];
39407
+ var POSTING_LEVEL_RESOLUTION_REASONS = [
39408
+ "already-implemented",
39409
+ "repo-does-not-build",
39410
+ "brief-insufficient"
39411
+ ];
39412
+ var RESOLUTION_REASON_BLURB = {
39413
+ "not-my-stack": "this is not the kind of work you take",
39414
+ "out-of-time": "you ran out of time for it",
39415
+ "already-implemented": "the repo already has this",
39416
+ "repo-does-not-build": "the repo will not build, so nobody can finish it",
39417
+ "brief-insufficient": "the brief does not say enough to do the work"
39418
+ };
39419
+ function isPostingLevelResolutionReason(reason) {
39420
+ return POSTING_LEVEL_RESOLUTION_REASONS.includes(reason);
39421
+ }
39422
+ function resolutionReasonHelpLines() {
39423
+ const lines = [];
39424
+ const width = Math.max(...CLAIM_RESOLUTION_REASONS.map((r) => r.length));
39425
+ const halves = [
39426
+ ["These take the posting off the market until the poster acts:", true],
39427
+ ["These hand it back for someone else:", false]
39428
+ ];
39429
+ for (const [heading, postingLevel] of halves) {
39430
+ lines.push(` ${heading}`);
39431
+ for (const reason of CLAIM_RESOLUTION_REASONS) {
39432
+ if (isPostingLevelResolutionReason(reason) !== postingLevel) continue;
39433
+ lines.push(` ${reason.padEnd(width)} ${RESOLUTION_REASON_BLURB[reason] ?? ""}`);
39434
+ }
39435
+ lines.push("");
39436
+ }
39437
+ return lines;
39438
+ }
39439
+ function resolveUsageLines() {
39440
+ return [
39441
+ 'Usage: terminalhire claim resolve <id> --reason <reason> [--note "..."]',
39442
+ "",
39443
+ " Hand a claim back and tell the poster why. Needs `terminalhire link`.",
39444
+ "",
39445
+ ...resolutionReasonHelpLines()
39446
+ ];
39447
+ }
39448
+ function printResolveHint(list, log) {
39449
+ const resolvable = list.filter(
39450
+ (c) => c?.approval?.claimId && !["merged", "abandoned"].includes(c.state)
39451
+ );
39452
+ if (resolvable.length === 0) return;
39453
+ log("");
39454
+ log(" Cannot finish one of these? Say why, and the poster hears it:");
39455
+ log(` terminalhire claim resolve <id> --reason <reason> (\`--help\` lists them)`);
39456
+ }
39457
+ async function cmdResolve(id, flags = {}, deps = {}) {
39458
+ const log = deps.log ?? console.log;
39459
+ const err = deps.err ?? console.error;
39460
+ const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
39461
+ const askImpl = deps.ask ?? ask;
39462
+ const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
39463
+ if (flags.help) {
39464
+ for (const line of resolveUsageLines()) log(line);
39465
+ return 0;
39466
+ }
39467
+ if (!id) {
39468
+ for (const line of resolveUsageLines()) err(line);
39469
+ return 1;
39470
+ }
39471
+ const claims = deps.claimsModule ?? await Promise.resolve().then(() => (init_claims(), claims_exports));
39472
+ const claim = claims.findClaim(id);
39473
+ if (!claim) {
39474
+ err(`terminalhire claim resolve: no claim with id '${id}'.`);
39475
+ return 1;
39476
+ }
39477
+ const claimId = claim.approval?.claimId ?? null;
39478
+ if (!claimId) {
39479
+ err(`terminalhire claim resolve: '${id}' is not a terminalhire posting, so there is`);
39480
+ err(" no poster to tell. Use `terminalhire claim release` to drop it locally.");
39481
+ return 1;
39482
+ }
39483
+ let reason = typeof flags.reason === "string" ? flags.reason.trim() : "";
39484
+ if (!reason) {
39485
+ if (!isTTY) {
39486
+ err("terminalhire claim resolve: --reason is required when there is no terminal.");
39487
+ for (const line of resolutionReasonHelpLines()) err(line);
39488
+ return 1;
39489
+ }
39490
+ log("");
39491
+ log(` Handing back ${id}. Why?`);
39492
+ log("");
39493
+ for (const line of resolutionReasonHelpLines()) log(line);
39494
+ reason = (await askImpl(" reason: ")).trim();
39495
+ }
39496
+ if (!CLAIM_RESOLUTION_REASONS.includes(reason)) {
39497
+ err(`terminalhire claim resolve: '${reason}' is not one of the reasons.`);
39498
+ for (const line of resolutionReasonHelpLines()) err(line);
39499
+ return 1;
39500
+ }
39501
+ const linked = deps.cookie ? { cookie: deps.cookie, mismatch: null } : webSessionCookieForHost(CLAIM_SYNC_BASE4);
39502
+ if (linked.mismatch) {
39503
+ err(
39504
+ `terminalhire claim resolve: your session was minted by ${linked.mismatch.linkedHost}, and this is ${linked.mismatch.currentHost}. Run \`terminalhire link\` against this one.`
39505
+ );
39506
+ return 1;
39507
+ }
39508
+ if (!linked.cookie) {
39509
+ err("terminalhire claim resolve: needs a linked session. Run `terminalhire link` first.");
39510
+ return 1;
39511
+ }
39512
+ const note = typeof flags.note === "string" && flags.note.trim() ? flags.note.trim() : null;
39513
+ const body = { bountyId: founderPostingIdOf(claim), claimId, reason };
39514
+ if (note) body.note = note;
39515
+ let res;
39516
+ try {
39517
+ res = await fetchImpl(`${CLAIM_SYNC_BASE4}/api/claim/resolution`, {
39518
+ method: "POST",
39519
+ headers: {
39520
+ "Content-Type": "application/json",
39521
+ Cookie: `${GH_SESSION_COOKIE}=${linked.cookie}`
39522
+ },
39523
+ body: JSON.stringify(body),
39524
+ signal: AbortSignal.timeout(15e3)
39525
+ });
39526
+ } catch {
39527
+ err("terminalhire claim resolve: could not reach terminalhire just now. Nothing changed.");
39528
+ return 1;
39529
+ }
39530
+ let answer = null;
39531
+ try {
39532
+ answer = await res.json();
39533
+ } catch {
39534
+ }
39535
+ if (!res.ok) {
39536
+ if (res.status === 401) {
39537
+ err("terminalhire claim resolve: terminalhire did not recognise your session.");
39538
+ err(" Run `terminalhire link` and try again.");
39539
+ return 1;
39540
+ }
39541
+ const message = typeof answer?.error === "string" && answer.error ? answer.error : `terminalhire answered ${res.status}`;
39542
+ err(`terminalhire claim resolve: ${message}`);
39543
+ return 1;
39544
+ }
39545
+ const suspended = typeof answer?.postingSuspended === "boolean" ? answer.postingSuspended : isPostingLevelResolutionReason(reason);
39546
+ log("");
39547
+ log(` \u2713 Handed ${id} back \u2014 reason: ${reason}`);
39548
+ log(
39549
+ suspended ? " The posting is off the market until the poster acts on it." : " The posting is open again for someone else."
39550
+ );
39551
+ log("");
39552
+ log(` The local record is still here. To drop it: terminalhire claim release ${id}`);
39553
+ log("");
39554
+ return 0;
39555
+ }
39318
39556
  async function run() {
39319
39557
  const verb = process.argv[2];
39320
39558
  const rawArgs = process.argv.slice(3).map((a) => a === "-y" ? "--yes" : a);
@@ -39374,6 +39612,15 @@ async function run() {
39374
39612
  case "release":
39375
39613
  await cmdRelease(positional[0], flags);
39376
39614
  break;
39615
+ // Sits beside `release` because that is where a developer giving up on a claim
39616
+ // already looks, and the two are the local and the told-the-poster halves of the
39617
+ // same act. `cmdResolve` returns its exit code rather than calling process.exit,
39618
+ // so its refusals can be tested without spawning a process.
39619
+ case "resolve": {
39620
+ const code = await cmdResolve(positional[0], flags);
39621
+ if (code !== 0) process.exit(code);
39622
+ break;
39623
+ }
39377
39624
  case "audit":
39378
39625
  await cmdAudit(positional[0], flags);
39379
39626
  break;
@@ -39382,7 +39629,7 @@ async function run() {
39382
39629
  break;
39383
39630
  default:
39384
39631
  console.error(
39385
- `terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | slice | list | status | update | submit | runs | notes | audit | release`
39632
+ `terminalhire claim: unknown verb '${verb ?? ""}'. Expected: preview | record | start | attach | slice | list | status | update | submit | runs | notes | audit | release | resolve`
39386
39633
  );
39387
39634
  process.exit(1);
39388
39635
  }
@@ -39397,8 +39644,10 @@ export {
39397
39644
  BRIEF_DIR,
39398
39645
  BRIEF_REL_PATH,
39399
39646
  CLAIM_CONSENT_VERSION,
39647
+ CLAIM_RESOLUTION_REASONS,
39400
39648
  CLOSED_STATES,
39401
39649
  OPENABLE_AGENTS,
39650
+ POSTING_LEVEL_RESOLUTION_REASONS,
39402
39651
  PUSH_TOKEN_REFUSAL,
39403
39652
  REVISE_RECOVERY_STATES,
39404
39653
  SUBMIT_ACCEPTS,
@@ -39417,6 +39666,8 @@ export {
39417
39666
  cloneFullTierRepo,
39418
39667
  cmdPush,
39419
39668
  cmdRecord,
39669
+ cmdRelease,
39670
+ cmdResolve,
39420
39671
  cmdRuns,
39421
39672
  cmdSlice,
39422
39673
  cmdSliceFullTier,
@@ -39439,6 +39690,7 @@ export {
39439
39690
  indexNativeIdForShortRef,
39440
39691
  inferSubmitClaim,
39441
39692
  isContested,
39693
+ isPostingLevelResolutionReason,
39442
39694
  isStrayArgShortRefClaim,
39443
39695
  isTerminalRunStatus,
39444
39696
  isVerblessShortRefClaim,
@@ -39455,12 +39707,15 @@ export {
39455
39707
  pickExistingPr,
39456
39708
  pickStartableClaim,
39457
39709
  printNextSteps,
39710
+ printReleaseIsLocalOnly,
39711
+ printResolveHint,
39458
39712
  readCredentialDisposition,
39459
39713
  recoverHeldClaim,
39460
39714
  renderAutoConsent,
39461
39715
  renderClaimHistory,
39462
39716
  renderRunView,
39463
39717
  renderServerRefusal,
39718
+ resolutionReasonHelpLines,
39464
39719
  resolveBounty,
39465
39720
  resolveDeliveryDir,
39466
39721
  resolveSubmitWorktree,