feat: local git checkout for PR code review with real diff

- Add git_local.py: fetch, checkout PR branch, generate git diff against
  target, restore branch after review
- Update fetch_pr_details to use local git diff when REPOS_BASE_DIR is set,
  with fallback to AzDo iteration API
- Update run_code_review to restore repo to target branch after review
- Refine Claude review prompt to only comment on diff changes, not
  pre-existing code
- Update README: WSL venv gotcha, local git checkout flow, flow diagram
This commit is contained in:
Yaojia Wang
2026-03-25 20:45:33 +01:00
parent b67cbcfd93
commit dee48dca2b
5 changed files with 354 additions and 26 deletions

View File

@@ -51,7 +51,7 @@ interactive notifications.
|---------|-------------|
| **PR Discovery** | Webhook-based (push) or polling-based (pull) — or both |
| **Auto-Create Jira Ticket** | When PR branch has no ticket ID, Claude generates summary + description and creates a Jira Story |
| **AI Code Review** | Claude Code CLI reviews PRs with full repo context (Read/Glob/Grep), using your subscription |
| **AI Code Review** | Claude Code CLI reviews PRs on the checked-out PR branch with full repo context (Read/Glob/Grep), using real `git diff` |
| **CI/CD Integration** | Triggers CI builds after merge, polls for completion, handles CD release approval gates |
| **Slack Interactive** | Approval requests with [Approve]/[Cancel] buttons, CI/CD status notifications |
| **Human-in-the-loop** | 5 interrupt points where operator confirmation is required before destructive actions |
@@ -120,7 +120,7 @@ All configuration is via environment variables. See `.env.example` for the full
| Variable | Default | Description |
|----------|---------|-------------|
| `REPOS_BASE_DIR` | `""` | Base dir with Billo repos (e.g., `/c/Users/yaoji/git/Billo`) |
| `REPOS_BASE_DIR` | `""` | Base dir with Billo repos (e.g., `/home/kai/git/billo`). Each repo must be cloned with its AzDo name as the directory name. |
| `WATCHED_REPOS` | `""` | Comma-separated repos to poll (e.g., `Billo.Platform.Payment,Billo.Platform.Document.DocumentAnalyser`) |
| `PR_POLL_ENABLED` | `False` | Enable periodic PR polling |
| `PR_POLL_INTERVAL_SECONDS` | `300` | Polling interval (5 min) |
@@ -177,6 +177,11 @@ All configuration is via environment variables. See `.env.example` for the full
```
parse_webhook -> fetch_pr_details -> route_after_fetch
| |
| +-- (local repo available?)
| yes -> git fetch + checkout PR branch + git diff
| no -> AzDo iteration API (file list only)
|
|-- merged -----------------> calculate_version -> update_staging -> CI build -> END
|-- active_with_ticket -----> move_jira_code_review -+
|-- active_no_ticket -------> auto_create_ticket ----+
@@ -184,6 +189,7 @@ parse_webhook -> fetch_pr_details -> route_after_fetch
run_code_review -> evaluate_review
|-- approve -> [Slack: Merge?] -> merge_pr
|-- request_changes -> notify -> END
-> restore branch to develop
-> Jira transitions -> calculate_version
-> update_staging -> CI build -> notify -> END
```
@@ -311,6 +317,7 @@ src/release_agent/
jira.py # Jira REST client (transitions + create_issue)
slack.py # Slack dual-mode (webhook + Web API)
claude_review.py # Claude Code CLI (review + ticket generation)
git_local.py # Local git ops (fetch, checkout PR branch, diff)
_http.py, _retry.py # Shared helpers
services/
pr_poller.py # Background PR polling loop
@@ -340,32 +347,47 @@ The app runs best on **WSL (Ubuntu)** because:
### Setup
```bash
# 1. Start PostgreSQL (from Windows or WSL)
cd /mnt/c/Users/yaoji/git/Billo/billo-release-agent
# 1. Clone the project to WSL native filesystem (NOT /mnt/c/)
cd ~/git/billo
git clone <repo-url> billo-release-agent
cd billo-release-agent
# 2. Start PostgreSQL (from Windows or WSL)
docker compose up -d db
# 2. Install uv in WSL (if needed)
# 3. Install uv in WSL (if needed)
curl -LsSf https://astral.sh/uv/install.sh | sh
export PATH="$HOME/.local/bin:$PATH"
# 3. Install dependencies
# 4. Create venv and install dependencies
# IMPORTANT: Run from the WSL-native path, not /mnt/c/.
# If .venv was created from /mnt/c/, delete and recreate:
# rm -rf .venv && uv venv --python python3.12
uv sync --all-extras
# 4. Configure .env
# 5. Configure .env
# Key settings:
# CLAUDE_CMD=claude (not claude.cmd — WSL finds it via PATH)
# REPOS_BASE_DIR=/mnt/c/Users/yaoji/git/Billo
# PR_POLL_ENABLED=False (disable during dev to avoid noise)
# SLACK_WEBHOOK_URL= (leave empty during dev)
# CLAUDE_CMD=claude
# REPOS_BASE_DIR=~/git/billo (WSL native path, not /mnt/c/)
# PR_POLL_ENABLED=False (disable during dev to avoid noise)
# SLACK_WEBHOOK_URL= (leave empty during dev)
# 5. Start the server
# 6. Start the server
uv run uvicorn release_agent.main:app --host 0.0.0.0 --port 8080
# 6. Test
# 7. Test
curl http://localhost:8080/status
curl -X POST http://localhost:8080/manual/pr/10443
```
### Important: venv Must Be Created from WSL Path
If the `.venv` was created while the working directory was `/mnt/c/...`, the
uvicorn shebang will point to the Windows-mounted path. This causes the server
to load stale code from the Windows filesystem instead of your WSL edits.
To fix: `rm -rf .venv && uv venv --python python3.12 && uv sync --all-extras`
### Windows-only (Fallback)
If you must run on Windows directly, use the provided `run.py` script which sets
@@ -378,13 +400,22 @@ uv run python run.py
Note: Claude CLI subprocess may return empty stdout on Windows due to event loop
incompatibility. WSL is the recommended approach.
### Performance Note: WSL + /mnt/c
### Local Git Checkout for Code Review
Claude Code CLI with `--allowedTools Read,Glob,Grep` on `/mnt/c` (Windows filesystem
mounted in WSL) is very slow. For faster code reviews, either:
When `REPOS_BASE_DIR` is set and the repo is cloned locally, the agent will:
1. **Clone repos to WSL native filesystem** (`~/git/Billo/`) and set `REPOS_BASE_DIR=~/git/Billo`
2. **Remove `--allowedTools`** so Claude only reviews the diff text (faster but less thorough)
1. `git fetch origin` to get the latest remote state
2. `git checkout` the PR source branch
3. `git diff origin/develop...HEAD` to generate a real diff (not just file names)
4. Run Claude Code CLI with `cwd` set to the repo on the PR branch
5. `git checkout develop` to restore the branch after review
This gives Claude full codebase context on the actual PR branch, producing much
more thorough reviews than the AzDo iteration API (which only returns file paths).
**Performance**: Clone repos to WSL native filesystem (`~/git/billo/`), not
`/mnt/c/`. Claude Code CLI with `--allowedTools Read,Glob,Grep` on `/mnt/c`
is very slow (10+ minutes per review vs seconds on native fs).
## Slack App Setup
@@ -409,6 +440,8 @@ To use interactive buttons (optional — REST API approvals still work without i
- Claude CLI ticket generation (tested: returns structured JSON)
- Claude CLI code review (tested: returns structured JSON with verdict + issues)
- PR review comments posted to Azure DevOps (inline + summary)
- Local git checkout + real `git diff` for PR code review (with fallback to AzDo API)
- Branch restore to develop after review completes
- Node type annotations fixed (`RunnableConfig` instead of `dict`)
### Known Issues
@@ -416,11 +449,13 @@ To use interactive buttons (optional — REST API approvals still work without i
| Issue | Severity | Workaround |
|-------|----------|------------|
| Windows: Claude CLI subprocess returns empty stdout | HIGH | Run in WSL |
| WSL: venv created from /mnt/c/ loads stale code | HIGH | Delete .venv, recreate from WSL native path |
| WSL + /mnt/c: Claude CLI Read/Glob very slow (10+ min) | MEDIUM | Clone repos to WSL native fs |
| Graph has no LangGraph checkpointer (interrupt not persistent) | MEDIUM | Graphs run to completion or fail; no resume |
| `_upsert_thread` only writes final state (no intermediate updates) | LOW | Query DB only after graph completes |
| CI poll may run indefinitely (no build to poll in dev) | LOW | Leave `PR_POLL_ENABLED=False` |
| Config test failures (env var leakage from .env) | LOW | Run with `-k "not test_config"` |
| Local git checkout mutates repo working tree | LOW | Review runs sequentially; branch restored after |
### TODO (Not Yet Implemented)