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
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.mdis 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_sha256is the sha256 of this file.- The packet of a round that received decisions has one
## Decisions the user madesection, 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-questionthat 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-F1and 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/andbase/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.jsonis the JSON schema of the reviewer result.- In a round without standpoints,
claudereceives the packet on standard input,codexon standard input, andagyas the-pargument. The-pvalue ofagyis 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 andsed -nwith|,&&,||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.mdstays 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),agyreceivesPACKET.mdwith the packet's bytes at the top level of its workspace, and-pis the note and a short instruction to read that file. The attempt record'sargvshows the packet after the note as<packet.md>. The attempt record'spacket_transport(stdin,argument,file) keeps this, and a change toPACKET.mdisworkspace_modified.commandreceives the path ofpacket.mdin 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.
claudeandcodexreceive the instruction and packet on standard input.agyreceives the command note, instruction and packet in-p; for a large packet,PACKET.mdholds the packet and-pholds the command note, instruction and file reference.commandreceives the reviewer'sinput.mdthrough{prompt};nativereceives the instruction after its fixed instructions inprompt.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.
{
"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 includesnativeandcommandreviewers, whose provider does not enforce the schema. A violation isinvalid_output, and the reason contains the name of the key at fault. - Finding enumerations:
proposed_classdefect|requirement-question|hardening|note,link_kindcriterion|rule|contract|none,introduced_by_changeandregressionyes|no|unknown,check_impactfalse_pass|meaning_inverted|contract_violation|none,confidencehigh|medium|low. The packet tells reviewers to numberidin reporting order asF1,F2, …, butidonly 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 theidthe reviewer gave is kept asreviewer_id. So round IDs do not collide even when a reviewer name oridcontains-or whenids repeat. smallest_fixis 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 inrequiredfor 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 isinvalid_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:
locationis G7,ruleandcontractlinks are G8,criterionlinks are G9. A finding whose location cannot be read is not dropped from the result either. could_not_reviewis recorded asfailed, 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):
- If the location is in the check paths and
check_impactisfalse_passormeaning_inverted,defect - If the location is in the check paths, it is
contract_violationand the link is confirmed,defect - If the location is in the check paths:
requirement-questionwhenproposed_classisrequirement-question, the link is confirmed, and that link is not acontractlink pointing to a file in the check paths; otherwisehardening, whatever the proposed class - If
proposed_classisrequirement-question,requirement-question - If
introduced_by_changeandregressionare bothyes,defect - If
proposed_classisdefectand the link is not confirmed,note - 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_deltaandcode(candidate and fixes). When the number of rounds of the same group in the series whosecountedistrueis 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 withoutfinished_atstopped 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.jsonlwithspar extend <series> --stage plan|plan-delta|code --by N [--cause <cause>] [--reason "…"] --user-quote "…".causeandreasonare 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--repois 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 fivechurn,recurring,unrelated,hardeningandother(case-sensitive), and these five are all. A missing value or one outside the list is a usage error (64).--reasonis recorded with leading and trailing whitespace removed, and when that leaves nothing there is no reason.--byof 2 or more and--cause otherneed a reason, and without one the extension is refused (65). Plan and plan-delta extensions do not take--causeor--reasonand 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
causeorreason. A line without a cause (one recorded before this feature) still counts bystageandbyas now. - The code cap refusal adds
; read why with `spar cap-report <series>` and record a code extension with --causeafter 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.
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"}towaivers.jsonlin that round folder.roundis the round folder's name,presetis the preset of that required reviewer, andstatusandreasonare that reviewer's status and cause inround.jsonat the time of recording. When the round did not run a reviewer with that name and preset,statusisnot_runand there is noreason.- Without the user's words, nothing is recorded.
- In the following cases it writes nothing and exits with 65 and the reason:
--user-quoteis missing or empty after leading and trailing whitespace is removed; the round cannot be read (round.json,findings.jsonorledger.jsonlis 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.jsonlcannot be read; a round recorded before this feature has a reviewer namedwaivers.jsonl(case-insensitive), whose folder takes the file's place. - A reviewer with a recorded decision is not a
degradedreason for that round and appears separately in the gate file. A round in which no required reviewer completed isdegradedeven 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).
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 (asspar gate), and writes nothing. It gives GitGIT_NO_LAZY_FETCH=1so 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 jsonis aspar-cap-report/v1JSON object. Both hold the same values. - Exit codes. 0 report, 64 usage error (no series, an unknown option, a format other than
mdorjson), 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.
# Cap report: <series>- code rounds counted: <counted> of <allowed> (cap <code>, extensions <n>). When the cap is unknown,of unknown (cap unknown, extensions <n>).- open: defect <n>, requirement-question <n>- The
## Roundstable (columnsRound | Stage | Sequence | Counted | Base | Head | defect | requirement-question | hardening | note, commits as their full IDs). When rounds were skipped,- skipped <round>: <reason>. ## Open findings:- <round> <finding> (<class>)## 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: 0line.### Check growth:- added lines: check <n>, other <n>; over hardening: yes|no
## 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.jsoncannot be opened or is not JSON. A folder withoutround.json, or with JSON of another format (anything but an object whoseschemaisreview-round/v1, a list included), belongs to another tool and is skipped. - The
stageof around.jsonis not one of the four stages of the contract. - A
round.jsonfield the report reads is missing,null, or not of the JSON type spar writes:stage,base,headandpacket_sha256(text),sequence(an integer),dry_runandcounted(true or false),config(an object) withcaps.code(an integer) andcheck_paths(a list of text;nullis no list), andfinished_at(text) when present. - A finished round (with
finished_at) has nofindings.json, orfindings.jsonexists withoutledger.jsonl. - The
findings.jsonof a listed round is not a list of findings (nullincluded), or a finding has noid, aclassoutside the four classes, anintroduced_by_changeother thanyes,noandunknown, or alink_kindother thancriterion,rule,contractandnone(the fields the signals read). Or a finding'sid,class,introduced_by_change,link_kind,linkorpathis missing,nullor not text, or itslocation_unreadable, when present, is not true or false. - A
ledger.jsonlline of a listed round cannot be interpreted, names nofinding, or has adispositionoutside the five dispositions. - An
extensions.jsonlline cannot be interpreted or is outside the form: astageother thanplan,plan-deltaandcode,bybelow 1, an emptyuser_quote, acauseon a line that is not a code line or outside the five values, or a line with acausewhosebyis 2 or more or whosecauseisotherand whosereasonis missing or empty. Code lines without a cause (recorded before this feature) need no reason. --repois 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 hasextensions.partialCloneor a trueremote.<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