사용하기원본 보기
이 페이지

Spar 독립 검토 안내

한국어 · English

standard·strict 목표를 처음 맡기기 전에, 또는 리뷰어 설정을 바꾸거나 검토 기록을 읽을 때 읽습니다. Spar는 작업한 에이전트가 놓친 문제를 그 대화 밖의 리뷰어들이 찾게 하는 독립 검토 도구입니다. 리뷰어는 요구사항을 바꾸지 못하고, 찾은 것은 defect, requirement-question, hardening, note로 나뉘고 그 처리가 기록됩니다. 끝난 검토는 gate 파일로 남고 ha가 완료 판정에 씁니다. 언제 필요한지는 아래와 Jaekit 이해하기의 검토 등급에 있습니다.

자세히는, Spar는 두 커밋에 고정한 검토 패킷 하나를 여러 리뷰어에게 한꺼번에 보내고, 그들이 찾은 것과 그 처리를 기록합니다. 라운드는 spar 실행 파일이 돌리고, spar 플러그인은 에이전트에게 언제 라운드를 돌리고 지적을 어떻게 닫는지 알려 줍니다.

v0.1.7부터 Seal은 새 목표를 run-rules/5로 시작하고, run-rules/5로 시작한 standard·strict 목표는 기록된 Spar gate가 있어야 완료됩니다. light 목표, Assurance: 줄이 없는 목표, run-rules/4 이하로 시작한 목표에는 Spar가 필요 없습니다. 검토 없이 끝내려면 등급을 light로 낮추는 목표 문서 수정을 사용자가 정합니다(검토 gate).

spar는 v0.1.7부터 릴리스 파일과 Homebrew formula에 ha와 함께 들어 있습니다. v0.1.6 이하의 릴리스 파일과 formula에는 없습니다. 설치 안내를 보세요.

컴퓨터 밖으로 나가는 것

리뷰어마다 패킷 전체를 받습니다. 패킷에는 기준 문서, 규칙 문서, 검토 대상 문서, 넘긴 사용자의 결정(사용자의 말 그대로)과 질문, 두 커밋 사이의 diff가 들어 있습니다. 리뷰어 CLI는 작업공간 사본에서 두 커밋의 추적 파일도 읽을 수 있고, 연 내용을 자기 제공자에게 보냅니다.

리뷰어 패킷을 받는 제공자
claude Claude Code CLI를 거쳐 Anthropic
codex Codex CLI를 거쳐 OpenAI
agy Antigravity CLI를 거쳐 Google
command 설정한 명령이 쓰는 서비스
native 지금 쓰는 에이전트의 제공자

커밋 메시지, Git 이력, 무시된 파일은 패킷에 없습니다. 기준 문서나 코드가 지금 쓰는 에이전트의 제공자 말고 다른 제공자에게 가면 안 되는 경우에는 native 리뷰어만 설정하세요. 컴퓨터 밖으로 아예 나가면 안 되는 경우에는 Spar를 돌리지 마세요. native를 포함한 모든 리뷰어가 패킷을 제공자에게 보냅니다.

공유 패킷의 ## Standpoints for this round 절에는 모든 관점과 리뷰어 배정이 들어간다. 리뷰어마다 자기 관점을 알리고 모든 각도를 확인하면서 그 위험을 공격하라는 지시도 받으며, 관점이 없는 리뷰어는 변경 전체를 검토하라는 지시를 받는다.

  • claude와 codex는 지시와 패킷을 표준 입력으로 받는다.
  • agy는 명령 안내, 지시, 패킷을 -p로 받으며, 큰 패킷이면 PACKET.md에 패킷을 두고 -p에는 명령 안내, 지시, 파일 안내를 둔다.
  • command는 {prompt}로 리뷰어의 input.md를 받고, native는 prompt.md의 고정 지시 뒤에 관점 지시를 받는다.

관점이 있는 라운드는 실제 실행과 --dry-run 모두 리뷰어마다 그 리뷰어가 받는 입력을 그대로 <round>/<reviewer>/input.md에 남긴다. 큰 패킷을 받는 agy의 input.md는 -p 글이고, 패킷은 PACKET.md에 있다.

리뷰어 설정

리뷰어는 사용자 설정에서만 옵니다. Spar는 $SPAR_CONFIG, $XDG_CONFIG_HOME/jaekit/spar.toml, ~/.config/jaekit/spar.toml 가운데 처음 찾은 파일을 읽습니다. 없으면 이미 있는 clean-review config.json의 reviewers 목록을, 그것도 없으면 claude, codex, agy를 씁니다. 빈 목록(reviewers = [])은 native 리뷰어 하나입니다.

이런 사용자 설정이 하나도 없으면 claude, codex, agy 셋이 모두 필수 리뷰어입니다. 필수 리뷰어의 CLI가 없거나 그 리뷰어가 라운드를 끝내지 못하면, 그 라운드에 사용자의 결정(spar waive)이 없는 한 gate는 degraded이고, 그래서 standard·strict 목표가 완료되지 않습니다. 첫 라운드 전에 가진 CLI만 리뷰어로 적은 사용자 설정을 ~/.config/jaekit/spar.toml에 만드세요. 예를 들어 claude CLI만 있으면 그 리뷰어 하나만 적습니다.

toml
[[reviewers]]
preset = "claude"

리뷰어를 하나만 적으면 그 리뷰어가 유일한 필수 리뷰어이고, 그 리뷰어가 끝낸 라운드는 complete가 될 수 있습니다. 리뷰어 CLI가 하나도 없으면 빈 목록으로 native 리뷰어 하나를 씁니다. 지금 쓰는 에이전트의 clean-context 하위 에이전트가 검토합니다.

toml
reviewers = []

세 CLI가 모두 있으면 이렇게 적을 수 있습니다.

toml
timeout_seconds = 1800

[[reviewers]]
preset = "claude"

[[reviewers]]
preset = "codex"

[[reviewers]]
name = "agy"
preset = "agy"
model = "gemini-4-argon-high"
fallback_model = "gemini-3.8-flash-high"
  • claude·codex·agy는 spar가 확인하는 읽기 전용·오프라인 보호로 CLI를 띄웁니다. 격리 등급 verified로 기록되고 필수 리뷰어에 듭니다.
  • agy는 파일 읽기·목록·검색 도구와, 읽기 전용 프로그램(ls, grep, cat, diff, true 등)과 sed -n을 |, &&, ||, ;로 이은 셸 명령만 쓸 수 있습니다. 코드·시험·빌드·Git이나 그 밖의 프로그램을 실행하면 spar가 그 리뷰어를 멈추고 tool_violation으로 기록하며 결과를 버립니다. spar는 이 규칙을 agy의 프롬프트 맨 앞에 짧게 적어 줍니다.
  • fallback_model은 지정 모델이 사용량·요청 한도로 거절될 때만, 같은 라운드에서 같은 패킷으로 한 번 실행합니다. fallback_model = ""는 preset의 기본 대체 모델을 끕니다.
  • native는 지금 쓰는 에이전트의 clean-context 하위 에이전트입니다. Spar가 라운드 폴더에 프롬프트와 작업공간을 쓰고, 에이전트가 가져온 답을 기록합니다. host로 기록되고 필수 리뷰어에 듭니다.

모델과 강도

claude·codex·agy의 모델과 claude·codex의 강도는 사용자 설정에 적은 값이 먼저입니다. 위 예시처럼 model을 적으면 그 모델로 고정됩니다. 적지 않은 리뷰어의 모델과 강도는 라운드를 돌리는 에이전트가 고릅니다. 에이전트는 spar run에 --model 이름=모델과 --effort 이름=강도를 넘기고, 넘기지 않으면 preset 값(모델 opus·gpt-6-astra·gemini-4-argon-high, 강도 high)으로 돕니다. 다음 값은 spar가 리뷰어를 하나도 띄우지 않고 거부합니다: 사용자 설정에 모델이나 강도가 적힌 리뷰어의 그 값(적힌 값과 같아도), native·command 리뷰어의 모델과 강도, agy의 강도, 이번 실행의 리뷰어가 아닌 이름, 같은 리뷰어에 두 번 넘긴 모델이나 강도, 허용 목록 밖의 값, 비었거나 올바른 UTF-8이 아니거나 -로 시작하거나 공백 문자나 제어 문자를 품은 값.

에이전트가 고를 수 있는 값을 묶으려면 allowed_models와 allowed_efforts를 적습니다.

toml
[[reviewers]]
preset = "codex"
allowed_models = ["gpt-6-astra", "example-model-mini"]
allowed_efforts = ["medium", "high"]
  • 목록이 있으면 에이전트는 그 안의 값만 고를 수 있습니다. 에이전트가 고르지 않으면 목록과 관계없이 preset 값입니다.
  • 에이전트가 고른 모델에는 사용자가 적은 fallback_model만 대체 모델로 붙습니다. 적지 않았으면 대체 모델 없이 돕니다.
  • spar config --model codex=gpt-6-astra처럼 같은 값을 주면 적용될 모델과 출처를 미리 봅니다. 라운드 기록과 요약은 리뷰어마다 모델·강도와 그 출처(user, agent, preset)를 남깁니다.

command 리뷰어

그 밖의 리뷰어는 명령으로 붙일 수 있습니다. 셸 없이 실행되고, 자리표시자로 패킷 파일·작업공간 사본·결과 schema를 받으며, schema를 따르는 JSON 객체 하나를 출력합니다. 아래는 합성 예시이고 example-reviewer는 직접 준비한 프로그램을 뜻합니다.

toml
[[reviewers]]
name = "example-reviewer"
preset = "command"
argv = ["/opt/example/bin/example-reviewer", "--prompt", "{prompt}", "--workspace", "{workspace}", "--schema", "{schema}"]
timeout_seconds = 600
  • {prompt}는 관점 없는 라운드의 packet.md 경로이고, 관점이 있는 라운드는 그 리뷰어의 input.md 경로입니다. {workspace}는 두 커밋의 사본(head/, base/), {schema}는 결과 JSON schema 경로입니다.
  • 명령이 무엇을 하는지 spar가 확인할 수 없으므로 unverified로 기록되고 필수 리뷰어 수에 들지 않습니다. 지적은 같은 규칙으로 분류되고 처리됩니다.

라운드를 돌리기 전에 spar config로 띄울 리뷰어를 확인합니다. spar config는 라운드가 띄울 리뷰어, 목록의 출처와 digest를 출력합니다. spar run --reviewers codex,agy는 한 번의 실행에서 목록을 바꾸고, 라운드 기록에 그렇게 남습니다.

프로젝트 설정

저장소의 .jaekit/spar.toml은 라운드 상한, 공격 각도, 규칙 문서, 검사 경로만 정할 수 있습니다.

toml
rules = ["AGENTS.md"]
check_paths = ["tools/checks/**"]

[caps]
code = 2

리뷰어·명령·실행 파일·모델·강도·허용 목록은 정할 수 없고, 정하면 spar가 그 키를 알리며 멈춥니다. 전체 형식은 설정 계약에 있습니다.

프로젝트 준비

패킷에 비공개 문서가 들어갈 수 있으므로 Spar는 Git이 무시하는 폴더에만 검토 기록을 씁니다. Spar를 처음 쓰는 프로젝트의 Git은 아직 그 폴더를 무시하지 않아서, 첫 라운드가 리뷰어를 띄우기 전에 65로 멈추고 아무것도 쓰지 않습니다. 프로젝트에 첫 standard·strict 목표를 맡기기 전에 프로젝트의 .gitignore에 한 줄을 더합니다.

text
docs/reviews/

docs/reviews/<goal>/은 Spar skill이 목표의 라운드와 gate를 두는 곳입니다. light 목표와 Spar를 쓰지 않는 목표에는 필요 없습니다.

  • 다른 폴더. 기록은 Git이 무시하는 어느 폴더에나 둘 수 있습니다. 그 폴더를 .gitignore에 더하고, 에이전트에게 라운드를 그곳에 두라고 말합니다.
  • 내 컴퓨터에서만. .gitignore를 바꾸지 않으려면 같은 줄을 .git/info/exclude에 더합니다. 이 파일은 commit되지 않으므로 다른 clone이나 컴퓨터에서는 다시 더해야 합니다.
  • Git이 추적하는 파일이 아래에 있는 폴더도 Spar는 거부합니다. 폴더를 무시하게 해도 이미 commit된 파일의 추적은 풀리지 않습니다.

검토 실행

에이전트에게 요청하세요. Codex에서는 $spar:spar, Claude Code에서는 /spar:spar입니다. skill은 다음 단계를 돌립니다.

단계 시점
plan 계획과 검사를 쓴 뒤 한 번
plan-delta plan 라운드로 계획이 바뀌었을 때 최대 한 번
candidate 모든 기준을 만족해야 하는 커밋에서
fixes 지적을 고친 뒤. base는 이전 코드 라운드의 head

한 series는 plan 1회, plan delta 1회, 코드 라운드 3회까지입니다. 상한을 넘는 라운드는 리뷰어를 띄우기 전에 거부되고, spar extend로 기록한 사용자의 말이 있어야 더 돌릴 수 있습니다. 사용량 거절 등으로 같은 패킷을 끝내지 못한 리뷰어만 다시 돌리는 실행은 세지 않습니다.

코드 라운드 상한을 다 쓰면 spar run의 거부 메시지가 그 series의 상한 보고를 안내합니다. spar cap-report는 그 series의 기록과 Git만 읽어 상한 사용, 라운드, 열린 지적, 네 신호(수정 연쇄, 반복 기준, 변경 무관, 검사 비대)를 보이고, 아무것도 쓰지 않습니다. 수정 연쇄는 파일 단위, 반복 기준은 연결 단위로 세고, 변경 무관은 리뷰어의 판단을 그대로 씁니다.

bash
spar cap-report docs/reviews/example

보고는 원인을 고르지 않습니다. 에이전트가 신호를 보고 원인(--cause) 하나를 churn, recurring, unrelated, hardening, other 가운데서 골라 권고와 함께 묻고, 더 돌리라는 사용자의 말이 있을 때만 spar extend <series> --stage code --by 1 --cause <원인> --user-quote "…"로 기록합니다. --by 2 이상이나 --cause other에는 --reason이 필요합니다. 기록한 원인과 이유는 ha cost에 --series로 그 series를 주면 상한 연장 목록에 보입니다.

사용자가 정한 것은 그 뒤의 모든 라운드에 결정으로 넘깁니다. requirement-question에 대한 사용자의 답이 대표적인 결정이고, Seal 없이 Spar만 쓸 때도 같습니다. 결정마다 --decision(정한 대상)과 바로 다음의 --decision-quote(사용자의 말)를 한 쌍으로 줍니다.

bash
spar run --stage fixes --base 2222222 --head 3333333 \
  --criteria docs/specs/example/SPEC.md --rules AGENTS.md \
  --decision "AC-2의 빈 입력" --decision-quote "빈 입력은 오류로 둬" \
  --out docs/reviews/example/03-fixes

결정은 패킷의 ## Decisions the user made 절에 받은 그대로 들어가고 round.json의 decisions에 남습니다. 절의 안내는 리뷰어에게 결정이 정한 대로 한 것은 지적하지 말고, 결정을 넘는 변경은 평소대로 지적하며, 결정을 따르면 그 결정이 다루지 않은 기준·규칙·계약이 깨질 때는 그것을 연결한 requirement-question으로 보고하라고 말합니다. spar는 그 말이 사용자의 말인지 확인하지 않습니다. 빈 결정, 짝이 없는 옵션, 올바른 UTF-8이 아닌 글은 리뷰어를 띄우기 전에 거부됩니다. 같은 결정으로 같은 입력을 다시 돌리면 같은 패킷이고, 결정이 다르면 다른 패킷입니다.

라운드마다 Git이 무시하는 새 폴더에 round.json, packet.md, findings.json, ledger.jsonl, summary.md가 생깁니다. summary.md의 지적 표는 주장을 자르지 않고 Claim 칸에 한 줄로 보이므로, 그 파일만 읽고도 지적을 분류하고 처리할 수 있습니다. 패킷에 비공개 문서가 들어갈 수 있으므로 Git이 추적할 폴더는 거부합니다(프로젝트 준비).

라운드·입력 형식과 위치를 읽을 수 없는 지적

형식은 라운드 계약에 있습니다. 리뷰어 결과의 위치·연결, 설정 값, 명령 인자처럼 spar가 받는 입력의 형식과 형식 밖 입력의 결과는 입력 형식 계약의 표 하나에 있습니다.

위치가 어떤 파일도 가리키지 않는 지적은 버리지 않고 읽을 수 없는 위치로 표시하며, 자동 보류하지 않습니다.

리뷰어별 관점

plan과 candidate 라운드(누적 라운드 포함)에서는 에이전트가 변경의 주된 위험을 골라 리뷰어마다 서로 다른 관점을 하나씩 줍니다. 위험보다 리뷰어가 많으면 나머지는 관점 없이 검토합니다. fixes와 plan-delta에서는 앞선 관점을 다시 주거나 주지 않을 수 있습니다. 관점은 공격할 위험, 먼저 볼 곳, 보는 입장을 적고 해결이 맞다는 주장이나 풀이 설명은 넣지 않습니다. 다음은 codex가 설정된 경우의 합성 예시입니다.

bash
spar run --stage candidate --base 1111111 --head 2222222 \
  --criteria docs/specs/example/SPEC.md --rules AGENTS.md \
  --standpoint "codex=저장 형식을 읽는 쪽의 입장에서, 이전 기록을 읽는 코드를 먼저 보고 바뀐 필드가 이전 기록을 거부하는 위험을 공격하라." \
  --out docs/reviews/example/02-candidate

spar run에 --standpoint NAME=TEXT로 준다. 첫 =가 리뷰어 이름과 글을 나누며, 옵션을 여러 번 쓸 수 있다. 거부(65): 이번 실행의 설정과 --reviewers에 없는 이름, 한 리뷰어에 관점 둘 이상, 앞뒤 공백을 뗀 뒤 빈 글, 올바른 UTF-8이 아닌 글. 이름은 G20처럼 대소문자를 구분해 정확히 비교한다. =가 없거나 이름이 비면 사용법 오류(64)다. 두 거부 모두 리뷰어를 띄우거나 라운드 폴더를 만들기 전에 일어나며, 메시지에 문제의 이름이나 값을 보인다. 그 밖의 글은 받은 그대로 쓴다.

받는 이름은 설정된 리뷰어와 --reviewers로 고른 이름의 합집합이다. 이번 실행에서 빠진 설정된 리뷰어의 관점도 패킷에 남길 수 있으므로, 일부만 재실행할 때도 앞선 관점 목록 전체를 같은 순서로 넘긴다. 관점은 실행 인자로만 받고 사용자·프로젝트 설정 키는 없으며, spar config는 그대로다.

관점의 글과 배정은 패킷 안에 있으므로 packet_sha256이 같은 입력의 일부로 식별하며, 어느 하나라도 바꾸면 새 입력이다. 끝내지 못한 리뷰어만 다시 돌리고 단계, base, head와 packet_sha256이 같은 관점을 포함해 그대로이면 상한에 세지 않는다.

standpoint_report는 관점 보고 객체다. checked는 불리언이고 detail은 공백뿐이 아닌 글이다. 참이면 확인한 것, 거짓이면 확인하지 못한 이유다. 관점을 받은 리뷰어의 보고가 없거나 null이거나 형식이 틀리면 invalid_output이고 이유에 standpoint_report가 들어가며, 관점이 없는 리뷰어에게는 보고를 요구하지 않는다. 관점이 없는 리뷰어의 standpoint_report는 확인하지 않고 null로 기록한다. 모든 preset에 spar run과 spar record --result 또는 --transcript가 같은 확인을 한다.

round.json은 standpoints에 reviewer/text 쌍, standpoints_uncovered에 완료한 리뷰어가 없는 관점의 리뷰어 이름, 리뷰어마다 standpoint와 standpoint_report에 글과 보고 또는 null을 남긴다. summary.md는 리뷰어마다 관점과 보고를 보이고, waive된 리뷰어를 포함해 완료한 리뷰어가 없는 관점을 보인다. 형식은 review-round/v1 그대로이며, 관점 필드가 없는 기록은 관점 없음으로 읽는다. gate를 보고할 때는 waive된 경우를 포함해 완료한 리뷰어 없이 남은 관점도 함께 알립니다.

관점은 각도, 분류, 처리, 상한, gate의 상태와 사유를 바꾸지 않는다.

지적과 gate

gate는 검토 기록이지 완료 근거가 아닙니다. Seal의 완료는 여전히 ha done에서만 나옵니다. run-rules/5로 시작한 standard·strict 목표에서는 Seal이 gate 파일을 ha note <goal> review --gate <file>로 기록하고, 기록된 gate가 지금 HEAD와 목표 문서를 덮어야 ha done이 완료를 기록합니다(검토 gate). 그래서 라운드에는 SPEC.md와 그것이 링크한 목표 문서를 모두 --criteria로 줍니다.

지적의 분류

Spar는 지적을 defect, requirement-question, hardening, note 가운데 하나로 분류합니다. 요구 질문은 사용자의 말로만 닫히고, 검사 코드에서 거짓 통과·결과 의미 뒤바뀜·이름을 댄 규칙 위반을 주장하지 않는 엣지 케이스는 hardening으로 보류됩니다. 끝내지 못한 리뷰어는 원인과 함께 보이고 지적 0건으로 세지 않습니다.

가장 작은 수정 방향

리뷰어는 지적마다 smallest_fix에 가장 작은 수정 방향을 적습니다. 실패 시나리오가 맞게 나오게 하는 가장 작은 변경을 패치가 아닌 문장으로 적고, 말할 수 없으면 비워 둘 수 있습니다.

방향은 findings.json에 남고 summary.md의 ## Smallest fixes에 보입니다. 방향은 제안이라 분류·처리·gate에 쓰지 않습니다. 에이전트는 수정을 그 방향 안에서 시작하고, 더 크게 고치면 그 까닭을 spar triage … --note로 남깁니다.

gate의 상태

spar gate는 라운드들과 완료하려는 head를 읽고 review-gate/v1 파일을 씁니다. complete(종료 0), 필수 리뷰어가 끝내지 못했고 그 라운드에 사용자의 결정이 없으면 degraded(3), 열린 지적·상한 초과·검토되지 않은 head가 있으면 blocked(4)입니다. 검토 수정이 검사 코드와 그 밖에 더한 줄 수도 남깁니다. gate 계약을 보세요.

리뷰어 없이 진행하기

필수 리뷰어가 사용량 한도 같은 이유로 라운드를 끝내지 못했고 사용자가 그 리뷰어 없이 진행하라고 하면, 에이전트가 spar waive <라운드> <리뷰어> --user-quote "…"로 그 말을 그 라운드에 기록합니다. 사용자의 말 없이는 기록하지 않습니다. 결정이 기록된 리뷰어는 그 라운드의 degraded 사유가 되지 않고, gate 파일의 그 라운드 waived_reviewers에 상태와 사용자의 말이 함께 보입니다. 결정은 그 라운드에만 적용되고, 완료한 필수 리뷰어가 하나도 없는 라운드는 결정이 있어도 degraded입니다.

guides/SPAR.md만든 커밋 bcaddb7603e8