1 line
7.8 KiB
JSON
1 line
7.8 KiB
JSON
{"body": "[Claude #12] Stage 3 code-edit — IMP-33 #61 (R8 u8)\n\nExecuting unit: u8\n\n■ unit_executed\n- id: u8\n- summary: Step 12 AI repair wiring (`src/phase_z2_ai_fallback/step12.py`). New module-level entry `gather_step12_ai_repair_proposals(units, *, route_for_label, get_contract_fn, frame_visual_loader, ...)` layers two structural gates on top of u7 router: (1) IMP-30 provisional gate — units with `provisional=False` are skipped before any route classification (per Stage 2 \"Step 12 IMP-30 provisional only\"); (2) reject gate — units whose V4 label maps to `design_reference_only` are skipped with `skip_reason=\"design_reference_only_no_ai\"` and NEVER call AI (per Stage 2 \"reject remains design_reference_only and no-AI\"). For provisional + `ai_adaptation_required` units, the helper assembles the u7 router inputs (cache_key, v4_result, frame_contract, frame_visual_html, figma_partial_json, internal_region, mdx_text) and invokes `route_ai_fallback(...)` once per unit. Save is NOT performed here — caller-driven AFTER visual_check + user_approved (u6 IMP-46 gate).\n\n■ files_changed\n- src/phase_z2_ai_fallback/step12.py (NEW, 141 lines incl. docstrings)\n- tests/phase_z2_ai_fallback/test_step12.py (NEW, 193 lines, 9 tests)\n\n■ diff_summary\n- `src/phase_z2_ai_fallback/step12.py`:\n - Module path locked by IMP-31-GATE-AUDIT (`src/phase_z2_ai_fallback/`, Stage 1 binding).\n - Module-level constants `_AI_ADAPTATION_ROUTE = \"ai_adaptation_required\"` and `_DESIGN_REFERENCE_ROUTE = \"design_reference_only\"` (mirrors `src/phase_z2_pipeline.py:572-576` `_IMP05_ROUTE_HINTS`).\n - Single import from the fallback package: `route_ai_fallback` (u7). NO Anthropic import, NO `messages.create()` call, NO `save_proposal` import (caller-only per u6 contract), NO MDX I/O (caller supplies loaded artifacts).\n - `gather_step12_ai_repair_proposals(units, *, route_for_label, get_contract_fn, frame_visual_loader, figma_partial_loader=None, internal_region_lookup=None, mdx_text_loader=None) -> list[dict]`:\n 1. Per-unit record schema is stable across every gate decision: `{unit_index, source_section_ids, frame_template_id, label, route_hint, provisional, ai_called, skip_reason, proposal, error}` — downstream Step 12 artifact consumers can rely on a single shape.\n 2. Provisional gate (FIRST): `if not unit.provisional: skip_reason=\"not_provisional\"; continue`. IMP-30 first-render survivors are the only path AI repair can touch.\n 3. Reject gate: `if route_hint == _DESIGN_REFERENCE_ROUTE: skip_reason=\"design_reference_only_no_ai\"; continue`. Reject = design reference only, NEVER AI (per Stage 2 + `feedback_ai_isolation_contract`).\n 4. Non-AI route catch-all: `if route_hint != _AI_ADAPTATION_ROUTE: skip_reason=f\"route_not_ai_adaptation:{route_hint}\"; continue`. Defence-in-depth against future route_hints.\n 5. AI path: build `cache_key = f\"{template_id}::{','.join(sorted(source_section_ids))}\"`, assemble `v4_result = {route, label, frame_id, rank, cardinality}`, resolve `frame_contract` via `get_contract_fn`, `frame_visual_html` via `frame_visual_loader`, optional `figma_partial_json` / `internal_region` / `mdx_text` via injected loaders (raw_content fallback). Call `route_ai_fallback(...)` inside try/except. RuntimeError / API errors → record `ai_called=True`, `error=f\"{type(exc).__name__}: {exc}\"` (no re-raise — pipeline can continue per-unit). `proposal is None` → `skip_reason=\"router_short_circuit\"` (flag-off or route-mismatch inside u7). Non-None → `proposal=proposal.model_dump()`.\n - Save-to-cache responsibility is explicitly NOT in this module — only u6 `save_proposal` (which the helper does not import) can persist proposals, and only AFTER visual_check + user_approved gates.\n- `tests/phase_z2_ai_fallback/test_step12.py` — 9 focused tests covering every Stage 2 axis u8 owns:\n - `test_non_provisional_unit_is_skipped_without_ai_call` — IMP-30 provisional gate; `router.assert_not_called()`.\n - `test_reject_route_is_skipped_without_ai_call` — label=\"reject\" (route_hint=design_reference_only) NEVER calls router; `router.assert_not_called()`.\n - `test_non_ai_route_is_skipped_with_reason` — label=\"light_edit\" (deterministic_minor_adjustment) records skip_reason and does NOT call router.\n - `test_router_short_circuit_returns_none_skip_reason` — router returns None (flag-off / route-mismatch surface) → `ai_called=False`, `skip_reason=\"router_short_circuit\"`.\n - `test_ai_adaptation_call_records_proposal` — label=\"restructure\" + provisional=True → `ai_called=True`, proposal serialized via `model_dump()`, router called with `v4_result.route == \"ai_adaptation_required\"`.\n - `test_router_exception_is_captured_per_record` — `RuntimeError(\"transient_boom\")` → `ai_called=True`, `error=\"RuntimeError: transient_boom\"`, no re-raise.\n - `test_mixed_units_each_independently_classified` — 4-unit mix (use_as_is non-prov / reject prov / restructure prov / restructure non-prov) → router called exactly 1× (only restructure+provisional), skip_reasons match expected schedule.\n - `test_cache_key_includes_template_and_section_ids` — cache_key contract = `\"{template_id}::{sorted_section_ids_csv}\"`.\n - `test_record_shape_contract_is_stable` — explicit `set(rec.keys())` assertion locks the per-record schema (downstream Step 12 artifact consumers).\n\n■ scope guardrails preserved (this unit)\n- PZ-1 — normal-path AI count unchanged. u8 is a HELPER ONLY — no pipeline.py call site is added in this unit (Stage 2 file list mentions `src/phase_z2_pipeline (Step 12 wiring)`; this unit completes the wire CONTRACT + tests; the in-pipeline call insertion is a 1-line opt-in that any later unit can drop in without modifying u8's contract). With u1 `ai_fallback_enabled=False` default, even when the helper is invoked the u7 router short-circuits before any Anthropic call.\n- `feedback_ai_isolation_contract` — u8 NEVER mutates MDX, NEVER reads MDX from disk (caller supplies via `mdx_text_loader` or unit.raw_content), NEVER bypasses u6 cache gate (no `save_proposal` import).\n- `feedback_phase_z_spacing_direction` — V4 rank-1 protected via u5 validator (called inside u7 router). u8 forwards the contract; never mutates frame_id.\n- `feedback_no_hardcoding` — no sample-specific branches; route map injected via `route_for_label` callable (test parity with `_IMP05_ROUTE_HINTS`).\n- `feedback_one_step_per_turn` — single unit (u8) executed; no follow-up unit started, no commit, no push.\n- Module path locked at `src/phase_z2_ai_fallback/` (IMP-31-GATE-AUDIT Stage 1 binding).\n\n■ tests scope-qualified\n- `tests/phase_z2_ai_fallback/test_step12.py`: 9 passed in 0.13s.\n- Regression — `tests/phase_z2_ai_fallback/` + `tests/test_phase_z2_ai_fallback_config.py` combined: 69 passed in 0.21s (u1 + u2 + u3 + u4 + u5 + u6 + u7 + u8 → 4 + 9 + 11 + 8 + 10 + 9 + 9 + 9 = 69 ✓).\n- Scope-qualified — verified only u8 surface in this round; broader Phase Z pipeline tests (overflow / V4 / composition) untouched by u8 (helper not yet wired into pipeline.py).\n\n■ remaining_units\n- [u9, u10, u11]\n\n■ follow_up_issue_candidates\n- (in-scope, planned for follow-up unit) pipeline.py 1-line opt-in call site at Step 12 artifact write — adds `ai_repair_proposals` field. Flag-off default = no-op; safe to drop in once Codex u8 verify is green. Can ride u9/u10/u11 OR a tiny u8.5 sub-step.\n- (out of scope IMP-33) `figma_partial_loader` default of `{}` is a placeholder until IMP-37 stage0 normalizer lands the figma_to_html_agent partial JSON pipeline; the helper signature already accepts the loader, so no API churn when IMP-37 lands.\n- (out of scope IMP-33) `internal_region_lookup` default of `{}` — once Layer A planning activates the render path (per current MEMORY note `project_design_agent_status`), this lookup will be injected by the caller.\n\nrewind_target: continue_same\nFINAL_CONSENSUS: NO\n"} |