Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# git.createpr
Create GitHub pull requests with auto-generated titles and descriptions based on commit analysis. Analyzes commit history, identifies related issues, and creates well-formatted PRs with proper linking and metadata. - git-commits - Commit history between base and feature branch - git-repository - Local git repository with commit information - github-credentials - GitHub token for API access (from gh CLI or environment) - pull-request - Created GitHub pull request with metadata - pr-report - Summary of PR creation including URL, number, and status - base_branch (string): Base branch for PR (default: main) - title (string): PR title (optional, auto-generated from commits if not provided) - draft (boolean): Create as draft PR (default: false) - auto_merge (boolean): Enable auto-merge if checks pass (default: false) - reviewers (array): List of GitHub usernames to request reviews from - labels (array): Labels to apply to PR (optional, auto-detected from commits) - body (string): PR description (optional, auto-generated if not provided) - git command line tool - GitHub CLI (gh) or GitHub API access with token - Access to git repository - GitHub repository with permissions to create PRs 1. Validate we're in a git repository 2. Get current branch name 3. Validate base branch exists 4. Fetch latest changes from remote 5. Get commit history between base and current branch 6. Analyze commits to extract: - Commit messages - Conventional commit types (feat, fix, docs, etc.) - Issue references (#123) - Breaking changes 7. Generate PR title (if not provided): - Use most recent commit message - Or summarize multiple commits - Format: "type(scope): description" 8. Generate PR description (if not provided): - Summary of changes - List of commits with links - Related issues section - Breaking changes warning (if any) 9. Detect labels from commit types: - feat → enhancement - fix → bug - docs → documentation - etc. 10. Create PR using GitHub CLI (gh pr create): - Set title and body - Set base and head branches - Apply labels - Request reviewers - Set draft status 11. Parse PR URL and number from output 12. Return structured result with PR metadata ```bash python3 skills/git.createpr/git_createpr.py python3 skills/git.createpr/git_createpr.py --draft python3 skills/git.createpr/git_createpr.py --reviewers alice bob python3 skills/git.createpr/git_createpr.py --base develop python3 skills/git.createpr/git_createpr.py --title "feat: add user authentication" python3 skills/git.createpr/git_createpr.py --labels enhancement breaking-change ``` ```json { "ok": true, "status": "success", "pr_number": 123, "pr_url": "https://github.com/owner/repo/pull/123", "title": "feat: add user authentication", "base_branch": "main", "head_branch": "feature/auth", "commits_analyzed": 5, "issues_linked": ["#45", "#67"], "labels_applied": ["enhancement", "feature"], "reviewers_requested": ["alice", "bob"], "is_draft": false } ``` - git - github - pull-request - automation - workflow - pr This skill requires SKILL_AND_COMMAND pattern due to: - 12 steps (exceeds threshold) - High autonomy (auto-generates PR content intelligently) - Highly reusable for release automation and CI/CD - Complex GitHub API interaction - Commit analysis and pattern detection - Multiple execution contexts (CLI, agents, workflows) This implementation uses GitHub CLI (gh) for simplicity and authentication: - Leverages existing gh authentication - Simpler than managing GitHub API tokens - Better error messages - Handles pagination automatically If gh CLI is not available, falls back to REST API with token from: - GITHUB_TOKEN environment variable - GH_TOKEN environment variable
## Overview
**Purpose:** Create GitHub pull requests with auto-generated titles and descriptions based on commit analysis. Analyzes commit history, identifies related issues, and creates well-formatted PRs with proper linking and metadata. - git-commits - Commit history between base and feature branch - git-repository - Local git repository with commit information - github-credentials - GitHub token for API access (from gh CLI or environment) - pull-request - Created GitHub pull request with metadata - pr-report - Summary of PR creation including URL, number, and status - base_branch (string): Base branch for PR (default: main) - title (string): PR title (optional, auto-generated from commits if not provided) - draft (boolean): Create as draft PR (default: false) - auto_merge (boolean): Enable auto-merge if checks pass (default: false) - reviewers (array): List of GitHub usernames to request reviews from - labels (array): Labels to apply to PR (optional, auto-detected from commits) - body (string): PR description (optional, auto-generated if not provided) - git command line tool - GitHub CLI (gh) or GitHub API access with token - Access to git repository - GitHub repository with permissions to create PRs 1. Validate we're in a git repository 2. Get current branch name 3. Validate base branch exists 4. Fetch latest changes from remote 5. Get commit history between base and current branch 6. Analyze commits to extract: - Commit messages - Conventional commit types (feat, fix, docs, etc.) - Issue references (#123) - Breaking changes 7. Generate PR title (if not provided): - Use most recent commit message - Or summarize multiple commits - Format: "type(scope): description" 8. Generate PR description (if not provided): - Summary of changes - List of commits with links - Related issues section - Breaking changes warning (if any) 9. Detect labels from commit types: - feat → enhancement - fix → bug - docs → documentation - etc. 10. Create PR using GitHub CLI (gh pr create): - Set title and body - Set base and head branches - Apply labels - Request reviewers - Set draft status 11. Parse PR URL and number from output 12. Return structured result with PR metadata ```bash python3 skills/git.createpr/git_createpr.py python3 skills/git.createpr/git_createpr.py --draft python3 skills/git.createpr/git_createpr.py --reviewers alice bob python3 skills/git.createpr/git_createpr.py --base develop python3 skills/git.createpr/git_createpr.py --title "feat: add user authentication" python3 skills/git.createpr/git_createpr.py --labels enhancement breaking-change ``` ```json { "ok": true, "status": "success", "pr_number": 123, "pr_url": "https://github.com/owner/repo/pull/123", "title": "feat: add user authentication", "base_branch": "main", "head_branch": "feature/auth", "commits_analyzed": 5, "issues_linked": ["#45", "#67"], "labels_applied": ["enhancement", "feature"], "reviewers_requested": ["alice", "bob"], "is_draft": false } ``` - git - github - pull-request - automation - workflow - pr This skill requires SKILL_AND_COMMAND pattern due to: - 12 steps (exceeds threshold) - High autonomy (auto-generates PR content intelligently) - Highly reusable for release automation and CI/CD - Complex GitHub API interaction - Commit analysis and pattern detection - Multiple execution contexts (CLI, agents, workflows) This implementation uses GitHub CLI (gh) for simplicity and authentication: - Leverages existing gh authentication - Simpler than managing GitHub API tokens - Better error messages - Handles pagination automatically If gh CLI is not available, falls back to REST API with token from: - GITHUB_TOKEN environment variable - GH_TOKEN environment variable
**Command:** `/git/createpr`
## Usage
### Basic Usage
```bash
python3 skills/git/createpr/git_createpr.py
```
### With Arguments
```bash
python3 skills/git/createpr/git_createpr.py \
--output-format json
```
## Integration
This skill can be used in agents by including it in `skills_available`:
```yaml
name: my.agent
skills_available:
- git.createpr
```
## Testing
Run tests with:
```bash
pytest skills/git/createpr/test_git_createpr.py -v
```
## Created By
This skill was generated by **meta.skill**, the skill creator meta-agent.
---
*Part of the Betty Framework*

View File

@@ -0,0 +1 @@
# Auto-generated package initializer for skills.

View File

@@ -0,0 +1,534 @@
#!/usr/bin/env python3
"""
git.createpr - Create GitHub pull requests with auto-generated content
Analyzes commit history, identifies related issues, and creates well-formatted PRs
with proper linking and metadata.
Generated by meta.skill with Betty Framework certification
"""
import os
import sys
import json
import yaml
import subprocess
import re
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
from betty.config import BASE_DIR
from betty.logging_utils import setup_logger
from betty.certification import certified_skill
logger = setup_logger(__name__)
class GitCreatepr:
"""
Create GitHub pull requests with auto-generated titles and descriptions.
"""
# Conventional commit types and their labels
COMMIT_TYPE_LABELS = {
"feat": "enhancement",
"fix": "bug",
"docs": "documentation",
"style": "style",
"refactor": "refactor",
"test": "testing",
"chore": "maintenance",
"perf": "performance",
"ci": "ci/cd",
"build": "build"
}
def __init__(self, base_dir: str = "."):
"""Initialize skill"""
self.base_dir = Path(base_dir)
def run_command(self, command: List[str]) -> Tuple[bool, str]:
"""
Run a command and return success status and output
Args:
command: Command as list of arguments
Returns:
Tuple of (success, output)
"""
try:
result = subprocess.run(
command,
cwd=self.base_dir,
capture_output=True,
text=True,
check=True
)
return True, result.stdout.strip()
except subprocess.CalledProcessError as e:
return False, e.stderr.strip()
def is_git_repository(self) -> bool:
"""Check if current directory is a git repository"""
success, _ = self.run_command(["git", "rev-parse", "--is-inside-work-tree"])
return success
def get_current_branch(self) -> Optional[str]:
"""Get the current branch name"""
success, output = self.run_command(["git", "branch", "--show-current"])
return output if success else None
def branch_exists(self, branch: str) -> bool:
"""Check if a branch exists"""
success, _ = self.run_command(["git", "rev-parse", "--verify", branch])
return success
def get_commits_between(self, base: str, head: str) -> List[Dict[str, str]]:
"""
Get commits between two branches
Args:
base: Base branch
head: Head branch
Returns:
List of commit dicts with hash, message, author
"""
success, output = self.run_command([
"git", "log", f"{base}..{head}",
"--format=%H|%s|%an|%ae"
])
if not success or not output:
return []
commits = []
for line in output.split('\n'):
if not line.strip():
continue
parts = line.split('|')
if len(parts) >= 4:
commits.append({
"hash": parts[0],
"message": parts[1],
"author": parts[2],
"email": parts[3]
})
return commits
def parse_conventional_commit(self, message: str) -> Dict[str, Optional[str]]:
"""
Parse conventional commit message
Args:
message: Commit message
Returns:
Dict with type, scope, description, breaking
"""
# Pattern: type(scope): description
pattern = r'^(\w+)(\([^)]+\))?:\s*(.+)$'
match = re.match(pattern, message)
if match:
commit_type = match.group(1)
scope = match.group(2)[1:-1] if match.group(2) else None
description = match.group(3)
breaking = "BREAKING CHANGE" in message or message.startswith(f"{commit_type}!")
return {
"type": commit_type,
"scope": scope,
"description": description,
"breaking": breaking
}
return {
"type": None,
"scope": None,
"description": message,
"breaking": False
}
def extract_issue_references(self, message: str) -> List[str]:
"""
Extract GitHub issue references from commit message
Args:
message: Commit message
Returns:
List of issue references (e.g., ["#123", "#456"])
"""
pattern = r'#(\d+)'
matches = re.findall(pattern, message)
return [f"#{num}" for num in matches]
def generate_pr_title(self, commits: List[Dict[str, str]]) -> str:
"""
Generate PR title from commits
Args:
commits: List of commits
Returns:
Generated PR title
"""
if not commits:
return "Update"
# Use the most recent commit message as base
first_commit = commits[0]
parsed = self.parse_conventional_commit(first_commit["message"])
if parsed["type"]:
# Use conventional commit format
if parsed["scope"]:
return f"{parsed['type']}({parsed['scope']}): {parsed['description']}"
else:
return f"{parsed['type']}: {parsed['description']}"
else:
# Use first commit message as-is
return first_commit["message"]
def generate_pr_body(self, commits: List[Dict[str, str]], issues: List[str]) -> str:
"""
Generate PR description from commits
Args:
commits: List of commits
issues: List of related issues
Returns:
Generated PR body in markdown
"""
body = []
# Summary section
body.append("## Summary\n")
if len(commits) == 1:
body.append(f"{commits[0]['message']}\n")
else:
body.append(f"This PR includes {len(commits)} commits:\n")
for commit in commits[:5]: # Show first 5
parsed = self.parse_conventional_commit(commit["message"])
body.append(f"- {commit['message']}")
if len(commits) > 5:
body.append(f"- ... and {len(commits) - 5} more commits")
body.append("")
# Related issues
if issues:
body.append("## Related Issues\n")
for issue in issues:
body.append(f"- Closes {issue}")
body.append("")
# Commits section
body.append("## Commits\n")
for commit in commits:
short_hash = commit['hash'][:7]
body.append(f"- {short_hash} {commit['message']}")
body.append("")
# Check for breaking changes
breaking = [c for c in commits if self.parse_conventional_commit(c["message"])["breaking"]]
if breaking:
body.append("## ⚠️ Breaking Changes\n")
for commit in breaking:
body.append(f"- {commit['message']}")
body.append("")
return "\n".join(body)
def detect_labels(self, commits: List[Dict[str, str]]) -> List[str]:
"""
Detect labels from commit types
Args:
commits: List of commits
Returns:
List of label names
"""
labels = set()
for commit in commits:
parsed = self.parse_conventional_commit(commit["message"])
if parsed["type"] and parsed["type"] in self.COMMIT_TYPE_LABELS:
labels.add(self.COMMIT_TYPE_LABELS[parsed["type"]])
if parsed["breaking"]:
labels.add("breaking-change")
return list(labels)
def check_gh_cli(self) -> bool:
"""Check if GitHub CLI is available"""
success, _ = self.run_command(["gh", "--version"])
return success
@certified_skill("git.createpr")
def execute(
self,
base_branch: str = "main",
title: Optional[str] = None,
draft: bool = False,
auto_merge: bool = False,
reviewers: Optional[List[str]] = None,
labels: Optional[List[str]] = None,
body: Optional[str] = None
) -> Dict[str, Any]:
"""
Execute the skill
Args:
base_branch: Base branch for PR
title: PR title (auto-generated if not provided)
draft: Create as draft PR
auto_merge: Enable auto-merge
reviewers: List of reviewer usernames
labels: Labels to apply
body: PR description (auto-generated if not provided)
Returns:
Dict with execution results
"""
try:
logger.info("Executing git.createpr...")
# Validate git repository
if not self.is_git_repository():
return {
"ok": False,
"status": "failed",
"error": "Not in a git repository"
}
# Check gh CLI
if not self.check_gh_cli():
return {
"ok": False,
"status": "failed",
"error": "GitHub CLI (gh) not found. Install: https://cli.github.com/"
}
# Get current branch
head_branch = self.get_current_branch()
if not head_branch:
return {
"ok": False,
"status": "failed",
"error": "Could not determine current branch"
}
# Validate base branch exists
if not self.branch_exists(base_branch):
return {
"ok": False,
"status": "failed",
"error": f"Base branch '{base_branch}' does not exist"
}
# Get commits
logger.info(f"Analyzing commits between {base_branch} and {head_branch}")
commits = self.get_commits_between(base_branch, head_branch)
if not commits:
return {
"ok": False,
"status": "failed",
"error": f"No commits found between {base_branch} and {head_branch}"
}
logger.info(f"Found {len(commits)} commits")
# Generate title if not provided
if not title:
title = self.generate_pr_title(commits)
logger.info(f"Generated title: {title}")
# Extract issue references
all_issues = set()
for commit in commits:
issues = self.extract_issue_references(commit["message"])
all_issues.update(issues)
# Generate body if not provided
if not body:
body = self.generate_pr_body(commits, list(all_issues))
logger.info("Generated PR description")
# Detect labels if not provided
if not labels:
labels = self.detect_labels(commits)
logger.info(f"Detected labels: {labels}")
# Build gh command
gh_cmd = ["gh", "pr", "create", "--base", base_branch, "--title", title, "--body", body]
if draft:
gh_cmd.append("--draft")
if reviewers:
gh_cmd.extend(["--reviewer", ",".join(reviewers)])
if labels:
gh_cmd.extend(["--label", ",".join(labels)])
# Create PR
logger.info("Creating pull request...")
success, output = self.run_command(gh_cmd)
if not success:
return {
"ok": False,
"status": "failed",
"error": f"Failed to create PR: {output}"
}
# Parse PR URL from output
pr_url = output.strip()
logger.info(f"PR created: {pr_url}")
# Extract PR number from URL
pr_number = None
match = re.search(r'/pull/(\d+)', pr_url)
if match:
pr_number = int(match.group(1))
# Build result
result = {
"ok": True,
"status": "success",
"pr_url": pr_url,
"pr_number": pr_number,
"title": title,
"base_branch": base_branch,
"head_branch": head_branch,
"commits_analyzed": len(commits),
"issues_linked": list(all_issues),
"labels_applied": labels or [],
"reviewers_requested": reviewers or [],
"is_draft": draft
}
logger.info("Skill completed successfully")
return result
except Exception as e:
logger.error(f"Error executing skill: {e}")
return {
"ok": False,
"status": "failed",
"error": str(e)
}
def main():
"""CLI entry point"""
import argparse
parser = argparse.ArgumentParser(
description="Create GitHub pull request with auto-generated content"
)
parser.add_argument(
"--base",
dest="base_branch",
default="main",
help="Base branch for PR (default: main)"
)
parser.add_argument(
"--title",
help="PR title (auto-generated if not provided)"
)
parser.add_argument(
"--draft",
action="store_true",
help="Create as draft PR"
)
parser.add_argument(
"--auto-merge",
dest="auto_merge",
action="store_true",
help="Enable auto-merge if checks pass"
)
parser.add_argument(
"--reviewers",
nargs="+",
help="GitHub usernames to request reviews from"
)
parser.add_argument(
"--labels",
nargs="+",
help="Labels to apply to PR"
)
parser.add_argument(
"--body",
help="PR description (auto-generated if not provided)"
)
parser.add_argument(
"--output-format",
choices=["json", "yaml", "human"],
default="human",
help="Output format"
)
args = parser.parse_args()
# Create skill instance
skill = GitCreatepr()
# Execute skill
result = skill.execute(
base_branch=args.base_branch,
title=args.title,
draft=args.draft,
auto_merge=args.auto_merge,
reviewers=args.reviewers,
labels=args.labels,
body=args.body
)
# Output result
if args.output_format == "json":
print(json.dumps(result, indent=2))
elif args.output_format == "yaml":
print(yaml.dump(result, default_flow_style=False))
else:
# Human-readable output
if result.get("ok"):
print(f"\n✓ Pull Request Created!")
print(f" URL: {result.get('pr_url')}")
if result.get('pr_number'):
print(f" Number: #{result.get('pr_number')}")
print(f" Title: {result.get('title')}")
print(f" Base: {result.get('base_branch')}{result.get('head_branch')}")
print(f" Commits: {result.get('commits_analyzed')}")
if result.get('issues_linked'):
print(f" Issues: {', '.join(result.get('issues_linked', []))}")
if result.get('labels_applied'):
print(f" Labels: {', '.join(result.get('labels_applied', []))}")
if result.get('reviewers_requested'):
print(f" Reviewers: {', '.join(result.get('reviewers_requested', []))}")
if result.get('is_draft'):
print(f" Status: Draft")
else:
print(f"\n✗ Error: {result.get('error', 'Unknown error')}")
# Exit with appropriate code
sys.exit(0 if result.get("ok") else 1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,58 @@
name: git.createpr
version: 0.1.0
description: "Create GitHub pull requests with auto-generated titles and descriptions\
\ based on commit analysis. Analyzes commit history, identifies related issues,\
\ and creates well-formatted PRs with proper linking and metadata. - git-commits\
\ - Commit history between base and feature branch - git-repository - Local git\
\ repository with commit information - github-credentials - GitHub token for API\
\ access (from gh CLI or environment) - pull-request - Created GitHub pull request\
\ with metadata - pr-report - Summary of PR creation including URL, number, and\
\ status - base_branch (string): Base branch for PR (default: main) - title (string):\
\ PR title (optional, auto-generated from commits if not provided) - draft (boolean):\
\ Create as draft PR (default: false) - auto_merge (boolean): Enable auto-merge\
\ if checks pass (default: false) - reviewers (array): List of GitHub usernames\
\ to request reviews from - labels (array): Labels to apply to PR (optional, auto-detected\
\ from commits) - body (string): PR description (optional, auto-generated if not\
\ provided) - git command line tool - GitHub CLI (gh) or GitHub API access with\
\ token - Access to git repository - GitHub repository with permissions to create\
\ PRs 1. Validate we're in a git repository 2. Get current branch name 3. Validate\
\ base branch exists 4. Fetch latest changes from remote 5. Get commit history between\
\ base and current branch 6. Analyze commits to extract: - Commit messages - Conventional\
\ commit types (feat, fix, docs, etc.) - Issue references (#123) - Breaking changes\
\ 7. Generate PR title (if not provided): - Use most recent commit message - Or\
\ summarize multiple commits - Format: \"type(scope): description\" 8. Generate\
\ PR description (if not provided): - Summary of changes - List of commits with\
\ links - Related issues section - Breaking changes warning (if any) 9. Detect labels\
\ from commit types: - feat \u2192 enhancement - fix \u2192 bug - docs \u2192 documentation\
\ - etc. 10. Create PR using GitHub CLI (gh pr create): - Set title and body - Set\
\ base and head branches - Apply labels - Request reviewers - Set draft status 11.\
\ Parse PR URL and number from output 12. Return structured result with PR metadata\
\ ```bash python3 skills/git.createpr/git_createpr.py python3 skills/git.createpr/git_createpr.py\
\ --draft python3 skills/git.createpr/git_createpr.py --reviewers alice bob python3\
\ skills/git.createpr/git_createpr.py --base develop python3 skills/git.createpr/git_createpr.py\
\ --title \"feat: add user authentication\" python3 skills/git.createpr/git_createpr.py\
\ --labels enhancement breaking-change ``` ```json { \"ok\": true, \"status\": \"\
success\", \"pr_number\": 123, \"pr_url\": \"https://github.com/owner/repo/pull/123\"\
, \"title\": \"feat: add user authentication\", \"base_branch\": \"main\", \"head_branch\"\
: \"feature/auth\", \"commits_analyzed\": 5, \"issues_linked\": [\"#45\", \"#67\"\
], \"labels_applied\": [\"enhancement\", \"feature\"], \"reviewers_requested\":\
\ [\"alice\", \"bob\"], \"is_draft\": false } ``` - git - github - pull-request\
\ - automation - workflow - pr This skill requires SKILL_AND_COMMAND pattern due\
\ to: - 12 steps (exceeds threshold) - High autonomy (auto-generates PR content\
\ intelligently) - Highly reusable for release automation and CI/CD - Complex GitHub\
\ API interaction - Commit analysis and pattern detection - Multiple execution contexts\
\ (CLI, agents, workflows) This implementation uses GitHub CLI (gh) for simplicity\
\ and authentication: - Leverages existing gh authentication - Simpler than managing\
\ GitHub API tokens - Better error messages - Handles pagination automatically If\
\ gh CLI is not available, falls back to REST API with token from: - GITHUB_TOKEN\
\ environment variable - GH_TOKEN environment variable"
inputs: []
outputs: []
status: active
permissions: []
entrypoints:
- command: /git/createpr
handler: git_createpr.py
runtime: python
description: Create GitHub pull requests with auto-generated titles and descriptions
based on commit analysis. An

View File

@@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""
Tests for git.createpr
Generated by meta.skill
"""
import pytest
import sys
import os
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../..")))
from skills.git_createpr import git_createpr
class TestGitCreatepr:
"""Tests for GitCreatepr"""
def setup_method(self):
"""Setup test fixtures"""
self.skill = git_createpr.GitCreatepr()
def test_initialization(self):
"""Test skill initializes correctly"""
assert self.skill is not None
assert self.skill.base_dir is not None
def test_execute_basic(self):
"""Test basic execution"""
result = self.skill.execute()
assert result is not None
assert "ok" in result
assert "status" in result
def test_execute_success(self):
"""Test successful execution"""
result = self.skill.execute()
assert result["ok"] is True
assert result["status"] == "success"
# TODO: Add more specific tests based on skill functionality
def test_cli_help(capsys):
"""Test CLI help message"""
sys.argv = ["git_createpr.py", "--help"]
with pytest.raises(SystemExit) as exc_info:
git_createpr.main()
assert exc_info.value.code == 0
captured = capsys.readouterr()
assert "Create GitHub pull requests with auto-generated ti" in captured.out
if __name__ == "__main__":
pytest.main([__file__, "-v"])