kcmt — AI-powered atomic Git staging and committing
kcmt is a small Python library and CLI that helps you:
- Parse and stage changes atomically (file-by-file).
- Generate clear, conventional-commit messages using an LLM.
- Commit safely with validation, retries, real-time progress, and helpful output.
- (Optional) auto-push successful commits to your remote.
It’s designed to be drop-in for your local repositories and integrates with multiple hosted LLM providers (OpenAI, Anthropic, xAI, GitHub Models).
Key features
- Atomic workflow: stage and commit per-file, with deletions handled first.
- LLM-assisted messages: conventional commit style with validation, retries, and auto-fixes.
- Strict failure on repeated invalid/empty LLM responses (no heuristic commit synthesis).
- Prepare phase aborts after 25 per-file failures/timeouts to avoid wasting additional requests.
- Built-in metrics summary (diff, queue, LLM, commit timings) to diagnose performance quickly.
- Connection pooling for provider APIs and batched diff collection for speed on large repos.
- Multi-provider support: OpenAI, Anthropic, xAI, and GitHub Models via a guided wizard.
- Parallel preparation: generate per-file commit messages concurrently with live stats.
- Automatic push to
originon success by default (use--no-auto-pushto disable). - Pricing-aware model selection and a cross-provider pricing board (
--list-models). - Built-in benchmarking across providers/models with a CLI leaderboard and optional JSON/CSV output.
- Small and composable core: use the CLI or import the library directly.
Supported Python versions
- Python 3.12+
Installation
Install from the repository with pip or uv:
-
Latest from GitHub (subdirectory install):
- pip:
- pip install "git+https://github.com/djh00t/arby#subdirectory=kcmt"
- uv:
- uv pip install "git+https://github.com/djh00t/arby#subdirectory=kcmt"
- pip:
-
Local editable install (from the monorepo root):
- pip:
- pip install -e ./kcmt
- uv:
- uv pip install -e ./kcmt
- pip:
Dependencies
- openai>=1.108.1 (shared client for OpenAI-compatible providers)
- httpx>=0.25.0 (Anthropic REST client)
Configuration
Run kcmt --configure inside a repository to launch a colourful wizard that:
- Detects available API keys (
OPENAI_API_KEY,ANTHROPIC_API_KEY,XAI_API_KEY,GITHUB_TOKEN). - Lets you choose the provider, tweak model/endpoint, and pick the env var to use.
- Persists the selection to
~/.config/kcmt/config.json(global, per-user).
Per-provider settings
kcmt maintains a per-provider settings map in ~/.config/kcmt/config.json. Each supported provider has:
name: friendly display nameendpoint: base URL for API callsapi_key_env: environment variable that holds your API key/token (kcmt stores the variable name, not the secret)preferred_model: your saved default model for that provider
Example (~/.config/kcmt/config.json excerpt):
{
"provider": "openai",
"model": "gpt-5-mini-2025-08-07",
"llm_endpoint": "https://api.openai.com/v1",
"api_key_env": "OPENAI_API_KEY",
"auto_push": true,
"providers": {
"openai": {
"name": "OpenAI",
"endpoint": "https://api.openai.com/v1",
"api_key_env": "OPENAI_API_KEY",
"preferred_model": "gpt-5-mini-2025-08-07"
},
"anthropic": {
"name": "Anthropic",
"endpoint": "https://api.anthropic.com",
"api_key_env": "ANTHROPIC_API_KEY",
"preferred_model": null
},
"xai": {
"name": "X.AI",
"endpoint": "https://api.x.ai/v1",
"api_key_env": "XAI_API_KEY",
"preferred_model": null
},
"github": {
"name": "GitHub Models",
"endpoint": "https://models.github.ai/inference",
"api_key_env": "GITHUB_TOKEN",
"preferred_model": null
}
}
}
First-use model selection
- When you first run kcmt against a provider that doesn't yet have a
preferred_model, the CLI shows a pricing-aware model menu and saves your selection underproviders[<provider>].preferred_model. - Non-interactive runs skip the prompt and use the provider default (override with
--model).
Configure API keys for multiple providers
kcmt --configure-alllets you pick which providers to configure and select which environment variable holds each API key. This updates both the legacyprovider_env_overridesand the newproviders[prov].api_key_enventries.kcmt --verify-keysprints a concise table of providers, the env var in use, whether it’s set, and any detected alternatives in your environment.
OpenAI batch mode
- Enable with
--batch(or via the configure menu) to route commit message generation through the OpenAI Batch API. - Pick the batch model interactively; defaults to your OpenAI preferred model. Override on the CLI with
--batch-model. - kcmt shows a spinner while the batch job runs and polls for up to 5 minutes by default (tunable via
--batch-timeoutorKCMT_BATCH_TIMEOUT).
Provider defaults
| Provider | Default model | Default endpoint |
|---|---|---|
| OpenAI | gpt-5-mini-2025-08-07 |
https://api.openai.com/v1 |
| Anthropic | claude-3-5-haiku-latest |
https://api.anthropic.com |
| xAI | grok-code-fast |
https://api.x.ai/v1 |
| GitHub | openai/gpt-4.1-mini |
https://models.github.ai/inference |
You can still override values at runtime:
kcmt --provider openai --model gpt-5-mini-2025-08-07 --endpoint https://api.openai.com/v1 \
--api-key-env OPENAI_API_KEY --repo-path .
Additional environment tweaks remain available:
KLINGON_CMT_LLM_MODELKLINGON_CMT_LLM_ENDPOINTKLINGON_CMT_GIT_REPO_PATHKLINGON_CMT_MAX_COMMIT_LENGTH(applies to subject line validation; body is no longer truncated)KCMT_PROVIDER– force provider selection for one-off runs without editing~/.config/kcmt/config.json(useful when CI supplies secrets that differ from the persisted repo defaults) Deprecated:KLINGON_CMT_ALLOW_FALLBACKpreviously enabled a heuristic fallback subject after repeated LLM failures. This path has been removed; kcmt now fails fast with an explicit LLMError so you never get an invented message. The variable is ignored if set.
Additional LLM behaviour environment variables:
KCMT_LLM_REQUEST_TIMEOUT– per-request HTTP timeout (seconds, default 5)KCMT_PREPARE_PER_FILE_TIMEOUT– per-file generation timeout in atomic workflowKCMT_PREPARE_WORKERS– override number of concurrent LLM preparations (or use--workers)KCMT_OPENAI_DISABLE_REASONING– disable reasoning / chain-of-thought (default on)KCMT_OPENAI_MINIMAL_PROMPT– force minimal prompt style (adaptive toggle)KCMT_OPENAI_MAX_TOKENS– max completion tokens for OpenAI-like providersKCMT_FAST_LOCAL_FOR_SMALL_DIFFS– opt-in local conventional subject for tiny diffs (<=3 changed lines)KLINGON_CMT_AUTO_PUSH=0|1(disable or enable automaticgit push; default is enabled)
List models and pricing
kcmt --list-models prints a simple cross-provider pricing board ordered by output (completion) price per 1M tokens, including context window and max output when available. Use --debug to see the raw structured listings.
Example:
kcmt --list-models
Benchmarking
kcmt --benchmark
Run a local benchmark across providers/models using a fixed set of example diffs. kcmt measures latency, estimates cost, and scores conventional-commit quality with lightweight heuristics.
Basic usage:
kcmt --benchmark --benchmark-limit 5
Options:
--benchmark-limit INT– max models per provider (default 0 = all models)--benchmark-timeout SECS– per-call timeout--benchmark-json– also emit machine-readable JSON--benchmark-csv– also emit CSV rows
Snapshots are saved under ~/.config/kcmt/repos/<repo-id>/benchmarks/benchmark-<timestamp>.json for later comparison.
Quick start (CLI)
kcmt --configure # guided setup -> ~/.config/kcmt/config.json
kcmt # per-file atomic commits with live stats
kcmt --workers 8 # explicitly set parallel LLM preparations
kcmt --oneshot --verbose # single best-effort commit
kcmt --file README.md # commit a specific file
kcmt --provider xai --model grok-code-fast --api-key-env XAI_API_KEY
Exit codes
- 0 on success
- 1 on workflow error (input/validation/LLM/Git failures)
- 2 on configuration error (no usable API key)
CLI reference
kcmt accepts the following common options:
--configure– launch the interactive setup wizard.--configure-all– select provider(s) and set which env var holds each API key.--verify-keys– show which env var is used per provider and whether it’s set.--provider,--model,--endpoint,--api-key-env– override saved provider details.--repo-path PATH– target repository (defaults to current working directory).--max-commit-length INT– validate (not hard truncate) the subject line length (default 72 for legacy compatibility; body is preserved).--auto-push/--no-auto-push– enable/disable automatic push (default: enabled; can also setKLINGON_CMT_AUTO_PUSH).--max-retries INT– retries when Git rejects (default 3).--oneshot– stage all changes, pick one file, and commit it once.--file PATH– stage & commit an explicit file.--no-progress– disable the live stats bar.--verbose,-v– emit detailed logs and per-file results.--list-models– show a pricing comparison board of models across providers.--benchmark– run the model benchmark and show leaderboards. Combine with--provider/--modelto focus a subset; seedocs/benchmark.mdfor the scoring rubric and pass/fail guidance. Missing credentials are listed in the "Excluded Models" table at the end of each run.--benchmark-json/--benchmark-csv– print results in machine-readable formats.
Conventional commit automation
kcmt now ships with a Commitizen configuration that mirrors the LLM-generated commit format. After installing the project you can:
- run
cz checkto validate a message before committing manually; - run
cz committo invoke Commitizen's prompt flow while still benefitting from kcmt's validation rules and version tracking (it watcheskcmt/__init__.py).
The configuration lives in pyproject.toml under [tool.commitizen], so any repository that
adopts kcmt inherits the same conventional commit guardrails automatically.
Library usage examples
Basic: generate a message from staged changes
- from kcmt.config import load_config
- cfg = load_config()
- from kcmt.commit import CommitGenerator
- gen = CommitGenerator(repo_path=cfg.git_repo_path, config=cfg)
- msg = gen.generate_from_staged(context="Refactor widgets", style="conventional")
- print(msg)
Generate from working tree changes
- from kcmt.config import load_config
- cfg = load_config()
- from kcmt.commit import CommitGenerator
- gen = CommitGenerator(repo_path=cfg.git_repo_path, config=cfg)
- msg = gen.generate_from_working(context="Work in progress", style="conventional")
- print(msg)
Run the full atomic workflow
- from kcmt.config import load_config
- cfg = load_config()
- from kcmt.core import KlingonCMTWorkflow
- wf = KlingonCMTWorkflow(repo_path=cfg.git_repo_path, max_retries=3, config=cfg)
- results = wf.execute_workflow()
- print(results["summary"])
- for r in results.get("file_commits", []):
- print(r.success, r.commit_hash, r.message)
Using GitRepo directly
- from kcmt.config import load_config
- cfg = load_config()
- from kcmt.git import GitRepo
- repo = GitRepo(cfg.git_repo_path, cfg)
- print(repo.get_working_diff())
- if repo.has_working_changes():
- repo.stage_file("README.md")
- repo.commit("docs: update readme")
API documentation (high-level)
Exceptions
- kcmt.exceptions.KlingonCMTError: Base error
- kcmt.exceptions.GitError: Git command errors
- kcmt.exceptions.LLMError: LLM call errors
- kcmt.exceptions.ConfigError: Config/ENV errors
- kcmt.exceptions.ValidationError: Validation failures
Configuration
- kcmt.config.Config
- Fields: provider, model, llm_endpoint, api_key_env, git_repo_path, max_commit_length, auto_push, providers (per-provider map), provider_env_overrides (legacy mapping)
- kcmt.config.load_config(overrides=None)
- Merge global
~/.config/kcmt/config.json, environment, and optional overrides.
- Merge global
- kcmt.config.save_config(config, repo_root=None)
- kcmt.config.get_active_config() / set_active_config()
LLM
- kcmt.llm.LLMClient
- generate_commit_message(diff: str, context: str = "", style: str = "conventional") -> str
Git operations
- kcmt.git.GitRepo
- has_staged_changes() -> bool
- get_staged_diff() -> str
- has_working_changes() -> bool
- get_working_diff() -> str
- get_commit_diff(commit_hash: str) -> str
- get_recent_commits(count: int = 5) -> list[str]
- stage_file(file_path: str) -> None
- unstage(file_path: str) -> None
- commit(message: str) -> None
- process_deletions_first() -> list[str]
- push(remote: str = "origin", branch: str | None = None) -> str
Commit generation
- kcmt.commit.CommitGenerator
- generate_from_staged(context: str = "", style: str = "conventional") -> str
- generate_from_working(context: str = "", style: str = "conventional") -> str
- generate_from_commit(commit_hash: str, context: str = "", style: str = "conventional") -> str
- suggest_commit_message(diff: str, context: str = "", style: str = "conventional") -> str
- validate_conventional_commit(message: str) -> bool
- validate_and_fix_commit_message(message: str) -> str
Core workflow
- kcmt.core.FileChange
- file_path: str
- change_type: str # "A" | "M" | "D"
- diff_content: str
- kcmt.core.CommitResult
- success: bool
- commit_hash: str | None
- message: str | None
- error: str | None
- kcmt.core.KlingonCMTWorkflow(repo_path: str | None = None, max_retries: int = 3)
- execute_workflow() -> dict
- deletions_committed: list[CommitResult]
- file_commits: list[CommitResult]
- errors: list[str]
- summary: str
- execute_workflow() -> dict
Notes on behavior
- Deletions are grouped and committed first with a generated message.
- Then remaining file changes are parsed from git diff and committed per-file.
- Commit messages are validated and may be LLM-fixed on failure; retries are applied.
- Enrichment pass: for substantial diffs (>=10 changed lines) a second LLM call may add a concise multi-line body explaining what and why while preserving the original header.
- Fast fail: after retry exhaustion (currently 3 attempts) kcmt raises an
LLMError; no heuristic commit message is generated. - If
--auto-pushis enabled and at least one commit succeeds, kcmt attemptsgit push origin <current-branch>and records the result (results['pushed']=True).
Development
Prereqs
- Python 3.12+
- A virtual environment
- Git
Set up
- uv venv && source .venv/bin/activate # or python -m venv .venv
- uv pip install -e ./kcmt # or pip install -e ./kcmt
- uv pip install pytest # or pip install pytest
Run tests (from project root)
- make test # quiet pytest run via Makefile
- uv run -m pytest tests # or: pytest
Lint/format (recommendations)
- Use Black and isort (not enforced by this package directly).
- Keep lines <=80 chars where practical, file size <500 lines for readability.
Publishing
- Version is defined in kcmt/init.py (managed by hatch).
- Build and publish via hatch (example):
- uv pip install hatch
- hatch build
- hatch publish # configure as needed
Security notes
- Your GITHUB_TOKEN is used as the API key for the configured endpoint.
- Treat it like any other secret; avoid checking it into logs or code.
- Prefer passing the token via the CLI or securely provisioned environment.
Changelog
- 0.1.2 — Update default OpenAI model to
gpt-5-mini-2025-08-07(automatic migration from legacygpt-5-mini), test helper env varKCMT_TEST_DISABLE_OPENAIto bypass real API calls in isolated tests. - 0.1.1 — Auto-push option, improved retries, subject-only length enforcement.
- 0.1.0 — Initial release: CLI + atomic workflow + LLM commit generation.
License
MIT (see repository license)
Strict testing and CI quick reference
Environment
- uv venv && source .venv/bin/activate
- uv sync --group dev
- uv pip install -e .
Lint/format/type-check
- uv run ruff check kcmt tests
- uv run black --check kcmt tests
- uv run isort --check-only kcmt tests
- uv run mypy kcmt
Run tests
- Basic: uv run pytest -ra -vv tests
- Strict CI-like run (parallel, warnings as errors, coverage):
uv run pytest -n auto -ra -vv -W default -W error::DeprecationWarning -W error::ResourceWarning --strict-config --strict-markers --cov=kcmt --cov-branch --cov-report=term-missing:skip-covered --cov-fail-under=85 tests
- On Windows/PowerShell use a single line (no trailing
\) or replace the line continuation with a backtick (`); PowerShell treats\as a literal character and will otherwise break up the arguments.
- On Windows/PowerShell use a single line (no trailing
Make targets
- make test
- make test-verbose
- make test-strict
- make check # lint + typecheck + strict tests
Metadata
Release files for kcmt 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| kcmt-0.3.1.tar.gz | 117.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| kcmt-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 250.2 kB
Release files / kcmt-0.3.1.tar.gz
| Download URL | kcmt-0.3.1.tar.gz |
|---|---|
| Size | 117.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c86c56a3816eab10a26552c63c6ca832d3081b0ba6a68bc392b089c299a97d19
|
|
BLAKE2b-256 checksum How to use checksums |
27d213a9df710b1356ab1155685df17eb4653c7dd780ec0332afda5dcad3d2d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|
Release files / kcmt-0.3.1-py3-none-any.whl
| Download URL | kcmt-0.3.1-py3-none-any.whl |
|---|---|
| Size | 132.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b173eb274743a644c319014ea6b88c7630664e09341448ff67cee4ce76ea5b6b
|
|
BLAKE2b-256 checksum How to use checksums |
2505ee7e1b5ef89d605f1236a8f86be91b5ca64df97d0c43c82bcb4a31efa03e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.12
|