From 3174b1ffefc47e7c4eb05a7b1d73a344168c7337 Mon Sep 17 00:00:00 2001 From: JK Date: Sat, 25 Apr 2026 23:22:21 +0900 Subject: [PATCH] =?UTF-8?q?confluence-mdx:=20reverse-sync=20=ED=98=84?= =?UTF-8?q?=EC=9E=AC=20=EA=B5=AC=ED=98=84=20=EC=83=81=ED=83=9C=EB=A5=BC=20?= =?UTF-8?q?=EB=AC=B8=EC=84=9C=EC=97=90=20=EB=B0=98=EC=98=81=ED=95=A9?= =?UTF-8?q?=EB=8B=88=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - architecture 문서의 reverse-sync/sidecar 설명을 현재 코드 기준으로 갱신합니다. - stale한 리팩토링 분석 문서를 현재 구현 상태 분석 문서로 전면 교체합니다. - mapping.yaml, roundtrip sidecar v3, patch_builder 중심 구조와 현재 한계를 문서화합니다. Co-Authored-By: Claude Opus 4.6 --- .../docs/analysis-reverse-sync-refactoring.md | 436 +++++++----------- confluence-mdx/docs/architecture.md | 317 +++++++------ 2 files changed, 333 insertions(+), 420 deletions(-) diff --git a/confluence-mdx/docs/analysis-reverse-sync-refactoring.md b/confluence-mdx/docs/analysis-reverse-sync-refactoring.md index 24e24574d..b9abc0b70 100644 --- a/confluence-mdx/docs/analysis-reverse-sync-refactoring.md +++ b/confluence-mdx/docs/analysis-reverse-sync-refactoring.md @@ -1,352 +1,246 @@ -# Reverse Sync 리팩토링 분석 +# Reverse Sync 현재 구현 상태 분석 -> 작성일: 2026-02-26 -> 대상: `confluence-mdx/bin/reverse_sync/` + `reverse_sync_cli.py` +이 문서는 2026-04-13 기준 reverse-sync의 실제 구현 상태를 코드베이스, 테스트케이스, 최근 커밋 로그를 바탕으로 다시 정리한 문서입니다. -## 1. 분석 목적 +기존 버전은 2026-02~03 시점의 리팩토링 제안과 phase 계획을 중심으로 작성되어 있었고, `text_transfer.py`, `list_patcher.py`, `table_patcher.py` 같은 과거 설계를 전제로 설명하는 부분이 남아 있었습니다. 현재 구현은 그 이후 다수의 refactor/fix를 거치며 구조가 크게 바뀌었으므로, 이 문서는 "무엇을 해야 하는가"보다 "지금 무엇이 구현되어 있고 어디가 여전히 취약한가"를 설명합니다. -최근 reverse-sync 관련 커밋 12건 중 **10건이 버그 수정**이다. 이 빈도는 단순한 구현 실수가 아니라 **설계 수준의 구조적 문제**에서 비롯되었을 가능성이 높다. 이 문서는 반복 버그의 근본 원인을 디자인 결함 관점에서 분석하고, 리팩토링 대상을 도출한다. +## 1. 한 문장 요약 ---- +현재 reverse-sync는 "MDX diff를 XHTML에 보수적으로 반영하고, sidecar 기반 identity preservation과 fragment reconstruction을 활용해 원본 fragment를 최대한 유지하며, 마지막에 forward roundtrip으로 검증하는 시스템"입니다. -## 2. 최근 버그 패턴 분류 +즉, 단순 역변환기나 text patcher가 아닙니다. -최근 커밋에서 수정된 버그를 원인별로 분류한다. +## 2. 최근 변경 흐름 요약 -### 2.1 텍스트 위치 매핑 오류 (4건) +2026-03-17 이후 reverse-sync 관련 커밋은 대체로 다음 흐름을 보입니다. -| 커밋 | 증상 | 근본 원인 | -|------|------|-----------| -| `2e9de1a` | `find_insert_pos(char_map, 0)`이 `char_map[0]` 존재 시에도 0 반환 | `text_transfer.py`의 문자 단위 정렬에서 경계 조건 누락 | -| `fb7efeec` | 인접 text node 경계에서 insert opcode 양쪽 적용 | `xhtml_patcher.py`의 `_map_text_range`에서 half-open range 미적용 | -| `60c5390` | `` 뒤 조사 앞 공백 미제거 | `_apply_text_changes`에서 인라인 태그 경계 gap 처리 누락 | -| `1e4dd43` | 재실행 시 "네이티브로 네이티브로" 중복 | `transfer_text_changes`가 이미 적용된 텍스트를 재매핑 | +1. Phase 5 초기 구현 + - sidecar 타입 기반 매핑 + - heading lookahead 제거 + - reconstruction 경로 도입 -### 2.2 인라인 포맷 변경 감지 실패 (3건) +2. 구조 재편 + - `text_transfer.py` 제거 + - legacy `list_patcher.py`, `table_patcher.py` 제거 + - `build_patches()` 내부로 매핑/라우팅 책임 집중 -| 커밋 | 증상 | 근본 원인 | -|------|------|-----------| -| `6438a16` | 연속 인라인 마커 사이 텍스트 변경(쉼표 등) 미감지 | `has_inline_format_change`가 마커 간 텍스트를 검사하지 않음 | -| `ea28d65` | flat list에서 backtick 변경 누락 | `_resolve_child_mapping` 실패 시 inline 변경 추적 경로 없음 | -| `7ebaf87` | inline format 변경이 text-only 패치로 처리됨 | inline 변경 감지 로직 자체가 부재 (이 커밋에서 신규 구현) | +3. 회귀 수정 집중 + - preserved anchor list 처리 + - callout 내부 list marker 공백 + - code span 뒤 공백 + - heading 내 badge roundtrip + - inline boundary whitespace + - `--no-normalize` 옵션 추가 + - visible segment 모델 도입 -### 2.3 리스트 처리 실패 (2건) +이 흐름은 현재 시스템이 아직도 활발히 안정화 중이며, 특히 리스트·인라인 경계·정규화·preserved anchor가 핵심 리스크 영역임을 보여줍니다. -| 커밋 | 증상 | 근본 원인 | -|------|------|-----------| -| `f5f307f` | 리스트 항목 수 변경 시 빈 패치 반환 | `build_list_item_patches`에 항목 수 불일치 분기 없음 | -| `acf4e3c` | 중첩 리스트 텍스트 붕괴 | `SequenceMatcher`의 `autojunk` 기본값이 CJK에 부적합 | +## 3. 현재 아키텍처의 중심축 -### 2.4 정규화/검증 불일치 (2건) +### 3.1 공개 진입점은 `reverse_sync_cli.py` -| 커밋 | 증상 | 근본 원인 | -|------|------|-----------| -| `29cdc9f` | 날짜 locale 불일치로 verify 실패 | forward converter의 locale이 reverse-sync 파이프라인과 불일치 | -| `18679d0` | callout 매핑 불완전으로 verify 실패 | `roundtrip_verifier`의 정규화 함수 부족 | +CLI 계층은 다음 책임을 맡습니다. ---- +- 단일 파일 verify +- branch 기준 batch verify +- 선택적 push +- 결과 YAML 기록 +- failures-only 출력 제어 +- `--lenient`, `--no-normalize` 같은 검증 옵션 전달 +- push 전 안전장치 및 충돌 처리 -## 3. 디자인 결함 분석 +하지만 이 파일이 reverse-sync의 "정책 엔진"은 아닙니다. -### 3.1 근본 원인: "텍스트 패칭" 전략의 본질적 취약성 +### 3.2 실제 정책 엔진은 `patch_builder.py` -현재 reverse-sync의 핵심 전략은 다음과 같다: +현재 reverse-sync의 핵심 지능은 `patch_builder.py`에 집중되어 있습니다. -``` -MDX diff → 텍스트 변경 추출 → XHTML 내 텍스트 위치 매핑 → 문자 단위 치환 -``` +이 모듈이 결정하는 것: -이 전략은 **XHTML의 DOM 구조를 보존하면서 텍스트만 교체**하는 것이 목표이지만, 근본적인 한계가 있다: +- 어떤 변경을 direct/containing/list/table/paired/skip으로 볼지 +- mapping.yaml만으로 처리할지 roundtrip sidecar fallback이 필요한지 +- delete/add 쌍을 fragment replacement로 승격할지 +- preserved anchor를 rewrite_on_stored_template로 다룰지 +- container를 outer wrapper template 기반으로 재구성할지 +- table 변경을 허용할지 skip할지 +- list 변경을 visible segment 기반으로 흡수할지 -**문제 1: 두 개의 독립된 좌표계 사이의 매핑** +현재 설계의 장점은 정책이 한곳에 모여 있다는 점이고, 단점은 정책/예외/skip 분기가 과도하게 집중되어 있다는 점입니다. -MDX의 텍스트 위치와 XHTML의 텍스트 위치는 서로 다른 좌표계이다. 이 둘을 문자 단위로 정렬(`align_chars`)하는 것은 본질적으로 불안정하다. +### 3.3 sidecar는 lookup 보조가 아니라 identity 계층 -- MDX에서 `**bold**`는 8글자, XHTML에서 `bold`는 23글자 -- Markdown의 인라인 마커, HTML 엔티티, Confluence 전용 태그 등이 좌표를 왜곡 -- `text_transfer.py`의 `align_chars()`는 비공백 문자만 정렬하므로, 공백 위치의 미묘한 차이에서 오류 발생 +현재 구현은 두 종류의 sidecar를 사용합니다. -**이것이 2.1의 4건의 버그가 모두 "위치 매핑"에서 발생한 이유이다.** +1. `mapping.yaml` + - top-level block alignment + - child alignment + - `lost_info` + - 기본 lookup 역인덱스 -**문제 2: "텍스트 변경"과 "구조 변경"의 구분 불가능** +2. `expected.roundtrip.json` schema v3 + - `xhtml_fragment` + - `mdx_content_hash` + - `mdx_line_range` + - `reconstruction` + - document envelope / separator -현재 파이프라인은 모든 변경을 "텍스트 변경"으로 시작하되, 특정 조건에서 "구조 변경"(inner XHTML 재생성)으로 전환한다. 이 전환 조건이 `has_inline_format_change()` 등의 휴리스틱에 의존하므로: +실전에서는 두 번째가 더 중요합니다. 현재 reverse-sync의 안정성은 "이 블록이 어떤 XHTML 요소인가"보다 "이 블록이 원래 어떤 fragment였고 어떤 템플릿으로 재사용할 수 있는가"에 더 크게 의존합니다. -- 감지하지 못하는 edge case가 계속 발견된다 (2.2의 3건) -- 감지 로직 추가 → 새로운 edge case 발견 → 또 추가, 의 무한 루프 +## 4. 현재 구현된 실행 파이프라인 -**문제 3: 리스트의 이중 구조** +표준 경로는 `run_verify()`로 이해하는 것이 가장 정확합니다. -MDX 리스트는 단일 블록(`type: 'list'`)이지만, XHTML에서는 `