To summarize, any change that offers more (as in, more fields in the output) and/or requires less (more of the original parameters made optional) is non-breaking.
Edit: Of course, this is all under the assumption that the semantics of methods should never change. Instead of changing semantics, he proposes to just create a new method with a new name.
A) you generate an export of the public API (entrypoints, arguments, types) in some stable format.
B) you run the (public-api) testsuite of the previous release against the current release.
- If A yields no changes wrt the previous release, then B should probably succeed as well (barring changed tests). Bump the patchlevel.
- If A has changes but B succeeds, it's a minor version bump.
- If A has changes and B reports failures, it's a major version bump.
Of course, this isn't fool-proof, so you still want some way of overriding the automated version bump. But I think it's reliable enough to at least force developers to think about the changes they're making. Of course, it does rely on having a testsuite, and explicitly marking parts of it as public-api validation.
Harder to detect cases in which the signature stays the same but the expected behavior changes, though (as noted in TFA).
But yeah, there are counter-examples, and anyway a user can expect a behavior to remain even if it is not explicitely specified. For instance, I developed a SAT solver. I could make a non-breaking change that improves the efficiency of the solver in like 99.9% of the cases, because of a cool heuristic. That would be considered a "patch", since I didn't change the API at all, and all unit tests are still working perfectly. But a former user could be pissed off because he's part of the 0.1% and now the tool became just a little too slow for his use. Is that a breaking change or not?