jmove/docs/EXAMPLES.md
loki5512344 ad1ad4f999
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
feat: SARIF 2.1.0 and Checkstyle XML reports for check/fix
--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.
2026-09-15 20:23:20 +02:00

7.3 KiB

jmove — examples

Concrete examples for both audiences: humans at a terminal and AI agents consuming --json. All outputs below are captured from the real binary.

Flags (see jmove --help): mv <source> <target> [--dry-run] [--json] [--no-git], check [--json], and the global --root <DIR> / --source-root <DIR> (monorepo subtree filter). <source> may also be a directory (mirrored batch move, emptied dirs pruned). Inside a git repo, mv of a tracked file uses git mv (the rename is staged); --no-git forces a plain filesystem rename. Exit codes: 0 ok · 1 operation error · 2 check found broken imports.

Human usage

Preview a move (always do this first)

$ jmove mv lib/sum.ts utils/sum.ts --dry-run
--- app.ts
+++ app.ts
@@ -1,4 +1,4 @@
-import { sum } from "./lib/sum";
+import { sum } from "./utils/sum";
...
move lib/sum.ts -> utils/sum.ts

Nothing on disk changed. If no file imports the source, the diff is empty.

Apply the move

$ jmove mv lib/sum.ts utils/sum.ts
moved lib/sum.ts -> utils/sum.ts, updated 1 import in 1 file

Rewrites happen first, the rename last; any failure rolls everything back.

Verify the project afterwards

$ jmove check
check: no findings

When something does point at nothing, check prints one line per broken import and exits with code 2:

$ jmove check
src/broken.ts:4: cannot resolve './gone'
$ echo $?
2

Operating on another project

--root points jmove at another project; path arguments stay relative to it:

$ jmove --root ~/code/frontend mv src/old.ts src/new.ts --dry-run

Errors

$ jmove mv lib/sum.ts app.ts
jmove: target path 'app.ts' already exists
  hint: remove or rename the existing target first
$ echo $?
1

jmove never overwrites: free the destination (or pick another name) and retry. A missing source reports SOURCE_NOT_FOUND; an unimported file simply moves with zero rewrites.

Java: package + imports + move in one step

Java couples the package declaration to the directory layout, so a move is three coordinated edits — jmove makes all of them:

$ jmove mv src/main/java/com/example/util/Text.java src/main/java/com/example/core/Text.java --dry-run
--- src/main/java/com/example/app/App.java
+++ src/main/java/com/example/app/App.java
 package com.example.app;

-import com.example.util.Text;
-import static com.example.util.Text.shout;
+import com.example.core.Text;
+import static com.example.core.Text.shout;
...
move src/main/java/com/example/util/Text.java -> src/main/java/com/example/core/Text.java
$ jmove mv src/main/java/com/example/util/Text.java src/main/java/com/example/core/Text.java
moved src/main/java/com/example/util/Text.java -> src/main/java/com/example/core/Text.java, updated 4 imports in 3 files

The 3 files include the moved file itself: its package line is the fourth rewrite. Targets outside the source root, non-.java targets and cross-directory moves of default-package classes are rejected with PLAN_REJECTED (exit 1) and change nothing.

Fix: auto-repair unused imports

fix runs deterministic rules and applies them through the same dry-run/atomic engine. Preview first, then apply:

$ cat src/main/java/com/example/app/App.java
import com.example.Text;
import com.example.unused.Ghost;   // never referenced

...
$ jmove fix --dry-run
--- src/main/java/com/example/app/App.java
+++ src/main/java/com/example/app/App.java
 package com.example.app;

 import com.example.Text;
-import com.example.unused.Ghost;
...
$ jmove fix
fixed 1 issue in 1 file

Only provably-dead imports are removed: the whole statement line (with its newline) disappears and every other line stays byte-identical. A name that also appears in a comment, string literal or a sibling static import is kept, so the rule can only under-report, never delete live code. Scope to one rule with --rule java/unused-import (java/import-order sorts the block google-style); an unknown id exits 1 with INVALID_ARGUMENT and lists the rules.

Fix: add a missing import for a bare type reference

After a Java mv changes a file's package, references to former same-package siblings stop resolving. java/missing-import inserts the import for a unique FQN in the class index; ambiguous ones are reported.

$ jmove fix --rule java/missing-import
fixed 1 issue in 1 file   # inserted: import com.example.util.Maths;

AI-agent usage (--json)

Every --json response is a flat envelope: status ("ok" | "dry_run" | "error") and operation plus the payload fields. Errors carry a stable code and an actionable hint.

1. Preview

$ jmove mv lib/sum.ts utils/sum.ts --dry-run --json
{
  "status": "dry_run",
  "operation": "mv",
  "would_move": "lib/sum.ts",
  "target": "utils/sum.ts",
  "would_update": 1,
  "affected_files": ["app.ts"],
  "would_move_via": "fs",
  "diff": "--- app.ts\n+++ app.ts\n@@ -1,4 +1,4 @@\n-import { sum } from \"./lib/sum\";\n+import { sum } from \"./utils/sum\";\n..."
}

Review affected_files and non_import_refs; abort and ask the user if the blast radius is unexpected.

2. Apply

$ jmove mv lib/sum.ts utils/sum.ts --json
{
  "status": "ok",
  "operation": "mv",
  "source": "lib/sum.ts",
  "target": "utils/sum.ts",
  "changed_files": [
    {
      "path": "app.ts",
      "changes": [{ "line": 1, "old": "./lib/sum", "new": "./utils/sum" }]
    }
  ],
  "moved": 1,
  "updated_imports": 1,
  "moved_via": "fs",
  "non_import_refs": [
    { "file": "README.md", "line": 1, "token": "lib/sum.ts",
      "kind": "path", "text": "Use [sum](./lib/sum.ts) via `lib/sum`." }
  ]
}

changed_files[].changes[] lists every rewritten specifier with its 1-based line; moved and updated_imports are the counters, moved_via tells whether the rename went through git ("git", staged) or the plain filesystem ("fs"). non_import_refs (omitted when empty, also present in dry-run) names references the import graph cannot see — markdown links, package.json fields, jest.mock strings. jmove never edits those; report them to the user.

3. Verify

$ jmove check --json
{
  "status": "ok",
  "operation": "check",
  "broken_imports": [
    {
      "file": "src/broken.ts",
      "line": 4,
      "import": "./gone",
      "reason": "file_not_found"
    }
  ],
  "total": 1
}

Note: the response keeps status: "ok" while the process exits 2; treat a non-zero total (or exit 2) as a failed refactor; clean returns exit 0.

CI report (same findings, machine formats)

$ jmove check --report build/jmove.sarif   # SARIF 2.1.0, GitHub/CodeQL
$ jmove fix --report build/jmove.xml       # Checkstyle XML, IDEs/Jenkins

--report never changes stdout or the exit code; clean runs write valid empty documents. Rule ids match the --json ones.

Error shape

$ jmove mv lib/sum.ts app.ts --json
{
  "status": "error",
  "operation": "mv",
  "code": "TARGET_EXISTS",
  "message": "target path 'app.ts' already exists",
  "hint": "remove or rename the existing target first"
}

Stable codes: TARGET_EXISTS, SOURCE_NOT_FOUND, INVALID_ARGUMENT, IO_ERROR, STALE_INDEX, PLAN_REJECTED (exit 1). The recommended agent loop — preview, inspect, apply, verify — is in docs/SKILL.md.