# Cross-Repo Impact

A change that is safe in one repository can break the services that depend on it; cross-repo impact shows who is affected, for teams running many connected repositories.

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it.

Bumpkit builds a service graph of your workspace: which repositories publish which packages and API contracts, and which repositories consume them. It uses the graph in two ways:

| Feature | What it does | Flag |
|---------|--------------|------|
| Cross-repo impact | When a release breaks a public API or contract, the gate lists every consuming repository and its call sites. | `cross_repo_impact` |
| Dependency upgrade autopilot | When a dependency your repositories use releases a new version, Bumpkit proposes an upgrade pull request for each repository. | `dependency_autopilot` |

Both need the Bumpkit GitHub App installed on the repositories. See [Integrations](/docs/integrations).

[Multi-repo release trains](/docs/release-trains#release-across-repositories) use the same graph to ship across repositories in dependency order, and show it as a service map.

## The service graph

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `cross_repo_impact`.

For every repository the GitHub App can see, Bumpkit reads the default branch and records what the repository provides and consumes.

| File | Provides | Consumes |
|------|----------|----------|
| `go.mod` | The Go module (`go`). | Each `require` line (`go`), marked direct or indirect. |
| `package.json` | The package (`npm`), unless `"private": true`. | `dependencies`, `devDependencies` and `peerDependencies` (`npm`). |
| `pyproject.toml` | The project (`pypi`) from `[project]` or `[tool.poetry]`. | `[project]` dependencies and `[tool.poetry.dependencies]` (`pypi`). |
| `requirements*.txt` | | Each requirement (`pypi`). |
| `pom.xml`, `build.gradle`, `build.gradle.kts`, `*.versions.toml`, `*.sbt` | The artifact it builds (`maven`). | Its dependencies (`maven`). |
| `*.csproj`, `Directory.Packages.props`, `Directory.Build.props`, `packages.config` | The NuGet package (`nuget`). | Its package references (`nuget`). |
| `Gemfile`, `Gemfile.lock`, `*.gemspec` | The gem, from `*.gemspec` (`rubygems`). | Its gems (`rubygems`). |
| `composer.json`, `composer.lock` | The package (`composer`). | `require` and `require-dev` (`composer`). |
| `pubspec.yaml`, `pubspec.lock` | The package (`pub`). | Its dependencies (`pub`). |
| `mix.exs`, `mix.lock` | The application (`hex`). | Its dependencies (`hex`). |
| `Cargo.toml`, `Cargo.lock` | The crate (`cargo`). | Its dependencies (`cargo`). |
| `CMakeLists.txt`, `conanfile.txt`, `conanfile.py`, `vcpkg.json` | The library it builds (`cmake`). | Its packages (`cmake`). |
| `Package.swift`, `Package.resolved` | The package (`swiftpm`). | Its packages (`swiftpm`). |
| `*.proto` | The proto package (`proto`). | Each `import`, except `google/` ones (`proto_import`). |
| `*.graphql`, `*.graphqls` | A GraphQL schema (`graphql`). | |
| OpenAPI or Swagger files (`.yaml`, `.yml` or `.json` with `openapi` or `swagger` in the name) | The API, named by `info.title` (`openapi`). | |
| `*.schema.json` with an `$id` | The JSON Schema (`jsonschema`). | |

These are the manifests of the 15 languages Bumpkit reads; see [Supported Languages](/docs/supported-languages). Bumpkit skips files under `.git`, `node_modules`, `vendor`, `testdata`, `third_party`, `dist`, `build`, `fixtures`, `__fixtures__`, `examples`, `example` and `samples`.

A repository consumes another when:

- It depends on a package the other publishes, matched by ecosystem and name.
- It has a copy of a contract the other publishes, matched by kind and name.
- It imports a `.proto` file the other publishes, matched by path.

### When the graph refreshes

- On every push to a repository's default branch.
- When the GitHub App is installed, or repositories are added to an installation.
- When an owner or admin calls `POST /v1/service-graph/refresh`.

Each refresh is recorded in the workspace audit log.

## Cross-repo impact in the gate

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `cross_repo_impact`.

When the [Release Gate](/docs/release-gate) runs on a repository in the graph, Bumpkit looks at its high-severity, deterministic breaking findings:

- `breaking` findings from the `exports` checker: a removed public symbol or a changed signature.
- `contract_breaking` findings from [Contract Checks](/docs/contract-checks).

For each break, Bumpkit finds the repositories that consume the package or contract and looks in each one's default branch for the changed name or route.

Each affected repository becomes one `consumer_break` finding from the `cross_repo` checker:

| Result | Severity | Detail |
|--------|----------|--------|
| Call sites found | high | How the repository depends on the change, what changed, and up to 3 call sites per file as `path:line: code`. |
| No call sites found | medium | How the repository depends on the change. The dependency is proven, but no use was found. |

```text
Breaks acme/web: Removed public Client.Charge
acme/web depends on github.com/acme/payments v1.8.0 (go.mod:12).
Changed: Removed public Client.Charge.
Call sites:
services/checkout/pay.go:41: res, err := client.Charge(ctx, order.Total)
```

If Bumpkit can't read the graph or can't search every consuming repository, it adds a `cross_repo_check_unavailable` finding, such as "Couldn't search for call sites in 2 of 12 consuming repositories".

On pull requests checked by the GitHub App, Bumpkit also posts a "Bumpkit cross-repo impact" comment that lists each affected repository, and updates the same comment on later runs.

### Gate policy

Set what consumer breaks do under `gate.cross_repo` in `bumpkit.yml`:

```yaml
gate:
  cross_repo:
    block_on_consumer_break: true
```

| Key | Default | Description |
|-----|---------|-------------|
| `block_on_consumer_break` | `false` | Fail the gate when the change breaks code in a consuming repository. |
| `require_approval` | `true` | Ask for an approval when the change breaks code in a consuming repository. |

Only high-severity `consumer_break` findings, the ones with call sites, trigger these rules. When the check was incomplete (`cross_repo_check_unavailable`), the gate always asks for an approval, even with `require_approval: false`, so a gap in the search never passes silently.

The gate reasons are named `cross_repo.block_on_consumer_break` and `cross_repo.require_approval`. See [cross_repo](/docs/bumpkit-yml#cross-repo) in the bumpkit.yml reference.

## Dependency upgrade autopilot

> **Beta, early access.** A workspace without access gets `403 FEATURE_EARLY_ACCESS` and a link to request it. Flag: `dependency_autopilot`.

The autopilot watches the Go module proxy, npm and PyPI for new versions of the dependencies in your service graph. It scans every 6 hours, and when an owner or admin calls `POST /v1/autopilot/scan`.

It considers direct dependencies pinned to a single version:

| Ecosystem | Pinned forms |
|-----------|--------------|
| Go | `v1.2.3` in `go.mod` |
| npm | `1.2.3`, `^1.2.3`, `~1.2.3`, `=1.2.3` |
| PyPI | `==1.2.3`, `~=1.2.3` |

Ranges such as `>=1.0` are skipped. Each scan checks up to 100 distinct packages.

### Breaking or routine

Bumpkit marks an upgrade as breaking when any of these hold:

- A Go module moved to a new major version module path.
- The version is a major change under semver, or a minor change below 1.0.
- The GitHub release notes for the new tag mention "breaking".
- For Go, the exported API differs between the two versions: symbols removed, renamed or changed.

Each proposal gets a priority:

| Priority | When |
|----------|------|
| `security` | The current version has a known vulnerability in [OSV](https://osv.dev). |
| `breaking` | The upgrade is breaking. |
| `routine` | Anything else. |

Proposals are listed security first, then breaking, then routine.

### What a proposal contains

- **Edits**: mechanical line changes Bumpkit can make safely. The version in each manifest, Go import paths for a new major version module, and renamed Go symbols.
- **TODOs**: exact file and line locations that need a person. Removed symbols, changed signatures, and uses of a package whose new version is breaking.

### Approve a proposal

Nothing is opened until an owner or admin approves a proposal. When you approve one, Bumpkit:

1. Creates the branch `bumpkit/upgrade-<name>-<version>` from the default branch.
2. Commits the edits with the message `chore(deps): bump <name> from <from> to <to>`.
3. Opens a pull request whose body lists the reasons, any known vulnerabilities, every edit and a checklist of the TODOs.

Bumpkit never merges the pull request. Review it, finish the TODOs, refresh your lockfile and merge it yourself. If a file changed since the scan, opening fails with "line N changed since the scan; scan again".

| Status | Meaning |
|--------|---------|
| `pending` | Waiting for a decision. A new scan updates it in place. |
| `opening` | Approved; the pull request is being opened. |
| `opened` | The pull request is open. `pull_request_url` links to it. |
| `dismissed` | An owner or admin dismissed it. |
| `failed` | Opening failed. `error` says why. You can approve it again. |

Approving, dismissing and scanning are recorded in the workspace audit log.

```json
{
  "id": "0b6f3c2a-9e1d-4f7a-8c55-3a2e9d1b7f40",
  "repository": "acme/web",
  "ecosystem": "go",
  "name": "github.com/acme/payments",
  "from": "v1.8.0",
  "to": "v2.0.0",
  "priority": "breaking",
  "breaking": true,
  "reasons": ["github.com/acme/payments moved to the new major version module github.com/acme/payments/v2."],
  "advisories": [],
  "edits": [{"path": "go.mod", "line": 12, "old": "\tgithub.com/acme/payments v1.8.0", "new": "\tgithub.com/acme/payments/v2 v2.0.0", "why": "bump github.com/acme/payments from v1.8.0 to v2.0.0"}],
  "todos": [{"path": "services/checkout/pay.go", "line": 41, "code": "res, err := client.Charge(ctx, order.Total)", "why": "payments.Charge was removed in v2.0.0."}],
  "status": "pending",
  "created_at": "2026-10-22T06:00:00Z"
}
```

## API endpoints

All endpoints take the `x-api-key` header. Endpoints return `403 FEATURE_EARLY_ACCESS` when the feature isn't on for your workspace yet, and `403 FORBIDDEN` when an owner-only endpoint is called by someone who isn't an owner or admin.

| Method | Path | Who | Description |
|--------|------|-----|-------------|
| `GET` | `/v1/service-graph` | members | Every repository in the graph with what it `provides`, `consumes`, and the `consumers` that depend on it. |
| `POST` | `/v1/service-graph/refresh` | owners and admins | Re-read every repository. Returns `{"repositories": N}`, plus `error` if some failed. |
| `GET` | `/v1/autopilot/proposals` | members | Upgrade proposals. `?limit=` from 1 to 200, default 50. |
| `POST` | `/v1/autopilot/scan` | owners and admins | Scan for new versions now. Returns `checked` and `proposed` counts. |
| `POST` | `/v1/autopilot/proposals/{id}/approve` | owners and admins | Open the pull request. Returns the proposal. |
| `POST` | `/v1/autopilot/proposals/{id}/dismiss` | owners and admins | Dismiss the proposal. Returns `204`. |

Approving or dismissing a proposal that was already decided returns `409`.

```bash
curl https://bumpkit.dev/v1/service-graph \
  -H "x-api-key: $BUMPKIT_API_KEY"
```

```json
{
  "repositories": [
    {
      "repository": "acme/payments",
      "default_branch": "main",
      "head_sha": "9c1e4b2f0a7d3e6b8c5f1a2d4e7b9c0f3a6d8e1b",
      "refreshed_at": "2026-10-22T09:14:03Z",
      "provides": [{"kind": "go", "name": "github.com/acme/payments", "path": "go.mod", "line": 1, "direct": true}],
      "consumes": [],
      "consumers": ["acme/checkout", "acme/web"]
    }
  ]
}
```

`truncated` is set when GitHub returned only part of the repository's file tree.

There are no CLI commands or MCP tools for cross-repo impact or the autopilot yet. Consumer breaks show up in `bumpkit gate` output and in the `POST /v1/gate` response like any other finding. See the [API Reference](/docs/api-reference).
