@biffo/cli 0.261.1 → 0.262.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 +69 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.261.1",
3
+ "version": "0.262.0",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/claim.sh CHANGED
@@ -102,24 +102,63 @@ usage() {
102
102
  exit 2
103
103
  }
104
104
 
105
+ # Colours are needed inside the parse loop below (a missing-value message),
106
+ # so they are defined before it rather than after.
107
+ RED=$(printf '\033[31m')
108
+ GREEN=$(printf '\033[32m')
109
+ YELLOW=$(printf '\033[33m')
110
+ DIM=$(printf '\033[90m')
111
+ OFF=$(printf '\033[0m')
112
+
113
+ # A flag that takes a value must not blindly `shift 2`: with exactly one
114
+ # argument left -- the flag itself, nothing after it -- that shifts past the
115
+ # end of `$@` (#826). `shift` is a POSIX SPECIAL builtin, and dash (the real
116
+ # `sh` on this workstation, and on every machine this ships to) aborts the
117
+ # WHOLE SCRIPT on a special builtin's error, non-interactively, before a
118
+ # single line of this script's own handling runs -- printing dash's own
119
+ # "shift: can't shift that many" and exiting 2 by dash's choice, not this
120
+ # script's. bash instead treats the overflowing `shift` as a silent no-op:
121
+ # `$1` never changes, so `while [ $# -gt 0 ]` spins forever. Neither behaviour
122
+ # was ever something this script decided; both were found by running the
123
+ # actual failing command, under the actual interpreter, not by reasoning
124
+ # about `shift` from memory.
125
+ missing_value() {
126
+ echo "${RED}claim: $1 requires a value${OFF}" >&2
127
+ exit 2
128
+ }
129
+
105
130
  while [ $# -gt 0 ]; do
106
131
  case "$1" in
107
132
  -R | --repo)
108
- REPO="${2:-}"
133
+ [ $# -ge 2 ] || missing_value "$1"
134
+ REPO="$2"
109
135
  shift 2
110
136
  ;;
111
137
  --check) CHECK_ONLY=1; shift ;;
112
138
  --as)
113
- HOLDER="${2:-}"
139
+ [ $# -ge 2 ] || missing_value "$1"
140
+ HOLDER="$2"
114
141
  shift 2
115
142
  ;;
116
143
  --release)
117
- HOLDER="${2:-}"
144
+ # No bounds check here on purpose. A bare trailing `--release` (no
145
+ # token) is not a generic usage error like the flags above -- it is
146
+ # the exact place AGENTS.md's own FIRST documented form, the untokened
147
+ # `claim <issue>`, leads a session that later wants to release what it
148
+ # claimed. It gets its own deliberate refusal below (a missing token
149
+ # is a definite "no", not a malformed invocation), not this generic
150
+ # "$flag requires a value" message.
118
151
  RELEASE=1
119
- shift 2
152
+ if [ $# -ge 2 ]; then
153
+ HOLDER="$2"
154
+ shift 2
155
+ else
156
+ shift
157
+ fi
120
158
  ;;
121
159
  --guard)
122
- GUARD_BRANCH="${2:-}"
160
+ [ $# -ge 2 ] || missing_value "$1"
161
+ GUARD_BRANCH="$2"
123
162
  shift 2
124
163
  ;;
125
164
  -h | --help) usage ;;
@@ -130,12 +169,6 @@ while [ $# -gt 0 ]; do
130
169
  esac
131
170
  done
132
171
 
133
- RED=$(printf '\033[31m')
134
- GREEN=$(printf '\033[32m')
135
- YELLOW=$(printf '\033[33m')
136
- DIM=$(printf '\033[90m')
137
- OFF=$(printf '\033[0m')
138
-
139
172
  gh_issue() { if [ -n "$REPO" ]; then gh issue "$@" --repo "$REPO"; else gh issue "$@"; fi; }
140
173
  gh_pr() { if [ -n "$REPO" ]; then gh pr "$@" --repo "$REPO"; else gh pr "$@"; fi; }
141
174
  gh_label() { if [ -n "$REPO" ]; then gh label "$@" --repo "$REPO"; else gh label "$@"; fi; }
@@ -146,7 +179,14 @@ gh_label() { if [ -n "$REPO" ]; then gh label "$@" --repo "$REPO"; else gh label
146
179
  # session supersedes an older one rather than both matching for ever. Returns
147
180
  # non-zero when it cannot tell -- an unreadable comment list must never read as
148
181
  # "yours", or the flag becomes a way to steal a claim by guessing.
182
+ #
183
+ # Requires $2 non-empty (#826): the match below is `*"$HOLDER_MARK$2"*`, and
184
+ # with $2 empty that collapses to `*"claim-holder:"*` -- true of ANY comment
185
+ # naming ANY holder at all, regardless of whose. An empty token must never
186
+ # read as "matches every claim"; it is refused explicitly, before the network
187
+ # call this would otherwise spend.
149
188
  claim_held_by() {
189
+ [ -n "$2" ] || return 1
150
190
  _c=$(gh_issue view "$1" --json comments \
151
191
  --jq "[.comments[]? | select(.body | contains(\"$HOLDER_MARK\"))] | last | .body // \"\"" \
152
192
  2>/dev/null) || return 1
@@ -225,6 +265,24 @@ note() { REASONS="${REASONS} $1\n"; TAKEN=1; }
225
265
  # label left behind by a crashed session is indistinguishable from a live one.
226
266
  # Refusing a mismatched release is what makes the token worth recording.
227
267
  if [ -n "$RELEASE" ]; then
268
+ # A missing token (#826) is a definite "no", not a malformed invocation —
269
+ # AGENTS.md's own first documented `claim <issue>` form never records one,
270
+ # so this is the ordinary route a session that skipped `--as` takes when it
271
+ # tries to release later, not a typo. Decided here rather than falling into
272
+ # `claim_held_by`'s ordinary mismatch branch below: without this, an empty
273
+ # $HOLDER would either (pre-#826-hardening) match ANY held claim via a
274
+ # substring accident, or now correctly fail but with a message talking
275
+ # about "not held by ''", which explains nothing. This says plainly what is
276
+ # wrong and what to do about it, and reuses the SAME refusal exit code (1)
277
+ # as an ordinary token mismatch just below — it is that case's degenerate
278
+ # form, not a new kind of outcome, so it does not invent a third code.
279
+ if [ -z "$HOLDER" ]; then
280
+ echo "${RED}claim: --release needs the token you claimed with (--release <token>).${OFF}" >&2
281
+ echo "${DIM} Claimed without --as? There is no token to prove ownership by, so this${OFF}" >&2
282
+ echo "${DIM} script cannot tell it is you. Release it by hand instead:${OFF}" >&2
283
+ echo "${DIM} gh issue edit $ISSUE --remove-label $LABEL${OFF}" >&2
284
+ exit 1
285
+ fi
228
286
  if claim_held_by "$ISSUE" "$HOLDER"; then
229
287
  gh_issue edit "$ISSUE" --remove-label "$LABEL" >/dev/null 2>&1 || {
230
288
  echo "${RED}claim: could not remove the '$LABEL' label.${OFF}" >&2