From 45aedc2ce92be6d11a47f99ce50219d8f9f95832 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 14 Dec 2025 14:51:26 -0700 Subject: [PATCH] feat(cli): require description for -B flag The -B flag now accepts a string argument containing the breaking change description instead of being a boolean flag. This description is formatted and inserted as a BREAKING CHANGE: footer between the body and git trailers, following the Conventional Commits specification. The flag still adds ! to the subject line. When used with heredoc syntax, multi-line breaking change descriptions are supported. Also re-added the -a/--amend flag that was present in main but missing from this feature branch. Implements: bug-e75a648 Assisted-by: Claude Sonnet 4.5 via Crush --- AGENTS.md | 33 ++++++++++++++++++++++++++++----- main.go | 32 ++++++++++++++++++++++---------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 431ae443511d32fab5167e31fa13460e386686d3..6543a61148593ba1700b5abbb011e494b7dfb47e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,14 +104,37 @@ Assisted-by: This is a very long value, with spaces and - `-t` / `--type`: Commit type (required) - e.g., `feat`, `fix`, `refactor` - `-m` / `--message`: Commit message (required) - the description after the colon - `-s` / `--scope`: Commit scope (optional) - goes in parentheses after type -- `-B` / `--breaking`: Boolean flag for breaking changes (adds `!` to subject) +- `-B` / `--breaking`: String flag for breaking change description (optional) - adds `!` to subject AND creates `BREAKING CHANGE:` footer - `-b` / `--body`: String flag for commit body text (can use heredoc for multiline) - `-T` / `--trailer`: Repeatable flag accepting full trailer strings in `Key: value` format (not separate key/value args) -- `-a` / `--add`: Boolean flag to stage all modified files before committing (optional) -- `--amend`: Boolean flag to amend the previous commit instead of creating a new one (optional) +- `-a` / `--amend`: Boolean flag to amend the previous commit instead of creating a new one -Trailer format detail: Each `-T` flag takes a complete trailer string like `-T "Assisted-by: GLM 4.6 via Crush"`, NOT separate key and value arguments. +Breaking change detail: The `-B` flag value becomes the description in a `BREAKING CHANGE:` footer (with space, per Conventional Commits spec). This footer is distinct from git trailers and allows spaces in the key. It's inserted between the body and trailers. + +Trailer format detail: Each `-T` flag takes a complete trailer string like `-T "Assisted-by: Claude Sonnet 4.5 via Crush"`, NOT separate key and value arguments. ### Final Output -The formatted commit message must be piped to `git commit -F -` to read from stdin. When the `--amend` flag is used, it pipes to `git commit --amend -F -` instead. +The formatted commit message structure: + +1. Subject: `type(scope)!: message` (or `type!: message` if no scope) +2. Blank line +3. Body (if `-b` provided) +4. Blank line (if body or breaking change present) +5. `BREAKING CHANGE: description` footer (if `-B` provided) +6. Blank line (if trailers present) +7. Trailers (if `-T` provided) + +Example with all components: +``` +feat!: restructure config + +Improves readability and supports comments + +BREAKING CHANGE: Configuration format changed from JSON to TOML. +Migrate by running: ./migrate-config.sh + +Assisted-by: Claude Sonnet 4.5 via Crush +``` + +The formatted commit message must be piped to `git commit -F -` to read from stdin. When the `-a`/`--amend` flag is used, it pipes to `git commit --amend -F -` instead. diff --git a/main.go b/main.go index fb1c037d1880accb075ff0ad1b7d5e72c54eec4e..2fc88d31c70a6660820db3f4168837562b54a5f5 100644 --- a/main.go +++ b/main.go @@ -37,15 +37,16 @@ body wrapping, and trailer formatting.`, # With Assisted-by formatted-commit -t feat -m "do a thing" -T "Assisted-by: GLM 4.6 via Crush" -# Breaking change with longer body -formatted-commit -t feat -m "do a thing that borks a thing" -B -b "$(cat <<'EOF' -Multi-line -- Body -- Here - -This is what borked because of new shiny, this is how migrate +# Breaking change with description +formatted-commit -t feat -m "remove deprecated API" \ + -B "The old /v1/users endpoint is removed. Use /v2/users instead." + +# Breaking change with multi-line description using heredoc +formatted-commit -t feat -m "restructure config format" -B "$(cat <<'EOF' +Configuration format changed from JSON to TOML. +Migrate by running: ./migrate-config.sh EOF -)" +)" -b "Improves readability and supports comments" # Including scope for more precise changes formatted-commit -t refactor -s "web/git-bug" -m "fancy shmancy" \ @@ -79,6 +80,16 @@ formatted-commit upgrade -a commitMsg.WriteString(formattedBody) } + if breakingChange != "" { + formattedBreaking, err := formatBody(breakingChange) + if err != nil { + return fmt.Errorf("failed to format breaking change: %w", err) + } + commitMsg.WriteString("\n\n") + commitMsg.WriteString("BREAKING CHANGE: ") + commitMsg.WriteString(formattedBreaking) + } + if len(trailers) > 0 { trailersBlock, err := buildTrailersBlock(trailers) if err != nil { @@ -96,6 +107,7 @@ formatted-commit upgrade -a gitArgs = append(gitArgs, "--amend") } gitArgs = append(gitArgs, "-F", "-") + gitCmd := exec.Command("git", gitArgs...) gitCmd.Stdout = os.Stdout gitCmd.Stderr = os.Stderr @@ -143,7 +155,7 @@ func init() { } } -func buildAndValidateSubject(commitType, scope, message string, breaking bool) (string, error) { +func buildAndValidateSubject(commitType, scope, message string, breaking string) (string, error) { var subject strings.Builder subject.WriteString(commitType) @@ -154,7 +166,7 @@ func buildAndValidateSubject(commitType, scope, message string, breaking bool) ( subject.WriteString(")") } - if breaking { + if breaking != "" { subject.WriteString("!") }