# JSON output

> The --json contract: one envelope for every command.

Add `--json` to any command and `revise` prints exactly one JSON object on
stdout, and nothing else on stdout or stderr. Every command uses the same
envelope, so a script or an agent can handle every result the same way.

```bash
revise status contract.docx --json
```

```json
{
  "schema": 1,
  "ok": true,
  "command": "status",
  "meta": { "version": "3.0.0", "build": "3881be46a" },
  "data": {
    "file": "/work/contract.docx",
    "format": "docx",
    "mode": "suggest",
    "blockCount": 68,
    "wordCount": 2174,
    "trackedChangeCount": 0,
    "checkpoint": 0,
    "sourceFidelity": {
      "fidelity": "exact",
      "regeneratedBlockIds": [],
      "changedDomains": [],
      "convertedReason": null
    }
  },
  "warnings": [],
  "error": null
}
```

The contract is also a JSON Schema, shipped in the package:
`revise/cli-output.schema.json`.

## The envelope

| Key | Type | Meaning |
| --- | --- | --- |
| `schema` | `1` | The version of this contract. |
| `ok` | boolean | `true` exactly when `error` is `null`. |
| `command` | string or `null` | The command that ran, by its canonical name: `status`, `xml patch`, `skill install`; `call` for the [tool shorthand](#tool-calls); `help` for `help` and any `--help`; `version` for `version` and `--version`. `null` when the command was not recognized. |
| `meta` | object | `version` (the `revise` package version) and `build` (the commit it was built from). |
| `data` | object or `null` | The command's result. Present on failure too whenever there is something to report, such as what was saved before the failure. `null` when there is nothing. Its presence does not mean success. |
| `warnings` | array | Things worth knowing that did not stop the command. |
| `error` | object or `null` | Why the command failed. |

### Compatibility

All seven keys are always present. Within schema 1, fields may be added;
existing fields keep their names, types, and meanings. Ignore fields you
don't know, and handle error and warning codes you don't know generically.
Breaking changes come with a new `schema` number and a new major version of
`revise`.

Inside `data`, every documented key is present. A value that does not apply
or is unknown is `null`; an empty list is `[]`; a zero count is `0`. Keys are
camelCase, except inside a tool's own `result` (see [Tool calls](#tool-calls)).
Counts end in `Count`. Paths are absolute; package part names (like
`word/document.xml`) are relative to the package.

The output is UTF-8 with one trailing newline. It is indented when stdout is
a terminal and on one line otherwise; both parse the same way. Don't rely on
key order.

### Exit codes

| Code | `ok` | Meaning |
| --- | --- | --- |
| `0` | `true` | Done, including when there was nothing to do. |
| `1` | `false` | Failed, and nothing was saved: a usage error, a missing file, a refused edit. |
| `2` | `false` | Partly done: some of what was asked was saved or written, and some was not. `data` says what happened; `error` says what didn't. A redline that was written but failed its check is also `2`. |
| `70` | `false` | An internal error, or a save whose outcome is uncertain (`save_uncertain`, with `details.file` and `details.saved: null`). Check the file before going on; the next revise command on it finishes or undoes the save. |

A signal, a crash while starting up, or a closed stdout can still prevent a
complete envelope.

### Errors

```json
{
  "code": "change_not_found",
  "message": "Not pending: prop_17915. 1 other change was accepted and saved.",
  "hint": "revise changes contract.docx",
  "details": { "notFound": ["prop_17915"], "failed": [] }
}
```

| Key | Meaning |
| --- | --- |
| `code` | Stable and snake_case. Branch on this. |
| `message` | One or two sentences for a person or a model. The wording may change. |
| `hint` | A command to run next, shell-quoted, or `null`. Advice, not an instruction. |
| `details` | Structured facts about the failure, or `null`. |

### Warnings

Same `code`, `message`, and `details` as errors, without `hint`. In human
output (without `--json`), warnings go to stderr as
`warning[code]: message`, and errors (partial results too) as
`error[code]: message` followed by a `hint:` line. A command that fails with
exit `1` prints nothing on stdout.

### Saving

Commands that can change the document report:

| Key | Meaning |
| --- | --- |
| `file` | The document. |
| `saved` | `true` when this command wrote the file and recorded a checkpoint. `false` when it saved nothing. |
| `checkpoint` | The checkpoint the file is at now, saved or not. Undo with `revise undo <file>`. |

Commands that write another file report `output` (its path) and
`outputWritten`. If such a command fails after writing the file (for
example `new` when its page setup fails), it exits `2` with that receipt:
`outputWritten: true`, and `null` for what it could not determine.

`undo` and `restore` move the file back to an earlier checkpoint and discard
the later ones: they don't add a checkpoint.

## Vocabulary

| Term | Meaning |
| --- | --- |
| block | One unit of the document: a paragraph, heading, list item, table, or image. Block ids stay the same across commands until the block is removed. Block indexes start at 0. |
| tracked change | A Word revision waiting to be accepted or rejected. Each has a change id. |
| mode | `suggest` (edits become tracked changes) or `edit` (edits apply directly). |
| checkpoint | A saved state in the file's history. `0` is the file as revise first read it. |
| section | The part of the document under one heading. Not a Word page-layout section. |

## Commands

### Reading

**`status`**: `file`, `format`, `mode`, `blockCount`, `wordCount`,
`trackedChangeCount`, `checkpoint`, and `sourceFidelity`: for a `.docx`, how
an export now would relate to the file it came from (otherwise `null`):

- `fidelity`: `"exact"` (reproduces the original file byte for byte),
  `"grafted"` (the original file plus your edits; untouched content is byte
  for byte), or `"converted"` (written fresh).
- `regeneratedBlockIds`: blocks an export would rewrite.
- `changedDomains`: other parts it would rewrite (`"styles"`, `"numbering"`, …).
- `convertedReason`: why it is `"converted"`, or `null`.

**`outline`**: `file`, `blockCount`, `wordCount`, `sectionCount`, `sections`
(each: `title`, `level` (1–3), `startIndex`, `endIndex` (inclusive),
`startBlockId`, `endBlockId`, `blockCount`, `wordCount`), and `nextOffset`:
pass it to `--offset` for the next sections, `null` at the end.

**`grep`**: `file`, `pattern`, `regex`, `ignoreCase`, `view`, `matchCount`,
and `blocks` in document order (each: `blockId`, `blockIndex`, `matchCount`,
`text`, `matches`). `text` is the block in the view; in `tracked`, changes are
inline as `[-deleted-]{+inserted+}`. `matches` lists each match (`text`,
`change`: `"deleted"`, `"inserted"`, or `null` for text no change touches,
and `changeId`).

**`read`** and **`search`** are [tool calls](#tool-calls).

### Editing

**`replace-all`**: `file`, `find`, `replace`, `matchedBlockIds` (blocks that
contain `find`), `replacedOccurrences`, `regions` (occurrences per part:
`body`, `header`, `footer`, `footnote`, `endnote`; parts with none left
out), `nearMisses` (text looser matching would have replaced; each: `text`,
`count`, `reason`: `case` or `characters`), `changeIds` (tracked changes
created), `failures` (blocks that could not be edited; each: `blockId`,
`code`, `message`), `saved`, `checkpoint`. Some failures: exit `2`. All
failed: exit `1`. Run as `replace-text`, its 3.0 name, the envelope's
`command` is `replace-text`.

**`mode`**: `file`, `mode`, `changed` (whether this command switched it). The
mode is revise's setting, not part of the file.

**`tools`**: `tools` (each: `name`, `summary`). **`tools <name>`**: `name`,
`description`, `inputSchema`.

### Review

**`changes`**: `file`, `section` and `blocks` (the filters, or `null`),
`trackedChangeCount`, and `changes` in document order (each: `changeId`,
`type`, `author`, `blockId`, `blockIndex`, `section`, `deleted`, `inserted`).
`deleted` and `inserted` are the full text, never shortened.

**`accept`**, **`reject`**: `file`, `action`, `dryRun`, `resolved` (change
ids accepted or rejected), `wouldResolve` (with `--dry-run`: what would be;
otherwise `[]`), `notFound` (requested ids that are not pending), `failed`
(pending changes that could not be resolved; each: `changeId`, `code`,
`message`), `saved`, `checkpoint`. If some ids were not found or failed while
others were resolved: exit `2`. If none were resolved: exit `1`.

**`diff`**: `file`, `since` (the `--since` checkpoint, or `null` for the
pending tracked changes), `changedParagraphCount`, `text`. `text` is for
reading: `[-removed-]` and `{+added+}` inline, which can be ambiguous if the
document itself contains those markers.

**`history`**: `file`, `checkpoint` (current), `checkpoints` (each:
`checkpoint`, `at` (ISO 8601, UTC), `command`, `summary`).

**`undo`**, **`restore`**: `file`, `checkpoint` (now), `from` (before),
`discarded` (checkpoint numbers removed), `saved`.

### Output

**`render`**: `file`, `output` (the image), `outputWritten`, `mimeType`,
`width`, `height`, `pages` (1-based), `totalPages`, `view`, `description`,
`placeholderImageCount`, `failedImageCount`.

**`export`**: `file`, `output`, `outputWritten`, `format`, `byteCount`,
`final` (whether the output shows no tracked changes: only a `.docx` can
hold them, so every other format is `true`), `view` (`tracked`, `accepted`,
or `original`: what the output shows),
`trackedChangeCount` (tracked changes in the written file, as revise reads it
back; Word stores a replacement as a deletion and an insertion, so this can
be higher than the source's count; `0` for other formats; `null` if a `.docx`
could not be read back), `docxExport`, and `summary`. For `.docx` output, `docxExport` says what the export did:
`method` (`"graft"`: untouched content copied from the original file;
`"regenerated"`: written fresh), `exact`, `regeneratedBlockCount`,
`fallbackCode` (why it regenerated, or `null`). `summary` describes the
written `.docx` (read back from it). Both are `null` for other formats.

### Create and convert

**`new`**: `output`, `outputWritten`, `format`, `mode`, `checkpoint`,
`summary`. `checkpoint` and `summary` are `null` when setting up the file
failed after it was written (exit `2`). Page options are checked before
anything is written.

**`convert`**: `input`, `inputFormat`, `output`, `outputWritten`, `format`,
`byteCount`, `final`, `view` (as for `export`). `input` is `null` when the
document came from stdin
(`-`); `inputFormat` is the format read (`docx`, `odt`, `rtf`, `html`, `md`,
or `txt`), from `--from`, from stdin's bytes, or from the input's extension.
Writing to stdout (`-` as the output) can't be combined with `--json`.

**`compare`**: `original`, `revised`, `output`, `outputWritten`, `author`,
`inlineChangeCount`, `insertedBlockCount`, `deletedBlockCount`,
`unmarkedBlockCount` (changed blocks that can't carry a tracked change),
`untrackedRegions` (parts that differ but can't be tracked, taken from the
revised file), and `verification`: `scope` (`"normalized_text"`),
`acceptAllMatchesRevised`, `rejectAllMatchesOriginal`. The check compares
text with whitespace normalized; it does not check formatting or comments. A
redline that fails it is still written: exit `2`, `redline_unverified`.

**`summary`** (in `new` and `export`): `pageCount` (`null` when not
measured), `wordCount`, `pageSize`, `orientation`, `pageInches` (`width`,
`height`), `marginsInches` (`top`, `right`, `bottom`, `left`), `title`,
`headingCounts` (`{"H1": 1, "H2": 4}`), `lists` (each: `itemCount`), `tables`
(each: `rowCount`, `columnCount`), `imageCount`, `headersFooters` (each:
`side`, `slot`, `text`), `trackedChangeCount`.

### Raw package

**`xml ls`**: `file`, `parts` (each: `path`, `byteCount`).
**`xml cat`**: `file`, `part`, `match`, `fragments` (the distinct elements
that contain `match`: several matches in one paragraph show it once),
`truncated` (more than 20 such elements; the first 20 are shown).
**`xml patch`**: `file`, `part`, `replacementCount`, `saved`, `checkpoint`,
`copiedFrom`.

**`copiedFrom`** (`replace-all`, `call`, `accept`, `reject`, `xml patch`):
with `--out`, the original the copy was made from, and `file` is the copy the
edit was saved to; otherwise `null`. When the edit fails outright (exit `1`),
no copy is left behind.

### Everything else

**`examples`**: `examples`, each `{ description, command }`, plus `docs`
(a page to read more) on some.

**`skill`**: `markdown`. **`skill install`** (its own command name):
`output`, `outputWritten`.

**`help`** and **`--help`**: `commands` and `globalOptions`, or `command` for
one command's help (each command: `name`, `usage`, `summary`, `options`).

**`version`**: `version`, `build`.

## Tool calls

`call`, the tool shorthand (`revise replace contract.docx '{…}'`), `read`,
and `search` run an [editing tool](https://revise.io/developer/docs/revise/agents) and report:

| Key | Meaning |
| --- | --- |
| `file`, `saved`, `checkpoint` | As for every command that can change a document. |
| `tool` | The tool that ran. |
| `message` | The tool's summary, for reading; may span lines. `null` if none. |
| `changeIds` | Tracked changes this call created. An edit that extended an existing change creates none, so an empty list does not mean nothing changed: check `saved`. |
| `html` | The blocks the call returned, as HTML with block ids, or `null`. |
| `result` | The tool's own structured result, or `null`. Its keys are the tool's own and include snake_case ids such as `search_result_id`, which later calls take as input. |
| `view` | `read` and `search` only: the view `html` shows (`tracked`, `accepted`, or `original`). |
| `viewMatches` | `search` only: blocks the search tool can't see in the view (each: `query`, `blockId`, `blockIndex`, `text`, `changeIds`). Tracked: matches inside deleted text. Original: every match in the original text, and `html` and `result` are `null` (no `search_result_id`). |

A `search_result_id` stays valid across commands until the document changes.
revise remembers the 20 most recent searches per file; an older id is
refused with `unknown_search_result`.

When a tool refuses a call, the command exits `1` with `error.code`
`tool_error`, the tool's own code in `error.details.toolCode`, and a `data`
receipt showing `saved: false`.

## Error codes

| Code | When |
| --- | --- |
| `usage` | Wrong arguments, a missing argument, or an unknown option. |
| `unknown_command`, `unknown_tool` | No such command or tool. |
| `file_not_found` | A file the command needs does not exist: the document, an input, `--from`, or `@file.json`. |
| `checkpoint_not_found` | No such checkpoint. |
| `unsupported_format` | revise can't read or write that format. |
| `read_only_format` | revise reads the format but can't save it in place. |
| `import_failed`, `read_failed` | The file did not open or could not be read. |
| `would_overwrite` | The output exists; add `--force`. |
| `same_file` | The output is the input. |
| `file_busy` | Another revise command held the file too long. |
| `file_changed` | Another program saved the file while revise worked on it. Nothing was written. |
| `write_failed`, `commit_failed` | The file or its history could not be written. The document was left as it was; a file `new` had already created is reported with `outputWritten: true` (exit `2`). |
| `io_error` | The system refused a read or write (permissions, a full disk). `details`: `errno`, `syscall`. |
| `save_uncertain` | A save failed and could not be rolled back. Exit `70`. The next command on the file finishes or undoes it. |
| `invalid_regex` | `grep --regex` got a pattern that does not compile. |
| `invalid_json` | Tool input is not a JSON object. |
| `invalid_selection` | `accept`/`reject` got a selection that can't be combined, or a block range written last..first. |
| `section_not_found` | `--section` or `--except-section` matched no heading. The message lists the headings. |
| `section_ambiguous` | The `--section` text is in more than one heading. Name one by its number or full title. |
| `block_not_found` | `--blocks` named a block id the document doesn't have. |
| `tool_error` | A tool refused the call; see `details.toolCode`. |
| `unknown_search_result`, `stale_search_result` | The search result id is unknown, or the document changed since that search. |
| `change_not_found` | Some requested change ids are not pending. |
| `change_not_resolved` | Some pending changes could not be accepted or rejected. |
| `replace_failed` | Some blocks could not be edited. |
| `redline_unverified` | The redline was written but failed its check. |
| `page_setup_failed` | `new` wrote the file, but applying the page setup failed. |
| `no_package` | The file is not a `.docx`, so it has no package XML. |
| `no_such_part` | No such part in the package. |
| `malformed_xml` | A patch would produce XML that does not parse. |
| `find_not_found`, `find_ambiguous` | `xml patch --find` matched nothing, or more than once without `--all`. |
| `body_shape_changed` | `xml patch` on `word/document.xml` would add or remove paragraphs; use the tools. |
| `canvas_missing` | Rendering needs the optional `@napi-rs/canvas` package. |
| `render_failed`, `page_out_of_range`, `too_large` | Rendering failed, the page does not exist, or the image would be too big. |
| `internal` | A bug. Run again with `REVISE_DEBUG=1` for a stack trace on stderr. |

## Warning codes

| Code | When |
| --- | --- |
| `file_changed_outside` | The file changed since revise last saw it, and revise re-read it. `details`: `keptBlockCount`, `changedBlockIds`, `newBlockCount`, `removedBlockIds`. Read changed and new blocks again before editing them. |
| `save_recovered` | An earlier command was interrupted after writing the file; revise finished recording it. |
| `changes_accepted` | The output format can't hold tracked changes (anything but `.docx`), so it shows them accepted. `details`: `trackedChangeCount`. |
| `font_fallback` | A PDF used fallback fonts. `details`: `fonts`. |
| `untracked_regions` | `compare`: parts that differ but can't carry tracked changes. `details`: `regions`. |
| `summary_unavailable` | `export` wrote the file but could not read it back to describe it. |
| `export_note`, `engine_warning` | Anything else the export or engine reported. |
