There’s a community standard out there called Conventional Commit for structured git-commit messages. If you don’t know about it already, it recommends a strict type(scope?): description format for describing a code change in a repo.
A normal git commit history might look like this:
> git log --oneline
9d30ff6 fix typo
3062496 oops
d0b38ad shit
b5f8df7 redesign site
That’s all well and good, but lacks a bit of structure and detail for a big project with lots of contributors. That’s where Conventional Commit comes in. Instead of a git-commit message like add login page, it might look like this:
feat: add user login endpoint
Or you can “scope” it to a particular section of your application:
feat(auth): add user login endpoint
There’s even a ! flag to signal a breaking change to your coworkers:
feat!: remove all authentication API endpoints
The predefined conventional commit types cover most types of changes: fix:, feat:, chore:, docs:, style:, refactor:, perf:, test:, build:, and ci:. I find these cover most situations but I’m also a bad boy and like to break rules, so I’ll throw in custom types like wip:, a11y:, hotfix:, or an rm: (as a treat) when I feel it adds clarity to the work.
But why would anyone voluntarily conform to a standard like this? My answer is… a feeling. Look at our git-log history now:
> git log --oneline
9d30ff6 fix(input): restore focus state on input
3062496 style(checkbox): update tokens to tokens v2
d0b38ad chore: update dependencies via dependabot
b5f8df7 feat(auth): add user login endpoint
...
In a noisy repo that’s increasingly getting noisier (thanks, robots) anywhere you can add predictability is a net-positive. The cumulative effect over time is that you have a highly-scannable, uniform history of what’s happening in your project.
Conventional Commit can even help with code review. A style: fix that adds dozens of npm packages should be a signal that you need to break up the work. Types might also inform if you route all a11y: commits to the local expert for code review. And a refactor! breaking change should signal coworkers to be extra-cautious in review and may trigger other process-oriented workflows like broadcasting the upcoming breaking change to anyone in the blast radius.
And because it’s a community standard, like the Custom Element Manifest, you can build tooling around it like commitlint. As its name implies, commitlint is a linter that gates all commit messages from your entire team to ensure they abide in the type(scope): description format. I personally tend to avoid adding extra tooling to enforce simple conventions, but I might change my mind on this because the benefits start compounding.
If my commits were all similarly formatted, I could also automate our entire changelog and semantic versioning bumps for releases.
fix:=PATCHfeat:=MINORany!:=MAJOR
Of course, Conventional Commit and Semver itself aren’t perfect but it adds a degree of predictability about the impact of a change from the description alone. Abiding in the Conventional Commit format is a small micro-adjustment to my process, but it has potential to positively impact the entire project and I like those sorts of zero-dollar improvements.
Extending Conventional Commit beyond commits
One place you’ll also want to extend conventional commit to is pull request titles. Not technically a git-commit message, but if you squash and merge commits the PR title tends to become the commit message by default. Preferring or enforcing conventional commit in pull request titles helps preserve our pristine commit history.
If you’re doing pull request titles, why not use them for issue titles too:
#123 > bug(input): required focus indcator missing
#122 > feat(checkbox): update tokens to v2
#112 > chore: update dependencies from depdendabot
#106 > feat(input): make input more engaging
There’s harmony when the issue and the solution are matching. Having the type in the issue title sets a clear deliverable; a bug: needs a fix: to resolve it. Consider these duplicate issues for the same bug:
#189 > 4.2.7 Focus Visible [bug] [components] [severity:HIGH]
#210 > bug(input): focus indicator is missing
Which one would get picked up first? I’d argue that even with labels like [bug] and [severity:HIGH] that the second one would be easier to fix because it gives the developer almost all the information they need to fix the issue.
It makes the issue a little more actionable and that’s what you want. If every issue is an investigation, nothing gets done. Triaging issues takes cognitive bandwidth. Labels are great at grouping and providing queryable views, but when scanning a list of issues pairing both the tag and the title to determine the ask adds an extra cognitive tax.
I’m projecting here, but uniform data in your PRs and issue queues could also allow you to collect stats on which components or areas of your application are the most buggy and expensive to maintain. It can tell you who’s getting saddled with all the bugs or which issue filer is adding too much feature debt. And it can help your team quantify your recurring maintenance tasks that might be hiding because it gets rolled into other work. That might be valuable to an organization.
Unconventional usage of Conventional Commit
Now let’s extend beyond the practical. I’m finding the Conventional Commit format to be so useful, I’ve started rolling them out to a lot of different places (surprise, coworkers!).
One example is our folder full of random scripts/ in the monorepo. What was a randomized list of dozens of scripts and tests…
scripts/
├── docs-tokens.mjs
├── playwright-cli.mjs
├── storybook-compat-table.mjs
├── style-dictionary.mjs
├── sync-fluent-fluent-custom-elements-manifest.mjs
├── validate-themes.mjs
└── visual-review-harness.mjs
Can become an organized paradise of co-located responsibility:
scripts/
├── build/
│ └── themes-style-dictionary.mjs
├── docs/
│ ├── storybook-generate-compat-table.mjs
│ ├── tokens-generate-table.mjs
│ └── sync-fluent-custom-elements-manifest.mjs
└── test/
├── playwright-cli.mjs
└── playwright-visual-review-harness.mjs
└── themes-validator.mjs
I’ve found those Conventional Commit types tend to map well to areas of responsibility for repo-level scripts. Manage docs, tests, build tasks, and so on all fit under the Conventional Commit umbrella and it adds some much needed organization to the junk drawer of copy-pasta and generated helper scripts.
And if your scripts/ folder is following the Conventional Commit format… why not roll that out to your scripts in package.json:
{
"scripts": {
"docs:generate": "node ./scripts/docs/generate-storybook-compat-table.mjs && node ./scripts/docs/generate-storybook-tokens-table.mjs",
"build:themes": "node ./scripts/build/themes-style-dictionary.mjs",
"test:themes": "node ./scripts/test/themes-validator.mjs"
}
}
Built-in npm features like pre* and post* hooks mess up our perfect naming system a bit. A prebuild:themes task reads fine but predocs:generate is a bit marble-mouthy. But that’s an eye-twitching discrepancy I’m willing to accept to abide in what npm gives me for free.
Then… if you’ve added Conventional Commit to your scripts/ and your "scripts" and are living that agentic coding life, why not apply the same organization principle to your skills/ folder?
skills/
├── build/
│ ├── themes/SKILL.md
├── docs/
│ ├── component-jsdoc/SKILL.md
│ ├── storybook/SKILL.md
│ └── technical-writing/SKILL.md
└── test/
├── playwright-tests/SKILL.md
├── visual-tests/SKILL.md
└── writing-tests/SKILL.md
This looks great… it falls apart in the real world. Skills installed from the internet don’t abide in this format. Having some skills nested and others not creates a feeling of unease, so a flatter folder structure for skills is probably better. That said, it’s not uncommon to need a skill to inform your model about your repo-level scripts and those are places you can still maintain parity.
skills/
├── build-themes/SKILL.md
├── docs-storybook/SKILL.md
├── docs-components/SKILL.md
├── docs-technical-writing/SKILL.md
├── grill-me/SKILL.md
├── i-have-adhd/SKILL.md
├── test-playwright/SKILL.md
├── test-visual-review/SKILL.md
└── test-writing/SKILL.md
While applying Conventional Commit to your skills/ folder is a bit impractical, one area I can confirm it does help is organizing your agent chats. Fire off a handful of agents and it can get confusing about which one is doing what task, even with their autogenerated chat label. I’ve found it super helpful to ask your bot to rename its chat using the Conventional Commit format.
Try it out and bask in the uniform list of tasks. To get this to work consistently in the GitHub Copilot app, I had to modify my session prompt in Sessions > App instructions:
At the start of every new chat, before answering my first request,
call `rename_session` using `type(scope): description`. Do this even
for non-coding chats. Rename only once unless I ask otherwise.
I’m not sure how it works in your preferred agent manager, but I assume you could write a skill or put it in your AGENTS.md, but I’d have a conversation with your coworkers first before codifying your obsessive-compulsions in the project repo.
Join me in my obsession
You might have read all this and said, “This is a man who –in a world of chaos– is looking to over-manage a small thing like title text so that he can feel in control of his own life!” and to that I’d say…
Probably.
I like to think I’ve just hit the point in my life where I don’t want to spend an single brain cell thinking about how I should format title text or name a file. I wasn’t this passionate about Conventional Commit a year ago, I think I got radicalized. Generative AI almost certainly plays a big part where every robot names files and fixes different every time. That’s built-in to its dice-roll mechanic. Knowing there’s a chaos agent in the mix, I feel a need to find low-hanging fruit for consistency and (for right now) this feels pretty good to me.