Real version-bump wrap-step handler (open-queue #3 post-#139)#202
Merged
Conversation
Replaces the last remaining no-op stub in `lib/wrap-steps/` (`version-bump`, sitting unchanged since #139 Chunk 3) with a real handler that promotes `CHANGELOG.md`'s `[Unreleased]` section to a dated release and bumps `version.json`. **Bump-level precedence:** 1. `options.bumpLevel` override (`patch` | `minor` | `major`) 2. `BREAKING` marker anywhere in [Unreleased] body → `major` 3. `### Added` / `### Changed` / `### Removed` / `### Deprecated` present → `minor` 4. Otherwise (only `### Fixed` / `### Security`) → `patch` **Single-transaction discipline + composite-staged-keys.** Handler stages two entries under composite keys (`'version-bump:version-json'` and `'version-bump:changelog'`) so the Chunk-9 `commit` step's `_flushStagedWrites` flushes both. Establishes the multi-file write-staging convention for any future step that needs more than one file write. **Commit body line.** `lib/wrap-steps/commit.js:_buildBodyLines` gains a new duck-type matching `{oldVersion, newVersion, bumpLevel}` — emits `- Bumped <old> → <new> (<level>)` once per pipeline run, deduped via a local flag since both staged entries carry the metadata. **Never blocks** (matches the ADR 0002 step-kind contract). Every degraded condition — missing `version.json`, malformed JSON, non-semver version field, missing `CHANGELOG.md`, missing `[Unreleased]` section, empty `[Unreleased]` body — returns `{ok:true, status:'skipped'}` with a `reason` / `detail` for the wrap drawer to render inline. **Idempotent on re-wrap.** After a successful bump the `[Unreleased]` body is empty; next wrap observes no entries and skips. **Banner emoji NOT auto-injected.** `> 🛟` (bug-fix) / `> 🚀` (feature) banners stay a curated operator decision; the handler promotes `[Unreleased]` body byte-for-byte under the new dated heading. **Test coverage.** 39 net new tests in `test/wrap-pipeline.test.js`: - Pure-helper decision tables for `_parseSemver`, `_bumpSemver`, `_parseUnreleased`, `_decideBumpLevel`, `_promoteUnreleased`. - Handler tests for every skip path + happy path + idempotency + override + BREAKING-marker + never-blocks contract sweep. - Commit-body-line duck-type test + dedupe pin. The Chunk-3 `wrap-pipeline step stubs` describe block updates: `kinds` array marked empty with a sentinel pin (`assert.equal(kinds.length, 0, …)`) so any future stub-introducing chunk has to add its kind and trip the sentinel — explicit handoff signal rather than vacuous block. ADR 0002 Status line extended with the open-queue-#3 marker. Full suite: 2324 / 2324 pass.
m1 (MINOR): wrap-drawer.js carried a stale "Chunk-3 no-op stub"
comment for the `version-bump` case in `deriveDetail`. Now that the
real handler ships in this same PR, the comment was wrong as of
merge. Replaced with a one-liner summarizing the actual emitted
shape: `{from, to, bumpLevel, detail}` on done, `{skipped, reason,
detail}` on skip.
n1 (NIT): `BREAKING_RE` was `\bBREAKING\b` — broader than the build
plan documented (`BREAKING:` or `(BREAKING)` markers). Tightened to
`/\bBREAKING(?::|\s*\()/` so casual uppercase `BREAKING` in prose
(e.g. `## NOT BREAKING — just renamed`) no longer falsely forces a
major bump. Added a new `_decideBumpLevel` test covering the
false-positive shapes the old regex would have matched; updated the
existing test data to include the colon so the assertions stay
meaningful with the tighter regex.
n4 (NIT): documented in the module docstring that `version.json` is
always re-serialized to 2-space indent + trailing newline; projects
using a different style will have the bump re-normalize silently.
Open follow-up if a methodology adopts a different style.
Coverage gaps (3 new pins):
- Byte-exact whitespace pin: `_promoteUnreleased` emits exactly one
blank line between `[Unreleased]` and the new dated heading. A
stray double-blank would not previously have been caught.
- Cross-file invariant pin: promoted CHANGELOG output goes through
the same regex detectors as `test/changelog-structure.test.js`
(descending semver order + no-dupes + heading-parseable). Drift
surfaces as a test failure in BOTH places — the desired symmetry.
- Stage-+-flush integration test: new describe block bridging
`versionBump.run` and `commitStep._flushStagedWrites`. Proves the
two composite-keyed staged entries both make it to disk on a
single flush call, with bytes on disk matching the staged content.
The 4th gap (drawer-side `deriveDetail` for version-bump skip path)
is already covered by `test/wrap-drawer.test.js:196-205`.
n2 (NIT — CHANGELOG descending-order runtime guard) and n3 (NIT —
`output.skipped:true` convention lockstep across handlers) filed as
follow-up issues per Critic's recommendation; both touch
out-of-scope surface beyond this chunk's mandate.
Full suite: 2328 / 2328 pass (+4 from this fixup commit; PR total
now +43 vs main).
This was referenced May 23, 2026
[bug] version-bump handler uses UTC date instead of local-timezone date for new release heading
#205
Closed
Jason-Vaughan
added a commit
that referenced
this pull request
May 23, 2026
Eats its own dogfood: invokes the new `lib/wrap-steps/version-bump.js` handler (shipped in PR #202 a few hours ago) programmatically against this repo, then flushes the two staged writes via the same `commit._flushStagedWrites` the V2 wrap pipeline would use. Handler output: - oldVersion: 3.16.2 - newVersion: 3.17.0 - bumpLevel: minor (subsections: Changed, Fixed, Added) - detail: 3.16.2 → 3.17.0 (minor) Highlights of this release (full notes in CHANGELOG.md): - **#139 Methodology-aware single-button session wrap (series closed).** 12 chunks across the May 14–19 window. `wrapV2: true` is now the default. Full server-side pipeline (`pr-check` → `critic-check` → `version-bump` → `ai-content` × 3 → `priming-roll` → `commit`) replaces the legacy NL-prompt-via-tmux flow. ADR 0002 is the durable home. - **Open-queue #2 (PR #200): prawduct ai-content prompts populated.** Three placeholder steps (`changelog-update` / `learnings-capture` / `memory-update`) gain real prompts. V2 wraps now produce CHANGELOG entries / `learnings.md` entries / MEMORY.md session blocks instead of three SKIPPED rows. - **Open-queue #3 (PR #202): real version-bump handler.** The handler that cut THIS release. Last #139-era no-op stub replaced. - **PR #191: tmux env-ordering hotfix.** Engine `launch.env` now reaches the spawned engine process (was silently dropped pre-fix). Surfaced via Aider / LiteLLM `OPENAI_API_KEY` integration. - **#168 CHANGELOG structural-invariants test.** Pinned post-PR #166 regression class. CHANGELOG `[Unreleased]` heading retained at the top with an empty body (per the handler's promote contract) so the next session has somewhere to accumulate entries. Banner emoji NOT auto-injected — to be added manually as `> 🚀` (this is a feature release, not a bug-fix) in a follow-on commit or release-notes edit per the project's banner convention. The UTC-vs-local-date bug in the version-bump handler (surfaced cutting this release: handler stamped 2026-05-23 in UTC; manually patched to 2026-05-22 to match the local-zoned convention used by every prior CHANGELOG entry) is filed as #205. Future releases pick up the fix when that lands.
This was referenced May 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Replaces the last remaining no-op stub in
lib/wrap-steps/—version-bump, sitting unchanged since #139 Chunk 3 — with a real handler that promotesCHANGELOG.md's[Unreleased]section to a dated release and bumpsversion.json.Why
version-bumpwas the final piece of the V2 wrap pipeline still returning a placeholder{ok:true, status:'done'}from the early Chunk-3 scaffolding. After PR #200 populated the prawductai-contentprompts, this is the cleanest remaining open-queue item before V2 prawduct wraps produce complete output end-to-end. Closes that gap and unblocks future v3.17.0 release cuts being driven through the V2 pipeline rather than manualversion.json+ CHANGELOG edits.How
Bump-level precedence
options.bumpLeveloverride (patch|minor|major) — wins outright if in the allowed setBREAKINGmarker anywhere in[Unreleased]body →major### Added/### Changed/### Removed/### Deprecatedpresent →minor### Fixed/### Security) →patchSingle-transaction discipline + composite-staged-keys
Handler never writes the filesystem; it stages two entries under composite keys:
The Chunk-9
commitstep's_flushStagedWritesis already duck-typed on{primingPath, newContent, changed}— it iteratesObject.entries(staged)and flushes any matching shape, so both composite-keyed entries get written without a dispatch-table edit. This establishes the multi-file write-staging convention for any future step that needs to stage more than one file write.Commit body line
lib/wrap-steps/commit.js:_buildBodyLinesgains a new duck-type matching{oldVersion, newVersion, bumpLevel}— emits- Bumped <old> → <new> (<level>)once per pipeline run (deduped via a localemittedVersionBumpflag since both staged entries carry the metadata). Pinned with a dedupe test (both insertion orders).Never blocks
Per the ADR 0002 step-kind contract ("Optional, never blocks"). Every degraded condition — missing
version.json, malformed JSON, non-semverversionfield, missingCHANGELOG.md, missing[Unreleased]section, empty[Unreleased]body — returns{ok:true, status:'skipped'}withoutput.reason+output.detailfor the wrap drawer to render inline.Idempotent on re-wrap
After a successful bump, the
[Unreleased]body is empty (just the heading + blank line). On the next wrap the handler observes no entries and returns{status:'skipped', output:{reason:'no entries to promote'}}— no double-bump. Pinned with a dedicated test.Banner emoji NOT auto-injected
> 🛟(bug-fix) /> 🚀(feature) leading banners stay a curated operator decision per the project's CHANGELOG convention. The handler promotes[Unreleased]body byte-for-byte under the new dated heading; banner insertion is out of scope and tracked separately in the build plan.Output for the wrap drawer
output.from,output.to,output.bumpLevel,output.detail(pre-formatted"3.16.2 → 3.17.0 (minor)"string). Matches the existingpublic/wrap-drawer.js:deriveDetail'sversion-bumpcase (if (output.from && output.to) return …) — no drawer-side change required.Test plan
39 net new tests in
test/wrap-pipeline.test.js:_parseSemver(5): canonical x.y.z, all invalid forms (novprefix, no pre-release, partial, non-string)._bumpSemver(5): patch / minor / major arithmetic + reset behavior + invalid input._parseUnreleased(6): finds + categorizes, stops at next## [heading, hasEntries true/false, ok:false on missing, nullish-safe, dedupes duplicate subsections._decideBumpLevel(6): override wins, unknown-override fallthrough, BREAKING marker, minor-trigger subsections, patch-only path, mixed-subsection vote, default to patch._promoteUnreleased(4): promote shape pin, no auto-banner injection, no-op when [Unreleased] missing, multi-paragraph + nested-bullet preservation.kindsarray now empty + sentinel pin so a future stub-introducing chunk has to update the list.Full suite: 2324 / 2324 pass on this branch (was 2285 on
mainpost-PR-Populate prawduct ai-content prompts (open-queue #2 post-#139) #200; net +39).Migration note
This is template-and-handler-only; no schema changes, no template structure changes. Existing prawduct projects pick up the new handler automatically on next TC server restart (no methodology template cache invalidation needed since the step's
kindandidare unchanged — only the implementation behind the kind moved from no-op to real). Projects that don't have aCHANGELOG.mdorversion.jsoncontinue to see the step reportskippedcleanly; no breaking behavior.Files touched
lib/wrap-steps/version-bump.js— replaces no-op stub with real handlerlib/wrap-steps/commit.js— adds version-bump duck-type to_buildBodyLinestest/wrap-pipeline.test.js— adds 41 new tests + adjusts 2 existing tests for the stub-→-real transition (Chunk-3 stub-block sentinel + monkey-patch list in the "no-op stubs end-to-end" test)docs/adr/0002-wrap-pipeline-contract.md— Status line extension markerCHANGELOG.md—[Unreleased]### AddedentryOut of scope
> 🛟/> 🚀release banners (curated decision per repo convention).gh release create) — separate step per CLAUDE.md "Releases & Versioning".step.prompt(cross-cutting concern shared with PR Populate prawduct ai-content prompts (open-queue #2 post-#139) #200's migration note).The full build plan lives at
.claude/plans/version-bump-handler.md(project-local, gitignored per CLAUDE.md plan convention).