Communitygithub.com

berkayturk/appstore-precheck

Read-only iOS App Store pre-submission check: scans 20 rejection vectors, wraps Apple's fastlane precheck, watches guideline drift, and runs an adversarial reviewer pass before you submit.

対応Claude Code~Codex CLI~Cursor
npx skills add berkayturk/appstore-precheck

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

ドキュメント

App Store Precheck

A one-command gate to run before every iOS App Store submission. It minimizes the risk of rejection by statically scanning the most common rejection vectors, running Apple's own metadata linter, watching for guideline drift, and simulating an adversarial review pass.

This skill is read-only. It never edits code, metadata, or assets. It only reports and writes a pass token. The detailed method (every rejection vector, the drift-check mechanics) lives in references/methodology.md; read it when you need the specifics behind a check.

When to run

  • Before archiving for TestFlight.
  • Before pressing "Submit for Review" in App Store Connect.
  • Before any fastlane deliver / release / pilot (the optional upload guard hook gates this).
  • On every point release.

Run it deliberately. This is a human-triggered gate, not an automatic background step.

Configuration (optional)

The scanner auto-detects a standard fastlane + Xcode layout, so most projects need zero configuration. To override detection, copy config.example.json to .appstore-precheck.json at the repo root. Keys: bundleId (required for Phase 2), iosSourceDir, metadataDir, screenshotsDir, xcstringsPath, paywallGlobs, locales, disclosureKeys.{subscription,trial}, optionalChecks.familyControls, reviewPrepNotes. See config.example.json for the full annotated list.

Output contract

The skill reaches one of three terminal states:

StateMeaning.precheck-pass tokenGuard behavior
GREENNo FAIL, ≤2 WARNWritten (valid 60 min)Upload allowed
YELLOWNo FAIL but 3+ WARNNot writtenGuard blocks; ask for explicit confirmation
REDAt least 1 FAILRemovedGuard blocks; show the FAIL list

When you present the verdict to the user, open with a single in-character line from Pierre (the French App Review critic, see Phase 3), then drop straight into the plain, surgical breakdown. The voice is a thin wrapper. The data underneath, the FAIL/WARN list, file:line references, and fixes, stays clean and machine-faithful. Never rewrite or paraphrase scan.sh output, and keep Pierre to one line.

Flow (5 phases: 0–4)

Phase 0: Live guideline drift check

Diff the live App Store Review Guidelines section numbers against guidelines-baseline.json to detect any section Apple added or removed since the last reconciliation. Always non-blocking (WARN at most). Drift is a gap in our coverage, never a fault of the build. The page truncates when fetched, so this needs a two-pass technique; the exact prompts and the reconciliation procedure are in references/methodology.md. The baseline is never auto-updated. Reconciliation is a deliberate human step.

Phase 1: Static scan

bash skills/appstore-precheck/scripts/scan.sh

Emits FAIL: / WARN: / PASS: lines covering 20 rejection vectors: Privacy Manifest parity (5.1.1(v)), purpose strings (5.1.1), ATT (5.1.2), other-platform mentions (2.3.10), metadata limits (2.3.1), localized parity (2.3.7), screenshots (2.3.3), trial & auto-renew disclosures (3.1.2), Restore/Terms/Privacy links (3.1.2), private API (2.5.1), minimum functionality (4.0), Sign in with Apple parity (4.8), external purchase links (3.1.1(a)), an opt-in Screen Time / FamilyControls justification (5.1.5), tracking/IDFA SDK without an ATT prompt (5.1.2), the export-compliance key (ITSAppUsesNonExemptEncryption), support/privacy URLs in fastlane metadata (2.3), analytics SDK vs PrivacyInfo data-types (5.1.1), and placeholder/dummy metadata copy (2.1). The IAP checks (8–10) are skipped automatically when no in-app-purchase signals are present. The full check table is in references/methodology.md.

The scanner is portable Bash, so you can also run it directly, outside any agent, for a quick CI or pre-commit check.

Phase 2: Apple's official fastlane precheck

Requires bundleId in config (or pass app_identifier directly) and App Store Connect API credentials. Never commit the key. The bundled wrapper builds the ASC API key JSON from your environment, runs precheck, and deletes the key on exit (use --dry-run to preview the command with no credentials and no network):

ASC_KEY_ID=… ASC_ISSUER_ID=… ASC_P8_PATH=/path/AuthKey.p8 \
  bash skills/appstore-precheck/scripts/phase2-precheck.sh com.example.app

Or run it by hand: generate the key JSON from your environment, run precheck, then delete it.

fastlane run precheck \
  app_identifier:"<YOUR_BUNDLE_ID>" \
  api_key_path:"/tmp/asc-key.json" \
  include_in_app_purchases:false \
  default_rule_level:":error"
rm -f /tmp/asc-key.json   # delete the secret immediately

Apple's own rule engine checks URLs, GitHub mentions, profanity, Apple trademarks, pricing language, and beta keywords. Result: true → PASS; any violation line → FAIL. IAP is already covered by Phase 1, so include_in_app_purchases:false avoids the API-key IAP limitation.

Phase 3: Adversarial review (most important)

Dispatch a subagent that role-plays Pierre, a skeptical Apple App Reviewer with the exacting palate of a French critic. Use this prompt verbatim, filling in the app's specifics:

You are Pierre, a veteran Apple App Reviewer who critiques like a French critic. You have seen ten thousand rejections and are impressed by none of them. A new submission just landed on your desk. Your job is to realistically try to reject it, with no approval bias. Pick 5 random guideline items with a spread across sections (one 2.x, two 3.x weighted toward paywall, one 4.x, one 5.x); choose a different combination each run (include a seed line so reruns vary). For each item: (Pass A) grep the relevant files for at least 2 concrete pieces of evidence: metadata for 2.3.x; the paywall view + String Catalog for 3.1.x; Core/navigation for 4.x; Info.plist + PrivacyInfo for 5.1.x. (Pass B) ask "as a reviewer, on what basis would I flag this?" (Pass C) write a rejection draft in Apple's real voice (Guideline X.Y.Z – Category / We noticed… / Specifically… / Next Steps… / Resources…). Assign each item a risk: REJECT-CERTAIN / REJECT-RISK / WARN / PASS. End with a submit recommendation (HOLD / SUBMIT WITH WARNINGS / GO) and the single most critical fix. Read-only: never modify files; if you can't find evidence, say so rather than inventing it. Keep it under 500 words. Include at least one PASS and at least one risk-bearing item, to give a realistic distribution.

Phase 4: Consolidation + token

The GREEN/YELLOW/RED decision and token action are deterministic, derived purely from the FAIL/WARN counts. scripts/verdict.sh computes them so the verdict is machine-testable, not just an agent judgement; pipe the scan into it:

bash skills/appstore-precheck/scripts/verdict.sh < scan-output.txt   # prints VERDICT / COUNTS / TOKEN

It exits 0 GREEN / 1 RED / 2 YELLOW, and with --apply writes or removes .precheck-pass accordingly (YELLOW holds the token for explicit human confirmation). Phase 0–3 still produce the narrative; verdict.sh just pins the threshold arithmetic.

  1. Gather Phase 0–3 output; tally FAIL + WARN + PASS into the output-contract table.
  2. For each FAIL, give a file:line reference and a suggested fix.
  3. Open the verdict with one line in Pierre's voice (vary the wording each run; keep it short and in his deadpan French-critic register), then drop into the plain breakdown. Decide:
    • GREEN: Pierre, grudgingly: "Hmf. I find nothing. Acceptable. Do not make me regret this." then date +%s > .precheck-pass && echo "token written" (valid 60 min).
    • YELLOW: Pierre: "A few small uglinesses. I would not reject, but I noticed." List the WARNs plainly, ask the user "confirm and submit anyway?", write the token only on confirmation.
    • RED: Pierre: "Non. {n} faults. Apple would have found fewer. Suivant." No token; then the plain FAIL list with file:line + fixes, and state plainly that submission is BLOCKED. The Pierre line is flavor only. The FAIL/WARN list, file:line, and fixes below it stay plain and surgical, never paraphrased.
  4. Print the final manual checklist (see references/methodology.md).

Rules

  • READ-ONLY: never change code or assets. Only report and write the token.
  • Speed > exhaustiveness: scan.sh uses parallel grep/jq and finishes in seconds.
  • No error swallowing: if any scan command fails, that line is reported as FAIL and the scan continues.
  • Token location: .precheck-pass at the repo root; the guard tests it with an mmin -60 filter.
  • Local-only: designed for manual, local runs; keep it out of CI to avoid false signals.

Known limits

  • No runtime crash testing; that's TestFlight + a crash reporter. Static analysis only.
  • Several checks are advisory WARNs gated on detected signals (Sign in with Apple 4.8, external-purchase 3.1.1(a), tracking/IDFA without ATT, analytics vs privacy manifest, metadata URLs and placeholder copy). The export-compliance key is flagged when absent, but the actual encryption answer still belongs in App Store Connect.
  • The adversarial reviewer is a heuristic simulation, not a guarantee of Apple's decision.
  • Most accurate for native Swift / SwiftUI. The metadata, privacy-manifest, screenshots, and export-compliance checks apply to any iOS app, but the code-level checks read Swift source, so on React Native (JavaScript) or Flutter (Dart) they under-detect rather than false-fire.
  • iOS only.
  • Phase 0 detects only structural drift (added/removed section numbers); see the reference for why.

Optional: upload guard hook

hooks/fastlane-guard.sh blocks fastlane deliver/pilot/release unless a fresh .precheck-pass token exists. In Claude Code it auto-wires via hooks/hooks.json. In other environments, wire it as a pre-command check yourself, or treat the token as a manual go/no-go signal.

関連スキル