Skip to content
Back to skills

Stack Guard

BSecurity

After /bootstrap-stack, generate verify scripts and a unified `validate` command for the project's stack.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added September 29, 2026
developmenttypescriptpythonrustgoshellbashnextjsnodeexpressrails

Works with

  • cli
  • api
  • mcp

Security analysis

B76/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add kohj1018/agentic-dev-harness --skill stack-guard --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Stack Guard?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Stack Guard
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/kohj1018-stack-guard-agentic-dev-harness/badge)](https://www.skillsdirectory.com/skills/kohj1018-stack-guard-agentic-dev-harness)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: stack-guard
description: After /bootstrap-stack, generate verify scripts and a unified `validate` command for the project's stack.
argument-hint: "[stack summary | empty to read existing docs] [--no-ci]"
disable-model-invocation: true
allowed-tools: Read Glob Grep Write Edit Bash
---

너의 역할은 스택이 확정된 직후 통합 검증 명령(`validate`)과 검증 스크립트를 생성하는 것이다. 본 skill은 메인 세션에서 직접 실행된다(ADR-050 D1).

이 skill의 1단계 범위:
- green-field 스캐폴드(공식 생성기 1종, harness 파일 미덮어쓰기) + 카탈로그 `설치: baseline` 행의 기초 라이브러리 설치 — 그 뒤에 probe·boot smoke·design gate·CI를 실제 코드 위에서 실측한다(ADR-071 D5·D6).
- 통합 진입점 — 이름은 **`validate`로 고정** (`pnpm validate` / `npm run validate` / `make validate` / `task validate` 중 스택에 자연스러운 단일 명령). **단 `validate:design` 진입점만은 npm 계열로 둔다** — `make`·`task`는 하위 명령의 종료코드를 자기 코드로 대체해 adapter의 차단(`exit 1`)/실행불가(`exit 2`) 구분을 없앤다. Flutter 스택은 `validate` 자체도 npm으로 둔다(ADR-059 D2) — **이때 `package.json` 이 없으면**(순수 Dart/Flutter 프로젝트엔 기본적으로 없다) **최소 형태로 생성하고 `scripts` 에 진입점을 박는다.** 생성하지 않으면 `npm run validate` 자체가 성립하지 않는다. **이미 생성된 진입점은 소급 교체하지 않는다**(도구 감지 우선순위 정합 — 기존 도구 미덮어씀); 기존 `validate:design`이 `make`·`task`로 물려 있으면 자동 변경 없이 출력에 1줄 보고 + 사용자 결정으로 넘긴다.
- `scripts/verify.{sh,ps1,mjs,py}` 중 스택에 가장 자연스러운 런타임 1종.
- UI 판정 시 canonical design gate v3 asset을 project-native `validate:design`에 배선하고 **자가 검사 1회**(해당 스택에서 실행되는 케이스 전부)를 통과시켜 `STACK_SETUP_PLAN.md ## Design Gate Adapter`(6필드)에 기록(ADR-072 D6). 비-UI는 asset을 읽거나 복사하지 않는다.
- cross-platform 차이가 큰 팀이면 `.claude/settings.local.json` 예시 동봉 권장.
- 생성된 `docs/00-meta/STACK_SETUP_PLAN.md`에 hook 절차 SSOT([GUARDRAILS_STRATEGY.md "## PostToolUse hook 매뉴얼 등록 절차"](../../../docs/00-meta/GUARDRAILS_STRATEGY.md))를 link하는 1줄 안내. 절차 본문은 embed 금지 (SSOT 정합).

**1단계 비범위**: PostToolUse hook 자동 등록은 본 skill에서 수행하지 않는다(prototyping 미완료 — 자세한 이유는 [GUARDRAILS_STRATEGY.md의 "/stack-guard 1단계 산출물 범위" 섹션](../../../docs/00-meta/GUARDRAILS_STRATEGY.md#guardrails-stack-guard-scope) 참조). 사용자가 GUARDRAILS_STRATEGY.md 절차에 따라 매뉴얼 등록.

입력:
- `$ARGUMENTS`가 있으면 스택 요약을 받아 사용한다.
- 비어 있으면 `docs/20-system/ARCHITECTURE_OVERVIEW.md`의 "기술 선택" 섹션을 읽어 스택을 추정한다.
- `--no-ci` 플래그가 있으면 CI workflow를 생성하지 않는다(`## CI 생성` 절 참조). **플래그는 스택 요약으로 해석하지 않는다** — 플래그만 넘어오면 스택 추정은 위 규칙을 따른다.

반드시 먼저 읽을 파일:
- `docs/00-meta/GUARDRAILS_STRATEGY.md`
- `docs/00-meta/STACK_SETUP_PLAN.md` (있으면)
- `docs/20-system/ARCHITECTURE_OVERVIEW.md`
- UI 판정 시에만 `.claude/skills/stack-guard/assets/design-gate.mjs`(v3) — 소유 ADR-072 D6
- `docs/00-meta/STACK_SETUP_PLAN.md ## Stack Decision Registry`(수행 0·6-2-b 입력)
- `.claude/skills/bootstrap-stack/stack-catalog.md`(`설치: baseline` 판별 — registry에는 `설치` 열이 없어 id로 조인한다, ADR-071 D6)

R0 — 운영 환경 가정 확인:
- 단일 OS/셸인가, mixed env인가?
- 단일 OS/셸이면 단일 verify 스크립트로 충분.
- mixed env면 cross-platform 친화적 런타임(예: Node.js, Python) 우선, 또는 `scripts/verify.sh` + `scripts/verify.ps1` 모두 생성.
- `.gitattributes`로 line ending 통일은 항상 1단계 산출물에 포함한다(예: `* text=auto eol=lf`).
- 단일 OS/셸이 Windows로 판정되면 `scripts/verify.ps1` 우선 + 매뉴얼 hook 예시는 PowerShell exec form ([GUARDRAILS_STRATEGY.md Windows 예시](../../../docs/00-meta/GUARDRAILS_STRATEGY.md)).
- macOS/Linux 판정 또는 mixed env면 `scripts/verify.sh` 우선 + exec form 그대로 (Unix/macOS 예시).
- mixed env면 *두 verify 스크립트 모두 생성* (`.sh` + `.ps1`) + 두 hook 예시 모두 출력.
- 두 OS 모두 매뉴얼 hook 예시 본문은 `${CLAUDE_PROJECT_DIR}` + `args` 배열로 박는다 (Anthropic open issue #50960 다중 reproducer 대응).

수행:
0. **스캐폴드 (green-field scope 한정 — ADR-071 D5)**: scope(registry `cat-common-repo-layout`)마다 «등록 소스 루트 0 **그리고** 프레임워크 manifest 부재»면 아래를 수행하고, 아니면 `## Scaffold`에 `skipped (<사유>)`를 적고 다음 scope로. 전 scope 처리 후 수행 1로 간다.
   1. 생성기 = 그 scope 유형의 `cat-<유형>-framework` 확정 행(registry에서 `scope`가 그 scope이거나 `*`인 행 — ADR-071 D2 `(scope, id)` 키)의 공식 생성기 1종(없으면 최소 골격 — 소스 루트 1 + 테스트 루트 1 + manifest, `generator: minimal`). 옵션은 registry `확정` 행에서 도출(언어·PM·스타일링·라우팅·src 디렉터리·테스트 도구). 도출 불가 옵션은 생성기 기본값 + 출력에 명시.
   2. **임시 디렉터리에 생성**한 뒤 scope 디렉터리로 복사한다. 아래는 **절대 덮어쓰지 않는다**: `README.md` `README_ko.md` `LICENSE` `AGENTS.md` `CLAUDE.md` `docs/**` `.claude/**` `.codex/**` `.agents/**` `.boilerplate/**` `.github/**`. `.gitignore`는 줄 단위 합집합(저장소 기존 줄 우선, 중복 제거). 그 외 충돌 파일은 덮어쓰지 않고 `Scaffold conflict: <경로>` 출력 + 사용자 결정.
   2-0. **scope 하위 harness 파일 탐지 (monorepo 필수 — dogfood Round 12)**: 2-1 의 보호 경로 목록은 **저장소 루트 기준**이라 `apps/web/AGENTS.md` 같은 것을 보지 못한다. 복사 직후 그 scope 트리에서 harness **이름**(`AGENTS.md` `CLAUDE.md` `.claude/` `.codex/` `.agents/` `.boilerplate/`)을 찾아 발견하면 `Scaffold harness-file in scope: <경로> — <추정 원인>` 을 출력하고 `STACK_SETUP_PLAN ## Scaffold` 의 그 행 «제외·충돌» 칸에 적는다. **자동 삭제하지 않는다** — 생성기가 매 실행 되살리는 경우가 있고(Next.js 의 `next dev`) 되돌리기는 사용자 결정이다. 왜 보고해야 하는가: 저장소 안에 **두 번째 `AGENTS.md`** 가 생기면 그 디렉터리를 cwd 로 일하는 에이전트가 보일러플레이트 규율 대신 그것을 읽는다(실측: `create-next-app` 이 `apps/web/AGENTS.md` + `CLAUDE.md` 를 생성, 루트 201파일 해시는 완전 동일).
   2-1. **보호 경로 검사(내용 대조)**: 보호 경로(`README.md` `README_ko.md` `LICENSE` `AGENTS.md` `CLAUDE.md` `docs` `.claude` `.codex` `.agents` `.boilerplate` `.github`) 아래 **«파일 경로 + 내용 해시» 목록**을 복사 **직전**과 **직후**에 각각 만들어 **완전히 같아야** 한다(해시가 바뀐 파일 · 새로 생긴 파일 · 사라진 파일 = 위반). 예: `find <보호 경로> -type f -print0 | sort -z | xargs -0 shasum`. 달라진 경로가 있으면 `Scaffold protected-path violation: <경로>` 출력 + 종료(되돌리기는 사용자 결정 — 자동 checkout 금지).
   3. 생성기가 만든 `README`류·예제 페이지는 그대로 둔다(정리는 M1 계획 소관). 생성기가 만든 `.git`은 버린다. **단 그 산출물을 프로젝트가 고른 formatter 로 1회 정규화한다** — 생성기 기본 스타일(따옴표·세미콜론 등)은 registry 가 확정한 formatter 와 다른 것이 보통이라, 그대로 두면 수행 5 의 통합 `validate` format 단계가 **영구히 실패**하고 probe 회차 판정이 프로젝트 실패에 묻힌다(dogfood Round 12 실측). 삭제가 아니라 형식만 맞추는 것이므로 «생성기 산출물을 그대로 둔다»와 충돌하지 않는다.
   4. `## Scaffold`에 scope별 1행(scope·status·생성기·버전·옵션·생성 파일 수·제외·충돌·실행일)을 적는다. 생성 파일 전량은 `git status --porcelain`으로 보여 준다(문서 복사 금지).
   5. **커밋하지 않는다.** 출력에 `권장 커밋: chore(scaffold): initialize <framework> project skeleton` 한 줄.
   6. Dart/Flutter면 수행 0 직후 `## Dart Source Roots`를 실측 갱신한다(생성기가 `lib/`·`test/`를 만들었으므로).
   7. 이 뒤 수행 1은 생성기 manifest의 `scripts`에 `validate*` 키를 **추가**한다(기존 키·의존 보존 — 덮어쓰기 금지). 도입부의 «Flutter는 `package.json`이 없으면 최소 형태로 생성» 규칙은 그 scope에 `pubspec.yaml`만 있고 `package.json`이 없을 때만 적용된다.
0-H. **harness 경로 무결성 (실행 시작·종료 — ADR-071#amend-1)**: 수행 0의 2-1 보호 경로 검사는 *스캐폴드 복사 전후*만 본다. 그러나 본 skill 은 그 뒤로도 생성기·설치기·개발 서버를 돌리고, **그 도구들이 harness 파일을 고치는 사례가 실재한다** — 예: Next.js 16 의 `next dev` 는 실행할 때마다 `AGENTS.md`(없으면 `CLAUDE.md`)에 자기 규칙 블록을 append 하고 지워도 되살린다(`node_modules/next/dist/server/lib/generate-agent-files.js`). 그래서 **본 skill 실행 맨 앞과 맨 끝에** 보호 경로(`README.md` `README_ko.md` `LICENSE` `AGENTS.md` `CLAUDE.md` `docs` `.claude` `.codex` `.agents` `.boilerplate` `.github`)의 «파일 경로 + 내용 해시» 목록을 각각 만들어 대조한다(수행 0 2-1 과 같은 방법). 달라진 경로가 있으면 `Harness-path drift: <경로> — <추정 원인>` 을 출력에 남긴다. **자동으로 되돌리지 않는다** — 그 변경이 의도된 것일 수 있고(본 skill 이 `docs/00-meta/STACK_SETUP_PLAN.md` 를 갱신하는 것이 정상 경로다) 되돌리기는 사용자 결정이다. **본 skill 이 스스로 쓰는 파일**(`docs/00-meta/STACK_SETUP_PLAN.md`, `.gitignore`, `.gitattributes`)과 직전 skill 이 쓴 미커밋 문서는 대조 대상에서 뺀다. **이 대조도 루트 기준이다** — scope 하위에 새로 생긴 harness 이름 파일은 수행 0 2-0 이 담당한다. `AGENTS.md` 가 커졌으면 ADR-011 의 100줄 상한을 함께 확인해 출력에 줄 수를 적는다.
1. `package.json`/`pyproject.toml`/`Makefile`/`Taskfile.yaml` 중 스택에 자연스러운 곳에 `validate` 진입점을 만든다. Flutter 포함 프로젝트는 `validate:ci`도 만든다 — test 단계만 `--exclude-tags golden`(`test/design_gate/` 제외 유지)이고 나머지 단계는 `validate`와 같다(ADR-059#amend-2).
2. `scripts/verify.{sh,ps1,mjs,py}` 중 자연스러운 런타임 1종을 생성. 내용은 스택의 `format + lint + typecheck + test` 통합(아래 `## 스택별 verify 풀세트` 의 4단계와 같다 — 이 줄이 3단계로 남으면 수행-5 1회차의 format probe 가 갈 곳이 없고 `missing: format` 이 매 실행 발화한다). **이미 존재하면 덮어쓰지 않고 4단계 커버리지 부족만 출력에 보고한다**(아래 `## 재실행 계약` 표 정합). **생성하는 `validate`는 추적 파일을 바꾸지 않는다** — format 단계는 검사 모드(`--check` 류)로 두고, 실행 중 다시 생성되는 파일(예: Next.js `next-env.d.ts` — 공식 문서가 `.gitignore`를 권장)은 `.gitignore`에 넣는다(이미 추적 중이면 `git rm --cached <경로>`로 뺄 것을 보고에 권장). 그 파일이 타입 검사에 필요하면 validate가 먼저 생성하게 한다(예: `next typegen && tsc --noEmit` — Next.js 15.5+). 검증이 작업 트리를 바꾸면 finalize의 차이 검사와 `/run-milestone`의 «validate 뒤 무편집»이 매 task 충돌한다(ADR-076 D7·D13).
2-1. **harness 경로 배제 (ADR-063 D2)**: 생성하는 도구 config 중 **formatter / linter / 타입 검사 include / 테스트 커버리지 집계 / 의존성 그래프**의 검사 범위에서 아래를 제외한다 — 이들은 프로젝트 소스가 아니라 agent harness다.
   - `.claude/`, `.codex/`, `.agents/`, `.boilerplate/`
   - **design gate 산출물** `design-gate-shots/`·`design-gate-storybook/` — monorepo 면 **그 scope 안에** 생기므로 scope 도구 config 에도 넣는다(6-4-1 참조).
   - `STACK_SETUP_PLAN.md ## Design Gate Adapter` 에 기록된 **materialized adapter 경로**(기본 `scripts/design-gate.mjs`). 이 사본은 프로젝트 소스 트리 안에 있어 harness 디렉터리 제외만으로는 보호되지 않는다. **포맷되면 자가 검사 fixture 문자열이 깨질 수 있다(자가 검사 재실행으로 복구 — 재실행 계약).**
   - **formatter 의 Markdown 대상에서 `docs/`** — 이 저장소의 기계 점검 다수가 문서 문자열에 의존한다(로스터의 종 수 표기·ADR 인덱스 행·Amendments 칸·`## Amendment N` 카운트). formatter 가 표를 재정렬하면 그 점검들이 조용히 깨진다. lint·typecheck 와는 무관한 항목이다.
   - **⚠️ secret scanner 는 배제 대상이 아니다 — 반대로 harness 경로를 포함해야 한다.** `.claude/settings.json`·`.codex/config.toml`·agent 설정에 토큰·키가 유입될 수 있고 그것이 정확히 scanner 가 잡아야 하는 대상이다. 포맷·타입 검사의 배제와 보안 스캔의 범위를 분리한다.
   - **정확한 exclude 설정 키는 도구·버전마다 다르므로 실행 시점에 그 도구 문서로 확인한다** — 본 SKILL 에 특정 키를 박지 않는다(도구 버전업 시 틀린 지시가 된다).
   - 기존 config 가 있으면 배제 항목만 **추가**하고 기존 규칙을 덮어쓰지 않는다.
3. `docs/00-meta/STACK_SETUP_PLAN.md`을 다음 규칙으로 처리한다:
   - **소유 책임 분리**: STACK_SETUP_PLAN.md는 `/bootstrap-stack`이 *최초 골격*(스택 선택 사실 + 추후 추가 필요한 자동화 목록)을 만들고, 본 `/stack-guard`는 거기에 *통합 명령 사용법 + hook 등록 안내 섹션*을 **append/갱신**한다. `/bootstrap-stack`이 만든 기존 섹션을 통째로 덮어쓰지 않는다.
   - 본 skill이 채울 섹션:
     - 통합 명령 사용법
     - `## Dart Source Roots` — Dart/Flutter 스택이면 **실제 소스 트리를 조회해 채운다**(`pubspec.yaml` 하나당 한 묶음, 경로는 그 pubspec 디렉터리 기준 상대경로). **예시 행을 그대로 두면 안 된다** — `dart format` 이 예시 경로를 대상으로 돌아 형식 검사가 조용히 통과한다. 조회는 pubspec 디렉터리에서 아래 중 host OS 에 맞는 것으로 한다(§1.0-1 의 OS 별 분기 규율과 동형). 둘 다 의도가 같다 — *"`.dart` 파일을 품은 최상위 디렉터리 목록"*.
        - Unix/macOS: `find . -name '*.dart' -not -path './.dart_tool/*' -not -path './build/*' | cut -d/ -f2 | sort -u`
        - Windows PowerShell: `Get-ChildItem -Directory | Where-Object { $_.Name -notin '.dart_tool','build' } | Where-Object { Get-ChildItem $_ -Recurse -Filter *.dart -File | Select-Object -First 1 } | Select-Object -ExpandProperty Name`

     비-Dart 프로젝트는 `/bootstrap-stack` 이 이 절을 이미 삭제했으므로 대상이 아니다(ADR-059 D2).
     - `## E2E Smoke Registry` — e2e 대상 프로젝트면 **선언한 runtime target마다 한 행**으로 `status | 파일 경로 | 테스트 이름 | 실행 대상 선택 규칙 | 마지막 PASS(host·날짜·커밋) | 등록일`을 기록. 실행 대상 칸에는 재부팅하면 달라지는 임시 id 대신 선택 규칙을 적는다(이 칸은 기록용이며 실행 명령에 그대로 들어가지 않는다). 대상 아니면 `status: n/a`만 기록(ADR-052#amend-1).
     - `## Design Gate Adapter` — UI면 6필드(status·command template·adapter path·manifest 규약·self-test 일자·copied-from)를 6-4-1이 기록, 비-UI면 `status: n/a`만(ADR-072 D6).
     - `## Dependency Tools` — **보완만**(ADR-075 D14 수행-6-2-0): 표·행이 없으면 관측 신호로 채우고, `/bootstrap-stack`이 기록한 행은 덮어쓰지 않는다(불일치는 출력 보고 + 사용자 결정).
     - `## 로컬 환경 동기화` — **보완만**(ADR-076 D4): 행이 없으면 감지한 의존성 도구의 lockfile 고정 설치 명령(예: `npm ci`·`pnpm install --frozen-lockfile`·`uv sync --frozen`·`flutter pub get --enforce-lockfile`)을 `자동 실행: 허용`으로, 로컬 DB가 있으면 그 초기화 명령을 `자동 실행: 확인 필요`로 적는다. 기존 행은 덮어쓰지 않는다.
     - PostToolUse hook 자동 등록은 prototyping 후 별도 항목 — 현재 단계에서는 매뉴얼 등록 안내
     - hook 등록 절차는 [GUARDRAILS_STRATEGY.md "## PostToolUse hook 매뉴얼 등록 절차"](../../../docs/00-meta/GUARDRAILS_STRATEGY.md) link만 박는다 (SSOT — 본 skill이 절차 본문 embed 금지).
   - 파일이 아예 없으면(`/bootstrap-stack` 산출물이 빠진 경우) `/stack-guard`가 새로 생성하되, 출력에 "`/bootstrap-stack`이 STACK_SETUP_PLAN.md를 만들지 않았음 — 사후 검토 권장"을 명시.
4. `.gitattributes` 처리 — **전역 규칙 우선**: 파일이 없으면 `* text=auto eol=lf` + Windows 전용 스크립트(`*.ps1`/`*.bat`) `eol=crlf` 예외로 생성한다. 파일이 있으면 **전역 규칙(`* text=auto eol=lf`)이 존재하는지 먼저 확인**하고 없으면 추가한다. 확장자 열거 방식만 있는 경우 새 확장자를 조용히 놓치므로 전역 규칙으로 보강한다(열거 줄은 제거하지 않는다 — 의도 문서화로 유지 가능). 기존 fork 에 전역 규칙을 새로 넣으면 `git add --renormalize .` 1회 커밋이 필요하다는 안내를 출력에 포함한다.
5. **Smoke test (필수)**: 수행-6 의 toolchain 설치가 성공한 경우 생성된 `validate` 명령을 **아래 5-c 실행 표대로 돌린다**(probe 배치를 바꿔가며 **최대 5회** — 부재 단계가 있으면 그만큼 줄어든다. ADR-063 D1) (`allowed-tools` 의 Bash 권한 활용 — 신규 권한 추가 불필요). e2e 대상 프로젝트(수행-6 의 runtime target 이 e2e 대상)면 `validate:e2e` 도 실행한다 — **선언된 e2e 대상 target 이 하나면 1회**(러너가 후보를 좁혀 자동 선택), **둘 이상이면 `validate:e2e:<target>` 을 target마다**(집계 `validate:e2e` 1회로 대체 가능) 실행한다. native target 의 device id 는 `scripts/e2e-target.mjs` 가 실행 시점에 `flutter devices --machine` 으로 회수한다(ADR-059#amend-1).
   본 smoke test 는 *wiring 검증* 이 목적 (명령이 올바르게 연결됐는지) — *프로젝트 자체의 lint/test 통과 여부* 와 분리해 보고한다.
   설치가 `Needs Install` 로 보류된 경우(수행-6) smoke test 를 실행하지 못하므로 `validate smoke test: SKIPPED (deps not installed — Needs Install)` 로 보고하고 종료하지 않는다(사용자 설치 후 재실행 안내).

   **5-a. probe 배치 (ADR-063 D1)** — 프로젝트 도구의 **검사 범위 안**에 만든다.

   > ⚠️ **닷 디렉터리(`.stack-guard-probe/` 등)나 `.gitignore` 등재 경로에 만들면 안 된다.** (i) 다수 formatter/linter 가 `.gitignore` 를 기본 존중해 대상에서 제외하고, (ii) TypeScript `include` 의 `**/*` 는 `.` 로 시작하는 세그먼트를 매칭하지 않으며, (iii) 테스트 러너의 glob 은 dot 파일을 기본 제외한다. 그러면 위반 probe 가 실패하지 않아 **판정력이 정상인 검사도 FAIL 로 오분류**된다.

   - **위치**: 등록된 소스 루트 / 테스트 루트 **안**. 파일명은 그 스택의 include·test glob 에 걸리는 형태 + 명백한 표식. 예: `src/__stackguard_probe__.ts` · `src/__stackguard_probe__.test.ts` · `lib/__stackguard_probe__.dart` · `test/__stackguard_probe___test.dart` · `tests/test___stackguard_probe__.py`
   - **scope 가 여럿이면(monorepo) 회차마다 scope 마다 하나씩 둔다** — 같은 단계가 scope 별로 한 번씩 돌고 앞 scope 가 fail-fast 로 멈추면 뒤 scope 의 판정력이 측정되지 않는다. 멈춰서 도달하지 못한 scope 의 그 단계는 **5-c-0 (ii) 의 단독 실행으로 재측정**한다(예: `pnpm --dir apps/web exec biome format .`). 재측정이므로 **회차 수는 늘지 않는다**(dogfood Round 12 실측 — 2 scope × 5회차를 5회차로 유지).
   - **등록된 소스/테스트 루트가 아직 없으면**(수행 0 스캐폴드가 `skipped`인 brownfield·부분 초기화 상태 — green-field는 수행 0이 먼저 만들므로 정상 경로에서는 발생하지 않는다, ADR-071 D5) probe 를 둘 자리가 없으므로 `SKIPPED (probe unavailable — 등록된 소스 루트 부재)` 로 보고한다. **디렉터리를 새로 만들지 않는다** — 소스 트리 구조는 스캐폴드·계획의 소관이고 본 skill 은 `## 재실행 계약` 대로 *변경이 필요한 것만* 건드린다. 이 SKIPPED 는 5-f 로 기록되고 스캐폴드 후 재실행 때 해소된다(`[Guard-drift]` 가 그 재실행을 권고한다).
   - **`.gitignore` 에 등재하지 않는다.** 잔여물은 5-d 삭제로만 통제하고, 남았을 때 `git status` 에 보이는 것이 정상이다(조용히 무시되는 것보다 안전하다).
   - 같은 이름의 파일이 이미 있으면 **덮어쓰지 않고** 그 항목만 건너뛰고 사유를 보고한다.

   **5-b. 판정 단위 — 파일 귀속 진단** (전체 exit code 가 아니다): probe 판정은 **`validate` 출력에 그 probe 파일 경로가 진단으로 등장하는가**로 한다. brownfield fork 는 기존 위반으로 전체 exit code 가 이미 1일 수 있어, 전체 코드로 판정하면 *배선 실패*와 *프로젝트 실패*를 구분할 수 없다.

   **5-c. 실행 순서 — 위반 probe 는 한 번에 하나만 둔다**: `validate` 는 4단계를 **순차 fail-fast** 로 묶으므로(본 SKILL 의 `## 스택별 verify 풀세트` 정합), 위반을 여러 개 동시에 두면 첫 단계에서 멈춰 뒤 단계의 판정력을 측정할 수 없다.

   **5-c-0. 회차별 판정의 두 전제 (ADR-063 D1 — 아래 표보다 먼저 적용한다)**

   - **(i) 단계 실재** — 대상 단계를 수행하는 명령이 `validate` 파이프라인에 있는가. **없으면 `missing: <단계>` 로 보고하고 그 회차만 건너뛰고 다음 회차를 계속한다.** 이것은 *probe 가 범위 밖*이 아니라 **커버리지 누락**이며, 둘을 합쳐 `SKIPPED` 로 적으면 **본 검증이 잡으려는 배선 누락이 비차단 SKIPPED 로 숨는다.** **겸업 도구가 그 단계를 겸하면 부재가 아니다** — 아래 겸업 규칙을 먼저 적용하고 `missing` 으로 보고하지 않는다.
   - **(ii) 실행 도달** — 이번 실행에서 그 단계까지 실제로 도달했는가. **앞 단계가 *프로젝트 소유* 위반으로 fail-fast 를 유발해 도달하지 못했으면**(brownfield 의 기존 format 위반 등) 그 단계 명령을 **단독 실행**해 5-b 기준으로 판정하고, 단독 실행이 불가하면 `SKIPPED(미도달: <사유>)` 로 보고한다.
   - **(i)·(ii) 어느 경우도 `PROBE FAIL` 이 아니다.** 아래 표의 `불일치 시` 열은 **그 단계가 실재하고 도달한 회차**에만 적용한다 — 배선 결함이 아닌 것을 결함으로 적으면 오분류를 방향만 바꿔 재생산한다.
   - **1회차의 (a) 범위 확인은 파이프라인에 실재하는 *첫* 단계의 회차에서 수행한다** — format 단계가 없으면 그 역할을 lint 회차가 이어받는다.

   | 실행 | probe 배치 | 판정 | 불일치 시 |
   |---|---|---|---|
   | **1회** | 명백한 **형식 위반** probe 1개만 | **(a) 범위 확인** — 그 파일 경로가 진단에 등장하는가 / **(b) format 판정력** — 그 진단이 format 단계에서 나왔는가 | **(a) 실패** → `SKIPPED — probe 가 검사 범위 밖: <추정 원인>`. **FAIL 아님, 종료 X.** 2~5회를 돌리지 않고 **5-d 정리 → 5-e 판정 → 5-f 기록** 순으로 마친다(정리를 건너뛰면 probe 가 저장소에 남고, 기록을 건너뛰면 이 SKIPPED 가 잊힌다) / **(b) 실패** → `PROBE FAIL(format)` |
   | **2회** | 위반 probe 를 **lint 위반 1개로 교체** | lint 단계 진단에 그 경로 | `PROBE FAIL(lint)` |
   | **3회** | **타입 오류 1개로 교체** | typecheck 단계 진단에 그 경로 | `PROBE FAIL(typecheck)` |
   | **4회** | **실패 테스트 1개로 교체** | test 단계 진단에 그 경로 | `PROBE FAIL(test)` |
   | **5회** | 위반 probe 전부 제거 + 규칙 준수 소스 1개 + 통과 테스트 1개 | probe 파일에 귀속된 진단 **0건** (전체 exit 0 을 요구하지 않는다) | `PROBE FAIL(pass)` — 준수 파일이 지적됨 = 규칙 설정 문제 |

   **`validate` 실행은 최대 5회다** — 1회차의 (a)(b)는 *같은 실행의 두 판정*이다(같은 probe·같은 명령이므로 따로 돌리지 않는다). **단계 부재로만** 회차가 줄어든다(겸업은 줄이지 않는다 — 아래 규칙). 5-c-0 (ii) 의 단독 실행은 그 회차의 **재측정**이므로 새 회차로 세지 않는다.

   **한 도구가 두 단계를 겸해도 회차는 줄이지 않는다** — Biome = format + lint, `flutter analyze` = lint + typecheck 면 **같은 명령을 두 회차에서 각각 다른 위반 probe 로 호출**한다. 겸업 도구도 linter 나 개별 규칙을 따로 끌 수 있으므로 **format 진단은 lint 규칙의 판정력을 증명하지 않는다** — 한 단계의 통과를 다른 단계 판정으로 대체하면 D1 이 막으려는 얕은 검증이 *겸업*이라는 이름으로 되돌아온다.
   - 겸업 시 **단계 귀속은 명령이 아니라 진단의 규칙·카테고리로 판정한다** — Biome 은 formatter 진단과 lint 규칙 id 로, `flutter analyze` 는 타입 오류와 lint 규칙 이름으로 구분한다.
   - 겸업 단계는 `missing: <단계>` 로 보고하지 않는다(단계는 실재한다 — 5-c-0 (i)). 겸업 여부를 산문으로 예외 처리할 필요도 없다.
   - 한 명령이 두 단계를 함께 보고하므로 **그 두 회차 사이에는 fail-fast 가 없다**(5-c-0 (ii) 의 미도달이 겸업 단계끼리는 발생하지 않는다). 그래도 위반 probe 는 회차당 하나만 둔다 — 진단 귀속을 단순하게 유지한다.

   **5-d. probe 정리 (필수)**: 생성한 probe 파일을 **전부 삭제**하고 결과를 보고한다 — `probe cleanup: DONE (<n>개)` 또는 `probe cleanup: FAILED — 수동 삭제 필요: <경로 목록>`. 실패 경로·조기 종료 경로에서도 정리한다. **조용히 남기지 않는다.**

   **5-e. 판정 표**:
   - **전 회차 기대대로 + 프로젝트 진단 0건** → `validate smoke test: PASS (probe verified, project clean)`.
   - **전 회차 기대대로 + 프로젝트 빈 케이스**(빈 lint 룰 / **프로젝트 테스트 0건**) → `validate smoke test: PASS (probe verified, empty rules/tests warning)`. **프로비저닝 단계에서는 정상이며 차단하지 않는다.**
   - **전 회차 기대대로 + 프로젝트 실 위반** → `validate smoke test: PROBE OK, PROJECT FAIL` + stderr 요약. stack-guard 자체는 성공이라 종료 X, 사용자에게 *프로젝트 수정* 안내.
   - **일부 단계 미도달**(5-c-0 (ii) — 앞 단계의 기존 프로젝트 위반으로 멈추고 단독 실행도 불가) → `validate smoke test: PARTIAL (probe verified: <단계 목록> / not reached: <단계 목록>)` + 프로젝트 수정 안내. **종료 X — 배선 결함이 아니다.**
   - **일부 단계 부재**(5-c-0 (i)) → `validate smoke test: PARTIAL (probe verified: <단계 목록> / missing: <단계 목록>)`. **`PASS` 로 기록하지 않는다** — 부재 단계는 판정력이 *측정되지 않은* 것이고, `PASS` 로 적으면 커버리지 누락이 `[Guard-drift]` (d) 에서 침묵해 영구히 잊힌다. **종료하지 않는다** — **이번 실행에서 방금 생성한** 파이프라인이면 4단계를 채워 다시 구성하고(재측정으로 `PARTIAL` 이 해소된다), **이미 존재해 보존한** `validate`/`scripts/verify.*` 는 **덮어쓰지 않고 커버리지 부족만 보고**한다(`## 재실행 계약` 정합).
   - **1회차 (a) 실패**(범위 밖) → `validate smoke test: SKIPPED (probe out of tool scope — <추정 원인>)` + 확인 권고(도구 include·ignore 설정). **종료 X.** 프로비저닝 단계에서는 정상이며, 아래 5-f 기록을 통해 다음 마일스톤의 `[Guard-drift]` 가 재실행을 권고한다(ADR-063 D4 — **졸업 차단 항목은 없다**).
   - **probe 생성 불가**(권한·sandbox·이름 충돌·**등록된 소스/테스트 루트 부재** — 5-a) → `validate smoke test: SKIPPED (probe unavailable — <사유>)`. **종료 X.** 소스 루트 부재는 사유를 `등록된 소스 루트 부재` 로 고정해 적는다(5-a 와 동일 문자열).
   - **1회차 (b) 또는 2~5회 불일치**(그 단계가 실재하고 도달한 회차에서) → `validate smoke test: PROBE FAIL(<단계>)` + 생성된 명령 + 실패 stderr + 제안 대체(예: pnpm 비호환 → `npm run validate`). **stack-guard 산출물 수정 필요** — 5-d 정리 + 5-f 기록 후 종료.

   **5-f. 판정 기록 (필수 — ADR-063 D3)**: 위 최종 판정을 `docs/00-meta/STACK_SETUP_PLAN.md` 의 `## 통합 명령 사용법` 절에 `probe smoke: <판정> (<YYYY-MM-DD>)` 1줄로 기록한다(이미 있으면 갱신). probe 파일은 지워도 판정은 남는다 — 이 줄이 `/stabilize-milestone` `[Guard-drift]` (d) 의 유일한 입력이며, 없으면 `SKIPPED`·`PARTIAL` 이 조용히 잊힌다. **조기 종료 경로에서도 기록한다.**

   `validate:e2e` 판정 행 (e2e 대상 프로젝트 한정, ADR-052#amend-1 5상태):
   - **`NOT_APPLICABLE`** (e2e 대상 아님) → `validate:e2e: NOT_APPLICABLE (비-e2e 스택)`. 종료 X.
   - **`EMPTY`** (선언된 e2e 디렉터리 하위 suite에서 실제 **실행된** 테스트 0개 — 디렉터리가 비었거나 러너가 다른 디렉터리를 대신 실행한 경우) → `validate:e2e: EMPTY (프로비저닝 단계 정상 — 졸업 시점엔 차단)`. **본 단계에서는 종료하지 않는다.**
   - **`PASS`** (선언된 e2e 디렉터리 하위에서 1개 이상 실제 실행·성공 + 러너 전체 성공) → `validate:e2e: PASS (wiring OK)`.
   - **`FAIL`** — 두 원인을 **하위 라벨로 분리해 보고한다**(수행-5 smoke 의 wiring/프로젝트 책무 분리 원칙 유지). `FAIL(wiring)`: 진입점·config 부재로 러너가 기동조차 못 함 → **stack-guard 산출물 수정 필요**, 종료. `FAIL(project)`: 러너는 정상 기동했고 프로젝트 e2e 가 실패 → 프로젝트 책무로 분리 보고, 본 단계에서 종료하지 않는다(졸업 시점에는 차단).
   - **`BLOCKED_ENV`** (browser/device/toolchain 미설치·미기동) → `validate:e2e: BLOCKED_ENV — Needs Install: <실제 명령>`. 종료 X.

   **상태 정의와 판정 순서의 SSOT는 ADR-052#amend-1 결정 3이다** — 위 행은 *본 단계의 처리*(종료/비종료)만 정하고 정의를 재선언하지 않는다. 순서는 `FAIL(wiring)`·`BLOCKED_ENV` → `EMPTY` → `FAIL(project)` → `PASS`이며 먼저 성립하는 상태로 확정한다(행 나열 순서가 아니라 이 순서다 — 뒤집으면 진입점 부재를 `EMPTY`로 오분류해 고쳐야 할 산출물 결함을 통과시킨다). 상태 판정은 **구조화된 러너 출력**으로 한다(출력 문자열 매칭은 보조 fallback). `STACK_SETUP_PLAN.md ## E2E Smoke Registry`에 등록이 있으면 **선언된 runtime target별로** 읽어 `PASS` 조건을 *등록된 smoke 이름 일치*까지 좁히고, target마다 상태를 따로 낸다. **등록이 없어도 명령은 실행한다** — 위 경로 기준으로 판정하고 `P1 [E2E-registry] <target> — canonical smoke 미등록` 만 함께 기록한다(ADR-052#amend-1 결정 4).
   `validate:design` 판정 행 (UI 한정, ADR-072 D6):
   - **자가 검사 통과** → registry `status: ready (self-test PASS <YYYY-MM-DD>)`, 출력 `validate:design self-test: PASS`.
   - **module/browser/flutter 부재** → `status: needs-install` + `Needs Install: <실제 명령>`; design artifact 승인 보류.
   - **자가 검사 기대 불일치**(known-bad를 통과시킴) → `status: wiring-fail` + `validate:design self-test: FAIL`; 원인을 고치고 재실행 전까지 `ready` 금지.
   - **비-UI** → `status: n/a`; asset read/copy·browser 설치 없음.


   > 핵심 구분: stack-guard 의 책무는 *wiring* (`validate` + `validate:e2e` + UI `validate:design` entry·browser·conformance 까지). 프로젝트 실 위반은 *프로젝트 책무* 라 smoke test 가 잡되 stack-guard 가 차단하지 않는다.

6. **Toolchain 선설치 + E2E readiness** (실행 순서상 step 5 smoke test *앞*에 수행 — `allowed-tools` 의 Bash 활용, 신규 권한 불필요):
   - **6-1. 판정 (3축, 상호배타 아님 — ADR-059 D8 / ADR-073 D9 / ADR-072 D6)**: 매 실행 현재 파일로 다시 판정한다. 세 축을 **각각** 결정하며 동시에 참일 수 있다.
     - **design surface**: `docs/20-system/DESIGN.md` 부재 → 없음. 존재 + `## 0. Status` ≠ `draft` → 있음. 존재 + status == `draft` → 추가 신호((a) ARCH `## 7-4` 또는 `## 7-5` 활성, (b) ARCHITECTURE_OVERVIEW 기술 선택이 화면 있는 유형) ≥1 → 있음(의심 포함). 신호 0 → 없음. **design gate(6-4-1)는 이 축만 본다.**
     - **runtime target**: ARCH `## 7. 기술 선택`과 프로젝트 manifest로 판정. canonical 값은 `web`(브라우저에서 도는 앱) / `native/android` / `native/ios` / `desktop` / `none`(라이브러리·CLI)이다 — **Android 와 iOS 는 개별 값이며 `native` 하나로 합쳐 적지 않는다**(합치면 ADR-059 D4 플랫폼별 판정이 성립하지 않는다). `native/*`는 값이 아니라 그 둘을 묶는 클래스 표기이며 toolchain 분기에만 쓴다. **e2e 도구 선택(6-3·6-4)은 이 축만 본다.** 여러 값이 동시에 참일 수 있다.
     - **host environment**: 현재 OS(`windows` / `macos`). 실행 가능 범위 판단에만 쓴다. iOS 관련 항목은 `macos` 에서만 수행하고, 그 외에서는 `[미수행 — host 제약]` 으로 기록한다.
     정상 `/bootstrap-stack → /stack-guard → /bootstrap-design` 순서에서는 DESIGN status 가 draft 이고 `/bootstrap-stack` 이 ARCH `## 7-4` 또는 `## 7-5` 를 채우므로 design surface 가 "있음(의심)" 으로 발화한다. 기존 registry 가 `n/a` 여도 이후 신호가 생기면 이번 실행에서 재분류한다.
   - **6-2-0. Dependency Tools 교차 확인 (ADR-075 D14)**: `docs/00-meta/STACK_SETUP_PLAN.md` `## Dependency Tools` 표(있으면)와 저장소의 실제 *tool-specific* 신호(`package-lock.json`·`pnpm-lock.yaml`·`yarn.lock`·`bun.lockb`·`poetry.lock`·`uv.lock`·`Cargo.lock`·`go.mod`·`pubspec.yaml`+`pubspec.lock` 등)를 **scope별로 대조**한다. **표·행 부재 → 관측 신호로 보완 기록**(green-field면 이번 6-2 설치로 생성될 도구를 적는다). **표↔저장소 불일치 → 자동 수정하지 않고** 출력에 `Dependency Tool 불일치: <scope> 표=<A> 저장소=<B>`로 보고 + 사용자 결정 요청(아래 "도구 감지 우선 순서" 4와 동일 정책). **검증 도구 자체를 설치하는 PM 의 신호는 이 대조에서 제외한다** — 이 표는 *builder 가 프로젝트·기능 의존성을 설치할 때 쓰는 PM* 만 적는다(ADR-059 D2). 그래서 Flutter scope 의 `package-lock.json`(design gate·통합 명령용 npm)은 `pubspec.lock` 과의 *동일 scope 신호 충돌* 로 보지 않고, 표에는 `pub` 1행만 둔다. 웹 프로젝트의 `@playwright/test` 를 이 표에 적지 않는 것과 같은 경계다. 아래 6-2 설치는 이 확인을 통과한 scope 도구로 실행한다.
   - **6-2. Toolchain 설치 (전 스택 공통, 기계적 — 기본은 진행)**: 감지된 패키지 매니저로 authored devDeps 를 설치한다 — `pnpm install` / `npm install` / `pip install -e .` (또는 `uv sync`) / `go mod download` / `cargo fetch` / `flutter pub get` 중 스택에 자연스러운 1종. lockfile 존재 시 frozen 설치(`pnpm install --frozen-lockfile` / `npm ci`) 우선. 설치 후 lock 파일 변경은 그대로 둔다(finalize 자동 화이트리스트, ADR-007#amend-1). **주의 — *validate 가 부르는 도구 자체*가 깔리는지 확인**: 패키지 deps 만 받는 명령은 lint/type/test 도구를 빠뜨릴 수 있다(예: `pip install -e .` 는 dev 도구 미설치 → `pip install -e '.[dev]'` 또는 `uv sync --all-extras`; Go `golangci-lint`·Rust `clippy` 는 별도 설치). step 5 smoke 가 command-not-found 면 도구 설치 명령을 보강한다.
   - **6-2-1. 테스트 격리 권장 (ADR-075 D9)**: 생성하는 e2e/통합 설정에 *가능한 범위에서* 격리를 권장한다 — playwright `webServer`는 동적 포트, 통합 테스트는 트랜잭션 롤백/임시 스키마/testcontainers. stack-guard가 unit-test 격리를 직접 authoring하긴 어려우므로, 미보장 시 `STACK_SETUP_PLAN.md`에 "테스트 격리 미설정 — 병렬 builder 시 foreman 순차 권장" 1줄 부기(implement partition이 실제 보호).
   - **6-2-b. 기초 라이브러리 baseline 설치 (ADR-071 D6)**: registry에서 `disposition: 확정` **그리고** 카탈로그 `설치: baseline`인 행의 패키지를 **scope별로**(그 scope의 행을 그 scope의 PM으로 — ADR-071 D2 `(scope, id)` 키) 설치한다(UI 킷·스타일링·아이콘·UI 미리보기 도구·계측 SDK 등 — 폰트 제외). 버전은 registry `확인일`의 researcher 고정값. `설치: task` 행은 설치하지 않는다(plan-workitem → implement). 설치 실패는 6-5 `Needs Install` fallback.
     - **Storybook (웹 UI + registry `cat-web-ui-preview` = Storybook)**: 프레임워크 공식 통합으로 설치하고 **애드온은 `a11y` 하나만 둔다** — *viewport 는 Storybook 8 부터 코어 global 이라 별도 애드온이 존재하지 않는다*(기준 뷰포트는 `.storybook/preview` 의 `parameters.viewport.options` 로 둔다). 최신 `storybook init` 은 Chromatic·vitest·docs·mcp 애드온을 함께 깔고 `vitest.config` 까지 생성하므로 **설치 직후 결정 집합 밖 애드온·생성 파일을 제거한다**(ADR-071#amend-1). `package.json`에 `storybook`·`build-storybook` 스크립트가 없으면 추가한다. 정적 빌드 출력 `design-gate-storybook/`이 `.gitignore`에 없으면 추가한다. Flutter는 미리보기 도구를 설치하지 않는다.
     - **생성형 UI 킷(`cat-web-ui-kit` — shadcn/ui 등)**: 킷 자체는 설치한다(컴포넌트 코드가 import 한다). 다만 그 `init` 이 스타일시트에 쓴 **토큰·팔레트·폰트 변수 블록은 손대지 않고 기록만 한다** — `STACK_SETUP_PLAN` 에 `킷 토큰 블록 — R6-1 재배선 대상: <파일>` 한 줄. 그 시점에는 DESIGN `## 2`·`## 3` 이 없어 그 값들은 결정이 아니라 자리표시자이며, `/bootstrap-design` R6-1 이 킷 변수를 DESIGN semantic 토큰의 **별칭**으로 재정의해 출처를 하나로 만든다(ADR-071 D6). **킷이 끌고 오는 번들 의존(`class-variance-authority` 등)은 결정의 일부이므로 제거하지 않는다** — ADR-071#amend-1 의 「결정 밖 애드온 제거」는 *선택 가능한 애드온*(Storybook 의 Chromatic·vitest·docs·mcp)을 가리킨다. registry 그 행에 번들 의존 목록을 적는다.
     - **폰트 패키지·파일은 설치하지 않는다** — DESIGN `## 3` 확정 뒤 `/bootstrap-design` R6가 추가한다(ADR-071 D6 예외).
     - 설치 결과를 출력에 `baseline libs: <패키지 목록> (installed | Needs Install)`로 낸다.
   - **6-3. e2e 실행 환경 준비 (runtime target 기준)**:
     - **target 에 `web` 포함**: `npx playwright install` (CI/Linux 환경이면 `npx playwright install --with-deps` 제안만 부기, 자동 실행 X — OS 패키지 sudo 필요). **웹 경로는 개선 전과 동일하다.**
     - **target 에 `native/*` 포함**: **앱 e2e 목적의** 브라우저는 설치하지 않는다. 대신 **`flutter devices --machine` 으로 연결된 device 를 조회**해 선언된 target 별 후보(android / ios)와 각 device id 를 회수한다 — `flutter doctor` 는 toolchain 점검이 본업이고 device 는 개수만 요약해 *어느 것이 android/ios 인지* 와 *후보가 몇 개인지* 를 알 수 없다(ADR-059 D4). 어떤 target 의 후보가 0개면 그 target 에 대해 `Needs Device: <에뮬레이터/시뮬레이터 기동 명령>` 을 출력한다. iOS 는 host 가 `macos` 일 때만 확인한다.
     - **design surface 가 있음**: target 과 무관하게 `@playwright/test` 와 `@axe-core/playwright` 를 **devDep 으로 설치**하고 **`npx playwright install chromium` 까지 수행한다.** 모듈만 있고 브라우저 바이너리가 없으면 design gate adapter 가 `exit 2`(실행 불가)를 내고 registry 가 `status: needs-install` 로 굳어 **디자인 산출물 승인·프로토타입 승격·task 분해가 연쇄로 막힌다.** 즉 native 프로젝트에서도 design gate 용 chromium 은 필수다(ADR-059 D8). 이때 설치하는 Playwright 버전은 같은 저장소의 다른 웹 패키지와 맞추면 브라우저 캐시를 공유한다 — 버전이 다르면 브라우저 세트를 새로 내려받는다.
   - **6-4. `validate:e2e` scaffold (runtime target 기준, e2e 필요 시)**:
     - **target 에 `web` 포함**: `playwright.config.*` 가 *부재* 하면 최소 config(`testDir: 'e2e'`, 단일 chromium project, `webServer` 는 주석 placeholder)를 생성하고 `validate:e2e` 진입점(`playwright test`)을 박는다.
     - **target 에 `native/*` 포함**: 아래 셋을 모두 한다. **Playwright 에 배선하지 않는다** — `package.json` 이 design gate 때문에 존재하더라도 그것을 앱 e2e 의 근거로 삼지 않는다(ADR-059 D8).
       1. `flutter pub add "dev:integration_test:{sdk: flutter}"` 로 dev 의존성을 명시하고 `flutter pub get` 을 수행한다. `integration_test` 는 SDK 동봉이지만 `pubspec.yaml` 에 적히지 않으면 `import 'package:integration_test/...'` 가 해석되지 않아 e2e 파일이 컴파일조차 안 된다.
       2. `integration_test/` 디렉터리를 만든다.
       3. `validate:e2e` 진입점을 **`flutter test integration_test` 로 박는다 — `-d` 를 넣지 않는다.** 이유: `-d` 는 device id 또는 이름 접두사만 받으므로 `emulator-5554` 처럼 재부팅하면 달라지는 값이나 `연결된 Android device 1대` 같은 자연어를 넣으면 명령 자체가 성립하지 않는다. `-d` 를 생략하면 러너가 **`integration_test` 지원 device 로 후보를 좁힌 뒤** 하나만 남으면 자동 선택한다(실측: android emulator + windows + chrome + edge 4개가 붙은 상태에서 emulator 자동 선택, 오류 없음). 후보가 둘 이상 남을 때만 호출 측이 `npm run validate:e2e -- -d <id>` 로 지정한다. 어떤 device 를 골라야 하는지의 *규칙*은 registry 에 적는다. (참조 갱신: target이 둘 이상이면 target별 진입점은 `scripts/e2e-target.mjs`가 실행 시점에 id를 해석한다 — ADR-059#amend-1 결정 1. 진입점 문자열에 id를 박지 않는 원칙은 불변)
       4. **`--machine` 을 진입점에 박지 않는다** — 사람이 읽을 때는 기본 리포터가 낫고, 판정하는 쪽이 `npm run validate:e2e -- --machine` 으로 덧붙여 다시 호출한다(ADR-059 D4).
       5. **target이 둘 이상이면** `scripts/e2e-target.mjs`를 생성한다(ADR-059#amend-1 결정 1) — 입력: `node scripts/e2e-target.mjs <android|ios> [-- <flutter test 인자…>]`. 동작: registry `## E2E Smoke Registry`의 그 target 행 실행 대상 선택 규칙을 `flutter devices --machine` 출력에 대입해 device id를 얻고, **`--` 뒤 인자를 그대로 써서**(없으면 기본 `integration_test`) `flutter test <그 인자들> -d <id>`를 실행하며 `--machine` 스트림을 그대로 전달한다. **resolver는 device 선택만 더한다 — 테스트 경로·`--flavor` 같은 옵션을 삼키지 않는다.** 후보 0개면 `BLOCKED_ENV` 종료코드·메시지로 끝낸다. `validate:e2e:android`/`validate:e2e:ios` 진입점은 이 스크립트를 부르고, `validate:e2e`는 선언된 target을 순차 호출하는 집계 진입점으로 둔다.
     - **공통**: *같은 계열로 이미 존재* 하면 덮어쓰지 않고 발견 사실만 출력에 기록한다(도구 감지 우선순위 정합 — 기존 도구 미덮어씀). **target이 둘 이상이면** `validate:e2e:<target>`을 target마다 만들고(native는 `node scripts/e2e-target.mjs <target>` — device id를 실행 시점에 해석하며 `-d` 리터럴은 어디에도 박지 않는다) `validate:e2e`는 집계 진입점으로 배선한다(ADR-059#amend-1). 단일 target은 기존과 같다. 기존 `validate:e2e`가 한 계열로만 물려 있고 새 target이 추가되면 그 계열을 `validate:e2e:<target>`으로 옮기고 집계를 새로 만든다(덮어쓰기 아님 — 이동 사실을 출력에 기록). **native를 옮길 때는 기존 명령의 테스트 경로·옵션(`--flavor` 등)을 `node scripts/e2e-target.mjs <target> -- <그 인자들>`의 `--` 뒤로 그대로 옮긴다** — 고정 명령으로 바꾸면 옵션이 사라지고, 명령만 통째로 옮기면 device 해석이 빠진다. 이 e2e provision/smoke 는 milestone graduation 의 E2E hard-block(ADR-052#amend-1 / ADR-068 D3 item 3)이 검사할 대상을 선readiness 한다.
     - **6-4-a. canonical boot smoke (조건부 생성)**: e2e 대상이고 등록된 smoke 가 없을 때 —
       - **새 프로젝트이고 앱 진입점이 결정적이면**: 프레임워크 수준 boot smoke 를 1개 생성한다. 내용은 *"앱이 기동하고 첫 프레임이 예외 없이 렌더된다"* 까지이며 **어떤 화면을 볼지 고르지 않는다**(화면 선택은 제품 결정이라 계획 단계 소관).
       - **기존 코드가 있거나 로그인·외부 의존이 필요해 부팅만으로 성립하지 않으면**: 생성하지 않고 `Needs E2E Smoke — /plan-workitem 이 작성 line item 을 authoring 해야 함` 을 출력한다.
       - 생성했든 아니든 결과를 `STACK_SETUP_PLAN.md ## E2E Smoke Registry` 에 **runtime target 별로 한 행씩** 기록한다. **`native/*` 는 클래스 표기이므로 행으로 쓰지 않는다** — `native/android` 와 `native/ios` 를 함께 선언했으면 **두 행**이고 `web` 까지 선언했으면 세 행이다(ADR-059 D8). 판정도 각 행마다 따로 난다 — 한쪽 target 의 통과를 다른 쪽 근거로 쓰지 않는다.
     - **6-4-b. golden 초기 절차 안내 (runtime target 에 `native/*` 가 포함되고 화면이 있을 때)**: golden(픽셀 비교) 정답 사진은 **커밋하지 않으며 머신마다 로컬 생성**한다(ADR-059 D3). `.gitignore` 에 `**/test/**/goldens/` 와 `**/test/**/failures/` 가 있는지 확인하고 없으면 추가한다 — **`**/test/goldens/` 처럼 한 단계만 쓰면 안 된다**: golden key 는 그 테스트 파일이 있는 디렉터리 기준 상대경로라(`LocalFileComparator.basedir`) `test/widgets/foo_test.dart` 의 `goldens/foo.png` 는 `test/widgets/goldens/` 에 생성되고 한 단계 패턴에는 걸리지 않는다. 그리고 `STACK_SETUP_PLAN.md` 에 아래 절차를 1회성 안내로 기록한다 — *"새 체크아웃 직후 첫 `validate` 는 정답 사진 부재로 실패한다. `flutter test --update-goldens` 를 1회 실행하고 생성된 이미지를 육안 확인한 뒤 진행한다."* golden 테스트 위젯에는 `debugShowCheckedModeBanner: false` 를 준다. **재생성 규율도 함께 적는다** — *"`--update-goldens` 는 (a) 정답 사진이 아직 없을 때, (b) UI 를 의도적으로 바꾸고 새 모습을 육안 확인했을 때만 쓴다. golden 실패를 통과시키려고 덮어쓰지 않는다 — 그러면 회귀가 정답으로 굳는다."* golden 테스트 파일은 `@Tags(['golden'])`을 달고 `dart_test.yaml`에 `tags: golden:` 을 선언한다(ADR-059#amend-2).
     - e2e 대상이 아니면 6-3·6-4 를 skip 하되 6-2 toolchain 설치는 수행한다.
   - **6-4-1. design gate v3 어댑터 + Visual-QA scaffold (UI 한정 — ADR-072 D6 / ADR-058#amend-3)**:
     - **JIT read 경계**: 6-1이 UI 확정/의심일 때만 `.claude/skills/stack-guard/assets/design-gate.mjs`를 읽는다. 비-UI는 로드·복사·설치 없음(ADR-019).
     - **물질화**: canonical v3를 project-native 경로(기본 `scripts/design-gate.mjs`)로 복사하고 `validate:design` 진입점을 **npm 계열**로 박는다(`make`·`task` 금지 — exit 1/2 구분 보존, ADR-059 D2). Flutter도 design gate 진입점만 npm이다. command template: `<pm> validate:design -- <args>`. **`--` 는 npm 계열 PM 마다 취급이 다르다** — npm 은 제거해 스크립트에 넘기고 pnpm 은 리터럴로 그대로 넘긴다. 어댑터가 bare `--` 를 무시하므로(ADR-072#amend-1) 어느 PM 이든 같은 template 이 성립하지만, **기록할 때는 그 프로젝트 PM 으로 실제 1회 실행해 본 형태를 적는다.**
     - **Storybook 정적 빌드 출력**: `design-gate-storybook/`을 `.gitignore`에 추가(첫 실행 전). **monorepo 면 그 scope 에도 추가한다** — 게이트는 각 화면의 `scope` 를 작업 디렉터리로 쓰므로 빌드 출력이 `apps/web/design-gate-storybook/` 처럼 **scope 안에** 생긴다. **루트 `.gitignore` 만으로는 부족하다**: git 은 그 패턴을 어느 깊이에서든 무시하지만, scope 의 도구(예: `useIgnoreFile` 을 켠 Biome)는 **자기 루트의 ignore 파일만 읽어** 빌드 산출물을 format·lint 대상으로 삼는다(dogfood Round 12 실측 — `validate` format 단계가 빌드 파일을 훑다 사실상 멈췄다). 그래서 scope 의 `.gitignore` **와** 그 scope 도구 config 의 제외 목록 **양쪽에** `design-gate-storybook/`·`design-gate-shots/` 를 넣는다(수행 2-1 harness 경로 배제와 같은 자리).
     - **Flutter 자가 검사 fixture**: `pubspec.yaml`이 있으면 `test/design_gate/self_bad_test.dart`(known-bad 위젯 — 20px 탭 타겟 + 2:1 대비; 헤더 주석 «design gate self-test fixture — 실패가 정상»)와 `test/design_gate/self_ok_test.dart`(known-good)를 생성한다. **주의**: `flutter test`가 이 파일을 통합 `validate`에서 실행하지 않도록 `validate`의 test 단계에서 `test/design_gate/`를 제외한다(제외 방법은 도구 문서 확인 — SKILL에 키를 박지 않는다).
     - **자가 검사(최대 5케이스 — 웹 전용 프로젝트는 4)**: `<pm> validate:design -- --self-test` 실행(Flutter scope가 루트가 아니면 `--scopes apps/mobile`처럼 fixture를 만든 scope를 함께 넘긴다 — 루트 `pubspec.yaml`만 보면 monorepo에서 (d)가 조용히 빠진다) → 위 판정 행. **(d) Flutter fixture 케이스는 `pubspec.yaml` 이 있는 scope 에서만 돈다 — Dart 가 없는 웹 전용 프로젝트의 정상 결과는 (a)(b)(c)(e) 4케이스 PASS 이며 그것이 «자가 검사 통과»다.** **(e) 렌더 조건 케이스**(ADR-072#amend-5 결정 3)는 스택과 무관하게 항상 돈다. 케이스 수로 통과를 판정하지 않고 *실행된 케이스가 전부 기대와 같은가*로 판정한다(ADR-072#amend-1). **capability 버전 핸드셰이크·고정 적합성 oracle은 없다**(ADR-072 D6이 ADR-058#amend-2를 대체). 복사 직후 canonical asset의 SHA-256을 registry `copied-from`에 적는다(caller 대조 없음).
     - **registry 기록(6필드)**: `status | command template | adapter path | manifest 규약(docs/20-system/prototypes/<M|_theme>/manifest.json — ADR-072 D3) | self-test 일자 | copied-from`. 비-UI는 `status: n/a`만.
     - **single-origin**: `design-gate-shots/`는 렌더 실행마다 초기화, `design-gate-storybook/`는 빌드할 때만 재생성(`--no-build` 실행은 보존 — ADR-072 D6). 어느 쪽이든 같은 checkout에서 동시 2실행 금지(ADR-063 D7). 이 사실을 registry 하단에 1줄 부기.
     - **구현 앱 Visual-QA (별도 surface)**: e2e scaffold 시 `e2e/visual-qa.spec.*`도 생성해 렌더된 앱을 검사한다. breakpoint 320/375/768/1440 page overflow는 차단, 요소 겹침은 권고, populated axe serious/critical은 차단·moderate/minor는 권고. 가능한 runner에서는 generated geometry/axe helper를 두 surface가 재사용한다.
       - **전제 처리 (ADR-058#amend-3 — 표현을 고정한다)**: ① **spec이 전제를 소유하고, 소유한 전제가 화면에 반영됐는지 먼저 단언한다** — seed/fixture로 populated 상태를 만든 뒤 대표 항목 1건의 가시성을 첫 단언으로 두고, 그 뒤 검사한다. 대상 요소 부재는 *항상 실패*이며 seed 미반영도 실패다(ADR-058#amend-5). ② 소유가 불가능한 표면(로그인·외부 의존 필수)만 2분기로 한다 — **독립적인 empty 신호**(라우트 응답·명시적 seed 상태 표시. **대상 landmark 부재를 empty 근거로 쓰지 않는다** — selector·wiring 파손과 구분되지 않는다)로 앱이 비었음이 확인되면 `test.skip()`, **populated인데 selector·fixture·라우트가 준비되지 않았으면 실패시킨다**(그 실패가 `validate:e2e`의 `FAIL(project)`로 졸업을 차단한다 — 새 게이트를 만들지 않는다).
       - **`annotations`만 남기고 `return`하는 형태를 생성하지 않는다** — 러너가 passed로 집계해 "검사가 돌았다"는 거짓 신호가 된다. 판정 보류는 `test.skip()`(또는 그 스택의 동등 API)으로만 표현한다.
       - 앱이 비어 있어 skip한 경우에도 정적 adapter conformance를 통과시킨 것으로 간주하지 않는다.
       - **기존 spec 점검 (기록 등급)**: 선언된 e2e 디렉터리에서 `annotations` 기록 직후 `return`하는 패턴을 grep해 발견 시 `P1 [E2E-vacuous-skip] <file:line> — runner-native skip으로 교체 필요`를 출력에 남긴다. 문자열 검사라 차단하지 않는다(ADR-063 D6).
       - **전제를 자동으로 만들 수 없으면**(도메인 seed 수단 부재·자격증명 필요) spec을 생성하거나 덮어쓰지 않고 `Needs Visual-QA Precondition — /plan-workitem 이 seed·전제 line item 을 authoring 해야 함`을 출력한다(6-4-a의 `Needs E2E Smoke` 선례와 동형). **stack-guard가 도메인 seed를 발명하지 않는다.**
       - **판정 기록 (필수 — ADR-058#amend-3 결정 5)**: 위 결과를 `docs/00-meta/STACK_SETUP_PLAN.md` 의 `## 통합 명령 사용법` 절에 `visual-qa: <READY|PENDING> (<사유 — PENDING일 때만>) (<YYYY-MM-DD>)` 1줄로 기록한다(이미 있으면 갱신). **UI/web 대상일 때만 쓰고 비-UI에서는 줄을 만들지 않는다.** 이 줄이 `/stabilize-milestone` `[Guard-drift]` (e)의 유일한 입력이며, 없으면 전제 미준비가 조용히 잊힌다. **조기 종료 경로에서도 기록한다**(5-f `probe smoke:` 기록 규율과 동형).
     - 기존 `e2e/visual-qa.spec.*`는 덮어쓰지 않되 capability가 약하면 별도 adapter/helper로 보완한다. **스크린샷 vision 비평은 hot-loop 제외**(탐색/사람 검토는 stabilize §3-P). 졸업 e2e 게이트는 ADR-052 D3 / ADR-068 D3 item 3가 SSOT.
   - **6-5. Graceful fallback (날조·우회 금지)**: 6-2/6-3 의 설치 명령이 sandbox/네트워크/승인 차단으로 *실제 실패* 하면 fabricate 하지 않고 `Needs Install: <명령> — 메인 세션/사용자 실행 필요` 를 출력하고, 가능한 산출(진입점·config·verify 스크립트)은 계속 생성한다. 이후 step 5 smoke 는 해당 항목을 SKIPPED 로 처리한다. (implement-workitem 의 ADR-040#amend-1 `Needs Install` 패턴과 동일.)
   - **설치-소유 경계 주의(SSOT)**: 본 step 이 까는 것은 *toolchain + e2e 의존* + **카탈로그 `설치: baseline` 확정 행**(6-2-b)이다. *task 단위 런타임/기능 패키지*(결제 SDK 등)는 plan-workitem 이 authoring → implement-workitem 이 설치한다(ADR-040#amend-1). 경계 결정은 ADR-052(3분할) + ADR-071(baseline 라이브러리·스캐폴드 4번째 class).

마지막 출력:
- 스캐폴드 결과 (`done <생성기>` / `skipped <사유>`) + 권장 커밋 메시지
- **harness 경로 무결성 (수행 0-H)**: `변경 0건` 또는 `Harness-path drift: <경로> — <추정 원인>` (+ `AGENTS.md` 줄 수)
- baseline libs 설치 결과
- 생성/갱신한 파일 목록
- 운영 환경 가정 (R0 결과)
- 통합 명령 호출 방법 (예: `pnpm validate`; UI/web 이면 `pnpm validate:e2e` 도)
- 판정 결과 3축 (design surface: 있음(의심 포함)/없음 — ADR-073 D9 근거 신호 / runtime target: web·native/android·native/ios·desktop·none — 복수 선언 가능, `native` 단독 표기 금지 / host: windows·macos) — ADR-059 D8
- Toolchain 설치 결과 (`deps install: DONE (<pkg-manager>)` / `Needs Install: <명령>`); UI/web 이면 browser 설치 결과 (`playwright install: DONE` / `Needs Install: npx playwright install`)
- 매뉴얼 hook 등록 절차 SSOT 위치 ([GUARDRAILS_STRATEGY.md "## PostToolUse hook 매뉴얼 등록 절차"](../../../docs/00-meta/GUARDRAILS_STRATEGY.md)) — 생성된 STACK_SETUP_PLAN.md에는 link만 박힘.
- validate smoke test 결과 (`PASS (probe verified, project clean)` / `PASS (probe verified, empty rules/tests warning)` / `PROBE OK, PROJECT FAIL` / `PARTIAL (probe verified: … / not reached: …)` / `PROBE FAIL(<단계>)` / `SKIPPED (probe out of tool scope — …)` / `SKIPPED (probe unavailable — …)`) + 해당 시 `missing: <단계>` + **probe cleanup 결과** (`DONE (<n>개)` / `FAILED — 수동 삭제 필요: <경로 목록>`) + `STACK_SETUP_PLAN` 의 `probe smoke:` 기록 갱신 여부
- validate:e2e 상태 (e2e 대상 한정, runtime target별 — NOT_APPLICABLE / EMPTY / PASS / FAIL(wiring) / FAIL(project) / BLOCKED_ENV — ADR-052#amend-1)
- validate:design adapter 결과 (UI 한정 — registry status + self-test 일자 + command; 비-UI는 n/a)
- 후속 권장 단계 (`/plan-milestone` — M/F가 아직 없으면(ADR-057); `contract-ready` M에 task 0건/`draft`가 있으면 `/plan-workitem M<N>` → `/seal-milestone M<N>`(ADR-060); 이미 봉인·구현 중이면 `/implement-workitem` 또는 다음 M)
- 스택별 default verify template은 본 skill의 "스택별 verify 풀세트" 표 기준. 도구 변경 시 ARCHITECTURE_OVERVIEW.md ## 7-X 갱신.
- **옵션: Claude PostToolUse async adapter 예시** (사용자가 채택 시 `.claude/settings.local.json` 에 복사). GUARDRAILS_STRATEGY.md 의 PostToolUse 동기 hook 예시와 동일하게 *Unix / Windows 2 OS 예시* 모두 제공 — 동일 schema 에 `async: true` + `asyncRewake: true` 만 추가:

**Unix/macOS 예시:**

```json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "${CLAUDE_PROJECT_DIR}/scripts/verify.sh",
        "args": ["--changed"],
        "async": true,
        "asyncRewake": true
      }]
    }]
  }
}
```

**Windows 예시 (PowerShell 또는 `verify.mjs` 대응):**

```json
{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write|Edit",
      "hooks": [{
        "type": "command",
        "command": "powershell",
        "args": ["-File", "${CLAUDE_PROJECT_DIR}/scripts/verify.ps1", "--changed"],
        "async": true,
        "asyncRewake": true
      }]
    }]
  }
}
```

**Schema 주의**: 위 GUARDRAILS_STRATEGY.md PostToolUse 동기 hook 예시와 동일 패턴 — `matcher` 만 사용 (도구 이름 필터). 파일 확장자 필터는 *verify 스크립트 내부* 에서 처리 — Anthropic [hooks docs](https://code.claude.com/docs/en/hooks) 의 `if` 필드 단일-rule 제약 (`|`/`&&` 미지원) 회피. `asyncRewake: true` 는 verify 가 **exit code 2** 로 종료 시 Claude 를 깨워 stderr 를 system reminder 로 주입. Windows `command`/`args` 조합은 fork 적용 시 docs 직접 확인 — **본 예시는 1 차 해석이며 schema variant 가 발견되면 SSOT 가 아님**.

도입은 사용자 결정. 본 hook은 *조기 피드백 adapter* — 실패 시 Claude를 깨워 stderr를 system reminder로 주입. **차단형 게이트 아님** (완료 판정은 동기 `validate-workitem` / `finalize-workitem` / `stabilize-milestone`이 책임).

## 재실행 계약 (idempotent — ADR-063 D3)

본 skill 은 재실행 가능하며 **변경이 필요한 것만 건드린다**. 아래 표가 그 경계의 SSOT다.

| 산출물 | 재실행 동작 |
|---|---|
| `## Scaffold` (수행 0) | `status: done`이면 **재실행하지 않는다**. `skipped`인데 이후 소스 루트가 생겼으면 그대로 `skipped` 유지(brownfield 승격) |
| 6-2-b baseline 라이브러리 | registry 확정 행 중 **미설치분만** 설치. registry가 바뀌어 새 확정 행이 생기면 그것만 |
| `validate` / `validate:e2e` / `validate:design` 진입점 | 존재하면 **교체하지 않는다** |
| 도구 선택 (Biome / ESLint / Vitest / Jest 등) | 존재하면 **교체하지 않는다** (도구 감지 우선 순서 2) |
| toolchain 설치 | 이미 설치돼 있으면 재설치하지 않고 `deps already present` 출력 |
| `scripts/verify.*` 본문 | **존재하면 덮어쓰지 않는다.** 4단계 커버리지 부족만 출력에 보고한다 |
| 도구 config 의 harness 경로 배제 | 누락된 배제 항목만 **추가** (기존 규칙 미수정 — 수행-2-1) |
| `## Dart Source Roots` | 매 실행 **실측 갱신** (실제 소스 트리 조회) |
| `## E2E Smoke Registry` | 매 실행 **실측 갱신** (runtime target 별 재판정) |
| `## Design Gate Adapter` | 매 실행 **자가 검사 재실행**. canonical asset sha ≠ registry `copied-from`이면 갱신 대상 — project 사본 sha == `copied-from`(무수정)일 때만 새 canonical로 교체 + `copied-from` 갱신, 사본 sha == canonical sha면 코드 동일 — copied-from만 갱신(ADR-063#amend-1 결정 3); 다르면(local modification) 덮어쓰지 않고 diff 보고 + 사용자 결정 |
| `## Dependency Tools` | **보완만** — `/bootstrap-stack` 기록 행 미수정, 불일치는 보고 + 사용자 결정 |
| `## 로컬 환경 동기화` | **보완만** — 기존 행 미수정 (ADR-076 D4) |
| `## 운영 함정` | **수정하지 않는다** — `/run-milestone` 승격 행·사용자 행 보존. 절이 없으면 만들지 않는다 (ADR-076 D9) |
| `.gitattributes` | 전역 규칙 존재 확인 + 누락 규칙만 추가 (수행-4) |
| 임시 probe (`src/__stackguard_probe__.*` 등 등록 소스·테스트 루트 안) | 실행 시 생성 → 회차별 판정 → **전부 삭제**. `.gitignore` 에 등재하지 않는다(등재하면 도구가 검사에서 제외해 판정 불가) |
| `STACK_SETUP_PLAN ## 통합 명령 사용법` 의 `probe smoke:` 줄 | 매 실행 **최종 판정으로 갱신** (수행-5-f — `[Guard-drift]` (d) 의 유일한 입력) |

**재실행 시점**: `/bootstrap-stack --migrate` 직후, `/stabilize-milestone` 이 `P2 [Guard-drift]` 를 기록한 뒤(다음 `/plan-milestone` R0 가 회수해 안내), design gate canonical asset 갱신 시.

## 정적 분석 도구 권장 (스택별 1종, ADR-021)

| 스택 | 도구 | 비고 |
|------|------|------|
| TypeScript / JS | `dependency-cruiser` | layer 위반 룰을 ARCHITECTURE_OVERVIEW `## 3-1` 채움 시 함께 권장. |
| Python | `import-linter` | 동일 layer 룰 패턴 |
| Go | `go vet` (built-in) | 후속 보강 가능 |
| Rust | `cargo deny` + `cargo udeps` | unused deps + license/advisory 동시 점검 |
| Dart / Flutter | `flutter analyze`(내장) + `dependency_validator` | layer 위반 룰은 `custom_lint` 로 확장 가능. 보안 취약점은 `dart pub get` 이 표시하는 advisory + `pubspec.lock` 대상 OSV 스캐너 |

## 스택별 verify 풀세트 (default template)

본 표는 *runtime / 언어* 축으로 verify 도구 default 를 박는다. [ADR-031](../../../docs/90-decisions/boilerplate/ADR-031-non-web-out-of-scope.md) 의 *프로젝트 유형 축* (web frontend / API server / CLI / monorepo / Supabase / Flutter(Android·iOS) — 범위 SSOT는 ADR-031#amend-1·ADR-059 D1) 과는 *직교 차원* — 한 프로젝트는 *유형 1 + runtime 1* 의 조합으로 자기 verify 명령을 박는다 (예: *TS web frontend* = 유형 "web frontend" × runtime "TS" → TS web 행 적용). 본 표 자체는 ADR-031 의 직접 지원 유형 집합을 *축소하거나 대체하지 않는다*.

| runtime / 언어 (예시 프로젝트 유형) | format | lint | typecheck | unit test | e2e test |
|------|--------|------|-----------|-----------|----------|
| TS web (Next/Vite — 유형: web frontend) | Biome (또는 Prettier) | Biome (또는 ESLint) | `tsc --noEmit` | Vitest | Playwright |
| TS API (Express/Fastify/Hono — 유형: API server) | Biome (또는 Prettier) | Biome (또는 ESLint) | `tsc --noEmit` | Vitest | supertest 또는 동등 |
| TS CLI (유형: CLI) | Biome (또는 Prettier) | Biome (또는 ESLint) | `tsc --noEmit` | Vitest | (선택, snapshot) |
| TS monorepo (유형: monorepo — Nx/Turbo) | Biome (또는 Prettier) | Biome (또는 ESLint) | `tsc --noEmit` (workspace 별) | Vitest | 패키지별 적용 |
| TS + Supabase (유형: Supabase 통합) | Biome (또는 Prettier) | Biome (또는 ESLint) | `tsc --noEmit` | Vitest | Supabase test runner |
| Python | `ruff format --check` | `ruff check` | `mypy --strict` (또는 pyright) | pytest | (선택, 스택별) |
| Go | `gofmt -l` (출력이 비어 있지 않으면 실패로 처리) | `golangci-lint` | `go vet` (built-in) | `go test` | (선택) |
| Rust | `cargo fmt --check` | `clippy` | `cargo check` | `cargo test` | (선택) |
| Flutter/Dart (유형: 모바일 앱 — Android·iOS) | `dart format --output=none --set-exit-if-changed <등록 source root>` | `flutter analyze` (플래그 없이 — 완화하면 info 진단이 전부 빠진다, ADR-059 D2) | (lint 단계에 포함 — `flutter analyze`가 타입 검사를 겸한다) | `flutter test` | `flutter test integration_test` |

생성된 `validate` 명령은 위 표의 **format / lint / typecheck / unit test 4단계**를 *순서대로* 묶고(**Flutter/Dart는 `flutter analyze`가 lint와 typecheck를 겸하므로 3단계이며, 이때 "missing: typecheck"로 보고하지 않는다** — ADR-059 D2), **e2e는 `validate:e2e` 별도 명령으로 분리**한다 (task 단위 finalize는 e2e 제외, milestone 단위 stabilize만 실행). 4단계 중 어느 하나라도 빠지면 출력에 *"missing: <단계>"* 명시. **Dart/Flutter 의 `<등록 source root>` 는 placeholder 가 아니다** — verify 스크립트가 `STACK_SETUP_PLAN.md ## Dart Source Roots` 의 경로 목록을 읽어 인자로 넘기고, 존재하지 않는 경로는 제외한다. 그리고 **매 실행마다 표 ↔ 실제 소스 트리를 대조**해(위 `find … | cut -d/ -f2 | sort -u`) 표에 없는 최상위 Dart 디렉터리가 있으면 `Dart source root drift: <디렉터리> — STACK_SETUP_PLAN ## Dart Source Roots 갱신 필요` 를 출력한다(ADR-059 D2). **이 대조는 경고이며 종료코드를 바꾸지 않는다** — 문서 drift 로 무관한 작업을 막지 않되, 새 디렉터리가 형식 검사에서 조용히 빠지는 것은 알린다. **runtime target 이 web 이면** stack-guard 가 `validate:e2e` 진입점 + 최소 playwright config 를 scaffold 하고 browser 를 설치한 뒤(`npx playwright install`) `validate:e2e` 까지 smoke 한다(수행-6 + Step 5). **runtime target 에 `native/*`(Android·iOS) 가 포함되면** `validate:e2e` 를 `flutter test integration_test` 로 배선하고 **앱 e2e 를 Playwright 에 배선하지 않는다**(ADR-059 D8 — 단 design surface 가 있으면 design gate 용 chromium 은 설치한다). e2e 대상이 아니면 scaffold 를 skip 하되 toolchain 설치는 수행한다.

도구 선택은 **첫 fork에서 결정 + ARCHITECTURE_OVERVIEW.md `## 7-X`에 박힌다** — 이후 변경 시 [/bootstrap-stack](../bootstrap-stack/SKILL.md) 재실행 또는 수동 갱신.

**TS-first depth 권고**: TS 스택은 본 보일러플레이트 직접 지원 ratio가 가장 큼 → default를 `Biome (format+lint 통합) + tsc + Vitest + Playwright`로 박는다 (`Biome` 단일 선택으로 paralysis 차단). 사용자가 ESLint+Prettier 분리 선호 시 ARCHITECTURE_OVERVIEW.md에 명시 후 verify 갱신.

**도구 감지 우선 순서** (기존 프로젝트에 fork되는 경우):

1. **감지**: `package.json` 의존성·devDependencies / `.eslintrc*` / `.prettierrc*` / `biome.json` / `vitest.config.*` / `jest.config.*` / `playwright.config.*` 등 *기존 도구 흔적* 먼저 확인.
2. **존재 → 그대로 사용**: 위 도구 중 어느 것이 *이미 박혀 있으면* default로 *덮어쓰지 않는다*. 예: ESLint+Prettier+Jest가 박힌 프로젝트에 Biome+Vitest를 강제 install 금지. 발견 도구를 ARCHITECTURE_OVERVIEW.md `## 7-X`에 기록.
3. **부재 → Biome+tsc+Vitest+Playwright default 박음**: green-field 또는 도구 미정 프로젝트에만 적용.
4. **충돌(Biome ↔ ESLint+Prettier 둘 다 박힘 등)**: 사용자에게 출력으로 보고 + 결정 요청. 자동 선택 X.

**Dependency 설치 정책** (toolchain 선설치 — 기본은 *설치한다*, 차단 시 graceful fallback):

- `/stack-guard` 는 *authored toolchain 을 기본 설치* 한다(수행-6). 산출은 `package.json` 의 `scripts.validate`(+ UI/web 이면 `scripts.validate:e2e`) 진입점 + verify 스크립트 본문 + 실제 설치된 devDeps(예: `biome / typescript / vitest / @playwright/test`) + (UI/web) playwright browser.
- 패키지 매니저 설치는 lockfile 존재 시 frozen(`pnpm install --frozen-lockfile` / `npm ci`) 우선, 부재 시 일반 install. 설치된 devDeps 목록을 출력에 박는다.
- **설치 범위 경계(SSOT)**: stack-guard 가 까는 것은 *toolchain + e2e 의존* + **카탈로그 `설치: baseline` 확정 행**(6-2-b)이다. *task 단위 기능 패키지*는 plan-workitem authoring → implement-workitem 설치(ADR-040#amend-1). 경계 결정 기록은 ADR-052(3분할) + ADR-071(baseline 라이브러리·스캐폴드).
- **Graceful fallback (날조·우회 금지)**: 네트워크 / 사용자 승인 / lockfile 충돌 / monorepo workspace 라우팅 / sandbox 정책으로 설치가 *실제 실패* 하면 fabricate 하지 않고 `Needs Install: <명령> — 메인 세션/사용자 실행 필요` 를 출력하고 가능한 산출(진입점·config·verify 스크립트)은 계속 생성한다(implement-workitem ADR-040#amend-1 패턴 동일). 이후 smoke 는 SKIPPED.
- 이미 설치돼 있으면(노드 모듈/lock 정합) 재설치하지 않고 verify 스크립트만 박되, 설치 상태를 `deps already present` 로 출력한다.

## Secret scanner 권장 (전 스택, ADR-021)
- `gitleaks` 또는 `trufflehog`. 둘 중 1종 선택.
- finalize 직전 staged 파일에 secret 패턴 검출 시 보고 → 프로젝트가 `validate`/CI fail 처리 선택.
- *강제 X, 권장만* (ADR-010 multi-tool 호환).

**이미 추적 중인 서명·인증 자산 점검 (전 스택, 필수 보고 — ADR-059 D9)**: 위 scanner 는 *staged 내용*을 보는 권장 도구이고, 이 항목은 **이미 커밋돼 추적 중인 파일**을 찾는 별개 점검이다. `.gitignore` 는 이미 추적 중인 파일을 보호하지 않으므로 brownfield 저장소에서는 규칙 추가만으로 해결되지 않는다. 매 실행 1회 확인한다.

```bash
git ls-files | grep -Ei '\.(jks|keystore|p12|mobileprovision|p8)$|key\.properties|-firebase-adminsdk-.*\.json|service-?account.*\.json'
```

- **출력 없음** → `tracked secrets: none` 1줄 보고.
- **출력 있음** → `Needs Secret Rotation: <경로 목록>` 을 출력하고 순서대로 안내한다 — ① 파일을 저장소 밖으로 옮긴다 ② `git rm --cached <path>` 로 추적을 끊는다 ③ **원격에 올라간 이력이 있으면 키 자체를 재발급한다**(히스토리에 남은 키는 ignore 로 되돌릴 수 없다). **stack-guard 가 직접 옮기거나 `rm` 하지 않는다** — 키 취급은 사용자 결정이고 잘못 지우면 복구가 불가능하다. 종료하지 않고 보고만 한다.
- 파일명 열거의 한계는 ADR-059 D9와 같다 — 경계 있는 통제는 credential 을 `secrets/` 하위에 두는 것이다.

`validate` 명령에 lint 단계로 통합 권장 — CI fail 처리는 프로젝트 결정.

## DESIGN.md lint 권장 (UI + Node 계열 한정, ADR-073 D10)
- **조건**: `docs/20-system/DESIGN.md` 존재(UI 프로젝트) **그리고** 스택이 Node 계열(npx 사용 가능)일 때만.
- **권장 명령** (강제 X, shared 기본값 미등록 — 사용자가 채택 시 `validate` 의 lint 단계 또는 CI에 wiring):
  ```bash
  npx @google/design.md@<x.y.z> lint docs/20-system/DESIGN.md   # version-pin (alpha라 변동 — 실제 버전 고정)
  # 규칙 목록은 문서에 박지 말고 runtime 조회: npx -p @google/design.md@<x.y.z> designmd spec --rules
  # Windows: npx -p @google/design.md@<x.y.z> designmd lint docs/20-system/DESIGN.md
  ```
- 검사 항목은 버전마다 다르므로 `spec --rules`로 조회한다(현재 broken token ref / WCAG contrast / orphaned token / section ordering 등). **format·declared-token 보조일 뿐 browser a11y 게이트가 아니다** — 실화면 접근성은 위 breakpoint 루프 (c)의 populated axe·렌더가 담당. exit 1 on error.

**설치 배선 — `@axe-core/playwright` devDep**: 6-2 toolchain 설치 또는 6-4-1 scaffold 시점에 `@axe-core/playwright`를 **devDep로 설치**한다(감지된 PM으로, 예: `npm i -D @axe-core/playwright`) — 설치하지 않으면 generated visual-qa spec·`validate:design` adapter가 `needs-install`/exit 2로 끝난다. 설치 실패는 stack-guard 6-5 `Needs Install` fallback(날조 금지). UI 프로젝트는 Playwright(재사용) + `@axe-core/playwright`(신규 devDep) 둘을 갖는다.

> **기존 fork 마이그레이션**: 기존 `e2e/visual-qa.spec.*`는 덮어쓰지 않는다. v1/v2 adapter(`design-gate-conformance.mjs` 존재)는 v3 사본으로 교체하고 conformance 파일과 registry의 digest·capability·conformance 행을 제거한 뒤 자가 검사를 돌린다. local modification은 diff 보고 + 사용자 결정. 기존 `status=n/a`도 frontend 신호가 생긴 재실행에서 UI로 승격한다. UI→비-UI 전환은 사용자 확인 뒤 generated adapter/entry만 제거한다(ADR-072 D6).
- **Motion 확장 주의**: 본 보일러플레이트는 Motion 을 canonical 8섹션 외 확장으로 둔다(ADR-073 D2). lint 의 section-ordering 은 canonical 8섹션 상대 순서만 보므로 통과하지만, 만약 특정 버전이 비-canonical 섹션을 경고하면 그 경고는 *무시 가능*(의도된 확장).
- 비-Node 스택·비-UI 프로젝트는 본 항목 skip. *GUARDRAILS_STRATEGY "OS·런타임 종속 자동화 강제 X" 정합 — npm 의존이라 shared 기본값에는 넣지 않는다.*

## CI 생성 (ADR-025#amend-1)
**아래 두 조건이 모두 참이면 `.github/workflows/validate.yml`을 기본 생성한다** — (i) git remote가 GitHub이고(`git remote -v`로 확인), (ii) 스택 판정이 끝나 통합 `validate` 명령이 존재한다. 생성 사실과 파일 경로를 출력에 명시하고, `docs/00-meta/STACK_SETUP_PLAN.md`에 `CI: generated (.github/workflows/validate.yml)` 한 줄을 기록한다(Flutter 포함이면 `CI: generated (.github/workflows/validate.yml, validate:ci — golden 제외, ADR-059#amend-2)`).
- **`--no-ci` 플래그가 있으면 생성하지 않고** `CI: opt-out (사용자 지정)`을 기록한다.
- 위 두 조건 중 하나라도 거짓이면(GitHub 아님·스택 미정) 생성하지 않고 **형식 권장 텍스트만** 출력하며 `CI: n/a (<사유>)`를 기록한다.
- **이미 `.github/workflows/validate.yml`이 있으면 덮어쓰지 않는다** — `CI: existing (preserved)`를 기록하고 커버리지 부족만 출력에 보고한다(`## 재실행 계약` 정합). brownfield 첫 실행의 정상 결과이며 `generated`·`n/a` 어느 쪽으로도 적지 않는다. Flutter 포함이면 마지막 단계가 `validate`를 부르는지 확인하고, 그렇다면 golden 부재로 fresh runner에서 실패하므로 **`validate:ci`로 교체 권장** 한 줄을 출력에 남긴다(ADR-059#amend-2).
- **생성하는 YAML은 fresh runner에서 실제로 도는 것이어야 한다.** checkout 뒤 곧바로 `validate`만 부르면 런타임·의존성이 없어 실패한다. 감지한 스택에 맞춰 아래 3단계를 반드시 포함한다 — ① 런타임 setup(`actions/setup-node@v4` + `node-version` / `actions/setup-python@v5` / `actions/setup-go@v5` / `subosito/flutter-action@v2` 등 1종), ② 의존성 설치(수행-6-2와 같은 명령 — lockfile 있으면 frozen), ③ 통합 `validate` 실행(Flutter 포함이면 `validate:ci`, 아니면 `validate` — ADR-059#amend-2). **e2e는 기본 워크플로에 넣지 않는다**(브라우저·device 프로비저닝이 필요해 실패 소음이 된다 — 별도 워크플로는 사용자 결정).
로컬 PostToolUse hook 1-명령 설정 안내([GUARDRAILS_STRATEGY.md "## PostToolUse hook 매뉴얼 등록 절차"](../../../docs/00-meta/GUARDRAILS_STRATEGY.md))도 함께 출력:
```yaml
name: validate
on: [push, pull_request]
jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4  # 스택에 맞는 런타임 setup (setup-python/setup-go/flutter-action 등 1종)
        with:
          node-version: <버전>
      - run: <의존성 설치 명령>
      - run: <stack의 validate 명령>
```
GUARDRAILS_STRATEGY *"OS/셸 종속 hook 강제 X"* 정신 — **로컬 PostToolUse hook은 언제나 권장만**이고, 위 CI 파일은 조건 충족 시 기본 생성한다(ADR-025#amend-1).

## validate --changed (incremental, ADR-020)
- git diff 기반 변경 파일만 lint/typecheck/test.
- Nx affected / Turbo affected 패턴 차용.
- **사용 시점**:
  - `/finalize-workitem` 직전 → `--changed`만 (빠른 회전).
  - `/stabilize-milestone` → full validate (누락 차단).

## Context 정책 (ADR-019)
`반드시 먼저 읽을 파일`은 *최소 충분*. 추가 ADR/architecture 섹션은 task 본문에서 발화 시 인용 — 사전 fork-load 금지.

Files in this skill

  • SKILL.md77.8 KB
  • assets/design-gate.mjs46.5 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.