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.
npx claudepluginhub tjdrhs90/rn-launch-harness --plugin rn-launch-harnessThis skill is limited to using the following tools:
계약 기준에 따라 React Native + Expo 앱을 빌드한다.
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.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Executes ctx7 CLI to fetch up-to-date library documentation, manage AI coding skills (install/search/generate/remove/suggest), and configure Context7 MCP. Useful for current API refs, skill handling, or agent setup.
Share bugs, ideas, or general feedback.
계약 기준에 따라 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/react-logo@2x.png
rm -f assets/images/react-logo@3x.png
# 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 사용