Code Guru
A CLI tool that leverages AI (Claude Code CLI or OpenAI API) to automatically review pull requests across GitHub and Azure DevOps, enforcing coding standards from configurable rule files.
Features
- Multi-provider support via gitforge: GitHub and Azure DevOps
- Dual AI backend: Claude Code CLI (
claude --print) or OpenAI Chat Completions API
- Rule-based reviews using Markdown files from guide (or any directory)
- YAML
frontmatter in rules for file-glob-based filtering (e.g., paths: ["**/*.go"])
- Inline and general PR comments posted back via gitforge
- Three modes: single PR review, batch review-all, and discover (list open PRs)
- Reviews each PR exactly once — subsequent pushes are no-ops. To request a re-review, post a PR comment that mentions
@code-guru (case-insensitive). On a re-review the bot acts as a reviewer who reads the existing conversation: it loads every prior bot inline thread plus every reply, classifies each as resolved / outstanding / outdated, posts one short reply per prior thread, and auto-closes the threads it considers resolved (Azure DevOps thread state fixed). Net-new findings only land if the diff genuinely warrants one and it does NOT overlap a thread the bot already addressed — replacing the pre-existing failure mode where every re-review flooded the PR with reworded duplicates of every prior comment
Installation
go install github.com/rios0rios0/codeguru/cmd/code-guru@latest
Or build from source:
git clone https://github.com/rios0rios0/code-guru.git
cd code-guru
go build -o code-guru ./cmd/code-guru/
Configuration
Create a .code-guru.yaml file (searched in ., .config, configs, ~, ~/.config):
providers:
- type: 'github'
token: '${GITHUB_TOKEN}'
organizations:
- 'rios0rios0'
- type: 'azuredevops'
token: '${AZURE_DEVOPS_PAT}'
organizations:
- 'MyOrg'
ai:
backend: 'claude'
# When true, the bot also records a native pull request review (Approved /
# Changes Requested) on the platform's reviewer panel in addition to the
# text completion annotation. Defaults to true; set to false to opt out.
submit_native_review: true
# When false (the default), draft PRs are skipped entirely — set to true to
# opt back in.
review_drafts: false
claude:
binary_path: 'claude'
model: 'sonnet'
max_turns: 1
openai:
api_key: '${OPENAI_API_KEY}'
model: 'gpt-4o'
rules:
path: '${HOME}/Development/github.com/rios0rios0/guide/.ai/claude/rules'
categories: []
Token Resolution
Tokens support three resolution strategies:
- Environment variable:
${GITHUB_TOKEN} expands from the environment
- File path: if the resolved string is a file path, its contents are read
- Inline: literal token string
Usage
Discover open PRs
code-guru discover -c .code-guru.yaml
Review all open PRs (batch mode)
code-guru review-all -c .code-guru.yaml --dry-run
code-guru review-all -c .code-guru.yaml
Review a single PR
code-guru review https://github.com/org/repo/pull/123
code-guru review https://dev.azure.com/org/project/_git/repo/pullrequest/456
Flags
| Flag |
Description |
-c, --config |
Path to config file (default: auto-discover) |
--backend |
AI backend: openai, claude, or anthropic |
--rules-path |
Path to rules directory |
--dry-run |
Run review without posting comments |
-v, --verbose |
Enable debug logging |
Supported Providers
| Provider |
Type Key |
PR Comments |
Inline Comments |
| GitHub |
github |
Yes |
Yes |
| Azure DevOps |
azuredevops |
Yes |
Yes |
AI Backends
| Backend |
Key |
How It Works |
| Anthropic |
anthropic |
Calls the Anthropic Messages API directly via Go SDK |
| Claude Code |
claude |
Invokes claude --print CLI as a subprocess |
| OpenAI |
openai |
Calls the Chat Completions API with JSON response format |
AI Verdict
Each review returns a verdict alongside comments:
| Verdict |
Meaning |
approve |
No blocking issues, safe to merge |
request_changes |
Error-level issues that must be fixed |
comment |
Informational feedback only, not blocking |
The verdict is printed as VERDICT:<value> for machine parsing.
Trivial PR Detection
When trivial detection is enabled and CI has passed, PRs matching built-in adapters are handled without calling the LLM, saving tokens. In webhook mode, CI status is provided by the webhook event. In CLI mode, CI status detection is planned via gitforge's GetPullRequestCheckStatus() (not yet available).
There are two categories of trivial adapters:
Update Adapters (Dependency Updates)
These detect dependency update PRs and auto-approve them.
| Adapter |
Matches When |
update-go |
Only go.mod, go.sum, CHANGELOG.md changed |
update-node |
Only package.json, lock files, CHANGELOG.md changed |
update-python |
Only pyproject.toml, requirements*.txt, CHANGELOG.md |
Bump Adapters (Version Bumps / Releases)
These detect version bump (release ceremony) PRs. If the repo contains an .autobump.yaml config file, the adapter validates that all version files declared in the config are present in the PR. Missing files result in a reject verdict.
| Adapter |
Default Files |
AutoBump Language Key |
bump-go |
CHANGELOG.md |
go |
bump-node |
package.json, CHANGELOG.md |
typescript |
bump-python |
*/__init__.py, CHANGELOG.md |
python |
Other Adapters
| Adapter |
Matches When |
docs-only |
Only *.md files changed |
Configuration
Configure in .code-guru.yaml:
trivial:
enabled: true
adapters:
- 'update-go'
- 'bump-go'
- 'docs-only'
auto_merge: false # opt-in; true completes the PR after a trivial-approve verdict
merge_strategy: '' # 'merge' / 'squash' / 'rebase' — empty falls back to platform default
Or via environment variables:
CODE_GURU_TRIVIAL_ADAPTERS=update-go,bump-go,docs-only
CODE_GURU_TRIVIAL_AUTO_MERGE=true
CODE_GURU_TRIVIAL_MERGE_STRATEGY=squash
auto_merge is intentionally off by default — it bypasses human review and merges cross-system, so the gate is "operator must explicitly opt in". A merge failure logs at warn and the trivial-approve verdict still stands; the PR author can complete the merge manually from the platform UI.
Server / Webhook Mode
Code Guru can run as a long-lived HTTP server that receives webhook events from
GitHub Apps and Azure DevOps Service Hooks. Each event is enqueued onto a bounded
worker pool and the HTTP response returns immediately (202 Accepted), so the
review runs asynchronously and never blocks the sender.
code-guru serve --port 8080
Endpoints
| Endpoint |
Method |
Auth |
Notes |
/health |
GET |
none |
Liveness probe |
/webhooks/github |
POST |
HMAC-SHA256 |
Validates the X-Hub-Signature-256 header against server.webhook_secret. Acts on pull_request opened/synchronize/reopened. |
/webhooks/azuredevops |
POST |
HTTP Basic |
Username must be code-guru; password must equal server.webhook_secret. Acts on git.pullrequest.created/git.pullrequest.updated for active PRs. |
Authentication Models
- GitHub -- the secret is the value configured on the GitHub App webhook
("Webhook secret" in the App settings). When
github_app.app_id and
github_app.private_key are configured the server signs an RS256 JWT and
exchanges it for a per-installation access token, cached until 5 minutes
before expiry. Without github_app.* the handler falls back to the configured
github PAT in providers[].
- Azure DevOps -- ADO does not sign Service Hooks, so it uses HTTP Basic.
Configure the Service Hook with username
code-guru and password equal to
server.webhook_secret.
Configuration
server:
port: 8080
webhook_secret: '${CODE_GURU_WEBHOOK_SECRET}'
workers: 8
queue_size: 100
shutdown_timeout: 30s
allowed_organizations:
- 'ExampleOrg'
allowed_projects:
- 'Platform'
github_app:
app_id: 123456
private_key: '${CODE_GURU_GITHUB_PRIVATE_KEY}'
Webhook Environment Variables
| Variable |
Description |
Default |
CODE_GURU_PORT |
HTTP port to listen on |
8080 |
CODE_GURU_WEBHOOK_SECRET |
Shared secret for HMAC (GitHub) and Basic Auth password (ADO) |
|
CODE_GURU_SERVER_WORKERS |
Worker count draining the review queue |
runtime.NumCPU() |
CODE_GURU_SERVER_QUEUE_SIZE |
Maximum buffered jobs before submitters get 503 Service Unavailable |
100 |
CODE_GURU_SERVER_SHUTDOWN_TIMEOUT |
Maximum drain time on SIGINT/SIGTERM |
30s |
CODE_GURU_SERVER_ALLOWED_ORGANIZATIONS |
Comma-separated allowlist of org/owner names (empty = allow all) |
|
CODE_GURU_SERVER_ALLOWED_PROJECTS |
Comma-separated allowlist of ADO project names (empty = allow all) |
|
CODE_GURU_GITHUB_APP_ID |
Numeric GitHub App ID |
|
CODE_GURU_GITHUB_PRIVATE_KEY |
PEM-encoded RSA private key for the GitHub App |
|
Running with Docker
docker build -t code-guru:latest .
docker run --rm -p 8080:8080 \
-e CODE_GURU_BACKEND=anthropic \
-e CODE_GURU_ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY \
-e CODE_GURU_WEBHOOK_SECRET=$WEBHOOK_SECRET \
-e CODE_GURU_PROVIDER_TOKEN=$AZURE_DEVOPS_PAT \
-e CODE_GURU_SERVER_ALLOWED_ORGANIZATIONS=ExampleOrg \
code-guru:latest
The Dockerfile uses a multi-stage build (golang:1.26-alpine builder,
gcr.io/distroless/static-debian12:nonroot runtime) and runs as the
non-root user.
The image ships with a HEALTHCHECK directive that calls code-guru health against the local listener every 30 seconds. The health
subcommand can also be invoked directly for ad-hoc smoke tests:
code-guru health --url http://127.0.0.1:8080/health --timeout 4s
Exit codes: 0 on 200, 1 on any other status, network error, or
timeout.
Kubernetes deployment
When the serve controller starts inside a Kubernetes pod (detected
via the standard KUBERNETES_SERVICE_HOST env var that the kubelet
always injects), the dispatcher automatically swaps its default
per-pod in-memory webhook dedup for a cross-pod backend backed by
coordination.k8s.io/v1 Lease objects. This is required when the
deployment runs with replicas > 1 because Azure DevOps fires both
git.pullrequest.created and git.pullrequest.updated for every
new PR; without a shared lock the K8s Service round-robins one
delivery to each replica and the bot posts duplicate reviews. The
lease is named code-guru-{sanitised-key}-{hash} (the SHA-256 suffix
prevents collisions from the lossy character substitution) and is
created with leaseDurationSeconds: 900 (must exceed the bot's
maximum review wall-time so the takeover path never steals an
actively-held lease — the worst review observed was ≈8 minutes)
as freshness metadata —
Kubernetes does NOT auto-delete Lease objects when that duration
elapses, so the dedup contract relies on two explicit pieces of work:
- The owning pod
Deletes the lease after the worker finishes
(success or failure), so a real follow-up push minutes later
re-acquires immediately.
- A subsequent webhook delivery whose
Create returns
AlreadyExists runs a stale-lease takeover: it Gets the holding
lease, checks whether acquireTime + leaseDurationSeconds has
already passed, and if so Deletes the stale lease (with a UID
precondition for race safety) and retries Create. This recovers
from a pod crash mid-review — the maximum window during which a
crashed lease blocks new work is leaseDurationSeconds.
The K8s API server's optimistic concurrency on Create is what
makes the dedup atomic across replicas: exactly one Create for a
given (namespace, name) succeeds and every concurrent Create
returns 409 AlreadyExists.
Required RBAC (apply once per namespace the bot runs in):
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: code-guru
name: code-guru-webhook-dedup
rules:
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "create", "delete", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: code-guru
name: code-guru-webhook-dedup
subjects:
- kind: ServiceAccount
name: code-guru
namespace: code-guru
roleRef:
kind: Role
name: code-guru-webhook-dedup
apiGroup: rbac.authorization.k8s.io
If the RBAC is missing, the bot logs a Warn at startup and falls
back to the per-pod in-memory cache: still safe, but cross-pod
duplicates will not be suppressed. Outside a pod (local CLI runs,
unit tests) no Kubernetes API is contacted.
Environment Variable Configuration
For CI/CD environments without a config file, all settings can be provided via CODE_GURU_* environment variables:
| Variable |
Description |
Default |
CODE_GURU_BACKEND |
AI backend |
openai |
CODE_GURU_OPENAI_API_KEY |
OpenAI API key |
|
CODE_GURU_ANTHROPIC_API_KEY |
Anthropic API key |
|
CODE_GURU_RULES_PATH |
Path to rules directory |
|
CODE_GURU_PROVIDER_TOKEN |
Git provider token |
|
CODE_GURU_TRIVIAL_ADAPTERS |
Comma-separated adapter names |
|
CODE_GURU_TRIVIAL_AUTO_MERGE |
Opt-in flag that completes the PR after a trivial-approve verdict |
false |
CODE_GURU_TRIVIAL_MERGE_STRATEGY |
gitforge merge strategy (merge / squash / rebase); empty = default |
|
CODE_GURU_AI_SUBMIT_NATIVE_REVIEW |
Records a native review (Approved / Changes Requested) on the platform's reviewer panel; set to false to opt out |
true |
CODE_GURU_AI_REVIEW_DRAFTS |
When true, the bot reviews draft PRs as well — by default drafts are skipped |
false |
Rules
Rules are Markdown files loaded from the configured rules.path. Each file represents a rule category (e.g., security.md, golang.md, testing.md).
Rules can include YAML frontmatter with paths globs for file-specific filtering:
---
paths:
- "**/*.go"
---
# Go Conventions
Use `gofmt` for formatting...
Universal categories (always included): architecture, ci-cd, code-style, design-patterns, documentation, git-flow, security, testing.
Contributing
Contributions are welcome. See CONTRIBUTING.md for guidelines.
License
See LICENSE for details.