Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions aragora/cli/commands/review_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,15 @@ def add_review_queue_parser(subparsers: argparse._SubParsersAction) -> None:
collect_evidence_arg(
"--apply", action="store_true", help="Post Tier 0-2 evidence; Tier 3-4 prepare only."
)
collect_evidence_arg(
"--out",
type=Path,
default=None,
help=(
"Write the dry-run collect-evidence JSON artifact to this path so it "
"can later be reused with --prepared-json."
),
)
collect_evidence_arg("--json", dest="json_output", action="store_true", help="Output as JSON")

lint_comment_p = sub.add_parser(
Expand Down Expand Up @@ -1367,6 +1376,7 @@ def _cmd_collect_evidence(args: argparse.Namespace) -> int:
author=getattr(args, "author", None),
apply=bool(getattr(args, "apply", False)),
json_output=json_output,
out_path=getattr(args, "out", None),
)


Expand Down
10 changes: 10 additions & 0 deletions aragora/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import argparse
import os
from pathlib import Path

from aragora.cli._mission_parser import add_mission_parser
from aragora.config import DEFAULT_AGENTS, DEFAULT_CONSENSUS, DEFAULT_ROUNDS
Expand Down Expand Up @@ -2353,6 +2354,15 @@ def _add_review_queue_parser(subparsers) -> None:
action="store_true",
help="Post evidence for Tier 0-2 PRs (Tier 3-4 always prepare-only).",
)
collect_evidence_parser.add_argument(
"--out",
type=Path,
default=None,
help=(
"Write the dry-run collect-evidence JSON artifact to this path so it "
"can later be reused with --prepared-json."
),
)
collect_evidence_parser.add_argument(
"--json", dest="json_output", action="store_true", help="Output as JSON"
)
Expand Down
12 changes: 12 additions & 0 deletions aragora/swarm/quorum_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -2279,6 +2279,7 @@ def run_collect_cli(
apply: bool,
json_output: bool,
prepared_json: Path | None = None,
out_path: Path | None = None,
printer: Callable[[str], None] = print,
) -> int:
"""Shared entry point for the script and ``review-queue collect-evidence``.
Expand All @@ -2289,6 +2290,14 @@ def run_collect_cli(
return 1 (quorum is enforced as N-of-M elsewhere). Inspect ``posted_families``
in the JSON output rather than treating exit-code 1 as "nothing posted".
"""
if out_path is not None and apply:
msg = "--out is only valid for dry-run evidence preparation"
if json_output:
printer(json.dumps({"mode": "collect_evidence", "error": msg}, indent=2))
else:
printer(f"error: {msg}")
return 2

fams = tuple(families) if families else DEFAULT_FAMILIES
resolved_author = author or resolve_author()
try:
Expand All @@ -2313,6 +2322,9 @@ def run_collect_cli(
env=merge_quorum_io.aragora_env(),
quorum_reconciler=default_quorum_reconciler if apply else None,
)
if out_path is not None:
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(outcome.to_dict(), indent=2) + "\n", encoding="utf-8")
except (ValueError, RuntimeError, OSError, subprocess.SubprocessError) as exc:
if json_output:
printer(json.dumps({"mode": "collect_evidence", "error": str(exc)}, indent=2))
Expand Down
10 changes: 10 additions & 0 deletions scripts/collect_quorum_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,15 @@ def main(argv: list[str] | None = None) -> int:
"re-running reviewers."
),
)
parser.add_argument(
"--out",
type=Path,
default=None,
help=(
"Write the dry-run collect-evidence JSON artifact to this path so it "
"can later be reused with --prepared-json."
),
)
parser.add_argument("--json", dest="json_output", action="store_true", help="Output as JSON")
args = parser.parse_args(argv)

Expand All @@ -76,6 +85,7 @@ def main(argv: list[str] | None = None) -> int:
apply=args.apply,
json_output=args.json_output,
prepared_json=args.prepared_json,
out_path=args.out,
)


Expand Down
3 changes: 3 additions & 0 deletions tests/cli/commands/test_review_queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -5753,6 +5753,8 @@ def test_parser_registers_build_packet_run_and_act(self) -> None:
"openai",
"--author",
"an0mium",
"--out",
"/tmp/prepared-evidence.json",
"--json",
]
)
Expand All @@ -5761,6 +5763,7 @@ def test_parser_registers_build_packet_run_and_act(self) -> None:
assert ns_collect.pr == 6280
assert ns_collect.reviewers == ["claude", "openai"]
assert ns_collect.author == "an0mium"
assert str(ns_collect.out) == "/tmp/prepared-evidence.json"
assert ns_collect.apply is False
assert ns_collect.json_output is True
# run invocation parses
Expand Down
59 changes: 59 additions & 0 deletions tests/swarm/test_quorum_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -2318,6 +2318,65 @@ def fake_apply_prepared_evidence(**kwargs) -> CollectOutcome:
assert seen["families"] == ("claude", "grok")


def test_run_collect_cli_writes_prepared_artifact(monkeypatch, tmp_path, capsys) -> None:
def fake_collect(**kwargs) -> CollectOutcome:
return CollectOutcome(
repo="o/r",
pr=1,
head_sha=HEAD,
head_committed_at=COMMITTED,
tier=1,
action="prepare",
action_reason="dry-run",
items=[
EvidenceItem("claude", _prepared_body("claude"), True, ["claude"], [], "pass"),
EvidenceItem("grok", _prepared_body("grok"), True, ["grok"], [], "pass"),
],
)

monkeypatch.setattr(qe, "collect_evidence", fake_collect)
monkeypatch.setattr(qe, "resolve_author", lambda default="local": "me")

out_path = tmp_path / "artifacts" / "prepared.json"
rc = qe.run_collect_cli(
repo="o/r",
pr=1,
families=None,
author=None,
apply=False,
json_output=True,
out_path=out_path,
)

assert rc == 0
stdout_payload = json.loads(capsys.readouterr().out)
artifact_payload = json.loads(out_path.read_text(encoding="utf-8"))
assert artifact_payload == stdout_payload
assert artifact_payload["head_sha"] == HEAD
assert artifact_payload["items"][0]["family"] == "claude"


def test_run_collect_cli_rejects_out_with_apply(monkeypatch, tmp_path, capsys) -> None:
def boom_collect(**kwargs) -> CollectOutcome:
raise AssertionError("collect_evidence must not run when --out and --apply conflict")

monkeypatch.setattr(qe, "collect_evidence", boom_collect)

rc = qe.run_collect_cli(
repo="o/r",
pr=1,
families=None,
author=None,
apply=True,
json_output=True,
out_path=tmp_path / "prepared.json",
)

assert rc == 2
payload = json.loads(capsys.readouterr().out)
assert payload["error"] == "--out is only valid for dry-run evidence preparation"


def test_run_collect_cli_exit_code_quorum_incomplete(monkeypatch) -> None:
def fake_collect(**kwargs) -> CollectOutcome:
return CollectOutcome(
Expand Down
Loading