map-your-market について
製品チームやマーケター向けの市場調査スキルです。Reddit、HN、GitHub Issues、G2、Google Trendsから課題のシグナルを集めて分類し、収集データに基づくICPとポジショニングの枠組みを作成します。
主な機能
- 01指定された5つの情報源からシグナル収集
- 02課題シグナルのスコア付けと分類
- 03メタデータからICPを作成
- 04根拠に基づくポジショニング案
- 05金額を推定せず市場の方向性を報告
活用シーン
- 01製品カテゴリの顧客課題を調査
- 02競合と市場シグナルを比較
- 03顧客の言葉に基づくポジショニング策定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ varnan-tech/ opendirectory --skill map-your-marketインストール後、Claude Code で/map-your-marketを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.env.exampleREADME.mdevals/evals.jsonreferences/icp-signals.mdpain-scoring.mdsubreddit-map.mdscripts/fetch.py詳細
市場をマッピングする
製品の説明、カテゴリのキーワード、または競合他社名を入力します。Reddit、HN、GitHub Issues、G2、Google Trendsを検索し、実際の課題を示すシグナルを探します。それらにスコアを付けて分類し、ICPの定義、順位付けされた課題テーマとその逐語的な引用、市場規模のシグナル、実際に人々が使っている言葉から導き出したメッセージングの切り口を含む、完全なポジショニングフレームワークを作成します。
重要なルール: 出力に含める課題の引用はすべて、スクリプトが収集した生データに逐語的に存在しなければなりません。市場マップに含めるベンダー名はすべて、G2のスクレイピング結果またはGitHubの検索結果から取得する必要があります。市場規模については「シグナルから示唆される」と記載しなければなりません。根拠の乏しい代理指標から金額を推定してはいけません。ソースからの結果が0件の場合は、0件と報告し、作り上げた例で補ってはいけません。
よくある間違い
| エージェントがやりがちなこと | それが間違いである理由 |
|---|---|
| 課題や市場規模の数値を作り上げる | 課題の引用はすべて、生データからの逐語的な引用でなければなりません。市場規模は、見つかったシグナルを根拠として示す必要があります。「一般的な」市場規模を推定してはいけません。 |
| pain_scoreではなく投稿数でスコアを付ける | 価格について2,000件の賛成票を集めた投稿は、それぞれ10件の賛成票を集めた50件の投稿より強いシグナルです。references/pain-scoring.mdに記載されたpain_scoreの計算式を使用してください。 |
| どのカテゴリでも同じサブレディットを使う | r/politicsはdevops検索にノイズを加えます。検索前に、カテゴリと競合他社名から関連するサブレディットを自動検出してください。 |
| スコア付けせずにすべての生シグナルをAIに送る | まずローカルでスコアを付けてください。AIによる分類には、pain_scoreの高い上位60件のシグナルのみを送ります。トークンを節約し、分類の品質も向上します。 |
| 投稿のメタデータからのICP抽出を省略する | サブレディット、フレア、投稿者のプロフィール(HN)、GitHubの組織タイプは、投稿内容よりも豊かなICPのシグナルです。必ず取得して報告してください。 |
| ベンダー数と市場規模を混同する | 「G2にベンダーが47社」は、競争が激しいことを意味するのであって、市場が大きいことを意味するのではありません。すべてのシグナルを確定的な数値ではなく、方向性を示す指標として提示してください。 |
ステップ1:セットアップを確認する
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-not set -- GitHub Issues search runs at 60 req/hr unauthenticated}"
echo "No other API keys required."
echo ""
echo "Data sources this run will use:"
echo " Reddit public JSON (no auth, 10 req/min)"
echo " HN Algolia API (no auth, free)"
echo " GitHub Issues API (${GITHUB_TOKEN:+authenticated, }60-5000 req/hr)"
echo " G2 category scrape (no auth, HTML parse)"
echo " Google Trends (no auth, unofficial endpoint)"
GITHUB_TOKENが設定されていない場合:そのまま続行します。認証なしのGitHub検索は1時間あたり60リクエストまでで、通常の実行には十分です。繰り返し使用する場合は、github.com/settings/tokensでトークンを追加してください(公開リポジトリにはスコープは不要です)。
ステップ2:入力を解析する
会話から次の情報を収集します。
category-- 市場分野を表すキーワード(例:「開発者向けオブザーバビリティ」「B2Bアナリティクス」「devopsツール」)competitors-- 競合製品名またはドメインの任意のリスト(例:「Datadog, New Relic, Grafana」)product_context-- ユーザーの製品が何をするものか(任意。メッセージングの切り口を調整するのに役立ちます)
カテゴリのキーワードなしで製品の説明だけが提供された場合は、そこからカテゴリのキーワードを2~3個抽出します。
カテゴリなしで競合他社名だけが提供された場合は、競合他社を調べてカテゴリを推測します。
解析した入力を記述します。
python3 << 'PYEOF'
import json, os
data = {
"category": "CATEGORY_HERE",
"competitors": ["COMP_1", "COMP_2"],
"product_context": "PRODUCT_CONTEXT_HERE"
}
with open("/tmp/mym-input.json", "w") as f:
json.dump(data, f, indent=2)
print("Input written to /tmp/mym-input.json")
print(f"Category: {data['category']}")
print(f"Competitors: {', '.join(data['competitors']) if data['competitors'] else 'none provided'}")
PYEOF
ステップ3:単独で実行できるデータ収集スクリプトを実行する
このスクリプトがすべてのデータ収集を処理します。まず、スクリプトが存在するか確認します。
ls scripts/fetch.py 2>/dev/null && echo "script available" || echo "not found"
利用可能な場合は、スクリプトを実行します。
GITHUB_TOKEN="${GITHUB_TOKEN:-}" python3 scripts/fetch.py \
"$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['category'])")" \
--competitors "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(','.join(d['competitors']))")" \
--context "$(python3 -c "import json; d=json.load(open('/tmp/mym-input.json')); print(d['product_context'])")" \
--output /tmp/mym-raw.json
完了するまで待ちます(最大4分を見込んでください。RedditとHNの検索には合計で約90秒かかります)。
出力を確認します。
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print(f'Reddit signals: {d[\"market_signals\"][\"reddit_signals_found\"]}')
print(f'HN signals: {d[\"market_signals\"][\"hn_signals_found\"]}')
print(f'GitHub signals: {d[\"market_signals\"][\"github_issue_signals\"]}')
print(f'G2 vendors: {d[\"market_signals\"][\"vendor_count_g2\"]}')
print(f'Trends: {d[\"market_signals\"][\"trends_direction\"]}')
print(f'Total signals: {d[\"summary\"][\"total_pain_signals\"]}')
"
シグナルの合計が10件未満の場合は停止します。ユーザーに次のように伝えてください。「このカテゴリでは、課題を示すシグナルが10件未満しか見つかりませんでした。この市場はReddit/HNで取り上げられるにはニッチすぎるか、カテゴリのキーワードを調整する必要がある可能性があります。より広いキーワードを試すか、競合他社名を追加してください。」
ステップ 4: AI による課題のクラスタリング
AI 分析用に、上位 60 件の課題シグナルを出力します。
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
top60 = sorted(d['raw_pains'], key=lambda x: x['pain_score'], reverse=True)[:60]
print(json.dumps(top60, indent=2))
"
上位 60 件の課題シグナルが揃いました。これらを分析し、課題クラスターを作成してください。
AI 分析の指示:
- すべてのソースに見られる、繰り返し登場する課題テーマを 5~7 個特定します
- 各テーマには、市場で実際に使われている言葉を用いた名前を付けます(独自の表現は使いません)
- 各クラスターに含まれるすべてのシグナルの
pain_scoreを合計します - 各テーマについて、内容が最も具体的でスコアの高い、最適な逐語引用を 3~5 個選びます
- 各テーマがどのソースやサブレディットに集中しているかを記載します
- 1 つのソースにしか現れないテーマには、信頼度が低いことを示すフラグを付けます
クラスターを /tmp/mym-clusters.json に書き込みます。
{
"clusters": [
{
"theme": "exact language from the data",
"total_score": 847,
"signal_count": 34,
"sources": {"reddit": 18, "hn": 12, "github_issue": 4},
"top_subreddits": ["devops", "sysadmin"],
"verbatim_quotes": [
{"text": "exact quote", "source": "reddit", "score": 234, "url": "..."},
{"text": "exact quote", "source": "hn", "score": 87, "url": "..."}
],
"who_has_this_pain": "description of who is posting about this"
}
]
}
python3 -c "
import json, os
# Confirm clusters file was written
with open('/tmp/mym-clusters.json') as f:
d = json.load(f)
print(f'Clusters written: {len(d[\"clusters\"])}')
for c in d['clusters']:
print(f' {c[\"theme\"]} -- score: {c[\"total_score\"]}, signals: {c[\"signal_count\"]}')
"
ステップ 5: ICP プロファイリング
未加工データから ICP シグナルを出力します。
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
print('ICP signals:')
print(json.dumps(d['icp_signals'], indent=2))
print()
print('Subreddit distribution:')
sub_counts = {}
for p in d['raw_pains']:
s = p.get('subreddit', '')
if s:
sub_counts[s] = sub_counts.get(s, 0) + 1
for sub, count in sorted(sub_counts.items(), key=lambda x: -x[1])[:10]:
print(f' r/{sub}: {count} signals')
"
ICP シグナルと上記のサブレディット分布を使って ICP プロファイルをまとめます。icp キーを追加し、/tmp/mym-clusters.json に書き込んでください。
{
"icp": {
"who_they_are": "2-3 sentence profile using language from the data",
"where_they_live": ["r/devops (89 posts)", "r/sysadmin (67 posts)", "HN ask-hn (34 threads)"],
"what_they_say": ["verbatim quote 1", "verbatim quote 2", "verbatim quote 3"],
"what_they_have_tried": ["alternative tools or approaches mentioned in the data"],
"confidence": "high|medium|low -- based on signal volume and source diversity"
}
}
ステップ 6: 市場規模の統合
市場シグナルを出力します。
python3 -c "
import json
with open('/tmp/mym-raw.json') as f:
d = json.load(f)
ms = d['market_signals']
print('Market signals:')
print(f' G2 vendors: {ms[\"vendor_count_g2\"]}')
print(f' Trends direction: {ms[\"trends_direction\"]}')
print(f' HN signals (12mo): {ms[\"hn_signals_found\"]}')
print(f' Reddit signals: {ms[\"reddit_signals_found\"]}')
print(f' G2 top vendors: {json.dumps(ms.get(\"top_vendors\", []), indent=4)}')
"
これらのシグナルのみを使って、市場規模を概略評価してください。金額は推定しないでください。次のような表現を使います。
- 「シグナルは、競争が激しく成長中の市場を示しています」(ベンダーが多く、トレンドは上向き)
- 「シグナルは、初期段階の市場を示しています」(ベンダーが少なく、シグナルの量も少ない)
- 「シグナルは、飽和した市場を示しています」(ベンダーが多く、トレンドは横ばいまたは下向き)
評価を market_size キーとして /tmp/mym-clusters.json に追加します。
ステップ 7: ポジショニングフレームワーク
クラスター(ステップ 4)、ICP(ステップ 5)、市場規模(ステップ 6)を使って、ポジショニングフレームワークを作成します。
指示:
- 上位 3 つの課題クラスターを、主要なポジショニングの切り口として選びます
- 各切り口について、データ中の表現をそのまま使ったポジショニング文を 1 つ作成します(言い換えはしません)
- 課題データで実際に使われている正確なフレーズを用いて、ランディングページの見出しを 3 つ作成します
- 課題を表す表現に基づいて、コールドメールの件名を 3 つ作成します
- 禁止語を使わないでください:powerful、robust、seamless、innovative、game-changing、streamline、leverage、transform、revolutionize
ポジショニングフレームワーク全体を /tmp/mym-output.json に書き込みます。
{
"positioning_angles": [
{
"pain": "theme name",
"statement": "one-line positioning using market language",
"headline": "landing page headline using verbatim pain language",
"cold_email_subject": "subject line"
}
],
"icp_card": {
"one_liner": "one sentence: who they are + what they care about",
"where_to_find_them": [...],
"how_to_talk_to_them": "tone + vocabulary notes from the data"
},
"market_map": [...top vendors from G2 with positioning notes...]
}
ステップ 8: 自己 QA と出力の保存
自己 QA チェックを実行します。
python3 -c "
import json
# Load all outputs
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
raw_texts = set()
for p in raw['raw_pains']:
raw_texts.add(p.get('title', ''))
raw_texts.add(p.get('body_excerpt', ''))
# Check 1: No em dashes
import json as j
full_text = j.dumps(output)
if '—' in full_text:
print('FAIL: em dash found in output')
else:
print('PASS: no em dashes')
# Check 2: No banned words
banned = ['powerful', 'robust', 'seamless', 'innovative', 'game-changing',
'streamline', 'leverage', 'transform', 'revolutionize']
found = [w for w in banned if w.lower() in full_text.lower()]
if found:
print(f'FAIL: banned words found: {found}')
else:
print('PASS: no banned words')
# Check 3: Market size language check
if 'billion' in full_text.lower() or 'trillion' in full_text.lower() or 'worth \$' in full_text.lower():
print('FAIL: hard market size estimate found -- use directional language only')
else:
print('PASS: no hard market size estimates')
# Check 4: Signal counts match
total = raw['summary']['total_pain_signals']
print(f'PASS: {total} total pain signals in raw data')
print('Self-QA complete.')
"
失敗があれば、保存する前に修正します。
最終レポートを保存します。
python3 << 'PYEOF'
import json, re
from datetime import datetime
with open('/tmp/mym-input.json') as f:
inp = json.load(f)
with open('/tmp/mym-raw.json') as f:
raw = json.load(f)
with open('/tmp/mym-clusters.json') as f:
clusters = json.load(f)
with open('/tmp/mym-output.json') as f:
output = json.load(f)
slug = re.sub(r'[^a-z0-9]+', '-', inp['category'].lower()).strip('-')
date = datetime.now().strftime('%Y-%m-%d')
outpath_md = f"docs/market-maps/{slug}-{date}.md"
outpath_json = f"docs/market-maps/{slug}-{date}.json"
# Build markdown report
ms = raw['market_signals']
icp = clusters.get('icp', {})
market_assessment = clusters.get('market_size', {})
angles = output.get('positioning_angles', [])
icp_card = output.get('icp_card', {})
market_map = output.get('market_map', [])
lines = [
f"# Market Map: {inp['category'].title()}",
f"Date: {date} | Signals analyzed: {raw['summary']['total_pain_signals']} | Sources: Reddit ({ms['reddit_signals_found']}) + HN ({ms['hn_signals_found']}) + GitHub Issues ({ms['github_issue_signals']})",
"",
"---",
"",
"## Market Size Signals",
f"Vendors on G2: {ms['vendor_count_g2']} | Google Trends: {ms['trends_direction'].upper()} | Market stage: {market_assessment.get('stage', 'see signals below')}",
"",
market_assessment.get('summary', ''),
"",
"---",
"",
"## Your ICP",
"",
f"**Who they are:** {icp.get('who_they_are', '')}",
"",
f"**Where they live:** {', '.join(icp.get('where_they_live', []))}",
"",
"**What they say:**",
]
for q in icp.get('what_they_say', []):
lines.append(f'> "{q}"')
lines += ["", "---", "", "## Top Pains (ranked by signal strength)", ""]
for i, c in enumerate(clusters.get('clusters', []), 1):
lines.append(f"### Pain {i}: {c['theme']} [score: {c['total_score']}]")
sources = c.get('sources', {})
source_str = " + ".join(f"{src} ({cnt})" for src, cnt in sources.items())
lines.append(f"{c['signal_count']} signals | Sources: {source_str}")
lines.append(f"Who has this pain: {c.get('who_has_this_pain', '')}")
lines.append("")
lines.append("Verbatim:")
for q in c.get('verbatim_quotes', [])[:4]:
lines.append(f'> "{q[\"text\"]}" ({q["source"]}, score: {q["score"]})')
lines.append("")
lines += ["---", "", "## Market Map (Key Players)", ""]
if market_map:
lines.append("| Vendor | Positioning |")
lines.append("|---|---|")
for v in market_map:
lines.append(f"| {v.get('name','')} | {v.get('positioning','')} |")
else:
top = ms.get('top_vendors', [])
if top:
lines.append("| Vendor | G2 Reviews | Rating |")
lines.append("|---|---|---|")
for v in top:
lines.append(f"| {v.get('name','')} | {v.get('review_count','')} | {v.get('rating','')} |")
lines += ["", "---", "", "## Messaging Framework", ""]
for a in angles:
lines.append(f"**{a['pain']}:** {a['statement']}")
lines.append(f"Headline: \"{a['headline']}\"")
lines.append(f"Cold email subject: \"{a['cold_email_subject']}\"")
lines.append("")
lines += ["---", "", "## ICP Card", "",
f"**One liner:** {icp_card.get('one_liner', '')}",
"",
f"**Find them at:** {', '.join(icp_card.get('where_to_find_them', []))}",
"",
f"**How to talk to them:** {icp_card.get('how_to_talk_to_them', '')}",
"",
"---",
"",
"## Data Quality Notes",
f"- All pain quotes are verbatim from raw signals",
f"- All vendor names from G2 scrape",
f"- Market size is directional only (no dollar estimates)",
f"- Sources: Reddit ({ms['reddit_signals_found']}), HN ({ms['hn_signals_found']}), GitHub Issues ({ms['github_issue_signals']}), G2 ({ms['vendor_count_g2']} vendors)",
"",
f"Saved to: {outpath_md}",
f"JSON snapshot: {outpath_json}",
]
with open(outpath_md, 'w') as f:
f.write('\n'.join(lines))
# Save JSON snapshot
snapshot = {"input": inp, "market_signals": ms, "clusters": clusters.get('clusters', []),
"icp": icp, "market_size": market_assessment, "positioning": output, "date": date}
with open(outpath_json, 'w') as f:
json.dump(snapshot, f, indent=2)
print(f"Report saved: {outpath_md}")
print(f"JSON snapshot: {outpath_json}")
PYEOF
一時ファイルを削除します。
rm -f /tmp/mym-input.json /tmp/mym-raw.json /tmp/mym-clusters.json /tmp/mym-output.json
echo "Done. Market map saved to docs/market-maps/"
保存した .md ファイルの内容全体をユーザーに提示します。