I noticed I was doing the same thing every time I sat down to write a blog post.
I'd open a project, run git log to see what happened, skim through diffs to find the interesting bits, then try to shape all of that into something readable. The pattern was always the same: gather git context, figure out the narrative, write it up in my voice. Every single time.
The thing is, this process has clear inputs — git history, branch structure, file change frequency — and clear constraints — my tone, my post structure, the kind of technical depth I care about. It's not a creative blank-canvas problem. It's a repeatable process with known rules.
So I wrote a 133-line markdown file that does it. A single file, sitting in ~/.claude/commands/blog.md, that I can invoke from any project with /blog in Claude Code. It reads the repo's git history, identifies what's interesting, and drafts a blog post in my voice. That's it.
What Are Custom Commands?
Custom commands in Claude Code are .md files that become slash commands. Drop a file called blog.md into ~/.claude/commands/ and it becomes /blog — available in every Claude Code session, across every project on your machine.
There are two levels:
- Global —
~/.claude/commands/— available everywhere, personal to you - Project-level —
.claude/commands/in a repo — shared with your team via git
If you've read my previous post on skills, the distinction is simple: skills teach Claude what to know, commands teach it what to do. A skill is reference material Claude consults when relevant. A command is a workflow — a sequence of steps with a specific output.
What My /blog Command Does
The command has four parts that work together. Here's what each one does and why it matters.
Git context gathering
The first thing the command does is run a series of git commands to build a picture of the project:
# All branches, sorted by most recent activity
git branch -a --sort=-committerdate
# Visual commit graph across all branches (last 100)
git log --all --oneline --graph --decorate -100
# Recent commits with stats (last 2 weeks)
git log --all --since="2 weeks ago" --pretty=format:"%h %s%n%b" --stat
# Files that changed the most — where the interesting work is
git log --all --since="4 weeks ago" --name-only --pretty=format:"" | sort | uniq -c | sort -rn | head -30
Each command serves a purpose. The branch list shows what features are in flight. The commit graph reveals the shape of development — was it a sprint on one branch, or scattered work across many? The stat-annotated log shows the scale of changes. And the file frequency count points directly at where the interesting work happened — if parser.rs was touched 47 times in a month, that's a story.
Story detection heuristics
Raw git data isn't a blog post. The command includes heuristics for spotting narratives:
- A commit that fixes something from an earlier commit — that's a debugging story. Something went wrong, something was learned.
- A branch with a long chain of commits on one feature — that's a deep dive. Sustained effort on a hard problem.
- Reverts or rollbacks — something went wrong in a more dramatic way. Good "what I'd do differently" material.
- Architectural changes across many files — refactors, migrations, new patterns. These are decision stories: why was the old approach insufficient?
- Integration work connecting different systems — API stitching, service boundaries, data flow problems. Usually messy and instructive.
The command tells Claude to look for these patterns explicitly instead of just summarizing commits chronologically.
Tone constraints
This is the part that makes the output sound like me instead of generic AI prose. A few of the rules baked in:
"First person, conversational — 'I built...', 'The tricky part was...', 'Here's what I wish I knew...'"
"Honest about struggles — don't make it sound easy. Show the wrong turns."
"Practical, not academic — readers should walk away knowing how to do something."
These match the conventions I already have in my CLAUDE.md, which means the command and the project config reinforce each other. The command isn't fighting the project's style — it's extending it into a specific workflow.
The $ARGUMENTS trick
The last few lines of the command use Claude Code's $ARGUMENTS variable — whatever you type after the slash command gets injected into the prompt:
## User Input
$ARGUMENTS
If the user provides arguments (a topic, a branch name, a time range,
or specific guidance), focus the blog post accordingly. If no arguments
are given, pick the most interesting story from recent git history.
This means one command handles multiple use cases:
/blog— Claude picks the most interesting story from recent history/blog rust memory management— focuses on a specific topic/blog feat/auth-system— focuses on a specific branch
No flags, no config files. Just natural language after the slash.
How It Works in Practice
The actual experience is minimal. I open a project, start Claude Code, type /blog, and wait. Claude runs the git commands, reads through the diffs, identifies a narrative, and drafts a full post. My Solo IDE post came from this workflow.
What still needs human editing? Accuracy, mostly. Claude doesn't know which decisions were agonized over and which were obvious. It doesn't know that the reason I chose WebSockets over SSE was a conversation with a coworker, not a technical tradeoff. It also can't always tell what's important to me versus what's important in the abstract. So I read through the draft, adjust emphasis, add personal context, and fact-check anything that looks like it could be a hallucination. The structure and prose are usually solid — the human pass is about truth and emphasis, not rewriting.
The Bigger Idea
The thing I keep coming back to is how underused this pattern is. Most people interact with AI tools in a blank-prompt, one-off way. Every session starts from scratch. But if you have a process you repeat — and most developers do — encoding that process into a reusable prompt is a genuine multiplier.
What makes this different from a bash script? Judgment. A script can collect git data and format it, but it can't decide that a sequence of three commits tells a debugging story, or that a refactor across twelve files deserves a section on architectural tradeoffs. It can't write prose that reads like a person wrote it. The command handles the structured parts (data gathering, output format) while leaving the unstructured parts (narrative detection, writing quality) to the model.
The pattern generalizes beyond blog posts. Changelogs from release branches. PR descriptions that actually explain why and not just what. Architecture decision records generated from a series of refactoring commits. Onboarding docs from a repo's evolution. Anywhere you have structured history and need human-readable narrative, a markdown file with the right instructions can get you 80% of the way there.
One file. No dependencies. Available everywhere. That's the whole thing.