commitcraft
AI commit messages that follow your repo's conventions. commitcraft reads your staged diff, does the heavy lifting deterministically (filtering lockfiles and binaries, ranking hunks, fitting a token budget, learning your history's style) and then asks Claude Haiku 5.5 for one small structured answer, which it validates and formats locally into a Conventional Commits message.
Features
- Parses
git diff --stagedinto files and hunks; lockfiles, generated and binary files become one-line summaries (configurable globs). - Ranks hunks by information (new functions/classes, signature changes, deleted public symbols; Python, Go, Rust, JS/TS and a generic fallback) and fits them into a token budget with
… N more lineselisions. - Infers scope candidates from paths (monorepo
packages/<name>, top-level module). - Learns style from the last 50 commits: conventional or not, common types and scopes, capitalization, ticket ids from the branch name.
- Structured JSON output (type, scope, subject, body, breaking, footer), then local validation: 72-char header, wrapped body,
BREAKING CHANGE:footer and!, past-tense subjects auto-fixed to imperative ("added" becomes "add"). -cto commit through your editor,--hook installfor aprepare-commit-msghook,--alternatives N,--dry-run,--lang,--max-diff-tokens.- Responses cached on disk by hash of (model, prompt);
--verboseshows tokens and cost.
Install
uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
export ANTHROPIC_API_KEY=sk-ant-... # or: ant auth login
Credentials come only from the environment. Without them, commitcraft prints setup instructions; --dry-run never needs a key.
Quickstart
git add -p
commitcraft # print a message
commitcraft -c # edit in $EDITOR, then commit
commitcraft -n 3 # three alternatives
commitcraft --hook install
Usage
commitcraft [-c] [-n 1-5] [--dry-run] [--lang TEXT] [--max-diff-tokens N]
[--model ID] [--no-cache] [-v] [--hook install|uninstall [--force]]
--dry-run output (real, from examples/demo.sh --dry-run, a throwaway repo with a lockfile change, a new binary and a new function; the fixed system prompt is omitted here):
Number of alternatives: 1
Language: English
Allowed types: feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert
Scope candidates inferred from paths: api
Branch: feature/ABC-123-users
Repository style: 100% of the last 3 commits are Conventional Commits; common types: fix, docs, feat; common scopes: web, api; subjects start lowercase; branch ticket: ABC-123
Staged changes:
### packages/api/server.py (modified, +3 -0)
@@ -1,2 +1,5 @@
def health():
return "ok"
+
+def list_users(limit=50):
+ return []
### Skipped files
package-lock.json: generated/lock file modified (+1 -1), content omitted
packages/web/logo.png: binary file added
--- estimate ---
model: claude-haiku-5-5
estimated input tokens: ~366
hunks omitted: 0, skipped files: 2
Example of a generated message (needs a real key; shape shown from the formatter, not a captured live call):
feat(api): add list_users endpoint
Clients need to page through users instead of fetching them one by one;
limit defaults to 50 to keep responses small.
Refs: ABC-123
Hook
commitcraft --hook install writes .git/hooks/prepare-commit-msg. It fills the message only when it is empty (ignoring comments) and the commit is not a merge, squash, amend, -m or -F/template commit. Failures never block the commit. Installing twice is a no-op; a foreign hook is left alone unless --force (it is backed up to .bak). --hook uninstall removes it.
Configuration
.commitcraft.toml at the repo root (see examples/.commitcraft.toml):
| key | meaning |
|---|---|
ignore |
extra globs summarized instead of sent (added to the defaults) |
types |
allowed Conventional Commits types |
scopes |
allowed scopes; anything else is dropped with a warning |
ticket_pattern |
regex for ticket ids in the branch name; adds a Refs: footer |
max_diff_tokens |
default diff budget (6000) |
Environment: ANTHROPIC_API_KEY, COMMITCRAFT_MODEL, COMMITCRAFT_CACHE_DIR (default ~/.cache/commitcraft).
Cost
Haiku 5.5 costs $0.10 per 1M input and $0.50 per 1M output tokens. A typical commit sends 500 to 3000 input tokens and gets about 150 back, so roughly $0.0001 to $0.0004 per message (the 6000-token cap bounds the worst case near $0.0007). Repeating a command on the same staged diff is free thanks to the cache.
Architecture
| module | role |
|---|---|
diff.py |
unified diff parser, ignore globs, hunk scoring |
budget.py |
filtering, summaries, budget fitting, elision |
scope.py |
scope candidates from paths |
style.py |
history/branch style learning |
config.py |
.commitcraft.toml |
llm.py |
prompt, structured call, disk cache, cost |
message.py |
validation and formatting |
hook.py |
hook install/uninstall/fill rules |
git.py, cli.py |
git plumbing and the click CLI |
See docs/design.md for the algorithms.
Tests
.venv/bin/pytest
All model calls use an injected fake client. Live API calls were not exercised when this was written (no key available).
Limitations
- Token counts are a 4 chars/token estimate.
- Symbol detection is regex based and line oriented.
- The API call needs network and a key; there is no offline fallback.
- Staged diff only;
commit --amendmessages are not regenerated.