26 KiB
Spec B — Judge Verdict Schema & Adversarial Quorum Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking. Commits excluded per user instruction — nogit commitsteps; each task ends with a test-green checkpoint.
Goal: Make judge-agent verdicts machine-validated at the SubagentStop boundary and make the adversarial-review kill-decision a deterministic quorum tally — without the Workflow tool (Claude subagents lack tool-layer schema enforcement).
Architecture: Extend the shared wiki_rules.py with validate_verdict_block (P1 schema + verdict↔count consistency) and tally_quorum (≥2 REJECT = KILL, default-refute, abstain≠pass). wiki_claim_gate.subagent_stop_gate validates any subagent output carrying a wiki-verdict marker (block on schema error; non-judge output untouched). New wiki_quorum.py CLI tallies N adversarial-review outputs. Five judge .md files emit the machine block; wiki-adversarial-reviewer also flips to default-refute. Deterministic core is unit-tested; agent prose is mirrored across 3 platforms by hand (the generator is absent).
Tech Stack: Python 3 stdlib (re, json, argparse, unittest, subprocess). Claude Code hooks. Markdown agent specs.
Spec: docs/superpowers/specs/2026-06-06-spec-b-judge-verdict-schema-and-quorum-design.md
File Structure
- Modify
.claude/hooks/wiki_rules.py— addparse_verdict_block,validate_verdict_block,tally_quorum, constants. - Modify
.claude/hooks/test_wiki_rules.py— addTestVerdictBlock,TestTallyQuorum. - Create
.claude/hooks/wiki_quorum.py— CLI overtally_quorum. - Create
.claude/hooks/test_wiki_quorum.py— CLI integration tests. - Modify
.claude/hooks/wiki_claim_gate.py—subagent_stop_gatecallsvalidate_verdict_block. - Modify
.claude/hooks/test_wiki_claim_gate.py— addTestSubagentStopVerdict(subprocess). - Modify
.claude/agents/wiki-adversarial-reviewer.md— per-finding block + default-refute + quorum doc. - Modify
.claude/agents/{branch-depth-auditor,coverage-auditor,project-readiness-auditor,wiki-diagram-reviewer}.md— standard verdict block. - Mirror (manual) the 4 shared judges into
.agents/plugins/wiki-superpowers/agents/<name>.md,.codex/agents/<name>.md,.codex/agents/<name>.toml. (project-readiness-auditoris Claude-only — no mirror.)
Task 1: wiki_rules — verdict block parse + validate
Files:
-
Modify:
.claude/hooks/wiki_rules.py(append functions + constants) -
Test:
.claude/hooks/test_wiki_rules.py(appendTestVerdictBlock) -
Step 1: Write the failing test
Append to .claude/hooks/test_wiki_rules.py before the if __name__ line:
STD_OK = "리포트...\n```wiki-verdict\nagent: branch-depth-auditor\nverdict: not-ready\nblocking: 2\nshould_fix: 1\nadvisory: 0\n```\n끝"
STD_CONTRADICT = "```wiki-verdict\nagent: branch-depth-auditor\nverdict: ready\nblocking: 2\nshould_fix: 0\nadvisory: 0\n```"
STD_BADVERDICT = "```wiki-verdict\nagent: x\nverdict: foo\nblocking: 0\nshould_fix: 0\nadvisory: 0\n```"
ADV_OK = "```wiki-verdict\nagent: wiki-adversarial-reviewer\nfinding: 4.1.1 action: KEEP\nfinding: 4.2.1 action: REJECT\n```"
ADV_BADACTION = "```wiki-verdict\nagent: wiki-adversarial-reviewer\nfinding: 4.1.1 action: NOPE\n```"
ADV_EMPTY = "```wiki-verdict\nagent: wiki-adversarial-reviewer\n```"
class TestVerdictBlock(unittest.TestCase):
def test_no_marker_returns_none(self):
parsed, errors = wr.validate_verdict_block("그냥 산문, 마커 없음")
self.assertIsNone(parsed)
self.assertEqual(errors, [])
def test_standard_valid(self):
parsed, errors = wr.validate_verdict_block(STD_OK)
self.assertEqual(errors, [])
self.assertEqual(parsed["agent"], "branch-depth-auditor")
self.assertEqual(parsed["kv"]["verdict"], "not-ready")
def test_standard_contradiction_flagged(self):
_, errors = wr.validate_verdict_block(STD_CONTRADICT)
self.assertTrue(any("blocking" in e for e in errors))
def test_standard_bad_verdict_flagged(self):
_, errors = wr.validate_verdict_block(STD_BADVERDICT)
self.assertTrue(any("verdict" in e for e in errors))
def test_adversarial_valid(self):
parsed, errors = wr.validate_verdict_block(ADV_OK)
self.assertEqual(errors, [])
self.assertEqual(len(parsed["findings"]), 2)
def test_adversarial_bad_action_flagged(self):
_, errors = wr.validate_verdict_block(ADV_BADACTION)
self.assertTrue(any("action" in e for e in errors))
def test_adversarial_empty_findings_flagged(self):
_, errors = wr.validate_verdict_block(ADV_EMPTY)
self.assertTrue(any("finding" in e for e in errors))
- Step 2: Run test to verify it fails
Run: python3 .claude/hooks/test_wiki_rules.py -k Verdict
Expected: FAIL — AttributeError: module 'wiki_rules' has no attribute 'validate_verdict_block'.
- Step 3: Implement in
wiki_rules.py
Append to .claude/hooks/wiki_rules.py (the import re must be added to the top import block — currently wiki_rules.py imports only json, shlex, sys):
import re # (add to the existing import block at top of file)
VERDICT_FENCE_RE = re.compile(r"```wiki-verdict\s*\n(.*?)\n```", re.S)
VALID_VERDICT = {"ready", "not-ready", "blocked"}
VALID_ACTION = {"KEEP", "DOWNGRADE", "REJECT"}
REFUTATIONS_REQUIRED = 2 # ≥2 REJECT → kill (deep-research 기본값)
def parse_verdict_block(text):
"""본문에서 wiki-verdict fenced 블록을 찾아 dict 로 파싱. 없으면 None."""
m = VERDICT_FENCE_RE.search(text or "")
if not m:
return None
out = {"agent": None, "kv": {}, "findings": []}
for line in m.group(1).splitlines():
line = line.strip()
if not line:
continue
fm = re.match(r"finding:\s*(\S+)\s+action:\s*(\S+)", line)
if fm:
out["findings"].append((fm.group(1), fm.group(2)))
continue
kv = re.match(r"([a-z_]+):\s*(.+)$", line)
if kv:
k, v = kv.group(1), kv.group(2).strip()
if k == "agent":
out["agent"] = v
else:
out["kv"][k] = v
return out
def validate_verdict_block(text):
"""(parsed, errors). parsed None → 마커 없음(judge 아님, caller 통과).
errors 비어있지 않으면 스키마 위반 → SubagentStop 차단."""
parsed = parse_verdict_block(text)
if parsed is None:
return None, []
errors = []
if not parsed["agent"]:
errors.append("wiki-verdict 블록에 `agent:` 누락")
if parsed["agent"] == "wiki-adversarial-reviewer":
if not parsed["findings"]:
errors.append("adversarial verdict 블록에 `finding: <id> action: <act>` 행 ≥1 필요")
for fid, act in parsed["findings"]:
if act not in VALID_ACTION:
errors.append(f"finding {fid}: action '{act}' 비허용(KEEP|DOWNGRADE|REJECT)")
else:
v = parsed["kv"].get("verdict")
if v not in VALID_VERDICT:
errors.append(f"verdict '{v}' 비허용(ready|not-ready|blocked)")
blocking = None
try:
blocking = int(parsed["kv"].get("blocking", ""))
int(parsed["kv"].get("should_fix", ""))
int(parsed["kv"].get("advisory", ""))
except ValueError:
errors.append("blocking/should_fix/advisory 는 정수여야 함")
if blocking is not None and v == "ready" and blocking != 0:
errors.append("verdict=ready 인데 blocking≠0 (모순)")
if blocking is not None and v == "not-ready" and blocking < 1:
errors.append("verdict=not-ready 인데 blocking<1 (모순)")
return parsed, errors
- Step 4: Run test to verify it passes
Run: python3 .claude/hooks/test_wiki_rules.py -k Verdict
Expected: PASS (7 tests).
- Step 5: Checkpoint (no commit) —
python3 .claude/hooks/test_wiki_rules.pyall green.
Task 2: wiki_rules — quorum tally
Files:
-
Modify:
.claude/hooks/wiki_rules.py(appendtally_quorum) -
Test:
.claude/hooks/test_wiki_rules.py(appendTestTallyQuorum) -
Step 1: Write the failing test
Append:
def _adv(*pairs):
lines = "\n".join(f"finding: {fid} action: {act}" for fid, act in pairs)
return f"```wiki-verdict\nagent: wiki-adversarial-reviewer\n{lines}\n```"
class TestTallyQuorum(unittest.TestCase):
def test_two_rejects_kill(self):
blocks = [_adv(("A", "REJECT")), _adv(("A", "REJECT")), _adv(("A", "KEEP"))]
per = wr.tally_quorum(blocks)
self.assertEqual(per["A"]["decision"], "KILL")
def test_unanimous_keep(self):
blocks = [_adv(("A", "KEEP")), _adv(("A", "KEEP")), _adv(("A", "KEEP"))]
self.assertEqual(wr.tally_quorum(blocks)["A"]["decision"], "KEEP")
def test_reject_plus_downgrade_is_downgrade(self):
blocks = [_adv(("A", "REJECT")), _adv(("A", "DOWNGRADE")), _adv(("A", "KEEP"))]
self.assertEqual(wr.tally_quorum(blocks)["A"]["decision"], "DOWNGRADE")
def test_abstain_not_pass(self):
# 한 블록만 KEEP, 나머지 둘은 A 를 누락(abstain) → 정족수 미달 → UNVERIFIED
blocks = [_adv(("A", "KEEP")), _adv(("B", "KEEP")), _adv(("C", "KEEP"))]
self.assertEqual(wr.tally_quorum(blocks)["A"]["decision"], "UNVERIFIED")
- Step 2: Run test to verify it fails
Run: python3 .claude/hooks/test_wiki_rules.py -k Tally
Expected: FAIL — AttributeError: ... 'tally_quorum'.
- Step 3: Implement in
wiki_rules.py
Append:
def tally_quorum(block_texts, refutations_required=REFUTATIONS_REQUIRED):
"""N개 adversarial verdict 블록 → per-finding 결정론 판정.
refute = DOWNGRADE 또는 REJECT (원 severity 반박).
default-refute: 어떤 pass 가 finding 을 누락/malformed → abstain(non-KEEP).
결정: reject≥req → KILL · (reject+downgrade)≥req → DOWNGRADE ·
keep≥req → KEEP · 그 외(정족수 미달) → UNVERIFIED(통과 금지).
"""
parsed_all = [parse_verdict_block(t) for t in block_texts]
all_fids = set()
for p in parsed_all:
if p:
for fid, _ in p["findings"]:
all_fids.add(fid)
per = {}
for fid in all_fids:
keep = downgrade = reject = abstain = 0
for p in parsed_all:
act = None
if p:
for f, a in p["findings"]:
if f == fid:
act = a
break
if act == "KEEP":
keep += 1
elif act == "DOWNGRADE":
downgrade += 1
elif act == "REJECT":
reject += 1
else:
abstain += 1
if reject >= refutations_required:
decision = "KILL"
elif (reject + downgrade) >= refutations_required:
decision = "DOWNGRADE"
elif keep >= refutations_required:
decision = "KEEP"
else:
decision = "UNVERIFIED"
per[fid] = {"keep": keep, "downgrade": downgrade, "reject": reject,
"abstain": abstain, "n": len(block_texts), "decision": decision}
return per
- Step 4: Run test to verify it passes
Run: python3 .claude/hooks/test_wiki_rules.py -k Tally
Expected: PASS (4 tests).
- Step 5: Checkpoint (no commit) —
python3 .claude/hooks/test_wiki_rules.pyall green.
Task 3: wiki_quorum.py CLI
Files:
-
Create:
.claude/hooks/wiki_quorum.py -
Test:
.claude/hooks/test_wiki_quorum.py -
Step 1: Write the failing test
Create .claude/hooks/test_wiki_quorum.py:
#!/usr/bin/env python3
"""wiki_quorum.py CLI 통합 테스트."""
import subprocess
import tempfile
import unittest
from pathlib import Path
CLI = str(Path(__file__).with_name("wiki_quorum.py"))
def _adv(*pairs):
lines = "\n".join(f"finding: {fid} action: {act}" for fid, act in pairs)
return f"```wiki-verdict\nagent: wiki-adversarial-reviewer\n{lines}\n```"
class TestQuorumCLI(unittest.TestCase):
def _files(self, d, *texts):
paths = []
for i, t in enumerate(texts):
p = Path(d) / f"v{i}.md"
p.write_text(t)
paths.append(str(p))
return paths
def test_kill_exits_1(self):
with tempfile.TemporaryDirectory() as d:
paths = self._files(d, _adv(("A", "REJECT")), _adv(("A", "REJECT")), _adv(("A", "KEEP")))
r = subprocess.run(["python3", CLI] + paths, capture_output=True, text=True)
self.assertEqual(r.returncode, 1)
self.assertIn("KILL", r.stdout)
def test_all_keep_exits_0(self):
with tempfile.TemporaryDirectory() as d:
paths = self._files(d, _adv(("A", "KEEP")), _adv(("A", "KEEP")), _adv(("A", "KEEP")))
r = subprocess.run(["python3", CLI] + paths, capture_output=True, text=True)
self.assertEqual(r.returncode, 0)
self.assertIn("KEEP", r.stdout)
if __name__ == "__main__":
unittest.main(verbosity=2)
- Step 2: Run test to verify it fails
Run: python3 .claude/hooks/test_wiki_quorum.py
Expected: FAIL — FileNotFoundError (wiki_quorum.py missing) → subprocess returncode nonzero / can't run.
- Step 3: Implement
wiki_quorum.py
Create .claude/hooks/wiki_quorum.py:
#!/usr/bin/env python3
"""wiki_quorum.py — N개 adversarial verdict 블록의 결정론 quorum tally CLI.
사용:
python3 wiki_quorum.py vote1.md vote2.md vote3.md
cat votes.md | python3 wiki_quorum.py --stdin # '---' 구분 멀티블록
exit: 1 if any KILL/UNVERIFIED, else 0.
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
import wiki_rules
def main():
args = sys.argv[1:]
if "--stdin" in args:
blob = sys.stdin.read()
blocks = [b for b in blob.split("\n---\n") if "wiki-verdict" in b]
else:
blocks = []
for a in args:
try:
blocks.append(Path(a).read_text(encoding="utf-8"))
except Exception as e:
print(f"파일 읽기 실패: {a} — {e}", file=sys.stderr)
if not blocks:
print("verdict 블록 입력 없음", file=sys.stderr)
sys.exit(2)
per = wiki_rules.tally_quorum(blocks)
print(f"== Quorum tally: N={len(blocks)} votes, {len(per)} findings ==")
print("| finding | keep | down | reject | abstain | decision |")
print("|---|---|---|---|---|---|")
bad = 0
for fid in sorted(per):
r = per[fid]
if r["decision"] in ("KILL", "UNVERIFIED"):
bad += 1
print(f"| {fid} | {r['keep']} | {r['downgrade']} | {r['reject']} | {r['abstain']} | {r['decision']} |")
print(f"\nKILL/UNVERIFIED: {bad} / {len(per)}")
sys.exit(1 if bad else 0)
if __name__ == "__main__":
main()
- Step 4: Run test to verify it passes
Run: python3 .claude/hooks/test_wiki_quorum.py
Expected: PASS (2 tests).
- Step 5: Checkpoint (no commit) — green.
Task 4: claim_gate.subagent_stop_gate verdict enforcement
Files:
-
Modify:
.claude/hooks/wiki_claim_gate.py(subagent_stop_gate) -
Test:
.claude/hooks/test_wiki_claim_gate.py(appendTestSubagentStopVerdict, subprocess-based) -
Step 1: Write the failing test
Append to .claude/hooks/test_wiki_claim_gate.py:
import json as _json
import subprocess as _sp
_GATE = str(Path(__file__).with_name("wiki_claim_gate.py"))
def _run_stop(message):
ev = {"hook_event_name": "SubagentStop", "last_assistant_message": message}
return _sp.run(["python3", _GATE], input=_json.dumps(ev), capture_output=True, text=True)
class TestSubagentStopVerdict(unittest.TestCase):
def test_malformed_verdict_blocks(self):
msg = "리뷰 끝.\n```wiki-verdict\nagent: branch-depth-auditor\nverdict: ready\nblocking: 3\nshould_fix: 0\nadvisory: 0\n```"
r = _run_stop(msg)
self.assertEqual(r.returncode, 2)
def test_valid_verdict_allows(self):
msg = "리뷰 끝.\n```wiki-verdict\nagent: branch-depth-auditor\nverdict: ready\nblocking: 0\nshould_fix: 0\nadvisory: 0\n```"
r = _run_stop(msg)
self.assertEqual(r.returncode, 0)
def test_no_marker_allows(self):
r = _run_stop("그냥 일반 subagent 출력, 마커 없음")
self.assertEqual(r.returncode, 0)
- Step 2: Run test to verify it fails
Run: python3 .claude/hooks/test_wiki_claim_gate.py -k SubagentStopVerdict
Expected: FAIL — test_malformed_verdict_blocks gets returncode 0 (gate not yet checking verdict).
- Step 3: Implement — extend
subagent_stop_gate
In .claude/hooks/wiki_claim_gate.py, replace the subagent_stop_gate body's final emit_allow() with a verdict check before it:
def subagent_stop_gate(event: dict) -> None:
message = event.get("last_assistant_message") or ""
if not isinstance(message, str):
emit_allow()
if re.search(r"Verdict:\s*COMPLETE|\bDONE\b|완료", message):
missing = []
for term in ("Claim ID", "Decision Evidence Map", "UNSUPPORTED_DECISION"):
if term not in message:
missing.append(term)
if missing and not event.get("stop_hook_active"):
emit_block(
"Subagent output claims completion but does not report claim-traceability checks: "
+ ", ".join(missing)
)
# judge 출력에 wiki-verdict 마커가 있으면 스키마 검증(없으면 judge 아님 → 통과).
parsed, verr = wiki_rules.validate_verdict_block(message)
if parsed is not None and verr and not event.get("stop_hook_active"):
emit_block("judge verdict 블록 스키마 오류:\n- " + "\n- ".join(verr))
emit_allow()
(wiki_rules is already imported in wiki_claim_gate.py from Spec A.)
- Step 4: Run test to verify it passes
Run: python3 .claude/hooks/test_wiki_claim_gate.py -k SubagentStopVerdict
Expected: PASS (3 tests).
- Step 5: Checkpoint (no commit) —
python3 .claude/hooks/test_wiki_claim_gate.pyall green (old 11 + 3).
Task 5: wiki-adversarial-reviewer.md — block + default-refute + quorum doc
Files:
-
Modify:
.claude/agents/wiki-adversarial-reviewer.md -
Step 1: Add the machine verdict block to the Output section
In the ## Output markdown template (after the ## Aggregate metrics block, before the closing ```), add — as part of the report the agent must emit:
## Machine verdict (필수 — SubagentStop 가 검증)
```wiki-verdict
agent: wiki-adversarial-reviewer
finding: 4.1.1 action: KEEP
finding: 4.2.1 action: DOWNGRADE
```
(모든 Falsification Summary 행의 Finding ID 를 `finding: <id> action: KEEP|DOWNGRADE|REJECT` 로 1:1 반영.)
- Step 2: Flip to default-refute
In the ## Severity Adjustment and Shortcut Trap sections, change the uncertainty default. Replace the current INSUFFICIENT_CONTEXT guidance so that uncertainty leans toward REJECT/DOWNGRADE, not KEEP:
-
Add to
## Severity Adjustment:Default-refute (deep-research 정렬): 세 검사 중 하나라도 확신이 안 서면 KEEP 이 아니라 최소 DOWNGRADE.
INSUFFICIENT_CONTEXT는 "판단 보류 후 KEEP" 이 아니라 "근거 부족 → 그 finding 의 원 severity 를 신뢰할 수 없음 → DOWNGRADE 권고" 로 처리한다. KEEP 은 세 검사가 적극적으로 통과할 때만. -
Step 3: Document the N=3 quorum flow
Add a new section ## Quorum (opt-in N=3):
고위험 검증 시 controller 가 이 에이전트를 **독립적으로 N=3 병렬 dispatch** 하고, 각 출력의 `wiki-verdict` 블록을 `wiki_quorum.py` 에 투입한다:
```
python3 .claude/hooks/wiki_quorum.py vote1.md vote2.md vote3.md
```
`wiki_quorum.py` 가 per-finding 결정(KILL/DOWNGRADE/KEEP/UNVERIFIED)을 **결정론적으로** 계산한다(≥2 REJECT=KILL, abstain≠pass). controller 는 임계값을 못 바꾼다. 기본은 N=1(단일 패스).
- Step 4: Verify block present
Run: grep -c "wiki-verdict" .claude/agents/wiki-adversarial-reviewer.md
Expected: ≥1.
- Step 5: Checkpoint (no commit).
Task 6: Standard 4 judges — verdict block
Files:
-
Modify:
.claude/agents/branch-depth-auditor.md,coverage-auditor.md,project-readiness-auditor.md,wiki-diagram-reviewer.md -
Step 1: Add the block to each judge's Output section
In each agent's output template (## 출력 / ## Output), directly under the human Verdict: line, add the machine block. Use the agent's own name and map its verdict:
```wiki-verdict
agent: <이 에이전트 name>
verdict: ready|not-ready|blocked
blocking: <N>
should_fix: <M>
advisory: <K>
```
Per-agent mapping note to include inline:
-
branch-depth-auditor:verdict=ready⟺ Blocking 0; elsenot-ready. -
coverage-auditor:verdict=ready⟺ missing(Blocking) 0; elsenot-ready. (Covered→ready,Not-covered→not-ready.) -
project-readiness-auditor:verdict=ready⟺ Blocking 0 (Ready); elsenot-ready. -
wiki-diagram-reviewer:verdict=ready⟺ 점수 ≥95 (PASS);<95→not-ready; BLOCKED→blocked.blocking= HARD-STOP 수. -
Step 2: Verify all four have the block
Run: for a in branch-depth-auditor coverage-auditor project-readiness-auditor wiki-diagram-reviewer; do echo -n "$a: "; grep -c "wiki-verdict" .claude/agents/$a.md; done
Expected: each prints ≥1.
- Step 3: Checkpoint (no commit).
Task 7: 3-platform manual mirror (4 shared judges)
Files (mirror the SAME block/edits made in Tasks 5-6):
-
.agents/plugins/wiki-superpowers/agents/{branch-depth-auditor,coverage-auditor,wiki-adversarial-reviewer,wiki-diagram-reviewer}.md -
.codex/agents/{branch-depth-auditor,coverage-auditor,wiki-adversarial-reviewer,wiki-diagram-reviewer}.md -
.codex/agents/{...}.toml(the verdict block goes inside thedeveloper_instructionsstring) -
NOT
project-readiness-auditor— Claude-only, no variants exist. -
Step 1: Mirror the body changes
For each of the 4 shared judges, copy the verdict-block addition (and for adversarial, the default-refute + quorum sections) from the .claude/agents/<name>.md into the three variant files. Keep each platform's frontmatter/format; only the body content is mirrored.
- Step 2: Grep-verify parity
Run:
for a in branch-depth-auditor coverage-auditor wiki-adversarial-reviewer wiki-diagram-reviewer; do
echo "== $a =="
grep -l "wiki-verdict" .claude/agents/$a.md .agents/plugins/wiki-superpowers/agents/$a.md .codex/agents/$a.md .codex/agents/$a.toml
done
Expected: all 4 files listed for each judge (16 total).
- Step 3: Checkpoint (no commit).
Task 8: Full regression + acceptance smoke (spec §6)
Files: none (verification)
- Step 1: Run all hook test suites
Run:
for t in test_wiki_rules test_wiki_claim_gate test_wiki_structure_lint test_wiki_quorum; do
out=$(python3 .claude/hooks/$t.py 2>&1 | tail -1); echo "$t -> $out"
done
Expected: all OK.
- Step 2: Acceptance §6.4 — SubagentStop live (malformed blocks, valid passes)
Run (printf to avoid JSON mangling; no > redirect to avoid the claim_gate bash-gate):
printf '%s' '{"hook_event_name":"SubagentStop","last_assistant_message":"x\n```wiki-verdict\nagent: branch-depth-auditor\nverdict: ready\nblocking: 4\nshould_fix: 0\nadvisory: 0\n```"}' | python3 .claude/hooks/wiki_claim_gate.py; echo "malformed exit=$?"
printf '%s' '{"hook_event_name":"SubagentStop","last_assistant_message":"x\n```wiki-verdict\nagent: branch-depth-auditor\nverdict: ready\nblocking: 0\nshould_fix: 0\nadvisory: 0\n```"}' | python3 .claude/hooks/wiki_claim_gate.py; echo "valid exit=$?"
Expected: malformed exit=2, valid exit=0.
- Step 3: Acceptance §6.5 — wiki_quorum CLI live
Run (build 3 vote files in /tmp via a /tmp helper to avoid the bash-gate, then tally):
python3 - <<'PY'
from pathlib import Path
d = Path("/tmp/qsmoke"); d.mkdir(exist_ok=True)
def adv(fid_acts):
lines = "\n".join(f"finding: {f} action: {a}" for f,a in fid_acts)
return f"```wiki-verdict\nagent: wiki-adversarial-reviewer\n{lines}\n```"
(d/"v0.md").write_text(adv([("A","REJECT")]))
(d/"v1.md").write_text(adv([("A","REJECT")]))
(d/"v2.md").write_text(adv([("A","KEEP")]))
print("wrote", d)
PY
python3 .claude/hooks/wiki_quorum.py /tmp/qsmoke/v0.md /tmp/qsmoke/v1.md /tmp/qsmoke/v2.md; echo "quorum exit=$?"
rm -rf /tmp/qsmoke
Expected: table with finding A … KILL, quorum exit=1.
- Step 4: Acceptance §6.7 — grep parity
Run the Task 7 Step 2 grep + the Task 6 Step 2 grep. Expected: every judge file carries wiki-verdict.
- Step 5: Final checkpoint (no commit) — report all results. Leave changes in working tree.
Self-Review (completed by plan author)
- Spec coverage: §4.1 block format → Tasks 5/6. §4.2 validate+tally → Tasks 1/2. §4.3 SubagentStop → Task 4. §4.4 wiki_quorum.py → Task 3. §4.5 agent edits + mirror → Tasks 5/6/7. §6 acceptance 1-7 → Tasks 1-4 tests + Task 8 smokes. No requirement unmapped.
- Placeholder scan: agent-edit tasks give the exact block text + exact mapping per agent + exact mirror file list; no "TBD"/"similar to". Deterministic-core tasks carry full runnable code.
- Type/name consistency:
parse_verdict_block/validate_verdict_block/tally_quorumnames match across Tasks 1-4 and the CLI; thewiki-verdictfence string is identical in tests, impl, agent blocks, and smokes;REFUTATIONS_REQUIRED=2matches the tally tests (REJECT×2 = KILL).