#!/usr/bin/env python3 """Agent Skills Pipeline Runner — full 8-stage pipeline.""" import yaml import json import sys import os import datetime sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from pipeline.scout import scout from pipeline.filter import filter_repos from pipeline.reader import read_repo from pipeline.extractor import extract_workflow from pipeline.scorer import score_workflow from pipeline.generator import generate_skill from pipeline.reviewer import review_skill from pipeline.publisher import publish_skill def load_config(): config_path = os.path.join(os.path.dirname(__file__), "config", "settings.yaml") with open(config_path) as f: return yaml.safe_load(f) def main(): config = load_config() runs_dir = os.path.join(os.path.dirname(__file__), "runs") os.makedirs(runs_dir, exist_ok=True) run_id = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") run_log = {"run_id": run_id, "started_at": datetime.datetime.now().isoformat(), "stages": {}} print("=" * 60) print("Agent Skills Pipeline — Run", run_id) print("=" * 60) # --- Stage 1: Scout --- print("\n[1/8] Scout — Discovering repos from GitHub...") state = {} scout_result = scout(config, state) if scout_result.get("status") in ("COOLDOWN", "RATE_LIMITED"): print(f" ⏸ {scout_result.get('message', scout_result.get('status'))}") return repos = scout_result.get("repos", []) print(f" Found {scout_result.get('count', 0)} repos") run_log["stages"]["scout"] = {"count": scout_result.get("count", 0)} if not repos: print(" No repos found. Exiting.") return # --- Stage 2: Filter --- print("\n[2/8] Filter — Applying deterministic rules...") filter_result = filter_repos(repos, config) kept = filter_result.get("kept", []) print(f" Kept: {filter_result.get('kept_count', 0)} | Rejected: {filter_result.get('rejected_count', 0)}") run_log["stages"]["filter"] = {"kept": filter_result.get("kept_count", 0), "rejected": filter_result.get("rejected_count", 0)} for rej in filter_result.get("rejected", [])[:3]: print(f" ✗ {rej['repo'].get('name', '?')}: {', '.join(rej['reasons'])}") if not kept: print(" All repos filtered out. Exiting.") return # --- Stages 3-8: Process each kept repo --- print(f"\n[3-8/8] Processing {len(kept)} repos through pipeline...") results = {"extracted": 0, "scored": 0, "generated": 0, "reviewed": 0, "published": 0} for i, repo in enumerate(kept[:5]): # Cap at 5 per run repo_name = repo.get("full_name", repo.get("name", "?")) repo_url = repo.get("url", "") print(f"\n ── Repo {i+1}/{min(len(kept), 5)}: {repo_name} ──") # Stage 3: Reader print(f" [3/8] Reader — Loading context...") reader_output = read_repo(repo.get("clone_url", repo_url), config) if reader_output.get("status") == "INSUFFICIENT": print(f" ⏸ Insufficient context: {reader_output.get('decision_reason', '')}") continue print(f" Loaded {len(reader_output.get('context_loaded', []))} files") # Stage 4: Extractor print(f" [4/8] Extractor — Looking for reusable workflow...") extract_output = extract_workflow(reader_output, config) if extract_output.get("status") != "EXTRACTED": print(f" ✗ No workflow: {extract_output.get('reason', extract_output.get('status', ''))}") continue workflow = extract_output.get("workflow", {}) print(f" ✓ Extracted: {workflow.get('skill_name', '?')} (confidence: {workflow.get('confidence', 0)})") results["extracted"] += 1 # Stage 5: Scorer print(f" [5/8] Scorer — Evaluating...") extract_output["reader_output"] = reader_output score_output = score_workflow(extract_output, config) if score_output.get("decision") != "PASS": print(f" ✗ Score {score_output.get('score', 0)} < {score_output.get('min_score', 0.85)}") continue print(f" ✓ Score: {score_output.get('score', 0)} (passed)") results["scored"] += 1 # Stage 6: Generator print(f" [6/8] Generator — Building Skill package...") gen_output = generate_skill(score_output, config) if gen_output.get("status") != "GENERATED": print(f" ✗ Generation blocked: {gen_output.get('reason', '')}") continue print(f" ✓ Generated: {gen_output.get('skill_name', '?')} ({len(gen_output.get('files', {}))} files)") results["generated"] += 1 # Stage 7: Reviewer print(f" [7/8] Reviewer — LLM review...") review_output = review_skill(gen_output, config) if review_output.get("status") != "APPROVED": print(f" ✗ Review: {review_output.get('status', '?')} — {review_output.get('reason', '')[:100]}") continue print(f" ✓ Approved (confidence: {review_output.get('confidence', 0)})") results["reviewed"] += 1 # Stage 8: Publisher print(f" [8/8] Publisher — Creating PR...") publish_output = publish_skill(review_output, config) if publish_output.get("status") == "PUBLISHED": print(f" ✓ Published! PR: {publish_output.get('pr_url', '')}") results["published"] += 1 elif publish_output.get("status") == "SKIP": print(f" ⏸ Skipped: {publish_output.get('reason', '')}") else: print(f" ! {publish_output.get('status', '?')}: {publish_output.get('message', publish_output.get('error', ''))[:100]}") # --- Summary --- print("\n" + "=" * 60) print("PIPELINE COMPLETE") print("=" * 60) print(f" Scout: {scout_result.get('count', 0)} discovered") print(f" Filter: {filter_result.get('kept_count', 0)} kept / {filter_result.get('rejected_count', 0)} rejected") print(f" Extracted: {results['extracted']}") print(f" Scored: {results['scored']}") print(f" Generated: {results['generated']}") print(f" Reviewed: {results['reviewed']}") print(f" Published: {results['published']}") # Save run log run_log["results"] = results run_log["ended_at"] = datetime.datetime.now().isoformat() log_path = os.path.join(runs_dir, f"{run_id}.json") with open(log_path, 'w') as f: json.dump(run_log, f, indent=2) print(f"\n Run log: {log_path}") if __name__ == "__main__": main()