Skip to content

rtb-cli changelog subcommand

Status: APPROVED — GTB-parity gap from the v0.22.0 audit (Phase 2, §B). Feature::Changelog exists in rtb-app (features.rs:36, "Structured release-notes display") but no command is registered behind it — a dead flag. This backs it. Also resolves the feature-has-backing-command edge in the MCP-gating + feature-toggle specs (see cross-impact). No open design decisions.

Distinction (important): this is not changelog generation — that is release-plz's job (per CLAUDE.md §Release; per-crate CHANGELOG.md, do-not-hand-edit), and remains a non-goal. This is the runtime end-user command mytool changelog that renders the tool's own already-generated release notes to the user, mirroring GTB's pkg/cmd/changelog.

1. Surface

mytool changelog [--latest] [--version <X.Y.Z>] [--since <X.Y.Z>]
  • no flags → render the full changelog.
  • --latest → only the newest version's entry.
  • --version <v> → only that version's entry (error if absent).
  • --since <v> → all entries newer than v (exclusive).
  • --latest/--version/--since are mutually exclusive (clap group).

Gated by Feature::Changelogopt-in (not in Feature::defaults()), consistent with RTB's safer-by-default posture.

2. Source of the changelog

The rendered content is the tool's own CHANGELOG.md, resolved through App::assets (the rtb-assets overlay filesystem — embedded default + user override), so a downstream tool embeds its release notes at build time and may override at runtime. Absent asset → a clean miette-diagnostic ("this tool ships no changelog").

3. Mechanism

  • New builtin ChangelogCmd in crates/rtb-cli/src/builtins.rs, spec()feature: Some(Feature::Changelog), registered via #[distributed_slice(BUILTIN_COMMANDS)] like version/doctor.
  • Parse CHANGELOG.md into (version, body) sections by the Keep-a-Changelog / release-plz heading convention (## [X.Y.Z] - date). Version compare via semver (already in the tree via rtb-update).
  • Render the selected section(s) with termimad (already a rtb-docs dependency) for styled terminal markdown; fall back to plain text when not a TTY (std::io::IsTerminal).
  • Filtering (--latest/--version/--since) operates on the parsed section list before rendering.

4. Scaffolder note

The cli preset should ship a starter CHANGELOG.md asset so a scaffolded tool's changelog command works once the feature is enabled. (Pairs with the feature-toggle spec: enabling Changelog now yields a working command, not a dead flag.)

5. Testing (TDD, ≥90%)

  • Unit: section parser (well-formed, missing-heading, out-of-order versions); --version selects the right body; --since is exclusive + ordered; --latest picks the max semver; mutually-exclusive flags rejected by clap; absent asset → typed diagnostic.
  • E2E (assert_cmd + insta): a tool with an embedded fixture CHANGELOG.md renders full / --latest / --version / --since; snapshots of stdout; exit codes. Feature off → command absent from --help.

6. Cross-impact

  • 2026-06-23-scaffolder-feature-toggle.md / -mcp-command-exposure-gating.mdFeature::Changelog was the lone counterexample of a toggleable/gateable feature with no registered command. Implementing this command removes the edge case (the amendments to those specs still add the general "feature-has-backing-command" invariant as a guard against regressions).

7. Out of scope

  • Changelog generation (release-plz owns it — non-goal).
  • Aggregating per-crate workspace changelogs (the tool renders its own single CHANGELOG.md; a multi-crate downstream curates what it embeds).