jmove/todo.md
loki5512344 b1af375763
feat: tsconfig compilerOptions.paths — aliased bare specifiers join the graph and follow moves
- index/tsconfig: JSONC sanitize (comments, trailing commas), baseUrl +
  star/exact path keys, first candidate only, longest-prefix wins; invalid
  or missing tsconfig silently means no aliases
- resolution pass: '.'-prefixed specifiers resolve relatively as before,
  bare ones go through the alias table and join the importer graph
- planner: remap keeps the alias shape while the moved file stays inside
  the mapped tree (morphological, since destinations do not exist yet),
  exact keys keep meaning only while still resolving to the target;
  otherwise the previous relative rewrite applies unchanged
- resolve_base extracted from resolve_module (shared extension/index
  guessing); MODULE_EXTS is the exact inverse for remapping
- e2e: @utils/str.ts -> @utils/text.ts inside alias, -> ./core/text.ts
  when leaving it, @cfg falls back to ./settings; check passes on aliases
2026-09-15 19:17:06 +02:00

11 KiB
Raw Blame History

jmove — TODO

Правила

  • KISS — simplest solution that works; no speculative abstractions.
  • DRY — no duplicated logic; extract into a function/module instead.
  • SOLID — single responsibility per module (cli/core/parser/cache), open for extension (new languages) via the parser trait, no deps on implementation details.
  • Max 250 lines per file — split when exceeded.
  • Max 4 files per folder (module) — split the module when exceeded.
  • Каждый коммит проходит cargo fmt --check и cargo clippy -- -D warnings.
  • Запрещён mёртвый код: unused code удаляется или реализуется; #[allow(dead_code)] не использовать.

MVP (Phase 1)

  • CLI skeleton (clap) — команды: mv, check, --dry-run, --json
  • Сканер файлов проекта (уважать .gitignore через ignore крейт)
  • Парсер импортов для TypeScript/JS через tree-sitter
  • Разрешение путей: extension guessing (.ts/.tsx/.js/...), index.* (в MVP — без этого инструмент игрушка)
  • Построение графа зависимостей (файл → что импортирует)
  • Инвертированный граф (файл → кто его импортирует)
  • Вычисление нового относительного пути после mv
  • Rewrite импортов в файлах (трогаем только specifier-строку, никогда не реформатим statement)
  • Атомарный apply (сначала rewrite, потом mv) + rollback при ошибке
  • Dry-run режим с diff выводом
  • Команда check (битые импорты, exit code 2) — self-test инструмента
  • Тесты: unit (parser, core) + e2e (CLI на фикстурах)

AI / Agent support

  • --json флаг на всех командах (status ok|dry_run|error)
  • --dry-run + --json (preview без записи на диск)
  • Стабильные error codes (TARGET_EXISTS, SOURCE_NOT_FOUND, ...) + hint поле
  • Exit codes: 0 ok / 1 error / 2 broken imports
  • docs/SKILL.md — машиночитаемая документация для AI агентов
  • docs/EXAMPLES.md — примеры для людей и агентов

Phase 1.5 — Java (наша ниша, аналогов в CLI нет)

  • tree-sitter Java грамматика: package + import extraction
  • Детект source root (src/main/java, src/) и соответствие package ⇄ директория
  • mv = три синхронных правки: package, все import в проекту, физический перенос
  • e2e фикстуры tests/java/

Phase 1.6 — fix (auto-fix мелких ошибок; тот же safety-движок, что mv)

Мотивация: jmove уже умеет index → план правок → dry-run diff → atomic apply → --json. Превращаем «mv» в обобщённый «найти правки → применить безопасно». Слоган: mv + fix, один движок правок, два генератора планов. Решение: паттерны (вариант 2) — ДА; ML (вариант 1) — НЕТ (детерминизм = продукт: dry-run/rollback/error-codes не переживают недетерминированный движок). AI оставляем СНАРУЖИ: при неоднозначности jmove отдаёт candidates в --json, агент (LLM) выбирает и повторяет команду — в рамках нашего agent-first UX.

  • Edit engine: core::Edit{span,old_text,new_text} — replace/insert/delete в одном движке (rewrite_bytes generic над &[Edit], пустой span = вставка, new_text="" = удаление; overlap/malformed spans → PlanRejected, не-char-boundary/content-mismatch → StaleIndex до записи); apply/rollback/diff/json переведены минимально (MovePlan.rewrites → Edit через From)
  • Fix trait рядом с Language: fixes(path, source, index) -> Vec<FixCandidate> {rule, severity, auto_fixable, edits}; jmove fix [--rule ...] [--dry-run] [--json] (тот же apply/rollback/diff: apply_edits + render_edits_diff без move)
  • [~] Java v1: unused-imports (DONE, skip wildcard/ambiguous), missing-import (DONE: unique FQN candidate → insert import pkg.Type; at the import-block end; ambiguous/wildcard → --json candidates, applied:false; закрыт guava-разрыв «перенесли файл, соседняя ссылка без импорта умерла» — проверено mv+fix+javac SUCCESS), import-order (DONE: Google-стиль — statics первыми, ASCII-сортировка, дедуп; конфликтующие с другими правилами откладываются (prune_overlaps по severity) и сходятся за 2-3 прогона), class-name-mismatch (DONE как ПОВЕРХНОСТЬ check, не fix: починка = переименование файла, а fix-движок умеет только байтовые правки; check отдаёт находку с готовой командой jmove mv, exit code 2; rename не меняет FQN → импорты не трогаются)
  • TS v1: unused-imports (DONE: whole-statement delete, все биндинги мертвы → строка уходит; mixed used/unused НЕ трогаем — в ESM импорт исполняет побочные эффекты модуля, partial-удаление specifier'ов отложено осознанно)
  • TS v1: import-order (нет кэнона без eslint-config — grouping-конвенции плавающие; ждать запроса), add-import требует индекс экспортов (символ→файл)
  • Форматирование: свой cargo-fmt НЕ строим (вечный long-tail). Только «import formatting» (порядок/группировка — у нас уже есть spans). Опционально --format-after <cmd> (prettier / google-java-format), не зависимость
  • Интероп PMD/Checkstyle/eslint (фаза 2.5): jmove fix --report checkstyle.xml маппит violation(file,line,rule) на паттерны; на выход SARIF для CI/IDE. Маркетинг: «auto-fix for what Checkstyle only reports»

Guava real-world smoke test (google/guava @ main, JDK21, mvnw) — ПРОВЕРЕНО

  • mv Primitives primitives→util: 5 правок (4 imports + package), mvn -pl guava compile = BUILD SUCCESS, jmove check чисто
  • mv VisibleForTesting annotations→annotations.testing (63 файла): jmove переписал все 62 явных импорта корректно, НО javac упал: сам перенесённый файл ссылался на соседний GwtCompatible БЕЗ импорта (тот же пакет) → после mv ссылка битая. jmove в v1 осознанно НЕ добавляет импорты. Это главный driver для fix/missing-import из Phase 1.6 выше
  • Обе перемещения повторены как mv + авто-fix, compile = BUILD SUCCESS (guava main, JDK21): VisibleForTesting annotations→annotations.testing: mv без --source-root давал 1 правку (62 потеряны!), с --source-root guava — 63/63 через git mv; fix добавил в перенесённый файл import com.google.common.annotations.GwtCompatible (тот самый разрыв v1) + 36 import-order (сошёлся за 2 прогона); Primitives primitives→util: 5 правок + fix; jmove check = 0 broken
  • Индексация в monorepo с дублями пакетов — ПРОВЕРЕНО НА guava (см. выше): глобальный --source-root DIR — индексирует (mv/check/fix) только поддерево, FQN-коллизии исчезают, соседнее дерево не трогается; авто-определение по mv-цели осознанно НЕ делаем (явный флаг предсказуемее, см. KISS)

Phase 2

  • Кэш индекса на диске (bincode/rkyv) → .jmove/index
  • Инкрементальная переиндексация (только изменённые файлы)
  • Поддержка tsconfig paths / алиасов (@/...): JSONC-парсер (комментарии/хвостовые запятые), baseUrl + star/exact keys, longest-prefix wins; при mv алиас сохраняется, если файл остался в дереве алиаса, иначе fallback на относительный; extends/2+ кандидаты — осознанно не делаем (v1)
  • Параллельная индексация через rayon
  • --git интеграция (git mv для stage/истории): auto для tracked файлов, --no-git флаг, moved_via/would_move_via в --json
  • Перенос директорий целиком (mv папки): зеркальный batch-move всех индексируемых файлов, merged rewrites, prune пустых исходных каталогов, left_behind для неиндексируемых
  • Предупреждения о не-import ссылках: package.json exports, jest mocks, tsconfig includes, markdown links
  • prettier интеграция после rewrite (по желанию)

Phase 3

  • Поддержка Python (from/import, относительные точки)
  • Поддержка Go (per-file, НЕ whole-package как refac)
  • Команда split (авто-разбивка файла на несколько)
  • Windows-пути: core::rel_str — единый формат относительных путей на границе CLI (human/JSON/diff-заголовки/git-pathspecs всегда через /, не Path::display()); CI matrix linux+windows (cargo test --locked), checkout с core.autocrlf=input. Camino не ввели: PathBuf остаётся внутренней валютой, славши нужен только на выводе

Идеи на потом

  • LSP интеграция (jmove сам как LSP server)
  • Watch mode
  • VS Code расширение как обёртка над CLI

Конкуренты (см. docs/PLAN.md)

  • refac / ai_refac (jav-ed): TS/Py/Rust/Go/Dart, но без dry-run, лимит 30 файлов в TS, Go = весь пакет, Java нет. Наш edge: dry-run+атомарность, Java, split, скорость (без LSP), UX.