ADR implementation H2 partial matcher + PR 표 헤더 오인 차단
요약blog ADR detail-view는 implementation 섹션의 PR 표를 PhaseStrip 카드로 시각화한다(Sprint 163). 그런데 resolveCanonical이 implementation을 exact alias 매칭으로만 처리해, ## 구현 (8 PR squash merge, ...) / ## Implementation (single PR, ...) 처럼 괄호·접미사가 붙은 헤딩이 'other'로 분류되어 sprint-153~165(KR + EN)의 PR 표가 Phase 카드로 렌더되지 않던 갭을 회수한다(Sprint 163 이월 후속).
목표
- blog ADR detail-view는 implementation 섹션의 PR 표를 PhaseStrip 카드로 시각화한다(Sprint 163). 그런데
resolveCanonical이 implementation을 exact alias 매칭으로만 처리해,## 구현 (8 PR squash merge, ...)/## Implementation (single PR, ...)처럼 괄호·접미사가 붙은 헤딩이'other'로 분류되어 sprint-153~165(KR + EN)의 PR 표가 Phase 카드로 렌더되지 않던 갭을 회수한다(Sprint 163 이월 후속). - 착수 후 브라우저 검증에서 더 깊은 pre-existing 취약점을 발견:
parsePrTable의 헤더 탐지가 "PR" 문자열을 가진 데이터 행까지 헤더로 오인 → partial matcher 적용 시 sprint-163이 garbage Phase 카드를 렌더. 이를 함께 차단한다.
결정
D1. resolveCanonical에 implementation tolerant 정규식 fallback 추가
resolveCanonical(section-aliases.ts)은 numbered prefix 제거 후 exact alias lookup → carryover/lessons만 tolerant 정규식(CARRYOVER_RE/LESSONS_RE) fallback을 가졌고 implementation은 tolerant matcher가 없었다. 따라서 구현 exact는 매칭되나 구현 (8 PR ...)는 'other'로 떨어졌다.
IMPLEMENTATION_RE = /^(?:구현|implementation|execution)(?:\s|$|\()/i를 추가하고 carryover/lessons 다음 fallback으로 배치했다.
- 키워드 직후가 공백/끝/
(일 때만 매치 →구현체(비공백 후속)는 미매칭(보수적). - FP 전수 측정: 전 corpus(KR sprints + EN sprints + permanent ADR)에서 old→new canonical 변화 66건 전부
other → implementation— 기존 올바른 분류(carryover/lessons/verification 등)를 빼앗는 변화 0건. - Phase 카드 생성은 PR 표 + Phase 컬럼을 요구하는 다운스트림 가드(
extractPhaseEntries)가 별도로 막으므로, PR 표 없는 섹션(구현 작업,구현 예정 위치등)이 implementation으로 분류돼도 bogus 카드는 생성되지 않는다.
D2. parsePrTable/stripPrTableLines 헤더 탐지를 "구분자 직전 라인"으로 한정
parsePrTable(parser.ts)의 헤더 탐지가 lines.findIndex(l => /\|/ && /pr/i)로 임의의 "PR" 포함 라인을 헤더로 잡았다. sprint-163의 implementation 표는 헤더가 | Phase | 담당 | 변경 | 라인 |(PR 컬럼 없음)인데, 데이터 행 | A — PR 표 strip 기반 | ... |의 "PR" 문자열이 헤더로 오인 → A를 헤더·B를 구분자로 먹고 C~R9를 garbage 카드로 추출, 표도 strip되지 않았다.
GFM 표의 헤더는 항상 구분자(|---|) 직전 라인이라는 불변식을 이용해, 헤더 탐지에 "다음 라인이 구분자" 조건을 추가했다(isTableSeparatorRow 헬퍼). 이로써 데이터 행 오인이 구조적으로 불가능해진다. 동일 취약점을 가진 stripPrTableLines도 같은 규칙으로 정렬(별도 구분자 검증 분기 제거, 헬퍼 공유).
PR 컬럼이 없는 표(sprint-163)는 헤더 탐지가 실패해 undefined 반환 → graceful하게 raw 표로 prose에 유지(변경 전과 동일, 회귀 없음).
구현
PR #321 (단일 작업 브랜치 fix/sprint-184-impl-h2-partial-matcher, 1 commit → squash, 소스 2파일 +36/-10)
5640326fix — section-aliases.ts에IMPLEMENTATION_RE+ resolveCanonical fallback 1줄, parser.ts에isTableSeparatorRow헬퍼 + parsePrTable/stripPrTableLines 헤더 탐지 보강.
핵심 변경 (section-aliases.ts):
const IMPLEMENTATION_RE = /^(?:구현|implementation|execution)(?:\s|$|\()/i;
// resolveCanonical fallback:
if (IMPLEMENTATION_RE.test(stripped)) return 'implementation';
핵심 변경 (parser.ts):
const headerIdx = lines.findIndex(
(l, i) =>
/\|/.test(l) &&
/pr|pull\s*request/i.test(l) &&
i + 1 < lines.length &&
isTableSeparatorRow(lines[i + 1]),
);
Critic 사이클
codex review --base main 1라운드.
- R1: 0건 통과 — "변경은 PR 표 탐지를 GFM 구분자 직후를 요구하도록 강화하고 implementation 헤딩 aliasing을 확장하되 명백한 회귀를 도입하지 않는다. diff에서 조치 가능한 정확성 이슈를 찾지 못함." 머지 가능.
검증
브라우저 end-to-end (blog 빌드 후 정적 서버 + 실제 DOM 확인)
- sprint-157 (KR):
구현 PhasePhaseStrip 카드 10개 렌더(P1~UX추가, PR 링크·담당·요약 포함) + implementation 섹션 원시 PR 표 strip(Mermaid작업 흐름만 잔존) ✓ - sprint-157 (EN):
Implementation Phases카드 10개 + 표 strip ✓ - sprint-163: garbage 카드(A/B 누락·C~R9 오추출) 완전 제거 →
구현 Phase섹션 미생성, 원시 표 graceful 유지 ✓
document 단위 시뮬레이션 (old vs new)
- KR sprints 11개(153/154/155/156/157/158/159/160/161/164/165) + EN sprints 11개 = 22 ADR이 Phase 카드 신규 획득(0→N).
- 전 corpus 회귀 0건(N→0/N→M 변화 없음), permanent ADR 무영향(PR 컬럼 없는
구현 작업등은 graceful).
로컬
tsc --noEmit0 errors ·npm run build244 페이지.- ADR/blog 게이트 7종 무회귀: adr-conversion(fixture 10/10) · doc-refs(329 files 0 broken) · en-coverage(131/131) · index-count(8/1/122) · i18n(max 2.19%<8%) · blog-crosscheck(0 위반) · adr-links(KR 1616 / EN 1614, 0 broken).
CI
- 작업 PR #321 전체 checks green(Build Blog 포함 — blog 소스 변경 트리거), mergeStateStatus CLEAN. ADR PR은
sprints/**트리거로 green.
결과
- 머지: origin/main →
c38a54f(PR #321 squash merge, 작업 브랜치 삭제). - 순변경:
blog/src/lib/adr/section-aliases.ts(+16) +blog/src/lib/adr/parser.ts(+20/-10). 신규 파일 없음. - ADR sprint-184(KR+EN) + README sprint ADR count 122→123·범위 62~184 (별도 ADR PR).
신규 패턴
- 계획이 브라우저 검증으로 scope 재정의됨: "implementation H2 partial matcher" 단일 변경으로 시작했으나, 1차 빌드의 브라우저 DOM 검증에서 partial matcher가 sprint-163의 기존
parsePrTable취약점(데이터 행 헤더 오인)을 노출시켜 garbage 카드를 만드는 것을 발견 →parsePrTable보강을 scope에 추가. 정적 시뮬레이션("PR 컬럼 있는 표는 렌더되어야 함")이 가리킨 것과 실제 렌더(garbage 카드)가 달랐고, 실제 DOM이 판정자였다(Sprint 183 "브라우저가 결정자" 계승). - 구조적 불변식으로 휴리스틱 오인 차단: "임의의 PR 포함 라인 = 헤더" 휴리스틱은 데이터 행을 오인했다. GFM의 "헤더는 구분자 직전 라인" 불변식을 탐지 조건에 넣으면 오인이 "운"이 아니라 "발생 불가"가 된다(Sprint 183 "정확한 패턴으로 입력 한정" 계승).