Agent Skills Pipeline v1.0

8-stage pipeline: Scout → Filter → Reader → Extractor → Score → Generator → Reviewer → Publisher
- Scout: GitHub search with token auth + rate limit retry
- Filter: Deterministic rules (language, stars, age, keywords)
- Reader: Incremental context loading (README → docs → examples → code)
- Extractor: LLM workflow extraction with JSON retry
- Score: Rule-based evaluation (no LLM)
- Generator: Standardized Hermes Skill format
- Reviewer: Independent LLM review (separate from generator)
- Publisher: Branch + PR to Gitea

First run: 5 repos discovered, 0 extracted (correct — all frameworks, no workflows)
This commit is contained in:
VPS admin
2026-08-05 05:51:06 +00:00
commit 8da8d703da
25 changed files with 1159 additions and 0 deletions
+164
View File
@@ -0,0 +1,164 @@
#!/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
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()