mobileapp-builder
Given a spec.md, autonomously build and ship a Swift/SwiftUI iOS app to the App Store.
Full spec: .cursor/plans/ios/1.6.3/mobileapp-builder-spec.md
INPUT
spec.md path (required fields: app_name, bundle_id, version, price_monthly_usd,
price_annual_usd, output_dir, concept, screens, paywall, metadata)
See references/spec-template.md for the full spec.md format.
OUTPUT
asc review submissions-list → state = WAITING_FOR_REVIEW
SELF-IMPROVEMENT RULE
失敗 → 即この SKILL.md に正しい CLI コマンドを書き込む → git push → 書き込み前に次フェーズへ進むのは禁止。
| トリガー | アクション |
|---|---|
| Apple リジェクット | 原因分析 → 該当 PHASE に修正ルール追記 → git push |
| CLI コマンドがエラー | 正しいコマンドを発見したら即 SKILL.md を修正 → git push |
| 「手動が必要」と思った | まず --help で確認する。CLI にある可能性が高い。確認前に「手動」と言うのは禁止 |
| 想定外のことが起きた | 修正せずに進むのは禁止。SKILL.md を直してから次へ |
書き方ルール(具体例 + CLI コマンド必須):
❌ 禁止: 「IAP は手動でASC Webから追加が必要」
✅ 必須: 「`asc subscriptions submit --subscription-id <ID> --confirm` で CLI から直接 submit できる。
2026-02-25 実機確認済み(実機確認済み。自分のサブスク ID は `asc subscriptions list --app <APP_ID>` で取得)」
禁止:
- 確認せずに「手動が必要」と言う → 必ず
asc <subcommand> --helpで確認してから判断 - 「次は気をつける」だけで終わらせる → 具体的な CLI コマンドを SKILL.md に追記すること
- git push せずに進む →
git add -A && git commit && git pushまで完了させる - セッション注入コンテキスト(
projectSettings:mobileapp-builder)だけを信用する → 必ずReadツールで実ファイル(/Users/cbns03/Downloads/mobileapp-builder/SKILL.md)を確認する → セッション注入は古いキャッシュ版の可能性がある。実ファイルが SSOT。
CRITICAL RULES (違反 = リジェクト確定)
| # | ルール |
|---|---|
| 1 | 提出前に全サブスクが READY_TO_SUBMIT。MISSING_METADATA のまま提出 → Guideline 2.1 拒否 |
| 2 | IAP pricing は全175カ国。US のみは Guideline 2.1 拒否 |
| 3 | Superwall 使用禁止。RevenueCat のみ |
| 4 | ビルドは Fastlane gym のみ。ビルド後(upload/submit/metadata/screenshots)は ASC CLI のみ。Fastlane の deliver/produce/pilot は禁止 |
| 5 | PHASE 8 が STOP ゲート。blocking=0 + READY_TO_SUBMIT でなければ絶対に次に進まない |
| 6 | availability set は pricing の前。順序を逆にすると全pricing call が Apple 500エラーで失敗する |
| 7 | Privacy Policy URL は en-US AND ja 両方必須。片方だけでは submit 時にエラー |
| 8 | RC Offerings は TestFlight 前に設定必須。未設定だと「Apple IAP key is invalid」エラーで課金不可 |
| 9 | locale は ja(ja-JP は無効)。ASC API は ja-JP を拒否する |
| 10 | IAP key は同一 Apple Developer アカウントで使い回し。新規作成不要。<AuthKey_XXXXXXXX.p8> を流用 |
| 11 | Paywall コピーは必ずコードから実機能を確認してから書く。存在しない機能を訴求するのは罪(Apple レビュー違反 + ユーザー詐欺)。FreePlanService.swift, SubscriptionManager.swift を必ず読め |
| 12 | Mixpanel 必須。全新規アプリに Mixpanel SDK を組み込み、paywall_viewed(offering_id プロパティ付き)を送信すること。Mixpanel なしでは paywall-ab スキルによる A/B 評価が不可能 |
| 13 | RevenueCat → Mixpanel 連携必須。RC Dashboard で「Send data to Mixpanel」を有効化し、presented_offering_id が rc_trial_started_event に含まれることを確認すること。未設定 = A/B 変換追跡ゼロ |
| 14 | スクショ撮影シミュレータは iPhone SE 3rd gen 必須。iPhone 14+ は Dynamic Island(黒い丸)が写り込む。`xcrun simctl list devices |
| 15 | Pencil テキストノードに width: "fill_container" 必須。未設定だとテキストがフレーム外にはみ出す。日本語ヘッドラインの fontSize は 22以下(28は14文字でオーバーフロー) |
| 16 | Pencil 画像キャッシュ問題。同じパスのファイルを上書きしても Pencil はキャッシュした旧版を使い続ける。画像差し替え時は必ず新しいファイル名を使うこと |
| 17 | mcp__pencil__get_screenshot はディスクに保存しない。返ってくるのは MCP レスポンス内の base64 のみ。ASC アップロード用ファイルは別途シミュレータから xcrun simctl io で取得すること |
| 18 | App Privacy(データの使用方法)は ASC API で設定不可。/v1/apps/{id}/appDataUsages は 404 を返す。PHASE 12 の前にユーザーに手動設定させること。設定手順は PHASE 11.5 参照 |
| 19 | ISSUER_ID は ASC_ISSUER_ID 環境変数から取得。間違った ID は全 curl 呼び出しが 401 を返す。ASC → Users and Access → Integrations → Keys 画面の上部に表示されている UUID が ISSUER_ID。キー一覧の「キー ID」欄の値(短い英数字)と混同しない |
| 20 | アイコンはビルド前に配置する。ビルド後にアイコンを変更した場合は CURRENT_PROJECT_VERSION をバンプして再ビルドが必要。「The bundle version must be higher than the previously uploaded version」エラーが出たらバンプして再アップロード |
| 21 | Playwright + Chrome 競合。Chrome 起動中に Playwright を実行すると「既存のブラウザセッションで開いています」エラー。先に pkill -f "Google Chrome" で Chrome を終了してから Playwright を起動 |
| 22 | asc submit create --confirm が正解の提出方法。PATCH reviewSubmissions.state は 409 を返す。asc review submissions-list で確認できる ID は appStoreVersionSubmissions とは別物 |
| 23 | RevenueCat delegate 名前衝突。SubscriptionManager.swift の内部クラス名を PurchasesDelegate にすると同名プロトコルと衝突してビルドエラー。必ず RCPurchasesDelegate: NSObject, PurchasesDelegate と命名する(2026-02-26 実機確認済み) |
| 24 | iOS 15 ターゲット: Locale.current.language.languageCode 使用禁止。iOS 16+ API。iOS 15 ターゲットでは Locale.current.languageCode を使う(deprecated だが iOS 15 互換)。2026-02-26 実機確認済み |
| 25 | iOS 15 ターゲット: scrollContentBackground 使用禁止。iOS 16+ API。ZStack + Color で背景色を設定する workaround を使う。Form の背景を透明にしたい場合: ZStack { Color(hex:"#0f0f1a").ignoresSafeArea(); Form { ... } } |
| 26 | Fastfile シミュレータ destination は名前でなく UDID。"iPhone SE (3rd generation)" は not found エラーになる。xcrun simctl list devices available | grep SE で UDID を取得して id=<UDID> 形式で指定する |
| 27 | asc apps create は Apple ID + パスワード必須(API Key 不可)。asc apps create --apple-id <email> --password <pass> が必要。パスワード不明時はユーザーに事前確認。APPLE_ID_PASSWORD を .env に事前設定しておくこと。2026-02-27 実機確認済み |
| 27b | RC v1 platform_product_identifier は v2 product-package 変更で自動更新されない。RC SDK v5 は v1 offerings API (/v1/subscribers/{id}/offerings) を使い、platform_product_identifier を StoreKit に渡す。v2 で products を attach しても v1 の値は変わらない。修正手順: (1) DELETE /v2/projects/{project_id}/packages/{package_id} でパッケージ削除(⚠️ offering path を含めると 404 — /packages/{id} のみ正しい)→ (2) POST /offerings/{id}/packages で再作成 → (3) attach_products で正しい product を attach → v1 API が正しい platform_product_identifier を返すようになる。2026-02-27 実機確認済み |
| 28 | ASC REST API /v1/apps POST は禁止操作。GET_COLLECTION, GET_INSTANCE, UPDATE のみ許可。アプリ作成は必ず asc apps create を使う(Apple ID 必須) |
| 28b | RC v1 offerings API での platform_product_identifier 確認方法(診断必須)。curl -H "Authorization: Bearer <iOS_API_KEY>" https://api.revenuecat.com/v1/subscribers/\$RCAnonymousID:test/offerings -H "X-Platform: ios" で platform_product_identifier を確認。ASC の Product ID(例: com.bundle.premium.monthly)と一致してなければ「プランの取得に失敗しました」エラーが出る。v2 ダッシュボードでは正しく見えていても v1 が古い値を返し続けることがある |
| 29 | Netlify は GitHub App Webhook なしで push に反応しない。aniccaai.com は Netlify ホスト済みだが GitHub App Webhook がないため、push してもビルドされない。Netlify デプロイには NETLIFY_AUTH_TOKEN + NETLIFY_SITE_ID が必須。これらを .env および GitHub Secrets に必ず事前設定すること。2026-02-27 実機確認済み |
| 29b | 初回 IAP は asc subscriptions submit で単独 submit 禁止。STATE_ERROR.FIRST_SUBSCRIPTION_MUST_BE_SUBMITTED_ON_VERSION エラーが出る。これは Apple ルール: 初回 IAP は必ずアプリバージョンと同時提出。PHASE 11.6 の asc subscriptions submit はスキップして、PHASE 12 で asc publish appstore --submit --confirm を実行すれば IAP も自動的に review に含まれる。2026-02-27 実機確認済み |
| 30 | アプリ作成前に必要なクレデンシャルを PHASE 0 で確認する。APPLE_ID_PASSWORD(Apple ID パスワード)と NETLIFY_AUTH_TOKEN が PHASE 0 STEP 3 の ENV チェックに追加必須。これらなしに PHASE 3.5/4 は完了不可。 |
| 31 | ImageMagick v7 では convert コマンドは非推奨。magick コマンドを使う。magick -size 1024x1024 gradient:... icon.png が正解。 |
| 32 | iPad 13" スクショ(APP_IPAD_PRO_3GEN_129)は Submit 必須(2026-02-28 実機確認)。iPhone スクショだけでは asc submit create が失敗する。正しいサイズ: 2048×2732(2064×2752 は IMAGE_INCORRECT_DIMENSIONS エラー)。iPhone スクショを sips -z 2732 2048 でリサイズして流用可。PHASE 9 Step 3b を参照 |
| 33 | copyright + contentRightsDeclaration + app pricing の3つは Submit 必須(2026-02-28 実機確認)。いずれか未設定で asc submit create → App is not eligible for submission エラー。copyright は asc versions update --copyright、content rights は curl PATCH で contentRightsDeclaration: DOES_NOT_USE_THIRD_PARTY_CONTENT、pricing は appPriceSchedules POST で設定。PHASE 9 Step 5 を参照 |
| 34 | iPad スクショのサイズ: 2048×2732 が正解(2026-02-28 実機確認)。Apple の APP_IPAD_PRO_3GEN_129 display type は 2048×2732。sips -z 2732 2048 input.png で変換(-z height width の順序に注意)。2064×2752 は誤り |
| 35 | primaryCategory 未設定 → INVALID_BINARY になる(2026-02-28 実機確認)。asc submit create 後に version が INVALID_BINARY になる主原因。PHASE 4 で必ず appInfos の primaryCategory relationship を設定する。コマンド: curl -X PATCH /v1/appInfos/<ID> で relationships.primaryCategory.data.id = "UTILITIES" 等を設定。確認: curl /v1/appInfos/<ID>/primaryCategory → id が返れば OK。INVALID_BINARY になってしまった場合の回復手順: canceled: true で既存提出をキャンセル → version 状態が PREPARE_FOR_SUBMISSION に戻る → asc submit create で再提出。2026-02-28 実機確認済み |
| 36 | usesIdfa: None(未設定)→ INVALID_BINARY になる(2026-02-28 実機確認)。Apple の自動バイナリ検証が usesIdfa 未設定を検出して INVALID_BINARY に自動変換し、提出が UNRESOLVED_ISSUES+REJECTED になる。PHASE 9 Step 5(または PHASE 4)で必ず usesIdfa: false を設定する。コマンド: curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" "https://api.appstoreconnect.apple.com/v1/appStoreVersions/$VERSION_ID" -d '{"data":{"type":"appStoreVersions","id":"$VERSION_ID","attributes":{"usesIdfa":false}}}'。設定後 appStoreState が即座に INVALID_BINARY → READY_FOR_REVIEW に回復する。INVALID_BINARY 回復手順: (1) 既存 UNRESOLVED_ISSUES 提出を canceled: true でキャンセル → (2) usesIdfa: false を PATCH → (3) 新規 reviewSubmission を作成 → (4) version item を追加 → (5) submitted: true で提出。2026-02-28 実機確認済み |
| 37 | Distribution 証明書が REVOKED → ITMS-90035: Invalid Signature(2026-02-28 実機確認)。Keychain の Distribution 証明書が全て REVOKED になると、どのプロビジョニングプロファイルを使っても error: exportArchive Signing certificate is invalid が出る。根本原因: openssl req で CSR を作成しても Apple API が 409 で拒否する。正解: asc certificates csr generate → asc certificates create --certificate-type IOS_DISTRIBUTION の順で新証明書を発行。回復手順: (1) asc certificates csr generate ~/Downloads/.signing/dist.csr でCSR生成 → (2) asc certificates create --certificate-type IOS_DISTRIBUTION --csr ~/Downloads/.signing/dist.csr で証明書発行 → (3) ダウンロードした .cer と秘密鍵 .pem をKeychain にインポート → (4) REVOKED 証明書を Keychain から削除 → (5) asc profiles create --profile-type IOS_APP_STORE で新 Provisioning Profile 作成 → (6) Fastfile で signingStyle: "manual" + provisioningProfiles: { "bundle.id" => "profile-uuid" } を指定してビルド。REVOKED cert が embedded.mobileprovision に残ったまま Xcode 管理プロファイルを使い続けると何度やっても失敗する。2026-02-28 実機確認済み |
| 38 | asc submit create で appStoreVersions already added to another reviewSubmission エラー → キャンセルしてから新規作成(2026-02-28 実機確認)。同じバージョンが既存の submission に紐付いている場合、新規 submit create が失敗する。回復手順: (1) asc submit cancel --id <problematic-submission-id> --confirm でキャンセル → (2) asc submit create を再実行。READY_FOR_REVIEW 状態の submission はキャンセル不可だが、UNRESOLVED_ISSUES や PREPARING は canceled: true でキャンセル可能。キャンセル後も新規 asc submit create を実行すれば、古い READY_FOR_REVIEW は無視して正しく新規提出が作られる。2026-02-28 実機確認済み |
🏭 工場ルール(FACTORY RULES — 違反 = ファクトリー停止)
| # | ルール | 詳細 |
|---|---|---|
| F1 | 承認待ち禁止 | フェーズ間で人間の承認を待つな。Slack に報告して即次フェーズへ。 |
| F2 | 詰まったら SKILL.md を必ず修正 | エラー・失敗・想定外 → 修正して git commit/push → 次の実行で自動修正される。修正せずに進むのは禁止。 |
| F3 | クローズドループ強制 | 今日の失敗は明日の成功のソース。全ての例外・エラー・ハマりポイントを即座にこの SKILL.md に書き込め。 |
| F4 | オリジナル禁止 | 判断する前に既存ベストプラクティスを検索する。ソース引用なき判断行は削除。 |
| F5 | TDD 強制(PHASE 3) | ralph-autonomous-dev は tdd-workflow スキルと組み合わせて使う。テストなし実装 = ビルドはできても品質がない。 |
| F6 | Worktree 必須 | 全作業は git worktree で隔離する。dev に直接コミットしない。TestFlight 承認後に dev にマージする。詳細は PHASE 0 PRE-FLIGHT STEP 0 参照。 |
⚠️ Paywall コピー作成ルール(必読)
Paywall に書く機能は全てコードに実在すること。存在しない機能を訴求してはいけない。
Paywall 作成・更新の前に以下を確認する(Anicca の場合):
| ファイル | 確認する内容 |
|---|---|
FreePlanService.swift |
Free の制限(本数・時刻・ルールベース) |
LLMNudgeService.swift |
Pro の AI 機能 |
NudgeStatsManager.swift |
フィードバック学習の仕組み |
SubscriptionInfo.swift |
Free/Pro の差分定義 |
新規アプリの場合: PaywallView がある画面と SubscriptionManager 相当のファイルを読んで、
Free と Pro の実際の差分を確認してからコピーを書く。
禁止パターン(実在しないのに書く):
- ❌ "30-day insight reports" — 分析機能がなければ書くな
- ❌ "Progress tracking" — 進捗画面がなければ書くな
- ❌ "Premium support" — サポートチームがなければ書くな
- ❌ "All features unlocked" — 意味がない。何が解禁されるか具体的に書け
14 PHASES
PHASE 0: PRE-FLIGHT(セットアップウィザード)
PRE-FLIGHT はサイレントチェックではなくガイド付きウィザードとして実行する。 問題が1つでも見つかれば、ユーザーに解決手順を提示し、確認を取ってから次の項目へ進む。 全 STEP が PASS になるまで PHASE 0 TREND RESEARCH に進まない。
STEP 0: Git Worktree セットアップ(必須 — devを汚さない)
全作業は git worktree で dev から隔離する。dev に直接コミット禁止。
理由: 複数のファクトリーエージェントが同時に dev で作業すると競合が起きる。TestFlight テスト前のコードが dev に混入するとデプロイが汚れる。
# slug を spec.md から取得(例: breath-calm)
SLUG=$(python3 -c "import re; s=open('.cursor/app-factory/<SLUG>/02-spec.md').read(); print(re.search(r'output_dir.*?([a-z-]+)-app', s).group(1))" 2>/dev/null || echo "<SLUG>")
# worktree 作成(~/Downloads/ 直下)
WORKTREE_PATH="$HOME/Downloads/anicca-${SLUG}"
git worktree add "$WORKTREE_PATH" -b "app-factory/${SLUG}"
echo "✅ Worktree 作成: $WORKTREE_PATH (branch: app-factory/${SLUG})"
# 以降の全作業はこの worktree 内で行う
cd "$WORKTREE_PATH"
TestFlight 承認後のマージ手順(PHASE 10 完了後):
# 1. worktree から dev にマージ
cd /Users/cbns03/Downloads/anicca-project # メインリポジトリ
git checkout dev
git merge app-factory/${SLUG} --no-ff -m "feat(app-factory): merge ${SLUG} → dev (TestFlight approved)"
git push origin dev
# 2. worktree クリーンアップ
git worktree remove "$HOME/Downloads/anicca-${SLUG}"
git branch -d app-factory/${SLUG}
STEP 1: Sub-skills(自動インストール)
# まずすべてのスキルをチェックし、足りなければ自動インストール
required_skills=(x-research tiktok-research apify-trend-analysis ralph-autonomous-dev screenshot-creator slack-approval)
for skill in "${required_skills[@]}"; do
if ! npx skills list 2>/dev/null | grep -q "$skill"; then
echo "⏳ Installing: $skill"
npx skills add Daisuke134/anicca-products@$skill -g -y
fi
done
if ! npx skills list 2>/dev/null | grep -q "app-icon"; then
npx skills add code-with-beto/skills@app-icon -g -y
fi
echo "✅ All sub-skills ready."
STEP 2: CLI Tools
check_tool() {
local name="$1"; local cmd="$2"; local install="$3"
if eval "$cmd" &>/dev/null 2>&1; then echo "✅ $name"
else echo "❌ $name → $install"; return 1; fi
}
TOOL_FAIL=0
check_tool "asc" "asc --version" "brew install nickvdyck/tap/asc" || TOOL_FAIL=1
check_tool "fastlane" "fastlane --version" "brew install fastlane" || TOOL_FAIL=1
check_tool "greenlight" "greenlight --version" "cd /tmp && git clone https://github.com/RevylAI/greenlight.git && cd greenlight && make build && sudo cp build/greenlight /usr/local/bin/" || TOOL_FAIL=1
check_tool "imagemagick" "convert --version" "brew install imagemagick" || TOOL_FAIL=1
check_tool "snapai" "npx snapai --version" "npm install -g snapai" || TOOL_FAIL=1
check_tool "ios-deploy" "ios-deploy --version" "brew install ios-deploy" || TOOL_FAIL=1
check_tool "Pillow" "python3 -c 'import PIL'" "pip3 install Pillow" || TOOL_FAIL=1
check_tool "PyJWT" "python3 -c 'import jwt'" "pip3 install PyJWT" || TOOL_FAIL=1
check_tool "requests" "python3 -c 'import requests'" "pip3 install requests" || TOOL_FAIL=1
if [ "$TOOL_FAIL" -ne 0 ]; then
echo ""
echo "⚠️ 上記の CLI ツールが不足しています。インストール後「完了」と入力してください。"
# ← ユーザー入力待ち。「完了」を受けたら STEP 2 を再実行し PASS になったら STEP 3 へ
fi
STEP 3: 環境変数
ENV_FILE="$HOME/.config/mobileapp-builder/.env"
[ -f "$ENV_FILE" ] && source "$ENV_FILE"
ENV_FAIL=0
check_env() {
local name="$1"; local link="$2"; local hint="$3"
if [ -n "${!name:-}" ]; then echo "✅ $name"
else echo "❌ $name → $link ($hint)"; ENV_FAIL=1; fi
}
check_env ASC_KEY_ID "https://appstoreconnect.apple.com → Users and Access → Integrations → Keys" "キーID(例: ABC123DEFG)"
check_env ASC_ISSUER_ID "同上" "Issuer ID(UUID形式: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)"
check_env ASC_KEY_PATH "上記ページで .p8 ダウンロード → ~/Downloads/ に保存(1度しかダウンロードできない)" "例: ~/Downloads/AuthKey_XXXXXX.p8"
check_env REVENUECAT_API_KEY "https://app.revenuecat.com → Project Settings → API Keys" "sk_ で始まるキー"
check_env MIXPANEL_TOKEN "https://mixpanel.com → Project Settings → Project Token" "英数字トークン"
check_env X_BEARER_TOKEN "https://developer.twitter.com → App → Bearer Token" "AAAA... で始まる"
check_env APIFY_TOKEN "https://console.apify.com → Settings → Integrations" "apify_api_ で始まる"
check_env GEMINI_API_KEY "https://console.cloud.google.com → APIs & Services → Credentials" "AIza... で始まる"
check_env OPENAI_API_KEY "https://platform.openai.com → API keys" "sk- で始まる"
check_env SLACK_BOT_TOKEN "https://api.slack.com/apps → OAuth & Permissions" "xoxb- で始まる"
check_env SLACK_APP_TOKEN "https://api.slack.com/apps → Basic Information → App-Level Tokens" "xapp- で始まる"
check_env SLACK_CHANNEL_ID "Slack でチャンネル右クリック → リンクをコピー → 末尾 C... 部分" "例: C0123456789"
check_env PRIVACY_POLICY_DOMAIN "自分が所有するドメイン(Privacy Policy と Landing Page をホストする)" "例: example.com(https://は含めない)"
check_env NETLIFY_AUTH_TOKEN "https://app.netlify.com → User Settings → Applications → Personal access tokens → New access token" "tok_ で始まるトークン(PHASE 3.5 で必須)"
check_env NETLIFY_SITE_ID "https://app.netlify.com → サイト選択 → Site settings → General → Site ID" "UUID形式(PHASE 3.5 で必須)"
check_env APPLE_ID_PASSWORD "keiodaisuke@gmail.com の Apple ID パスワード" "PHASE 4 の asc apps create で必須"
if [ "$ENV_FAIL" -ne 0 ]; then
echo ""
echo "⚠️ 環境変数が不足しています。設定方法:"
echo " 1. mkdir -p ~/.config/mobileapp-builder"
echo " 2. 上のリンクから各キーを取得"
echo " 3. ~/.config/mobileapp-builder/.env に: export 変数名=値"
echo " 4. source ~/.config/mobileapp-builder/.env"
echo " 設定が完了したら「完了」と入力してください。"
# ← ユーザー入力待ち。「完了」を受けたら STEP 3 を再実行し PASS になったら STEP 4 へ
fi
STEP 4: ASC API Key (.p8)
if ls ~/Downloads/AuthKey_*.p8 &>/dev/null 2>&1; then
echo "✅ ASC API Key (.p8) 確認済み"
else
echo "❌ .p8 ファイルが ~/Downloads に見つかりません"
echo ""
echo " 取得手順:"
echo " 1. https://appstoreconnect.apple.com を開く"
echo " 2. Users and Access → Integrations → Keys → + ボタン"
echo " 3. 名前: mobileapp-builder / アクセス: App Manager"
echo " 4. ダウンロード → ~/Downloads/ に保存"
echo " 5. ASC_KEY_ID と ASC_KEY_PATH を .env に追記"
echo " 完了したら「完了」と入力してください。"
# ← ユーザー入力待ち
fi
STEP 5: snapai 設定
[ -n "${OPENAI_API_KEY:-}" ] && npx snapai config --openai-api-key "$OPENAI_API_KEY" && echo "✅ snapai 設定済み"
PRE-FLIGHT 完了
echo "✅ PRE-FLIGHT 完了。全チェック通過。PHASE 0 TREND RESEARCH を開始します。"
PHASE 0: TREND RESEARCH
x-research + tiktok-research + apify-trend-analysis スキルを並列実行
[x-research]
X (Twitter) でバズってるキーワード・トレンドトピックを調査
→ 「今週 JP/EN でバズってるメンタル・健康・生産性系のキーワード TOP5」
[tiktok-research]
TikTok で伸びているショート動画のテーマ・フック・視聴者の悩みを調査
→ 「今週バズってる動画のテーマ TOP5 + 共通するペイン」
[apify-trend-analysis]
App Store カテゴリ別ランキング + Google Trends を調査
→ 「今上位に入っているアプリジャンル + 検索ボリューム増加中のトピック」
3つの結果を統合して判断:
- 共通して出てくるテーマ = 今作るべきアプリのジャンル
- アプリアイデアを1つに絞る(選択肢提示禁止。1つに決める)
OUTPUT → .cursor/app-factory/{slug}/01-trend.md
- 決定したアプリアイデア(タイトル仮 + 一言説明)
- 根拠(どのトレンドデータから判断したか)
- slug(例: sleep-tracker、breath-calm 等)
- 【必須】なぜこれが人間の苦しみを解決するか: 実データ + ソースURL 付き3点
例: "不安障害は世界2.8億人(WHO: https://who.int/news-room/fact-sheets/detail/anxiety-disorders)"
- 【必須】なぜバイラルになるか(実測数値): 各ツールの実測データを引用
例: "Apify実測: 9D Breathwork TikTok 動画 平均2.3M再生 / 月"
例: "Google Trends実測: 日本式ウォーキング 2,986% YoY増"
※ LLM の推測 = 禁止。実ツール(Apify/X/Google Trends)の実数値のみ
PHASE 0.5: SPEC 生成(SDD)
01-trend.md を読んで spec.md を自動生成する
スラッシュコマンド不要。以下の手順をそのまま実行する。
Step 1: spec.md の全フィールドを埋める(PHASE 1 の必須フィールドを全部)
- app_name, bundle_id, version, output_dir
- price_monthly_usd: 9.99, price_annual_usd: 49.99(デフォルト)
- paywall.cta_text_en / paywall.cta_text_ja
- metadata: title_en/ja, subtitle_en/ja, description_en/ja, keywords_en/ja
- urls.privacy_en: "https://$PRIVACY_POLICY_DOMAIN/{slug}/privacy/en"
- urls.privacy_ja: "https://$PRIVACY_POLICY_DOMAIN/{slug}/privacy/ja"
- urls.terms: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
- urls.landing: "https://$PRIVACY_POLICY_DOMAIN/{slug}"
- localization: "os_language"
- supported_locales: ["en", "ja"]
- concept: (1行説明。スクショヘッドライン生成に使う)
- 画面構成(Onboarding / Main / Paywall / Settings)
Step 2: plan.md を生成(技術設計)
- アーキテクチャ(SwiftUI MVC)
- ファイル構成
- API / RevenueCat 設計
Step 3: tasks.md を生成(実装タスクリスト)
- 依存順に並んだチェックボックス形式
- PHASE 2〜12 の各フェーズに対応するタスクを網羅
OUTPUT →
.cursor/app-factory/{slug}/02-spec.md ← PHASE 1 が読む
.cursor/app-factory/{slug}/03-plan.md
.cursor/app-factory/{slug}/04-tasks.md
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📢 PHASE 0.5 完了 — Slack 報告して即 PHASE 1 へ
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⛔ 承認を待つな。Slack に投稿したら返信を待たずに即 PHASE 1 を開始する。
Slack (#metrics / SLACK_CHANNEL_ID) に以下を投稿:
🏭 {app_name} のビルドを開始します
💊 なぜこれが人間の苦しみを解決するか:
[01-trend.md の「コアペイン」セクションから3点引用 — 実データ・ソースURL付き]
例: "不安障害は世界2.8億人(WHO)。薬なし6分で解決できる呼吸法は科学的証拠あり"
📈 なぜバイラルになるか(実測データ):
[x-research・tiktok-research・apify-trend-analysis の実測数値を3点引用]
例: "TikTok: 9D Breathwork が2026年明示的にバイラル(Apify実測)"
例: "日本式ウォーキング 2,986% YoY増(Google Trends実測)"
例: "日本市場 CAGR 16.31% → $822M by 2035(GlobeNewswire)"
💰 {price_monthly_usd}/月 | {price_annual_usd}/年 | EN+JA
📁 SDD ファイル(フルパス):
.cursor/app-factory/{slug}/01-trend.md
.cursor/app-factory/{slug}/02-spec.md
.cursor/app-factory/{slug}/03-plan.md
.cursor/app-factory/{slug}/04-tasks.md
Phase 1→12 を自律実行します。完了時に報告します。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PHASE 1: VALIDATE INPUT
spec.md の必須フィールド確認(全項目 MUST — 1つでも欠ければ STOP)
■ 技術
- app_name, bundle_id, version, output_dir
■ 課金
- price_monthly_usd, price_annual_usd
- paywall.cta_text_en, paywall.cta_text_ja
■ App Store メタデータ(EN + JA 両方必須)
- metadata.title_en, metadata.title_ja
- metadata.subtitle_en, metadata.subtitle_ja
- metadata.description_en, metadata.description_ja
- metadata.keywords_en, metadata.keywords_ja
■ URL(アプリ専用 URL 必須 — 全アプリ共通 URL 禁止)
- urls.privacy_en 例: "https://example.com/myapp/privacy/en"(PRIVACY_POLICY_DOMAIN + slug で構成)
- urls.privacy_ja 例: "https://example.com/myapp/privacy/ja"
- urls.terms 固定値: "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
- urls.landing 例: "https://example.com/myapp"
■ ローカライズ方針
- localization: "os_language" (日本語OS→日本語、その他→英語)
- supported_locales: ["en", "ja"]
■ コンセプト
- concept (1行説明。スクショヘッドライン生成に使う)
欠けていれば STOP + 不足フィールドを報告
PHASE 2: SCAFFOLD
# 新規 Xcode プロジェクトを output_dir に作成
mkdir -p <output_dir>/<app_name>ios
# Bundle ID / バージョン / チーム ID を project.pbxproj に設定
# RevenueCat SDK を SPM で追加
# PrivacyInfo.xcprivacy を追加(必須)
PHASE 3: BUILD
【TDD 強制】tdd-workflow スキルを起動してから ralph-autonomous-dev でループ実行。
手順:
1. `tdd-workflow` スキルを読み込む(テストファースト実装の強制)
2. 各機能について RED → GREEN → REFACTOR を必ず通す
3. ralph-autonomous-dev が fix_plan.md を読み → 実装 → テスト → ✅ のループを回す
4. 全タスク `[x]` → EXIT_SIGNAL: true → PHASE 3 完了
ralph-autonomous-dev で SwiftUI 実装
■ 必須実装(1つでも欠ければ PHASE 11 で STOP)
コア機能:
- spec の画面構成・コア機能を実装
- 通知: UserNotifications で APNs 登録 + 通知カタログ
- Paywall: RevenueCat SDK のみ(Superwall 禁止)
- Paywall の accessibilityIdentifier(必須 5要素):
paywall_plan_monthly / paywall_plan_yearly
paywall_cta / paywall_skip / paywall_restore
ローカライズ(OS言語対応 — 必須):
- Localizable.strings を EN + JA 両方作成
- 表示言語は OS 言語に自動追従(Locale.current で判定)
- 日本語 OS → 日本語 UI、その他 OS → 英語 UI
- Paywall コピーも EN/JA 両対応(spec.md の paywall.cta_text_en/ja を使う)
- ハードコード日本語・英語テキスト禁止。全て Localizable.strings 経由
Settings 画面(必須):
- Privacy Policy リンク → spec.md の urls.privacy_en / urls.privacy_ja(OS言語で切替)
- Terms of Use リンク → spec.md の urls.terms(Apple 標準 EULA 固定)
URL: https://www.apple.com/legal/internet-services/itunes/dev/stdeula/
■ PHASE 9 スクショパイプラインの前提セットアップ(必須 — これがないと PHASE 9 Step 2 が動かない)
1. ScreenshotTests.swift を作成
ファイル: <output_dir>/<slug>UITests/ScreenshotTests.swift
内容: XCUITest でアプリを起動し、各画面をスクロール・遷移してスクショを撮影するテストケース
テストケース:
- testScreenshot_Main : メイン画面
- testScreenshot_Streak : カレンダー/ストリーク画面(存在する場合)
- testScreenshot_Paywall: Paywall 画面(paywall_skip で到達可能にすること)
accessibilityIdentifier を使って各画面に確実に到達すること
2. Makefile に generate-store-screenshots ターゲットを追加
場所: <output_dir>/Makefile
コマンド:
generate-store-screenshots:
xcodebuild test -project <app_name>.xcodeproj -scheme <app_name>UITests \
-sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 16 Pro Max' \
-resultBundlePath docs/screenshots/output.xcresult
python3 docs/screenshots/scripts/extract_screenshots.py
python3 docs/screenshots/scripts/process_screenshots.py
3. extract_screenshots.py + process_screenshots.py を docs/screenshots/scripts/ に配置
- extract_screenshots.py: xcresulttool で output.xcresult から PNG を抽出 → docs/screenshots/raw/
- process_screenshots.py: PIL で 1290×2796 に合成(ヘッドラインを screenshots.yaml から読む)
- screenshots.yaml: 各画面のヘッドライン + カラー設定
⚠️ これらのセットアップが完了してから PHASE 9 Step 2 に進む。スキップ禁止。
PHASE 3.5: PRIVACY POLICY & LANDING PAGE デプロイ
$PRIVACY_POLICY_DOMAIN/{slug}/ のページを作成してデプロイする。
PHASE 4 の前に必須(URL が死んでいると ASC Privacy URL 設定が通らない)。
■ 必要なページ(4つ)
1. https://$PRIVACY_POLICY_DOMAIN/{slug}/ ← ランディングページ
- アプリ名・コンセプト・App Store リンク(EN のみ可)
2. https://$PRIVACY_POLICY_DOMAIN/{slug}/privacy/en ← Privacy Policy(英語)
- 収集データ: Device ID(Analytics)、Usage Data(Analytics)
- RevenueCat: Purchase History(サブスク管理)
- 収集しないデータ: 日記内容・アファメーション(ローカル保存のみ)
3. https://$PRIVACY_POLICY_DOMAIN/{slug}/privacy/ja ← Privacy Policy(日本語)
- 同上の日本語版
4. https://$PRIVACY_POLICY_DOMAIN/{slug}/terms ← Terms(EULA リダイレクト)
- https://www.apple.com/legal/internet-services/itunes/dev/stdeula/ へリダイレクト
■ 推奨デプロイ方法(3択 — 自分のインフラに合わせて選ぶ)
オプション A: GitHub Pages(無料・ドメイン持ちであれば最速)
1. リポジトリに docs/ フォルダを作成して HTML ファイルを追加
2. GitHub Settings → Pages → /docs を公開
3. カスタムドメインを設定 → $PRIVACY_POLICY_DOMAIN を向ける
オプション B: Vercel / Netlify(Next.js や静的サイトがあれば)
1. プロジェクトに pages/{slug}/privacy/en.html 等を追加
2. push → 自動デプロイ
オプション C: 既存サーバー(VPS/Nginx/Apache)
1. /var/www/html/{slug}/ にファイルを配置
2. Nginx で location /{slug}/ { root /var/www/html; } を設定
■ Privacy Policy の最小テンプレート(EN)
```html
<!DOCTYPE html><html><body>
<h1>{app_name} Privacy Policy</h1>
<p>We collect: Device identifiers (for analytics), usage data.</p>
<p>We use RevenueCat for subscription management (purchase history).</p>
<p>We do NOT collect: journal entries, affirmations, or any personal content.</p>
<p>Contact: {your_email}</p>
</body></html>
■ デプロイ後の確認(必須 — URL が死んでいると PHASE 11 GATE 4 で STOP)
SLUG="{slug}"
DOMAIN="$PRIVACY_POLICY_DOMAIN"
curl -I "https://$DOMAIN/$SLUG/privacy/en" -s -o /dev/null -w "%{http_code}" | grep -q "200|301|302"
&& echo "✅ EN privacy URL 生きています" || echo "❌ STOP: EN privacy URL が死んでいます"
curl -I "https://$DOMAIN/$SLUG/privacy/ja" -s -o /dev/null -w "%{http_code}" | grep -q "200|301|302"
&& echo "✅ JA privacy URL 生きています" || echo "❌ STOP: JA privacy URL が死んでいます"
200/301/302 でなければ STOP。デプロイを確認してから PHASE 4 へ
■ ⚠️ Netlify CI クラッシュ警告(F6違反時 — 2026-02-26 実機確認)
F6ルール違反(worktree 未使用で main/dev に直接作業)すると Netlify CI が完全クラッシュする。
原因: aniccaai.com の Next.js プロジェクトは .worktrees/main/apps/landing/ にある。
worktree を使わず main ブランチや dev ブランチで作業すると、Netlify が
Next.js ビルドを実行できず CI が失敗する。
リカバリ手順(CI クラッシュ時):
cd /Users/cbns03/Downloads/anicca-project/.worktrees/main/apps/landing
next build
npx netlify deploy --dir=out --prod
# → 手動で本番デプロイして CI をバイパスする
予防: 必ず F6 に従い git worktree add ~/Downloads/anicca-{slug} -b app-factory/{slug} で隔離すること。
### PHASE 4: ASC APP SETUP
> **✅ アプリ作成は `asc apps create` + `asc bundle-ids create` で全自動(ASC CLI 0.34.0 — 2026-02-26 更新)**
>
> fastlane produce は不要。ASC CLI 0.34.0 で `asc apps create` が追加された。
# Step 0: Bundle ID を作成(Apple Developer Portal に登録)
asc bundle-ids create --identifier "<bundle_id>" --name "<app_name>" --platform IOS
# Step 1: asc apps create でアプリを作成(App Store Connect に登録)
asc apps create --name "<app_name>" --bundle-id "<bundle_id>" --sku "<slug>" --primary-locale en-US
# ⚠️ fastlane produce create は使わない(CRITICAL RULE 4 — 禁止)
# 過去に試したが PRODUCE_USERNAME が必要で Spaceship が Apple ID ログインを要求する。
# ASC CLI 0.34.0 で asc apps create が追加されたため fastlane produce は不要になった。
# Step 2: 作成されたアプリの APP_ID を取得
APP_ID=$(asc apps list --bundle-id "<bundle_id>" --output json | \
python3 -c "import sys,json;d=json.load(sys.stdin);print(d['data'][0]['id'])")
echo "APP_ID: $APP_ID"
# Privacy Policy URL を en-US AND ja 両方に設定(片方だけでは submit 時にエラー)
# locale は必ず "ja"("ja-JP" は ASC で無効)
APP_INFO_ID=$(asc apps info list --app "<APP_ID>" --output json | \
python3 -c "import sys,json;d=json.load(sys.stdin);print(d['data'][0]['id'])")
TOKEN=$(python3 -c "
import jwt,time,os,pathlib
key=pathlib.Path(os.environ['ASC_KEY_PATH']).read_text()
payload={'iss':os.environ['ASC_ISSUER_ID'],'iat':int(time.time()),'exp':int(time.time())+1200,'aud':'appstoreconnect-v1'}
print(jwt.encode(payload,key,algorithm='ES256',headers={'kid':os.environ['ASC_KEY_ID'],'typ':'JWT'}))
")
# en-US Privacy URL(spec.md の urls.privacy_en を使う)
PRIVACY_EN="<urls.privacy_en>" # 例: https://example.com/myapp/privacy/en
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"https://api.appstoreconnect.apple.com/v1/appInfoLocalizations/<EN_LOC_ID>" \
-d "{\"data\":{\"type\":\"appInfoLocalizations\",\"id\":\"<EN_LOC_ID>\",\"attributes\":{\"privacyPolicyUrl\":\"$PRIVACY_EN\"}}}"
# ja Privacy URL(locale = "ja" 確認必須 / spec.md の urls.privacy_ja を使う)
PRIVACY_JA="<urls.privacy_ja>" # 例: https://example.com/myapp/privacy/ja
curl -s -X PATCH -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
"https://api.appstoreconnect.apple.com/v1/appInfoLocalizations/<JA_LOC_ID>" \
-d "{\"data\":{\"type\":\"appInfoLocalizations\",\"id\":\"<JA_LOC_ID>\",\"attributes\":{\"privacyPolicyUrl\":\"$PRIVACY_JA\"}}}"
asc subscriptions groups create --app "<APP_ID>" --reference-name "Premium"
asc subscriptions create --group "<GROUP_ID>" --name "Monthly" \
--product-id "<bundle_id>.premium.monthly" --period ONE_MONTH --level 2
asc subscriptions create --group "<GROUP_ID>" --name "Annual" \
--product-id "<bundle_id>.premium.yearly" --period ONE_YEAR --level 1
# ★ availability を pricing の前に必ず設定(これなしでpricingは全件Apple 500エラー)
asc subscriptions availability set --id "<MONTHLY_ID>" \
--available-in-new-territories --territory USA,JPN
asc subscriptions availability set --id "<ANNUAL_ID>" \
--available-in-new-territories --territory USA,JPN
# ★★ primaryCategory 設定(CRITICAL RULE 35 — 未設定で INVALID_BINARY になる)
APP_INFO_ID=$(curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.appstoreconnect.apple.com/v1/apps/<APP_ID>/appInfos" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data'][0]['id'])")
# カテゴリ設定(アプリ内容に応じて変更: UTILITIES / PRODUCTIVITY / SOCIAL_NETWORKING / EDUCATION)
curl -s -X PATCH \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
"https://api.appstoreconnect.apple.com/v1/appInfos/$APP_INFO_ID" \
-d "{\"data\":{\"type\":\"appInfos\",\"id\":\"$APP_INFO_ID\",\"relationships\":{\"primaryCategory\":{\"data\":{\"type\":\"appCategories\",\"id\":\"UTILITIES\"}}}}}"
# 確認(id が返れば OK)
curl -s -H "Authorization: Bearer $TOKEN" \
"https://api.appstoreconnect.apple.com/v1/appInfos/$APP_INFO_ID/primaryCategory" | \
python3 -c "import sys,json; d=json.load(sys.stdin); print('primaryCategory:', d.get('data',{}).get('id','NOT SET'))"
PHASE 4.5: RC OFFERINGS SETUP(TestFlight 前に必須)
RC Dashboard → <app_name> プロジェクト(または新規作成)
→ Offerings → New Offering → identifier: "default"
→ Packages を追加:
$rc_annual → Apple Product ID: <bundle_id>.premium.yearly
$rc_monthly → Apple Product ID: <bundle_id>.premium.monthly
→ Offering を Current に設定
確認: Offerings が "current" に設定されていること
未設定 = TestFlight で「Apple IAP key is invalid」エラー
IAP Key: $ASC_KEY_ID(同一 Apple Developer アカウントで全アプリ共通、新規作成不要)
p8 file: $ASC_KEY_PATH を RC にアップロード済みであること確認
【RC プロジェクトが未作成の場合】
RevenueCat Dashboard → Projects → New Project → App name を <app_name> に設定
→ Add iOS App → Bundle ID: <bundle_id>
→ App Store Connect App-Specific Shared Secret は ASC → My Apps → App Information から取得
PHASE 5: IAP PRICING ★最重要
# US 価格ポイント ID を取得してから scripts/add_prices.py を実行
python3 ~/.claude/skills/mobileapp-builder/scripts/add_prices.py \
--annual-sub "<ANNUAL_ID>" \
--annual-pp "<ANNUAL_US_PP_ID>" \
--monthly-sub "<MONTHLY_ID>" \
--monthly-pp "<MONTHLY_US_PP_ID>"
# 確認(175 でなければ STOP)
asc subscriptions prices list --id "<MONTHLY_ID>" --paginate | \
python3 -c "import sys,json;d=json.load(sys.stdin);print(len(d['data']))"
詳細手順 → references/iap-bible.md の「価格ポイント ID の取得方法」
PHASE 6: IAP LOCALIZATION
asc subscriptions localizations create --subscription-id "<MONTHLY_ID>" \
--locale "en-US" --name "<app_name> Monthly" \
--description "Unlock all features with monthly subscription."
asc subscriptions localizations create --subscription-id "<MONTHLY_ID>" \
--locale "ja" --name "<app_name> 月額プラン" \
--description "月額プランで全機能を解放します。"
# Annual も同様(en-US + ja)
PHASE 7: IAP REVIEW SCREENSHOT
⚠️ 絶対ルール(2026-02-24 実機検証済み)
ルール 理由 リサイズ禁止 900×1956等の任意リサイズ → Apple「寸法が正しくありません」エラーで即拒否ネイティブ解像度をそのまま使う iPhone 16 Pro Max シミュレータ = 1320×2868。これが Apple 標準サイズ JPEG変換のみ sips -s format jpeg(-zフラグ使用禁止)CLI が full upload asc subscriptions review-screenshots create --file= reserve+PUT+commit を内部で全実行width=0 は正常 upload 直後は常に imageAsset.width=0。Apple 非同期処理中。再アップロード不要asc subscriptions images createは使わないプロモーショナル広告用。IAP review screenshot とは別物
ステップ 1: Booted シミュレータの UDID を取得
xcrun simctl list devices | grep Booted
# 例: iPhone 16 Pro Max (AF68C54D-D527-4A19-B4D1-5DEF182D8DE5) (Booted)
# UDID をメモする
ステップ 2: Maestro MCP でアプリを起動しペイウォール画面まで遷移
Maestro MCP(mcp__maestro__launch_app + mcp__maestro__run_flow)を使う。
CLI(maestro test)は禁止。MCP 経由のみ。
# 2-1. アプリ起動
mcp__maestro__launch_app(device_id="<UDID>", appId="<BUNDLE_ID>")
# 2-2. オンボーディング → ペイウォールまで遷移(順番通りに実行)
# Anicca の場合の実証済みフロー:
mcp__maestro__run_flow(device_id="<UDID>", flow_yaml="""
appId: <BUNDLE_ID>
---
- tapOn:
id: "onboarding-welcome-cta"
""")
# 苦しみ選択画面: 何か1つ選んで「次へ」
mcp__maestro__run_flow(device_id="<UDID>", flow_yaml="""
appId: <BUNDLE_ID>
---
- tapOn: "夜更かし"
- waitForAnimationToEnd
- tapOn: "次へ"
""")
# ライブデモ画面: primary action をタップ
mcp__maestro__run_flow(device_id="<UDID>", flow_yaml="""
appId: <BUNDLE_ID>
---
- tapOn:
id: "nudge-primary-action"
- waitForAnimationToEnd
""")
# 通知許可画面: 許可ボタンをタップ → ペイウォール表示
mcp__maestro__run_flow(device_id="<UDID>", flow_yaml="""
appId: <BUNDLE_ID>
---
- tapOn:
id: "onboarding-notifications-allow"
- waitForAnimationToEnd
""")
別アプリで画面構成が異なる場合は mcp__maestro__inspect_view_hierarchy でペイウォール画面の要素を確認してから遷移する。
ステップ 3: ペイウォール画面のスクリーンショットを撮影 → JPEG変換
# PNG 撮影(ネイティブ解像度: iPhone 16 Pro Max = 1320×2868)
xcrun simctl io "<UDID>" screenshot /tmp/paywall-review.png
# JPEG変換のみ(-z リサイズフラグは絶対に使わない)
sips -s format jpeg /tmp/paywall-review.png --out /tmp/paywall-review.jpg
# サイズ確認(1320×2868 であることを確認)
identify /tmp/paywall-review.jpg 2>/dev/null || sips -g pixelWidth -g pixelHeight /tmp/paywall-review.jpg
ステップ 4: Monthly と Annual 両方にアップロード
# Monthly
asc subscriptions review-screenshots create \
--subscription-id "<MONTHLY_SUB_ID>" \
--file /tmp/paywall-review.jpg
# Annual
asc subscriptions review-screenshots create \
--subscription-id "<ANNUAL_SUB_ID>" \
--file /tmp/paywall-review.jpg
# 成功判定: JSON レスポンスに fileSize > 0 があれば OK
# width=0, height=0 は正常(Apple が非同期で処理中)
エラーハンドリング
| エラー | 原因 | 対処 |
|---|---|---|
寸法が正しくありません |
リサイズした | 削除して正しいサイズで再アップロード |
Screenshot already exists |
既に存在する | 既存を削除してから再アップロード |
Element not found (Maestro) |
画面遷移が違う | inspect_view_hierarchy で現在の画面を確認 |
# 既存削除("already exists" 時)
# まず既存 ID を取得
python3 -c "
import os, time, json, requests
import jwt as pyjwt
KEY_ID='<KEY_ID>'; ISSUER_ID='<ISSUER_ID>'
PRIVATE_KEY=open(os.path.expanduser('~/.asc/private_keys/AuthKey_<KEY_ID>.p8')).read()
payload={'iss':ISSUER_ID,'iat':int(time.time()),'exp':int(time.time())+1200,'aud':'appstoreconnect-v1'}
token=pyjwt.encode(payload,PRIVATE_KEY,algorithm='ES256',headers={'kid':KEY_ID})
h={'Authorization':f'Bearer {token}','Content-Type':'application/json'}
r=requests.get('https://api.appstoreconnect.apple.com/v1/subscriptions/<SUB_ID>/appStoreReviewScreenshot',headers=h)
print(r.json()['data']['id'])
"
# → ID を取得したら削除
asc subscriptions review-screenshots delete --id "<EXISTING_ID>" --confirm
# その後 ステップ 4 を再実行
PHASE 8: IAP VALIDATE ★STOP GATE
# blocking=0 確認
asc validate subscriptions --app "<APP_ID>"
# blocking > 0 なら PHASE 5-7 に戻る
# READY_TO_SUBMIT 確認(両方必須)
asc subscriptions get --id "<MONTHLY_ID>" # state = READY_TO_SUBMIT ?
asc subscriptions get --id "<ANNUAL_ID>" # state = READY_TO_SUBMIT ?
# どちらかが MISSING_METADATA なら絶対に次に進まない
PHASE 9: APP ASSETS
Step 1: アイコン生成(app-icon スキルを使う)
スキル: code-with-beto/skills@app-icon(インストール: npx skills add code-with-beto/skills@app-icon -g -y)
# Step 1-A: SnapAI の設定確認(OpenAI key が必要)
npx snapai config --show
# → "Not configured" ならユーザーに OpenAI key を要求して設定:
# npx snapai config --openai-api-key sk-xxxxxxxx
# Step 1-B: SnapAI で 1024×1024 PNG を生成(透過背景)
npx snapai icon \
--prompt "<app_name> iOS app icon. Minimalist design. <concept_1_line>. Central symbol fills 70% of canvas. No text. Premium, App Store ready." \
--background transparent \
--output-format png \
--style minimalism \
--quality high
# → ./assets/icon-[timestamp].png に保存される
# ⚠️ 重要: SnapAI は --background transparent を指定しても白背景で出力する。
# プロンプトで "gradient background" を指定しても無視される。
# 必ず Step 1-C で ImageMagick を使って背景を追加する。
# ⛔ SnapAI 未設定の場合はここで停止。フォールバックなし。
# → ユーザーに「OpenAI API key が必要です: npx snapai config --openai-api-key sk-xxxx」と伝える
# Step 1-C: ImageMagick でグラデーション背景を追加(必須 — App Store は透過NG)
# brew install imagemagick # 未インストールの場合
ICON_SRC="./assets/icon-[timestamp].png"
# 1. 白背景をアルファ透過に変換
convert "$ICON_SRC" -fuzz 5% -transparent white /tmp/icon-transparent.png
# 2. グラデーション背景を作成してアイコンと合成
convert -size 1024x1024 \
gradient:"#F5A623-#E8563A" \
/tmp/icon-transparent.png \
-compose over -composite \
/tmp/icon-final.png
# 3. 結果をユーザーに見せて確認(必須 — OKが出るまで色変更して繰り返す)
open /tmp/icon-final.png
# Step 1-D: Xcode xcassets に配置(Swift/Xcode プロジェクト用)
cp /tmp/icon-final.png \
<output_dir>/<app_name>ios/<app_name>/Assets.xcassets/AppIcon.appiconset/icon.png
# ※ Contents.json の "filename": "icon.png" と一致していること確認
注意: app-icon スキルは本来 Expo 向け(Step 4 以降の iOS 26 .icon フォルダ / app.json は Swift/Xcode では不要)。PNG 生成(Step 3)だけを使う。
Step 2: スクショ生成(screenshot-creator スキル)
→ .claude/skills/screenshot-creator/SKILL.md を読んで Step 0〜7 を実行する
(A/B テストではなく新規生成のため、screenshot-ab の PHASE 1/2 はスキップ)
⚠️ Pencil 設計の絶対ルール(違反 = テキスト崩れ / 画像表示バグ):
| ルール | 理由 |
|---|---|
テキストノードに必ず width: "fill_container" |
未設定だとフレーム外にはみ出す |
日本語ヘッドラインは fontSize: 22 以下 |
28px × 14文字でオーバーフロー確認済み |
親フレームに layout: "vertical" を明記 |
省略すると CaptionArea/MockupArea が横並びになる |
| 画像差し替えは必ず新しいファイル名 | 同じパスの上書きはPencilキャッシュで反映されない |
get_screenshot = MCP base64のみ(ファイル保存されない) |
ASC用ファイルはシミュレータ撮影で別途取得 |
[シミュレータ準備(Dynamic Island 除去)]
0. iPhone SE 3rd gen を使う(Dynamic Island/ノッチなし)
UDID: xcrun simctl list devices | grep "SE (3rd"
→ CBA51D41-D404-4843-AA18-738C5068FFE4 等
iPhone 14+ を使う場合、Dynamic Island 除去が必要:
…(truncated)