tldr: Semver dictates three version fields with specific definitions. At least one of those fields as defined is always useless to everyone except in a very rare circumstance. Another of those fields as defined is always risky to trust except in a very rare circumstance. Semver itself is silly. Better tooling is great but won't fix that.

...

This article starts with the wrong assumption that semver is itself meaningfully correct and useful. Semver is neither of those things except in a very specific circumstance that almost nobody has.

Semver violations are common because:

1. Semver as defined by semver.org is founded on the fundamentally false/flawed premise that you can predict whether a change will break compatibility. You can't. You should still try, but you should also recognize that sometimes you will guess wrong. And sometimes when you guess right you will still be wrong. The notion that just because you didn't add/remove an input or output parameter means you didn't change your API is extremely naive.

But this isn't the important reason. The important reason is...

2. Even if that weren't the case, it still would only make sense for approximately 0% of all versioned software projects, because almost no projects in the wild continually preserve separate feature branches with backported bug fixes, and continually preserved feature branches with backported bug fixes is the only scenario where semver's separation of features that aren't expected to break compatibility and fixes that aren't expected to break compatibility makes any kind of sense, because there is no such thing as a clean separation between feature and fix.

I hope that the truth of #1 is obvious to everyone, but if it isn't please refer to https://xkcd.com/1172/
Less pithily, internal changes alter outward behavior literally all the time in unintentional ways, and no software project on the planet defines and constrains the expectations for its interface with sufficient clarity to avoid that. Not one. It's probably not even possible.

As for #2, consider the following scenario...

You have released version 1.0.0 of something. Then you add a feature and fix a bug unrelated to that feature. Are you at version 1.1.0 or 1.1.1? Well, it depends on the order you added your changes, doesn't it? If you fixed the bug first you'll go from 1.0.0 to 1.0.1 to 1.1.0, and if you add the feature first you'll go from 1.0.0 to 1.1.0 to 1.1.1. And if that difference doesn't matter, then the last digit doesn't matter.

The problem here is that Semver treats feature and fix as fundamentally distinct from each other. But non-breaking is non-breaking. The user on the other side of the fence wants the best version of whatever you have that doesn't break compatibility with what they're doing. If they trust you to make that distinction, then they only care about your major versions. If they don't trust you to make that distinction, then they only care about strictly matching the whole string. If you trust yourself (lol), you can cater to both groups with two-part major.minor. If you don't trust yourself, you can just use a single value that increases over time. But semver has three fields, and one of those fields is basically always completely useless except to satisfy a contractual obligation.

There is only one scenario where three version fields matter, and that's when a government defense contract forces you to fork development at the start of each project and then maintain separate code branches for separate projects, where they only get the features they ask for and you only fix the bugs they ask you to fix (this is exactly how defense contracts work), and you laboriously backport bug fixes to all of them, and the major and minor versions indicate the point of the fork, and the patch version is all the changes applied to that fork.