feat: actions/changelog-monotonic — shipped release headings are append-only #41

Merged
claude-bot-andresmgsl merged 3 commits from build/6-changelog-monotonic into main 2026-07-22 21:23:07 +00:00
claude-bot-andresmgsl commented 2026-07-22 21:10:18 +00:00 (Migrated from github.com)

Closes #6

Acceptance criteria

  • Action + script + tests land; CI green; shellcheck/actionlint clean (verified locally: shellcheck sweep clean, actionlint clean, test suite 7 files / all cases passing — CI is the standing proof).
  • Exercised via uses: ./actions/changelog-monotonic in this repo's CI — a scratch fixture job until #11 gives the repo a changelog of its own, and a real one: the exercise commits a fixture base in the job's checkout, commits an insert-above edit on top, and runs the action against that history, so green means a genuine merge-base containment run, not an action.yml parse.
  • Header comment carries box#122/#118 and the three-reasons-for-a-separate-file essay; incident references from cast's port note preserved (cast#133, box#143, both extractor shapes).

Builder notes

  • Port source: box (the guard's origin), with cast#133's port notes folded in. One adaptation over a verbatim carry: box's duplicate-heading lore says the published body absorbs what sits between the copies, because box's extractor had no exit. This repo's canonical extractor (changelog_section, #4) is the if (found) exit shape — so the header and the duplicate error message tell the truth for this repo (truncation; the stranded entry is dropped from this release's notes and the next's), while preserving both shapes' history per cast#133.
  • Interface: changelog-monotonic.sh [<base-ref>] [<changelog>], env fallbacks CHANGELOG_MONOTONIC_BASE / CHANGELOG / CHANGELOG_MONOTONIC_STRICT (script default 0 for sensible local degradation, exactly as the issue specifies; the action defaults strict to "1" because it runs in CI). action.yml documents the fetch-depth: 0 requirement and what its absence looks like (a red STRICT failure naming the checkout fix). No version source — the guard reads the diff, never VERSION/package.json.
  • Ordering under test: uniqueness-before-history is box#143's lesson, so the suite proves the duplicate fails even with an unresolvable base ref, even with STRICT=1 on that same broken ref, and even outside a git repo entirely.
  • CI exercise base ref: passed explicitly (an in-job fixture branch) rather than relying on the input's origin/main default, because the exercise job's shallow PR checkout carries no origin/main; consumers get the default via fetch-depth: 0, which is the action's documented requirement.

Worklog

  • Port actions/changelog-monotonic/changelog-monotonic.sh from box (origin), fold in cast#133 port notes, adapt references to this repo's lib/changelog.sh extractor
  • action.yml — composite, inputs base-ref / changelog / strict (default "1"), fetch-depth: 0 requirement documented
  • test/changelog-monotonic.test.sh — constructed git repos covering every case in the issue's test list
  • CI: exercise uses: ./actions/changelog-monotonic against a scratch history with real containment
  • Local verification: test suite (20/20 new cases, 7/7 files), shellcheck-all clean, actionlint-all clean
  • Mark ready, request the panel

Changelog

  • No entry, stated exception: CHANGELOG.md intentionally does not exist on main yet — #11 owns its bootstrap and backfills pre-#11 entries (family precedent: PRs #28/#29/#31/#33/#39).
Closes #6 ## Acceptance criteria - [x] Action + script + tests land; CI green; shellcheck/actionlint clean (verified locally: shellcheck sweep clean, actionlint clean, test suite 7 files / all cases passing — CI is the standing proof). - [x] Exercised via `uses: ./actions/changelog-monotonic` in this repo's CI — a scratch fixture job until #11 gives the repo a changelog of its own, and a *real* one: the exercise commits a fixture base in the job's checkout, commits an insert-above edit on top, and runs the action against that history, so green means a genuine merge-base containment run, not an action.yml parse. - [x] Header comment carries box#122/#118 and the three-reasons-for-a-separate-file essay; incident references from cast's port note preserved (cast#133, box#143, both extractor shapes). ## Builder notes - **Port source:** box (the guard's origin), with cast#133's port notes folded in. One adaptation over a verbatim carry: box's duplicate-heading lore says the published body *absorbs* what sits between the copies, because box's extractor had no `exit`. This repo's canonical extractor (`changelog_section`, #4) is the `if (found) exit` shape — so the header and the duplicate error message tell the truth for *this* repo (truncation; the stranded entry is dropped from this release's notes and the next's), while preserving both shapes' history per cast#133. - **Interface:** `changelog-monotonic.sh [<base-ref>] [<changelog>]`, env fallbacks `CHANGELOG_MONOTONIC_BASE` / `CHANGELOG` / `CHANGELOG_MONOTONIC_STRICT` (script default `0` for sensible local degradation, exactly as the issue specifies; the action defaults `strict` to `"1"` because it runs in CI). `action.yml` documents the `fetch-depth: 0` requirement and what its absence looks like (a red STRICT failure naming the checkout fix). No version source — the guard reads the diff, never VERSION/package.json. - **Ordering under test:** uniqueness-before-history is box#143's lesson, so the suite proves the duplicate fails even with an unresolvable base ref, even with STRICT=1 on that same broken ref, and even outside a git repo entirely. - **CI exercise base ref:** passed explicitly (an in-job fixture branch) rather than relying on the input's `origin/main` default, because the exercise job's shallow PR checkout carries no `origin/main`; consumers get the default via `fetch-depth: 0`, which is the action's documented requirement. ## Worklog - [x] Port `actions/changelog-monotonic/changelog-monotonic.sh` from box (origin), fold in cast#133 port notes, adapt references to this repo's `lib/changelog.sh` extractor - [x] `action.yml` — composite, inputs `base-ref` / `changelog` / `strict` (default "1"), fetch-depth: 0 requirement documented - [x] `test/changelog-monotonic.test.sh` — constructed git repos covering every case in the issue's test list - [x] CI: exercise `uses: ./actions/changelog-monotonic` against a scratch history with real containment - [x] Local verification: test suite (20/20 new cases, 7/7 files), shellcheck-all clean, actionlint-all clean - [x] Mark ready, request the panel ## Changelog - [x] No entry, stated exception: `CHANGELOG.md` intentionally does not exist on main yet — #11 owns its bootstrap and backfills pre-#11 entries (family precedent: PRs #28/#29/#31/#33/#39).
danmt (Migrated from github.com) reviewed 2026-07-22 21:10:18 +00:00
grok-bot-andresmgsl commented 2026-07-22 21:15:22 +00:00 (Migrated from github.com)

🔎 reviewing head a0bea573a1

🔎 reviewing head a0bea573a14fcc4a0b43eaacc3bbfc583b047171
kimi-bot-andresmgsl commented 2026-07-22 21:16:27 +00:00 (Migrated from github.com)

🔎 reviewing head a0bea573a1

🔎 reviewing head a0bea573a14fcc4a0b43eaacc3bbfc583b047171
grok-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-22 21:16:45 +00:00
grok-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: approve

Reviewed head a0bea573a14fcc4a0b43eaacc3bbfc583b047171 against #6.

Acceptance criteria

  1. Action + script + tests; CI green; shellcheck cleanactions/changelog-monotonic/{action.yml,changelog-monotonic.sh} and test/changelog-monotonic.test.sh land. CI test + action-exercise both SUCCESS. Local: full suite 7/7 files green (monotonic 20/20), shellcheck clean on the new scripts.

  2. CI exercise via uses: ./actions/changelog-monotonicaction-exercise builds a real two-commit history (fixture base branch → insert-above mutation) and runs the composite against it with an explicit base-ref, so green means genuine merge-base containment, not an action.yml parse. Correct choice given the shallow PR checkout has no origin/main for the default.

  3. Header / incident lore — box#122 / box#118, the three-reasons-for-a-separate-file essay, cast#133 extractor shapes, and box#143 uniqueness-before-history are all present and accurate for this repo's if (found) exit extractor.

Spec fidelity

  • Uniqueness on HEAD first, then containment as superset of ## X.Y.Z set members; ## Unreleased excluded by version shape.
  • STRICT default "1" in the action, "0" in the script; skip path names fetch-depth: 0.
  • Inputs match #6 (base-ref / changelog / strict); no version source.
  • Issue test list covered, plus the honest edges (file-first-added, vacuous push-to-main, env wiring, duplicate wins over STRICT/history gates).

Changelog exception

No CHANGELOG.md entry is correct here — file does not exist on main yet; #11 owns bootstrap (family precedent with prior guard PRs).

Approve.

## Verdict: approve Reviewed head `a0bea573a14fcc4a0b43eaacc3bbfc583b047171` against #6. ### Acceptance criteria 1. **Action + script + tests; CI green; shellcheck clean** — `actions/changelog-monotonic/{action.yml,changelog-monotonic.sh}` and `test/changelog-monotonic.test.sh` land. CI `test` + `action-exercise` both SUCCESS. Local: full suite 7/7 files green (monotonic 20/20), shellcheck clean on the new scripts. 2. **CI exercise via `uses: ./actions/changelog-monotonic`** — `action-exercise` builds a real two-commit history (fixture base branch → insert-above mutation) and runs the composite against it with an explicit `base-ref`, so green means genuine merge-base containment, not an `action.yml` parse. Correct choice given the shallow PR checkout has no `origin/main` for the default. 3. **Header / incident lore** — box#122 / box#118, the three-reasons-for-a-separate-file essay, cast#133 extractor shapes, and box#143 uniqueness-before-history are all present and accurate for *this* repo's `if (found) exit` extractor. ### Spec fidelity - Uniqueness on HEAD first, then containment as superset of `## X.Y.Z` set members; `## Unreleased` excluded by version shape. - STRICT default `"1"` in the action, `"0"` in the script; skip path names `fetch-depth: 0`. - Inputs match #6 (`base-ref` / `changelog` / `strict`); no version source. - Issue test list covered, plus the honest edges (file-first-added, vacuous push-to-main, env wiring, duplicate wins over STRICT/history gates). ### Changelog exception No `CHANGELOG.md` entry is correct here — file does not exist on main yet; #11 owns bootstrap (family precedent with prior guard PRs). Approve.
codex-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-22 21:17:25 +00:00
codex-bot-andresmgsl (Migrated from github.com) left a comment

Verdict: I agree with everything and have no additional feedback.

Reviewed the complete current head a0bea57 against issue #6. Uniqueness runs before every history-dependent gate, containment and STRICT behavior match the contract, the composite inputs and documentation are correct, and the CI exercise performs a real merge-base containment run. Current test and action-exercise checks are green.

Verdict: I agree with everything and have no additional feedback. Reviewed the complete current head a0bea57 against issue #6. Uniqueness runs before every history-dependent gate, containment and STRICT behavior match the contract, the composite inputs and documentation are correct, and the CI exercise performs a real merge-base containment run. Current test and action-exercise checks are green.
kimi-bot-andresmgsl (Migrated from github.com) approved these changes 2026-07-22 21:19:26 +00:00
kimi-bot-andresmgsl (Migrated from github.com) left a comment

Approve at a0bea57. Verified, not just read:

Acceptance criteria (issue #6)

  • Action + script + tests land; CI green on this head (run 29958320359, both jobs success; the test job includes the shellcheck and actionlint gates). Locally: test/changelog-monotonic.test.sh 20/20 pass; the 3 failing files in the suite are pre-existing environment gaps here (no node/npm/jq on this box), unrelated to this PR.
  • Exercised via uses: ./actions/changelog-monotonic in the action-exercise job — and it is a genuine containment run: the job commits a fixture base, branches it, commits an insert-above edit, and runs the action against that history with strict at its CI default of "1". The explicitly-passed base-ref (rather than the input's origin/main default) is correct for a shallow PR checkout and documented in the job comment.
  • Header comment carries box#122/#118, the three-reasons-for-a-separate-file essay, and cast's port notes (cast#133, box#143, both extractor shapes).

The rule, tested against constructed repos I drove myself

  • Ran the new suite in a detached worktree: all 8 cases from the issue's test list are present and passing — insert-above pass, ceremony stamp pass, deleted-heading fail naming the version, duplicate fail with no usable base (including STRICT=1 on a broken ref and outside a git repo entirely, the box#143 ordering), heading-less edit pass, STRICT=0 loud skip, STRICT=1 hard failure naming fetch-depth: 0, and 0.7.0 vs 0.7.0-rc1 as distinct set members.
  • Extra adversarial probe beyond the suite: a changelog keeping 0.7.0-rc1 and adding 0.7.0 passes (all 1 release heading(s) at the merge base ... are still present), confirming superset is exact and the whole-field set membership works in both directions.
  • Uniqueness correctly runs before any history-dependent gate, and the vacuous-containment success line on the push-to-main shape honestly names which half ran.

Port fidelity

  • Diffed the script against box's original at the pinned ref: every change is comments (incident references rewritten to this repo's names: changelog_section/lib/changelog.sh for release-notes.sh, tree's version for VERSION), plus the specified interface additions (CHANGELOG/CHANGELOG_MONOTONIC_BASE env fallbacks). Zero logic drift. The extractor-shape adaptation (truncation here vs absorption in box) is truthful for this repo's if (found) exit extractor.

action.yml

  • Matches the issue: base-ref default origin/${{ github.base_ref || github.ref_name }}, changelog default CHANGELOG.md, strict default "1" with the script's local default staying 0, and the fetch-depth: 0 requirement documented in the description along with the red-STRICT failure shape its absence produces. No version source, as specified.

Changelog exception stated and consistent with family precedent (#28/#29/#31/#33/#39 — #11 owns the bootstrap).

Nothing blocking. Non-blocking note: the ## 0.7.0-rc1 — DATE duplicate-error quick-check line embeds the first dupe name unquoted in a heredoc — fine as-is, since heading tokens can't contain shell metacharacters that would matter there.

Approve at a0bea57. Verified, not just read: **Acceptance criteria (issue #6)** - Action + script + tests land; CI green on this head (run 29958320359, both jobs success; the `test` job includes the shellcheck and actionlint gates). Locally: `test/changelog-monotonic.test.sh` 20/20 pass; the 3 failing files in the suite are pre-existing environment gaps here (no node/npm/jq on this box), unrelated to this PR. - Exercised via `uses: ./actions/changelog-monotonic` in the `action-exercise` job — and it is a genuine containment run: the job commits a fixture base, branches it, commits an insert-above edit, and runs the action against that history with `strict` at its CI default of "1". The explicitly-passed `base-ref` (rather than the input's `origin/main` default) is correct for a shallow PR checkout and documented in the job comment. - Header comment carries box#122/#118, the three-reasons-for-a-separate-file essay, and cast's port notes (cast#133, box#143, both extractor shapes). **The rule, tested against constructed repos I drove myself** - Ran the new suite in a detached worktree: all 8 cases from the issue's test list are present and passing — insert-above pass, ceremony stamp pass, deleted-heading fail naming the version, duplicate fail with no usable base (including STRICT=1 on a broken ref and outside a git repo entirely, the box#143 ordering), heading-less edit pass, STRICT=0 loud skip, STRICT=1 hard failure naming `fetch-depth: 0`, and `0.7.0` vs `0.7.0-rc1` as distinct set members. - Extra adversarial probe beyond the suite: a changelog keeping `0.7.0-rc1` and adding `0.7.0` passes (`all 1 release heading(s) at the merge base ... are still present`), confirming superset is exact and the whole-field set membership works in both directions. - Uniqueness correctly runs before any history-dependent gate, and the vacuous-containment success line on the push-to-main shape honestly names which half ran. **Port fidelity** - Diffed the script against box's original at the pinned ref: every change is comments (incident references rewritten to this repo's names: `changelog_section`/lib/changelog.sh for release-notes.sh, tree's version for VERSION), plus the specified interface additions (`CHANGELOG`/`CHANGELOG_MONOTONIC_BASE` env fallbacks). Zero logic drift. The extractor-shape adaptation (truncation here vs absorption in box) is truthful for this repo's `if (found) exit` extractor. **action.yml** - Matches the issue: `base-ref` default `origin/${{ github.base_ref || github.ref_name }}`, `changelog` default `CHANGELOG.md`, `strict` default `"1"` with the script's local default staying `0`, and the `fetch-depth: 0` requirement documented in the description along with the red-STRICT failure shape its absence produces. No version source, as specified. Changelog exception stated and consistent with family precedent (#28/#29/#31/#33/#39 — #11 owns the bootstrap). Nothing blocking. Non-blocking note: the `## 0.7.0-rc1 — DATE` duplicate-error quick-check line embeds the first dupe name unquoted in a heredoc — fine as-is, since heading tokens can't contain shell metacharacters that would matter there.
claude-bot-andresmgsl commented 2026-07-22 21:20:42 +00:00 (Migrated from github.com)

Round summary — converged, handing off

What shipped (a0bea57, 3 commits): the monotonic changelog guard ported from box with cast#133's port notes folded in.

  • actions/changelog-monotonic/changelog-monotonic.sh — uniqueness of ## X.Y.Z headings on HEAD first, then merge-base containment (base's shipped-heading set must be a subset of HEAD's); ## Unreleased excluded by version shape; STRICT defaults 0 in the script for local degradation. Header carries box#122/box#118, the three-reasons-for-a-separate-file essay, and both extractor shapes' history — with the duplicate-heading lore adapted to tell the truth for this repo's if (found) exit extractor (truncation, not absorption).
  • actions/changelog-monotonic/action.yml — composite wrapper, inputs base-ref/changelog/strict (default "1" in CI), fetch-depth: 0 requirement documented along with what its absence looks like.
  • test/changelog-monotonic.test.sh — 20 cases over constructed git repos, covering the issue's full test list plus the box#143 ordering edges: the duplicate fails before history is consulted, even with an unresolvable base ref, even with STRICT=1 on that ref, even outside a git repo.
  • .github/workflows/ci.ymlaction-exercise runs uses: ./actions/changelog-monotonic against a real two-commit fixture history (base branch → insert-above edit) with an explicit base-ref, so green is a genuine containment run, not an action.yml parse.

Rounds: one. The panel (grok, codex, kimi) reviewed the first ready head a0bea57; all three approved it with no changes requested, so the head never moved during review.

Verified: CI green at a0bea57 (test + action-exercise); local full suite 7/7 files (monotonic 20/20); shellcheck and actionlint sweeps clean. All three approvals are of the current head; mergeStateStatus CLEAN, no blocker:*.

Post-merge residue:

  • No CHANGELOG.md entry — the file doesn't exist on main yet; #11 owns its bootstrap and backfills pre-#11 entries (family precedent: #28/#29/#31/#33/#39). This PR's line lands in that backfill.
  • The CI exercise stays fixture-based until #11 gives the repo a changelog of its own; wiring the guard into the merge door on the repo's real CHANGELOG.md belongs to that later work, not here.

Requesting @danmt's review and setting state:needs-human — the PR is yours.

## Round summary — converged, handing off **What shipped** (`a0bea57`, 3 commits): the monotonic changelog guard ported from box with cast#133's port notes folded in. - `actions/changelog-monotonic/changelog-monotonic.sh` — uniqueness of `## X.Y.Z` headings on HEAD first, then merge-base containment (base's shipped-heading set must be a subset of HEAD's); `## Unreleased` excluded by version shape; STRICT defaults `0` in the script for local degradation. Header carries box#122/box#118, the three-reasons-for-a-separate-file essay, and both extractor shapes' history — with the duplicate-heading lore adapted to tell the truth for *this* repo's `if (found) exit` extractor (truncation, not absorption). - `actions/changelog-monotonic/action.yml` — composite wrapper, inputs `base-ref`/`changelog`/`strict` (default `"1"` in CI), `fetch-depth: 0` requirement documented along with what its absence looks like. - `test/changelog-monotonic.test.sh` — 20 cases over constructed git repos, covering the issue's full test list plus the box#143 ordering edges: the duplicate fails before history is consulted, even with an unresolvable base ref, even with STRICT=1 on that ref, even outside a git repo. - `.github/workflows/ci.yml` — `action-exercise` runs `uses: ./actions/changelog-monotonic` against a real two-commit fixture history (base branch → insert-above edit) with an explicit `base-ref`, so green is a genuine containment run, not an `action.yml` parse. **Rounds**: one. The panel (grok, codex, kimi) reviewed the first ready head `a0bea57`; all three approved it with no changes requested, so the head never moved during review. **Verified**: CI green at `a0bea57` (`test` + `action-exercise`); local full suite 7/7 files (monotonic 20/20); shellcheck and actionlint sweeps clean. All three approvals are of the current head; `mergeStateStatus` CLEAN, no `blocker:*`. **Post-merge residue**: - No `CHANGELOG.md` entry — the file doesn't exist on main yet; #11 owns its bootstrap and backfills pre-#11 entries (family precedent: #28/#29/#31/#33/#39). This PR's line lands in that backfill. - The CI exercise stays fixture-based until #11 gives the repo a changelog of its own; wiring the guard into the merge door on the repo's real `CHANGELOG.md` belongs to that later work, not here. Requesting @danmt's review and setting `state:needs-human` — the PR is yours.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/ceremony#41
No description provided.