At any given point we have several customer projects running in parallel and a handful of products of our own, and one team moving between all of them. That is only survivable because most of the questions you would otherwise ask on every project already have answers by the time the repository exists. How the code is formatted. What a commit message looks like. How a branch gets built, versioned, and shipped. Where things live.
None of those answers is interesting. Every one of them is expensive if you arrive at it twice.
That is the whole argument, and it splits cleanly in two. Most of what a project needs has already been specified by somebody else, carefully, years before we got there, and the correct response is to adopt it without editing. The remainder is specific to how we build, nobody outside has written it down for us, and so we write it down once and depend on it everywhere. A question mark for the first half, an exclamation mark for the second.

The question mark: the things we refuse to build
Three of the posts on this blog are, underneath, the same post. oidc-token-cli is one OIDC client so that kubectl, frpc and CI stop each having their own. Killing GitHub PATs trades an OIDC token for a short-lived GitHub credential. Killing the ArgoCD token does the same trade against ArgoCD, which already speaks OIDC through the Dex it ships with. In every one of them we wrote no protocol. The work was arranging existing pieces so that a specification somebody else had already written could do its job.
That is deliberate, and it is not humility. A protocol like OIDC has had more than a decade of people whose entire job is finding what is wrong with it. Every plausible mistake in it has been made in public, argued about, and fixed. Anything we invented in its place would start that clock at zero, and we would be the only ones running it.
The rule we actually apply is a question about the problem rather than about the solution: is this specific to us? Authentication is not. Moving bytes over a network is not. Packaging a process so that a scheduler can run it is not. Deciding what a version number means is not. Every one of those has a standard, and the standard is better than what we would produce, not because we are bad at it but because we would be doing it on the side while building something else.
The exclamation mark: the things we insist on building
The other half is everything nobody outside could have written for us. Not because it is clever, but because it is ours: which lint rules we run, how a commit message is shaped, what a repository looks like on the day it is created.
For JavaScript and TypeScript that lives in js-commons, which publishes four packages: an ESLint base config, an ESLint React config, a Prettier config, and a commitlint config. Adopting them is one line each.
{ "prettier": "@abinnovision/prettier-config", "commitlint": { "extends": ["@abinnovision/commitlint-config"] }}💥 Commons for the JavaScript ecosystem (includes ESLint configs, Prettier config, etc.)
The claim is not that our rules are better than anybody else’s. Most of them are somebody else’s, with a handful of decisions taken on top. The claim is that they are the same everywhere, that changing them is one pull request rather than a search across every repository, and that nobody has to have the semicolon conversation a second time.

The same instinct produced nestjs-commons and react-commons, one per ecosystem we work in regularly. The pattern is always the same: the moment a decision stops being about one project and starts being about how we work, it becomes a package with a version and a changelog, rather than a paragraph in a document that somebody has to remember to apply.
That distinction matters more than it sounds. A convention written in prose is re-applied by hand in every repository, and it drifts the first time somebody is in a hurry. A convention in a dependency arrives with the install and is updated the same way everything else is.
The delivery pipeline is a dependency, not a file
The furthest we take this is CI. The workflow file in a project contains no build steps at all. It is a call into a shared reusable workflow, pinned to a version, with a handful of inputs describing what this particular project is.
💥 Reusable GitHub Actions and workflows for Node.js/polyglot monorepos, GitOps deployments, releases, and tooling setup (GCP, K8s, Node).
Everything the pipeline actually does, installing, linting, testing, building the image, cutting the release, deploying it, lives in one place and is versioned like any other dependency. A project upgrades its entire delivery pipeline by moving a reference. When we improve how images are built, every repository that has already moved gets the improvement without being touched.
This is the highest-leverage standard we have, and also the one with the sharpest edge. A shared pipeline means a bad change lands everywhere at once, so it has to be versioned properly and upgraded deliberately, project by project. That cost is real. It is still much smaller than maintaining a slightly different pipeline per repository, each of which was correct on the day it was written and has been quietly rotting since.
The boring layer is where standards actually pay
The parts of this that pay the most are the parts nobody would write an essay about.
Commit messages follow a convention, and that convention is enforced twice, once locally before the commit is made and once again in CI, because a rule that is only enforced on one side is a rule people learn to route around. Because commit messages are structured, version numbers and changelogs are derived from them rather than decided by a person. Task names are the same in every project, so building, checking and testing are the same three commands no matter which repository is open.
None of that is a technical achievement. That is exactly the point. These are decisions with no upside to revisiting: there is no version of “how should this commit message be shaped” where a project-specific answer is worth anything. So they get made once, and then they stop existing as decisions at all.
The compound effect shows up when somebody moves between projects. There is no per-project knowledge to acquire before being useful, because the things that would have been per-project knowledge were made identical on purpose.
Standardise when it starts to hurt
The honest objection to all of this is premature standardisation, and it is a good objection. You cannot generalise a shape you have built once. Extract too early and you get an abstraction fitted to a single case, which every later project has to work around, and working around a shared abstraction is more expensive than the duplication it replaced.
The rule we use is not a count. Copying something three times is fine if the three copies stay identical, because identical copies cost close to nothing: they are easy to read, easy to grep for, and easy to change together. Duplication only starts charging you when the copies begin to disagree. That is the moment the same question has two different answers in two places, and somebody has to know which one is current.

That splits the work into two layers with very different timing. The mechanical layer, formatting, linting, commit format, CI, is safe to standardise on day one, because there is no legitimately project-specific version of it. Nobody’s product is better because its Prettier config is bespoke.
Anything that encodes a design opinion waits. How a feature is structured, where a boundary sits, what an abstraction should be called: those are the parts where being wrong is expensive and where the second and third project teach you something the first could not. We would rather carry that duplication for a while and extract something true later than extract something plausible now.
Standards stopped being advice
There is a newer reason to write this down, and it changed the arithmetic more than we expected.
For a long time a written convention was a suggestion. Anything mechanical could be enforced by a linter and therefore was, and everything above that line lived in a document that people read once during onboarding and then approximated from memory. The document had a real cost and a soft benefit, which is why so many of them are out of date.
Coding agents changed which side of that ledger the document sits on. A written convention is now an input. It determines what gets produced rather than describing what should have been. The same file that used to be documentation is now closer to configuration, and the payoff for having written it precisely arrives immediately instead of at the next onboarding.
Two things follow. The first is that vague conventions are now actively expensive: “keep components small” was harmless as advice and is useless as an instruction, while a rule that says exactly where a component lives and what it is called is worth writing down. The second is that the old advice holds harder than before. Anything a linter can check belongs in the linter, not in prose. A deterministic check costs nothing to run and never gets skipped, and every line of prose competes for a finite amount of attention, whether the reader is a person or not.
Which lands back where the rest of this started. The reason to standardise was never that uniformity is a virtue. It is that a decision made once and encoded somewhere it will be applied automatically is a decision you never have to spend anything on again.
What it buys
Concretely: one team moving across several products without relearning how anything works, a new repository that can be built and deployed on the day it is created, and a codebase we can hand over without a week of explanation attached to it.
None of that comes from being clever about standards. It comes from adopting the ones that already exist, writing down the few that could only have come from us, and refusing to answer the same question a second time.
