Communitygithub.com

Varnan-Tech/opendirectory

競合の公開GitHubリポジトリのURLからIssueを取得し、ローカルでノイズを除去した後、6つの需要カテゴリにクラスタリングし、Issueごと・クラスタごとに需要スコアを計算。ランク付けされた需要ギャップレポートとGTMメッセージングブリーフを出力します。

opendirectory とは?

このスキルは、競合の公開GitHubリポジトリのIssueを分析し、ユーザーの生の声から需要ギャップを特定し、プロダクトのポジショニングやメッセージングに活用するための強力なツールです。リポジトリURLまたはオーナー/レポ名を入力すると、GitHub REST APIを使用して最大200件のIssueを取得し、ボット作成のIssue、Pull Request、エンゲージメントゼロのIssueなどをローカルでフィルタリングしてノイズを除去します。その後、残ったIssueを「feature_gap」「bug_pattern」「ux_complaint」「performance」「integration_missing」「docs_missing」の6カテゴリに分類し、それぞれに需要スコア(リアクション数×2 + コメント数×0.5)を計算します。最終的に、需要スコアでランク付けされたクラスタテーマと、各テーマに紐づく実際のIssueタイトルを引用したGTMメッセージングブリーフを生成します。このスキルは、Claude CodeやCursor、CodexなどのAIエージェントで動作し、開発者が競合のユーザーから直接需要シグナルを抽出し、プロダクト戦略に反映させることを可能にします。

対応✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/gh-issue-to-demand-signal

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

GitHub Issue 需要シグナル分析

競合の公開GitHubリポジトリからIssueを取得し、ローカルでノイズをフィルタリング。6つの需要カテゴリにクラスタリングし、実際のエンゲージメントでスコアリング。ランク付けされた需要ギャップレポートとGTMメッセージングブリーフを出力します。


重要ルール: 出力されるすべてのIssueタイトルは、GitHub APIレスポンスからそのまま引用すること。各クラスタのテーマ名は、そのクラスタ内の実際のIssueタイトルから導出すること。ノイズフィルタリング後に10件未満のIssueしか残らなかった場合は、処理を停止しユーザーにその旨を伝えること。リポジトリが小さすぎて信頼性のあるクラスタリングができません。どの部分にも発明したIssueコンテンツを含めないこと。


よくある間違い

AIエージェントがやりがちなことそれが間違いである理由
フィルタリングなしで200件の生のIssueをすべてAIに送信するボットIssue、PR、エンゲージメントゼロのノイズがクラスタ数を水増しし、コンテキストを浪費します。まずローカルでフィルタリングしてください。
コメント数を主要な需要シグナルとして使用するコメントにはメンテナの返信、オフトピックの議論、スパムが含まれます。reactions["+1"]が最もクリーンな購入シグナルです。
クラスタを要約する際にIssueタイトルを言い換える言い換えると購入者の正確な言葉が失われ、それが分析の本質です。Issueタイトルはそのまま使用してください。
フィルタリング後に10件未満のIssueしか残っていないのにステップ4以降を続行する10件未満の場合、リポジトリが小さすぎるか、間違ったURLが指定されています。データが少なすぎると意味のあるクラスタリングはできません。
Pull Requestを分析に含めるGitHub IssuesエンドポイントはPRも返します。Issueオブジェクトにpull_requestキーがないことを確認してPRを除外してください。
3つの条件すべてを確認せずにIssueを無視された需要としてマークする3つすべてが真である必要があります:リアクション数 >= 10、経過日数 >= 180日、planned/in-progress/roadmapラベルなし。1つでも欠けるとそのIssueは無視された需要とはみなされません。

ステップ 1: セットアップ確認

echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-未設定、未認証のレート制限が適用されます(60 req/hr)}"

GITHUB_TOKENが設定されていない場合: 処理を続行し、ユーザーに次のように伝えてください。「GITHUB_TOKENが設定されていません。未認証の場合、レート制限は1時間あたり60リクエストです。これは2回のフェッチで制限に達することを意味します。繰り返し使用するには、github.com/settings/tokens でトークンを追加してください(公開リポジトリにはスコープは不要です)。」


ステップ 2: 入力の収集

必要なもの:

入力からオーナーとレポを解析:

python3 << 'PYEOF'
import re, sys, os

raw = "REPO_INPUT_HERE"

# Normalize to owner/repo
if raw.startswith("http"):
    m = re.search(r"github\.com/([^/]+)/([^/?\s]+)", raw)
    if not m:
        print("ERROR: Could not parse GitHub URL. Expected format: https://github.com/owner/repo")
        sys.exit(1)
    owner, repo = m.group(1), m.group(2).rstrip("/")
elif "/" in raw:
    parts = raw.strip().split("/")
    owner, repo = parts[0], parts[1]
else:
    print("ERROR: Input must be a GitHub URL or owner/repo slug (e.g. vercel/next.js)")
    sys.exit(1)

print(f"Owner: {owner}")
print(f"Repo: {repo}")

with open("/tmp/ghd-target.txt", "w") as f:
    f.write(f"{owner}/{repo}")
PYEOF

解析に失敗した場合: 処理を停止し、次のように尋ねてください。「GitHubリポジトリをURL(https://github.com/owner/repo)またはオーナー/レポ名(例:vercel/next.js)で指定してください。」


ステップ 3: GitHub REST APIからIssueを取得

最大200件のIssueを取得(2ページ、各100件)。最初のフェッチ後にレート制限を確認。

python3 << 'PYEOF'
import json, urllib.request, os, sys
from datetime import datetime, timezone

target = open("/tmp/ghd-target.txt").read().strip()
owner_repo = target
token = os.environ.get("GITHUB_TOKEN", "")

headers = {"Accept": "application/vnd.github+json", "User-Agent": "gh-issue-demand-signal/1.0"}
if token:
    headers["Authorization"] = f"Bearer {token}"

all_issues = []
rate_limit_hit = False

for page in [1, 2]:
    url = f"https://api.github.com/repos/{owner_repo}/issues?state=open&per_page=100&page={page}"
    req = urllib.request.Request(url, headers=headers)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            # Check rate limit after first page
            if page == 1:
                remaining = int(resp.headers.get("X-RateLimit-Remaining", 999))
                reset_ts = resp.headers.get("X-RateLimit-Reset", "")
                if remaining == 0:
                    reset_str = datetime.fromtimestamp(int(reset_ts), tz=timezone.utc).strftime("%H:%M UTC") if reset_ts else "不明"
                    print(f"ERROR: GitHub APIのレート制限に達しました。リセット予定時刻: {reset_str}")
                    print(".envファイルにGITHUB_TOKENを追加すると、1時間あたり5000リクエストに増加します。github.com/settings/tokens(スコープ不要)を参照。")
                    sys.exit(1)
                print(f"残りレート制限: {remaining}")

                # Check for 404/403
                status = resp.status
                if status == 404:
                    print(f"ERROR: リポジトリ '{owner_repo}' が見つかりません。URLまたはスラッグを確認してください。")
                    sys.exit(1)

            page_data = json.loads(resp.read())
            if not page_data:
                print(f"Page {page}: 空のため停止。")
                break
            all_issues.extend(page_data)
            print(f"Page {page}: {len(page_data)} 件のIssueを取得")
    except urllib.error.HTTPError as e:
        if e.code == 404:
            print(f"ERROR: リポジトリ '{owner_repo}' が見つかりません(404)。URLまたはスラッグを確認してください。")
        elif e.code == 403:
            print(f"ERROR: アクセスが拒否されました(403)。リポジトリがプライベートであるか、レート制限に達した可能性があります。")
        else:
            print(f"ERROR: GitHub APIがHTTP {e.code}を返しました")
        sys.exit(1)
    except Exception as e:
        print(f"ERROR: ページ {page} の取得に失敗しました: {e}")
        sys.exit(1)

print(f"取得した生のIssue数: {len(all_issues)}")
json.dump(all_issues, open("/tmp/ghd-raw-issues.json", "w"), indent=2)
PYEOF

GitHubが404を返した場合: 処理を停止し、ユーザーに伝えてください。「リポジトリが見つかりません。URLまたはスラッグを確認して再試行してください。プライベートリポジトリは認証と明示的なリポジトリスコープなしではアクセスできません。」

GitHubがレート制限ヘッダー付きで403を返した場合: 処理を停止し、リセット時間を表示し、GITHUB_TOKENを追加するようユーザーに伝えてください。


ステップ 4: ローカルで前処理 -- フィルタリング、スコアリング、無視された需要の検出

APIコールなし。純粋なPython。AIに何かを送信する前に実行。

python3 << 'PYEOF'
import json, re
from datetime import datetime, timezone

raw = json.load(open("/tmp/ghd-raw-issues.json"))
target = open("/tmp/ghd-target.txt").read().strip()
now = datetime.now(tz=timezone.utc)

noise_patterns = re.compile(
    r"^(chore|deps|bump|renovate|dependabot|release|ci|build|revert)[\s:\[]",
    re.IGNORECASE
)
pr_title_patterns = re.compile(
    r"^(feat|fix|refactor|docs|test|style|perf|chore)(\(.+\))?:",
    re.IGNORECASE
)

filtered = []
noise_count = 0
noise_reasons = {}

for issue in raw:
    # Pull Requestをスキップ(GitHub IssuesエンドポイントはPRも返す)
    if "pull_request" in issue:
        noise_count += 1
        noise_reasons["pull_request"] = noise_reasons.get("pull_request", 0) + 1
        continue

    title = issue.get("title", "")
    reactions = issue.get("reactions", {}).get("+1", 0)
    comments = issue.get("comments", 0)
    user_type = (issue.get("user") or {}).get("type", "User")

    # ボット作成のIssueをスキップ
    if user_type == "Bot":
        noise_count += 1
        noise_reasons["bot_author"] = noise_reasons.get("bot_author", 0) + 1
        continue

    # ボットパターンのタイトルをスキップ
    if noise_patterns.match(title):
        noise_count += 1
        noise_reasons["bot_title"] = noise_reasons.get("bot_title", 0) + 1
        continue

    # PRのようなタイトルをスキップ
    if pr_title_patterns.match(title):
        noise_count += 1
        noise_reasons["pr_as_issue"] = noise_reasons.get("pr_as_issue", 0) + 1
        continue

    # シグナルゼロのIssueをスキップ
    if reactions == 0 and comments == 0:
        noise_count += 1
        noise_reasons["zero_signal"] = noise_reasons.get("zero_signal", 0) + 1
        continue

    # 需要スコアを計算
    demand_score = (reactions * 2) + (comments * 0.5)

    # 無視された需要を検出
    created_at = issue.get("created_at", "")
    if created_at:
        created = datetime.fromisoformat(created_at.replace("Z", "+00:00"))
        age_days = (now - created).days
    else:
        age_days = 0

    labels = [l.get("name", "").lower() for l in issue.get("labels", [])]
    has_planned_label = any(
        kw in label for label in labels
        for kw in ["in-progress", "planned", "roadmap", "wip", "in progress"]
    )

    ignored_demand = (
        reactions >= 10 and
        age_days >= 180 and
        not has_planned_label
    )

    filtered.append({
        "number": issue["number"],
        "title": title,
        "url": issue.get("html_url", f"https://github.com/{target}/issues/{issue['number']}"),
        "reactions_plus1": reactions,
        "comments": comments,
        "demand_score": demand_score,
        "age_days": age_days,
        "labels": labels,
        "ignored_demand": ignored_demand,
        "body_snippet": (issue.get("body") or "")[:300]
    })

# 需要スコア降順でソート
filtered.sort(key=lambda x: x["demand_score"], reverse=True)

print(f"生のIssue数: {len(raw)}")
print(f"ノイズフィルタリング数: {noise_count} ({', '.join(f'{k}: {v}' for k, v in noise_reasons.items())})")
print(f"分析対象Issue数: {len(filtered)}")

if len(filtered) < 10:
    print(f"ERROR: フィルタリング後に残ったIssueは{len(filtered)}件のみです。このリポジトリは信頼性のあるクラスタリングを行うには Issue が少なすぎます。")
    print("より大きなリポジトリ、またはコミュニティのエンゲージメントが多いリポジトリを試してください。")
    sys.exit(1)

# 無視された需要のサマリー
ignored = [i for i in filtered if i["ignored_demand"]]
print(f"無視された需要のIssue数: {len(ignored)}")

json.dump(filtered, open("/tmp/ghd-filtered-issues.json", "w"), indent=2)
print("前処理が完了しました。")
PYEOF

フィルタリング後に10件未満のIssueしか残らなかった場合: 処理を停止し、ユーザーに実際に見つかりフィルタリングされたIssueの数と、そのリポジトリが需要クラスタリングに適さない理由を正確に伝えてください。


ステップ 5: Issueのクラスタリング

分析のためにフィルタリングされたIssueを表示:

python3 << 'PYEOF'
import json
filtered = json.load(open("/tmp/ghd-filtered-issues.json"))
target = open("/tmp/ghd-target.txt").read().strip()

issue_list = filtered[:150]
print(f"リポジトリ: {target}")
print(f"クラスタリング対象Issue数: {len(issue_list)}")
print()
for i in issue_list:
    labels_str = f" [{', '.join(i['labels'][:3])}]" if i['labels'] else ""
    print(f"#{i['number']} [score:{round(i['demand_score'],1)} reactions:{i['reactions_plus1']}] {i['title']}{labels_str}")
PYEOF

上記に表示された各Issueを、次の6つのカテゴリのいずれかに分類: feature_gap, bug_pattern, ux_complaint, performance, integration_missing, docs_missing

ルール:

  • 各Issueを正確に1つのカテゴリに分類
  • タイトルからユーザーの正確な表現を使用して、1文のペインステートメントを抽出。言い換えないこと
  • 5〜8個のクラスタテーマを特定:すべてのIssueにわたる支配的な不満パターンを捉えた短いフレーズ(3〜6語)
  • エムダッシュ(—)は使用禁止。マーケティング用語は使用禁止。

分析結果を以下の正確な構造で /tmp/ghd-clusters.json に書き込む:

{
  "classified_issues": [
    {"number": 123, "category": "feature_gap", "pain_statement": "ユーザーはまだ存在しないXを必要としている"}
  ],
  "cluster_themes": [
    {"theme_name": "エクスポートオプションの欠如", "category": "feature_gap", "issue_numbers": [123, 456, 789]}
  ],
  "category_counts": {"feature_gap": 5, "bug_pattern": 3, "ux_complaint": 4, "performance": 2, "integration_missing": 6, "docs_missing": 1}
}

ファイルを書き込んだ後、次のコマンドで確認:

python3 -c "
import json
d = json.load(open('/tmp/ghd-clusters.json'))
print(f'分類: {len(d[\"classified_issues\"])} Issue, {len(d[\"cluster_themes\"])} テーマ')
print('カテゴリ:', d['category_counts'])
"

ステップ 6: メッセージングブリーフ

クラスタごとの総需要スコアを計算し、上位3件を表示:

python3 << 'PYEOF'
import json

filtered = json.load(open("/tmp/ghd-filtered-issues.json"))
clusters = json.load(open("/tmp/ghd-clusters.json"))
target = open("/tmp/ghd-target.txt").read().strip()

demand_by_issue = {i["number"]: i["demand_score"] for i in filtered}
issue_titles = {i["number"]: i["title"] for i in filtered}
issue_reactions = {i["number"]: i["reactions_plus1"] for i in filtered}

enriched_themes = []
for theme in clusters.get("cluster_themes", []):
    issue_nums = theme.get("issue_numbers", [])
    total_demand = sum(demand_by_issue.get(n, 0) for n in issue_nums)
    top_issues = sorted(issue_nums, key=lambda n: demand_by_issue.get(n, 0), reverse=True)[:3]
    enriched_themes.append({
        "theme_name": theme["theme_name"],
        "category": theme["category"],
        "issue_count": len(issue_nums),
        "total_demand_score": round(total_demand, 1),
        "top_issues": [
            {"number": n, "title": issue_titles.get(n, ""), "reactions": issue_reactions.get(n, 0)}
            for n in top_issues
        ]
    })

enriched_themes.sort(key=lambda x: x["total_demand_score"], reverse=True)
json.dump(enriched_themes, open("/tmp/ghd-enriched-themes.json", "w"), indent=2)

print(f"メッセージングブリーフのための上位3クラスタ(リポジトリ: {target}):")
for t in enriched_themes[:3]:
    print(f"\n  {t['theme_name']} ({t['category']}) -- 総需要: {t['total_demand_score']}")
    for ti in t["top_issues"]:
        print(f"    #{ti['number']}: \"{ti['title']}\" ({ti['reactions']} reactions)")
PYEOF

上記で表示された上位3クラスタからGTMメッセージングブリーフを生成。

ルール:

  • 各ポジショニングの視点は、それが由来する特定のクラスタを引用すること
  • 各アウトリーチフックは、Issueタイトルを引用符で囲んでそのまま引用すること
  • 見出しには数値または具体的なペイン名を含めること。一般的な表現は不可
  • エムダッシュ(—)は使用禁止。禁止ワード:強力な、堅牢な、シームレスな、革新的な、ゲームチェンジャー、効率化、活用、変革

ブリーフを以下の正確な構造で /tmp/ghd-brief.json に書き込む:

...

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

Varnan-Tech/opendirectory

Use when the user asks to generate a blog cover image, thumbnail, or article header. Automatically uses modern typography, brand logos, and Google Search grounding to create beautiful 16:9 images with Gemini 3.1 Flash Image Preview.

Varnan-Tech/opendirectory

World-class brand strategist and naming expert. Uses an interrogation-led discovery phase to extract your brand's DNA, then applies scientific naming frameworks (Phonosemantics) and automated multi-TLD domain checking.

Varnan-Tech/opendirectory

Use when the user asks to generate or update a project's CLAUDE or AGENTS context file from a codebase scan. Writes a focused file under 100 lines containing only the non-obvious build commands, conventions, and gotchas Claude Code needs.

Varnan-Tech/opendirectory

Use when the user wants to verify cold emails, enrich a lead list, or autonomously guess email addresses from a CSV using ValidEmail.co or the open-source Reacher engine.

Varnan-Tech/opendirectory

Competitive intelligence orchestrator tracking companies across 8+ platforms (GitHub, Twitter, Reddit, HN, PH, YC Jobs) with heat scores and AI briefings.

Varnan-Tech/opendirectory

Give it your product URL or description. It finds your top 5 competitors, runs three-track PR research across all of them (editorial, podcasts, communities), identifies which channels appear most frequently, looks up the journalist or host for each, and returns a tiered outreach list with story angles and ready-to-send cold pitch drafts tailored to your product. Use when asked to find PR opportunities, discover where competitors got featured, build a media outreach list, find which journalists cover my space, or get pitch templates for press coverage.

Varnan-Tech/opendirectory

Generate high-converting, deep-dive growth case studies in MDX format. Use this skill when asked to write a case study or blog post about a company's growth, tech stack, or product-led strategy. It handles the full pipeline (researching the company via Tavily, generating a 16:9 cover image, quality checking the draft, uploading assets to cloud storage, and pushing directly to the target repository).

Varnan-Tech/opendirectory

Scans your project for outdated npm, pip, Cargo, Go, or Ruby packages. Runs a CVE security audit. Fetches changelogs, summarizes breaking changes with Gemini, and opens one PR per risk group (patch, minor, major). Includes Diagnosis Mode for install conflicts. Use when asked to update dependencies, check for outdated packages, open dependency PRs, scan for package updates, audit for CVEs, or flag breaking changes in upgrades. Trigger when a user says "check for outdated packages", "update my dependencies", "open PRs for dependency updates", "scan for CVEs", or "which packages need upgrading".

Varnan-Tech/opendirectory

Generates and updates README.md and API reference docs by reading your codebase's functions, routes, types, schemas, and architecture. Uses graphify to build a knowledge graph first, then writes accurate docs from it. Use when asked to write docs, generate a README, document an API, update stale docs, create an API reference from code, add an architecture section, or document a project in any language. Trigger when a user says their docs are missing, outdated, or wants to document their codebase without writing it manually.

Varnan-Tech/opendirectory

Evaluates expired domain candidates against a target niche, scores them by topical relevance, historical activity level, and history cleanliness, then outputs a ranked shortlist with explainable reasoning and risk flags.

Varnan-Tech/opendirectory

Brutally honest developer-experience audit for a GitHub repo. Scores 10 DX dimensions (time-to-first-success, README clarity, visual proof, install, quick-start, docs, examples, community, trust, marketing), writes a shareable roast in the requested tone (brutal/honest/kind), produces a prioritized action plan ranked by impact × effort, and sketches an ideal README. Trigger when user says "roast my repo", "audit my README", "dx audit", "developer experience review", "score my GitHub project", "before launch checklist", or "make my repo shareable".

Varnan-Tech/opendirectory

Drafts and designs a complete HTML email newsletter from a topic or content brief. Outputs paste-ready HTML for Loops, Mailchimp, Beehiiv, Resend, or any standard email platform. Includes subject line options and plain-text fallback. Trigger when a user says "write a newsletter", "draft an email newsletter", "create an HTML email", "design an email for my subscribers", or "write a newsletter about [topic]".

Varnan-Tech/opendirectory

Takes a GitHub PR URL or the current branch and writes a plain-English explanation of what it does and why, then posts it as a PR comment. Use when asked to explain a PR, summarize a pull request, write a plain-English description of a PR, add a summary comment to a PR, or understand what a PR changes. Trigger when a user says "explain this PR", "summarize this pull request", "what does this PR do", "add a comment explaining the PR", or shares a GitHub PR URL and asks what it does.

Varnan-Tech/opendirectory

Audit how often LLMs recommend your brand vs competitors and generate a GEO action plan.

Varnan-Tech/opendirectory

Find recurring confusion in your repo's GitHub Discussions, rank it by urgency, and draft the actual docs fixes and content angles — with verbatim community quotes and source links as evidence.

Varnan-Tech/opendirectory

SEO keyword research workflow for blog generation using Google Trends data. Use when writing blog posts, planning content calendars, or optimizing articles for search engines. Finds breakout keywords, builds content structure, and generates SEO-optimized blog outlines targeting tech and developer audiences.

Varnan-Tech/opendirectory

Generates a professionally designed case study PDF for B2B SaaS sales and marketing. Supports 7 page layouts, 9 style presets, 1-4 page output. Trigger when user says "create a case study", "write a customer story", "make a case study PDF", "design a success story", "turn this customer win into a PDF".

Varnan-Tech/opendirectory

Generates data visualization charts (bar, line, area, pie, doughnut, scatter, radar, treemap) as PNG using Apache ECharts v6. 1080×1080px default, 5 style presets, highlight annotations. Trigger when user says "create a chart", "visualize data", "make a bar chart", "line graph", "pie chart", "data visualization", "chart this data", "plot", "graph", or "visualize these numbers".

Varnan-Tech/opendirectory

Creates professionally designed B2B SaaS e-books in HTML + CSS, exported as print-ready PDF. 3–10 pages, 9 style presets, 11 page layout types. Trigger when user says "create an ebook", "design a lead magnet", "make a PDF guide", "build a gated content piece", "write a B2B ebook", "design a white paper", "create a nurture asset", or "make a PDF report".

Varnan-Tech/opendirectory

Fetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief with positioning opportunities.

関連スキル