File and record formats and rulesView source

This is a formats and rules document. You do not need it to hand work to your agent; read it when you build a tool that handles the records or need the exact behavior. New here? Start with Understanding Jaekit.

On this page

Contract: review-round/v1

한국어 · English

This is a translation of the Korean original. Where the two differ, the Korean original decides.

spar run runs one round and leaves a review-round/v1 record in the round folder. Reviewer results and finding dispositions accumulate only in this record, and this format is fixed so that other tools (later, ha) can read it. The accepted format of each input field and the result for input outside that format are set by the table and standpoint input rules (section 2) in Input formats, and this document points to its rows. The commits, paths and sentences in the examples below are synthetic.

1. Command

bash
spar run --stage candidate --base 1111111 --head 2222222 \
  --criteria docs/specs/example/SPEC.md --rules AGENTS.md \
  --out docs/reviews/example/02-candidate
Argument Meaning
--stage plan, plan-delta, candidate, fixes
--base, --head The two commits to compare. --head defaults to HEAD
--out A new round folder. If it already exists, it is refused (checked again after the series is locked). Record path rule (G28): every Git repository that contains the path (the repository under review, another repository, a nested repository inside an ignored folder) must ignore it, with no tracked files under it; when there is a .git above the path but Git cannot judge that path (ownership refusal, a linked worktree without Git data, an empty .git folder and so on), it is refused
--criteria Criteria documents. At least one is required. Names of document arguments: G26
--rules Rule and contract documents. The project configuration's rules (G23) are added
--subject Documents to review in the plan stage
--question A question asked in this round
--standpoint NAME=TEXT A reviewer standpoint, following the format and refusals below
--decision, --decision-quote One decision of the user. --decision is what the decision settles, and the --decision-quote right after it is the user's words. Give one pair per decision; it may be repeated. The form is G35, and a decision out of form is refused before any reviewer starts. spar does not check that the words are the user's
--check-path Check path globs for this run (G24). An invalid glob is refused before any reviewer starts
--reviewers The list of reviewer names for this run (G25)
--model 이름=모델. This run's model for a claude, codex or agy reviewer whose user configuration does not write model (G25, configuration contract section 1, "How the model and effort are decided"). Can be given more than once
--effort 이름=강도. This run's effort for a claude or codex reviewer whose user configuration does not write effort (G25). Can be given more than once
--repo, --label The repository location, and a name to keep in the record
--dry-run Builds only the packet and workspaces and starts no reviewer. The workspace stays in the round folder's workspace/. It does not count toward the cap, and the gate does not accept it

The parent folder of a round folder is the series of one review. Caps, the previous head and previous findings are read from the other review-round/v1 rounds of the same series. Record folders in other formats are neither read nor changed. The series folder itself is locked (no lock file is created), so each series has only one write at a time (G33). spar run holds the same lock from the cap decision until it finishes writing the round record, and spar record, spar triage, spar extend and spar waive hold it while they read and write records. When another write holds the lock, it says so on standard error and waits until that write ends. spar gate does not lock.

Exit codes: 0 the round record was written and some reviewer completed or is pending, 1 the round record was written but no reviewer completed or is pending, 64 usage error, 65 refusal (configuration error, tracked record folder, cap exceeded, broken chain, unreadable input, a decision or standpoint out of form), 70 internal error. A refusal happens before any reviewer starts and creates no round folder.

Pass --standpoint NAME=TEXT to spar run. The first = separates the reviewer name from the text; the option may be repeated. Refused (65): a name outside this run's configured reviewers and --reviewers, more than one standpoint for a reviewer, text empty after trimming whitespace, or invalid UTF-8. Names are compared exactly and case-sensitively, as in G20. A missing = or an empty name is a usage error (64). Both refusals happen before any reviewer starts or any round folder is created; the message names the offending name or value. Other text is kept unchanged. A diagnostic shows at most the first 40 characters of the text.

The accepted names are the union of the configured reviewers and the names selected by --reviewers. A configured reviewer omitted from this run may still have a standpoint in the packet; when rerunning a subset, pass the full earlier list of standpoints in the same order. Standpoints are run arguments only, with no user or project configuration key; spar config is unchanged.

2. Packet and workspaces

  • packet.md is the one text every reviewer receives. It contains the stage description, the two commit IDs and tree IDs, the attack angles and how to report, the full text of the criteria documents, rule documents and documents under review, the user's decisions, the questions, the findings of earlier rounds in the same series, and the diff between the two commits. Commit messages, Git history, ignored files and the reviewer list are not in it. The exception is ## Standpoints for this round, which names the assigned reviewers when standpoints are given. packet_sha256 is the sha256 of this file.
  • The packet of a round that received decisions has one ## Decisions the user made section, after the documents under review and before the questions, in all four stages. The section opens with the guidance below, and each decision, under ### Decision <n>, gives what it settles and the user's words as received, in the order received. Each text is inside its own code fence longer than any run of backticks in it, so a heading or fence in the text neither makes nor ends a section outside the decision section. A round without decisions has no such section, and, if it also has no standpoints, its packet is byte for byte the packet spar made from the same input before this section existed. Decisions are part of the packet: the same input with the same decisions is the same packet, and any different decision makes another packet (section 8). The guidance to reviewers is the four lines below; spar does not enforce it through classification.
    • Each decision below was recorded by the caller as the user's words; spar does not check that it is.
    • Do not report the change for doing what a decision settles.
    • Report as usual a change that goes beyond what a decision settles.
    • When following a decision breaks a criterion, rule or contract that the decision does not address, report a requirement-question that links that criterion, rule or contract.
  • The earlier rounds' findings are those of the plan and plan-delta rounds for plan-delta, and those of the candidate and fixes rounds for fixes. The packet shows those findings by finding IDs that contain the reviewer name (codex-F1 and so on). Rounds with the same stage, base and head are left out, so a rerun of the same input receives the same packet.
  • A temporary workspace copy is made for each reviewer. head/ and base/ hold the tracked files of the two commits exactly as their blobs, with no .git. Symlinks are not created; each is a regular file holding its blob (the target path).
  • schema.json is the JSON schema of the reviewer result.
  • In a round without standpoints, claude receives the packet on standard input, codex on standard input, and agy as the -p argument. The -p value of agy is a command note before the packet. The note says to use the file read, list and search tools first, to join only the read-only programs of section 4 and sed -n with |, &&, || or ; in shell commands, and that running code, tests, builds, git or any other program stops the review and drops its result (tool_violation). packet.md stays the same, and no other reviewer receives the note. When the note and the packet together exceed the limit of one argument (120 KiB on Linux, 768 KiB elsewhere), agy receives PACKET.md with the packet's bytes at the top level of its workspace, and -p is the note and a short instruction to read that file. The attempt record's argv shows the packet after the note as <packet.md>. The attempt record's packet_transport (stdin, argument, file) keeps this, and a change to PACKET.md is workspace_modified. command receives the path of packet.md in the {prompt} placeholder. The packet is sent to each reviewer's provider.

The shared packet's ## Standpoints for this round section contains all standpoints and their reviewer assignments. Each reviewer also receives an instruction identifying its own standpoint and asking it to attack that risk while still checking every angle; an unassigned reviewer is told to review the whole change.

  • claude and codex receive the instruction and packet on standard input.
  • agy receives the command note, instruction and packet in -p; for a large packet, PACKET.md holds the packet and -p holds the command note, instruction and file reference.
  • command receives the reviewer's input.md through {prompt}; native receives the instruction after its fixed instructions in prompt.md.

For a round with standpoints, both a real run and --dry-run leave in <round>/<reviewer>/input.md exactly the input that reviewer receives. For agy with a large packet, input.md holds its -p text and the packet is in PACKET.md.

The assigned reviewer's instruction is: "Your standpoint for this round is the one under ### <name> in ## Standpoints for this round. Attack that risk first and hardest, and still check every angle as the packet asks. Report what you checked from it in standpoint_report." The unassigned reviewer's instruction is: "You have no standpoint this round. Review the whole change as the packet asks; the standpoints below belong to other reviewers." The instruction precedes the packet with a blank line between them; the per-reviewer instruction is determined by the assignments and needs no separate digest.

The standpoint section appears only when standpoints are given, after ## Decisions the user made if present and before the questions. It starts with: "Each standpoint below belongs to one reviewer. The reviewer it names attacks that risk first; every reviewer still checks every angle." Each assignment follows in argument order under ### <reviewer>, with the text unchanged inside a code fence longer than any run of backticks in it. Newlines, headings, code fences and non-ASCII text cannot end the fence or make another packet section.

Without standpoints, reviewer inputs, schema.json, packet_sha256 and summary.md remain byte for byte unchanged; no standpoint instruction or input.md is added. Only standpoints: [] is added to round.json.

3. Reviewer result

A reviewer outputs one JSON object.

json
{
  "reviewed_head": "2222222",
  "verdict": "blocking_findings",
  "angles_checked": [{"angle": "requirement-counterexample", "result": "checked the empty-name input"}],
  "angles_unchecked": [{"angle": "design", "reason": "out of time"}],
  "findings": [{
    "id": "F1", "proposed_class": "defect", "angle": "requirement-counterexample",
    "location": "head/app/greet.py:3", "claim": "An empty name gives a trailing comma.",
    "failure_scenario": "greet('') returns 'hello, '", "evidence": "head/app/greet.py:3",
    "link_kind": "criterion", "link": "AC-1", "introduced_by_change": "yes", "regression": "no",
    "check_impact": "none", "confidence": "high",
    "smallest_fix": "Drop the comma only when the name is empty."
  }],
  "limits": "did not run the code"
}
  • The result's format is G1 (the object and top-level keys), G2 (reviewed_head), G3 (verdict), G4 (angle items), G5 (finding items) and G6 (limits). The same checks apply to the results of every preset. This includes native and command reviewers, whose provider does not enforce the schema. A violation is invalid_output, and the reason contains the name of the key at fault.
  • Finding enumerations: proposed_class defect|requirement-question|hardening|note, link_kind criterion|rule|contract|none, introduced_by_change and regression yes|no|unknown, check_impact false_pass|meaning_inverted|contract_violation|none, confidence high|medium|low. The packet tells reviewers to number id in reporting order as F1, F2, …, but id only needs to be a string, as the schema says. A round's finding ID is <리뷰어>-F<n> (starting at 1), which spar assigns in the reporting order of the reviewer result, and the id the reviewer gave is kept as reviewer_id. So round IDs do not collide even when a reviewer name or id contains - or when ids repeat.
  • smallest_fix is the smallest fix direction a reviewer writes for each finding: the smallest change that would make the failure scenario come out right, written as a sentence rather than a patch or code, or an empty string when it cannot be stated. The schema puts this key in required for providers that enforce values strictly, but spar's check (G5) also accepts results without the key. An empty string and a missing key both mean no direction, and a value that is not a string is invalid_output. A direction is the reviewer's suggestion, so it is not used for classes, dispositions or the gate (sections 6–8). spar does not judge whether a fix kept to the direction.
  • How spar reads them: location is G7, rule and contract links are G8, criterion links are G9. A finding whose location cannot be read is not dropped from the result either.
  • could_not_review is recorded as failed, not as completion.

standpoint_report is an object with two fields. checked is a boolean and detail is nonblank text: what was checked when true, or why it could not be checked when false. For an assigned reviewer, a missing, null or malformed report is invalid_output, with standpoint_report in the reason; an unassigned reviewer need not report one. An unassigned reviewer's standpoint_report is not checked and is recorded as null. Every preset uses the same check in spar run and spar record --result or --transcript.

In a round with standpoints, schema.json adds standpoint_report with type ["object","null"] and includes it in required for CLIs that enforce a strict schema. The object has exactly checked and detail; the runtime check allows an unassigned reviewer to omit the key or use null. The claude preset's --json-schema argument is this round's schema. In a round without standpoints the key remains unknown and the earlier schema is unchanged. A valid checked: false report explains a limit; it does not by itself prevent completed.

4. Reviewer status

Status Meaning
completed The result met the schema, head, angle and assigned standpoint report requirements
pending native is waiting for the reply of the host's subagent
unavailable The CLI is missing, or it refused because of usage or rate limits or authentication
timed_out No result within the time limit
invalid_output No result, not JSON, or it broke the schema, head, angle or assigned standpoint report requirements
failed The executable could not run, a nonzero exit without a result, or could_not_review
tool_violation It used a tool or command outside what is allowed. The result is not used
workspace_modified head/ or base/ of the workspace copy changed. The result is not used
not_run --dry-run

The findings of a reviewer that did not complete is null, and the summary shows its status and cause. It is never turned into zero findings.

Protections of verified presets (G10, G11): claude runs with -p --safe-mode --restricted --strict-mcp-config --tools Read,Grep,Glob --permission-mode dontAsk --no-session-persistence --disable-slash-commands and stops when it sees another tool or MCP server. codex runs with exec --ignore-user-config --ignore-rules -s read-only --ephemeral --skip-git-repo-check -c web_search="disabled" and --disable for apps, browser_use, browser_use_external, computer_use, in_app_browser, image_generation, memories, multi_agent, plugins and hooks, and stops when it sees an item type outside what is allowed. agy runs with --sandbox --disable-slash-commands and allows only the file read, list and search tools and, among shell commands, lines that join read-only programs (find, ls, grep, rg, cat, head, tail, wc, sort, uniq, cut, stat, file, diff, tree, true and so on) and sed -n that only prints with the operators |, &&, || and ; only. 2>/dev/null and 2>&1 are removed before checking, and operator characters inside quotes or after \ are not operators. Any other redirection, command substitution or operator (&, |&, ;;, parentheses and so on), an operator with no command before, after or between it, and a program outside the list (cd, git, go, python3, for and so on) are violations. A program named by its path is accepted only from /bin, /usr/bin, /usr/local/bin or /opt/homebrew/bin (head/bin/ls and ./ls are violations). A $ outside single quotes that starts an expansion (before a name, a digit, {, [ or a special parameter, and $' or $" outside double quotes), brace expansion outside quotes (the word {} alone aside), and arguments that start with -exec, -execdir, -ok, -okdir, -delete, --pre, --compress-program or --hostname-bin are violations. A $ before a closing quote or at the end of a word ("foo$", x$) is accepted as it is. sed is accepted only with -n (or --quiet, --silent) and when every script (-e, --expression=, or the first operand when there is no -e) only prints. Options may come only before the first operand (BSD sed stops reading options at the first operand), and everything after -- is an operand. A sed option other than -E and -r (-f, -i and so on) is a violation. The reason of a violation shows the command line and its first problem: a shell construct anywhere in the line (backtick, $(, <(, >(, newline), else the first part, read from the left, that breaks the rule. Unlike clean-review 0.2.1, it accepts &&, ||, ; and true, and it does not accept shell expansion or a sed that does more than print; each joined part is a command that clean-review 0.2.1 accepts too. The file read tools are the same as in 0.2.1 (the file read tools of 0.2.0 plus read_file, which agy 1.4.3 uses). agy receives this rule in the command note of section 2. Beyond that, options with which an allowed program writes files (for example sort -o) are not blocked separately. File writes are left to agy's --sandbox, and a change to head/, base/ or PACKET.md is workspace_modified.

Fallback model: when the designated model is refused for usage or rate limits (refusal text: G13, Input formats section 5) and fallback_model is set, the reviewer runs once more with the same packet and a new workspace copy. It is not used for authentication refusals or other failures. The only fallback for a model the agent chose with --model is the fallback_model the user wrote (configuration contract section 1). The fallback run uses the same effort as the designated model's run. attempts keeps the model, status, cause and reset text of each attempt, and fallback_used becomes true.

5. Round folder

File Contents
round.json The fields below
packet.md, schema.json The packet and the result schema
findings.json The array of classified findings. Each finding keeps the smallest_fix the reviewer gave as it is, or an empty string when there is none
ledger.jsonl The disposition ledger. One disposition per line, append-only
waivers.jsonl User decisions that drop a required reviewer (section 10). One decision per line, append-only. Absent in a round without decisions
summary.md A summary for people to read. In it, the Claim column of the finding table holds the whole claim, never cut: whitespace (line breaks included) is collapsed to single spaces and a vertical bar is escaped with a backslash (\|) to keep one row. A summary rewritten after a disposition takes the same form. Findings with a direction appear after the findings table in a ## Smallest fixes section as - <지적 ID>: <방향> (smallest_fix with leading and trailing whitespace removed; lines after a line break inside it are indented two spaces). Without findings that have a direction, the section is absent. A reviewer line shows the requested model and its source (model <모델> [<출처>]) and, for claude and codex, the effort and its source (effort <강도> [<출처>]; an effort the user left empty shows as effort none [user]). An earlier record without sources shows only the model, without a source, and no effort
<리뷰어>/ That reviewer's standard output and error, and its result. A fallback model's are in <리뷰어>/fallback/
workspace/ Only in a dry run: the workspaces the reviewers would receive (head/, base/)
<native 리뷰어>/ prompt.md, the workspace and the recorded result. native/ with the default name

Main fields of round.json: schema (review-round/v1), spar_version, stage, label, series, sequence, repo, base, head, base_tree, head_tree, diff_sha256, criteria, rules and subjects (name, source, sha256), criteria_digest (the sha256 of the criteria document names and their sha256s), questions, decisions (only in a round that received decisions: one item per decision in the order received, keeping settles (what it settles) and user_quote (the user's words) as received), prior, packet_sha256, config (the record of applying the configuration contract), links (the criteria_ids used for link checks and the rule documents' rules[]: names, file_name, lines), requested_reviewers, required_reviewers, dry_run, counted, rerun_of, started_at, finished_at, reviewers.

reviewers.<이름>: name, preset, isolation (verified, host, unverified), required, status, reason, requested_model, model_source (only claude, codex and agy: user, agent, preset), observed_model, fallback_model, fallback_used, effort, effort_source (only claude and codex, the same three values), elapsed_seconds, timeout_seconds (G19), resets, attempts, findings (the number of findings when completed, otherwise null), verdict, angles_checked, angles_unchecked, limits, warnings, agent, recorded_from, workspace_digest (the digest when the native workspace was prepared).

round.json keeps standpoints as reviewer/text pairs, standpoints_uncovered as reviewer names without a completed reviewer, and each reviewer's standpoint and standpoint_report as text/report or null. summary.md shows each reviewer's standpoint and report, and standpoints without a completed reviewer, including waived reviewers. The schema remains review-round/v1; records without standpoint fields mean no standpoints.

standpoints is an array of objects with reviewer and text, in argument order, or [] without standpoints. standpoints_uncovered and the per-reviewer fields appear only in rounds with standpoints. Uncovered means that the assigned reviewer did not complete that round, including a reviewer not selected to run; it does not mean checked: false. A waiver does not complete a reviewer. When spar record completes a reviewer, it recalculates the uncovered names and the summary. The head of the summary has - Standpoints: <n> for <names> and, when any are uncovered, - Standpoints without a completed reviewer: <names>. Under each assigned reviewer it shows - standpoint: <first line, at most 80 characters> and - standpoint report: checked|unchecked: <first line of detail>, or - standpoint report: none when there is no report.

6. Classification

An item of findings.json adds to the fields of the reviewer result id (<리뷰어>-F<n>, in reporting order), reviewer, reviewer_id (the id the reviewer gave), class, class_reason, path, in_check_paths, location_unreadable, claimed_link_kind and claimed_link. path and in_check_paths are the result of reading the location (G7). link_kind and link keep only confirmed links (G8, G9); when not confirmed, they are none and an empty string. A finding without location_unreadable, as in records written before this goal, is a finding whose location was read. If a reviewer leaves out smallest_fix, it stays an empty string, and a finding in an older record without smallest_fix has no direction. The rules below do not read smallest_fix.

Classification rules for findings whose location was read (the first match from the top applies):

  1. If the location is in the check paths and check_impact is false_pass or meaning_inverted, defect
  2. If the location is in the check paths, it is contract_violation and the link is confirmed, defect
  3. If the location is in the check paths: requirement-question when proposed_class is requirement-question, the link is confirmed, and that link is not a contract link pointing to a file in the check paths; otherwise hardening, whatever the proposed class
  4. If proposed_class is requirement-question, requirement-question
  5. If introduced_by_change and regression are both yes, defect
  6. If proposed_class is defect and the link is not confirmed, note
  7. Otherwise, proposed_class

A hardening whose location was read is deferred automatically, with a {"disposition": "defer", "auto": true} ledger entry, when the round is recorded. A requirement-question from rule 3 is not deferred automatically and closes only with a disposition that quotes the user's words, as in section 7. Its link must be the criterion, rule or contract the question asks about (the reviewer packet says so), and spar confirms that the linked target exists and, for a contract link, that the target (the rule document or the repository path of the head file that G8 found) is outside the check paths. A rule document outside the repository has no repository path, so it is always outside the check paths. A contract link pointing to a file in the check paths points to the check code itself, so rule 3 does not count it as a confirmed link. The link confirmation in rule 2 has no such restriction. A finding whose location cannot be read is classified as in section 4 of Input formats and is not deferred automatically: rules 3 and 6 are not used, and rules 1 and 2 are used without the check-path condition.

The class is fixed when a finding is recorded. The gate and the summary read the class recorded in findings.json, so findings already recorded are not classified again even when the rules change.

7. Dispositions

spar triage <라운드> <지적> <처리> [옵션] adds one line to the ledger. When it does not fit the rules, it writes nothing and exits with 65. The last entry is the finding's disposition.

Disposition Requires
fix --commit (a commit after the round head), --failed-before, --passed-after
reject --evidence (--user-quote instead for a requirement-question)
defer --to. For a defect, --user-quote too
duplicate --of another finding of the same round. A defect or requirement-question points only to a finding of the same class, and closes only when the finding it points to is closed (gate)
answer Only for a requirement-question

fix, reject, defer and answer that close a requirement-question, and fix of a hardening (both automatically deferred ones and ones whose location cannot be read), need --user-quote quoting the user's words. Format of finding IDs and options: G30. A note needs no disposition.

Ledger entry: finding, disposition, auto, reason, commit, failed_before, passed_after, evidence, of, to, user_quote, note, recorded_at.

8. Caps, reruns and the chain

  • The stage groups are plan, plan_delta and code (candidate and fixes). When the number of rounds of the same group in the series whose counted is true is at least the sum of the cap and the extensions, a new run is refused.
  • When earlier runs with the same stage, base, head and packet digest exist and every reviewer of this run is a reviewer that did not complete in those runs (a reviewer with the same name and preset), the run does not count toward the cap (counted: false, rerun_of). Rounds run one at a time per series, so an earlier round without finished_at stopped partway, and its reviewers did not complete. Every other run counts.
  • The base of a fixes round must be the head of the last code round of the same series. Otherwise the run is refused. Only an earlier round whose stage, base, head and packet digest all match this run (a rerun of the same packet) is skipped. When the packet differs, that round is the last of the chain even if base and head are the same.
  • An extension adds {"stage", "by", "cause", "reason", "user_quote", "recorded_at"} to the series' extensions.jsonl with spar extend <series> --stage plan|plan-delta|code --by N [--cause <cause>] [--reason "…"] --user-quote "…". cause and reason are written only when present. Without the user's words, it is refused. The series path follows the same record path rule (G28) as --out (even when the repository under review cannot be found, it is checked against the repository the path belongs to), and when --repo is not a Git repository, it is refused.
  • A code extension takes the cause the cap ran out with as --cause. The value is one of the five churn, recurring, unrelated, hardening and other (case-sensitive), and these five are all. A missing value or one outside the list is a usage error (64). --reason is recorded with leading and trailing whitespace removed, and when that leaves nothing there is no reason. --by of 2 or more and --cause other need a reason, and without one the extension is refused (65). Plan and plan-delta extensions do not take --cause or --reason and end with a usage error (64) when given them; their line is {"stage", "by", "user_quote", "recorded_at"} as before. No error or refusal writes anything. The executor picks the cause after reading the report of section 11; spar does not pick it.
  • The cap computation, the rerun judgment and the gate do not read cause or reason. A line without a cause (one recorded before this feature) still counts by stage and by as now.
  • The code cap refusal adds ; read why with `spar cap-report <series>` and record a code extension with --cause after the text above. The plan and plan-delta cap refusals are unchanged.

Standpoint text and assignments are in the packet, so packet_sha256 identifies them as part of the same input; changing either makes a new input. A rerun of only unfinished reviewers is not counted when stage, base, head and packet_sha256 are unchanged, including the same standpoints.

Standpoints do not change angles, classification, dispositions, caps or the gate's state and reasons. A fallback model receives the same standpoint instruction and input.md. Standpoints add no reviewers, attempts or rounds and do not change which reviewers are required. Reruns, the fixes chain, the gate's completion matching, spar waive and spar cap-report use the same packet identity rule.

9. The native reviewer

A reviewer of the native preset starts as pending and leaves prompt.md (the instructions and the full packet) and workspace/ in a folder with its name (native/prompt.md with the default name). When the host's clean-context subagent answers, the answer is recorded with spar record <라운드> <이름> --result <파일|-> or --transcript <전사 기록>. Dry run rounds and reviewers that already completed are not recorded. Recording checks that the workspace is the same as when it was prepared, that the result meets the schema, head, angle and assigned standpoint report requirements, and that the round's findings.json and ledger.jsonl exist (it is refused otherwise). From the transcript (G15), use of tools other than Read, Grep and Glob, other tools the host provided, context the host added (Git status, instruction files, hook context), not reading the prompt, and Read, Grep or Glob reading outside the native workspace and prompt.md are kept in warnings. The content of the context and the paths read are not recorded; only the kind and the tool name are kept. The isolation grade is host, and it counts as a required reviewer.

10. Decision to drop a required reviewer

When a required reviewer did not finish a round and the user said to go on without that reviewer, the user's words are recorded for that round.

bash
spar waive docs/reviews/example/02-candidate agy --user-quote "agy 없이 진행"
  • spar waive <라운드> <리뷰어> --user-quote "…" adds one line {"round", "reviewer", "preset", "status", "reason", "user_quote", "recorded_at"} to waivers.jsonl in that round folder. round is the round folder's name, preset is the preset of that required reviewer, and status and reason are that reviewer's status and cause in round.json at the time of recording. When the round did not run a reviewer with that name and preset, status is not_run and there is no reason.
  • Without the user's words, nothing is recorded.
  • In the following cases it writes nothing and exits with 65 and the reason: --user-quote is missing or empty after leading and trailing whitespace is removed; the round cannot be read (round.json, findings.json or ledger.jsonl is missing or cannot be parsed, G32); a dry run round; the reviewer is not a required reviewer of that round (Review gate section 2); the reviewer completed as a required reviewer of the same preset in that round or in another round of the same series with the same stage, base, head and packet (a rerun); the round already has a decision for that reviewer; waivers.jsonl cannot be read; a round recorded before this feature has a reviewer named waivers.jsonl (case-insensitive), whose folder takes the file's place.
  • A reviewer with a recorded decision is not a degraded reason for that round and appears separately in the gate file. A round in which no required reviewer completed is degraded even with decisions (Review gate section 2).
  • A decision applies only to its round. A rerun of the same packet is also a different round, so if the reviewer does not finish in that round either, the decision is recorded again.

11. Cap report

spar cap-report reads the code round records of a series and Git, and shows how the cap was used and the signals for seeing why the work did not finish within it. It never picks a cause or advises an extension. The executor picks the cause, and the user decides an extension (section 8).

bash
spar cap-report docs/reviews/example --format json --repo .
  • What it reads. Only the series' rounds (round.json, findings.json, ledger.jsonl), extensions.jsonl, and the Git repository that holds --repo (the current folder without it). It reads no configuration file, does not lock the series (as spar gate), and writes nothing. It gives Git GIT_NO_LAZY_FETCH=1 so that Git does not fetch objects a partial clone lacks (Git follows it from 2.44). It does not use the current time, so the same records and Git give the same output byte for byte.
  • Formats. --format md (the default) is Markdown for people, and --format json is a spar-cap-report/v1 JSON object. Both hold the same values.
  • Exit codes. 0 report, 64 usage error (no series, an unknown option, a format other than md or json), 65 refusal. A refusal writes nothing to standard output.
Key Value
schema spar-cap-report/v1
series The series path as given
cap code: the configured code cap of the series' round with the largest sequence (whatever its stage), null when there is no round. extensions: the sum of by over the code extension lines (lines without a cause count too). allowed: code + extensions, null when code is null. counted: the number of code rounds whose counted is true and that are not dry runs. It is the same as the cap decision of spar run, so a round stopped before recording findings counts too
rounds The candidate and fixes rounds that are not dry runs and did not stop before recording findings, in sequence order (folder name order when equal). Each round has round (the folder name), stage, sequence, base, head (full SHAs), counted and classes (the number of findings for each of defect, requirement-question, hardening and note). A rerun of the same packet is listed with counted: false
skipped The code rounds left out of the list and the signals: {round, reason}. reason is dry run for a dry run and stopped before recording findings for a round that stopped before recording findings (as for the gate, neither finished_at nor findings.json)
open The defect and requirement-question findings open by the gate's rule (review gate): no disposition, or a duplicate chain that ends at an open finding. {round, finding, class}
churn For each round after the first counted candidate round, {round, count, findings}. It counts the defect and requirement-question findings whose location was read and whose path is a file changed between the head of that first round and the round's head (git diff --name-only --no-renames)
recurring A link (link_kind, link) that appears as a defect or requirement-question in rounds of two or more different packets (stage, base, head, packet digest): {link_kind, link, rounds}. Reruns of one packet are one round, so rounds holds, for each packet, only the round that comes first in the list. Sorted by link
unrelated The defect findings whose introduced_by_change is no or unknown: {round, finding}. Their count is the length of the list
check_growth The same computation as the gate's growth: the lines the listed fixes rounds added (git diff --numstat -M), summed inside (check) and outside (other) the round's configured check paths. over_hardening is true when check is larger than other
limits The limit sentences of the three finding signals (below)

The three finding signals (churn, recurring, unrelated) leave out findings whose last disposition is reject or duplicate. The open findings and the class counts count the records as they are, whatever the dispositions. The limits of the signals come with them in limits as fixed sentences.

  • churn: "Counted by file: a finding in a file the fixes changed counts even when it is about another line." It works by file, so a finding about an unrelated line of a changed file counts too.
  • recurring: "Counted by link: the same problem reported under different links is not grouped." Findings that report the same problem under different links are not grouped.
  • unrelated: "Uses each reviewer's introduced_by_change as reported." It uses the reviewer's judgment as given.

The Markdown holds every value of the JSON but schema (the format's name), in this order. An empty list is - none.

  1. # Cap report: <series>
  2. - code rounds counted: <counted> of <allowed> (cap <code>, extensions <n>). When the cap is unknown, of unknown (cap unknown, extensions <n>).
  3. - open: defect <n>, requirement-question <n>
  4. The ## Rounds table (columns Round | Stage | Sequence | Counted | Base | Head | defect | requirement-question | hardening | note, commits as their full IDs). When rounds were skipped, - skipped <round>: <reason>.
  5. ## Open findings: - <round> <finding> (<class>)
  6. ## Signals:
    • ### Churn: - <round>: <count> (<ID>, …), or - <round>: 0.
    • ### Recurring links: - <link_kind> <link>: <round>, …
    • ### Unrelated defects: first - count: <n>, then - <round> <finding>. When it is 0, only the - count: 0 line.
    • ### Check growth: - added lines: check <n>, other <n>; over hardening: yes|no
  7. ## Limits: - churn: …, - recurring: …, - unrelated: …

In these cases it names the failed path or commit on standard error, ends with 65 and gives no report. It never turns a broken input into a report without signals.

  • The series does not exist or is not a folder.
  • A round.json cannot be opened or is not JSON. A folder without round.json, or with JSON of another format (anything but an object whose schema is review-round/v1, a list included), belongs to another tool and is skipped.
  • The stage of a round.json is not one of the four stages of the contract.
  • A round.json field the report reads is missing, null, or not of the JSON type spar writes: stage, base, head and packet_sha256 (text), sequence (an integer), dry_run and counted (true or false), config (an object) with caps.code (an integer) and check_paths (a list of text; null is no list), and finished_at (text) when present.
  • A finished round (with finished_at) has no findings.json, or findings.json exists without ledger.jsonl.
  • The findings.json of a listed round is not a list of findings (null included), or a finding has no id, a class outside the four classes, an introduced_by_change other than yes, no and unknown, or a link_kind other than criterion, rule, contract and none (the fields the signals read). Or a finding's id, class, introduced_by_change, link_kind, link or path is missing, null or not text, or its location_unreadable, when present, is not true or false.
  • A ledger.jsonl line of a listed round cannot be interpreted, names no finding, or has a disposition outside the five dispositions.
  • An extensions.jsonl line cannot be interpreted or is outside the form: a stage other than plan, plan-delta and code, by below 1, an empty user_quote, a cause on a line that is not a code line or outside the five values, or a line with a cause whose by is 2 or more or whose cause is other and whose reason is missing or empty. Code lines without a cause (recorded before this feature) need no reason.
  • --repo is not a Git repository, or Git cannot start.
  • The running Git is older than 2.44 (it does not know GIT_NO_LAZY_FETCH) and the repository is a partial clone (it has extensions.partialClone or a true remote.<name>.promisor). The report refuses before reading any object, and the reason names the Git version.
  • The base or head of a listed round is not in the repository (a shallow clone too), or an object the report needs is missing from a partial clone.
contracts/review-round.en.mdBuilt from bcaddb7603e8