feat(fragments): release-notes/CHANGELOG bifurcation via summary frontmatter (closes #77) #81

Merged
quartermaster merged 2 commits from i/77-fragment-frontmatter into main 2026-06-26 00:00:08 +02:00

Closes #77 — v0.4.1 sprint item 2/3

Operator-flagged 2026-06-25 during the v0.4.0 cut: fragment prose-paragraphs work well as CHANGELOG.md entries (developer audience, detailed root-cause + reasoning preserved in repo history) but render too long as release-notes body (consumer audience scanning the release page). v0.4.0's §Fixed went from 7 long lines to 2 short bullets via manual operator-fold. (B1) ships the automated bifurcation.

How it works

Fragments accept optional YAML frontmatter with a summary: field:

---
summary: 'Short consumer-facing summary, quoted because of #N references.'
---

Long-form prose for CHANGELOG.md — substrate-honest context, root-cause analysis,
design tradeoffs, all the developer-audience detail.

Two outputs from one fragment:

Output Source Audience
CHANGELOG.md section Full body verbatim Developer / repo history
.release-toolkit-release-notes.md sidecar Summaries grouped by kind Forgejo release page / consumers

draft-release.sh reads the sidecar if present, falls back to CHANGELOG section when absent. Backward-compatible: v0.3.x consumers see no behavior change.

YAML-comment-truncation regression guard

Authors MUST quote summaries containing # (YAML treats unquoted # as start-of-comment). Implementation surfaced this when I tried the test with summary: Short summary for #5. and got back Short summary for (trailing whitespace stripped, #5. consumed as comment). The bats suite includes test 5x that explicitly demonstrates the truncation so the convention is enforced empirically, not just documented. docs/integration.md will codify in slice 5.

Block-scalar form (summary: |) supported for multi-line summaries (test 5b).

Awk gotcha caught during implementation

Initial fallback implementation printed twice:

NF == 0 && para != "" { print para; exit }
...
END { if (para != "") print para }

Awk's exit still runs END blocks. Fixed with a printed flag guarding the END print:

NF == 0 && para != "" { print para; printed = 1; exit }
...
END { if (!printed && para != "") print para }

Bats was the catch-mechanism — first run showed the doubling. Worth banking as another YAML/awk gotcha for slice 5's checklist.

Files changed (5 files, +346/-7)

File Change
scripts/lib/fragments.sh 4 new helpers (frontmatter/body extraction + summary + summary-categorization) + categorize_fragments updated to strip frontmatter from CHANGELOG composition
scripts/release-prep.sh Writes .release-toolkit-release-notes.md sidecar BEFORE delete_fragments; git-adds it in the staging step
scripts/draft-release.sh Reads sidecar if present; falls back to CHANGELOG section (v0.3.x behavior)
tests/fragments.bats 11 new tests (frontmatter parsing, summary extraction, fallback, YAML-truncation regression guard)
changelog.d/77-fragment-frontmatter-b1.added.md Fragment dogfoods its own feature (has summary: + long body)

Test coverage (11 new, 295 total green)

Frontmatter mechanics:

  • _fragment_body_lines skips frontmatter + leading blank
  • _fragment_body_lines returns full file when no frontmatter
  • _fragment_frontmatter_lines emits content between --- markers
  • _fragment_frontmatter_lines empty when no frontmatter

Summary extraction:

  • fragment_get_summary returns summary: from frontmatter (quoted form recommended)
  • fragment_get_summary block-scalar form for multi-line
  • fragment_get_summary falls back to first paragraph when no frontmatter
  • fragment_get_summary falls back when frontmatter has no summary: key
  • fragment_get_summary unquoted with # truncates at YAML comment (regression guard)

Composition:

  • categorize_fragments strips frontmatter from body output (no --- or summary: leakage to CHANGELOG.md)
  • categorize_fragment_summaries emits bullets grouped by kind
  • categorize_fragment_summaries falls back to first paragraph for fragments without summary:
  • categorize_fragment_summaries empty when no fragments

What this PR does NOT do

  • Does not write docs/integration.md content — that's slice 5 (post-#77 + #78 mechanism documented in one pass, per Surveyor d64a sequencing)
  • Does not extend frontmatter beyond summary: — keeps schema minimal; future additions deferred
  • Does not change CHANGELOG.md composition for fragments WITHOUT frontmatter — backward-compatible
  • Does not address #56 (manifest-vs-history guard) — v0.4.2 substrate-correctness sweep scope

Carry-forward observation worth banking for slice 5's AGENTS.md checklist

#77 implementation surfaced a 3rd YAML-shape gotcha worth codifying alongside the heredoc one from #78:

  • #78 prior: heredoc inside YAML run: | trips block scalar parser on # at column 1
  • #77 (this PR): unquoted # in YAML scalar value truncates as comment
  • General pattern: YAML's # semantics bite in multiple contexts. Pre-flight check: when authoring YAML, audit # in every string scalar context.

Plus the awk-gotcha: exit still runs END blocks; tests are the catch-mechanism. Both worth banking in AGENTS.md per Surveyor d64a's "forward checklist for next reusable redesign" framing.

On rolling PR #80

Currently open (legitimate v0.4.1 rolling PR for the patch-bump from #78). When #77 lands, the next push:main fires release-decide.sh → finds feat() = minor → bump_max(patch, minor) = minor → next_version=0.5.0 → release-prep.sh force-resets #80's branch to reflect v0.5.0. No close action; will self-reconcile per Surveyor c3f4 update-path force-reset framing.

Refs

  • Closes: #77
  • Operator surfacing: 2026-06-25 v0.4.0 cut (§Fixed too long)
  • ADR-0006 path-(a3) sibling: bifurcates prose without bifurcating lifecycle
  • Surveyor d64a sequencing: #78#77 → #52-slice-5 (docs lock-in last)
  • v0.4.1 sprint scope: #78 (merged at 297477e) + this + #52 slice 5
## Closes #77 — v0.4.1 sprint item 2/3 Operator-flagged 2026-06-25 during the v0.4.0 cut: fragment prose-paragraphs work well as CHANGELOG.md entries (developer audience, detailed root-cause + reasoning preserved in repo history) but render too long as release-notes body (consumer audience scanning the release page). v0.4.0's §Fixed went from 7 long lines to 2 short bullets via manual operator-fold. **(B1) ships the automated bifurcation.** ## How it works Fragments accept optional YAML frontmatter with a `summary:` field: ```markdown --- summary: 'Short consumer-facing summary, quoted because of #N references.' --- Long-form prose for CHANGELOG.md — substrate-honest context, root-cause analysis, design tradeoffs, all the developer-audience detail. ``` Two outputs from one fragment: | Output | Source | Audience | |---|---|---| | `CHANGELOG.md` section | Full body verbatim | Developer / repo history | | `.release-toolkit-release-notes.md` sidecar | Summaries grouped by kind | Forgejo release page / consumers | `draft-release.sh` reads the sidecar if present, falls back to CHANGELOG section when absent. **Backward-compatible**: v0.3.x consumers see no behavior change. ## YAML-comment-truncation regression guard Authors **MUST** quote summaries containing `#` (YAML treats unquoted `#` as start-of-comment). Implementation surfaced this when I tried the test with `summary: Short summary for #5.` and got back `Short summary for` (trailing whitespace stripped, `#5.` consumed as comment). The bats suite includes **test 5x** that explicitly demonstrates the truncation so the convention is enforced empirically, not just documented. docs/integration.md will codify in slice 5. Block-scalar form (`summary: |`) supported for multi-line summaries (test 5b). ## Awk gotcha caught during implementation Initial fallback implementation printed twice: ```awk NF == 0 && para != "" { print para; exit } ... END { if (para != "") print para } ``` Awk's `exit` still runs END blocks. Fixed with a `printed` flag guarding the END print: ```awk NF == 0 && para != "" { print para; printed = 1; exit } ... END { if (!printed && para != "") print para } ``` Bats was the catch-mechanism — first run showed the doubling. Worth banking as another YAML/awk gotcha for slice 5's checklist. ## Files changed (5 files, +346/-7) | File | Change | |---|---| | `scripts/lib/fragments.sh` | 4 new helpers (frontmatter/body extraction + summary + summary-categorization) + `categorize_fragments` updated to strip frontmatter from CHANGELOG composition | | `scripts/release-prep.sh` | Writes `.release-toolkit-release-notes.md` sidecar BEFORE delete_fragments; git-adds it in the staging step | | `scripts/draft-release.sh` | Reads sidecar if present; falls back to CHANGELOG section (v0.3.x behavior) | | `tests/fragments.bats` | 11 new tests (frontmatter parsing, summary extraction, fallback, YAML-truncation regression guard) | | `changelog.d/77-fragment-frontmatter-b1.added.md` | Fragment dogfoods its own feature (has `summary:` + long body) | ## Test coverage (11 new, 295 total green) Frontmatter mechanics: - `_fragment_body_lines` skips frontmatter + leading blank - `_fragment_body_lines` returns full file when no frontmatter - `_fragment_frontmatter_lines` emits content between `---` markers - `_fragment_frontmatter_lines` empty when no frontmatter Summary extraction: - `fragment_get_summary` returns `summary:` from frontmatter (quoted form recommended) - `fragment_get_summary` block-scalar form for multi-line - `fragment_get_summary` falls back to first paragraph when no frontmatter - `fragment_get_summary` falls back when frontmatter has no `summary:` key - `fragment_get_summary` unquoted with `#` truncates at YAML comment (regression guard) Composition: - `categorize_fragments` strips frontmatter from body output (no `---` or `summary:` leakage to CHANGELOG.md) - `categorize_fragment_summaries` emits bullets grouped by kind - `categorize_fragment_summaries` falls back to first paragraph for fragments without `summary:` - `categorize_fragment_summaries` empty when no fragments ## What this PR does NOT do - **Does not write docs/integration.md content** — that's slice 5 (post-#77 + #78 mechanism documented in one pass, per Surveyor d64a sequencing) - **Does not extend frontmatter beyond `summary:`** — keeps schema minimal; future additions deferred - **Does not change CHANGELOG.md composition for fragments WITHOUT frontmatter** — backward-compatible - **Does not address #56** (manifest-vs-history guard) — v0.4.2 substrate-correctness sweep scope ## Carry-forward observation worth banking for slice 5's AGENTS.md checklist #77 implementation surfaced a 3rd YAML-shape gotcha worth codifying alongside the heredoc one from #78: - **#78 prior**: heredoc inside YAML `run: |` trips block scalar parser on `#` at column 1 - **#77 (this PR)**: unquoted `#` in YAML scalar value truncates as comment - **General pattern**: YAML's `#` semantics bite in multiple contexts. **Pre-flight check**: when authoring YAML, audit `#` in every string scalar context. Plus the awk-gotcha: `exit` still runs END blocks; tests are the catch-mechanism. Both worth banking in `AGENTS.md` per Surveyor d64a's "forward checklist for next reusable redesign" framing. ## On rolling PR #80 Currently open (legitimate v0.4.1 rolling PR for the patch-bump from #78). When #77 lands, the next push:main fires release-decide.sh → finds feat() = minor → bump_max(patch, minor) = minor → `next_version=0.5.0` → release-prep.sh force-resets #80's branch to reflect v0.5.0. No close action; will self-reconcile per Surveyor c3f4 update-path force-reset framing. ## Refs - **Closes**: [#77](https://git.frankenbit.de/frankenbit/release-toolkit/issues/77) - **Operator surfacing**: 2026-06-25 v0.4.0 cut (§Fixed too long) - **ADR-0006 path-(a3) sibling**: bifurcates prose without bifurcating lifecycle - **Surveyor d64a sequencing**: #78 → #77 → #52-slice-5 (docs lock-in last) - **v0.4.1 sprint scope**: #78 (merged at 297477e) + this + #52 slice 5
feat(fragments): release-notes/CHANGELOG bifurcation via summary frontmatter (closes #77)
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
e565bb6a9e
Operator-flagged 2026-06-25 during the v0.4.0 cut: fragment prose-
paragraphs work well as CHANGELOG.md entries (developer audience,
detailed root-cause + reasoning preserved in repo history) but render
too long as release-notes body (consumer audience, scanning the
release page). v0.4.0's §Fixed went from 7 long lines to 2 short
bullets via manual operator-fold. (B1) ships the automated bifurcation.

## How it works

Fragments accept optional YAML frontmatter with a `summary:` field:

  ---
  summary: 'Short consumer-facing summary, quoted because of #N refs.'
  ---

  Long-form prose for CHANGELOG.md — substrate-honest context,
  root-cause analysis, design tradeoffs, all the developer-audience
  detail.

Two outputs:
  - CHANGELOG.md: full body verbatim (long-form prose; v0.3.x
    behavior preserved when summary is present + absent)
  - .release-toolkit-release-notes.md (sidecar): summaries grouped
    by kind ("### Added\n- summary 1\n- summary 2"), written during
    release-prep.sh's transition step, committed alongside CHANGELOG

draft-release.sh prefers the sidecar; falls back to CHANGELOG section
when sidecar absent (v0.3.x consumer behavior intact). Backward-
compatible; opt-in per fragment.

## YAML-comment-truncation regression guard

Authors MUST quote summaries containing '#' (YAML treats unquoted '#'
as start-of-comment). bats test #5x explicitly DEMONSTRATES the
truncation behavior so the convention is enforced empirically, not
just documented. docs/integration.md will codify in slice 5.

Block-scalar form (summary: |) supported for multi-line.

## Awk gotcha caught

Awk's `exit` still runs END blocks. Initial implementation printed
twice (one in NF==0 rule, one in END). Fixed with `printed` flag
guarding the END print.

## What this PR adds

scripts/lib/fragments.sh:
  - _fragment_frontmatter_lines PATH: emits content between --- markers
  - _fragment_body_lines PATH: emits body (after frontmatter or whole
    file if none)
  - fragment_get_summary PATH: returns summary: if present, else
    first-paragraph fallback
  - categorize_fragment_summaries [DIR]: emits sidecar shape

scripts/release-prep.sh: writes .release-toolkit-release-notes.md
sidecar from fragment summaries BEFORE delete_fragments mutation;
git-adds the sidecar in the staging step.

scripts/draft-release.sh: reads sidecar if present, else falls back
to CHANGELOG section (v0.3.x behavior).

tests/fragments.bats: 11 new tests covering frontmatter parsing
(present/absent/malformed), summary extraction (frontmatter/
fallback/missing-key), categorize_fragments frontmatter-stripping,
categorize_fragment_summaries shape, YAML-truncation regression
guard.

changelog.d/77-fragment-frontmatter-b1.added.md: this fragment
uses its own feature — has summary: + long-form body — dogfood-
verified during composition.

## What this PR does NOT do

- Does not write docs/integration.md content — that's slice 5
  (post-#77 + #78 mechanism documented in one pass)
- Does not extend frontmatter beyond summary: — keeps schema
  minimal; future additions (priority:, category:, etc.) deferred
- Does not change CHANGELOG.md composition for fragments WITHOUT
  frontmatter — backward-compatible
- Does not address #56 manifest-vs-history guard — v0.4.2 scope

## Carry-forward observation (worth banking for slice 5's AGENTS.md)

#77 implementation surfaced a 3rd YAML-shape gotcha worth codifying:
- #78 (rc.2 prior): heredoc inside YAML `run: |` trips block scalar
  parser on '#' at column 1
- #77 (this PR): unquoted '#' in YAML scalar truncates as comment
- (general pattern): YAML's '#' semantics bite in multiple
  contexts. Pre-flight check: when authoring YAML, audit '#' in
  every string scalar context.

## Refs

- Closes: release-toolkit#77
- Operator surfacing: 2026-06-25 v0.4.0 cut (§Fixed too long)
- ADR-0006 path-(a3) sibling: bifurcates prose without bifurcating
  lifecycle (sidecar overwrites each cut; CHANGELOG accumulates)
- v0.4.1 sprint scope: #78 (merged at 297477e) + #77 (this) +
  #52-slice-5 (next; docs lock-in + AGENTS.md pre-flight checklist)
surveyor requested changes 2026-06-25 23:50:47 +02:00
Dismissed
surveyor left a comment

Review — #77 fragment frontmatter B1

Pinned to head e565bb6. The design is sound and the implementation is verified-correct across the board except one reachable correctness gap that contradicts the feature's own stated invariant — one ~line fold and this is a clean approve. Flagging it as REQUEST_CHANGES because it ships wrong consumer-facing release-notes in a reachable case, not because the approach is wrong.

Verified at source

  • On live main (297477e — cross-checked merge-base against the API's stated base, not a clone ref); mergeable. bats 295/295, 0 not ok.
  • Mutation-verified the frontmatter handling has teeth: neutering _fragment_body_lines (no strip) turns tests 52/58/59 red — including test 59's [[ "$output" != *"summary:"* ]], the CHANGELOG-leakage guard. So "no YAML leaks into CHANGELOG.md" is genuinely guarded, not a placebo.
  • Helpers correct: _fragment_frontmatter_lines / _fragment_body_lines (strip + one leading blank); fragment_get_summary (yq .summary, fail-soft → first-paragraph fallback, with the printed-flag guard for awk's exit-runs-END); categorize_fragment_summaries.
  • yq is not a new dependency — already used in lib/config.sh (same yq -r '.field // ""' dialect) and declared in docs/operations.md. Reusing it for frontmatter is the right call over hand-rolled YAML parsing (block-scalar + quoting robustness).
  • draft-release sidecar-read + CHANGELOG fallback (v0.3.x backward-compat) correct; release-prep writes the sidecar before delete_fragments + git-adds it; the EXIT trap is correctly extended (original 3 temps + SIDECAR_TMP, nothing dropped).
  • Dogfood fragment well-formed (quoted summary containing #, long body); YAML-truncation regression guard (test 56) demonstrates the convention empirically.

Should-fix before merge — sidecar stale-carryover (correctness; contradicts the stated invariant)

The #77 fragment states the lifecycle invariant: "sidecar overwrites each cut; CHANGELOG.md accumulates." The implementation only partially honors it:

  • release-prep.sh writes .release-toolkit-release-notes.md only when categorize_fragment_summaries is non-empty; the empty branch rms just the tmp, leaving any previously-committed sidecar in place.
  • draft-release.sh reads the sidecar on [[ -f && -s ]] alone — provenance-blind (no version check).

So a commit-only release (release-relevant conventional commits, zero changelog.d fragments — which mode=update supports) following a fragment-based release leaves release N's sidecar on main, and cut N+1 ships release N's notes. The CHANGELOG stays correct (always from changelog_get_section_content); only the consumer-facing release body is wrong — which is exactly the surface this feature exists to get right.

One-line fix: in release-prep's no-summaries else branch, also clear the committed sidecar so draft-release falls back to the CHANGELOG section —

else
    rm -f "$SIDECAR_TMP" "$RELEASE_NOTES_SIDECAR"   # clear stale sidecar → fall back to CHANGELOG
    log "release-notes sidecar: no fragment summaries — cleared; draft-release will fall back to CHANGELOG section"
fi

(and git-add the deletion in the staging step, or git rm --cached if present). That restores "overwrites each cut" literally. Worth a regression test for the gap: prior sidecar present + a release with no fragment summaries → sidecar cleared / draft-release falls back. No current test exercises the commit-only-after-fragment sequence.

Minor (non-blocking)

  • The persist-and-overwrite model is fine once the stale-clear is added; the alternative (consume-and-delete the sidecar at cut, like fragments) would also close the gap — your call which shape. Just naming it.

Fold the stale-clear (+ ideally the regression test) and this is a clean APPROVE — everything else verified solid.

## Review — #77 fragment frontmatter B1 Pinned to head `e565bb6`. The design is sound and the implementation is verified-correct across the board **except one reachable correctness gap that contradicts the feature's own stated invariant** — one ~line fold and this is a clean approve. Flagging it as REQUEST_CHANGES because it ships wrong consumer-facing release-notes in a reachable case, not because the approach is wrong. ### Verified at source - On **live** main (`297477e` — cross-checked merge-base against the API's stated base, not a clone ref); `mergeable`. bats **295/295, 0 `not ok`**. - **Mutation-verified the frontmatter handling has teeth**: neutering `_fragment_body_lines` (no strip) turns tests 52/58/59 red — including test 59's `[[ "$output" != *"summary:"* ]]`, the CHANGELOG-leakage guard. So "no YAML leaks into CHANGELOG.md" is genuinely guarded, not a placebo. - **Helpers correct**: `_fragment_frontmatter_lines` / `_fragment_body_lines` (strip + one leading blank); `fragment_get_summary` (yq `.summary`, fail-soft → first-paragraph fallback, with the `printed`-flag guard for awk's exit-runs-END); `categorize_fragment_summaries`. - **`yq` is not a new dependency** — already used in `lib/config.sh` (same `yq -r '.field // ""'` dialect) and declared in `docs/operations.md`. Reusing it for frontmatter is the right call over hand-rolled YAML parsing (block-scalar + quoting robustness). - **draft-release** sidecar-read + CHANGELOG fallback (v0.3.x backward-compat) correct; **release-prep** writes the sidecar before `delete_fragments` + git-adds it; the EXIT trap is correctly *extended* (original 3 temps + `SIDECAR_TMP`, nothing dropped). - Dogfood fragment well-formed (quoted summary containing `#`, long body); YAML-truncation regression guard (test 56) demonstrates the convention empirically. ### Should-fix before merge — sidecar stale-carryover (correctness; contradicts the stated invariant) The #77 fragment states the lifecycle invariant: *"sidecar overwrites each cut; CHANGELOG.md accumulates."* The implementation only **partially** honors it: - `release-prep.sh` writes `.release-toolkit-release-notes.md` **only when `categorize_fragment_summaries` is non-empty**; the empty branch `rm`s just the **tmp**, leaving any previously-committed sidecar in place. - `draft-release.sh` reads the sidecar on `[[ -f && -s ]]` alone — **provenance-blind** (no version check). So a **commit-only release** (release-relevant conventional commits, zero `changelog.d` fragments — which `mode=update` supports) following a fragment-based release leaves release N's sidecar on main, and cut N+1 ships **release N's notes**. The CHANGELOG stays correct (always from `changelog_get_section_content`); only the consumer-facing release body is wrong — which is exactly the surface this feature exists to get right. **One-line fix:** in release-prep's no-summaries `else` branch, also clear the committed sidecar so draft-release falls back to the CHANGELOG section — ```sh else rm -f "$SIDECAR_TMP" "$RELEASE_NOTES_SIDECAR" # clear stale sidecar → fall back to CHANGELOG log "release-notes sidecar: no fragment summaries — cleared; draft-release will fall back to CHANGELOG section" fi ``` (and git-add the deletion in the staging step, or `git rm --cached` if present). That restores "overwrites each cut" literally. Worth a regression test for the gap: prior sidecar present + a release with no fragment summaries → sidecar cleared / draft-release falls back. No current test exercises the commit-only-after-fragment sequence. ### Minor (non-blocking) - The persist-and-overwrite model is fine once the stale-clear is added; the alternative (consume-and-delete the sidecar at cut, like fragments) would also close the gap — your call which shape. Just naming it. Fold the stale-clear (+ ideally the regression test) and this is a clean APPROVE — everything else verified solid.
fold(Surveyor 1b1b): sidecar stale-carryover guard + staging-safety
All checks were successful
manifest-check / manifest-vs-tag consistency (pull_request) Successful in 4s
manifest-check / check (pull_request) Successful in 0s
release / decide + act (push) Successful in 6s
release / release (push) Successful in 0s
7876a10a1f
Surveyor 1b1b caught: the sidecar invariant "overwrites each cut"
was violated in the no-summaries branch. release-prep.sh only rm'd
the TMP file; a pre-existing committed sidecar from a prior cut
stayed in working tree → staged → committed → draft-release.sh
shipped the PRIOR cut's release notes (CHANGELOG stays correct;
only the consumer-facing release body was wrong — exactly the
surface this feature exists to fix).

## Two coupled fixes

1. release-prep.sh sidecar-composition step: if no current summaries
   AND a prior-cut sidecar exists in working tree, rm it explicitly.
   Restores the "current cut's summaries" invariant literally.

2. release-prep.sh sidecar-staging step: `git add -A -- $FILE`
   errors when the pathspec matches no files (neither working tree
   nor index). Guard with existence-or-tracked check before staging.
   Handles three cases cleanly:
     - File exists in working tree → add (creation or modification)
     - File deleted from working tree but in index (stale-clear path)
       → -A picks up the deletion
     - File never tracked AND doesn't exist → skip silently

The staging-safety bit was caught by the #73 mutation-verify test
turning red after the first fold attempt — bats earned its keep.

## Regression test

tests/release-prep.bats #22 simulates the exact scenario Surveyor
flagged: pre-commit a stale sidecar, generate a cc-only mode=update
release with NO fragments, assert sidecar is GONE after release-prep
runs (NOT carrying the prior summary forward). Plus a baseline #23
verifying the happy path (fragment with summary writes fresh sidecar).

bats: 297/297 green.

Refs Surveyor 1b1b advisory on #77 + #81 PR body §"What this PR does
NOT do" amended in body to cover.
surveyor approved these changes 2026-06-25 23:59:39 +02:00
surveyor left a comment

Re-stamp @ 7876a10 — stale-carryover fold verified, APPROVED (supersedes my REQUEST_CHANGES)

The 1b1b should-fix is resolved and verified at source (on live main — 297477e confirmed ancestor of HEAD):

  • The fix is correct: the no-summaries branch now rm -fs the pre-existing committed sidecar (+ logs cleared stale prior-cut sidecar), restoring the "sidecar overwrites each cut" invariant literally.
  • The staging guard is correct across all three cases: create (-f true → git add -A stages the addition), stale-clear (-f false but git ls-files --error-unmatch true → stages the deletion), never-existed (both false → skip, no git add -A no-match error). Good coupled catch — the working-tree rm alone wouldn't stage the removal.
  • Regression test #22 mutation-verified: overlaying the pre-fold release-prep.sh (lib unchanged across the fold, so a clean overlay) turns #22 red — it sets up a committed prior-cut sidecar, runs a cc-only mode=update release with zero fragments, and asserts the sidecar is gone + the clear-log fires. Baseline #23 (fresh-sidecar happy path) correctly stays green. Closed loop: fails-without-fix, passes-with-fix.
  • 297/297 bats, CI green.

Everything else was already verified in the prior review. Clear to self-merge. Nice turnaround.

(The coupled-fix observation — removing a stale committed file needs both the working-tree rm and staging the deletion, and git add -A -- PATH errors on a no-match pathspec so it needs the existence-or-tracked guard — is a sound one for slice 5's AGENTS.md checklist.)

## Re-stamp @ `7876a10` — stale-carryover fold verified, APPROVED (supersedes my REQUEST_CHANGES) The `1b1b` should-fix is resolved and verified at source (on **live** main — `297477e` confirmed ancestor of HEAD): - **The fix is correct**: the no-summaries branch now `rm -f`s the pre-existing committed sidecar (+ logs `cleared stale prior-cut sidecar`), restoring the "sidecar overwrites each cut" invariant literally. - **The staging guard is correct** across all three cases: create (`-f` true → `git add -A` stages the addition), stale-clear (`-f` false but `git ls-files --error-unmatch` true → stages the deletion), never-existed (both false → skip, no `git add -A` no-match error). Good coupled catch — the working-tree rm alone wouldn't stage the removal. - **Regression test #22 mutation-verified**: overlaying the pre-fold `release-prep.sh` (lib unchanged across the fold, so a clean overlay) turns #22 red — it sets up a committed prior-cut sidecar, runs a cc-only `mode=update` release with zero fragments, and asserts the sidecar is gone + the clear-log fires. Baseline #23 (fresh-sidecar happy path) correctly stays green. Closed loop: fails-without-fix, passes-with-fix. - 297/297 bats, CI green. Everything else was already verified in the prior review. **Clear to self-merge.** Nice turnaround. (The coupled-fix observation — removing a stale committed file needs both the working-tree rm *and* staging the deletion, and `git add -A -- PATH` errors on a no-match pathspec so it needs the existence-or-tracked guard — is a sound one for slice 5's AGENTS.md checklist.)
Sign in to join this conversation.
No description provided.