---
title: C3 — detail_steps focus rewrite 폐기 v1 implementation plan (fix-critical-1 Tier α #3)
date: 2026-05-20+
status: closed (C3 v1 W1-W2 atomic execution 완료 2026-05-20+ — Codex per-wave APPROVED; §5 Closure 참조)
spec_ref: docs/superpowers/specs/2026-05-20-c3-detail-steps-focus-rewrite-v1-design.md (status APPROVED_FOR_PLAN)
roadmap_ref: docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md (Track B Tier α #3, G4 §4.4 §225)
fix_critical_doc_ref: docs/fix-critical-1/index.html C3 section
prerequisites:
  - C3 spec v1 APPROVED_FOR_PLAN (Codex spec re-review, amend 1 — 2 IMPORTANT + 1 MINOR 흡수; status closed only at W2 closure-docs commit).
  - C2 owned_object_usage[] echo v1 closed (origin/main HEAD `fd9c4e3`) — HEAD anchor. C2 owned path / sentinel v2 변경 0.
non_supersedes:
  - C7 #12 / C6 #10 / C9 L-7 / C4 #8 / C5 #9 / C8 #7 / C10 (fix-critical-1 잔여 area).
---

# C3 — detail_steps focus rewrite 폐기 v1 implementation plan

## 1. Purpose + Spec Reference

C3 = `detail_steps.py:2872-2882` 3-regex prompt body cleanup loop 폐기. spec OQ option (b) upstream token-complete fix — orphan `'s` 를 만드는 VE-violation 강제 제거 strip 2 site 를 `_strip_invalid_sids()` helper 로 token-complete 화 → cleanup loop dead → delete.

- spec: `docs/superpowers/specs/2026-05-20-c3-detail-steps-focus-rewrite-v1-design.md` §1-§10.
- 2-wave: **W1** = `detail_steps.py` modify + `test_c3_detail_steps_focus_rewrite.py` new (1 atomic commit — helper + canary 동시 landing, Codex spec review IMPORTANT 2) / **W2** = closure docs atomic.
- HEAD anchor = `fd9c4e3` (origin/main, C2 v1 closed).

## 2. Plan Iter Trace

- iter 0 (2026-05-20+): plan v1 drafting.
- iter 1 (2026-05-20+): Codex plan review NEEDS_REVISION_NARROW 3 IMPORTANT 흡수 (amend 1) — IMPORTANT 1 helper docstring 의 `the figure's` literal 이 G5 source canary 자기-trip → docstring wording 교체 / IMPORTANT 2 W1·W2 gate 를 `PYTHONPATH=backend backend/.venv/bin/python|pytest` 로 교체 (system python app import fail) / IMPORTANT 3 Gate W1.4 `grep -c` 0-hit exit 1 false-fail → python assert heredoc 로 교체. Codex re-review APPROVED_FOR_EXECUTION.
- iter 2 (2026-05-20+): W1 execution 중 plan-gap 발견 — cleanup loop 폐기가 `backend/tests/core/test_scene_detail_analyze_one.py::test_analyze_one_cleans_double_spaces` (무조건 연속-공백 collapse 검증 stale test) 1건 regression 유발 (clean HEAD `fd9c4e3` git stash 대조로 나머지 5 failure = C2 closure 기록과 일치하는 pre-existing 확정). Codex W1 plan-gap 의논 `APPROVED_W1_SCOPE_EXPANSION_WITH_TEST_REPURPOSE` — option (b) stale test repurpose (delete 아님, Track B no-blind-mutation integration guard 유지), W1 scope 3-file 로 확대. test 를 `test_analyze_one_preserves_valid_entity_prompt_spacing` 로 rename + section header `C3: t2i_prompt blind whitespace cleanup 폐기` + valid-entity 연속 공백 보존 assert (`"  at  " in prompt`).

## 3. W1 — helper + 2-site apply + cleanup loop 폐기 + C3 regression test

### 3.1 Pre-W1 Entry Sanity

- W1 진입 전 Codex W1 entry sanity 받을 것 (file edit 0 상태에서 송신).
- 진입 직전 read: 승인된 spec + 본 plan + `detail_steps.py` 현재 상태 (line drift 확인 — 본 plan 의 old_string anchor 가 현 HEAD 와 일치하는지).
- HEAD = `fd9c4e3` verify.

### 3.2 W1 File Operations (2 file modify + 1 file new, 1 atomic commit)

| op | file |
|----|------|
| modify | `backend/app/core/steps/detail_steps.py` |
| new | `backend/tests/integration/test_c3_detail_steps_focus_rewrite.py` |
| modify | `backend/tests/core/test_scene_detail_analyze_one.py` (iter 2 plan-gap absorb — stale test repurpose) |

### 3.3 detail_steps.py — `_strip_invalid_sids` helper 신설 (W1.1)

module-level private function. 기존 module-level helper `_detect_state_variant_chars` (현 `:283`) **직전**에 insert (module-level, 사용처 `:2706`/`:2775` 보다 앞). 삽입 코드:

```python
def _strip_invalid_sids(text: str, invalid_sids: Iterable[str]) -> str:
    """무효 closed-world SID 토큰을 text 에서 token-complete 하게 제거.

    invalid_sids = _check_prompts() pure_remove / bad_in_sfp 가 산출한 VE 위반
    SID 집합 (C##/L##/P##/C##O## — _ve_pattern 형식의 closed-world ID). 각 SID 와
    그 후행 소유격('s) 를 한 단위로 소거하되, 양쪽이 모두 공백이면 단일 공백으로,
    한쪽이라도 비공백이면 공백 없이 잇는다 (제거 위치 국소 정규화만). 무효 SID 가
    text 에 없으면 (subn substitution 0건) 원본 text 를 그대로 반환 — blind
    global trim/collapse 없음. 제거 자리에 semantic subject phrase 재삽입 없음
    (글자 삭제 + 국소 공백 정규화만).
    """
    sids = sorted({s for s in invalid_sids if s}, key=len, reverse=True)
    if not sids or not text:
        return text
    pattern = re.compile(
        r"(?P<left>\s*)\b(?:" + "|".join(re.escape(s) for s in sids)
        + r")\b(?:'s)?(?P<right>\s*)"
    )

    def _repl(m: "re.Match[str]") -> str:
        return " " if (m.group("left") and m.group("right")) else ""

    cleaned, n = pattern.subn(_repl, text)
    return cleaned if n else text
```

- **import 확인**: `detail_steps.py` 상단 `from typing import ...` 줄에 `Iterable` 없으면 추가. `re` 는 이미 module import (기존 `re.compile`/`re.sub` 사용). Gate W1.1 이 검증.

### 3.4 detail_steps.py — strip site A / B helper 호출로 교체 (W1.2 / W1.3)

**W1.2 — strip site A** (`if pure_remove:`, 현 `:2702-2706`). old:

```python
                        if pure_remove:
                            strip_pat = re.compile(
                                r'\b(?:' + '|'.join(re.escape(v) for v in pure_remove) + r')\b'
                            )
                            prompt = strip_pat.sub("", prompt).strip()
```

new:

```python
                        if pure_remove:
                            prompt = _strip_invalid_sids(prompt, pure_remove)
```

**W1.3 — strip site B** (`if bad_in_sfp:`, 현 `:2772-2775`). old:

```python
                if bad_in_sfp:
                    strip_sfp = re.compile(r'\b(?:' + '|'.join(re.escape(v) for v in bad_in_sfp) + r')\b')
                    sfp = strip_sfp.sub("", sfp).strip()
                result["representative_moment"] = sfp
```

new:

```python
                if bad_in_sfp:
                    sfp = _strip_invalid_sids(sfp, bad_in_sfp)
                result["representative_moment"] = sfp
```

### 3.5 detail_steps.py — cleanup loop 폐기 (W1.4)

현 `:2872-2882` `# 빈 ID 패턴 정리` for loop 전체 delete (주석 포함). delete 대상 old:

```python
            # 빈 ID 패턴 정리 (엑스트라 인물 등으로 인한 빈 문자열 잔류)
            for var in t2i_vars:
                prompt = var.get("t2i_prompt", "")
                if prompt:
                    # "showing  lying_down" → "showing lying_down"
                    prompt = re.sub(r'\s{2,}', ' ', prompt)
                    # "focus on 's" → "focus on the figure's"
                    prompt = re.sub(r"focus on\s+'s", "focus on the figure's", prompt)
                    # " 's " (빈 소유격) → "the figure's "
                    prompt = re.sub(r"\s+'s\s+", " the figure's ", prompt)
                    var["t2i_prompt"] = prompt.strip()
```

→ 11줄 + 직전/직후 빈 줄 정리. 앞뒤 코드 (`legacy: shot_cinematography` block 끝 ~ `Fix B (2026-05-10)` block 시작) 사이에서 자연스럽게 제거.

### 3.6 test_c3_detail_steps_focus_rewrite.py — G1-G7 (W1.5)

`backend/tests/integration/test_c3_detail_steps_focus_rewrite.py` 신규. no live LLM, NO VLM — `_strip_invalid_sids` helper unit-test + `detail_steps.py` source-level canary. 전체 내용:

```python
"""C3 — detail_steps focus rewrite 폐기 regression test (fix-critical-1 Tier α #3).

_strip_invalid_sids() helper unit-test (G1-G4, G6) + helper-output·source-level
canary (G5, G7). no live LLM, NO VLM — pure helper + source 문자열 검사.
"""
from pathlib import Path

import app.core.steps.detail_steps as _detail_steps_mod
from app.core.steps.detail_steps import _strip_invalid_sids

_SRC = Path(_detail_steps_mod.__file__).read_text(encoding="utf-8")


def test_g1_t2i_prompt_invalid_id_possessive_removal():
    # 무효 ID + 후행 소유격 token-complete 제거 — orphan 's 0
    assert _strip_invalid_sids("focus on C08's hand", {"C08"}) == "focus on hand"


def test_g2_representative_moment_invalid_id_possessive_removal():
    # representative_moment(sfp) path 동일 helper — 선두 무효 ID 소유격 제거
    assert (
        _strip_invalid_sids("C08's silhouette in the doorway", {"C08"})
        == "silhouette in the doorway"
    )


def test_g3_composite_id_possessive_removal():
    # composite ID(C##O##) 를 한 단위로 제거
    assert _strip_invalid_sids("a man C08O02's coat", {"C08O02"}) == "a man coat"


def test_g4_three_digit_id_preservation():
    # 무효 set 밖 유효 3-digit ID(P123) 보존, prefix misalign 0
    assert _strip_invalid_sids("P123 holds C12", {"C12"}) == "P123 holds"


def test_g5_no_semantic_injection():
    # helper 출력 + detail_steps.py source 어디에도 "the figure's" 삽입 0
    assert "the figure's" not in _strip_invalid_sids("focus on C08's hand", {"C08"})
    assert "the figure's" not in _SRC
    assert "focus on the figure's" not in _SRC


def test_g6_local_normalization_and_no_match_noop():
    # (a) 무매치 no-op — clean prompt 불변 (blind trim 0)
    assert _strip_invalid_sids("  clean prompt  ", {"C08"}) == "  clean prompt  "
    # (b) far-whitespace 보존 — 제거 위치에서 떨어진 double space 불변
    assert (
        _strip_invalid_sids("alpha  beta C08's gamma", {"C08"})
        == "alpha  beta gamma"
    )


def test_g7_cleanup_loop_removed():
    # :2872-2882 3-regex cleanup loop 폐기 증명 — old literal 0
    assert "빈 ID 패턴 정리" not in _SRC
    assert r"focus on\s+'s" not in _SRC
    assert r"\s+'s\s+" not in _SRC
```

### 3.7 W1 Pre-commit Gate

repo-root cwd (`/Users/manta/Documents/Projects/TheRoad-I1`) 에서 실행. 모든 gate 는 프로젝트 venv python + `PYTHONPATH=backend` 사용 — system python 은 fastapi 등 미설치라 app import fail (Codex plan review IMPORTANT 2, C1/C2 N-exec 정합). `cd backend` cwd drift 금지 (Carry-P021 N-exec-1 정합).

```bash
# Gate W1.1: helper module-level + Iterable import
PYTHONPATH=backend backend/.venv/bin/python -c "from app.core.steps.detail_steps import _strip_invalid_sids; print('helper OK')"
# (detail_steps.py 의 from typing import 줄에 Iterable 포함 확인 — import 실패 시 추가)

# Gate W1.2: C3 regression test G1-G7 전수 PASS
PYTHONPATH=backend backend/.venv/bin/pytest backend/tests/integration/test_c3_detail_steps_focus_rewrite.py -q

# Gate W1.3: detail_steps / scene_detail regression
PYTHONPATH=backend backend/.venv/bin/pytest backend/tests -k "detail_steps or scene_detail" -q

# Gate W1.4: cleanup loop 폐기 + helper 적용 증명 — executable assertion
#            (grep -c 는 0-hit 시 exit 1 → set -e/automation 에서 false fail; python assert 로 대체, Codex IMPORTANT 3)
PYTHONPATH=backend backend/.venv/bin/python - <<'PY'
from pathlib import Path
src = Path("backend/app/core/steps/detail_steps.py").read_text(encoding="utf-8")
assert "빈 ID 패턴 정리" not in src, "cleanup loop 주석 잔존"
assert "the figure's" not in src, "semantic 삽입 literal 잔존"
n = src.count("_strip_invalid_sids")
assert n == 3, f"_strip_invalid_sids count={n} (def 1 + 호출 2 = 3 기대)"
print("Gate W1.4 OK")
PY

# Gate W1.5: 백엔드 전체 회귀 (pre-existing failure 제외 동일 — C2 closure 시 5 pre-existing
#            failure 확인: text_cleaner.clean_text ImportError 등, C3 무관)
PYTHONPATH=backend backend/.venv/bin/pytest backend/tests -q
```

- Gate W1.4 가 AssertionError 없이 `Gate W1.4 OK` 출력해야 W1 commit 가능.
- Gate W1.5 의 신규 failure 가 C3 변경 기인이면 commit 금지 → 원인 수정.

### 3.8 W1 Commit + per-wave Codex review

- commit message: `W1(c3-detail-steps-focus-rewrite-v1): _strip_invalid_sids helper + 2-site token-complete strip + cleanup loop 폐기 + C3 regression test — detail_steps.py blind prompt body mutation 3-regex 제거 (1 file modify + 1 file new)`
- staged: `backend/app/core/steps/detail_steps.py` + `backend/tests/integration/test_c3_detail_steps_focus_rewrite.py` 만 (`git add` 명시 경로 — global add 금지).
- commit 후 Codex W1 per-wave review 송신 → APPROVED_FOR_W2_ENTRY 받고 W2 진입.

## 4. W2 — Closure docs atomic

### 4.1 Pre-W2 Entry Sanity

- W1 per-wave APPROVED_FOR_W2_ENTRY 후 진입.
- W2 = closure docs only — code/test 변경 0.

### 4.2 W2 File Operations (5 docs, path-limited)

| op | file |
|----|------|
| modify | `docs/superpowers/specs/2026-05-20-c3-detail-steps-focus-rewrite-v1-design.md` |
| modify | `docs/superpowers/plans/2026-05-20-c3-detail-steps-focus-rewrite-v1-implementation.md` |
| modify | `docs/superpowers/specs/2026-05-16-track-b-semantic-debt-roadmap-design.md` |
| modify | `docs/fix-critical-1/index.html` |
| modify | `docs/visual-reliability-audit/2026-05-14-semantic-string-routing-debt-audit/01-code-side-regex-audit.md` |

### 4.3 W2 closure docs 내용

1. **spec** — frontmatter `status` → closed + §10 Closure (commit chain / Gate result / Codex review trace / 함정).
2. **plan** — frontmatter `status` → closed + §5 Closure.
3. **roadmap** — §5.15 C3 entry 신설 (§5.13 C1 / §5.14 C2 정합 형식 — 본질 / Fix applied / 결과).
4. **fix-critical-1 index.html** — C3 marker: badge `✓ CLOSED` + priority list `C3 → CLOSED` / `C7 → NEXT` + priority matrix row 3 status 갱신.
5. **audit 01** (`01-code-side-regex-audit.md`) — §7 (Excluded — Closed ID syntax cleanup) detail_steps row 에 C3 closure/reclassification inline note. audit 01 #10 = `t2i_review` 별건 (C3 무관) — #10 에는 marker 배치 X (Codex W2 audit-01 marker verdict `APPROVED_FOR_AUDIT_01_SECTION_7_MARKER_WITH_DOC_REF_CORRECTION`).

- **W2 self-hash 금지** (C2 N-W4-2 정합) — closure docs 안 W2 자체 commit hash literal 없음. "W1-W2" / "W2 closure commit" wording. W1 hash 는 W1 commit 후 확정 — closure docs 에 W1 hash 기재 가능. W2 hash 는 push 후 external memory 기록.

### 4.4 W2 Pre-commit Gate

```bash
# Gate W2.1: closure docs 5 file staged 만 (code/test 변경 0)
git diff --cached --name-only   # 위 5 docs 만

# Gate W2.2: W2 self-hash 부재 — closure docs 안 W2 commit hash literal 0
#            (W2 commit 전이라 hash 미존재 — wording "W1-W2"/"W2 closure commit" 확인)

# Gate W2.3: backend 무변경 재확인 (venv python + PYTHONPATH — Codex IMPORTANT 2)
PYTHONPATH=backend backend/.venv/bin/pytest backend/tests/integration/test_c3_detail_steps_focus_rewrite.py -q
```

### 4.5 W2 Commit + W1-W2 range review + push

- commit message: `W2(c3-detail-steps-focus-rewrite-v1): closure docs atomic — spec/plan status closed + roadmap §5.15 + fix-critical-1 C3 marker + audit 01 §7 detail_steps row 정정 (5 docs path-limited)`
- commit 후 Codex W1-W2 range review 송신 → APPROVED_FOR_PUSH.
- APPROVED_FOR_PUSH 시 사용자 ask 0 즉시 push ([[feedback_push_no_user_ask]]).
- push 후 §5 Closure / external memory 기록 + Codex pane `clear` 요청.

## 5. Closure (2026-05-20+)

C3 v1 W1-W2 atomic execution 완료. 종합 closure (commit chain / Gate / Codex trace / 함정 N-1~N-5) = spec `2026-05-20-c3-detail-steps-focus-rewrite-v1-design.md` §10.

- **W1 `4740b03`** — `_strip_invalid_sids()` helper + 2-site token-complete strip + cleanup loop 폐기 + C3 regression test (3 file — iter 2 plan-gap absorb 로 `test_scene_detail_analyze_one.py` repurpose 포함). Codex per-wave `APPROVED_FOR_W2_ENTRY`.
- **W2 closure commit** — closure docs atomic 5 docs (spec/plan status closed + roadmap §5.15 + fix-critical-1 index.html C3 marker + audit 01 §7 detail_steps row 정정). Codex W1-W2 range review.
- Gate W1.1-W1.5 PASS — 백엔드 4043 passed / 5 failed 전부 pre-existing (clean HEAD `fd9c4e3` git stash 대조 확정).
- plan iter trace = §2 (iter 1 plan review amend 1 / iter 2 W1 plan-gap absorb).

## 6. Doctrine references

- [[feedback_llm_based_judgment]] — 4-Gate (Semantic Regex Ban). helper regex = closed-world SID literal set only.
- [[feedback_no_vlm_dependency]] — NO VLM. C3 코드·테스트 VLM 의존 0.
- [[feedback_codex_mcp_discussion_workflow]] / [[feedback_codex_mcp_claude_mcp_response_trigger]] — 매 wave Codex per-wave review, tmux-bridge raw typing + prefix/suffix.
- [[feedback_no_user_ask_codex_only]] — OQ·결정 사용자 ask 0, Codex MCP 의논.
- [[feedback_push_no_user_ask]] — APPROVED_FOR_PUSH 즉시 push.
- [[feedback_channel_separation_4way]] — 4채널 fact source 구분.
- [[project_fix_critical_1_persistent]] — fix-critical-1 Tier α #3, C3 → C7 priority.
