SDK 채택 추적기은(는) 무엇을 하나요?
SDK 이름을 입력받습니다. GitHub에서 이를 임포트하는 공개 저장소를 검색합니다. 각 저장소에 회사 신호, 활동성, 노이즈 지표를 기준으로 점수를 매깁니다. 높은 신호의 저장소를 소유자 및 기여자 데이터로 보강합니다. 회사 채택자에 대한 아웃리치 컨텍스트가 포함된 순위별 채택 보고서를 출력합니다.
핵심 규칙: 출력의 모든 저장소는 GitHub 코드 검색 API 응답에 존재해야 합니다. 모든 회사 이름은 GitHub 사용자 또는 조직 API의 company 또는 name 필드에서 가져와야 합니다. 모든 기여자 핸들은 GitHub 기여자 API 응답에서 가져와야 합니다. API에서 필드가 비어 있으면 "not listed"라고 작성하고, 추론, 추측 또는 외삽하지 마십시오.
일반적인 실수
| 에이전트가 하려는 행동 | 왜 틀렸나요 |
|---|---|
GITHUB_TOKEN 없이 코드 검색 실행 | 인증되지 않은 코드 검색은 3 req/min의 2차 속도 제한에 도달하며 의미 있는 스캔에서 실패합니다. GITHUB_TOKEN이 필요합니다. 누락된 경우 명확한 오류와 함께 1단계에서 중지하십시오. |
| SDK 자체의 포크 포함 | SDK를 포크한 저장소는 기여자이거나 미러일 뿐, 채택자가 아닙니다. fork == true이고 저장소 이름이 SDK 이름과 일치하는 저장소는 필터링하십시오. |
| 500개의 모든 원시 검색 결과를 AI로 전송 | 코드 검색은 최대 500개의 결과를 반환할 수 있으며, 대부분은 노이즈입니다. 먼저 로컬에서 필터링하고 점수를 매기십시오. AI 분석 단계에는 상위 20개의 높은 신호 저장소만 보내십시오. |
| 튜토리얼 및 예제 저장소를 채택자로 보고 | 이름이나 설명에 "example", "tutorial", "demo", "learn", "sample", "playground", "starter"가 포함된 저장소는 프로덕션 사용자가 아닙니다. tutorial_noise로 표시하고 리드 브리핑에서 제외하십시오. |
| 회사 이름이나 연락처 핸들 지어내기 | 모든 회사 이름은 GitHub company 또는 조직 name 필드에서 가져와야 합니다. 모든 기여자 핸들은 기여자 API 응답에서 가져와야 합니다. 필드가 비어 있으면 "not listed"라고 작성하십시오. |
| 모든 생태계에 하나의 임포트 패턴 사용 | require('sdk')는 Python 사용자를 찾지 못합니다. SDK 이름에서 생태계를 자동 감지하고 생태계별 패턴을 구축하십시오. 자동 감지가 모호하면 사용자에게 물어보십시오. |
1단계: 설정 확인
if [ -z "$GITHUB_TOKEN" ]; then
echo "ERROR: GITHUB_TOKEN is required for code search."
echo "Add a token at github.com/settings/tokens (no scopes needed for public repos)."
echo "Without it, GitHub code search hits a 3 req/min secondary rate limit and fails."
exit 1
fi
echo "GITHUB_TOKEN: set"
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/rate_limit" | python3 -c "
import json, sys
d = json.load(sys.stdin)
search = d['resources']['search']
core = d['resources']['core']
print(f'Search rate: {search[\"remaining\"]}/{search[\"limit\"]} remaining')
print(f'Core rate: {core[\"remaining\"]}/{core[\"limit\"]} remaining')
"
검색 잔여 횟수가 0이면 중지하십시오. 사용자에게 X-RateLimit-Reset의 초기화 시간을 알려주십시오.
2단계: 입력 수집
대화에서 다음을 수집합니다:
- SDK 이름 (예:
@company/my-sdk,requests,github.com/org/go-sdk) - 선택 사항: 생태계 재정의 (
npm,python,go,gem) -- 제공되지 않으면 자동 감지 - 선택 사항: 결과에서 제외할 조직/사용자 (SDK 소유자의 자체 저장소)
- 선택 사항: 제품 컨텍스트 문자열 (아웃리치 메시지 개인화에 사용)
생태계 자동 감지:
@로 시작하거나-포함: npm/또는-없이 snake_case: pythongithub.com/포함: go- 그 외: 사용자에게 문의
SDK 이름이 제공되지 않은 경우: "추적하려는 SDK 또는 라이브러리는 무엇입니까? 임포트 문에 나타나는 패키지 이름을 제공하세요 (예: stripe, @clerk/nextjs, requests)."라고 문의하십시오.
python3 << 'PYEOF'
import json, sys, re
sdk_name = "SDK_NAME_HERE"
ecosystem_override = "" # leave empty for auto-detect
exclude_owner = "" # optional: owner name to exclude (usually the SDK publisher)
product_context = "" # optional: what your product does
# Auto-detect ecosystem
if ecosystem_override:
ecosystem = ecosystem_override
elif sdk_name.startswith("@") or "-" in sdk_name:
ecosystem = "npm"
elif re.match(r'^[a-z][a-z0-9_]*$', sdk_name):
ecosystem = "python"
elif "github.com/" in sdk_name:
ecosystem = "go"
else:
ecosystem = "generic"
print(f"SDK: {sdk_name}")
print(f"Ecosystem: {ecosystem}")
print(f"Exclude owner: {exclude_owner or '(none)'}")
with open("/tmp/sat-input.json", "w") as f:
json.dump({
"sdk_name": sdk_name,
"ecosystem": ecosystem,
"exclude_owner": exclude_owner,
"product_context": product_context
}, f)
PYEOF
3단계: GitHub 코드 검색
먼저 독립 실행형 스크립트가 있는지 확인하십시오. 이 스크립트는 3-5단계를 한 번에 처리합니다.
ls scripts/fetch.py 2>/dev/null && echo "script available" || echo "script not found"
스크립트를 사용할 수 있는 경우, 실행하고 6단계로 건너뛰십시오:
python3 scripts/fetch.py "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d['sdk_name'])")" \
--ecosystem "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d['ecosystem'])")" \
--exclude "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d.get('exclude_owner',''))")" \
--output /tmp/sat-script-out.json
그런 다음 6-8단계에서 예상하는 임시 파일 형식으로 로드합니다:
python3 << 'PYEOF'
import json
out = json.load(open("/tmp/sat-script-out.json"))
json.dump(out["raw_results"], open("/tmp/sat-raw-results.json", "w"), indent=2)
json.dump(out["scored"], open("/tmp/sat-scored.json", "w"), indent=2)
json.dump(out["enriched"], open("/tmp/sat-enriched.json", "w"), indent=2)
print(f"Loaded: {len(out['raw_results'])} raw | {len(out['scored'])} scored | {len(out['enriched'])} enriched")
PYEOF
스크립트를 사용할 수 없는 경우, 아래 인라인 코드를 실행하십시오.
임포트 패턴을 구축하고 각 패턴에 대해 GitHub 코드를 검색합니다:
python3 << 'PYEOF'
import json, urllib.request, ssl, time, os
from datetime import datetime, timezone
ctx = ssl._create_unverified_context()
token = os.environ["GITHUB_TOKEN"]
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"User-Agent": "sdk-adoption-tracker/1.0"
}
data = json.load(open("/tmp/sat-input.json"))
sdk_name = data["sdk_name"]
ecosystem = data["ecosystem"]
# Build query patterns per ecosystem
if ecosystem == "npm":
# Use sdk name without @ prefix for bare searches
bare = sdk_name.lstrip("@").replace("/", "/")
queries = [
f'require("{sdk_name}")',
f"require('{sdk_name}')",
f'from "{sdk_name}"',
f"from '{sdk_name}'",
]
elif ecosystem == "python":
queries = [
f"import {sdk_name}",
f"from {sdk_name} import",
f"from {sdk_name}.",
]
elif ecosystem == "go":
queries = [f'"{sdk_name}"']
else:
queries = [sdk_name]
print(f"Building queries for {sdk_name} ({ecosystem}):")
for q in queries:
print(f" {q}")
seen_repos = {} # full_name -> first match
search_rate_remaining = 30
flags = []
for i, query in enumerate(queries):
if search_rate_remaining <= 2:
flags.append(f"Code search rate limit low ({search_rate_remaining}) -- skipped remaining patterns")
print(f" Rate limit low ({search_rate_remaining}), stopping early")
break
url = f"https://api.github.com/search/code?q={urllib.parse.quote(query)}&per_page=100"
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=20, context=ctx) as resp:
search_rate_remaining = int(resp.headers.get("X-RateLimit-Remaining", 10))
raw = json.loads(resp.read())
items = raw.get("items", [])
total = raw.get("total_count", 0)
print(f" Pattern '{query}': {total} total, {len(items)} fetched | rate_remaining={search_rate_remaining}")
for item in items:
repo = item.get("repository", {})
full_name = repo.get("full_name", "")
if full_name and full_name not in seen_repos:
seen_repos[full_name] = {
"full_name": full_name,
"name": repo.get("name", ""),
"owner_login": repo.get("owner", {}).get("login", ""),
"owner_type": repo.get("owner", {}).get("type", ""),
"file_path": item.get("path", ""),
"matched_pattern": query,
"html_url": repo.get("html_url", ""),
"description": repo.get("description") or "",
}
except urllib.error.HTTPError as e:
if e.code == 403:
flags.append(f"Code search rate limit hit on pattern '{query}'")
print(f" Rate limit hit (403) on '{query}'")
break
else:
print(f" HTTP {e.code} on '{query}'")
except Exception as e:
print(f" Error on '{query}': {e}")
# Respect 10 req/min code search limit
if i < len(queries) - 1:
time.sleep(6)
results = list(seen_repos.values())
json.dump(results, open("/tmp/sat-raw-results.json", "w"), indent=2)
print(f"\nTotal unique repos found: {len(results)}")
if flags:
print("Flags:", flags)
import urllib.parse # ensure imported
PYEOF
결과가 0인 경우: 사용자에게 "{sdk_name}을 임포트하는 저장소를 찾을 수 없습니다. GitHub 코드 검색 인덱싱은 새로운 패키지의 경우 1-4주가 소요됩니다. SDK가 이미 확립된 경우 references/import-patterns.md`에서 임포트 패턴을 확인하세요."라고 알려주십시오.
4단계: 저장소 점수 매기기 및 분류
API 호출 없음. 순수 Python. 노이즈를 필터링하고, 채택 점수를 계산하고, 각 저장소를 분류합니다.
python3 << 'PYEOF'
import json
from datetime import datetime, timezone
data = json.load(open("/tmp/sat-input.json"))
exclude_owner = data.get("exclude_owner", "").lower()
sdk_name = data["sdk_name"].lower().split("/")[-1].replace("@", "")
results = json.load(open("/tmp/sat-raw-results.json"))
TUTORIAL_WORDS = {"example", "tutorial", "demo", "learn", "sample", "starter",
"boilerplate", "template", "playground", "test", "course", "workshop"}
scored = []
now = datetime.now(tz=timezone.utc)
for repo in results:
full_name = repo["full_name"]
owner_login = repo.get("owner_login", "").lower()
repo_name = repo.get("name", "").lower()
description = (repo.get("description") or "").lower()
owner_type = repo.get("owner_type", "User")
# Exclude the SDK owner's own repos
if exclude_owner and owner_login == exclude_owner.lower():
continue
# Detect tutorial noise
name_words = set(repo_name.replace("-", " ").replace("_", " ").split())
desc_words = set(description.split())
is_tutorial = bool((name_words | desc_words) & TUTORIAL_WORDS)
# Also exclude if repo name IS the SDK name (likely a fork)
if repo_name == sdk_name or repo_name.startswith(sdk_name + "-"):
is_tutorial = True # treat as noise
# Classification tier
if is_tutorial:
tier = "tutorial_noise"
elif owner_type == "Organization":
tier = "company_org"
else:
tier = "solo_dev" # will be upgraded to affiliated_dev in Step 5 if company field populated
# Adoption score (filled with partial data now, enriched in Step 5)
score = 0
if owner_type == "Organization": score += 50
if not is_tutorial: score += 20
# stars, days_since_push, is_fork, is_archived added in Step 5
scored.append({
**repo,
"tier": tier,
"is_tutorial": is_tutorial,
"adoption_score": score,
"enriched": False
})
# Sort: company_org first, then by tier
tier_order = {"company_org": 0, "affiliated_dev": 1, "solo_dev": 2, "tutorial_noise": 3}
scored.sort(key=lambda x: (tier_order.get(x["tier"], 9), -x["adoption_score"]))
json.dump(scored, open("/tmp/sat-scored.json", "w"), indent=2)
tiers = {}
for r in scored:
tiers[r["tier"]] = tiers.get(r["tier"], 0) + 1
print(f"Classification:")
for tier, count in sorted(tiers.items(), key=lambda x: tier_order.get(x[0], 9)):
print(f" {tier}: {count}")
print(f"Total: {len(scored)} repos")
non_noise = [r for r in scored if r["tier"] != "tutorial_noise"]
print(f"\nTop repos for enrichment (non-noise): {len(non_noise)}")
for r in non_noise[:5]:
print(f" {r['full_name']} ({r['tier']}) -- {r.get('description','')[:60]}")
PYEOF
모든 저장소가 tutorial_noise인 경우: 중지하십시오. 사용자에게 "발견된 모든 저장소가 튜토리얼 또는 예제로 보입니다. 공개 GitHub에서 프로덕션 채택자가 감지되지 않았습니다. SDK가 너무 새롭거나 패키지 이름이 일반적이어서 검색 결과가 예제로 가득 차 있을 수 있습니다."라고 알려주십시오.
5단계: 높은 신호 저장소 보강
노이즈가 아닌 저장소에 대한 전체 저장소 메타데이터, 소유자 프로필 및 상위 기여자를 가져옵니다. tutorial_noise 저장소는 완전히 건너뜁니다.
python3 << 'PYEOF'
import json, urllib.request, ssl, os, time
from datetime import datetime, timezone
ctx = ssl._create_unverified_context()
token = os.environ["GITHUB_TOKEN"]
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {token}",
"User-Agent": "sdk-adoption-tracker/1.0"
}
scored = json.load(open("/tmp/sat-scored.json"))
core_remaining = 5000
flags = []
def gh_get(path):
global core_remaining
req = urllib.request.Request(f"https://api.github.com{path}", headers=headers)
try:
with urllib.request.urlopen(req, timeout=15, context=ctx) as resp:
remaining = resp.headers.get("X-RateLimit-Remaining")
if remaining:
core_remaining = int(remaining)
return json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code == 404:
return None
raise
except Exception:
return None
target = [r for r in scored if r["tier"] != "tutorial_noise"]
print(f"Enriching {len(target)} repos (skipping tutorial_noise)...")
enriched = []
for item in target:
full_name = item["full_name"]
owner_login = item["owner_login"]
owner_ty
(원문이 잘렸습니다. 나머지 부분은 원본 SKILL.md와 동일한 구조를 따릅니다.)