App Store Precheck
一個在每次 iOS App Store 提交前執行的單一指令關卡。它能透過靜態掃描最常見的拒絕原因、執行 Apple 官方的中繼資料檢查工具、監控指南變動,以及模擬審查員的挑剔審查,將被拒絕的風險降至最低。
此技能為唯讀。 它絕不會編輯程式碼、中繼資料或資源。它只會報告並寫入通行令牌。詳細方法(每個拒絕向量、漂移檢查機制)位於 references/methodology.md;當你需要某個檢查的具體細節時,請參閱該文件。
何時執行
- 在 為 TestFlight 封存之前。
- 在 於 App Store Connect 按下「提交以供審查」之前。
- 在 任何
fastlane deliver/release/pilot之前(可選的上傳守衛鉤子會對此進行閘控)。 - 每個次要版本更新時。
請刻意執行此檢查。這是一個人為觸發的關卡,而非自動的背景步驟。
設定(可選)
掃描器會自動偵測標準的 fastlane + Xcode 佈局,因此大多數專案不需要任何設定。若要覆蓋自動偵測,請將 config.example.json 複製到專案根目錄並命名為 .appstore-precheck.json。設定鍵值:bundleId(第 2 階段需要)、iosSourceDir、metadataDir、screenshotsDir、xcstringsPath、paywallGlobs、locales、disclosureKeys.{subscription,trial}、optionalChecks.familyControls、reviewPrepNotes。請參閱 config.example.json 以獲得完整的註解列表。
輸出合約
技能會達到以下三種終止狀態之一:
| 狀態 | 意義 | .precheck-pass 令牌 | 守衛行為 |
|---|---|---|---|
| 綠燈 | 無 FAIL,≤2 個 WARN | 已寫入(有效 60 分鐘) | 允許上傳 |
| 黃燈 | 無 FAIL,但 3+ 個 WARN | 未寫入 | 守衛阻擋;要求明確確認 |
| 紅燈 | 至少 1 個 FAIL | 已移除 | 守衛阻擋;顯示 FAIL 列表 |
當你向使用者呈現結果時,請先以 一句 Pierre 的角色台詞 開頭(請參閱第 3 階段的法國 App Review 評論家),然後直接切入簡潔精準的分析。角色的聲音只是一個薄薄的包裝。底層的資料——FAIL/WARN 列表、file:line 參考和修正建議——必須保持乾淨且忠於機器輸出。絕不要重寫或改寫 scan.sh 的輸出,並將 Pierre 限制在一句話內。
流程(5 個階段:0–4)
第 0 階段:即時指南漂移檢查
比較即時的 App Store 審查指南 章節編號與 guidelines-baseline.json,以偵測 Apple 新增或移除 了哪些章節。始終不阻擋(最多 WARN)。 漂移是我們涵蓋範圍上的缺口,絕非建置的錯誤。網頁在獲取時會被截斷,因此需要雙通道技術;確切的提示和協調程序在 references/methodology.md 中。基準檔案絕不會自動更新。協調是一個需要人為介入的步驟。
第 1 階段:靜態掃描
bash skills/appstore-precheck/scripts/scan.sh
輸出 FAIL: / WARN: / PASS: 行,涵蓋 20 個拒絕向量:隱私權清單一致性(5.1.1(v))、用途字串(5.1.1)、ATT(5.1.2)、提到其他平台(2.3.10)、中繼資料限制(2.3.1)、在地化一致性(2.3.7)、螢幕截圖(2.3.3)、試用與自動續訂揭露(3.1.2)、恢復/條款/隱私權連結(3.1.2)、私有 API(2.5.1)、最低功能要求(4.0)、Apple 登入一致性(4.8)、外部購買連結(3.1.1(a))、選擇性的 Screen Time / FamilyControls 理由說明(5.1.5)、追蹤/IDFA SDK 但無 ATT 提示(5.1.2)、出口合規金鑰(ITSAppUsesNonExemptEncryption)、fastlane 中繼資料中的支援/隱私權 URL(2.3)、分析 SDK 與 PrivacyInfo 資料類型的比對(5.1.1)以及佔位符/假的中繼資料複製(2.1)。IAP 檢查(8–10)在沒有應用程式內購買訊號時會自動跳過。完整的檢查表位於 references/methodology.md。
此掃描器是可攜式 Bash 腳本,因此你也可以直接在代理程式之外執行它,作為快速的 CI 或 pre-commit 檢查。
第 2 階段:Apple 官方 fastlane precheck
需要在設定檔中有 bundleId(或直接傳遞 app_identifier)以及 App Store Connect API 憑證。絕不要提交金鑰。 包裝的包裝腳本會從你的環境中建構 ASC API 金鑰 JSON,執行 precheck,然後在結束時刪除金鑰(使用 --dry-run 來預覽命令,不需憑證且不連線網路):
ASC_KEY_ID=… ASC_ISSUER_ID=… ASC_P8_PATH=/path/AuthKey.p8 \
bash skills/appstore-precheck/scripts/phase2-precheck.sh com.example.app
或者手動執行:從你的環境中產生金鑰 JSON,執行 precheck,然後刪除它。
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 # 立即刪除秘密
Apple 自己的規則引擎會檢查 URL、GitHub 提及、不雅用語、Apple 商標、定價用語和 Beta 關鍵字。Result: true → PASS;任何違規行 → FAIL。IAP 已由第 1 階段涵蓋,因此 include_in_app_purchases:false 可避免 API 金鑰的 IAP 限制。
第 3 階段:敵意審查(最重要)
派遣一個子代理程式,扮演 Pierre,一位多疑的 Apple App 審查員,擁有法國評論家般挑剔的品味。請逐字使用這個提示,填入應用程式的具體細節:
你是 Pierre,一位資深 Apple App 審查員,評論風格如同法國評論家。你看過上萬次拒絕,沒有哪個能讓你印象深刻。一份新的提交剛剛送到你的桌上。你的工作是實際地嘗試拒絕它,沒有任何批准的偏見。隨機挑選 5 個指南項目,並分散在不同章節(一個 2.x,兩個 3.x 偏向付費牆,一個 4.x,一個 5.x);每次執行選擇不同的組合(包含一個種子行,以便重新執行時有所變化)。針對每個項目:(通道 A)在相關檔案中搜尋至少 2 個具體證據:2.3.x 的中繼資料;3.1.x 的付費牆視圖 + 字串目錄;4.x 的核心/導航;5.1.x 的 Info.plist + PrivacyInfo。(通道 B)問自己「身為審查員,我根據什麼理由標記這個?」(通道 C)以 Apple 的真實語氣撰寫拒絕草稿(指南 X.Y.Z – 類別 / 我們注意到… / 具體來說… / 後續步驟… / 資源…)。為每個項目分配風險:REJECT-CERTAIN / REJECT-RISK / WARN / PASS。最後附上提交建議(HOLD / SUBMIT WITH WARNINGS / GO)以及單一最重要的修正。唯讀:絕不修改檔案;如果找不到證據,就誠實說出來,而不是憑空捏造。保持在 500 字以內。包含至少一個 PASS 和至少一個有風險的項目,以呈現真實的分佈。
第 4 階段:整合與令牌
GREEN/YELLOW/RED 的決定和令牌動作是確定性的,純粹根據 FAIL/WARN 的計數得出。scripts/verdict.sh 計算它們,使結果可透過機器測試,而不僅僅是代理程式的判斷;將掃描結果輸入其中:
bash skills/appstore-precheck/scripts/verdict.sh < scan-output.txt # 輸出 VERDICT / COUNTS / TOKEN
它退出時回傳 0(綠燈)/ 1(紅燈)/ 2(黃燈),並在加上 --apply 時相應地寫入或移除 .precheck-pass(黃燈會保留令牌以等待明確的人為確認)。第 0–3 階段仍會產生敘述;verdict.sh 僅固定閾值算術。
- 收集第 0–3 階段的輸出;將 FAIL + WARN + PASS 計入輸出合約表格。
- 對於每個 FAIL,提供
file:line參考和建議的修正。 - 以 一句 Pierre 的角色台詞開場(每次執行時變換措辭;保持簡短,並以其死板、法國評論家的風格呈現),然後切入簡潔的分析。決定:
- 綠燈: Pierre,勉強地:「哼。我什麼也沒找到。可以接受。別讓我後悔。」 然後執行
date +%s > .precheck-pass && echo "token written"(有效 60 分鐘)。 - 黃燈: Pierre:「一些小的不完美。我不會拒絕,但我注意到了。」直接列出 WARN,詢問使用者「確認並仍要提交?」,僅在確認時才寫入令牌。
- 紅燈: Pierre:「不。{n} 個錯誤。Apple 會發現得更少。下一個。」不寫入令牌;然後直接列出 FAIL 清單,包含
file:line+ 修正建議,並明確說明提交已被阻擋。 Pierre 的台詞僅是點綴。其下方的 FAIL/WARN 清單、file:line和修正建議必須保持簡潔精準,絕不 paraphrased。
- 綠燈: Pierre,勉強地:「哼。我什麼也沒找到。可以接受。別讓我後悔。」 然後執行
- 列印最終的手動檢查清單(請參閱
references/methodology.md)。
規則
- 唯讀: 絕不更改程式碼或資源。僅報告和寫入令牌。
- 速度 > 詳盡性:
scan.sh使用並行的 grep/jq,數秒內完成。 - 不吞沒錯誤: 如果任何掃描命令失敗,該行會報告為 FAIL,並且掃描會繼續。
- 令牌位置: 位於專案根目錄的
.precheck-pass;守衛使用mmin -60過濾器測試它。 - 僅限本機: 設計用於手動、本機執行;請勿在 CI 中使用,以避免誤報。
已知限制
- 沒有執行時崩潰測試;那需要 TestFlight 和崩潰回報器。僅限靜態分析。
- 幾個檢查是建議性的 WARN,取決於偵測到的訊號(Apple 登入 4.8、外部購買 3.1.1(a)、追蹤/IDFA 但無 ATT、分析與隱私權清單、中繼資料 URL 和佔位符複製)。出口合規金鑰在缺失時會被標記,但實際的加密答案仍屬於 App Store Connect。
- 敵意審查是一種啟發式模擬,不能保證 Apple 的決定。
- 對於原生 Swift / SwiftUI 最為準確。中繼資料、隱私權清單、螢幕截圖和出口合規檢查適用於任何 iOS 應用程式,但程式碼層級的檢查會讀取 Swift 原始碼,因此在 React Native(JavaScript)或 Flutter(Dart)上,它們會偵測不足,而非產生誤報。
- 僅限 iOS。
- 第 0 階段僅偵測結構性漂移(新增/移除的章節編號);詳細原因請參閱參考文獻。
可選:上傳守衛鉤子
hooks/fastlane-guard.sh 會阻擋 fastlane deliver/pilot/release,除非存在一個有效的 .precheck-pass 令牌。在 Claude Code 中,它會透過 hooks/hooks.json 自動接線。在其他環境中,請自行將其接線為指令前檢查,或將令牌視為手動的執行/停止信號。