Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/scripts/codebase_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Codebase Agent - AI-powered code review assistant."""
166 changes: 166 additions & 0 deletions .github/scripts/codebase_agent/ai_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
"""AI client and GitHub API utilities."""
import os
import sys
import requests
from anthropic import Anthropic

try:
from anthropic import AnthropicVertex

VERTEX_AVAILABLE = True
except ImportError:
VERTEX_AVAILABLE = False

# Hardcoded agent context for portability (template-friendly)
AGENT_CONTEXT = """
**Your Role**:
You are the Codebase Agent for this repository. You assist with code reviews,
technical guidance, and maintaining code quality standards.

**Operating Principles**:

1. **Safety First**
- Show plan before major changes
- Explain reasoning and alternatives
- Ask for clarification when requirements are ambiguous

2. **High Signal, Low Noise**
- Only comment when adding unique value
- Be concise and get to the point
- Focus on critical issues, not minor style differences

**Code Review Focus**:
When reviewing code, prioritize:
- **Bugs**: Logic errors, edge cases, error handling
- **Security**: Input validation, OWASP Top 10 vulnerabilities
- **Performance**: Inefficient algorithms, unnecessary operations
- **Style**: Code quality and maintainability
- **Testing**: Coverage, missing test cases

**Feedback Guidelines**:
- Be specific and actionable
- Provide code examples for fixes
- Explain "why" not just "what"
- Prioritize critical issues
- Acknowledge good practices

**Communication Style**:
- Direct and technical (assume user has context)
- Code-focused (show examples, not just descriptions)
- Actionable (always provide next steps)
- Honest (admit uncertainty, ask for clarification)

**What NOT to Do**:
- No generic AI responses or "AI slop"
- Don't state the obvious or add filler content
- Don't make assumptions about ambiguous requirements
- Don't include unnecessary praise or validation
"""


def _get_claude_client():
"""Get Claude client with Vertex AI fallback to Anthropic API.

Tries Vertex AI first (if GCP_PROJECT_ID set), falls back to Anthropic API.

Returns:
Anthropic or AnthropicVertex client

Raises:
RuntimeError: If no credentials configured
"""
# Try Vertex AI first if credentials available
if VERTEX_AVAILABLE:
project_id = os.environ.get("GCP_PROJECT_ID")
region = os.environ.get("GCP_REGION", "us-central1")

if project_id:
try:
return AnthropicVertex(project_id=project_id, region=region)
except Exception as e:
print(
f"⚠️ Vertex AI unavailable ({e}), falling back to Anthropic API",
file=sys.stderr,
)

# Fall back to Anthropic API
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
raise RuntimeError(
"No AI credentials found. Set either:\n"
" - GCP_PROJECT_ID (for Vertex AI), or\n"
" - ANTHROPIC_API_KEY (for Anthropic API)"
)

return Anthropic(api_key=api_key)


def call_claude(repo_name: str, command: str, url: str) -> str:
"""Call Claude API with context.

Args:
repo_name: Repository name (owner/repo)
command: User command to execute
url: GitHub issue/PR URL

Returns:
AI response text

Raises:
RuntimeError: If AI API call fails
"""
client = _get_claude_client()

prompt = f"""You are the Codebase Agent for {repo_name}.

{AGENT_CONTEXT}

---

**Current Task**:
Command: {command}
Context: {url}

Provide a helpful, concise response following the operating principles above."""

try:
message = client.messages.create(
model="claude-sonnet-4-5-20250929",
max_tokens=2000,
messages=[{"role": "user", "content": prompt}],
)
return message.content[0].text
except Exception as e:
raise RuntimeError(f"AI API error: {e}")


def post_github_comment(repo: str, issue_number: int, body: str):
"""Post comment to GitHub issue/PR.

Args:
repo: Repository name (owner/repo)
issue_number: Issue or PR number
body: Comment body text

Raises:
requests.HTTPError: If GitHub API call fails
"""
token = os.environ.get("GITHUB_TOKEN")
if not token:
raise RuntimeError("GITHUB_TOKEN environment variable not set")

url = f"https://api.github.com/repos/{repo}/issues/{issue_number}/comments"

try:
response = requests.post(
url,
headers={
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json",
},
json={"body": body},
timeout=30,
)
response.raise_for_status()
except requests.exceptions.RequestException as e:
raise RuntimeError(f"GitHub API error: {e}")
54 changes: 54 additions & 0 deletions .github/scripts/codebase_agent/github_parser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""GitHub context parsing utilities."""
import json


def parse_github_context(context_json: str) -> dict:
"""Parse GitHub Actions context.

Args:
context_json: JSON string of GitHub context

Returns:
Dict with repository, number, url, and event

Raises:
ValueError: If no issue or PR found in context
"""
context = json.loads(context_json)

# Extract number and URL
if "pull_request" in context["event"]:
number = context["event"]["pull_request"]["number"]
url = context["event"]["pull_request"]["html_url"]
elif "issue" in context["event"]:
number = context["event"]["issue"]["number"]
url = context["event"]["issue"]["html_url"]
else:
raise ValueError("No issue or PR found in context")

return {
"repository": context["repository"],
"number": number,
"url": url,
"event": context["event"],
}


def extract_command(context: dict) -> str:
"""Extract command from @cba mention or labels.

Args:
context: Parsed GitHub context from parse_github_context()

Returns:
Command string to execute
"""
# Check for @cba mention in comment
if "comment" in context["event"]:
body = context["event"]["comment"]["body"]
if "@cba" in body:
command = body.split("@cba", 1)[1].strip()
return command if command else "review this code"

# Default command
return "review this code"
38 changes: 38 additions & 0 deletions .github/scripts/codebase_agent/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Codebase Agent - AI-powered code review assistant."""
import sys
import json
from .github_parser import parse_github_context, extract_command
from .ai_client import call_claude, post_github_comment


def main():
"""Main entry point."""
try:
# Parse GitHub context from argument
context = parse_github_context(sys.argv[1])

# Extract command
command = extract_command(context)

# Call AI
response = call_claude(
repo_name=context["repository"], command=command, url=context["url"]
)

# Post comment
post_github_comment(
repo=context["repository"],
issue_number=context["number"],
body=f"## πŸ€– Codebase Agent\n\n{response}",
)

print(f"βœ… Posted response to {context['url']}")

except Exception as e:
print(f"❌ Error: {e}", file=sys.stderr)
sys.exit(1)


if __name__ == "__main__":
main()
55 changes: 55 additions & 0 deletions .github/workflows/codebase-agent.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Codebase Agent

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [opened, labeled]
pull_request:
types: [opened, labeled, ready_for_review]

permissions:
contents: write
pull-requests: write
issues: write
id-token: write # Required for GCP Workload Identity (if using Vertex AI)

jobs:
codebase-agent:
runs-on: ubuntu-latest
if: |
contains(github.event.comment.body, '@cba') ||
contains(github.event.issue.labels.*.name, 'cba-review') ||
contains(github.event.pull_request.labels.*.name, 'cba-review') ||
contains(github.event.issue.labels.*.name, 'cba-help') ||
contains(github.event.pull_request.labels.*.name, 'cba-help')

steps:
- uses: actions/checkout@v4

# Uncomment for Vertex AI (removes API key dependency)
# - name: Authenticate to Google Cloud
# uses: google-github-actions/auth@v2
# with:
# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}

- uses: actions/setup-python@v5
with:
python-version: '3.11'

- run: pip install anthropic requests google-cloud-aiplatform

- name: Run Codebase Agent
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Option 1: Anthropic API (default)
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
# Option 2: Vertex AI (optional - remove API key dependency)
GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
GCP_REGION: ${{ vars.GCP_REGION }}
run: |
cd .github/scripts
python3 -m codebase_agent.main '${{ toJson(github) }}'
Loading