diff --git a/docs/reference.md b/docs/reference.md index 753fe09..908ad1b 100644 --- a/docs/reference.md +++ b/docs/reference.md @@ -120,7 +120,8 @@ Primary commands write compact JSON to stdout by default. This does not require genome accession ... --include genome --filename ...` commands, records return codes and ZIP readiness for later inspection, and still does not mutate manifests, install genomes, accept final genome usability, or create - strict scientific deliverables. + strict scientific deliverables. For blocked download-smoke outputs, summary + `blockers` are repeated in the top-level `blocking` array for AI routing. AI-facing stdout must stay short. Long logs, reports, tables, diagnostics, and evidence belong in the run directory. diff --git a/tests/test_cli_download_smoke.py b/tests/test_cli_download_smoke.py index 6f76c67..50a0716 100644 --- a/tests/test_cli_download_smoke.py +++ b/tests/test_cli_download_smoke.py @@ -593,6 +593,9 @@ def test_download_smoke_execute_blocks_mutated_command_manifest(capsys, tmp_path assert payload["status"] == "blocked" assert payload["downloads_triggered"] == 0 assert payload["network_access"] is False + assert payload["blocking"] == [ + {"id": "command_manifest_invalid", "message": "command_manifest_invalid"} + ] assert summary["command_invalid_count"] == 1 assert summary["blockers"] == ["command_manifest_invalid"] assert rows[0]["command_valid"] == "false" @@ -1310,6 +1313,12 @@ def test_download_smoke_prepare_blocks_without_planned_rows(capsys, tmp_path): payload = json.loads(capsys.readouterr().out) summary = payload["bounded_download_smoke_summary"] assert payload["status"] == "blocked" + assert payload["blocking"] == [ + { + "id": "no_planned_ncbi_download_rows", + "message": "no_planned_ncbi_download_rows", + } + ] assert summary["ready"] is False assert summary["blockers"] == ["no_planned_ncbi_download_rows"] diff --git a/typetreeflow/download_smoke_cli.py b/typetreeflow/download_smoke_cli.py index 22aac63..0865640 100644 --- a/typetreeflow/download_smoke_cli.py +++ b/typetreeflow/download_smoke_cli.py @@ -378,6 +378,10 @@ def run_download_smoke_command( ), outdir=str(args.outdir) if args.write else "", ) + if status != "pass": + summary_blocking = _blocking_from_summary(result["summary"]) + if summary_blocking: + payload["blocking"] = summary_blocking if args.action == "prepare": payload["bounded_download_smoke_summary"] = result["summary"] elif args.action == "inspect": @@ -2361,6 +2365,18 @@ def _payload( return payload +def _blocking_from_summary(summary: dict[str, object]) -> list[dict[str, str]]: + blockers = summary.get("blockers", []) + if not isinstance(blockers, list): + return [] + result: list[dict[str, str]] = [] + for blocker in blockers: + blocker_id = str(blocker).strip() + if blocker_id: + result.append({"id": blocker_id, "message": blocker_id}) + return result + + def _payload_schema_version(command: str) -> str: if command == INSPECT_COMMAND: return "download_smoke_inspect.v1"