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
15 changes: 10 additions & 5 deletions .github/homebrew/git-aicommit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# ruby -c .github/homebrew/git-aicommit.rb
# brew style .github/homebrew/git-aicommit.rb
class GitAicommit < Formula
desc "Generate git commit messages from staged diffs using Claude"
desc "Generate git commit messages using Codex or Claude"
homepage "https://github.com/getkono/git-aicommit"
version "__VERSION__"
license any_of: ["MIT", "Apache-2.0"]
Expand Down Expand Up @@ -38,12 +38,17 @@ def install

def caveats
<<~EOS
git-aicommit shells out to two tools this formula does NOT install:
* git (brew install git, or use your system git)
* claude the Claude Code CLI, which is not available in Homebrew.
Install and authenticate it per:
git-aicommit shells out to git and one supported agent CLI. This formula
does NOT install them:
* git brew install git, or use your system git
* codex install and authenticate OpenAI Codex:
https://developers.openai.com/codex/cli/
* claude install and authenticate Claude Code:
https://docs.claude.com/en/docs/claude-code

Only one agent CLI is required. When both are installed, Codex is used by
default; pass --agent claude to override it.

Invoke it as a git subcommand once installed: git aicommit
EOS
end
Expand Down
21 changes: 19 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ homepage = "https://github.com/getkono/git-aicommit"

[workspace.dependencies]
aicommit-core = { version = "0.2.0", path = "crates/aicommit-core" }
agent-text = { version = "0.1.0", default-features = false }
agent-text = { version = "0.1.1", default-features = false }
clap = { version = "4", features = ["derive"] }
indicatif = "0.17"
thiserror = "2"
tokio = { version = "1", features = ["macros", "rt"] }
which = "8"

# Must live at the workspace root: a `[profile.*]` table in a member manifest is
# ignored (with only a warning), which would silently drop these from releases.
Expand Down
37 changes: 27 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# git-aicommit

A tiny Rust CLI that drafts a commit message from your staged changes using
[OpenAI Codex](https://developers.openai.com/codex/cli/) or
[Claude Code](https://docs.claude.com/en/docs/claude-code), then opens
`git commit` with the message pre-filled so you can review, edit, or abort.
The model is picked automatically from the diff size — Haiku for everyday
commits, Sonnet for large ones — or pinned with `--model`.
When both agent CLIs are installed, Codex is preferred; use `--agent` to choose
explicitly. The model is picked automatically from the diff size or pinned with
`--model`.

## How it works

1. Parses standard `git commit` flags (see [Supported flags](#supported-flags)) to decide what to diff and how to prompt.
2. Checks you're in a git repo and that there's something to commit.
3. For plain and `--amend` commits, runs the `pre-commit` hook as an early check (`git hook run` on Git ≥ 2.36, executing the hook script directly on older git) — if it fails, the tool aborts before making any API call. (For `-a` and pathspec commits the staged index isn't what gets committed, so hooks run at commit time instead.)
4. Picks a model from the diff size (Haiku by default; Sonnet with higher effort for large or many-file diffs, announced on its own line) unless you pin one with `--model`, then feeds the relevant diff to `claude -p` over stdin.
4. Selects Codex first when available, otherwise Claude, unless you pin one with `--agent`. It picks a provider-specific model from the diff size (Luna/Terra for Codex, Haiku/Sonnet for Claude) unless you pin one with `--model`, then sends the relevant diff to that local agent CLI.
5. Cleans up the response and runs `git commit -e -m "<message>" …`, inheriting your terminal so `$EDITOR` opens normally.

Large diffs are truncated at 60KB to keep the prompt sane.
Expand All @@ -20,8 +22,10 @@ Large diffs are truncated at 60KB to keep the prompt sane.

- Rust (stable)
- `git` (any reasonably recent version; the pre-commit pre-check uses `git hook run` on ≥ 2.36 and falls back to running the hook script directly on older git)
- [`claude`](https://docs.claude.com/en/docs/claude-code) CLI, installed and authenticated
(tested with Claude Code 2.x; requires a version that supports `--output-format json` and `--disable-slash-commands`)
- At least one supported agent CLI installed and authenticated:
- [`codex`](https://developers.openai.com/codex/cli/) 0.146.0 or newer
- [`claude`](https://docs.claude.com/en/docs/claude-code) 2.x with
`--output-format json` and `--disable-slash-commands` support

## Install

Expand All @@ -31,8 +35,9 @@ Large diffs are truncated at 60KB to keep the prompt sane.
brew install getkono/tap/git-aicommit
```

`git-aicommit` calls the [`claude`](https://docs.claude.com/en/docs/claude-code) CLI (not in Homebrew) and
`git` at runtime — see [Requirements](#requirements). `brew info getkono/tap/git-aicommit` repeats this.
`git-aicommit` calls `git` and one of the supported agent CLIs at runtime; the
formula does not install those tools. See [Requirements](#requirements).
`brew info getkono/tap/git-aicommit` repeats this.

**From crates.io** (requires Rust):

Expand Down Expand Up @@ -67,7 +72,13 @@ git aicommit

Your editor opens with the AI-generated message. Save to commit, or quit with an empty message to abort.

`git aicommit` aims to be a drop-in for `git commit`: it understands the common flags and forwards anything else straight through. By default it auto-selects the model from the diff size (`haiku`, or `sonnet` with higher effort for large/many-file diffs, announced when it isn't haiku); pass `--model <name>` to pin one (before any git flags). Run `git aicommit --help` for a summary, or `git aicommit --version` to print the version, build metadata, and binary path.
`git aicommit` aims to be a drop-in for `git commit`: it understands the common
flags and forwards anything else straight through. It uses Codex when `codex`
is on `PATH`, otherwise Claude when `claude` is available. Pass
`--agent codex|claude` to override that choice. By default it selects
`gpt-5.6-luna` or `gpt-5.6-terra` for Codex and `haiku` or `sonnet` for Claude,
escalating on large or many-file diffs. Pass `--model <name>` to pin a model for
the selected agent. `--agent` and `--model` must come before any Git flags.

### Supported flags

Expand All @@ -83,6 +94,8 @@ git aicommit --amend # regenerate the message from the previous messag
**Steer the AI:**

```sh
git aicommit --agent claude # override Codex-first detection
git aicommit --agent codex --model gpt-5.6-terra # pin both agent and model
git aicommit -m "call out the perf fix" # an instruction, NOT a literal message (repeatable)
git aicommit -t .gitmessage # make the output follow a template file
```
Expand Down Expand Up @@ -137,7 +150,9 @@ git aicommit -- --weird-filename
- The prompt asks for Conventional Commits style (`feat:`, `fix:`, etc.), imperative subject ≤72 chars, optional body explaining the *why*.
- When a commit bundles several unrelated changes, the message leads with the primary one in the subject and itemizes the rest as body bullets. A `git diff --stat` inventory of every changed file is sent alongside the diff so small or buried changes aren't dropped.
- By default the editor opens so you can review before committing; quit with an empty message to abort. `-y`/`--yes` (or `--no-edit`) commits the generated message directly, and `--dry-run` never commits.
- No API key handling here; auth is delegated entirely to the `claude` CLI.
- No API key handling here; auth is delegated entirely to the selected local
`codex` or `claude` CLI. If neither executable is installed, generation exits
with an actionable error.

## Using it from your own application

Expand All @@ -147,7 +162,9 @@ editor wanting an AI-drafted message for its commit box, say. Hand it a diff and
it hands you a string; what you do with that string is yours. It never invokes
`git`, and it accepts any async
[`agent_text::Agent`](https://docs.rs/agent-text/latest/agent_text/trait.Agent.html).
The application chooses the concrete adapter; this CLI uses
The application chooses the concrete adapter; this CLI selects between
[`agent_text::Codex`](https://docs.rs/agent-text/latest/agent_text/struct.Codex.html)
and
[`agent_text::ClaudeCode`](https://docs.rs/agent-text/latest/agent_text/struct.ClaudeCode.html).

```rust,no_run
Expand Down
4 changes: 4 additions & 0 deletions crates/aicommit-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ println!("{}", generated.message);
# }
```

`auto_select` preserves the default Claude model tiers. Frontends selecting a
different provider can call `auto_select_with_models` with model names that
provider understands while reusing the same diff thresholds and effort policy.

See `examples/editor.rs` for a runnable version.

`CommitRequest` also carries a template, steering instructions, a changed-file
Expand Down
2 changes: 1 addition & 1 deletion crates/aicommit-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub use agent_text::{Agent, GenerationRequest, Usage};
pub use error::{CoreError, Result};
pub use model::{
ESCALATE_DIFF_BYTES, ESCALATE_FILE_COUNT, Effort, LARGE_DIFF_MODEL, ModelChoice,
SMALL_DIFF_MODEL, auto_select,
SMALL_DIFF_MODEL, auto_select, auto_select_with_models,
};
pub use prompt::{
DEFAULT_MAX_DIFF_BYTES, build_prompt, build_prompt_with_max, build_system_prompt, truncate_diff,
Expand Down
34 changes: 31 additions & 3 deletions crates/aicommit-core/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,27 @@ pub const LARGE_DIFF_MODEL: &str = "sonnet";
/// secondary changes aren't lost in the summary. Pass the *full* diff length,
/// before any truncation.
pub fn auto_select(diff_len: usize, file_count: usize) -> ModelChoice {
auto_select_with_models(diff_len, file_count, SMALL_DIFF_MODEL, LARGE_DIFF_MODEL)
}

/// Choose between caller-supplied small- and large-diff models.
///
/// This keeps the selection thresholds and effort policy provider-neutral while
/// allowing a frontend to supply model names understood by its chosen agent.
pub fn auto_select_with_models(
diff_len: usize,
file_count: usize,
small_model: &str,
large_model: &str,
) -> ModelChoice {
if diff_len >= ESCALATE_DIFF_BYTES || file_count >= ESCALATE_FILE_COUNT {
ModelChoice {
model: LARGE_DIFF_MODEL.to_string(),
model: large_model.to_string(),
effort: Some(Effort::Medium),
}
} else {
ModelChoice {
model: SMALL_DIFF_MODEL.to_string(),
model: small_model.to_string(),
effort: None,
}
}
Expand Down Expand Up @@ -78,9 +91,24 @@ mod tests {
assert_eq!(auto_select(100, ESCALATE_FILE_COUNT), large());
}

#[test]
fn auto_select_accepts_provider_model_names() {
assert_eq!(
auto_select_with_models(0, 1, "gpt-small", "gpt-large"),
ModelChoice::new("gpt-small")
);
assert_eq!(
auto_select_with_models(ESCALATE_DIFF_BYTES, 1, "gpt-small", "gpt-large",),
ModelChoice {
model: "gpt-large".to_string(),
effort: Some(Effort::Medium),
}
);
}

#[test]
fn effort_wire_form() {
// Agent adapters pass this through; "medium" is what Claude expects.
// Bundled agent adapters pass these stable wire names through.
assert_eq!(Effort::Medium.as_str(), "medium");
assert_eq!(Effort::Low.to_string(), "low");
assert_eq!(Effort::High.to_string(), "high");
Expand Down
7 changes: 4 additions & 3 deletions crates/git-aicommit/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
[package]
name = "git-aicommit"
version = "1.4.0"
description = "Generate git commit messages from staged diffs using Claude."
description = "Generate git commit messages from staged diffs using local AI agent CLIs."
readme = "../../README.md"
keywords = ["git", "commit", "ai", "claude", "cli"]
keywords = ["git", "commit", "ai", "claude", "codex"]
categories = ["command-line-utilities", "development-tools"]
edition.workspace = true
rust-version.workspace = true
Expand All @@ -14,8 +14,9 @@ homepage.workspace = true

[dependencies]
aicommit-core.workspace = true
agent-text = { workspace = true, features = ["claude-code"] }
agent-text = { workspace = true, features = ["claude-code", "codex"] }
clap.workspace = true
indicatif.workspace = true
thiserror.workspace = true
tokio.workspace = true
which.workspace = true
Loading