Claude Code Workflow for Flow-to-TypeScript Migration

CameronCat Intermediate 8/6/2026 449 views 6 likes 3 min read

Tossing a thousand files at an LLM and asking for a conversion isn't a migration strategy—it's a gamble. When dealing with a legacy codebase, specifically one with over 1,100 files trapped in Flow (circa 2019), the primary bottleneck isn't the model's intelligence, but the context window and the lack of a verification loop. If you try to migrate file-by-file in isolation, the AI will hallucinate types because it can't see the dependencies in other files, leading to a cascade of broken imports.

To solve this, we built a custom AI migration engine that treated the codebase as a graph rather than a folder of text files. Here is the breakdown of the AI workflow and the architectural decisions that made it work.

The Pre-Migration Infrastructure

Before touching a single line of code with an LLM, we had to stabilize the environment. We switched the build system from Webpack to Vite + esbuild. This was non-negotiable because it allowed .js and .ts files to coexist seamlessly. Without this, you can't maintain a "green" build while migrating incrementally.

Next, we performed a deep static analysis of the codebase to create two maps:

  • Dependency Graph: Identifying who imports whom and flagging every circular dependency.
  • Type-Ownership Map: Tracking which file defines a type and which files consume it.

We used this data to group files into "atomic units." If File A defines a type used by File B, they migrate together. We then sorted these groups leaf-first, ensuring that dependencies were converted before the components that relied on them. For massive files (some over 4,000 lines), we manually split them into smaller domain modules first, because one-shotting a monolith is a recipe for truncation and errors.

The AI Orchestration Pipeline

We didn't use a single prompt; we built a state machine using LangGraph JS. This allowed us to separate the roles of planning, execution, and validation.

 Planner ──► Implementer ──► Reviewer ──► ✅ commit
 (next group (Claude writes (tsc + tests)
 from plan) the TS) │
 ▲ │ errors?
 │ ▼
 └──────── retry (feed errors back in) ◄──┘
 │
 stuck after 3 tries?
 ▼
 🧠 Architect (bigger model)
 → retry smarter / reorder / flag a human

The logic follows a strict "Reviewer" pattern:

  • Implementer: Claude Sonnet handles the bulk of the work (about 95% of files) because it is fast and efficient.
  • Reviewer: A combination of tsc (TypeScript Compiler) and existing test suites. If the build fails, the exact error message is fed back to the Implementer for a retry.
  • Architect: If a file fails three times, we escalate to Claude Opus. The heavier model analyzes why the conversion is failing and decides if the plan needs to be reordered or if a human needs to step in.

Strict Prompt Engineering Constraints

To prevent the AI from taking the "easy way out," we imposed hard constraints in the system prompts. We explicitly forbade the use of any and the as unknown as X casting trick, which often hides bugs. The model was forced to trace prop types from actual parent component usage and import legitimate library types rather than inventing shapes.

Crucially, the LLM was restricted to editing files only. It had no shell or git access; all verification and commits happened in the Reviewer layer, ensuring the AI couldn't "fake" a successful migration.

  • Orchestration: LangGraph JS
  • LLM Engine: Claude Code CLI (Sonnet for implementation, Opus for architecture)
  • Verification: tsc + Vite/esbuild
softwareengineering

All Replies (3)

Want a live back-and-forth? Join the global AI chat room — login to talk.

J
JordanSurfer Intermediate 8/6/2026

Stressed by those hallucinated types. Did a custom lint pass fix everything for you or were there leftovers?

0 Reply
R
Riley97 Advanced 8/6/2026

Nightmare trying bulk converts before. How many files do you migrate per incremental batch?

0 Reply
K
KaiDev Expert 8/6/2026

Skeptical about those complex union types. Does it actually avoid using 'any' for everything?

0 Reply

Write a Reply

Markdown supported