{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "HashOutput",
"description": "Records of `hash`: one digest per file, then the terminal summary.",
"type": "object",
"anyOf": [
{
"title": "HashOutput",
"description": "NDJSON output for BLAKE3 hash computation.",
"type": "object",
"properties": {
"type": {
"description": "Event type discriminator.",
"type": "string"
},
"path": {
"description": "File path, absent for stdin.",
"type": [
"string",
"null"
]
},
"source": {
"description": "Source identifier for stdin input.",
"type": [
"string",
"null"
]
},
"algorithm": {
"description": "Hash algorithm name.",
"type": "string"
},
"checksum": {
"description": "Computed hash value (GAP-107: renamed to `checksum` for schema consistency).",
"type": "string"
},
"bytes": {
"description": "File size in bytes.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"verified": {
"description": "Verification result against expected hash.",
"type": [
"boolean",
"null"
]
},
"elapsed_ms": {
"description": "Duration in milliseconds.",
"type": "integer",
"format": "uint64",
"minimum": 0
}
},
"required": [
"type",
"algorithm",
"checksum",
"elapsed_ms"
]
},
{
"title": "FailureSummary",
"description": "[`Summary`] plus the per-file failure fields; both are omitted when no file failed.\n\nTerminal event of `search`, `replace` and `transform`: a run that reached\nevery file reads exactly like before, and a run that could not read or write\nsome files says how many and why instead of staying silent.",
"type": "object",
"properties": {
"type": {
"description": "Event type discriminator.",
"type": "string"
},
"files_visited": {
"description": "Total files examined.\n\n`search --target files` counts the files the walk handed over, and\n`copy` counts the sources it examined, so a copy that failed is counted\nhere and in `files_failed` instead of disappearing.",
"type": "integer",
"format": "uint64",
"minimum": 0
},
"files_matched": {
"description": "Files that contributed at least one published answer record.",
"type": "integer",
"format": "uint64",
"minimum": 0
},
"files_modified": {
"description": "Files actually modified: rewritten by `replace`, `transform`, `case`,\n`scope` or `codemod`, deleted by `delete`, copied by `copy`.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"files_planned": {
"description": "Files a dry run would have modified.\n\nPresent only on a dry run: `files_modified` stays at zero because no\nbyte changed, and this is the blast radius the caller asked to see. It\nis never a claim that the write happened.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"files_skipped": {
"description": "Files the run did not answer: binary detection, size cap, no match,\nunchanged after a rewrite or an unreadable file. Which of these apply\ndepends on the verb, and the verb's own section in `HOW_TO_USE` says.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"offset_skipped": {
"description": "Answer records `search` paged out because `--offset` skipped them.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"matches_suppressed": {
"description": "Files where `search --max-count` cut at least one match.\n\nEach file counted here hid at least one more match, so the number is a\nfloor on what the page left out, not an exact count of the remainder.\nThe ceiling is on records, not on reading: to prove that a file held\nmore, the search reads at most one match past the ceiling in that file.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"replacements_suppressed": {
"description": "Occurrences `replace --max-replacements` left unchanged.\n\nThe verb counts every occurrence of `old` before it applies the ceiling,\nso this is exact and the agent can size the second run from it.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"below_min_score": {
"description": "Lines `semantic-search --min-score` discarded for scoring below it.\n\nA zero-match answer with a high floor is otherwise indistinguishable from\na tree that has nothing to say about the query.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"files_read": {
"description": "Files opened and read, the stage between the walk handing a path over\nand the scoring that decides whether it answers.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"files_budget_dropped": {
"description": "Items the `--limit` budget cut before the verb worked on them.\n\nThe unit is the one the verb produces: files for a verb that walks\npaths, records for a verb that streams them.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"backups_kept": {
"description": "Backups this run created and left on disk.\n\nA destructive verb that keeps a copy must say so: the caller decides\nwhether the bytes are still there, and silence read as \"removed\".",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"backup_bytes": {
"description": "Bytes those backups occupy on disk.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"dirs_left": {
"description": "Directories the run could not remove because entries remained.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"hint": {
"description": "One sentence telling the caller what to change to finish the job.\n\n`dirs_left` counts the leftovers, and the count alone leaves the reader\nto guess why. The hint appears only when the run itself asked to skip\nentries, through `--include`, `--exclude`, `--older-than` or `--limit`,\nso it names the filter that kept the directory in place.",
"type": [
"string",
"null"
]
},
"files_total": {
"description": "Files the progress precount found eligible, published only when that\nprecount ran (`--progress-every` on and quiet off); it is the total the\nrun is working through, not the page it published.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"binary_rejects": {
"description": "Files refused because they hold binary content, a subset of\n`files_skipped` that lets the agent tell a binary refusal from a size cap\nor an unchanged rewrite.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"total_matches": {
"description": "Answer records actually published, after `--limit` and `--offset`; for\n`transform` and `transform --rules` it is the replacements performed,\nand for `scope` it is the scopes matched across every file.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"total_replacements": {
"description": "Total replacements performed across all files.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"total_bytes": {
"description": "Bytes the run left on disk or moved: the size after a rewrite, the size\ncopied, or the size hashed, depending on the verb.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"elapsed_ms": {
"description": "Operation duration in milliseconds.",
"type": "integer",
"format": "uint64",
"minimum": 0
},
"walker_stopped_early": {
"description": "Present and true when the walker itself quit before the tree ended.",
"type": [
"boolean",
"null"
]
},
"files_failed": {
"description": "Files that matched or were visited but could not be processed, plus\nthe root named in the invocation that does not exist.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"partial": {
"description": "The run answered from part of its targets, so the answer is partial.\n\nPresent exactly when `files_failed` is present, so a consumer reads one\nflag instead of comparing two counters. A run that reached every file\nkeeps the shape it always had.",
"type": [
"boolean",
"null"
]
},
"errors": {
"description": "First failures, capped at `summary.errors_max_entries` (50 by default);\n`files_failed` is the full count.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/$defs/FileFailure"
}
},
"ok": {
"description": "Outcome of the run, stamped by the writer",
"type": "boolean"
},
"code": {
"description": "Machine error code, present when ok is false",
"type": "string"
},
"exit": {
"description": "Process exit status, present when ok is false",
"type": "integer",
"minimum": 0
},
"target_resolved": {
"description": "Absolute targets the run resolved, in invocation order, and never a joined string, so a path holding `,` stays readable; a run with one target publishes a one-item array, so the shape never depends on the count",
"type": "array",
"items": {
"type": "string"
}
},
"target_source": {
"description": "Where the target came from: named in the invocation line, inherited from the workspace slot, or the standard-input stream",
"type": "string",
"enum": [
"argv",
"workspace",
"stdin"
]
},
"workspace_source": {
"description": "Where the workspace root came from: the `--workspace` flag or the process working directory",
"type": "string",
"enum": [
"argv",
"cwd"
]
},
"records_suppressed": {
"description": "Records the writer dropped before the terminal event: `--filter` rejections, `--select` projections that emptied the record, dedupe duplicates and `--limit` cuts. It counts records, never files, so it does not compare with `files_matched`: one file can contribute several records",
"type": "integer",
"minimum": 0
},
"truncated": {
"description": "The record itself was cut by `--truncate-content` or `--max-output-bytes`, so the fields are shorter than the source",
"type": "boolean"
},
"truncated_records": {
"description": "Records the writer cut this run, counted once per record",
"type": "integer",
"minimum": 0
},
"line_end": {
"description": "1-based last line of that block, when the block spans more than a line.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0
},
"line": {
"description": "1-based line of the block that holds the syntax error (v0.1.38).\n\nPresent only for [`AtomwriteError::SyntaxError`]. Without it the caller\nhad to re-read the file to learn where the refusal points.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0
},
"pairs_applied": {
"description": "Pairs that were written to the target before the error (v0.1.37).\n\nAlways `0` for the all-or-nothing `EditPairFailed` abort (nothing is\nwritten) and `total - unmatched` for `edit-loop --partial`.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"message": {
"description": "Human-readable error message.",
"type": "string"
},
"path": {
"description": "Filesystem path related to the error, if applicable.",
"type": [
"string",
"null"
]
},
"error_class": {
"description": "Error class: transient, conflict, `precondition_failed`, or permanent.",
"type": "string"
},
"retryable": {
"description": "Whether a retry may resolve this error.",
"type": "boolean"
},
"suggestion": {
"description": "Optional actionable suggestion for the caller.",
"type": [
"string",
"null"
]
},
"suggestion_lang": {
"description": "Language of `suggestion` when it is not English, so a consumer knows\nwhat it is reading.",
"type": [
"string",
"null"
]
},
"workspace": {
"description": "Workspace root used for jail validation, if applicable.",
"type": [
"string",
"null"
]
},
"failed_pair_index": {
"description": "1-based index of the failed `--old`/`--new` pair (multi-pair edit, G117).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"pairs_total": {
"description": "Total number of `--old`/`--new` pairs in the invocation.\n\nEmitted by multi-pair edit (G117) and by a partial `edit-loop` run\n(v0.1.36) — one key, two producers.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"pair_results": {
"description": "Per-pair diagnostics up to and including the failed pair (multi-pair edit, G117).\nPairs after the failure were never attempted and are absent.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/$defs/PairResult"
}
},
"best_candidate": {
"description": "Closest near-miss when a match cascade fails (v0.1.29 P0-2).",
"anyOf": [
{
"$ref": "#/$defs/BestCandidate"
},
{
"type": "null"
}
]
},
"candidates": {
"description": "Additional near-miss candidates for `did_you_mean` (v0.1.30).",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/$defs/BestCandidate"
}
},
"match_count": {
"description": "Match count when ambiguity is reported (v0.1.30).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"similar_paths": {
"description": "Similar basenames when path is missing (v0.1.30).",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"conflict_count": {
"description": "Number of conflicting regions a three-way merge could not align (v0.1.36).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"missing_flag": {
"description": "Flag literal the operation required but did not receive (v0.1.36).\n\nPresent only for [`AtomwriteError::MissingFlag`]; the duplicate `code`\non that variant is deliberate — callers branch on `missing_flag` and\nstill see the `INVALID_INPUT` family in `code`.",
"type": [
"string",
"null"
]
},
"pairs_unmatched": {
"description": "Pairs that did not match in a partial `edit-loop` run (v0.1.36).\n\n`pairs_total` already exists above for the multi-pair edit family and\nis reused by this variant instead of being duplicated.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
}
},
"required": [
"type",
"files_visited",
"files_matched",
"elapsed_ms"
]
},
{
"title": "ErrorJson",
"description": "Serializable error envelope emitted as a single NDJSON line.",
"type": "object",
"properties": {
"ok": {
"description": "Always false on error envelopes (GAP-2026-020 agent sentinel).\n\n`ok: false` together with `code` is the whole verdict: the redundant\n`error: true` duplicate left the envelope because every failure paid\nfor it and no caller branched on it alone.",
"type": "boolean"
},
"code": {
"description": "Machine-readable error code string.",
"type": "string"
},
"exit": {
"description": "Process exit status this failure maps to.",
"type": "integer",
"format": "uint8",
"minimum": 0,
"maximum": 255
},
"message": {
"description": "Human-readable error message.",
"type": "string"
},
"path": {
"description": "Filesystem path related to the error, if applicable.",
"type": [
"string",
"null"
]
},
"error_class": {
"description": "Error class: transient, conflict, `precondition_failed`, or permanent.",
"type": "string"
},
"retryable": {
"description": "Whether a retry may resolve this error.",
"type": "boolean"
},
"suggestion": {
"description": "Optional actionable suggestion for the caller.",
"type": [
"string",
"null"
]
},
"suggestion_lang": {
"description": "Language of `suggestion` when it is not English, so a consumer knows\nwhat it is reading.",
"type": [
"string",
"null"
]
},
"workspace": {
"description": "Workspace root used for jail validation, if applicable.",
"type": [
"string",
"null"
]
},
"failed_pair_index": {
"description": "1-based index of the failed `--old`/`--new` pair (multi-pair edit, G117).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"pairs_total": {
"description": "Total number of `--old`/`--new` pairs in the invocation.\n\nEmitted by multi-pair edit (G117) and by a partial `edit-loop` run\n(v0.1.36) — one key, two producers.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"pair_results": {
"description": "Per-pair diagnostics up to and including the failed pair (multi-pair edit, G117).\nPairs after the failure were never attempted and are absent.",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/$defs/PairResult"
}
},
"best_candidate": {
"description": "Closest near-miss when a match cascade fails (v0.1.29 P0-2).",
"anyOf": [
{
"$ref": "#/$defs/BestCandidate"
},
{
"type": "null"
}
]
},
"candidates": {
"description": "Additional near-miss candidates for `did_you_mean` (v0.1.30).",
"type": [
"array",
"null"
],
"items": {
"$ref": "#/$defs/BestCandidate"
}
},
"match_count": {
"description": "Match count when ambiguity is reported (v0.1.30).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"similar_paths": {
"description": "Similar basenames when path is missing (v0.1.30).",
"type": [
"array",
"null"
],
"items": {
"type": "string"
}
},
"conflict_count": {
"description": "Number of conflicting regions a three-way merge could not align (v0.1.36).",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"missing_flag": {
"description": "Flag literal the operation required but did not receive (v0.1.36).\n\nPresent only for [`AtomwriteError::MissingFlag`]; the duplicate `code`\non that variant is deliberate — callers branch on `missing_flag` and\nstill see the `INVALID_INPUT` family in `code`.",
"type": [
"string",
"null"
]
},
"pairs_unmatched": {
"description": "Pairs that did not match in a partial `edit-loop` run (v0.1.36).\n\n`pairs_total` already exists above for the multi-pair edit family and\nis reused by this variant instead of being duplicated.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"pairs_applied": {
"description": "Pairs that were written to the target before the error (v0.1.37).\n\nAlways `0` for the all-or-nothing `EditPairFailed` abort (nothing is\nwritten) and `total - unmatched` for `edit-loop --partial`.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"line": {
"description": "1-based line of the block that holds the syntax error (v0.1.38).\n\nPresent only for [`AtomwriteError::SyntaxError`]. Without it the caller\nhad to re-read the file to learn where the refusal points.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0
},
"line_end": {
"description": "1-based last line of that block, when the block spans more than a line.",
"type": [
"integer",
"null"
],
"format": "uint",
"minimum": 0
},
"target_resolved": {
"description": "Absolute targets the run resolved, in invocation order, and never a joined string, so a path holding `,` stays readable; a run with one target publishes a one-item array, so the shape never depends on the count",
"type": "array",
"items": {
"type": "string"
}
},
"target_source": {
"description": "Where the target came from: named in the invocation line, inherited from the workspace slot, or the standard-input stream",
"type": "string",
"enum": [
"argv",
"workspace",
"stdin"
]
},
"workspace_source": {
"description": "Where the workspace root came from: the `--workspace` flag or the process working directory",
"type": "string",
"enum": [
"argv",
"cwd"
]
},
"records_suppressed": {
"description": "Records the writer dropped before the terminal event: `--filter` rejections, `--select` projections that emptied the record, dedupe duplicates and `--limit` cuts. It counts records, never files, so it does not compare with `files_matched`: one file can contribute several records",
"type": "integer",
"minimum": 0
},
"files_budget_dropped": {
"description": "Entries the `--limit` budget dropped, in the unit the verb produces: files for walking verbs, records for streaming ones; a floor, because workers may stop before producing the rest",
"type": "integer",
"minimum": 0
},
"files_planned": {
"description": "Files the run would have modified in `--dry-run` or `--preview`; absent from a run that really wrote",
"type": "integer",
"minimum": 0
},
"truncated": {
"description": "The record itself was cut by `--truncate-content` or `--max-output-bytes`, so the fields are shorter than the source",
"type": "boolean"
},
"truncated_records": {
"description": "Records the writer cut this run, counted once per record",
"type": "integer",
"minimum": 0
}
},
"required": [
"ok",
"code",
"exit"
]
}
],
"$defs": {
"FileFailure": {
"description": "One file an operation could not process, as listed in a summary's `errors[]`.",
"type": "object",
"properties": {
"path": {
"description": "Path of the file that failed.",
"type": "string"
},
"code": {
"description": "Machine error code of the failure (for example `PERMISSION_DENIED`).",
"type": "string"
},
"message": {
"description": "Human-readable failure message.",
"type": "string"
}
},
"required": [
"path",
"code",
"message"
]
},
"PairResult": {
"description": "Outcome of matching a single `--old`/`--new` pair in multi-pair edit mode.\n\nEmitted in `pair_results` on both success and error envelopes so agents\ncan verify each pair objectively without re-running the edit (G117).\nIn the default all-or-nothing mode, pairs after the first failure are\nnever attempted and are absent from the array.",
"type": "object",
"properties": {
"index": {
"description": "1-based position of the pair in the `--old`/`--new` invocation order.",
"type": "integer",
"format": "uint64",
"minimum": 0
},
"matched": {
"description": "Whether the pair matched and was applied to the content.",
"type": "boolean"
},
"strategy": {
"description": "Matching strategy that succeeded (e.g. `exact`, `whitespace_normalized`).",
"type": [
"string",
"null"
]
},
"similarity": {
"description": "Similarity score of the fuzzy match, 0.0-1.0 (block-anchor and context-aware only).",
"type": [
"number",
"null"
],
"format": "double"
},
"source": {
"description": "Source of the match/replacement content: `\"arg\"` or `\"file\"`.",
"type": [
"string",
"null"
]
}
},
"required": [
"index",
"matched"
]
},
"BestCandidate": {
"description": "Closest near-miss when a fuzzy/exact match fails (v0.1.29 P0-2).\n\nAgents use this to correct `old` without re-reading the full file.",
"type": "object",
"properties": {
"text": {
"description": "Snippet of the best matching region (truncated to 500 chars).",
"type": [
"string",
"null"
]
},
"line": {
"description": "1-based line of the candidate start in the file.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"column": {
"description": "1-based column of the candidate start.",
"type": [
"integer",
"null"
],
"format": "uint64",
"minimum": 0
},
"similarity": {
"description": "Similarity score 0.0–1.0 of the best attempt.",
"type": [
"number",
"null"
],
"format": "double"
},
"strategy": {
"description": "Strategy that produced this candidate score.",
"type": [
"string",
"null"
]
},
"diff_preview": {
"description": "Optional short unified-diff style preview.",
"type": [
"string",
"null"
]
}
}
}
}
}