# Миграция с JavaScript на TypeScript с ИИ: безопасный план

URL: https://agentvera.dev/ru/blog/migrate-javascript-to-typescript-with-ai/ · Опубликовано: 2026-10-27

Как перевести JavaScript на TypeScript с ИИ-агентами: tsconfig, принимающий JS, порядок файлов, постепенная строгость, бюджет any и tsc как шаг проверки.

**Коротко:**

- Начните с tsconfig, который принимает JavaScript (allowJs и намеренно выключенный strict), чтобы приложение продолжало собираться, пока агенты переводят файлы по одному, начиная с листьев.
- Дайте каждому агенту одно и то же определение готовности: tsc проходит, тесты проходят, нет @ts-ignore, а число явных any укладывается в бюджет, который только уменьшается.
- Включайте строгие флаги по одному и делите работу между параллельными агентами по папкам в отдельных worktree, а общими типами и конфигом пусть владеет только один агент.

Чтобы перевести JavaScript на TypeScript с ИИ, не нужно просить агента «сконвертировать проект». Вы настраиваете конфиг TypeScript, который принимает уже существующий JavaScript, а затем агенты переводят файлы в фиксированном порядке, и готовность каждого шага определяют проверка типов и тесты. Компилятор — лучший ревьюер, которого вы можете дать агенту: он быстрый, никогда не устаёт и его невозможно уговорить на «вроде нормально».

Это руководство — практическая версия этой идеи: какой именно `tsconfig.json` взять для старта, как выбрать порядок файлов, как ужесточать строгость без недели красных сборок, как держать `any` под контролем, как сделать проверку типов собственным шагом проверки агента и как разделить работу между параллельными агентами без конфликтов слияния. Об общей страховке при рефакторинге legacy-кода (характеризационные тесты, откат, ревью) сначала прочитайте [рефакторинг legacy-кода с ИИ](/ru/blog/refactor-legacy-code-with-ai/ "Рефакторинг legacy-кода с ИИ"); здесь это не повторяется.

Детали, зависящие от версии, приведены по официальной документации TypeScript на октябрь 2026 года.

## Почему агенты хорошо справляются с этой работой

Миграция с JS на TS — в основном механическая работа с ясным сигналом в конце каждого шага. Именно в этом кодинг-агенты сильны:

- **Обратная связь мгновенная.** `tsc` выводит файл, строку и код ошибки. Агент может прочитать это и исправить без догадок.
- **Работа естественно делится.** Файлы и папки можно переводить по одному, так что задача распадается на множество мелких шагов, которые легко проверить.
- **Скучной части очень много.** Добавить типы параметров, написать интерфейсы для уже существующих объектов и поставить пакеты `@types` — это та работа, которую люди откладывают годами.

Риск так же очевиден. Агент, которого торопят сделать компилятор зелёным, потянется к `any`, приведениям `as` и `@ts-ignore`, потому что они заставляют ошибки исчезнуть. Большая часть этого руководства — о том, как закрыть эти лазейки.

## Шаг 1: tsconfig, который принимает ваш JavaScript

Первый коммит не должен менять код приложения. Он добавляет TypeScript и конфиг, который проверяет типы в проекте, пока все файлы ещё `.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"]
}
```

Что делает каждая настройка:

- **`allowJs`** позволяет файлам `.js` и `.ts` жить в одной программе, так что файл `.ts` может импортировать файл `.js` и наоборот.
- **`checkJs: false`** не даёт TypeScript сообщать об ошибках в файлах, которых вы ещё не касались. Вы будете подключать файлы по одному.
- **`strict: false`** задан намеренно. Начиная с TypeScript 6.0 `strict` по умолчанию равен `true`, так что если его не указать, все строгие проверки включатся в первый же день.
- **`types: ["node"]`** важен по той же причине: в TypeScript 6.0 `types` по умолчанию — пустой массив, поэтому глобальные объекты Node не подхватываются, пока вы их не перечислите. Добавьте `"jest"` или `"vitest/globals"`, если ваши тесты полагаются на глобальные объекты.
- **`noEmit`** подходит проектам, где код в JavaScript уже превращает сборщик, `tsx` или фреймворк, а TypeScript только проверяет его. Если ваше приложение собирает `tsc`, уберите его и задайте `outDir`.
- **`module` и `moduleResolution`**: для приложений на Node используйте `nodenext`; для кода, который собирает сборщик, обычно правильная пара — `"module": "esnext"` с `"moduleResolution": "bundler"`.

Добавьте один скрипт и пусть CI его запускает:

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

Запустите его один раз. На кодовой базе JavaScript с выключенным `checkJs` он должен пройти или показать лишь несколько ошибок конфига. Исправьте их, закоммитьте, и у вас есть отправная точка. С этого момента «сборка зелёная» означает, что проходят и `npm run typecheck`, и `npm test`.

## Шаг 2: посмотрите ошибки через @ts-check до переименования

Ещё до переименования файла можно увидеть, что о нём думает TypeScript. Добавьте `// @ts-check` первой строкой в файл `.js`, и TypeScript покажет в нём ошибки, используя JSDoc-комментарии как типы:

```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);
}
```

Это хорошая первая задача для агента на большом файле: «Добавь `// @ts-check` в `src/orders/pricing.js`, исправь ошибки только типами JSDoc, не меняй поведение». Вы получаете типобезопасность без переименования, а последующий перевод становится почти механическим. Если вам ближе обратный подход, задайте `checkJs: true` и поставьте `// @ts-nocheck` в файлы, которые ещё не готовы; тогда шагом становится удаление этой строки.

## Шаг 3: выберите порядок, сначала листья

Сначала переводите файлы, которые ничего не импортируют из вашего собственного кода, затем файлы, которые импортируют только их, и так далее до точек входа. Так каждый переводимый файл может опираться на уже существующие типы.

Выстраивать порядок вручную не нужно. [madge](https://github.com/pahen/madge "madge") читает граф импортов:

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

`--leaves` выводит модули без зависимостей, а `--circular` — циклы импортов. О циклах стоит знать заранее: файлы в цикле приходится переводить вместе, поэтому планируйте их как один шаг.

Практичное правило для разбивки: один шаг — это либо один большой файл, либо одна папка мелких файлов, достаточно маленькая, чтобы дифф можно было проверить за один присест. Запишите список в файл в репозитории, например `MIGRATION.md`, по строке на шаг, и пусть агенты отмечают выполненное. Этот файл становится общим планом для каждого агента и каждой сессии.

## Шаг 4: один промпт для перевода, одно определение готовности

Используйте одну и ту же форму промпта для каждого шага, чтобы агенты не придумывали собственные правила:

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

Большую часть работы в этом промпте делают две строки. «Не меняй поведение во время выполнения» держит дифф проверяемым, потому что чистая миграция типов должна добавлять только аннотации, переименования и импорты. «Не редактируй tsconfig.json» перекрывает самый дешёвый путь к зелёной сборке: ослабить компилятор.

Положите стабильные части этого промпта в `CLAUDE.md` или `AGENTS.md`, чтобы каждая сессия начиналась с них; как разделить один набор правил между агентами, объясняет статья [CLAUDE.md и AGENTS.md](/ru/blog/claude-md-agents-md/ "CLAUDE.md и AGENTS.md").

## Шаг 5: бюджет any, который только уменьшается

За один проход до нуля `any` не дойти, а полный запрет обычно порождает лазейки похуже, вроде `as unknown as Foo`. Бюджет честнее: вы разрешаете некоторое число, и это число только уменьшается.

Сначала сделайте `any` и комментарии-заглушки видимыми с помощью 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,
  }],
}
```

`ban-ts-comment` с этими настройками отклоняет `@ts-ignore` и `@ts-nocheck` и принимает `@ts-expect-error` только с письменным объяснением длиной не меньше десяти символов. `@ts-expect-error` в любом случае лучшая лазейка: как только ошибку исправят, сам комментарий становится ошибкой, так что он не может устареть незаметно.

Затем посчитайте явные `any` и сравните с числом, которое хранится в репозитории:

```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; }
```

Запускайте его в CI и включите в определение готовности для агента. Когда шаг уменьшает счётчик, снижайте `.any-budget` в том же коммите. Скажите агенту, где лежит бюджет: число агенты соблюдают гораздо лучше, чем «по возможности избегай any».

Следите и за другими лазейками, которые счётчик не ловит: приведения `as` для значений, пришедших извне (`JSON.parse`, тела запросов), `Function` и `object` как типы параметров и строка `declare module '*'`, которая превращает каждый импорт в `any`. Попросите ревьюера, человека или агента, перечислить каждое новое приведение в диффе.

## Шаг 6: сделайте проверку типов шагом проверки агента

Агент, который говорит «готово», не запустив `tsc`, гадает. Сделайте проверку частью того, как агент завершает ход, а не тем, о чём вы должны не забыть попросить.

В Claude Code хук Stop может не дать ходу завершиться, пока проверка типов не проходит. Сохраните это как `.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
```

и зарегистрируйте его в `.claude/settings.json`:

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

Когда хук Stop завершается с кодом 2, Claude Code не останавливается и показывает Claude вывод stderr хука, так что агент видит ошибки компилятора и продолжает исправлять. Проверка `stop_hook_active` позволяет ходу завершиться со второй попытки, а не зациклиться. Подробнее о хуках — в [руководстве по хукам Claude Code](/ru/blog/claude-code-hooks-guide/ "Руководство по хукам Claude Code"). Хук `PostToolUse` на `Edit|Write` может запускать более быструю проверку после каждой правки, хотя на больших проектах полный `tsc` после каждой правки становится медленным.

Codex и Gemini CLI не читают хуки Claude, поэтому запишите то же правило в `AGENTS.md` или `GEMINI.md`: «Прежде чем сказать, что задача готова, запусти `npm run typecheck` и `npm test` и вставь последние строки вывода». Именно требование показать вывод заставляет инструкцию работать.

## Шаг 7: повышайте строгость по одному флагу

Когда все файлы стали `.ts` (или все файлы одной области), включайте строгие проверки в таком порядке, с отдельным коммитом и отдельной задачей для агента на каждую:

1. **`noImplicitAny`**: у каждого параметра и переменной должен быть известный тип. Этот флаг даёт больше всего ошибок и самые полезные типы.
2. **`strictNullChecks`**: `null` и `undefined` становятся отдельными типами. Этот флаг находит настоящие баги вида «cannot read property of undefined».
3. **Остальное семейство strict**: `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `useUnknownInCatchVariables`. Обычно у каждого меньше ошибок.
4. **`"strict": true`** заменяет отдельные флаги, когда все они проходят.
5. **Необязательные дополнения**, не входящие в `strict`: `noUncheckedIndexedAccess` (чтение по индексу может вернуть `undefined`) и `exactOptionalPropertyTypes`.

Включать флаг сразу для всего репозитория не обязательно. Второй конфиг, который расширяет первый и охватывает только готовые папки, позволяет строгому коду оставаться строгим, пока остальной догоняет:

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

Сохраните его как `tsconfig.strict.json` и запускайте `tsc -p tsconfig.strict.json` в CI рядом с основной проверкой. Каждый раз, когда агент заканчивает папку, он добавляет её в `include`. Когда `include` охватывает `src`, перенесите `strict: true` в основной конфиг и удалите второй файл.

Предостережение насчёт `strictNullChecks`: самый быстрый способ исправить «possibly undefined» — non-null assertion `!`. Несколько штук — нормально; десятки означают, что агент прячет проблему. Просите вместо этого проверку, значение по умолчанию или изменение типа, и на ревью относитесь к новому `!` так же, как к новому `any`.

## Шаг 8: разделите работу между параллельными агентами

Миграция — одна из немногих задач, где запуск нескольких агентов одновременно действительно окупается, потому что папки в основном независимы. Чтобы это сработало, делите по папкам и дайте каждому агенту свой [git worktree](/ru/glossary/git-worktree/ "Что такое 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
```

Запустите по агенту в каждой папке с промптом для шага, приведённым выше, нацелив его на свою папку. Несколько правил не дают веткам столкнуться:

- **У общих файлов один владелец.** `tsconfig.json`, `package.json`, lock-файл, `eslint.config.js` и общие объявления в `src/types/` меняются только в основной ветке — вами или одним координирующим агентом. Агенты папок просят изменить их, а не меняют сами.
- **Общие типы идут первыми.** Если нескольким папкам нужен один и тот же тип `User` или `Order`, сделайте его последовательным шагом до начала параллельных. Иначе три агента напишут три немного разных интерфейса `User`.
- **Ставьте пакеты `@types` централизованно.** Две ветки, каждая из которых добавляет свой пакет, переписывают lock-файл и каждый раз конфликтуют.
- **Не распараллеливайте цикл.** Папки, которые импортируют друг друга по кругу, принадлежат одному агенту.
- **Сливайте по одной ветке и проверяйте типы после каждого слияния.** Две ветки могут по отдельности проходить `tsc` и вместе падать, например когда одна меняет экспортируемый тип, на который опирается другая. Перед слиянием следующей ветки сделайте ей rebase на новый main.

Настройка и уборка подробно описаны в статье [Git worktree для параллельных агентов](/ru/blog/git-worktree-parallel-agents/ "Git worktree для параллельных агентов"). Три-четыре агента — разумное начало; дальше узким местом обычно становится не написание веток, а их проверка.

## Шаг 9: пусть тесты докажут, что ничего не изменилось

Типы говорят, что код согласован сам с собой. Только тесты говорят, что он по-прежнему делает то, что делал. Во время миграции:

- **Запускайте весь набор тестов после каждого шага**, а не только тесты переведённой папки. Изменённый экспорт может сломать вызывающий код в другом месте.
- **Переводите файлы тестов после кода, который они тестируют**, отдельным шагом, и не меняйте утверждения при переводе. Тест, у которого ожидаемое значение изменилось в коммите «только типы», — тревожный сигнал.
- **Проверьте, что тест-раннер понимает `.ts`.** Vitest запускает файлы TypeScript без дополнительной настройки; Jest нужен трансформер вроде `ts-jest` или `babel-jest` с пресетом TypeScript.
- **Добавляйте тесты там, куда не дотягиваются типы.** Код, который разбирает ввод, обращается к API или читает переменные окружения, во время выполнения получает данные формы `any`, что бы ни говорили типы. Несколько тестов там стоят больше, чем идеальные аннотации. Как заставить агентов писать полезные тесты, показано в статье [юнит-тесты с ИИ](/ru/blog/write-unit-tests-with-ai/ "Юнит-тесты с ИИ").

## Частые ошибки

- **Замена `require` на `import` меняет семантику.** Экспорты по умолчанию, `module.exports = fn` и взаимодействие модулей различаются в CommonJS и ES-модулях. Делайте смену формата модулей отдельным шагом или вообще не включайте её в миграцию.
- **Типы, которые врут.** Интерфейс, написанный по одному объекту-примеру, может упустить поля, которые появляются лишь иногда. Предпочитайте типы, написанные по реальным данным или по схеме API.
- **«Исправления» поведения, спрятанные в коммите с типами.** `==` превратилось в `===`, добавлен параметр по умолчанию, переставлен `if`. Каждое из них может изменить поведение. Отклоняйте их и просите отдельный коммит.
- **Заглушить компилятор конфигом.** `skipLibCheck` — это нормально; выключить строгий флаг, чтобы ветка прошла, — нет. Поэтому агентам и не дают редактировать `tsconfig.json`.
- **Сгенерированные и вендорные файлы.** Исключите их в `tsconfig.json`, а не позволяйте агенту их «типизировать».

## Как это сделать в AgentVera

AgentVera запускает Claude Code, Codex, Gemini CLI и других CLI-агентов бок о бок в каждом проекте, и это хорошо подходит для миграции, разбитой на много мелких шагов.

- **Worktree на каждую папку.** В [панели Git](/ru/features/git-and-github/ "Git и GitHub") создайте worktree для каждой папки и запустите в нём агента одним кликом: если хотите, Claude Code в одной папке и Codex в другой.
- **[Владение файлами](/ru/features/file-ownership/ "Владение файлами").** Отдайте координирующему агенту `tsconfig.json`, `package.json` и `src/types/**`. Пока он работает, правки других агентов в этих файлах отклоняются до того, как произойдут, и агентам говорят обратиться к владельцу; агентов Claude Code останавливает хук, другие CLI получают правило в своих инструкциях.
- **[Verify](/ru/features/verify/ "Verify").** Напишите в сообщении «проверка типов проходит, тесты проходят, ни одно утверждение в тестах не изменено». Когда ход заканчивается, отдельный верификатор, который не может править файлы, запускает разрешённые команды тестов, читает дифф и отмечает каждый критерий как пройденный или нет, с доказательством.
- **[Ревью кода](/ru/features/code-review/ "Ревью кода").** Вторая модель читает каждый завершённый ход и перечисляет замечания с file:line — хорошее место, чтобы поймать новые приведения и утверждения `!`.
- **[Очередь слияния](/ru/features/merge-queue/ "Очередь слияния").** Ветки папок попадают по одной на свежий main, и ваша проверочная команда, например `npm run typecheck && npm test`, запускается на результате слияния, прежде чем что-то будет слито.
- **[Параллельная команда](/ru/features/parallel-team/ "Параллельная команда") (Pro и Team).** Агент может разделить миграцию на до восьми частей с файлами, которые может трогать каждая часть, запустить каждую часть в своём worktree, слить готовые части в одну интеграционную ветку и запустить финальную проверку, так что вы проверяете один pull request.

## Подводим итоги

Миграция с JavaScript на TypeScript с ИИ работает, когда главные — компилятор и тесты, а не уверенность агента. Начните с конфига, который принимает JavaScript и намеренно задаёт `strict: false`, переводите сначала листья мелкими шагами, держите `any` в бюджете, который только сокращается, сделайте `tsc` проверкой на выходе для агента, повышайте строгость флаг за флагом и делите параллельную работу по папкам, а общие файлы отдайте одному агенту. Каждый шаг скучен и легко проверяется — именно этого и хочется от миграции.

Если во время миграции вы будете следить за агентами шаг за шагом, статья [парное программирование с ИИ](/ru/blog/ai-pair-programming/ "Парное программирование с ИИ") объясняет, когда позволить агенту вести, а когда проверять каждый шаг.

## Частые вопросы

**Может ли ИИ перевести проект с JavaScript на TypeScript?** Да, и это одна из задач, с которыми агенты справляются хорошо, если дать им работающую проверку типов, фиксированный порядок и мелкие шаги. Пусть агент переводит по одному файлу или папке за раз, после каждого шага запускает tsc и тесты и коммитит каждый шаг отдельно.

**Стоит ли включать strict с самого начала?** Не в существующей кодовой базе. Начиная с TypeScript 6.0 strict включён по умолчанию, поэтому сначала явно задайте "strict": false, переведите файлы, а затем включайте noImplicitAny, strictNullChecks и остальные строгие флаги по одному.

**Как не дать агенту везде ставить any?** Сделайте это измеримым. Показывайте явные any правилом typescript-eslint no-explicit-any, запретите @ts-ignore правилом ban-ts-comment, храните число-бюджет в репозитории и роняйте CI, когда счётчик его превышает. Сообщите агенту бюджет в его инструкциях.

**Могут ли несколько агентов одновременно переводить разные папки?** Да, если каждый работает в своём git worktree и владеет одной папкой. Общие файлы вроде tsconfig.json, package.json и глобальных объявлений типов должны принадлежать одному агенту, а каждую ветку нужно снова проверить на типы после слияния.

**Нужно ли переписывать тесты во время миграции?** Нет. Сначала пусть существующие тесты работают против переведённого кода: именно они показывают, что поведение не изменилось. Переводите файлы тестов после модуля, который они покрывают, и не меняйте утверждения в том же шаге.

---

**Об AgentVera.** AgentVera — настольное приложение для macOS, Windows и Linux от Solviera Teknoloji. Оно запускает 18 ИИ-агентов для программирования (Claude Code, Codex, Gemini CLI, Copilot, Cursor, Antigravity, opencode, Hermes, Qwen Code, Kimi Code, Kiro, Amp, Droid, Goose, Vibe, Crush, Aider, Cline) рядом, по проектам, в настоящих терминалах с собственными установками CLI и входами пользователя. В бесплатном тарифе количество агентов не ограничено. Скачать: https://agentvera.dev/ru/download/ · Цены: https://agentvera.dev/ru/pricing/
