DEV Community

erico964-blip
erico964-blip

Posted on

How I Built an AI-Powered Conventional Commit Generator in Python

I shipped my first Python CLI to PyPI this week. Here's what I learned building **gitpulse** — a tool that reads your staged git diff and generates Conventional Commit messages using AI.

## The Problem

Every developer has lazy commits in their history:
`git commit -m "fix"`
`git commit -m "update"`
`git commit -m "stuff"`

Clean commit histories matter — for changelogs, code reviews, and your future self trying to understand why you changed that line 6 months ago. But writing `feat(auth): add JWT token validation with refresh support` for every single commit is mental overhead nobody needs.

## The Solution

gitpulse reads your staged diff and generates the message for you:

`pip install gitpulse-commit`
`git add src/auth.py`
`git-pulse`
`# → feat(auth): add JWT token validation`

You confirm, edit, or abort. One key. Done.

## What I Learned

### 1. PyPI packaging is simpler than it looks

`pyproject.toml` + `python -m build` + `twine upload` is all you need. No `setup.py`, no `setup.cfg`. The hardest part was finding an available package name (hint: check PyPI before you start coding).

### 2. ABC + factory pattern for pluggable providers

The core `AIClient` is abstract. Each provider (OpenCode, OpenAI, Ollama) implements `generate(diff) -> str`. The factory function just does `return OpenAIClient(...)` based on a string. Adding a new provider is ~20 lines of code.

### 3. Prompt engineering is API design

The system prompt is the most important file in the project. It enforces:
- Conventional Commits format (`type(scope): description`)
- 72-character max
- No markdown, no explanations, no fluff
- Imperative mood, no period at end

If the AI ignores any of these, the CLI still strips markdown and truncates — defense in depth.

### 4. Git hooks are easy but fragile

`git-pulse init` writes a prepare-commit-msg hook. It works great, but detecting an existing hook without breaking it required a sentinel marker (`## GITPULSE_HOOK_V0.1`) rather than just searching for a comment string.

### 5. GitHub Actions CI/CD is a superpower

Enter fullscreen mode Exit fullscreen mode


yaml
on:
push:
tags:
- 'v*'


Now `git tag v0.2.0 && git push --tags` automatically publishes to PyPI. Zero manual steps.

## The Code

7 modules, ~300 lines of Python:
- `cli.py` — argparse with subcommands
- `ai_client.py` — ABC + 3 providers
- `git_ops.py` — subprocess wrappers
- `hook.py` — git hook installer
- `config.py` — env var getters
- `prompts.py` — the system prompt
- `__init__.py` — version

Only dependency: `requests`. Python 3.8+. MIT license.

## Try It

`pip install gitpulse-commit`

GitHub: [erico964-blip/gitpulse](https://github.com/erico964-blip/gitpulse)
PyPI: [gitpulse-commit](https://pypi.org/project/gitpulse-commit/)

Built this over a weekend. Feedback welcome — especially on the prompt design and hook workflow!
Enter fullscreen mode Exit fullscreen mode

Top comments (0)