AI-powered git commits and workflow
5 min read
Sometimes, when I’m doing edits directly, (i.e: without some agent, —yes, I do write code myself sometimes— ), I still want a LLM-generated, conventional-commit message.
That’s usually a single message to Claude “give me a conventional commit message for this diff”, which I have later converted to a skill. This allows me to tune some nuances around when to use chore vs build, etc.,
However, even the use of the skill became a bit tedious, so I wrote a small set of git functions for the fish shell that I use daily.
These are ac, ghpr and gitm.
I have them released as a fisher plugin, check out the repo!
Once you have fisher installed, adding the functions is as easy as doing
fisher install Guernik/fish-ai-git@v1.0.0Make sure to use the v1.0.0 since that’s the signed release.
Project setup and repo hygiene
I like setting up my projects with good industry standard practices.
In this case we have:
1. pre-commit hooks
end-of-file-fixertrailing-whitespacecheck-merge-conflict- And the local checks:
fish-lint,fish-audit, andfish-test
2. Unit tests
Thanks to fishtape, (available as a fisher plugin), we have some decent coverage:
emilio@Emilios-MacBook-Pro ~/p/fish_functions (main)> just testTAP version 13ok 1 ac bails when there is nothing to commitok 2 ac reports nothing to commitok 3 ac commits with the model's message subjectok 4 ac sends the real source change to the modelok 5 ac excludes lockfile content from the model prompt...ok 46 gitm -h prints usage
1..46# pass 46# ok3. CI/CD
lint-and-test and audit checks are provided on the ci.yml workflow file

The release is handled by the release.yml workflow, activated upon a newly pushed tag.
The job will check that the tag was signed and generates a new GitHub release (look at the v1.0.0 release for an example job run)
4. Security
Since this project install code that runs on your machine, checking out git branches, and calling and external LLM, some security considerations have been made. You can read more about that on the repos SECURITY page Basically all tags and releases are signed by my personal singing key (which you can verify locally). Releases are blocked if the tag is not sign.
Dependabot and code attestation have also been considered, but discarded at the moment (no dependencies to update, and no artifacts to sign).
Functions logic
ac (for AutoCommit)
How it works
flowchart LR
A[git add -A] --> B[Build diff<br/>exclude noisy files] --> C([<strong>claude -p<br/>generate message</strong>]) --> D[Show + prompt] --> E[git commit]
classDef ai fill:#4AAE47,stroke:#2f7a2d,color:#fff;
class C ai;
Example
Help text:
emilio@emilio ~/ (main)> ac --helpusage: ac [-h|--help]
Stage all changes (git add -A) and commit with an AI-generatedConventional Commit message. Shows the message and prompts beforecommitting: [Y] commit, [e] edit in $EDITOR, [n] abort (changesstay staged).
The model defaults to $AC_MODEL (haiku). Noisy/generated files(lockfiles, minified assets, dist/, build/, snapshots) are hiddenfrom the diff sent to the model, but every staged file is committed.Usage example:
emilio@emilio ~/p/fish_functions (docs/mermaid-diagrams)> acGenerating commit message…
docs: reorganize README sections and add mermaid flow diagrams
Restructured the README to improve clarity and readability by moving theRequirements section before Functions and relocating the `ac` and `ghpr` flowdiagrams into a dedicated Functions subsection.
Changes:- Add Requirements section with dependencies: fish ≥ 3.6, claude, gh, and git- Move Requirements section before the Functions section- Create `ac` flow and `ghpr` flow subsections with mermaid diagrams- Relocate flow diagrams from "How `ac` and `ghpr` work" section into functionsubsections- Remove "How `ac` and `ghpr` work" heading (content now integrated intofunction flow subsections)
Commit? [Y/n/e(dit)]It’s not perfect though! For instance, look at this commit
Some LLM related context got to the commit message. (Commit title should have been ci: harden supply chain with signed releases...)
In my defense, I did set up the e(dit) positivity to always have the last word, but this one slipped away!
(It does look ugly, but I will not rewrite the history of the repo for a small commit message mistake).
The rest of the commits are good though

ghpr (for GitHub Pull Request)
ghpr pushes the current branch and opens a GitHub PR with an AI-generated title and body. It detects the base branch automatically, so most of the time it’s a single command.
How it works
flowchart LR
A[ghpr] --> B[git push] --> C([<strong>claude -p<br/>PR title + body</strong>]) --> D[gh pr create]
classDef ai fill:#4AAE47,stroke:#2f7a2d,color:#fff;
class C ai;
Example
emilio@Emilios-MacBook-Pro ~/p/fish_functions (docs/mermaid-diagrams)> ghpr# → pushes the branch and opens a PR with an AI title + bodyPush 'docs/mermaid-diagrams' and open PR against 'main'? [Y/n/w(eb)]Same idea as ac: accept (Y), reject (n), or hit w to open the browser with the PR pre-filled instead of creating it directly.
gitm (for Git Merge cleanup)
gitm is the tidy-up step after a PR gets merged. It switches to the repo’s default branch, pulls, and deletes the branch you just left — but only if it’s already merged, so unmerged work is never dropped.
How it works
flowchart LR
A[gitm] --> B[git switch default] --> C[git pull] --> D{branch merged?} -->|yes| E[git branch -d] --> F[done]
D -->|no| F
Example
emilio@Emilios-MacBook-Pro ~/p/fish_functions (docs/mermaid-diagrams)> gitm# → back on main, pulled, and the merged feature branch cleaned upTogether, ac, ghpr and gitm cover the whole loop — commit, open the PR, and clean up once it’s merged — without ever leaving the terminal or hand-writing a message.
Configuration
By default, ac and ghpr use the AC_MODEL and GHPR_MODEL universal variables, set to haiku on install.
Since I’m mostly using Claude as my primary coding agent/LLM, I didn’t set up any means to configure other providers.
Further improvements
fish-ai is another fish plugin that aims to incorporate AI into the shell in a more deep, involved manner.
Even when such a setup is a bit too much for me (it actually uses a python backend each time you call the plugin), the configuration mechanism they provide is interesting, particularly the OpenRouter and local LLM setup.
As a further improvement of my functions, I could provide some similar configuration options. Better yet, feel free to fork and open a PR!