@biffo/cli 0.298.0 → 0.298.2

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 (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/claim.sh +80 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.298.0",
3
+ "version": "0.298.2",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/claim.sh CHANGED
@@ -241,20 +241,38 @@ gh_label() { if [ -n "$REPO" ]; then gh label "$@" --repo "$REPO"; else gh label
241
241
  # Does the newest claim comment on $1 carry holder token $2? (#1279)
242
242
  #
243
243
  # Reads the LAST claim comment rather than any, so a re-claim by a different
244
- # session supersedes an older one rather than both matching for ever. Returns
245
- # non-zero when it cannot tell -- an unreadable comment list must never read as
246
- # "yours", or the flag becomes a way to steal a claim by guessing.
244
+ # session supersedes an older one rather than both matching for ever.
245
+ #
246
+ # TRI-STATE return, not a boolean (#1691). `gh issue view --json comments`
247
+ # failing (network, auth, wrong repo) and the comment list genuinely holding
248
+ # no claim of $2's are NOT the same fact, and collapsing both onto a single
249
+ # non-zero used to make every caller unable to tell "we don't know" from "we
250
+ # checked and it's someone else's" -- with opposite costs depending which
251
+ # side reads it: `--release` reported a definite-sounding "not held by you"
252
+ # for a claim it simply could not read, and the claim path had no way to
253
+ # refuse ONLY on genuine ambiguity without also refusing every ordinary
254
+ # non-match.
255
+ #
256
+ # 0 held by $2
257
+ # 1 determined NOT held by $2 -- the read succeeded, no match
258
+ # 2 cannot tell -- the read itself failed
259
+ #
260
+ # An unreadable comment list must never read as "yours" (a caller could steal
261
+ # a claim by guessing) and must never read as plain "not yours" either (a
262
+ # caller on the CLAIM path would then treat "cannot tell" as "free", which is
263
+ # exactly the fail-open this replaces). Every caller below must branch on all
264
+ # three values, not just truthy/falsy.
247
265
  #
248
266
  # Requires $2 non-empty (#826): the match below is `*"$HOLDER_MARK$2"*`, and
249
267
  # with $2 empty that collapses to `*"claim-holder:"*` -- true of ANY comment
250
- # naming ANY holder at all, regardless of whose. An empty token must never
251
- # read as "matches every claim"; it is refused explicitly, before the network
252
- # call this would otherwise spend.
268
+ # naming ANY holder at all, regardless of whose. An empty token is a caller
269
+ # bug, not an unreadable issue, so it is refused as a definite non-match (1),
270
+ # before the network call this would otherwise spend.
253
271
  claim_held_by() {
254
272
  [ -n "$2" ] || return 1
255
273
  _c=$(gh_issue view "$1" --json comments \
256
274
  --jq "[.comments[]? | select(.body | contains(\"$HOLDER_MARK\"))] | last | .body // \"\"" \
257
- 2>/dev/null) || return 1
275
+ 2>/dev/null) || return 2
258
276
  [ -n "$_c" ] || return 1
259
277
  case "$_c" in
260
278
  *"$HOLDER_MARK$2"*) return 0 ;;
@@ -547,10 +565,19 @@ LABEL=in-progress
547
565
  # person, and appears in a public comment.
548
566
 
549
567
  TAKEN=0
568
+ CANNOT_TELL=0
550
569
  REASONS=""
551
570
 
552
571
  note() { REASONS="${REASONS} $1\n"; TAKEN=1; }
553
572
 
573
+ # A DEFINITE signal (open PR, remote branch, someone else's label) always
574
+ # outranks an ambiguous one: if anything sets TAKEN=1 the verdict is "Taken"
575
+ # regardless of CANNOT_TELL, so an unreadable check downgrades the verdict
576
+ # only when nothing conclusive was found either way (#1691). See
577
+ # `claim_held_by` above for why "cannot tell" must never collapse into either
578
+ # "free" or "taken" for certain.
579
+ note_cannot_tell() { REASONS="${REASONS} $1\n"; CANNOT_TELL=1; }
580
+
554
581
  # --- --release <token>: only the holder clears it (#1279) --------------------
555
582
  #
556
583
  # A claim nobody can prove ownership of is one anybody can clear, and a stale
@@ -575,7 +602,9 @@ if [ -n "$RELEASE" ]; then
575
602
  echo "${DIM} gh issue edit $ISSUE --remove-label $LABEL${OFF}" >&2
576
603
  exit 1
577
604
  fi
578
- if claim_held_by "$ISSUE" "$HOLDER"; then
605
+ claim_held_by "$ISSUE" "$HOLDER"
606
+ _held_status=$?
607
+ if [ "$_held_status" -eq 0 ]; then
579
608
  gh_issue edit "$ISSUE" --remove-label "$LABEL" >/dev/null 2>&1 || {
580
609
  echo "${RED}claim: could not remove the '$LABEL' label.${OFF}" >&2
581
610
  exit 2
@@ -583,6 +612,18 @@ if [ -n "$RELEASE" ]; then
583
612
  echo "${GREEN}Released.${OFF} ${DIM}(held by $HOLDER)${OFF}"
584
613
  exit 0
585
614
  fi
615
+ # Cannot tell (#1691): the comment read itself failed, so this is NOT "we
616
+ # checked and it's not yours" -- it is "we could not check". Reporting the
617
+ # latter as the former is what used to make a genuinely-held claim
618
+ # unreleasable on nothing but a network blip. Refuse the same as a real
619
+ # mismatch (clearing on a guess is worse than a stuck label) but say so
620
+ # honestly and with the estate's own "cannot tell" exit code.
621
+ if [ "$_held_status" -eq 2 ]; then
622
+ echo "${RED}claim: cannot tell whether #$ISSUE is held by '$HOLDER'${OFF} — the issue's comments were unreadable." >&2
623
+ echo "${DIM} Not releasing on an unreadable read: that could clear somebody else's claim.${OFF}" >&2
624
+ echo "${DIM} Retry once gh/network is working, or check by hand before removing the label.${OFF}" >&2
625
+ exit 2
626
+ fi
586
627
  echo "${RED}claim: #$ISSUE is not held by '$HOLDER' — refusing to release it.${OFF}" >&2
587
628
  echo "${DIM} Clearing somebody else's claim is how two sessions end up on one issue.${OFF}" >&2
588
629
  echo "${DIM} If the claim is genuinely stale (AGENTS.md: over an hour, no branch, no PR),${OFF}" >&2
@@ -833,11 +874,26 @@ case ",$labels," in
833
874
  # it is the orchestrator that dispatched us. Without this a delegated agent
834
875
  # cannot tell its own reservation from a stranger's and correctly refuses to
835
876
  # start, which is the failure this flag exists to remove.
836
- if [ -n "$HOLDER" ] && claim_held_by "$ISSUE" "$HOLDER"; then
837
- echo "${DIM}label carries '$LABEL', held by ${HOLDER} that is you${OFF}"
838
- else
839
- note "${YELLOW}label${OFF} carries '$LABEL' (issue last updated $updated)"
877
+ #
878
+ # Tri-state (#1691): a comment-read failure must not read as "not held by
879
+ # you" (label present + genuinely someone else's) OR as "held by you"
880
+ # (which would waive the label past the four-signal check entirely) --
881
+ # either reading lets a session claim over the top of a holder it simply
882
+ # failed to read. `note_cannot_tell` still blocks the claim (fail closed,
883
+ # same as an ordinary someone-else's-label), but reports the verdict
884
+ # honestly as "cannot tell" rather than a confident "Taken" it did not
885
+ # earn -- unless something else on the four signals IS conclusive, in
886
+ # which case that conclusive finding is what decides the verdict.
887
+ _held_status=1
888
+ if [ -n "$HOLDER" ]; then
889
+ claim_held_by "$ISSUE" "$HOLDER"
890
+ _held_status=$?
840
891
  fi
892
+ case "$_held_status" in
893
+ 0) echo "${DIM}label carries '$LABEL', held by ${HOLDER} — that is you${OFF}" ;;
894
+ 2) note_cannot_tell "${YELLOW}label${OFF} carries '$LABEL' — cannot tell who holds it (comments unreadable)" ;;
895
+ *) note "${YELLOW}label${OFF} carries '$LABEL' (issue last updated $updated)" ;;
896
+ esac
841
897
  ;;
842
898
  esac
843
899
 
@@ -918,6 +974,18 @@ if [ "$TAKEN" -eq 1 ]; then
918
974
  exit 1
919
975
  fi
920
976
 
977
+ # A DEFINITE signal above always wins (checked first), so this only fires when
978
+ # nothing conclusive was found but at least one check genuinely could not run
979
+ # (#1691). Refuse exactly like "Taken" -- an unreadable check must never grant
980
+ # a claim -- but say so honestly instead of reporting certainty nobody has.
981
+ if [ "$CANNOT_TELL" -eq 1 ]; then
982
+ printf '%b' "${RED}Cannot tell.${OFF} Signals:\n$REASONS"
983
+ echo
984
+ echo "${DIM}Refusing to claim over an unknown holder -- an unreadable check must never${OFF}"
985
+ echo "${DIM}read as free. Retry once gh/network is working.${OFF}"
986
+ exit 2
987
+ fi
988
+
921
989
  if [ -n "$CHECK_ONLY" ]; then
922
990
  echo "${GREEN}Free.${OFF} ${DIM}(--check: nothing changed)${OFF}"
923
991
  exit 0