@biffo/cli 0.247.8 → 0.248.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/scripts/claim.sh +112 -8
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.247.8",
3
+ "version": "0.248.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/claim.sh CHANGED
@@ -91,6 +91,9 @@ set -u
91
91
 
92
92
  ISSUE=""
93
93
  REPO=""
94
+ HOLDER=""
95
+ RELEASE=""
96
+ HOLDER_MARK="claim-holder:"
94
97
  CHECK_ONLY=""
95
98
  GUARD_BRANCH=""
96
99
 
@@ -106,6 +109,15 @@ while [ $# -gt 0 ]; do
106
109
  shift 2
107
110
  ;;
108
111
  --check) CHECK_ONLY=1; shift ;;
112
+ --as)
113
+ HOLDER="${2:-}"
114
+ shift 2
115
+ ;;
116
+ --release)
117
+ HOLDER="${2:-}"
118
+ RELEASE=1
119
+ shift 2
120
+ ;;
109
121
  --guard)
110
122
  GUARD_BRANCH="${2:-}"
111
123
  shift 2
@@ -128,6 +140,46 @@ gh_issue() { if [ -n "$REPO" ]; then gh issue "$@" --repo "$REPO"; else gh issue
128
140
  gh_pr() { if [ -n "$REPO" ]; then gh pr "$@" --repo "$REPO"; else gh pr "$@"; fi; }
129
141
  gh_label() { if [ -n "$REPO" ]; then gh label "$@" --repo "$REPO"; else gh label "$@"; fi; }
130
142
 
143
+ # Does the newest claim comment on $1 carry holder token $2? (#1279)
144
+ #
145
+ # Reads the LAST claim comment rather than any, so a re-claim by a different
146
+ # session supersedes an older one rather than both matching for ever. Returns
147
+ # non-zero when it cannot tell -- an unreadable comment list must never read as
148
+ # "yours", or the flag becomes a way to steal a claim by guessing.
149
+ claim_held_by() {
150
+ _c=$(gh_issue view "$1" --json comments \
151
+ --jq "[.comments[]? | select(.body | contains(\"$HOLDER_MARK\"))] | last | .body // \"\"" \
152
+ 2>/dev/null) || return 1
153
+ [ -n "$_c" ] || return 1
154
+ case "$_c" in
155
+ *"$HOLDER_MARK$2"*) return 0 ;;
156
+ *) return 1 ;;
157
+ esac
158
+ }
159
+
160
+ # This repo as `owner/name`, so a closing reference can be checked against it.
161
+ # GitHub records a CROSS-REPO closing reference in the same list as a local one
162
+ # -- tabsii-lms#43 closes tabsii-platform#553 -- so the number alone is not an
163
+ # answer, and matching on it reproduces the very defect this replaces (#1281).
164
+ #
165
+ # Resolved LAZILY, on first use. Computing it at load cost a `gh repo view` on
166
+ # every single push, including the overwhelmingly common case where `--guard`
167
+ # skips silently because the branch names no issue -- a guard that is meant to
168
+ # touch nothing was suddenly making a network call per push.
169
+ SLUG=""
170
+ SLUG_RESOLVED=""
171
+ repo_slug() {
172
+ if [ -z "$SLUG_RESOLVED" ]; then
173
+ SLUG_RESOLVED=1
174
+ if [ -n "$REPO" ]; then
175
+ SLUG="$REPO"
176
+ else
177
+ SLUG=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || echo "")
178
+ fi
179
+ fi
180
+ printf '%s' "$SLUG"
181
+ }
182
+
131
183
  # `git ls-remote --heads ""` fails outright ("fatal: bad repository ''") rather
132
184
  # than falling back to the default remote — `${REPO:+url}` expands to an empty
133
185
  # STRING ARGUMENT when $REPO is unset, not to no argument at all. That silently
@@ -144,11 +196,51 @@ remote_branches() {
144
196
  }
145
197
 
146
198
  LABEL=in-progress
199
+
200
+ # --- holder identity (#1279) -------------------------------------------------
201
+ #
202
+ # A claim used to record WHEN and WHO, and every session on a workstation claims
203
+ # under the same GitHub actor. So a delegated agent could not distinguish
204
+ # "my orchestrator claimed this for me" from "a stranger claimed it 90 seconds
205
+ # ago", and the safe reading is to stop. On 2026-08-04 four agents were
206
+ # dispatched onto pre-claimed issues; one ran the check first and correctly
207
+ # refused to start, producing nothing. Whether a delegate works or stalls
208
+ # depended on whether it happened to check before starting.
209
+ #
210
+ # `--as <token>` makes the claim answerable: an existing claim carrying the same
211
+ # token reads as YOUR claim and returns free. `--release <token>` refuses to
212
+ # clear somebody else's.
213
+ #
214
+ # The token is opaque and never a secret -- it identifies a session, not a
215
+ # person, and appears in a public comment.
216
+
147
217
  TAKEN=0
148
218
  REASONS=""
149
219
 
150
220
  note() { REASONS="${REASONS} $1\n"; TAKEN=1; }
151
221
 
222
+ # --- --release <token>: only the holder clears it (#1279) --------------------
223
+ #
224
+ # A claim nobody can prove ownership of is one anybody can clear, and a stale
225
+ # label left behind by a crashed session is indistinguishable from a live one.
226
+ # Refusing a mismatched release is what makes the token worth recording.
227
+ if [ -n "$RELEASE" ]; then
228
+ if claim_held_by "$ISSUE" "$HOLDER"; then
229
+ gh_issue edit "$ISSUE" --remove-label "$LABEL" >/dev/null 2>&1 || {
230
+ echo "${RED}claim: could not remove the '$LABEL' label.${OFF}" >&2
231
+ exit 2
232
+ }
233
+ echo "${GREEN}Released.${OFF} ${DIM}(held by $HOLDER)${OFF}"
234
+ exit 0
235
+ fi
236
+ echo "${RED}claim: #$ISSUE is not held by '$HOLDER' — refusing to release it.${OFF}" >&2
237
+ echo "${DIM} Clearing somebody else's claim is how two sessions end up on one issue.${OFF}" >&2
238
+ echo "${DIM} If the claim is genuinely stale (AGENTS.md: over an hour, no branch, no PR),${OFF}" >&2
239
+ echo "${DIM} remove the label by hand and say so in a comment.${OFF}" >&2
240
+ exit 1
241
+ fi
242
+
243
+
152
244
  # --- --guard <branch>: the enforced pre-push gate -----------------------------
153
245
  #
154
246
  # Deliberately short-circuits before any of the four-signal machinery below —
@@ -171,9 +263,11 @@ if [ -n "$GUARD_BRANCH" ]; then
171
263
  cannot_tell_reasons=""
172
264
 
173
265
  # --- an open PR referencing the issue, excluding our own branch's PR --------
266
+ slug=$(repo_slug)
174
267
  pr_err=$(mktemp)
175
- open_prs=$(gh_pr list --state open --limit 100 --json number,title,body,headRefName \
176
- --jq "[.[] | select(((.title + \" \" + .body) | test(\"(^|[^0-9])#$guard_issue([^0-9]|\$)\")) or (.headRefName | test(\"(^|[^0-9])$guard_issue([^0-9]|\$)\")))] | .[] | select(.headRefName != \"$GUARD_BRANCH\") | \"#\(.number) \(.headRefName)\"" \
268
+ slug=$(repo_slug)
269
+ open_prs=$(gh_pr list --state open --limit 100 --json number,headRefName,closingIssuesReferences \
270
+ --jq "[.[] | select(([.closingIssuesReferences[]? | select(.number == $guard_issue and ((.repository.owner.login + \"/\" + .repository.name) == \"$slug\"))] | length > 0) or (.headRefName | test(\"(^|[^0-9])$guard_issue([^0-9]|\$)\")))] | .[] | select(.headRefName != \"$GUARD_BRANCH\") | \"#\(.number) \(.headRefName)\"" \
177
271
  2>"$pr_err")
178
272
  pr_status=$?
179
273
  pr_err_text=$(cat "$pr_err")
@@ -265,7 +359,15 @@ fi
265
359
  case ",$labels," in
266
360
  *",$LABEL,"*)
267
361
  updated=$(gh_issue view "$ISSUE" --json updatedAt --jq .updatedAt 2>/dev/null)
268
- note "${YELLOW}label${OFF} carries '$LABEL' (issue last updated $updated)"
362
+ # Whose claim is it? (#1279) A claim carrying OUR token is not a collision --
363
+ # it is the orchestrator that dispatched us. Without this a delegated agent
364
+ # cannot tell its own reservation from a stranger's and correctly refuses to
365
+ # start, which is the failure this flag exists to remove.
366
+ if [ -n "$HOLDER" ] && claim_held_by "$ISSUE" "$HOLDER"; then
367
+ echo "${DIM}label carries '$LABEL', held by ${HOLDER} — that is you${OFF}"
368
+ else
369
+ note "${YELLOW}label${OFF} carries '$LABEL' (issue last updated $updated)"
370
+ fi
269
371
  ;;
270
372
  esac
271
373
 
@@ -275,8 +377,9 @@ esac
275
377
  # Matches the issue number in the title or body as a whole number, so #118 does
276
378
  # not match #1188.
277
379
 
278
- open_prs=$(gh_pr list --state open --limit 100 --json number,title,body,headRefName \
279
- --jq "[.[] | select(((.title + \" \" + .body) | test(\"(^|[^0-9])#$ISSUE([^0-9]|\$)\")) or (.headRefName | test(\"(^|[^0-9])$ISSUE([^0-9]|\$)\")))] | .[] | \"#\(.number) \(.headRefName)\"" 2>/dev/null)
380
+ slug=$(repo_slug)
381
+ open_prs=$(gh_pr list --state open --limit 100 --json number,headRefName,closingIssuesReferences \
382
+ --jq "[.[] | select(([.closingIssuesReferences[]? | select(.number == $ISSUE and ((.repository.owner.login + \"/\" + .repository.name) == \"$slug\"))] | length > 0) or (.headRefName | test(\"(^|[^0-9])$ISSUE([^0-9]|\$)\")))] | .[] | \"#\(.number) \(.headRefName)\"" 2>/dev/null)
280
383
 
281
384
  if [ -n "$open_prs" ]; then
282
385
  printf '%s\n' "$open_prs" | while IFS= read -r pr; do
@@ -305,8 +408,9 @@ fi
305
408
  # Not "taken" — "possibly already done". #1165 was built and merged in three
306
409
  # minutes; the only trace afterwards is a merged PR.
307
410
 
308
- merged=$(gh_pr list --state merged --limit 30 --json number,title,body,mergedAt \
309
- --jq "[.[] | select((.title + \" \" + .body) | test(\"(^|[^0-9])#$ISSUE([^0-9]|\$)\"))] | .[0] | select(. != null) | \"#\(.number) merged \(.mergedAt[0:16])\"" 2>/dev/null)
411
+ slug=$(repo_slug)
412
+ merged=$(gh_pr list --state merged --limit 30 --json number,mergedAt,closingIssuesReferences \
413
+ --jq "[.[] | select(([.closingIssuesReferences[]? | select(.number == $ISSUE and ((.repository.owner.login + \"/\" + .repository.name) == \"$slug\"))] | length > 0))] | .[0] | select(. != null) | \"#\(.number) merged \(.mergedAt[0:16])\"" 2>/dev/null)
310
414
 
311
415
  if [ -n "$merged" ]; then
312
416
  echo " ${YELLOW}merged${OFF} $merged"
@@ -361,7 +465,7 @@ gh_issue edit "$ISSUE" --add-label "$LABEL" >/dev/null 2>&1 || {
361
465
  exit 2
362
466
  }
363
467
  gh_issue comment "$ISSUE" \
364
- --body "Claimed at $(date -u +%FT%TZ) by \`$(git config user.name 2>/dev/null || echo agent)\`. Release it — remove the label — on merge, or if you stop." \
468
+ --body "Claimed at $(date -u +%FT%TZ) by \`$(git config user.name 2>/dev/null || echo agent)\`.${HOLDER:+ ${HOLDER_MARK}${HOLDER}} Release it — remove the label — on merge, or if you stop." \
365
469
  >/dev/null 2>&1
366
470
 
367
471
  echo "${GREEN}Claimed.${OFF}"