diff --git a/.claude/skills/README.md b/.claude/skills/README.md index 30d094319..0d6712ba4 100644 --- a/.claude/skills/README.md +++ b/.claude/skills/README.md @@ -17,6 +17,7 @@ ### Reverse Sync / XHTML Skills - **reverse-sync.md** - Reverse Sync (MDX → Confluence XHTML 역반영) 사용 가이드 +- **reverse-sync-debugging.md** - Reverse Sync 디버깅 워크플로우 (verify 실패 원인 분석 및 수정) - **xhtml-beautify-diff.md** - XHTML Beautify-Diff Viewer 사용 가이드 ### 유틸리티 Skills @@ -39,6 +40,7 @@ | sync-ko-to-en-ja.md | [docs/translation.md](/docs/translation.md) | | mdx-skeleton-comparison.md | [docs/translation.md](/docs/translation.md) | | reverse-sync.md | [confluence-mdx/bin/reverse_sync_cli.py](/confluence-mdx/bin/reverse_sync_cli.py) | +| reverse-sync-debugging.md | reverse-sync.md, [confluence-mdx/bin/reverse_sync_cli.py](/confluence-mdx/bin/reverse_sync_cli.py) | | sync-confluence-url.md | [confluence-mdx/bin/sync_confluence_url.py](/confluence-mdx/bin/sync_confluence_url.py) | | xhtml-beautify-diff.md | [confluence-mdx/bin/xhtml_beautify_diff.py](/confluence-mdx/bin/xhtml_beautify_diff.py) | | commit.md | [docs/commit-pr-guide.md](/docs/commit-pr-guide.md) (Commit 및 PR 작성) | diff --git a/.claude/skills/reverse-sync-debugging.md b/.claude/skills/reverse-sync-debugging.md new file mode 100644 index 000000000..ac2718c51 --- /dev/null +++ b/.claude/skills/reverse-sync-debugging.md @@ -0,0 +1,336 @@ +# Reverse Sync 디버깅 가이드 + +## 개요 + +`reverse_sync_cli.py verify` 실행 시 실패하는 문서를 발견했을 때, 원인을 분석하고 수정하는 디버깅 워크플로우이다. + +- **소스 코드**: [confluence-mdx/bin/reverse_sync_cli.py](/confluence-mdx/bin/reverse_sync_cli.py) +- **관련 Skill**: [reverse-sync.md](reverse-sync.md) — Reverse Sync 기본 사용 가이드 + +## 사전 준비 + +### 1. Confluence 원문 최신화 + +`reverse_sync_cli.py`를 실행하려면 Confluence 원본 XHTML이 로컬에 있어야 한다. + +```bash +cd confluence-mdx + +# 방법 A: 최근 변경 페이지만 가져오기 (빠름) +bin/fetch_cli.py --recent --attachments + +# 방법 B: 전체 페이지를 첨부파일과 함께 가져오기 (완전) +bin/fetch_cli.py --remote --attachments +``` + +main 브랜치가 최신인지 먼저 확인한다: + +```bash +git fetch origin main +git log HEAD..origin/main --oneline # 뒤처진 커밋이 있는지 확인 +``` + +### 2. 대상 브랜치 준비 + +디버깅 대상 브랜치를 로컬에 체크아웃하고 remote 변경사항을 반영한다: + +```bash +git checkout {branch} +git pull origin {branch} # remote 변경사항 반영 +``` + +## 검증 실행 + +```bash +git checkout main # 검증은 main 브랜치에서 수행한다. +cd confluence-mdx +bin/reverse_sync_cli.py verify --branch={branch} +``` + +### 결과 판정 + +- **모든 파일이 `pass` 또는 `no_changes`**: 디버깅할 것이 없다. 작업 완료. +- **`fail` 또는 `error`가 있는 경우**: 아래 디버깅 절차를 진행한다. + +실패 건만 빠르게 확인하려면: + +```bash +bin/reverse_sync_cli.py verify --branch={branch} --failures-only +``` + +## 용어와 개념 + +- **page.xhtml**: Confluence에서 내려받은 원본 XHTML 파일. `var//page.xhtml`에 저장된다. +- **patched.xhtml**: XHTML Patch를 적용하여 생성된 XHTML 파일. `var//reverse-sync.patched.xhtml`에 저장된다. +- **original.mdx**: main 브랜치의 MDX 파일. + - Confluence 원문의 XHTML 을 Forward Convert 로 변환한 파일이다. + - 일반적으로, Confluence 원문은 original.mdx 와 동일한 내용을 가진다. + - 단, Confluence 원문이 변경된 이후 fetch → convert → PR 병합 과정이 완료되지 않은 경우에는 불일치할 수 있다. +- **improved.mdx**: 대상 브랜치의 변경된 MDX 파일. 사람 또는 AI Agent 가 변경한 MDX 파일이다. +- **verify.mdx**: Reverse Sync 를 위해 Round-Trip Verification 과정에서 생성된 MDX 파일이다. + - original.mdx 와 improved.mdx 의 차이를 기반으로, page.xhtml 을 변경(Patch)한 후, + 이 patched.xhtml 을 다시 Forward Convert 로 변환한 파일이다. + - verify.mdx 와 improved.mdx 가 동일하면, verify 에 성공한 것이다. +- **Forward Convert**: + - page.xhtml 을 Markdown(정확히는 MDX) 형식으로 변환하는 코드의 실행 + - 이 코드는 XHTML 원문의 구조와 특성을 가능한 한 그대로 Markdown 으로 변환하는 것을 목표로 한다. + - XHTML 의 컨텐츠, 문구의 오류, 오기, 불필요한 요소를 제거하거나 변경하는 행위를 최소화하는 것을 지향한다. + - 이러한 오류가 발견되면, 교정/교열 과정을 통해 MDX 를 수정하고 + Reverse Sync 로 Confluence 원문에 반영하는 것을 지향한다. +- **XHTML Patch**: + - original.mdx 와 improved.mdx 의 차이를 기반으로, page.xhtml 을 변경(Patch)하는 코드의 실행 +- **Reverse Sync**: + - original.mdx 를 교정/교열하여 improved.mdx 를 만든 경우, 이 변경사항을 Confluence XHTML 에 반영하여 Confluence 원문을 업데이트하는 것 + - Reverse Sync 과정의 오류를 방지하기 위해, Round-Trip Verification 과정으로 검증한다. +- **Round-Trip Verification**: + - improved.mdx 와 verify.mdx 가 동일한지 여부를 검증하는 것 + - 실제로 Confluence 원문을 업데이트하지 않고, 변경 결과를 미리 확인하는 과정이다. + - XHTML Patch, Forward Convert 과정의 오류를 발견할 수 있다. + +### verify 실패 원인 + +다음 두 가지 중 하나이며, XHTML Patch 코드의 버그일 가능성이 높다. + +- XHTML Patch 를 수행하는 코드의 버그 +- Forward Convert 를 수행하는 코드의 버그 + +## 디버깅 절차 + +실패하는 문서가 발견된 경우, 다음 절차를 통해 원인을 분석하고 수정한다. + +### 1. 실패 문서에 대해 검증 결과 확인하기 + +실패한 MDX 파일에 대해 단일 파일 단위로 검증 결과를 확인한다. + +**verify 모드**: 결과 상태와 불일치 diff를 확인한다. +```bash +bin/reverse_sync_cli.py verify {branch}:{path/to/failed.mdx} +``` + +**debug 모드**: 세 가지 diff를 상세 출력한다. 원인 분석의 핵심 정보이다. +```bash +bin/reverse_sync_cli.py debug {branch}:{path/to/failed.mdx} +``` + +debug 모드에서 출력되는 세 가지 diff: +1. **MDX diff** (original.mdx → improved.mdx): 입력 변경 내용 +2. **XHTML diff** (page.xhtml → patched.xhtml): XHTML에 적용된 패치 +3. **Verify diff** (improved.mdx → verify.mdx): round-trip 불일치 부분 — 이 diff가 실패의 핵심 단서 + +XHTML 레벨의 변경을 정밀 분석하려면 `xhtml_beautify_diff.py`를 사용한다: +```bash +bin/xhtml_beautify_diff.py \ + var//page.xhtml \ + var//reverse-sync.patched.xhtml +``` + +중간 산출물은 `var//`에 생성된다: +``` +var// +├── reverse-sync.diff.yaml # MDX 블록 변경 목록 +├── reverse-sync.mapping.original.yaml # 원본 XHTML 블록 매핑 +├── reverse-sync.patched.xhtml # 패치된 XHTML +├── reverse-sync.mapping.patched.yaml # 패치 후 매핑 +├── reverse-sync.result.yaml # 검증 결과 (status, diff 포함) +└── verify.mdx # 패치된 XHTML → MDX 재변환 결과 +``` + +### 2. 실패 결과를 재현하는 테스트케이스를 추가하기 + +테스트케이스를 추가하는 방법은 두 가지이다. 상황에 따라 선택한다. + +#### 방법 A: 셸 기반 통합 테스트 (`tests/reverse-sync/`) + +페이지 전체의 verify 실패를 그대로 재현하는 방법이다. +실제 XHTML, MDX 파일을 사용하므로 end-to-end 검증에 적합하다. + +```bash +cd confluence-mdx/tests + +# 1. 테스트케이스 디렉토리 생성 (page_id 사용) +mkdir -p reverse-sync/ + +# 2. 필요한 파일 복사 +cp ../var//page.xhtml reverse-sync//page.xhtml +cp ../var//mapping.yaml reverse-sync//mapping.yaml +``` + +original.mdx와 improved.mdx를 git에서 추출한다: +```bash +# original.mdx: main 브랜치의 MDX 파일 +git show main:{path/to/file.mdx} > reverse-sync//original.mdx + +# improved.mdx: 대상 브랜치의 변경된 MDX 파일 +git show {branch}:{path/to/file.mdx} > reverse-sync//improved.mdx +``` + +테스트케이스가 실패를 재현하는지 확인한다: +```bash +make test-reverse-sync-bugs-one TEST_ID= +``` + +결과는 `reverse-sync//output.verify.log`에서 확인할 수 있다. + +#### 방법 B: Python pytest 단위 테스트 + +버그가 발생하는 함수를 직접 호출하여 재현하는 방법이다. +원인이 되는 함수를 특정할 수 있을 때 적합하며, 실행이 빠르고 정확한 조건을 통제할 수 있다. + +테스트 파일은 `tests/test_reverse_sync_*.py`에 추가한다. 버그가 발생하는 모듈에 대응하는 테스트 파일을 선택한다: + +| 버그 위치 | 테스트 파일 | +|-----------|------------| +| `patch_builder.py` (`build_patches`, `build_list_item_patches`) | `test_reverse_sync_patch_builder.py` | +| `text_transfer.py` (`transfer_text_changes`, `find_insert_pos`) | `test_reverse_sync_text_transfer.py` | +| `xhtml_patcher.py` | `test_reverse_sync_xhtml_patcher.py` | +| 기타 모듈 | 대응하는 `test_reverse_sync_*.py` | + +테스트 작성 시 기존 헬퍼 함수를 활용한다: +- `_make_mapping()` — XHTML 블록 매핑 fixture 생성 +- `_make_change()` — MDX 블록 변경 fixture 생성 +- `_make_sidecar()` — sidecar 매핑 fixture 생성 + +**테스트 작성 패턴** (실제 PR #845, #846, #847 참조): + +```python +class TestBugDescription: + """버그 현상과 재현 시나리오를 클래스 docstring으로 기술한다.""" + + def test_specific_failure_case(self): + """실제 사례의 데이터로 버그를 재현한다. + + 재현 시나리오: + Original MDX: "원래 텍스트" + Improved MDX: "변경된 텍스트" + 현상: 어떤 오동작이 발생하는지 기술 + """ + # 1. fixture 구성 — 실제 실패 데이터에서 최소한의 재현 데이터를 추출 + mapping = _make_mapping('id', 'xhtml text', xpath='p[1]') + change = _make_change(0, 'old content', 'new content') + # ... + + # 2. 버그가 발생하는 함수 호출 + result = build_patches(...) + + # 3. 버그가 재현되는지 assert (수정 전에는 실패해야 함) + assert '[expected]' in result # 올바른 동작 + assert not result.startswith('[') # 버그 동작이 아님을 검증 +``` + +**핵심 원칙:** +- 실제 실패 데이터에서 **최소한의 재현 데이터**를 추출하여 테스트에 사용한다. +- docstring에 **실제 사례**(파일명, 변경 내용)를 기록하여 테스트의 맥락을 보존한다. +- **버그 동작이 아님**을 검증하는 부정 assert를 포함한다 (예: `assert '또한또한' not in result`). +- 수정 전에 테스트를 실행하여 **실패하는 것을 확인**한 후 수정에 착수한다. + +테스트 실행: +```bash +cd confluence-mdx/tests +../venv/bin/python3 -m pytest test_reverse_sync_patch_builder.py::TestBugDescription -v +``` + +### 3. commit 하고 PR 을 생성하기 + +테스트케이스를 추가한 상태에서 commit 하고 Draft PR을 생성한다. +이 시점에서 테스트는 실패하는 것이 정상이다 (버그 재현 상태). + +```bash +# 방법 A의 경우 +git add tests/reverse-sync// + +# 방법 B의 경우 +git add tests/test_reverse_sync_*.py + +git commit -m "confluence-mdx: reverse_sync verify 실패 테스트케이스 추가" +``` + +Draft PR을 생성하여 작업 진행 상황을 공유한다. + +### 4. 실패 원인을 분석하고 수정하기 + +debug 모드의 세 가지 diff와 중간 산출물을 기반으로 원인을 분석한다. + +verify 실패의 원인은 두 가지 중 하나이다: +- **XHTML Patch 코드의 버그** (가능성 높음): `bin/reverse_sync/` 아래의 패치 관련 모듈 +- **Forward Convert 코드의 버그**: `bin/converter/` 아래의 변환 관련 모듈 + +**분석 방법:** +1. **Verify diff** (improved.mdx ↔ verify.mdx)를 확인하여 어떤 부분이 다른지 파악한다. +2. **XHTML diff** (page.xhtml ↔ patched.xhtml)를 확인하여 XHTML 패치가 의도대로 적용되었는지 검증한다. +3. patched.xhtml이 잘못되었다면 → XHTML Patch 코드의 버그 +4. patched.xhtml은 정상인데 verify.mdx가 다르다면 → Forward Convert 코드의 버그 + +수정 후 해당 테스트케이스가 통과하는지 확인한다: +```bash +make test-reverse-sync-bugs-one TEST_ID= +``` + +### 5. 로컬 테스트 수행하기 + +CI 워크플로우 ([`.github/workflows/test-confluence-mdx.yml`](/.github/workflows/test-confluence-mdx.yml))와 동일한 테스트를 모두 수행하여 기존 테스트가 깨지지 않았는지 확인한다. + +```bash +cd confluence-mdx/tests + +# 1. Python 단위 테스트 +../venv/bin/python3 -m pytest -v + +# 2. XHTML → MDX 정방향 변환 테스트 +make test-convert + +# 3. Skeleton MDX 테스트 +make test-skeleton + +# 4. Reverse-Sync 테스트 (testcases/ 기반) +make test-reverse-sync + +# 5. Image-Copy 테스트 +make test-image-copy + +# 6. XHTML Beautify-Diff 테스트 +make test-xhtml-diff + +# 7. Byte-equal 라운드트립 검증 +make test-byte-verify + +# 8. MDX → HTML 렌더링 테스트 (vitest) +make test-render +``` + +모든 테스트가 통과하면 다음 단계로 진행한다. +실패하는 테스트가 있으면 수정이 기존 동작을 깨뜨린 것이므로, 4단계로 돌아가 수정한다. + +### 6. commit 하고 PR 을 업데이트하기 + +수정 사항을 commit 하고, Draft PR을 Review 가능 상태로 전환한다. + +```bash +git add -p # 변경사항을 확인하며 staging +git commit -m "confluence-mdx: <수정 내용 요약>" +``` + +PR을 업데이트한다: +- Draft 상태를 해제하여 Review 가능으로 전환한다. +- 최종 commit 을 포함한 상태를 반영하여 PR title과 description을 재작성한다. + +### 7. CI 확인 및 완료 + +PR push 후 CI ([`test-confluence-mdx.yml`](/.github/workflows/test-confluence-mdx.yml)) 실행 결과를 확인한다. + +```bash +# PR의 CI check 상태 확인 +gh pr checks +``` + +- **CI 성공**: 디버깅 작업을 성공적으로 종료한다. +- **CI 실패**: 실패한 테스트를 확인하고, 4단계(원인 분석 및 수정) → 5단계(로컬 테스트) → 6단계(commit 및 PR 업데이트)를 반복 수행한다. + +## 실제 사례 + +다음 PR들이 이 디버깅 워크플로우의 실제 사례이다. 모두 Python pytest 단위 테스트(방법 B)로 버그를 재현하고 수정하였다. + +| PR | 버그 현상 | 테스트 방법 | 수정 대상 | +|----|-----------|------------|-----------| +| [#845](https://github.com/chequer-io/querypie-docs/pull/845) | push+fetch 후 verify 재실행 시 텍스트 중복 ("또한또한") | `test_reverse_sync_patch_builder.py` — `TestBuildPatchesIdempotency` (2건) | `patch_builder.py` — 멱등성 체크 추가 | +| [#846](https://github.com/chequer-io/querypie-docs/pull/846) | flat list에서 backtick(inline code) 변경이 XHTML 패치에 누락 | `test_reverse_sync_patch_builder.py` — flat list inline code 테스트 (2건) | `patch_builder.py` — inline 마커 감지 + inner XHTML 재생성 | +| [#847](https://github.com/chequer-io/querypie-docs/pull/847) | flat list에서 `[` 삽입 시 XHTML 텍스트 맨 앞에 잘못 배치 | `test_reverse_sync_text_transfer.py` + `test_reverse_sync_patch_builder.py` (3건) | `text_transfer.py` — `find_insert_pos` forward 탐색 추가 |