appstore-precheck 1.7.0 → 1.9.0
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 +76 -0
- package/README.md +5 -4
- package/package.json +1 -1
- package/skills/appstore-precheck/SKILL.md +17 -8
- package/skills/appstore-precheck/guidelines-baseline.json +9 -8
- package/skills/appstore-precheck/guidelines-fingerprints.json +233 -0
- package/skills/appstore-precheck/references/methodology.md +29 -2
- package/skills/appstore-precheck/references/pierre-deep-review.md +5 -2
- package/skills/appstore-precheck/references/screenshot-vision-review.md +69 -0
- package/skills/appstore-precheck/scripts/findings.sh +2 -1
- package/skills/appstore-precheck/scripts/image-dims.sh +76 -0
- package/skills/appstore-precheck/scripts/scan.sh +38 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,82 @@ All notable changes to this project are documented here. Versioning follows
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.9.0] - 2026-07-02
|
|
9
|
+
|
|
10
|
+
Smarter analysis (roadmap #2a): **screenshot vision**, in two layers.
|
|
11
|
+
|
|
12
|
+
**Layer 1 (deterministic, offline, zero-dependency).** The static scanner now
|
|
13
|
+
reads each in-repo screenshot's file format and, for PNGs, its pixel dimensions —
|
|
14
|
+
a new `screenshot-dimensions` check (catalog vector 42) under guideline 2.3.3. It
|
|
15
|
+
WARNs on a file whose content does not match its extension, a truncated PNG, or a
|
|
16
|
+
PNG whose dimensions match no known App Store screenshot size (either orientation).
|
|
17
|
+
WARN-only: it never forces a RED verdict, and the offline scan stays
|
|
18
|
+
byte-identical on inputs without real screenshots. Parsing uses only `bash`+`od`+
|
|
19
|
+
`awk` — no new runtime dependency.
|
|
20
|
+
|
|
21
|
+
**Layer 2 (agent-mode, non-blocking).** A dedicated structured screenshot vision
|
|
22
|
+
review that uses the host model's vision capability to check screenshots for
|
|
23
|
+
placeholder / dev-debug / empty-state content, text overflow / truncation, wrong
|
|
24
|
+
device frame / aspect, misleading marketing (2.3.3 "show the app in use" / 2.3.10),
|
|
25
|
+
and metadata mismatches. Like Pierre deep-review it is advisory (`REVIEW-FINDING`)
|
|
26
|
+
and never changes the GREEN/YELLOW/RED verdict. It runs only in agent-skill mode
|
|
27
|
+
(host vision model), never in the CLI / npx / GitHub-Action path.
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **`skills/appstore-precheck/scripts/image-dims.sh`** — zero-dependency PNG
|
|
31
|
+
magic-byte + IHDR pixel-dimension parser (pure `bash`+`od`+`awk`) and the Apple
|
|
32
|
+
accepted-screenshot-size table (verified against Apple's screenshot
|
|
33
|
+
specifications page).
|
|
34
|
+
- **`scan.sh` §7b** — screenshot format + PNG-dimension validation (rule
|
|
35
|
+
`screenshot-dimensions`, catalog vector 42), WARN-only.
|
|
36
|
+
- **`skills/appstore-precheck/references/screenshot-vision-review.md`** — agent-mode
|
|
37
|
+
structured screenshot vision checklist (5 checks), non-blocking, wired into
|
|
38
|
+
Pierre deep-review check #8 and SKILL.md Phase 4.
|
|
39
|
+
- Tests: `tests/test-image-dims.sh` (parser unit tests) and
|
|
40
|
+
`tests/fixtures/screenshots-app/` (end-to-end fixture); `tests/make-png.py`
|
|
41
|
+
fixture generator.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
- Advertised deterministic rejection-vector count **41 → 42** (the new
|
|
45
|
+
screenshot-dimensions check) across README, SKILL.md, and methodology.
|
|
46
|
+
- Existing screenshot test fixtures: replaced 1-byte placeholders with real
|
|
47
|
+
accepted-size PNGs (scan output byte-identical).
|
|
48
|
+
|
|
49
|
+
### Notes
|
|
50
|
+
- JPEG dimensions are not parsed in this release; JPEGs are format-validated
|
|
51
|
+
(magic bytes) and counted only.
|
|
52
|
+
|
|
53
|
+
## [1.8.0] - 2026-07-02
|
|
54
|
+
|
|
55
|
+
Smarter analysis (roadmap #2c): a deterministic **semantic guideline-drift**
|
|
56
|
+
detector. Beyond the existing section-number check, the tool now fingerprints the
|
|
57
|
+
*text* of every guideline section our checks depend on and reports when Apple
|
|
58
|
+
rewrites a section's meaning (even if its number is unchanged), naming the
|
|
59
|
+
affected check(s). It is a maintainer/CI tool — network-using (`curl`), never
|
|
60
|
+
sourced by `scan.sh`, never in the offline user scan path. Zero new runtime
|
|
61
|
+
dependency for the distributed scanner; default scan output stays byte-identical.
|
|
62
|
+
|
|
63
|
+
### Added
|
|
64
|
+
- **`scripts/guideline-drift.sh`** (maintainer/CI): fetches the full live App
|
|
65
|
+
Store Review Guidelines and reports section-number drift AND text drift of
|
|
66
|
+
covered sections, deriving the affected check(s) from `scan.sh`'s `set_rule`
|
|
67
|
+
blocks. Non-blocking (WARN lines, always exits 0); the parse/diff logic is
|
|
68
|
+
unit-tested offline via `--html`. `--reconcile` regenerates the fingerprint
|
|
69
|
+
baseline (a deliberate human step) and skips/ WARNs on sections Apple has
|
|
70
|
+
removed rather than writing empty placeholders.
|
|
71
|
+
- **`skills/appstore-precheck/guidelines-fingerprints.json`** — human-reconciled
|
|
72
|
+
per-covered-section text fingerprints (57 sections).
|
|
73
|
+
- A non-blocking scheduled + manual GitHub workflow (`guideline-drift.yml`) that
|
|
74
|
+
surfaces drift; it never gates a PR.
|
|
75
|
+
|
|
76
|
+
### Changed
|
|
77
|
+
- **Baseline reconciled to Apple's current guidelines** (the drift tool's first
|
|
78
|
+
run caught real drift): Apple consolidated section 5.6 to end at **5.6.4** —
|
|
79
|
+
the removed **5.6.5–5.6.7** were dropped from the baseline and Pierre
|
|
80
|
+
deep-review check 28 (rating/review-manipulation) was remapped to the surviving
|
|
81
|
+
**5.6.1 / 5.6.3**. Six sub-sections a prior truncated fetch had missed
|
|
82
|
+
(2.5.7, 2.5.10, 4.2.4, 4.2.5, 4.4.3, 4.6) were added to `all_sections`.
|
|
83
|
+
|
|
8
84
|
## [1.7.0] - 2026-07-02
|
|
9
85
|
|
|
10
86
|
Smarter analysis (roadmap #2b): the scanner now resolves the iOS source directory
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ each, divided by horizontal rules), not one compressed sentence. The breakdown b
|
|
|
44
44
|
|
|
45
45
|
## What it checks
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
42 rejection vectors across code, fastlane metadata, screenshots, `PrivacyInfo.xcprivacy`, and the paywall:
|
|
48
48
|
|
|
49
49
|
| Guideline | Check |
|
|
50
50
|
|-----------|-------|
|
|
@@ -56,6 +56,7 @@ each, divided by horizontal rules), not one compressed sentence. The breakdown b
|
|
|
56
56
|
| **2.3.1** | Metadata length limits (name, subtitle, keywords, promo, description) |
|
|
57
57
|
| **2.3.1** | Misleading marketing claims (iOS virus / malware scanners, fake speed boosters) in metadata |
|
|
58
58
|
| **2.3.3** | At least one screenshot per locale |
|
|
59
|
+
| **2.3.3** | Screenshot format + PNG dimensions match a known App Store screenshot size *(advisory, WARN-only)* |
|
|
59
60
|
| **2.3.7** | Localized metadata parity across every locale |
|
|
60
61
|
| **2.3.8** | "For Kids" / "For Children" wording outside the Kids Category |
|
|
61
62
|
| **2.3.10** | No other-platform / competitor names in metadata |
|
|
@@ -132,7 +133,7 @@ signals with a higher false-positive rate; Pierre prefers `not applicable` when
|
|
|
132
133
|
| **5.2.1–5.2.3** | Obvious third-party trademark or brand misuse in metadata or UI copy |
|
|
133
134
|
| **5.3.1–5.3.3** | Contest / sweepstakes copy includes official rules and eligibility |
|
|
134
135
|
| **5.6.2–5.6.3** | Developer identity consistent (app name, support URL content, domains) |
|
|
135
|
-
| **5.6.
|
|
136
|
+
| **5.6.1 / 5.6.3** † | Rating / review manipulation dark patterns (withhold features until 5 stars, write-review links without `requestReview`) |
|
|
136
137
|
|
|
137
138
|
Pierre runs **all 28 every time** and reports each as `REVIEW-PASS:` or `REVIEW-FINDING:`. When the
|
|
138
139
|
static scan already flagged a guideline, the deep check adds semantic context the scanner could not see.
|
|
@@ -146,7 +147,7 @@ how the app is built:
|
|
|
146
147
|
|
|
147
148
|
| App type | Coverage |
|
|
148
149
|
|----------|----------|
|
|
149
|
-
| 🟢 **Native Swift / SwiftUI** | **Full.** All
|
|
150
|
+
| 🟢 **Native Swift / SwiftUI** | **Full.** All 42 vectors apply. |
|
|
150
151
|
| 🟡 **React Native / Flutter** | Metadata, privacy manifest, screenshots, and export compliance apply in full. The Swift-source checks (ATT, paywall links, private API, SDK detection, navigation) **under-detect rather than misfire**: that logic lives in JS/Dart, so they stay quiet instead of blocking. |
|
|
151
152
|
|
|
152
153
|
## Quick start
|
|
@@ -268,7 +269,7 @@ a RED verdict. No App Store Connect credentials are needed; the action runs the
|
|
|
268
269
|
| Phase | Step |
|
|
269
270
|
|-------|------|
|
|
270
271
|
| **0** | **Guideline drift**: diff the live App Store Review Guidelines against a tracked baseline. Never blocks. |
|
|
271
|
-
| **1** | **Static scan**: `scan.sh` over the
|
|
272
|
+
| **1** | **Static scan**: `scan.sh` over the 42 vectors above. |
|
|
272
273
|
| **2** | **`fastlane precheck`**: Apple's own metadata rule engine. |
|
|
273
274
|
| **3** | **Pierre commentary**: explains **every** FAIL and WARN from Phases 0–2 in 2–3 sentences each. |
|
|
274
275
|
| **4** | **Pierre deep review**: 28 semantic checks (22 Tier A + 6 Tier B v1 heuristic). Advisory only. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-precheck",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Read-only iOS App Store pre-submission check, packaged as a portable Agent Skill with native Claude Code, Cursor, and Codex plugins, plus an npx CLI.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Berkay Turk (https://github.com/berkayturk)",
|
|
@@ -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 42 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.
|
|
7
|
+
version: 1.9.0
|
|
8
8
|
allowed-tools: Bash Read Grep Glob WebFetch
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -104,13 +104,19 @@ two-pass technique; the exact prompts and the reconciliation procedure are in
|
|
|
104
104
|
[`references/methodology.md`](references/methodology.md#phase-0-guideline-drift-check). The
|
|
105
105
|
baseline is **never auto-updated**. Reconciliation is a deliberate human step.
|
|
106
106
|
|
|
107
|
+
The deterministic, full-page drift detector is `scripts/guideline-drift.sh` (maintainer/CI;
|
|
108
|
+
curl-based, so it also covers the 5.5–5.6.x tail that `WebFetch` truncates). It reports
|
|
109
|
+
section-number drift AND text (semantic) drift of covered sections, naming the affected
|
|
110
|
+
check(s). It is non-blocking and never runs in the offline user scan; the fingerprint baseline
|
|
111
|
+
(`guidelines-fingerprints.json`) is human-reconciled via `guideline-drift.sh --reconcile`.
|
|
112
|
+
|
|
107
113
|
### Phase 1: Static scan
|
|
108
114
|
|
|
109
115
|
```bash
|
|
110
116
|
bash skills/appstore-precheck/scripts/scan.sh
|
|
111
117
|
```
|
|
112
118
|
|
|
113
|
-
Emits `FAIL:` / `WARN:` / `PASS:` lines covering
|
|
119
|
+
Emits `FAIL:` / `WARN:` / `PASS:` lines covering 42 rejection vectors: Privacy Manifest /
|
|
114
120
|
Required Reason API parity (5.1.1), purpose strings (5.1.1), ATT (5.1.2), other-platform mentions
|
|
115
121
|
(2.3.10), metadata limits (2.3.1), localized parity (2.3.7), screenshots (2.3.3), trial &
|
|
116
122
|
auto-renew disclosures (3.1.2), Restore/Terms/Privacy links (3.1.2), private API (2.5.1), minimum
|
|
@@ -127,9 +133,10 @@ NetworkExtension usage (5.4), a demo account for a login-gated app (2.1), execut
|
|
|
127
133
|
/ native hot-patching (2.5.2), unused background modes (2.5.4), cryptocurrency wallet/mining
|
|
128
134
|
(3.1.5(a)), thin WKWebView wrappers (4.2.3), remote-desktop apps (4.2.7), Safari extensions
|
|
129
135
|
(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),
|
|
131
|
-
IAP checks (8–10) are skipped automatically
|
|
132
|
-
signal-gated advisory checks (16–
|
|
136
|
+
third-party ads/analytics (5.1.4), real-money gambling copy (5.3.4), MDM signals (5.5), and
|
|
137
|
+
screenshot format/dimension mismatches (2.3.3). The IAP checks (8–10) are skipped automatically
|
|
138
|
+
when no in-app-purchase signals are present, and the signal-gated advisory checks (16–42) stay
|
|
139
|
+
silent unless their triggering signal is found. The
|
|
133
140
|
full check table is in
|
|
134
141
|
[`references/methodology.md`](references/methodology.md#phase-1-rejection-vectors).
|
|
135
142
|
|
|
@@ -220,7 +227,9 @@ After Phase 3, Pierre runs the **Review Simulator**: 28 read-only, evidence-base
|
|
|
220
227
|
static scanner cannot fully judge (**22 Tier A** + **6 Tier B v1** heuristic — marked † below).
|
|
221
228
|
The full checklist, per-check procedure, and output format live in
|
|
222
229
|
[`references/pierre-deep-review.md`](references/pierre-deep-review.md) — read it before starting
|
|
223
|
-
Phase 4.
|
|
230
|
+
Phase 4. When screenshots are present, also run the structured screenshot vision review in
|
|
231
|
+
[`references/screenshot-vision-review.md`](references/screenshot-vision-review.md) (non-blocking;
|
|
232
|
+
host vision model; never changes the verdict).
|
|
224
233
|
|
|
225
234
|
**What Pierre does:** read Swift, metadata, entitlements, xcstrings, paywall views, review notes,
|
|
226
235
|
screenshot/preview assets; fetch privacy and support URLs; cross-check claims vs code, policy vs
|
|
@@ -265,7 +274,7 @@ SDK usage, screenshots vs features, and paywall disclosure quality.
|
|
|
265
274
|
| 25 | **5.2.1–5.2.3** | Obvious trademark/brand misuse in metadata or UI? |
|
|
266
275
|
| 26 | **5.3.1–5.3.3** | Contest/sweepstakes copy includes official rules? |
|
|
267
276
|
| 27 | **5.6.2–5.6.3** | Developer identity consistent (support URL, domains, app name)? |
|
|
268
|
-
| 28 † | **5.6.
|
|
277
|
+
| 28 † | **5.6.1 / 5.6.3** | Rating manipulation dark patterns beyond scan §25? |
|
|
269
278
|
|
|
270
279
|
Use this prompt after Phase 3:
|
|
271
280
|
|
|
@@ -1,8 +1,8 @@
|
|
|
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-
|
|
5
|
-
"apple_last_updated_seen": "Last Updated: June 8, 2026 (now exposed at the page footer); drift signal remains the set of section numbers",
|
|
4
|
+
"reconciled_on": "2026-07-02",
|
|
5
|
+
"apple_last_updated_seen": "Last Updated: June 8, 2026 (now exposed at the page footer); drift signal remains the set of section numbers. 2026-07-02: confirmed 5.6 now ends at 5.6.4 (5.6.1 App Store Reviews, 5.6.2 Developer Identity, 5.6.3 Discovery Fraud, 5.6.4 App Quality) — 5.6.5/5.6.6/5.6.7 (rating/review manipulation) were removed/consolidated; that content now lives under 5.6.1 and 5.6.3.",
|
|
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
8
|
"1.2", "1.5", "1.6",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"5.1.1", "5.1.2", "5.1.3", "5.1.4", "5.4",
|
|
22
22
|
"5.2.1", "5.2.2", "5.2.3",
|
|
23
23
|
"5.3.1", "5.3.2", "5.3.3",
|
|
24
|
-
"5.6.
|
|
24
|
+
"5.6.1", "5.6.2", "5.6.3", "5.6.4"
|
|
25
25
|
],
|
|
26
26
|
"all_sections": [
|
|
27
27
|
"1.1", "1.1.1", "1.1.2", "1.1.3", "1.1.4", "1.1.5", "1.1.6", "1.1.7",
|
|
@@ -30,18 +30,19 @@
|
|
|
30
30
|
"2.1", "2.2", "2.3", "2.3.1", "2.3.2", "2.3.3", "2.3.4", "2.3.5", "2.3.6",
|
|
31
31
|
"2.3.7", "2.3.8", "2.3.9", "2.3.10", "2.3.11", "2.3.12", "2.3.13",
|
|
32
32
|
"2.4", "2.4.1", "2.4.2", "2.4.3", "2.4.4", "2.4.5",
|
|
33
|
-
"2.5", "2.5.1", "2.5.2", "2.5.3", "2.5.4", "2.5.5", "2.5.6", "2.5.8", "2.5.9",
|
|
34
|
-
"2.5.11", "2.5.12", "2.5.13", "2.5.14", "2.5.15", "2.5.16", "2.5.17", "2.5.18",
|
|
33
|
+
"2.5", "2.5.1", "2.5.2", "2.5.3", "2.5.4", "2.5.5", "2.5.6", "2.5.7", "2.5.8", "2.5.9",
|
|
34
|
+
"2.5.10", "2.5.11", "2.5.12", "2.5.13", "2.5.14", "2.5.15", "2.5.16", "2.5.17", "2.5.18",
|
|
35
35
|
"3.1", "3.1.1", "3.1.2", "3.1.3", "3.1.4", "3.1.5",
|
|
36
36
|
"3.2", "3.2.1", "3.2.2",
|
|
37
|
-
"4.1", "4.2", "4.2.1", "4.2.2", "4.2.3", "4.2.6", "4.2.7",
|
|
38
|
-
"4.3", "4.4", "4.4.1", "4.4.2",
|
|
37
|
+
"4.1", "4.2", "4.2.1", "4.2.2", "4.2.3", "4.2.4", "4.2.5", "4.2.6", "4.2.7",
|
|
38
|
+
"4.3", "4.4", "4.4.1", "4.4.2", "4.4.3",
|
|
39
39
|
"4.5", "4.5.1", "4.5.2", "4.5.3", "4.5.4", "4.5.5", "4.5.6",
|
|
40
|
+
"4.6",
|
|
40
41
|
"4.7", "4.7.1", "4.7.2", "4.7.3", "4.7.4", "4.7.5",
|
|
41
42
|
"4.8", "4.9", "4.10",
|
|
42
43
|
"5.1", "5.1.1", "5.1.2", "5.1.3", "5.1.4", "5.1.5",
|
|
43
44
|
"5.2", "5.2.1", "5.2.2", "5.2.3", "5.2.4", "5.2.5",
|
|
44
45
|
"5.3", "5.3.1", "5.3.2", "5.3.3", "5.3.4",
|
|
45
|
-
"5.4", "5.5", "5.6", "5.6.1", "5.6.2", "5.6.3", "5.6.4"
|
|
46
|
+
"5.4", "5.5", "5.6", "5.6.1", "5.6.2", "5.6.3", "5.6.4"
|
|
46
47
|
]
|
|
47
48
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
{
|
|
2
|
+
"sections": {
|
|
3
|
+
"1.2": {
|
|
4
|
+
"fingerprint": "ce18f5618681eafe5b4010d3162e0cdfe026aed127295d1762a1fe0c23f68026",
|
|
5
|
+
"snapshot": "1.2 user-generated content apps with user-generated content present particular challenges, ranging from intellectual property infringement to anonymous bullying"
|
|
6
|
+
},
|
|
7
|
+
"1.2.1": {
|
|
8
|
+
"fingerprint": "ffadee3ecbf75b6cf5b293fa2d9f59b2d68c1ba61e6e4c0cb33852324081e84b",
|
|
9
|
+
"snapshot": "1.2.1 creator content apps which feature content from a specific community of users called “creators” are a great opportunity if properly moderated. these apps "
|
|
10
|
+
},
|
|
11
|
+
"1.4.1": {
|
|
12
|
+
"fingerprint": "30045ffe09e8c2fe50923eb5f718ddec394b048bef67c6b7959abadb178c8406",
|
|
13
|
+
"snapshot": "1.4.1 medical apps that could provide inaccurate data or information, or that could be used for diagnosing or treating patients may be reviewed with greater scr"
|
|
14
|
+
},
|
|
15
|
+
"1.5": {
|
|
16
|
+
"fingerprint": "5553347bd97263487f23eae8b7577d036a875ba0934bbde44ac4216218fe85fa",
|
|
17
|
+
"snapshot": "1.5 developer information people need to know how to reach you with questions and support issues. make sure your app and its support url include an easy way to "
|
|
18
|
+
},
|
|
19
|
+
"1.6": {
|
|
20
|
+
"fingerprint": "0620b0807840c795dfcdf546b4a9e648564a192b48a995e3b507f60f26ee690f",
|
|
21
|
+
"snapshot": "1.6 data security apps should implement appropriate security measures to ensure proper handling of user information collected pursuant to the apple developer pr"
|
|
22
|
+
},
|
|
23
|
+
"2.1": {
|
|
24
|
+
"fingerprint": "f5cd98793df128268b4fe5b3b31b006776b0678f6d79d0b88d0d9b3986ea5fd4",
|
|
25
|
+
"snapshot": "2.1 app completeness (a) submissions to app review, including apps you make available for pre-order, should be final versions with all necessary metadata and fu"
|
|
26
|
+
},
|
|
27
|
+
"2.2": {
|
|
28
|
+
"fingerprint": "5df8bc50bf6ef5ce31be95dc03f8bc4ece4cd107055a5124add9336b49471a0d",
|
|
29
|
+
"snapshot": "2.2 beta testing demos, betas, and trial versions of your app don’t belong on the app store – use testflight instead. any app submitted for beta distribution vi"
|
|
30
|
+
},
|
|
31
|
+
"2.3": {
|
|
32
|
+
"fingerprint": "f7b9c31377de7f721ecb9f203a2f107d3600c464ce0633e0d79dd5318250d4df",
|
|
33
|
+
"snapshot": "2.3 accurate metadata customers should know what they’re getting when they download or buy your app, so make sure all your app metadata, including privacy infor"
|
|
34
|
+
},
|
|
35
|
+
"2.3.1": {
|
|
36
|
+
"fingerprint": "4745cb96a6e7665103163daa8a6f07dfc08008288116132a1f3c668d621c61b3",
|
|
37
|
+
"snapshot": "2.3.1 (a) don’t include any hidden, dormant, or undocumented features in your app; your app’s functionality should be clear to end users and app review. all new"
|
|
38
|
+
},
|
|
39
|
+
"2.3.10": {
|
|
40
|
+
"fingerprint": "a770ee4d270784e3bfa0784aab62563acccae50f02e4d85c4bb65bda14a4133d",
|
|
41
|
+
"snapshot": "2.3.10 make sure your app is focused on the experience of the apple platforms it supports, and don’t include names, icons, or imagery of other mobile platforms "
|
|
42
|
+
},
|
|
43
|
+
"2.3.11": {
|
|
44
|
+
"fingerprint": "393e4075d4b403ff201771e040768963299b7df6f01693317ea87c208276b63a",
|
|
45
|
+
"snapshot": "2.3.11 apps you submit for pre-order on the app store must be complete and deliverable as submitted. ensure that the app you ultimately release is not materiall"
|
|
46
|
+
},
|
|
47
|
+
"2.3.12": {
|
|
48
|
+
"fingerprint": "c4fe778434d20e2157b2093ceab8e780d6a98f85c05c57823507992f58de54a3",
|
|
49
|
+
"snapshot": "2.3.12 apps must clearly describe new features and product changes in their “what’s new” text. simple bug fixes, security updates, and performance improvements "
|
|
50
|
+
},
|
|
51
|
+
"2.3.13": {
|
|
52
|
+
"fingerprint": "40d66f89efc57625525782e40afc24aabd8a4cbe2a745eca725a7a2d6324f742",
|
|
53
|
+
"snapshot": "2.3.13 in-app events are timely events that happen within your app. to feature your event on the app store, it must fall within an event type provided in app st"
|
|
54
|
+
},
|
|
55
|
+
"2.3.2": {
|
|
56
|
+
"fingerprint": "e070f3780bbbbf41f191f71ef396904f9d42d531ab782f6f35b4e0c9b95dbf0a",
|
|
57
|
+
"snapshot": "2.3.2 if your app includes in-app purchases, make sure your app description, screenshots, and previews clearly indicate whether any featured items, levels, subs"
|
|
58
|
+
},
|
|
59
|
+
"2.3.3": {
|
|
60
|
+
"fingerprint": "599fcbd06e68d93bab64101dc412f5c6ffb13eb76e8490c7e93d95d95e4dafae",
|
|
61
|
+
"snapshot": "2.3.3 screenshots should show the app in use, and not merely the title art, login page, or splash screen. they may also include text and image overlays (e.g. to"
|
|
62
|
+
},
|
|
63
|
+
"2.3.4": {
|
|
64
|
+
"fingerprint": "581fe91e7677d96de480c0edca7761023e5606f5d6cd39ae42a06d29081ab393",
|
|
65
|
+
"snapshot": "2.3.4 previews are a great way for customers to see what your app looks like and what it does. to ensure people understand what they’ll be getting with your app"
|
|
66
|
+
},
|
|
67
|
+
"2.3.5": {
|
|
68
|
+
"fingerprint": "b4fb364fef12344219399d477daa73bca645261f6feee1a67dc88916a417c5c5",
|
|
69
|
+
"snapshot": "2.3.5 select the most appropriate category for your app, and check out the app store category definitions if you need help. if you’re way off base, we may chang"
|
|
70
|
+
},
|
|
71
|
+
"2.3.6": {
|
|
72
|
+
"fingerprint": "a6804ee336bd78da1ddc44613ea6fe131b5dcf4cbe482b8cb696ef3720435c3d",
|
|
73
|
+
"snapshot": "2.3.6 answer the age rating questions in app store connect honestly so that your app aligns properly with parental controls. if your app is mis-rated, customers"
|
|
74
|
+
},
|
|
75
|
+
"2.3.7": {
|
|
76
|
+
"fingerprint": "3493119147fdb0b4735ce6760d2a0186a4941531ff209e4198879f0a1c201a2c",
|
|
77
|
+
"snapshot": "2.3.7 choose a unique app name, assign keywords that accurately describe your app, and don’t try to pack any of your metadata with trademarked terms, popular ap"
|
|
78
|
+
},
|
|
79
|
+
"2.3.8": {
|
|
80
|
+
"fingerprint": "9c1bc1fbee57f292a14e4b6cfa465021ad0f7acb79691337b2a0f5c2e41392bb",
|
|
81
|
+
"snapshot": "2.3.8 metadata should be appropriate for all audiences, so make sure your app and in-app purchase icons, screenshots, and previews adhere to a 4+ age rating eve"
|
|
82
|
+
},
|
|
83
|
+
"2.3.9": {
|
|
84
|
+
"fingerprint": "5161fcd6aaa9dfd7b3cfff5f32912448dc937083d095fe9049cef0f80a09fa32",
|
|
85
|
+
"snapshot": "2.3.9 you are responsible for securing the rights to use all materials in your app icons, screenshots, and previews, and you should display fictional account in"
|
|
86
|
+
},
|
|
87
|
+
"2.5.1": {
|
|
88
|
+
"fingerprint": "7b055c4d2438faeaee3b1b77f5ce9b9cec38e713466ea0c85d31117d6484ba8b",
|
|
89
|
+
"snapshot": "2.5.1 apps may only use public apis and must run on the currently shipping os. learn more about public apis . keep your apps up-to-date and make sure you phase "
|
|
90
|
+
},
|
|
91
|
+
"2.5.2": {
|
|
92
|
+
"fingerprint": "353ebb9358966707a33b8b1b29575e44c1a2db44e8a42c013bcd48ca296fec44",
|
|
93
|
+
"snapshot": "2.5.2 apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or e"
|
|
94
|
+
},
|
|
95
|
+
"2.5.4": {
|
|
96
|
+
"fingerprint": "9695ae52e1472dc7f5d977a88287c590b468401dd6a1fb30ddbe252029c59c81",
|
|
97
|
+
"snapshot": "2.5.4 multitasking apps may only use background services for their intended purposes: voip, audio playback, location, task completion, local notifications, etc."
|
|
98
|
+
},
|
|
99
|
+
"3.1.1": {
|
|
100
|
+
"fingerprint": "16d14be80e2243e9456be40bbc6c8dfb2cb6e90c6e98e5c36ba6e7f7147a61e8",
|
|
101
|
+
"snapshot": "3.1.1 in-app purchase: if you want to unlock features or functionality within your app, (by way of example: subscriptions, in-game currencies, game levels, acce"
|
|
102
|
+
},
|
|
103
|
+
"3.1.2": {
|
|
104
|
+
"fingerprint": "015f5baa628e085d550f8bc6d47f0cc33508915a5dced6d8b238e6a52616690a",
|
|
105
|
+
"snapshot": "3.1.2 subscriptions: apps may offer auto-renewable in-app purchase subscriptions, regardless of category on the app store. when incorporating auto-renewable sub"
|
|
106
|
+
},
|
|
107
|
+
"3.1.5": {
|
|
108
|
+
"fingerprint": "ff34b24b578bec14defe707f1333593160535ab927d03676f1e533b4d9ef03bb",
|
|
109
|
+
"snapshot": "3.1.5 cryptocurrencies: (i) wallets: apps may facilitate virtual currency storage, provided they are offered by developers enrolled as an organization. (ii) min"
|
|
110
|
+
},
|
|
111
|
+
"4.2": {
|
|
112
|
+
"fingerprint": "381768384e5d58de6c2c75ccfae3524279669ea710b29bd12994561ba002e9d6",
|
|
113
|
+
"snapshot": "4.2 minimum functionality your app should include features, content, and ui that elevate it beyond a repackaged website. if your app is not particularly useful,"
|
|
114
|
+
},
|
|
115
|
+
"4.2.1": {
|
|
116
|
+
"fingerprint": "186350931d2f6ae6faf4fab30b4ad0000cb386890f59dfc1f0d55904e0d31ebc",
|
|
117
|
+
"snapshot": "4.2.1 apps using arkit should provide rich and integrated augmented reality experiences; merely dropping a model into an ar view or replaying animation is not e"
|
|
118
|
+
},
|
|
119
|
+
"4.2.2": {
|
|
120
|
+
"fingerprint": "504f8404893912593ded8f89748de71a847897cc0cf7f987e475323edef8650a",
|
|
121
|
+
"snapshot": "4.2.2 other than catalogs, apps shouldn’t primarily be marketing materials, advertisements, web clippings, content aggregators, or a collection of links."
|
|
122
|
+
},
|
|
123
|
+
"4.2.3": {
|
|
124
|
+
"fingerprint": "60bde0e85a028024660dd1b6da7a487083fea5c8e346e1f9d4ef287e6bce7829",
|
|
125
|
+
"snapshot": "4.2.3 (i) your app should work on its own without requiring installation of another app to function. (ii) if your app needs to download additional resources in "
|
|
126
|
+
},
|
|
127
|
+
"4.2.7": {
|
|
128
|
+
"fingerprint": "debd395643a03b8dec28d32aed55ec883b91ce24df156ed8365846245a56d436",
|
|
129
|
+
"snapshot": "4.2.7 remote desktop clients: if your remote desktop app acts as a mirror of specific software or services rather than a generic mirror of the host device, it m"
|
|
130
|
+
},
|
|
131
|
+
"4.4.1": {
|
|
132
|
+
"fingerprint": "520a1ac7a5d13b54061b05bdc57a851e7d5f541a352144623b16c52ae578e5c6",
|
|
133
|
+
"snapshot": "4.4.1 keyboard extensions have some additional rules. they must: provide keyboard input functionality (e.g. typed characters); follow sticker guidelines if the "
|
|
134
|
+
},
|
|
135
|
+
"4.4.2": {
|
|
136
|
+
"fingerprint": "6b7f6bde5787ba828a1f18833340140d858d1cc520430b0519f487a7d86dc54c",
|
|
137
|
+
"snapshot": "4.4.2 safari extensions must run on the current version of safari on the relevant apple operating system. they may not interfere with system or safari ui elemen"
|
|
138
|
+
},
|
|
139
|
+
"4.5.1": {
|
|
140
|
+
"fingerprint": "9a82e38f4117ddc59f65993ff1ba5c8fba392fb91f7aad3936725e289d348b38",
|
|
141
|
+
"snapshot": "4.5.1 apps may use approved apple rss feeds such as the itunes store rss feed, but may not scrape any information from apple sites (e.g. apple.com, the itunes s"
|
|
142
|
+
},
|
|
143
|
+
"4.5.2": {
|
|
144
|
+
"fingerprint": "285371a2e18e84a6b20f61f4b9e4f342c2890debecfaf527c57051d4e15c48b4",
|
|
145
|
+
"snapshot": "4.5.2 apple music (i) musickit on ios lets users play apple music and their local music library natively from your apps and games. when a user provides permissi"
|
|
146
|
+
},
|
|
147
|
+
"4.5.3": {
|
|
148
|
+
"fingerprint": "66dcdc8015b616f09d2dc81e96567f1e003d3c71d8549e89d911ff1a7a3a528e",
|
|
149
|
+
"snapshot": "4.5.3 do not use apple services to spam, phish, or send unsolicited messages to customers, including game center, push notifications, live activities, etc. do n"
|
|
150
|
+
},
|
|
151
|
+
"4.8": {
|
|
152
|
+
"fingerprint": "4d97c6a408a925e9209c7f01ca4377b17c52eeb837a15023ac776b198f3f203e",
|
|
153
|
+
"snapshot": "4.8 login services apps that use a third-party or social login service (such as facebook login, google sign-in, log in with x, sign in with linkedin, login with"
|
|
154
|
+
},
|
|
155
|
+
"4.9": {
|
|
156
|
+
"fingerprint": "6ac6eb5bc1047758fde6986c12411e5ccd0901ae38c71facf71076dab9a8f671",
|
|
157
|
+
"snapshot": "4.9 apple pay apps using apple pay must provide all material purchase information to the user prior to sale of any good or service and must use apple pay brandi"
|
|
158
|
+
},
|
|
159
|
+
"5.1.1": {
|
|
160
|
+
"fingerprint": "fed9edf9021681ed9f27139e162f963bf4823e95775d6efad1156c59e9bd7baf",
|
|
161
|
+
"snapshot": "5.1.1 data collection and storage (i) privacy policies: all apps must include a link to their privacy policy in the app store connect metadata field and within "
|
|
162
|
+
},
|
|
163
|
+
"5.1.2": {
|
|
164
|
+
"fingerprint": "9817b825aa227ee7959bdecde672a78dffa147ce2ded3e3bcc0c0a4b3010f80c",
|
|
165
|
+
"snapshot": "5.1.2 data use and sharing (i) unless otherwise permitted by law, you may not use, transmit, or share someone’s personal data without first obtaining their perm"
|
|
166
|
+
},
|
|
167
|
+
"5.1.3": {
|
|
168
|
+
"fingerprint": "12a61190ea01b69c11d8d029b473777b5e9590739fd3043afa25f4f0f05c114a",
|
|
169
|
+
"snapshot": "5.1.3 health and health research health, fitness, and medical data are especially sensitive and apps in this space have some additional rules to make sure custo"
|
|
170
|
+
},
|
|
171
|
+
"5.1.4": {
|
|
172
|
+
"fingerprint": "43671205363120d06dd34eb1971c4751e8582fb8f7dfe69abbcadf2cd36195ea",
|
|
173
|
+
"snapshot": "5.1.4 kids (a) for many reasons, it is critical to use care when dealing with personal data from kids, and we encourage you to carefully review all the requirem"
|
|
174
|
+
},
|
|
175
|
+
"5.1.5": {
|
|
176
|
+
"fingerprint": "07911be837521693da654a46f08e84c9ca2ec3cf9ad87c65f54ae9624d157b3e",
|
|
177
|
+
"snapshot": "5.1.5 location services use location services in your app only when it is directly relevant to the features and services provided by the app. location-based api"
|
|
178
|
+
},
|
|
179
|
+
"5.2.1": {
|
|
180
|
+
"fingerprint": "358d2d81a7b375d308077bbb71b07989235f5667d2cda7fffb14ac1b006c7ae8",
|
|
181
|
+
"snapshot": "5.2.1 generally: don’t use protected third-party material such as trademarks, copyrighted works, or patented ideas in your app without permission, and don’t inc"
|
|
182
|
+
},
|
|
183
|
+
"5.2.2": {
|
|
184
|
+
"fingerprint": "b7e091540eb347bfa61de6c24a960913e95e67bfe4b40197a464d56b21c45424",
|
|
185
|
+
"snapshot": "5.2.2 third-party sites/services: if your app uses, accesses, monetizes access to, or displays content from a third-party service, ensure that you are specifica"
|
|
186
|
+
},
|
|
187
|
+
"5.2.3": {
|
|
188
|
+
"fingerprint": "af448918a1af7645066c9cce2be68952a841908a49efe143123b5641e8cf2215",
|
|
189
|
+
"snapshot": "5.2.3 audio/video downloading: apps should not facilitate illegal file sharing or include the ability to save, convert, or download media from third-party sourc"
|
|
190
|
+
},
|
|
191
|
+
"5.3.1": {
|
|
192
|
+
"fingerprint": "b1b9f19d97e0303c2b658f1b7d9d0db70dc417bdff7ce826c1e5b93b4a35ca55",
|
|
193
|
+
"snapshot": "5.3.1 sweepstakes and contests must be sponsored by the developer of the app."
|
|
194
|
+
},
|
|
195
|
+
"5.3.2": {
|
|
196
|
+
"fingerprint": "49577a0affde1ea6a425b47803c251eaede1565ee4a92676587bda910b5f0632",
|
|
197
|
+
"snapshot": "5.3.2 official rules for sweepstakes, contests, and raffles must be presented in the app and make clear that apple is not a sponsor or involved in the activity "
|
|
198
|
+
},
|
|
199
|
+
"5.3.3": {
|
|
200
|
+
"fingerprint": "7770603fbb406eeaa5d1320ca6baa5f57a72ee680200d82373bbc9dd6e41b8fe",
|
|
201
|
+
"snapshot": "5.3.3 apps may not use in-app purchase to purchase credit or currency for use in conjunction with real money gaming of any kind."
|
|
202
|
+
},
|
|
203
|
+
"5.3.4": {
|
|
204
|
+
"fingerprint": "aaca06600d77f4c3d63ffd9c6c3f5eef979135c2ce3f1263bb5349420d036387",
|
|
205
|
+
"snapshot": "5.3.4 apps that offer real money gaming (e.g. sports betting, poker, casino games, horse racing) or lotteries must have necessary licensing and permissions in t"
|
|
206
|
+
},
|
|
207
|
+
"5.4": {
|
|
208
|
+
"fingerprint": "3d639b21b98c7d15167fdfbd71559a91a5de51a7b5c22f300203e5f341af8616",
|
|
209
|
+
"snapshot": "5.4 vpn apps apps offering vpn services must utilize the nevpnmanager api and may only be offered by developers enrolled as an organization. you must make a cle"
|
|
210
|
+
},
|
|
211
|
+
"5.5": {
|
|
212
|
+
"fingerprint": "f8c3db6e7237d7c5a5002a507e20296153e906118b7ce05c537d1873e2adeab0",
|
|
213
|
+
"snapshot": "5.5 mobile device management mobile device management apps that offer mobile device management (mdm) services must request this capability from apple. such apps"
|
|
214
|
+
},
|
|
215
|
+
"5.6.1": {
|
|
216
|
+
"fingerprint": "17224df537dcc818fd69678e5f067156127cc8513cb0188ad6e3431641673172",
|
|
217
|
+
"snapshot": "5.6.1 app store reviews app store customer reviews can be an integral part of the app experience, so you should treat customers with respect when responding to "
|
|
218
|
+
},
|
|
219
|
+
"5.6.2": {
|
|
220
|
+
"fingerprint": "960f0c0dab8442a42b03926ae1123ffabbc676ecd4dc1890d31fd58c3ba9cf36",
|
|
221
|
+
"snapshot": "5.6.2 developer identity providing verifiable information to apple and customers is critical to customer trust. your representation of yourself, your business, "
|
|
222
|
+
},
|
|
223
|
+
"5.6.3": {
|
|
224
|
+
"fingerprint": "1d9e3acfb31ec0ba65a77d09e230f5a3c408bc4438343437aa8dc3bd17d5967b",
|
|
225
|
+
"snapshot": "5.6.3 discovery fraud participating in the app store requires integrity and a commitment to building and maintaining customer trust. manipulating any element of"
|
|
226
|
+
},
|
|
227
|
+
"5.6.4": {
|
|
228
|
+
"fingerprint": "2f63efa4160b309b6ef4d1280e3d16a06165676f1421c3c36f6dbe305ca1291a",
|
|
229
|
+
"snapshot": "5.6.4 app quality customers expect the highest quality from the app store, and maintaining high quality content, services, and experiences promotes customer tru"
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
"reconciled_on": "2026-07-02"
|
|
233
|
+
}
|
|
@@ -52,6 +52,13 @@ number is unchanged but whose *text* changed is not caught here, but is partly c
|
|
|
52
52
|
Phase 2 (Apple's own rule engine) and Phase 3 (Pierre explains every FAIL/WARN). Apple does not expose a
|
|
53
53
|
machine-readable "last updated" date in the page DOM, so the section-number set is the signal.
|
|
54
54
|
|
|
55
|
+
**Automated detector:** `scripts/guideline-drift.sh` is the deterministic, curl-based counterpart
|
|
56
|
+
to the two-pass technique above — it runs unattended in CI (see
|
|
57
|
+
`.github/workflows/guideline-drift.yml`, scheduled + `workflow_dispatch`, always non-blocking) and
|
|
58
|
+
also covers text (semantic) drift of covered sections via `guidelines-fingerprints.json`, naming
|
|
59
|
+
the affected check(s) when a section's fingerprint no longer matches. Reconcile the fingerprint
|
|
60
|
+
baseline the same deliberate way, with `guideline-drift.sh --reconcile`.
|
|
61
|
+
|
|
55
62
|
---
|
|
56
63
|
|
|
57
64
|
## Phase 1: Rejection vectors
|
|
@@ -101,11 +108,31 @@ machine-readable "last updated" date in the page DOM, so the section-number set
|
|
|
101
108
|
| 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
109
|
| 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
110
|
| 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 |
|
|
111
|
+
| 42 | **2.3.3 Screenshot format/dimensions** *(advisory)* | Reads each in-repo screenshot's magic bytes and, for PNGs, its IHDR pixel dimensions; WARNs on a file whose content does not match its extension, a truncated PNG, or a PNG whose size matches no known App Store screenshot size (either orientation). JPEG dimensions are not parsed (format-checked only). WARN-only — never forces a RED verdict. |
|
|
104
112
|
|
|
105
113
|
Vectors 8–10 only run when in-app-purchase signals are detected (StoreKit / RevenueCat import,
|
|
106
|
-
or a paywall view). Otherwise the scanner emits a single PASS and skips them. Vectors 16–
|
|
114
|
+
or a paywall view). Otherwise the scanner emits a single PASS and skips them. Vectors 16–42 are
|
|
107
115
|
signal-gated advisory WARNs: each emits nothing unless its triggering signal is present.
|
|
108
116
|
|
|
117
|
+
### Screenshot format + dimensions (§7b, 2.3.3)
|
|
118
|
+
|
|
119
|
+
Vector 7 (above) checks that every locale has at least one screenshot. §7b goes one layer deeper,
|
|
120
|
+
purely with zero-dependency byte reads (`image-dims.sh`, pure bash + `od`/`awk`): for every
|
|
121
|
+
in-repo screenshot it reads the file's magic bytes to confirm the content actually matches its
|
|
122
|
+
extension (`.png`/`.jpg`/`.jpeg`), and for PNGs it also parses the IHDR chunk for pixel width and
|
|
123
|
+
height and checks that pair against a table of known App Store screenshot sizes (tried in both
|
|
124
|
+
orientations, sourced from Apple's [screenshot specifications
|
|
125
|
+
page](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications/)).
|
|
126
|
+
JPEG dimensions are not parsed — JPEGs are format-checked only (magic-byte match), not size-checked.
|
|
127
|
+
|
|
128
|
+
This is **WARN-only, never FAIL**: the accepted-size table can drift as Apple adds device sizes,
|
|
129
|
+
and the scanner has no way to know which display slot (iPhone 6.9", iPad 13", …) a given file is
|
|
130
|
+
meant to target, so a size that matches nothing in the table is a prompt to verify against the
|
|
131
|
+
current spec, not proof of a rejection. A mismatched-format file (e.g. a renamed JPEG saved as
|
|
132
|
+
`.png`) or an unreadable/truncated PNG WARNs the same way. Findings surface under
|
|
133
|
+
`rule_id == "screenshot-dimensions"` (catalog vector 42) with messages beginning
|
|
134
|
+
`WARN: 2.3.3 Screenshot …`.
|
|
135
|
+
|
|
109
136
|
**Scope by app type.** The metadata, privacy-manifest, screenshots, and export-compliance checks
|
|
110
137
|
apply to any iOS app regardless of how it is built. The code-level checks grep the app's Swift
|
|
111
138
|
source (`*.swift`, plus `*.m`/`*.h` and `*.entitlements` where relevant), so they are most accurate
|
|
@@ -147,7 +174,7 @@ finds; Pierre explains.
|
|
|
147
174
|
After Phase 3, Pierre runs the **Review Simulator**: 28 evidence-based checks the static scanner
|
|
148
175
|
cannot fully judge — **22 Tier A** (high-confidence) plus **6 Tier B v1** heuristic checks (items
|
|
149
176
|
**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.
|
|
177
|
+
5.6.1/5.6.3). Full procedure, output format, and per-check steps are in
|
|
151
178
|
[`pierre-deep-review.md`](pierre-deep-review.md).
|
|
152
179
|
|
|
153
180
|
**Verdict impact:** none. Phase 4 emits `REVIEW-PASS:` or `REVIEW-FINDING: <guideline> WARN — …`.
|
|
@@ -87,7 +87,7 @@ REVIEW-PASS: <guideline> — not applicable (<reason>)
|
|
|
87
87
|
| 25 | **5.2.1–5.2.3** | Obvious third-party trademark/brand misuse in metadata, assets, or UI copy? | metadata, asset filenames, Swift strings |
|
|
88
88
|
| 26 | **5.3.1–5.3.3** | Contest/sweepstakes/lottery copy → official rules/eligibility/disclosure present in metadata? | description, keywords, in-app contest UI |
|
|
89
89
|
| 27 | **5.6.2–5.6.3** | Developer identity consistent: app name, support URL content, bundle/marketing domain match? | fetch support URL, metadata, legal/footer copy |
|
|
90
|
-
| 28 | **5.6.
|
|
90
|
+
| 28 | **5.6.1 / 5.6.3** | Rating/review manipulation dark patterns (withhold features until 5 stars, direct write-review links without `requestReview`)? | Swift, metadata, §25 scan context |
|
|
91
91
|
|
|
92
92
|
---
|
|
93
93
|
|
|
@@ -147,6 +147,9 @@ REVIEW-PASS: <guideline> — not applicable (<reason>)
|
|
|
147
147
|
1. Open ≥1 screenshot per primary locale.
|
|
148
148
|
2. List visible features (tabs, paywall, login, maps, etc.).
|
|
149
149
|
3. Flag screenshots showing features absent from the build or metadata.
|
|
150
|
+
4. For the full structured screenshot vision review (placeholder/empty-state, text overflow,
|
|
151
|
+
wrong device frame, misleading marketing, metadata mismatch), follow
|
|
152
|
+
`references/screenshot-vision-review.md`.
|
|
150
153
|
|
|
151
154
|
### 9 — 2.3.6 Pricing language
|
|
152
155
|
|
|
@@ -253,7 +256,7 @@ REVIEW-PASS: <guideline> — not applicable (<reason>)
|
|
|
253
256
|
2. Compare app name, support domain, and privacy policy domain for consistency.
|
|
254
257
|
3. Flag placeholder support pages or identity mismatch.
|
|
255
258
|
|
|
256
|
-
### 28 — 5.6.
|
|
259
|
+
### 28 — 5.6.1 / 5.6.3 Rating/review manipulation *(Tier B v1)*
|
|
257
260
|
|
|
258
261
|
1. Grep Swift and metadata for: `itms-apps://` write-review URLs, `apps.apple.com/.../write-review`,
|
|
259
262
|
"rate 5 stars", "only enable after review", custom star-rating UI tied to App Store review.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Screenshot vision review (agent-mode, non-blocking)
|
|
2
|
+
|
|
3
|
+
Deepens Pierre deep-review check #8 (2.3.5) into a dedicated, structured screenshot review.
|
|
4
|
+
This is the **vision layer** of the Review Simulator: the host model reads the actual screenshot
|
|
5
|
+
images and cross-checks their content against the metadata and the shipped app.
|
|
6
|
+
|
|
7
|
+
**Identity:** this runs ONLY in agent-skill mode (Claude Code / Codex / …), using the host LLM's
|
|
8
|
+
vision capability — exactly like Pierre reads code and the drift phase fetches URLs. It is NOT a
|
|
9
|
+
bundled dependency and does NOT run in the offline CLI / npx / GitHub-Action path.
|
|
10
|
+
|
|
11
|
+
**This phase does not change the GREEN/YELLOW/RED verdict** (verdict comes only from scan
|
|
12
|
+
`FAIL:`/`WARN:` counts). It emits `REVIEW-PASS:` / `REVIEW-FINDING: … WARN` lines, like Pierre
|
|
13
|
+
deep-review.
|
|
14
|
+
|
|
15
|
+
## Rules
|
|
16
|
+
|
|
17
|
+
- Read-only. Never modify project files.
|
|
18
|
+
- Evidence-based: cite the screenshot **filename** (and locale) for every finding. If you cannot
|
|
19
|
+
read an image, say so — do not invent findings.
|
|
20
|
+
- Read at least one screenshot per primary locale; when several are present, scan them all.
|
|
21
|
+
- All five checks, every run: report each as `REVIEW-PASS:` or `REVIEW-FINDING: … WARN`.
|
|
22
|
+
- If there are no in-repo screenshots, report each check as
|
|
23
|
+
`REVIEW-PASS: <guideline> — not applicable (no in-repo screenshots; managed in App Store Connect)`.
|
|
24
|
+
- Severity is always WARN (advisory). Cautious language ("may trigger review questions").
|
|
25
|
+
- Write Pierre's 2–3 sentence explanations in the user's conversation language.
|
|
26
|
+
|
|
27
|
+
## The 5 checks
|
|
28
|
+
|
|
29
|
+
| # | Guideline | Question |
|
|
30
|
+
|---|-----------|----------|
|
|
31
|
+
| S1 | 2.3.3 / 2.3.7 | Placeholder / dev-debug / empty-state content: Lorem ipsum, debug overlays or logs, visible TODO/FIXME, empty lists or skeleton loaders shown as content, simulator status bar with placeholder carrier/time. |
|
|
32
|
+
| S2 | 2.3.3 | Text overflow / truncation / clipping: clipped or overlapping labels, cut-off buttons, text running off-screen. |
|
|
33
|
+
| S3 | 2.3.5 | Wrong device frame / aspect: an iPad screenshot in an iPhone slot (or vice-versa), letterboxing, obviously stretched/squished aspect. |
|
|
34
|
+
| S4 | 2.3.3 / 2.3.10 | Misleading marketing: 2.3.3 "show the app in use" — the shot is a splash/title/logo/pure marketing art, not actual app UI; 2.3.10 — a feature is depicted that the app does not ship. |
|
|
35
|
+
| S5 | 2.3.5 | Metadata ↔ screenshot claim mismatch: visible UI text/features contradict the description, keywords, or promo text. |
|
|
36
|
+
|
|
37
|
+
## Per-check procedure
|
|
38
|
+
|
|
39
|
+
### S1 — Placeholder / dev-debug / empty-state
|
|
40
|
+
1. Open each screenshot; read visible text and UI state.
|
|
41
|
+
2. Flag Lorem ipsum, debug HUDs, log text, "TODO"/"FIXME", empty/skeleton content presented as real, or a simulator status bar with placeholder carrier/time.
|
|
42
|
+
3. Cite the filename. Not applicable if no screenshots.
|
|
43
|
+
|
|
44
|
+
### S2 — Text overflow / truncation
|
|
45
|
+
1. Inspect labels, buttons, and headings for clipping, overlap, or off-screen text.
|
|
46
|
+
2. Flag any truncation that suggests an unfinished or broken layout.
|
|
47
|
+
|
|
48
|
+
### S3 — Wrong device frame / aspect
|
|
49
|
+
1. Compare each screenshot's aspect to its locale/slot (iPhone vs iPad).
|
|
50
|
+
2. Flag an iPad-aspect image in an iPhone slot (or vice-versa), letterboxing, or stretched aspect.
|
|
51
|
+
|
|
52
|
+
### S4 — Misleading marketing
|
|
53
|
+
1. Determine whether each screenshot shows the app actually in use (real UI) vs pure title/splash/logo art.
|
|
54
|
+
2. Flag shots that are marketing art rather than the app in use (2.3.3), or that depict a feature absent from the build (2.3.10).
|
|
55
|
+
|
|
56
|
+
### S5 — Metadata ↔ screenshot mismatch
|
|
57
|
+
1. Read the visible UI text/features and compare to the description, keywords, and promo text.
|
|
58
|
+
2. Flag direct contradictions (a feature/claim in the screenshot not in metadata, or vice-versa).
|
|
59
|
+
|
|
60
|
+
## Output format
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
REVIEW-PASS: <guideline> — <one-line why it looks OK, with screenshot filename>
|
|
64
|
+
```
|
|
65
|
+
or
|
|
66
|
+
```
|
|
67
|
+
REVIEW-FINDING: <guideline> WARN — <one-line concrete issue, with screenshot filename>
|
|
68
|
+
Pierre: <2–3 sentences: why Apple cares, what you saw, what to fix or verify>
|
|
69
|
+
```
|
|
@@ -26,7 +26,8 @@ rule_slug() {
|
|
|
26
26
|
35) echo webview-wrapper ;; 36) echo remote-desktop ;;
|
|
27
27
|
37) echo safari-extension ;; 38) echo account-no-delete ;;
|
|
28
28
|
39) echo kids-ads-analytics ;; 40) echo realmoney-gambling ;;
|
|
29
|
-
41) echo mdm ;;
|
|
29
|
+
41) echo mdm ;; 42) echo screenshot-dimensions ;;
|
|
30
|
+
*) echo "" ;;
|
|
30
31
|
esac
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# image-dims.sh — zero-dependency image format + PNG dimension helpers for scan.sh.
|
|
3
|
+
# Pure bash + od + awk; bash 3.2 compatible; sourced, no side effects, no output on load.
|
|
4
|
+
|
|
5
|
+
# img_format <file> -> "png" | "jpeg" | "unknown"
|
|
6
|
+
img_format() {
|
|
7
|
+
local f="$1" sig
|
|
8
|
+
[[ -r "$f" ]] || { echo unknown; return; }
|
|
9
|
+
sig="$(od -An -tx1 -N8 "$f" 2>/dev/null | tr -d ' \n')"
|
|
10
|
+
case "$sig" in
|
|
11
|
+
89504e470d0a1a0a*) echo png ;;
|
|
12
|
+
ffd8ff*) echo jpeg ;;
|
|
13
|
+
*) echo unknown ;;
|
|
14
|
+
esac
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# png_dims <file> -> "W H" (decimal) and return 0, or return 1 with no output.
|
|
18
|
+
# IHDR width = big-endian bytes 16-19, height = 20-23 (8-byte signature + 4-byte
|
|
19
|
+
# length + "IHDR" tag precede the 8 dimension bytes at offset 16).
|
|
20
|
+
png_dims() {
|
|
21
|
+
local f="$1"
|
|
22
|
+
[[ "$(img_format "$f")" == png ]] || return 1
|
|
23
|
+
local b
|
|
24
|
+
read -r -a b < <(od -An -tu1 -j16 -N8 "$f" 2>/dev/null)
|
|
25
|
+
[[ "${#b[@]}" -ge 8 ]] || return 1
|
|
26
|
+
local w=$(( b[0]*16777216 + b[1]*65536 + b[2]*256 + b[3] ))
|
|
27
|
+
local h=$(( b[4]*16777216 + b[5]*65536 + b[6]*256 + b[7] ))
|
|
28
|
+
printf '%s %s\n' "$w" "$h"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
# ACCEPTED_SIZES — Apple App Store screenshot pixel sizes (portrait "W H").
|
|
32
|
+
# The matcher tries both orientations. This table is intentionally generous
|
|
33
|
+
# (includes current + legacy device sizes) because this check is WARN-only.
|
|
34
|
+
# Verified against Apple's official page:
|
|
35
|
+
# https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications/
|
|
36
|
+
ACCEPTED_SIZES="
|
|
37
|
+
1260 2736
|
|
38
|
+
1290 2796
|
|
39
|
+
1320 2868
|
|
40
|
+
1284 2778
|
|
41
|
+
1242 2688
|
|
42
|
+
1179 2556
|
|
43
|
+
1206 2622
|
|
44
|
+
1170 2532
|
|
45
|
+
1125 2436
|
|
46
|
+
1080 2340
|
|
47
|
+
1242 2208
|
|
48
|
+
750 1334
|
|
49
|
+
640 1096
|
|
50
|
+
640 1136
|
|
51
|
+
640 920
|
|
52
|
+
640 960
|
|
53
|
+
2064 2752
|
|
54
|
+
2048 2732
|
|
55
|
+
1488 2266
|
|
56
|
+
1668 2420
|
|
57
|
+
1668 2388
|
|
58
|
+
1640 2360
|
|
59
|
+
1668 2224
|
|
60
|
+
1536 2008
|
|
61
|
+
1536 2048
|
|
62
|
+
768 1004
|
|
63
|
+
768 1024
|
|
64
|
+
"
|
|
65
|
+
|
|
66
|
+
# dims_match_accepted <W> <H> -> return 0 if W×H or H×W is an accepted size.
|
|
67
|
+
dims_match_accepted() {
|
|
68
|
+
local w="$1" h="$2" aw ah
|
|
69
|
+
while read -r aw ah; do
|
|
70
|
+
[[ -z "$aw" ]] && continue
|
|
71
|
+
if { [[ "$w" == "$aw" && "$h" == "$ah" ]]; } || { [[ "$w" == "$ah" && "$h" == "$aw" ]]; }; then
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
done <<< "$ACCEPTED_SIZES"
|
|
75
|
+
return 1
|
|
76
|
+
}
|
|
@@ -15,6 +15,7 @@ cd "$ROOT" || { echo "FAIL: repo-root — could not enter repository root"; exit
|
|
|
15
15
|
source "$(dirname "${BASH_SOURCE[0]}")/findings.sh"
|
|
16
16
|
source "$(dirname "${BASH_SOURCE[0]}")/suppress.sh"
|
|
17
17
|
source "$(dirname "${BASH_SOURCE[0]}")/project-model.sh"
|
|
18
|
+
source "$(dirname "${BASH_SOURCE[0]}")/image-dims.sh"
|
|
18
19
|
FINDINGS_TMP="$(mktemp)"; export FINDINGS_TMP
|
|
19
20
|
trap 'rm -f "$FINDINGS_TMP"' EXIT
|
|
20
21
|
FORMAT="text"
|
|
@@ -395,6 +396,42 @@ else
|
|
|
395
396
|
pass "2.3.3 Screenshots — no in-repo screenshots dir; assumed managed in App Store Connect (set .screenshotsDir to check in-repo)"
|
|
396
397
|
fi
|
|
397
398
|
|
|
399
|
+
# ===================================================================
|
|
400
|
+
# §7b — 2.3.3 Screenshot format + PNG dimensions (deterministic; PNG dims,
|
|
401
|
+
# JPEG format only). WARN-only: the accepted-size table can drift, and we
|
|
402
|
+
# cannot know which display slot a file targets, so mismatches never FAIL.
|
|
403
|
+
# ===================================================================
|
|
404
|
+
if [[ -n "$SCREEN_DIR" && -d "$SCREEN_DIR" ]]; then
|
|
405
|
+
set_rule "screenshot-dimensions"
|
|
406
|
+
while IFS= read -r img; do
|
|
407
|
+
[[ -z "$img" ]] && continue
|
|
408
|
+
fmt="$(img_format "$img")"
|
|
409
|
+
case "$img" in
|
|
410
|
+
*.png)
|
|
411
|
+
if [[ "$fmt" != png ]]; then
|
|
412
|
+
warn "2.3.3 Screenshot $img is not a valid PNG (file content does not match .png extension)" "$img"
|
|
413
|
+
continue
|
|
414
|
+
fi
|
|
415
|
+
dims="$(png_dims "$img")"
|
|
416
|
+
if [[ -z "$dims" ]]; then
|
|
417
|
+
warn "2.3.3 Screenshot $img — could not read PNG dimensions (possibly truncated)" "$img"
|
|
418
|
+
else
|
|
419
|
+
w="${dims% *}"; h="${dims#* }"
|
|
420
|
+
if ! dims_match_accepted "$w" "$h"; then
|
|
421
|
+
warn "2.3.3 Screenshot $img is ${w}x${h}, which matches no known App Store screenshot size — verify against the current spec" "$img"
|
|
422
|
+
fi
|
|
423
|
+
fi
|
|
424
|
+
;;
|
|
425
|
+
*.jpg|*.jpeg)
|
|
426
|
+
if [[ "$fmt" != jpeg ]]; then
|
|
427
|
+
warn "2.3.3 Screenshot $img is not a valid JPEG (file content does not match extension)" "$img"
|
|
428
|
+
fi
|
|
429
|
+
;;
|
|
430
|
+
esac
|
|
431
|
+
done < <(find "$SCREEN_DIR" -maxdepth 3 -type f \( -name '*.png' -o -name '*.jpg' -o -name '*.jpeg' \) 2>/dev/null | sort)
|
|
432
|
+
set_rule ""
|
|
433
|
+
fi
|
|
434
|
+
|
|
398
435
|
# ===================================================================
|
|
399
436
|
# In-app purchase gate — only run 3.1.2 checks if IAP signals exist
|
|
400
437
|
# ===================================================================
|
|
@@ -403,7 +440,7 @@ grep -rqE 'SKPaymentQueue|SKProduct|SKMutablePayment|Product\.products|Product\(
|
|
|
403
440
|
[[ -n "$SUB_VIEW" ]] && iap_detected=1
|
|
404
441
|
|
|
405
442
|
if [[ -z "$iap_detected" ]]; then
|
|
406
|
-
# Not one of the
|
|
443
|
+
# Not one of the 42 catalog sections — clear the rule_id so this PASS doesn't
|
|
407
444
|
# inherit §7's "screenshots-per-locale" slug in the JSON output.
|
|
408
445
|
set_rule ""
|
|
409
446
|
pass "3.1.2 IAP — no in-app purchase / subscription signals detected, skipping paywall checks"
|