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 tofetchUserdownstream. - 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_searchfor 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_impactoutput 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. Usefile_findingson 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.