Let me paint you a picture.
It's 1am. I've been coding for 4 hours straight. I'm in flow, fixing bugs, adding features, refactoring stuff that was embarrassing to look at. Then I finally decide to push and I stare at the git commit prompt like it personally offended me.
What do I type? fix stuff? update? changes?
Yeah. We've all been there.
The worst part wasn't even the lazy messages. It was that I'd sometimes go days without pushing because I'd get so deep into coding that committing felt like an interruption. Then I'd look at my GitHub contribution graph and it looked like I hadn't touched a computer in a week. Meanwhile I'd written 2000 lines of code.
So I did what any reasonable developer would do at 1am ā I decided to build a tool to fix it instead of just... committing.
š” The idea
The concept is simple: what if a tool just watched my code, noticed when I saved files, figured out what changed, and wrote the commit message for me?
I'd just have to press Y.
That's it. That's the whole pitch.
I called it phantomit because it commits silently in the background like a ghost. Also because I thought it was a cool name at 1am and I stand by that decision.
āļø How it works
Here's the basic flow:
- You run
phantomit watch --on-save - It watches your project directory using chokidar
- When you save files, it waits 8 seconds (debounce window)
- After the window closes, it runs
git add .thengit diff --staged - That diff goes to Groq's llama-3.1-8b-instant with a strict prompt
- The AI returns a conventional commit message (10-20 words, nothing more)
- You see this in your terminal:
[11:42 PM] ā src/auth.ts, ā src/middleware.ts
⦠Commit message:
"feat(auth): add JWT validation middleware with token expiry handling"
[Y] commit & push [E] edit [N] skip
ā
You press Y. It commits. It pushes. You go back to coding.
That's literally it.
š The problems I didn't expect
Okay here's the part where it gets interesting. Building the concept took maybe an hour. The edge cases took the rest of the night.
Saving two files felt like saving one and a half
My original debounce was 2 seconds. Save auth.ts, wait 1.8 seconds, save middleware.ts ā boom, two separate commit triggers. The first one commits, the second one gets skipped because the first is still processing.
So I increased the debounce to 8 seconds and made it configurable. Now if you save 5 files within 8 seconds they all batch into one commit. One clean diff, one good message.
The race condition nobody warned me about
Here's a fun one. What if you delete a file and save another file at exactly the same time?
Old behavior: first event triggers, commit starts, second event comes in while isCommitting = true, gets silently dropped. You just lost a change.
The fix was a commit queue. If a commit is already running when a new trigger fires, it queues up and runs immediately after. Nothing gets dropped. I felt very smart when I figured this out and then immediately felt dumb for not thinking of it from the start.
Chokidar doesn't read .gitignore
This one annoyed me. Chokidar is great for watching files but it has no idea what a .gitignore is. So by default phantomit would pick up changes in node_modules, .env, dist ā all the stuff you'd never want to commit.
I pulled in the ignore npm package which parses .gitignore properly ā including negations (!important.js), globs (**/*.log), comments, the whole spec. Now phantomit automatically reads your .gitignore and merges it with whatever extra patterns you define in .phantomit.json.
Your .env is safe. You're welcome.
Only catching "save" events is naive
My first version only listened for change events ā file edits. But what about:
- Creating a new file? (
add) - Deleting a file? (
unlink) - Creating a folder? (
addDir) - Deleting a folder? (
unlinkDir)
All of these are meaningful changes that deserve to be in a commit. So I added all 5 event types and now the terminal shows you exactly what happened:
[9:14 AM] ā src/newfile.ts, ā src/old.ts, ā src/index.ts
Much better context for the AI too ā it knows you deleted something, not just edited it.
š¤ The AI part
I'm using Groq because it's genuinely fast and has a free tier that's more than enough for personal use. The model is llama-3.1-8b-instant ā small, quick, doesn't overthink it.
The prompt is strict:
You are a Git commit message generator.
Rules:
- Use conventional commits format: type(scope): description
- Types: feat, fix, refactor, chore, docs, style, test, perf
- Keep it between 10-20 words
- Be specific and descriptive, not vague
- No period at the end
- Output ONLY the commit message, nothing else
The "output ONLY the commit message" is doing a lot of work there. Without it you get things like:
"Here is a commit message for your changes: feat(auth)..."
No thanks.
I also truncate diffs over 6000 characters because sending a 50kb diff to an LLM is both slow and expensive and the model doesn't need to read your entire codebase to write a 15 word sentence.
š Key rotation (my favorite feature nobody asked for)
Groq's free tier has rate limits. If you're coding heavily you might hit them.
My solution: support unlimited API keys picked at random on every commit.
GROQ_API_KEY_1=first_key
GROQ_API_KEY_2=second_key
GROQ_API_KEY_3=third_key
# add as many as you want
How it works under the hood:
Object.entries(process.env).forEach(([key, value]) => {
if (key.startsWith('GROQ_API_KEY_') && value) keys.push(value);
});
Just loops through every env variable matching the pattern. No hardcoded limit. You could have GROQ_API_KEY_99 and it would still work. Each commit picks one at random.
Three free Groq accounts = effectively triple the rate limit. I call that a feature.
šļø Three watch modes
Different devs work differently so I built three modes:
--on-save ā commits 8 seconds after your last file save. This is the one I use personally.
phantomit watch --on-save
phantomit watch --on-save --daemon # background, silent
--every <minutes> ā commits on a fixed interval if there are changes. Set it and forget it.
phantomit watch --every 30
--lines <count> ā commits when your accumulated diff crosses a line threshold.
phantomit watch --lines 20
And if you just want the AI message without any automation:
phantomit push # generate + commit right now
phantomit push --mock # test without a Groq key
š Install it
npm install -g phantomit-cli
Note: the package is phantomit-cli on npm because some package called phantomjs exists and npm decided our names were too similar, which is honestly a bit offensive. But the command is still just phantomit.
Then:
cd your-project
phantomit init
echo "GROQ_API_KEY=your_key" >> .env
phantomit watch --on-save
Get a free Groq key at console.groq.com. Takes about 30 seconds.
šø What the commit messages actually look like
Here are some real ones generated during development of phantomit itself (yes, I used phantomit to build phantomit, which felt very appropriate):
feat(ai): implement mock mode for generateCommitMessage function
fix(test): Remove test file as it is no longer needed
feat(test): add test generation feature with mock mode support
fix(test): Remove unused test file and refactor generateCommitMessage function
fix(ai): Update AI commit message generation to use new model
Not perfect but genuinely better than update or misc changes. And I typed zero of those.
š® What's next
A few things I want to add:
- Auto-retry with next key if one Groq key hits a rate limit instead of just failing
- Better daemon logging with timestamps and session stats
- Custom prompt templates so you can tune the commit style to your team's conventions
If you want to contribute or have ideas, the repo is open: github.com/var-raphael/phantomit
Docs at phantomit-docs.vercel.app
That's it. Go install it, stop writing commit messages, and put that mental energy toward literally anything else.
Your GitHub graph will thank you. š
Raphael, 18, coding on a mobile phone in Nigeria at 1am š³š¬

Top comments (2)
Cool idea. How does it handle large diffs or merge commits?
Great question!š¤ For large diffs phantomit truncates anything over 6000 characters before sending to Groq, so it takes the first 6000 chars of the diff which usually covers the most significant changes. Not perfect for massive refactors but works well for typical saves.
For merge commits it doesn't handle them specially yet, it would just see the combined diff which could be noisy. Honestly that's a gap worth addressing. I'm thinking of adding a --no-merge flag that skips committing if it detects a merge in progress.
Thanks for pointing that out, adding it to the roadmap. š