Installs into .claude/skills of the current project.
Are you the author of Stabilize Milestone?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/kohj1018-stabilize-milestone-agentic-dev-harness)
---
name: stabilize-milestone
description: Stabilize a milestone — run E2E + regression + refactoring/ADR review. No code changes, no commits.
argument-hint: "<milestone-id>"
disable-model-invocation: true
allowed-tools: Read Glob Grep Write Edit Bash Agent
---
본 skill은 evaluator-optimizer pattern의 evaluator orchestration이다 (ADR-068 D8).
이 skill은 **코드 수정·커밋·workitem status 변경을 하지 않는다.**
다음 네 종류의 문서 갱신만 정상 책임이다:
1. `docs/40-validation/QA_FINDINGS.md` 누적 기록 (결함 — 보고자 무관, ADR-070 D7).
2. `docs/40-validation/IMPROVEMENT_GUIDE.md` 누적 기록 (개선 — 보고자 무관 + deterministic preflight 결과).
3. milestone 문서의 `## 8. 회고` 섹션 자동 채움 ([ADR-068](../../../docs/90-decisions/boilerplate/ADR-068-milestone-closure-and-graduation-v3.md) graduation contract — status 변경 X, 본문 단락 갱신만).
- 회고 본문: **graduation 줄(`YES|PENDING_ACCEPTANCE|NO|BLOCKED (날짜)` — 단계 8 판정 영속, ADR-057#amend-1·ADR-068 D4)** + `open 항목 스냅샷` + **`post-close 수정` 줄(ADR-068 D5)** + 4 항목: 목표 달성도 / scope creep / 비목표 위반 / 핵심 학습 3개 이내.
4. `docs/10-charter/DECISION_REGISTER.md` **append** — 점검에서 드러난 *기획 결정*을 `status: open` + `- 발견: 봉인 후 (M<N>)`으로 등재(ADR-060 D11 writer). 기존 항목의 상태는 바꾸지 않는다. 상세는 아래 `봉인 후 새 결정 등재` 절.
milestone 문서 `## 11. 수용 기록`은 본 skill의 write 대상이 **아니다** — `/accept-milestone`(마일스톤 스코프) 단독 소유다(ADR-066 D1).
그 외 변경은 금지한다 — milestone 문서의 `## 0. Status` / `## 1~7` 섹션 / 다른 workitem 문서 / 코드 일체.
후속 작업이 필요하면 `/repair-milestone <M>`으로 텍스트 제안만 출력한다. **inner-loop 4종(`/implement-workitem`·`/validate-workitem`·`/repair-workitem`·`/finalize-workitem`)의 호출을 권하지 않으며 새 task 자동 추가도 하지 않는다** (ADR-068 D1 / ADR-057#amend-3 결정 6).
**실행 single-origin (ADR-054)**: `validate`/`validate:e2e`/`npm audit` 실행 + QA_FINDINGS/IMPROVEMENT_GUIDE/회고 *쓰기*는 본 skill(origin)이 *한 번만*. **같은 checkout에서 stabilize 2개 동시 실행 금지**(포트·테스트DB·Playwright outputDir·빌드캐시 충돌 + tracked-doc clobber). 다른 모델 2nd opinion은 *읽기 전용* `/validate-milestone <M> --reviewer-tag <tag>` 병렬 → `/repair-milestone`이 종합(실행은 origin 1회).
입력:
- `$ARGUMENTS`에는 milestone ID(예: `M1`)가 들어온다. **`M[0-9]+` 패턴만 허용**(미일치 즉시 종료). 다른 인자·플래그는 없다 — 정의되지 않은 플래그를 받으면 안내 후 종료한다.
수행:
1. milestone 문서를 읽고 포함된 feature/task 목록을 회수한다.
### 1.0. Deterministic pre-flight (LLM 위임 전 cheap mechanical check)
LLM 호출 전 다음을 순서대로 점검 (모두 deterministic, fail-fast X — 보고만):
1. **docs/ 내부 markdown link 유효성** (기본: *내부 / ADR 참조 / 로컬 파일* 만 점검 — 외부 URL 검사는 optional):
- **기본 (내부 link only — deterministic 보장)**: `markdown-link-check --config <(echo '{"ignorePatterns":[{"pattern":"^https?://"}]}') docs/**/*.md` (외부 URL 무시).
- OS 별 glob 처리:
- Unix/macOS bash: `markdown-link-check docs/**/*.md` (bash glob 자동 확장).
- Windows PowerShell (glob 미확장 안전 패턴): `Get-ChildItem docs -Recurse -Filter *.md | ForEach-Object { markdown-link-check $_.FullName }`.
- OS 무관 fallback: repo 의 `scripts/verify.{sh,ps1,mjs}` 에 한 줄 helper 박거나 `npx markdown-link-check` 를 *각 파일 인자로 직접 호출* — `glob` npm 패키지 의존 회피.
- **optional (외부 URL 검사 — 네트워크 의존 / flaky)**: 위 명령에서 `ignorePatterns` 제거. 단 *deterministic preflight 의 기본 단계가 아님* — `--with-external-links` 플래그로 사용자 명시 발화 시만.
- 깨진 link 발견 시 IMPROVEMENT_GUIDE.md 에 `P1 [Doc-link] <file:line> — <broken link>` 라벨 기록.
- `markdown-link-check` 미설치 환경은 본 항목만 skip + 출력에 명시 (`Doc-link check skipped: markdown-link-check not installed`).
2. **ADR 참조 유효성 (ADR-045#d1·#d8)**:
- `ADR-NNN` 참조 → 실제 파일 존재 매칭. 예외(오류 아님): (a) `<!-- -->` 주석 안 참조, (b) **allowlist된 ADR-100/101**의 bootstrap 전 미존재, (c) Reserved/Parked/Dropped 표 등재 번호. boilerplate(001~099) 미존재 → `P1 [ADR-ref]`. **그 외 project ADR(102+) 미존재 → `P2 [ADR-ref-project]`** (무시 X).
- **앵커 존재 (ADR-045#d2)**: `ADR-NNN#amend-M` → 대상 ADR에 `## Amendment M`(또는 `<a id="adr-NNN-amend-M">`) 존재. 누락 시 `P1 [Ref-anchor] <file:line>`. (`#dK`는 token-only — 대상 ADR에 "K." 결정 항목 존재는 *best-effort*, 미존재 의심만 `P2`.)
- **내부 anchor 링크 (ADR-045#d9)**: `[label](file.md#anchor)`의 anchor가 대상 파일에 `<a id>` 또는 대응 heading으로 실재. 누락 시 `P1 [Link-anchor] <file:line>`.
- **Surfaces forward check (ADR-045#d3·#d4)**: `## Surfaces` 블록을 가진 각 ADR에 대해 — 등재 파일이 모두 존재하고 본문에 `ADR-NNN` 역참조를 갖는가. 누락 시 `P1 [Surface-backref] ADR-NNN → <file>`. 대상 ADR의 `## Status` **본문이 `superseded` 또는 `deprecated`로 시작할 때만** forward-check에서 skip한다(**아래 «죽은 ADR 인용»과 동일 기준** — `accepted (부분 superseded — ...)`는 살아 있으므로 skip하지 않는다. 느슨하게 «문자열 포함»으로 읽으면 부분 supersede된 **살아있는** ADR의 Surfaces drift가 조용히 검출되지 않는다). live sync 소스만 점검 — 죽은 ADR의 잔존 Surfaces는 별도 [Ref-dead]가 담당. **이 forward 방향만 Phase 4 범위** (역방향은 휴리스틱이라 Phase 5 검토).
- **죽은 ADR 인용**: 인용된 ADR의 `## Status` **본문이 `superseded` 또는 `deprecated`로 시작할 때만** `P2 [Ref-dead] <file:line>`. **`accepted (부분 superseded — ...)`로 시작하는 ADR은 살아 있다** — 문자열 `superseded`가 괄호 안에 있다고 죽은 것으로 세지 않는다(이 구분을 빼면 부분 supersede된 ADR 하나가 100건 단위의 오탐을 만든다). **예외(오탐 아님, ADR-045#amend-2 D10)**: (a) 같은 줄에 `(현재 SSOT:`이 병기된 인용(역사 서술 + 현행 포인터를 함께 둔 정본형 — D10의 검사 제외 마커), (b) 그 ADR 자신을 가리키는 인용만 있는 줄(`## Status`·`## Amendment` 등), (c) `README.md` 인덱스 표 행의 supersede 계보 칸, (d) `<!-- -->` 주석 안. 예외에 해당하면 기록하지 않는다.
- **인덱스 amend 동기**: `boilerplate/README.md` Amendments 컬럼 amend 수 ↔ 코드펜스(```)·`<!-- -->` 주석 *밖의* 본문 `## Amendment N` 수 일치(예시·주석 헤딩 제외; 불일치 `P1 [ADR-index]`). (review-doc과 중복 가능.)
3. **FAC ↔ AC unmapped 검출** ([ADR-037](../../../docs/90-decisions/boilerplate/ADR-037-spec-coverage-audit.md)#amend-1 영속 SSOT `## 7-1` 정합):
- 본 마일스톤의 모든 feature 문서 `## 7-1. FAC ↔ AC 매핑표`에서 *unmapped* 또는 *비어 있음* 항목 회수. **우변이 `승인 스냅샷 <경로> (manifest: …)` 형식인 행은 unmapped 가 아니다**(ADR-037#amend-4 — 승인 표면이 검증자다). 우변이 비었거나 `(해당 task 없음)` 처럼 **검증자를 지목하지 않은** 행만 센다. `## 7-3` PX↔AC 도 같다(결정 1-b).
- 발견 시 **QA_FINDINGS.md 본 마일스톤 `### P0`**에 `P0 [Spec-gap] F-NNN:FAC-N → unmapped`(`- 출처: preflight`)로 등재한다(ADR-073#amend-2 결정 4 / ADR-070 D7 — 계약 결함은 QA 원장, 그래야 졸업 item 5가 센다) + graduation NO 유지 + 사용자 보고. 구현 후에는 plan-workitem으로 task를 자동 추가하지 않으며, 담당 task가 있으면 repair, 없으면 ADR-057#amend-3 결정 6의 사용자 명시 결정 경로를 따른다.
3-1. **pattern-scan 범위 밖 잔존 회수 (deterministic)**: 본 마일스톤 산하 task 문서 `## 8`에서 **HTML 주석 밖의** `- pattern-scan` 줄을 회수해 `범위 밖 M건`이 1 이상인 항목을 모은다. 발견 시 IMPROVEMENT_GUIDE에 아래 스키마로 기록(WORKFLOW 4-A 면제 기록을 stabilize가 회수하는 것과 동형).
- **폐쇄 후 라운드의 pattern-scan은 task `## 8`이 아니라 `IMPROVEMENT_GUIDE.md` `## 5`의 항목 하위 줄에 있다**(ADR-066#amend-1). 본 항목은 그 두 위치를 모두 읽는다 — task `## 8`은 폐쇄 전 `/repair-workitem` 라운드분, `## 5`는 폐쇄 후 마일스톤 층 라운드분이다.
- **ID는 안정적으로 만든다** — `<task-id>-pspread-<패턴 슬러그>` (예: `T-004-pspread-null-guard`). 항목 형식: `- **<ID>** | P1 | [관측됨] | linked: <task-id> | status: open` + 하위 줄 `- [Pattern-spread] <패턴> — 범위 밖 M건 미수정: <경로>`.
- **재등재 금지(dedup)**: 그 ID가 IMPROVEMENT_GUIDE에 이미 있으면(status가 `open`이든 `resolved`든) **다시 등재하지 않는다.** `- pattern-scan` 줄은 task 문서에 영속되므로 이 규칙이 없으면 매 마일스톤 같은 P1이 재생산된다.
- **예외 — 같은 패턴의 새 출현**: 기존 ID가 `resolved`인데 그 task `## 8`에 **경로 목록이 다른** `- pattern-scan` 줄이 새로 append됐으면(두 번째 repair 라운드가 더 찾은 경우) `-2`·`-3` suffix를 붙인 새 ID로 등재한다. 판별 기준은 *경로 목록의 차이*이며, 같으면 재등재하지 않는다.
- 후속은 `/repair-milestone`(cross-cutting) 또는 다음 마일스톤 후보로 라우팅한다. 잔존이 0건이거나 전부 기존 ID면 침묵한다.
3-2. **잠정 해석 회수 (deterministic — ADR-076 D5)**: 본 마일스톤 산하 task 문서 `## 8`(HTML 주석 밖)에서 `해석 잠정:` 줄 중 같은 AC의 `해석 확정:` 줄이 없는 것을 모아 `IMPROVEMENT_GUIDE.md` `## 2. 열린 항목`의 `### M<N>` 그룹에 기록한다. 사용자 authority 항목이라 6-S 채택 전 검토를 거치지 않고 판정값을 바로 적는다.
- ID는 그 줄의 `확인: IMPROVEMENT_GUIDE <ID>`에 적힌 값을 그대로 쓴다(없으면 `<task-id>-prov-ac-<N>`). 형식: `- **<ID>** | P1 | [관측됨] | linked: <task-id> | status: open | decision: needs-confirmation` + 하위 줄 `- [Provisional-AC] AC-N = <선택> — 대안: <대안>` · `- 대상: AC-N 해석 / 확인 방법: 사용자 확인 (/repair-milestone) / 막힌 이유: 사용자 authority` · `- 출처: run-milestone`.
- **재등재 금지**: 같은 ID가 IMPROVEMENT_GUIDE나 `docs/40-validation/archive/M<N>.md`에 이미 있으면(status 무관) 다시 쓰지 않는다.
- **졸업을 막지 않는다** — IMPROVEMENT_GUIDE P1이라 graduation 판정 입력이 아니다. 대신 아래 «다음 단계»가 확인을 권한다.
- 회수할 것이 없으면 침묵한다.
4. **모드 라벨 ↔ 본문 정합 휴리스틱** (ADR-012): 모든 `docs/00-meta/` 파일의 `> 모드: ...` 라벨이 본문과 명백히 어긋나는지 점검 (휴리스틱 한계 명시).
- mismatch 시 P2 `[Doc-mode] <file>` 기록.
5. **DESIGN.md + ARCH 7-x cross-surface drift 검출** (ADR-073 D8) — *(5-2) 는 정규식 기반 deterministic, (5-3)(5-4) 는 mechanical/best-effort heuristic — 휴리스틱 한계는 항목별 echo*:
5-0. **변경 파일 회수 — git diff 의존 금지**: stabilize-milestone 은 정상 lifecycle 에서 `/finalize-workitem` 으로 *이미 커밋된* 후 호출되므로 working tree `git diff` 는 비어 있다. 본 마일스톤 task 의 변경 파일 회수 우선순위:
- **(a) 1차 — task 문서**: 본 마일스톤 산하 모든 task (`docs/30-workitems/tasks/T-*.md`) 의 `## 4-1. 변경 예정 파일/경로` 본문 회수. (TASK_TEMPLATE 정합 — finalize 시 `--apply` 또는 명시 update 로 채워짐)
- **(b) 2차 — commit log fallback**: `## 4-1` 비어 있거나 git 실제 변경과 어긋난 task 는 `git log --grep "Refs: T-NNN" --name-only` 로 commit 로그의 변경 파일 회수 (ADR-008#amend-2 Refs footer 정합).
- **(c) 3차 — validation report fallback**: 위 둘 다 비어 있는 task 는 `docs/40-validation/reports/<task-id>.md` 의 diff trace audit 단락 회수.
- **(d) 4차 — 폐쇄 후 수리 기록**: `IMPROVEMENT_GUIDE.md ## 5`의 본 마일스톤 항목 `files:` 목록(ADR-073#amend-2 결정 3 — 마일스톤 층 수리가 바꾼 파일은 여기에만 있다).
- **(e) 모두 실패 시**: 본 task 는 *조사 불가* 로 표시 + IMPROVEMENT_GUIDE 에 `P2 [Stabilize-recovery] T-NNN — 변경 파일 회수 불가` 기록 후 다음 task 로 계속.
- **(f) 회수 결과 echo (ADR-073#amend-2 결정 3)**: `회수 파일 N개 ((a) x · (b) y · (c) z · (d) w)`. N = 0이면 5-2·5-2b·5-3을 실행하지 않고 `P2 [Stabilize-scope-empty] 회수 파일 0건 — 5-x 미실행`을 기록한다. 5-2·5-2b·5-3 출력은 `대상 K파일 / 위반 M건`으로 둘을 함께 적는다.
5-1. **UI 프로젝트 판정** — **ADR-073 D9 "UI 판정 다중신호 절차"** 적용(부재→비-UI: 5-1~5-3 skip+사유 echo / status≠draft→UI: 5-1~5-3 활성 / status=draft+추가신호≥1→UI 의심: IMPROVEMENT_GUIDE에 `P1 [Design-draft] DESIGN.md status=draft + UI 신호 감지 — /bootstrap-design 권장` 기록 + 5-1~5-3 활성 / 신호 0→silent skip).
5-2. **UI 프로젝트 — raw color grep** (정규식 deterministic, **기록 등급 — 진행 차단 안 함**): 5-0 에서 회수한 변경 파일 중 아래 두 갈래로 검사한다.
- **웹 계열** — 확장자 `.tsx`/`.jsx`/`.ts`/`.js`/`.vue`/`.svelte`/`.astro`/`.css`/`.scss`/`.html`. **단축 hex 는 스타일시트에서만 잡는다**(ADR-072#amend-1 결정 3 — design gate 어댑터와 같은 규칙):
- `.css`/`.scss`/`.sass`/`.less`: `#([0-9A-Fa-f]{8}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{4}|[0-9A-Fa-f]{3})\b`(3~8자리 전부)
- 그 밖의 코드 파일(`.tsx`·`.ts`·`.vue`·`.svelte`·`.astro`·`.html` 등): `#([0-9A-Fa-f]{8}|[0-9A-Fa-f]{6})\b`(**6·8자리만**)
`\b`로 더 긴 hex 런의 부분매치를 막는다. 일치 시 `P1 [Design-rawhex] <file:line> — DESIGN.md ## 2 token 으로 교체 권장`.
**왜 갈랐는가**: 3~5자리 단축 hex 는 `#412` 같은 이슈·번호 표기와 구별되지 않아 카피 문자열·테스트 fixture 를 통째로 오탐한다(dogfood Round 11 실측 5/5 오탐 — 전부 「PR #412」). 대가는 «코드에 직접 쓴 `#abc` 단축 색을 놓친다»이며, 토큰 규율상 코드에 색 리터럴 자체를 두지 않으므로 수용한다.
- **Dart 계열** — 확장자 `.dart`: ① `Color\(0x[0-9A-Fa-f]{6,8}\)` 및 `Color\.from(ARGB|RGBO)\(` → `P1 [Design-rawhex] <file:line>` ② `\b(Colors|CupertinoColors)\.(?!transparent\b)[a-zA-Z]+` (프레임워크 기본 팔레트 사용 — 값을 박은 것은 아니나 프로젝트 토큰을 우회함). **`Colors.transparent` 는 제외한다** — 그것은 색이 아니라 «색을 칠하지 않는다»는 뜻이고 어느 디자인 시스템에서도 토큰화 대상이 아니다(dogfood Round 11 실측 2건 오탐 — 발견 60). 한 낱말 예외라 미탐 위험이 거의 없고, 매번 브리프 `## 14` 에 사유 주석을 쓰게 만드는 비용을 없앤다 → `P1 [Design-token-grep] <file:line> — 테마에서 가져오도록 교체 권장`. 두 라벨은 원인과 수정 방법이 달라 분리한다. **`[Design-token]`(접미 없음)은 reviewer 의 LLM 판정 차원이 이미 쓰는 라벨이므로 grep 결정분에는 `-grep` 접미를 붙인다** — `[Design-voice-grep]`/`[Design-voice]` 분리와 같은 규칙이며, 회귀 신호 집계가 라벨 정확 일치로 동작하므로 겹치면 오집계된다.
- **Dart 정의 라인 예외**: `static +const +(Color +)?[A-Za-z_][A-Za-z0-9_]* *= *Color\(` 형태의 줄은 토큰 *정의*이므로 제외한다(웹의 CSS custom property 정의 라인 예외와 동형). **타입 표기는 선택이다** — `static const paper50 = Color(0xFF…)` 처럼 중복 타입을 생략하는 것이 Dart 관례이고 `dart format`·lint 가 그쪽을 민다. 예전 패턴(`static +const +Color +…`)은 **타입을 적은 줄만** 잡아 토큰 정의 파일 전건을 오탐했다(dogfood Round 12 실측 — 발견 77).
- **`_theme` 매니페스트 예외 (발견 77)**: `docs/20-system/prototypes/_theme/manifest.json` 의 `source[]` 에 등록된 파일은 **토큰 정의·표시 표면**이므로 본 검사 대상이 아니다. R6-1 이 만드는 쇼케이스(`theme_gallery.dart`·`Theme.stories.tsx`)는 **토큰 값을 라벨로 보여 주는 것이 존재 이유**라 hex 문자열이 정상이고, 매 라운드 같은 오탐을 낸다. **파일명 휴리스틱이 아니라 매니페스트를 근거로 삼는다** — 「파일명으로 파일 전체를 빼지 않는다」는 원칙과 충돌하지 않고, 제품 코드의 사용처 위반은 그대로 잡힌다(그 파일들은 `source[]` 에 없다).
- **자가 검사 fixture 예외 (발견 77 ③)**: design gate 자가 검사 fixture 는 **색을 의도적으로 박아 둔 입력**이다(known-bad 의 2:1 대비 쌍이 그 파일의 존재 이유다). Dart 는 `@Tags(['design-gate'])` 가 붙은 파일, 웹은 게이트 어댑터 안의 인라인 HTML 이므로 `apps/**` 밖이다. **태그를 근거로 삼는다** — 그 태그는 이미 `--exclude-tags=design-gate` 로 통합 validate 에서 그 파일들을 빼는 데 쓰이고 있어 새 표식을 만들지 않는다.
- **한계(사실 기록)**: 본 검사는 문자열 검색이라 문법을 이해하지 못한다. **주석 안·문자열 리터럴 안의 색값도 함께 잡는다**(실측 확인). 그래서 본 항목은 기록 등급이며 진행을 차단하지 않는다. 문법을 이해하는 검사가 필요하면 Dart 의 공식 analyzer plugin 방식과 별도 패키지인 `custom_lint` 중 하나를 고른다(둘은 같은 것이 아니다 — ADR-059 D6).
> 웹 계열의 **제외 (ADR-072 — 정의/사용처 라인 구분)**: (a) DESIGN.md 자체 파일, (b) **CSS custom property *정의* 라인**(`--<name>: #hex` 형태 — 토큰 정의는 정상; dogfood `src/index.css :root` 오탐 해소). **파일명(`theme`/`tokens` 등)으로 파일 전체를 빼지 않는다**(사용처 위반 은폐 방지 — 정의/사용처는 (b) 라인 형태로만 구분). 검사 대상은 정의 밖 *사용처*(`color:#hex`·`background:#hex` 등) raw hex — 전면 `:root` 제외 금지.
5-2b. **UI 프로젝트 — voice grep** (정규식 deterministic — ADR-073 D6): 5-0 회수 변경 파일 중 **UI surface 파일 집합**(ADR-073#amend-2 결정 1)의 파일에서, 각 줄의 **렌더되는 텍스트 구간** — 따옴표 안 문자열(`"…"`·`'…'`·`` `…` ``) **과 JSX·HTML 텍스트 노드**(`>`와 다음 `<` 사이) — 에만 (a) placeholder 카피 패턴(`lorem ipsum`/`TODO copy`/`sample text`/`여기에 텍스트`), (b) DESIGN.md `## 10` "금지 표현"의 `[grep 가능]` 정규식을 적용한다(ADR-073#amend-2 결정 2 — 식별자·키 이름은 대상이 아니다). 일치 시 `P1 [Design-voice-grep] <file:line> — DESIGN.md ## 10 위반`(기록 등급 — 차단 아님) 기록. DESIGN.md `## 10` 부재 시 (a)만 수행. **DESIGN.md 자체는 grep 대상 제외** (규칙 정의 영역 — 5-2와 동형). 출력은 `대상 K파일 / 위반 M건`.
5-3. **UI 프로젝트 — 컴포넌트 인벤토리 drift** (best-effort heuristic): `src/components/`, `app/components/`, `components/`, `lib/widgets/`, `lib/**/widgets/` 중 존재하는 디렉터리의 컴포넌트 파일명 (예: `Button.tsx`, `Card.tsx`, `primary_button.dart`) 목록 ↔ DESIGN.md `## 7. Components` 본문에 명시된 컴포넌트 이름 비교. 코드에는 있지만 DESIGN.md 에 없는 컴포넌트 발견 시 `P1 [Design-inventory-drift] <component> — DESIGN.md ## 7 등록 권장` 기록. 반대 (DESIGN.md 에 있는데 코드에 없음) 는 unimplemented planned component → `P2 [Design-inventory-pending] <component>` 기록. **휴리스틱 한계 echo 권장** (`인벤토리 drift 검출은 free-form 문서 키워드 매칭 — false positive/negative 가능`).
5-4. **7-x Don'ts 위반 grep** (best-effort heuristic): ARCH 의 `## 7-1` / `## 7-2` / `## 7-5` 의 `### Don'ts` 본문에서 *명시적 금지 키워드* 를 추출 → 5-0 회수 변경 파일에서 해당 키워드 grep. 위반 의심 발견 시 `P1 [Arch-iface-violation] <file:line> — ARCH ## 7-N Don'ts 위반 의심: <키워드>` 기록 — **free-form 키워드 적중은 확정 위반이 아니라 *확인 후보*이므로 P1 로 적고 `/repair-milestone` 의 4-판정에서 확인한다**(확정 위반과 같은 라벨을 쓰면 라벨을 읽는 쪽이 차단력을 오인한다 — 실측). **휴리스틱 한계 echo 강제** (`Don'ts 키워드 추출은 free-form 텍스트 기반 — false negative 多. 본 grep 미작동 시 reviewer 의 design surface 위임이 보조 catch`).
> **7-3 백엔드 / 7-4 프론트 의 milestone-level deterministic gap 명시**: 현 ARCH TEMPLATE 의 `## 7-3` / `## 7-4` 는 `### Don'ts` 자리가 없어 본 5-4 grep 이 *skip* 한다(`## 7-5`는 `### Don'ts`를 가지므로 대상이다). 단 이것이 *전면 gap 은 아니다* — 7-3/7-4 결정과의 정합은 **per-task validate-workitem (validator.md 의 인터페이스 CHECK 규칙 — UI/API/CLI/7-x) 의 CHECK 단계가 task 단위로 점검**한다 (DB migration / 인증 / 트랜잭션 / 라우팅 / 상태관리 등). 즉 *milestone-level deterministic preflight* 에서만 빠지고, *task-level validation* 에서는 잡힌다. milestone 누적 drift 가 우려되면 stabilize 의 reviewer `code` surface (Step 5) 가 아키텍처 부채로 추가 catch. 향후 7-3/7-4 에 `### Don'ts` 자리를 TEMPLATE 에 추가하면 본 grep 도 확장 가능 (별도 ADR-073 amend 후보).
5-5. **해당 스택 부재 시 본 항목 skip** + skip 사유 echo: `[Design/Arch-iface] check skipped: <reason>`. 예: `[Design] check skipped: docs/20-system/DESIGN.md 부재 (비-UI 프로젝트)`.
6. **고-stakes 설계 근거 누락 (ADR-053 backstop, best-effort)**: ARCHITECTURE_OVERVIEW `## 7`의 *실제 작성된*(HTML 주석 placeholder 제외) 결정 블록에서 필수 칸(옵션≥2/신뢰도/재검토)이 비면 `P2 [Design-rationale] <위치>` 기록. **한계**: 고-stakes 결정을 한 줄 산문으로 쓴 경우는 못 잡음(휴리스틱).
7. **Skill 로스터 fan-out 정합 (cross-doc, deterministic — ADR-010#amend-3 README SSOT + STRUCTURE 로스터 정합 집행)**:
- `.claude/skills/*/` 디렉터리명 집합 ↔ `docs/00-meta/STRUCTURE.md`의 `Claude skill 본문` 행 괄호 목록이 일치하는가. 불일치 시 `P1 [Roster-drift] <skill> — STRUCTURE 로스터`.
- `README.md`/`README_ko.md`의 *자연어 호출 skill 목록 두 곳*(정책 요약 문단의 "Remaining skills/나머지 skill" + "For remaining skills/나머지 skill(...)" 명시 목록)이 서로 일치하고, `(.claude/skills 집합) − (.agents/skills wrapper 집합)`과 같은가. 불일치 시 `P1 [Roster-drift] <skill> — README 자연어 목록(<어느 위치>)`.
- **cross-LLM 리뷰 skill 등재 (D3 재발 지점, deterministic)**: `validate-plan`·`validate-discovery`·`validate-milestone` 각각이 `docs/00-meta/DELEGATION_STRATEGY.md` 위임 표에 등장하는가(skill 이름 grep). 미등재 시 `P2 [Roster-drift] <skill> — DELEGATION 위임 표`.
- **cross-LLM 리뷰 skill wrapper 존재 (ADR-010#amend-4, deterministic)**: `validate-plan`·`validate-discovery`·`validate-milestone`과 각 repair 짝(`repair-plan`·`repair-discovery`·`repair-milestone`)에 `.agents/skills/<name>/SKILL.md`가 존재하는가. 부재 시 `P1 [Roster-drift] <skill> — Codex wrapper 부재 (ADR-010#amend-4)`.
- 발견은 IMPROVEMENT_GUIDE에 기록(보고만 — 차단 X). **한계**: WORKFLOW 산문 흐름 등재는 본 grep 범위 밖(reviewer 위임이 보조 catch).
8. **검증 장치 노후 감지 `[Guard-drift]` (deterministic — ADR-063 D4)**: **침묵 우선 — 아래 (a)~(e) 가 전부 정상이면 출력에 한 줄도 남기지 않는다** (skip 사유 echo 도 하지 않는다. 정상 상태를 매번 보고하면 그것이 노이즈이고, 검증 장치가 매 마일스톤 변경되는 것은 정상이 아니다). **단 아래 "선행: 파일 부재 처리" 는 예외다** — 점검을 아예 수행하지 못했다는 사실은 침묵하면 안 된다.
- **선행: 파일 부재 처리** — `docs/00-meta/STACK_SETUP_PLAN.md` 는 baseline 이 아니라 `/bootstrap-stack` 생성물이다. 부재 시 본 항목 전체를 skip 하고 `Guard-drift check skipped: STACK_SETUP_PLAN.md 부재` 1줄만 남긴다(§1.0 의 `markdown-link-check` 미설치·원장 부재 선례와 동형).
- (a) **registry 경로 실재** — **대상 절·검사할 열·조건을 아래로 고정한다**(deterministic 이려면 같은 입력에 같은 판정이 나야 한다. 절마다 스키마가 다르므로 "registry 행"만으로는 무엇을 볼지 정해지지 않는다). 부재 시 `P2 [Guard-drift] <절>:<경로> 부재 — /stack-guard 재실행 권장`.
| 대상 절 | 검사할 열 | 조건 |
|---|---|---|
| `## E2E Smoke Registry` | `smoke 파일 경로` | 그 **행**의 `status` 가 `n/a` 가 아닐 때 |
| `## Design Gate Adapter` | `adapter path` **만** | **절**의 `status` 가 `ready` 일 때 |
| `## Dart Source Roots` | `경로` (pubspec 위치 기준 상대경로) | 그 절이 존재할 때 (비-Dart 스택은 `/bootstrap-stack` 이 절을 삭제하므로 대상 0) |
- **`output path` 는 검사하지 않는다** — `design-gate-shots/` 는 `.gitignore` 대상이고 adapter 가 매 실행 통째로 생성·초기화하는 ephemeral 산출물이라 **fresh clone·정리 직후 부재가 정상**이다. 검사하면 매 마일스톤 오탐이 나 침묵 우선 원칙과 충돌한다.
- `## Dependency Tools` 는 경로 열이 없어 대상이 아니다. `status: n/a`·미대상 행도 대상이 아니다 — e2e 비대상·비-UI 프로젝트에서 경로가 없는 것은 정상이다.
- 템플릿 예시 행(`(예: …)`)이 남아 있으면 그 경로는 실재하지 않으므로 그대로 `P2` 가 되며 처방(`/stack-guard` 재실행)이 정확하다 — 별도 예외를 두지 않는다.
- (b) **design gate copied-from 4방향 판정 (ADR-063#amend-1)** — `status: ready`인 경우에만 세 값을 읽는다: canonical sha C(`.claude/skills/stack-guard/assets/design-gate.mjs`), registry `copied-from` R, 사본 sha P(`adapter path`). (i) P = R, C ≠ R → `P2 [Guard-drift] canonical 갱신됨 — /stack-guard 재실행 권장(무수정 사본이라 교체된다)` (ii) P ≠ R, C = R → `P2 [Guard-drift] 사본 로컬 수정됨 — 재실행해도 덮어쓰지 않음(diff 보고). 수정분을 canonical로 역류할지 검토: IMPROVEMENT_GUIDE [ADR-candidate]` (iii) P = C ≠ R → `P2 [Guard-drift] registry만 낡음(코드 동일) — copied-from 갱신 권장(/stack-guard 재실행이 갱신)` (iv) P ≠ R, C ≠ R, P ≠ C → 둘 다 + `사용자 결정 필요`. 전부 읽기 전용 — mtime·날짜 비교 없음; sha256 도구 부재 시 skip + 사유 echo.
- (c) **등록 밖 소스 디렉터리** — **소스 루트 registry 를 갖는 스택에서만 수행한다.** 현재 그 registry 를 갖는 것은 `## Dart Source Roots`(Dart/Flutter)뿐이며, **비-Dart 스택에서는 `/bootstrap-stack` 이 그 절을 삭제하므로 판정 기준이 없다 → 이 항목을 건너뛴다**(사유 echo 불요 — 침묵). 기준 없이 "등록 밖"을 판정하면 TS/Python/Go 의 `src/`·`tests/` 가 매 마일스톤 오탐으로 찍혀 침묵 우선 원칙과 정면 충돌한다. Dart 스택에서 발견 시 `P2 [Guard-drift] 등록 밖 Dart source root: <경로> — /stack-guard 재실행 권장`.
- (d) **probe 판정 기록** — `## 통합 명령 사용법` 의 `probe smoke:` 값으로 판정한다.
- **정상(무출력)**: `PASS (probe verified, …)` 2종 · **`PROBE OK, PROJECT FAIL`**. 후자는 probe 전 회차가 기대대로였고 **프로젝트 코드만** 실패한 상태이므로 검증 장치의 노후가 아니다 — 재실행해도 같은 결과이니 처방이 무의미하고, 그 프로젝트 실패는 졸업 item 2(`통합 validate Pass`)와 단계 3 이 이미 잡는다.
- **`P2 [Guard-drift] validate 판정력 미검증 — /stack-guard 재실행 권장`**: `PROBE FAIL(<단계>)` · `PARTIAL` · `SKIPPED (…)` · **줄 자체 부재**.
- **여기서 probe 를 다시 돌리지 않는다 — 기록된 문자열만 읽는다**(read-only 계약). 이것이 `/stack-guard` 의 미검증 상태가 조용히 잊히지 않는 유일한 경로다(ADR-063 D3 기록 → D4 회수).
- (e) **visual-QA 전제 기록 (ADR-058#amend-3 결정 5)** — `## 통합 명령 사용법`의 `visual-qa:` 값이 `PENDING`이면 `P2 [Guard-drift] visual-QA 전제 미준비 — /plan-workitem 이 전제 line item authoring 후 /stack-guard 재실행 권장`. 값이 `READY`거나, 줄이 없거나(비-UI·비대상), **줄은 있으나 미치환 angle-bracket placeholder면 침묵한다** — 비-UI 프로젝트에는 템플릿 복사분이 그대로 남을 수 있고(`/bootstrap-stack`은 *절* 단위로만 비해당 삭제한다) 그 placeholder 문자열 자체에 `PENDING`이 들어 있어, 명시하지 않으면 deterministic 검사가 오발화한다. **여기서 spec을 실행하지 않는다 — 기록된 문자열만 읽는다**((d)와 동형, read-only 계약).
- `validate` 4단계 커버리지의 **재측정**은 본 항목이 아니다 — 실측은 `/stack-guard` 재실행 시 probe 가 하고, 본 항목은 (d) 로 그 **기록**만 읽는다(ADR-063 D1·D4 — 중복 회피 + read-only 유지).
- 회수 경로는 기존과 동일하다 — IMPROVEMENT_GUIDE 에 기록하면 다음 `/plan-milestone` R0 의 open 항목 회수가 사용자에게 재실행을 안내한다(`[ADR-candidate]`·`[Stack-drift]` 와 동형, 신설 없음).
본 단계는 모두 *보고만* — 발견이 있어도 stabilize 후속 단계 차단 X (LLM 위임 단계로 계속). 후속 처리는 ADR-057#amend-3 결정 6 분기(기존 task 약속 결함=repair, cross-cutting=repair-milestone, 담당 없는 새 범위=사용자 보고+다음 M 후보)를 따른다.
**봉인 후 새 결정 등재 (ADR-060 D11)**: 마일스톤 점검에서 *기획 결정*(사용자가 정하거나 승인해야 할 것)이 드러나면 `docs/10-charter/DECISION_REGISTER.md`에 `status: open` + `- 발견: 봉인 후 (M<N>)` 줄로 등재한다. QA_FINDINGS/IMPROVEMENT_GUIDE는 *결함·개선*을 담고 원장은 *결정*을 담는다(둘 다 해당하면 양쪽에 각자 형식으로). 본 skill의 read-only 계약은 불변 — 원장 등재는 기존 QA_FINDINGS 기록과 동일한 정상 책임 범위다. 원장 파일이 없으면 등재를 건너뛰고 보고만 한다.
**review-doc 책임 분담**: [review-doc](../review-doc/SKILL.md)은 *단일 문서 ad-hoc 검토*에 한정. cross-doc / link / FAC↔AC는 본 deterministic preflight가 담당 — review-doc을 `--all`/`--milestone` 모드로 확장하지 않는다.
### 1.5. Graduation 판정 입력 수집 (ADR-068 D3)
MILESTONE 문서의 `## 5. 완료 기준` 각 항목을 아래 deterministic 평가로 계산한다(LLM 즉흥 판정 금지). **여기서는 계산만 하고 회고에 기록하지 않는다** — 최종 기록은 단계 8이다.
**정적 항목 (본 단계에서 확정하고 이후 재계산하지 않는다)** — 본 skill 자신이 단계 4~6에서 문서를 쓰므로, 그 쓰기가 아래 판정에 영향을 주지 않도록 여기서 값을 고정한다.
- **item 1 — 마감 스냅샷 유효**: 본 milestone에 속한 모든 task 파일(`docs/30-workitems/tasks/T-*.md`)에 대해
1. `## 0. Status` 값이 `done`인가.
2. `## 8. 메모`의 **HTML 주석 밖** 마지막 `- closure` 줄이 존재하는가.
3. 그 줄의 `verdict`가 `Pass` 또는 `Pending Acceptance`인가. **`Needs Fix`는 미충족이다.**
4. 그 줄의 `audit`가 `complete`인가. `미완:<축>`이면 미충족이며 graduation은 `BLOCKED (audit incomplete: <축>)`이다.
- 하나라도 어긋나면 그 task를 미충족 목록에 넣는다(사유를 `status` / `closure 부재` / `verdict` / `audit`로 구분해 적는다). **`## 0. Status`가 `done`인데 `- closure` 줄만 없는 task**의 처방은 **«사용자가 그 task `## 8`에 `- closure` 줄을 직접 기재»** 다(ADR-068 D3 item 1). **`done`이 아닌 task에는 이 처방을 내지 않는다** — 그 task는 아직 task 층이고 단계 2가 종료시킨다. **`/validate-workitem`·`/finalize-workitem` 재실행도 처방하지 않는다** — 전자는 폐쇄 상태에서 종료하고 후자는 `done`에 read-only no-op이므로 그 처방은 실행 불가다. 수기 기재는 이 개선 이전에 마감된 task에서만 발생한다.
- **채점표(`docs/40-validation/reports/`)를 읽지 않는다.** 졸업 판정은 커밋된 task 문서만 본다(ADR-068 D2).
- **item 4 — 관측 AC receipt 유효**: **각 task `## 6-1`에서 modality가 `[사용자 관측]`·`[플랫폼 관측]`인 AC를 전수 회수**하고, 그 AC마다 task `## 8`의 (HTML 주석 밖) **마지막 이벤트가 `- ac-acceptance`** 인가(ADR-065 D3 판독 규칙 2)를 본다. `- ac-pending`·`- invalidated`이거나 이벤트가 없으면 미충족. 관측 modality AC가 0건인 task는 해당 없음.
- `- closure`의 `관측대기=` 목록은 **회수 편의용 색인**일 뿐이다. 그 목록만 읽으면 안 된다 — 수기 기재분이나 구 정의로 쓰인 closure 줄이 목록을 비워 둘 수 있고, 그러면 receipt 없는 관측 AC가 통과한다. **어긋나면 `## 6-1` 스캔 결과를 신뢰한다.**
**동적 항목 (단계 3~6 결과를 반영해 단계 8에서 확정한다)**
- **item 2 — 통합 `validate` Pass**: 단계 3에서 실행한 결과의 exit code 0. 마일스톤 층 수리가 추가한 회귀 테스트도 이 명령에 묶여 있으므로 함께 검사된다(ADR-068 D6).
- **item 3 — E2E Pass**: 단계 3의 e2e 상태 판정을 그대로 반영(ADR-052#amend-1 5상태).
- **`NOT_APPLICABLE`** (비-UI ∧ item 6에 e2e 미선언) → 해당 없음(통과).
- **`PASS`** → 통과.
- **`EMPTY`** (선언된 e2e 디렉터리 하위에서 **실행된** 테스트 0개) → `졸업 가능: NO` (hard) + `Needs E2E Smoke`. 프로비저닝 단계와 달리 졸업 시점에는 차단한다(ADR-068 D3 item 3). *registry 미등록은 이 상태의 사유가 아니다* — 미등록은 `P1 [E2E-registry]` 기록 대상일 뿐이다.
- **`FAIL`** → `졸업 가능: NO` (hard).
- **`BLOCKED_ENV`** → **item 3 미충족** (hard, blocked-on-env). real failure가 아니므로 라벨을 구분해 출력하고 환경 복구를 안내한다. **최종 graduation은 `NO`가 아니라 단계 8이 확정하는 `BLOCKED (e2e blocked-on-env: <target>)`이다**(ADR-068 D4 — 우선순위 `BLOCKED` > `NO`).
- **item 5 — P0 severity finding 0건**: `docs/40-validation/QA_FINDINGS.md`의 본 milestone 헤더(`## M-N`) 아래 `### P0` 섹션에서 **`status: resolved`가 아닌 항목 수 0**. **«항목» 은 `- **<ID>** |` 로 시작하는 줄만 센다** — 항목의 하위 산문 줄에도 `status: open` 같은 문자열이 나올 수 있고(수리 라운드가 «`status: open` 유지» 라고 적는다), 문자열 grep 으로 세면 계수가 부풀려진다(dogfood Round 11 실측 — 2건이 4건으로). 단계 4의 qa 팬아웃이 새 P0를 등재할 수 있으므로 단계 8에서 확정한다.
- **item 6 — (선택) 본 마일스톤 한정 추가 기준**: 본문 텍스트 그대로 평가(사용자가 자유 기재한 영역 — 해당 항목만 LLM 해석 허용).
**본 단계의 출력** (판정을 회고에 쓰지 않는다):
- item 1 미충족 task 목록 (사유별 — status / closure 부재 / verdict / audit)
- item 4 미충족 `<task-id>:AC-N` 목록
- item 1·4가 전부 충족이면 그 사실 한 줄
- **조기 종료 옵션 (ADR-068 D3)**: **item 1이 미충족이면** 그 목록과 함께 조기 종료 옵션을 사용자에게 제시한다(강제 종료 아님 — 계속을 택할 수 있다). item 1이 깨진 상태로 단계 4~6 팬아웃을 끝까지 도는 것은 낭비다. **item 4만 미충족인 경우에는 제시하지 않는다** — 결함이 아니라 «사람 확인만 남은» 상태이므로 되돌아가 고칠 것이 없고, 그대로 진행해 단계 8에서 `PENDING_ACCEPTANCE`를 확정한다.
2. 각 task의 status를 점검 — `done`이 아닌 항목이 있으면 명단을 출력하고 종료(완료를 강제하지 않음).
3. 통합 `validate` 명령을 실행한다 + **e2e 필요성 판정 후 필수 실행**(ADR-052 — silent-skip 금지):
- **3-a. e2e 필요성 판정**: 본 마일스톤이 e2e를 필요로 하는가를 deterministic 신호로 결정한다 —
(i) **UI 프로젝트** (ADR-073 D9 다중신호 UI 판정: DESIGN.md status≠draft, 또는 status=draft+신호≥1) → 필요,
(ii) graduation item 6 `(선택) 본 마일스톤 한정 추가 기준`이 e2e를 명시 선언 → 필요.
둘 다 아니면 *불필요* → `E2E: 불필요 (비-UI ∧ item 6 미선언)` 한 줄 echo 후 통과 처리(이 경우만 skip 허용 — 사유 명시).
- **3-b. 필요 시 `validate:e2e` 실행 (silent-skip 금지)**: e2e가 필요하면 반드시 실행하고 **구조화된 러너 출력**을 수집해 ADR-052#amend-1의 5상태로 분류한다. exit code만으로 판정하지 않는다.
- **선행 확인**: `STACK_SETUP_PLAN.md ## E2E Smoke Registry`를 읽어 **선언된 runtime target 목록**과 각 target의 등록 여부를 회수한다. target이 둘 이상이면 아래 판정을 **target마다 따로** 내고, 하나라도 `PASS`가 아니면 졸업은 차단된다. **판정이 target마다 나므로 실행도 target마다 한다** — target이 둘 이상이면 `validate:e2e:<target>`을 target마다(또는 집계 `validate:e2e` 1회) 호출하고 target별 구조화 출력으로 각각 판정한다(ADR-059#amend-1). native target의 device id는 `scripts/e2e-target.mjs`가 registry의 *실행 대상 선택 규칙* 칸을 `flutter devices --machine` 출력에 대입해 실행 시점에 얻는다 — 규칙 문자열을 명령에 그대로 넣지 않는다. target이 하나면 1회 호출로 충분하다. **등록이 없는 target도 명령은 실행한다** — 판정은 아래 경로 기준으로 하고 `P1 [E2E-registry] <target> — canonical smoke 미등록`만 기록한다(ADR-052#amend-1 결정 4).
- **판정 규칙은 여기서 재서술하지 않는다** — `PASS` 필요조건 넷과 **판정 순서**(`FAIL(wiring)`·`BLOCKED_ENV` → `EMPTY` → `FAIL(project)` → `PASS`, 먼저 성립하는 상태로 확정)는 **ADR-052#amend-1 결정 3이 SSOT**다. 그 절을 읽고 그대로 적용한다. 순서를 뒤집으면 기동 불가나 테스트 실패를 `EMPTY`로 오분류해 "테스트를 쓰라"는 틀린 처방이 나간다. 종료코드가 0이어도 e2e 경로 밖 suite만 돌았으면 `EMPTY`다.
- `FAIL`은 **`FAIL(wiring)`/`FAIL(project)` 하위 라벨을 구분해 기록**하고, `BLOCKED_ENV`는 real failure와 라벨을 구분한다 — 후속 처리 주체가 다르다(stack-guard 산출물 / 프로젝트 코드 / 환경 복구).
- **host 제약으로 `BLOCKED_ENV`인 target은 registry의 `마지막 PASS(host·날짜·커밋)` 칸을 증거로 쓸 수 있다**(예: Windows에서 iOS). **유효 조건은 하나 — `git diff --name-only <기록 커밋> <판정 커밋> -- . ':(exclude)docs/' ':(exclude)*.md'`가 종료코드 0으로 끝나고 출력이 비어 있을 때만**(기록과 판정 사이에 문서 밖 변경이 없음) 인정하고 그 target을 `PASS`로 본다. 출력이 있거나 명령이 실패하면(기록 커밋이 없으면 오류로 끝나도 출력이 비어 있다) `BLOCKED_ENV`를 유지한다 — 판정은 이 명령 하나이며 **"그 사이 앱 코드가 바뀌었는지"를 사람이 판단하지 않는다**(ADR-059 D4·#amend-3 — 증거 기록 커밋 자체가 문서 변경이라 이전의 «커밋 동일» 조건은 구성상 충족 불가능했다). 증거를 채택했으면 출력에 `<target>: PASS (registry 증거 — <host>·<커밋>)`로 근거를 남긴다.
- 출력 문자열 매칭은 위 판정을 보조하는 용도로만 쓴다.
- **stabilize는 read-only다 — 여기서 e2e/코드를 고치지 않는다.** 실패(real/env 무관)는 단계 8의 `/repair-milestone` 분기로 텍스트 라우팅만 한다.
3-P. **(옵션) 탐색적 QA via browser/E2E MCP** (ADR-048#d6 registry-driven / ADR-043 보안 — STACK_SETUP_PLAN `## Optional MCP Connectors`에 browser/E2E capability MCP가 *등재 + `agent access` 부여* + UI 프로젝트일 때만; 미등재·access 미부여·비-UI는 silent skip + 사유 echo): 실제 앱을 구동해 본 마일스톤 feature의 시나리오(happy/alt/fail) + qa 엣지케이스를 *탐색*한다(accessibility 트리·클릭/입력·스크린샷·네트워크). 발견한 결함을 `docs/40-validation/QA_FINDINGS.md`에 기록하고, **재현 케이스를 영속 E2E 테스트(`validate:e2e`에 묶이는 커밋 가능한 파일)로 남길 것을 권장**(자동 커밋 X — stabilize는 코드·커밋 금지, 후속 task 제안). 실패는 `Type: bugfix` task(ADR-039)로 라우팅. **보안: `browser_run_code_unsafe`류 RCE급 도구는 사용하지 않는다** — accessibility snapshot·표준 브라우저 조작만. **본 단계는 메인 세션이 직접 수행한다** — 탐색 중 임시 스크립트가 필요하면 메인이 scratch 경로에 두고 경로를 넘긴다(ADR-050#amend-2 결정 2 — 프로젝트 트리에 만들지 않는다).
3-V. **경험 게이트 — 구현 화면 vs 승인 스냅샷 대조 (ADR-072 D7, UI 확정 마일스톤 한정)**: MCP 불요 체계 감사. **실행 자체는 의무 — silent skip 금지**(미실행 사유 echo; 판정은 report-only). **본 단계도 메인 세션이 직접 수행한다** — 렌더·대조에 쓰는 임시 스크립트는 메인이 scratch 경로에 두고 경로를 넘긴다(ADR-050#amend-2 결정 2).
- (a) `docs/20-system/prototypes/M<N>/manifest.json`을 읽는다(**이 M이 등록한 화면이 있어야 하는데** 부재면 `blocked-on-env`가 아니라 계약 결함 `P0 [Experience-contract] 매니페스트 부재`. 전 화면이 `프로토타입 면제:`이거나 이전 M 재사용이면 이 M 파일 부재가 정상이며, 그때는 feature `## 7`의 `프로토타입:` id가 가리키는 M의 매니페스트·스냅샷을 읽는다). 화면마다 두 렌더를 만든다(**매니페스트에 없는 UI 화면 — `프로토타입 면제:` feature·이전 M 승인본 재사용 — 은 ②만 만들어 (b)의 ② 경로로 대조한다, ADR-072 D7**): **① 스토리/위젯 렌더** — `validate:design -- --manifest <경로> --snapshot docs/40-validation/visual/M-N/proto/`; **② 제품 렌더** — 웹은 dev server 기동(명령은 STACK_SETUP_PLAN·`package.json` `dev`/`start`에서 회수, readiness 대기, 종료 시 kill — 재사용 규칙 기존대로) 후 매니페스트 `product_entry`(`null`이면 «제품 진입점 미기록» 사유 echo + ① 대조만)의 라우트를 프로필 뷰포트로 캡처해 `docs/40-validation/visual/M-N/app/`, Flutter는 통합 테스트 스크린샷이 가능하면 그것, 아니면 `blocked-on-env` 명시(ADR-059#amend-1 결정 4). blocker가 있어 `--snapshot` 복사에서 빠진 화면은 `design-gate-shots/`의 캡처를 ①로 쓴다.
- (b) 각 화면에 대해 **승인 스냅샷(`snapshots/`) ↔ ① ↔ ②**를 Read(멀티모달)로 나란히 대조한다. 앵커 위계: ① 승인 스냅샷(존재 시) ② DESIGN.md §2/§7/§9/§10 파생 체크리스트(면제·부재 화면). 관점: 레이아웃·상태·카피·토큰 준수 — 픽셀 일치가 아니라 경험 계약 준수. ① vs 승인 스냅샷 불일치는 «승인 후 UI 코드 변경»이므로 같은 M 재승인 또는 다음 M `supersedes` 등재 여부를 함께 본다. 화면의 현재 기준선은 «가장 최근 M의 등록·supersedes»다(ADR-072 D5-5).
- (c) 불일치는 QA_FINDINGS에 `P1 [Experience-drift] <screen> — <1줄> (앵커: 스냅샷|DESIGN 파생 / 렌더: proto|app)` report-only. 판독 불확실은 «판독 불확실» 명시. 시각 불일치가 봉인 AC·PX↔AC 위반을 동반하고 재현되면 그것은 별도 P0 결함(재현 줄 포함)으로 등재한다(ADR-070 D1).
- (d) 단계 8 출력에 갤러리 경로 + «사용자 육안 확인은 `/accept-milestone <M>`이 수행한다» 1줄. 관측 modality AC가 1건이라도 있으면 사실상 필수 경로(ADR-068 D3).
- Codex: 캡처까지 수행 + 대조는 «사용자 수동 검토» 안내로 degrade.
4. **병렬 qa verifier 팬아웃 — 고정 1개가 아니라 *필요한 만큼*** (feature / user-flow / surface 단위로 분할). 메인 세션이 본 마일스톤의 feature·핵심 시나리오·surface 목록을 회수해 *독립 점검 단위*로 쪼개고, 각 단위마다 qa agent를 1개씩 병렬 위임한다(회귀·엣지케이스 점검). qa는 보고만 한다(qa.md의 tools에 Write 없음).
- **위임 시 ADR-046#d3 적용: finding은 cap 때문에 누락하지 말고 전수 반환 — cap은 서술/과정 설명에만.**
- **입력에 «작업 파일을 프로젝트에 만들지 않는다 — scratch 경로: <세션 scratchpad>/stabilize-M<N>/» 한 줄(ADR-050#amend-2).**
- **회수 분할 (ADR-075 D11 (b))**: 한 단위의 회수 문서가 **10개 이상이면** 축·범위를 더 나눠 dispatch 한다 — 한 dispatch 에 전부 싣지 않는다.
- **입력에 ADR-070 D1 severity 정의표를 그대로 전달한다**(기준 전달 — 사전판정 아님). P0 재현 줄 형식도 함께 전달한다.
- **축 미반환 회수 규율 (ADR-075 D13)**: qa 단위가 구조화 반환 없이 멈추면 ① 1회 재개(같은 단위·같은 형식, 이미 확립한 finding 전량 포함 명시) → ② 다른 qa에 재위임 → ③ 메인이 그 단위를 직접 감사 → ④ 그래도 불가하면 **`QA_FINDINGS.md`의 본 마일스톤 `### P0`에 `- **M<N>-audit-<K>** | P0 | [관측됨] | linked: M<N> | status: open` + 하위 줄 `- 감사 미완(unavailable): <단위> — <4단계 실패 사유>`를 등재하고**, 단계 8의 graduation을 `BLOCKED (audit incomplete: <단위>)`로 기록한다(ADR-068 D4). **qa 팬아웃은 졸업 predicate ⑤의 유일한 입력이므로, 돌지 않은 감사를 근거로 "P0 0건"을 단정하지 않는다.**
- 다음 라운드에서 그 단위의 감사가 성공하면 본 skill이 그 `M<N>-audit-<K>` 항목의 `status: open`을 `resolved`로 갱신한다(**본 skill이 자기가 만든 감사-미완 항목에 한해 status를 닫는 유일한 예외** — 그 밖의 finding status는 `/repair-milestone`·`/repair-acceptance` 소유).
- **Codex: 서브에이전트는 GA이나 본 저장소가 Claude persona 위임을 Codex subagent로 아직 매핑하지 않아 순차 단일 실행으로 degrade** (동일한 분할 단위를 *순차 단일 qa 호출*로 한 단위씩 처리, 결과는 동일하게 누적).
5. **병렬 reviewer verifier 팬아웃 — 필요한 만큼** (리팩토링 후보·아키텍처 부채). 각 reviewer 입력에 Clean Code 6항목 체크리스트(ADR-006) + `review surface: code` + **ADR-046#d3(finding 전수 반환 — report-only)** 를 명시 전달한다. **UI 프로젝트는 추가로 `review surface: design` reviewer를 1개 더 팬아웃** — DESIGN.md `## 9. Do's and Don'ts` 위반 의심 grep 결과를 입력으로 받아 비판적 검토. reviewer도 보고만 한다. design reviewer 입력에는 grep 결과에 더해 **렌더 증거**를 주입한다 — §3-V 갤러리 경로(`proto/`·`app/`) + **승인 스냅샷 경로(매니페스트)** + visual-qa.spec 최근 결과. reviewer는 Read로 이미지를 열람한다(ADR-073 D8). Codex: 경로 echo + 텍스트 결과만 전달로 degrade.
- **입력에 «작업 파일을 프로젝트에 만들지 않는다 — scratch 경로: <세션 scratchpad>/stabilize-M<N>/» 한 줄(ADR-050#amend-2).**
- **회수 분할 (ADR-075 D11 (b))**: 한 단위의 회수 문서가 **10개 이상이면** 축·범위를 더 나눠 dispatch 한다 — 한 dispatch 에 전부 싣지 않는다.
- **Codex: 서브에이전트는 GA이나 본 저장소가 Claude persona 위임을 Codex subagent로 아직 매핑하지 않아 순차 단일 실행으로 degrade** (분할 단위를 순차 reviewer 호출로 처리).
- **축 미반환 회수 규율**: 단계 4와 같은 4단계를 밟는다. 단 ④에 도달해도 **판정을 바꾸지 않는다** — reviewer 결과는 졸업 predicate 입력이 아니다(report-only). `IMPROVEMENT_GUIDE.md`에 `P2 [Audit-unavailable] reviewer:<단위> — 감사 미완`을 기록하고 단계 8 출력에 사유를 echo한다. 미반환을 P0로 올리면 "결과 없음"이 "결과 있음"(정상 반환한 P1 부채는 졸업을 막지 않는다)보다 강해지는 역전이 생긴다. reviewer가 정상 반환한 **결함**은 6-S가 QA_FINDINGS로 보내 졸업 입력이 된다(ADR-070 D7). 미반환 자체는 여전히 판정을 바꾸지 않는다.
6-S. **메인 세션 self-synthesis (report-only 계약 유지)**: 위 4·5의 *모든* 병렬 verifier가 반환한 보고를 메인 세션이 직접 종합한다.
- **task report 잔여 finding도 이 종합의 입력이다 (ADR-076 D10)**: 본 마일스톤 산하 `done` task의 `docs/40-validation/reports/<task-id>.md`에서 `## 잔여 finding` 절의 줄을 4·5의 보고와 함께 받는다. 그 절이 없는 옛 report와 report가 없는 task(checkout-local)는 건너뛰고 건수만 출력한다. 출처 값은 `report-residual(<task-id>)`다. P0·P1은 아래 채택 전 검토를 똑같이 거치며 등급은 ADR-070 D1 표로 다시 판정한다(validate의 등급과 다르면 판정 근거 한 줄에 적는다). **P2는 채택 전 검토 대상이 아니다**(ADR-070 D2는 P0·P1) — 성격 라우팅만 거치고, 아래 5-D와 같은 동일성 기준으로 이미 있으면 적지 않는다. report 줄은 과거 시점의 기록이라 verifier 보고와 다른 예외가 둘 있다 — 그 task `## 8`에 같은 라벨·위치의 repair `Reject-*` 결정 줄이 있으면 task 층에서 이미 판정됐으므로 등재하지 않고, P0·P1 중 뒤 task가 이미 고쳐 현재 코드·문서에 근거가 없는 줄도 등재하지 않는다(둘 다 건수만 출력). P2는 해소 여부를 여기서 판정하지 않는다(`/repair-milestone`이 4-판정으로 닫는다).
- **성격 기준 라우팅 (ADR-070 D7)**: 보고자와 무관하게 결함(동작·데이터·보안·계약 위반)은 `docs/40-validation/QA_FINDINGS.md`, 개선(구조·중복·명명·부채·문서 정합)은 `docs/40-validation/IMPROVEMENT_GUIDE.md`에 적는다. 항목 하위 줄 `- 출처: qa | reviewer(<surface>) | preflight | peer(<tag>) | report-residual(<task-id>)`로 보고자를 남긴다.
- **채택 전 검토 (ADR-070 D2 — 등재 전 1회, P0·P1 전부)**: 각 항목을 `confirmed | rejected-fp | rejected-context | needs-confirmation | unsubstantiated` 중 하나로 판정해 `decision:`에 적는다. **P0는 `- 재현:` 절차를 qa 단발 sub-call(명령 1회면 메인)이 다시 실행해 관측했을 때만 `confirmed`다. 코드·문서·상태를 바꾸는 재현은 메인이 직접 돌리지 않는다 — 단계 3이 띄운 환경에서 qa로, 불가하면 `needs-confirmation`(확인 방법 = `/repair-milestone`).** P1은 보고의 재현·근거를 코드·문서로 확인하면 `confirmed`(재실행 불요). **P0인데** 재현 줄이 없으면 `[Finding-unreproduced]`를 붙여 `needs-confirmation`(하위 줄 `- 대상 / 확인 방법 / 막힌 이유` 3필드)으로 둔다(ADR-070 D1 — 이 조건은 P0 한정이다. 정적으로 확인되는 P1에 재현을 요구하지 않는다). `rejected-*`·`unsubstantiated`는 `status: resolved`로 등재한다(unsubstantiated는 `### 관찰 메모`에 한 줄). 판정 근거 한 줄을 남긴다. 이것은 사후 판정이며 verifier에게 심각도를 미리 지정하는 것이 아니다.
- **같은 마일스톤의 2회차 이후 stabilize에서 새로 등재하는 P0**는 ADR-070 D6의 4가지 사실 줄(이전 감사 범위 / 새 근거 / 종결 항목과 동일성 / 종결을 뒤집는 증거)을 하위에 적는다. 종결 항목과 동일하면 새 ID를 만들지 않는다 — **이번 판정이 `confirmed`일 때만** 그 항목을 `status: open`으로 재개하고 재개 사유(= 종결을 뒤집는 증거)를 적는다. `rejected-*`·`unsubstantiated`로 다시 판정됐으면 **종결을 유지하고** 그 항목 하위에 `- 재보고: <날짜> — 동일 증상, 종결 유지(<decision> 근거 한 줄)`만 남긴다(ADR-070 D6 — 근거 없이 재개하면 같은 오탐이 매 라운드 졸업을 막는다).
- **no-cap-drop (ADR-046#d3)**: 여러 verifier의 finding을 합칠 때도 cap 때문에 누락 금지 — finding은 전수 기록하고, cap은 *대화 출력의 서술/과정 요약*에만 적용한다.
- **dedup**: 분할 단위가 겹쳐 동일 finding이 여러 verifier에서 중복 반환될 수 있다. 동일 `<라벨> <file:line> <증상>` 항목은 1건으로 병합하되, *서로 다른 단위에서 관측됨*은 근거로 보존(병합 시 관측 surface를 한 줄로 합산).
- reviewer 결과에 구조 변경이 필요해 보이면 메인 세션에 architect 추가 호출을 텍스트로 제안.
- **stabilize는 여전히 read-only다** — self-synthesis는 *문서 누적 기록*까지만. 코드·커밋·workitem status 변경 없음(도입부 책임 경계 정합).
5-D. **등재 전 dedup (필수 — ADR-070#amend-1 결정 2)**: 6-S 검토를 통과한 각 P0·P1 을 원장에 적기 **전에**, 같은 사실이 본 마일스톤 원장에 이미 있는지 본다. 동일성 기준은 «`<라벨> <file:line> <증상>`»(§1.0 3-1 과 같다)이며 preflight 정규식 발화처럼 file:line 이 여럿인 항목은 **경로 집합**으로 비교한다. **이미 있으면 `status` 가 `open` 이든 `resolved` 든 새 ID 를 만들지 않는다** — 대신 그 항목에 `- 재발화: <YYYY-MM-DD> (stabilize <N>회차) — 판정 유지` 한 줄만 붙인다. 경로 집합이 달라졌으면 새 항목이다. **이 규칙이 없으면 preflight 의 구조적 오탐이 매 라운드 새 ID 로 재생산된다**(dogfood Round 11 실측 — raw-hex 5건·voice 10건이 3회차 내내 동일하게 재발화).
6. 미흡한 ADR 후보 제안 — 마일스톤 중에 내려진 결정인데 ADR이 없는 것을 식별하고, **IMPROVEMENT_GUIDE 항목 스키마(필수 4필드)로 영속 기록**한다: `- **M<N>-adrc-<K>** | P2 | [관측됨] | linked: M<N> | status: open` + 하위 줄 `- [ADR-candidate] <결정 한 줄> — 회수: 다음 /plan-milestone R0` (ADR-000#amend-2 — 기록 전 _ADR_GUIDE의 ADR 대상 기준 self-check, 남발 방지. 후보 ≠ 자동 작성). ADR 후보 기준에 "layer 경계·의존성 규칙 변경"도 포함(ADR-006 정책).
- ARCHITECTURE_OVERVIEW.md에 비해당 7-x sub-section이 *잔존*하면 IMPROVEMENT_GUIDE.md에 P2 보고 — *"조건부 sub-section 미삭제. /bootstrap-stack 재실행 또는 수동 삭제 권장."*
- layer 경계·의존성 규칙 변경(ARCHITECTURE_OVERVIEW의 ## 3-1)이 마일스톤 중에 발생했으면 ADR 후보로 표시한다(정책: ADR-006).
- **[Stack-drift] ADR-101 staleness 감지 (ADR-055, report-only)**: 본 마일스톤에서 순증한 dep(산하 task `## 3` install line-item / lockfile diff)을 T2 임계 카테고리(언어/런타임/프레임워크/DB·영속성/인증/배포 토폴로지/핵심 외부 의존, 또는 ARCH §7 결정·charter §7 제약을 뒤엎음 — 개별로 사소해도 cluster로 넘으면 포함)와 대조. 임계를 넘으면 `P2 [Stack-drift] ADR-101 stale — 누적 dep가 T2 임계 도달 → /bootstrap-stack --migrate 후보 또는 ADR-101 amend`를 IMPROVEMENT_GUIDE에 기록. 임계 미달 누적은 침묵(피로 방지 — ADR-101은 dep 원장이 아님). 휴리스틱 한계 echo(키워드/lockfile diff 기반 — false negative 가능).
### 6.5. DISCOVERY ↔ Charter staleness 감지 (ADR-035#amend-1)
다음 4 시그널을 점검한다 (보고만, 자동 차단 X — validator 책임 경계 정합).
1. **«Charter 마지막 커밋 이후 DISCOVERY에 Charter 공급 절의 실질 변경이 있는가»**(ADR-035#amend-4)로 판정한다. 방법: `C=$(git log -1 --format=%H -- docs/10-charter/PROJECT_CHARTER.md)`; `git diff -U0 $C -- docs/10-charter/DISCOVERY.md`(워킹트리 변경 포함)의 각 hunk를 **새 파일의 줄 범위(`@@ -a,b +c,d @@`의 c..c+d)**로 현재 DISCOVERY의 `## N.` 헤딩 줄 범위에 대응시켜 절을 정한다(hunk 헤더의 함수명 칸은 markdown에서 절 제목을 보장하지 않으므로 쓰지 않는다). **제외**: `## 14`·`## 15`·`### Repair history` 절의 hunk 전부, 그리고 `## 12` 절의 hunk 중 변경 줄이 전부 원장 상호참조 패턴(`원장 D-[0-9]{3}`·`risk-accepted`)만 담은 것. `## 12`의 그 밖의 변경(가정 추가·결과 변경)은 Charter `## 9`의 공급원이므로(ADR-069 D3 전파표) 발화한다. 제외 뒤 남는 hunk가 1개 이상이면 발화. git 이력이 없으면(fresh fork) mtime 규칙으로 degrade + 사유 echo.
2. DISCOVERY.md `## 12. Assumption Tracker` 표에서 *"미검증"* 결과 항목 수.
3. PROJECT_CHARTER.md `## 2.1 페르소나` / `## 3.1 핵심 시나리오` / `## 9 핵심 가정` 섹션 중 비어 있거나 DISCOVERY.md와 명백히 어긋난 섹션 수.
4. (ADR-035#amend-2) DISCOVERY.md `## 15. Insight Backlog`에서 `status=open`(미반영) 인사이트 수 — 있으면 *"미반영 인사이트 N건 — 다음 /plan-milestone 후보로 회수 권장"* P1 보고.
위 1~3 시그널 중 1개라도 *stale 의심* 판정 시 IMPROVEMENT_GUIDE.md에 P1 보고:
*"DISCOVERY ↔ Charter drift 의심 — 절 단위 부분 갱신이면 `/amend-ssot`, 전면 재정의면 `/bootstrap-project --apply` 권장."*
(시그널 4는 drift가 아니라 *미반영 인사이트* 신호 — 위 4번 줄에서 별도 P1 보고하므로 본 집계에 포함하지 않는다.)
7. ARCHITECTURE_OVERVIEW의 `## 3-1. 레이어 경계 + 의존성 규칙` 섹션이 비어 있고 모듈 수가 3개 이상이면 채울 것을 권장 출력한다(정책: ADR-006).
7-T. **Telemetry aggregate** ([ADR-047](../../../docs/90-decisions/boilerplate/ADR-047-code-as-agent-harness.md) D7 deep telemetry + D1 inspectability 정합). 본 마일스톤 산하 task의 *이미 수집된 데이터*를 수치 dashboard로 echo. 새 데이터 수집 X — surface만.
수집 소스:
- 본 마일스톤 산하 모든 task의 `docs/40-validation/reports/<task-id>.md` (존재 시). **부재하면 그 task `## 8`의 마지막 `- closure` 줄에서 `기계AC`·`자동화율`을 읽는다**(ADR-068 D2 — 새 체크아웃에서 telemetry가 통째로 비는 것을 막는다). 그 줄도 없으면 그 task를 «집계 불가»로 세고 출력에 건수를 남긴다.
- **검증 레벨 분포는 산하 task의 `## 6-1`에서 직접 집계한다** — 채점표에도 `- closure`에도 그 값이 없으므로(closure는 `기계AC`·`자동화율`만 담는다) 커밋된 `## 6-1`이 유일한 항구 출처다. 판정 규칙은 `/validate-workitem`과 같다(**`[자동 테스트]` AC만 레벨 대상**, 명시 `{레벨}` 태그 우선, 미기재는 테스트 경로 추론, 나머지는 `레벨 비대상`).
- 본 마일스톤 산하 feature의 `## 7-1. FAC ↔ AC 매핑표`.
- `docs/40-validation/QA_FINDINGS.md` 본 milestone 헤더(`## M-N`) 아래.
- `docs/40-validation/IMPROVEMENT_GUIDE.md`의 `## 2. 열린 항목` 안의 본 milestone sub-section (`### M-N` 그룹) — Cross-stabilize 회귀 신호 grep 대상. **`## 5. Repair decision log`는 제외** (Step 3 신설 영역, *closed records*라 *open finding 재등장* 측정 대상 아님).
집계 항목:
- Tasks: M done / N total (M/N %)
- AC↔검증 매핑: 충족 A / 전체 B — 충족률 <%> · 자동화율 <%>
- AC 검증 레벨 분포: unit N / integration M / contract K / e2e L · 레벨 비대상 P (`[자동 테스트]` AC만 레벨 대상 — 출처: task `## 6-1`. `[산출물 검사]`·관측 2종은 «레벨 비대상»이며 자동화율 분류와는 별개다 — ADR-065 D4)
- FAC coverage: C ✅ / D total (C/D %)
- Evidence Bundle 신뢰도 분포: High K / Medium L / Low J (Step 2 도입 후 채워짐 — 미도입 마일스톤은 "해당없음" 한 줄)
- Validate exit code (가장 최근 실행): 0 / non-zero / 미설정
- Findings 분포: P0 X / P1 Y / P2 Z (본 milestone 헤더 산하)
- **Open 전체 스냅샷** — 세 수를 **정의대로** 센다(겹치지 않게 한다).
- **N** = `QA_FINDINGS.md`의 **본 마일스톤 헤더(`## M-N`)** 아래 미해소 항목 수(P0 a / P1 b / P2 c)
- **M** = `IMPROVEMENT_GUIDE.md`의 **본 마일스톤 그룹(`### M-N`)** 안 미해소 항목 수(P0 d / P1 e / P2 f). `## 5. Repair decision log`는 closed records라 제외.
- **K** = **다른(이전) 마일스톤** 헤더·그룹의 미해소 P0/P1 수 = carry-over
- **합계 = N + M + K**. 형식: `Open 전체: QA_FINDINGS N + IMPROVEMENT_GUIDE M + carry-over(P0/P1) K = (N+M+K)건` — **carry-over에 `(P0/P1)`을 붙여 출력한다**. N·M은 전 severity인데 K는 P0/P1만 세므로(다른 마일스톤 항목은 색인 스캔 대상 — `/repair-milestone` 회수 규율과 동형), 표기하지 않으면 `## 8. 회고`에 영속되는 수치를 읽는 사람이 기준을 오독한다.
- **두 원장을 각각 읽어야만 알 수 있는 수이므로 한 줄로 합산해 남긴다**(한쪽만 읽고 남은 항목 수를 오독한 사례가 관측됨). 같은 값을 milestone `## 8. 회고`의 `open 항목 스냅샷:` 줄에도 기록한다(ADR-068 D5).
- Cross-stabilize 회귀 신호: *이전 모든 milestone들*(`## M-1` ~ `## M-(N-1)`)의 P1 라벨 finding이 본 milestone의 **QA_FINDINGS(`## M-N`)** 또는 **IMPROVEMENT_GUIDE 의 `## 2. 열린 항목` 안 `### M-N`** 두 sub-section에 *재등장*한 항목 수 (라벨 grep, 휴리스틱 한계 echo — 동의어/오타 false-negative 가능. 본 grep은 *정확한 라벨 매칭*만 잡음. `## 5. Repair decision log`는 *closed records*라 회귀 신호 대상 아님).
- **위임 예산 (ADR-074 D11)**: `턴 소진 0건 보고: N회` — 산하 task 의 report·repair 로그에서 «sub-agent 가 턴 상한에 닿았는데 보고가 0건이었다»고 적힌 건수를 센다(기록이 없으면 `0회 (미기록)`). 이 실패는 지금까지 사람이 알아채야만 보였다. **1회 이상이면 ADR-074 D14의 slice 강제 조건이 발화한다** — 단계 8 출력에 그 사실을 한 줄로 적는다.
- **수렴 (ADR-070 D8)**: `round K`(IMPROVEMENT_GUIDE `## 5` `### M-N`의 `- round:` 최대값, 없으면 0) / P0 신규 a · 해소 b · 재개 c / P0 재현 첨부율 <%> / needs-confirmation d건
본 단계는 *수치 echo만* — IMPROVEMENT_GUIDE / QA_FINDINGS에 새 항목 박지 않음. Cross-stabilize 회귀 신호가 1+ 건이면 단계 8 출력의 "P1 / P2 후속 작업"에 *patterned drift 의심* 한 줄 추가.
출력 형식 (단계 8의 최종 출력에 *Telemetry* 단락으로 포함):
```
Telemetry — M1
- Tasks: 12 / 12 (100%)
- AC↔검증 매핑: 충족 34 / 전체 36 — 충족률 94.4% · 자동화율 88.9%
- AC 검증 레벨 분포: unit 20 / integration 9 / contract 2 / e2e 1 · 레벨 비대상 4
- FAC coverage: 8 / 8 (100%)
- Evidence Bundle 신뢰도: High 9 / Medium 2 / Low 1
- Validate exit code: 0
- Findings: P0 0 / P1 3 / P2 7
- Open 전체: QA_FINDINGS 4 + IMPROVEMENT_GUIDE 6 + carry-over(P0/P1) 1 = 11건
- Cross-stabilize 회귀 신호: 0건
- 위임 예산: 턴 소진 0건 보고 0회
- 수렴: round 2 / P0 신규 1 · 해소 3 · 재개 0 / 재현 첨부율 100% / needs-confirmation 0
```
8. 최종 출력:
- 통합 `validate` 결과 + E2E 결과 (있으면)
- P0 / P1 / P2 후속 작업
- QA_FINDINGS / IMPROVEMENT_GUIDE 갱신 위치
- **변경한 tracked 파일 목록**: `QA_FINDINGS.md`·`IMPROVEMENT_GUIDE.md`·마일스톤 문서(`## 8` 회고)·`DECISION_REGISTER.md`(등재 시)
- **커밋 안내**: 본 skill은 커밋하지 않는다 — 위 파일을 **사용자가 직접 커밋한 뒤** 다음 단계로 진행한다(commit owner는 사용자/`/finalize-workitem`). 미커밋으로 두면 후속 task의 `/finalize-workitem`이 그 파일을 task `## 4-1` 밖 변경으로 보고 `Needs Review`로 멈춘다.
- **수용 대기 AC (item 4 미충족)**: `<task-id>:AC-N (<modality>)` 목록 / 해당없음. **`/accept-milestone <M>` 처방은 최종 판정이 `PENDING_ACCEPTANCE`일 때만 붙인다** — `NO`·`BLOCKED`면 목록만 남기고 처방은 아래 「다음 단계」 분기가 소유한다(한 출력이 두 명령을 지시하면 사용자가 어느 쪽을 칠지 알 수 없다)
- 다음 마일스톤으로 넘기는 항목
- (UI) 경험 게이트 결과: [Experience-drift] N건 + 스크린샷 갤러리 경로 (사용자 육안 확인 권장) — 미실행 시 사유 필수 echo (silent skip 금지)
- architect 호출 권장 (있으면)
- ADR 후보 (있으면): `[ADR-candidate]` 라벨 목록 — 다음 /plan-milestone R0가 회수 (ADR-000#amend-2)
- instruction improvement 후보:
본 마일스톤 동안 builder/validator/reviewer가 반복적으로 막힌 패턴,
AGENTS.md 또는 agent/skill body 문구가 *비자명하거나 모호*했던 지점,
새로 박을 만한 *self-check 항목* 후보를
[IMPROVEMENT_GUIDE.md](../../../docs/40-validation/IMPROVEMENT_GUIDE.md)에 보고.
각 항목에 [ADR-022](../../../docs/90-decisions/boilerplate/ADR-022-ratchet-principle.md) evidence label 부착.
*AGENTS.md / agent / skill body는 자동 수정 X — 보고만*.
**경험·사용 관점 교훈(제품을 실제로 써 본 결과에서 나온 것) 유무를 별도로 확인**한다 — 교훈이 검증-정교화 방향으로만 쌓이는 편향 방지([관측됨] 실사용에서 경험 축 교훈 0건).
- DESIGN.md / ARCH 7-x cross-surface drift 가 본 마일스톤 중에 N회 이상 발견됐다면 *ADR-073 D8 적용 본문* 이 누락된 fork 인지 점검 권장.
- **Telemetry aggregate** (단계 7-T 결과 echo — 수치만, IMPROVEMENT_GUIDE 신규 항목 X).
- **다음 단계** ([WORKFLOW.md "스킬 종료 시 다음 단계 출력 contract"](../../../docs/00-meta/WORKFLOW.md) 양식 정합):
- **졸업 가능 = PENDING_ACCEPTANCE** (item 4만 미충족):
- **유일한 권장: `/accept-milestone <M>`** — 사람이 직접 실행·확인하는 수용 라운드이며(ADR-066 D1), 미발급 receipt는 그 자리에서만 발급된다(사용자 authority — ADR-065 D1). 미발급 AC 목록(`<task-id>:AC-N`)을 함께 출력한다.
- **여기서 `/validate-workitem`·`/finalize-workitem` 재실행을 권장하지 않는다** — 그 마일스톤은 이미 폐쇄됐고(ADR-068 D1), 졸업 item 4는 task `## 8`을 직접 읽으므로 receipt 발급만으로 충족된다.
- 수용 라운드 뒤에는 그 skill의 출력이 지시하는 순서를 따른다. 코드 변경이 있었으면 `/repair-acceptance <M>`이 그 자리에서 고치고 회귀 테스트까지 끝낸다 — **task를 재개방하지 않으므로 사용자가 돌릴 `/validate-workitem`·`/finalize-workitem`은 없다.** 사용자는 그 뒤 **본 skill을 한 번 더 실행**해 `YES`를 확정한다.
- **졸업 가능 = YES + P0 후속 0건**:
- **(ADR-076 D5)** IMPROVEMENT_GUIDE 본 마일스톤 그룹에 `status: open`인 `[Provisional-AC]`가 있으면 기본 권장을 `/repair-milestone <M>` — "잠정 해석 N건 사용자 확인(승인/변경)"으로 두고, 아래 `/plan-milestone`은 그 뒤 순서로 안내한다.
- **기본 권장: `/plan-milestone`** — 새 milestone(M-(N+1)) + feature 문서 생성 → `contract-ready`. 뒤이어 `/plan-workitem M-(N+1)`(전체 계획 스냅샷, task는 `draft`) → **`/seal-milestone M-(N+1)`**(검사·승인·일괄 `ready`) 순으로 진행(ADR-057#amend-3 / ADR-060 D7)
- **`/accept-milestone <M>`은 이 상태에서 선택이다** — `YES`는 관측 modality AC가 0건이거나 그 receipt가 이미 전부 유효하다는 뜻이므로 receipt로 막힐 것이 없다. 사용자가 경험 확인을 원하면 실행한다. **단 마일스톤 문서 `## 11`에 `- 판정: 승인`이 이미 기록돼 있고 그 뒤 코드·receipt 변경이 없으면 재권장하지 않는다** — 그 상태에서 다시 권장하면 라운드 상한 3이 무의미하게 소모된다.
- 프롬프트 동봉 권장: 본 라운드 Telemetry 의 신뢰도 분포 + Cross-stabilize 회귀 신호 (다음 milestone 의 우선순위 조정 입력)
- **졸업 가능 = BLOCKED** (평가 실행 불가 — ADR-068 D4). **이 분기가 아래 `NO` 분기보다 우선한다**(우선순위 `BLOCKED` > `NO`) — 감사 미완은 `M<N>-audit-<K>` P0를 등재하므로 «P0 후속 있음»에도 걸리지만, 그 P0의 처방은 repair가 아니다:
- **감사 미완(`BLOCKED (audit incomplete: <단위>)`)**: 처방은 **그 축의 재감사**다 — 단계 4의 축 미반환 회수 규율 ①~③(1회 재개 → 다른 qa 재위임 → 메인 직접 감사)을 다시 시도하거나 본 skill을 재실행한다. **`/repair-milestone`은 처방이 아니다** — «못 재봤다»는 상태이지 코드 결함이 아니어서 4-판정할 대상이 없다. 등재된 `M<N>-audit-<K>` P0는 감사가 완료된 뒤 `status: resolved`로 닫는다.
- **e2e blocked-on-env**: 처방은 아래 `NO` 분기의 «e2e blocked-on-env» 불릿과 같다(환경 복구 후 본 skill 재실행 — real failure가 아니므로 repair 대상이 아니다).
- `BLOCKED`은 이전 라운드에 기록된 `YES`·`PENDING_ACCEPTANCE`를 덮어쓴다(책임 경계의 회고 기록 규칙) — 낡은 값이 남아 하류가 졸업으로 읽는 것을 막는다.
- **졸업 가능 = NO 또는 P0 후속 있음** (분기 옵션 ≤3):
- **수렴 실패 브리프 (ADR-070 D5)**: 7-T의 `round K`가 **3 이상**이고 `### P0`에 `status: open`이 남아 있으면, 판정은 그대로 두고 `[Convergence]` 라벨로 Decision Brief 6블록(`authority: user-choice`, `영향: M<N>`, DECISION_REGISTER 등재)을 출력한다 — 남은 P0/P1 각각의 `decision`·근거·영향·수리 범위·보류 가능 여부 + 선택지 A(계속 수리) / B(비차단 항목에 `- 수렴-보류: 회수 <시점> | 조건: …` 하위 줄을 달아 이번 라운드 대상에서 빼고 차단 항목만 수리 — `status`는 `open` 유지, finding 원장에 `deferred` 상태를 만들지 않는다) / C(`NO` 유지 + 병렬 Now 승인으로 다음 마일스톤 진행 — carry-over 표시 지속, 해소 후 `/repair-milestone M<N>`이 원본을 닫고 본 skill 재실행으로 `YES`). **남은 `### P0` open 항목이 *전부* `- 판정: Adopt — blocked:` 줄을 가지면 선택지 A·B 를 제시하지 않는다**(ADR-070#amend-1 결정 3) — 그 상태에서 A(한 라운드 더)의 기대 효과는 0 이고 B(차단 항목만 수리)의 대상도 없다. 그때 선택지는 **C** 와 **«재승인·계약 정정 경로 착수»** 둘이며, 브리프에 각 항목의 `blocked:` 경로를 그대로 옮겨 적는다. 하나라도 `blocked` 가 아니면 기존대로 셋을 낸다. **판정값 신설·`YES` 병기·P0의 P1 재분류는 하지 않는다.** 항목을 DECISION_REGISTER에 `open`으로 등재하고(정상 책임 4), 사용자가 같은 세션에서 답하면 *그 항목에 한해* `closed` + 앵커를 쓴다. **`## 5`의 `- convergence-decision:` 줄은 답변 시점과 무관하게 후속 skill이 남긴다**(A·B → `/repair-milestone` 1-R, C → `/plan-milestone` R0 — 본 skill은 `## 5`에 쓰지 않는다, ADR-070 D5). `## 5` `### M-N`에 `- convergence-decision:` 줄이 이미 있으면 새 P0 ID가 생겼거나 K − 결정 round ≥ 2일 때만 다시 낸다. **`BLOCKED` 분기에서도 같은 조건(round ≥ 3 + open P0)이면 동일 브리프를 낸다** — 감사 미완·환경 불가와 별개로 남은 P0의 진행 경로를 사용자가 정해야 한다.
- **milestone-level P0/P1 (여러 task 교차) 또는 e2e real failure 있음: `/repair-milestone M-N` 권장** (ADR-052) — 단일 task로 격리되지 않는 회귀·교차 결함과 실제 e2e 수정은 milestone 단위 repair로 라우팅. stabilize가 read-only로 남기 위한 코드 수정 경로다.
- `[Provisional-AC]`가 open이면 같은 `/repair-milestone M-N` 호출에서 함께 확인한다 (ADR-076 D5).
- QA_FINDINGS 발견(P0/P1)은 단일 task 격리든 교차든 `/repair-milestone M-N` 로 회수한다 — **repair-milestone이 4-판정 후 per-task 귀속이든 cross-cutting이든 직접 수정하고**(재개방·위임 없음 — ADR-068 D1) QA_FINDINGS status를 닫는다.
- `[Spec-gap]` finding 있음: 미커버 FAC를 분기 — (i) M-N이 *약속한* FAC의 구현 누락·버그이고 담당 task가 있으면 **현재 M-N에서 `/repair-milestone M-N`이 직접 수정**(단일 task 귀속이든 교차든 — ADR-068 D1); **담당 task 자체가 없으면**(정상적으론 plan-time `[Plan-FAC-coverage]` 100% 게이트가 막으므로 드묾) 현재 M-N의 미이행 약속으로 graduation `NO`를 유지하고 사용자에게 보고한다. 현재 M에 새 task를 자동·권장 생성하거나 FAC 취소로 거짓 통과시키지 않는다. 본 계약에 정해진 자동 해소 경로는 없으며 사용자 판단을 기다린다. (ii) *새 기능·기획 변경*(M-N 약속 아님)이면 다음 마일스톤(M-(N+1)), (iii) 계획이 근본적으로 잘못됐으면 자동 수정 없이 **사용자 중단·보고** (ADR-057#amend-3 결정 6 — F-NNN 재계획 경로 없음)
- `[Doc-link]` / `[ADR-ref]` 등 문서 정합 P0: 사용자 직접 수정 (architect 또는 메인)
- **e2e blocked-on-env (ENVIRONMENT failure)**: real failure가 아니므로 repair 대상 아님 — 사용자에게 환경 복구(브라우저 설치 / 앱 기동 / E2E MCP 등재·access)를 안내하고 환경 복구 후 `/stabilize-milestone M-N` 재실행 권장.
- **공통 프롬프트 동봉 권장**:
- 미해결 P0/P1 라벨 목록 (다음 호출의 우선 처리 대상)
- Cross-stabilize 회귀 신호 항목 (있으면 — patterned drift 경고)
- 본 마일스톤의 instruction improvement 후보 (있으면 — 다음 stabilize 라운드에서 회수)
책임 경계:
- 코드 수정·커밋·workitem status 변경 금지.
- 누적 문서 갱신 + milestone `## 8. 회고` 자동 채움 — **회고의 `graduation:` 줄은 §1.5의 정적 항목 값(item 1·4)과 단계 3~6의 동적 항목 값(item 2·3·5·6)을 합쳐 단계 8에서 1회만 기록한다**(ADR-068 D3·D4). **§1.5의 정적 값을 여기서 재계산하지 않는다** — 본 skill이 단계 4~6에서 QA_FINDINGS·IMPROVEMENT_GUIDE를 쓰므로 재계산하면 자기 쓰기가 판정에 섞인다. 값은 `YES|PENDING_ACCEPTANCE|NO|BLOCKED`+날짜 4종이며 우선순위는 `BLOCKED` > `NO` > `PENDING_ACCEPTANCE` > `YES`다.
- **item 4만 미충족이고 1·2·3·5가 전부 충족이면 `PENDING_ACCEPTANCE (관측 AC 미발급: <task-id>:AC-N 목록)`** 으로 기록한다 — `NO`로 쓰지 않는다.
- **감사 미완이 있으면 `BLOCKED (audit incomplete: <축>)`**, e2e 환경 불가면 `BLOCKED (e2e blocked-on-env: <target>)`. 이 값은 이전 라운드의 `YES`·`PENDING_ACCEPTANCE`를 덮어쓴다.
- P0 기준은 `QA_FINDINGS.md`의 미해소 P0만 반영한다(성격 기준으로 등재된 결함 — 보고자 무관, ADR-070 D7).
- 회고의 `open 항목 스냅샷:` 줄도 여기서 채운다(ADR-068 D5).
- **회고의 `post-close 수정:` 줄도 여기서 채운다 (ADR-068 D5)** — `IMPROVEMENT_GUIDE.md` `## 5. Repair decision log`의 본 마일스톤 `### M-N` 그룹 항목 수를 세고 각 항목의 `scope:` 값으로 in-AC/out-of-AC를 분해한다. 그룹이 없으면 `없음`.
- 로드맵 파일은 안 건드린다(다음 plan-milestone R0가 이 줄을 읽어 재조정).
- *상세 SSOT 는 본 skill 도입부 책임 경계 단락* — 본 단락은 단순 재확인.
E2E는 단계 3-a의 *필요성 판정*으로 결정한다 — e2e 불필요(비-UI ∧ graduation item 6 미선언) 마일스톤만 통합 `validate`만 돌리고 e2e를 skip한다(사유 출력 명시). **e2e 필요 마일스톤은 silent-skip 금지** — `validate:e2e`를 반드시 실행하고, 미통과(real) 시 졸업 hard-block, 실행 불가(env) 시 사용자 환경 복구 안내(ADR-052).
## Dependency hygiene
> 실행 시점: 단계 4~5(qa·reviewer 위임)와 함께 수행하고 결과를 단계 8 최종 출력 *전에* IMPROVEMENT_GUIDE 에 기록한다 — 본 섹션이 문서 끝에 있다고 *마지막에* 실행하는 것이 아니다.
- `npm audit` / `pip-audit` (스택별 대응) 1회 실행. **Dart/Flutter 는 audit 에 정확히 대응하는 명령이 없어 아래 셋으로 나뉜다** — 목적이 서로 달라 하나로 묶어 보고하지 않는다. **셋 다 읽기 전용 경로만 쓰고, 도구를 설치하지 않는다**(본 skill 은 파일을 고치지 않는다). 미설치 도구는 그 항목만 skip 하고 사유를 출력에 명시한다(`Doc-link check skipped` 와 동형).
- **갱신 가능 여부**: `flutter pub outdated` — 내장이고 `pubspec.lock` 을 바꾸지 않는다.
- **미선언·미사용 의존**: `dart pub global run dependency_validator`. 미활성이면 skip + `dep hygiene skipped: dependency_validator not activated (dart pub global activate dependency_validator — 사용자/stack-guard 소관)`.
- **취약점**: `pubspec.lock` 을 대상으로 `osv-scanner` 실행. 미설치면 skip + 사유 명시. **`dart pub get` 의 advisory 출력을 쓰려면 반드시 `--enforce-lockfile` 을 붙인다** — 플래그 없는 `pub get` 은 해결 결과를 `pubspec.lock` 에 **쓰므로** read-only 계약을 깨뜨린다. 플래그를 못 쓰는 환경이면 이 경로를 생략하고 `osv-scanner` 결과만 쓴다.
- 결과를 IMPROVEMENT_GUIDE.md에 P1 severity로 보고.
- 6개월 unused deps는 P2로 자동 등록.
## Context 정책 (ADR-019)
`반드시 먼저 읽을 파일`은 *최소 충분*. 추가 ADR/architecture 섹션은 task 본문에서 발화 시 인용 — 사전 fork-load 금지.
fan-out 병렬 오케스트레이션 정책은 [ADR-075](../../../docs/90-decisions/boilerplate/ADR-075-main-session-orchestration-v2.md) D2 정합.