Skip to content

Commit ac3670f

Browse files
jeremyederclaude
andcommitted
refactor: Simplify and modularize Codebase Agent workflow
Extract inline Python to standalone module for testability and maintainability. Reduce workflow from 207 lines to 43 lines. Changes: - Extract Python to .github/scripts/codebase_agent/ module - Add Vertex AI support with automatic Anthropic API fallback - Add error handling for API failures and timeouts - Simplify error handling (trust base exception messages) - Remove unused GCP Workload Identity setup from workflow - Update docs with dual authentication options - Remove "Powered by Vertex AI" messaging - Add comprehensive GCP Workload Identity setup guide Authentication: - Try Vertex AI first (if GCP_PROJECT_ID set) - Fall back to Anthropic API automatically - Clear error messages when neither configured Result: 71% code reduction with flexible authentication Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent bc0ca0f commit ac3670f

6 files changed

Lines changed: 563 additions & 0 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Codebase Agent - AI-powered code review assistant."""
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""AI client and GitHub API utilities."""
2+
import os
3+
import sys
4+
import requests
5+
from anthropic import Anthropic
6+
7+
try:
8+
from anthropic import AnthropicVertex
9+
10+
VERTEX_AVAILABLE = True
11+
except ImportError:
12+
VERTEX_AVAILABLE = False
13+
14+
# Hardcoded agent context for portability (template-friendly)
15+
AGENT_CONTEXT = """
16+
**Your Role**:
17+
You are the Codebase Agent for this repository. You assist with code reviews,
18+
technical guidance, and maintaining code quality standards.
19+
20+
**Operating Principles**:
21+
22+
1. **Safety First**
23+
- Show plan before major changes
24+
- Explain reasoning and alternatives
25+
- Ask for clarification when requirements are ambiguous
26+
27+
2. **High Signal, Low Noise**
28+
- Only comment when adding unique value
29+
- Be concise and get to the point
30+
- Focus on critical issues, not minor style differences
31+
32+
**Code Review Focus**:
33+
When reviewing code, prioritize:
34+
- **Bugs**: Logic errors, edge cases, error handling
35+
- **Security**: Input validation, OWASP Top 10 vulnerabilities
36+
- **Performance**: Inefficient algorithms, unnecessary operations
37+
- **Style**: Code quality and maintainability
38+
- **Testing**: Coverage, missing test cases
39+
40+
**Feedback Guidelines**:
41+
- Be specific and actionable
42+
- Provide code examples for fixes
43+
- Explain "why" not just "what"
44+
- Prioritize critical issues
45+
- Acknowledge good practices
46+
47+
**Communication Style**:
48+
- Direct and technical (assume user has context)
49+
- Code-focused (show examples, not just descriptions)
50+
- Actionable (always provide next steps)
51+
- Honest (admit uncertainty, ask for clarification)
52+
53+
**What NOT to Do**:
54+
- No generic AI responses or "AI slop"
55+
- Don't state the obvious or add filler content
56+
- Don't make assumptions about ambiguous requirements
57+
- Don't include unnecessary praise or validation
58+
"""
59+
60+
61+
def _get_claude_client():
62+
"""Get Claude client with Vertex AI fallback to Anthropic API.
63+
64+
Tries Vertex AI first (if GCP_PROJECT_ID set), falls back to Anthropic API.
65+
66+
Returns:
67+
Anthropic or AnthropicVertex client
68+
69+
Raises:
70+
RuntimeError: If no credentials configured
71+
"""
72+
# Try Vertex AI first if credentials available
73+
if VERTEX_AVAILABLE:
74+
project_id = os.environ.get("GCP_PROJECT_ID")
75+
region = os.environ.get("GCP_REGION", "us-central1")
76+
77+
if project_id:
78+
try:
79+
return AnthropicVertex(project_id=project_id, region=region)
80+
except Exception as e:
81+
print(
82+
f"⚠️ Vertex AI unavailable ({e}), falling back to Anthropic API",
83+
file=sys.stderr,
84+
)
85+
86+
# Fall back to Anthropic API
87+
api_key = os.environ.get("ANTHROPIC_API_KEY")
88+
if not api_key:
89+
raise RuntimeError(
90+
"No AI credentials found. Set either:\n"
91+
" - GCP_PROJECT_ID (for Vertex AI), or\n"
92+
" - ANTHROPIC_API_KEY (for Anthropic API)"
93+
)
94+
95+
return Anthropic(api_key=api_key)
96+
97+
98+
def call_claude(repo_name: str, command: str, url: str) -> str:
99+
"""Call Claude API with context.
100+
101+
Args:
102+
repo_name: Repository name (owner/repo)
103+
command: User command to execute
104+
url: GitHub issue/PR URL
105+
106+
Returns:
107+
AI response text
108+
109+
Raises:
110+
RuntimeError: If AI API call fails
111+
"""
112+
client = _get_claude_client()
113+
114+
prompt = f"""You are the Codebase Agent for {repo_name}.
115+
116+
{AGENT_CONTEXT}
117+
118+
---
119+
120+
**Current Task**:
121+
Command: {command}
122+
Context: {url}
123+
124+
Provide a helpful, concise response following the operating principles above."""
125+
126+
try:
127+
message = client.messages.create(
128+
model="claude-sonnet-4-5-20250929",
129+
max_tokens=2000,
130+
messages=[{"role": "user", "content": prompt}],
131+
)
132+
return message.content[0].text
133+
except Exception as e:
134+
raise RuntimeError(f"AI API error: {e}")
135+
136+
137+
def post_github_comment(repo: str, issue_number: int, body: str):
138+
"""Post comment to GitHub issue/PR.
139+
140+
Args:
141+
repo: Repository name (owner/repo)
142+
issue_number: Issue or PR number
143+
body: Comment body text
144+
145+
Raises:
146+
requests.HTTPError: If GitHub API call fails
147+
"""
148+
token = os.environ.get("GITHUB_TOKEN")
149+
if not token:
150+
raise RuntimeError("GITHUB_TOKEN environment variable not set")
151+
152+
url = f"https://api.github.com/repos/{repo}/issues/{issue_number}/comments"
153+
154+
try:
155+
response = requests.post(
156+
url,
157+
headers={
158+
"Authorization": f"token {token}",
159+
"Accept": "application/vnd.github.v3+json",
160+
},
161+
json={"body": body},
162+
timeout=30,
163+
)
164+
response.raise_for_status()
165+
except requests.exceptions.RequestException as e:
166+
raise RuntimeError(f"GitHub API error: {e}")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""GitHub context parsing utilities."""
2+
import json
3+
4+
5+
def parse_github_context(context_json: str) -> dict:
6+
"""Parse GitHub Actions context.
7+
8+
Args:
9+
context_json: JSON string of GitHub context
10+
11+
Returns:
12+
Dict with repository, number, url, and event
13+
14+
Raises:
15+
ValueError: If no issue or PR found in context
16+
"""
17+
context = json.loads(context_json)
18+
19+
# Extract number and URL
20+
if "pull_request" in context["event"]:
21+
number = context["event"]["pull_request"]["number"]
22+
url = context["event"]["pull_request"]["html_url"]
23+
elif "issue" in context["event"]:
24+
number = context["event"]["issue"]["number"]
25+
url = context["event"]["issue"]["html_url"]
26+
else:
27+
raise ValueError("No issue or PR found in context")
28+
29+
return {
30+
"repository": context["repository"],
31+
"number": number,
32+
"url": url,
33+
"event": context["event"],
34+
}
35+
36+
37+
def extract_command(context: dict) -> str:
38+
"""Extract command from @cba mention or labels.
39+
40+
Args:
41+
context: Parsed GitHub context from parse_github_context()
42+
43+
Returns:
44+
Command string to execute
45+
"""
46+
# Check for @cba mention in comment
47+
if "comment" in context["event"]:
48+
body = context["event"]["comment"]["body"]
49+
if "@cba" in body:
50+
command = body.split("@cba", 1)[1].strip()
51+
return command if command else "review this code"
52+
53+
# Default command
54+
return "review this code"
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env python3
2+
"""Codebase Agent - AI-powered code review assistant."""
3+
import sys
4+
import json
5+
from .github_parser import parse_github_context, extract_command
6+
from .ai_client import call_claude, post_github_comment
7+
8+
9+
def main():
10+
"""Main entry point."""
11+
try:
12+
# Parse GitHub context from argument
13+
context = parse_github_context(sys.argv[1])
14+
15+
# Extract command
16+
command = extract_command(context)
17+
18+
# Call AI
19+
response = call_claude(
20+
repo_name=context["repository"], command=command, url=context["url"]
21+
)
22+
23+
# Post comment
24+
post_github_comment(
25+
repo=context["repository"],
26+
issue_number=context["number"],
27+
body=f"## 🤖 Codebase Agent\n\n{response}",
28+
)
29+
30+
print(f"✅ Posted response to {context['url']}")
31+
32+
except Exception as e:
33+
print(f"❌ Error: {e}", file=sys.stderr)
34+
sys.exit(1)
35+
36+
37+
if __name__ == "__main__":
38+
main()
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Codebase Agent
2+
3+
on:
4+
issue_comment:
5+
types: [created]
6+
pull_request_review_comment:
7+
types: [created]
8+
issues:
9+
types: [opened, labeled]
10+
pull_request:
11+
types: [opened, labeled, ready_for_review]
12+
13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
issues: write
17+
id-token: write # Required for GCP Workload Identity (if using Vertex AI)
18+
19+
jobs:
20+
codebase-agent:
21+
runs-on: ubuntu-latest
22+
if: |
23+
contains(github.event.comment.body, '@cba') ||
24+
contains(github.event.issue.labels.*.name, 'cba-review') ||
25+
contains(github.event.pull_request.labels.*.name, 'cba-review') ||
26+
contains(github.event.issue.labels.*.name, 'cba-help') ||
27+
contains(github.event.pull_request.labels.*.name, 'cba-help')
28+
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
# Uncomment for Vertex AI (removes API key dependency)
33+
# - name: Authenticate to Google Cloud
34+
# uses: google-github-actions/auth@v2
35+
# with:
36+
# workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }}
37+
# service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }}
38+
39+
- uses: actions/setup-python@v5
40+
with:
41+
python-version: '3.11'
42+
43+
- run: pip install anthropic requests google-cloud-aiplatform
44+
45+
- name: Run Codebase Agent
46+
env:
47+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48+
# Option 1: Anthropic API (default)
49+
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
50+
# Option 2: Vertex AI (optional - remove API key dependency)
51+
GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
52+
GCP_REGION: ${{ vars.GCP_REGION }}
53+
run: |
54+
cd .github/scripts
55+
python3 -m codebase_agent.main '${{ toJson(github) }}'

0 commit comments

Comments
 (0)