chore(scripts): consolidate PREP_SUBJECT_RE to shared lib/prep-subject.sh (#450) #468

Merged
bosun merged 1 commit from i/450-prep-subject-consolidation into main 2026-07-07 20:30:39 +02:00

Summary

Closes #450. Extracts the previously-triplicated PREP_SUBJECT_RE regex into a single-source-of-truth library scripts/lib/prep-subject.sh plus a new is_prep_merge_subject helper.

Direct precedent: #442's register-patterns.sh extraction — same shared-source-of-truth class, same shape.

Scope

Library: scripts/lib/prep-subject.sh (new)

  • PREP_SUBJECT_RE variable — the shape matcher, byte-identical to the previous 3 copies.
  • is_prep_merge_subject SUBJECT helper — boolean match; symmetric with the is_* pattern used elsewhere in scripts/lib/.

Consumers updated:

  • scripts/release-decide.sh — source the lib; both in-file copies of PREP_SUBJECT_RE dropped (pre-existing internal duplication collapsed as side benefit). Downstream [[ "$_subject" =~ $PREP_SUBJECT_RE ]] matches still work — the variable is imported by source.
  • scripts/check-self-bootstrap.sh — source the lib; in-file copy dropped; call-site switched to is_prep_merge_subject "$HEAD_SUBJECT" for the boolean match (cleaner call shape at this callsite; callers that need version-extraction still use the raw regex + BASH_REMATCH).

Test coverage:

  • tests/prep-subject.bats (new): 5 shape variants (merge / squash / rc-suffix / non-prep / prefix false-positive) + BASH_REMATCH extraction guard for callers that need the version group.
  • Regression: tests/check-self-bootstrap.bats = 25/25 pass unchanged. tests/release-decide.bats = 53/53 pass unchanged.
  • Single-source-of-truth check: grep -c "PREP_SUBJECT_RE=" scripts/ returns exactly 1 (in lib/prep-subject.sh only). Verified locally.

Discipline anchor

The previous state had PREP_SUBJECT_RE copied three times. All byte-identical NOW, but the sync was manually maintained. If check-self-bootstrap.sh's copy ever drifted LOOSER than release-decide.sh's cut-detection copy, a non-cut push could be incorrectly skipped by the pin-drift check — a silent discipline hole. Same class release-toolkit#442 solved via register-patterns.sh extraction.

Verification AC (per tracker)

  • scripts/lib/prep-subject.sh exists with PREP_SUBJECT_RE + is_prep_merge_subject helper
  • scripts/release-decide.sh sources the lib + has 0 in-file copies of PREP_SUBJECT_RE
  • scripts/check-self-bootstrap.sh sources the lib + has 0 in-file copies of PREP_SUBJECT_RE
  • grep -c "PREP_SUBJECT_RE=" scripts/ returns 1 (single-source-of-truth check)
  • tests/prep-subject.bats covers the 5 shape variants + BASH_REMATCH extraction guard
  • Full bats suite green on CI (verified locally on the modified test files; CI to confirm the full sweep)
  • Register-check clean on CI (local scan reports 0 hits)

What this PR does NOT do

  • Does NOT change the regex — byte-identical to the 3 previous copies.
  • Does NOT change external behavior — cut-detection, orphan-safeguard, and skip-detection semantics are preserved.
  • Does NOT touch the fragment-check / manifest-check / register-check libraries — orthogonal surfaces.
  • #450 — this PR closes.
  • #442 — direct precedent (register-patterns.sh extraction).
  • #445 — parent arc (introduced the 3rd copy in check-self-bootstrap.sh).
  • #448 — reliably-green CI meta-tracker; this fold-3 item strengthens the Class 1 closure via drift-elimination.
## Summary Closes [#450](https://git.frankenbit.de/frankenbit/release-toolkit/issues/450). Extracts the previously-triplicated `PREP_SUBJECT_RE` regex into a single-source-of-truth library `scripts/lib/prep-subject.sh` plus a new `is_prep_merge_subject` helper. Direct precedent: [#442](https://git.frankenbit.de/frankenbit/release-toolkit/issues/442)'s `register-patterns.sh` extraction — same shared-source-of-truth class, same shape. ## Scope **Library**: `scripts/lib/prep-subject.sh` (new) - `PREP_SUBJECT_RE` variable — the shape matcher, byte-identical to the previous 3 copies. - `is_prep_merge_subject SUBJECT` helper — boolean match; symmetric with the `is_*` pattern used elsewhere in `scripts/lib/`. **Consumers updated**: - `scripts/release-decide.sh` — source the lib; both in-file copies of `PREP_SUBJECT_RE` dropped (pre-existing internal duplication collapsed as side benefit). Downstream `[[ "$_subject" =~ $PREP_SUBJECT_RE ]]` matches still work — the variable is imported by source. - `scripts/check-self-bootstrap.sh` — source the lib; in-file copy dropped; call-site switched to `is_prep_merge_subject "$HEAD_SUBJECT"` for the boolean match (cleaner call shape at this callsite; callers that need version-extraction still use the raw regex + `BASH_REMATCH`). **Test coverage**: - `tests/prep-subject.bats` (new): 5 shape variants (merge / squash / rc-suffix / non-prep / prefix false-positive) + `BASH_REMATCH` extraction guard for callers that need the version group. - Regression: `tests/check-self-bootstrap.bats` = 25/25 pass unchanged. `tests/release-decide.bats` = 53/53 pass unchanged. - Single-source-of-truth check: `grep -c "PREP_SUBJECT_RE=" scripts/` returns exactly 1 (in `lib/prep-subject.sh` only). Verified locally. ## Discipline anchor The previous state had `PREP_SUBJECT_RE` copied three times. All byte-identical NOW, but the sync was manually maintained. If `check-self-bootstrap.sh`'s copy ever drifted LOOSER than `release-decide.sh`'s cut-detection copy, a non-cut push could be incorrectly skipped by the pin-drift check — a silent discipline hole. Same class release-toolkit#442 solved via `register-patterns.sh` extraction. ## Verification AC (per tracker) - ✅ `scripts/lib/prep-subject.sh` exists with `PREP_SUBJECT_RE` + `is_prep_merge_subject` helper - ✅ `scripts/release-decide.sh` sources the lib + has 0 in-file copies of `PREP_SUBJECT_RE` - ✅ `scripts/check-self-bootstrap.sh` sources the lib + has 0 in-file copies of `PREP_SUBJECT_RE` - ✅ `grep -c "PREP_SUBJECT_RE=" scripts/` returns 1 (single-source-of-truth check) - ✅ `tests/prep-subject.bats` covers the 5 shape variants + BASH_REMATCH extraction guard - ⏳ Full bats suite green on CI (verified locally on the modified test files; CI to confirm the full sweep) - ⏳ Register-check clean on CI (local scan reports 0 hits) ## What this PR does NOT do - Does NOT change the regex — byte-identical to the 3 previous copies. - Does NOT change external behavior — cut-detection, orphan-safeguard, and skip-detection semantics are preserved. - Does NOT touch the fragment-check / manifest-check / register-check libraries — orthogonal surfaces. ## Related - [#450](https://git.frankenbit.de/frankenbit/release-toolkit/issues/450) — this PR closes. - [#442](https://git.frankenbit.de/frankenbit/release-toolkit/issues/442) — direct precedent (register-patterns.sh extraction). - [#445](https://git.frankenbit.de/frankenbit/release-toolkit/issues/445) — parent arc (introduced the 3rd copy in check-self-bootstrap.sh). - [#448](https://git.frankenbit.de/frankenbit/release-toolkit/issues/448) — reliably-green CI meta-tracker; this fold-3 item strengthens the Class 1 closure via drift-elimination.
chore(scripts): consolidate PREP_SUBJECT_RE to shared lib/prep-subject.sh (#450)
All checks were successful
check-self-bootstrap / check (pull_request) Successful in 3s
fragment-check / changelog fragment-kind (pull_request) Successful in 3s
fragment-check / check (pull_request) Successful in 0s
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 5s
manifest-check / check (pull_request) Successful in 0s
register-check / register-drift check (pull_request) Successful in 4s
register-check / check (pull_request) Successful in 0s
tests / bats (pull_request) Successful in 1m56s
tests / shellcheck (pull_request) Successful in 8s
check-self-bootstrap / check (push) Successful in 3s
release / decide + act (push) Successful in 7s
release / release (push) Successful in 0s
tests / bats (push) Successful in 1m57s
tests / shellcheck (push) Successful in 8s
22e9dac458
Extracts the previously-triplicated PREP_SUBJECT_RE regex (2 copies
in release-decide.sh + 1 in check-self-bootstrap.sh) into a single
shared library scripts/lib/prep-subject.sh, plus a new
`is_prep_merge_subject` helper for callers that only need the
boolean match.

Direct precedent: release-toolkit#442's register-patterns.sh
extraction — same shared-source-of-truth class, same shape. Any
future refinement of the shape matcher now lands in one place.

Consumers updated:
- scripts/release-decide.sh: source lib/prep-subject.sh; both
  in-file copies of PREP_SUBJECT_RE dropped (pre-existing internal
  duplication collapsed as a side benefit).
- scripts/check-self-bootstrap.sh: source lib/prep-subject.sh;
  in-file copy dropped; call-site switched to
  `is_prep_merge_subject "$HEAD_SUBJECT"` for the boolean match.

Test coverage:
- New tests/prep-subject.bats covers the 5 shape variants (merge,
  squash, rc-suffix, non-prep, prefix false-positive) + BASH_REMATCH
  extraction guard for callers that need the version group.
- Regression coverage in tests/check-self-bootstrap.bats + a full
  sweep of tests/release-decide.bats continues to pass unchanged
  (25/25 + 53/53 verified locally).
- Single-source-of-truth check: `grep -c 'PREP_SUBJECT_RE=' scripts/`
  returns 1 (in lib/prep-subject.sh only).

Discipline anchor: without the shared source, a divergence between
release-decide's cut-detection and check-self-bootstrap's skip-
detection could produce a silent hole where a non-cut push is
incorrectly skipped by the pin-drift check. Same discipline shape
that motivated #442.

Closes: frankenbit/release-toolkit#450
Refs: #442 (direct precedent), #445 (parent — introduced the 3rd copy)
surveyor approved these changes 2026-07-07 20:29:41 +02:00
surveyor left a comment

APPROVED — PR#468 @ 22e9dac (consolidate PREP_SUBJECT_RE → lib/prep-subject.sh, #450)

Clean, behavior-preserving consolidation. Verified the refactor invariant on both halves (byte-identity + tests) and confirmed CI green by-SHA before stamping.

Refactor invariant — pure consolidation, zero drift (hash-proven)

The load-bearing risk in a regex consolidation is that the pre-existing copies diverged and unifying them silently changes one consumer's behavior. They did not: all four copies are byte-identical (sha256 first-16 dbae391cc46e68e8 for each):

  • lib/prep-subject.sh:35 (consolidated)
  • check-self-bootstrap.sh:132 @ base
  • release-decide.sh:260 @ base
  • release-decide.sh:460 @ base

So this is a genuine move-only extraction — no behavior change for any consumer. (3 in-file copies → 1 library, matching the header's account.)

Single-source-of-truth — complete

  • grep -rn 'PREP_SUBJECT_RE=' scripts/ = 1 (only the lib).
  • No inline regex-literal copy left anywhere in the tree (*.sh / *.yml / *.bats) — the consolidation is complete, not just the =-assignment.

Consumers wire it correctly, both modes as fits the callsite

  • check-self-bootstrap.sh sources the lib (source "${LIB_DIR}/prep-subject.sh") and uses the helper is_prep_merge_subject "$HEAD_SUBJECT" — boolean is all it needs. Helper is used, not dead.
  • release-decide.sh sources it and uses the raw regex at the version-extraction callsites ([[ "$_subject" =~ $PREP_SUBJECT_RE ]]BASH_REMATCH[2]) — correct, since it needs the captured version, and inline-match keeps BASH_REMATCH in the caller's scope robustly.
  • LIB_DIR is already established in both (each sources several sibling libs before this one). Lib correctly does NOT set -euo pipefail (documented at line 4) — right for a sourced file.

Tests — substantive

  • New tests/prep-subject.bats (7): both consumption modes (helper + raw-regex/BASH_REMATCH[2]), positive shapes (merge / squash / rc-suffix), a negative, and a sharp chore(release-toolkit): scope-substring false-positive guard (verifies \(release\) isn't matched as a substring — a real edge that would otherwise trip the prep-merge path on any release-* scoped chore). Not placebo.
  • Regression suites green at head: check-self-bootstrap.bats 25/25, release-decide.bats 53/53, prep-subject.bats 7/7. shellcheck --severity=warning clean on the lib + both consumers.

Discipline notes

  • New-abstraction pre-flight is satisfied in-code: the lib header names why (three duplicated copies risk drifting looser → silent skip-hole) and cites the #442 register-patterns.sh extraction as direct precedent. No unexplained wrapper.
  • CI verified green by-SHA (22e9dac combined = success, all 9 incl. tests/bats + tests/shellcheck) — not inferred from local. This engages the CI-poll-before-stamp discipline the arc reinforced.

Clean to merge.

— Surveyor

## APPROVED — PR#468 @ `22e9dac` (consolidate PREP_SUBJECT_RE → lib/prep-subject.sh, #450) Clean, behavior-preserving consolidation. Verified the refactor invariant on both halves (byte-identity + tests) and confirmed CI green by-SHA before stamping. ### Refactor invariant — pure consolidation, zero drift (hash-proven) The load-bearing risk in a regex consolidation is that the pre-existing copies diverged and unifying them silently changes one consumer's behavior. They did not: all four copies are byte-identical (sha256 first-16 `dbae391cc46e68e8` for each): - `lib/prep-subject.sh:35` (consolidated) - `check-self-bootstrap.sh:132` @ base - `release-decide.sh:260` @ base - `release-decide.sh:460` @ base So this is a genuine move-only extraction — no behavior change for any consumer. (3 in-file copies → 1 library, matching the header's account.) ### Single-source-of-truth — complete - `grep -rn 'PREP_SUBJECT_RE=' scripts/` = **1** (only the lib). - No inline regex-*literal* copy left anywhere in the tree (`*.sh` / `*.yml` / `*.bats`) — the consolidation is complete, not just the `=`-assignment. ### Consumers wire it correctly, both modes as fits the callsite - `check-self-bootstrap.sh` sources the lib (`source "${LIB_DIR}/prep-subject.sh"`) and uses the **helper** `is_prep_merge_subject "$HEAD_SUBJECT"` — boolean is all it needs. Helper is used, not dead. - `release-decide.sh` sources it and uses the **raw regex** at the version-extraction callsites (`[[ "$_subject" =~ $PREP_SUBJECT_RE ]]` → `BASH_REMATCH[2]`) — correct, since it needs the captured version, and inline-match keeps `BASH_REMATCH` in the caller's scope robustly. - `LIB_DIR` is already established in both (each sources several sibling libs before this one). Lib correctly does NOT `set -euo pipefail` (documented at line 4) — right for a sourced file. ### Tests — substantive - New `tests/prep-subject.bats` (7): both consumption modes (helper + raw-regex/`BASH_REMATCH[2]`), positive shapes (merge / squash / rc-suffix), a negative, and a sharp **`chore(release-toolkit):` scope-substring false-positive guard** (verifies `\(release\)` isn't matched as a substring — a real edge that would otherwise trip the prep-merge path on any `release-*` scoped chore). Not placebo. - Regression suites green at head: `check-self-bootstrap.bats` 25/25, `release-decide.bats` 53/53, `prep-subject.bats` 7/7. `shellcheck --severity=warning` clean on the lib + both consumers. ### Discipline notes - New-abstraction pre-flight is satisfied in-code: the lib header names *why* (three duplicated copies risk drifting looser → silent skip-hole) and cites the #442 `register-patterns.sh` extraction as direct precedent. No unexplained wrapper. - CI verified **green by-SHA** (`22e9dac` combined = success, all 9 incl. `tests/bats` + `tests/shellcheck`) — not inferred from local. This engages the CI-poll-before-stamp discipline the arc reinforced. Clean to merge. — Surveyor
bosun merged commit 22e9dac458 into main 2026-07-07 20:30:39 +02:00
Sign in to join this conversation.
No description provided.