| name | rhwp-visual-regression |
| description | 편집/변환 전후의 HWP/HWPX 레이아웃 회귀를 숫자로 판정합니다. render-diff(자기 라운드트립·두 파일 비교·폴더 배치, px 변위+구조 불일치) → ir-diff(구조 차이) → thumbnail/export-png(눈 검증) 판단 트리를 태우고, STRUCT_MISMATCH 를 반사적으로 실패 처리하지 않고 노드 경로로 판독합니다. 트리거 — 사용자가 "편집 전후 화면 비교", "레이아웃 회귀/깨졌는지 확인", "라운드트립 시각 검증", "render-diff 돌려줘", "바뀐 게 의도한 것뿐인지" 등을 요청할 때. |
rhwp-visual-regression — 전후 시각 회귀 판정 Skill
edit/convert/export-hwpx 를 돌린 뒤 "내용이 바뀌었다"가 아니라
"의도한 것만 바뀌고 나머지 레이아웃은 그대로다"를 px 단위 수치로 판정한다.
이 스킬은 실 에이전트 경로다. gym 이 아니고, 새 CLI 명령을 발명하지 않는다.
코어는 이미 있는 render-diff / ir-diff / thumbnail / export-png 를
그대로 부른다.
IR 비교(--verify)로는 안 잡히지만 화면에서는 티가 나는 차이(표 병합·폰트
치환·페이지 넘김)를 잡는다. 판정은 예외가 아니라 데이터다.
상세는 references/ 를 단계별로 연다. SKILL.md 는 인덱스와 정지 규칙만 담는다.
바이너리
cargo build --release
./target/release/rhwp <명령> [옵션]
export-png 은 native-skia feature 빌드 필요(release 바이너리에 포함됨).
공통 규약은 rhwp-cli skill 참조.
산출물은 output/ 아래 분리 권장. 원본은 어떤 실패에서도 불변이다.
사다리 (강제 순회 아님)
render-diff(자기) → render-diff(전후) → ir-diff --json → thumbnail/export-png
질문이 이미 답이면 다음 단으로 내려가지 않는다. 각 단의 정지 조건은
14_failure_signals.md 와 아래 정지 표.
render-diff <파일> [--via hwpx|hwp] 자기 라운드트립
render-diff <A> <B> [--max-disp PX] [-p N] 두 파일 직접 비교
render-diff --batch <폴더> [-o 출력] geom_inventory.tsv
│
├─ PASS ──▶ 끝 (F01). A==A 는 항상 PASS 여야 한다 (F02)
│
├─ STRUCT_MISMATCH ──▶ 변위 노드 경로를 읽는다 (반사적 실패 금지, F03)
│ 예: Page/Body2/Column0/TextLine10/TextRun0 가 편집 필드면 정상
│ 경로가 편집한 위치와 일치 → 정상 (값이 바뀌면 그 자리 구조도 바뀐다)
│ 편집과 무관한 페이지/단 → 진짜 회귀 (F04)
│
├─ PAGE_MISMATCH ──▶ dump-pages 로 갈라지는 쪽 좁힘 (F05)
├─ OVER ──▶ 임계만 넘음, 구조는 동일. worst_page 로 좁힘 (F06)
└─ LOAD_FAIL ──▶ info 로 그 파일만 따로 연다 (F07)
2. ir-diff <A> <B> --json 구조(IR) 차이. 차이 = exit 3 (F08)
3. thumbnail / export-png 눈 검증. thumbnail 은 저장 미리보기 (F09)
4. export-render-tree <파일> -p N 정밀 bbox 좌표 (선택)
요청 → 명령
| 사용자 요청 | 명령 | 레퍼런스 |
|---|
| 이 파일 포맷 왕복이 안전한지 | render-diff <파일> --via hwpx (HWP 어댑터는 --via hwp) | 01_render_diff_self.md |
| 편집 전후 비교해줘 | render-diff <전> <후> [-p N] [--max-disp PX] | 02_render_diff_two_file.md |
| 폴더 전체 회귀 게이트 | render-diff --batch <폴더> [-o 출력] → geom_inventory.tsv | 03_render_diff_batch.md |
| STRUCT 가 떴다 | 노드 경로를 읽는다. 반사적 실패 금지 | 04_struct_mismatch.md |
| 어느 구조가 달라졌는지 | ir-diff <a> <b> --json (차이 = exit 3) | 06_ir_diff.md |
| 빨리 눈으로 확인 | export-png <파일> [-p N] (재렌더). thumbnail 은 저장 미리보기 | 07_thumbnail_vs_png.md |
| 자기 비교가 흔들린다 | render-diff <A> <A> 가 PASS 가 아니면 도구 비결정성 | 08_determinism.md |
| 임계를 조여라 | --max-disp 기본 1.0px. 구조 불일치는 임계와 무관 | 09_max_disp.md |
정지 규칙
| ID | 언제 | 행동 |
|---|
| F01 | status: PASS | 끝. 다음 단으로 내려가지 않는다 |
| F02 | render-diff A A 가 PASS 가 아님 | 도구/렌더 비결정성. 문서 회귀보다 심각. 중단 |
| F03 | STRUCT_MISMATCH + 경로가 편집 위치 | 정상. 값이 바뀌면 그 자리 구조도 바뀐다. 실패로 읽지 않는다 |
| F04 | STRUCT_MISMATCH + 경로가 편집과 무관 | 진짜 회귀. export-png 로 그 쪽을 본다 |
| F05 | PAGE_MISMATCH | 쪽 수 자체가 다름. 의도면 정상, 아니면 dump-pages --json |
| F06 | OVER (구조 동일, 변위 > 임계) | worst_page 로 좁힘. 임계가 빡센지 실제 여백 회귀인지 |
| F07 | LOAD_FAIL 또는 파일 읽기 실패 | 단건 exit 1 / 배치 폴더 오류 exit 2. info 로 그 파일만 |
| F08 | ir-diff --json 이 exit 3 | 차이 검출은 데이터. identical/diffCount/categories 로 판정 |
| F09 | 눈으로 확인이 필요 | export-png/export-svg 를 본다. thumbnail 은 저장 시점 PrvImage |
| F10 | --batch TSV 에 혼합 상태 | 요약 줄만 보지 않는다. 행별 status 로 격리 |
| F11 | 질문이 이미 답 | 다음 단 금지 |
| F12 | WARN_TEXTRUN | TextRun ±1 조성 노이즈(#1773). 하드 실패가 아니다 |
금지 기본값
- 새 비교 CLI 발명 (기존 네 명령만)
- gym pack / gym 과제 작성
STRUCT_MISMATCH 를 경로도 안 읽고 롤백
thumbnail 을 편집 후 재렌더로 착각
A==A 실패를 문서 회귀로 오진
--max-disp 를 키워 STRUCT 를 숨기려 함 (구조 불일치는 임계와 무관)
- 원본을 비교 과정에서 덮어쓰기
- 이 스킬 안에서 onboarding / mcp-session / safe-edit / provenance / doc-triage / form-fill 를 재작성
인계
- 누름틀 채움 자체 →
rhwp-form-fill (채운 뒤 여기로 돌아와 비교)
- 표 CSV 왕복 →
rhwp-table-exchange
- 원본을 계획서로 여러 번 고침 →
rhwp-safe-edit
- 배포 전 점검 →
rhwp-security-sweep
- 미지 문서 파악만 →
rhwp-doc-triage (읽기, 비교하지 않음)
상세: 13_handoff.md
봉투·종료 코드 — 판정은 데이터다
사람 모드(render-diff 텍스트): PASS/WARN_TEXTRUN 만 0,
OVER/STRUCT_MISMATCH/PAGE_MISMATCH/LOAD_FAIL 은 1.
이미 1 을 실패로 읽는 CI 가 있다.
--json 모드: 단건은 JSON 한 줄, 배치는 NDJSON.
하드 실패는 exit 3 (회귀 검출). 파일을 못 읽으면 1, 사용법 2.
ir-diff --json 과 같은 "판정은 데이터" 축이다.
{"schemaVersion":"1.0","mode":"pair"|"roundtrip","status","maxDisp","regression",
"pageCountA","pageCountB","threshold","pages":[{path,disp,...}]}
ir-diff --json:
{"schemaVersion":"1.0","a","b","identical","diffCount","categories":{…}}
- 0 = 동일 / 3 = 차이 발견 / 1 = 읽기·파싱 실패(stdout 0바이트) / 2 = 사용법
- 기본(텍스트) 모드의 정상 비교는 차이가 있어도 0 (기존 소비자 무변경)
--max-disp 기본 1.0px. 구조 불일치는 임계값과 무관하게 항상 플래그된다.
필드 표: 10_envelopes.md · 20_exit_codes.md
통과 게이트 (기계)
rhwp render-diff 산출.hwp 산출.hwp; test $? -eq 0
rhwp render-diff --batch samples -o output/rd
awk -F'\t' 'NR>1 && $2!="PASS" && $2!="WARN_TEXTRUN" {print; bad=1} END{exit bad}' \
output/rd/geom_inventory.tsv
rhwp ir-diff A.hwpx B.hwp --json || 격리처리
STRUCT_MISMATCH 행은 게이트에서 바로 실패로 접지 말고, struct_delta 와
노드 경로가 편집 위치와 맞는지를 한 번 더 본다.
레퍼런스 목차
- 00_tree.md — 판단 트리
- 01_render_diff_self.md — 자기 라운드트립
- 02_render_diff_two_file.md — 두 파일
- 03_render_diff_batch.md — 배치·TSV
- 04_struct_mismatch.md — 경로로 읽기
- 05_status_codes.md — PASS/OVER/STRUCT/PAGE/LOAD
- 06_ir_diff.md — ir-diff --json
- 07_thumbnail_vs_png.md — 미리보기 vs 재렌더
- 08_determinism.md — A==A
- 09_max_disp.md — 임계 1.0px
- 10_envelopes.md — JSON 봉투
- 11_pitfalls.md — 함정
- 12_journeys.md — 실사용 여정
- 13_handoff.md — 다른 스킬로
- 14_failure_signals.md — 신호 → 처방
- 15_node_paths.md — 노드 경로 읽는 법
- 16_worked_traces.md — 재현 트레이스
- 17_intent_matrix.md — 발화 → 명령
예제: examples/. 기계 가독 픽스처: fixtures/.
권위