fix(generate): place release changelog entries deterministically#129
fix(generate): place release changelog entries deterministically#129
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #129 +/- ##
==========================================
- Coverage 94.86% 94.70% -0.17%
==========================================
Files 26 26
Lines 4888 5004 +116
Branches 4888 5004 +116
==========================================
+ Hits 4637 4739 +102
- Misses 159 173 +14
Partials 92 92 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request refactors the changelog update process by replacing the LLM-based editing logic with a deterministic local implementation. It introduces upsert_version_changelog and associated helper functions to handle version header formatting and section placement, specifically ensuring new versions are inserted after the [Unreleased] section. Feedback focuses on improving the robustness of the insertion logic when the [Unreleased] anchor is missing and ensuring that the upsert calculation is performed during dry runs to catch potential errors early.
| let Some(unreleased) = find_unreleased_section(&without_existing)? else { | ||
| return Ok(join_changelog_head_tail(§ion, &without_existing)); | ||
| }; |
There was a problem hiding this comment.
If the [Unreleased] section is missing in an existing changelog, the current logic prepends the new version section to the very beginning of the file using join_changelog_head_tail(§ion, &without_existing). This is problematic if the file contains a title (e.g., # Changelog) or introductory text, as the new version will be placed before them.
Consider either erroring out (as is done for HEAD updates) or appending to the end of the file as a safer fallback if the anchor section is missing.
| if !dry_run { | ||
| let full = join_changelog_head_tail(&updated_head, tail); | ||
| let content = format!("{}\n", full.trim_end()); | ||
| let content = | ||
| upsert_version_changelog(&existing, &ctx.tag, &date, &release_url, &parsed.changelog)?; | ||
| xx::file::write(&changelog_path, content)?; | ||
| info!("wrote {}", changelog_path.display()); | ||
| } |
There was a problem hiding this comment.
The upsert_version_changelog logic is currently skipped entirely during a dry run. It is better to perform the upsert calculation regardless of the dry_run flag to ensure that any potential errors (such as multiple [Unreleased] sections) are caught and reported to the user before an actual run. This also maintains consistency with how the HEAD update is handled earlier in this function.
| if !dry_run { | |
| let full = join_changelog_head_tail(&updated_head, tail); | |
| let content = format!("{}\n", full.trim_end()); | |
| let content = | |
| upsert_version_changelog(&existing, &ctx.tag, &date, &release_url, &parsed.changelog)?; | |
| xx::file::write(&changelog_path, content)?; | |
| info!("wrote {}", changelog_path.display()); | |
| } | |
| let content = | |
| upsert_version_changelog(&existing, &ctx.tag, &date, &release_url, &parsed.changelog)?; | |
| if !dry_run { | |
| xx::file::write(&changelog_path, content)?; | |
| info!("wrote {}", changelog_path.display()); | |
| } |
Greptile SummaryThis PR replaces the LLM-driven Confidence Score: 5/5Safe to merge — no P0/P1 issues; only minor P2 style findings that don't affect correctness. All changed logic is unit-tested, error paths are validated, file writes are guarded behind error propagation, and the [Unreleased] insertion logic is correct. Only two P2 issues (stale doc, redundant strip) which don't affect runtime behaviour. No files require special attention. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[update_changelog called] --> B{is_unreleased_head?}
B -- yes --> C[replace_unreleased_section]
B -- no --> D[read CHANGELOG.md\nor use default skeleton]
D --> E[upsert_version_changelog]
E --> F[repair_changelog_section_boundaries]
F --> G[format_version_header\ndetect style from existing]
G --> H[remove_version_section\nstrip old entry if present]
H --> I[find_unreleased_section]
I -- None --> J[return Err: missing Unreleased section]
I -- Some --> K[splice: before + section + after]
K --> L{dry_run?}
L -- no --> M[write CHANGELOG.md]
L -- yes --> N[return Ok]
Reviews (2): Last reviewed commit: "fix(generate): validate release changelo..." | Re-trigger Greptile |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1b8e3d8. Configure here.
A patch release that takes the LLM out of the loop when editing `CHANGELOG.md` for tagged releases, fixes a section-boundary bug that could mash release sections together, and adds musl Linux binaries to the release matrix. ## Fixed - **Place tagged release entries deterministically** — `communique generate vX.Y.Z --changelog` no longer asks the model to rewrite `CHANGELOG.md`. Instead, it parses the existing file, removes any prior section for the target version, and inserts the new section immediately after `## [Unreleased]` / `## Unreleased`. The new `format_version_header` helper infers the existing header style (linked `## [1.1.0](url) - date`, plain `## 1.1.0`, bracketed `## [1.1.0]`, with or without dates) and matches it for the new entry, while the generated changelog body is used verbatim. If the file is missing an Unreleased anchor, the command now fails with an actionable error rather than guessing a placement, and duplicate Unreleased sections are caught even in `--dry-run`. ([#129](#129)) (@jdx) - **Preserve changelog section boundaries** — Fixed a bug where `--changelog` updates could concatenate a preserved release section onto the previous bullet, producing lines like `...))## [1.0.4]`. Only version-shaped `##` headings are now treated as release boundaries (category headings like `## Added` / `## Fixed` are no longer mistaken for them), regenerated heads and preserved tails are joined with an explicit blank line, and any pre-existing joined boundaries are repaired before writing. The malformed joins already present in this repo's own `CHANGELOG.md` were also cleaned up. ([#126](#126)) (@jdx) ## Added - **musl Linux release assets** — Future GitHub releases now include `communique-x86_64-unknown-linux-musl.tar.gz` and `communique-aarch64-unknown-linux-musl.tar.gz` alongside the existing GNU Linux, macOS, and Windows builds. ([#127](#127)) (@jdx) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk: this PR only updates release metadata (changelog, crate/package versions, and generated CLI docs) with no functional code changes. > > **Overview** > Adds a new `v1.1.2` entry to `CHANGELOG.md` describing fixes to deterministic `--changelog` updates and section-boundary preservation, plus added musl Linux release artifacts. > > Bumps the project version from `1.1.1` → `1.1.2` in `Cargo.toml`/`Cargo.lock`, the usage spec (`communique.usage.kdl`), and generated CLI documentation (`docs/cli`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 98c3f94. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->

Summary
[Unreleased][Unreleased]anchor instead of guessing a placementValidation
cargo fmt --checkcargo test generatecargo clippy -- -D warningsNote
Medium Risk
Replaces an LLM-driven
CHANGELOG.mdrewrite with deterministic parsing/insertion logic; risk is moderate because it changes how release sections are located/removed/inserted and could mis-handle unusual changelog formats.Overview
Stops using the LLM to rewrite
CHANGELOG.mdfor tagged releases, and instead deterministically upserts the target version section: remove any existing section for that version and insert the new one immediately after## [Unreleased]/## Unreleased.Adds header-style inference via
format_version_header(preserving bracket/link/date conventions), plus new helpers to remove/reinsert sections and to validate/repair changelog structure (including erroring on missing or duplicate Unreleased sections). Expands tests to cover ordering, validation in--dry-run, and header formatting.Reviewed by Cursor Bugbot for commit 406418c. Bugbot is set up for automated code reviews on this repo. Configure here.