Донести detector signals до repair prompt

This commit is contained in:
dctouch 2026-06-03 11:13:34 +03:00
parent 92e075157f
commit 50d4daf64e
3 changed files with 40 additions and 3 deletions

View File

@ -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/<stage_loop>.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-<id>` 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.

View File

@ -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 []:

View File

@ -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: