Capabilities
Release Checks & Blast Radius
What a Bumpkit release check proves, every checker and finding kind, verdicts, feedback, PR annotations and blast radius through the code graph.
Commit messages don't tell you what a release really changes; a release check reads the code and proves it, for engineers and reviewers deciding whether to ship.
A release check reads the diff between two versions and reports what it can prove: removed APIs, destructive migrations, committed secrets, vulnerable dependencies, untested code and more. Each finding points at a file and line. The Release Gate then applies your bumpkit.yml policy to those findings.
Run a check
The dashboard's Checks page has three tabs. Decisions lists saved checks and gate outcomes, one row per release. Run a check checks a selected open pull request in a connected repository, or tries an example or pasted diff. Stacks shows pull requests based on another open pull request's branch.
bumpkit check --base v1.4.2
bumpkit check --head working-tree
bumpkit check --base v1.4.2 --json--base defaults to the latest tag. --head defaults to HEAD; working-tree includes uncommitted work. The command prints the verdict, the suggested version, a summary and each finding, and exits 1 when the verdict is block.
List and reopen past checks:
bumpkit checks list --repository acme/api --limit 20
bumpkit checks view CHECK_IDThe same check is available as POST /v1/checks, GET /v1/checks and GET /v1/checks/{id}. See the CLI Reference and API Reference.
How a check works
- Deterministic checkers read the diff. They never guess: a finding is either proven by the diff or not reported.
- New or upgraded dependencies are looked up in the OSV vulnerability database.
- Bumpkit's AI review summarizes the release and lists behavior changes the checkers didn't cover. These findings are marked
inferenceand never decide the verdict or the gate. - Bumpkit suggests the next version from the higher of two signals: the commit messages and the code itself.
The code-based bump is major when a deterministic checker proves a break, minor when a new route or a new public export appears, patch for any other source change, and none when no source code changed.
If the diff is too large to read in full, the summary says the check is incomplete.
Findings
Every finding has these fields:
| Field | Values |
|---|---|
checker |
The checker that reported it (see below) |
kind |
What it is, for example breaking or secret |
severity |
high, medium, low or info |
confidence |
high or medium |
evidence |
deterministic (proven by the diff) or inference (the AI review's reading) |
file, line_start, line_end, snippet |
Where it is |
fingerprint |
A stable ID used to suppress false positives across checks |
Secrets are masked in snippets.
Checkers and finding kinds
Checkers skip generated files (lock files, vendored code, dist/, build/, .next/, *.pb.go, *.min.js and similar) and, except where noted, test files and sample code (examples/, docs/, testdata/, fixtures/, bench/ and similar).
| Checker | Kind | Severity | What it proves |
|---|---|---|---|
exports |
breaking |
high | A public function, type or constant was removed or its signature changed incompatibly, in any supported language (for example Go outside internal and cmd directories, TypeScript and JavaScript exports, Python def and class, Java public members and Rust pub items). Moving a symbol within its package, or adding an optional or variadic trailing parameter, is not reported. |
routes |
breaking |
high | An HTTP route was removed: Go Handle/HandleFunc and router methods, Express-style routers, Python decorators, deleted Next.js app/**/route or pages/api files, and the common frameworks of every other supported language except C and C++ (Rails, Laravel, Spring, ASP.NET, Phoenix, actix-web, Vapor and more; see Supported Languages). |
routes |
feature |
info | A new HTTP route was added. |
migrations |
data_loss |
high | A migration drops a table or column, or deletes every row (TRUNCATE, DELETE FROM without WHERE). |
migrations |
breaking |
high | A migration renames a column or table, or adds a NOT NULL column without a default. |
migrations |
migration |
medium / low / info | Changes a column type or sets NOT NULL (medium), creates an index without CONCURRENTLY (low), or adds a new migration file (info). Indexes on tables created in the same migration, or in another migration in the same change, are ignored, including UNIQUE, ON ONLY and USING indexes split over several lines. |
dependencies |
dependency |
low / medium / info | A dependency was added (low), changed major version (medium) or removed (info) in go.mod, package.json, requirements*.txt, Cargo.toml, Gemfile, *.gemspec, composer.json, Gradle files and version catalogs, pom.xml version properties, *.sbt, *.csproj, *.props, packages.config, pubspec.yaml, mix.exs, Package.swift, CMakeLists.txt, Conan or vcpkg.json files. |
vulnerabilities |
vulnerable_dependency |
high / medium / low | A new or upgraded, exactly pinned Go, npm, PyPI or crates.io dependency has known OSV advisories. Critical advisories are reported as high. The detail lists each advisory, its CVE and the version that fixes it. |
vulnerabilities |
vulnerability_check_unavailable |
medium | The vulnerability database didn't answer, so the dependencies couldn't be checked. |
secrets |
secret |
high or medium | A committed credential: private keys, cloud, CI, registry and AI provider tokens (60 rules), database passwords in connection strings, and high-entropy values assigned to credential-named variables. Committing a real environment file (such as .env) is also high. |
secrets |
secret_sample |
medium | The same, but in a test, sample, fixture or example file. There only values in a real provider key format count: high-entropy values assigned to credential-named variables and database passwords in connection strings are treated as test fixtures and not reported. |
security |
security_regression |
high / medium | The change is less secure than the base. See Security regressions. |
sensitive |
risk_area |
medium | The release changes authentication, authorization, billing or cryptography code, detected from path words and corroborated by the file's content. |
tests |
untested |
medium | At least 40 lines of code changed and no test file did. Generated files don't count toward the lines or the files it names: sqlc output, *_templ.go, *.pb.go and files with a Code generated ... DO NOT EDIT. header. |
config |
config |
low | The code starts reading a new environment variable that must be set before deploying, such as os.Getenv, process.env, os.environ, ENV[...], System.getenv or env::var. |
llm |
behavior |
low / info | A behavior change the model found in a file no checker covered. Always inference. |
backports |
missing_backport |
high | A fix shipped in a recent release tag isn't on this branch. Added by bumpkit gate; see Hotfixes. |
blast_radius |
blast_radius |
low / medium | The change reaches a product area through the call graph. Added by the CLI; see Blast radius. |
Security regressions
The security checker compares the head with the base and reports a security_regression when the change is less secure than what it replaces. It reads code only: comments, docstrings and text inside strings don't count, except CORS headers and cipher names written as strings. A call wrapped across several lines is read as one statement, so reformatting doesn't look like a change.
It doesn't report:
- Weaknesses that were already there and didn't change, or a setting moved unchanged to another file or function.
- Changes in tests, docs or sample code.
- A guard renamed, moved into middleware, or moved to a base controller, or a route removed along with its guard.
- New anonymous endpoints such as a login page or health check.
Each finding names the file and line, and for a removed protection, the line where it was at the base. A file shows at most five findings of the same kind, with a count of the rest.
High severity, which blocks the release under the default block_on:
- Authentication or authorization removed from a route, path, view or action that still exists, or that was renamed in the same change: middleware, a guard decorator, annotation or attribute, or an authorization check.
- Anonymous access allowed on an existing route or action, such as
permitAll(),[AllowAnonymous]or@Public(). - TLS certificate verification turned off, such as
InsecureSkipVerify: true,verify=Falsepassed to an HTTP client,verify_mode = CERT_NONE,rejectUnauthorized: falseorNODE_TLS_REJECT_UNAUTHORIZED=0. - JWTs decoded without checking their signature, such as
verify_signature: Falseoralgorithms=["none"]. - CORS that allows any website and sends credentials, including reflecting the request's origin.
Medium severity, which asks for review:
- A removed auth guard when the same change adds a different one elsewhere, so a person can confirm the swap.
- Anonymous access or an auth-off flag added in new code.
- CSRF protection removed, turned off or exempted.
- A new route without authentication in a file where its sibling routes have it.
- Roles or permissions widened: an admin or owner role granted, or a wildcard permission or scope.
- Weak cryptography: MD5 or SHA-1 for passwords or signatures, DES, 3DES, RC4 or ECB mode, short RSA keys, a non-cryptographic random generator for tokens or secrets, or a lowered password-hashing cost.
- CORS that allows any website without credentials.
It reads Go, TypeScript, JavaScript, Python, Java, Kotlin, Ruby, PHP and C#. When the CLI sends a blast radius, each security regression also lists the product areas its changed code reaches.
Verdicts
The check's verdict uses deterministic, unsuppressed findings only:
| Verdict | When |
|---|---|
block |
Any high-severity secret, data_loss or security_regression finding |
review |
Any other high or medium finding |
ship |
Nothing above |
The verdict is the check's own opinion. Whether a release may merge is decided by the Release Gate, whose outcomes are pass, needs_approval and fail.
AI review coverage
Every check records what the AI review covered. You see it on the check page, in the pull request comment, in bumpkit checks view, in checks_view over MCP and as ai_review in the API.
| State | What was observed |
|---|---|
Reviewed (reviewed) |
The whole of every changed file went to the AI, and its answer listed each one as read |
Partly reviewed (partly_reviewed) |
Some files weren't covered. Each is listed with the reason |
Couldn't review (could_not_review) |
There is no usable AI answer. The reason says why: the model refused, a content filter stopped it, the answer hit its length limit, the provider didn't answer, the answer couldn't be read or didn't say what it read, the release didn't fit the AI budget, no provider is set up, or the check ran in fast mode. Bumpkit uses none of that answer |
A file is listed as not reviewed when it is binary or generated, was left out or cut off to fit the AI budget, had more than the first 150 lines of its diff left out, or wasn't in the answer's list of files read. The list is what the AI reported, not proof that it understood each file.
The state never changes the verdict or the default gate outcome. Blocking takes a proven finding. To ask for a person's approval when the review was partial or didn't run, add ai_review_incomplete to require_approval.
File names in the list follow your retention mode: with no stored file names, the check keeps the state, reason and counts only.
Finding feedback
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:finding_feedback.
Mark a finding real or a false positive so Bumpkit learns which checkers to trust:
bumpkit findings feedback FINDING_ID --false-positive --reason "Test key from the vendor docs"
bumpkit findings feedback FINDING_ID --real
bumpkit findings precision --days 90- A false positive marked on a release decision in the dashboard's Checks page suppresses that finding (matched by fingerprint) in later checks of the same repository. Suppressed findings stay visible but no longer count toward the verdict or the gate.
- Feedback sent with the CLI or
POST /v1/findings/{id}/feedbackis recorded for precision but doesn't suppress. bumpkit findings precisionandGET /v1/precision?days=Nshow each checker's measured precision over the last 1 to 365 days (default 90).
The reason is optional and must be under 500 characters.
PR line annotations
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:pr_annotations.
With the Bumpkit GitHub App, every deterministic, unsuppressed finding that has a file and line is posted on that line of the pull request as a check annotation:
| Annotation level | When |
|---|---|
failure |
High severity, and its kind is in block_on |
warning |
Any other high or medium finding |
notice |
Everything else |
Blast radius
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:blast_radius.
Blast radius shows which areas of your product a change reaches, call by call. It runs locally in the CLI, on your machine or in CI, so your code never leaves CI.
bumpkit blast-radius
bumpkit blast-radius --base v1.4.0 --head HEAD --jsonBumpkit reads the code at --head as a graph of what calls what, maps the changed lines since --base to the functions, methods and types they touch, and follows their callers to the entry points they reach. Each affected area comes with the call path and the file and line of every call.
bumpkit check (except with --head working-tree) and bumpkit gate run the same analysis and add one blast_radius finding per affected area, up to 50.
Languages
Blast radius reads all 15 supported languages: Go, TypeScript and JavaScript, Python, Ruby, PHP, Java, Kotlin, Scala, C#, Dart, Elixir, Rust, C, C++ and Swift. It's limited for C and C++: in large, template-heavy headers about half of the code-graph facts are missed. Release checks are unaffected, because they read diff lines.
Tests, type declarations (.d.ts), minified files and directories such as node_modules, vendor, dist, build, testdata, tests and virtualenvs are skipped.
How areas are named
Each reached file is assigned the first area that matches:
- Areas listed in
gate.blast_radius.approve_areas - Areas named in
require_approvalrules - Bumpkit's built-in risk areas:
authentication,authorization,billing,cryptography - The file's owners in
CODEOWNERS - Otherwise its top-level directory, or the first two segments under a container directory (
internal,pkg,src,lib,libs,app,apps,packages,services,cmd,modules), or(root)
Depth and confidence
gate:
blast_radius:
max_depth: 4
approve_areas: [billing, authentication]max_depthbounds how many callers deep Bumpkit follows, from 1 to 10 (default 4).- A
blast_radiusfinding is low severity, or medium when its area is inapprove_areas. When the change reaches an area inapprove_areas, the gate asks for an approval. - Confidence drops to medium when a call in the path was matched by method name rather than resolved exactly. The finding detail marks that hop "matched by method name", and
bumpkit blast-radiusnotes how many calls on the path were matched that way (by_nameandcandidatesin--json). Hops marked "at the base" are code that this change has since changed or removed.
The policy is read from bumpkit.yml at the base; pass --policy FILE to use another. See blast_radius.
Regression predictor
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:regression_predictor.
bumpkit predict maps the change's blast radius, then ranks each area it reaches by the repository's own history of incidents, regressions and findings. An area shows a failure probability only where Bumpkit has measured one on your releases; otherwise it shows a likelihood and the evidence, with no percentage.
bumpkit predict --base v1.4.0 --jsonIt is also available as POST /v1/predict. When a regression does ship, see Rollback & Time Machine.
Behavior-change and hotspot checks
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:behavior_checks.
These checks catch small changes in behavior that no type checker sees, in every supported language (Elixir skips guard checks). Every finding names the commit that introduced it (commit_sha, commit_subject).
| Checker | Kind | What it reports |
|---|---|---|
guards |
behavior_change |
A removed guard clause or early return |
error_handling |
behavior_change |
A removed error check, error path or error handling |
validation |
behavior_change |
A removed validation call or check, a loosened check, or a dropped .validate()-style call |
comparisons |
behavior_change |
A changed comparison boundary, such as < to <= |
defaults |
behavior_change |
A changed default value |
ordering |
behavior_change |
Calls that were reordered while the calls around them kept their order |
hotspots |
hotspot |
The release changes a file with a history of reverts and regression fixes |
Behavior findings are low severity with medium confidence. Hotspots come from your git history and are graded high, medium or low by how often the file regressed.
These findings are informational until your policy uses them:
gate:
behavior:
changes: approve
hotspots: approve
severity: medium
hotspots_min: medium| Key | Values | Meaning |
|---|---|---|
changes |
off, approve, block |
What the gate does with behavior_change findings (default off) |
hotspots |
off, approve, block |
What the gate does with hotspot findings (default off) |
severity |
high, medium, low |
Raise matching findings to at least this severity |
hotspots_min |
high, medium, low |
Only hotspots at or above this severity count (default medium) |
gate.exports tunes the exports checker:
gate:
exports:
severity: medium
ignore_paths: ["sdk/legacy/**"]
max_findings: 100severity sets the severity of exports findings (high, medium, low or info; default high), ignore_paths takes CODEOWNERS-style patterns (up to 100), and max_findings (0 to 1000) caps how many are listed, with one summary finding for the rest.
Behavioral verification
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:behavioral_verification.
bumpkit verify checks out the base and head into separate worktrees and runs them side by side in your CI, never on Bumpkit's servers:
- the project's test command at both versions (
verify.test, or detected fromgo.mod,package.json,pyproject.toml,pytest.iniorsetup.py) - for Go, each changed exported function called with the same generated inputs at both versions
- for HTTP services (
verify.http.start), recorded requests (verify.http.requests) and requests derived from the OpenAPI spec replayed against both versions, with responses compared after masking times and IDs - with Playwright, the pages in
verify.browser.pagescompared by text, DOM structure and screenshot
bumpkit verify
bumpkit verify --base v1.4.0 --head HEAD --no-upload --jsonCommands run with a time limit, CPU and memory limits (set with ulimit where the system supports it) and a safe set of environment variables. Unless verify.network is allow (the default is localhost), outside network access is cut off: on Linux runners that allow user namespaces, the commands run in their own network namespace with only localhost; elsewhere, HTTP proxies point at a dead address and Go, npm and pip downloads are switched off, which stops well-behaved tools but isn't a hard block. The report records which sandbox ran (netns, proxy or none). verify.timeout defaults to 10m and can be up to 2h. The settings are read from bumpkit.yml at the base, so a pull request can't change how it is verified.
verify:
test: go test ./...
timeout: 10m
network: localhost
http:
start: ./bin/server
ready: /healthz
port_env: PORT
openapi: docs/openapi.yaml
gate:
require_checks: [behavior]The report is uploaded signed with your API key and, in GitHub Actions with id-token: write, attested by the job's OIDC token. require_checks: [behavior] makes the gate require a report for the commit: a regression or failing test fails the gate, and a changed response or return value needs approval. The gate accepts only OIDC-attested reports by default and uses the latest one, so a later report signed only with a key neither replaces nor blocks it. A key's signature only shows that someone with the key uploaded the report. With the GitHub App installed, a report doesn't count if the pull request changed or added the workflow file named by the token, changed anything under .github/, or changed a local action or script the default branch's workflow names in uses: ./ or run:, or if its diff was too large to read in full. Merge that change first. An attestation proves which workflow ran on which commit. It doesn't prove the code under test is honest: the workflow still runs the pull request's own code and tests, and anything those call. A report from a pull request's merge commit counts only while that merge commit is the pull request's current one, its head is the commit being gated and its base is your default branch. verify.require_ci_attestation: false in your repository's policy opts out and trusts anyone with a key; use it where no GitHub Actions OIDC token is available, such as on GitLab, Bitbucket or Azure DevOps connections. With no App or connection, Bumpkit uses the policy your CI sends, setting included, and the decision says policy_source: client. See verify.
Regression evidence
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:regression_evidence.
Prove the tests this change can break ran and passed on the exact commit you're shipping. Bumpkit picks the tests from the change's blast radius, and your CI uploads the results.
Set it up
-
Turn on the rule in
bumpkit.ymlon your default branch:YAMLgate: require_regression_evidence: true -
Give the CI job
permissions: id-token: write. That lets GitHub Actions prove which repository, commit and workflow run produced the results. -
After your tests run, upload the results before the gate:
Terminalgo test -json ./... > results.json bumpkit test attest --file results.json --base mainIt takes JUnit XML or
go test -jsonoutput. If your tests run in shards, run it once per shard: uploads from the same workflow run add up.
To see the list before you push, run bumpkit test expected --base main.
What's expected
A test is expected when it refers to a changed symbol or to code that calls one, when its own test file changed, when its test file pairs with a changed source file, or when it sits next to a changed fixture. Each test says which symbol or path put it on the list. Tests are matched by name across the call graph, so a shared helper can pull in more tests than strictly need to run, and a test that only reaches the change through a helper in another file can be left out.
What you'll see
The check page, the gate reason, bumpkit test evidence and the test_evidence MCP tool show the same list: each expected test as passed, failed or didn't run, and why it's expected. A skipped test counts as didn't run.
| State | Means |
|---|---|
passed |
Every expected test passed, or the change touches no code |
failed |
An expected test failed |
partial |
An expected test didn't run or was skipped |
missing |
No results were uploaded for this commit |
not_attested |
Results were uploaded, and none are attested by a CI run for this commit. A plain API key doesn't count, and a merge-commit attestation counts only for its own pull request. With the GitHub App, the workflow that ran also has to match the copy on your default branch |
unparseable |
An uploaded file couldn't be read |
undetermined |
Bumpkit can't work out the tests: a language or file type it can't map, no test reaches the changed code, or the change reaches too much code to list |
other_change |
The upload was computed against a different base, so it doesn't cover every changed file. Pass the pull request's base to --base |
not_enabled |
The policy asks for the rule, and the flag isn't on for this workspace |
Anything but passed fails the gate, or needs approval with regression_evidence_action: approve. Docs, images and data files such as JSON and YAML need no tests. Other file types, such as .sql or .sh, count as undetermined.
Test evolution
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:test_evolution.
bumpkit tests lists each new function or method, and each changed one that gained branches, that no test reaches, with its exact file and lines. It also finds obsolete tests: tests that refer to symbols this release removes and, with --results, tests that never failed and never touched changed code in the last --releases releases.
go test -coverprofile=cover.out -json ./... > results.json
bumpkit tests --coverage cover.out --results results.json --patch tests.patchIt reads every supported language. With --coverage (a Go cover profile), a function counts as tested only when the profile shows its lines run; otherwise a test must refer to it by name. --patch writes test skeletons for each gap in the language's usual test framework (for example Go tests, JUnit 5, xUnit, RSpec, PHPUnit, GoogleTest and swift-testing); --llm has Bumpkit's AI write the test bodies.
Intent versus implementation
Beta, early access. A workspace without access gets
403 FEATURE_EARLY_ACCESSand a link to request it. Flag:intent_check.
bumpkit criteria extracts acceptance criteria from the pull request description, the GitHub, Jira or Linear issues it links, and any --spec files (markdown checklists, Acceptance criteria sections and Given/When/Then lines). With --llm, Bumpkit's AI extracts them too. Each criterion is mapped to the changed files, the tests touching them and the behavioral verification results, and reported as covered or not evidenced.
bumpkit criteria --spec docs/specs/export.md
bumpkit criteria --spec docs/specs/export.md --llm --jsonJira needs JIRA_BASE_URL, JIRA_EMAIL and JIRA_API_TOKEN; Linear needs LINEAR_API_KEY.
This never passes or fails a release on its own. Add the intent rule, and pass the same spec files to bumpkit gate --spec (comma-separated), to ask for an approval when a criterion has no evidence; each such criterion is an intent_unevidenced finding:
gate:
require_approval:
- intentSee require_approval.