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 Opus

Reads 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 Sonnet

Applies 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 Sonnet

Runs 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 Opus

Writes 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`);
}
Simplified from the planner. TypeScript, Anthropic SDK.

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.

  1. Step 1: Connect

    You grant read access to the repositories in scope and permission to open pull requests. CodeFlow never pushes to protected branches.

  2. 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.

  3. 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.

  4. 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.

  5. 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.