# Mobileapp Builder

> mobileapp-builder

- Skill: `aibot88/mobileapp-builder` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add aibot88/mobileapp-builder`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aibot88/mobileapp-builder/raw
- Safety review: pending (external: skill-scanner PASS, skillspector FAIL)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: aibot88 (https://skillmd.com/u/aibot88)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/aibot88/mobileapp-builder

---


# 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 | grep "SE (3rd"` で UDID を確認して使う |
| 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 に混入するとデプロイが汚れる。

```bash
# 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 完了後）:**

```bash
# 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（自動インストール）

```bash
# まずすべてのスキルをチェックし、足りなければ自動インストール
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

```bash
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: 環境変数

```bash
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)

```bash
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 設定

```bash
[ -n "${OPENAI_API_KEY:-}" ] && npx snapai config --openai-api-key "$OPENAI_API_KEY" && echo "✅ snapai 設定済み"
```

---

#### PRE-FLIGHT 完了

```bash
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
```bash
# 新規 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 クラッシュ時）:
  ```bash
  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 ★最重要
```bash
# 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
```bash
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 を取得**
```bash
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変換**
```bash
# 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 両方にアップロード**
```bash
# 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` で現在の画面を確認 |

```bash
# 既存削除（"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
```bash
# 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`）

```bash
# 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)
