# Migrar JavaScript para TypeScript com IA: plano seguro

URL: https://agentvera.dev/pt/blog/migrate-javascript-to-typescript-with-ai/ · Publicado: 2026-10-27

Como migrar JavaScript para TypeScript com agentes de IA: tsconfig que aceita JS, ordem arquivo a arquivo, rigor gradual, orçamento de any e tsc como verificação.

**Resumo:**

- Comece com um tsconfig que aceita JavaScript (allowJs, strict em false de propósito), para o app continuar compilando enquanto os agentes convertem um arquivo por vez, começando pelas folhas.
- Dê a todo agente a mesma definição de pronto: tsc passa, testes passam, nada de @ts-ignore, e a contagem de any explícito fica dentro de um orçamento que só diminui.
- Ative as flags strict uma a uma e divida o trabalho entre agentes em paralelo por diretório, em worktrees separados, com tipos compartilhados e configuração sob a responsabilidade de um único agente.

Para migrar JavaScript para TypeScript com IA, você não pede a um agente para “converter o projeto”. Você monta uma configuração de TypeScript que aceita o JavaScript que já existe e deixa os agentes converterem arquivos numa ordem fixa, com o type checker e os testes decidindo quando cada passo terminou. O compilador é o melhor revisor que você pode dar a um agente: é rápido, nunca se cansa e não dá para convencê-lo de que “parece ok”.

Este guia é a versão prática dessa ideia: o `tsconfig.json` exato para começar, como escolher a ordem dos arquivos, como apertar o rigor sem uma semana de builds vermelhos, como manter o `any` sob controle, como fazer do type check a etapa de verificação do próprio agente e como dividir o trabalho entre agentes em paralelo sem conflitos de merge. Para a rede de segurança geral de uma refatoração de código legado (testes de caracterização, rollback, revisão), leia antes [refatorar código legado com IA](/pt/blog/refactor-legacy-code-with-ai/ "Refatorar código legado com IA"); este post não repete esse conteúdo.

Os detalhes que dependem de versão seguem a documentação oficial do TypeScript em outubro de 2026.

## Por que os agentes são bons nesse trabalho

Uma migração de JS para TS é, na maior parte, trabalho mecânico com um sinal claro no fim de cada passo. É exatamente aquilo em que agentes de código são bons:

- **O feedback é instantâneo.** O `tsc` mostra arquivo, linha e código de erro. Um agente consegue ler isso e corrigir sem chutar.
- **O trabalho se divide naturalmente.** Arquivos e pastas podem ser convertidos um de cada vez, então o trabalho se quebra em muitos passos pequenos e revisáveis.
- **A parte chata é enorme.** Adicionar tipos de parâmetros, escrever interfaces para objetos que já existem e instalar pacotes `@types` é o tipo de trabalho que as pessoas adiam por anos.

O risco é igualmente claro. Um agente pressionado a deixar o compilador verde vai recorrer a `any`, casts com `as` e `@ts-ignore`, porque eles fazem os erros sumirem. A maior parte deste guia é sobre fechar essas saídas.

## Passo 1: um tsconfig que aceita o seu JavaScript

O primeiro commit não deve mudar nenhum código da aplicação. Ele adiciona o TypeScript e uma configuração que faz o type check do projeto enquanto todos os arquivos ainda são `.js`.

```bash
npm install --save-dev typescript @types/node
```

```json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "noEmit": true,
    "incremental": true,
    "target": "es2022",
    "module": "nodenext",
    "moduleResolution": "nodenext",
    "types": ["node"],
    "skipLibCheck": true
  },
  "include": ["src"]
}
```

O que cada escolha faz:

- **`allowJs`** permite que arquivos `.js` e `.ts` vivam no mesmo programa, então um arquivo `.ts` pode importar um `.js` e vice-versa.
- **`checkJs: false`** impede o TypeScript de reportar erros em arquivos em que você ainda não mexeu. Você vai incluir os arquivos um a um.
- **`strict: false`** é proposital. Desde o TypeScript 6.0, o `strict` vem como `true` por padrão, então deixá-lo de fora ligaria todas as verificações strict no primeiro dia.
- **`types: ["node"]`** importa pelo mesmo motivo: no TypeScript 6.0 o `types` padrão é um array vazio, então os globais do Node não são carregados se você não os listar. Adicione `"jest"` ou `"vitest/globals"` se os seus testes dependem de globais.
- **`noEmit`** serve para projetos em que um bundler, o `tsx` ou um framework já transforma o código em JavaScript e o TypeScript só verifica. Se o `tsc` faz o build do seu app, remova essa opção e defina `outDir`.
- **`module` e `moduleResolution`**: use `nodenext` para apps Node; para código que um bundler compila, `"module": "esnext"` com `"moduleResolution": "bundler"` costuma ser o par certo.

Adicione um script e faça o CI rodá-lo:

```json
{
  "scripts": {
    "typecheck": "tsc -p tsconfig.json"
  }
}
```

Rode uma vez. Numa base JavaScript com o `checkJs` desligado, ele deve passar ou mostrar só alguns erros de configuração. Corrija esses erros, faça o commit e você tem uma linha de base. Daqui em diante, “o build está verde” significa que `npm run typecheck` e `npm test` passam.

## Passo 2: veja os erros com @ts-check antes de renomear

Antes de renomear um arquivo, você pode ver o que o TypeScript acha dele. Adicione `// @ts-check` como primeira linha de um arquivo `.js` e o TypeScript reporta os erros dele, usando comentários JSDoc como tipos:

```js
// @ts-check

/**
 * @param {number} cents
 * @param {string} currency
 * @returns {string}
 */
export function formatPrice(cents, currency) {
  return new Intl.NumberFormat('en', { style: 'currency', currency }).format(cents / 100);
}
```

Esse é um bom primeiro trabalho para um agente num arquivo grande: “Adicione `// @ts-check` a `src/orders/pricing.js`, corrija os erros só com tipos JSDoc, não mude o comportamento.” Você ganha segurança de tipos sem renomear, e a conversão posterior fica quase mecânica. Se preferir o caminho oposto, defina `checkJs: true` e coloque `// @ts-nocheck` nos arquivos que ainda não estão prontos; aí remover essa linha é o passo.

## Passo 3: escolha a ordem, folhas primeiro

Converta primeiro os arquivos que não importam nada do seu próprio código, depois os arquivos que só importam esses, e assim por diante até os pontos de entrada. Assim, cada arquivo que você converte pode se apoiar em tipos que já existem.

Você não precisa descobrir a ordem na mão. O [madge](https://github.com/pahen/madge "madge") lê o grafo de imports:

```bash
npx madge --leaves --extensions js,jsx src/
npx madge --circular --extensions js,jsx src/
```

O `--leaves` lista módulos sem dependências, e o `--circular` lista ciclos de import. Vale conhecer os ciclos logo de cara: arquivos num ciclo precisam ser convertidos juntos, então planeje-os como um único passo.

Uma regra prática de agrupamento: um passo é um arquivo grande ou uma pasta de arquivos pequenos, pequeno o bastante para o diff caber numa sessão de revisão. Coloque a lista num arquivo versionado como `MIGRATION.md`, uma linha por passo, e deixe os agentes irem marcando. Esse arquivo vira o plano compartilhado de todos os agentes e todas as sessões.

## Passo 4: um prompt de conversão, uma definição de pronto

Use o mesmo formato de prompt em todo passo, para que os agentes não inventem regras próprias:

```text
Convert src/billing/ from JavaScript to TypeScript.

- Rename with git mv so history follows the files (.js -> .ts, .jsx -> .tsx).
- Add types for parameters, return values and exported objects.
  Prefer types inferred from real usage over wide ones.
- Don't change runtime behaviour. No refactoring, no renaming,
  no "while I'm here" fixes. If you find a bug, note it in
  MIGRATION.md under "Found during migration" and leave it.
- No @ts-ignore, no @ts-nocheck. @ts-expect-error only with a reason
  on the same line. No new `any` beyond the budget in .any-budget.
- Don't edit tsconfig.json, package.json or src/types/.
  If you need a change there, stop and tell me what and why.

Done means: npm run typecheck passes, npm test passes,
npm run lint passes, and MIGRATION.md is updated.
```

Duas linhas desse prompt fazem a maior parte do trabalho. “Não mude o comportamento em runtime” mantém o diff revisável, porque uma migração só de tipos deve adicionar apenas anotações, renomeações e imports. “Não edite o tsconfig.json” bloqueia o caminho mais barato para o verde: afrouxar o compilador.

Coloque as partes estáveis desse prompt no `CLAUDE.md` ou no `AGENTS.md` para que toda sessão comece com elas; [CLAUDE.md e AGENTS.md](/pt/blog/claude-md-agents-md/ "CLAUDE.md e AGENTS.md") explica como compartilhar um único conjunto de regras entre agentes.

## Passo 5: um orçamento de any que só diminui

Você não vai chegar a zero `any` numa passada só, e proibi-lo de vez costuma gerar saídas piores, como `as unknown as Foo`. Um orçamento é mais honesto: você permite um número, e o número só diminui.

Primeiro torne visíveis o `any` e os comentários de supressão com o typescript-eslint:

```js
// eslint.config.js (excerpt)
rules: {
  '@typescript-eslint/no-explicit-any': 'warn',
  '@typescript-eslint/ban-ts-comment': ['error', {
    'ts-expect-error': 'allow-with-description',
    'ts-ignore': true,
    'ts-nocheck': true,
    minimumDescriptionLength: 10,
  }],
}
```

O `ban-ts-comment` com essas opções rejeita `@ts-ignore` e `@ts-nocheck` e só aceita `@ts-expect-error` com um motivo escrito de pelo menos dez caracteres. O `@ts-expect-error` é a melhor saída de qualquer forma: quando o erro é corrigido, o próprio comentário vira um erro, então ele não fica esquecido.

Depois conte os `any` explícitos e compare com um número guardado no repositório:

```bash
#!/usr/bin/env bash
# scripts/any-budget.sh
count=$(npx eslint src --format json \
  | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);console.log(r.flatMap(f=>f.messages).filter(m=>m.ruleId==="@typescript-eslint/no-explicit-any").length)})')
budget=$(cat .any-budget)
echo "explicit any: $count (budget $budget)"
[ "$count" -le "$budget" ] || { echo "any budget exceeded"; exit 1; }
```

Rode isso no CI e na definição de pronto do agente. Quando um passo baixa a contagem, reduza o `.any-budget` no mesmo commit. Diga ao agente onde o orçamento fica; os agentes respeitam um número muito melhor do que “evite any sempre que possível”.

Fique de olho nas outras saídas, que a contagem não pega: casts com `as` em valores que vieram de fora (`JSON.parse`, corpos de requisição), `Function` e `object` como tipos de parâmetro, e uma linha `declare module '*'` que transforma todo import em `any`. Peça ao revisor, humano ou agente, para listar cada cast novo no diff.

## Passo 6: faça do type check a etapa de verificação do agente

Um agente que diz “pronto” sem rodar o `tsc` está chutando. Faça a verificação parte de como o agente encerra um turno, não algo que você precisa lembrar de pedir.

No Claude Code, um hook Stop pode impedir o fim do turno enquanto o type check falhar. Salve isto como `.claude/hooks/typecheck.sh`:

```bash
#!/usr/bin/env bash
# Exit 2 sends stderr back to Claude and keeps it working.
input=$(cat)
if [ "$(echo "$input" | jq -r '.stop_hook_active')" = "true" ]; then
  exit 0   # already retried once; let the turn end
fi
npx tsc -p tsconfig.json --pretty false 1>&2 || exit 2
```

e registre em `.claude/settings.json`:

```json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          { "type": "command", "command": ".claude/hooks/typecheck.sh" }
        ]
      }
    ]
  }
}
```

Quando um hook Stop sai com código 2, o Claude Code não para e mostra o stderr do hook ao Claude, então o agente vê os erros do compilador e continua corrigindo. A verificação de `stop_hook_active` deixa o turno terminar na segunda tentativa em vez de entrar em loop. O [guia de hooks do Claude Code](/pt/blog/claude-code-hooks-guide/ "Guia de hooks do Claude Code") aprofunda os hooks. Um hook `PostToolUse` em `Edit|Write` pode rodar uma verificação mais rápida depois de cada edição, mas em projetos grandes um `tsc` completo por edição fica lento.

O Codex e o Gemini CLI não leem os hooks do Claude, então coloque a mesma regra no `AGENTS.md` ou no `GEMINI.md`: “Antes de dizer que uma tarefa terminou, rode `npm run typecheck` e `npm test` e cole as últimas linhas da saída.” Pedir a saída é o que faz a instrução pegar.

## Passo 7: aumente o rigor uma flag de cada vez

Quando todos os arquivos forem `.ts`, ou uma área inteira for, ative as verificações strict nesta ordem, com um commit e uma tarefa de agente separados para cada uma:

1. **`noImplicitAny`**: todo parâmetro e variável precisa ter um tipo conhecido. Essa gera mais erros e os tipos mais úteis.
2. **`strictNullChecks`**: `null` e `undefined` viram tipos próprios. Essa encontra bugs reais, do tipo “cannot read property of undefined”.
3. **O resto da família strict**: `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `useUnknownInCatchVariables`. Geralmente menos erros cada uma.
4. **`"strict": true`** substitui as flags individuais quando todas passam.
5. **Extras opcionais** que não fazem parte do `strict`: `noUncheckedIndexedAccess` (leituras por índice podem ser `undefined`) e `exactOptionalPropertyTypes`.

Você não precisa ligar uma flag para o repositório inteiro de uma vez. Uma segunda configuração que estende a primeira e cobre só as pastas terminadas permite que o código strict continue strict enquanto o resto alcança:

```json
{
  "extends": "./tsconfig.json",
  "compilerOptions": { "strict": true },
  "include": ["src/billing", "src/utils"]
}
```

Salve como `tsconfig.strict.json` e rode `tsc -p tsconfig.strict.json` no CI ao lado da verificação principal. Cada vez que um agente termina uma pasta, ele a adiciona ao `include`. Quando o `include` cobrir `src`, mova o `strict: true` para a configuração principal e apague o segundo arquivo.

Um cuidado com o `strictNullChecks`: a correção mais rápida para “possibly undefined” é a non-null assertion `!`. Algumas não fazem mal; dezenas significam que o agente está escondendo o problema. Peça uma guarda, um valor padrão ou uma mudança de tipo em vez disso, e trate cada `!` novo como um `any` novo na revisão.

## Passo 8: divida o trabalho entre agentes em paralelo

Uma migração é um dos poucos trabalhos em que rodar vários agentes ao mesmo tempo compensa de verdade, porque as pastas são bastante independentes. O jeito de fazer funcionar é dividir por diretório e dar a cada agente o próprio [git worktree](/pt/glossary/git-worktree/ "O que é um git worktree"):

```bash
git worktree add ../app-ts-billing -b ts/billing
git worktree add ../app-ts-orders  -b ts/orders
git worktree add ../app-ts-users   -b ts/users
```

Inicie um agente em cada pasta com o prompt de passo acima, apontado para o diretório dele. Algumas regras evitam que as branches colidam:

- **Arquivos compartilhados têm um único dono.** `tsconfig.json`, `package.json`, o lockfile, `eslint.config.js` e as declarações compartilhadas em `src/types/` só mudam na branch principal, por você ou por um agente coordenador. Os agentes das pastas pedem mudanças ali em vez de fazê-las.
- **Os tipos compartilhados vêm primeiro.** Se várias pastas precisam do mesmo tipo `User` ou `Order`, faça disso um passo sequencial antes de os paralelos começarem. Senão, três agentes escrevem três interfaces `User` ligeiramente diferentes.
- **Instale os pacotes `@types` de forma centralizada.** Duas branches que adicionam pacotes diferentes reescrevem o lockfile e entram em conflito toda vez.
- **Não paralelize um ciclo.** Pastas que se importam em círculo pertencem a um único agente.
- **Faça o merge de uma branch por vez e rode o type check depois de cada merge.** Duas branches podem passar no `tsc` separadamente e falhar juntas, por exemplo quando uma muda um tipo exportado de que a outra depende. Faça o rebase da próxima branch sobre a nova main antes do merge.

[Git worktrees para agentes em paralelo](/pt/blog/git-worktree-parallel-agents/ "Git worktrees para agentes em paralelo") detalha a configuração e a limpeza. Três ou quatro agentes é um começo sensato; além disso, revisar as branches costuma virar o gargalo, não escrevê-las.

## Passo 9: deixe os testes provarem que nada mudou

Os tipos dizem que o código é coerente consigo mesmo. Só os testes dizem que ele ainda faz o que fazia. Durante uma migração:

- **Rode a suíte inteira depois de cada passo**, não só os testes da pasta convertida. Um export alterado pode quebrar quem o chama em outro lugar.
- **Converta os arquivos de teste depois do código que eles testam**, num passo separado, e não mude asserções durante a conversão. Um teste cujo valor esperado mudou num commit “só de tipos” é um sinal de alerta.
- **Confira se o test runner lida com `.ts`.** O Vitest roda arquivos TypeScript sem configuração extra; o Jest precisa de um transform como `ts-jest` ou `babel-jest` com o preset de TypeScript.
- **Adicione testes onde os tipos não alcançam.** Código que faz parsing de entrada, conversa com uma API ou lê variáveis de ambiente recebe dados com cara de `any` em runtime, digam os tipos o que disserem. Alguns testes ali valem mais que anotações perfeitas. [Escrever testes unitários com IA](/pt/blog/write-unit-tests-with-ai/ "Escrever testes unitários com IA") mostra como fazer os agentes escreverem testes úteis.

## Armadilhas comuns

- **Converter `require` em `import` muda a semântica.** Exports default, `module.exports = fn` e a interoperabilidade diferem entre CommonJS e ES modules. Deixe a mudança de formato de módulo como um passo próprio, ou fora da migração.
- **Tipos que mentem.** Uma interface escrita a partir de um único objeto de exemplo pode perder campos que só aparecem às vezes. Prefira tipos escritos a partir de dados reais ou do schema da API.
- **“Correções” de comportamento escondidas num commit de tipos.** `==` virando `===`, um parâmetro padrão adicionado, um `if` reordenado. Cada um pode mudar o comportamento. Rejeite e peça um commit separado.
- **Silenciar o compilador pela configuração.** O `skipLibCheck` não tem problema; desligar uma flag strict para uma branch passar, sim. É por isso que os agentes não podem editar o `tsconfig.json`.
- **Arquivos gerados ou de terceiros.** Exclua-os no `tsconfig.json` em vez de deixar um agente “tipá-los”.

## Fazendo isso com o AgentVera

O AgentVera roda Claude Code, Codex, Gemini CLI e outros agentes de CLI lado a lado por projeto, o que combina com uma migração dividida em muitos passos pequenos.

- **Um worktree por pasta.** No [painel Git](/pt/features/git-and-github/ "Git e GitHub"), crie um worktree para cada diretório e inicie um agente nele com um clique, Claude Code numa pasta e Codex em outra, se quiser.
- **[File ownership](/pt/features/file-ownership/ "File ownership") (posse de arquivos).** Dê ao agente coordenador `tsconfig.json`, `package.json` e `src/types/**`. Enquanto ele roda, as edições de outros agentes nesses arquivos são recusadas antes de acontecer e eles são orientados a pedir ao dono; agentes Claude Code são barrados por um hook, outras CLIs recebem a regra nas instruções.
- **[Verify](/pt/features/verify/ "Verify").** Escreva “o typecheck passa, os testes passam, nenhuma asserção de teste mudou” na sua mensagem. Quando o turno termina, um verificador separado que não consegue editar arquivos roda os comandos de teste permitidos, lê o diff e marca cada critério como aprovado ou reprovado, com evidências.
- **[Code review](/pt/features/code-review/ "Code review").** Um segundo modelo lê cada turno concluído e lista os apontamentos com arquivo:linha, um bom lugar para pegar casts novos e asserções `!`.
- **[Merge queue](/pt/features/merge-queue/ "Merge queue") (fila de merge).** As branches das pastas entram uma por vez sobre a main mais recente, e o seu comando de verificação, como `npm run typecheck && npm test`, roda no resultado do merge antes de qualquer coisa ser mesclada.
- **[Parallel team](/pt/features/parallel-team/ "Parallel team") (Pro e Team).** Um agente pode dividir a migração em até oito partes, com os arquivos que cada parte pode mexer, rodar cada parte no próprio worktree, mesclar as partes concluídas numa branch de integração e rodar a verificação final, para você revisar um único pull request.

## Conclusão

Migrar JavaScript para TypeScript com IA funciona quando o compilador e os testes estão no comando, não a confiança do agente. Comece com uma configuração que aceita JavaScript e define `strict: false` de propósito, converta as folhas primeiro em passos pequenos, mantenha o `any` dentro de um orçamento que só encolhe, faça do `tsc` a verificação de saída do agente, aumente o rigor flag por flag e divida o trabalho em paralelo por diretório, com os arquivos compartilhados sob a responsabilidade de um único agente. Cada passo é chato e revisável, que é exatamente o que você quer de uma migração.

Se você vai acompanhar os agentes passo a passo durante a migração, [pair programming com IA](/pt/blog/ai-pair-programming/ "Pair programming com IA") explica quando deixar o agente conduzir e quando revisar cada passo.

## Perguntas frequentes

**A IA consegue converter um projeto JavaScript para TypeScript?** Sim, e é um dos trabalhos em que os agentes vão bem, desde que você dê a eles um type checker funcionando, uma ordem fixa e passos pequenos. Deixe o agente converter um arquivo ou pasta por vez, rodar o tsc e os testes depois de cada passo e fazer um commit separado para cada um.

**Devo ativar o strict mode desde o início?** Não numa base de código existente. Desde o TypeScript 6.0 o strict vem ligado por padrão, então defina "strict": false explicitamente no começo, converta os arquivos e depois ative noImplicitAny, strictNullChecks e as outras flags strict uma de cada vez.

**Como impeço o agente de usar any em todo lugar?** Torne isso mensurável. Reporte any explícito com a regra no-explicit-any do typescript-eslint, proíba @ts-ignore com ban-ts-comment, mantenha um número de orçamento no repositório e faça o CI falhar quando a contagem passar dele. Informe o orçamento nas instruções do agente.

**Vários agentes podem migrar pastas diferentes ao mesmo tempo?** Sim, se cada um trabalhar no próprio git worktree e for dono de um diretório. Arquivos compartilhados como tsconfig.json, package.json e declarações de tipos globais devem pertencer a um único agente, e cada branch deve passar pelo type check de novo depois do merge.

**Preciso reescrever meus testes durante a migração?** Não. Primeiro mantenha os testes existentes rodando contra o código convertido; são eles que mostram que o comportamento não mudou. Converta os arquivos de teste depois do módulo que eles cobrem e evite mudar asserções no mesmo passo.

---

**Sobre o AgentVera.** AgentVera é um app desktop para macOS, Windows e Linux, desenvolvido pela Solviera Teknoloji. Ele roda 18 agentes de programação com IA (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) lado a lado, por projeto, em terminais reais com as instalações e logins de CLI do próprio usuário. O plano gratuito tem agentes ilimitados. Baixar: https://agentvera.dev/pt/download/ · Preços: https://agentvera.dev/pt/pricing/
