Suivi d'Adoption de SDK
Prenez un nom de SDK. Recherchez sur GitHub les dépôts publics qui l'importent. Évaluez chaque dépôt par signal d'entreprise, activité et indicateurs de bruit. Enrichissez les dépôts à fort signal avec les données du propriétaire et des contributeurs. Produisez un rapport d'adoption classé avec un contexte de prospection pour les entreprises adoptantes.
Règle critique : Chaque dépôt dans la sortie doit exister dans la réponse de l'API de recherche de code GitHub. Chaque nom d'entreprise doit provenir du champ company ou name de l'API utilisateur ou organisation GitHub. Chaque identifiant de contributeur doit provenir de la réponse de l'API des contributeurs GitHub. Si un champ est vide dans l'API, écrivez "non listé" — n'inférez pas, ne devinez pas, n'extrapolez pas.
Erreurs Courantes
| L'agent voudra... | Pourquoi c'est faux |
|---|---|
| Lancer la recherche de code sans GITHUB_TOKEN | La recherche non authentifiée rencontre une limite de taux secondaire de 3 requêtes/minute et échoue sur toute analyse significative. GITHUB_TOKEN est requis. Arrêtez à l'étape 1 avec une erreur claire s'il manque. |
| Inclure les forks du SDK lui-même | Les dépôts qui forment le SDK sont des contributeurs ou des miroirs, pas des adoptants. Filtrez les dépôts où fork == true ET le nom du dépôt correspond au nom du SDK. |
| Envoyer les 500 résultats bruts de la recherche à l'IA | La recherche de code peut retourner jusqu'à 500 résultats, dont la plupart sont du bruit. Filtrez et évaluez d'abord localement. N'envoyez que les 20 meilleurs dépôts à fort signal à l'étape d'analyse IA. |
| Signaler les dépôts de tutoriel et d'exemple comme adoptants | Les dépôts avec "example", "tutorial", "demo", "learn", "sample", "playground", "starter" dans le nom ou la description ne sont pas des utilisateurs en production. Marquez-les comme tutorial_noise et excluez-les des briefs de prospection. |
| Inventer des noms d'entreprise ou des identifiants de contact | Chaque nom d'entreprise doit provenir du champ company ou du nom d'organisation GitHub. Chaque identifiant de contributeur doit provenir de la réponse de l'API des contributeurs. Si un champ est vide, écrivez "non listé". |
| Utiliser un seul motif d'import pour tous les écosystèmes | require('sdk') ne trouvera pas les utilisateurs Python. Détectez automatiquement l'écosystème à partir du nom du SDK et construisez des motifs spécifiques à l'écosystème. Demandez à l'utilisateur si la détection automatique est ambiguë. |
Étape 1 : Vérification de Configuration
if [ -z "$GITHUB_TOKEN" ]; then
echo "ERREUR : GITHUB_TOKEN est requis pour la recherche de code."
echo "Ajoutez un jeton sur github.com/settings/tokens (aucun scope nécessaire pour les dépôts publics)."
echo "Sans cela, la recherche de code GitHub atteint une limite de taux secondaire de 3 req/min et échoue."
exit 1
fi
echo "GITHUB_TOKEN : défini"
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'Limite de recherche : {search[\"remaining\"]}/{search[\"limit\"]} restantes')
print(f'Limite de base : {core[\"remaining\"]}/{core[\"limit\"]} restantes')
"
Si les recherches restantes sont à 0 : arrêtez. Dites à l'utilisateur l'heure de réinitialisation à partir de X-RateLimit-Reset.
Étape 2 : Recueillir les Entrées
Collectez à partir de la conversation :
- Nom du SDK (ex.
@company/my-sdk,requests,github.com/org/go-sdk) - Optionnel : remplacement d'écosystème (
npm,python,go,gem) -- détecté automatiquement si non fourni - Optionnel : organisation/utilisateur à exclure des résultats (les propres dépôts du propriétaire du SDK)
- Optionnel : chaîne de contexte produit (utilisée pour personnaliser les messages de prospection)
Détection automatique de l'écosystème :
- Commence par
@ou contient-: npm - snake_case sans
/ni-: python - Contient
github.com/: go - Sinon : demandez à l'utilisateur
Si aucun nom de SDK n'est fourni : Demandez : "Quel SDK ou bibliothèque souhaitez-vous suivre ? Fournissez le nom du package tel qu'il apparaît dans les instructions d'import (ex. stripe, @clerk/nextjs, requests)."
python3 << 'PYEOF'
import json, sys, re
sdk_name = "NOM_DU_SDK_ICI"
ecosystem_override = "" # laissez vide pour détection automatique
exclude_owner = "" # optionnel : nom du propriétaire à exclure (généralement l'éditeur du SDK)
product_context = "" # optionnel : ce que fait votre produit
# Détection automatique de l'écosystème
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 = "générique"
print(f"SDK : {sdk_name}")
print(f"Écosystème : {ecosystem}")
print(f"Propriétaire exclu : {exclude_owner or '(aucun)'}")
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
Étape 3 : Rechercher du Code sur GitHub
Vérifiez d'abord s'il existe un script autonome -- il gère les étapes 3 à 5 en un seul appel.
ls scripts/fetch.py 2>/dev/null && echo "script disponible" || echo "script non trouvé"
Si le script est disponible, exécutez-le et passez à l'étape 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
Ensuite, chargez dans le format de fichier temporaire attendu par les étapes 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"Chargé : {len(out['raw_results'])} bruts | {len(out['scored'])} évalués | {len(out['enriched'])} enrichis")
PYEOF
Si le script n'est pas disponible, exécutez le code en ligne ci-dessous.
Construisez des motifs d'import et recherchez du code GitHub pour chacun :
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"]
# Construire les motifs de requête par écosystème
if ecosystem == "npm":
# Utiliser le nom du SDK sans préfixe @ pour les recherches nues
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"Construction des requêtes pour {sdk_name} ({ecosystem}) :")
for q in queries:
print(f" {q}")
seen_repos = {} # full_name -> première correspondance
search_rate_remaining = 30
flags = []
for i, query in enumerate(queries):
if search_rate_remaining <= 2:
flags.append(f"Limite de taux de recherche de code basse ({search_rate_remaining}) -- motifs restants ignorés")
print(f" Limite de taux basse ({search_rate_remaining}), arrêt précoce")
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" Motif '{query}' : {total} total, {len(items)} récupérés | taux_restant={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"Limite de taux de recherche de code atteinte sur le motif '{query}'")
print(f" Limite de taux atteinte (403) sur '{query}'")
break
else:
print(f" HTTP {e.code} sur '{query}'")
except Exception as e:
print(f" Erreur sur '{query}' : {e}")
# Respecter la limite de 10 requêtes/min de recherche de code
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 de dépôts uniques trouvés : {len(results)}")
if flags:
print("Drapeaux :", flags)
import urllib.parse # s'assurer qu'il est importé
PYEOF
Si 0 résultats : Dites à l'utilisateur : "Aucun dépôt trouvé important {sdk_name}. L'indexation de la recherche de code GitHub prend 1 à 4 semaines pour les nouveaux packages. Si le SDK est établi, vérifiez les motifs d'import dans references/import-patterns.md."
Étape 4 : Évaluer et Classer les Dépôts
Pas d'appel API. Python pur. Filtrez le bruit, calculez le score d'adoption, classez chaque dépôt.
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")
# Exclure les propres dépôts du propriétaire du SDK
if exclude_owner and owner_login == exclude_owner.lower():
continue
# Détection du bruit de tutoriel
name_words = set(repo_name.replace("-", " ").replace("_", " ").split())
desc_words = set(description.split())
is_tutorial = bool((name_words | desc_words) & TUTORIAL_WORDS)
# Exclure également si le nom du dépôt EST le nom du SDK (probablement un fork)
if repo_name == sdk_name or repo_name.startswith(sdk_name + "-"):
is_tutorial = True # traiter comme bruit
# Niveau de classification
if is_tutorial:
tier = "tutorial_noise"
elif owner_type == "Organization":
tier = "company_org"
else:
tier = "solo_dev" # sera mis à niveau vers affiliated_dev à l'étape 5 si le champ entreprise est renseigné
# Score d'adoption (rempli avec des données partielles maintenant, enrichi à l'étape 5)
score = 0
if owner_type == "Organization": score += 50
if not is_tutorial: score += 20
# étoiles, jours_depuis_poussée, est_fork, est_archivé ajoutés à l'étape 5
scored.append({
**repo,
"tier": tier,
"is_tutorial": is_tutorial,
"adoption_score": score,
"enriched": False
})
# Trier : company_org en premier, puis par niveau
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)} dépôts")
non_noise = [r for r in scored if r["tier"] != "tutorial_noise"]
print(f"\nMeilleurs dépôts pour enrichissement (hors bruit) : {len(non_noise)}")
for r in non_noise[:5]:
print(f" {r['full_name']} ({r['tier']}) -- {r.get('description','')[:60]}")
PYEOF
Si tous les dépôts sont tutorial_noise : Arrêtez. Dites à l'utilisateur : "Tous les dépôts trouvés semblent être des tutoriels ou des exemples. Aucun adoptant en production détecté dans les dépôts publics GitHub. Le SDK est peut-être trop récent, ou le nom du package est suffisamment générique pour que les résultats de recherche soient dominés par des exemples."
Étape 5 : Enrichir les Dépôts à Fort Signal
Récupérez les métadonnées complètes du dépôt, le profil du propriétaire et les meilleurs contributeurs pour les dépôts hors bruit. Ignorez complètement les dépôts 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"Enrichissement de {len(target)} dépôts (ignore tutorial_noise)...")
enriched = []
for item in target:
full_name = item["full_name"]
owner_login = item["owner_login"]
owner_ty