Recipe · Refactoring

How to refactor safely with AI agents

TL;DR: An AI agent renaming an exported function will, by default, miss the dynamic callers, ESM-only re-exports, and barrel files that grep doesn't find. Use imports_impact + imports_hotspots + ast_find_pattern + diagnostics to see the full blast radius before you start. Tools used: imports_impact, imports_hotspots, ast_find_pattern, file_findings, lint_files, typescript_diagnostics, git_precommit_check.

The scenario

You want to rename getUserById(id) to fetchUser(id). The function is in lib/users/queries.ts and is used "everywhere" — but you don't actually know how many callers there are or what shape they take.

The vanilla approach (and why it falls over)

An AI agent without an import graph reaches for grep:

$ rg -n "getUserById" .

That misses:

  • Re-exports through barrels: export { getUserById } from "../users/queries" renamed to fetchUser downstream.
  • Default-export callers that import as import getUser from "...".
  • Dynamic import() patterns.
  • Test mocks at __mocks__/lib/users/queries.ts.
  • Generated client SDKs that re-export from queries.ts.

The agent renames the source, "all tests pass" (because the mocks shadow the real module), and three days later production breaks for the SDK consumers.

The agentmako approach

Step 1 — imports_impact

Ask the index "what depends on this file?"

{
  "tool": "imports_impact",
  "args": { "filePath": "lib/users/queries.ts", "limit": 200 }
}

Returns every indexed file that imports anything from queries.ts, including re-export chains. You learn there are 47 direct importers, 11 of them re-exports through lib/users/index.ts, and 3 generated SDK files under packages/sdk/ that you would have missed entirely.

Step 2 — imports_hotspots

{
  "tool": "imports_hotspots",
  "args": { "limit": 30 }
}

Tells you which of those importers are themselves widely used. If lib/users/index.ts is imported by 120 other files, that's the file you want to update first to keep the re-export name surface stable.

Step 3 — ast_find_pattern

Catch the patterns grep can't:

{
  "tool": "ast_find_pattern",
  "args": {
    "pattern": "getUserById($A)",
    "languages": ["ts", "tsx"],
    "maxMatches": 200
  }
}

Structural search returns every actual call site as an AST match, with file paths and line ranges. Each match has an ackableFingerprint if you want to mark intentional legacy uses.

Step 4 — diagnostics before edits

{
  "tool": "typescript_diagnostics",
  "args": { "files": ["lib/users/queries.ts", "lib/users/index.ts"] }
}

Get a clean baseline. If TSC reports issues unrelated to your rename, you'll know they were pre-existing — not caused by the refactor.

Step 5 — make the edits, then re-run diagnostics

Rename in source, update the barrel re-exports, update the SDK files. Re-run typescript_diagnostics on the same files. Compare findings: any new ones are caused by the refactor.

Step 6 — pre-commit gate

{
  "tool": "git_precommit_check",
  "args": {}
}

Catches staged route auth and boundary regressions before you ship.

Variations

  • API surface change (not just a rename): add cross_search for the old name across schema, RPCs, and routes — sometimes a "function name" leaks into a DB function or a Next.js route.
  • Breaking change you can't avoid: use imports_impact output as a downstream PR list. Each downstream owner gets a focused PR rather than a single giant one.
  • Public package: after the in-repo edits, add a deprecation shim that re-exports the old name with @deprecated. Use file_findings on the shim to catch acks/diagnostics that flag it for removal in a later release.

Tools used in this recipe

  • imports_impact — files that depend on a file.
  • imports_hotspots — most widely-imported files in the project.
  • ast_find_pattern — structural ast-grep over indexed JS/TS/TSX.
  • file_findings — durable findings for a file.
  • lint_files — Mako rule-pack diagnostics.
  • typescript_diagnostics — TSC diagnostics persisted into Reef.
  • git_precommit_check — staged TS/TSX boundary checks.
  • cross_search — search a term across code, schema, RPC, routes, memories.