Breaking Change Detection
Purpose
Standardize how a change is classified against Semantic Versioning by reasoning about its effect on consumers, including behavioral breaks that leave the syntax intact. Humans get binary compatibility right only about 60% of the time; this methodology closes the gap with an explicit decision matrix and per-ecosystem catalogs. Tech-agnostic.
First: is the change on the public surface?
Only changes to the public API surface can be breaking or minor. Establish the surface before classifying (detailed per ecosystem in references/breaking-change-catalog.md):
| Ecosystem |
Public surface |
| JS/TS |
package.json exports/main, named/default exports, .d.ts |
| Python |
__all__, non-_ names, entry points |
| Go |
Capitalized identifiers outside internal/ |
| Java/C# |
public/protected members of exported packages |
| Rust |
pub items reachable from the crate root |
| CLI |
flags, subcommands, arg order, exit codes, stdout format |
| HTTP |
routes, methods, request/response schema, status codes |
| GraphQL |
types, fields, args, nullability, enum values |
| Config |
recognized keys, env vars, defaults |
A change entirely within private/internal code is at most a PATCH.
The decision matrix
| Change |
Classification |
| Remove/rename a public export, field, route, flag, env var |
MAJOR |
| Change a signature (add required param, reorder, retype, change return type) |
MAJOR |
| Change a default value that alters observable behavior |
MAJOR |
| Make validation stricter (reject formerly-valid input) |
MAJOR |
| Change output shape, field names, units, or serialization |
MAJOR |
| Change/narrow error or exception types, or HTTP status codes |
MAJOR |
| Remove an enum value or change its meaning |
MAJOR |
| Add a new public export/endpoint/optional param/flag |
MINOR |
| Add an optional field to output (additive) |
MINOR |
| Loosen validation (accept more input) |
MINOR (usually) |
| Deprecate without removing |
MINOR |
| Bug fix restoring documented behavior |
PATCH |
| Performance improvement, no behavior change |
PATCH |
| Internal refactor, docs, tests, CI |
PATCH / no release |
Aggregate rule: the single highest-severity change in the range sets the bump.
Behavioral vs syntactic breaks
Syntactic breaks (removed symbol, changed signature) are caught by compilers. Behavioral breaks are not and cause most surprise incompatibilities:
- Same signature, stricter validation → previously-passing inputs now throw.
- Same return type, changed field meaning/units (seconds → milliseconds).
- Same endpoint, changed default page size or sort order.
- Same function, changed default argument that flips behavior.
- Same enum type, a value's semantics changed.
Always read the diff of behavior, not only the diff of declarations.
Additional Resources
Reference Files
references/breaking-change-catalog.md: per-language and per-interface signals (JS/TS, Python, Go, Java, Rust, HTTP/REST, GraphQL, database schema, CLI): what is a break vs an additive change, with concrete examples.
references/semver-rules.md: a concise SemVer 2.0.0 summary, pre-1.0.0 rules, deprecation policy (deprecate before removing), and how to communicate breaks to users.
1---2name: breaking-change-detection3description: This skill should be used when the user mentions "breaking change", "semver", "semantic versioning", "major minor patch", "version bump", "backward compatible", "API compatibility", or "deprecation". Provides a decision matrix and per-ecosystem signals for reasoning about behavioral (not just syntactic) breaking changes.4---56# Breaking Change Detection78## Purpose910Standardize how a change is classified against Semantic Versioning by reasoning about its effect on *consumers*, including behavioral breaks that leave the syntax intact. Humans get binary compatibility right only about 60% of the time; this methodology closes the gap with an explicit decision matrix and per-ecosystem catalogs. Tech-agnostic.1112## First: is the change on the public surface?1314Only changes to the **public API surface** can be breaking or minor. Establish the surface before classifying (detailed per ecosystem in `references/breaking-change-catalog.md`):1516| Ecosystem | Public surface |17|-----------|----------------|18| JS/TS | `package.json` `exports`/`main`, named/default exports, `.d.ts` |19| Python | `__all__`, non-`_` names, entry points |20| Go | Capitalized identifiers outside `internal/` |21| Java/C# | `public`/`protected` members of exported packages |22| Rust | `pub` items reachable from the crate root |23| CLI | flags, subcommands, arg order, exit codes, stdout format |24| HTTP | routes, methods, request/response schema, status codes |25| GraphQL | types, fields, args, nullability, enum values |26| Config | recognized keys, env vars, defaults |2728A change entirely within private/internal code is at most a PATCH.2930## The decision matrix3132| Change | Classification |33|--------|----------------|34| Remove/rename a public export, field, route, flag, env var | **MAJOR** |35| Change a signature (add required param, reorder, retype, change return type) | **MAJOR** |36| Change a default value that alters observable behavior | **MAJOR** |37| Make validation stricter (reject formerly-valid input) | **MAJOR** |38| Change output shape, field names, units, or serialization | **MAJOR** |39| Change/narrow error or exception types, or HTTP status codes | **MAJOR** |40| Remove an enum value or change its meaning | **MAJOR** |41| Add a new public export/endpoint/optional param/flag | **MINOR** |42| Add an optional field to output (additive) | **MINOR** |43| Loosen validation (accept more input) | **MINOR** (usually) |44| Deprecate without removing | **MINOR** |45| Bug fix restoring documented behavior | **PATCH** |46| Performance improvement, no behavior change | **PATCH** |47| Internal refactor, docs, tests, CI | **PATCH** / no release |4849Aggregate rule: the single highest-severity change in the range sets the bump.5051## Behavioral vs syntactic breaks5253Syntactic breaks (removed symbol, changed signature) are caught by compilers. **Behavioral breaks are not** and cause most surprise incompatibilities:5455- Same signature, stricter validation → previously-passing inputs now throw.56- Same return type, changed field meaning/units (seconds → milliseconds).57- Same endpoint, changed default page size or sort order.58- Same function, changed default argument that flips behavior.59- Same enum type, a value's semantics changed.6061Always read the *diff of behavior*, not only the diff of declarations.6263## Additional Resources6465### Reference Files66- **`references/breaking-change-catalog.md`**: per-language and per-interface signals (JS/TS, Python, Go, Java, Rust, HTTP/REST, GraphQL, database schema, CLI): what is a break vs an additive change, with concrete examples.67- **`references/semver-rules.md`**: a concise SemVer 2.0.0 summary, pre-1.0.0 rules, deprecation policy (deprecate before removing), and how to communicate breaks to users.