- core: gitignore-aware index (ignore crate), forward/reverse import graph,
relative-specifier math, pure-data move plans
- apply: rewrite-first + rename-last with fsync, temp+rename, full rollback
(incl. stale-plan detection before any write); unified diff for dry-run
- cli: mv + check commands, human output, --json envelope with stable error
codes (SOURCE_NOT_FOUND, TARGET_EXISTS, ...) and hints; exit codes 0/1/2
- parser/core split into submodules to keep files <=250 lines
- tests: 37 unit + 10 e2e + 2 doctests; fixtures tests/typescript/{basic,normal,complex}
- docs: SKILL.md synced (no --force in Phase 1), EXAMPLES.md
- plan: AI/agent support done; Phase 1.5 = Java niche
4 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],
check [--json], and the global --root <DIR>. Paths may be relative to
the root or absolute inside it; .gitignored files are never indexed.
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";
export function main(): number {
return sum(1, 2);
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 broken imports found
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 a project other than the current directory; all
path arguments stay relative to that root:
$ 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 the same way, and a
file nobody imports simply moves with zero rewrites.
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"
],
"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; 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
}
changed_files[].changes[] lists every rewritten specifier with its
1-based line; moved and updated_imports are the counters.
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: this response keeps status: "ok" (the command itself succeeded)
while the process exits 2; treat a non-zero total — or exit code 2 —
as a failed refactor. A clean project returns "broken_imports": [], "total": 0
and exit code 0.
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.