Skip to content

Commit aa10a2f

Browse files
anvansterclaude
andcommitted
feat: CI PR review — --graph-only, --run-tool, pr_context markdown, GH Action
Enables codegraph as a GitHub Action that posts a code-graph PR review comment on every PR, with no embeddings and no API keys. New server flags (main.rs): --graph-only skip embedding generation entirely (no ONNX model load, 10-50× faster index). Structural tools only. --run-tool <name> one-shot: index, run one tool, print result, exit. No MCP stdio handshake. Pairs with --tool-args. --tool-args <json> arguments for --run-tool (default {}). pr_context (server.rs): - format:"markdown" → ready-to-post PR comment (risk emoji, blast radius, test gaps, stale docs, suggested reviewers, commit hint). The --run-tool path prints the markdown field raw for CI piping. - Fixed functions_changed to count only modified functions (was counting every function in changed files — reported 683 vs actual 29). - Test functions excluded from test-gap list (a test doesn't need its own coverage). - Dedup test gaps by (function, file) — incremental indexing leaves duplicate nodes. GitHub Action (.github/workflows/codegraph-pr.yml): - pull_request trigger, fetch-depth:0 (required for git diff base...HEAD) - npm install codegraph, run graph-only pr_context, post/update PR comment via github-script + GITHUB_TOKEN (no secrets needed) - Updates existing comment instead of spamming new ones README: GitHub Action section + --graph-only/--run-tool flag docs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 580b652 commit aa10a2f

5 files changed

Lines changed: 316 additions & 7 deletions

File tree

.github/workflows/codegraph-pr.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# CodeGraph PR Review — posts a code-graph analysis comment on every PR.
2+
#
3+
# Runs graph-only (no embeddings, no ONNX model) so it's fast and light:
4+
# typically completes in well under a minute even on large repos.
5+
#
6+
# Copy this file into your own repo at .github/workflows/codegraph-pr.yml
7+
# No secrets or API keys required — uses the built-in GITHUB_TOKEN.
8+
name: CodeGraph PR Review
9+
10+
on:
11+
pull_request:
12+
types: [opened, synchronize, reopened]
13+
14+
permissions:
15+
contents: read
16+
pull-requests: write
17+
18+
jobs:
19+
review:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Checkout (full history)
23+
uses: actions/checkout@v4
24+
with:
25+
# fetch-depth: 0 is REQUIRED — pr_context runs
26+
# `git diff <base>...HEAD`, which needs the base branch history.
27+
# The default shallow clone breaks the diff.
28+
fetch-depth: 0
29+
30+
- name: Install CodeGraph
31+
run: npm install -g @astudioplus/codegraph-mcp
32+
33+
- name: Run PR review
34+
id: review
35+
run: |
36+
# Resolve the npm-installed binary for this platform
37+
BIN="$(npm root -g)/@astudioplus/codegraph-mcp/bin/codegraph-server-linux-x64"
38+
chmod +x "$BIN"
39+
"$BIN" \
40+
--graph-only \
41+
--run-tool codegraph_pr_context \
42+
--tool-args "{\"baseBranch\":\"${{ github.base_ref }}\",\"format\":\"markdown\"}" \
43+
> review.md 2>/dev/null || echo "CodeGraph review failed" > review.md
44+
45+
- name: Post or update PR comment
46+
uses: actions/github-script@v7
47+
with:
48+
script: |
49+
const fs = require('fs');
50+
const body = fs.readFileSync('review.md', 'utf8');
51+
const marker = '## 🔍 CodeGraph PR Review';
52+
53+
// Find an existing CodeGraph comment to update (avoids comment spam)
54+
const { data: comments } = await github.rest.issues.listComments({
55+
owner: context.repo.owner,
56+
repo: context.repo.repo,
57+
issue_number: context.issue.number,
58+
});
59+
const existing = comments.find(c => c.body.startsWith(marker));
60+
61+
if (existing) {
62+
await github.rest.issues.updateComment({
63+
owner: context.repo.owner,
64+
repo: context.repo.repo,
65+
comment_id: existing.id,
66+
body,
67+
});
68+
} else {
69+
await github.rest.issues.createComment({
70+
owner: context.repo.owner,
71+
repo: context.repo.repo,
72+
issue_number: context.issue.number,
73+
body,
74+
});
75+
}

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,26 @@ to grep / multi-file reads. Maps natural-language intent to the right
4747
Setup is `cp <agent>/codegraph.md ~/<agent>/` (one line per agent — see
4848
the rules repo's README).
4949

50+
### GitHub Action — PR review in CI
51+
52+
Drop a workflow into your repo to get an automatic code-graph analysis
53+
comment on every PR — blast radius, test gaps, stale docs, suggested
54+
reviewers. Runs **graph-only** (no embeddings, no ONNX model), so it's
55+
fast and needs no API keys — just the built-in `GITHUB_TOKEN`.
56+
57+
Copy [`.github/workflows/codegraph-pr.yml`](.github/workflows/codegraph-pr.yml)
58+
into your repo. The core invocation is a single command:
59+
60+
```bash
61+
codegraph-server --graph-only \
62+
--run-tool codegraph_pr_context \
63+
--tool-args '{"baseBranch":"main","format":"markdown"}'
64+
```
65+
66+
This prints a ready-to-post markdown comment. The `--graph-only` flag
67+
skips embedding generation (10-50× faster indexing); `--run-tool` runs
68+
one tool and exits without the MCP stdio handshake — ideal for scripting.
69+
5070
---
5171

5272
## Configuration
@@ -61,6 +81,8 @@ the rules repo's README).
6181
| `--full-body-embedding` | `true` | Embed full function body (~50 lines) for better semantic search and duplicate detection |
6282
| `--max-files <n>` | 5000 | Maximum files to index |
6383
| `--profile <name>` | `all` | Filter the exposed MCP tool surface to a named subset (see below) |
84+
| `--graph-only` | off | Skip embedding generation — build the graph and serve structural tools only. No ONNX model load, 10-50× faster indexing. Semantic search unavailable. For CI / one-shot graph queries. |
85+
| `--run-tool <name>` || One-shot mode: index, run a single tool, print its result, exit. No MCP handshake. Pair with `--tool-args '<json>'`. |
6486

6587
#### `--profile` — narrow the MCP tool surface
6688

crates/codegraph-server/src/main.rs

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,25 @@ struct Args {
7272
/// MCP mode only — LSP mode ignores this.
7373
#[arg(long)]
7474
profile: Option<String>,
75+
76+
/// Skip embedding generation — build the graph and serve structural
77+
/// tools only. The ONNX model is never loaded (faster startup, lower
78+
/// memory, ~10-50× faster indexing). Semantic search and similarity
79+
/// tools are unavailable. Ideal for CI / one-shot graph queries.
80+
#[arg(long)]
81+
graph_only: bool,
82+
83+
/// One-shot mode: index the workspace, run a single tool, print its
84+
/// JSON result to stdout, and exit. No MCP stdio handshake. Pair with
85+
/// --tool-args for arguments and --graph-only for CI speed. Example:
86+
/// codegraph-server --graph-only --run-tool codegraph_pr_context \
87+
/// --tool-args '{"baseBranch":"main","format":"markdown"}'
88+
#[arg(long)]
89+
run_tool: Option<String>,
90+
91+
/// JSON arguments for --run-tool (default: {}).
92+
#[arg(long, default_value = "{}")]
93+
tool_args: String,
7594
}
7695

7796
/// Re-entrancy guard for the panic hook. A second panic during hook
@@ -168,6 +187,53 @@ async fn main() {
168187
// before any RocksDB open so the LOCK release path is wired up first.
169188
spawn_signal_listeners();
170189

190+
// One-shot tool mode: index, run a single tool, print JSON, exit.
191+
if let Some(tool_name) = args.run_tool.clone() {
192+
let workspaces = if args.workspace.is_empty() {
193+
vec![std::env::current_dir().expect("Failed to get current directory")]
194+
} else {
195+
args.workspace.clone()
196+
};
197+
let embedding_model = match args.embedding_model.as_str() {
198+
"jina-code-v2" => codegraph_memory::CodeGraphEmbeddingModel::JinaCodeV2,
199+
"granite-97m" | "granite" | "granite-97m-multilingual-r2" => {
200+
codegraph_memory::CodeGraphEmbeddingModel::Granite97mMultilingualR2
201+
}
202+
_ => codegraph_memory::CodeGraphEmbeddingModel::BgeSmall,
203+
};
204+
let tool_args: serde_json::Value = serde_json::from_str(&args.tool_args)
205+
.unwrap_or_else(|e| {
206+
eprintln!("Invalid --tool-args JSON: {e}");
207+
std::process::exit(2);
208+
});
209+
210+
let mut server = codegraph_server::mcp::McpServer::new(
211+
workspaces,
212+
args.exclude.clone(),
213+
args.max_files,
214+
embedding_model,
215+
args.full_body_embedding,
216+
)
217+
.with_graph_only(args.graph_only);
218+
219+
match server.run_single_tool(&tool_name, Some(tool_args)).await {
220+
Ok(result) => {
221+
// If the tool returned a markdown field, print it raw —
222+
// CI pipes it straight into a PR comment. Otherwise print JSON.
223+
if let Some(md) = result.get("markdown").and_then(|v| v.as_str()) {
224+
println!("{md}");
225+
} else {
226+
println!("{}", serde_json::to_string_pretty(&result).unwrap_or_default());
227+
}
228+
}
229+
Err(e) => {
230+
eprintln!("Tool '{tool_name}' failed: {e}");
231+
std::process::exit(1);
232+
}
233+
}
234+
return;
235+
}
236+
171237
if args.mcp {
172238
// MCP mode
173239
let workspaces = if args.workspace.is_empty() {
@@ -211,7 +277,8 @@ async fn main() {
211277
embedding_model,
212278
args.full_body_embedding,
213279
)
214-
.with_tool_profile(tool_profile);
280+
.with_tool_profile(tool_profile)
281+
.with_graph_only(args.graph_only);
215282
if let Err(e) = server.run().await {
216283
tracing::error!("MCP server error: {}", e);
217284
std::process::exit(1);

0 commit comments

Comments
 (0)