AI & Development

Refactor Legacy TypeScript with OpenCode

Tech Setup4 min read
TS

Tech Setup

Published October 20, 2026 · Editorial policy

Refactor Legacy TypeScript with OpenCode

That monolithic TypeScript service written back in 2021 is actively slowing down every feature release. You are a senior full-stack developer staring down 45,000 lines of any types, circular dependency warnings, and missing null checks, and rewriting it from scratch is not an option. You want to systematically clean up the architecture, enforce strict mode, and extract modular services without breaking production, but manual refactoring will take weeks of tedious find-and-replace work.

Here is how to use OpenCode to automate the heavy lifting of refactoring a legacy TypeScript codebase, moving from loose configurations to a strict, modern module structure in a single afternoon.

Fast-Track Implementation Plan

If you want the solution immediately without reading the theory, execute this sequence in your terminal at the root of your legacy project.

First, install OpenCode globally or run it via npx:

npx @opencode/cli@latest init

When prompted by the configuration wizard, select the following parameters:

  • Target Language: TypeScript (v4.5+ or v5.0+)
  • Refactoring Strategy: Incremental Strictness & Module Decoupling
  • AST Parser: TypeScript Compiler API (Native)
  • AI Backend: Anthropic Claude 3.5 Sonnet (recommended for code generation accuracy)

Verify your installation by running a dry-run analysis on your source directory:

npx @opencode/cli analyze --src ./src --target-strictness

Expected output in your terminal:

[OpenCode] Scanning ./src...
[OpenCode] Found 142 TypeScript files.
[OpenCode] Analysis complete:
  - Implicit 'any' usages: 312
  - Circular dependencies detected: 8 modules
  - Estimated refactoring complexity: Medium-High
[OpenCode] Ready to execute transformation pipeline. Run 'npx @opencode/cli refactor' to apply fixes.

Step 1: Enforcing Strict Nulls and Eliminating any

Legacy codebases often accumulate any types out of expediency, disabling TypeScript's primary safety net. OpenCode reads your Abstract Syntax Tree (AST), infers the correct types based on usage patterns and runtime boundaries, and replaces loose definitions without breaking downstream consumers.

Configuring the Inference Rules

Do not let OpenCode guess blindly. Create an opencode.config.json file in your root directory to set explicit boundaries for type generation.

{
  "$schema": "https://opencode.ai/schemas/v1/config.json",
  "compilerOptions": {
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  },
  "inference": {
    "strategy": "conservative",
    "fallbackType": "unknown",
    "maxUnionTypes": 4,
    "excludePatterns": ["src/legacy/third-party/*.d.ts"]
  },
  "output": {
    "format": "ts",
    "addTodoComments": true
  }
}

Running the Type Hardening Engine

Execute the transformation command pointing directly to your configuration:

npx @opencode/cli refactor --config opencode.config.json --focus types

OpenCode will process files incrementally. For every ambiguous any it cannot confidently resolve, it injects an unknown type and adds an explicit // @opencode-todo: Verify type inference comment above the line so you can audit it later.

Step 2: Decoupling Circular Dependencies

Circular dependencies in legacy TypeScript lead to undefined initialization errors, memory leaks, and agonizingly slow Webpack or Vite build times. OpenCode includes a topological sorting engine that detects import cycles and refactors them using dependency injection or extraction into shared utility modules.

Run the module-decoupling command to map and break cycles:

npx @opencode/cli decouple --src ./src --max-depth 5 --strategy extract-interface

During this process, OpenCode performs these actions:

  1. Identifies files involved in circular import chains (e.g., src/models/user.ts importing src/services/auth.ts which imports src/models/user.ts).
  2. Extracts shared interfaces into a neutral src/types/ directory.
  3. Rewrites relative import paths across all affected files.

Verify that your dependency graph is clean by running your bundler or test suite:

npm run build

Troubleshooting Common Blockers

Refactoring engines interact deeply with your local environment. If the process stalls or throws errors, check these common failure points:

1. Out-of-Memory Errors on Large Codebases

  • The Symptom: Node.js exits abruptly with FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory during the AST analysis phase.
  • The Fix: Increase the Node.js memory limit for the execution command:
    NODE_OPTIONS="--max-old-space-size=8192" npx @opencode/cli analyze --src ./src
    

2. Broken Imports After Path Aliasing

  • The Symptom: OpenCode rewrites relative paths (../../components/Button) into absolute paths, but your tsconfig.json paths mapping is misconfigured, causing compilation failures.
  • The Fix: Ensure your tsconfig.json has explicit baseUrl and paths defined before running OpenCode:
    {
      "compilerOptions": {
        "baseUrl": ".",
        "paths": {
          "@/*": ["src/*"]
        }
      }
    }
    
    Then run OpenCode with the --respect-tsconfig flag:
    npx @opencode/cli refactor --respect-tsconfig
    

3. Over-Aggressive Type Narrowing

  • The Symptom: OpenCode replaces valid union types with overly restrictive literals based on a single code path.
  • The Fix: Adjust the inference.maxUnionTypes property in your opencode.config.json up to 6 or 8 to allow broader type definitions, or lock specific files using an .opencodeignore file matching .gitignore syntax.

Prevention and Next Steps

Once OpenCode has modernized your types and broken your circular dependencies, you need to ensure the codebase does not regress into its previous state.

Add OpenCode's linter check directly into your continuous integration pipeline to block PRs that introduce any types or new circular imports. In your .github/workflows/ci.yml:

name: TypeScript Quality Gate

on: [pull_request]

jobs:
  lint-architecture:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - name: Run OpenCode Architecture Check
        run: npx @opencode/cli lint --strict --no-cycles

By pairing OpenCode's AST transformation engine with strict CI enforcement, you turn a painful legacy codebase into a predictable, maintainable TypeScript service without halting feature development.