Skip to content

feat: E4 문단 구조 편집기 — insert-para/delete-para (preserve-first)#112

Merged
pignuante merged 9 commits into
mainfrom
feat/e4-paragraph-patcher
Jul 23, 2026
Merged

feat: E4 문단 구조 편집기 — insert-para/delete-para (preserve-first)#112
pignuante merged 9 commits into
mainfrom
feat/e4-paragraph-patcher

Conversation

@pignuante

Copy link
Copy Markdown
Contributor

요약

에이전트 편집 마스터플랜의 마지막 미착수 능력 — 임의 구조 편집(문단 삽입·삭제)을 배송한다. E1~E3·E5·E6은 "구멍이 정해진" 편집(값/셀/스탬핑/읽기)이라 구조 무변경이었고, E4만 구조를 바꾼다.

  • insert-para — 앵커 문단의 모양을 상속해 그 앞/뒤에 새 문단 삽입(스타일 발명 없음). 새 문단을 expected 문서로 인코딩 후 그 <hp:p> 바이트만 원본 XML에 스플라이스 → 이스케이핑·포맷은 인코더가 처리하고 나머지 바이트는 완전 보존.
  • delete-para — 최상위 문단을 배치로 삭제, all-or-nothing, 바이트 스플라이스로 나머지 완전 보존.
  • 검증 — 재decode 후 reverse-delta self-verify(선언 델타 == 구조 델타, admission_compare). E5 diff로 "문단 1개 Added/Removed, section0.xml만 변경" 정확 확인 = 편집 루프(outline→read→edit→diff)가 닫힘.

CLI 2커맨드 + MCP 2툴(hwpforge_insert_para/hwpforge_delete_para) + SKILL.md 결정 트리. 전부 추가 전용 feat: (Core 구조체 무변경 — 최소성 원칙).

설계 재검토 (E4의 "설계 재검토" 게이트)

E4는 마스터플랜에서 유일하게 설계 재검토 게이트가 걸린 에픽. research + Codex 토론으로 스케치를 실측 교정:

  • 행(<hp:tr>) 편집은 별도 에픽 E4b로 분리 — 마스터플랜의 TableRow{row_addr:u32} 스케치가 wire 현실과 틀림(<hp:tr>엔 속성 없고 주소는 셀마다 <hp:cellAddr>에 산다). 순수 스플라이스 불가·메커니즘 다름 → 문단 릴리스가 행 불확실성에 인질 안 되게 분리.
  • 거부 규칙 = 보수적, corpus 실측으로 확정 — dangling ref 거부를 "삭제 대상에 참조 재료가 있으면 거부"로 시작(정밀 global index는 후속). 근거: 정부 서식 268 문단 과잉거부율 0.00%(scratchpad e4-corpus-probe.py). 참조 스캔은 serde 완전 구조 순회(Group children·표 셀·각주 본문 — 컨테이너 누락 불가, 안전 규칙 fail-closed).
  • self-verify = reverse-delta(E5 diff 아님) — diff의 prefix/suffix 정렬이 정확히 문단 삽입에서 약함(diff.rs 자체 명시). reverse-delta는 휴리스틱 정렬 없음.

안전성 (fail-closed 거부)

삭제/삽입이 조용한 손상 대신 거부하는 조건: ① 참조(책갈피/상호참조/각주/inst_id) 든 문단 삭제 ② hard page/column break 소실 ③ 섹션 속성(secPr) 보유 문단 — HWPX는 용지 설정을 첫 문단에 저장, self-verify가 실측으로 이 손상을 잡아 명확한 거부로 승격 ④ 섹션을 비우는 삭제 ⑤ 중첩 컨테이너 문단(비대상) ⑥ round-trip-safe 아닌 입력(admission).

G2 실측 (사용자 한컴 확인)

한컴은 모든 문단에 <hp:linesegarray>(누적 vertpos)를 방출하나, 우리 convert 출력은 linesegarray 0개인데도 정상 렌더 → 부재 시 렌더러 재계산. 디코더는 linesegarray 무시(Core 불참 → self-verify 무영향). ∴ 구조 편집 시 편집 지점부터 아래 stale 캐시 제거 = 항상 안전·정확(strip_line_segs_from). 편집 지점 위 캐시는 보존.

검증

  • 신규 테스트: smithy 구조 편집 26 · CLI 게이트 2 · MCP 표면 3. 영향 크레이트 1210 통과.
  • 시각 게이트 통과 (2026-07-23, 사용자 한컴 확인): examples/hwp5_review/_verify/e4-demo-{before,insert,delete}.hwpx — 삽입/삭제 반영 + 레이아웃 온전.
  • 편집 루프 실증: outline → read → insert-para/delete-para → diff(델타 정확).

한계 (문서화)

  • round-trip-safe 입력만 편집 가능(admission). 일부 한컴 파일은 codec fidelity 갭(예: tabs 2 vs 3)으로 거부.
  • 중첩 컨테이너(표 셀/글상자/각주) 내부 문단·표 행 편집·sub-paragraph = 비대상.
  • Outline cross-ref 타깃 heading 문단은 Core에 링크 표현 자리가 없어 보수 규칙이 구조적으로 못 잡음(기지 한계, heading 문단 삭제는 실질적으로 드묾).
  • 후속: E4b(표 행) · 정밀 dangling 규칙 · batch insert · secPr 이동 · <hp:t> 분할 보존.

설계·재검토 이력: .docs/planning/2026-07-23-e4-structural-patcher.md (내부 문서).

structural 모듈 신설(E4 문단 구조 편집기 토대). scan_paragraph_references
= serde 완전 구조 스캔(모든 컨테이너 방문 보장 — 안전 규칙 fail-closed,
typed walker 의 컨테이너 누락 위험 회피): Bookmark/CrossRef/inst_id 존재
검출, Group children·표 셀·각주 본문 재귀. patch.rs collect_direct_child_
outer_spans·find_root_span pub(crate) 승격(최상위 hp:p 주소용). W1 은
delete(W2) 가 소비할 토대라 dead_code allow.
HwpxStructuralEditor::delete_paragraphs: 최상위 문단 삭제, all-or-nothing,
나머지 바이트 완전 보존. 파이프라인 = 입력 admission → per-target 거부
(참조 stranding 보수/hard-break/secPr carrier/범위/중복) → 선언 델타
build + validate(EmptySection 정밀) → 섹션 XML 바이트 컷 → 재decode
reverse-delta self-verify(admission_compare).

self-verify 가 실측으로 secPr 손상을 잡음: HWPX 섹션 속성(<hp:secPr>)이
첫 문단 안에 살아, 문단 0 삭제 시 begin_num 리셋 → opaque DeltaMismatch
를 명확한 SectionPropertiesParagraph 거부로 승격(Wave 1 non-goal, secPr
이동은 후속). patch.rs section_path pub(crate) 승격. fixture 3종(평문
4문단/crossref/page_break) 소형 커밋. 17 테스트.
HwpxStructuralEditor::insert_paragraph: 앵커 문단 shape 상속(paraPrIDRef/
styleIDRef/charPrIDRef 발명 없음), 새 문단은 expected 문서로 인코딩 후
그 <hp:p> 바이트를 원본 XML에 스플라이스 — 이스케이핑/포맷을 인코더가
처리하고 나머지 바이트·캐시는 원본째 보존. before/after, multiline 거부,
secPr 앞 삽입 거부, reverse-delta self-verify.

G2 실측(한컴 저장본): 모든 문단이 누적-vertpos linesegarray 보유 →
구조 편집 시 편집 지점 아래 캐시가 stale. strip_line_segs_from 으로
편집 지점부터 제거(우리 인코더는 linesegarray 미방출 = 부재도 정상 렌더,
디코더는 무시 = self-verify 무영향). delete 도 동일 처리로 보강.

한컴 저장본 plain_inserted 는 codec 탭 갭(tabs 2 vs 3)으로 admission
거부 = round-trip-safe 입력만 편집(문서화 테스트). 26 테스트.
delete-para <file> --section N --index I.. -o out (배치, all-or-nothing).
insert-para <file> --section N --anchor I [--before] --text "…" -o out.
StructuralEditError → 안정 에러코드(SECTION_PROPERTIES_PARAGRAPH·
REFERENCE_STRANDED·HARD_BREAK_LOSS·INPUT_NOT_ROUNDTRIP_SAFE 등).
게이트 2종: insert→diff 델타 정확 확인·delete + secPr 거부.
hwpforge_insert_para/hwpforge_delete_para 3-layer 툴, StructuralEditError
→ 안정 에러코드+hint, next=diff 검증 연결. SKILL.md 결정 트리에 문단
구조 편집 경로 추가(insert-para/delete-para, 거부 규칙, diff 검증). 표면
테스트 3종.
@pignuante pignuante self-assigned this Jul 23, 2026
H1(High): <hp:p id> 트랜스플랜트 버그 — insert=중복 id·delete=id 갭
(매 편집 확정 발생, Core 에 id 없어 self-verify 사각). renumber_paragraph
_ids 로 스플라이스 후 섹션 직계 id 순차 재번호(0..N). wire-only라
self-verify 무영향, ObjectId 와 별도 id-space라 cross-ref 무영향.
데모 실측 insert 0-8·delete 0-6 유일연속.
M1(Med): strip_line_segs_from substring→collect_direct_child_outer_spans
(depth 인지) — 표 든 문단서 셀 캐시 오제거 방지.
M2(Med): insert encoded 경로 span-count 가드(패닉→grace-fail).
L1: 참조 스캔 docstring 정직화(opaque 페이로드 미순회 명시).
L3: 라이브러리 빈 delete = 바이트 동일 no-op.
+커버리지 게이트 보강(Display 전 variant·에러경로·중첩 strip). 신규 테스트 다수.
@pignuante
pignuante added this pull request to the merge queue Jul 23, 2026
Merged via the queue into main with commit 1418c37 Jul 23, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant