
Semver Checks
io.github.kyungseopk1mPublished by GitHub account kyungseopk1mv0.17.0Updated Nov 7, 2026
Detect breaking changes in your TypeScript library's public API and recommend the SemVer bump.
Overview
Runs semver-checks as an MCP tool so an assistant can diff a TypeScript package's public API between two versions and get a recommended SemVer bump.
- What you can do
- Compare two versions of a TypeScript package and get a recommended major, minor or patch bump with per-change details
- Diff the public API surface, including removed or renamed exports, narrowed signatures, and added required parameters or properties
- Compare against npm specs, git refs, or local directories, and handle packages with multiple export subpaths
- Inspect the API surface of a current or past version as a snapshot
- Report findings as text, JSON, Markdown, or CI annotations
- When to use it
- Use it before publishing a TypeScript library release, to check whether the intended bump matches what the type signatures actually changed. Also useful when reviewing a pull request or a release pull request that declares a bump, or when investigating why a dependency upgrade broke downstream builds.
- Requirements
- Runs locally over stdio via npx, so Node.js ^20.0.0 or >=22.0.0 is needed (Node 21 is excluded). Local path and git ref inputs need a tsconfig.json and TypeScript sources in the analyzed project; npm specs need nothing extra. Network access is needed to fetch published tarballs from the npm registry. No accounts, API keys, or environment variables are declared.
Installation
In SourceWeft
- Open Semver Checks in the dashboard and add it to a workspace.
- Enable the server for the chats that should use its tools.
Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.
Other MCP clients
Follow the launch instructions in the repository.
README
[npm version] [CI] [License: MIT] [Node.js Version]
semver-checks
pino 10.4.0, a minor release, added export { pino as default, pino } inside its pino namespace. That turns off implicit export for 9 members of the namespace, so const t: pino.TimeFn = () => '' compiles against 10.3.1 and fails against 10.4.0 with TS2694, "Namespace 'pino' has no exported member 'TimeFn'".
Run with no arguments in a publishable package directory, npx semver-checks compares <name>@latest against .. This default is CLI-only: the GitHub Action and the MCP semver_compare tool need old explicitly.
The output is trimmed to the bump counts and three of the 9 removed exports (... marks the cut, and stderr warnings are omitted); the full run also lists the other six, two required properties added to MultiStreamRes, the review-only findings and the additions.
semver-checks compares the TypeScript declarations on both sides and recommends the bump the type changes require, so the answer comes from the API rather than from the commit message. Neither side needs a checkout: each can be an npm spec, a git ref, or the working tree.
The same check as a pull request gate, which annotates the changed lines and fails on a proven break:
Every input, and the release-pull-request variant that grades the declared bump and posts the verdict as a comment, are under GitHub Action.
Why semver-checks?
A bump is a claim about what a release did to its public API, and it is usually written by hand, from memory, at the end of the work. A refactor that drops a required export reads as a patch to the person who wrote it and as a broken build to everyone downstream.
Release tooling can only act on the evidence it is given. semantic-release derives the release type from commit conventions, and changesets records an author-written release note with the bump declared in its frontmatter. Both inherit whatever the author believed at the time.
semver-checks reads the public API directly using ts-morph and decides the bump from what the type signatures did.
The example above is not constructed. Run the tool across real releases and it finds breaking type changes that shipped as minors and patches. It is most dependable on structural changes, which is to say removed or renamed exports, narrowed signatures, and added required parameters and properties. Equivalence-preserving type rewrites are a known weak spot it can over-report, and docs/accuracy.md says exactly where to trust it and where not to.
The reverse of that change is not a break: narrowing User | null down to User hands every consumer a value they were already prepared for, and semver-checks reports it as return-type-narrowed, a MINOR.
semver-checks is complementary to your existing release workflow. Use it as a verification step before publishing — it tells you whether your intended bump is safe, or whether you're about to ship a breaking change by accident.
Quick Start
Compare a git tag to the latest commit:
Compare the published npm release against your working tree — answers "is my current change a breaking release?" without needing git tags:
A <package>@<version> argument is fetched from the npm registry (via npm pack) and used as the old version. Concrete versions, ranges, and common dist-tags are auto-detected ([email protected], your-package@^1, your-package@next). For an uncommon dist-tag, make the intent explicit with the npm: prefix or --old-as npm (npm:your-package@my-custom-tag) so it isn't mistaken for a git ref.
Compare two local directories:
Existing relative paths without a ./ prefix are also treated as local directories:
If a git ref collides with an existing path name, force ref interpretation explicitly:
Output as JSON, Markdown (for PR comments), or GitHub Actions annotations:
Fail in CI if breaking changes are detected (exit 1):
Inspect the API surface of the current or a past version:
Every flag is in docs/cli.md.
Multiple entry points
When package.json declares an "exports" map with several subpaths, every
subpath with a declared .d.ts entry is extracted and compared independently.
Adding a subpath is a MINOR change and removing one is MAJOR; a change inside a
subpath is reported with a # separator (e.g. ./utils#helper). No flags are
needed — the map is auto-detected.
For projects without an "exports" map, pass multiple entries explicitly by
repeating --entry or comma-separating them:
Example output
The second finding is a renamed parameter and an added optional one, which oblige nobody — but call signatures are compared as printed text, so the tool cannot prove that and says so instead of guessing. --strict exits 1 on the confident break only; the review-only item passes the gate unless you opt into --strict-review.
CI Integration
GitHub Action
semver-checks ships a reusable composite action. The most ergonomic setup compares the published latest release against the PR's working tree, so it needs no git tags and posts inline annotations on the diff:
A full example lives in examples/github-actions.yml.
On a release pull request
Does the bump this release declares cover what its API surface did? @clerk/break-check and semvet also score the declared bump from the two package.json versions; reading it from .changeset files is what is specific here. declared: auto reads the bump from .changeset/*.md and falls back to the two package.json versions, and comment: true puts the verdict on the pull request instead of leaving it in the log.
The step fails when the declaration understates a proven break, and passes with a note when it only understates an addition. token defaults to github.token; pass it explicitly if your setup needs a different one. A pull request from a fork gets a read-only token, so the comment is skipped with a warning there rather than failing the step.
The comment is posted with gh pr comment --edit-last --create-if-none, which updates the token owner's most recent comment on the pull request. It does not look for a marker of its own, so if the same token posts other comments from other workflows, run this one under its own token or leave comment off.
Without the action
Run the CLI directly — for example, compare the published release to the working tree:
Or compare against a git tag:
Accuracy
Every breaking change carries a confidence. proven means the change is its own evidence, or a type relation the analyzer resolved and found genuinely unrelated; --strict fails on those and only those. heuristic means a conservative MAJOR it could not prove — surfaced for review, off the default gate, unless you opt into --strict-review.
The scorecard uses tsc as its oracle rather than the author's published bump, because the tool exists on the premise that authors get that bump wrong. 111 adjacent minor/patch release pairs across 24 packages, each with a consumer program compiled against both sides:
A name-and-arity baseline with no type resolution scores 87.5% / 32.6% on the same corpus. Leave --strict on for every push, and run --strict-review where a person reads the output before it ships.
docs/accuracy.md has the rest: how the corpus was built, the 7 pairs --strict stays quiet on and why each one, the single remaining false positive, what the variance probe refuses to answer, and the eleven known limitations.
Programmatic API
You can also extract a snapshot independently:
Reference
MCP server
semver-checks ships as a Model Context Protocol server, so an agent can call it as a tool:
Two tools: semver_compare and semver_snapshot. Setup for other clients and the full argument schemas are in docs/mcp.md.
Comparison with Other Tools
semver-checks is a verification layer, not a release tool. Use it alongside semantic-release or changesets to check whether the declared bump matches the API changes.
How it differs
ts-semver-checks, @clerk/break-check and semvet each compare a local build against a baseline. ts-semver-checks and semvet use compiler assignability; ts-semver-checks takes the bump you expect through --expect and has no Action or MCP server, while semvet scores the declared bump from package.json. @clerk/break-check diffs API Extractor snapshots, adds an LLM review pass, scores the declared bump from package.json, and handles multi-package monorepos.
semver-checks can compare two published npm releases directly (compare pkg@x pkg@y), reads the declared bump from .changeset files, ships an MCP server, and publishes its accuracy numbers in docs/accuracy.md.
How It Works
- Extract: Parse old and new TypeScript source files using ts-morph, building a typed API snapshot (functions, interfaces, enums, classes, type aliases, variables, namespaces)
- Diff: Compare the two snapshots symbol by symbol — detect additions, removals, and signature changes
- Classify: Assign each diff a
major,minor, orpatchseverity - Report: Return a structured
SemverReportwith the recommended bump and per-change details
For git ref comparisons, the ref is extracted to a temporary directory via git archive, dependencies are installed there, and the directory is cleaned up afterwards. For npm specs, the published tarball is downloaded with npm pack and extracted to a temporary directory, with no dependency install, because the tarball already bundles its build output. Local path comparisons install nothing by default. --install-deps (or installDeps: true) overrides that and runs the install on both sides whatever their source kind — it is there for the local-path case, which is the only one that needs it.
FAQ
Will semver-checks catch every semver violation?
No. It catches API surface changes that are mechanically detectable from TypeScript's static type system: removed exports, signature changes, type changes, optionality changes, and similar structural changes. It does not detect behavioral changes, documentation changes, or changes hidden behind conditional compilation. When a package ships distinct ESM and CJS declaration files for the same entry point (for example, divergent import.types and require.types), only one surface is analyzed, so a break confined to the other surface can be missed. See docs/accuracy.md.
Does it have false positives?
Yes. It errs toward over-reporting MAJOR rather than missing a break, but the default CI gate only fails on proven breaks. Parameter and return type changes go through a structural assignability check, so widened parameters, narrowed returns, and equivalent rewrites such as readonly T[] vs ReadonlyArray<T> avoid false majors. Type aliases and variables still have conservative cases because they are compared as normalized serialized text first, and the probe upgrades that to a resolved relation only where it can. The concrete patterns are listed under Known limitations.
Does it support default exports?
Yes. A default export is extracted under the symbol name default and compared like the corresponding function, class, or value declaration.
Can I compare against a published npm version?
Yes. Pass a <package>@<version> spec and semver-checks downloads that release from the registry with npm pack, extracts the tarball, and analyzes its bundled .d.ts declarations:
Because a published tarball ships compiled .d.ts files while your working tree ships .ts source, type representation can differ slightly between the two sides (TypeScript materializes some inferred types in declarations). Removals, additions, and signature changes are detected reliably; a handful of equivalent-but-reworded types may show up as a noisy diff. Comparing two published releases (.d.ts vs .d.ts) avoids that asymmetry.
Can I use it without a tsconfig.json?
Not for local-path or git-ref inputs: those are opened as a TypeScript project and need a tsconfig.json at the project root, or at the root inferred from the exports field in package.json. Published npm inputs are different — a permissive config is synthesized when the tarball does not ship one.
What happens if the analyzed project has TypeScript errors?
semver-checks will print a warning to stderr listing up to 5 errors and continue. Results may be incomplete if type errors affect the API surface. Set SEMVER_CHECKS_VERBOSE=1 for full diagnostics.
How is the entry point determined?
semver-checks looks for the entry file in this order:
- The
--entryflag if provided - The declaration under
exports['.']inpackage.json— every condition is walked (types,require/import/node/browser/module/default, nested, and fallback arrays), and.d.ts/.d.mts/.d.ctsare all accepted. A bare-string"exports": "./index.js"or a flat conditions object"exports": { "types": "./index.d.ts", "default": "./index.js" }(no.subpath key) is treated as the.entry, so itstypescondition is read; for a published package, a.jstarget is also checked for a sibling declaration of the same name. A subpath-only map with no.key is left without a root entry (no fabricated root) - The top-level
typesortypingsfield inpackage.json src/index.ts, thenindex.ts, then a conventional rootindex.d.ts/.d.mts/.d.ctsas fallbacks
If none of these resolve, pass --entry explicitly.
When a project ships an "exports" map with several subpaths, each subpath is resolved and compared independently (see Multiple entry points).
Does it work with monorepos?
Yes. Point --entry at the package's entry file, or run the CLI from that package's directory. A monorepo tag shaped like [email protected] reads as an npm spec to auto-detection, so pass --old-as ref for those.
Requirements
- Node.js
^20.0.0 || >=22.0.0(Node 21 is excluded by a transitive dependency and is itself end of life) - For local path / git-ref inputs: a
tsconfig.jsonand TypeScript source files (.ts/.tsx) in the analyzed project - For npm specs: nothing extra — the tarball's bundled
.d.tsdeclarations are analyzed, and atsconfig.jsonis synthesized if absent
Dual module support
semver-checks ships both CommonJS and ES module builds:
Contributing
Contributions are welcome. Please read CONTRIBUTING.md before submitting a pull request.
License
MIT. See LICENSE.
Author
Kyungseop Kim — @kyungseopk1m
Source: README.md at commit 9a9fc3e
Tools
0Version history
7- v0.17.0LatestNov 7, 2026
- v0.16.0Nov 1, 2026
- v0.15.2Oct 23, 2026
- v0.15.1Oct 23, 2026
- v0.15.0Oct 4, 2026
- v0.14.0Oct 3, 2026
- v0.13.0Sep 16, 2026
Score
50% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 days25 pts
Keep the endpoint answering the MCP handshake; SourceWeft probes it every 30 days.
- Auth declared10 pts
Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts