feat(s42): --live-dry-run CLI flag + dry-run summary

This commit is contained in:
Kjell Tore Guttormsen 2026-07-15 18:08:28 +02:00
commit d7313593bc
2 changed files with 97 additions and 2 deletions

View file

@ -610,6 +610,7 @@ def main(argv: list[str] | None = None) -> int:
"""Single-command console entry: run the slice for one project against a docs folder."""
import argparse
import asyncio
import sys
parser = argparse.ArgumentParser(description="portfolio-optimiser vertical slice")
parser.add_argument("project_id")
@ -626,10 +627,47 @@ def main(argv: list[str] | None = None) -> int:
)
parser.add_argument("--decision", default="approved", choices=["approved", "rejected"])
parser.add_argument("--rationale", default="reviewed by expert")
parser.add_argument(
"--live-dry-run",
action="store_true",
help="offline drill: build contracts/clients/budget, STOP before the first model call",
)
args = parser.parse_args(argv)
# S4.2: main() currently drives only full runs (no --live-dry-run flag yet — that is Step 3,
# which replaces this cast with an isinstance(DryRunReport) branch + AZURE-refusal try/except).
if args.live_dry_run:
# S4.2 drill (comparison protocol §4 pkt 2/3): walk the offline path, STOP before the first
# model call, print the run-config. A misconfigured profile (e.g. AZURE with a
# REPLACE-WITH-* placeholder) makes resolve_model raise inside the eager factory build —
# refuse cleanly (mirror preflight.main) instead of tracebacking; S4.1 is the config gate.
try:
report = asyncio.run(
run_project(
args.project_id,
args.profile,
docs_dir=args.docs_dir,
bundle_dir=args.bundle_dir,
verdict_dir=args.verdict_dir,
verdict_input={"decision": args.decision, "rationale": args.rationale},
live_dry_run=True,
)
)
except ValueError as exc:
print(
f"live-dry-run refused: {exc}\n"
"kjør 'python -m portfolio_optimiser.preflight --profile azure' først "
"(S4.1 offline config-gate)",
file=sys.stderr,
)
return 1
assert isinstance(report, DryRunReport) # live_dry_run=True always returns a DryRunReport
print(
f"{args.project_id}: LIVE-DRY-RUN OK (profile={report.profile}, "
f"models={report.resolved_models}, max_rounds={report.max_rounds}, "
f"max_tokens={report.max_tokens}, top_k={report.top_k}) — "
"ingen modellkall gjort (stoppet før første debate.run)"
)
return 0
result = cast(
RunResult,
asyncio.run(