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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,157 @@
|
|
|
3
3
|
All notable changes to this project are documented here. Versioning follows
|
|
4
4
|
[SemVer](https://semver.org/). Released as git tags.
|
|
5
5
|
|
|
6
|
+
## [1.5.1] - 2026-06-30
|
|
7
|
+
|
|
8
|
+
### Changed
|
|
9
|
+
|
|
10
|
+
- Plugin manifest descriptions no longer embed drifting check counts
|
|
11
|
+
- README uninstall: Codex uses /plugins UI
|
|
12
|
+
- cross-tool-verification.md plugin install paths documented
|
|
13
|
+
- GitHub release v1.5.0 notes amended; npm 1.5.1 published
|
|
14
|
+
|
|
15
|
+
## [1.5.0] - 2026-06-30
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Tier B v1 — 6 heuristic Pierre deep-review checks** (28 total, up from 22). New Phase 4 items
|
|
19
|
+
in guideline order: **2.1** review notes / demo account quality, **2.2** beta/test language,
|
|
20
|
+
**2.3.4** app preview consistency, **2.3.9** incentivized review copy, **4.5.1–4.5.3** push /
|
|
21
|
+
HomeKit abuse patterns, **5.6.4–5.6.7** rating manipulation dark patterns. Marked † in docs;
|
|
22
|
+
higher false-positive risk — advisory `REVIEW-FINDING: WARN` only.
|
|
23
|
+
- Expanded `covered_by_pierre_deep_review` in `guidelines-baseline.json` with 2.2, 2.3.4, 2.3.9,
|
|
24
|
+
4.5.1–4.5.3, 5.6.4–5.6.7.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
- Phase 4 checklist 22 → **28** across `pierre-deep-review.md`, `SKILL.md`, `methodology.md`, README
|
|
28
|
+
(full guideline-ordered table with † Tier B labels), plugin manifest, and social preview
|
|
29
|
+
(`41 static + 28 Pierre deep checks`).
|
|
30
|
+
|
|
31
|
+
## [1.4.0] - 2026-06-30
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
- **Phase 4: Pierre deep review — 22 semantic checks.** After Phase 3 (explaining every scan
|
|
35
|
+
FAIL/WARN), Pierre runs a read-only Review Simulator: fetches privacy/support URLs, reads
|
|
36
|
+
screenshots, compares metadata claims to Swift code, validates paywall disclosure quality, and
|
|
37
|
+
cross-checks permissions vs policy. Each check emits `REVIEW-PASS:` or advisory
|
|
38
|
+
`REVIEW-FINDING: … WARN` (does not change GREEN/YELLOW/RED counts). Full checklist in
|
|
39
|
+
[`references/pierre-deep-review.md`](skills/appstore-precheck/references/pierre-deep-review.md).
|
|
40
|
+
- **`covered_by_pierre_deep_review`** in `guidelines-baseline.json` (26 guideline numbers across
|
|
41
|
+
the 22 checks). Consolidation moves to **Phase 5** (6 phases total: 0–5).
|
|
42
|
+
- [`examples/pierre-deep-review.md`](examples/pierre-deep-review.md) showing `REVIEW-FINDING` output.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
- Flow is now **6 phases (0–5)**: Phase 3 = scan commentary, Phase 4 = deep review, Phase 5 =
|
|
46
|
+
verdict + token. Updated `SKILL.md`, `methodology.md`, README (static table + deep-review table
|
|
47
|
+
in guideline order), examples, plugin manifest, and social preview copy (`41 static + 22 Pierre
|
|
48
|
+
deep checks`).
|
|
49
|
+
|
|
50
|
+
## [1.3.1] - 2026-06-30
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
- **Phase 3 (Pierre) now explains every FAIL and WARN** from Phases 0–2 in **2–3 sentences
|
|
54
|
+
each** — no more random 5-guideline sampling. Pierre repeats each machine line verbatim,
|
|
55
|
+
then explains why Apple cares, what the scan found, and what to fix. Updated `SKILL.md`,
|
|
56
|
+
`methodology.md`, the README example, `examples/red-reject.md`, and behavioral eval
|
|
57
|
+
assertions in lockstep.
|
|
58
|
+
- Phase 4 presentation order clarified: trilingual verdict block → Pierre commentary → verbatim
|
|
59
|
+
scan lines + `file:line` fixes → verdict/token.
|
|
60
|
+
- **Trilingual verdict block format:** each language in its own **bold label + blockquote**, separated
|
|
61
|
+
by horizontal rules (`---`) under a `### Pierre` heading — never FR/EN/user-lang compressed on one line.
|
|
62
|
+
|
|
63
|
+
## [1.3.0] - 2026-06-30
|
|
64
|
+
|
|
65
|
+
### Added
|
|
66
|
+
- **Eleven new signal-gated advisory checks (30 → 41 rejection vectors), all WARN-only:**
|
|
67
|
+
- **2.1** a login-gated app with no demo account / credentials for App Review (fastlane
|
|
68
|
+
`review_information` or `.reviewPrepNotes`).
|
|
69
|
+
- **2.5.2** executable-code download / native hot-patching (JSPatch, Rollout, DynamicCocoa).
|
|
70
|
+
Allowed JS-bundle OTA (React Native CodePush) is deliberately **not** flagged.
|
|
71
|
+
- **2.5.4** a background mode declared in `UIBackgroundModes` with no matching API used in Swift.
|
|
72
|
+
- **3.1.5(a)** a cryptocurrency wallet / exchange / mining signal.
|
|
73
|
+
- **4.2.3** a thin WKWebView wrapper around a website (heuristic: WKWebView + very few Swift files).
|
|
74
|
+
- **4.2.7** a remote-desktop / host-mirroring signal.
|
|
75
|
+
- **4.4.2** a Safari content-blocker / web extension.
|
|
76
|
+
- **5.1.1(v)** account creation offered without an in-app account-deletion path (the real
|
|
77
|
+
5.1.1(v) Account Sign-In rule).
|
|
78
|
+
- **5.1.4** metadata targeting a child audience while a third-party ads/analytics SDK is linked.
|
|
79
|
+
- **5.3.4** real-money gambling language in metadata.
|
|
80
|
+
- **5.5** a Mobile Device Management (MDM) signal.
|
|
81
|
+
- `tests/fixtures/risky-app-2` (advisory §31–§41 except web-wrapper) and `tests/fixtures/webview-app`
|
|
82
|
+
(the 4.2.3 heuristic), with assertions in `tests/run.sh`. Both are advisory-only (no FAIL).
|
|
83
|
+
|
|
84
|
+
### Changed
|
|
85
|
+
- **Corrected the Required Reason API label from `5.1.1(v)` to `5.1.1`.** Apple documents the
|
|
86
|
+
Required Reason API rules under 5.1.1 + the privacy-manifest developer docs; sub-item **(v)** is
|
|
87
|
+
"Account Sign-In", a different rule. The `(v)` label now belongs to the new account-deletion
|
|
88
|
+
check (vector 38). Updated `scan.sh` output, the methodology table, the README table, the
|
|
89
|
+
examples, and the field-test notes in lockstep.
|
|
90
|
+
- §18 (support / privacy URL) now also cites the guidelines it satisfies: **1.5** (developer
|
|
91
|
+
contact via the support URL) and **5.1.1(i)** (privacy policy link).
|
|
92
|
+
- Expanded `guidelines-baseline.json` `covered_by_scan` with 1.5, 2.5.2, 2.5.4, 3.1.5, 4.2.3,
|
|
93
|
+
4.2.7, 4.4.2, 5.1.4, 5.3.4, and 5.5.
|
|
94
|
+
|
|
95
|
+
## [1.2.0] - 2026-06-30
|
|
96
|
+
|
|
97
|
+
### Added
|
|
98
|
+
- **Ten new signal-gated advisory checks (20 → 30 rejection vectors), all WARN-only:**
|
|
99
|
+
- **3.1.1** third-party payment SDK (Stripe, Braintree, PayPal, Square, Adyen, …) linked for
|
|
100
|
+
digital goods instead of in-app purchase.
|
|
101
|
+
- **1.2** user-generated content detected without a report / block / moderation affordance.
|
|
102
|
+
- **1.6** App Transport Security disabled app-wide (`NSAllowsArbitraryLoads=true`).
|
|
103
|
+
- **4.9** recurring Apple Pay without the renewal / cancel disclosure.
|
|
104
|
+
- **5.6.1** a direct App Store write-review link/prompt without the system `requestReview` API.
|
|
105
|
+
- **2.3.1** misleading marketing claims (iOS virus / malware scanners, fake speed boosters) in metadata.
|
|
106
|
+
- **2.3.8** "For Kids" / "For Children" wording outside the Kids Category.
|
|
107
|
+
- **4.4.1** keyboard extension requiring full access (`RequestsOpenAccess=true`).
|
|
108
|
+
- **5.1.3** HealthKit used together with an iCloud / CloudKit sync path.
|
|
109
|
+
- **5.4** VPN / NetworkExtension (`NEVPNManager`) usage.
|
|
110
|
+
- `tests/fixtures/risky-app` plus assertions in `tests/run.sh` exercising all ten new vectors
|
|
111
|
+
(advisory only — the fixture is YELLOW, never RED).
|
|
112
|
+
|
|
113
|
+
### Changed
|
|
114
|
+
- **YELLOW threshold raised from 3+ to 5+ WARN.** The ten new advisory checks are signal-gated
|
|
115
|
+
(most apps trip only one or two), but the bump keeps a normal submission from sliding into
|
|
116
|
+
YELLOW on advisory noise alone. GREEN is now 0 FAIL and ≤4 WARN; YELLOW is 0 FAIL and ≥5 WARN.
|
|
117
|
+
Updated `verdict.sh`, the verdict tests, the output-contract tables, and the docs in lockstep.
|
|
118
|
+
- Corrected the minimum-functionality check's label from `4.0` to `4.2` (its real guideline
|
|
119
|
+
number) in the scanner output, the methodology table, and the README.
|
|
120
|
+
- Reconciled `guidelines-baseline.json` against the live guidelines (Last Updated June 8, 2026):
|
|
121
|
+
no structural section drift; expanded `covered_by_scan` to reflect every guideline the scan now
|
|
122
|
+
touches (adds 1.2, 1.6, 2.1, 2.3, 2.3.8, 3.1.1, 4.4.1, 4.8, 4.9, 5.1.3, 5.4, 5.6.1; 4.0 → 4.2).
|
|
123
|
+
|
|
124
|
+
## [1.1.1] - 2026-06-30
|
|
125
|
+
|
|
126
|
+
### Changed
|
|
127
|
+
- **Pierre now speaks in a trilingual block.** The verdict opens with his native **French** line,
|
|
128
|
+
then an **English** rendering, then a rendering in the **user's conversation language** — each an
|
|
129
|
+
idiomatic, in-character re-expression in that language's own rhythm, not a literal translation.
|
|
130
|
+
Collapses to two lines when the user already converses in French or English. The block stays
|
|
131
|
+
flavor only; the FAIL/WARN list, `file:line` references, and fixes below it remain plain and
|
|
132
|
+
machine-faithful. Updated the output contract, Phase 4 step 3, and the behavioral eval
|
|
133
|
+
assertions accordingly.
|
|
134
|
+
|
|
135
|
+
### Fixed
|
|
136
|
+
- **Pierre no longer treats local-only files as Apple submission evidence.** The Phase 3 prompt
|
|
137
|
+
now scopes reject-risk evidence to Apple-facing artifacts (fastlane metadata, paywall Swift,
|
|
138
|
+
String Catalog, Info.plist, PrivacyInfo.xcprivacy). Internal/local files (`.planning/` notes,
|
|
139
|
+
`reviewPrepNotes` drafts, build scripts) and Google Play / non-Apple sections are out of scope —
|
|
140
|
+
cited at most as a WARN labeled "internal draft — not submitted to Apple", never REJECT-RISK. A
|
|
141
|
+
REJECT risk now requires a contradiction *within* submission-facing artifacts, not an internal
|
|
142
|
+
doc disagreeing with metadata. An eligibility-gated/conditional offer paired with metadata that
|
|
143
|
+
mentions it is WARN at most (unless the metadata promises it unconditionally). Prevents the
|
|
144
|
+
false REJECT-RISK overreach seen when dogfooding an already-approved build.
|
|
145
|
+
- **2.3.7 locale check no longer hard-FAILs on a config/disk mismatch.** A locale listed in
|
|
146
|
+
`.appstore-precheck.json` `locales` but with no metadata folder on disk is now a WARN (with an
|
|
147
|
+
actionable "add it or remove it from the config" message), not a FAIL — that locale was simply
|
|
148
|
+
never submitted, so it must not turn an approved set RED. A missing *file* inside a present
|
|
149
|
+
locale folder is still a FAIL.
|
|
150
|
+
- **2.1 placeholder check no longer false-fires on words containing "changeme".** The `changeme`
|
|
151
|
+
pattern is now word-bounded (`\bchangeme\b`) in both the metadata-URL and store-copy scans, so
|
|
152
|
+
legitimate copy such as the French "changement" ("change") is not flagged as unfinished.
|
|
153
|
+
- Added regression coverage in `tests/test-config.sh` for both fixes.
|
|
154
|
+
- CI: bumped `actions/checkout@v4 -> v7` and `actions/setup-node@v4 -> v6` to clear the
|
|
155
|
+
GitHub Actions Node.js 20 deprecation warning (both now run natively on Node 24).
|
|
156
|
+
|
|
6
157
|
## [1.1.0] - 2026-06-28
|
|
7
158
|
|
|
8
159
|
### Added
|
package/README.md
CHANGED
|
@@ -16,8 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
`appstore-precheck` is a read-only, pre-submission gate for iOS apps. It statically scans the most
|
|
18
18
|
common rejection vectors, runs Apple's own metadata linter, watches the App Store Review Guidelines
|
|
19
|
-
for drift,
|
|
20
|
-
|
|
19
|
+
for drift, has Pierre explain every FAIL and WARN, then runs **28 semantic deep-review checks**
|
|
20
|
+
(22 high-confidence Tier A + 6 heuristic Tier B v1 — beta language, review notes quality, app preview,
|
|
21
|
+
incentivized review, push/HomeKit abuse, rating manipulation).
|
|
22
|
+
It hands you a single **GREEN / YELLOW / RED** verdict. It never edits your code.
|
|
21
23
|
|
|
22
24
|
It ships as a portable [Agent Skill](https://agentskills.io): the same `SKILL.md` runs natively in
|
|
23
25
|
Claude Code, OpenAI Codex, Cursor, and Gemini CLI. The scanner is plain Bash, so you can also run it
|
|
@@ -28,95 +30,243 @@ by hand or wire it into CI.
|
|
|
28
30
|
<img src="assets/mascot.png" align="right" width="124" alt="Pierre, the French app reviewer">
|
|
29
31
|
|
|
30
32
|
Your verdict is delivered by **Pierre**, a French critic who has seen ten thousand rejections and is
|
|
31
|
-
impressed by none of them. He reviews your build harder than Apple would, in private
|
|
33
|
+
impressed by none of them. He reviews your build harder than Apple would, in private — first with a
|
|
34
|
+
fast static scan, then with **28 deep semantic checks** (22 confident + 6 heuristic). A GREEN from
|
|
32
35
|
Pierre means Apple will wave you through.
|
|
33
36
|
|
|
34
37
|
- 🔴 **RED**: *"Non. Restore Purchases, absent. Guideline 3.1.2. Suivant."*
|
|
35
38
|
- 🟡 **YELLOW**: *"A few small uglinesses. I would not reject. But I noticed."*
|
|
36
39
|
- 🟢 **GREEN**: *"Hmf. I find nothing. Acceptable. Do not make me regret this."*
|
|
37
40
|
|
|
38
|
-
The verdict line is in Pierre's voice
|
|
39
|
-
|
|
41
|
+
The verdict line is in Pierre's voice — **three separate language blocks** (bold label + blockquote
|
|
42
|
+
each, divided by horizontal rules), not one compressed sentence. The breakdown beneath it, every
|
|
43
|
+
`file:line` and fix, stays plain and surgical.
|
|
40
44
|
|
|
41
45
|
## What it checks
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
41 rejection vectors across code, fastlane metadata, screenshots, `PrivacyInfo.xcprivacy`, and the paywall:
|
|
44
48
|
|
|
45
49
|
| Guideline | Check |
|
|
46
50
|
|-----------|-------|
|
|
47
|
-
| **
|
|
48
|
-
| **
|
|
49
|
-
| **
|
|
50
|
-
| **2.
|
|
51
|
+
| **1.2** | User-generated content without a report / block / moderation mechanism |
|
|
52
|
+
| **1.6** | App Transport Security disabled app-wide (`NSAllowsArbitraryLoads`) |
|
|
53
|
+
| **2.1** | No placeholder / dummy copy (lorem ipsum, TODO, `example.com`) in store metadata |
|
|
54
|
+
| **2.1** | A login-gated app ships demo credentials / review notes for App Review |
|
|
55
|
+
| **2.3** | A working support URL and a privacy URL in fastlane metadata (no placeholders) — also satisfies 1.5 and 5.1.1(i) |
|
|
51
56
|
| **2.3.1** | Metadata length limits (name, subtitle, keywords, promo, description) |
|
|
52
|
-
| **2.3.
|
|
57
|
+
| **2.3.1** | Misleading marketing claims (iOS virus / malware scanners, fake speed boosters) in metadata |
|
|
53
58
|
| **2.3.3** | At least one screenshot per locale |
|
|
59
|
+
| **2.3.7** | Localized metadata parity across every locale |
|
|
60
|
+
| **2.3.8** | "For Kids" / "For Children" wording outside the Kids Category |
|
|
61
|
+
| **2.3.10** | No other-platform / competitor names in metadata |
|
|
62
|
+
| **2.5.1** | No private / banned APIs |
|
|
63
|
+
| **2.5.2** | No executable-code download / native hot-patching (JSPatch, Rollout, …) |
|
|
64
|
+
| **2.5.4** | Background modes declared in `UIBackgroundModes` but never used |
|
|
65
|
+
| **3.1.1** | Third-party payment SDK (Stripe, Braintree, PayPal, …) linked for digital goods instead of in-app purchase |
|
|
66
|
+
| **3.1.1(a)** | External purchase link entitlement + disclosure, when external purchase APIs are used |
|
|
54
67
|
| **3.1.2** | Trial & auto-renew subscription disclosures |
|
|
55
68
|
| **3.1.2** | Restore Purchases + Terms (EULA) + Privacy Policy on the paywall |
|
|
56
|
-
| **
|
|
57
|
-
| **4.
|
|
69
|
+
| **3.1.5(a)** | Cryptocurrency wallet / exchange / mining signal |
|
|
70
|
+
| **4.2** | Minimum functionality (real navigation) |
|
|
71
|
+
| **4.2.3** | Thin WKWebView wrapper around a website |
|
|
72
|
+
| **4.2.7** | Remote-desktop / host-mirroring app |
|
|
73
|
+
| **4.4.1** | Keyboard extension that requires full access (`RequestsOpenAccess`) |
|
|
74
|
+
| **4.4.2** | Safari content-blocker / web extension |
|
|
58
75
|
| **4.8** | Sign in with Apple offered when a third-party social login is used |
|
|
59
|
-
| **
|
|
60
|
-
| **5.1.
|
|
76
|
+
| **4.9** | Recurring Apple Pay (`PKRecurringPaymentRequest`) — verify the renewal / cancel disclosure |
|
|
77
|
+
| **5.1.1** | A non-empty purpose string for every sensitive framework |
|
|
78
|
+
| **5.1.1** | Analytics SDK present ↔ `PrivacyInfo.xcprivacy` declares collected data / tracking domains |
|
|
79
|
+
| **5.1.1** | Privacy Manifest ↔ Required Reason API parity |
|
|
80
|
+
| **5.1.1(v)** | Account creation offered without an in-app account-deletion path |
|
|
81
|
+
| **5.1.2** | ATT usage ↔ `NSUserTrackingUsageDescription` |
|
|
61
82
|
| **5.1.2** | Tracking / IDFA SDK (AdMob, AppLovin, AppsFlyer, Adjust, …) shipped without an ATT prompt |
|
|
83
|
+
| **5.1.3** | HealthKit data with an iCloud / CloudKit sync path |
|
|
84
|
+
| **5.1.4** | Kids-audience metadata shipping a third-party ads / analytics SDK |
|
|
85
|
+
| **5.1.5** | Sensitive-API justification *(opt-in)* |
|
|
86
|
+
| **5.3.4** | Real-money gambling language in metadata |
|
|
87
|
+
| **5.4** | VPN / NetworkExtension usage (org account + on-screen data disclosure) |
|
|
88
|
+
| **5.5** | Mobile Device Management (MDM) signal |
|
|
89
|
+
| **5.6.1** | A custom App Store review prompt instead of the system `requestReview` API |
|
|
62
90
|
| **encryption** | `ITSAppUsesNonExemptEncryption` set, so App Store Connect skips the export-compliance question |
|
|
63
|
-
| **2.3** | A working support URL and a privacy URL in fastlane metadata (no placeholders) |
|
|
64
|
-
| **5.1.1** | Analytics SDK present ↔ `PrivacyInfo.xcprivacy` declares collected data / tracking domains |
|
|
65
|
-
| **2.1** | No placeholder / dummy copy (lorem ipsum, TODO, `example.com`) in store metadata |
|
|
66
91
|
|
|
67
|
-
Paywall checks are skipped automatically when no in-app-purchase signals are present
|
|
92
|
+
Paywall checks are skipped automatically when no in-app-purchase signals are present, and the
|
|
93
|
+
signal-gated advisory checks stay silent unless their triggering signal is found.
|
|
94
|
+
|
|
95
|
+
### Pierre deep review (28 semantic checks)
|
|
96
|
+
|
|
97
|
+
After the static scan, Pierre reads your project end-to-end and runs **28 evidence-based checks**
|
|
98
|
+
the grep layer cannot fully judge. These emit advisory `REVIEW-FINDING:` lines (they do **not**
|
|
99
|
+
change the GREEN/YELLOW/RED verdict). Full procedure:
|
|
100
|
+
[`references/pierre-deep-review.md`](skills/appstore-precheck/references/pierre-deep-review.md).
|
|
101
|
+
|
|
102
|
+
**22 checks (Tier A)** are high-confidence cross-reads (privacy policy fetch, claims vs code,
|
|
103
|
+
screenshots, paywall copy). **6 checks (Tier B v1, marked †)** are heuristic — useful pre-submit
|
|
104
|
+
signals with a higher false-positive rate; Pierre prefers `not applicable` when no signal is present.
|
|
105
|
+
|
|
106
|
+
| Guideline | Deep check |
|
|
107
|
+
|-----------|------------|
|
|
108
|
+
| **1.2.1** | User-generated content → is there a real report / block / moderation UI flow? |
|
|
109
|
+
| **1.4.1** | Health or medical claims in metadata/UI without appropriate disclaimers? |
|
|
110
|
+
| **2.1** | Store metadata claims match features actually implemented in code |
|
|
111
|
+
| **2.1** † | App Review demo account / review notes actionable (credentials, steps — not placeholder) |
|
|
112
|
+
| **2.2** † | Beta, test, preview, or work-in-progress language in store-facing copy or UI |
|
|
113
|
+
| **2.3.2** | Primary App Store category fits the app type |
|
|
114
|
+
| **2.3.4** † | App preview video/assets (if in-repo) match shipped features and metadata |
|
|
115
|
+
| **2.3.5** | Screenshot images match shipped features (no misleading frames or missing UI) |
|
|
116
|
+
| **2.3.6** | Metadata pricing / subscription language matches the paywall |
|
|
117
|
+
| **2.3.9** † | Incentivized review copy ("rate 5 stars", "review for reward") in metadata or UI |
|
|
118
|
+
| **2.3.11–2.3.13** | Cross-locale metadata materially consistent (trial terms, features, URLs) |
|
|
119
|
+
| **3.1.1** | Digital goods unlocked via external purchase links (web checkout in WebView, etc.) |
|
|
120
|
+
| **3.1.2** | Trial, auto-renew, and cancel disclosures are legible sentences — not keyword stubs |
|
|
121
|
+
| **4.2.1–4.2.2** | Minimum functionality beyond a thin WebView shell or template app |
|
|
122
|
+
| **4.5.1–4.5.3** † | Push notification or HomeKit entitlement used as intended (no spam-push / HomeKit without home UI) |
|
|
123
|
+
| **4.8** | Third-party login → Sign in with Apple offered, or a valid exempt case |
|
|
124
|
+
| **5.1.1(i)** | Privacy policy text (fetched live) matches code, PrivacyInfo, and SDK usage |
|
|
125
|
+
| **5.1.1(ii)** | Purpose strings are specific and tied to a visible feature |
|
|
126
|
+
| **5.1.1(iii)** | Permissions and SDKs proportionate to the app's stated purpose |
|
|
127
|
+
| **5.1.1(iv)** | Permission denial handled without forced re-prompt loops |
|
|
128
|
+
| **5.1.2** | ATT prompt, tracking description, privacy policy, and ad SDK usage align |
|
|
129
|
+
| **5.1.3** | HealthKit data not used for advertising or marketing |
|
|
130
|
+
| **5.1.4** | Kids-audience signals → parental gate before external links / IAP / account areas |
|
|
131
|
+
| **5.4** | VPN / NetworkExtension → on-screen disclosure copy visible in UI strings |
|
|
132
|
+
| **5.2.1–5.2.3** | Obvious third-party trademark or brand misuse in metadata or UI copy |
|
|
133
|
+
| **5.3.1–5.3.3** | Contest / sweepstakes copy includes official rules and eligibility |
|
|
134
|
+
| **5.6.2–5.6.3** | Developer identity consistent (app name, support URL content, domains) |
|
|
135
|
+
| **5.6.4–5.6.7** † | Rating / review manipulation dark patterns (withhold features until 5 stars, write-review links without `requestReview`) |
|
|
136
|
+
|
|
137
|
+
Pierre runs **all 28 every time** and reports each as `REVIEW-PASS:` or `REVIEW-FINDING:`. When the
|
|
138
|
+
static scan already flagged a guideline, the deep check adds semantic context the scanner could not see.
|
|
139
|
+
† Tier B v1 items are heuristic — treat findings as "verify before submit", not automatic blockers.
|
|
140
|
+
|
|
141
|
+
### Supported app types
|
|
142
|
+
|
|
143
|
+
Every check that reads store metadata, the privacy manifest, screenshots, or the export-compliance
|
|
144
|
+
key works on **any iOS app**. The code-level checks read Swift source, so their coverage depends on
|
|
145
|
+
how the app is built:
|
|
146
|
+
|
|
147
|
+
| App type | Coverage |
|
|
148
|
+
|----------|----------|
|
|
149
|
+
| 🟢 **Native Swift / SwiftUI** | **Full.** All 41 vectors apply. |
|
|
150
|
+
| 🟡 **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. |
|
|
68
151
|
|
|
69
152
|
## Quick start
|
|
70
153
|
|
|
71
|
-
**Claude Code
|
|
154
|
+
One `SKILL.md`, every host. **Claude Code, Cursor, and Codex** install as native **plugins** from
|
|
155
|
+
this GitHub repo. **Gemini CLI** installs the same skill with `gemini skills install` (no plugin
|
|
156
|
+
marketplace yet). [`install.sh`](install.sh) remains the fallback for vendoring into a project.
|
|
157
|
+
|
|
158
|
+
### Install the full skill (Pierre + all phases)
|
|
159
|
+
|
|
160
|
+
| Host | Install method |
|
|
161
|
+
|------|----------------|
|
|
162
|
+
| **Claude Code** | Plugin (recommended) |
|
|
163
|
+
| **Cursor** | Plugin (recommended) |
|
|
164
|
+
| **OpenAI Codex** | Plugin (recommended) |
|
|
165
|
+
| **Gemini CLI** | `gemini skills install` one-liner |
|
|
166
|
+
|
|
167
|
+
**Claude Code:**
|
|
72
168
|
|
|
73
169
|
```
|
|
74
170
|
/plugin marketplace add berkayturk/appstore-precheck
|
|
75
171
|
/plugin install appstore-precheck@appstore-precheck
|
|
76
172
|
```
|
|
77
173
|
|
|
78
|
-
**
|
|
174
|
+
**Cursor** — import this repo as a marketplace, then install the plugin:
|
|
175
|
+
|
|
176
|
+
1. **Customize → Plugins → Import marketplace**
|
|
177
|
+
2. Repo URL: `https://github.com/berkayturk/appstore-precheck`
|
|
178
|
+
3. Install **appstore-precheck**
|
|
179
|
+
|
|
180
|
+
After the plugin is listed on the [Cursor Marketplace](https://cursor.com/marketplace), you can
|
|
181
|
+
install from **Customize → Plugins** without importing the repo.
|
|
182
|
+
|
|
183
|
+
**OpenAI Codex:**
|
|
79
184
|
|
|
80
185
|
```bash
|
|
81
|
-
|
|
82
|
-
|
|
186
|
+
codex plugin marketplace add berkayturk/appstore-precheck
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Then run `codex`, open `/plugins`, select the **appstore-precheck** marketplace tab, and install
|
|
190
|
+
**appstore-precheck**. Start a new thread after install.
|
|
191
|
+
|
|
192
|
+
On newer Codex builds you may also be able to run:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
codex plugin add appstore-precheck@appstore-precheck
|
|
83
196
|
```
|
|
84
197
|
|
|
85
|
-
|
|
86
|
-
`--fail-on YELLOW`). Read-only, like everything else here.
|
|
198
|
+
Refresh after repo updates:
|
|
87
199
|
|
|
88
|
-
|
|
200
|
+
```bash
|
|
201
|
+
codex plugin marketplace upgrade appstore-precheck
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
**Gemini CLI** (native skill install — no plugin marketplace):
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
gemini skills install https://github.com/berkayturk/appstore-precheck.git \
|
|
208
|
+
--path skills/appstore-precheck --scope workspace
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Use `--scope user` for a global install. Verify with `gemini skills list`.
|
|
212
|
+
|
|
213
|
+
**Fallback — `install.sh`** (vendors the skill into `.claude/skills/` and/or `.agents/skills/`):
|
|
89
214
|
|
|
90
215
|
```bash
|
|
91
216
|
git clone https://github.com/berkayturk/appstore-precheck.git
|
|
92
|
-
|
|
217
|
+
cd your-ios-app
|
|
218
|
+
/path/to/appstore-precheck/install.sh # all hosts → .claude/skills + .agents/skills
|
|
219
|
+
/path/to/appstore-precheck/install.sh cursor # Cursor only
|
|
220
|
+
/path/to/appstore-precheck/install.sh codex # Codex only
|
|
221
|
+
/path/to/appstore-precheck/install.sh gemini # Gemini only
|
|
222
|
+
/path/to/appstore-precheck/install.sh claude user # Claude Code user-wide → ~/.claude/skills
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Then ask your agent to run the precheck before you submit. See [Cross-tool support](#cross-tool-support)
|
|
226
|
+
for which directory each host reads. Maintainer notes for marketplace submission:
|
|
227
|
+
[`docs/publishing-plugins.md`](docs/publishing-plugins.md).
|
|
228
|
+
|
|
229
|
+
### Scan only (no agent skill install)
|
|
230
|
+
|
|
231
|
+
**npx** — static scan + verdict in the terminal; no clone, no skill vendoring (Phases 3–4 Pierre
|
|
232
|
+
commentary require an agent with the skill installed):
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
npx appstore-precheck # scans the current directory, prints the verdict
|
|
236
|
+
npx appstore-precheck --fail-on YELLOW
|
|
93
237
|
```
|
|
94
238
|
|
|
95
|
-
**Standalone
|
|
239
|
+
**Standalone Bash** — same static scan, run by hand or in CI:
|
|
96
240
|
|
|
97
241
|
```bash
|
|
98
|
-
bash skills/appstore-precheck/scripts/scan.sh
|
|
242
|
+
bash skills/appstore-precheck/scripts/scan.sh # from a clone of this repo
|
|
99
243
|
```
|
|
100
244
|
|
|
101
|
-
|
|
245
|
+
### CI
|
|
246
|
+
|
|
247
|
+
Drop the static scan into a workflow with the bundled composite action. It fails the
|
|
102
248
|
job on a RED verdict; set `fail-on: YELLOW` to be stricter:
|
|
103
249
|
|
|
104
250
|
```yaml
|
|
105
|
-
- uses: berkayturk/appstore-precheck@v1.
|
|
251
|
+
- uses: berkayturk/appstore-precheck@v1.5.0
|
|
106
252
|
with:
|
|
107
|
-
working-directory: . # optional
|
|
108
|
-
fail-on: RED # optional (RED | YELLOW)
|
|
253
|
+
working-directory: . # optional, default: . (repo root)
|
|
254
|
+
fail-on: RED # optional, default: RED (RED | YELLOW)
|
|
109
255
|
```
|
|
110
256
|
|
|
257
|
+
Both inputs are optional: with none set, the action scans the repo root and fails the job only on
|
|
258
|
+
a RED verdict. No App Store Connect credentials are needed; the action runs the static scan only.
|
|
259
|
+
|
|
111
260
|
## How it works
|
|
112
261
|
|
|
113
262
|
| Phase | Step |
|
|
114
263
|
|-------|------|
|
|
115
264
|
| **0** | **Guideline drift**: diff the live App Store Review Guidelines against a tracked baseline. Never blocks. |
|
|
116
|
-
| **1** | **Static scan**: `scan.sh` over the
|
|
265
|
+
| **1** | **Static scan**: `scan.sh` over the 41 vectors above. |
|
|
117
266
|
| **2** | **`fastlane precheck`**: Apple's own metadata rule engine. |
|
|
118
|
-
| **3** | **
|
|
119
|
-
| **4** | **
|
|
267
|
+
| **3** | **Pierre commentary**: explains **every** FAIL and WARN from Phases 0–2 in 2–3 sentences each. |
|
|
268
|
+
| **4** | **Pierre deep review**: 28 semantic checks (22 Tier A + 6 Tier B v1 heuristic). Advisory only. |
|
|
269
|
+
| **5** | **Verdict**: GREEN / YELLOW / RED from Phases 0–2 counts, plus `.precheck-pass` token the upload guard gates on. |
|
|
120
270
|
|
|
121
271
|
## Demo
|
|
122
272
|
|
|
@@ -129,25 +279,45 @@ counts are deterministic ([`verdict.sh`](skills/appstore-precheck/scripts/verdic
|
|
|
129
279
|
|
|
130
280
|
## Example
|
|
131
281
|
|
|
282
|
+
### Pierre
|
|
283
|
+
|
|
284
|
+
**Français**
|
|
285
|
+
> *Non. Trois fautes. Apple en aurait trouvé moins. Suivant.*
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
**English**
|
|
290
|
+
> *No. Three faults. Apple would have found fewer. Next.*
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
**Türkçe**
|
|
295
|
+
> *Hayır. Üç hata. Apple daha azını bulurdu. Sıradaki.*
|
|
296
|
+
|
|
297
|
+
**RED — 3 FAIL** · submission blocked
|
|
298
|
+
|
|
299
|
+
```
|
|
300
|
+
FAIL: 3.1.2 Restore Purchases — not found in SubscriptionView.swift
|
|
301
|
+
Pierre: Apple requires a Restore Purchases control on every subscription paywall …
|
|
132
302
|
```
|
|
133
|
-
🔴 Pierre: "Non. Three faults. Apple would have found one. Suivant."
|
|
134
303
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
304
|
+
**Phase 4 (deep review, excerpt):**
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
REVIEW-FINDING: 5.1.1(i) WARN — privacy policy says "no location data" but ContentView.swift uses CLLocationManager
|
|
308
|
+
Pierre: Apple compares your privacy policy to actual SDK and permission usage under 5.1.1(i) …
|
|
140
309
|
```
|
|
141
310
|
|
|
142
|
-
See [`examples/`](examples/) for full [GREEN](examples/green-pass.md)
|
|
143
|
-
|
|
311
|
+
See [`examples/`](examples/) for full [GREEN](examples/green-pass.md), [RED](examples/red-reject.md), and
|
|
312
|
+
[deep-review](examples/pierre-deep-review.md) runs, plus real [Phase 0 drift-check](examples/drift-check.md)
|
|
313
|
+
and [Phase 2 `fastlane precheck`](examples/fastlane-precheck.md) results.
|
|
144
314
|
|
|
145
315
|
## Output
|
|
146
316
|
|
|
147
317
|
| State | Meaning | Token | Upload guard |
|
|
148
318
|
|-------|---------|-------|--------------|
|
|
149
|
-
| 🟢 **GREEN** | 0 FAIL, ≤
|
|
150
|
-
| 🟡 **YELLOW** | 0 FAIL,
|
|
319
|
+
| 🟢 **GREEN** | 0 FAIL, ≤4 WARN | written (60 min) | allowed |
|
|
320
|
+
| 🟡 **YELLOW** | 0 FAIL, 5+ WARN | not written | blocked; needs confirmation |
|
|
151
321
|
| 🔴 **RED** | ≥1 FAIL | removed | blocked; shows the FAIL list |
|
|
152
322
|
|
|
153
323
|
## Configuration
|
|
@@ -177,8 +347,13 @@ App Store apps (DuckDuckGo, Pocket Casts, Wikipedia).
|
|
|
177
347
|
|
|
178
348
|
## Requirements
|
|
179
349
|
|
|
180
|
-
`bash`, `git`, `grep`, `find`
|
|
181
|
-
|
|
350
|
+
`bash`, `git`, `grep`, `find` are all the static scan, `npx appstore-precheck`, and the GitHub
|
|
351
|
+
Action need, and they run with **zero credentials and no network**. `jq` (config + String Catalog
|
|
352
|
+
checks) and `python3` (exact Unicode length counts) are optional and sharpen a few checks.
|
|
353
|
+
|
|
354
|
+
Only the **optional Phase 2** (`fastlane precheck`) needs `fastlane` and an
|
|
355
|
+
[App Store Connect API key](https://developer.apple.com/documentation/appstoreconnectapi/creating-api-keys-for-app-store-connect-api).
|
|
356
|
+
Everything else works without one.
|
|
182
357
|
|
|
183
358
|
**Secrets**: the ASC API key is read from your environment at runtime and deleted immediately after
|
|
184
359
|
`fastlane precheck`. Never commit it; `.gitignore` blocks `*asc-key*.json` and `.env`.
|
|
@@ -186,16 +361,22 @@ length counts) · `fastlane` + an App Store Connect API key for Phase 2 only.
|
|
|
186
361
|
## Uninstall
|
|
187
362
|
|
|
188
363
|
```bash
|
|
189
|
-
/plugin uninstall appstore-precheck@appstore-precheck
|
|
190
|
-
|
|
191
|
-
|
|
364
|
+
/plugin uninstall appstore-precheck@appstore-precheck # Claude Code plugin
|
|
365
|
+
# Codex: run `codex`, open `/plugins`, uninstall appstore-precheck (CLI 0.125.x has no `plugin remove`)
|
|
366
|
+
# Cursor: Customize → Plugins → appstore-precheck → Uninstall
|
|
367
|
+
gemini skills uninstall appstore-precheck # Gemini (if installed via gemini skills)
|
|
368
|
+
rm -rf .claude/skills/appstore-precheck # install.sh (Claude Code / Cursor)
|
|
369
|
+
rm -rf .agents/skills/appstore-precheck # install.sh (Codex / Cursor / Gemini)
|
|
370
|
+
rm -rf .cursor/skills/appstore-precheck # if you mirrored there manually
|
|
371
|
+
rm -rf .gemini/skills/appstore-precheck # if installed to workspace scope manually
|
|
372
|
+
rm -f .precheck-pass # runtime token
|
|
192
373
|
```
|
|
193
374
|
|
|
194
375
|
## Development
|
|
195
376
|
|
|
196
377
|
```bash
|
|
197
378
|
npm run lint # bash -n on every script
|
|
198
|
-
npm run check-versions # plugin
|
|
379
|
+
npm run check-versions # plugin manifests / package.json / SKILL.md in lockstep
|
|
199
380
|
npm test # run scan.sh against fixture projects and assert
|
|
200
381
|
claude plugin validate .
|
|
201
382
|
```
|
|
@@ -204,9 +385,11 @@ CI runs ShellCheck, JSON validation, the version-consistency guard, and the fixt
|
|
|
204
385
|
|
|
205
386
|
## Disclaimer
|
|
206
387
|
|
|
207
|
-
This is a static heuristic tool. A GREEN result **lowers but does
|
|
208
|
-
Apple's guidelines change frequently and reviewer decisions are
|
|
209
|
-
|
|
388
|
+
This is a static heuristic tool plus Pierre's semantic deep review. A GREEN result **lowers but does
|
|
389
|
+
not eliminate** rejection risk; Apple's guidelines change frequently and reviewer decisions are
|
|
390
|
+
judgment calls. `REVIEW-FINDING` lines are advisory and do not block the token by themselves. It
|
|
391
|
+
performs no runtime crash testing; always do a final manual review before you submit. Not affiliated
|
|
392
|
+
with Apple.
|
|
210
393
|
|
|
211
394
|
## Star History
|
|
212
395
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appstore-precheck",
|
|
3
|
-
"version": "1.1
|
|
4
|
-
"description": "Read-only iOS App Store pre-submission check, packaged as a portable Agent Skill
|
|
3
|
+
"version": "1.5.1",
|
|
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)",
|
|
7
7
|
"homepage": "https://github.com/berkayturk/appstore-precheck",
|