惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
云风的 BLOG
云风的 BLOG
aimingoo的专栏
aimingoo的专栏
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
F
Full Disclosure
A
About on SuperTechFans
C
Check Point Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
Know Your Adversary
Know Your Adversary
K
Kaspersky official blog
L
LINUX DO - 热门话题
Recorded Future
Recorded Future
C
Cisco Blogs
M
MIT News - Artificial intelligence
T
Tenable Blog
G
GRAHAM CLULEY
月光博客
月光博客
Recent Announcements
Recent Announcements
V
Visual Studio Blog
IT之家
IT之家
T
The Exploit Database - CXSecurity.com
The GitHub Blog
The GitHub Blog
T
Threat Research - Cisco Blogs
D
DataBreaches.Net
P
Privacy International News Feed
P
Proofpoint News Feed
I
Intezer
博客园 - 叶小钗
C
CXSECURITY Database RSS Feed - CXSecurity.com
The Hacker News
The Hacker News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - Franky
SecWiki News
SecWiki News
宝玉的分享
宝玉的分享
P
Palo Alto Networks Blog
Last Week in AI
Last Week in AI
小众软件
小众软件
Hacker News - Newest:
Hacker News - Newest: "LLM"
O
OpenAI News
N
News and Events Feed by Topic
Microsoft Security Blog
Microsoft Security Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
N
News and Events Feed by Topic
The Cloudflare Blog
Spread Privacy
Spread Privacy
酷 壳 – CoolShell
酷 壳 – CoolShell
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
B
Blog RSS Feed

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Я перевёл 200K строк JS на TS с Claude Code. Что прошло, что сломалось
Александр Рогов · 2026-05-28 · via Все публикации подряд на Хабре

10 мин

15K

За 6 недель Claude Code преобразовал 200K строк JS в strict TypeScript. Не переименование файлов, а настоящая типизация: интерфейсы, строгие null-чеки, перехваченные баги в проде. Тут разбор реального кейса с цифрами, ошибками агента и главным вопросом: стоит ли вам это повторять?

1. Зачем мигрировали

Кодовой базе было 6 лет. Node.js-монолит на 200K строк, который обслуживал 50K DAU. Восемь разработчиков за эти годы оставили след: файлы с JSDoc, файлы без него, 200+ комментариев // @ts-ignore от попытки миграции в 2022 году, которая дошла до 15% и остановилась.

Боль была конкретная: 30% каждого спринта уходило на отладку ошибок, которые TypeScript поймал бы при компиляции. Null reference в проде. API-ответы с неожиданной структурой. Рефакторинг любого модуля превращался в игру в минёра.

Статистика за последние 12 месяцев до миграции:

  • 4–6 type-related багов на спринт

  • 3 недели онбординга нового разработчика

  • Один инцидент в проде на каждые 2 месяца с причиной «неожиданный null»

Всё это хорошо известно. Непонятно было другое: как мигрировать не замораживая разработку на полгода.

Ручная оценка: 2000+ человеко-часов. При команде в 8 человек — больше 3 месяцев работы только над типами, если заморозить фичи. Нереально.

2. Почему Claude Code, а не ручная миграция

Первый инструмент который приходит в голову — codemods. ts-migrate от Airbnb, jscodeshift. Мы пробовали. Они умеют переименовывать файлы и расставлять any везде где нет явного типа. Это не миграция, это просто смена расширения с легализованным any в каждой функции.

Проблема в том, что тип функции невозможно вывести статически не зная контекста. Вот простой пример:

// src/utils/format-price.js
function formatPrice(value, currency) {
  if (!value) return '—';
  return ${value.toFixed(2)} ${currency};
}

Codemod поставит any, any. Claude прочитает 15 мест где эта функция вызывается и выведет:

function formatPrice(value: number | null | undefined, currency: string): string {
  if (!value) return '—';
  return ${value.toFixed(2)} ${currency};
}

Это разница между формальным выполнением и пониманием кода.

Ключевое ограничение, которое нужно понять до старта: Claude не запускает ваш код. Он рассуждает о типах по тексту. Это означает, что для динамических паттернов (eval, runtime-зависимые типы, магия через Proxy) он будет ошибаться. Об этом подробнее в разделе 11.

3. Подготовка кодовой базы

Это 40% успеха. Большинство команд пропускают этот шаг и потом жалуются что «Claude ставит any везде». Не ставит — просто нет контекста.

Шаг 1: tsconfig.json для миграции

{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "outDir": "./dist",
    "rootDir": "./src",
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/*/"]
}

allowJs: true — JS и TS файлы живут вместе. strict: false — затягивать будем постепенно. checkJs: false — не проверяем старый JS, только новый TS.

Сразу добавьте tsc --noEmit в CI. На первом этапе он ловит ноль ошибок — это нормально. Инфраструктура уже есть.

Шаг 2: CLAUDE.md для миграционного проекта

Это не опционально. Без него Claude будет делать то что кажется ему правильным — а не то что нужно вам.

Migration Rules
Convert one module at a time, never mix migration with featuresPrefer explicit types over inference when in doubtIf you cannot infer the type, use unknown not anyAdd // MIGRATION: reason when using type assertions (as)Do not refactor logic during conversion — types onlyIf a function has side effects that depend on runtime values,  flag it with // MIGRATION: needs manual review

Правило «не рефакторить логику» — самое важное. Без него Claude будет попутно «улучшать» код, и вы не сможете отличить баг от его улучшения в diff.

Шаг 3: src/types/ с boundary types

До того как отдавать что-либо Claude, создайте типы для границ системы: API-ответы, модели БД, shared interfaces. Это даёт каждому конвертируемому файлу что-то, на что можно опереться.

// src/types/api.ts
interface User {
  id: string;
  email: string;
  role: 'admin' | 'editor' | 'viewer';
  createdAt: Date;
  profile: UserProfile | null;
}
interface ApiResponse {
  data: T;
  meta: { total: number; page: number; perPage: number };
}

Правило: типизировать границы системы раньше внутренностей. API и DB-модели — первые, бизнес-логика — последняя.

4. Стратегия батчей

«Отдай Claude весь проект и пусть переконвертирует» — проверенный путь к катастрофе. Context window лопается на 800+ строках, агент начинает путать типы из начала файла с концом, ставит any там где раньше справлялся.

Правило: один модуль = один PR = один батч.

Приоритизация модулей: leaf modules первые. Это файлы без внутренних импортов — утилиты, хелперы, валидаторы. Они самодостаточны, конвертируются без побочных эффектов.

Неделя 1-2:  utils/, helpers/, validators/    ← leaf, начало
Неделя 3-5:  services/                         ← зависят от types/
Неделя 6-8:  controllers/, routes/             ← зависят от services/
Неделя 9-10: core/, app.ts                     ← последние

Размер батча: 5–15 файлов. Больше — Claude теряет нить.

Еженедельный scorecard помогает не потерять мотивацию:

#!/bin/bash
# migration-stats.sh
JS_COUNT=$(find src -name ".js" | wc -l)
TS_COUNT=$(find src -name ".ts" -o -name ".tsx" | wc -l)
TOTAL=$((JS_COUNT + TS_COUNT))
PCT=$((TS_COUNT  100 / TOTAL))
echo "JS: $JS_COUNT | TS: $TS_COUNT | Progress: $PCT%"
echo ""
echo "Strict mode errors:"
npx tsc --noEmit --strict 2>&1 | grep "error TS" | wc -l

Запускали каждую пятницу, скидывали в Slack. Видеть как 12% → 45% → 78% — это работает на мотивацию лучше любого митинга.

Практическое правило: PR с миграцией должен быть скучным. Reviewer смотрит на diff и думает «ну да, types добавили». Если PR интересный — значит Claude что-то не то сделал.

5. Промпт-паттерны которые сработали

Это самый ценный раздел. Промпты которые работают в продакшене, не в демо.

Промпт #1 — Базовая конвертация с контекстом

Convert src/utils/format-price.js to TypeScript.
Context: this function is called in:
src/components/ProductCard.jsx (line 34)src/api/checkout.js (line 112)src/reports/revenue.js (line 78, 89)
Rules from CLAUDE.md:
no any — use unknown if type is unclearadd // MIGRATION: reason for type assertionsdo not refactor logic, types only
After conversion, list any places where you had to make
a judgment call about the type.

Последняя строчка важна: Claude честно скажет «я решил что это string, потому что видел только строковые вызовы, но есть строка 89 в revenue.js которая мне непонятна».

Промпт #2 — Поиск типа через использование

I need to type the return value of getUserData() in
src/services/user.service.js.
Look at every place where getUserData() is called across
the codebase. For each call site, show me:
1. The file and line
2. How the result is used
3. What TypeScript type this implies
Then suggest the return type.

Это работает на 200K строк потому что у Claude есть codebase как контекст — он буквально ищет по файлам.

Промпт #3 — Восстановление после ошибки

In the last conversion of format-price.ts you changed
behavior on line 23: the original checked !value
(catches null, undefined, 0, ''), your version checks
value === null || value === undefined (misses 0 and '').
Revert ONLY that logic change. Keep all type annotations.
Explain why the original check was correct.

Конкретность — ключ. «Ты что-то сломал» не работает. «На строке 23 ты изменил логику вот так, верни как было» — работает.

Промпт #4 — Batch check после пачки файлов

Run through the TypeScript errors in the last migration batch
(src/utils/). For each error:
Is it a type annotation gap I need to fill?Or is it a real logic bug you found during conversion?
For real bugs: describe what the bug is and whether it
exists in the original JS.

Один из таких прогонов нашёл баг в user.service.js который жил в проде полтора года.

Инсайт: Claude находит баги не как цель, а как побочный эффект типизации. Это одна из главных ценностей миграции с AI.

6. Что Claude сломал

Честная часть. Без этого статья была бы рекламой.

Случай 1: Тихое изменение поведения

// Оригинал: src/utils/concat-name.js
function concatName(first, last) {
  return (first || '') + ' ' + (last || '');
}

Claude сконвертировал:

// После конвертации
function concatName(first: string | null, last: string | null): string {
  return ${first} ${last};
}

Логически «правильно» — типы проставлены верно. Но поведение изменилось: null теперь рендерится как строка "null" вместо пустой строки. В продакшене это сломало отображение имён пользователей у которых не заполнено поле.

Поймали через integration-тест который зафиксировал снапшот вывода.

Случай 2: Неправильный вывод типа из большинства

Функция возвращала string | number в зависимости от env-переменной PRICE_FORMAT. Claude посмотрел на 47 call sites, в 46 из них тип использовался как string, и поставил string.

Сорок седьмой кейс — метрика в Grafana которая ждала number. Падала раз в неделю с NaN.

Случай 3: Потеря контекста в больших файлах

Файл 800 строк. Тип из начала файла к середине Claude «терял» — переопределял его менее конкретным вариантом. Решение: файлы больше 300 строк конвертировать частями, по 150–200 строк за раз.

Главный урок: Каждый migration PR обязан проходить review у человека. Не формально — реальное diff-review с вопросом «изменилась ли логика?» Автоматика этого не поймает, потому что тесты проверяют поведение, а не намерение.

7. Тесты как safety net

Без тестов миграция с AI — это Russian roulette. Красивый TypeScript который ломает логику.

Главный инсайт: integration-тесты важнее unit перед AI-миграцией. Unit-тесты проверяют что функция делает одно конкретное действие. Integration-тесты фиксируют поведение модуля целиком — именно это и меняет Claude когда «улучшает» код.

Стратегия простая: перед тем как отдавать модуль Claude, покрой его snapshot-тестом.

// Перед миграцией: фиксируем текущее поведение
describe('formatPrice', () => {
  it('snapshot: existing behavior', () => {
    expect(formatPrice(10.5, 'USD')).toMatchSnapshot();
    expect(formatPrice(null, 'USD')).toMatchSnapshot();
    expect(formatPrice(0, 'EUR')).toMatchSnapshot();
    expect(formatPrice(undefined, 'RUB')).toMatchSnapshot();
  });
});

После конвертации тот же тест должен пройти. Если снапшот изменился — Claude изменил поведение. Разбирайся почему.

Три правила:

  1. Если у модуля нет тестов — конвертируй вручную. Не отдавай Claude то, что не можешь проверить. Исключение: чистые utility-функции с очевидной логикой.

  2. Тесты конвертируй последними, отдельным PR. Мы сначала хотели конвертировать всё разом — исходники и тесты. Не делайте так. Нетипизированные тесты проверяют типизированный код — это нормально. Зато если тест упадёт, ты точно знаешь что сломал Claude, а не то что тест написан криво.

  3. any в тестах = сигнал тревоги. Если после конвертации в тестовом файле появился any — это значит что тип в источнике недостаточно конкретный.

После того как мы добавили обязательные snapshot-тесты перед каждым батчем, количество случаев «Claude тихо сломал логику» упало с 3-4 за неделю до нуля за последние 4 недели миграции.

8. CI/CD изменения

Миграция без CI-закрепления — это строительство без фундамента. Через месяц кто-нибудь добавит .js файл «временно» и всё пойдёт откатываться.

Шаг 1: Guard против новых JS-файлов

# .github/workflows/typescript-guard.yml
name: No new JS files  run: |
    NEW_JS=$(git diff --name-only origin/main \
      | grep '\.js$' \
      | grep -v '\.config\.js$' \
      | grep -v '\.eslintrc\.js$')
    if [ -n "$NEW_JS" ]; then
      echo "New .js files detected. All new code must be TypeScript."
      echo "$NEW_JS"
      exit 1
    fi

Шаг 2: Поэтапное включение strict-флагов

Не включай strict: true сразу — это сотни ошибок, которые демотивируют команду. Включай по одному:

// Неделя 8: первый флаг
{ "strictNullChecks": true }
// Результат у нас: 847 ошибок → 2 недели → +3 реальных prod-бага найдено
// Неделя 10
{ "noImplicitAny": true }
// Результат: 312 ошибок → 1 неделя → все function parameters
// Неделя 11
{ "strictFunctionTypes": true }
// Результат: 56 ошибок → 3 дня
// Неделя 12: финал
{ "strict": true }
// Результат: 23 edge-case ошибки → 2 дня

Шаг 3: ESLint запрет any — после 100% TS

{
  "@typescript-eslint/no-explicit-any": "error",
  "@typescript-eslint/no-unsafe-assignment": "error",
  "@typescript-eslint/no-unsafe-member-access": "error"
}

И последнее: добавь вывод прогресса в CI. Мы показывали процент конвертации в каждом PR-check — это маленькая деталь, которая сильно работает на мотивацию.

9. Метрики после миграции

Прошло 6 месяцев после финала. Вот реальные цифры:

Метрика

До миграции

После

Δ

Type-related баги на спринт

4–6

0–1

-85%

Время онбординга нового разработчика

3 нед

1.5 нед

-50%

Уверенность команды в рефакторинге (опрос, 10-балльная шкала)

3.2

8.1

+153%

Catch rate в CI

~40%

~95%

+137%

Время code review сложного PR

~45 мин

~20 мин

-55%

Время сборки

12 сек

18 сек

+50%

Последняя строчка — да, build замедлился на 50%. Приняли без раздумий.

Одна цифра которой нет в таблице и которую невозможно померить количественно: разработчики перестали бояться трогать чужой код. До миграции «PR в user-сервис» звучало как предупреждение. После — это просто PR.

Неожиданный бонус: strictNullChecks нашёл 3 производственных бага которые мы не искали. Один — race condition: профиль пользователя мог быть null первые 200мс после регистрации. Мы тихо глотали ошибку несколько месяцев. Второй — функция в модуле платежей принимала amount: number но в одном flow прилетал string из FormData. TypeScript это поймал сразу, а runtime просто умножал строку на 1 и получал NaN в сумме заказа. Третий баг нашёл разработчик, которого мы взяли через месяц после окончания миграции: он сказал «не понимаю как это раньше работало». Вот именно.

10. Что бы я сделал иначе

  1. Включил strictNullChecks с первого дня. Мы ждали 80% конвертации. Ошибка. Если бы включили сразу, нашли бы prod-баги на месяц раньше, и каждый конвертируемый файл сразу писался бы с правильными null-паттернами.

  2. Типизировал тесты первыми, а не последними. Мы оставили тестовые файлы на самый конец. В итоге имели типобезопасный source code покрытый нетипизированными тестами — парадокс. Тесты сами по себе содержали type-ошибки которые маскировали проблемы.

  3. Запретил as без комментария с первого PR. Результат: 200+ необъяснённых type assertions в кодовой базе. Половину уже не помним зачем. Правильно:

    // MIGRATION: Prisma returns any here until we upgrade to v5
    const user = result as User;
    
  4. Сделал CLAUDE.md специфичным для миграции сразу. Первые 2 недели работали с общим CLAUDE.md. Когда добавили секцию ## Migration Rules с явным запретом any и требованием unknown — качество конвертаций заметно выросло. Claude начал задавать уточняющие вопросы вместо того чтобы молча ставить any.

11. Когда НЕ стоит мигрировать с AI

Это не серебряная пуля. Есть случаи когда Claude Code скорее навредит чем поможет.

Кодовая база без тестов с запутанными side effects. Claude не запускает код — он рассуждает. В коде с неочевидными глобальными эффектами он поставит «правильные» типы но изменит логику. Без тестов ты этого не заметишь.

Файлы больше 500 строк со сложной бизнес-логикой. Context window не справится. Конвертируй вручную или разбей файл сначала.

Динамические типы через runtime. eval, dynamic require, сложные Proxy-паттерны — Claude угадывает. В лучшем случае поставит unknown, в худшем — поставит конкретный тип который окажется неверным в 10% случаев.

Команда плохо знает TypeScript. Это звучит контринтуитивно — «как раз AI поможет». Не поможет. Migration PR-ы «будут выглядеть правильно» но таить ошибки, которые некому заметить. Сначала команде нужно понять TypeScript, потом делегировать механику Claude.

Правило большого пальца: если ты не можешь проверить что Claude сделал правильно — не давай ему это делать.

12. Чек-лист для вашей команды

Всё что выше — в одном списке. Скопируй в свой Notion или Confluence.

Подготовка

  1. Настроить tsconfig.json: allowJs: true, strict: false, tsc --noEmit в CI

  2. Создать src/types/ с boundary types: API-ответы, DB-модели, shared interfaces

  3. Написать секцию ## Migration Rules в CLAUDE.md с явным запретом any

Процесс

  1. Начинать с leaf modules: utils/, helpers/, validators/

  2. Покрыть модуль snapshot-тестами ДО отдачи Claude

  3. Один модуль = один PR, никогда не смешивать с фичами

  4. Файлы > 300 строк конвертировать частями по 150–200 строк

  5. Еженедельный scorecard (% TS-файлов, количество strict-ошибок)

CI/CD

  1. Guard против новых .js файлов в PR

  2. Включать strict-флаги поочерёдно: strictNullChecksnoImplicitAnystrict

  3. Запретить any через ESLint после 100% конвертации

Review

  1. Каждый migration PR — живой code review на предмет изменения логики

  2. Запрет as без комментария // MIGRATION: reason с первого дня

  3. Тестовые файлы конвертировать последними, отдельным PR

Миграция заняла 6 недель вместо оценочных 6 месяцев ручной работы. Это не значит что Claude Code делает всё сам — он делает механическую работу точно и быстро, пока ты контролируешь архитектурные решения и проверяешь логику.

Главный инсайт который я не ожидал: миграция с AI — это дисциплина, а не инструмент. Правила в CLAUDE.md, batch-стратегия, обязательные тесты — без этого Claude будет просто быстрым способом создать технический долг в TypeScript.

Если у вас есть вопросы по конкретным паттернам или вы сами в процессе миграции — пишите в комментарии, отвечу.

Слежу за темой AI-инструментов в продакшене в Twitter @Alex_Rogov_js и в Telegram-канале AI-усиленный разработчик — там короткие разборы без воды.*