pabal-web-mcp 1.3.10 → 1.3.11

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.
@@ -1215,13 +1215,30 @@ function generatePrimaryOptimizationPrompt(args) {
1215
1215
  prompt += `Optimize the PRIMARY locale (${primaryLocale}) using **saved keyword research** + full ASO field optimization.
1216
1216
 
1217
1217
  `;
1218
- prompt += `## Step 1: Keyword Research (${primaryLocale})
1218
+ prompt += `## Step 1: Use Saved Keyword Research (${primaryLocale})
1219
1219
 
1220
1220
  `;
1221
1221
  const researchSections = keywordResearchByLocale[primaryLocale] || [];
1222
1222
  const researchDir = keywordResearchDirByLocale[primaryLocale];
1223
1223
  if (researchSections.length > 0) {
1224
- prompt += `Use the **saved keyword research below**. Do NOT invent new keywords. Choose the top 10 from the recommended set.
1224
+ prompt += `**CRITICAL: Use ONLY the saved keyword research below. Do NOT invent or research new keywords.**
1225
+
1226
+ `;
1227
+ prompt += `The research data includes:
1228
+ `;
1229
+ prompt += `- **Tier 1 (Core):** Use these in title and subtitle - highest traffic, best opportunity
1230
+ `;
1231
+ prompt += `- **Tier 2 (Feature):** Use these in keywords field and descriptions
1232
+ `;
1233
+ prompt += `- **Tier 3 (Longtail):** Use these in intro, outro, and feature descriptions
1234
+ `;
1235
+ prompt += `- **Keyword Details:** Each keyword has traffic/difficulty scores and rationale - use this to prioritize
1236
+ `;
1237
+ prompt += `- **Strategy:** Overall optimization strategy based on competitor analysis
1238
+ `;
1239
+ prompt += `- **Keyword Gaps:** Opportunities where competitors are weak
1240
+ `;
1241
+ prompt += `- **User Language Patterns:** Phrases real users use in reviews - incorporate naturally
1225
1242
 
1226
1243
  `;
1227
1244
  prompt += `Saved research:
@@ -1238,46 +1255,60 @@ ${researchSections.join("\n")}
1238
1255
  prompt += `## Step 2: Optimize All Fields (${primaryLocale})
1239
1256
 
1240
1257
  `;
1241
- prompt += `Apply the selected keywords to ALL fields:
1258
+ prompt += `**Apply keywords strategically based on tier priority:**
1259
+
1260
+ `;
1261
+ prompt += `### Tier 1 Keywords (Core) \u2192 Title & Subtitle
1242
1262
  `;
1243
- prompt += `- \`aso.title\` (\u226430): **"App Name: Primary Keyword"** format (app name in English, keyword in target language, keyword starts with uppercase after the colon)
1263
+ prompt += `- \`aso.title\` (\u226430): **"App Name: [Tier1 Keyword]"** format
1244
1264
  `;
1245
- prompt += ` - **Do NOT translate/rename the app name**; keep the original English app name across all locales.
1265
+ prompt += ` - App name in English, keyword in target language, uppercase after colon
1246
1266
  `;
1247
- prompt += `- \`aso.subtitle\` (\u226430): Complementary keywords
1267
+ prompt += ` - **Do NOT translate/rename the app name**
1248
1268
  `;
1249
- prompt += `- \`aso.shortDescription\` (\u226480): Primary keywords (no emojis/CAPS)
1269
+ prompt += `- \`aso.subtitle\` (\u226430): Use remaining Tier 1 keywords
1250
1270
  `;
1251
- prompt += `- \`aso.keywords\` (\u2264100): Comma-separated 10 keywords
1271
+ prompt += `- \`aso.shortDescription\` (\u226480): Tier 1 + Tier 2 keywords (no emojis/CAPS)
1272
+
1273
+ `;
1274
+ prompt += `### Tier 2 Keywords (Feature) \u2192 Keywords Field & Descriptions
1252
1275
  `;
1253
- prompt += `- \`aso.template.intro\` (\u2264300): Keyword-rich, use full length
1276
+ prompt += `- \`aso.keywords\` (\u2264100): ALL tiers, comma-separated (Tier 1 first, then Tier 2, then Tier 3)
1254
1277
  `;
1255
- prompt += `- \`aso.template.outro\` (\u2264200): Natural keyword integration
1278
+ prompt += `- \`landing.hero.title\`: Tier 1 + Tier 2 keywords
1256
1279
  `;
1257
- prompt += `- \`landing.hero.title\`: Primary keywords
1280
+ prompt += `- \`landing.hero.description\`: Tier 2 keywords naturally integrated
1258
1281
  `;
1259
- prompt += `- \`landing.hero.description\`: Keywords if present
1282
+ prompt += `- \`landing.screenshots.images[].title\`: Tier 2 keywords
1260
1283
  `;
1261
- prompt += `- \`landing.screenshots.images[].title\`: Keywords in screenshot titles
1284
+ prompt += `- \`landing.screenshots.images[].description\`: Tier 2 + Tier 3 keywords
1285
+
1262
1286
  `;
1263
- prompt += `- \`landing.screenshots.images[].description\`: Keywords in screenshot descriptions
1287
+ prompt += `### Tier 3 Keywords (Longtail) \u2192 Content Sections
1264
1288
  `;
1265
- prompt += `- \`landing.features.items[].title\`: Keywords in feature titles
1289
+ prompt += `- \`aso.template.intro\` (\u2264300): Tier 2 + Tier 3 keywords, keyword-rich, use full length
1266
1290
  `;
1267
- prompt += `- \`landing.features.items[].body\`: Keywords in feature descriptions
1291
+ prompt += `- \`aso.template.outro\` (\u2264200): Tier 3 keywords, natural integration
1268
1292
  `;
1269
- prompt += `- \`landing.reviews.title\`: Keywords if applicable
1293
+ prompt += `- \`landing.features.items[].title\`: Tier 2 keywords
1270
1294
  `;
1271
- prompt += `- \`landing.reviews.description\`: Keywords if applicable
1295
+ prompt += `- \`landing.features.items[].body\`: Tier 3 keywords with user language patterns
1272
1296
  `;
1273
- prompt += `- \`landing.cta.headline\`: Keywords if applicable
1297
+ prompt += `- \`landing.reviews.title/description\`: Keywords if applicable
1274
1298
  `;
1275
- prompt += `- \`landing.cta.description\`: Keywords if applicable
1299
+ prompt += `- \`landing.cta.headline/description\`: Keywords if applicable
1300
+
1301
+ `;
1302
+ prompt += `### User Language Integration
1303
+ `;
1304
+ prompt += `- Use **User Language Patterns** from research in intro/outro/features
1305
+ `;
1306
+ prompt += `- These are actual phrases users search for - incorporate naturally
1276
1307
 
1277
1308
  `;
1278
1309
  prompt += `**Guidelines**: 2.5-3% keyword density, natural flow, cultural appropriateness
1279
1310
  `;
1280
- prompt += `**CRITICAL**: You MUST include the complete \`landing\` object in your optimized JSON output, with all screenshots, features, reviews, and cta sections properly translated and keyword-optimized.
1311
+ prompt += `**CRITICAL**: You MUST include the complete \`landing\` object in your optimized JSON output.
1281
1312
 
1282
1313
  `;
1283
1314
  prompt += `## Step 3: Validate
@@ -1622,13 +1653,46 @@ function extractRecommended(data) {
1622
1653
  const summary = data?.summary || data?.data?.summary;
1623
1654
  const recommended = summary?.recommendedKeywords;
1624
1655
  if (Array.isArray(recommended)) {
1625
- return recommended.map(String);
1626
- }
1627
- if (typeof recommended === "string") {
1628
- return [recommended];
1656
+ return recommended.map((item) => {
1657
+ if (typeof item === "object" && item?.keyword) {
1658
+ return {
1659
+ keyword: String(item.keyword),
1660
+ tier: String(item.tier || ""),
1661
+ difficulty: Number(item.difficulty) || 0,
1662
+ traffic: Number(item.traffic) || 0,
1663
+ rationale: String(item.rationale || "")
1664
+ };
1665
+ }
1666
+ if (typeof item === "string") {
1667
+ return { keyword: item, tier: "", difficulty: 0, traffic: 0, rationale: "" };
1668
+ }
1669
+ return null;
1670
+ }).filter((item) => item !== null);
1629
1671
  }
1630
1672
  return [];
1631
1673
  }
1674
+ function extractKeywordsByTier(data) {
1675
+ const summary = data?.summary || data?.data?.summary;
1676
+ const byTier = summary?.keywordsByTier || {};
1677
+ const extractKeywords = (tier) => Array.isArray(tier) ? tier.map((k) => typeof k === "object" ? k.keyword : String(k)).filter(Boolean) : [];
1678
+ return {
1679
+ tier1_core: extractKeywords(byTier.tier1_core),
1680
+ tier2_feature: extractKeywords(byTier.tier2_feature),
1681
+ tier3_longtail: extractKeywords(byTier.tier3_longtail)
1682
+ };
1683
+ }
1684
+ function extractRationale(data) {
1685
+ const summary = data?.summary || data?.data?.summary;
1686
+ return summary?.rationale || "";
1687
+ }
1688
+ function extractCompetitorInsights(data) {
1689
+ const summary = data?.summary || data?.data?.summary;
1690
+ const insights = summary?.competitorInsights || {};
1691
+ return {
1692
+ keywordGaps: Array.isArray(insights.keywordGaps) ? insights.keywordGaps : [],
1693
+ userLanguagePatterns: Array.isArray(insights.userLanguagePatterns) ? insights.userLanguagePatterns : []
1694
+ };
1695
+ }
1632
1696
  function extractMeta(data) {
1633
1697
  const meta = data?.meta || data?.data?.meta || {};
1634
1698
  return {
@@ -1642,31 +1706,170 @@ function formatEntry(entry) {
1642
1706
  const { filePath, data } = entry;
1643
1707
  const recommended = extractRecommended(data);
1644
1708
  const meta = extractMeta(data);
1709
+ const byTier = extractKeywordsByTier(data);
1710
+ const rationale = extractRationale(data);
1711
+ const insights = extractCompetitorInsights(data);
1645
1712
  if (data?.parseError) {
1646
1713
  return `File: ${filePath}
1647
1714
  Parse error: ${data.parseError}
1648
1715
  ----`;
1649
1716
  }
1650
1717
  const lines = [];
1651
- lines.push(`File: ${filePath}`);
1718
+ lines.push(`### File: ${filePath}`);
1652
1719
  if (meta.platform || meta.country) {
1653
1720
  lines.push(
1654
1721
  `Platform: ${meta.platform || "unknown"} | Country: ${meta.country || "unknown"}`
1655
1722
  );
1656
1723
  }
1657
- if (meta.seedKeywords?.length) {
1658
- lines.push(`Seeds: ${meta.seedKeywords.join(", ")}`);
1724
+ if (byTier.tier1_core.length > 0) {
1725
+ lines.push(`
1726
+ **Tier 1 (Core - use in title/subtitle):** ${byTier.tier1_core.join(", ")}`);
1727
+ }
1728
+ if (byTier.tier2_feature.length > 0) {
1729
+ lines.push(`**Tier 2 (Feature - use in keywords field/descriptions):** ${byTier.tier2_feature.join(", ")}`);
1730
+ }
1731
+ if (byTier.tier3_longtail.length > 0) {
1732
+ lines.push(`**Tier 3 (Longtail - use in intro/outro/features):** ${byTier.tier3_longtail.join(", ")}`);
1733
+ }
1734
+ if (recommended.length > 0) {
1735
+ lines.push(`
1736
+ **Keyword Details (${recommended.length} keywords):**`);
1737
+ recommended.forEach((kw, idx) => {
1738
+ const tierLabel = kw.tier ? ` [${kw.tier}]` : "";
1739
+ const scores = kw.traffic > 0 || kw.difficulty > 0 ? ` (traffic: ${kw.traffic.toFixed(2)}, difficulty: ${kw.difficulty.toFixed(2)})` : "";
1740
+ lines.push(`${idx + 1}. **${kw.keyword}**${tierLabel}${scores}`);
1741
+ if (kw.rationale) {
1742
+ lines.push(` \u2192 ${kw.rationale}`);
1743
+ }
1744
+ });
1659
1745
  }
1660
- if (meta.competitorApps?.length) {
1661
- const competitors = meta.competitorApps.map((c) => `${c.platform || "?"}:${c.appId || "?"}`).join(", ");
1662
- lines.push(`Competitors: ${competitors}`);
1746
+ if (rationale) {
1747
+ lines.push(`
1748
+ **Strategy:** ${rationale}`);
1663
1749
  }
1664
- if (recommended.length) {
1665
- lines.push(`Recommended keywords (${recommended.length}): ${recommended.join(", ")}`);
1666
- } else {
1667
- lines.push("Recommended keywords: (not provided)");
1750
+ if (insights.keywordGaps.length > 0) {
1751
+ lines.push(`
1752
+ **Keyword Gaps (opportunities):**`);
1753
+ insights.keywordGaps.forEach((gap) => lines.push(`- ${gap}`));
1754
+ }
1755
+ if (insights.userLanguagePatterns.length > 0) {
1756
+ lines.push(`
1757
+ **User Language Patterns (from reviews):**`);
1758
+ insights.userLanguagePatterns.forEach((pattern) => lines.push(`- ${pattern}`));
1759
+ }
1760
+ lines.push("\n----");
1761
+ return lines.join("\n");
1762
+ }
1763
+ function mergeKeywordData(entries) {
1764
+ const merged = {
1765
+ tier1_core: [],
1766
+ tier2_feature: [],
1767
+ tier3_longtail: [],
1768
+ allKeywords: [],
1769
+ rationale: "",
1770
+ keywordGaps: [],
1771
+ userLanguagePatterns: [],
1772
+ platforms: []
1773
+ };
1774
+ const seenKeywords = /* @__PURE__ */ new Set();
1775
+ const seenGaps = /* @__PURE__ */ new Set();
1776
+ const seenPatterns = /* @__PURE__ */ new Set();
1777
+ for (const entry of entries) {
1778
+ if (entry.data?.parseError) continue;
1779
+ const meta = extractMeta(entry.data);
1780
+ if (meta.platform && !merged.platforms.includes(meta.platform)) {
1781
+ merged.platforms.push(meta.platform);
1782
+ }
1783
+ const byTier = extractKeywordsByTier(entry.data);
1784
+ byTier.tier1_core.forEach((kw) => {
1785
+ if (!seenKeywords.has(kw.toLowerCase())) {
1786
+ merged.tier1_core.push(kw);
1787
+ seenKeywords.add(kw.toLowerCase());
1788
+ }
1789
+ });
1790
+ byTier.tier2_feature.forEach((kw) => {
1791
+ if (!seenKeywords.has(kw.toLowerCase())) {
1792
+ merged.tier2_feature.push(kw);
1793
+ seenKeywords.add(kw.toLowerCase());
1794
+ }
1795
+ });
1796
+ byTier.tier3_longtail.forEach((kw) => {
1797
+ if (!seenKeywords.has(kw.toLowerCase())) {
1798
+ merged.tier3_longtail.push(kw);
1799
+ seenKeywords.add(kw.toLowerCase());
1800
+ }
1801
+ });
1802
+ const recommended = extractRecommended(entry.data);
1803
+ for (const kw of recommended) {
1804
+ if (!seenKeywords.has(kw.keyword.toLowerCase())) {
1805
+ merged.allKeywords.push(kw);
1806
+ seenKeywords.add(kw.keyword.toLowerCase());
1807
+ }
1808
+ }
1809
+ const rationale = extractRationale(entry.data);
1810
+ if (rationale && !merged.rationale) {
1811
+ merged.rationale = rationale;
1812
+ } else if (rationale && merged.rationale) {
1813
+ merged.rationale += ` | ${meta.platform}: ${rationale}`;
1814
+ }
1815
+ const insights = extractCompetitorInsights(entry.data);
1816
+ insights.keywordGaps.forEach((gap) => {
1817
+ if (!seenGaps.has(gap)) {
1818
+ merged.keywordGaps.push(gap);
1819
+ seenGaps.add(gap);
1820
+ }
1821
+ });
1822
+ insights.userLanguagePatterns.forEach((pattern) => {
1823
+ if (!seenPatterns.has(pattern)) {
1824
+ merged.userLanguagePatterns.push(pattern);
1825
+ seenPatterns.add(pattern);
1826
+ }
1827
+ });
1828
+ }
1829
+ merged.allKeywords.sort((a, b) => b.traffic - a.traffic);
1830
+ return merged;
1831
+ }
1832
+ function formatMergedData(merged, researchDir) {
1833
+ const lines = [];
1834
+ lines.push(`### Combined Keyword Research (${merged.platforms.join(" + ")})`);
1835
+ lines.push(`Source: ${researchDir}`);
1836
+ if (merged.tier1_core.length > 0) {
1837
+ lines.push(`
1838
+ **Tier 1 (Core - use in title/subtitle):** ${merged.tier1_core.join(", ")}`);
1839
+ }
1840
+ if (merged.tier2_feature.length > 0) {
1841
+ lines.push(`**Tier 2 (Feature - use in keywords field/descriptions):** ${merged.tier2_feature.join(", ")}`);
1842
+ }
1843
+ if (merged.tier3_longtail.length > 0) {
1844
+ lines.push(`**Tier 3 (Longtail - use in intro/outro/features):** ${merged.tier3_longtail.join(", ")}`);
1845
+ }
1846
+ if (merged.allKeywords.length > 0) {
1847
+ lines.push(`
1848
+ **Top Keywords by Traffic (${merged.allKeywords.length} total):**`);
1849
+ merged.allKeywords.slice(0, 15).forEach((kw, idx) => {
1850
+ const tierLabel = kw.tier ? ` [${kw.tier}]` : "";
1851
+ const scores = kw.traffic > 0 || kw.difficulty > 0 ? ` (traffic: ${kw.traffic.toFixed(2)}, difficulty: ${kw.difficulty.toFixed(2)})` : "";
1852
+ lines.push(`${idx + 1}. **${kw.keyword}**${tierLabel}${scores}`);
1853
+ if (kw.rationale) {
1854
+ lines.push(` \u2192 ${kw.rationale}`);
1855
+ }
1856
+ });
1668
1857
  }
1669
- lines.push("----");
1858
+ if (merged.rationale) {
1859
+ lines.push(`
1860
+ **Strategy:** ${merged.rationale}`);
1861
+ }
1862
+ if (merged.keywordGaps.length > 0) {
1863
+ lines.push(`
1864
+ **Keyword Gaps (opportunities):**`);
1865
+ merged.keywordGaps.slice(0, 5).forEach((gap) => lines.push(`- ${gap}`));
1866
+ }
1867
+ if (merged.userLanguagePatterns.length > 0) {
1868
+ lines.push(`
1869
+ **User Language Patterns (from reviews):**`);
1870
+ merged.userLanguagePatterns.slice(0, 5).forEach((pattern) => lines.push(`- ${pattern}`));
1871
+ }
1872
+ lines.push("\n----");
1670
1873
  return lines.join("\n");
1671
1874
  }
1672
1875
  function loadKeywordResearchForLocale(slug, locale) {
@@ -1697,6 +1900,15 @@ function loadKeywordResearchForLocale(slug, locale) {
1697
1900
  });
1698
1901
  }
1699
1902
  }
1903
+ const validEntries = entries.filter((e) => !e.data?.parseError);
1904
+ if (validEntries.length > 1) {
1905
+ const merged = mergeKeywordData(validEntries);
1906
+ const mergedSection = formatMergedData(merged, researchDir);
1907
+ return { entries, sections: [mergedSection], researchDir };
1908
+ } else if (validEntries.length === 1) {
1909
+ const sections2 = entries.map(formatEntry);
1910
+ return { entries, sections: sections2, researchDir };
1911
+ }
1700
1912
  const sections = entries.map(formatEntry);
1701
1913
  return { entries, sections, researchDir };
1702
1914
  }
@@ -2596,11 +2808,31 @@ var keywordResearchTool = {
2596
2808
 
2597
2809
  **IMPORTANT:** Always use 'search-app' tool first to resolve the exact slug before calling this tool. The user may provide an approximate name, bundleId, or packageName - search-app will find and return the correct slug. Never pass user input directly as slug.
2598
2810
 
2599
- **Locale coverage:** If the product ships multiple locales, run this tool SEPARATELY for EVERY locale (including non-primary markets). Do NOT rely on "template-only" coverage for secondary locales\u2014produce a full keyword research file per locale.
2811
+ ## CRITICAL: Multi-Locale Execution Plan
2812
+
2813
+ **MANDATORY WORKFLOW - Complete each locale fully before moving to next:**
2600
2814
 
2601
- **Platform coverage:** Use search-app results to confirm supported platforms/locales (App Store + Google Play). Run this tool for EVERY supported platform/locale combination\u2014ios + android runs are separate.
2815
+ For EACH locale+platform combination, execute this cycle:
2816
+ 1. **Plan:** Call keyword-research(slug, locale, platform) with writeTemplate=false \u2192 get research plan
2817
+ 2. **Research:** Execute COMPLETE mcp-appstore workflow (all 16 steps) for that locale
2818
+ 3. **Save:** Call keyword-research again with researchData or researchDataPath \u2192 persist actual data
2819
+ 4. **Next:** Move to next locale+platform and repeat steps 1-3
2602
2820
 
2603
- Run this before improve-public. It gives a concrete MCP-powered research plan and a storage path under .aso/keywordResearch/products/[slug]/locales/[locale]/. Optionally writes a template or saves raw JSON from mcp-appstore tools.`,
2821
+ **IMPORTANT: Research \u2192 Save \u2192 Next pattern**
2822
+ - Complete ONE locale fully (research + save) before starting the next
2823
+ - This prevents data loss if the session is interrupted
2824
+ - Each locale's data is persisted immediately after research
2825
+
2826
+ **FORBIDDEN:**
2827
+ - \u274C Using writeTemplate=true as final output
2828
+ - \u274C Skipping secondary locales
2829
+ - \u274C Researching multiple locales then saving all at once at the end
2830
+ - \u274C Stopping before all locale+platform combinations are done
2831
+
2832
+ **REQUIRED:**
2833
+ - \u2705 Research locale \u2192 Save locale \u2192 Move to next (one at a time)
2834
+ - \u2705 Run for EVERY platform (ios AND android separately)
2835
+ - \u2705 Use researchData or researchDataPath to save (NOT writeTemplate)`,
2604
2836
  inputSchema: inputSchema7
2605
2837
  };
2606
2838
  function buildTemplate({
@@ -2733,7 +2965,6 @@ async function handleKeywordResearch(input) {
2733
2965
  const { config, locales } = loadProductLocales(slug);
2734
2966
  const primaryLocale = resolvePrimaryLocale(config, locales);
2735
2967
  const productLocales = Object.keys(locales);
2736
- const remainingLocales = productLocales.filter((loc) => loc !== locale);
2737
2968
  const primaryLocaleData = locales[primaryLocale];
2738
2969
  const { supportedLocales, path: supportedPath } = getSupportedLocalesForSlug(slug, platform);
2739
2970
  const appStoreLocales = registeredApp?.appStore?.supportedLocales || [];
@@ -2879,24 +3110,56 @@ Context around ${pos}: ${context}`
2879
3110
  `Registered supported locales not found for ${platform} (checked: ${supportedPath}).`
2880
3111
  );
2881
3112
  }
3113
+ const allCombinations = [];
3114
+ const platformsToRun = declaredPlatforms.length > 0 ? declaredPlatforms : [platform];
3115
+ for (const plat of platformsToRun) {
3116
+ for (const loc of productLocales.length > 0 ? productLocales : [locale]) {
3117
+ allCombinations.push({ loc, plat });
3118
+ }
3119
+ }
3120
+ const currentIndex = allCombinations.findIndex(
3121
+ (c) => c.loc === locale && c.plat === platform
3122
+ );
3123
+ const completedCount = currentIndex >= 0 ? currentIndex : 0;
3124
+ const remainingCombinations = allCombinations.slice(currentIndex + 1);
2882
3125
  if (productLocales.length > 0) {
2883
3126
  lines.push(
2884
3127
  `Existing product locales (${productLocales.length}): ${productLocales.join(", ")}`
2885
3128
  );
2886
- lines.push(
2887
- "MANDATORY: Run FULL keyword research (mcp-appstore workflow) for EVERY locale above\u2014no template-only coverage for secondary markets."
2888
- );
2889
- if (remainingLocales.length > 0) {
2890
- lines.push(
2891
- `After finishing ${locale}, immediately queue runs for: ${remainingLocales.join(", ")}`
2892
- );
2893
- }
2894
- if (declaredPlatforms.length > 1) {
2895
- lines.push(
2896
- "Also run separate FULL keyword research for each supported platform (e.g., ios + android) across all locales."
2897
- );
2898
- }
2899
3129
  }
3130
+ lines.push("");
3131
+ lines.push("---");
3132
+ lines.push("## \u{1F3AF} EXECUTION PROGRESS TRACKER");
3133
+ lines.push("");
3134
+ lines.push(`**Total combinations to complete:** ${allCombinations.length} (${platformsToRun.length} platforms \xD7 ${productLocales.length || 1} locales)`);
3135
+ lines.push(`**Current:** ${locale} + ${platform} (${completedCount + 1}/${allCombinations.length})`);
3136
+ lines.push("");
3137
+ if (researchData || researchDataPath) {
3138
+ lines.push(`\u2705 SAVED: ${locale} + ${platform} - Full research data persisted`);
3139
+ } else if (writeTemplate) {
3140
+ lines.push(`\u26A0\uFE0F WARNING: ${locale} + ${platform} - Only template written! You MUST run full mcp-appstore research and save actual data.`);
3141
+ } else {
3142
+ lines.push(`\u{1F4CB} PLANNING: ${locale} + ${platform} - Research plan shown. Now execute mcp-appstore workflow.`);
3143
+ }
3144
+ if (remainingCombinations.length > 0) {
3145
+ lines.push("");
3146
+ lines.push("## \u23ED\uFE0F MANDATORY NEXT STEPS");
3147
+ lines.push("");
3148
+ lines.push("**After completing current locale+platform, you MUST continue with:**");
3149
+ lines.push("");
3150
+ remainingCombinations.forEach((combo, idx) => {
3151
+ lines.push(`${idx + 1}. keyword-research(slug="${slug}", locale="${combo.loc}", platform="${combo.plat}") \u2192 full mcp-appstore workflow \u2192 save results`);
3152
+ });
3153
+ lines.push("");
3154
+ lines.push("\u26D4 DO NOT mark this task as complete until ALL combinations above have FULL research data (not templates).");
3155
+ } else {
3156
+ lines.push("");
3157
+ lines.push("## \u2705 FINAL STEP");
3158
+ lines.push("");
3159
+ lines.push("This is the LAST locale+platform combination. After saving full research data for this one, the task is complete.");
3160
+ }
3161
+ lines.push("---");
3162
+ lines.push("");
2900
3163
  lines.push(
2901
3164
  `Seeds: ${resolvedSeeds.length > 0 ? resolvedSeeds.join(", ") : "(none set; add seedKeywords or ensure ASO keywords/title exist)"}`
2902
3165
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pabal-web-mcp",
3
- "version": "1.3.10",
3
+ "version": "1.3.11",
4
4
  "type": "module",
5
5
  "description": "MCP server for ASO data management with shared types and utilities",
6
6
  "author": "skyu",