1. V4 evidence 확장 (pipeline_17b_extend_missing_sections.py):
'결과물이 아니라 프로세스' 원칙 — pipeline_17 과 동일 평가 코드로 누락 3개
섹션(01-intro/05-1/05-2)만 평가해 v4_full32_result.yaml 병합 (기존 무접촉,
blind/ANSWER_MAP 불변). 결과: 05-1 → F20 light_edit 0.77 (design-matched!),
01-intro/05-2 → all-reject (catalog gap 정직 노출 — F19 가 05 주제와 이름까지
일치하나 partial 없음 → #2 프로모션 최우선 근거)
2. renderable-aware provisional (IMP-30 u1 정밀화):
rank-1 무조건 승격 → partial 존재 AND (비-reject OR verbatim builder 보유)
첫 후보 승격. reject+builder 미보유 renderable 의 mapper 네이티브 렌더는
원문 drop 위험 (F23 1-atom 손실 실측) — 원문 보존 > design 개선 우선순위.
3. 게이트 순서 버그 수정 (_apply_quality_gate_downgrades 추출):
T28.5d popup 승격 후 재계산 경로에 quality gate 강등 3종(coverage/forbidden/
consistency) 미적용 → 텍스트 손실이 overall=PASS 로 통과 (mdx05 실측).
양 경로 공통 헬퍼로 통일 — mdx04 의 #16 시점 PASS 일부가 이 버그 덕이었음을
정직하게 정정 (현재 PARTIAL + frame mismatch 라벨, 텍스트는 완전).
최종 5-MDX: 전부 missing_atoms=0 (무손실) / 01·03 PASS / 02·04·05 PARTIAL(정직
frame-mismatch 라벨) / mdx01 readiness not_ready→needs_review 개선.
게이트: 1061 passed. SHA baseline 재캡처 (정당 변경).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
구조 결함 2개 수정으로 IMP-35 popup 레버를 실동작화:
1. 트리거 모순 해소: 기존 T28.5d 트리거는 eligible_candidate_count==0 요구
— 후보가 '존재만' 해도 미발화인데 그 후보의 자동 소비는 T19d 로 금지
→ 영구 미발화 (mdx04 재현). salvage 정책 차단 터미널
(T19d layout / T28.5 font) 소진 시 overflow zone 을 대상으로 하는
두 번째 트리거 추가 (salvage_exhausted_overflow_pressure).
2. 높이 실감소: slide_base 는 partial 을 그대로 렌더 + <details> 추가일 뿐
(preview_text 는 렌더 경로 미소비 — popup 이 높이를 못 줄였음).
_trim_slot_payload_for_overflow 로 본문 라인을 예산만큼 트리밍 —
재귀 수집 (list-of-str + {text,indent} line-object, f13b 3중 중첩 포함),
라인 경계만, 슬롯당 최소 1라인, label/title 무접촉.
측정 피드백 루프 (최대 3회): 후보 렌더의 잔여 excess 로 추가 트림.
트리밍 전문은 popup body(raw_content 원문)에 보존 — 텍스트 무손실.
실전 검증:
- mdx04: RENDERED_WITH_VISUAL_REGRESSION → **PASS** (trimmed=6, coverage missing 0,
promoted). presentation_ready=False 는 density tight — #15 게이트 정직 판정
- mdx01/02/03/05: 오발화 0, 상태 불변
- SHA baseline 재캡처 (mdx04 final.html 정당 변경), 전체 게이트 1027 passed
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기계 PASS vs 사람 '빽빽함' 판정 간극을 잡는 결정론적 밀도 신호:
- _estimate_max_card_lines: repeat 슬롯/list-of-dict 컬럼/<br> 라인 payload 추정 (v0)
- _compute_density_gate: px_per_line = (측정 zone 높이 − 56) / 카드 최대 라인
임계값은 mdx 01~05 실측 캘리브레이션 — dense<16 / tight<45 px/line
(mdx03 105.8=ready, mdx02 40~44=cramped, mdx01 bottom 11.5=dense 와 정렬)
- design_readiness 통합: dense/tight → warnings(needs_review) — 사람 판정 정렬
(mdx01 11.5 도 사람 판정은 needs_review 였음 — not_ready 아님)
- presentation_ready 에 density AND 조건 추가 (#98 추천 1: 신호 통합)
- popup 승격 후 재계산 경로에도 동일 배선. 리포팅 전용 — final.html 영향 0
(SHA parity 게이트 무접촉 확인: regression 40/40 포함 전체 1016 passed)
실전 acceptance:
- mdx02: presentation_ready=False + density_tight×2 (44.0/40.4 px/line) — 정직 표시
- mdx03: PASS + comfortable(105.8) 유지, density warnings 0
(mdx03 design_readiness=needs_review 는 기존 reject-label warning — 회귀 아님)
후속: B5 slot metrics(#20) 성숙 시 payload 추정 → DOM 실측 교체
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gitea #98 Task 12 완결. 롤백된 _emergency_p4_ai_redistribute docstring 이 명시한
승인 아키텍처 5단계('AI: plan only → CODE: apply') 구현.
- _apply_design_adaptation_plan: op 분류 + repeat-count op 만 builder 재실행 적용
- set/increase_repeat_count → _emergency_p4b_build_verbatim_slot_payload
(override_slot_count, repeat 패턴 4종) — max(override, len(groups)) floor 로
원문 그룹 절대 비손실 (Task 11c)
- compact_spacing → policy_skip (T28.5 no_font_shrink) / rebalance_zone_ratio
→ policy_skip (CSS code 소유) / split_group(frame_repeat) → unsupported (#26)
/ frame_contract 변이 → unsupported (catalog 소유) — 전 op 사유와 함께
design_plan_op_status 기록 (침묵 drop 금지, PZ-4)
- count 필드 어휘 관용 추출 (count/repeat_count/to/target_count — 실측 변동)
- prompts.py: task12 계약에 op 스키마 예시 명시
- 시그니처: units 옵션 kwarg (구 호출 backward compat — 미제공 시 명시 status)
검증:
- 신규 테스트 13종 + 영향권/전체 게이트 1003 passed 0 failed
- mdx04 live: 04-1 apply=applied:design_adaptation_plan_no_delta (AI plan 이
apply 계층 최초 통과 — count 5 = 현행 동일이라 no_delta 가 정답), coverage ok
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11건 전수 분류 결과 프로덕션 결함 0, 전부 '의도된 #98 변경 대비 낡은 테스트':
- A. compute_zone_layout_cols (1건): Task 11d min-width floor(≤260px) 계약으로 갱신
- B. build_layout_css fixtures (3건): floor 반영값으로 재생성 (fixture refresh = 설계된 lock surface)
- C. imp36 self-fire (1건): auto-fit collapsed 0px track 을 점유 트랙 카운트에서 제외
- D. imp88 salvage (5건): T19d(silent layout mutation 금지) 차단 계약으로 재작성
— 차단 터미널/feasibility 선행 차단/전진 금지/trace 필드 4개 측면 + 정책 차단 체인
- E. step17 case_c (1건): 실행 2단 + T28.5 no_font_shrink 정책 차단 터미널로 갱신
- 89a_pre_baseline_sha.json 재캡처 (capture 스크립트 docstring 이 명시한 선행 drift
— #98 스퍼트 기간 누적, 정규 절차)
검증 게이트: pytest tests/phase_z2 + phase_z2_ai_fallback + regression = 990 passed 0 failed
참고: T19d 차단으로 _attempt_salvage_chain 의 layout_adjust 실행 코드는 도달 불가
dead code 상태 — 제거는 #16 (height-fit 재설계) 에서 판단
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
문제: 현재 모든 unit 이 mapper FitError → Emergency P4b verbatim/inline-AI 복구
경로로 흐르는데, 이 경로의 debug_zones 기록이 이미 계산된 placement_trace 를
누락 → PHASE_Z_B4_V4_EVIDENCE=1 에서 v4_evidence_consumed 가 전 zone 관측 불가
(u10 aggregate 테스트 red — 'flag ON이 OFF처럼 동작' silent regression 상태).
수정:
- P4b verbatim(L9107) / inline-AI(L9170) debug_zones 에 placement_trace 추가
(debug 전용 — zones_data/final.html 영향 X. adapter_needed 경로는 기존부터 보유)
- imp95_pre_baseline_sha.json 재캡처 (capture_imp95_pre_baseline.py 정규 절차 —
스냅샷 이후 상류 drift 로 구 baseline 무효화. 89a baseline 과 동일 패턴)
검증:
- trace fields 7/7 + SHA parity 포함 regression 14/14 passed
- tests/phase_z2 전체 709 passed / 11 failed — 실패 11건은 stash 검증으로
본 변경과 무관한 기존 실패 확인 (layout cols/fixtures/imp88 salvage/step17 axis)
참고 (이슈 #10 전제 정정): call-site v4_candidates 배선(L8888)과 Step 11
trace hoist(L8913)는 이미 구현돼 있었음 — 실제 갭은 복구 경로의 trace 유실.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
b836e79 스냅샷에서 UTF-8→CP949 오변환으로 한글 주석·UI 문자열 156줄 손상.
복구 방법:
- CP949 재인코딩→UTF-8 역변환 (ASCII 코드 줄 불변 보장)
- 소실 바이트('?' 잔여)가 있는 줄만 4da22ad 정상본에서 회수 (134줄 + 수작업 9줄)
- 순수 ASCII 줄 변조 0 검증 — b836e79의 기능 변경(pendingSlideBody 등) 전부 보존
- BOM 제거
검증: tsc --noEmit 0 에러, vitest 425/425 통과
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
이슈 #1 점검 결과 figma_to_html_agent는 이미 main에 추적·push되어 있음을 확인
(9fbe3ac 등에서 커밋됨, origin/main에 408 files 존재).
Front_test_v515/ 내 사본은 추적본과 동일 내용의 작업 사본.
남은 갭이던 MCP 의존성 문서화만 반영.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외
미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strip the two additive IMP-94 attributes (data-region-id,
data-content-unit-id) symmetrically at both the 89-a fixture capture
script and the b4 mapper source SHA parity test before SHA-256 hashing,
honoring the issue body guardrail "mdx 01-05 의 final.html SHA =
byte-equivalent except for new data-* attrs" without recapturing the
pre-89-a baseline. The strip regex is anchored on the leading-space +
attr-token shape emitted by src/region_marker_stamper.py:131-135 so the
#96 data-frame-slot-id axis stays disjoint.
The marker-parity cross-axis tests for emergency_p4b_verbatim_code and
emergency_p4_ai_inline append sites are converted from pytest.skip to
vacuous-truth early return when the Emergency P4/P4b anchors are absent
in HEAD — the assertion target does not exist in IMP-94 scope, but the
contract still locks placement_markers=[] when the Emergency axis lands
later. Refreshed 89a_pre_baseline_sha.json (2026-05-27T04:19:30Z) holds
the normalized sizes/SHAs for mdx 01-05 post-stamper.
Scope: regression harness + fixture only; zero src/ edits. Verified
35/35 marker-parity + 18/18 SHA parity in a clean detached worktree at
HEAD 2afedfc with these four files applied.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
app_sw_package_vs_solution.html + pre_construction_model_info_stacked.html
were staged as new files but missing from prior commits. catalog
frame_contracts.yaml already references both (family=table / family=list);
this commit reconciles the on-disk partials with the registry so the
13-family baseline matches `git ls-tree` after a clean checkout.
No marker work (data-region-id / data-content-unit-id) — that axis stays
with the marker-injection issue. Disjoint from family/variant
architecture refactor (별 tracking issue).
P7 Patch B used `comments[-1]` at line 1868 but `comments` is defined
inside run_stage, not run_issue scope. The KEEP_OPEN guard runs after
run_stage returns, where `comments` is no longer in scope, causing
NameError crash after Stage 6 YES was already accepted and exit report
generated.
Fix: fetch comments fresh via get_comments(n) at the guard entry.
exit_path file check (fallback) still works as designed.
Refs: #84 (Stage 6 crash during normal close path)
- u1 FramePanel.tsx: extract `applyFrameSelection(candidate, onFrameSelect)`
pure helper; collapse `handleFrameSelect` to direct onFrameSelect for every
V4 label; drop `window.confirm` reject popup (IMP-47B u11 regression noise
per `feedback_auto_pipeline_first`). New vitest pin `imp84_framepanel_reject_silent.test.ts`
covers helper invocation across all 4 V4 labels + source-presence pins.
- u2 templates/phase_z2/slide_base.html: delete `.zone--provisional` CSS,
`.zone__needs-adaptation-badge` CSS, the zone--provisional class fragment
in the zone div, and the badge `<span>` render at the provisional zone.
Preserve `data-provisional="1"` attribute as silent telemetry. New pytest
`tests/phase_z2/test_imp84_provisional_silent_render.py` pins the silent
contract independently of the IMP-30 first-render file.
- u3 tests/test_phase_z2_imp30_first_render.py: invert the three IMP-30 u5
positive provisional-visual assertions to IMP-84 silent-contract negatives
(no class, no badge, no CSS selectors); preserve positive `data-provisional`
telemetry assertions. Docstrings updated to IMP-84 silent contract.
Out of scope (Round #4 + #92 contract): Home.tsx `toast.error(aiReviewMsg)`
call line, designAgentApi.ts `api_error_kinds`/`api_error_kind` schema and
operational-only formatter, FramePanel reject badge/tooltip read-only labels
(L102/L147/L156), and backend `zone.provisional` flag emission.
Stage 4 PASS: u1 vitest 10/10, u2 pytest 5/5, u3 pytest 29/29 (incl. 3
IMP-84 inverted assertions: `test_imp84_provisional_zone_silent_no_class_no_badge`,
`test_imp84_provisional_badge_never_rendered_in_mixed_zones`,
`test_imp84_slide_base_css_strips_provisional_visual_selectors`).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Block Stage 2 YES when IMPLEMENTATION_UNITS contains tests: [].
- Prevent fallback from accepting orchestrator supplement examples as valid plans.
- Honor KEEP_OPEN/DO NOT CLOSE final-close dispositions by skipping close PATCH.
- Add final-close casual self-contradiction guard for YES bodies (allows explicit
`disposition: KEEP_OPEN_*` to pass through to Patch B).
- Inject rejected approaches from failure reports into next-round context with
BANNED_APPROACHES block (tests: [] / DOM mount without jsdom / Home.tsx toast
removal / git add -A).
Refs: #83 (governance break — reopen pending user decision)
#84 (Stage 2 round 5 slip — replay required after this fix)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
u1 KNOWN_AXES tuple gains slide_css entry in src/user_overrides_io.py
(snake_case parity with image_overrides); round-trip test extends
to 6 axes.
u2 src/mdx_normalizer.py surfaces nested slide_overrides.css from the
MDX frontmatter into the normalize_mdx_content return dict; absent
key -> {}, non-string css drops. 4 unit cases in tests/test_mdx_normalizer.py
(present / absent / non-string / title-only).
u3 src/slide_css_injector.py NEW (88 lines) mirrors the
inject_image_overrides_style contract from src/image_id_stamper.py:
marker pair <!--IMP45-SLIDE-CSS:OPEN--> / <!--IMP45-SLIDE-CSS:CLOSE-->,
idempotent re-injection, </head> > <body> > document-start three-tier
fallback, empty/None -> unchanged. 8 fixtures in
tests/test_slide_css_injector.py mirror test_image_id_stamper.py.
u4 run_phase_z2_mvp1 accepts override_slide_css: Optional[str] = None;
None -> frontmatter slide_overrides.css fallback. Step 13 calls
inject_slide_css after image override injection and before the
final.html disk write, so CLI/CI/regression renders observe the same
backend artifact.
u5 argparse adds mutually-exclusive --override-slide-css TEXT (inline
CSS, <style> wrapper optional) and --slide-css-file PATH (UTF-8 read,
fail-closed sys.exit(2) on missing path / decode error / both flags
present). Resolved string is forwarded as override_slide_css kwarg.
6 cases in tests/test_phase_z2_cli_overrides.py (inline / file / both
/ missing / non-utf8 / neither).
u6 samples/mdx_batch/04.mdx frontmatter gains slide_overrides.css
block (verbatim of the former MDX04_DEFAULT_OVERRIDE_CSS constant,
no sample/frame gate). Subprocess smoke in
tests/test_phase_z2_slide_css_smoke.py verifies the marker pair and
CSS substring land in final.html.
u7 Front/client removes the sample/frame-gated frontend-only injection:
Home.tsx drops the MDX04_DEFAULT_OVERRIDE_CSS constant and the
sample==="04"+frame==="process_product_two_way" branch (-28 lines);
SlideCanvas.tsx drops the iframe contentDocument.head injection of
that prop (-14 lines). Live preview now reads backend final.html only.
u8 tests/regression/fixtures/89a_pre_baseline_sha.json 04.mdx entry
resyncs to the live SHA ddb6bf2f... / 28042 bytes (overwrites the
earlier 5-byte-drift d02c76fd... / 28047). Other entries untouched.
Note: 01.mdx baseline drift (ad6f16a3... / 29089 -> live f26a7fac...
/ 29084) predates this branch and is split to a follow-up issue per
the closed-issue fresh validation rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PHASE_Z_B4_MAPPER_SOURCE env flag (default OFF) switches slot_payload
source-of-truth from legacy mapper-only / V4 rank-1 to B4 PlacementPlan
.selected_template_id at the single switch site in the runtime loop.
OFF preserves final.html SHA byte-equivalence (u4 parity guard, mdx 01-05).
ON requires Layer A render-active path; BLOCKED exits on B4 no-cover
and on B4-selected FitError (IMP-87 honesty gate pattern — NO silent
fallback). Distinct from PHASE_Z_B4_GATEKEEPER (mismatch render-skip).
Units (1 commit = 1 axis per Stage 1 scope_lock):
u1 — _b4_mapper_source_enabled() flag reader (default OFF)
u2 — _select_mapper_template_id() selector wired at the switch site
u3 — _b4_mapper_source_blocked_exit() for b4_no_cover / b4_selected_fit_error
u4 — render SHA parity regression (tests/regression/ baseline mdx 01-05)
u5 — slot_payload byte-equivalence (matches_mapper=True axis, mdx 01-05)
Targeted 89-a suite 63 PASS; Phase Z regression 323 PASS; IMP-87 mirror
20 PASS. Demo activation via .env only (no vite.config hardcoding).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EMPTY_SHELL_NO_CONTENT overall enum + 3-marker detection (frame_template_id="__empty__"
OR label="empty_shell" OR merge_type="empty_shell") routes empty-placeholder-only
slides to BLOCKED CLI exit 1 + red final_status.html, blocking fake PASS reports
(feedback_artifact_status_naming). Coverage accounting split: legacy covered_section_ids
preserved + new content_rendered_section_ids / empty_shell_section_ids. mdx05 Case B
(zero V4 evidence) honestly classified instead of synthesizing fabricated rank-1 reject
frames. IMP-30 u6/u7 stale empty-shell PASS assertions inverted (29 tests). IMP-85 smoke
parametrize: mdx05 removed from exit-0 list + dedicated BLOCKED exit test added (4 tests).
No production behavior change for chain_exhausted Case A; no AI route activation; no
mdx-id hardcoding. 53 targeted + 76 adjacent Phase Z tests PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a test-only invariance gate that locks the pre-existing four-test red
baseline so IMP-35 cannot silently grow the red surface while in-flight.
u11 does NOT fix the four reds — Stage 2 follow_up_candidates tracks the
actual repair as a separate issue. u1~u10 production work remains in the
worktree and is explicitly out of this commit per Stage 3 R7 carve-out.
Frozen registry (IMP35_BASELINE_RED_NODE_IDS, set semantics):
1. tests/test_imp47b_step12_ai_wiring.py
::test_mixed_units_classified_by_route_and_provisional_flag
2. tests/test_imp47b_step12_ai_wiring.py
::test_reject_provisional_unit_reaches_router_short_circuit
3. tests/test_imp47b_step12_ai_wiring.py
::test_step12_ai_repair_artifact_writes_json_serialisable_records
4. tests/test_phase_z2_ai_fallback_config.py
::test_ai_fallback_master_flag_default_off
Gate semantics (subprocess pytest, set comparison):
- All 4 node ids resolve to collectible pytest items
(rename / delete is caught up front).
- Broader baseline-area sweep across the two registry files yields
EXACTLY 4 FAILED and 0 ERROR, with FAILED set ≡ registry.
- A new red in the baseline area flips count above 4 OR introduces a
FAILED id outside the registry; either branch fails the gate.
- Cross-lock test ensures registry node ids cannot point outside the
declared area-files inventory.
AI isolation contract (feedback_ai_isolation_contract):
Gate body uses stdlib only (subprocess + re + ast). An AST self-verify
test rejects `anthropic` imports and `route_ai_fallback` references in
this file, structurally preventing AI routing inside the gate.
Stage 4 verification (HEAD c1df656 pre-commit):
pytest -q tests/phase_z2/test_imp35_baseline_red_invariance.py
→ 7 passed in 15.26s.
Baseline area sweep
(tests/test_imp47b_step12_ai_wiring.py +
tests/test_phase_z2_ai_fallback_config.py)
→ 4 failed / 6 passed / 0 errors; FAILED set ≡ registry (identity).
pytest --collect-only on the 4 registered node ids → all 4 resolve.
py_compile clean. Codex R1 = YES (independent verify).
Guardrails honored:
- Scope-locked: test-only file; zero production code in this commit.
- 1 commit = 1 decision unit (u11 only).
- No hardcoding: registry = Stage 2 contract frozen tuple, not
sample-specific literal; gate body has zero magic constants.
- AI isolation: stdlib-only gate, AST self-verify locks isolation.
- baseline-red 4 body repair = separate follow-up issue, not u11 scope.
source_comment_ids: Stage 1 problem-review; Stage 2 plan R2 + Codex R2
YES; Stage 3 Claude #30 + Codex #31 R7 YES; Stage 4 Claude #32 + Codex
#33 R1 YES.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add resplit_all_reject_merges() helper in phase_z2_composition.py that
detects parent_merged / parent_merged_inferred units with label=reject
and rebuilds them as per-section single units using each section's own
rank-1 V4 evidence (no frame swap, MDX raw_content preserved).
Pipeline hook fires once after Step 6 settling chain (u12/u4/empty-shell)
and section_assignment_plan resolution, before Step 6 artifact write.
Guards: beneficial-split rule (>=1 non-reject), coverage equality, layout
cap (>4 abort), max_retry=1, section_assignment_override short-circuit.
Audit: comp_debug["imp48_resplit"] additive payload (applied, split_units,
skipped_units, post_split_unit_count, post_split_layout_preset);
selection_path="resplit_from_merge" telemetry on rebuilt singles;
layout_preset re-derived via select_layout_preset(new_units).
Tests: 39/39 PASS (composition u1~u6: 14 cases; pipeline u7~u9: 25 cases).
Scoped regression 720/6 with 6 failures isolated as pre-existing on
baseline 79f9ea5 (independent of IMP-48). mdx03 golden lock preserved.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bound donor capacity in plan_zone_ratio_retry by min(static_slack,
max(0, clientHeight-scrollHeight)) when both Step 14 measured fields
are present; fall back to static contract slack when absent. Prevents
the donor from being over-allocated when full-but-not-overflowing,
avoiding a wasted Selenium rerender before cascade falls to
cross_zone_redistribute.
- src/phase_z2_retry.py: planner block L122-157 only; donor filter
(L107-112), slack<=0 gate, base_plan, greedy aggregation untouched.
Adds measured_empty_px + slack_bound_source telemetry to
donor_candidates_considered (additive only).
- tests/phase_z2/test_phase_z2_retry_measured_bound.py: 5-axis
regression (static_fallback / measured<static / measured>=static /
measured==0 excludes / filter+bool guard).
Guardrails honored: V4 rank-1 frame lock preserved, no frame_swap,
no spacing/padding/gap/line-height/font shrink, no content drop,
no MDX 03/04/05 branching, no Step 14 schema mutation. Static
fallback idempotent when measured fields absent.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BACKLOG line 93 + INSIGHT-MAP line 150 (verbatim mirror per anchor sync
rule). Trigger axis now requires both Phase R' archive trigger AND
§2.1/§2.2 SoT signature unification to keep guardrail = code-removal-only.
No source files touched.
Bug discovered during #24 IMP-24 K6 Stage 2 (2026-05-20):
- Codex r1, r2, r3 started with '=== IMPLEMENTATION_UNITS ===' on first line
(not '[Codex #N] ...'), so detect_agent (P0-1 strict, first-line only)
returned None.
- For non-audit issues, the P5 supplement guard was audit-only gated → silent
loop until Codex r4 happened to use correct format. 4 rounds wasted.
Verified that #21 Stage 4 had the same latent silent loop pattern
('## [Codex #1]' first line) — orchestrator looped through ~10 Claude rounds
before random recovery. P5b fix addresses this long-standing bug.
Patch (defensive parser-contract hardening; does not assume single root cause):
1. RULES global gets explicit "FIRST non-empty line MUST be [Claude #N] /
[Codex #N]" rule that OVERRIDES any stage-specific "body MUST contain"
constraint.
2. COMPACT_PLAN_RULE wording clarified: "body" begins AFTER the first-line
agent header. The 'body MUST contain ONLY' set no longer accidentally
permits '=== IMPLEMENTATION_UNITS ===' on line 1.
3. is_codex None supplement guard:
- audit-only gate REMOVED → fires for all issues (#24 latent loop fixed)
- Throttle: max 2 supplements per stage; on 3rd violation, orchestrator
hard-stops the issue with explicit "user action required" message
and exits run_stage cleanly
- Supplement message names both Claude AND Codex (Claude's first-line
violation also breaks downstream via Codex mimicry)
- Body-head 80 chars logged on detection failure (debugging aid)
4. Regression tests (+5 cases in test_orchestrator_core.py):
- TestDetectAgent: '=== IMPLEMENTATION_UNITS ===' first line → None
- TestDetectAgent: [Codex #N] first line + units after → 'codex' OK
- TestDetectAgent: '## ', '📌 **', '**' prefix all → None
- TestRulesAndCompactPlanFirstLineContract: RULES wording has FIRST/OVERRIDES
- TestRulesAndCompactPlanFirstLineContract: COMPACT_PLAN_RULE has carve-out
Cosmetic side effect (accepted): Claude's '📌 **[Claude #N] ...**' or
'## [Codex #N] ...' decoration prefixes will fail detect_agent. Agents
will drop decorations from line 1; line 2+ can still use them.
Out of scope (NOT included to keep regression risk low):
- detect_agent function logic UNCHANGED (P0-1 strict preserved)
- consensus parser UNCHANGED
- stage loop structure UNCHANGED
- git/Gitea retrieval logic UNCHANGED
- audit-only mode P4/P4a guards UNCHANGED
- pre-post comment validation (future axis, larger refactor)
Total: 131/131 pytest pass (126 prior + 5 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bug discovered during #56 INTEGRATION-AUDIT-02 execution (2026-05-20):
- Both Claude and Codex put "Audit anchor: ..." as the FIRST line of every
Gitea comment per the #56 issue body instruction "cite anchor at start
of every stage".
- detect_agent (P0-1 strict, first-line only) then returns None for these
comments because the first line is "Audit anchor:..." not "[Codex #N]"
or "[Claude #N]".
- Result: orchestrator's "is_codex" check (line ~1288) flips false →
"Codex 응답 미감지 — continuing" → infinite Stage 4 loop. #56 reached
Round #14 (>300 comments, ~2 hours wasted token).
Fix path (NOT relaxing detect_agent — that would revive the original #45
pre-P0-1 bug where [Claude #N] citations inside Codex bodies caused
mis-detection):
1. AUDIT_ONLY_NOTE updated to enforce comment format:
- FIRST non-empty line MUST be `[Claude #N] <stage>` or `[Codex #N] <stage>`
- Audit anchor / banners / prefaces MUST appear line 2 or later
- Concrete CORRECT example included
- Explicit warning that violation breaks stage advance
2. is_codex None guard auto-supplements:
- When _audit_mode(title) AND detect_agent returns None, orchestrator
posts a Gitea supplement comment requesting the correct format
- Next round's Claude/Codex see the supplement and correct
- Breaks the infinite loop automatically (no manual ctrl-C needed)
3. Regression tests in TestDetectAgent (test_orchestrator_core.py):
- test_audit_anchor_preface_breaks_detection: confirms P0-1 strict
correctly returns None when anchor is first line
- test_audit_anchor_after_header_works: correct format passes
Total: 96/96 pytest pass (94 prior + 2 P5 regression).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
프로젝트의 왜 / 무엇을 위해 / 어떻게 라는 질문에 대한 master 답.
이 문서가 있으면 매번 처음부터 framing 설명할 필요 없음.
구조:
1. Destination — Phase Z 22-step + AI zone-fit frame generation
2. Q~Y 검토 = 이미 완료 (과거형). 결과 = INSIGHT-MAP + 28 초기 이슈.
3. INSIGHT-MAP catalog 구조 (§0~§5)
4. IMP 이슈 좌표 체계 (관련 step + source + priority + scope + guardrails)
5. orchestrator 의 disciplined executor 역할 (Claude + Codex 합의)
6. Audit cycle (meta-governance) — 발견은 follow-up 이슈로 분리
7. 도착점 도달 기준 5 항목
8. 자주 헷갈리는 anti-patterns (heritage 보존 X, MDX 최적화 X 등)
9. 핵심 참조 문서 인덱스
10. 한 줄 요약
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit follow-up F-2 (INTEGRATION-AUDIT-01 §10.2). Phase Z families surface
showed 11 tracked / 11 contracted / 13 on disk. The 2 untracked WIP files
(app_sw_package_vs_solution.html, pre_construction_model_info_stacked.html)
are now declared in _WIP_FILES.md as uncontracted and out-of-scope for the
runtime matcher; promote/remove is gated on #42. The 11/11 tracked +
contracted baseline is unchanged. A new pytest enforces tracked families ↔
frame_contracts.yaml set-equality modulo the WIP allowlist parsed from
_WIP_FILES.md, so future drift fails fast in CI before #42 expands to 32
frames.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Append IMP-50 audit completion row referencing INTEGRATION-AUDIT-01-REPORT
(commit 8c7d693) with CONDITIONAL GO for #19 decision.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
P4 had two production issues blocking #50 integration audit deployment:
1. Stage 3 guard had no baseline awareness — flagged ALL forbidden-path
changes including pre-existing dirty WIP. Empirical: 328 such files
already in current working tree (tests/matching/ artifacts etc).
#50 would have hit reject loops immediately without Claude doing
anything wrong.
2. Stage 5 had no commit-scope guard — if Claude ran `git add -A` and
committed user's existing WIP, audit commit would be polluted with
unrelated production changes.
P4a additions:
- _audit_baseline_path / _ensure_audit_baseline / _load_audit_baseline:
snapshot working-tree dirty paths at run_issue entry for audit issues.
Resumed runs preserve existing baseline (no overwrite).
- _check_audit_only_violations(baseline=None): accept baseline set,
subtract from violations — only flags NEW forbidden changes introduced
after audit start.
- _check_audit_commit_scope: verify HEAD commit's file list matches
AUDIT_ALLOWED_COMMIT_GLOBS (INTEGRATION-AUDIT-*.md, BACKLOG.md).
- run_issue: save baseline on audit-mode entry only — no impact on
normal issues.
- Stage 5 (commit-push) YES gate: new guard rejects on out-of-scope
files with remediation prompt (git reset --soft + force-with-lease).
19 new tests:
- baseline subtraction (5): pre-existing removed, None=keep-all,
empty-set=catch-all, full-coverage filter, Windows path normalize.
- baseline persist (5): roundtrip, no-overwrite on resume, missing
fallback, corrupt JSON fallback, non-list fallback.
- commit scope detection (7): report-only allowed, backlog allowed,
src/ rejected, unrelated docs rejected, git error fail-open,
Windows backslash, empty commit pass.
- allowed globs sanity (2): every glob has audit marker, all under
docs/architecture/.
Total: 94/94 pytest pass (75 prior + 19 new).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue: #48 (IMP-15 실행-4, axis 4: debug.json + spec doc trace).
Parent: #15. Depends on 실행-1/2/3 (events + classifier outputs).
Surfaces the image/table event streams that 실행-1/2/3 already produced
and consumed, mirroring the existing `zone_geometries_px` top-level
precedent (no new pattern introduced). Adds the matching taxonomy row
to the Phase Z fit-classifier/router spec.
src/phase_z2_pipeline.py (+3):
- write_debug_json now lifts `image_events` and `table_events` to
top-level of `debug.json` via `(visual_runtime_check or {}).get(<k>, [])`,
exactly mirroring the immediately preceding `zone_geometries_px`
surfacing line. Defaults to `[]` when `visual_runtime_check` is None
— additive, no consumer-visible breakage.
docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md (+1):
- §3.1 taxonomy adds `image_aspect_mismatch` row. Row text explicitly
marks the signal as post-render `fail_reasons` from Step 14
visual_runtime_check (rendered vs declared aspect ratio mismatch),
NOT a router-routed fit_classifier output, and notes the separate
`image_events` stream surface. Prevents future readers from wiring
this taxonomy into §3.2 priority list or §4 router action map.
tests/phase_z2/test_debug_json_event_surfacing.py (new, 2 tests):
- `test_write_debug_json_surfaces_image_and_table_events` invokes
write_debug_json with synthetic visual_runtime_check containing
both event lists; reads back the on-disk debug.json and asserts
both keys are present at top level with the exact payloads.
- `test_write_debug_json_defaults_when_visual_runtime_check_none`
asserts both new keys default to `[]` when visual_runtime_check
is None — guards the defensive `(… or {})` pattern.
tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py (new, 2 tests):
- `test_spec_has_image_aspect_mismatch_row` opens the spec file and
asserts exactly one `^\| image_aspect_mismatch \|` row exists
inside the §3.1 table block (no markdown-parser dependency).
- `test_spec_row_marks_post_render_fail_reasons_semantic` asserts the
row text carries both "Post-render" and "fail_reasons" tokens —
enforces the Stage 1 guardrail wording.
Verification (Stage 4 PASS, Claude + Codex independent):
- pytest -q tests/phase_z2/test_debug_json_event_surfacing.py \
tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py
→ 4 passed in 0.07s.
- git diff scope: 4 files, +148 insertions / 0 deletions.
Scope-locked: no edits to classifier (실행-3), event generation
(실행-1/2), Step 21 viewer, §3.2 priority list, §4 router action
mapping, or `table_self_overflow` taxonomy row. Pre-existing
dirty/untracked working-tree files left untouched.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Issue #47 (IMP-15 실행-3 axis 3): extend `classify_visual_runtime_check`
to consume the `image_events[]` and `table_events[]` arrays produced by
`run_overflow_check` (실행-1/2) and widen `visual_check_passed`.
Changes (src/phase_z2_classifier.py):
- Remove `overflow.passed=True` early-return so image/table event scans
always run, even when zone-level overflow was clean.
- Deferred import of `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX`
from `phase_z2_pipeline` (circular-safe SSoT; no duplicate literals).
- New `image_events` scan emits `image_aspect_mismatch` when
`delta is not None AND |delta| > IMAGE_ASPECT_DELTA_TOL`
(delta=None ⇒ skip, image not loaded).
- New `table_events` scan emits `tabular_overflow` when
`wrapper_clipped_index is None AND (excess_x or excess_y > TABLE_SCROLL_TOL_PX)`
(wrapper-clipped tables deduped against the existing zone cascade).
- `visual_check_passed = overflow.passed AND not classifications` —
any image/table classification now flips the gate.
Guardrails preserved:
- §3.2 8-rule zone cascade (clipped_inner / zone-self) untouched —
the new emitters are ADDITIONAL.
- `placement_diagnostics`, `categories_seen`, `unclassified_signals`
return-shape preserved.
- No `pipeline.py` production changes; no router action or
`debug.json` passthrough changes.
Tests (tests/phase_z2/test_phase_z2_visual_classifier.py — new):
- `test_image_aspect_mismatch_emits_classification` (|delta|>TOL fires)
- `test_image_aspect_delta_below_tol_no_classification` (≤TOL skipped)
- `test_standalone_table_overflow_emits_classification`
(wrapper_clipped_index=None, excess>TOL fires)
- `test_table_dedup_when_wrapper_clipped`
(wrapper_clipped_index set ⇒ no `tabular_overflow` emit)
All 4 pure-dict (no Selenium / chromedriver / pipeline execution).
Tolerances imported from `phase_z2_pipeline` (SSoT enforced via test
import — no classifier-local literals).
Verification (Stage 4):
- New classifier tests: 4/4 PASS.
- Regression `tests/phase_z2/` excluding new file: 93/93 PASS.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add table self-overflow detection with element-identity wrapper dedup,
mirroring the image_aspect_mismatch axis pattern (#45).
JS layer: TABLE_SCROLL_TOL_PX=5 module constant; clippedWrapperMap
built as Map<Element,int> keyed by DOM node reference (NOT className)
so two wrappers with identical class strings remain distinguishable;
table_events collected via querySelectorAll('table').forEach with
closest()-ancestor walk resolving wrapper_clipped_index = int|null.
Py layer: aggregate result['table_events'] and append fail_reason
'table_self_overflow' only when (excess_x>TOL OR excess_y>TOL)
AND wrapper_clipped_index is None; wrapper-clipped path continues
to fail via existing clipped_inner reporting.
Tests (Selenium, chromedriver guard mirrored from image_check):
- Fixture D: standalone <table> overflow → table_self_overflow fail
- Fixture E: <table> in clipped wrapper → dedup suppresses table fail
- Fixture F (F1 acceptance): two wrappers with identical className
f13b-cell, W1 clipped by non-table child, W2 hosts self-overflow
<table> with W2 itself NOT clipped → element-identity ensures W2's
table is not suppressed by W1's class; both fails emitted.
Out of scope: image_events behavior (intact from #45), classifier
pass/fail consumer (→실행-3), debug.json surfacing (→실행-4).
Refs: #46
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/generate_frame_previews.py iterates figma_to_html_agent/blocks/{frame_id}/index.html,
renders preview.png via Selenium headless (capture_slide_screenshot pattern reuse), and writes
_preview_manifest.json (schema v1) with idempotent stale-detect (mtime+sha256). Build-time only
— no runtime pipeline integration, no AI calls, no MDX/Jinja regen. Stage 2 baseline (commit
56619a0): total=33, renderable=20, missing_index_html=13, orphan=1 (1171281192).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stage 3 lock implementation: extend build_layout_css dispatch beyond
the horizontal-2 / vertical-2 1-D dynamic paths. T / inverted-T /
side-T-left / side-T-right / 2x2 now flow through a 2-D track solver
instead of the fr_default sink, with length-locked heights_px (R) +
widths_px (C) on every return path (default and override).
PR 2 scope (u1~u5):
- u1: _aggregate_zone_signals_per_track — per-row + per-col virtual
zones via max(weight) + max(min_height_px) of single-span zones,
falling back to all-span when a track has none.
- u2: _build_grid_dynamic_2d default builder — feeds virtual zones
into compute_zone_layout + compute_zone_layout_cols; emits
computation="2d_dynamic_aggregated", dynamic_rows=True,
dynamic_cols=True.
- u3: _override_to_grid_tracks override builder — single-span
aggregation (max h per row, max w per col), normalize, multiply
by avail_h/avail_w, last-element diff absorb; emits
computation="user_override_geometry"; falls back to u2 when
total_h or total_w == 0.
- u4: build_layout_css dispatcher wiring — topology in
{T, inverted-T, side-T-left, side-T-right, 2x2} routes to
_build_grid_dynamic_2d (default) or _override_to_grid_tracks
(override); legacy [override-warning] stderr removed for the
5 presets; step08 trace gains a 2-D-aware print line that fires
before the dynamic_rows / dynamic_cols branches.
- u5: PR 1 lock test test_top_1_bottom_2_fr_default_populates_geometry
renamed to test_top_1_bottom_2_dynamic_2d_populates_geometry and
flipped to PR 2 reality (computation="2d_dynamic_aggregated",
dynamic_rows=True, dynamic_cols=True).
Fixtures: 10 build_layout_css (5 presets × {default, override}) +
5 retry_gate *_dynamic_2d.yaml locking the retry gate skip reason
"dynamic_cols (2-D topology) ... IMP-09 lock" for the 5 presets.
Tests: python -m pytest -q tests = 104 passed (Stage 2 baseline
10 RED → GREEN, 0 regressions). Kei archive
(build_containers_type_b / page_structure) untouched —
rg "build_containers_type_b|page_structure" src/phase_z2_pipeline.py
returns 0 hits.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Codex #1 (Stage 5) reproduced a smoke regression on the actual checkout :
when V4 carries the parent exact key (e.g., `04-2`) AND the drag/drop
override targets a sub-id (`primary=04-2-sub-1`), the aligner kept the
parent at parent granularity and emit `['04-1', '04-2']`, so the override
flag failed with `unknown section_id(s) ['04-2-sub-1']`.
Fix : `align_sections_to_v4_granularity` gains an optional
`override_target_section_ids` keyword. From each canonical
`${parent}-sub-N` target it derives the parent id and adds it to a
`force_drill_parents` set. Sections in that set are drilled into
sub-sections regardless of whether V4 carries the parent exact key.
Top-level override targets (no derived parent) do not trigger
force-drill, so backward-compat is preserved for parent-granularity
overrides.
The call site in `run_phase_z2_mvp1` collects sub-ids from
`override_section_assignments` and forwards them to the aligner.
Generalization (RULE 0) :
- Trigger is the override schema (`X-sub-N`), not a specific MDX / section /
frame id. Applies to all 32-frame MDX uniformly.
- Decision is deterministic on the override target shape, independent of
V4 yaml content.
- Default (no override) path is unchanged byte-for-byte.
Side fixes (forward-only RULE 1 cleanup, no history rewrite) :
- `align_sections_to_v4_granularity` docstring rewritten in English
(overwrites the Korean docstring committed in 5191aca).
- Step 9 diagnostic comment quoted-string rewritten in English
(overwrites `"V4 entry 없음"` committed in a422d72).
Tests : 3 new cases in `test_phase_z2_subsection_schema.py` —
`test_align_parent_v4_exact_keeps_section_when_no_override_targets_sub`
(backward-compat axis), `test_align_force_drills_when_override_targets_sub_id_with_parent_in_v4`
(blocker regression), `test_align_top_level_override_target_does_not_force_drill_other_sections`
(force-drill scope guard). Pytest scope-qualified result :
`test_phase_z2_subsection_schema.py` + `_section_assignment_override.py` +
`_v4_fallback.py` = 40 / 40 PASS.
Smoke (axis = sub-id override -> aligner -> assignment plan, both V4 yaml
shapes) :
- HEAD V4 yaml (`04-1`, `04-2.1`, `04-2.2` only) :
`--override-section-assignment primary=04-2-sub-1` ->
`aligned_section_ids=['04-1', '04-2-sub-1', '04-2-sub-2']`,
`plan[0].assignment_source='cli_override'`,
`plan[0].source_section_ids=['04-2-sub-1']`.
- V4 yaml with `04-2` exact key (Codex's stress case) : identical
aligned output and identical assignment plan.
Downstream `composition_planner` abort
(`phase_z_status_not_allowed:extract_matched_zone`) is IMP-05 territory,
unchanged in both shapes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>