Add Publisher v2 + 5 extracted skills

Publisher fixes:
- Checkout new branch before push (was pushing main ref)
- Verify files staged before commit
- Handle duplicate files gracefully
- Clean error reporting per stage

Skills merged to main:
- mcp-server-setup (from pipeshub-ai)
- research-pipeline (from Blacknode)
- multi-agent-sequential-workflow (from Fast-LLM-Agent-MCP)
- unifai-workflow-execution (from UnifAI)
- code-review-agent-workflow (from AgentKit)
This commit is contained in:
Epictetus
2026-08-05 15:15:09 +00:00
parent 09b62adb93
commit 5f917f4121
26 changed files with 782 additions and 37 deletions
+79 -37
View File
@@ -1,10 +1,10 @@
"""Stage 8: Publisher — Create branch, commit, open PR on Gitea."""
import json
import subprocess
import os
import tempfile
import shutil
import datetime
import requests
def publish_skill(review_result, config):
"""
@@ -33,23 +33,20 @@ def publish_skill(review_result, config):
branch_name = f"skill/{skill_name}-{ts}"
with tempfile.TemporaryDirectory() as tmpdir:
# Clone repo
repo_dir = os.path.join(tmpdir, "agent-skills")
# Clone repo
result = subprocess.run(
["git", "clone", "--branch", "main", "--single-branch", clone_url, repo_dir],
["git", "clone", "--branch", "main", "--depth", "1", clone_url, repo_dir],
capture_output=True, text=True, timeout=30
)
if result.returncode != 0:
# Try without --branch (might not exist yet)
result = subprocess.run(
["git", "clone", clone_url, repo_dir],
["git", "clone", "--depth", "1", clone_url, repo_dir],
capture_output=True, text=True, timeout=30
)
if result.returncode != 0:
return {
"status": "CLONE_ERROR",
"error": result.stderr[:500],
}
return {"status": "CLONE_ERROR", "error": result.stderr[:500]}
# Configure git
subprocess.run(["git", "config", "user.email", "hermes@agent.local"], cwd=repo_dir)
@@ -59,34 +56,80 @@ def publish_skill(review_result, config):
skill_dir = os.path.join(repo_dir, "skills", skill_name)
os.makedirs(skill_dir, exist_ok=True)
# Write files
# Write skill files
for filename, content in files.items():
filepath = os.path.join(skill_dir, filename)
with open(filepath, 'w') as f:
with open(filepath, "w") as f:
f.write(content)
# Add and commit
subprocess.run(["git", "add", "."], cwd=repo_dir, capture_output=True)
subprocess.run(
["git", "commit", "-m", f"Add Skill: {skill_name}\n\nExtracted from: {gen.get('metadata', {}).get('source_repo', 'unknown')}\nScore: {gen.get('metadata', {}).get('score', 0)}"],
cwd=repo_dir, capture_output=True
# Verify files were written
written_files = []
for root, dirs, fnames in os.walk(skill_dir):
for fn in fnames:
written_files.append(os.path.join(root, fn))
if not written_files:
return {"status": "EMPTY_SKILL", "reason": "No files written to skill directory"}
# Stage and commit
add_result = subprocess.run(
["git", "add", "skills/"], cwd=repo_dir, capture_output=True, text=True
)
# Check if there are actually staged changes
status_result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
cwd=repo_dir, capture_output=True, text=True
)
staged_files = status_result.stdout.strip().split("\n") if status_result.stdout.strip() else []
if not staged_files:
# Nothing to commit — files might already exist. Force add.
subprocess.run(["git", "add", "-f", "skills/"], cwd=repo_dir, capture_output=True, text=True)
status_result = subprocess.run(
["git", "diff", "--cached", "--name-only"],
cwd=repo_dir, capture_output=True, text=True
)
staged_files = status_result.stdout.strip().split("\n") if status_result.stdout.strip() else []
if not staged_files:
return {
"status": "NO_CHANGES",
"reason": f"No new files to commit for {skill_name}. Files already exist in repo.",
}
commit_result = subprocess.run(
[
"git", "commit", "-m",
f"Add Skill: {skill_name}\n\nExtracted from: {gen.get('metadata', {}).get('source_repo', 'unknown')}\nScore: {gen.get('metadata', {}).get('score', 0)}"
],
cwd=repo_dir, capture_output=True, text=True
)
if commit_result.returncode != 0:
return {
"status": "COMMIT_ERROR",
"error": commit_result.stderr[:500],
}
# Checkout new branch
checkout_result = subprocess.run(
["git", "checkout", "-b", branch_name],
cwd=repo_dir, capture_output=True, text=True
)
if checkout_result.returncode != 0:
return {
"status": "CHECKOUT_ERROR",
"error": checkout_result.stderr[:500],
}
# Push branch
auth_url = clone_url.replace("http://", f"http://tonyjbala:{token}@")
push_result = subprocess.run(
["git", "push", "-u", auth_url, f"main:{branch_name}"],
capture_output=True, text=True, timeout=30
["git", "push", "-u", auth_url, branch_name],
cwd=repo_dir, capture_output=True, text=True, timeout=30
)
if push_result.returncode != 0:
# Try creating from current branch
subprocess.run(["git", "checkout", "-b", branch_name], cwd=repo_dir, capture_output=True)
push_result = subprocess.run(
["git", "push", "-u", auth_url, branch_name],
capture_output=True, text=True, timeout=30
)
if push_result.returncode != 0:
return {
"status": "PUSH_ERROR",
@@ -97,19 +140,19 @@ def publish_skill(review_result, config):
pr_url = f"{base_url}/api/v1/repos/{owner}/{repo_name}/pulls"
pr_payload = {
"title": f"Add Skill: {skill_name}",
"body": f"## Skill: {skill_name}\n\n"
f"**Goal:** {gen.get('metadata', {}).get('goal', '')}\n"
f"**Source:** {gen.get('metadata', {}).get('source_repo', '')}\n"
f"**Score:** {gen.get('metadata', {}).get('score', 0)}\n"
f"**Confidence:** {gen.get('metadata', {}).get('confidence', 0)}\n"
f"**Review:** {review_result.get('reason', '')}\n\n"
f"### Files\n"
+ "".join(f"- `{f}`\n" for f in files.keys()),
"body": (
f"## Skill: {skill_name}\n\n"
f"**Goal:** {gen.get('metadata', {}).get('goal', '')}\n"
f"**Source:** {gen.get('metadata', {}).get('source_repo', '')}\n"
f"**Score:** {gen.get('metadata', {}).get('score', 0)}\n"
f"**Confidence:** {gen.get('metadata', {}).get('confidence', 0)}\n\n"
f"### Files\n"
+ "".join(f"- `{f}`\n" for f in files.keys())
),
"head": branch_name,
"base": "main",
}
import requests
headers = {
"Authorization": f"token {token}",
"Content-Type": "application/json",
@@ -127,7 +170,6 @@ def publish_skill(review_result, config):
"message": f"PR opened: {pr_data.get('html_url', '')}",
}
elif resp.status_code == 409:
# PR already exists for this branch
return {
"status": "PUBLISHED",
"skill_name": skill_name,