Skip to content
Merged
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
94 changes: 56 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,57 @@
<div align="center">
<img src="assets/icon.svg" alt="CodeBoarding Logo" height="120" />
# CodeBoarding Review

# CodeBoarding Visual Architecture Review
Review system design on every pull request, not just the diff.

Visual system-design review for pull requests. CodeBoarding analyzes the architecture before and after a change, then comments on the PR with an inline Mermaid diagram showing what changed.
</div>
CodeBoarding analyzes your architecture before and after a change, then comments on the PR with an inline Mermaid diagram of what changed — added, modified, and deleted components and the relationships between them. It runs the [CodeBoarding](https://github.com/CodeBoarding/CodeBoarding) engine in CI: static analysis combined with LLM reasoning.

## What It Does
[CodeBoarding](https://github.com/CodeBoarding/CodeBoarding) · [Website](https://codeboarding.org) · [Explore examples](https://codeboarding.org/diagrams) · [VS Code extension](https://marketplace.visualstudio.com/items?itemName=Codeboarding.codeboarding) · [Discord](https://discord.gg/T5zHTJYFuy)

[![JavaScript](https://img.shields.io/badge/JavaScript-222222?style=flat-square&logo=javascript&logoColor=F7DF1E)](https://developer.mozilla.org/en-US/docs/Web/JavaScript)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
[![Java](https://img.shields.io/badge/Java-E76F00?style=flat-square&logo=openjdk&logoColor=white)](https://www.java.com/)
[![Python](https://img.shields.io/badge/Python-3776AB?style=flat-square&logo=python&logoColor=white)](https://www.python.org/)
[![Go](https://img.shields.io/badge/Go-00ADD8?style=flat-square&logo=go&logoColor=white)](https://go.dev/)
[![PHP](https://img.shields.io/badge/PHP-777BB4?style=flat-square&logo=php&logoColor=white)](https://www.php.net/)
[![Rust](https://img.shields.io/badge/Rust-000000?style=flat-square&logo=rust&logoColor=white)](https://www.rust-lang.org/)
[![C#](https://custom-icon-badges.demolab.com/badge/C%23-512BD4.svg?style=flat-square&logo=cshrp&logoColor=white)](https://learn.microsoft.com/en-us/dotnet/csharp/)

## What it does

- Builds or reuses a baseline architecture analysis for the PR base.
- Runs incremental analysis on the PR head, then diffs components and relationships.
- Posts a sticky PR comment with an inline Mermaid map — 🟩 added · 🟨 modified · 🟥 deleted (dashed), for both nodes and edges.
- Posts a sticky PR comment with an inline Mermaid map. Green is added, yellow is modified, red (dashed) is deleted, for both nodes and edges.

A PR comment looks like this:

```mermaid
graph LR
Gateway["API Gateway"]
Auth["Auth Service"]
Cache["Cache"]
Gateway -- "routes to" --> Auth
Auth -- "reads/writes" --> Cache
Orchestration_Workflow_Manager["Orchestration & Workflow Manager"]
Incremental_Analysis_Controller["Incremental Analysis Controller"]
Static_Analysis_Engine["Static Analysis Engine"]
Agentic_Intelligence_Core["Agentic Intelligence Core"]
Health_Quality_Monitor["Health & Quality Monitor"]
Rendering_Output_Engine["Rendering & Output Engine"]
Persistence_Provider_Infrastructure["Persistence & Provider Infrastructure"]
Orchestration_Workflow_Manager -- "triggers change detection" --> Incremental_Analysis_Controller
Incremental_Analysis_Controller -- "passes filtered file sets" --> Static_Analysis_Engine
Static_Analysis_Engine -- "provides CFGs and symbol tables" --> Agentic_Intelligence_Core
Static_Analysis_Engine -- "supplies structural metrics" --> Health_Quality_Monitor
Agentic_Intelligence_Core -- "delivers summaries and diagrams" --> Rendering_Output_Engine
Health_Quality_Monitor -- "provides health reports" --> Rendering_Output_Engine
Persistence_Provider_Infrastructure -- "supplies LLM clients" --> Agentic_Intelligence_Core
Orchestration_Workflow_Manager -- "persists pipeline state" --> Persistence_Provider_Infrastructure
classDef added fill:#1f883d,stroke:#0b5d23,color:#fff;
classDef modified fill:#bf8700,stroke:#7d4e00,color:#fff;
classDef deleted fill:#cf222e,stroke:#82071e,color:#fff,stroke-dasharray:5 3;
class Cache added;
class Auth modified;
class Gateway deleted;
linkStyle 0 stroke:#cf222e,stroke-width:2px,stroke-dasharray:5 3;
linkStyle 1 stroke:#1f883d,stroke-width:2px;
class Health_Quality_Monitor added;
class Static_Analysis_Engine,Agentic_Intelligence_Core modified;
class Persistence_Provider_Infrastructure deleted;
linkStyle 3,5 stroke:#1f883d,stroke-width:2px;
linkStyle 2 stroke:#bf8700,stroke-width:2px;
linkStyle 6,7 stroke:#cf222e,stroke-width:2px,stroke-dasharray:5 3;
```

## Usage
## Quick start

Create `.github/workflows/codeboarding.yml`:

Expand All @@ -40,7 +60,7 @@ name: CodeBoarding review

on:
pull_request:
# Generate ONCE, when the PR becomes reviewable not on every push, so you
# Generate once, when the PR becomes reviewable, not on every push, so you
# don't spend an LLM job per commit. Use [opened] for strictly creation-only,
# or add `synchronize` to re-run on each push. Refresh anytime with /codeboarding.
types: [opened, reopened, ready_for_review]
Expand Down Expand Up @@ -70,15 +90,15 @@ jobs:
llm_api_key: ${{ secrets.OPENROUTER_API_KEY }}
```

Add the API key as a repository secret (**Settings → Secrets and variables → Actions**):
Add the API key as a [repository secret](https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets) (Settings → Secrets and variables → Actions):

```text
OPENROUTER_API_KEY = sk-or-...
```

That's the only required setup — it's passed via `llm_api_key` above. (For local runs with `scripts/run_local.sh`, export `OPENROUTER_API_KEY` as an env var instead.)
That is the only required setup, passed via `llm_api_key` above. For local runs with `scripts/run_local.sh`, export `OPENROUTER_API_KEY` as an environment variable instead.

**Models are optional.** Omit `agent_model` / `parsing_model` to use the engine's default for your provider, or pin them inline or from a repository **variable** (a model name isn't a secret, so use `vars.`, not `secrets.`):
Models are optional. Omit `agent_model` and `parsing_model` to use the engine's default for your provider, or pin them inline or from a repository variable (a model name is not a secret, so use `vars.`, not `secrets.`):

```yaml
with:
Expand All @@ -87,25 +107,23 @@ That's the only required setup — it's passed via `llm_api_key` above. (For loc
parsing_model: google/gemini-3-flash-preview # optional
```

**Model format (OpenRouter):** a bare OpenRouter slug (e.g. `anthropic/claude-sonnet-4`) — exactly one `/`, **no `openrouter/` prefix** (that's the LiteLLM form; the action rejects it early). Other providers use their own native model ids.

## Bring your own LLM provider

OpenRouter is the default, but you can use any provider the engine supports — set `llm_provider` and pass that provider's key:
OpenRouter is the default, but you can use any provider the engine supports. Set `llm_provider` and pass that provider's key:

```yaml
with:
llm_provider: anthropic # omit for OpenRouter (default)
llm_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
```

`llm_provider: <name>` hands your key to the engine as `<NAME>_API_KEY`, and the engine auto-selects that provider. Set **exactly one** key per run.
`llm_provider: <name>` hands your key to the engine as `<NAME>_API_KEY`, and the engine auto-selects that provider. Set exactly one key per run.

<details><summary><strong>Supported providers</strong></summary>
<details><summary>Supported providers</summary>

| `llm_provider` | env var the engine reads |
| `llm_provider` | Environment variable the engine reads |
|---|---|
| `openrouter` *(default)* | `OPENROUTER_API_KEY` |
| `openrouter` (default) | `OPENROUTER_API_KEY` |
| `openai` | `OPENAI_API_KEY` |
| `anthropic` | `ANTHROPIC_API_KEY` |
| `google` | `GOOGLE_API_KEY` |
Expand All @@ -116,31 +134,31 @@ OpenRouter is the default, but you can use any provider the engine supports —
| `aws_bedrock` | `AWS_BEARER_TOKEN_BEDROCK` |
| `ollama` | `OLLAMA_BASE_URL` |

This table mirrors the engine and may lag it — the source of truth is the engine's provider registry ([`agents/llm_config.py`](https://github.com/CodeBoarding/CodeBoarding/blob/main/agents/llm_config.py)). Any provider it adds that follows the `<NAME>_API_KEY` convention works here with no action change.
This table mirrors the engine and may lag it. The source of truth is the engine's provider registry, [`agents/llm_config.py`](https://github.com/CodeBoarding/CodeBoarding/blob/main/agents/llm_config.py). Any provider it adds that follows the `<NAME>_API_KEY` convention works here with no action change.

</details>

## When it runs

- **PR opened / reopened / marked ready** — generated once (per the `on:` triggers above). It does **not** re-run on every push, so you never spend an LLM job per commit; the comment reflects that point until refreshed.
- **`/codeboarding` comment** — a trusted collaborator (`OWNER`/`MEMBER`/`COLLABORATOR`) regenerates the diagram against the **current** PR head, even if one already exists. It re-runs and updates the same comment in place (the action reacts with 👀). Change the keyword via `trigger_command`.
- On a PR being opened, reopened, or marked ready for review, the diagram is generated once (per the `on:` triggers above). It does not re-run on every push, so you never spend an LLM job per commit; the comment reflects that point until refreshed.
- On a `/codeboarding` comment, a trusted collaborator (`OWNER`, `MEMBER`, or `COLLABORATOR`) regenerates the diagram against the current PR head, even if one already exists. It re-runs and updates the same comment in place. Change the keyword via `trigger_command`.

The command needs the `issue_comment` trigger and runs from your **default branch** (GitHub's rule), so it only works once the workflow is merged there. On-demand runs on fork PRs are refused, so fork code is never analyzed with your secrets.
The command needs the `issue_comment` trigger and runs from your default branch (a GitHub rule), so it only works once the workflow is merged there. On-demand runs on fork PRs are refused, so fork code is never analyzed with your secrets.

## Inputs

| Input | Default | Description |
|---|---|---|
| `llm_api_key` | required | Your LLM provider API key (see `llm_provider`). |
| `llm_provider` | `openrouter` | Provider for the key mapped to `<NAME>_API_KEY` (e.g. `anthropic`, `openai`, `google`). |
| `github_token` | `${{ github.token }}` | Token used to post/update the PR comment. |
| `llm_provider` | `openrouter` | Provider for the key, mapped to `<NAME>_API_KEY` (e.g. `anthropic`, `openai`, `google`). |
| `github_token` | `${{ github.token }}` | Token used to post or update the PR comment. |
| `engine_ref` | `v0.12.0` | CodeBoarding engine ref. Pin for reproducibility. |
| `depth_level` | `1` | Analysis depth, 1 to 3. Higher is slower and richer. |
| `render_depth` | `1` | Display depth for the PR diagram. Keep `1` for a clean top-level view. |
| `diagram_direction` | `LR` | Mermaid direction: `LR`, `TD`, `TB`, `RL`, or `BT`. |
| `changed_only` | `false` | Render only changed components and incident edges. |
| `agent_model` | engine default | Analysis model. Bare OpenRouter slug (e.g. `anthropic/claude-sonnet-4`); empty = engine's per-provider default. |
| `parsing_model` | engine default | Parsing model. Bare OpenRouter slug; empty = engine's per-provider default. |
| `agent_model` | `google/gemini-3-flash-preview` | Analysis model. OpenRouter default shown; other providers use their own engine default. |
| `parsing_model` | `google/gemini-3.1-flash-lite-preview` | Parsing model. OpenRouter default shown; other providers use their own engine default. |
| `comment_header` | `Architecture review` | Heading for the PR comment. |
| `trigger_command` | `/codeboarding` | Slash command for trusted on-demand runs. |
| `cta_base_url` | empty | Optional click-proxy base URL for editor and extension links. |
Expand All @@ -160,7 +178,7 @@ The command needs the `issue_comment` trigger and runs from your **default branc
- Do not use `pull_request_target` for this action. It can expose secrets to PR-head code.
- GitHub renders Mermaid in strict mode, so node click-through links are not supported in the PR diagram.

## Local Testing
## Local testing

Fast path, no LLM calls:

Expand Down
23 changes: 21 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ inputs:
required: false
default: '1'
agent_model:
description: 'Analysis model (AGENT_MODEL env var). A bare OpenRouter slug, e.g. anthropic/claude-sonnet-4. Empty (default) uses the engine''s own per-provider default.'
description: 'Analysis model (AGENT_MODEL env var). A bare OpenRouter slug. Defaults to google/gemini-3-flash-preview on OpenRouter; for other providers, empty uses the engine''s per-provider default.'
required: false
default: ''
parsing_model:
description: 'Parsing model (PARSING_MODEL env var). A bare OpenRouter slug. Empty (default) uses the engine''s own per-provider default.'
description: 'Parsing model (PARSING_MODEL env var). A bare OpenRouter slug. Defaults to google/gemini-3.1-flash-lite-preview on OpenRouter; for other providers, empty uses the engine''s per-provider default.'
required: false
default: ''
comment_header:
Expand Down Expand Up @@ -155,6 +155,21 @@ runs:
gh api -X POST "repos/${REPOSITORY}/issues/comments/${COMMENT_ID}/reactions" \
-f content=eyes >/dev/null 2>&1 || true

- name: Post in-progress comment
if: steps.guard.outputs.skip != 'true'
continue-on-error: true
uses: marocchino/sticky-pull-request-comment@v2
with:
header: codeboarding-architecture-diff
number: ${{ steps.guard.outputs.pr_number }}
message: |
### ${{ inputs.comment_header }} · analyzing…

⏳ CodeBoarding is analyzing the architecture changes in this PR. This usually takes a few minutes.

<sub>codeboarding-action · run ${{ github.run_id }}</sub>
GITHUB_TOKEN: ${{ inputs.github_token }}

- name: Checkout CodeBoarding engine
if: steps.guard.outputs.skip != 'true'
uses: actions/checkout@v4
Expand Down Expand Up @@ -282,6 +297,10 @@ runs:
echo "Provider: $PROVIDER -> $PROVIDER_ENV; key length: ${#KEY}"

if [ "$PROVIDER" = "openrouter" ]; then
# Default models on OpenRouter when the user didn't pin one (cheap Gemini).
# Other providers fall through to the engine's own per-provider default.
AGENT_MODEL="${AGENT_MODEL:-google/gemini-3-flash-preview}"
PARSING_MODEL="${PARSING_MODEL:-google/gemini-3.1-flash-lite-preview}"
# OpenRouter-only checks. The litellm 'openrouter/...' model prefix 400s
# the engine's native OpenRouter call; other providers use native ids.
for M in "$AGENT_MODEL" "$PARSING_MODEL"; do
Expand Down
9 changes: 5 additions & 4 deletions scripts/run_local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ run_engine() {
PROJECT_ROOT="$ENGINE" \
DIAGRAM_DEPTH_LEVEL="$DEPTH" \
CACHING_DOCUMENTATION="false" \
ENABLE_MONITORING="false" \
OPENROUTER_API_KEY="${OPENROUTER_API_KEY:-}"
ENABLE_MONITORING="false"
# OPENROUTER_API_KEY is inherited from the environment (full mode requires it).
# Pass the model only when set; empty -> engine's own valid per-provider default.
if [ -n "$AGENT_MODEL" ]; then export AGENT_MODEL; fi
if [ -n "$PARSING_MODEL" ]; then export PARSING_MODEL; fi
Expand All @@ -78,8 +78,9 @@ if [ -n "$BASE_JSON" ] && [ -n "$HEAD_JSON" ]; then
BASE_ANALYSIS="$BASE_JSON"
HEAD_ANALYSIS="$HEAD_JSON"
else
[ -n "$REPO" ] && [ -n "$BASE_REF" ] && [ -n "$HEAD_REF" ] || {
echo "Need either --base-json/--head-json, or --repo/--base/--head." >&2; exit 2; }
if [ -z "$REPO" ] || [ -z "$BASE_REF" ] || [ -z "$HEAD_REF" ]; then
echo "Need either --base-json/--head-json, or --repo/--base/--head." >&2; exit 2
fi
[ -d "$ENGINE" ] || { echo "Engine not found at $ENGINE (set --engine or \$ENGINE)." >&2; exit 2; }
[ -n "${OPENROUTER_API_KEY:-}" ] || { echo "Export OPENROUTER_API_KEY for the full pipeline." >&2; exit 2; }
REPO="$(cd "$REPO" && pwd)"
Expand Down
Loading