dep-radius

Use radius from an AI agent

What your agent should run after a dependency upgrade, and what to do with the result.

Why your agent needs it

When an agent upgrades a dependency, it checks two things: does the code compile, and do the tests pass. Both can be fine while the package now behaves differently: a new default, a stricter check.

Those changes are written in the release notes. The agent either skips the notes, or reads all of them and guesses which files they affect.

radius does that part for it. It returns the changes that affect your code, with the files and lines. The agent then checks those lines and runs the tests.

The steps

1. Upgrade

The agent upgrades the way the project does it: pnpm up zod, npm install react@19. No need to commit.

2. Run radius

npx dep-radius --since HEAD --json

--since HEAD compares with the last commit, so radius looks at exactly what the agent just changed. On a branch with several commits, use --since main.

3. Check the lines

Exit codeWhat the agent does
0Nothing your code uses changed. Run the tests.
1Open each line radius lists. Read the notes it couldn't place. Fix what breaks.
2Same as 1, and first fix every call to what was removed.
3radius failed. Report it. Don't guess.

4. Run the tests

Run the tests for the files radius listed. If a note describes something no test covers, add a test. radius shows where to look. The tests confirm the code still works.

Paste this into CLAUDE.md or AGENTS.md

It works in CLAUDE.md (Claude Code), AGENTS.md, .cursor/rules, .github/copilot-instructions.md, or any file your agent reads.

CLAUDE.md
## Dependency upgrades

After you change a dependency version, run this before you say you're done:

    npx dep-radius --since HEAD --json

Then look at the exit code:

- 0: nothing this project uses changed. Run the tests.
- 1 or 2: for each package where "verdict" is not "quiet":
  1. If "verdict" is "blocked", first fix every site in `types.touched[]` where "change" is
     "removed".
  2. Open every site in `types.touched[].sites[]`.
  3. For each note in `notes.matched[]`, find its names (`names[].name`) in `usage.sitesByName`,
     open those sites, and check if the change in the note's `title` affects that code.
  4. Read the titles in `notes.breakingWithoutApi[]` and `notes.changesWithoutApi[]`. radius could
     not link them to the code. Search the code for what they describe.
  5. Fix the code. Run the tests for the files you opened. Add a test if nothing covers the change.
  6. Run radius again.
- 3: radius failed. Report the error. Don't guess.

`usage.cannotSee[]` lists usage radius could not follow, like scripts or config files. Check those
with the tests. Never call an upgrade safe because of radius alone.

How I use it

I don't run radius myself. The block above is in my projects' CLAUDE.md. When Claude Code upgrades a dependency, it runs radius, opens the lines, and runs the tests for those files. Then it tells me the upgrade is done.

Why not just let the agent read the changelog?

Agent reads the changelogAgent runs radius
Finds the files a change affectsGuesses, often misses someFinds every place, even through other files
Same answer next timeNoYes
Says what it didn't checkNoYes
Tokens usedEvery note of every versionOnly the result

The agent still reads. It just reads the right lines.

The JSON

Here is what an agent needs from the output. This is a real run, zod 4.4.3 → 4.5.0, shortened. The full format is on The JSON output.

{
  "schemaVersion": 1,
  "exitCode": 1,
  "packages": [
    {
      "name": "zod",
      "from": "4.4.3",
      "to": "4.5.0",
      "verdict": "review",
      "reasons": [
        { "code": "notes-match", "detail": "5 release notes mention names you use (4 by name in the text)" },
        { "code": "unattributed-change", "detail": "23 release notes describe changes radius cannot tie to your code" }
      ],
      "notes": {
        "matched": [
          {
            "version": "4.5.0",
            "title": "⚠️ String length counts code points",
            "breaking": true,
            "direct": true,
            "names": [
              { "name": "string", "certainty": "exact", "where": "inline-code" },
              { "name": "max", "certainty": "by-name", "where": "inline-code" },
              { "name": "min", "certainty": "by-name", "where": "inline-code" }
            ]
          }
        ],
        "changesWithoutApi": [{ "version": "4.5.0", "title": "Zod 4.5 is now available.", "refs": [] }]
      },
      "usage": {
        "sitesByName": {
          "min": [
            {
              "file": "src/feedback/schema.ts",
              "line": 5,
              "column": 12,
              "typeOnly": false,
              "code": "message: z.string().min(10).max(2000),",
              "via": ["src/lib/zod.ts"]
            }
          ]
        }
      }
    }
  ]
}
FieldWhat it is
notes.matchedNotes that name something your code uses
usage.sitesByNameWhere your code uses each name: file, line, and the code itself
types.touchedFunctions and types that changed or were removed, with where they're used
notes.changesWithoutApiNotes radius couldn't link to your code. Skim them.
notes.breakingWithoutApiBreaking notes that name nothing specific

notes.changesWithoutApi can be long. Some entries are just a release intro, like "Zod 4.5 is now available." It's a list to skim, not a list of problems.

Handy commands

These use jq:

# every file:line a note lands on, with the note
npx dep-radius --since HEAD --json | jq -r '.packages[] as $p | $p.notes.matched[] as $n | $n.names[].name as $name | ($p.usage.sitesByName[$name] // [])[] | "\(.file):\(.line)  \($n.title)"' | sort -u

# the notes radius couldn't link to your code
npx dep-radius --since HEAD --json | jq -r '.packages[] | .name as $pkg | (.notes.breakingWithoutApi + .notes.changesWithoutApi)[] | "\($pkg) \(.version)  \(.title)"'

# every use of a changed or removed function
npx dep-radius --since HEAD --json | jq -r '.packages[].types.touched[]?.sites[] | "\(.file):\(.line)"'

# only the packages that need a look
npx dep-radius --json | jq -c '.packages[] | select(.verdict != "quiet") | {name, from, to, verdict}'

Tips

  • Give it a GitHub token. Set GITHUB_TOKEN, or log in with gh. Without it, GitHub allows 60 requests an hour and some notes may be skipped. radius says so when it happens.
  • The output is only JSON. With --json, nothing else is printed to stdout.
  • Running it again is fast. Downloads are cached. --offline uses only the cache.
  • Monorepos. Point it at one package: npx dep-radius ./packages/api --since HEAD --json.
  • The JSON won't break. Version 1 only adds fields. It never removes or renames one.

Docs for agents

This site can be read by agents:

URLWhat
/llms.txtA list of every page
/llms-full.txtEvery page in one Markdown file
Any page URL + .mdThat page as Markdown, like /docs/agents.md
Accept: text/markdownAny page answers in Markdown when asked

On this page