About aso-analysis
An AppFollow skill for app store optimization analysis, intended for teams managing app keywords and metadata. It assesses keyword effectiveness, search results, and rankings, and requires explicit selection before changing tracked keywords.
Key features
- 01Analyze keyword positions and effectiveness
- 02Group tracked keywords by effectiveness tier
- 03Compare search results and competitor gaps
- 04Suggest metadata optimizations
- 05Produce HTML reports and findings.json
Use cases
- 01Review app keyword rankings and effectiveness
- 02Find tracked terms to retain or skip
- 03Compare search visibility with competitors
- 04Develop metadata suggestions for store listings
MCP support
The skill uses AppFollow MCP tools to read keyword, ranking, search, and rating data. It can replace a tracked keyword set only after explicit user approval.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ appfollow/ appfollow-ai-toolkit --skill aso-analysisAfter installing, type/aso-analysisin Claude Code to confirm it loaded.
Bundled filesreferences/report-template.html
Details
From the project READMEaso-analysis
Read using-appfollow first (session, write ritual, error codes).
Inputs
- scope: app, whole collection, or a user-chosen subset of apps from one collection (e.g. "just
these 3 apps from my "). For a subset: resolve the collection via
list_collections, list its apps vialist_apps, then gather ASO data for only the chosen apps — emitscope.type: "collection_subset"withcollection_nameset andapps= the chosen subset (not the full collection). Spanning multiple collections in one report is not supported yet (deferred). - countries: default 1 primary.
- devices: App Store →
iphone,ipad; Google Play →android. - keyword seeds (optional).
Default depth
Estimate ≈ A×C×D×[S×P×12 (list_keywords) + S×12 (get_rankings)] + A×C×[K_seed×12 (suggestions) + K_serp×12 (search)] + A(as)×12 (search_ads) + A×12 (aso report, optional). Default = deep: max
S=4 trend sample-dates (today / −7d / −14d / −28d), max K_serp=10. A single app/country/device at
deep with one keyword page and 10 seed suggestions ≈ 360 cr (with full trend sampling and the ASO
report). quick/standard only on explicit user request (degrade S 4→2, K_serp 10→5).
Trend section — verify-gated (smoke gate iv)
The trend features (per-sample-date keyword positions and category rank over S = today, −7d, −14d, −28d) depend on list_keywords / get_rankings with date=<past> returning a true historical
snapshot. This is gated on smoke gate iv. Build the trend section only if gate iv passed. If it
did not, ship current-snapshot-only (buckets TOP3 / TOP10 / TOP30 / TOP100 / UNRANKED,
effectiveness tiers, competitor gaps); the HTML/method footer states plainly that trend-over-time is
unavailable pending a historical endpoint. Never re-read today's data under a past-date label.
Add-keyword loop (the ONLY write path)
- Propose a table of ≤15 candidate keywords; the user explicitly selects (never auto-add).
edit_keywordsREPLACES the tracked keyword set for(apps_id, country, device)— any keyword not in the CSV is dropped. Never send only the new candidates.- Per
(country, device): read the current set withlist_keywords({ext_id, country, device})withoutdate(today), paging until exhausted. Read it fresh before each preview, even if an earlier read exists: after anyedit_keywordson the same(apps_id, country, device)the old read is stale and would drop what that edit added. Current set = every tracked keyword in the response, ranked AND unranked (no_pos) — dropping the unranked ones deletes them on REPLACE. Any page fails → stop, no write. - CSV = current set ∪ selected (dedupe, case-insensitive).
edit_keywords_preview({country, device, keywords CSV, apps_id})— always passapps_idexplicitly (omitting it defaults to the account's first collection — a wrong-collection hazard). A size refusal (VALIDATION_ERRORnaming a byte limit) → stop and tell the user. Never split the set across calls (each call replaces the set; only the last chunk would survive) and never trim current keywords to fit unless the user names which to drop (then list them as dropped). - Show what the call replaces: collection, current keywords kept (count), added list, dropped = none.
list_keywordsreads byext_idand does not say which collection it read. Checklist_collections+list_apps: if the app sits in more than one collection, the current set may belong to another collection — do not claim dropped = none; say so, and write only on the user's explicit acceptance (otherwise stop). Get approval. - Before
edit_keywords, re-readlist_keywordsas in step 3 (no upstream revision check exists). If the set changed since step 3, go back to step 4 (rebuild, re-preview, re-approve). Thenedit_keywordswith verbatim args + the token. OnCONFIRMATION_REQUIRED→ re-preview. edit_keywordsis a Keywords-Edit action (not Advanced-gated), so this works on Keywords-Edit plans. Positions appear next parse cycle; offer an optional live SERP spot-check of ≤3 keywords. Applied batches → findingsstatus:"applied".
Metadata optimization — honest read ladder (suggestions only; no metadata write exists)
Most-reliable last:
get_app_metadata— only if the session advertises it (probe). Treat as an unverified passthrough until confirmed.- Own SERP card via
aso_search_results(title / subtitle / genre; no description) — also unverified. - User paste — the only reliable current-metadata source today. The App Store keyword field is developer-private: always pasted or skipped.
Output per field: CURRENT → SUGGESTED + keywords gained/lost + rationale + localization risk + "apply in App Store Connect / Play Console."
Keyword effectiveness tiers (print the real definitions verbatim in the footer)
Tier every tracked keyword by the real list_keywords.effectiveness field only — WORTH IT
(effectiveness > 20) vs SKIP (effectiveness <= 20); sort by effectiveness descending within
each tier. effectiveness (0–100) is AppFollow's Keyword Effectiveness Index weighing popularity
against competition; above 20 is worth putting in the app title/subtitle (see the support article
below).
Show popularity and difficulty as informational context columns only — they never drive the
tier:
- Popularity (0–100): search-traffic score. Scale is exponential/non-linear; AppFollow doesn't recommend keywords scoring below ~20.
- Difficulty (0–100): how hard the keyword is to rank Top-10 for. Higher = tougher.
There is no public composite formula. Effectiveness, popularity, and difficulty are
AppFollow-computed values surfaced as-is from list_keywords — state this plainly in the report.
Link the reader to the support article for what each score means:
https://support.appfollow.io/hc/en-us/articles/360020831817-Keyword-Tracking.
list_keywords.score (usually null, an undocumented secondary "AF score") is shown as a secondary
display value only and never feeds the tier. Every finding carries expected_impact{metric, direction, magnitude, confidence, basis:"effectiveness-v1"} and reproducible
evidence[]{source:"mcp:<tool>", args, observed, at}.
Optional tools
aso_featured_apps is HOLD until its ext_id schema fix lands (expect VALIDATION_ERROR before then —
mark the section n/a). get_aso_report_app_store / get_aso_report_gdc_google_play are optional; on any
error mark the section "n/a".
Emit
Write aso-analysis-<scope>-<date>.html (fill references/report-template.html) + findings.json
(appfollow.toolkit.findings/v1, report_type:"aso"; validate against schemas/findings.schema.json
before writing). On Claude Code publish the artifact; on Codex write the files and state the path. Tell
the user their own connected agent can file the findings into their tracker.