Slash commands: no more meaningless commits
Source: Dev.to
One of the things I use the most in Claude Code is slash commands.
A command every developer should have is /commit. It cleans up a habit many devs share—even the experienced ones. /commit makes the commit bar explicit, and the whole team ends up writing commits in the same, consistent way.
I’ve always cared about commit messages. Even developers who care can still end up with a "fixes" or a generic "update" slipping into the repo on a tired Friday or in the middle of a massive refactor. That’s how a repository accumulates a layer of noise that nobody can decode three months later.
What Is a Slash Command?
Slash commands aren’t unique to Claude Code. Tools like OpenCode, Codex CLI, Aider, and Continue also let you wire up your own commands. The format and trigger differ, but the idea is the same: a short keystroke runs a longer prompt you wrote once.
Claude Code implementation
In Claude Code, a slash command is just a Markdown file:
| Scope | Path |
|---|---|
| Global | ~/.claude/commands/.md |
| Project‑specific | .claude/commands/.md |
When the file exists, / works as a shortcut. The file’s front‑matter contains a description; the body is the prompt the agent reads when you invoke the command.
No DSL, no SDK, no plugin manifest.
Markdown file in → slash command out.
The Commit Problem
Even with a good agent in the loop, a plain "commit my changes" yields something passable but not great:
- The agent picks a single type, writes a workable subject line, and lumps unrelated changes into one commit.
- It’s better than nothing, but we can do better.
Desired workflow
- Read the changes and group them by domain.
- Write a decent message for each group, following the Conventional Commits spec.
- Skip files that shouldn’t be committed (e.g., generated files, lockfiles).
/commit – My Slash Command
Below is the slash command I built to keep commits clean and consistent.
---
description: "Analyze all changes and commit in logical groups."
---
# Commit
Place this file at ~/.claude/commands/commit.md (or the project‑specific location) and invoke it with /commit.
When run, the command:
- Scans the working tree for changes.
- Groups modifications by logical domain (e.g.,
feat,fix,docs). - Generates a Conventional Commit message for each group.
- Executes
git addfor the appropriate files and creates the commits.
Feel free to extend the prompt body with additional logic (e.g., custom ignore patterns, interactive confirmation, or integration with your CI pipeline).
Step 1: Survey changes
- Run
bin/commit-surveyto get the file lists and classification. - Read diffs of key files if you need more context on the changes.
Step 2: Group the changes
Use the --- classified --- output as a starting point, then refine it into logical commits.
Grouping strategy
- By domain/feature – e.g., all authentication changes together.
- By layer – e.g., model tests, controller tests.
- By type – e.g., all configuration changes, all dependency updates.
Note: Files listed in
[skip]should not be committed. If you’re unsure about a file, skip it.
Step 3 – Commit Each Group
For each group, combine stage + commit in one call:
git add && git commit -m ""
Order commits from most independent to most dependent:
- Config / tooling changes first
- Source‑code changes
- Test changes
- Generated files last
The Engine in Three Steps
bin/commit-survey
Step 1 calls a script that classifies the files. Instead of asking the agent to run git status, git diff, and parse the output (which burns tokens each run), a short Ruby script does the classification once and returns a predictable result.
Ruby helper (≈ 22 lines)
SKIP_PATTERNS = %w[.env credentials master.key tasks.md notes.txt scratch .claude/]
BUCKETS = {
"skip" => ->(path) { SKIP_PATTERNS.any? { |pat| path.include?(pat) } },
"test" => ->(path) { path.start_with?("test/") },
"db" => ->(path) { path.start_with?("db/") },
"config" => ->(path) { path.start_with?("config/", "Gemfile", ".rubocop", "Procfile", "Rakefile") },
"docs" => ->(path) { path.start_with?("docs/", "README") || path.end_with?(".md") },
"app" => ->(_) { true }
}
paths = `git status --porcelain`.lines(chomp: true).map { |l| l[3..] }
grouped = BUCKETS.keys.to_h { |b| [b, []] }
paths.each do |path|
bucket = BUCKETS.find { |_, matcher| matcher.call(path) }.first
grouped[bucket] << path
end
BUCKETS.each_key do |bucket|
files = grouped[bucket]
puts "[#{bucket}] #{files.empty? ? "(none)" : files.join(", ")}"
end
skipruns first, so secrets and notes never get staged.- The remaining buckets can be tweaked per project (e.g., a Phoenix app would add
lib/,priv/repo/migrations/,assets/; a Next.js app would addpages/,app/,public/,prisma/).
Running the script on a dirty repo yields something like:
--- unstaged ---
M config.toml
M templates/index.html
--- untracked ---
content/blog/_index.md
content/blog/post.md
--- classified ---
[skip] (none)
[test] (none)
[db] (none)
[config] config.toml
[docs] content/blog/_index.md, content/blog/post.md
[app] templates/index.html
The agent can now produce focused commits such as:
chore(config): bump zola versionfeat(templates): add language togglefeat(blog): bootstrap section
instead of a single catch‑all commit.
In Action
When I type /commit, Claude:
- Runs
bin/commit-surveyand reads the output. - Reads diffs for any files that need more context.
- Stages and commits each group with a Conventional Commits message.
- Silently skips
.envand any other secrets.
The whole process takes ~30 seconds and produces clean, scoped commits.
Real example (blog post)
Survey output before the run:
[skip] (none)
[test] (none)
[db] (none)
[config] config.toml
[docs] content/blog/slash-commands-no-more-bad-commits.md,
content/blog/slash-commands-no-more-bad-commits.pt-br.md
[app] sass/_predefined.scss, sass/style.scss
Result of /commit:
feat(blog): add slash commands post in EN and pt‑br
chore(config): drop syntax theme, switch to monochrome code blocks
style(sass): apply osaka jade palette and bump body font
Three commits, each with a clear scope.
Make Your Own
- Copy the script above.
- Save it as
~/.claude/commands/commit.md(global) or.claude/commands/commit.md(project‑local). - Adjust the bucket patterns to match your stack.
You can create similar slash‑commands for other repetitive tasks: /changelog, /release, /pr-draft, /deploy, etc. Each command is just a markdown file containing the steps you would otherwise type into chat.
Wrapping Up
A tiny helper script plus a custom Claude slash‑command are enough to make AI handle routine Git work reliably. No more generic “update” commits—every commit reflects the intent you encoded in the markdown file.
Sources
- Conventional Commits 1.0.0
- Claude Code custom commands docs
- OpenCode
- OpenAI Codex CLI
- Aider
- Continue
Originally posted at guilherme44.com.