Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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_replacement"
    | "non_convergent_context"
    | "non_convergent_script"
    | "invalid_rename_map"
    | "rename_map_diverges"
    | "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,
  "remedies": [
      "at_least" | "at_most" | "max_bytes" | "max_files"
    | "word" | "allow_non_convergent" | "allow_syntax_errors"
    | "force_lock" | "threads"
  ]
}

The exit_code field mirrors the process exit code so agents can branch on kind: "error" without re-reading $?.

remedies lists the knobs that could clear this error, most useful first, and is empty when the fix is to change the pattern or the tree instead. Branch on it rather than reading message, which is prose. recast-core names the knob, not the flag, because it does not know whether it is serving the CLI or the MCP server — so the same non_convergent_replacement reports ["word", "allow_non_convergent"] here, prints see --word / --allow-non-convergent on the CLI, and says set word or allow_non_convergent over MCP.

One knob is deliberately CLI-only. force_lock appears in this list but has no MCP argument: a crashed holder releases its flock on exit, so a held lock always means a live process is mid-apply, and the MCP locked error returns "remedies": []. Wait and retry, or stop and ask — there is nothing to set.

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.