Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ and the project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v

## [Unreleased]

### Added
- **`commit` command — generate a commit message and commit (ADR-0019).**
`commitbrief commit` reads the staged diff, asks the configured provider for
a commit message, shows it for confirmation, and — on Yes (the default) or
`--yes` — runs `git commit`. This is the first path where the tool writes to
git; everything else stays read-only (PRD NG4 is rescoped to the review
path). Highlights:
- `--type` / `-t` picks the format: `plain` (default), `conventional`,
`conventional+body`, `gitmoji`, `subject+body`.
- `--generate` / `-g <N>` offers N alternatives in an arrow-key selector
(capped at 10); a single provider call produces all N.
- `--provider` / `--model` / `--cli` select the backend exactly as for a
review; messages are always written in English regardless of `--lang`.
- The pre-send `.commitbrief/**` guard, secret scan, and cost preflight all
run on the staged diff before the call; the suggestion is cached.
- With no staged changes it errors clearly; a non-TTY run without `--yes`
errors (it cannot confirm). `--yes` commits the first suggestion.
- New config keys `commit.type` and `commit.generate` set the defaults
(precedence: flag > config > built-in). This complements the existing
`--suggest-commit` review flag, which is unchanged.

### Changed
- **`--version` no longer prints the splash logo.** `commitbrief --version`
now emits only the single `commitbrief vX.Y.Z (commit <sha>, built <iso-ts>)`
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ commitbrief --unstaged --file app/Http/Controllers/API.php --file routes/web.php
commitbrief --unstaged --dir database/seeder --dir app/Models
commitbrief diff HEAD~3 HEAD --dir docs

# Commit message (writes to git, with confirmation)
commitbrief commit # suggest a message for the staged diff, then commit
commitbrief commit --type conventional # pick a format (-t); see "commitbrief commit" below
commitbrief commit --generate 3 # offer 3 alternatives to choose from (-g)
commitbrief commit --yes # commit the first suggestion non-interactively

# Setup and rules
commitbrief setup [--local] # provider + API key wizard
commitbrief providers list|use|test # switch active provider without re-running setup
Expand Down Expand Up @@ -236,6 +242,38 @@ the diff), `--no-cost-check` (skip cost preflight),
then exit — no provider call, no cost; honours `--output`), `--color`. See
`commitbrief --help`.

### `commitbrief commit`

Generate a commit message from the **staged** diff and, after you confirm,
run `git commit`. This is the only command that writes to git — every
review path is read-only.

```sh
commitbrief commit # suggest one message, confirm (default Yes), commit
commitbrief commit -t conventional+body # conventional subject + a generated body
commitbrief commit -g 4 # pick from 4 alternatives
commitbrief commit --provider openai --model gpt-5.4-mini
commitbrief commit --yes # CI/non-interactive: commit the first suggestion
```

- **`--type` / `-t`** — message format: `plain` (default), `conventional`,
`conventional+body`, `gitmoji`, `subject+body`.
- **`--generate` / `-g <N>`** — produce N alternatives (1–10) and choose one
in an arrow-key selector. A single provider call generates all N.
- **`--provider` / `--model` / `--cli`** — select the backend, same as a
review. Messages are always written in English regardless of `--lang`.
- Defaults come from the `commit.type` and `commit.generate` config keys when
the flags are omitted (precedence: flag > config > built-in).
- The pre-send `.commitbrief/**` guard, secret scan, and cost preflight run on
the staged diff before the call; the suggestion is cached.
- With **nothing staged** it errors (stage with `git add` first). On a
**non-TTY** without `--yes` it errors, because it cannot show the confirm or
selector. `--yes` commits the first suggestion (it does **not** bypass the
secret scan or cost preflight).

> The tool never auto-stages and never edits files — it only runs `git commit`
> on changes you already staged, and only after you say Yes.

### `--with-context` (CLI providers only)

By default a review sees only the diff. With `--with-context`, a
Expand Down Expand Up @@ -402,6 +440,9 @@ guard:
token_preflight: false # opt-in: confirm/abort when the prompt overflows the model's context window
command:
default: "" # args applied to a bare `commitbrief`; empty = `--staged`
commit:
type: plain # default --type for `commitbrief commit` (plain|conventional|conventional+body|gitmoji|subject+body)
generate: 1 # default --generate (number of message alternatives)
```

### Default command (`command.default`)
Expand Down
12 changes: 12 additions & 0 deletions internal/cache/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ type ComputeArgs struct {
// keeping diff-only keys byte-identical to pre-ADR-0017 entries — no
// mass cache invalidation on upgrade.
WithContext bool

// Mode namespaces non-review cache entries (e.g. "commit" for the
// commit-message generation, ADR-0019). The commit system prompt
// already differs from the review prompt, so collision is unlikely;
// the explicit marker makes the separation impossible. Folded in only
// when non-empty, so review keys stay byte-identical to before.
Mode string
}

// Compute returns the deterministic SHA-256 key (lowercase hex) for the
Expand All @@ -46,5 +53,10 @@ func Compute(args ComputeArgs) string {
if args.WithContext {
h.Write([]byte(":ctx"))
}
// Append the mode marker only when set, so review keys stay byte-
// identical to before ADR-0019 (see Mode doc).
if args.Mode != "" {
h.Write([]byte(":mode:" + args.Mode))
}
return hex.EncodeToString(h.Sum(nil))
}
2 changes: 1 addition & 1 deletion internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

func TestRootCommandHasSubcommands(t *testing.T) {
root := newRootCmd()
want := []string{"cache", "compress", "config", "diff", "doctor", "dry-run", "init", "install-hook", "list", "providers", "remote", "setup"}
want := []string{"cache", "commit", "compress", "config", "diff", "doctor", "dry-run", "init", "install-hook", "list", "providers", "remote", "setup"}
got := []string{}
for _, c := range root.Commands() {
// cobra adds `help` and `completion` automatically; filter to ours.
Expand Down
Loading
Loading