I (along with Natalie Weizenbaum) wrote the package manager that Dart uses[1]. It's based on semver. It's good linkbait to characterize semver as broken, but not at all true. Semver works fine when you realize what it is.

[1]: http://pub.dartlang.org/

Semver is a way to communicate between people about compatibility expectations they should have between two versions of a package. When library maintainer says "this version changes from 1.2.3 to 1.3.0" that tells something useful to a human consumer of that package.

It absolutely does not communicate anything precise in the language of machines. It's not a formal system, and compatibility isn't formally defined. If you want it to be that, you're setting yourself up for hearthbreak.

Code reuse is a social process. Understand that semver is a tool at that level and you'll be pretty happy with it. In my mind, the most important feature of semver is that by associating pre-arranged semantics to versions, consumers can specify version ranges before those versions exist.

If I'm using foo 1.2.3, I can depend on foo >=1.2.3 <1.3.0 with decent confidence even though 1.2.4 may not even exist yet. That sort of forward compatibility makes the package ecosystem a lot more resilient to changes over time.

Part of the major issues with semver is that patchs could
break. A feature could be "fixed", but a consumer could
have relied on that very buggy feature, and patching it
would break their app.

This is true. One man's bug is another man's feature. If you want to be fully strict, all of the behavior of a program is significant which means you can never release anything that isn't a breaking change.

Being that rigorous isn't useful. Instead, realize that most patches don't break most users. For the ones that do, that's why you pin. You are pinning, aren't you?

Versions < 1.0.0 do not have semantic versions according
to semver. These are considered libraries "in development"
and developers could version however they see fit.

This part is straight up annoying especially as the web moves to a world where damn near everything is <1.0.0. On the Dart team, we've adopted a policy that for sub 1.0.0 packages, we do semantically version them. We just shift the numbers one to the right.

So, going from 0.1.0 to 0.2.0 is a breaking change, 0.1.0 to 0.1.1 is an API addition, and 0.1.0 to 0.1.0+1 is a patch.

It seems to work really well for us.

The current solution to the above problems is to pin all
dependencies. However, this is absolutely stupid and
annoying to me. ... When every library pins, you're going
to have a lot of duplicate dependencies, even if they're
the same version!

No no no no no.

You don't pin versions in libraries, you pin them in applications.

Almost every package in the dependency graph should be using version ranges. That avoids version lock and makes it easier to satisfy shared dependencies. It also means libraries don't all have to bump patches that just change dependency versions.

But, in your application, in the root of the dependency graph, you pin everything that you're using right now. You check that in to ensure that everyone on your team and every machine gets the same versions of all of the dependencies.

You get good code reuse and deterministic builds.

This is exactly why Bundler separates out the lockfile from the Gemspec. It's unintuitive at first, but it works better than any other system I've seen once you grok it.

There is one think in semver that I think is totally inane. The latest version of the spec says build suffixes have no relative ordering. We don't do that in pub because it makes no sense. If a user has uploaded foo 1.0.0+a and foo 1.0.0+b, which version should an application get that depends on foo? Pick randomly?