$npx -y skills add Varnan-Tech/opendirectory --skill app-store-review-arbitrageFetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief with positioning opportunities.
| 1 | # app-store-review-arbitrage |
| 2 | |
| 3 | Convert a competitor's App Store or Google Play URL into a one-session GTM brief: ranked complaint clusters, a broken promise map, landing page headlines, and ad copy directions — all sourced from verbatim reviews. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## Critical Rules (read before Step 1) |
| 8 | |
| 9 | These rules apply throughout all steps. Violating any of them fails Self-QA (Step 6). |
| 10 | |
| 11 | 1. **Every quote must be verbatim.** No paraphrase, no grammar correction, no cleaning. Exact reviewer words only. |
| 12 | 2. **No fabricated statistics.** Do not write "40% faster" or "2× more reliable" unless a reviewer explicitly used similar language. The Self-QA step checks for uncited percentages. |
| 13 | 3. **Cluster names must use reviewer language.** Study the anti-pattern table in Step 3. |
| 14 | 4. **Every headline and ad copy direction must cite its source cluster.** Format: `[cluster: "cluster-name"]`. |
| 15 | 5. **Section 2 is always present** in the output — even when degraded. Never skip or omit it. |
| 16 | 6. **No banned words** in any generated copy: powerful, robust, seamless, innovative, game-changing, streamline, leverage, revolutionize, transform. |
| 17 | |
| 18 | --- |
| 19 | |
| 20 | ## Step 1 — Parse Input and Detect Platform |
| 21 | |
| 22 | Accept a natural language prompt containing one app URL. Extract the URL. |
| 23 | |
| 24 | **Platform detection:** |
| 25 | - `apps.apple.com` → App Store |
| 26 | - `play.google.com/store/apps/details?id=` → Google Play |
| 27 | - Any other URL → stop and respond: "Please provide a direct App Store or Google Play URL. I can't analyse review data from other sources." |
| 28 | |
| 29 | **ID extraction (do this before calling the script):** |
| 30 | |
| 31 | | Platform | What to extract | How | |
| 32 | |---|---|---| |
| 33 | | App Store | Numeric `app_id` | Digits after `/id` in the URL | |
| 34 | | App Store | `country` | 2-letter code after `apps.apple.com/` (e.g., `us`, `gb`) | |
| 35 | | Google Play | `package_name` | Value of `id=` query parameter | |
| 36 | |
| 37 | Persist the extracted values — you will need them for the output filename in Step 7. |
| 38 | |
| 39 | If `product_context` was provided in the user's prompt (what their own product does), store it — used to personalise copy in Step 5. |
| 40 | |
| 41 | --- |
| 42 | |
| 43 | ## Step 2 — Collect Reviews & Metadata |
| 44 | |
| 45 | Run the full fetch script: |
| 46 | |
| 47 | ```bash |
| 48 | python3 scripts/fetch_reviews.py "{app_url}" --output {tmpdir}/asr-raw.json |
| 49 | ``` |
| 50 | *(Note: Replace `{tmpdir}` with your operating system's temp directory, e.g., `/tmp` on macOS/Linux or `C:\Temp` on Windows).* |
| 51 | |
| 52 | This fetches both the store description metadata and the reviews. |
| 53 | - **App Store:** iTunes API — free, no auth. App Store reviews are fetched via Apple's public iTunes RSS feed. Some apps return 0 reviews due to Apple's API limitations — in that case the skill continues with available data and logs a warning. Google Play is the primary supported path. |
| 54 | - **Google Play:** `google-play-scraper` package — free, no auth |
| 55 | |
| 56 | If the script fails, read the error from stderr. Common causes: |
| 57 | - Package not installed: run `pip install google-play-scraper` |
| 58 | - App not found: verify the URL is a current, live listing |
| 59 | - Google Play API error: run `pip install --upgrade google-play-scraper` and retry |
| 60 | |
| 61 | The script will print collection progress to stderr. Wait for it to complete. After completion, read `{tmpdir}/asr-raw.json` and display the collection summary to the user: |
| 62 | ``` |
| 63 | ✓ Collected [N] reviews ([N] low-star 1–3★) from [platform] |
| 64 | Date range: [oldest] to [newest] |
| 65 | Package: [iTunes API | google-play-scraper] |
| 66 | ``` |
| 67 | |
| 68 | **Check the exit code:** |
| 69 | - Exit 0 → collection succeeded, check `metadata.store_description`. If null: note this — Section 2 will use the degraded state. Proceed to Step 3. |
| 70 | - Exit 1 → error (read stderr message, surface it to user, stop) |
| 71 | - Exit 2 → **Gate 1 triggered** (< 10 low-star reviews found) |
| 72 | |
| 73 | **Gate 1 — Low signal stop:** If the script exits with code 2, read the `gate_message` from `{tmpdir}/asr-raw.json` and surface it to the user verbatim. Do not proceed to Step 3. Do not produce a partial brief. |
| 74 | |
| 75 | --- |
| 76 | |
| 77 | ## Step 3 — Complaint Clustering |
| 78 | |
| 79 | Load `low_star_reviews` from `{tmpdir}/asr-raw.json`. |
| 80 | |
| 81 | Cluster all low-star reviews into **4–6 named complaint themes.** Apply this formula to score each review: |
| 82 | |
| 83 | ``` |
| 84 | complaint_weight = (4 - rating) × recency_factor |
| 85 | |
| 86 | recency_factor: |
| 87 | review age ≤ 90 days → 1.0 |
| 88 | review age 91–365 days → 0.7 |
| 89 | review age > 365 days → 0.4 |
| 90 | ``` |
| 91 | |
| 92 | `review age` = (today's date) − (review `date` field) in days. |
| 93 | |
| 94 | `cluster_score` = sum of `complaint_weight` for all reviews in the cluster. |
| 95 | |
| 96 | **Cluster naming — critical rule:** |
| 97 | |
| 98 | You will want to write abstract names. Resist. Use the exact verb and noun from reviews. |
| 99 | |
| 100 | | ❌ Abstracted (wrong) | ✅ Reviewer language (correct) | |
| 101 | |---|---| |
| 102 | | "Stability issues" | "Crashes when exporting to PDF" | |
| 103 | | "Sync problems" | "Data lost after sync between phone and desktop" | |
| 104 | | "Monetisation fricti |