4 Commits
Author SHA1 Message Date
Prad Nukala 3086dac5d6 refactor: replace Go binary with uv-run smartcommit.py launcher
The extension is now a bash launcher that runs smartcommit.py via
uv run --script (Crush backend, DuckDB scopes). Removes the Go
implementation and the gh-extension-precompile workflow; the bundled
gh-commit.tar.gz now packages the script instead of a compiled binary.
2026-08-18 14:26:07 -04:00
Prad Nukala f1302fd1ec Revert "fix: provider"
This reverts commit 735229b150.
2026-08-17 18:06:54 -04:00
Prad Nukala ae36a7052d build: package v3.0.0 binary tarball
Replaces the old Python source tarball with the compiled linux/amd64
binary (CGO-free, stripped) plus README and LICENSE.
2026-08-15 19:12:44 -04:00
t 9647c854c9 feat: rewrite gh-commit in Go with local Ollama backend
Replaces the Python/Crush/DuckDB implementation with a CGO-free Go binary:
cobra CLI, modernc.org/sqlite storage, huh/lipgloss UI, and speed-tuned raw
/api/generate calls (think:false, keep_alive, capped num_predict) against a
local qwen3.5:2b. Ships as a gh extension via cli/gh-extension-precompile.
2026-08-15 19:10:16 -04:00
8 changed files with 83 additions and 99 deletions
-2
View File
@@ -1,2 +0,0 @@
/.omc
/__pycache__
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 Prad
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+53 -54
View File
@@ -1,6 +1,11 @@
# gh-commit
AI-powered scoped git commits. Groups changes by project area, generates commit messages with `crush run`, and pushes — all in one command.
AI-powered scoped git commits as a GitHub CLI extension, driven by the
[Crush](https://github.com/charmbracelet/crush) CLI. Scopes map Conventional
Commit scope names to path prefixes. They are generated from the repository
file tree, stored in a local DuckDB database, and auto-regenerate whenever
`.gitignore` changes. Changed files are grouped by scope and committed one
scope at a time with generated `type(scope): message` subjects.
## Install
@@ -11,64 +16,33 @@ gh extension install prdlk/gh-commit
### Requirements
- [uv](https://docs.astral.sh/uv) — Python package runner (handles dependencies automatically)
- [Crush](https://github.com/charmbracelet/crush) — non-interactive AI runner
- `GROQ_API_KEY` — authenticates the default Groq model
- [Crush](https://github.com/charmbracelet/crush) — configured with an authenticated model provider
By default, gh-commit uses Groq's `openai/gpt-oss-120b`. Override the model with `GH_COMMIT_CRUSH_MODEL`.
By default, gh-commit uses `openrouter/qwen/qwen3.6-27b`. Configure OpenRouter in Crush or override the model with `GH_COMMIT_CRUSH_MODEL`.
## Usage
```sh
# Initialize scopes for your repo (uses Crush to analyze structure)
gh commit init
# Commit changes grouped by scope
gh commit
# Auto-confirm + auto-push
gh commit --auto --push
# Manually refresh scopes after structural changes
gh commit refresh
# Sync scopes as GitHub labels
gh commit sync
cd your-repo
gh commit init # generate scopes for this repo
gh commit # commit changes grouped by scope
```
## How it works
1. **`gh commit init`** — `crush run` analyzes your repo structure and generates scope definitions (e.g., `core → src/`, `docs → docs/, README.md`, `ci → .github/workflows/`)
2. **`gh commit`** — Groups dirty files by scope, sends each staged diff to `crush run`, and commits each group separately
3. **Auto-refresh** — Whenever your `.gitignore` changes, scopes are automatically regenerated before committing (a content hash of `.gitignore` is tracked per repo)
4. Remaining unscoped files are handled in a final pass
5. Unpushed commits are offered for push
Scopes are stored in a local DuckDB database (`~/.local/share/gh-commit/gh-commit.db`) — no config files in your repo.
### Why Crush?
The former Mods roles now live directly in `smartcommit.py`, so gh-commit no longer depends on Mods configuration. Each generation invokes Crush's supported non-interactive mode with a self-contained prompt.
## Commands
| Command | Description |
|---------|-------------|
| `gh commit` | Commit changes grouped by scope |
| `gh commit init` | Generate scopes for current repo |
| `gh commit refresh` | Update scopes from current structure |
| `gh commit sync` | Sync scopes → GitHub labels |
| `gh commit list` | List all configured repositories |
| `gh commit remove` | Remove current repo from database |
| `gh commit db-path` | Print database file path |
| `gh commit version` | Print version |
| `gh commit help` | Show help |
| Command | Behavior |
|---|---|
| `gh commit` | Full scoped-commit flow: group changed files by scope, generate a message per group, confirm, commit, offer to push |
| `gh commit init` | Generate scopes from the file tree via Crush; migrates legacy configs; confirms overwrite |
| `gh commit refresh` | Show current scopes, regenerate with existing scopes as context, confirm apply |
| `gh commit sync` | Create/update GitHub labels from scopes (color = first 6 hex chars of MD5 of the scope name) |
| `gh commit list` | Table of configured repositories: name, path, scope count |
| `gh commit remove` | Delete the current repo from the database after confirmation |
| `gh commit db-path` | Print the database file path |
| `gh commit version` | Print `gh-commit <version>` |
| `gh commit help` | Help, including the DB path and environment variable docs |
## Flags
| Flag | Description |
|------|-------------|
| `--auto` | Skip all confirmation prompts |
| `--push` | Auto-push after committing |
Flags: `--auto` (skip confirmations), `--push` (auto-push).
## Environment
@@ -78,14 +52,39 @@ The former Mods roles now live directly in `smartcommit.py`, so gh-commit no lon
| `GH_COMMIT_PUSH=1` | Auto-push after commits |
| `GH_COMMIT_NO_AUTO_REFRESH=1` | Don't auto-regenerate scopes when `.gitignore` changes |
| `GH_COMMIT_CRUSH_CMD` | Override the Crush command (default `crush`) |
| `GH_COMMIT_CRUSH_MODEL` | Override the Crush model (default `groq/openai/gpt-oss-120b`) |
| `GH_COMMIT_CRUSH_MODEL` | Override the Crush model (default `openrouter/qwen/qwen3.6-27b`) |
| `GH_COMMIT_CRUSH_TIMEOUT` | Per-prompt timeout in seconds (default `120`) |
| `GH_COMMIT_DEBUG=1` | Show scope-response parse diagnostics |
## Migration
## Storage
Existing `.github/Repo.toml` or `.github/scopes.json` files are automatically detected and migrated to DuckDB on first run.
Scopes live in `${XDG_DATA_HOME:-~/.local/share}/gh-commit/gh-commit.db`
(DuckDB). Legacy `.github/Repo.toml` and `.github/scopes.json` files are
migrated automatically on first run (the source file is archived as
`*.migrated.<timestamp>`).
## License
## Manual acceptance
MIT
```sh
mkdir /tmp/accept && cd /tmp/accept && git init
mkdir -p src docs
echo 'package app' > src/app.go
echo '# Docs' > docs/README.md
gh commit init # scopes generated and saved
echo '// change' >> src/app.go
echo 'More docs' >> docs/README.md
gh commit # one commit per scope
git log --format='%s' # verify type(scope): message subjects
```
## Development
The extension is a bash launcher (`gh-commit`) that runs `smartcommit.py`
with `uv run --script`; uv resolves the inline dependencies (duckdb, rich,
questionary) automatically.
```sh
python -m py_compile smartcommit.py # syntax check
bash -n gh-commit # launcher check
./gh-commit version # smoke test
```
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
{
"$schema": "https://charm.land/crush.json",
"providers": {
"groq": {
"type": "openai-compat",
"base_url": "https://api.groq.com/openai/v1",
"api_key": "$GROQ_API_KEY",
"models": [
{
"id": "openai/gpt-oss-120b",
"name": "GPT-OSS 120B on Groq",
"context_window": 131072,
"default_max_tokens": 8192
}
]
}
}
}
+5 -16
View File
@@ -1,23 +1,12 @@
#!/usr/bin/env bash
# gh-commit launcher — runs the bundled Python implementation via uv.
set -euo pipefail
# gh-commit: AI-powered scoped git commits (Crush for scopes and messages)
# Install: gh extension install prdlk/gh-commit
# Requires: uv (https://docs.astral.sh/uv) and the Crush CLI
# (https://github.com/charmbracelet/crush)
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXTENSION_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT="${EXTENSION_DIR}/smartcommit.py"
if ! command -v uv &>/dev/null; then
echo "error: 'uv' is required but not installed"
echo "install: curl -LsSf https://astral.sh/uv/install.sh | sh"
if ! command -v uv >/dev/null 2>&1; then
echo "gh-commit: 'uv' is required (https://docs.astral.sh/uv)" >&2
exit 1
fi
if [[ -z "${GH_COMMIT_CRUSH_CMD:-}" ]] && ! command -v crush &>/dev/null; then
echo "warning: 'crush' not found — gh-commit uses 'crush run' for scopes and commit messages."
echo "install Crush (https://github.com/charmbracelet/crush), or set GH_COMMIT_CRUSH_CMD."
fi
exec uv run --script "${SCRIPT}" "$@"
exec uv run --script "$DIR/smartcommit.py" "$@"
BIN
View File
Binary file not shown.
+4 -9
View File
@@ -44,9 +44,7 @@ DB_PATH = DB_DIR / "gh-commit.db"
# Crush handles both AI steps in non-interactive mode. The instructions formerly
# stored as mods roles are embedded below so this tool is self-contained.
CRUSH_CMD = os.environ.get("GH_COMMIT_CRUSH_CMD", "crush").split()
DEFAULT_CRUSH_MODEL = "groq/openai/gpt-oss-120b"
CRUSH_MODEL = os.environ.get("GH_COMMIT_CRUSH_MODEL", DEFAULT_CRUSH_MODEL)
CRUSH_CONFIG_DIR = Path(__file__).with_name("crush-provider")
CRUSH_MODEL = os.environ.get("GH_COMMIT_CRUSH_MODEL", "openrouter/qwen/qwen3.6-27b")
CRUSH_TIMEOUT = int(os.environ.get("GH_COMMIT_CRUSH_TIMEOUT", "120"))
AUTO_CONFIRM = os.environ.get("GH_COMMIT_AUTO", "0") == "1"
@@ -54,7 +52,7 @@ AUTO_PUSH = os.environ.get("GH_COMMIT_PUSH", "0") == "1"
NO_AUTO_REFRESH = os.environ.get("GH_COMMIT_NO_AUTO_REFRESH", "0") == "1"
DEBUG = os.environ.get("GH_COMMIT_DEBUG", "0") == "1"
VERSION = "2.0.0"
VERSION = "5.0.0"
# ── Helpers ───────────────────────────────────────────────────────────────────
@@ -117,12 +115,9 @@ def crush_prompt(text: str, cwd: Path) -> str:
cmd = [*CRUSH_CMD, "run", "--quiet"]
if CRUSH_MODEL:
cmd += ["--model", CRUSH_MODEL]
env = os.environ.copy()
if CRUSH_MODEL == DEFAULT_CRUSH_MODEL:
env.setdefault("CRUSH_GLOBAL_CONFIG", str(CRUSH_CONFIG_DIR))
try:
result = subprocess.run(
cmd, input=text, capture_output=True, text=True, env=env,
cmd, input=text, capture_output=True, text=True,
cwd=str(cwd), timeout=CRUSH_TIMEOUT,
)
except FileNotFoundError as e:
@@ -677,7 +672,7 @@ def cmd_help():
console.print(" GH_COMMIT_PUSH=1 Auto-push after commits")
console.print(" GH_COMMIT_NO_AUTO_REFRESH=1 Don't auto-regenerate scopes on .gitignore change")
console.print(" GH_COMMIT_CRUSH_CMD=... Override the Crush command")
console.print(f" GH_COMMIT_CRUSH_MODEL=... Override the Crush model (default: {DEFAULT_CRUSH_MODEL})")
console.print(" GH_COMMIT_CRUSH_MODEL=... Override the Crush model (default: openrouter/qwen/qwen3.6-27b)")
console.print(" GH_COMMIT_CRUSH_TIMEOUT=... Per-prompt timeout in seconds (default: 120)")
console.print(" GH_COMMIT_DEBUG=1 Show parse diagnostics\n")
console.print("[cyan]Scopes auto-refresh whenever .gitignore changes.[/]")