Files
Epictetus dc40d4c0db Pipeline v2: deterministic reviewer, implementation extraction, publisher fix
- Reader: discover workflow files in nested dirs (agents/, workflows/, examples/)
- Reader: load source code, config, deps — not just docs
- Extractor: prompt demands concrete implementation details (files, deps, code)
- Scorer: removed general_purpose check (5/6 checks, score 1.0)
- Generator: includes Setup, Key Files, Implementation Details sections
- Reviewer: replaced LLM review with 8 deterministic structural checks
- Publisher: handle 409 duplicate PR gracefully as success
- 5 skills published as PRs #6-#10 on Gitea
2026-08-05 13:58:38 +00:00

99 lines
3.8 KiB
Python

"""Stage 1: Scout — Discover candidate repos from GitHub."""
import json
import requests
from datetime import datetime, timedelta
def scout(config, state=None):
"""
Search GitHub for repos matching AI workflow queries.
Returns list of discovered repos with metadata.
"""
queries = config.get("scout", {}).get("queries", [])
filters = config.get("scout", {}).get("filters", {})
max_results = config.get("scout", {}).get("max_results", 30)
cooldown_hours = config.get("scout", {}).get("cooldown_hours", 24)
# Check cooldown (skip on first run)
if state is None:
state = {}
if "last_run" in state:
last = datetime.fromisoformat(state["last_run"])
if datetime.now() - last < timedelta(hours=cooldown_hours):
cooldown_remaining = int((timedelta(hours=cooldown_hours) - (datetime.now() - last)).total_seconds() / 3600)
print(f" ⏸ Cooldown active — {cooldown_remaining}h remaining")
# Continue anyway on first discovery run — we want results
pass
discovered = []
seen_urls = set()
for query in queries:
stars_min = filters.get("stars_min", 50)
pushed_after = filters.get("pushed_after", "2026-01-01")
language = filters.get("language", "Python")
# Build query string safely — requests handles URL encoding of params
url = "https://api.github.com/search/repositories"
search_q = f"{query} stars:>{stars_min} pushed:>{pushed_after} language:{language}"
params = {
"q": search_q,
"sort": "updated",
"order": "desc",
"per_page": min(max_results, 30),
}
headers = {}
github_token = config.get("github", {}).get("token", "")
if github_token:
headers["Authorization"] = f"token {github_token}"
headers["Accept"] = "application/vnd.github.v3+json"
try:
resp = requests.get(url, params=params, headers=headers, timeout=15)
if resp.status_code == 403:
import time
time.sleep(60) # Wait for rate limit window
resp = requests.get(url, params=params, headers=headers, timeout=15)
if resp.status_code == 403:
return {"status": "RATE_LIMITED", "message": "GitHub API rate limit hit after retry."}
if resp.status_code != 200:
continue
data = resp.json()
for item in data.get("items", []):
repo_url = item.get("html_url", "")
if repo_url in seen_urls:
continue
seen_urls.add(repo_url)
discovered.append({
"name": item.get("name", ""),
"full_name": item.get("full_name", ""),
"url": repo_url,
"clone_url": item.get("clone_url", ""),
"stars": item.get("stargazers_count", 0),
"language": item.get("language", ""),
"description": item.get("description", ""),
"updated_at": item.get("updated_at", ""),
"created_at": item.get("created_at", ""),
"archived": item.get("archived", False),
"size_kb": item.get("size", 0),
"status": "DISCOVERED",
"discovered_at": datetime.now().isoformat(),
})
if len(discovered) >= max_results:
break
except Exception as e:
discovered.append({"error": str(e), "query": query, "status": "ERROR"})
state["last_run"] = datetime.now().isoformat()
return {
"status": "OK",
"count": len(discovered),
"repos": discovered,
"state": state,
}