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

250 lines
7.3 KiB
Markdown

# 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)
```console
$ 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
```console
$ 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
```console
$ jmove check
check: no findings
```
When something does point at nothing, `check` prints one line per broken
import and exits with code `2`:
```console
$ 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:
```console
$ jmove --root ~/code/frontend mv src/old.ts src/new.ts --dry-run
```
### Errors
```console
$ 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:
```console
$ 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:
```console
$ 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.
```console
$ 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
```console
$ 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
```console
$ 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
```console
$ 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)
```console
$ 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
```console
$ 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`.