--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.
Live smoke printed 'reference s to moved files' (the plural 's' was
separated from the noun by the format placeholder). The e2e now pins
the exact warning text.
The import graph only rewrites import/export-from/require. Files are
also named from markdown links, package.json fields, jest.mock strings
and tsconfig file lists, and a move that dangles those must not pass
silently. mv now scans text formats plus source strings for the moved
paths, module names and old specifier forms (word-boundary matched,
lockfiles/hidden dirs skipped) and reports each occurrence once per
line on stderr and as non_import_refs[] in --json (dry-run included).
References are never edited; exit codes are unchanged.
Also moves normalize_scope into Index (its only consumer) and the
Change/ChangedFile JSON structs into output where they are built, to
stay inside the 250-line-per-file budget.
- 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
- parser::java::class_name: exactly-one-public-top-level-type rule
(package-info/module-info and 0-or-2-public files skipped)
- check gains name_mismatches[] (JSON, omitted when clean) + human lines;
exit 2 covers both kinds; fix engine untouched by design — a file rename
is not a byte edit, and mv keeps the FQN so no imports change
- output split into output/{mod,check} to stay under the 250-line rule
- e2e: reports Bad.java, stays quiet on Good.java, suggested rename makes
check pass and preserves the file bytes
- MovePlan gains moves[] (file move = 1 relocation), left_behind[] for
unindexable files that deliberately stay, prune_dirs[] applied after the
batch (remove_dir only succeeds when empty, leftovers keep their home)
- plan::dir: nesting/target-exists guards, merged per-file rewrites
- apply: atomic N-rename batch (per-file git mv), rollback restores every
move, rewrite and created dir newest-first
- mv_reject accepts dirs; --json adds moved_files/would_move_files only for
real dir moves (single-file contract byte-identical); summary shows
(N files) and left-behind count
- engine rollback/prune tests moved to tests/apply.rs (public API; a
mid-batch failure cannot be forced through the CLI), java-rule e2e split
into its own binary to keep every file under the 250-line budget
- binds names via tree-sitter (default, namespace, named + alias, type-only),
keeps a statement when ANY bound name occurs outside it (word scan over
comments/strings included): under-delete is the safe direction, mirroring
the Java rule's asymmetry
- ESM caveat encoded in the policy: mixed live/dead statements are not
touched — removing a specifier would still drop the module's side effects
- side-effect imports and 'export .. from' re-exports are never candidates
- parser/ts became a folder (mod/extract/unused_imports) to keep the
4-entries-per-module rule; word_occurs shared via crate::parser
- java/rules/unused_imports now reuses the shared word scanner (DRY)