A syntax graph to find the change. Claude to make it. Your build to prove it.
CodeFlow combines three things that are each insufficient alone: structural knowledge of your code, a model that can read intent, and a verification loop that refuses to hand over anything that does not build.
The repository graph
Before anything is planned, each repository in scope is indexed into one graph. It answers the question every migration starts with: where, exactly, is this used?
- Syntax trees
- Every source file, parsed with a real parser for its language rather than read as text.
- Symbols and references
- Where each function, class and constant is defined, and every place it is used across files and packages.
- Types
- Resolved types where the language provides them, so an overloaded method or a re-exported name is matched correctly.
- Build and test targets
- Which modules build together and which tests cover them, so each batch is verified against the right suite.
- Ownership
- Code owners and directory boundaries, used to group changes into pull requests the right team can review.
Four Claude agents with narrow jobs
Each agent has one responsibility, its own tools and a clear handoff. The graph decides what is in scope; Claude decides how to change it.
Planner
Claude OpusReads the migration guide, release notes and the graph, then writes the change plan: what moves, in which order, and which cases need a decision.
find_referencesread_noderead_module
Rewriter
Claude SonnetApplies the plan call site by call site as edits to syntax-tree nodes. Output always parses, and only nodes marked in scope can change.
read_nodepropose_edit
Verifier
Claude SonnetRuns the build and tests in a sandbox. On failure it reads the compiler or test output, finds the edit that caused it and sends it back with the reason.
run_buildrun_testsread_log
Reviewer
Claude OpusWrites the pull request, lists every judgment call with its file and line, and holds back any batch it cannot verify.
diff_batchopen_pull_request
The agent loop
Planning a call site is a tool-use loop on the Claude API. Claude asks for what it needs from the graph, proposes an edit, and the loop ends when it has an answer or hits a turn limit. The tool list and migration playbook are cached, because they are identical for every call site in the run.
Tools Claude can call
find_references- Every use of a symbol, with file, line and enclosing node
read_node- The syntax node at a location, with its type information
read_module- A module's source and its direct callers, for cross-file changes
propose_edit- A replacement for one node; rejected if the result does not parse
run_build / run_tests- Run in a sandbox and return the compiler and test output
open_pull_request- Open a pull request with a schema-checked description
import Anthropic from "@anthropic-ai/sdk";
import { graphTools, runTool, type CallSite } from "./graph-tools";
const claude = new Anthropic();
const MAX_TURNS = 12;
// One call site at a time: Claude plans with tools that read the syntax graph.
export async function planCallSite(site: CallSite, playbook: string) {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: `Plan the edit for ${site.file}:${site.line}` },
];
for (let turn = 0; turn < MAX_TURNS; turn++) {
const response = await claude.messages.create({
model: "claude-opus-5-5",
max_tokens: 16000,
thinking: { type: "adaptive" },
output_config: { effort: "high" },
// Tools render before the system prompt, so this breakpoint caches both:
// they are identical for every call site in the run.
system: [{ type: "text", text: playbook, cache_control: { type: "ephemeral" } }],
tools: graphTools, // find_references, read_node, propose_edit (strict schemas)
messages,
});
if (response.stop_reason !== "tool_use") return response;
messages.push({ role: "assistant", content: response.content });
const results = await Promise.all(
response.content
.filter((block) => block.type === "tool_use")
.map(async (call) => ({
type: "tool_result" as const,
tool_use_id: call.id,
content: await runTool(call.name, call.input),
})),
);
messages.push({ role: "user", content: results });
}
throw new Error(`No plan for ${site.file}:${site.line} after ${MAX_TURNS} turns`);
} Claude capabilities CodeFlow depends on
Tool use over the syntax graph
Claude never greps or guesses. It calls typed tools such as find_references, read_node and propose_edit, with strict schemas, so every edit targets a real node in the graph.
Adaptive thinking for judgment calls
Mutable dates, implicit locales, security config that has to be restructured: Claude reasons through the cases a codemod rule cannot express, and explains the choice it made.
Prompt caching for repository context
The migration playbook, tool definitions and module context are cached once per run, so the thousandth call site costs a fraction of the first.
Message Batches for bulk work
Single-shot jobs that need no tools, such as classifying thousands of call sites by pattern and drafting pull request summaries, run through the Message Batches API at half the per-token price.
1M-token context for module-level reasoning
When a change crosses files, Claude sees the whole module and its callers at once instead of a window of lines.
Structured output for the pull request
Every batch ends in a schema-checked report: what changed, which tests ran, and each judgment call with its file and line.
How a change moves through
Large migrations run as a sequence of batches. Each batch goes through all five steps, and later batches benefit from what earlier ones learned about your code.
-
Step 1: Connect
You grant read access to the repositories in scope and permission to open pull requests. CodeFlow never pushes to protected branches.
-
Step 2: Index
Each repository is parsed into a graph of syntax trees, symbols, types, imports and build targets, so every use of an API is found.
-
Step 3: Plan
Claude works out the right edit for each call site through tools that read the graph. Cases that need a human decision are flagged, not guessed.
-
Step 4: Verify
Every batch is built and tested in a sandbox. Failures go back to Claude with the compiler output until the batch passes or is held.
-
Step 5: Deliver
Changes arrive as small pull requests grouped by module and owner. Your CI runs, your reviewers decide.
Keeping model cost predictable
A migration can mean thousands of model calls. Every run reports tokens and cache hits per batch, and these defaults keep the bill proportional to the work.
- Cached context
- The playbook, tool definitions and module context are cached, so most of each request is read from cache.
- Batched bulk work
- Classification and summaries that need no tools go through the Message Batches API at half the per-token price.
- Right model for the job
- Claude Opus plans and reviews. Claude Sonnet rewrites the high-volume, well-specified call sites.
- Effort per task
- Mechanical edits run at low effort; judgment calls get adaptive thinking at high effort.
Where CodeFlow fits, and where it does not
A good fit when
- Many files need a similar change that still varies case by case
- The target state is well defined: a version, an API, a library, a convention
- Your build and tests can tell a good change from a broken one
- The work keeps losing to roadmap priorities
Not the right tool for
- Redesigning architecture or deciding what the system should become
- Codebases with no build or tests that can run in CI
- Changes that need a product decision in every file
Become a design partner
We are working with a small group of engineering teams on real migrations. You bring one change you already need; we run CodeFlow on it with you and shape the product around what your team needs.