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.
- package/CHANGELOG.md +151 -0
- package/README.md +238 -55
- package/package.json +2 -2
- package/skills/appstore-precheck/SKILL.md +201 -59
- package/skills/appstore-precheck/evals/README.md +3 -2
- package/skills/appstore-precheck/evals/evals.json +6 -3
- package/skills/appstore-precheck/guidelines-baseline.json +19 -7
- package/skills/appstore-precheck/references/methodology.md +85 -7
- package/skills/appstore-precheck/references/pierre-deep-review.md +272 -0
- package/skills/appstore-precheck/scripts/scan.sh +335 -13
- package/skills/appstore-precheck/scripts/verdict.sh +3 -3
|
@@ -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
|
|
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
|
|
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,
|
|
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, ≤
|
|
47
|
-
| **YELLOW** | No FAIL but
|
|
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
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
|
75
|
-
(5.1.1
|
|
76
|
-
limits (2.3.1), localized parity (2.3.7), screenshots (2.3.3), trial &
|
|
77
|
-
(3.1.2), Restore/Terms/Privacy links (3.1.2), private API (2.5.1), minimum
|
|
78
|
-
Sign in with Apple parity (4.8), external purchase links (3.1.1(a)), an
|
|
79
|
-
FamilyControls justification (5.1.5), tracking/IDFA SDK without an ATT prompt
|
|
80
|
-
export-compliance key (`ITSAppUsesNonExemptEncryption`), support/privacy URLs in
|
|
81
|
-
metadata (2.3), analytics SDK vs PrivacyInfo data-types (5.1.1),
|
|
82
|
-
copy (2.1)
|
|
83
|
-
|
|
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:
|
|
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
|
-
|
|
119
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
|
|
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
|
|
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–
|
|
148
|
-
narrative; verdict.sh just pins the threshold arithmetic.
|
|
149
|
-
|
|
150
|
-
1. Gather Phase 0–
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
-
|
|
181
|
-
-
|
|
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:
|
|
10
|
-
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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-
|
|
5
|
-
"apple_last_updated_seen": "
|
|
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
|
-
"
|
|
9
|
-
"2.
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
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 (
|
|
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
|
|
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.
|
|
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–
|
|
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 ≤
|
|
111
|
-
| **YELLOW** | 0 FAIL and ≥
|
|
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
|
|
193
|
+
blocks, but it can be the fifth WARN that tips GREEN into YELLOW.
|
|
116
194
|
|
|
117
195
|
---
|
|
118
196
|
|