From rn-launch-harness
Builds React Native + Expo mobile apps in scaffold, API, UI sub-phases from harness contract, PRD, design docs; cleans templates, sets env, self-evaluates before handoff.
How this skill is triggered — by the user, by Claude, or both
Slash command
/rn-launch-harness:rn-harness-generatorThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
계약 기준에 따라 React Native + Expo 앱을 빌드한다.
계약 기준에 따라 React Native + Expo 앱을 빌드한다.
오케스트레이터에서 Phase 5 (Generator)로 호출됨.
docs/harness/contract.md (완료 기준)docs/harness/plans/YYYY-MM-DD-prd.md (PRD)docs/harness/plans/YYYY-MM-DD-design.md (디자인)docs/harness/feedback/round-N-*.md (Round 2+ 시 Evaluator 피드백)Read app_slug from docs/harness/config.md (set during Phase 2).
The Expo project is created as a subdirectory of the current working directory, keeping the claude session history intact.
# $APP_SLUG = kebab-case app name from config.md (e.g., budget-book)
npx create-expo-app@latest $APP_SLUG
cd $APP_SLUG
mkdir -p credentials
# Move harness artifacts into the project (from parent directory)
mv ../docs ./ 2>/dev/null || true
NOTE: Always use npx create-expo-app@latest to get the latest Expo SDK version.
NOTE: After project creation, docs/harness/ is moved inside the project so all artifacts are in one git repo. The parent directory only retains .claude/ session history.
Remove unused default files generated by create-expo-app:
# Remove default reset script
rm -f scripts/reset-project.js
rmdir scripts 2>/dev/null || true
# Remove default template images (will be replaced with app-specific assets)
rm -f assets/images/partial-react-logo.png
rm -f assets/images/react-logo.png
rm -f assets/images/[email protected]
rm -f assets/images/[email protected]
# Remove default example components/screens if they exist
rm -rf components/__tests__/
rm -f components/EditScreenInfo.tsx
rm -f components/ExternalLink.tsx
rm -f components/HelloWave.tsx
rm -f components/ParallaxScrollView.tsx
rm -f components/ThemedText.tsx
rm -f components/ThemedView.tsx
rm -f components/Collapsible.tsx
rm -f components/HapticTab.tsx
rm -rf components/ 2>/dev/null || true
# Remove default tab screens (will be rewritten)
rm -f app/(tabs)/explore.tsx
rm -f app/(tabs)/index.tsx
rm -f app/+not-found.tsx
rm -f app/+html.tsx
# Remove default constants
rm -f constants/Colors.ts
rm -rf constants/ 2>/dev/null || true
# Remove default hooks
rm -f hooks/useColorScheme.ts
rm -f hooks/useColorScheme.web.ts
rm -f hooks/useThemeColor.ts
rm -rf hooks/ 2>/dev/null || true
HARD GATE: Default template files MUST be removed. They cause confusion, import errors, and lint warnings. Only keep assets/images/icon.png, assets/images/splash-icon.png, assets/images/adaptive-icon.png, assets/images/favicon.png.
# Copy .env.example from plugin templates/
cp $CLAUDE_PLUGIN_ROOT/templates/.env.example .env.example
cp .env.example .env
# Copy .gitignore
cp $CLAUDE_PLUGIN_ROOT/templates/.gitignore.template .gitignore
IMPORTANT: .env and credentials/ are in .gitignore — never committed to git.
Dependencies install:
# Core
npx expo install expo-router react-native-safe-area-context react-native-screens expo-linking expo-constants expo-status-bar
# Styling
npm install nativewind tailwindcss
npx tailwindcss init
# State
npm install zustand @tanstack/react-query axios
# Forms
npm install react-hook-form zod @hookform/resolvers
# UI
npm install @shopify/flash-list react-native-reanimated @gorhom/bottom-sheet
# AdMob + ATT
npm install react-native-google-mobile-ads
npx expo install expo-tracking-transparency
# OTA Updates
npx expo install expo-updates
# Test
npm install -D vitest @testing-library/react-native
# Initialize EAS
eas init
# Configure EAS Update (OTA)
eas update:configure
This auto-adds updates.url and runtimeVersion to app.config.ts.
app.config.ts 필수 설정:
export default {
name: "$APP_NAME",
slug: "$APP_SLUG",
version: "1.0.0",
// Default language: Korean
primaryLanguage: "ko",
// Orientation: Portrait only
orientation: "portrait",
// EAS Update (OTA) — auto-configured by eas update:configure
updates: {
url: "https://u.expo.dev/[PROJECT_ID]",
},
runtimeVersion: {
policy: "appVersion",
},
// Bundle ID — same for iOS/Android
ios: {
bundleIdentifier: "com.{company}.{appname}",
buildNumber: "1",
supportsTablet: false, // iPad 지원 제거
infoPlist: {
// 암호화 미사용 선언 (심사 시 불필요한 질문 방지)
ITSAppUsesNonExemptEncryption: false,
// ATT 권한 요청 문구
NSUserTrackingUsageDescription:
"맞춤형 광고를 제공하기 위해 활동 추적 권한이 필요합니다.",
// Accessibility Bundle Name (다양한 검색 키워드)
CFBundleDisplayName: "$APP_DISPLAY_NAME",
CFBundleSpokenName: "$APP_SPOKEN_NAME", // 영문 발음명
},
config: {
usesNonExemptEncryption: false,
},
},
android: {
package: "com.{company}.{appname}", // iOS와 동일
versionCode: 1,
adaptiveIcon: {
foregroundImage: "./assets/images/adaptive-icon.png",
backgroundColor: "#ffffff",
},
// SafeArea: Android에서도 상태바/네비게이션바 침범 방지
softwareKeyboardLayoutMode: "pan",
},
plugins: [
"expo-router",
"expo-updates",
"expo-tracking-transparency",
[
"react-native-google-mobile-ads",
{
androidAppId: "ca-app-pub-XXXX~ZZZZ",
iosAppId: "ca-app-pub-XXXX~YYYY",
},
],
],
};
iOS에서 AdMob 광고 최적화를 위해 광고 추적 권한 요청이 필요. 앱 마운트 직후 바로 요청하면 얼럿이 안 나오므로 반드시 딜레이를 준다.
src/core/providers/TrackingProvider.tsx:
import { useEffect } from 'react';
import { Platform } from 'react-native';
import { requestTrackingPermissionsAsync } from 'expo-tracking-transparency';
export function useTrackingPermission() {
useEffect(() => {
if (Platform.OS !== 'ios') return;
// 앱 완전히 로드된 후 2초 딜레이
const timer = setTimeout(async () => {
await requestTrackingPermissionsAsync();
}, 2000);
return () => clearTimeout(timer);
}, []);
}
Root _layout.tsx에서 호출:
import '../global.css';
import { useTrackingPermission } from '@core/providers/TrackingProvider';
export default function RootLayout() {
useTrackingPermission();
// ...
}
HARD GATE: ATT 없이 AdMob 사용 시 Apple 심사 리젝 사유.
반드시 6가지 설정 완료:
babel.config.js — ['babel-preset-expo', { jsxImportSource: 'nativewind' }] + 'nativewind/babel'metro.config.js — withNativeWind(config, { input: './global.css' })tailwind.config.js — presets: [require('nativewind/preset')] + content pathsglobal.css — @tailwind base; @tailwind components; @tailwind utilities;_layout.tsx — import '../global.css'nativewind-env.d.ts — /// <reference types="nativewind/types" />하나라도 누락 시 className이 동작하지 않아 전체 UI가 깨짐.
PRD의 FSD 모듈 맵에 따라:
src/
├── core/providers/
├── features/{name}/
│ ├── api/{name}.api.ts
│ ├── hooks/use-{name}.ts
│ ├── types/{name}.types.ts
│ ├── ui/ (필요시)
│ ├── store/{name}.store.ts (필요시)
│ └── index.ts
├── entities/{name}/
│ ├── api/{name}.api.ts
│ ├── store/{name}.store.ts
│ ├── types/{name}.types.ts
│ └── index.ts
├── widgets/
└── shared/
├── api/client.ts
├── config/{env,theme}.ts
├── lib/
├── types/common.ts
└── ui/{Button,Card,Input,Typography}.tsx
Step 5~6은 한 번에 구현하지 않고 3개 서브 페이즈로 나누어 순차 진행한다. 각 서브 페이즈 완료 후 반드시 Quick QA 게이트를 통과해야 다음으로 진행한다.
PRD의 모든 feature와 entity에 대해 디렉토리 구조와 타입을 먼저 생성:
I prefix, Type T prefix, Enum E prefix)Quick QA Gate:
npm run typecheck && npm run lint
Spec Checkbox Update: docs/specs/ 디렉토리가 존재하면 완료된 항목의 체크박스를 업데이트한다 (- [ ] → - [x]).
Shared 레이어의 API 클라이언트를 기반으로 각 feature/entity의 API를 구현:
Quick QA Gate:
npm run typecheck && npm run lint
Spec Checkbox Update: docs/specs/ 디렉토리가 존재하면 완료된 항목의 체크박스를 업데이트한다 (- [ ] → - [x]).
Feature hooks를 화면에 연결하고 전체 UI를 구현:
SafeAreaView 필수className만 사용 (inline style 금지)Quick QA Gate:
npm run typecheck && npm run lint
Spec Checkbox Update: docs/specs/ 디렉토리가 존재하면 완료된 항목의 체크박스를 업데이트한다 (- [ ] → - [x]).
HARD GATE: 각 서브 페이즈의 typecheck + lint 게이트를 통과하지 못하면 다음 서브 페이즈로 진행 금지. 에러를 먼저 수정한 후 진행한다.
기존 Step 5 (Feature/Entity 구현)와 Step 6 (화면 구현)은 위의 서브 페이즈 패턴으로 대체되었다. 이 패턴은 react-native-fsd-agent-template에서 검증된 방식으로, 각 단계에서 타입 안전성을 확보한 후 다음 단계로 진행하여 에러 전파를 최소화한다.
Before self-evaluation, detect the user's environment and attempt to run the app.
Step 8a: Environment Detection
# OS detection
uname -s # Darwin = macOS, Linux, MINGW/MSYS = Windows
# Xcode check (iOS — macOS only)
xcode-select -p 2>/dev/null && echo "XCODE=yes" || echo "XCODE=no"
# Android SDK check
[ -d "$ANDROID_HOME" ] || [ -d "$ANDROID_SDK_ROOT" ] && echo "ANDROID_SDK=yes" || echo "ANDROID_SDK=no"
# Android emulator running?
adb devices 2>/dev/null | grep -q "emulator\|device" && echo "ANDROID_EMU=yes" || echo "ANDROID_EMU=no"
# iOS simulator running? (macOS only)
xcrun simctl list devices 2>/dev/null | grep -q "Booted" && echo "IOS_SIM=yes" || echo "IOS_SIM=no"
# Has native modules? (AdMob = yes → Expo Go won't work)
grep -q "react-native-google-mobile-ads" package.json && echo "NATIVE_MODULES=yes" || echo "NATIVE_MODULES=no"
Record results in handoff:
## Environment
- OS: [macOS/Linux/Windows]
- Xcode: [yes/no]
- Android SDK: [yes/no]
- Native modules: [yes/no]
- Expo Go compatible: [yes/no]
Step 8b: Choose Run Strategy
| Condition | Strategy |
|---|---|
| No native modules (no AdMob) | npx expo start (Expo Go OK) |
| Native modules + macOS + Xcode | npx expo run:ios |
| Native modules + Android SDK | npx expo run:android |
| Native modules + no local SDK | eas build --profile development (cloud) |
| Windows | Android only (no iOS) |
| Linux | Android only (no iOS) |
Step 8c: Run App
Based on detected environment:
# Option A: Expo Go (no native modules)
npx expo start
# Option B: Local native build (native modules present)
# macOS + Xcode → iOS simulator
npx expo run:ios
# Android SDK available → Android emulator
npx expo run:android
# Option C: Cloud dev build (no local SDK)
eas build --profile development --platform android
# Then install .apk on emulator/device and:
npx expo start --dev-client
IMPORTANT: AdMob + Expo Go = CRASH. If react-native-google-mobile-ads is installed, Expo Go cannot be used. The app MUST be built as a development client or via expo run:*.
Step 8d: Verify App Runs
After starting:
If NO local build environment is available at all:
npm run typecheck # 0 errors
npm run lint # 0 errors
npm test # all pass
Contract criteria self-verification + handoff document.
docs/harness/feedback/round-N-*.md 읽기docs/harness/handoff/round-N-gen.md:
# Generator Handoff — Round N
## What Was Built/Fixed
[요약]
## Contract Self-Assessment
- [DONE] 기준 1: [증거]
- [DONE] 기준 2: [증거]
- [PARTIAL] 기준 3: [미비 사항]
## Test Results
- npm run typecheck: PASS (에러 0)
- npm run lint: PASS (에러 0)
- npm test: 24 tests, 24 passed
## FSD Compliance
- 레이어 규칙: PASS
- barrel export: PASS
- any 타입: 0개
## Known Issues
- [미해결 사항]
Git commit:
git add .
git commit -m "feat: Round N — [요약]"
next_role: rn-harness-evaluator
any 타입 사용 금지@/ alias 사용npx claudepluginhub tjdrhs90/rn-launch-harness --plugin rn-launch-harnessProvides behavioral guidelines to reduce common LLM coding mistakes, focusing on simplicity, surgical changes, assumption surfacing, and verifiable success criteria.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.