@biffo/cli 0.301.17 → 0.301.19

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.
@@ -50,6 +50,35 @@ export BIFFO_ORIGINAL_CWD
50
50
 
51
51
  cd "$root"
52
52
 
53
+ # `claim` never depends on the TypeScript CLI's own logic -- it is a pure
54
+ # passthrough (`cli/src/commands/claim.ts`: `packagedScriptCommand({ script:
55
+ # 'scripts/claim.sh' })`, raw argv forwarded unchanged) to a POSIX shell
56
+ # script that needs only `gh` and `git`. Routing it through `tsx` anyway made
57
+ # RELEASING a claim depend on a toolchain the release itself has nothing to
58
+ # do with: in a worktree where `pnpm install` never completed in `cli/`, the
59
+ # final `exec` below hits a missing `tsx` binary, exits 127 with no
60
+ # reconciling action, and the caller sees a bare "not found" -- the claim/
61
+ # label is left stale, silently blocking re-dispatch of the same issue. Five
62
+ # confirmed instances since 2026-08-31 (biffo-fleet#1231), one costing ~1.25M
63
+ # tokens for zero progress.
64
+ #
65
+ # `scripts/claim.sh` only ships in the template checkout (satellites and
66
+ # instances get it via the published `@biffo/cli` package, never a root
67
+ # copy -- see the claim.ts doc comment), so this dispatches to it directly,
68
+ # unconditionally, whenever it is present at the repo root: eliminate the
69
+ # dependency for this command entirely rather than merely detecting its
70
+ # absence and failing louder. `sh` runs it explicitly rather than a bare
71
+ # `exec` on the path, so the script's own executable bit is irrelevant here
72
+ # too (`ensureExecutable` in packagedScriptCommand.ts exists to paper over
73
+ # exactly that on the tsx path; this path never needs it).
74
+ if [ "${1:-}" = "claim" ] && [ -f "$root/scripts/claim.sh" ]; then
75
+ shift
76
+ # Bare relative path, not "$root/scripts/claim.sh" -- interpreter-audit.sh
77
+ # can only statically resolve a literal `sh scripts/<name>.sh` target, and
78
+ # `cd "$root"` above already guarantees cwd is the repo root by this point.
79
+ exec sh scripts/claim.sh "$@"
80
+ fi
81
+
53
82
  # Satellites (sibling apps, plugin repos, runner fleets) carry neither
54
83
  # `biffo.core.json` nor `cli/`. They DO carry `.biffo-shared-version`, written
55
84
  # by `scripts/shared-sync.sh` and naming the template core version their shared
@@ -50,6 +50,35 @@ export BIFFO_ORIGINAL_CWD
50
50
 
51
51
  cd "$root"
52
52
 
53
+ # `claim` never depends on the TypeScript CLI's own logic -- it is a pure
54
+ # passthrough (`cli/src/commands/claim.ts`: `packagedScriptCommand({ script:
55
+ # 'scripts/claim.sh' })`, raw argv forwarded unchanged) to a POSIX shell
56
+ # script that needs only `gh` and `git`. Routing it through `tsx` anyway made
57
+ # RELEASING a claim depend on a toolchain the release itself has nothing to
58
+ # do with: in a worktree where `pnpm install` never completed in `cli/`, the
59
+ # final `exec` below hits a missing `tsx` binary, exits 127 with no
60
+ # reconciling action, and the caller sees a bare "not found" -- the claim/
61
+ # label is left stale, silently blocking re-dispatch of the same issue. Five
62
+ # confirmed instances since 2026-08-31 (biffo-fleet#1231), one costing ~1.25M
63
+ # tokens for zero progress.
64
+ #
65
+ # `scripts/claim.sh` only ships in the template checkout (satellites and
66
+ # instances get it via the published `@biffo/cli` package, never a root
67
+ # copy -- see the claim.ts doc comment), so this dispatches to it directly,
68
+ # unconditionally, whenever it is present at the repo root: eliminate the
69
+ # dependency for this command entirely rather than merely detecting its
70
+ # absence and failing louder. `sh` runs it explicitly rather than a bare
71
+ # `exec` on the path, so the script's own executable bit is irrelevant here
72
+ # too (`ensureExecutable` in packagedScriptCommand.ts exists to paper over
73
+ # exactly that on the tsx path; this path never needs it).
74
+ if [ "${1:-}" = "claim" ] && [ -f "$root/scripts/claim.sh" ]; then
75
+ shift
76
+ # Bare relative path, not "$root/scripts/claim.sh" -- interpreter-audit.sh
77
+ # can only statically resolve a literal `sh scripts/<name>.sh` target, and
78
+ # `cd "$root"` above already guarantees cwd is the repo root by this point.
79
+ exec sh scripts/claim.sh "$@"
80
+ fi
81
+
53
82
  # Satellites (sibling apps, plugin repos, runner fleets) carry neither
54
83
  # `biffo.core.json` nor `cli/`. They DO carry `.biffo-shared-version`, written
55
84
  # by `scripts/shared-sync.sh` and naming the template core version their shared
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.301.17",
3
+ "version": "0.301.19",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/scripts/claim.sh CHANGED
@@ -642,6 +642,91 @@ claim_select_expr() {
642
642
  "$_n" "$_slug" "$_n" "$_n"
643
643
  }
644
644
 
645
+ # --- continuity-language guard for --release (biffo-fleet#1232) -------------
646
+ #
647
+ # `--reaffirm` (above) gives a session the right TOOL for "this PR promises
648
+ # the claim stays alive past this session" -- but nothing stopped the WRONG
649
+ # one, ordinary `--release`, from running anyway. PR #1848 said, in its own
650
+ # body and in a same-session comment on the issue it referenced, that it
651
+ # would not touch that issue's claim -- then the same session's routine
652
+ # end-of-session `--release` fired 21 seconds later and cleared it, exactly
653
+ # the "written promise vs. actual mechanism disagree" shape AGENTS.md's own
654
+ # `--guard` was built to close for the branch/PR half of collision
655
+ # prevention. This is that fix's sibling for release.
656
+ #
657
+ # It is necessarily a DETECT-tier guard (level 4, not higher): whether a PR
658
+ # or comment PROMISES continuity is a fact about free text, not a structural
659
+ # fact `gh` can hand back the way `closingIssuesReferences` can. So it
660
+ # pattern-matches a small set of phrases drawn from the one real corpus this
661
+ # estate has for it -- PR #1848's own body, and the comment posted on #1083
662
+ # quoted in biffo-fleet#1232 -- rather than inventing hypothetical wording.
663
+ #
664
+ # Deliberately errs toward FALSE positives, not false negatives: blocking an
665
+ # ordinary release that turns out to be fine costs one manual override
666
+ # (documented in the refusal message, same shape as the "genuinely stale
667
+ # claim" carve-out already below); missing a real continuity promise
668
+ # reproduces the exact bug this guard exists to close. A guard that only
669
+ # recognised PR #1848's literal wording would be exactly the "fixed the
670
+ # sentence, not the class" failure this estate has already paid for
671
+ # (biffo-fleet#711) -- so this matches the SHAPE of a continuity claim
672
+ # (claim/label + stays/remains/not-touched/reaffirm), not its exact words.
673
+ #
674
+ # GitHub markdown wraps PR/issue bodies at ~80 columns, and the one real
675
+ # instance of this phrase ("...under the Foreman's existing claim throughout
676
+ # the review window; nothing here touches that label or that claim.") spans
677
+ # three wrapped lines. A per-line grep would never see "touches" and "claim"
678
+ # on the same line, so the text is whitespace-collapsed before matching --
679
+ # deliberately, unlike the closing-keyword parser's cross-newline match
680
+ # AGENTS.md warns about, because collapsing here only widens what gets
681
+ # BLOCKED (safe direction: the escape hatch is a manual override, not an
682
+ # action taken on the estate), not what gets closed or released.
683
+ CONTINUITY_RE='(stays?|remains?) claimed|reaffirm|under the [^.]*claim|nothing[^.]*touch[^.]*(claim|label)|not[^.]*(touch|release)[^.]*(claim|label)|nothing[^.]*claim[^.]*touch'
684
+
685
+ # claim_continuity_promise <issue> -- tri-state: does an OPEN PR referencing
686
+ # $1 (via `claim_select_expr`'s same three signals: closes, branch name, or
687
+ # `Refs #N`), or $1's OWN issue comments, contain continuity language?
688
+ #
689
+ # 0 a continuity promise was found -- refuse the release
690
+ # 1 none found -- reads succeeded, nothing matched
691
+ # 2 cannot tell -- a read itself failed
692
+ #
693
+ # Requires BOTH reads to succeed before returning 1 ("none found"), same
694
+ # fail-closed shape as `claim_held_by`: a read failure that happened to hide
695
+ # a real promise must never be indistinguishable from "checked, and it's
696
+ # clear".
697
+ #
698
+ # Note for the ONE call site this has today (the `--release` block below):
699
+ # the comment read here is the identical `gh issue view --json comments`
700
+ # call `claim_held_by` already made, in the same process, just above it --
701
+ # so a genuinely unreadable comment list is caught there first, and this
702
+ # function's own comment-failure branch is unreachable through that path.
703
+ # It stays, fail-closed, for any future caller that does not check
704
+ # `claim_held_by` first.
705
+ claim_continuity_promise() {
706
+ _cp_issue="$1"
707
+ _cp_slug=$(repo_slug)
708
+ _cp_select=$(claim_select_expr "$_cp_issue" "$_cp_slug")
709
+
710
+ _cp_pr_bodies=$(gh_pr list --state open --limit 100 \
711
+ --json number,headRefName,closingIssuesReferences,body \
712
+ --jq "[.[] | select($_cp_select)] | .[].body // \"\"" 2>/dev/null)
713
+ _cp_pr_status=$?
714
+
715
+ _cp_issue_comments=$(gh_issue view "$_cp_issue" --json comments \
716
+ --jq '[.comments[]?.body // ""] | join("\n---\n")' 2>/dev/null)
717
+ _cp_comment_status=$?
718
+
719
+ if [ "$_cp_pr_status" -ne 0 ] || [ "$_cp_comment_status" -ne 0 ]; then
720
+ return 2
721
+ fi
722
+
723
+ _cp_text=$(printf '%s\n%s' "$_cp_pr_bodies" "$_cp_issue_comments" | tr '\n' ' ')
724
+ if printf '%s' "$_cp_text" | grep -Eiq "$CONTINUITY_RE"; then
725
+ return 0
726
+ fi
727
+ return 1
728
+ }
729
+
645
730
  LABEL=in-progress
646
731
 
647
732
  # --- holder identity (#1279) -------------------------------------------------
@@ -756,6 +841,31 @@ if [ -n "$RELEASE" ]; then
756
841
  claim_held_by "$ISSUE" "$HOLDER"
757
842
  _held_status=$?
758
843
  if [ "$_held_status" -eq 0 ]; then
844
+ # Structural continuity check (biffo-fleet#1232) -- runs AFTER ownership
845
+ # is confirmed (a mismatched holder is refused below regardless) and
846
+ # BEFORE the label is actually removed, so an ordinary --release cannot
847
+ # silently contradict a promise an open PR or this issue's own comments
848
+ # have made about staying claimed. See claim_continuity_promise above.
849
+ claim_continuity_promise "$ISSUE"
850
+ _cp_status=$?
851
+ if [ "$_cp_status" -eq 0 ]; then
852
+ echo "${RED}claim: refusing to release #$ISSUE${OFF} — an open PR referencing it, or a" >&2
853
+ echo "${RED} comment on it, says the claim stays alive past this session.${OFF}" >&2
854
+ echo "${DIM} AGENTS.md §1: a PR that promises an issue stays claimed must REAFFIRM it,${OFF}" >&2
855
+ echo "${DIM} not just say so. If that promise still holds, run:${OFF}" >&2
856
+ echo "${DIM} sh scripts/biffo.sh claim $ISSUE --reaffirm $HOLDER${OFF}" >&2
857
+ echo "${DIM} If it has genuinely resolved (review window over, work finished), clear${OFF}" >&2
858
+ echo "${DIM} the label by hand and say so in a comment, rather than through --release:${OFF}" >&2
859
+ echo "${DIM} gh issue edit $ISSUE --remove-label $LABEL${OFF}" >&2
860
+ exit 1
861
+ fi
862
+ if [ "$_cp_status" -eq 2 ]; then
863
+ echo "${RED}claim: cannot tell whether #$ISSUE carries a continuity promise${OFF} — the" >&2
864
+ echo "${RED} referencing PR list or the issue's comments were unreadable.${OFF}" >&2
865
+ echo "${DIM} Not releasing on an unreadable read: that could clear a claim meant to${OFF}" >&2
866
+ echo "${DIM} stay held. Retry once gh/network is working.${OFF}" >&2
867
+ exit 2
868
+ fi
759
869
  gh_issue edit "$ISSUE" --remove-label "$LABEL" >/dev/null 2>&1 || {
760
870
  echo "${RED}claim: could not remove the '$LABEL' label.${OFF}" >&2
761
871
  exit 2