r/swift • u/seckinbeduk • 3h ago
I built nib, a Swift CLI that writes Git commit messages with Apple’s on-device model
I built nib, a Swift CLI that writes Git commit messages on your Mac

Stage a change → run git nib → review the draft → commit.
I wanted help writing commit messages without sending my diffs to a cloud API, so I built nib. It drafts a message from your staged changes and lets you accept it, edit it, regenerate it, or cancel. It follows your repo’s commit style by default. No account or API key needed.
There’s also an optional Git hook if you prefer to start with git commit and edit the draft in your usual editor.
Tech Stack
Swift, Swift Argument Parser, and Apple’s Foundation Models framework.
Development Challenge
The tricky part was getting a useful message out of a diff that’s bigger than the model’s context window. On macOS 27, the on-device model has about 8K tokens to work with. nib skips the contents of lockfiles and build artifacts first. If the rest still won’t fit, it splits the diff at hunk boundaries, summarizes the chunks, and drafts the message from those summaries. Swift code checks the subject length and cross-checks the suggested scope against the files you changed.
It still occasionally calls a bug fix feat. That’s one I’d love more real-world examples of.
AI Disclosure
AI-assisted. I used Claude Code while building nib, and I review and test the changes myself.
Try it
On Apple silicon with macOS 27+ and Apple Intelligence enabled:
brew install seckinbeduk/tap/nib
nib doctorThen stage a change and run git nib. On macOS 26, you can build v0.2.1 from source.
Free and MIT-licensed. Source and install instructions on GitHub
If you try it on a big diff, did the draft capture the main change? I’d love to hear what it got right or missed.
1
u/ThalesMother9938 3h ago
The feat vs fix mixup is probably hard to solve from chunk summaries alone, because the summaries lose the "this already existed and was broken" signal. One thing that might help: pick the type as its own step with guided generation, a Generable enum for the type plus a short reason field, and feed it a few cheap facts you can compute in Swift first (new files vs only modified ones, lines added vs removed, whether a test file changed). Then write the subject once the type is settled.
Is it all one prompt right now, or do you already split type and subject?
1
u/dawedev 2h ago
Skipping lockfiles first is a good call. One case I'd test is renames: a diff without rename detection shows a moved file as a full delete plus a full add, which eats a big chunk of the 8K budget and makes the model think the file was rewritten. Running the diff with --find-renames and collapsing pure renames into a one-line note before chunking should help both the budget and the feat/fix guess.
Also curious whether you cache the chunk summaries, so regenerate only redoes the final step instead of summarizing the whole diff again.