JSON output schema
--json emits exactly one line of compact JSON on stdout per
invocation. Snapshot-locked in crates/recast-core/src/snapshots/ —
changing field names or order is a breaking change.
Errors go to stdout too (not stderr) so agents have a single stream to parse.
Common shape
Every report carries a kind discriminator:
kind ∈ "plan" | "apply" | "check" | "error"
Non-error reports share outcome, files_scanned, and total_matches
as a header that appears in that order; the mode-specific count
(files_changed / files_written / files_would_change) follows.
plan (default mode)
{
"kind": "plan",
"outcome": "changes" | "already_applied",
"files_scanned": 5,
"total_matches": 3,
"files_changed": 2,
"changes": [
{ "path": "src/a.rs", "matches": 2 },
{ "path": "src/b.rs", "matches": 1 }
]
}
apply
{
"kind": "apply",
"outcome": "changes" | "already_applied",
"files_scanned": 5,
"total_matches": 3,
"files_written": 2
}
check
{
"kind": "check",
"outcome": "changes" | "already_applied",
"files_scanned": 5,
"total_matches": 3,
"files_would_change": 2
}
error
{
"kind": "error",
"error":
"too_few_matches"
| "too_many_matches"
| "non_convergent"
| "too_many_files"
| "file_too_large"
| "invalid_regex"
| "invalid_glob"
| "walk"
| "io"
| "script_parse"
| "script_runtime"
| "unknown_language"
| "structural_query"
| "structural_template"
| "structural_parse"
| "locked"
| "invalid_threads"
| "thread_pool",
"message": "human-readable description",
"exit_code": 2 | 3
}
The exit_code field mirrors the process exit code so agents can branch
on kind: "error" without re-reading $?.
Stability
Every shape above has an insta snapshot test. Any PR that changes
field names, drops a field, or reorders them shows up as a snapshot
diff in review — there’s no quiet schema drift.