diff --git a/docs/orchestration/domain_scenario_loop_repo_adapter.md b/docs/orchestration/domain_scenario_loop_repo_adapter.md index 57e2b5b..6cf1ca9 100644 --- a/docs/orchestration/domain_scenario_loop_repo_adapter.md +++ b/docs/orchestration/domain_scenario_loop_repo_adapter.md @@ -171,7 +171,7 @@ The status payload also exposes `effective_stage_status`, `effective_stage_statu Use `python scripts/stage_agent_loop.py continue --manifest docs/orchestration/.json` as the safe one-command continuation layer. From a cold start it materializes `domain_pack_loop.command.txt` without launching the long live loop; after a GUI review it can prepare a repair iteration and materialize `run-repair --dry-run` automatically; it will not run the real coder pass unless `--execute-repair` is passed, and it waits for a `--run-id assistant-stage1-` when the next required step is post-repair rerun/ingest validation. -It also writes `stage_repair_handoff.md/json` next to the stage summary. That handoff is the preferred input for the next coder pass: it lists primary repair targets, detector result signals, and sample user-facing failures without forcing the coder to reread the entire GUI conversation first. +It also writes `stage_repair_handoff.md/json` next to the stage summary. That handoff is the preferred input for the next coder pass: it lists primary repair targets, detector result signals, and sample user-facing failures without forcing the coder to reread the entire GUI conversation first. `prepare-repair` preserves those detector signals in the repair iteration plan, prompt, and checklist so the coder sees the concrete detector, issue code, message, and evidence preview. For live stage-pack failures, prefer `lead_coder_handoff.md` over immediately preparing a coder pass. The intent is: strong business audit first, Lead Codex code repair second, same replay/GUI validation third. diff --git a/scripts/stage_agent_loop.py b/scripts/stage_agent_loop.py index f4b243a..de495dd 100644 --- a/scripts/stage_agent_loop.py +++ b/scripts/stage_agent_loop.py @@ -1899,6 +1899,9 @@ def build_stage_repair_iteration_plan( if isinstance(target, dict) ] sample_findings = handoff.get("sample_findings") if isinstance(handoff.get("sample_findings"), list) else [] + detector_result_signals = ( + handoff.get("detector_result_signals") if isinstance(handoff.get("detector_result_signals"), list) else [] + ) candidate_files = list( dict.fromkeys( path @@ -1937,6 +1940,9 @@ def build_stage_repair_iteration_plan( "auto_propose_gate": ( handoff.get("auto_propose_gate") if isinstance(handoff.get("auto_propose_gate"), dict) else {} ), + "detector_result_signals": [ + signal for signal in detector_result_signals if isinstance(signal, dict) + ][:8], "primary_repair_targets": enriched_targets, "candidate_files": candidate_files, "sample_findings": [finding for finding in sample_findings if isinstance(finding, dict)][:8], @@ -2009,6 +2015,19 @@ def build_stage_repair_checklist(plan: dict[str, Any]) -> str: f"- `{target.get('severity')}` `{target.get('problem_layer')}` / `{target.get('issue_code')}`: " f"{target.get('occurrences')} occurrence(s)" ) + detector_signals = ( + plan.get("detector_result_signals") if isinstance(plan.get("detector_result_signals"), list) else [] + ) + if detector_signals: + lines.extend(["", "## Detector Signals"]) + for signal in detector_signals: + if not isinstance(signal, dict): + continue + issue_codes = ", ".join(dcl.normalize_string_list(signal.get("issue_codes"))) or "n/a" + lines.append( + f"- `{signal.get('status')}` `{signal.get('detector')}` `{issue_codes}`: " + f"{str(signal.get('message') or '').strip()}" + ) lines.extend(["", "## Acceptance"]) acceptance = plan.get("acceptance_rerun") if isinstance(plan.get("acceptance_rerun"), dict) else {} for item in acceptance.get("after_patch", []) if isinstance(acceptance.get("after_patch"), list) else []: diff --git a/scripts/test_stage_agent_loop.py b/scripts/test_stage_agent_loop.py index 944917e..9cae6bf 100644 --- a/scripts/test_stage_agent_loop.py +++ b/scripts/test_stage_agent_loop.py @@ -900,6 +900,14 @@ class StageAgentLoopTests(unittest.TestCase): "issue_codes": ["business_direct_answer_missing"], "rerun_matrix": ["failed_scenario", "direct_answer_surface_pack", "accepted_smoke_pack"], "auto_propose_gate": {"allowed": True, "reason": "auto_coder_gate_passed"}, + "detector_result_signals": [ + { + "detector": "business_direct_answer_missing_signal", + "status": "fail", + "issue_codes": ["business_direct_answer_missing"], + "message": "Direct answer is missing on the first line.", + } + ], "primary_repair_targets": [ { "problem_layer": "answer_shape_mismatch", @@ -937,6 +945,7 @@ class StageAgentLoopTests(unittest.TestCase): self.assertIn("business_direct_answer_missing", plan["issue_codes"]) self.assertIn("direct_answer_surface_pack", plan["rerun_matrix"]) self.assertTrue(plan["auto_propose_gate"]["allowed"]) + self.assertEqual(plan["detector_result_signals"][0]["detector"], "business_direct_answer_missing_signal") self.assertIn("ingest-gui-run", plan["acceptance_rerun"]["ingest_command_template"]) def test_handle_prepare_repair_materializes_prompt_and_checklist(self) -> None: @@ -965,6 +974,14 @@ class StageAgentLoopTests(unittest.TestCase): "question_quality_score": 100, "review_markdown": "review.md", "repair_targets_json": "repair_targets.json", + "detector_result_signals": [ + { + "detector": "business_direct_answer_missing_signal", + "status": "fail", + "issue_codes": ["business_direct_answer_missing"], + "message": "Direct answer is missing on the first line.", + } + ], "primary_repair_targets": [ { "problem_layer": "business_utility_gap", @@ -988,12 +1005,13 @@ class StageAgentLoopTests(unittest.TestCase): iteration_dir = stage_dir / "repair_iterations" / "repair_001" plan_exists = (iteration_dir / "repair_iteration_plan.json").exists() prompt = (iteration_dir / "repair_prompt.md").read_text(encoding="utf-8") - checklist_exists = (iteration_dir / "repair_checklist.md").exists() + checklist = (iteration_dir / "repair_checklist.md").read_text(encoding="utf-8") self.assertEqual(exit_code, 0) self.assertTrue(plan_exists) - self.assertTrue(checklist_exists) self.assertIn("business_answer_too_verbose", prompt) + self.assertIn("business_direct_answer_missing_signal", prompt) + self.assertIn("Direct answer is missing", checklist) def test_handle_run_repair_dry_run_materializes_coder_command(self) -> None: with tempfile.TemporaryDirectory() as tmp: