Files
kebab/docs/superpowers/plans/2026-05-26-normalize-absorption-plan.md
altair823 710945c4b0 refactor(parse-md): absorb kebab-normalize + kebab-parse-types — 24 → 22 crates + §3.7b 재작성
design §3.7b 의 thin layer (ParsedBlock 류) 가 4 parser 중 1개 (markdown) 만 lift 를
경유하는 현실 — fan-in/fan-out 모두 1 → layer 의미 잃음. kebab-normalize (1097 LOC)
+ kebab-parse-types (98 LOC) 둘을 kebab-parse-md 로 흡수.

설계: docs/superpowers/specs/2026-05-26-normalize-absorption-spec.md
플랜: docs/superpowers/plans/2026-05-26-normalize-absorption-plan.md
HOTFIXES: tasks/HOTFIXES.md 의 2026-05-26 entry (design deviation)

- 5 사용 type + 3 forward-declared struct → kebab-parse-md::types module 의 pub explicit re-export.
- build_canonical_document + derive_title + warning_agent → kebab-parse-md::normalize module.
- 4 hard-coded agent literal (lib.rs:122/128/134/153) + warning_agent body return + tracing target literal 모두 보존 — stage label 일관성.
- kebab-app callsite (lib.rs:51 use + :1119 context string) + Cargo.toml 의 2 dep (regular + dead) 제거.
- kebab-chunk + kebab-store-sqlite 의 [dev-dependencies] kebab-normalize → 제거 (kebab-parse-md 로 갈음). 통합 test source 의 use shift.
- test file 이동 (kebab-normalize/tests/normalize_snapshot.rs → kebab-parse-md/tests/).
- workspace Cargo.toml: Hunk (a) members 2 entry 삭제 + Hunk (b) version 0.18.0 → 0.19.0 (frozen contract 변경).
- design §3.7b 4-단락 재작성 (원래 intent 보존 + 현재 상태 + 보존된 surface + future re-extraction trigger).
- design §8 graph 갱신 (3 edge 제거 + 2 forbidden bullet 의미 갱신 + commentary).
- ARCHITECTURE.md crate graph + directory tree mechanical 갱신.
- tasks/INDEX.md L169 closure mention + "Future work / deferred" 섹션 신설 (image/pdf normalize integration entry).
- tasks/HOTFIXES.md 신규 entry (4-block — design deviation Symptom).
- HANDOFF.md cross-link 한 줄.
- 3 dead struct (ParsedImageRegion / ParsedPdfPage / ParsedAudioSegment) 는 보존 — v0.20+ image/pdf normalize integration 의 future surface (spec §11).

Wire / surface impact: 0건. CLI / TUI / MCP / --json 출력 / config / XDG path /
parser_version 모두 unchanged. wire-invisible provenance.events[].agent + tracing target
literal "kb-normalize" 도 보존 — old DB row 와 new DB row 의 audit log 일관성.

Verification: cargo test --workspace --no-fail-fast -j 1 → 1313 passed / 0 failed (172 result blocks).
cargo clippy --workspace --all-targets -j 1 -- -D warnings → 0 warning (5m 46s).
cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length' = 22.
cargo tree -p kebab-app --depth 2 | grep -E "kebab_(parse_types|normalize)" = 0 줄.
2026-05-26 15:00:59 +00:00

886 lines
65 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
status: open
target_version: 0.19.0
spec: docs/superpowers/specs/2026-05-26-normalize-absorption-spec.md
contract_sections: ["§3.7b", "§8"]
related_specs:
- docs/superpowers/specs/2026-04-27-kebab-final-form-design.md
- docs/superpowers/specs/2026-05-26-source-fs-dep-lightening-spec.md
sibling_plan: docs/superpowers/plans/2026-05-26-source-fs-dep-lightening-plan.md
---
# kebab-normalize + kebab-parse-types 흡수 — implementation plan
> spec round 3 APPROVE 직후의 plan (revision 2 — 10 step → **15 step** decompose). spec §3.1-§3.10 의 결정 + §3.5/§3.6 의 design contract diff + §3.7 (a)-(g) callsite migration + §3.9 의 HOTFIXES wording + §5.1-§5.11 의 verification gate 가 step 단위로 분산. design + doc 갱신 (구 Step 9) 을 4 step (§3.7b 재작성 / §8 graph / ARCHITECTURE + INDEX + HOTFIXES + HANDOFF / version 사이드카 verify) 으로 split — critic + verifier 의 closure granularity 향상.
## §0 Pre-flight + branch state
- **Branch**: `refactor/normalize-absorption` (현재 위치, main `d4395a3` 위에서 분기 완료).
- **Base SHA**: `d4395a3` (PR #185 sibling — source-fs dep lightening 머지 직후, v0.18.0 cut 완료 시점).
- **Working dir**: `/home/altair823/kebab`.
- **Env 강제** (CLAUDE.md disk-protection — `~/.claude/CLAUDE.md` 의 "Disk Layout — 루트 디스크 보호가 최우선" 룰):
- `export CARGO_TARGET_DIR=/build/out/cargo-target/target` — 본 plan 의 모든 cargo 명령에 적용. `target/` 가 repo root 아래에 생성되지 않게 (16 GB RAM 머신 의 `/` 250 G 보호).
- `export TMPDIR=/build/cache/tmp` — 대용량 임시 파일 발생 시 보호.
- **Cargo build 직렬화** (CLAUDE.md "Build / test / lint" + MEMORY.md `feedback_serial_build_only.md`):
- 모든 cargo 명령 `-j 1` 강제. 18 integration-test binary 동시 link 시 OOM (linker SIGKILL).
- per-crate `cargo test -p <crate>``-j 1` 없어도 OK 이나 일관성을 위해 명시.
- cargo test / clippy / build 동시 background 실행 금지. 하나 끝난 후 다음.
- **`target/` clean policy** (CLAUDE.md 룰 + spec §5.10): full workspace test 직전 `cargo clean` 1회 (Step 15). 중간 step (Step 2-14) 에서는 per-crate build 만 — `cargo clean` 불필요 (incremental cache 활용).
- **HOTFIXES.md / HANDOFF.md / README.md 변경 0** (spec §7 명시) — HOTFIXES.md 와 HANDOFF.md 는 본 plan 의 Step 14 에서 추가 (README 는 변경 0).
- **4 frozen task spec (p1-2, p1-3, p1-4, p9-fb-07) 변경 0** + ~25 referencing task spec mechanical update 0 (spec §2 + §5.7).
- **wire schema 변경 0** (spec §1.9 verified, 16 wire schema 0 hit).
- **workspace `Cargo.toml` version bump 0.18.0 → 0.19.0** (Step 10 Hunk (b)).
## §1 Approach summary
Spec §3 의 핵심 sequencing — destination = `kebab-parse-md` (spec §3.1, Option A):
1. **신규 module 부터 작성** (Step 2-3) — `kebab-parse-md/src/types.rs` (parse-types 98 LOC 1:1 이식) + `kebab-parse-md/src/normalize.rs` (normalize 의 production fn body 이식, 4 hard-coded agent literal + tracing target literal 모두 보존).
2. **`lib.rs` 에 module + pub explicit re-export 등록** (Step 4) — 5 사용 type + 3 forward-declared struct + `build_canonical_document` / `derive_title` 의 surface 보존 (spec §3.3). explicit (glob 아님) — Q3 closure.
3. **`blocks.rs` + `frontmatter.rs` use 갱신** (Step 5) — `use kebab_parse_types::*``use crate::types::*`. 동일 crate 내 in-source ref shift.
4. **`kebab-app` callsite + dep cleanup** (Step 6-7, atomic 2 step) — `lib.rs:51` use statement + `lib.rs:1119` context string (Step 6) → `Cargo.toml` 의 2 dep 제거 (Step 7, `kebab-normalize` regular + `kebab-parse-types` dead regular — spec §3.10 incidental cleanup).
5. **dev-dep migration** (Step 8) — `kebab-chunk` + `kebab-store-sqlite``kebab-normalize` dev-dep 제거 (이미 `kebab-parse-md` dev-dep 보유). 통합 test source 의 `use kebab_normalize::*;``use kebab_parse_md::*;`.
6. **test file 이동 + 자기 참조 verify** (Step 9) — `kebab-normalize/tests/normalize_snapshot.rs``kebab-parse-md/tests/normalize_snapshot.rs`. 자기 참조 dev-dep declare 없는 cargo standard behavior verify.
7. **workspace `Cargo.toml` 갱신 — anchor step** (Step 10) — Hunk (a) `members` 의 2 entry 삭제 + Hunk (b) `workspace.package.version` 0.18.0 → 0.19.0 (NIT #N6 closure).
8. **`kebab-normalize/` + `kebab-parse-types/` 디렉토리 삭제** (Step 11) — `git rm -r`. workspace 가 22 crate 로 collapse.
9. **design §3.7b 재작성** (Step 12) — spec §3.5 의 4-단락 wording 으로 design contract 의 line 703-764 replace.
10. **design §8 graph 갱신** (Step 13) — spec §3.6 의 diff (3 edge 제거 + 2 forbidden bullet 의미 갱신 + commentary) 적용.
11. **ARCHITECTURE + INDEX + HOTFIXES + HANDOFF 갱신** (Step 14) — 4 doc 의 mechanical update. INDEX.md 의 "Future work / deferred" 섹션 신설 (현재 부재 — §11.7).
12. **workspace 회귀 + clean commit — closure** (Step 15) — `cargo clean` + 7 cargo gate + wire diff verify + 1 clean commit.
핵심 ordering invariant:
- **Step 2-3 < Step 4-5**: destination module file 생성 후 lib.rs re-export — 둘 동시 commit 시 cargo build green 보장.
- **Step 4-5 < Step 6-7**: in-crate ref shift 후 kebab-app callsite + dep — 외부 caller redirect 완료 후 dep 제거.
- **Step 6-7 < Step 8**: kebab-app 정합 후 dev-dep migration — order independent 이나 sequential gate 분리.
- **Step 8 < Step 9**: dev-dep cleanup 후 test file 이동 — git mv 가 git 의 add/remove 동시 commit 시 file content 보존.
- **Step 6-9 < Step 10**: 모든 caller redirect 후 workspace.members 삭제 — 중간 build 깨짐 방지.
- **Step 10 < Step 11**: workspace.members 제거 후 디렉토리 삭제 — stale path 회피.
- **Step 11 < Step 12-14**: production code 갱신 완료 후 design contract + doc 갱신 — reality ≡ contract.
- **Step 12-14 < Step 15**: 모든 doc 갱신 후 회귀 + commit — single clean commit 의 일관성.
## §2 Steps (15 steps)
### Step 1: Pre-flight baseline 측정 + env 확인
- **Files affected**: 변경 0 (측정 only).
- **Action**:
- `cd /home/altair823/kebab && git rev-parse HEAD``d4395a3` 또는 그 위 commit 확인 (refactor branch 의 base).
- env 확인: `echo $CARGO_TARGET_DIR``/build/out/cargo-target/target` 인지. 비어있으면 §0 의 export 적용.
- workspace baseline count 측정: `cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length'`**24** (현재 시점).
- dead `kebab-parse-types` regular dep verify (spec §3.10): `grep -n "kebab-parse-types" crates/kebab-app/Cargo.toml` → 1 hit (line 16 부근), `grep -rn "kebab_parse_types" crates/kebab-app/src/` → 0 hit.
- baseline test count 측정 + persist (MAJOR GAP3 closure — spec §5.1 의 1313 + α 의 unit 정합화: test *함수 수* sum, NOT *binary 수* 행수):
```bash
$ mkdir -p .omc/state
$ cargo test --workspace --no-fail-fast -j 1 2>&1 \
| awk '/^test result: ok\./ {for(i=1;i<=NF;i++) if($i=="passed;") sum += $(i-1)} END {print sum}' \
> .omc/state/normalize-absorption-baseline.txt
$ cat .omc/state/normalize-absorption-baseline.txt
1313 # 예상 (spec §5.1)
```
본 file 은 Step 15 의 numeric compare gate 의 source-of-truth. `.omc/state/` 는 git untracked (gitignore default 또는 `.omc/.gitignore`).
- **Exit gate (cargo cli — falsifiable / observable / idempotent / scope-correct)**:
- `cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length'` = **24** (observable, idempotent).
- `cargo build --workspace -j 1 2>&1 | tail -5` 의 마지막 라인 = `Finished` 또는 `Compiling` (현 시점 baseline green — falsifiable).
- **Spec 참조**: §5.1 (baseline), §3.10 (dead dep verify).
### Step 2: 신규 `crates/kebab-parse-md/src/types.rs` 생성 (98 LOC 1:1 이식)
- **Files affected**:
- `crates/kebab-parse-md/src/types.rs` (신규, ≈ 98 LOC, byte-identical 이식).
- **Action**:
- `cp crates/kebab-parse-types/src/lib.rs crates/kebab-parse-md/src/types.rs` 또는 Write tool 로 신규 생성.
- 본문 = `kebab-parse-types/src/lib.rs` 의 98 LOC 의 1:1 이식:
- 5 사용 type (`ParsedBlock` + `ParsedBlockKind` + `ParsedPayload` + `Warning` + `WarningKind`) 의 serde 표현 + variant 명 byte-identical 보존 (spec §2.2 의 non-goal — 의미 변경 0).
- 3 forward-declared struct (`ParsedImageRegion` + `ParsedPdfPage` + `ParsedAudioSegment`) 보존 (spec §3.3 + §11.5 의 future surface).
- module-level doc 의 §3.7b reference 보존 + 한 줄 추가: `//! v0.19.0 부터 kebab-parse-md 의 in-crate module (이전 별 crate kebab-parse-types — HOTFIXES.md 2026-05-26 참조).`
- **본 step 은 *추가* only** — 기존 `crates/kebab-parse-types/` 는 아직 alive. lib.rs 의 mod declare 가 없으므로 cargo 가 `types.rs` 무시 → build 깨지지 않음.
- **Exit gate**:
- `wc -l crates/kebab-parse-md/src/types.rs` ≈ 98 (≤ 105) — falsifiable.
- `grep -c "ParsedBlock\|ParsedBlockKind\|ParsedPayload\|Warning\|WarningKind\|ParsedImageRegion\|ParsedPdfPage\|ParsedAudioSegment" crates/kebab-parse-md/src/types.rs` ≥ 8 — 8 type 모두 등장.
- `cargo build -p kebab-parse-md -j 1 2>&1 | tail -3` 의 마지막 라인 = `Finished` (mod declare 없으므로 file 무시).
- **Spec 참조**: §3.2 (module placement), §3.3 (visibility), §11.5 (future surface 보존).
### Step 3: 신규 `crates/kebab-parse-md/src/normalize.rs` 생성 + `Cargo.toml` dep 갱신 (1097 LOC 이식 + literal 보존 + dep migration)
- **Files affected**:
- `crates/kebab-parse-md/src/normalize.rs` (신규, ≈ 1097 LOC, byte-identical 이식 + literal 보존 정책).
- `crates/kebab-parse-md/Cargo.toml` (dep 갱신 — CRITICAL #1 + GAP2 closure).
- **Action**:
- **(a) normalize.rs 생성** — `cp crates/kebab-normalize/src/lib.rs crates/kebab-parse-md/src/normalize.rs` 또는 Write 로 신규 생성.
- 본문 = `kebab-normalize/src/lib.rs` 의 1097 LOC 의 production fn + comment + cfg(test) unit tests 1:1 이식:
- `build_canonical_document` (signature `(asset: &RawAsset, metadata: Metadata, blocks: Vec<ParsedBlock>, parser_version: &ParserVersion, warnings: Vec<Warning>) -> Result<CanonicalDocument>`) 의 production body — spec §1.5 의 actual source byte-identical.
- `derive_title(frontmatter_title: &str, blocks: &[Block], file_stem: &str) -> String` — spec §1.5 의 `blocks: &[Block]` (lifted, NOT ParsedBlock) inline 주석 보존.
- `warning_agent(kind: &WarningKind) -> &'static str` 의 4 variant `"kb-parse-md"` 단일 return body 보존 (spec §3.7e + NEW MAJOR #N2 closure).
- **(b) literal 보존 (CRITICAL — spec §3.7 (e)(g) + §1.9 의 6-row trace table)**:
| line (post-port) | string | 보존? |
|---|---|---|
| `:109` (approx) | `target: "kebab-normalize"` (tracing::debug! literal) | ★ 보존 |
| `:122` (approx) | `"kb-source-fs"` (Discovered event agent) | 보존 |
| `:128` (approx) | `"kb-parse-md"` (Parsed event agent) | 보존 |
| `:134` (approx) | `"kb-normalize"` (Normalized event agent) | ★ 보존 |
| `:143` (approx) | `warning_agent(&w.kind).to_string()` (Warning event agent — 동적, "kb-parse-md" 단일 return) | 보존 |
| `:153` (approx) | `"kb-normalize"` (lift_warnings event agent) | ★ 보존 |
- **(c) in-source ref shift — 3 갈래** (actual `crates/kebab-normalize/src/lib.rs` grep 결과 기반):
```diff
-use kebab_parse_types::{ParsedBlock, ParsedPayload, Warning, WarningKind};
+use crate::types::{ParsedBlock, ParsedPayload, Warning, WarningKind};
```
추가로 cfg(test) mod tests 안의 **9 hit fully-qualified call** 도 갱신 (lib.rs:489, :498, :507, :516, :525, :818, :862, :1070 — 모두 `kebab_parse_types::ParsedBlockKind::*` 패턴):
```diff
-kind: kebab_parse_types::ParsedBlockKind::Paragraph,
+kind: crate::types::ParsedBlockKind::Paragraph,
```
`sed -i 's/kebab_parse_types::/crate::types::/g' crates/kebab-parse-md/src/normalize.rs` (or Edit `replace_all`) — 9 hit 모두 mechanical 변경.
- **(d) `pub use kebab_core::{id_for_block, id_for_doc}` 제거 + in-body call 의 unqualified import 보존** (CRITICAL #2 closure):
actual `lib.rs:33` 의 `pub use` 는 *re-export + 동시 current module scope import*. line 67 (`id_for_doc(...)`) + line 241 (`id_for_block(...)`) 의 unqualified call 이 `pub use` 의 scope import 에 의존. `pub use` 만 제거 시 unqualified call unresolved → compile error.
```diff
-pub use kebab_core::{id_for_block, id_for_doc};
+// (re-export 제거 — spec §3.3 R10 decision; production caller 0 verified.
+// in-body unqualified call 은 아래 use block 의 import 로 대체.)
```
그리고 같은 file 의 *기존* `use kebab_core::{...}` block 에 `id_for_block, id_for_doc` 추가 (실제 use block 의 정확한 위치는 actual `lib.rs` 의 `use kebab_core::{` block 검색 후 정정 — Block / Metadata / SourceSpan 등의 normal import 와 함께 묶음):
```diff
use kebab_core::{
Block, BlockId, ...,
+ id_for_block, id_for_doc, // ← pub use 제거 후 in-body unqualified call 보존 (line 67, 241)
...
};
```
- **(e) `crates/kebab-parse-md/Cargo.toml` dep 갱신** (CRITICAL #1 + GAP2 closure — spec §3.4):
```diff
[package]
name = "kebab-parse-md"
...
-description = "Markdown frontmatter and block parsing into kb-core::Metadata / kb-parse-types intermediates"
+description = "Markdown frontmatter + block parsing + canonical-document lift (absorbed kb-parse-types + kb-normalize, see HOTFIXES.md 2026-05-26)"
[dependencies]
kebab-core = { path = "../kebab-core" }
-kebab-parse-types = { path = "../kebab-parse-types" }
anyhow = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
time = { workspace = true }
tracing = { workspace = true }
+# 흡수된 kb-normalize 의 NFKC 의존 — actual kebab-normalize/src/lib.rs:31 의
+# `use unicode_normalization::UnicodeNormalization;` 이 normalize.rs 이식 시 동반.
+# 이미 kebab-app 도 사용 중인 0.1 major (version drift 0).
+unicode-normalization = "0.1"
pulldown-cmark = { version = "0.13", default-features = false }
...
```
- **본 step 도 *추가* only** — 기존 `crates/kebab-normalize/` 는 아직 alive. lib.rs 의 mod declare (Step 4) 가 없으므로 normalize.rs file 무시되어 build 영향 0. 단 (e) 의 Cargo.toml 갱신은 Step 5/6 의 use shift 직후 fully active.
- **Exit gate**:
- `wc -l crates/kebab-parse-md/src/normalize.rs` ≈ 1097 (≤ 1110).
- `grep -c 'target: "kebab-normalize"' crates/kebab-parse-md/src/normalize.rs` = 1 — tracing target literal 보존 (spec §3.7 (g) + R8 mitigation).
- production agent literal 의 정확한 검증 (MINOR GAP6 — production body 의 `agent: "kb-..."` 패턴 grep): `grep -cE 'agent:\s*"kb-(source-fs|parse-md|normalize)"\.to_string\(\)' crates/kebab-parse-md/src/normalize.rs` ≥ 3 — 3 hard-coded literal (Discovered + Parsed + Normalized) 의 production body emission. warning_agent body 의 4 `"kb-parse-md"` return + lift_warnings 의 `"kb-normalize"` 합쳐 production agent literal 5 hit 보장.
- `grep -c "use crate::types::" crates/kebab-parse-md/src/normalize.rs` ≥ 1 — in-source ref shift.
- `grep -c "kebab_parse_types::" crates/kebab-parse-md/src/normalize.rs` = 0 — fully-qualified 9 hit 모두 갱신 (MAJOR #5 closure 의 normalize.rs 쪽).
- `grep -c "pub use kebab_core::" crates/kebab-parse-md/src/normalize.rs` = 0 — re-export 제거 (spec §3.3 R10).
- `grep -E "use kebab_core::\{" crates/kebab-parse-md/src/normalize.rs | grep -c "id_for_block\|id_for_doc"` ≥ 1 — id_for_* unqualified import 보존 (CRITICAL #2 closure).
- `grep -c "kebab-parse-types" crates/kebab-parse-md/Cargo.toml` = 0 — Cargo.toml dep 제거.
- `grep -c "unicode-normalization" crates/kebab-parse-md/Cargo.toml` ≥ 1 — Cargo.toml dep 추가 (CRITICAL #1 + GAP2 closure).
- `cargo build -p kebab-parse-md -j 1` green (mod declare 없으므로 normalize.rs file 무시. Cargo.toml dep 만 active).
- **Spec 참조**: §3.2 (module placement), §3.3 (visibility), §3.4 (Cargo.toml dep diff), §3.7 (c)(d)(e)(g) (in-source ref + id_for_* + literal), §1.5 (signature), §1.9 (6-row trace), §3.3 R10 (id_for_*), CRITICAL #1 + #2 + NEW MAJOR #N1 + #N2 + MINOR GAP6 closure.
### Step 4: `crates/kebab-parse-md/src/lib.rs` 갱신 (module declare + pub explicit re-export)
- **Files affected**: `crates/kebab-parse-md/src/lib.rs`.
- **Action**:
- module-level doc 의 마지막에 한 줄 추가:
```rust
//! v0.19.0 부터 `types` + `normalize` module 은 in-crate 흡수
//! (`kebab-parse-types` + `kebab-normalize` 의 historical crate 가 본 crate 로
//! collapse — see HOTFIXES.md 2026-05-26).
```
- module declare + pub explicit re-export 추가 (spec §3.3 + §4.2 Q3 — glob 아님):
```rust
mod types;
mod normalize;
// Spec §3.3 의 surface 보존 정책 — explicit (NOT glob) 으로 future addition leak 방지.
pub use crate::types::{
// 5 사용 type
ParsedBlock, ParsedBlockKind, ParsedPayload,
Warning, WarningKind,
// 3 forward-declared struct (보존 — spec §3.3 + §11.5 future surface)
ParsedImageRegion, ParsedPdfPage, ParsedAudioSegment,
};
pub use crate::normalize::{build_canonical_document, derive_title};
```
- **Exit gate**:
- `grep -c "^mod types;\|^mod normalize;" crates/kebab-parse-md/src/lib.rs` = 2.
- `grep "pub use crate::types" crates/kebab-parse-md/src/lib.rs | grep -c "ParsedImageRegion\|ParsedPdfPage\|ParsedAudioSegment"` ≥ 1 — 3 forward-declared struct 의 explicit re-export 확인.
- `grep "pub use crate::normalize" crates/kebab-parse-md/src/lib.rs | grep -c "build_canonical_document\|derive_title"` ≥ 1.
- `cargo build -p kebab-parse-md -j 1` green — 모듈 활성화 후 cargo 가 types.rs + normalize.rs 컴파일.
- `cargo test -p kebab-parse-md -j 1` green — 이식된 normalize unit test (~700 LOC) 도 in-crate 통과.
- **Spec 참조**: §3.3 (visibility 정책), §4.2 Q3 (explicit vs glob — explicit 선택), §11.5 (future surface 보존).
### Step 5: `crates/kebab-parse-md/src/{blocks,frontmatter}.rs` + `tests/{blocks_snapshots,frontmatter_snapshots}.rs` ref shift
- **Files affected**:
- `crates/kebab-parse-md/src/blocks.rs` (4 hit — actual line 1, 25, 37, 1589 verified).
- `crates/kebab-parse-md/src/frontmatter.rs` (1+ hit — actual line 22 verified).
- `crates/kebab-parse-md/tests/blocks_snapshots.rs` (BLOCKER #1 — actual line 19 verified).
- `crates/kebab-parse-md/tests/frontmatter_snapshots.rs` (BLOCKER #1 — actual line 23 verified).
- **Action**:
- **(a) blocks.rs — file-wide replace (MAJOR #5 closure, 4 hit)**:
- line 1 doc comment: `//! Markdown body → flat \`Vec<kebab_parse_types::ParsedBlock>\` (§3.4 / §3.7b).` → `//! Markdown body → flat \`Vec<crate::types::ParsedBlock>\` (§3.4 / §3.7b).`
- line 25 doc-link: `[\`kebab_parse_types::ParsedPayload::ImageRef\`]` → `[\`crate::types::ParsedPayload::ImageRef\`]`
- line 37 use: `use kebab_parse_types::*;` (또는 explicit list) → `use crate::types::*;` (또는 explicit list 갱신)
- line 1589 production fully-qualified call: `kebab_parse_types::ParsedBlockKind::List` → `crate::types::ParsedBlockKind::List`
- Edit tool 의 `replace_all: true` 로 `kebab_parse_types::` → `crate::types::` 단일 치환 가능 (4 hit + 가능한 hidden hit 모두 mechanical 갱신).
- **(b) frontmatter.rs — 동일 패턴**:
```diff
-use kebab_parse_types::{Warning, WarningKind};
+use crate::types::{Warning, WarningKind};
```
`MalformedFrontmatter` variant 의 fully-qualified call 등 추가 hit 검색 (`grep -n "kebab_parse_types" crates/kebab-parse-md/src/frontmatter.rs` 으로 확인 후 `replace_all`).
- **(c) tests/blocks_snapshots.rs (BLOCKER #1 — integration test 는 `crate::` 사용 불가, `kebab_parse_md::*` 사용)**:
```diff
-use kebab_parse_types::{ParsedBlock, Warning};
+use kebab_parse_md::{ParsedBlock, Warning};
```
line 19 의 use 갱신. integration test 는 자기 crate `lib` 자동 link → `kebab_parse_md::*` re-export 활성화 (Step 4 의 lib.rs explicit re-export 의 8 type 중 4 type 활용).
- **(d) tests/frontmatter_snapshots.rs (BLOCKER #1)**:
```diff
-warnings: Vec<kebab_parse_types::Warning>,
+warnings: Vec<kebab_parse_md::Warning>,
```
line 23 의 fully-qualified type 갱신 (function signature param). 추가 hit 검색 (`grep -n "kebab_parse_types" crates/kebab-parse-md/tests/frontmatter_snapshots.rs`) 후 replace_all.
- **Exit gate**:
- `grep -rn "kebab_parse_types" crates/kebab-parse-md/` = **0 hit** (src/ + tests/ 모두 — verify cmd 의 src/ 한정 해제, BLOCKER #1 closure).
- `grep -c "crate::types::" crates/kebab-parse-md/src/blocks.rs` ≥ 4 (line 1/25/37/1589 갱신).
- `grep -c "kebab_parse_md::" crates/kebab-parse-md/tests/blocks_snapshots.rs crates/kebab-parse-md/tests/frontmatter_snapshots.rs` ≥ 2.
- `cargo build -p kebab-parse-md -j 1` green.
- `cargo test -p kebab-parse-md -j 1` green (integration test 의 fixture builder 가 self-link 통해 redirect).
- **Spec 참조**: §3.7 (c) callsite migration (in-source ref shift), BLOCKER #1 + MAJOR #5 closure.
### Step 6: `crates/kebab-app/src/lib.rs:51, :1119` callsite migration
- **Files affected**: `crates/kebab-app/src/lib.rs`.
- **Action**:
- **(a) line 51 use statement** (spec §3.7 (a)):
```diff
-use kebab_normalize::build_canonical_document;
+use kebab_parse_md::build_canonical_document;
```
- **(b) line 1119 context string** (spec §3.7 (b) + MAJOR #4 closure):
```diff
- .context("kb-normalize::build_canonical_document")?;
+ .context("kb-parse-md::build_canonical_document")?;
```
*주의*: `kb-parse-md::parse_frontmatter` (line 1091) + `kb-parse-md::parse_blocks` (line 1099) 의 context string 은 변경 없음 — byte-identical hunk 적용 금지 (MAJOR #4 의 closure 명시).
- **Exit gate**:
- `grep -n "kebab_normalize" crates/kebab-app/src/lib.rs` = 0 hit (use 와 의 자취 모두 사라짐).
- `grep -n "kb-normalize::" crates/kebab-app/src/lib.rs` = 0 hit (context string 갱신).
- `cargo build -p kebab-app -j 1` green — kebab-parse-md 의 re-export 가 alive 인 상태에서 redirect.
- `cargo test -p kebab-app -j 1` green.
- **Spec 참조**: §3.7 (a)(b), MAJOR #4 closure.
### Step 7: `crates/kebab-app/Cargo.toml` dep cleanup (2 dep 제거 — regular + dead incidental) — **closure pre-pivot 1**
- **Files affected**: `crates/kebab-app/Cargo.toml`.
- **Action** (spec §3.4 + §3.10):
MINOR #3 closure — hunk 적용 전 actual context 확인 (sed 으로 idempotent):
```bash
$ sed -n '11,20p' crates/kebab-app/Cargo.toml # context 의 actual line 확인
```
hunk:
```diff
kebab-source-fs = { path = "../kebab-source-fs" }
kebab-parse-md = { path = "../kebab-parse-md" }
-kebab-parse-types = { path = "../kebab-parse-types" }
-kebab-normalize = { path = "../kebab-normalize" }
kebab-chunk = { path = "../kebab-chunk" }
```
- `kebab-normalize` regular dep 제거 — Step 6 의 use shift 가 정합되어 더 이상 dep 불필요.
- `kebab-parse-types` regular dep 제거 — *dead dep* (spec §3.10 incidental cleanup — Step 1 의 verify 에서 `kebab_parse_types` source import 0 hit 검증 완료).
- **Exit gate**:
- `grep -E "kebab-normalize|kebab-parse-types" crates/kebab-app/Cargo.toml` = 0 hit.
- `cargo build -p kebab-app -j 1` green.
- `cargo tree -p kebab-app --depth 2 | grep -E "kebab_(parse_types|normalize)"` = 0 줄 — spec §5.2 의 anchor invariant.
- **Spec 참조**: §3.4 (Cargo.toml diff), §3.10 (incidental cleanup), §5.2 (anchor invariant).
### Step 8: `crates/kebab-chunk/Cargo.toml` + `crates/kebab-store-sqlite/Cargo.toml` dev-dep migration + 통합 test source `use` 갱신
- **Files affected**:
- `crates/kebab-chunk/Cargo.toml`.
- `crates/kebab-store-sqlite/Cargo.toml`.
- `crates/kebab-chunk/tests/*.rs` (use statement shift).
- `crates/kebab-store-sqlite/tests/*.rs` (use statement shift).
- **Action**:
- **(a) `crates/kebab-chunk/Cargo.toml`** — spec §3.4:
```diff
[dev-dependencies]
# kb-parse-md / kb-normalize / kb-parse-code are dev-only — used by the
# snapshot integration tests to build a CanonicalDocument from fixture files.
# Forbidden as regular deps per design §8 (chunker consumes CanonicalDocument
# from kb-core only); `cargo tree -p kb-chunk --depth 1` (default scope,
# excludes dev-deps) confirms this.
kebab-parse-md = { path = "../kebab-parse-md" }
kebab-parse-code = { path = "../kebab-parse-code" }
-kebab-normalize = { path = "../kebab-normalize" }
serde_json = { workspace = true }
time = { workspace = true }
```
그리고 doc comment 의 `kb-parse-md / kb-normalize / kb-parse-code` mention 갱신 — `kb-parse-md / kb-parse-code` (kb-normalize 흡수 명시).
- **(b) `crates/kebab-store-sqlite/Cargo.toml`** — spec §3.4:
```diff
kebab-parse-md = { path = "../kebab-parse-md" }
-kebab-normalize = { path = "../kebab-normalize" }
kebab-chunk = { path = "../kebab-chunk" }
```
- **(c) 통합 test source 의 use statement 갱신** — actual grep 결과 명시 (MAJOR #2 closure, 2 file:line):
- `crates/kebab-chunk/tests/long_section_snapshot.rs:21` — `use kebab_normalize::build_canonical_document;` → `use kebab_parse_md::build_canonical_document;`
- `crates/kebab-store-sqlite/tests/contract_roundtrip.rs:16` — `use kebab_normalize::build_canonical_document;` → `use kebab_parse_md::build_canonical_document;`
추가 hit 검색 (`grep -rn "kebab_normalize" crates/kebab-chunk/tests/ crates/kebab-store-sqlite/tests/`) — 본 시점 의 verified hit = 2건. 새로 발견 시 동일 패턴으로 갱신.
- **Exit gate**:
- `grep -l "kebab-normalize" crates/kebab-chunk/Cargo.toml crates/kebab-store-sqlite/Cargo.toml` = 0 line.
- `grep -rn "kebab_normalize" crates/kebab-chunk/tests/ crates/kebab-store-sqlite/tests/` = 0 hit.
- `cargo test -p kebab-chunk -j 1` green — snapshot integration test 의 fixture builder 가 destination 으로 redirect.
- `cargo test -p kebab-store-sqlite -j 1` green — contract round-trip test.
- **Spec 참조**: §3.4 (Cargo.toml diff), MAJOR #11 closure.
### Step 9: test file 이동 (`kebab-normalize/tests/` → `kebab-parse-md/tests/`) + 자기 참조 verify
- **Files affected** (MAJOR #3 closure — actual `find` 결과 명시):
- `crates/kebab-normalize/tests/normalize_snapshot.rs` → `crates/kebab-parse-md/tests/normalize_snapshot.rs`.
- actual `find crates/kebab-normalize/tests/ -type f` 결과 = **`normalize_snapshot.rs` 단일 file** (sibling snapshots/ 또는 fixtures/ 서브디렉토리 부재). 본 step 의 mv 대상 = 1 file only.
- **Action**:
- **(a) git mv 로 mechanical move**:
```bash
# 실 실행 전 사전 verify (idempotent + observable):
$ find crates/kebab-normalize/tests/ -type f
crates/kebab-normalize/tests/normalize_snapshot.rs
# 단일 file mv (sibling fixture 부재 확인됨):
$ git mv crates/kebab-normalize/tests/normalize_snapshot.rs crates/kebab-parse-md/tests/normalize_snapshot.rs
```
- **(b) test file 의 use statement 갱신**:
- 기존 `use kebab_normalize::*;` 또는 `use kebab_normalize::{build_canonical_document, derive_title};` → `use kebab_parse_md::{build_canonical_document, derive_title};` 또는 in-crate `crate::*` 사용.
- `use kebab_parse_md::*;` 가 이미 alive 이면 keep (자기 crate import 는 integration test 에서 cargo standard behavior 로 자동 link — spec §3.7 (f), R3 / Q4 closure).
- `kebab_parse_types::*` 의 import 도 `kebab_parse_md::*` re-export 로 갈음.
- **(c) 자기 참조 dev-dep declare 제거 verify**: 본 step 이후 `kebab-normalize/` 디렉토리 자체가 Step 11 에서 삭제되므로 그 안의 Cargo.toml 의 `kebab-parse-md = { path = "..." }` dev-dep 도 vanish. 새 destination `kebab-parse-md/Cargo.toml` 에 *자기 참조* dev-dep 을 add 하지 않음 — cargo standard behavior (integration test 가 lib 자동 link).
- **Exit gate**:
- `ls crates/kebab-parse-md/tests/normalize_snapshot.rs` 존재.
- `ls crates/kebab-normalize/tests/normalize_snapshot.rs 2>&1 | grep -c "No such"` = 1 (이동 완료).
- `cargo test -p kebab-parse-md --test normalize_snapshot -j 1` green — spec §3.7 (f) 의 cargo standard behavior verified (R3/Q4 closure).
- `grep "kebab-parse-md = " crates/kebab-parse-md/Cargo.toml` = 0 hit (자기 참조 add 안 했는지 verify).
- **Spec 참조**: §3.7 (f) (test file 이동 + cargo standard behavior), R3 (Q4 closure).
### Step 10: workspace `Cargo.toml` Hunk (a) members + Hunk (b) version — anchor step
- **Files affected**: `Cargo.toml` (workspace root).
- **Action** (spec §3.8 + NIT #N6 closure — 2 hunk 분리):
- **Hunk (a) — `[workspace] members` 의 2 entry 삭제**:
```diff
[workspace]
resolver = "3"
members = [
"crates/kebab-core",
- "crates/kebab-parse-types",
"crates/kebab-config",
"crates/kebab-source-fs",
"crates/kebab-parse-md",
- "crates/kebab-normalize",
"crates/kebab-chunk",
...
]
```
- **Hunk (b) — `[workspace.package] version` 1-line 변경**:
```diff
[workspace.package]
edition = "2024"
rust-version = "1.85"
license = "MIT OR Apache-2.0"
repository = "https://github.com/altair823/org/kebab"
-version = "0.18.0"
+version = "0.19.0" # frozen design contract (§3.7b 재작성) 변경 trigger — CLAUDE.md "Release / binary version bump"
```
- 두 hunk 는 sequential 또는 parallel 적용 가능 — line context 가 충분히 분리되어 있음 (NIT #N6 의 의도).
- **Exit gate**:
- `cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length'` = **22** (spec §5.2 의 robust 명령).
- `grep '^version' Cargo.toml | head -1` = `version = "0.19.0"`.
- `cargo build --workspace -j 1` **green** — Step 6-9 가 모든 dep path 참조 제거 완료한 상태이므로, members 에서 제외된 두 orphan 디렉토리 (`crates/kebab-normalize/` + `crates/kebab-parse-types/`) 는 cargo 가 silently ignore. Cargo.lock 의 `[[package]]` entry 는 Step 11 직후 first `cargo build` 에서 자동 cleanup (MAJOR #1 closure — self-contradictory wording 제거).
- **Spec 참조**: §3.8 (Hunk a + b), NIT #N6 closure, §5.2 (workspace count invariant).
### Step 11: `crates/kebab-normalize/` + `crates/kebab-parse-types/` 디렉토리 삭제
- **Files affected**:
- `crates/kebab-normalize/` (전체 디렉토리).
- `crates/kebab-parse-types/` (전체 디렉토리).
- **Action**:
- `git rm -r crates/kebab-normalize/`
- `git rm -r crates/kebab-parse-types/`
- `cargo build --workspace -j 1` 의 자동 Cargo.lock cleanup 으로 두 crate 의 `[[package]]` entry 사라짐 (Step 15 의 verification).
- **Exit gate**:
- `ls crates/kebab-normalize/ 2>&1 | grep -c "No such"` = 1.
- `ls crates/kebab-parse-types/ 2>&1 | grep -c "No such"` = 1.
- `ls -d crates/*/ | wc -l` = **22** (spec §5.2 의 secondary robust cmd).
- `cargo build --workspace -j 1` green — 모든 dep 정합.
- **Spec 참조**: §3.8 (디렉토리 삭제).
### Step 12: design §3.7b 4-단락 재작성 (`docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` line 703-764)
- **Files affected**: `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` (§3.7b 부분).
- **Action**:
- line 703-764 의 §3.7b 본문을 spec §3.5 의 wording 으로 **replace** (strike 아닌 재작성).
- 4-단락 구조:
1. **원래 의도** (v0.1~v0.18 머지 시점) — thin layer + medium-agnostic lift.
2. **현재 상태 (v0.19.0~)** — 흡수 근거 (4 parser 중 1개만 lift 경유, fan-in/fan-out 모두 1).
3. **보존된 surface** — 5 사용 type + 3 forward-declared struct → `kebab-parse-md` 의 `pub` re-export.
4. **future re-extraction trigger** — 3 조건 명시 (fan-in ≥ 2 회복, ParsedBlock 변종 emit, medium-agnostic lift 일반화).
- 의존 그래프 ascii 갱신 (post-absorb):
```text
kebab-core (도메인 모델 — Block, Chunk, SourceSpan, IDs, …)
kebab-parse-md (markdown 의 frontmatter + block + types + normalize, 모두 in-crate)
kebab-parse-pdf, kebab-parse-image, kebab-parse-code (자체 CanonicalDocument emit)
```
- **Exit gate**:
- `sed -n '703,770p' docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "원래 의도\|현재 상태\|보존된 surface\|future re-extraction"` ≥ 4 — 4 단락 모두 존재.
- `sed -n '703,770p' docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "thin layer\|fan-in"` ≥ 1 — historical intent 의 wording 보존.
- `git diff main..HEAD -- docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | head -100` 의 hunk 가 line 703-764 만 touch (spec §5.6).
- **Spec 참조**: §3.5 (4-단락 wording), §5.6 (design doc 갱신 검증).
### Step 13: design §8 graph 갱신 (line 1457-1491) — 3 edge 제거 + 2 forbidden bullet 의미 갱신
- **Files affected**: `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` (§8 부분).
- **Action** (spec §3.6):
- **graph diff** (3 edge 제거):
```diff
- ├─> kebab-parse-md / kebab-parse-pdf / kebab-parse-image / kebab-parse-audio
- │ └─> kebab-parse-types (parser intermediate)
+ ├─> kebab-parse-md
+ │ (post-v0.19.0: absorbed kebab-parse-types + kebab-normalize — §3.7b)
+ ├─> kebab-parse-pdf / kebab-parse-image (self-emit CanonicalDocument)
├─> kebab-parse-code
│ └─> kebab-core (domain types only — NO store/embed/llm/rag/UI)
- ├─> kebab-normalize
- │ └─> kebab-parse-types
├─> kebab-chunk
```
- **commentary 갱신** (§3.7b reference 의 thin layer wording 폐기):
```diff
-`kebab-parse-types` 는 `kebab-core` 와 parsers/normalize 사이의 thin layer (§3.7b 참조).
+`kebab-parse-md` 는 v0.19.0 부터 `kebab-parse-types` (parser intermediate types) 와 `kebab-normalize` (CanonicalDocument lift) 를 흡수한다 (§3.7b 참조). 4 parser 중 markdown 한 갈래만 lift 를 경유하므로 thin layer 의 가치가 의미를 잃었다. 보존된 5 사용 type + 3 forward-declared struct 의 surface 는 `kebab-parse-md` 의 `pub` re-export 로 backward-compat.
```
- **forbidden bullet 갱신**:
```diff
- UI → store/llm/parse 직접 의존 ✗
- parse-* → store/llm/embed ✗
-- parse-* → kebab-normalize ✗ (단방향: parsers → kebab-parse-types ← normalize)
+- parse-* (pdf/image/code) → kebab-parse-md ✗ (parser 끼리 cross-import 금지 — markdown 의 lift 가 다른 parser 에 노출되면 안 됨)
- chunk → llm/embed ✗
-- normalize → store / parse-* ✗
-- kebab-parse-types → 어떤 parser/normalize/store/llm/embed/search/rag/ui ✗ (`kebab-core` 만 의존)
- 다른 store 와 cross-write ✗
```
*주의*: `kebab-parse-md → store / llm / embed ✗` 룰은 *추가 안 함* (MAJOR #5 closure — 기존 `parse-* → store/llm/embed ✗` 가 흡수된 lift 까지 자동 포함). 중복 룰 회피.
- **Exit gate**:
- `sed -n '1457,1495p' docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "kebab-parse-types"` ≤ 1 — 본문 commentary 의 historical reference 만 보존 (또는 0).
- `sed -n '1457,1495p' docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "kebab-normalize"` ≤ 1 — 동상.
- `sed -n '1457,1495p' docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "parse-\\* (pdf/image/code) → kebab-parse-md ✗"` = 1 — 신규 forbidden bullet 존재.
- `git diff main..HEAD -- docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` 의 hunk 가 §3.7b (Step 12) + §8 (Step 13) 의 2 section 만 touch — MINOR GAP7 closure 의 truncate-free numeric verify:
- `git diff main..HEAD -- docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -c "^@@"` = **2** (정확히 2 hunk).
- `git diff main..HEAD -- docs/superpowers/specs/2026-04-27-kebab-final-form-design.md | grep -cE "^\+\+\+|^---"` = **2** (file header — 1 file × 2 = 2).
- **Spec 참조**: §3.6 (§8 graph diff), MAJOR #5 closure (중복 룰 회피).
### Step 14: ARCHITECTURE.md + tasks/INDEX.md (L169 closure + Future work 신설) + HOTFIXES.md (4-block) + HANDOFF.md (cross-link)
- **Files affected**:
- `docs/ARCHITECTURE.md` (crate graph + directory tree).
- `tasks/INDEX.md` (L169 closure mention + Future work 섹션 신설).
- `tasks/HOTFIXES.md` (신규 entry — 4-block).
- `HANDOFF.md` (1 줄 cross-link).
- **Action**:
- **(a) `docs/ARCHITECTURE.md` 갱신** — spec §5.8:
- crate graph 의 entries 24 → 22 — `kebab-parse-types` + `kebab-normalize` 절 삭제.
- directory tree 의 `crates/kebab-parse-types/` + `crates/kebab-normalize/` 줄 삭제.
- 흡수 mention 한 줄 추가 (또는 §3.7b strike 의 cross-link).
- **(b) `tasks/INDEX.md` L169 closure** — spec §5.8:
```diff
- - **PR #181 chore: ... system-architect 의 component-level review 결론 = pre-cut nothing, all v0.18.1+ defer (kebab-normalize 흡수, Extractor dispatch unification, kebab-source-fs dep lightening 등).
+ - **PR #181 chore: ... system-architect 의 component-level review 결론 = pre-cut nothing, all v0.18.1+ defer (kebab-normalize 흡수 — v0.19.0 closure, see HOTFIXES.md 2026-05-26; Extractor dispatch unification; kebab-source-fs dep lightening 등).
```
- **(c) `tasks/INDEX.md` "Future work / deferred" 섹션 신설** — spec §11.7 + verified INDEX.md 에 현재 부재:
- 위치: "## Post-merge 핫픽스" 와 "## 모든 task 공통 규약" 사이 (신설).
- 본문:
```markdown
## Future work / deferred
- v0.20+ image/pdf normalize integration — design §3.7b intent 미구현 (3 dead struct 보존). PR #186 (normalize-absorption) 의 spec §11 참조.
```
- **(d) `tasks/HOTFIXES.md` 신규 entry** — spec §3.9 의 4-block 변형 그대로 (Symptom + Root cause + Action + Amends with inline Wire/surface impact). MAJOR #4 closure — anchor 검증 generic 화:
```bash
# insertion point 의 generic verify (hard-coded entry 인용 회피):
$ head -50 tasks/HOTFIXES.md # frontmatter + heading + first entry 위치 확인
$ FIRST_ENTRY_LINE=$(grep -n "^## 20" tasks/HOTFIXES.md | head -1 | cut -d: -f1)
$ echo "insertion point = line ${FIRST_ENTRY_LINE} 의 *위* (chronological reverse — newest top)"
```
본 시점 측정 결과 = line 17 (`## 2026-05-26 — S3 NLI unavailable`). 본 PR 의 entry 는 그 *위* 에 insert — 같은 2026-05-26 date 이지만 본 entry 는 source-fs sub-item 1 (PR #185) 의 sibling 인 sub-item 2 closure 라 chronologically later. Action 라인에 spec §11 cross-link 한 줄 포함 ("3 dead struct... 보존 — v0.20+ image/pdf normalize integration 의 future surface (spec §11 참조)").
- **(e) `HANDOFF.md` cross-link 한 줄** — `## 머지 후 발견된 버그 / 결정` 섹션 의 가장 최근 entry 위:
```markdown
- 2026-05-26 kebab-normalize + kebab-parse-types 흡수 (24 → 22 crates, design §3.7b 재작성). v0.19.0 cut. [HOTFIXES.md](tasks/HOTFIXES.md#2026-05-26--design-deviation--kebab-normalize--kebab-parse-types-흡수-24--22-crates).
```
- **Exit gate**:
- `grep -c "Future work\|Future Work" tasks/INDEX.md` ≥ 1 — 신설 섹션 존재.
- `grep -c "2026-05-26 — design deviation — kebab-normalize" tasks/HOTFIXES.md` = 1 — entry 추가.
- `grep -c "image/pdf normalize integration" tasks/INDEX.md tasks/HOTFIXES.md` ≥ 2 — 두 doc 모두 cross-link.
- `grep -c "2026-05-26 kebab-normalize" HANDOFF.md` = 1.
- `grep -c "kebab-parse-types\|kebab-normalize" docs/ARCHITECTURE.md` ≤ 1 — historical mention 만 보존 (또는 0).
- 4 frozen task spec (p1-2, p1-3, p1-4, p9-fb-07) `git diff main..HEAD` 의 path 에 0 hit.
- **Spec 참조**: §3.9 (HOTFIXES 4-block), §5.8 (ARCHITECTURE + INDEX), §11.7 (Future work 섹션 신설), §5.7 (frozen task spec invariant).
### Step 15: workspace 회귀 + 7 cargo gate + clean commit — closure
- **Files affected**: commit 만 (production code touch 0).
- **Action**:
- **(a) `cargo clean`** — spec §5.10 (16 GB RAM pressure 회피).
- **(b) full workspace test + numeric net-delta verify** (GAP4 closure — Step 1 의 baseline 과 numeric compare):
```bash
$ POST_COUNT=$(cargo test --workspace --no-fail-fast -j 1 2>&1 \
| awk '/^test result: ok\./ {for(i=1;i<=NF;i++) if($i=="passed;") sum += $(i-1)} END {print sum}')
$ echo "POST_COUNT=$POST_COUNT"
$ diff <(echo "$POST_COUNT") .omc/state/normalize-absorption-baseline.txt \
&& echo "✓ net delta = 0 (spec §5.1)" \
|| { echo "✗ net delta != 0 — REQUIRES INVESTIGATION"; exit 1; }
```
spec §5.1 의 expected net delta = 0 (1:1 lift, signature 무변, test 의미 무변 — Step 9 의 file 이동도 in-crate test 로 collapse 이므로 함수 수 보존). 만약 +N intentional addition 시 (예: NEW MAJOR #N1 + #N2 의 literal 보존 regression pin 신규) plan §1 approach summary 에 명시 + diff 의 numeric tolerance update.
- **(c) clippy gate** — `cargo clippy --workspace --all-targets -- -D warnings` 0 warning (spec §5.2).
- **(d) cargo deny** — `cargo deny check` 0 error 0 warning (spec §5.9).
- **(e) 22 crate workspace verify** — `cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length'` = 22 (spec §5.2 + §5.8).
- **(f) Cargo.lock 검증** — spec §5.11:
```bash
$ grep '^name = "kebab-normalize"\|^name = "kebab-parse-types"' Cargo.lock
(0 hit)
$ awk '/^\[\[package\]\]/,/^$/{if(/name = "kebab-parse-md"/)f=1; if(f) print; if(/^$/ && f){f=0; print "---"}}' Cargo.lock | grep "unicode-normalization"
unicode-normalization
```
- **(g) dep tree invariant** — `cargo tree -p kebab-app --depth 2 | grep -E "kebab_(parse_types|normalize)"` = 0 줄.
- **(h) wire schema diff = 0** — `git diff main..HEAD -- docs/wire-schema/v1/ | wc -l` = 0 (spec §5.4).
- **(i) clean commit**:
```bash
git add -A
git status # verify
git commit -m "$(cat <<'EOF'
refactor(parse-md): absorb kebab-normalize + kebab-parse-types — 24 → 22 crates + §3.7b 재작성
design §3.7b 의 thin layer (ParsedBlock 류) 가 4 parser 중 1개 (markdown) 만 lift 를
경유하는 현실 — fan-in/fan-out 모두 1 → layer 의미 잃음. kebab-normalize (1097 LOC)
+ kebab-parse-types (98 LOC) 둘을 kebab-parse-md 로 흡수.
설계: docs/superpowers/specs/2026-05-26-normalize-absorption-spec.md
플랜: docs/superpowers/plans/2026-05-26-normalize-absorption-plan.md
HOTFIXES: tasks/HOTFIXES.md 의 2026-05-26 entry (design deviation)
- 5 사용 type + 3 forward-declared struct → kebab-parse-md::types module 의 pub explicit re-export.
- build_canonical_document + derive_title + warning_agent → kebab-parse-md::normalize module.
- 4 hard-coded agent literal (lib.rs:122/128/134/153) + warning_agent body return + tracing target literal 모두 보존 — stage label 일관성.
- kebab-app callsite (lib.rs:51 use + :1119 context string) + Cargo.toml 의 2 dep (regular + dead) 제거.
- kebab-chunk + kebab-store-sqlite 의 [dev-dependencies] kebab-normalize → 제거 (kebab-parse-md 로 갈음). 통합 test source의 use shift.
- test file 이동 (kebab-normalize/tests/normalize_snapshot.rs → kebab-parse-md/tests/).
- workspace Cargo.toml: Hunk (a) members 2 entry 삭제 + Hunk (b) version 0.18.0 → 0.19.0 (frozen contract 변경).
- design §3.7b 4-단락 재작성 (원래 intent 보존 + 현재 상태 + 보존된 surface + future re-extraction trigger).
- design §8 graph 갱신 (3 edge 제거 + 2 forbidden bullet 의미 갱신 + commentary).
- ARCHITECTURE.md crate graph + directory tree mechanical 갱신.
- tasks/INDEX.md L169 closure mention + "Future work / deferred" 섹션 신설 (image/pdf normalize integration entry).
- tasks/HOTFIXES.md 신규 entry (4-block — design deviation Symptom).
- HANDOFF.md cross-link 한 줄.
- 3 dead struct (ParsedImageRegion / ParsedPdfPage / ParsedAudioSegment) 는 보존 — v0.20+ image/pdf normalize integration 의 future surface (spec §11).
Wire / surface impact: 0건. CLI / TUI / MCP / --json 출력 / config / XDG path /
parser_version 모두 unchanged. wire-invisible provenance.events[].agent + tracing target
literal "kb-normalize" 도 보존 — old DB row 와 new DB row 의 audit log 일관성.
Verification: cargo test --workspace --no-fail-fast -j 1 green / cargo clippy --workspace
--all-targets -- -D warnings 0 warning / cargo deny check 0 error / cargo metadata ...
workspace_members | length = 22 / cargo tree -p kebab-app | grep kebab_parse_types
+ kebab_normalize = 0 줄.
EOF
)"
```
- **Exit gate**:
- 모든 cargo gate green (a-h).
- `git log --oneline -1` = 위 commit message 의 first line.
- `git status` = clean (untracked file 0).
- **Spec 참조**: §5.1-§5.11 (모든 verification gate).
## §3 Step dependency graph
```text
Step 1 (Pre-flight baseline)
Step 2 (types.rs 신설) ──┐
│ │ parallel OK (둘 다 in-crate, lib.rs mod declare 아직 없음)
▼ │
Step 3 (normalize.rs 신설 + literal 보존) ◄─┘
Step 4 (lib.rs mod + pub explicit re-export)
Step 5 (blocks.rs + frontmatter.rs use shift)
Step 6 (kebab-app callsite lib.rs:51 + :1119)
Step 7 (kebab-app Cargo.toml dep cleanup) — anchor 1
Step 8 (kebab-chunk + kebab-store-sqlite dev-dep migration)
Step 9 (test file 이동 + 자기 참조 verify)
Step 10 (workspace Cargo.toml Hunk a + b) — anchor 2
Step 11 (kebab-normalize/ + kebab-parse-types/ 디렉토리 삭제)
Step 12 (design §3.7b 4-단락 재작성)
Step 13 (design §8 graph 3 edge 제거 + 2 forbidden bullet 갱신)
Step 14 (ARCHITECTURE + INDEX + HOTFIXES + HANDOFF)
Step 15 (회귀 + 7 cargo gate + clean commit) — closure
```
핵심 invariant:
- **Step 2 + 3 < Step 4**: file 생성 후 lib.rs mod declare — 동시 commit 시 cargo 자동 link.
- **Step 4-5 < Step 6**: 동일 crate 내 ref shift 후 외부 caller redirect.
- **Step 6 < Step 7**: callsite migration 후 dep 제거 — kebab-app build green 보장.
- **Step 7 < Step 8**: kebab-app 정합 후 dev-dep migration (sequential gate 분리, 자체 dep 영향 0).
- **Step 8 < Step 9**: dev-dep cleanup 후 test file 이동.
- **Step 6-9 < Step 10**: anchor 1 (kebab-app) + dev-dep + test file 모두 정합 후 anchor 2 (workspace.members).
- **Step 10 < Step 11**: workspace.members 제거 후 디렉토리 삭제 — stale path 회피.
- **Step 11 < Step 12-14**: production code 갱신 후 design + doc 갱신 — reality ≡ contract.
- **Step 12 < Step 13**: §3.7b 재작성 후 §8 graph 갱신 (§8 commentary 가 §3.7b 인용).
- **Step 13 < Step 14**: design 갱신 후 ARCHITECTURE + INDEX + HOTFIXES + HANDOFF 갱신 — design 이 source-of-truth, doc 들이 그 mirror.
- **Step 14 < Step 15**: 모든 file change 후 commit.
## §4 Verification gate (acceptance)
### §4.1 Step 별 verify (per-step exit gate)
각 Step 의 "Exit gate" 항목이 step-local gate. 핵심 anchor:
- **Step 4** (lib.rs re-export): destination surface alive — `cargo build -p kebab-parse-md` green.
- **Step 7** (kebab-app dep cleanup): anchor 1 — `cargo tree -p kebab-app | grep ...` = 0 줄.
- **Step 10** (workspace.members): anchor 2 — `cargo metadata ... | jq '.workspace_members | length'` = 22.
- **Step 11** (디렉토리 삭제): final invariant — `ls -d crates/*/ | wc -l` = 22 + `cargo build --workspace` green.
- **Step 15** (closure): 7 cargo gate (a-h) + wire diff 0 + clean commit.
### §4.2 Workspace 회귀 (spec §5.1, §5.2) — Step 15 시점
```bash
$ cd /home/altair823/kebab && export CARGO_TARGET_DIR=/build/out/cargo-target/target
$ cargo clean
$ cargo test --workspace --no-fail-fast -j 1 # baseline ± 작은 변동 (net delta = 0 또는 +N intentional)
$ cargo clippy --workspace --all-targets -- -D warnings # 0 warning
$ cargo deny check # 0 error 0 warning
$ cargo metadata --no-deps --format-version 1 | jq '.workspace_members | length' # = 22
$ ls -d crates/*/ | wc -l # = 22 (secondary)
$ cargo tree -p kebab-app --depth 2 | grep -E "kebab_(parse_types|normalize)" # 0 line
$ grep -rn "kebab_normalize\|kebab_parse_types" crates/kebab-app/src/ # 0 hit
$ grep -l "kebab-normalize\|kebab-parse-types" crates/*/Cargo.toml # 0 line
$ grep '^name = "kebab-normalize"\|^name = "kebab-parse-types"' Cargo.lock # 0 hit
```
### §4.3 Wire schema 회귀 (spec §5.4)
```bash
$ git diff main..HEAD -- docs/wire-schema/v1/ | wc -l # = 0
```
### §4.4 4 frozen task spec + ~25 referencing task spec frozen (spec §5.7)
```bash
$ git diff main..HEAD --name-only | grep -E "tasks/p1/p1-(2|3|4)|tasks/p9/p9-fb-07" # 0 line
$ git diff main..HEAD --name-only | grep "^tasks/p" | wc -l # 0 (모든 task spec mechanical update 0)
```
### §4.5 SMOKE 회귀 (spec §5.5) — informational only
`docs/SMOKE.md` 가 정의한 isolated TempDir KB pipeline 의 ingest + search + ask 가 흡수 전후 byte-identical wire 출력. *informational only* — acceptance gate 아님 (production code 의 lift 로직 byte-identical 이므로 SMOKE 결과 자동 일관).
### §4.6 Literal 보존 verify (spec §3.7 (e)(g) + NEW MAJOR #N1 + #N2)
Step 3 + Step 15 에서 두 번 verify:
```bash
$ grep -c 'target: "kebab-normalize"' crates/kebab-parse-md/src/normalize.rs # = 1 (tracing target literal)
$ grep -E '"kb-(source-fs|parse-md|normalize)"' crates/kebab-parse-md/src/normalize.rs | wc -l # >= 5 (4 hard-coded agent literal + warning_agent body return)
```
## §5 Commit strategy
**single clean commit** — sibling plan (PR #185) 의 패턴. 본 PR 의 모든 change 가 atomic refactor 이므로 split 의 의미 없음.
- Step 1-14 의 file edit 을 모두 staging 후 Step 15 의 commit message 로 single commit.
- 중간 step 의 *작업 progress* 는 git stash 없이 working tree 에 누적 (cargo build/test green 유지 시 — exit gate 가 step 별 정합 보장).
- Step 10-11 사이에 cargo build *fail* 또는 *workspace 무시* 발생 시 — expected behavior (§2 의 Step 10 exit gate 명시).
- 만약 Step 11 의 디렉토리 삭제 후 unexpected build error 발생 시 — `git reset --hard HEAD` 으로 모든 working change drop 하고 plan 재진입 (sibling plan §6.1 와 동일 패턴).
Commit message 의 구조 (Step 15 의 (i) 참조):
- first line: `refactor(parse-md): absorb kebab-normalize + kebab-parse-types — 24 → 22 crates + §3.7b 재작성`
- 본문: 14 bullet (deliverable + wire/surface impact + verification) + Co-Authored-By 제거 (사용자 commit pattern 와 일관).
## §6 Risks + mitigation
### §6.1 중간 단계 cargo build 깨짐 (step ordering 깨짐)
**위험**: Step 6 (kebab-app callsite) 가 Step 2-4 (destination 생성) 보다 먼저 진행되면 `use kebab_parse_md::build_canonical_document;` 의 destination surface 가 alive 안 됨 → cargo build fail.
**Mitigation**: §3 step dependency graph 의 ordering invariant 명시. Step 별 exit gate 가 cargo build green 보장 — gate 통과 후 다음 step.
### §6.2 Step 10 anchor 후 build fail (workspace.members 미정합)
**위험**: Step 10 의 Hunk (a) 만 적용하고 Step 11 디렉토리 삭제 안 하면 cargo 가 `crates/kebab-normalize/Cargo.toml` 의 path 를 lingering 으로 인식 가능 (단 members 에 없으므로 silently skip 예상).
**Mitigation**: Step 10 의 exit gate 가 "build *fail* 예상 또는 *workspace 무시*" 명시 — *expected* behavior. Step 11 즉시 진행으로 정합.
### §6.3 자기 참조 dev-dep 의 cargo behavior 미검증
**위험**: Step 9 의 `crates/kebab-parse-md/tests/normalize_snapshot.rs` 가 *자기 자신* 의 `lib` 를 link 하는 패턴. cargo 의 standard behavior 는 dev-dep declare 없이 integration test 가 자기 crate `lib` 자동 link — 그러나 misconfig 시 silently no-link 위험.
**Mitigation**: spec §6.3 R3 + §3.7 (f) 의 명시 — `cargo test -p kebab-parse-md --test normalize_snapshot -j 1` 가 green 인지 Step 9 의 exit gate 에서 확인. 만약 fail 시 *명시적* `kebab-parse-md = { path = "." }` dev-dep declare 추가 (cargo 의 어떤 edge case 일 수 있음).
### §6.4 hard-coded literal accidental drop
**위험**: Step 3 의 normalize.rs 이식 시 4 hard-coded agent literal (lib.rs:122/128/134/153) + tracing target literal (lib.rs:109) 중 일부가 cp/Write 과정에서 누락. spec §3.7e + §3.7 (g) + §1.9 의 6-row production flow trace 정합 깨짐.
**Mitigation**: Step 3 의 exit gate 가 `grep -c 'target: "kebab-normalize"' ... = 1` + `grep -E '"kb-(source-fs|parse-md|normalize)"' ... | wc -l >= 5` 명시. 5 literal (4 agent + 1 tracing target) 모두 grep 으로 verifiable. §4.6 의 spec-level verify gate 도 cross-check.
### §6.5 ~25 referencing task spec 의 accidental edit
**위험**: Step 12-14 의 design + doc 갱신 시 ~25 referencing task spec 도 같이 검색-치환 하면 frozen 룰 위반.
**Mitigation**: spec §5.7 의 invariant — `git diff main..HEAD --name-only | grep "^tasks/p"` = 0 line. Step 14 종료 후 verify 로 확인. 만약 stray edit 발생 시 `git checkout main -- tasks/p<N>/...` 으로 revert.
### §6.6 16 GB RAM 의 build pressure (full workspace test)
**위험**: Step 15 의 `cargo test --workspace --no-fail-fast -j 1` 가 lance / datafusion link step 에서 OOM 위험 (MEMORY.md `feedback_serial_build_only.md` + CLAUDE.md "Serial cargo builds only").
**Mitigation**:
- `cargo clean` 직전 후 (Step 15 (a)) — stale artifact 제거로 link memory 절약.
- `-j 1` 엄수 (sibling plan §6.6 패턴).
- per-crate 단위 검증 우선 (Step 2-9 의 각 exit gate 가 `-p <crate>` 단위) — full workspace 는 Step 15 의 1회.
- `cargo test/clippy/build` 동시 background 실행 금지.
### §6.7 design §3.7b 재작성 wording 의 부정확
**위험**: Step 12 의 §3.7b 재작성 시 spec §3.5 의 4-단락 wording 을 정확히 따르지 않으면 critic round 5 (만약 spec 이 다회 revision) 의 verification 가 fail 가능.
**Mitigation**: Step 12 의 source-of-truth = spec §3.5 의 code block (한국어 산문 + 의존 그래프 ascii). plan/executor 가 spec §3.5 를 copy-paste 후 markdown formatting (heading depth 등) 조정.
### §6.8 HOTFIXES.md entry 의 chronological reverse insert 실수
**위험**: Step 14 의 HOTFIXES entry 신규 추가 시 chronological reverse (newest top) 룰 위반 — 본 PR 의 entry 가 기존 entry *밑* 에 들어가면 reader 혼란.
**Mitigation**: Step 14 (d) 의 generic anchor verify cmd 활용 — `head -50 tasks/HOTFIXES.md` + `FIRST_ENTRY_LINE=$(grep -n "^## 20" tasks/HOTFIXES.md | head -1 | cut -d: -f1)` 으로 first entry line 위치 동적 측정 후 그 *위* 에 insert. hard-coded date / wording 인용 회피 — file 의 history 변경에도 robust.
### §6.9 kebab-parse-md 의 dep 폭증 (spec §6.9 R9)
**위험**: 흡수 후 `kebab-parse-md/Cargo.toml` 의 deps 가 기존 + `unicode-normalization` (흡수 후 추가) 로 1개 증가. lingua 의 build time + binary size 가 markdown parse + lift 두 책임을 모두 가지는 crate 에 concentrate.
**Mitigation**:
- 신규 deps = `unicode-normalization` 1 개만 (이미 `kebab-app` 도 사용 중인 `0.1` major). version drift 없음.
- Step 3 의 normalize.rs 이식 시 `Cargo.toml` 의 `[dependencies]` 에 `unicode-normalization = "0.1"` 추가 (sibling normalize 의 dep 와 동일 version).
- 본 위험의 실질 영향 ≈ +1 dep → 영향 minimal.
### §6.10 frozen p1-4 surface re-export 후퇴 (spec §6.10 R10)
**위험**: Step 3 의 normalize.rs 이식 시 `pub use kebab_core::{id_for_block, id_for_doc}` 제거 → p1-4 frozen public surface 의 후퇴.
**Mitigation**:
- spec §6.10 R10 의 production caller 0 verified — re-export 경유 caller = 0 (test mod imports 제외, R10 의 grep cmd).
- Step 3 의 exit gate 가 `grep -c "pub use kebab_core::" crates/kebab-parse-md/src/normalize.rs` = 0 verify — 명시적 제거.
- p1-4 frozen 룰은 historical contract 로 보존 — 본 PR 의 HOTFIXES entry (Step 14) 가 live source.
## §7 Out of scope (plan-level)
spec §8 의 모든 out-of-scope 그대로 + plan-level 추가:
- sibling spec (PR #185 source-fs dep lightening) 의 follow-up. 별 PR / 별 plan.
- kebab-parse-md 의 internal refactor (예: types.rs + normalize.rs 외 module 재배치). follow-up.
- kebab-app/src/lib.rs 의 다른 callsite 의 cosmetic 변경 — line 51, 1119 만 touch.
- Lens 3 (Extractor + Chunker dispatch unification) — 별도 작업 (spec §8 + §11 의 future direction sibling).
- v0.20+ image/pdf normalize integration — spec §11 의 영구 보존 entry (본 PR 의 scope 외).
- **kebab-app/src/lib.rs 의 comment 안 historical `kb-normalize` mention** (verified — line 1308 의 `mirroring \`kb-normalize::build_canonical_document\`` + line 1474-1475 의 `see kb-normalize's \`warning_agent\``) — comment 의 historical reference 로 **보존** (MINOR GAP8 closure). git blame 일관성 + reader 가 흡수 history 의 origin 추적 가능. context string (line 1119) 만 갱신 (Step 6) — production runtime behavior 의 wire-invisible 영향과 무관한 internal doc 형식의 historical reference 보존.
## §8 References
- spec: `docs/superpowers/specs/2026-05-26-normalize-absorption-spec.md` (1067 lines, round 3 APPROVE).
- sibling plan: `docs/superpowers/plans/2026-05-26-source-fs-dep-lightening-plan.md` (PR #185 머지 완료).
- design contract: `docs/superpowers/specs/2026-04-27-kebab-final-form-design.md` §3.7b + §8.
- 4 frozen task spec: `tasks/p1/p1-2-parser-types.md`, `tasks/p1/p1-3-markdown-parser.md`, `tasks/p1/p1-4-normalize.md`, `tasks/p9/p9-fb-07-md-title-fallback.md`.
- audit log root: `tasks/INDEX.md` L169 (PR #181 의 system-architect review).
- CLAUDE.md (project) + CLAUDE.md (machine) + MEMORY.md — disk / cargo / commit 룰.
## §9 Round closure status
### §9.1 Round 1 status
| Round | Reviewer | Verdict | Issues | Closure |
|---|---|---|---|---|
| 1 | critic-plan + verifier-plan (round 1) | REQUEST_CHANGES (2 CRITICAL + 2 BLOCKER + 6 MAJOR + 5 MINOR + 1 NIT = 16) | 본 round 2 revision 에서 all-closed — §9.2 의 finding-by-finding closure 참조 | 본 plan 의 round 2 revision (2026-05-26, planner) |
### §9.2 round 1 finding-by-finding closure
| ID | Severity | Source | Finding | Closure (plan-level edit location) |
|---|---|---|---|---|
| #1 (critic) | CRITICAL | critic-plan | `kebab-parse-md/Cargo.toml` 의 dep 갱신 step 부재 (spec §3.4 의 `-kebab-parse-types` + `+unicode-normalization` silently drop) | **CLOSED** — Step 3 의 Files affected 에 `crates/kebab-parse-md/Cargo.toml` 추가 + Action (e) 신설 (description string 갱신 + `kebab-parse-types` 제거 + `unicode-normalization = "0.1"` 추가). Exit gate 의 `grep -c "kebab-parse-types" Cargo.toml = 0` + `grep -c "unicode-normalization" Cargo.toml ≥ 1`. |
| #2 (critic) | CRITICAL | critic-plan | `pub use kebab_core::{id_for_block, id_for_doc}` 제거 시 in-body unqualified call (line 67, 241) unresolved → compile error | **CLOSED** — Step 3 의 Action (d) 갱신: `pub use` 제거 + 기존 `use kebab_core::{...}` block 에 `id_for_block, id_for_doc` 추가 명시. Exit gate `grep -E "use kebab_core::\{" ... \| grep -c "id_for_block\|id_for_doc" ≥ 1`. (대안 b: spec §3.3 retract — 채택 안 함, plan-side fix only) |
| #1 (verifier) | BLOCKER | verifier-plan | `kebab-parse-md/tests/` 의 `kebab_parse_types` import (2 hit verified — blocks_snapshots.rs:19 + frontmatter_snapshots.rs:23) 갱신 step 부재 | **CLOSED** — Step 5 의 Files affected 에 2 test file 추가 + Action (c)(d) 신설 (`kebab_parse_md::*` 로 갈음, integration test 는 `crate::` 사용 불가). Exit gate 의 grep scope src/ 한정 해제 → `grep -rn "kebab_parse_types" crates/kebab-parse-md/` = 0 hit. |
| #5 (verifier GAP5) | MAJOR | verifier-plan | blocks.rs 의 4 hit (line 1 doc, 25 doc-link, 37 use, 1589 production fully-qualified) 갱신 | **CLOSED** — Step 5 의 Action (a) 의 file-wide replace 명시. 4 hit 모두 `kebab_parse_types::` → `crate::types::` (Edit `replace_all: true` 가능). |
| (extra finding) | (planner self-detect) | normalize.rs | `cfg(test) mod tests` 안 9 hit fully-qualified `kebab_parse_types::ParsedBlockKind::*` (line 489/498/507/516/525/818/862/1070) 갱신 필요 | **CLOSED** — Step 3 의 Action (c) 의 sed/replace_all 명시. Exit gate `grep -c "kebab_parse_types::" crates/kebab-parse-md/src/normalize.rs = 0`. |
| #1 (critic) | MAJOR | critic-plan | Step 7 verify gate self-contradictory ("build fail 예상" — actual cargo behavior = silently ignore) | **CLOSED** — Step 10 verify gate 의 wording 정정: "`cargo build --workspace -j 1` **green** — 두 orphan 디렉토리는 cargo silently ignore. Cargo.lock 의 `[[package]]` entry 는 Step 11 직후 first `cargo build` 에서 자동 cleanup." (sibling plan revision 1 의 Step 7 → revision 2 의 Step 10) |
| #2 (critic) | MAJOR | critic-plan | Step 5 의 generic glob → 2 file:line 명시 | **CLOSED** — Step 8 의 Action (c) 갱신: `chunk/tests/long_section_snapshot.rs:21` + `store-sqlite/tests/contract_roundtrip.rs:16` explicit. |
| #3 (critic) | MAJOR | critic-plan | Step 6 의 fixture file enumerate (find 결과 명시) | **CLOSED** — Step 9 의 Files affected 갱신: `find crates/kebab-normalize/tests/` 결과 = `normalize_snapshot.rs` 단일 file (sibling fixture 부재) 명시. |
| #4 (critic) | MAJOR | critic-plan | HOTFIXES insertion anchor stale (hard-coded "S3 NLI" entry 인용) → generic 화 | **CLOSED** — Step 14 의 Action (d) 갱신: `head -50 tasks/HOTFIXES.md` + `FIRST_ENTRY_LINE=$(grep -n "^## 20" tasks/HOTFIXES.md \| head -1)` 동적 측정. §6.8 risk wording 도 generic 화. |
| GAP3 + GAP4 (verifier) | MAJOR | verifier-plan | Step 1 의 baseline N + Step 10 의 net delta 0 numeric compare cmd 부정확 (binary 수 행수 ≠ test 함수 수) | **CLOSED** — Step 1 의 baseline cmd 갱신: `awk '/^test result: ok\./ {sum += "passed;" 직전 숫자} END {print sum}'`. `.omc/state/normalize-absorption-baseline.txt` dump. Step 15 의 (b) numeric compare gate 추가: `diff <(echo $POST_COUNT) .omc/state/normalize-absorption-baseline.txt`. |
| GAP6 | MINOR | verifier-plan | Step 2 (현 Step 3) 의 4-literal grep production-only 분리 | **CLOSED** — Step 3 의 exit gate 의 grep cmd 정확화: `grep -cE 'agent:\s*"kb-(source-fs\|parse-md\|normalize)"\.to_string\(\)' ≥ 3` (production body 의 hard-coded literal 만). warning_agent body return + lift_warnings literal 합쳐 5 hit 보장. |
| GAP7 | MINOR | verifier-plan | Step 9 verify `git diff ... \| head -100` 의 truncate 위험 | **CLOSED** — Step 13 의 exit gate 갱신: `head -200` 삭제 + `grep -c "^@@" = 2` + `grep -cE "^\+\+\+\|^---" = 2` numeric verify 추가. |
| GAP8 | MINOR | verifier-plan | lib.rs comment stale `kb-normalize` mention (line 1308, 1474) 보존 결정 명시 | **CLOSED** — §7 out-of-scope 에 한 줄 추가: "`kebab-app/src/lib.rs` 의 comment 안 historical `kb-normalize` mention 은 보존 — git blame 일관성 + reader 가 흡수 history 추적 가능. context string (line 1119) 만 갱신." |
| #3 (critic) | MINOR | critic-plan | Step 4 (c) Cargo.toml hunk context — `sed -n '11,20p'` actual context 확인 명시 | **CLOSED** — Step 7 의 Action 갱신: `sed -n '11,20p' crates/kebab-app/Cargo.toml` 사전 context 확인 cmd 추가. |
| #1 (critic) | NIT | critic-plan | Step 7 "anchor" 명명 의미 — "closure pre-pivot" 정도 | **CLOSED** — Step 7 의 heading 정정: "anchor" → "closure pre-pivot 1". Step 10 의 "anchor 2" 도 의미상 "closure pre-pivot 2" 로 변경 가능하나 plan 의 convention 일관성 유지 차원에서 "anchor 1" / "anchor 2" / "closure" 의 3-stage 명명 유지 (NIT 수준 — semantic only). |
### §9.3 Round 2 metrics
- Plan line count: 567 (revision 1) → 761 (revision 2 start) → **현재** (revision 2 end, post-16-finding-closure).
- Step count: 15 (revision 2 무변).
- 신규 추가 (revision 2 end):
- Step 3 의 Action (e) — kebab-parse-md/Cargo.toml diff.
- Step 3 의 Action (d) — id_for_* unqualified import 보존.
- Step 3 의 Action (c) — 9 hit fully-qualified 갱신.
- Step 5 의 Action (c)(d) — kebab-parse-md/tests/ 의 2 file ref shift.
- Step 1 + Step 15 의 baseline N + numeric compare.
- §7 의 lib.rs comment 보존 명시.
- §9.2 의 16-row closure table.
- §3 Design 결정 무변 (Option A, dead struct 3 보존, §3.7b 4-단락 재작성, target_version 0.19.0, warning_agent + tracing target 보존 정책, id_for_* re-export 제거).
---
**Plan drafted by**: planner (team `normalize-absorption`, Phase B).
**Date**: 2026-05-26.
**Source spec**: 2026-05-26-normalize-absorption-spec.md (round 3 APPROVE, 1067 lines).
**Step count**: 15 (decompose from 10 step revision 1).
**Step ordering invariant**: Step 2 + 3 < Step 4-5 < Step 6 < Step 7 < Step 8 < Step 9 < Step 10 < Step 11 < Step 12 < Step 13 < Step 14 < Step 15.
**Anchor steps**: Step 7 (kebab-app dep cleanup) + Step 10 (workspace.members) + Step 15 (closure).
**Estimated complexity**: MEDIUM (15 step, 1:1 spec mapping, 단일 commit, ~3000 LOC code touch).