Files
Reza Rezvani 2f57ef8948 feat(agenthub): add AgentHub plugin with cross-domain examples, SEO optimization, and docs site fixes
- AgentHub: 13 files updated with non-engineering examples (content drafts,
  research, strategy) — engineering stays primary, cross-domain secondary
- AgentHub: 7 slash commands, 5 Python scripts, 3 references, 1 agent,
  dry_run.py validation (57 checks)
- Marketplace: agenthub entry added with cross-domain keywords, engineering
  POWERFUL updated (25→30), product (12→13), counts synced across all configs
- SEO: generate-docs.py now produces keyword-rich <title> tags and meta
  descriptions using SKILL.md frontmatter — "Claude Code Skills" in site_name
  propagates to all 276 HTML pages
- SEO: per-domain title suffixes (Agent Skill for Codex & OpenClaw, etc.),
  slug-as-title cleanup, domain label stripping from titles
- Broken links: 141→0 warnings — new rewrite_skill_internal_links() converts
  references/, scripts/, assets/ links to GitHub source URLs; skills/index.md
  phantom slugs fixed (6 marketing, 7 RA/QM)
- Counts synced: 204 skills, 266 tools, 382 refs, 16 agents, 17 commands,
  21 plugins — consistent across CLAUDE.md, README.md, docs/index.md,
  marketplace.json, getting-started.md, mkdocs.yml
- Platform sync: Codex 163 skills, Gemini 246 items, OpenClaw compatible

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 12:10:46 +01:00

276 lines
8.7 KiB
Python

#!/usr/bin/env python3
"""Analyze the AgentHub git DAG.
Detects frontier branches (leaves with no children), displays DAG graphs,
and shows per-agent branch status for a session.
Usage:
python dag_analyzer.py --frontier --session 20260317-143022
python dag_analyzer.py --graph
python dag_analyzer.py --status --session 20260317-143022
python dag_analyzer.py --demo
"""
import argparse
import json
import os
import re
import subprocess
import sys
from datetime import datetime
def run_git(*args):
"""Run a git command and return stdout."""
try:
result = subprocess.run(
["git"] + list(args),
capture_output=True, text=True, check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
print(f"Git error: {e.stderr.strip()}", file=sys.stderr)
return ""
def get_hub_branches(session_id=None):
"""Get all hub/* branches, optionally filtered by session."""
output = run_git("branch", "--list", "hub/*", "--format=%(refname:short)")
if not output:
return []
branches = output.strip().split("\n")
if session_id:
prefix = f"hub/{session_id}/"
branches = [b for b in branches if b.startswith(prefix)]
return branches
def get_branch_commit(branch):
"""Get the commit hash for a branch."""
return run_git("rev-parse", "--short", branch)
def get_branch_commit_count(branch, base_branch="main"):
"""Count commits ahead of base branch."""
output = run_git("rev-list", "--count", f"{base_branch}..{branch}")
try:
return int(output)
except ValueError:
return 0
def get_branch_last_commit_date(branch):
"""Get the last commit date for a branch."""
output = run_git("log", "-1", "--format=%ci", branch)
if output:
return output[:19]
return "unknown"
def get_branch_last_commit_msg(branch):
"""Get the last commit message for a branch."""
return run_git("log", "-1", "--format=%s", branch)
def detect_frontier(session_id=None):
"""Find frontier branches (tips with no child branches).
A branch is on the frontier if no other hub branch contains its tip commit
as an ancestor (i.e., it has no children in the DAG).
"""
branches = get_hub_branches(session_id)
if not branches:
return []
# Get commit hashes for all branches
branch_commits = {}
for b in branches:
commit = run_git("rev-parse", b)
if commit:
branch_commits[b] = commit
# A branch is frontier if its commit is not an ancestor of any other branch
frontier = []
for branch, commit in branch_commits.items():
is_ancestor = False
for other_branch, other_commit in branch_commits.items():
if other_branch == branch:
continue
# Check if commit is ancestor of other_commit
result = subprocess.run(
["git", "merge-base", "--is-ancestor", commit, other_commit],
capture_output=True
)
if result.returncode == 0:
is_ancestor = True
break
if not is_ancestor:
frontier.append(branch)
return frontier
def show_graph():
"""Display the git DAG graph for hub branches."""
branches = get_hub_branches()
if not branches:
print("No hub/* branches found.")
return
# Use git log with graph for hub branches
branch_args = [b for b in branches]
output = run_git(
"log", "--all", "--oneline", "--graph", "--decorate",
"--simplify-by-decoration",
*[f"--branches=hub/*"]
)
if output:
print(output)
else:
print("No hub commits found.")
def show_status(session_id, output_format="table"):
"""Show per-agent branch status for a session."""
branches = get_hub_branches(session_id)
if not branches:
print(f"No branches found for session {session_id}")
return
frontier = detect_frontier(session_id)
# Parse agent info from branch names
agents = []
for branch in sorted(branches):
# Pattern: hub/{session}/agent-{N}/attempt-{M}
match = re.match(r"hub/[^/]+/agent-(\d+)/attempt-(\d+)", branch)
if match:
agent_num = int(match.group(1))
attempt = int(match.group(2))
else:
agent_num = 0
attempt = 1
commit = get_branch_commit(branch)
commits = get_branch_commit_count(branch)
last_date = get_branch_last_commit_date(branch)
last_msg = get_branch_last_commit_msg(branch)
is_frontier = branch in frontier
agents.append({
"agent": agent_num,
"attempt": attempt,
"branch": branch,
"commit": commit,
"commits_ahead": commits,
"last_update": last_date,
"last_message": last_msg,
"frontier": is_frontier,
})
if output_format == "json":
print(json.dumps({"session": session_id, "agents": agents}, indent=2))
return
# Table output
print(f"Session: {session_id}")
print(f"Branches: {len(branches)} | Frontier: {len(frontier)}")
print()
header = f"{'AGENT':<8} {'BRANCH':<45} {'COMMITS':<8} {'STATUS':<10} {'LAST UPDATE':<20}"
print(header)
print("-" * len(header))
for a in agents:
status = "frontier" if a["frontier"] else "merged"
print(f"agent-{a['agent']:<4} {a['branch']:<45} {a['commits_ahead']:<8} {status:<10} {a['last_update']:<20}")
def run_demo():
"""Show demo output."""
print("=" * 60)
print("AgentHub DAG Analyzer — Demo Mode")
print("=" * 60)
print()
print("--- Frontier Detection ---")
print("Frontier branches (leaves with no children):")
print(" hub/20260317-143022/agent-1/attempt-1 (3 commits ahead)")
print(" hub/20260317-143022/agent-2/attempt-1 (5 commits ahead)")
print(" hub/20260317-143022/agent-3/attempt-1 (2 commits ahead)")
print()
print("--- Session Status ---")
print("Session: 20260317-143022")
print("Branches: 3 | Frontier: 3")
print()
header = f"{'AGENT':<8} {'BRANCH':<45} {'COMMITS':<8} {'STATUS':<10} {'LAST UPDATE':<20}"
print(header)
print("-" * len(header))
print(f"{'agent-1':<8} {'hub/20260317-143022/agent-1/attempt-1':<45} {'3':<8} {'frontier':<10} {'2026-03-17 14:35:10':<20}")
print(f"{'agent-2':<8} {'hub/20260317-143022/agent-2/attempt-1':<45} {'5':<8} {'frontier':<10} {'2026-03-17 14:36:45':<20}")
print(f"{'agent-3':<8} {'hub/20260317-143022/agent-3/attempt-1':<45} {'2':<8} {'frontier':<10} {'2026-03-17 14:34:22':<20}")
print()
print("--- DAG Graph ---")
print("* abc1234 (hub/20260317-143022/agent-2/attempt-1) Replaced O(n²) with hash map")
print("* def5678 Added benchmark tests")
print("| * ghi9012 (hub/20260317-143022/agent-1/attempt-1) Added caching layer")
print("| * jkl3456 Refactored data access")
print("|/")
print("| * mno7890 (hub/20260317-143022/agent-3/attempt-1) Minor optimizations")
print("|/")
print("* pqr1234 (dev) Base commit")
def main():
parser = argparse.ArgumentParser(
description="Analyze the AgentHub git DAG"
)
parser.add_argument("--frontier", action="store_true",
help="List frontier branches (leaves with no children)")
parser.add_argument("--graph", action="store_true",
help="Show ASCII DAG graph for hub branches")
parser.add_argument("--status", action="store_true",
help="Show per-agent branch status")
parser.add_argument("--session", type=str,
help="Filter by session ID")
parser.add_argument("--format", choices=["table", "json"], default="table",
help="Output format (default: table)")
parser.add_argument("--demo", action="store_true",
help="Show demo output")
args = parser.parse_args()
if args.demo:
run_demo()
return
if not any([args.frontier, args.graph, args.status]):
parser.print_help()
return
if args.frontier:
frontier = detect_frontier(args.session)
if args.format == "json":
print(json.dumps({"frontier": frontier}, indent=2))
else:
if frontier:
print("Frontier branches:")
for b in frontier:
print(f" {b}")
else:
print("No frontier branches found.")
print()
if args.graph:
show_graph()
print()
if args.status:
if not args.session:
print("Error: --session required with --status", file=sys.stderr)
sys.exit(1)
show_status(args.session, args.format)
if __name__ == "__main__":
main()