appstore-precheck 1.1.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: appstore-precheck
3
- description: Read-only pre-submission check for an iOS app before App Store review. Scans Swift code, fastlane metadata, screenshots, PrivacyInfo.xcprivacy, and the paywall for 20 rejection vectors, wraps Apple's official `fastlane precheck`, watches for live App Store Review Guideline drift, and runs an adversarial reviewer pass. Emits a GREEN/YELLOW/RED verdict and a `.precheck-pass` token an upload guard can gate on. Use when preparing an iOS App Store submission (before Archive, before "Submit for Review", before TestFlight, or before any `fastlane deliver/pilot/release`), or when the user mentions App Store rejection, app review, or fastlane upload.
3
+ description: Read-only pre-submission check for an iOS app before App Store review. Scans Swift code, fastlane metadata, screenshots, PrivacyInfo.xcprivacy, and the paywall for 41 rejection vectors, wraps Apple's official `fastlane precheck`, watches for live App Store Review Guideline drift, has Pierre explain every FAIL and WARN, then runs 22 semantic deep-review checks (Tier A) plus 6 heuristic checks (Tier B v1) — 28 total. Emits a GREEN/YELLOW/RED verdict and a `.precheck-pass` token an upload guard can gate on. Use when preparing an iOS App Store submission (before Archive, before "Submit for Review", before TestFlight, or before any `fastlane deliver/pilot/release`), or when the user mentions App Store rejection, app review, or fastlane upload.
4
4
  license: MIT
5
5
  metadata:
6
6
  author: Berkay Turk
7
- version: 1.1.0
7
+ version: 1.5.1
8
8
  allowed-tools: Bash Read Grep Glob WebFetch
9
9
  ---
10
10
 
@@ -12,7 +12,9 @@ allowed-tools: Bash Read Grep Glob WebFetch
12
12
 
13
13
  A one-command gate to run before every iOS App Store submission. It minimizes the risk of
14
14
  rejection by statically scanning the most common rejection vectors, running Apple's own
15
- metadata linter, watching for guideline drift, and simulating an adversarial review pass.
15
+ metadata linter, watching for guideline drift, having Pierre explain every FAIL and WARN, and
16
+ running 28 semantic deep-review checks (22 Tier A + 6 Tier B v1 heuristic). The deep-review checklist lives in
17
+ [`references/pierre-deep-review.md`](references/pierre-deep-review.md).
16
18
 
17
19
  **This skill is read-only.** It never edits code, metadata, or assets. It only reports and
18
20
  writes a pass token. The detailed method (every rejection vector, the drift-check mechanics)
@@ -43,17 +45,54 @@ The skill reaches one of three terminal states:
43
45
 
44
46
  | State | Meaning | `.precheck-pass` token | Guard behavior |
45
47
  |-------|---------|------------------------|----------------|
46
- | **GREEN** | No FAIL, ≤2 WARN | Written (valid 60 min) | Upload allowed |
47
- | **YELLOW** | No FAIL but 3+ WARN | Not written | Guard blocks; ask for explicit confirmation |
48
+ | **GREEN** | No FAIL, ≤4 WARN | Written (valid 60 min) | Upload allowed |
49
+ | **YELLOW** | No FAIL but 5+ WARN | Not written | Guard blocks; ask for explicit confirmation |
48
50
  | **RED** | At least 1 FAIL | Removed | Guard blocks; show the FAIL list |
49
51
 
50
- When you present the verdict to the user, open with a **single in-character line from Pierre**
51
- (the French App Review critic, see Phase 3), then drop straight into the plain, surgical
52
- breakdown. The voice is a thin wrapper. The data underneath, the FAIL/WARN list, `file:line`
53
- references, and fixes, stays clean and machine-faithful. **Never rewrite or paraphrase
54
- `scan.sh` output**, and keep Pierre to one line.
52
+ When you present the verdict to the user, open with Pierre's **trilingual verdict block** (see format
53
+ below and Phase 5), then Pierre's **finding commentary** (Phase 3 2–3 sentences per FAIL/WARN),
54
+ then Pierre's **deep-review commentary** (Phase 4 every `REVIEW-FINDING`), then the
55
+ machine-faithful `FAIL:`/`WARN:`/`PASS:` lines and `file:line` fixes from `scan.sh`.
56
+ **Never rewrite or paraphrase the scanner lines themselves**; Pierre explains them, he does not
57
+ replace them.
55
58
 
56
- ## Flow (5 phases: 0–4)
59
+ ### Trilingual verdict block (required format)
60
+
61
+ Pierre's opening lines must **not** run together on one row or one sentence separated by slashes.
62
+ Render them as **three visually distinct blocks** (two if the user already converses in French or
63
+ English — drop the duplicate language).
64
+
65
+ Use this markdown shape every time:
66
+
67
+ ```markdown
68
+ ### Pierre
69
+
70
+ **Français**
71
+ > *<Pierre's French one-liner in italics>*
72
+
73
+ ---
74
+
75
+ **English**
76
+ > *<Pierre's English one-liner in italics>*
77
+
78
+ ---
79
+
80
+ **<User language name>** ← e.g. **Türkçe**, **Deutsch**, **日本語**
81
+ > *<Same meaning, idiomatic one-liner in the user's conversation language, in italics>*
82
+ ```
83
+
84
+ Rules:
85
+
86
+ - `### Pierre` heading always opens the block.
87
+ - Each language gets a **bold label** on its own line, a blank line, then a **blockquote** with the
88
+ line in *italics*.
89
+ - Separate languages with a horizontal rule (`---`) — never cram FR/EN/TR into one paragraph.
90
+ - If the user's conversation language **is French**, omit the **Français** block (English + user lang
91
+ only — or just English if they asked in English). If the user's language **is English**, omit
92
+ **English** (Français + user lang, or just Français if they asked in French).
93
+ - Keep each one-liner short (one sentence). Vary wording each run; stay in Pierre's dry critic voice.
94
+
95
+ ## Flow (6 phases: 0–5)
57
96
 
58
97
  ### Phase 0: Live guideline drift check
59
98
 
@@ -71,16 +110,27 @@ baseline is **never auto-updated**. Reconciliation is a deliberate human step.
71
110
  bash skills/appstore-precheck/scripts/scan.sh
72
111
  ```
73
112
 
74
- Emits `FAIL:` / `WARN:` / `PASS:` lines covering 20 rejection vectors: Privacy Manifest parity
75
- (5.1.1(v)), purpose strings (5.1.1), ATT (5.1.2), other-platform mentions (2.3.10), metadata
76
- limits (2.3.1), localized parity (2.3.7), screenshots (2.3.3), trial & auto-renew disclosures
77
- (3.1.2), Restore/Terms/Privacy links (3.1.2), private API (2.5.1), minimum functionality (4.0),
78
- Sign in with Apple parity (4.8), external purchase links (3.1.1(a)), an opt-in Screen Time /
79
- FamilyControls justification (5.1.5), tracking/IDFA SDK without an ATT prompt (5.1.2), the
80
- export-compliance key (`ITSAppUsesNonExemptEncryption`), support/privacy URLs in fastlane
81
- metadata (2.3), analytics SDK vs PrivacyInfo data-types (5.1.1), and placeholder/dummy metadata
82
- copy (2.1). The IAP checks (8–10) are
83
- skipped automatically when no in-app-purchase signals are present. The full check table is in
113
+ Emits `FAIL:` / `WARN:` / `PASS:` lines covering 41 rejection vectors: Privacy Manifest /
114
+ Required Reason API parity (5.1.1), purpose strings (5.1.1), ATT (5.1.2), other-platform mentions
115
+ (2.3.10), metadata limits (2.3.1), localized parity (2.3.7), screenshots (2.3.3), trial &
116
+ auto-renew disclosures (3.1.2), Restore/Terms/Privacy links (3.1.2), private API (2.5.1), minimum
117
+ functionality (4.2), Sign in with Apple parity (4.8), external purchase links (3.1.1(a)), an
118
+ opt-in Screen Time / FamilyControls justification (5.1.5), tracking/IDFA SDK without an ATT prompt
119
+ (5.1.2), the export-compliance key (`ITSAppUsesNonExemptEncryption`), support/privacy URLs in
120
+ fastlane metadata (2.3 / 1.5 / 5.1.1(i)), analytics SDK vs PrivacyInfo data-types (5.1.1),
121
+ placeholder/dummy metadata copy (2.1), third-party payment SDK for digital goods (3.1.1),
122
+ user-generated content without moderation (1.2), App Transport Security disabled app-wide (1.6),
123
+ recurring Apple Pay disclosure (4.9), custom App Store review prompts (5.6.1), misleading
124
+ marketing claims (2.3.1), "For Kids" wording outside the Kids Category (2.3.8), keyboard
125
+ extensions requiring full access (4.4.1), HealthKit data with an iCloud sync path (5.1.3), VPN /
126
+ NetworkExtension usage (5.4), a demo account for a login-gated app (2.1), executable-code download
127
+ / native hot-patching (2.5.2), unused background modes (2.5.4), cryptocurrency wallet/mining
128
+ (3.1.5(a)), thin WKWebView wrappers (4.2.3), remote-desktop apps (4.2.7), Safari extensions
129
+ (4.4.2), account creation without in-app deletion (5.1.1(v) Account Sign-In), kids audience with
130
+ third-party ads/analytics (5.1.4), real-money gambling copy (5.3.4), and MDM signals (5.5). The
131
+ IAP checks (8–10) are skipped automatically when no in-app-purchase signals are present, and the
132
+ signal-gated advisory checks (16–41) stay silent unless their triggering signal is found. The
133
+ full check table is in
84
134
  [`references/methodology.md`](references/methodology.md#phase-1-rejection-vectors).
85
135
 
86
136
  The scanner is portable Bash, so you can also run it directly, outside any agent, for a quick CI
@@ -113,53 +163,141 @@ Apple's own rule engine checks URLs, GitHub mentions, profanity, Apple trademark
113
163
  language, and beta keywords. `Result: true` → PASS; any violation line → FAIL. IAP is already
114
164
  covered by Phase 1, so `include_in_app_purchases:false` avoids the API-key IAP limitation.
115
165
 
116
- ### Phase 3: Adversarial review (most important)
166
+ ### Phase 3: Pierre explains every finding
167
+
168
+ After Phases 0–2, role-play **Pierre** — a veteran Apple App Reviewer with a French critic's deadpan
169
+ tone. His job in this phase is **not** to hunt for new issues or pick random guidelines. The scanner
170
+ already did the detection. Pierre **explains every FAIL and WARN** the pipeline emitted.
171
+
172
+ **Input to explain (all of it, no sampling):**
117
173
 
118
- Dispatch a subagent that role-plays **Pierre**, a skeptical Apple App Reviewer with the exacting
119
- palate of a French critic. Use this prompt verbatim, filling in the app's specifics:
174
+ 1. Every `WARN:` from Phase 0 (guideline drift), if any.
175
+ 2. Every `FAIL:` and `WARN:` from Phase 1 (`scan.sh`), verbatim.
176
+ 3. Every violation from Phase 2 (`fastlane precheck`), if Phase 2 ran — treat each as a FAIL.
120
177
 
121
- > You are **Pierre**, a veteran Apple App Reviewer who critiques like a French critic. You have
122
- > seen ten thousand rejections and are impressed by none of them. A new submission just landed on
123
- > your desk. Your job is to **realistically try to reject it**, with no approval bias. Pick **5 random
124
- > guideline items** with a spread across sections (one 2.x, two 3.x weighted toward paywall,
125
- > one 4.x, one 5.x); choose a different combination each run (include a seed line so reruns
126
- > vary). For each item: (Pass A) grep the relevant files for at least 2 concrete pieces of
127
- > evidence: metadata for 2.3.x; the paywall view + String Catalog for 3.1.x; Core/navigation
128
- > for 4.x; Info.plist + PrivacyInfo for 5.1.x. (Pass B) ask "as a reviewer, on what basis would
129
- > I flag this?" (Pass C) write a rejection draft in Apple's real voice (Guideline X.Y.Z
130
- > Category / We noticed… / Specifically… / Next Steps… / Resources…). Assign each item a risk:
131
- > REJECT-CERTAIN / REJECT-RISK / WARN / PASS. End with a submit recommendation (HOLD / SUBMIT
132
- > WITH WARNINGS / GO) and the single most critical fix. Read-only: never modify files; if you
133
- > can't find evidence, say so rather than inventing it. Keep it under 500 words. Include at
134
- > least one PASS and at least one risk-bearing item, to give a realistic distribution.
178
+ **Rules:**
179
+
180
+ - **One entry per finding.** Do not merge, skip, or summarize away individual lines.
181
+ - **2–3 sentences per FAIL or WARN** in Pierre's voice: (1) which guideline Apple cares about and
182
+ why it matters at review, (2) what the scan found in plain language, (3) the concrete fix or
183
+ what to verify before submitting.
184
+ - Quote or repeat the **exact** `FAIL:`/`WARN:` line (or Phase 2 violation text) before each
185
+ explanation block so the user can match Pierre to the machine output.
186
+ - **Read-only:** never modify files; if a line lacks a path, say what to check manually do not
187
+ invent evidence.
188
+ - **Zero FAIL and zero WARN:** Pierre gives a short all-clear (2–3 sentences total). Do not fabricate
189
+ issues to seem thorough.
190
+ - **Language:** write the 2–3 sentence explanations in the **user's conversation language** (keep
191
+ Pierre's dry critic register). The Phase 5 trilingual one-liner stays separate.
192
+
193
+ **Output format (repeat for each finding):**
194
+
195
+ ```
196
+ FAIL: <verbatim line from scan.sh or Phase 2>
197
+ Pierre: <2–3 sentences>
198
+ ```
135
199
 
136
- ### Phase 4: Consolidation + token
200
+ For WARN lines, use the same shape with `WARN:` instead of `FAIL:`.
201
+
202
+ Use this prompt verbatim after Phases 0–2 complete, pasting in the collected findings:
203
+
204
+ > You are **Pierre**, a veteran Apple App Reviewer who speaks like a French critic — dry, exacting,
205
+ > never impressed. Phases 0–2 already ran. Your only job is to **explain every FAIL and WARN below**
206
+ > in **2–3 sentences each**. Do not pick random guidelines. Do not hunt for extra issues. Do not skip
207
+ > any line. For each finding: print the line verbatim, then `Pierre:` followed by your explanation
208
+ > (why Apple flags this guideline, what the scan found, what to fix or verify). If there are zero
209
+ > FAILs and zero WARNs, say so briefly in 2–3 sentences. Read-only — never modify files. Write the
210
+ > explanations in `<USER_LANGUAGE>`.
211
+
212
+ ### Phase 4: Pierre deep review (28 semantic checks)
213
+
214
+ After Phase 3, Pierre runs the **Review Simulator**: 28 read-only, evidence-based checks the
215
+ static scanner cannot fully judge (**22 Tier A** + **6 Tier B v1** heuristic — marked † below).
216
+ The full checklist, per-check procedure, and output format live in
217
+ [`references/pierre-deep-review.md`](references/pierre-deep-review.md) — read it before starting
218
+ Phase 4.
219
+
220
+ **What Pierre does:** read Swift, metadata, entitlements, xcstrings, paywall views, review notes,
221
+ screenshot/preview assets; fetch privacy and support URLs; cross-check claims vs code, policy vs
222
+ SDK usage, screenshots vs features, and paywall disclosure quality.
223
+
224
+ **Rules (summary):**
225
+
226
+ - Run **all 28 checks every time** — report each as `REVIEW-PASS:` or `REVIEW-FINDING:` (never skip).
227
+ - `REVIEW-FINDING:` is always **WARN** (advisory). It does **not** change FAIL/WARN counts or the verdict.
228
+ - † **Tier B v1** checks (4, 5, 7, 10, 15, 28) are heuristic — use cautious language; prefer not applicable when no signal.
229
+ - When Phase 1 already flagged a guideline, still run the matching deep check and add semantic context.
230
+ - Cite evidence (`file:line`, metadata path, screenshot name, fetched URL excerpt). Read-only — never edit files.
231
+
232
+ **The 28 checks (guideline order):**
233
+
234
+ | # | Guideline | Deep question |
235
+ |---|-----------|---------------|
236
+ | 1 | **1.2.1** | UGC → real report/block/moderation UI flow? |
237
+ | 2 | **1.4.1** | Health/medical claims without disclaimers? |
238
+ | 3 | **2.1** | Metadata claims match implemented features? |
239
+ | 4 † | **2.1** | Demo account / App Review notes actionable (not placeholder)? |
240
+ | 5 † | **2.2** | Beta / test / preview language in store-facing copy? |
241
+ | 6 | **2.3.2** | Primary category fits app type? |
242
+ | 7 † | **2.3.4** | App preview assets match shipped features? |
243
+ | 8 | **2.3.5** | Screenshots match shipped features? |
244
+ | 9 | **2.3.6** | Metadata pricing language matches paywall? |
245
+ | 10 † | **2.3.9** | Incentivized review copy (rate for reward)? |
246
+ | 11 | **2.3.11–2.3.13** | Cross-locale metadata materially consistent? |
247
+ | 12 | **3.1.1** | Digital goods unlocked via external purchase links? |
248
+ | 13 | **3.1.2** | Trial/auto-renew/cancel disclosures are legible sentences? |
249
+ | 14 | **4.2.1–4.2.2** | More than a thin WebView shell / template? |
250
+ | 15 † | **4.5.1–4.5.3** | Push / HomeKit entitlements used as intended? |
251
+ | 16 | **4.8** | Third-party login → Sign in with Apple or valid exempt case? |
252
+ | 17 | **5.1.1(i)** | Privacy policy text matches code + PrivacyInfo? |
253
+ | 18 | **5.1.1(ii)** | Purpose strings specific and feature-tied? |
254
+ | 19 | **5.1.1(iii)** | Permissions/SDKs proportionate to app purpose? |
255
+ | 20 | **5.1.1(iv)** | Permission denial handled without forced loops? |
256
+ | 21 | **5.1.2** | ATT, tracking description, policy, and ad SDKs align? |
257
+ | 22 | **5.1.3** | HealthKit data not used for ads/marketing? |
258
+ | 23 | **5.1.4** | Kids signals → parental gate before links/IAP/account? |
259
+ | 24 | **5.4** | VPN → on-screen disclosure copy in UI strings? |
260
+ | 25 | **5.2.1–5.2.3** | Obvious trademark/brand misuse in metadata or UI? |
261
+ | 26 | **5.3.1–5.3.3** | Contest/sweepstakes copy includes official rules? |
262
+ | 27 | **5.6.2–5.6.3** | Developer identity consistent (support URL, domains, app name)? |
263
+ | 28 † | **5.6.4–5.6.7** | Rating manipulation dark patterns beyond scan §25? |
264
+
265
+ Use this prompt after Phase 3:
266
+
267
+ > You are **Pierre**. Phase 3 is done. Now run **Phase 4 deep review**: all 28 checks in
268
+ > [`references/pierre-deep-review.md`](references/pierre-deep-review.md), in table order. For each
269
+ > check emit `REVIEW-PASS:` or `REVIEW-FINDING: <guideline> WARN — …`. For every REVIEW-FINDING,
270
+ > add `Pierre:` with 2–3 sentences (why Apple cares, what you found, what to fix). Read-only.
271
+ > Write explanations in `<USER_LANGUAGE>`. Do not change the scan verdict counts. † Tier B checks
272
+ > (4, 5, 7, 10, 15, 28): prefer not applicable when no signal; use cautious language when flagging.
273
+
274
+ ### Phase 5: Consolidation + token
137
275
 
138
276
  The GREEN/YELLOW/RED decision and token action are **deterministic**, derived purely from the
139
- FAIL/WARN counts. [`scripts/verdict.sh`](scripts/verdict.sh) computes them so the verdict is
140
- machine-testable, not just an agent judgement; pipe the scan into it:
277
+ FAIL/WARN counts from Phases 0–2. [`scripts/verdict.sh`](scripts/verdict.sh) computes them so the
278
+ verdict is machine-testable, not just an agent judgement; pipe the scan into it:
141
279
 
142
280
  ```bash
143
281
  bash skills/appstore-precheck/scripts/verdict.sh < scan-output.txt # prints VERDICT / COUNTS / TOKEN
144
282
  ```
145
283
 
146
284
  It exits 0 GREEN / 1 RED / 2 YELLOW, and with `--apply` writes or removes `.precheck-pass`
147
- accordingly (YELLOW holds the token for explicit human confirmation). Phase 0–3 still produce the
148
- narrative; verdict.sh just pins the threshold arithmetic.
149
-
150
- 1. Gather Phase 0–3 output; tally FAIL + WARN + PASS into the output-contract table.
151
- 2. For each FAIL, give a `file:line` reference and a suggested fix.
152
- 3. Open the verdict with **one** line in Pierre's voice (vary the wording each run; keep it short
153
- and in his deadpan French-critic register), then drop into the plain breakdown. Decide:
154
- - **GREEN:** Pierre, grudgingly: *"Hmf. I find nothing. Acceptable. Do not make me regret this."*
155
- then `date +%s > .precheck-pass && echo "token written"` (valid 60 min).
156
- - **YELLOW:** Pierre: *"A few small uglinesses. I would not reject, but I noticed."* List the
157
- WARNs plainly, ask the user "confirm and submit anyway?", write the token only on confirmation.
158
- - **RED:** Pierre: *"Non. {n} faults. Apple would have found fewer. Suivant."* No token; then the
159
- plain FAIL list with `file:line` + fixes, and state plainly that submission is BLOCKED.
160
- The Pierre line is flavor only. The FAIL/WARN list, `file:line`, and fixes below it stay plain
161
- and surgical, never paraphrased.
162
- 4. Print the final manual checklist (see
285
+ accordingly (YELLOW holds the token for explicit human confirmation). Phase 0–4 produce the
286
+ narrative; verdict.sh just pins the threshold arithmetic. `REVIEW-FINDING` lines are advisory only.
287
+
288
+ 1. Gather Phase 0–4 output; tally FAIL + WARN + PASS into the output-contract table (counts come
289
+ from Phase 1 + Phase 0/2 only Pierre's prose and REVIEW-FINDING lines do not add FAIL/WARN).
290
+ 2. Open with Pierre's **trilingual verdict block** using the required format in [Output contract](#trilingual-verdict-block-required-format) bold language label + blockquote per language, separated by `---`; never one compressed line.
291
+ 3. Present **Phase 3 commentary** Pierre's 2–3 sentence explanation for every FAIL and WARN.
292
+ 4. Present **Phase 4 deep review** summary count (`REVIEW-FINDING` vs `REVIEW-PASS` of 28), then
293
+ every `REVIEW-FINDING` with Pierre explanation; list `REVIEW-PASS` lines compactly or omit if all 28 passed.
294
+ 5. Present the **machine-faithful** scan output: each `FAIL:`/`WARN:` line verbatim, then for each
295
+ FAIL a `file:line` reference and a suggested fix (one line each, surgical, not paraphrased).
296
+ 6. State the verdict and token action (example one-liners each goes in its own language block, not inline):
297
+ - **GREEN:** FR *"Hmf. Je ne trouve rien. Acceptable. Ne me faites pas regretter."* · EN *"Hmf. I find nothing. Acceptable. Do not make me regret this."* · + user-language line write `.precheck-pass` (valid 60 min).
298
+ - **YELLOW:** FR *"Quelques petites laideurs. Je ne rejette pas, mais j'ai remarqué."* · EN *"A few small uglinesses. I would not reject, but I noticed."* · + user-language line → ask "confirm and submit anyway?"; token only on confirmation.
299
+ - **RED:** FR *"Non. {n} fautes. Apple en aurait trouvé moins. Suivant."* · EN *"No. {n} faults. Apple would have found fewer. Next."* · + user-language line → no token; state submission is BLOCKED.
300
+ 7. Print the final manual checklist (see
163
301
  [`references/methodology.md`](references/methodology.md#pre-submit-manual-checklist)).
164
302
 
165
303
  ## Rules
@@ -177,8 +315,12 @@ narrative; verdict.sh just pins the threshold arithmetic.
177
315
  external-purchase 3.1.1(a), tracking/IDFA without ATT, analytics vs privacy manifest, metadata
178
316
  URLs and placeholder copy). The export-compliance key is flagged when absent, but the actual
179
317
  encryption answer still belongs in App Store Connect.
180
- - The adversarial reviewer is a heuristic simulation, not a guarantee of Apple's decision.
181
- - iOS only. Android / Play Console is out of scope.
318
+ - Pierre's Phase 3 commentary explains the scan's FAIL/WARN findings; Phase 4 adds advisory
319
+ `REVIEW-FINDING` lines that do not change the verdict. Neither phase is a guarantee of Apple's decision.
320
+ - Most accurate for native Swift / SwiftUI. The metadata, privacy-manifest, screenshots, and
321
+ export-compliance checks apply to any iOS app, but the code-level checks read Swift source, so on
322
+ React Native (JavaScript) or Flutter (Dart) they under-detect rather than false-fire.
323
+ - iOS only.
182
324
  - Phase 0 detects only **structural** drift (added/removed section numbers); see the reference for why.
183
325
 
184
326
  ## Optional: upload guard hook
@@ -6,8 +6,9 @@ Behavioral evaluations for the `appstore-precheck` skill, in the
6
6
 
7
7
  These test the **agent-behavior layer**: given a realistic submission-intent prompt and a throwaway
8
8
  iOS project, does the agent invoke the skill, run the scanner, reach the correct **GREEN/YELLOW/RED**
9
- verdict, and present it faithfully: one Pierre line as a thin wrapper, the scan's `FAIL`/`WARN`/`PASS`
10
- lines verbatim, never paraphrased? Each case carries `assertions` describing what a correct run must
9
+ verdict, and present it faithfully: Pierre's trilingual block (`### Pierre`, bold language labels,
10
+ blockquotes, `---` separators), then **2–3 sentences explaining each FAIL and WARN**, then the
11
+ scan's `FAIL`/`WARN`/`PASS` lines verbatim (never paraphrased). Each case carries `assertions` describing what a correct run must
11
12
  contain.
12
13
 
13
14
  | # | Case | Input | Expected |
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "skill_name": "appstore-precheck",
3
- "description": "Behavioral evals for the appstore-precheck skill: does the agent invoke the skill, run the scanner, reach the correct GREEN/YELLOW/RED verdict, and present it faithfully (Pierre voice as a thin wrapper, scan output never paraphrased)? Inputs are self-contained throwaway iOS projects under evals/files/. The mechanical layer (scan.sh / verdict.sh / guard) is separately covered by the deterministic suite in tests/.",
3
+ "description": "Behavioral evals for the appstore-precheck skill: does the agent invoke the skill, run the scanner, reach the correct GREEN/YELLOW/RED verdict, and present it faithfully (Pierre trilingual one-liner + 2–3 sentence explanation per FAIL/WARN; Phase 4 deep review when applicable; scan output never paraphrased)? Inputs are self-contained throwaway iOS projects under evals/files/. The mechanical layer (scan.sh / verdict.sh / guard) is separately covered by the deterministic suite in tests/.",
4
4
  "evals": [
5
5
  {
6
6
  "id": 1,
@@ -22,7 +22,10 @@
22
22
  "The .precheck-pass token is NOT written (it is removed/withheld)",
23
23
  "Flags Guideline 2.3.10: the 'Android' / 'Google Play' reference in the metadata description",
24
24
  "Flags Guideline 3.1.2: missing Restore Purchases, Terms of Use, and Privacy Policy on the paywall",
25
- "The response opens with exactly one in-character Pierre line, then plain output",
25
+ "Pierre explains each FAIL and WARN in 2–3 sentences (one block per finding; no random guideline sampling)",
26
+ "When Phase 4 runs, Pierre reports all 28 deep-review checks as REVIEW-PASS or REVIEW-FINDING (advisory; does not change verdict)",
27
+ "Pierre's trilingual block is visually separated: ### Pierre heading, bold language label + blockquote per language, --- between languages — not one compressed FR/EN line",
28
+ "The response opens with Pierre's trilingual block (French, English, then the user's conversation language; two blocks if the user already speaks French or English), then plain output",
26
29
  "The scan's FAIL lines are shown verbatim (machine-faithful), not paraphrased or invented"
27
30
  ]
28
31
  },
@@ -46,7 +49,7 @@
46
49
  "The .precheck-pass token is written",
47
50
  "Confirms Restore Purchases, Terms of Use, and Privacy Policy are present on the paywall",
48
51
  "Reports zero FAIL lines",
49
- "The response opens with exactly one in-character Pierre line"
52
+ "The response opens with Pierre's trilingual block (### Pierre, bold labels, blockquotes, --- separators; two languages if the user already speaks French or English)"
50
53
  ]
51
54
  },
52
55
  {
@@ -1,15 +1,27 @@
1
1
  {
2
2
  "_doc": "Baseline of Apple App Store Review Guidelines section numbers, for drift detection. Phase 0 fetches the live guidelines page and diffs its section numbers against `all_sections`; any NEW/REMOVED number => WARN: guideline-drift. This file is updated ONLY by deliberate human reconciliation (see SKILL.md Phase 0), NEVER auto-edited by the skill, otherwise the warning would be silently swallowed.",
3
3
  "source_url": "https://developer.apple.com/app-store/review/guidelines/",
4
- "reconciled_on": "2026-06-24",
5
- "apple_last_updated_seen": "not-exposed-in-DOM (the page shows no visible date stamp; drift signal = the set of section numbers)",
4
+ "reconciled_on": "2026-06-30",
5
+ "apple_last_updated_seen": "Last Updated: June 8, 2026 (now exposed at the page footer); drift signal remains the set of section numbers",
6
6
  "drift_check_coverage": "Reliable for Sections 1-4 + 5.1-5.4 (high-churn regions: 2.5.x, 3.1.x, 4.7, 5.1.x). The 5.5-5.6.x tail can't be fetched (the page truncates ~5.4) and is structurally stable; review it by hand at reconciliation.",
7
7
  "covered_by_scan": [
8
- "2.3.1", "2.3.3", "2.3.7", "2.3.10",
9
- "2.5.1",
10
- "3.1.2",
11
- "4.0",
12
- "5.1.1", "5.1.2", "5.1.5"
8
+ "1.2", "1.5", "1.6",
9
+ "2.1", "2.3", "2.3.1", "2.3.3", "2.3.7", "2.3.8", "2.3.10",
10
+ "2.5.1", "2.5.2", "2.5.4",
11
+ "3.1.1", "3.1.2", "3.1.5",
12
+ "4.2", "4.2.3", "4.2.7", "4.4.1", "4.4.2", "4.8", "4.9",
13
+ "5.1.1", "5.1.2", "5.1.3", "5.1.4", "5.1.5",
14
+ "5.3.4", "5.4", "5.5", "5.6.1"
15
+ ],
16
+ "covered_by_pierre_deep_review": [
17
+ "1.2.1", "1.4.1",
18
+ "2.1", "2.2", "2.3.2", "2.3.4", "2.3.5", "2.3.6", "2.3.9", "2.3.11", "2.3.12", "2.3.13",
19
+ "3.1.1", "3.1.2",
20
+ "4.2.1", "4.2.2", "4.5.1", "4.5.2", "4.5.3", "4.8",
21
+ "5.1.1", "5.1.2", "5.1.3", "5.1.4", "5.4",
22
+ "5.2.1", "5.2.2", "5.2.3",
23
+ "5.3.1", "5.3.2", "5.3.3",
24
+ "5.6.2", "5.6.3", "5.6.4", "5.6.5", "5.6.6", "5.6.7"
13
25
  ],
14
26
  "all_sections": [
15
27
  "1.1", "1.1.1", "1.1.2", "1.1.3", "1.1.4", "1.1.5", "1.1.6", "1.1.7",
@@ -7,6 +7,8 @@ whole file to run the skill.
7
7
 
8
8
  - [Phase 0: Guideline drift check](#phase-0-guideline-drift-check)
9
9
  - [Phase 1: Rejection vectors](#phase-1-rejection-vectors)
10
+ - [Phase 3: Pierre explains every finding](#phase-3-pierre-explains-every-finding)
11
+ - [Phase 4: Pierre deep review (22 checks)](#phase-4-pierre-deep-review-22-semantic-checks)
10
12
  - [Auto-detection rules](#auto-detection-rules)
11
13
  - [Verdict thresholds](#verdict-thresholds)
12
14
  - [Pre-submit manual checklist](#pre-submit-manual-checklist)
@@ -47,7 +49,7 @@ would silently swallow the warning and defeat the entire purpose of drift detect
47
49
 
48
50
  **Limit:** this detects only *structural* drift (the set of section numbers). A section whose
49
51
  number is unchanged but whose *text* changed is not caught here, but is partly covered by
50
- Phase 2 (Apple's own rule engine) and Phase 3 (the adversarial reviewer). Apple does not expose a
52
+ Phase 2 (Apple's own rule engine) and Phase 3 (Pierre explains every FAIL/WARN). Apple does not expose a
51
53
  machine-readable "last updated" date in the page DOM, so the section-number set is the signal.
52
54
 
53
55
  ---
@@ -58,7 +60,7 @@ machine-readable "last updated" date in the page DOM, so the section-number set
58
60
 
59
61
  | # | Guideline | What it checks |
60
62
  |---|-----------|----------------|
61
- | 1 | **5.1.1(v) Privacy Manifest** | Required Reason API usage (`UserDefaults`/`@AppStorage`, file timestamp, system boot time, disk space, active keyboard) ↔ `PrivacyInfo.xcprivacy` declaration parity |
63
+ | 1 | **5.1.1 Privacy Manifest** | Required Reason API usage (`UserDefaults`/`@AppStorage`, file timestamp, system boot time, disk space, active keyboard) ↔ `PrivacyInfo.xcprivacy` declaration parity. (Apple documents Required Reason APIs under 5.1.1 + the privacy-manifest docs; it is **not** sub-item (v) — that is Account Sign-In, checked in vector 38.) |
62
64
  | 2 | **5.1.1 Purpose Strings** | Every imported sensitive framework (FamilyControls, CoreLocation, AVFoundation, Photos, Contacts, HealthKit) has a non-empty `NS*UsageDescription` in Info.plist |
63
65
  | 3 | **5.1.2 ATT** | If `AppTrackingTransparency`/`ATTrackingManager` is used, `NSUserTrackingUsageDescription` is present |
64
66
  | 4 | **2.3.10 Other platforms** | No "Android" / "Google Play" / competitor store names in store metadata |
@@ -69,7 +71,7 @@ machine-readable "last updated" date in the page DOM, so the section-number set
69
71
  | 9 | **3.1.2 Auto-renew disclosure** | A subscription disclosure string exists and covers each locale |
70
72
  | 10 | **3.1.2 Required links** | The paywall view contains Restore Purchases + Terms of Use (EULA) + Privacy Policy |
71
73
  | 11 | **2.5.1 Private API** | No banned identifiers (`UIWebView`, `setSelectionIndicatorImage`, `_UIBackdropView`, `NSURLConnection`, …) |
72
- | 12 | **4.0 Minimum functionality** | At least one navigation hub (`TabView` / `NavigationStack` / `NavigationSplitView`) |
74
+ | 12 | **4.2 Minimum functionality** | At least one navigation hub (`TabView` / `NavigationStack` / `NavigationSplitView`) |
73
75
  | 13 | **5.1.5 Sensitive APIs** *(opt-in)* | If FamilyControls is used and `optionalChecks.familyControls` is on, a reviewer-notes justification exists |
74
76
  | 14 | **4.8 Sign in with Apple** *(advisory)* | If a third-party social login SDK (Google, Facebook, Auth0, …) is used, Sign in with Apple is offered too |
75
77
  | 15 | **3.1.1(a) External purchase link** *(advisory)* | If StoreKit External Purchase APIs or the entitlement are present, the 3.1.1(a) disclosure/reporting requirements are flagged |
@@ -78,11 +80,87 @@ machine-readable "last updated" date in the page DOM, so the section-number set
78
80
  | 18 | **2.3 Support / Privacy URL** *(advisory)* | fastlane metadata has a non-empty `support_url.txt` and `privacy_url.txt` across locales, with no placeholder URLs |
79
81
  | 19 | **5.1.1 Privacy manifest** *(advisory)* | If an analytics SDK (Firebase, Amplitude, Mixpanel, Sentry, Segment, Bugsnag, App Center, Datadog) is linked but `PrivacyInfo.xcprivacy` declares no collected data types or tracking domains, it is flagged |
80
82
  | 20 | **2.1 Placeholder content** *(advisory)* | No lorem ipsum / TODO / FIXME / `example.com` / "insert X here" / changeme in store metadata |
83
+ | 21 | **3.1.1 Third-party payment SDK** *(advisory)* | If a third-party payment SDK (Stripe, Braintree, PayPal, Square, Adyen, …) is linked, flag it: digital goods/services must use in-app purchase (allowed only for physical goods/services) |
84
+ | 22 | **1.2 UGC moderation** *(advisory)* | If user-generated-content signals (post/comment/upload, chat SDKs) are present but no report/block/moderation affordance is found, flag the missing 1.2 safety controls |
85
+ | 23 | **1.6 App Transport Security** *(advisory)* | `NSAllowsArbitraryLoads=true` in Info.plist disables ATS app-wide |
86
+ | 24 | **4.9 Apple Pay recurring** *(advisory)* | If the recurring Apple Pay API (`PKRecurringPaymentRequest`) is used: verify the renewal term, what's provided, charges, and cancel disclosure |
87
+ | 25 | **5.6.1 Custom review prompt** *(advisory)* | If a direct App Store write-review link/prompt exists but no system `requestReview` / `SKStoreReviewController` call |
88
+ | 26 | **2.3.1 Misleading marketing** *(advisory)* | Claims iOS apps can't deliver (virus/malware scanners, fake speed boosters) in store metadata |
89
+ | 27 | **2.3.8 "For Kids" wording** *(advisory)* | Terms implying a child audience in metadata, reserved for the Kids Category |
90
+ | 28 | **4.4.1 Keyboard full access** *(advisory)* | A keyboard extension (`com.apple.keyboard-service`) with `RequestsOpenAccess=true` |
91
+ | 29 | **5.1.3 Health + iCloud** *(advisory)* | If HealthKit and iCloud/CloudKit are both used: health data must not be stored in iCloud |
92
+ | 30 | **5.4 VPN** *(advisory)* | If NetworkExtension / `NEVPNManager` is used: org-account, on-screen data disclosure, and no data sale/sharing requirements |
93
+ | 31 | **2.1 Demo account** *(advisory)* | If a credential login (`SecureField` / a Login/SignIn view) is present but no demo account/credentials for App Review are found (fastlane `review_information` or `.reviewPrepNotes`) |
94
+ | 32 | **2.5.2 Executable code** *(advisory)* | A native hot-patch / remote-code framework (JSPatch, Rollout, DynamicCocoa) that downloads code which changes features. Allowed JS-bundle OTA (React Native CodePush) is not flagged |
95
+ | 33 | **2.5.4 Background modes** *(advisory)* | A mode declared in `UIBackgroundModes` (location, audio, voip, fetch, processing, bluetooth, remote-notification) with no matching API used in Swift |
96
+ | 34 | **3.1.5(a) Cryptocurrency** *(advisory)* | A crypto wallet / exchange / mining signal (WalletConnect, web3swift, TrustWalletCore, mining libraries) with its entity/licensing and no-on-device-mining requirements |
97
+ | 35 | **4.2.3 Web wrapper** *(advisory)* | A `WKWebView` in a project with very few Swift files — heuristic for a thin wrapper around a website. The most false-positive-prone of the batch, so WARN/verify |
98
+ | 36 | **4.2.7 Remote desktop** *(advisory)* | A remote-desktop / host-mirroring signal (VNC/RDP libraries); host-mirroring apps must only show the owner's host and be free or use IAP |
99
+ | 37 | **4.4.2 Safari extension** *(advisory)* | A Safari content-blocker / web extension (`com.apple.Safari.*` extension point); must use the APIs as intended and not hide analytics/ads |
100
+ | 38 | **5.1.1(v) Account deletion** *(advisory)* | Account creation (`signUp`/`createUser`/`createAccount`/…) detected but no in-app account-deletion path (`deleteAccount`/`closeAccount`/…). This is the real 5.1.1(v) Account Sign-In rule |
101
+ | 39 | **5.1.4 Kids** *(advisory)* | Metadata targets a child audience **and** a third-party ads/analytics SDK is linked; Kids Category apps may not include third-party ads/analytics and need a parental gate |
102
+ | 40 | **5.3.4 Gambling** *(advisory)* | Real-money gaming language in metadata (casino, sportsbook, real money, wager); real-money gambling needs licensing, geo-restriction, and must be free on the store |
103
+ | 41 | **5.5 MDM** *(advisory)* | A Mobile Device Management signal (`DeviceManagement`, managed-app-config, `com.apple.mdm`); MDM apps need a commercial enterprise/education entity and purpose-limited data use |
81
104
 
82
105
  Vectors 8–10 only run when in-app-purchase signals are detected (StoreKit / RevenueCat import,
83
- or a paywall view). Otherwise the scanner emits a single PASS and skips them. Vectors 16–20 are
106
+ or a paywall view). Otherwise the scanner emits a single PASS and skips them. Vectors 16–41 are
84
107
  signal-gated advisory WARNs: each emits nothing unless its triggering signal is present.
85
108
 
109
+ **Scope by app type.** The metadata, privacy-manifest, screenshots, and export-compliance checks
110
+ apply to any iOS app regardless of how it is built. The code-level checks grep the app's Swift
111
+ source (`*.swift`, plus `*.m`/`*.h` and `*.entitlements` where relevant), so they are most accurate
112
+ for native Swift / SwiftUI. On React Native (JavaScript) or Flutter (Dart) apps that logic is not in
113
+ Swift, so the code-level checks under-detect rather than misfire. iOS only.
114
+
115
+ ---
116
+
117
+ ## Phase 3: Pierre explains every finding
118
+
119
+ After Phases 0–2, **Pierre** (the French critic reviewer persona) explains **every FAIL and WARN**
120
+ the pipeline emitted — no random sampling, no new hunts.
121
+
122
+ **Sources to explain (all lines, in order):**
123
+
124
+ 1. Phase 0 — any `WARN: guideline-drift` (or degraded drift-check) line.
125
+ 2. Phase 1 — every `FAIL:` and `WARN:` from `scan.sh` (including multi-line detail blocks
126
+ indented under a parent line — explain the parent once, cite the paths in the explanation).
127
+ 3. Phase 2 — every `fastlane precheck` violation, if Phase 2 ran (treat as FAIL-level).
128
+
129
+ **Per finding:** repeat the line verbatim, then **2–3 sentences** from Pierre: why Apple cares
130
+ about that guideline, what the scan found, what to fix or verify. Write explanations in the user's
131
+ conversation language; keep the Phase 5 trilingual verdict block separate (bold label + blockquote
132
+ per language, `---` between — see SKILL.md Output contract).
133
+
134
+ **Trilingual verdict block:** `### Pierre` heading; each language on its own — **bold label**, blank
135
+ line, `> *italic one-liner*`; horizontal rules between languages; never FR/EN/user-lang on one line.
136
+
137
+ **If zero FAIL and zero WARN:** Pierre gives a brief all-clear (2–3 sentences). Do not invent issues.
138
+
139
+ **What Phase 3 is not:** it does not add FAIL/WARN lines to the verdict count, does not paraphrase
140
+ the machine lines (those stay verbatim in Phase 5), and does not re-run detection. The scanner
141
+ finds; Pierre explains.
142
+
143
+ ---
144
+
145
+ ## Phase 4: Pierre deep review (28 semantic checks)
146
+
147
+ After Phase 3, Pierre runs the **Review Simulator**: 28 evidence-based checks the static scanner
148
+ cannot fully judge — **22 Tier A** (high-confidence) plus **6 Tier B v1** heuristic checks (items
149
+ **4, 5, 7, 10, 15, 28** in the checklist: 2.1 review notes, 2.2, 2.3.4, 2.3.9, 4.5.1–4.5.3,
150
+ 5.6.4–5.6.7). Full procedure, output format, and per-check steps are in
151
+ [`pierre-deep-review.md`](pierre-deep-review.md).
152
+
153
+ **Verdict impact:** none. Phase 4 emits `REVIEW-PASS:` or `REVIEW-FINDING: <guideline> WARN — …`.
154
+ These are advisory; FAIL/WARN counts and GREEN/YELLOW/RED come only from Phases 0–2.
155
+
156
+ **Coverage:** deepens scan hits where applicable (e.g. 5.1.1 purpose strings → 5.1.1(ii) quality;
157
+ §22 UGC keyword → 1.2.1 moderation UI) and adds net-new semantic areas (2.3.5 screenshots,
158
+ 5.1.1(i) privacy policy fetch, 2.3.11–13 locale consistency, etc.). Guideline numbers touched
159
+ are tracked in `guidelines-baseline.json` → `covered_by_pierre_deep_review`.
160
+
161
+ **Presentation (Phase 5):** after Phase 3 commentary, show Phase 4 summary (N of 28 findings) and
162
+ every `REVIEW-FINDING` with Pierre's 2–3 sentence explanation. Tier B v1 findings are heuristic.
163
+
86
164
  ---
87
165
 
88
166
  ## Auto-detection rules
@@ -107,12 +185,12 @@ match, so a vendored SwiftPM checkout is never mistaken for the app.
107
185
 
108
186
  | State | Rule |
109
187
  |-------|------|
110
- | **GREEN** | 0 FAIL and ≤2 WARN → write `.precheck-pass` (valid 60 min) |
111
- | **YELLOW** | 0 FAIL and ≥3 WARN → no token; require explicit user confirmation to proceed |
188
+ | **GREEN** | 0 FAIL and ≤4 WARN → write `.precheck-pass` (valid 60 min) |
189
+ | **YELLOW** | 0 FAIL and ≥5 WARN → no token; require explicit user confirmation to proceed |
112
190
  | **RED** | ≥1 FAIL → no token; submission blocked until fixed |
113
191
 
114
192
  The guideline-drift WARN from Phase 0 counts toward the same WARN threshold; on its own it never
115
- blocks, but it can be the third WARN that tips GREEN into YELLOW.
193
+ blocks, but it can be the fifth WARN that tips GREEN into YELLOW.
116
194
 
117
195
  ---
118
196