Some checks are pending
CI / lint (push) Waiting to run
CI / test (ubuntu-latest) (push) Waiting to run
CI / test (windows-latest) (push) Waiting to run
--report FILE on check and fix writes the findings a run already computed into CI-consumable formats chosen by extension: .sarif (single run, %SRCROOT%-relative URIs, properties.autoFixable, rule registry with descriptions) and .xml (Checkstyle, source=jmove.<rule>). Unknown suffixes fail fast with INVALID_ARGUMENT; stdout and exit codes are untouched, and clean runs write valid empty documents. The check handler moved into cli::report (its format siblings); fix emits the candidate list in dry-run and apply modes. Marketing angle from the plan: auto-fix for what Checkstyle only reports.
12 KiB
12 KiB
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_bytesgeneric над&[Edit], пустой span = вставка,new_text=""= удаление; overlap/malformed spans → PlanRejected, не-char-boundary/content-mismatch → StaleIndex до записи); apply/rollback/diff/json переведены минимально (MovePlan.rewrites → Edit черезFrom) Fixtrait рядом с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 →--jsoncandidates, 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), не зависимость - Интероп Checkstyle/eslint (фаза 2.5):
check --report f.sarif|.xmlиfix --report f.sarif|.xml— SARIF 2.1.0 (GitHub/CodeQL, autoFixable) и Checkstyle XML (source=jmove.); формат по расширению, чистый прогон = валидный пустой файл, stdout/exit не меняются. Маркетинг: «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 ссылках: скан text/md/json/yaml/html + строк в коде на path/module/specifier/dir токены (с word-границами); никогда не правит, только stderr + non_import_refs[] в --json; lockfiles/hidden/>512KiB пропускаются. Ограничение v1: ссылки из чужих директорий в своей относительной форме ('./sum' из tests/ при переносе 'lib/sum') не ловятся
- 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.