feat(pages): add Docs page with search, markdown rendering, and i18n support (#273)

* feat(pages): add Docs page with search, markdown rendering, and i18n support

- Add DocsPage with full-text search modal (⌘K trigger)
- Add MarkdownRenderer with DOMPurify sanitization
- Add bilingual docs content (en/zh) for all sections
- Add shared headingId utility for consistent TOC anchors
- Add search keyboard hints with i18n support
- Update Navbar with Docs navigation link
- Add icon-search.svg asset
- Configure webpack for markdown imports

* fix(pages): address PR #273 code review feedback

- Replace marked.setOptions() with new Marked instance (no global mutation)
- Escape heading ID attribute value to prevent XSS
- Use crypto.randomUUID() for mermaid diagram IDs (no collisions)
- Add cancellation flag for async mermaid renders on unmount
- Move inline <pre> styles to CSS class (only dynamic align-items inline)
- Move @types/dompurify to devDependencies
- Remove @ts-nocheck from docs/index.ts
- Extract getRawContent helper to reduce duplication
- Fix searchDocs fallback consistency (add enDocs fallback)
- Fix heading ID mismatch by stripping markdown links before ID generation
- Separate sidebar chevron (expand) from label (navigate)
- Guard ⌘K shortcut against input/textarea focus interception
This commit is contained in:
hezheng.lsw 2026-07-03 11:45:33 +08:00 committed by GitHub
parent 589a7249f5
commit 022ed75682
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
49 changed files with 9402 additions and 668 deletions

View file

@ -3,7 +3,7 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Open Code Review — AI Code Review</title> <title>Open Code Review — Agent Native Code Review</title>
<link rel="icon" type="image/svg+xml" href="<%= require('./logo.svg') %>" /> <link rel="icon" type="image/svg+xml" href="<%= require('./logo.svg') %>" />
<link rel="preconnect" href="https://fonts.googleapis.com" /> <link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />

View file

@ -9,6 +9,9 @@
}, },
"dependencies": { "dependencies": {
"@agentscope-ai/icons": "^1.0.68", "@agentscope-ai/icons": "^1.0.68",
"dompurify": "^3.4.11",
"marked": "^18.0.5",
"mermaid": "^11.16.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-router-dom": "^6.8.0", "react-router-dom": "^6.8.0",
@ -22,6 +25,7 @@
"@babel/preset-env": "^7.29.5", "@babel/preset-env": "^7.29.5",
"@babel/preset-react": "^7.23.5", "@babel/preset-react": "^7.23.5",
"@babel/preset-typescript": "^7.23.3", "@babel/preset-typescript": "^7.23.3",
"@types/dompurify": "^3.0.5",
"@types/react": "^18.2.0", "@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0", "@types/react-dom": "^18.2.0",
"@types/three": "^0.185.0", "@types/three": "^0.185.0",

View file

@ -22,7 +22,7 @@ const App: React.FC = () => {
<Route path="/" element={<LandingPage><FeaturesPage /></LandingPage>} /> <Route path="/" element={<LandingPage><FeaturesPage /></LandingPage>} />
<Route path="/benchmark" element={<LandingPage><BenchmarkPage /></LandingPage>} /> <Route path="/benchmark" element={<LandingPage><BenchmarkPage /></LandingPage>} />
<Route path="/quickstart" element={<LandingPage><QuickStartPage /></LandingPage>} /> <Route path="/quickstart" element={<LandingPage><QuickStartPage /></LandingPage>} />
<Route path="/docs" element={<LandingPage><DocsPage /></LandingPage>} /> <Route path="/docs" element={<DocsPage />} />
</Routes> </Routes>
</> </>
); );

View file

@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" width="64" height="64" xmlns="http://www.w3.org/2000/svg"><path fill="#FFFFFF" fill-opacity="0.8" d="M809.6 486.4c0 176.7296-143.2704 320-320 320S169.6 663.1296 169.6 486.4 312.8704 166.4 489.6 166.4s320 143.2704 320 320z m-64 0a253.728 253.728 0 0 0-17.9744-94.2336c-13.0112-32.864-32.0128-61.792-57.0048-86.784-24.9952-24.9952-53.9232-44-86.7872-57.008A253.7664 253.7664 0 0 0 489.6 230.4a253.7536 253.7536 0 0 0-94.2336 17.9744c-32.864 13.0112-61.792 32.0128-86.784 57.0048-24.9952 24.9952-44 53.9232-57.008 86.7872A253.7536 253.7536 0 0 0 233.6 486.4a253.7664 253.7664 0 0 0 17.9744 94.2336c13.0112 32.864 32.0128 61.792 57.0048 86.7872 24.9952 24.992 53.9232 43.9936 86.7872 57.0048A253.728 253.728 0 0 0 489.6 742.4a253.7408 253.7408 0 0 0 94.2336-17.9744c32.864-13.0112 61.792-32.0128 86.7872-57.0048 24.992-24.9952 43.9936-53.9232 57.0048-86.7872A253.7408 253.7408 0 0 0 745.6 486.4z"/><path fill="#FFFFFF" fill-opacity="0.8" d="M822.0608 867.3376l-148.688-148.7104a32 32 0 0 1 45.2544-45.2544l148.6912 148.7104a32 32 0 1 1-45.2576 45.2544z"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,187 @@
import React, { useMemo, useEffect, useRef, useState, useCallback, useId } from 'react';
import ReactDOM from 'react-dom';
import { Marked, Renderer } from 'marked';
import DOMPurify from 'dompurify';
import mermaid from 'mermaid';
import { useTranslation } from '../i18n';
import copyIcon from '../assets/icons/icon-copy.svg';
import { generateHeadingId } from '../utils/headingId';
// Initialize mermaid with dark theme
mermaid.initialize({
startOnLoad: false,
theme: 'dark',
themeVariables: {
primaryColor: '#1a1a2e',
primaryTextColor: 'rgba(255,255,255,0.85)',
primaryBorderColor: 'rgba(255,255,255,0.2)',
lineColor: 'rgba(255,255,255,0.4)',
secondaryColor: '#16213e',
tertiaryColor: '#0f3460',
background: '#000000',
mainBkg: 'rgba(255,255,255,0.04)',
nodeBorder: 'rgba(255,255,255,0.16)',
clusterBkg: 'rgba(255,255,255,0.02)',
titleColor: '#FFFFFF',
edgeLabelBackground: '#000000',
},
flowchart: {
htmlLabels: true,
curve: 'basis',
},
});
interface MarkdownRendererProps {
content: string;
}
/**
* Renders markdown content with dark theme styling matching the existing DocsPage design.
* Uses `marked` to parse markdown into HTML, then renders with styled container.
* Mermaid diagrams are rendered client-side after mount.
*/
const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({ content }) => {
const containerRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
const [toastVisible, setToastVisible] = useState(false);
const handleCopy = useCallback((text: string) => {
if (navigator.clipboard && window.isSecureContext) {
navigator.clipboard.writeText(text).then(() => {
setToastVisible(true);
}).catch(() => fallbackCopy(text));
} else {
fallbackCopy(text);
}
}, []);
const fallbackCopy = (text: string) => {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
const success = document.execCommand('copy');
document.body.removeChild(textarea);
if (success) setToastVisible(true);
};
useEffect(() => {
if (!toastVisible) return;
const timer = setTimeout(() => setToastVisible(false), 1200);
return () => clearTimeout(timer);
}, [toastVisible]);
const html = useMemo(() => {
// Custom renderer to generate heading IDs matching the TOC extraction logic
const renderer = new Renderer();
renderer.heading = function ({ text, depth }: { text: string; depth: number }) {
const id = generateHeadingId(text);
// Escape id attribute value to prevent XSS
const safeId = id.replace(/"/g, '&quot;');
return `<h${depth} id="${safeId}">${text}</h${depth}>\n`;
};
// Strip trailing newlines from code blocks to avoid empty line at bottom
renderer.code = function ({ text, lang, escaped }: { text: string; lang?: string; escaped?: boolean }) {
const trimmed = text.replace(/^\n+|\n+$/g, '');
const content = escaped ? trimmed : trimmed.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
const langClass = lang ? ` class="language-${lang}"` : '';
const isMultiline = trimmed.includes('\n');
const alignItems = isMultiline ? 'flex-start' : 'center';
return `<pre style="align-items:${alignItems};"><code${langClass}>${content}</code></pre>\n`;
};
const instance = new Marked({ gfm: true, breaks: false, renderer });
return DOMPurify.sanitize(instance.parse(content) as string);
}, [content]);
// Render mermaid diagrams and add copy buttons to code blocks after DOM update
useEffect(() => {
if (!containerRef.current) return;
let cancelled = false;
// Add copy buttons to all pre > code blocks (except mermaid)
const preBlocks = containerRef.current.querySelectorAll('pre');
preBlocks.forEach((pre) => {
const codeEl = pre.querySelector('code');
if (!codeEl || codeEl.classList.contains('language-mermaid')) return;
if (pre.querySelector('.code-copy-btn')) return; // already added
// Create copy button matching reference HTML
const btn = document.createElement('div');
btn.className = 'code-copy-btn';
btn.style.cssText = 'display:flex;flex-shrink:0;justify-content:flex-start;align-items:flex-start;flex-direction:column;padding-top:4px;padding-bottom:4px;cursor:pointer;';
btn.innerHTML = `<img src="${copyIcon}" alt="copy" style="width:16px;height:16px;" />`;
btn.addEventListener('click', () => {
const text = codeEl.textContent || '';
handleCopy(text);
});
pre.appendChild(btn);
});
// Render mermaid diagrams
const mermaidBlocks = containerRef.current.querySelectorAll('code.language-mermaid');
if (mermaidBlocks.length === 0) return;
const renderPromises = Array.from(mermaidBlocks).map(async (block) => {
const pre = block.parentElement;
if (!pre) return;
const code = block.textContent || '';
try {
const id = `mermaid-diagram-${crypto.randomUUID()}`;
const { svg } = await mermaid.render(id, code);
if (cancelled) return;
// Replace the <pre> with rendered SVG
const wrapper = document.createElement('div');
wrapper.className = 'mermaid-rendered';
wrapper.innerHTML = svg;
pre.replaceWith(wrapper);
} catch (e) {
if (cancelled) return;
// If rendering fails, show the code block normally
(block as HTMLElement).style.display = 'block';
console.warn('[Mermaid] render failed:', e);
}
});
return () => { cancelled = true; };
}, [html]);
return (
<>
<div
ref={containerRef}
className="docs-markdown"
dangerouslySetInnerHTML={{ __html: html }}
style={{ width: '100%' }}
/>
{ReactDOM.createPortal(
<div
style={{
position: 'fixed',
top: 88,
left: '50%',
transform: 'translateX(-50%)',
background: 'rgba(255,255,255,0.1)',
border: '1px solid rgba(255,255,255,0.2)',
color: 'rgba(255,255,255,0.85)',
padding: '5px 8px 5px 10px',
borderRadius: 6,
fontSize: 12,
fontWeight: 500,
pointerEvents: 'none',
opacity: toastVisible ? 1 : 0,
transition: 'opacity 0.15s ease',
zIndex: 9999,
backdropFilter: 'blur(8px)',
}}
>
{t('quickstart.copied')}
</div>,
document.body
)}
</>
);
};
export default MarkdownRenderer;

View file

@ -187,7 +187,7 @@ const Navbar: React.FC = () => {
rel="noopener noreferrer" rel="noopener noreferrer"
style={{ display: 'flex', alignItems: 'center', opacity: 0.6 }} style={{ display: 'flex', alignItems: 'center', opacity: 0.6 }}
> >
<img src={socialIcon} alt="Social" style={{ width: 20, height: 20 }} /> <img src={socialIcon} alt="Social" style={{ width: 22, height: 22 }} />
</a> </a>
<button <button
onClick={() => navigate('/quickstart')} onClick={() => navigate('/quickstart')}

View file

@ -0,0 +1,374 @@
---
title: Architecture
sidebar:
order: 8
---
A walk-through of how `ocr review` actually works inside, from the moment
you press Enter to the JSON that lands in your terminal. The goal is to
give you enough mental model to debug behaviour, tune flags, and read
the source code with confidence.
## High-level pipeline
```mermaid
flowchart TD
A["<b>ocr review</b>"]
B["<b>bootstrap</b><br/><span style='font-size:0.85em'>Resolve LLM endpoint (config → env → rc files)<br/>Load template, tool registry, system rules</span>"]
C["<b>diff provider</b><br/><span style='font-size:0.85em'>git diff / ls-files / show — produce []model.Diff<br/>Modes: Workspace · Commit · Range</span>"]
D["<b>filter & rules</b><br/><span style='font-size:0.85em'>5-gate filter (preview.go) — drop binaries,<br/>excluded paths, unsupported extensions. Pick rule per file.</span>"]
E["<b>subtask dispatch</b><br/><span style='font-size:0.85em'>For every diff in parallel (concurrency=N):<br/>Plan phase (optional) → Main loop → Comments</span>"]
F["<b>output writer</b><br/><span style='font-size:0.85em'>Synchronous line-resolution & review-filter; renders text<br/>or JSON depending on --format / --audience.</span>"]
A --> B --> C --> D --> E --> F
```
The orchestration lives in the
[`internal/agent/`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/)
package, which spans four files: `agent.go` (main loop & dispatch),
`compression.go` (memory compression), `preview.go` (the file filter),
and `util.go` (helpers). Two entry points matter: `Agent.Run` (top of
pipeline) and `Agent.dispatchSubtasks` (per-file fan-out).
## The diff provider
`internal/diff/git.go` defines a `Provider` struct whose unexported
`mode` field (of type `Mode`, an `int` enum) selects one of three modes
that mirror the CLI flags:
| Mode | Triggered by | What it returns |
|---|---|---|
| `Workspace` | no flags | staged + unstaged + untracked changes |
| `Commit` | `--commit <sha>` / `-c <sha>` | the changes introduced by `<sha>` (via `git show <sha>`, equivalent to the `<sha>^..<sha>` diff) |
| `Range` | `--from <a> --to <b>` | `merge-base(a, b)..b` |
Each diff carries: old/new path, old/new hunks, insertion/deletion counts,
binary flag, and rename detection. `DiffContextLines` is fixed at **3**
the same default Git uses.
Untracked files are read from disk and treated as full-file additions so
they're reviewed pre-commit.
## The five-gate file filter
Once diffs are loaded, every file passes through
[`whyExcluded`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/preview.go).
The function returns one of:
```
binary — file is binary
user_exclude — matched a pattern in your `exclude` list
unsupported_ext — extension is not in supported_file_types.json
default_path — matched a built-in test-file exclude pattern
```
…or empty if the file is kept. `deleted` is **not** returned by
`whyExcluded`; it's computed afterwards in `Preview()` when a kept
file's diff reports `IsDeleted`. The gates run in this order:
1. `binary` — binary files are dropped first.
2. `user_exclude` — your project's `exclude` always wins.
3. `user_include` — if the filter has include patterns **and** the file
matches one, it's kept immediately (returns empty), bypassing the
`unsupported_ext` and `default_path` gates below.
4. `unsupported_ext` filters by extension allowlist.
5. `default_path` is the last gate: it matches built-in **test-file**
exclude patterns (`**/*_test.go`, `**/*.test.{js,jsx,ts,tsx}`,
`**/__tests__/**`, `**/*_test.py`, `**/*_spec.rb`, `**/*.test.ets`, …).
Every pattern is rooted with a `**/` prefix.
The noisy-directory filtering (`vendor/`, `node_modules/`, `target/`, …)
happens earlier, at the diff-provider level, via the
`providerDirIgnoreDirs` list in `internal/diff/git.go` — diffs for those
directories are parsed and then stripped out by `filterDiffs` before
they ever reach the per-file filter.
Run `ocr review --preview` to see the full filter result without spending
a token. See [Review Rules](../review-rules/#how-files-are-filtered) for
the full algorithm.
## Per-file subtask: plan + main
For every file that survives filtering, OCR fires a sub-agent. Each
sub-agent runs in its own goroutine, bounded by `--concurrency` (default
**8**), and has its own LLM message buffer.
A subtask has up to **two phases**:
### Phase 1 — Plan (optional)
```go
threshold := template.PlanModeLineThreshold // 50
changeLines := d.Insertions + d.Deletions
if changeLines < threshold { skip plan }
```
For small diffs the plan adds latency without value, so it's skipped
silently and the main loop runs straight away. For larger diffs OCR
makes a **single** `PLAN_TASK` LLM call — no `Tools` field is sent, so
the model cannot call tools during planning. The read-only tool subset
(`code_search`, `file_read_diff`, `file_find` — the three tools whose
`plan_task` flag is `true` in `tools.json`) is embedded as plain text
via the `{{plan_tools}}` placeholder (rendered by
`formatToolDefs`) so the model knows what's available later. The model
returns a checklist that becomes `{{plan_guidance}}`
in the main prompt.
### Phase 2 — Main loop
The main loop assembles the `MAIN_TASK` prompt and runs a tool-use
conversation with the model. The full tool set adds **`task_done`**,
**`code_comment`**, and **`file_read`** to the plan-phase tools — see
[Tools](../tools/) for the full catalogue.
```
loop up to MAX_TOOL_REQUEST_TIMES (default 30):
response = llm.complete(messages, tools)
if response.toolCalls is empty:
nudge model with "You did not successfully call any tools.
Please try again or use task_done if finished."
continue
for each call: execute → collect result
if any call was task_done: break
addNextMessage(...) # may trigger compression
```
The loop has five exit conditions:
1. `task_done` was called.
2. `MAX_TOOL_REQUEST_TIMES` ran out.
3. 3 consecutive rounds produced no valid tool results
(`maxConsecutiveEmptyRounds = 3`).
4. The context was cancelled.
5. `addNextMessage` returned false — compression couldn't bring the
message buffer back under the warning threshold.
In all cases collected `code_comment` calls become review comments.
## Memory compression
A long tool-use loop will eventually overflow the context window. OCR
manages this with a **three-zone partitioning** strategy that triggers
on a token budget defined in `MAX_TOKENS = 58888`:
| Threshold | Constant | Action |
|---|---|---|
| 60 % of MAX_TOKENS | `tokenSoftThreshold` | Kick off **async** background compression; current loop continues uninterrupted. |
| 80 % of MAX_TOKENS | `tokenWarningThreshold` | Run compression **synchronously** before sending the next request. |
### The three zones
```mermaid
flowchart LR
subgraph messages["messages"]
direction LR
F["<b>frozen</b><br/>first 2 msgs<br/>(system +<br/>initial user)"]
C["<b>compress</b><br/>summarized<br/>into one<br/>user msg"]
A["<b>active</b><br/>K most recent<br/>complete<br/>rounds"]
end
F --- C --- A
```
A "round" is one assistant message plus the tool result messages that
followed it. `partitionMessages` walks rounds from the end, keeping as
many as fit within `(0.80 × MAX_TOKENS) - reservedTokens`. Everything
older becomes the **compress zone**.
The compress zone is rendered as XML and fed to the model with the
`MEMORY_COMPRESSION_TASK` prompt; the returned summary is appended to
the original user message inside `<previous_review_summary>` tags.
After compression: `messages = frozen[2] + compressed_user_msg + active`.
```go
// compression.go
func (a *Agent) runCompression(ctx context.Context, msgs []llm.Message, filePath string) ([]llm.Message, error) {
part := partitionMessages(msgs, a.args.Template.MaxTokens, 0)
contextXML := buildMessageXML(msgs[part.frozenEnd:part.compressEnd])
// … call MEMORY_COMPRESSION_TASK …
rebuilt[1] = llm.NewTextMessage(role, currentText+
"\n\n<previous_review_summary>\n"+rawSummary+"\n</previous_review_summary>")
for i := part.compressEnd; i < len(msgs); i++ {
rebuilt = append(rebuilt, msgs[i])
}
return rebuilt, nil
}
```
### Async vs sync
The async path lets the main loop keep emitting tool calls while
compression runs in the background; when the next token check happens, a
ready summary is swapped in via `tryApplyPendingCompression`. If the
ratio crosses the warning threshold before the async job finishes, the
loop stalls and runs `runCompression` synchronously — guaranteeing the
next request always fits.
## Comment processing pipeline
Every `code_comment` tool call produces one or more raw comments. They
go through a **CommentWorkerPool** (a fixed-size goroutine pool) so the
main tool-use loop never blocks on post-processing:
1. **Line resolution** (in-worker) — `existing_code` is matched against
the diff using a sliding-window algorithm to compute precise
`start_line` / `end_line`. If matching fails, both default to `0` — a
`0` line range is the implicit signal for an "unanchored" comment the
user must locate manually (there is no stored flag; downstream
consumers check `start_line == 0`).
2. **Re-location task** *(optional fallback)* — when line resolution
fails on a non-trivial diff, OCR runs the `RE_LOCATION_TASK` prompt
asking the model to re-anchor the snippet. Useful for paraphrased
`existing_code` strings.
3. **Review filter** — after the main loop finishes (and the worker pool
drains), the `REVIEW_FILTER_TASK` LLM call inspects the collected
comments against the diff and removes ones that are provably
incorrect. Errors here are logged and ignored.
4. **Second line-resolution pass** — once `Agent.Run` returns, the
top-level command re-runs `diff.ResolveLineNumbers` over the full
comment set (see `cmd/opencodereview/review_cmd.go`) to catch
comments whose `existing_code` spans multiple files or was updated by
the re-location step.
5. **Render** — into text or JSON depending on `--format`.
## Token budget guards
Before the LLM is even called, OCR runs a fail-fast check:
```go
tokenLimit := MaxTokens * 4 / 5 // 80 %
if countMessagesTokens(messages) > tokenLimit {
record warning "token_threshold_exceeded"
return nil // skip this file
}
```
This catches monstrous diffs (auto-generated lock files, refactors
touching thousands of lines) before they cost a request. The skipped
file is reported as a non-fatal warning in stdout and added to the JSON
`warnings` array.
A second check runs in `filterLargeDiffs`: if the diff alone exceeds
80 % of `MAX_TOKENS` it's filtered out before the per-file dispatcher is
even spawned.
## The template & placeholders
`internal/config/template/task_template.json` holds **five prompts**:
| Key | Purpose |
|---|---|
| `PLAN_TASK` | Planning phase — produces a checklist. |
| `MAIN_TASK` | Main review loop — emits `code_comment` calls. |
| `MEMORY_COMPRESSION_TASK` | Summarises the compress zone. |
| `REVIEW_FILTER_TASK` | Post-loop pass that removes provably-incorrect comments. |
| `RE_LOCATION_TASK` | Re-anchors a comment whose `existing_code` couldn't be matched. |
Each prompt is a list of `{role, prompt_file}` references that point to
`.md` files in the template directory (e.g.
`{"role": "system", "prompt_file": "main_task_system.md"}`). At load
time `resolveConversation` reads those files into in-memory
`{role, content}` messages, and template placeholders are then resolved
per-file:
| Placeholder | Replaced with |
|---|---|
| `{{system_rule}}` | The rule body resolved from the four-layer chain. |
| `{{change_files}}` | Status + path of every other changed file in the PR. |
| `{{diff}}` | This file's diff (raw `git diff` output). |
| `{{current_file_path}}` | The new path of this file. |
| `{{plan_guidance}}` | Output of the plan phase, or removed when plan is skipped. |
| `{{plan_tools}}` | Plan-phase tool definitions as plain text (rendered by `formatToolDefs`), used in the `PLAN_TASK` system prompt. |
| `{{requirement_background}}` | The `--background` flag content. |
| `{{current_system_date_time}}` | Local timestamp for the run, formatted `YYYY-MM-DD HH:MM` (no seconds or timezone). |
| `{{context}}` | (compression only) the XML-rendered messages to summarise. |
| `{{path}}` | File path, used in `REVIEW_FILTER_TASK`. |
| `{{comments}}` | Accumulated comments (JSON), used in `REVIEW_FILTER_TASK`. |
The placeholder substitution lives in
[`agent.go`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/agent.go).
The template itself isn't a CLI override — to change prompts you edit
[`task_template.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/template/task_template.json)
and rebuild. The `--tools` flag is a *tool-registry* override (it
swaps the JSON consumed by `internal/config/toolsconfig`), not a
template override — see [Tools](../tools/#customizing-tools).
> **Placeholder syntax caveat.** All the placeholders above use
> double-brace `{{…}}` syntax *except* `RE_LOCATION_TASK`, which
> substitutes single-brace `{diff}`, `{existing_code}`, and
> `{suggestion_content}` (see `internal/diff/relocation.go`).
## Persistence
Every review is written to disk as JSONL:
```
~/.opencodereview/sessions/<encoded-repo-path>/<session-id>.jsonl
```
The repo path is **not** base64-encoded; `encodeRepoPath` (in
`internal/session/persist.go`) replaces `/` and `\` with `-` and `:` with
`_` so the path is filesystem-safe.
Each line is one event: prompt sent, LLM response, tool call, tool
result, comment emitted, etc. The Web UI (`ocr viewer`) reads these
files directly — there's no database, just append-only logs. See
[Session Viewer](../viewer/) for the UI tour and event schema.
## Telemetry
When telemetry is enabled the agent emits three pipeline-level spans
(`review.run` wrapping the whole job, `diff.parse` wrapping diff
loading, and one `subtask.execute.<file>` per reviewed file) plus a
short-lived `event.<name>` span at each decision point (`plan.skipped`,
`token.threshold.exceeded`, `subtask.error`, …). LLM round trips and
tool calls are recorded only as metrics — not as spans. Prompt and
response content is **never** attached to telemetry; the
`OCR_CONTENT_LOGGING` flag is plumbed but currently dead. See
[Telemetry](../telemetry/) for the full schema.
## What's *not* automated
A few decisions are deliberately manual:
- **Endpoint discovery has no fallback.** If your config + env + rc
files don't yield a complete `(URL, token, model)` triple, OCR exits
with a non-zero code rather than guessing.
- **Sub-agent failures are isolated, not retried.** One failing file
produces a warning; the rest continue. Retries belong in the wrapping
CI pipeline, not the agent.
- **No cross-file reasoning.** Every file is reviewed in its own LLM
conversation. Cross-file questions go through `file_read_diff` /
`code_search` tool calls, not shared context. Findings in those
*other* files are also off-limits as comment targets — the
`main_task` prompt instructs the model to use context tools for
understanding only, and to ignore issues that surface in files
outside the current diff.
These choices keep the run **deterministic per-file** and keep cost
predictable.
## Source-code map
If you want to read along:
| Concern | File |
|---|---|
| Top-level command dispatch | `cmd/opencodereview/main.go` |
| `review` flag parsing | `cmd/opencodereview/flags.go` |
| Agent orchestration & compression | `internal/agent/` (agent.go, compression.go, util.go) |
| File filter / preview | `internal/agent/preview.go` |
| Diff loading (Git modes) | `internal/diff/git.go` |
| Rule resolution chain | `internal/config/rules/system_rules.go` |
| Tool registry & impls | `internal/tool/` |
| LLM endpoint resolver | `internal/llm/resolver.go` |
| Session JSONL writer | `internal/session/persist.go` |
| Web viewer | `internal/viewer/server.go` |
See [Contributing](../contributing/) for build & test instructions.
## See Also
- [Tools](../tools/) — the six tools the agent loop calls.
- [Review Rules](../review-rules/) — how per-file rule text is resolved.
- [Session Viewer](../viewer/) — inspect the transcripts this pipeline writes.

View file

@ -0,0 +1,396 @@
---
title: CLI Reference
sidebar:
order: 6
---
The complete reference for every `ocr` subcommand, flag, and exit
behaviour.
## Global usage
```text
OpenCodeReview - AI-Powered Code Review CLI
Usage:
ocr [command]
Commands:
review, r Start a code review
rules Inspect and debug review rules
config Manage configuration settings
llm LLM utility commands
viewer Start the WebUI session viewer
version Show version information
Examples:
ocr review --from master --to dev Review diff range
ocr review --commit abc123 Review a single commit
ocr config provider Interactive provider setup
ocr config model Interactive model selection
ocr config set llm.model opus-4-6 Set a config value
ocr llm test Test LLM connectivity
ocr llm providers List built-in providers
ocr version Show version info
Use "ocr review -h" for more information about review.
Use "ocr rules -h" for more information about rules.
Use "ocr config" for more information about config.
Use "ocr llm" for more information about LLM utilities.
GitHub: https://github.com/alibaba/open-code-review
```
## Command summary
| Command | Alias | What it does |
|---|---|---|
| `ocr review` | `ocr r` | Run a code review and emit comments. |
| `ocr rules check <file>` | — | Show which rule applies to a given file path and where it came from. |
| `ocr config set <key> <value>` | — | Persist a config value to `~/.opencodereview/config.json`. |
| `ocr config unset custom_providers.<name>` | — | Delete a custom provider (clears active `provider`/`model` if it was active). |
| `ocr config provider` | — | Interactive provider-setup TUI. |
| `ocr config model` | — | Interactive model-selection TUI. |
| `ocr llm test` | — | Send a small chat request to verify the configured endpoint. |
| `ocr llm providers` | — | List all built-in LLM providers. |
| `ocr viewer` | — | Launch the local web UI for past review sessions (`localhost:5483`). |
| `ocr version` | — | Print version, commit, platform, build date, and GitHub URL. |
`ocr` and `ocr -h` print top-level usage. Each subcommand also accepts
`-h` / `--help`.
## `ocr review`
The main command. Resolves a Git diff, dispatches per-file sub-agents,
collects review comments, and prints them.
### Synopsis
```text
ocr review [flags]
ocr r [flags] (alias)
```
If no flags are passed, OCR runs in **workspace mode** — review of all
staged + unstaged + untracked changes in the current directory's repo.
### Flags
| Flag | Short | Default | Description |
|---|---|---|---|
| `--repo <path>` | — | current dir | Git repository root. |
| `--from <ref>` | — | — | Source ref to start the diff from (e.g., `main`). |
| `--to <ref>` | — | — | Target ref to end the diff at (e.g., `feature-branch`). When set, OCR computes `merge-base(from, to)..to`. |
| `--commit <sha>` | `-c` | — | Single commit to review (vs its parent). |
| `--preview` | `-p` | `false` | Run the filter pipeline but skip the LLM. Prints the file list and exclusion reasons. |
| `--format <fmt>` | `-f` | `text` | `text` (human-readable) or `json` (machine-readable comment array). |
| `--audience <who>` | — | `human` | `human` streams progress lines; `agent` quiets stdout and prints only the final summary / JSON. |
| `--background <text>` | `-b` | — | Optional requirement / business context injected into the plan + main prompts. |
| `--concurrency <n>` | — | `8` | Maximum number of files reviewed in parallel. |
| `--timeout <minutes>` | — | `10` | Per-file deadline. `0` disables the timeout. |
| `--rule <path>` | — | — | Path to a custom JSON review rule file. Overrides the project-level and global `rule.json`. |
| `--max-tools <n>` | — | template default | Max tool-call rounds per file. `0` uses the template default (`30`); values 19 are clamped up to `10`; any value `≥ 10` overrides the template default (even if smaller than `30`). |
| `--model <name>` | — | — | Override the resolved LLM model for this review (e.g., `claude-opus-4-6`). |
| `--max-git-procs <n>` | — | `16` | Maximum number of concurrent git subprocesses. |
| `--tools <path>` | — | embedded | Path to a custom JSON tool-config file. Overrides the embedded tool definitions. |
> Mode flags are mutually exclusive: pass either `--from`/`--to`, or
> `--commit`, or neither (workspace mode). Mixing them is a hard error.
### Modes
#### Workspace mode (default)
```bash
ocr review
```
OCR assembles the working-tree changes from two git commands:
- tracked changes via `git diff HEAD` (staged + unstaged combined against
`HEAD`; if that comes back empty, OCR falls back to `git diff --staged`)
- untracked files via `git ls-files --others --exclude-standard`, read
from disk and treated as full-file additions
This is what you usually want pre-commit. Stage selectively if you want
narrower scope.
#### Range mode
```bash
ocr review --from main --to feature-branch
```
OCR computes `merge-base(main, feature-branch)..feature-branch` so you only
see the diff *introduced by* the feature branch — not unrelated changes
that landed on `main` since branching.
#### Commit mode
```bash
ocr review --commit abc123
ocr review -c abc123
```
Reviews the diff produced by `git show abc123` (i.e., the changes that
single commit introduced).
### Output
#### Text (default, `--audience human`)
Progress lines stream as the review runs, followed by one block per
comment (a dim Unicode-rule header with `path:start-end`, the comment
body wrapped to 100 columns, and — when present — a colored inline diff
of the suggested replacement). A run summary lands on stdout at the end:
```
[ocr] 17 file(s) changed, reviewing 9 in /path/to/repo
[ocr] Skipping image.png — filtered by path/extension rules
[ocr] ▶ file_read "src/foo.go"
[ocr] ✔ file_read (12ms)
[ocr] Plan completed for src/foo.go
─── src/foo.go:42-47 ───
Concurrent map access without a lock — wrap with sync.RWMutex.
- m[k] = v
+ mu.Lock(); defer mu.Unlock(); m[k] = v
[ocr] Summary: 9 file(s) reviewed, 14 comment(s), ~21344 token(s) used (input: ~18012, output: ~3332), 1m12s elapsed
```
#### Text (agent, `--audience agent`)
Identical comment output, but progress lines are suppressed via an internal
quiet-able stdout writer ([`internal/stdout`](https://github.com/alibaba/open-code-review/blob/main/internal/stdout/stdout.go)).
Use this in CI / when piping into another agent.
#### JSON
```bash
ocr review --format json --audience agent
```
```json
{
"status": "success",
"summary": {
"files_reviewed": 9,
"comments": 1,
"total_tokens": 21344,
"input_tokens": 18012,
"output_tokens": 3332,
"elapsed": "1m12s"
},
"comments": [
{
"path": "src/foo.go",
"content": "Concurrent map access without a lock — wrap with sync.RWMutex.",
"start_line": 42,
"end_line": 47,
"existing_code": "m[k] = v",
"suggestion_code": "mu.Lock(); defer mu.Unlock(); m[k] = v",
"thinking": "Looking at line 42, the map …"
}
]
}
```
Top-level fields:
| Field | Notes |
|---|---|
| `status` | `success`, `completed_with_warnings`, `completed_with_errors`, or `skipped`. |
| `message` | Optional. Human-readable summary, e.g. `"No comments generated. Looks good to me."`. |
| `summary` | Optional. Run aggregates: `files_reviewed`, `comments`, `total_tokens`, `input_tokens`, `output_tokens`, `cache_read_tokens` (omitempty), `cache_write_tokens` (omitempty), `elapsed`. Omitted for `skipped` runs. |
| `comments` | Always present, possibly empty. Per-comment fields are the ones in the example above. |
| `warnings` | Optional. Present when one or more sub-agents failed; each entry describes the affected file and the error. |
When no files were eligible for review, JSON mode emits a `skipped`
envelope instead so callers can distinguish "no changes" from "no findings":
```json
{
"status": "skipped",
"message": "No supported files changed.",
"comments": []
}
```
### Exit codes
| Code | Meaning |
|---|---|
| `0` | Review completed (possibly with zero comments, possibly with non-fatal warnings). |
| `1` | Fatal error — bad flags, can't resolve LLM endpoint, all per-file sub-agents failed, etc. The error text is printed to stderr. |
Non-fatal warnings (a single sub-agent failed, a file exceeded the token
threshold, etc.) are printed inline; in JSON mode they're added to the
`warnings` array.
## `ocr rules`
Rule introspection. There is exactly one subcommand:
```text
ocr rules check [flags] <file-path>
Flags:
--repo <path> Git repository root (default: current dir)
--rule <path> Path to a custom rule JSON file
```
For the given file path, OCR:
1. Walks the four-layer rule chain (custom → project → global → system).
2. Picks the first match.
3. Prints the **source layer**, the **glob pattern** that matched, and the
resolved **rule text**.
```bash
$ ocr rules check src/main/java/com/example/Foo.java
File: src/main/java/com/example/Foo.java
Source: System built-in
Pattern: **/*.java
Rule:
────────────────────────────────────────
<contents of internal/config/rules/rule_docs/java.md>
────────────────────────────────────────
```
Useful for debugging "why isn't my custom rule firing?" — see
[Review Rules](../review-rules/) for the full priority story.
## `ocr config`
Persists keys to `~/.opencodereview/config.json` and offers interactive
setup TUIs. Four subcommands:
```text
ocr config set <key> <value>
ocr config unset custom_providers.<name> Delete a custom provider
ocr config provider Interactive provider setup
ocr config model Interactive model selection
```
- **`set`** — write a single config value non-interactively.
- **`unset`** — delete a custom provider. Only
`custom_providers.<name>` is supported. If the deleted provider was the
active one, `provider` and `model` are cleared (run `ocr config provider`
to pick a new one).
- **`provider`** — launch the interactive provider-setup TUI (no extra
arguments; use `ocr config set provider <name>` for non-interactive
setup).
- **`model`** — launch the interactive model-selection TUI (no extra
arguments; use `ocr config set model <name>` for non-interactive
setup).
See [Configuration](../configuration/) for the full key reference,
schemas, and examples.
## `ocr llm`
LLM utility commands. Two subcommands:
```text
ocr llm <sub-command>
Sub-commands:
test Send a test conversation to the configured LLM model
providers List all built-in LLM providers
```
### `ocr llm test`
```text
ocr llm test
```
Resolves the LLM endpoint exactly the way `ocr review` does, sends a single
canned chat request from
[`internal/config/testconnection/task.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/testconnection/task.json),
and prints:
```
Source: <which strategy was used>
URL: <endpoint URL>
Model: <effective model>
<the model's reply>
✓ Connection test successful
```
A non-zero exit means either the endpoint isn't fully configured or the
request failed (network / auth / model error). The error message tells you
which.
### `ocr llm providers`
```text
ocr llm providers
```
Lists every built-in LLM provider in a three-column table:
```
Built-in providers:
NAME PROTOCOL BASE URL
---- -------- --------
anthropic anthropic https://api.anthropic.com
```
Followed by a hint to configure one interactively with `ocr config
provider` or non-interactively with `ocr config set provider <name>`.
## `ocr viewer`
```text
ocr viewer [flags]
Flags:
--addr <address> listen address (default: localhost:5483)
Examples:
ocr viewer # start on default port
ocr viewer --addr :3000 # bind to all interfaces on port 3000
```
Starts an embedded HTTP server that reads
`~/.opencodereview/sessions/...` and renders past review sessions in a
browser-friendly UI. See [Session Viewer](../viewer/).
## `ocr version`
```text
ocr version
ocr --version
ocr -V
```
Prints the version stamped at build time, the short Git commit (when
present), the platform (`<GOOS>/<GOARCH>`), the build date (when present),
and the GitHub URL (`https://github.com/alibaba/open-code-review`).
## Tips & gotchas
- `--audience agent` does **not** imply `--format json`. They control
different things — quiet UI vs structured payload. Combine them when you
want both.
- `--background` is one of the highest-leverage flags for review quality —
always pass the requirement / PR description when invoking from another
agent.
- A file whose diff alone exceeds 80 % of `MAX_TOKENS` (`58888` by default)
is dropped before the LLM is called. This is logged but does not fail
the run.
- The plan phase is **automatically skipped** when changed lines for a file
fall below `PLAN_MODE_LINE_THRESHOLD` (`50`).
## See Also
- [QuickStart](../quickstart/) — install and run your first review.
- [Configuration](../configuration/) — env vars and config keys behind the flags.
- [Review Rules](../review-rules/) — the `--rule` flag and rule resolution.
- [Integrations](../integrations/) — calling `ocr review` from agents and CI.

View file

@ -0,0 +1,283 @@
---
title: Configuration
sidebar:
order: 5
---
## Endpoint resolution
When `ocr review` or `ocr llm test` runs, it tries four sources **in order**
and uses the first one that yields a complete `(URL, token, model)` triple:
| Priority | Source | What it reads |
|---|---|---|
| 1 | `~/.opencodereview/config.json` | If `provider` is set, resolves via the `providers`/`custom_providers` maps (provider-first; see [Built-in providers](#built-in-providers)). Only falls back to the legacy `llm` section when no provider is set. |
| 2 | OCR environment variables | `OCR_LLM_URL`, `OCR_LLM_TOKEN`, `OCR_LLM_MODEL`, `OCR_USE_ANTHROPIC`, `OCR_LLM_AUTH_HEADER`. |
| 3 | Claude Code environment variables | `ANTHROPIC_BASE_URL`, `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_MODEL`. |
| 4 | Shell rc files | `export ANTHROPIC_*=…` lines parsed out of `~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, `~/.profile`. |
For Claude Code-style sources, if `ANTHROPIC_BASE_URL` lacks a versioned
path (`/v1/...`), OCR appends `/v1/messages` automatically.
If no strategy yields a complete triple, OCR exits with:
```
no valid LLM endpoint configured; one of OCR_LLM_URL/OCR_LLM_TOKEN/OCR_LLM_MODEL,
~/.opencodereview/config.json, or ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/
ANTHROPIC_MODEL must be set
```
> Resolution stops at the first source that **errors**, not just the first
> that's empty. In particular, if `provider` is set in `config.json` but the
> entry is misconfigured (unknown provider name, missing `api_key` with no
> env-var fallback, missing `model`, a custom provider lacking `url`/`protocol`),
> OCR exits with that error and does **not** fall through to the OCR env,
> Claude Code, or rc-file sources. To switch to env-based config, unset the
> `provider` key first.
> Source priority means the **environment overrides nothing** when the
> config file is fully populated. To force the environment to win, either
> delete the relevant `llm.*` keys from `~/.opencodereview/config.json` or
> use `ocr config set` to switch to the new values.
## `ocr config set` — managing `~/.opencodereview/config.json`
```bash
ocr config set <key> <value>
```
`config set` mutates the file via key/value pairs with schema-aware
parsing. The interactive TUI commands `ocr config provider` and
`ocr config model` also write to the same file (see
[Interactive setup](#interactive-setup--ocr-config-provider--ocr-config-model)).
Recognised keys:
| Key | Type | Notes |
|---|---|---|
| `provider` | string | Set the active provider (built-in name or custom). Switching provider clears the model. |
| `model` | string | Set the model for the active provider (stored under the provider entry, or top-level `model` if no provider is set). |
| `providers.<name>.<field>` | varies | Per-provider fields for built-in providers: `api_key`, `url`, `protocol`, `model`, `models`, `auth_header`, `extra_body`. |
| `custom_providers.<name>.<field>` | varies | Same fields as above, for custom (non-built-in) providers. Custom providers must set at least `url` and `protocol`. |
| `llm.url` | string | Endpoint URL. For Anthropic, full Messages URL like `https://api.anthropic.com/v1/messages`. For OpenAI-compatible, the chat-completions URL. |
| `llm.auth_token` | string | API key. Sent as `Authorization: Bearer …` (OpenAI) or, for the legacy Anthropic path, `Authorization: Bearer …` by default (the preset `anthropic` provider defaults to `x-api-key` instead). Use `x-api-key` only by explicitly setting `llm.auth_header`. |
| `llm.auth_header` | string | Auth header name (`x-api-key`, `authorization`, or `bearer`). Anthropic-only; required for some Anthropic setups that need `x-api-key`. |
| `llm.model` | string | Model name. A `[<digits>m]` suffix is stripped automatically. |
| `llm.use_anthropic` | boolean | `true` (default) → Anthropic Messages protocol. `false` → OpenAI Chat Completions. |
| `llm.extra_body` | JSON object | Vendor-specific request fields merged into every chat request body. Example: `'{"thinking":{"type":"disabled"}}'`. |
| `language` | string | Forwarded into a directive appended to the system prompt; defaults to `English` when unset. See [Choosing a language](#choosing-a-language). |
| `telemetry.enabled` | boolean | Master switch for OpenTelemetry export. Off by default. |
| `telemetry.exporter` | string | `console` or `otlp`. |
| `telemetry.otlp_endpoint` | string | OTLP collector address (e.g., `localhost:4317`). |
| `telemetry.content_logging` | boolean | Include LLM prompts / responses in exported event data. |
Examples:
```bash
ocr config set llm.url https://api.anthropic.com/v1/messages
ocr config set llm.auth_token sk-ant-xxxxxxxxxx
ocr config set llm.model claude-opus-4-6
ocr config set llm.use_anthropic true
ocr config set llm.extra_body '{"thinking":{"type":"disabled"}}'
ocr config set language English
ocr config set telemetry.enabled true
ocr config set telemetry.exporter otlp
ocr config set telemetry.otlp_endpoint localhost:4317
# Provider-based setup (recommended)
ocr config set provider anthropic
ocr config set model claude-opus-4-6
ocr config set providers.anthropic.api_key "$ANTHROPIC_API_KEY"
# Custom (non-built-in) provider
ocr config set provider my-gateway
ocr config set custom_providers.my-gateway.url https://gateway.internal.com/v1
ocr config set custom_providers.my-gateway.protocol openai
ocr config set custom_providers.my-gateway.model llama-3-70b
ocr config set custom_providers.my-gateway.api_key "$MY_API_KEY"
```
Booleans accept anything Go's `strconv.ParseBool` accepts (`true`, `false`,
`1`, `0`, `t`, `f`, …). `llm.extra_body` must be valid JSON.
## File schema reference
After the commands above, `~/.opencodereview/config.json` looks like:
```json
{
"llm": {
"url": "https://api.anthropic.com/v1/messages",
"auth_token": "sk-ant-xxxxxxxxxx",
"auth_header": "x-api-key",
"model": "claude-opus-4-6",
"use_anthropic": true,
"extra_body": {
"thinking": { "type": "disabled" }
}
},
"language": "English",
"telemetry": {
"enabled": true,
"exporter": "otlp",
"otlp_endpoint": "localhost:4317"
}
}
```
The provider-based form uses `provider`, `model`, `providers`, and
`custom_providers` instead of the legacy `llm` block:
```json
{
"provider": "anthropic",
"model": "claude-opus-4-6",
"providers": {
"anthropic": {
"api_key": "sk-ant-xxxxxxxxxx",
"model": "claude-opus-4-6"
}
},
"custom_providers": {
"my-gateway": {
"url": "https://gateway.internal.com/v1",
"protocol": "openai",
"model": "llama-3-70b",
"models": ["llama-3-70b", "llama-3-8b"],
"api_key": "gw-xxxxxxxxxx",
"auth_header": "authorization"
}
},
"language": "English"
}
```
When `provider` is set, the `providers`/`custom_providers` maps drive
resolution; the legacy `llm` section is ignored for that config.
You can edit this file by hand if you prefer, but `ocr config set` will
remarshal with `" "` indent on the next write.
## Interactive setup — `ocr config provider` / `ocr config model`
For provider and model selection without typing keys, OCR ships two
interactive Bubble Tea TUIs that also mutate `~/.opencodereview/config.json`.
```bash
ocr config provider
ocr config model
```
- `ocr config provider` — Interactive TUI for selecting a built-in or custom
provider, then entering URL / protocol / API key / model. Saves the choice
to config and runs `ocr llm test` automatically to verify the endpoint.
For built-in providers, the API key may be read from the provider's env var
(see [Built-in providers](#built-in-providers)) when not entered directly.
Selecting a manual configuration populates the legacy `llm.*` block instead.
- `ocr config model` — Interactive TUI for selecting a model from the current
provider's preset list, plus any user-added models stored under
`providers.<name>.models` / `custom_providers.<name>.models`. Requires a
provider to be set first (`ocr config provider`).
## Built-in providers
The following providers ship with OCR. Each has a preset `BaseURL`,
`Protocol`, and (where applicable) an API key environment variable used as a
fallback when `providers.<name>.api_key` is unset.
| Name | Protocol | Base URL | API key env var |
|---|---|---|---|
| `anthropic` | anthropic | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` |
| `openai` | openai | `https://api.openai.com/v1` | `OPENAI_API_KEY` |
| `dashscope` | openai | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_API_KEY` |
| `dashscope-tokenplan` | openai | `https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_TOKENPLAN_KEY` |
| `volcengine` | openai | `https://ark.cn-beijing.volces.com/api/v3` | `ARK_API_KEY` |
| `deepseek` | openai | `https://api.deepseek.com` | `DEEPSEEK_API_KEY` |
| `tencent-tokenhub` | openai | `https://tokenhub.tencentmaas.com/v1` | `TENCENT_TOKENHUB_API_KEY` |
| `hy-tokenplan` | openai | `https://api.lkeap.cloud.tencent.com/plan/v3` | `TENCENT_HUNYUAN_TOKENPLAN_KEY` |
| `kimi` | openai | `https://api.moonshot.cn/v1` | `MOONSHOT_API_KEY` |
| `z-ai` | openai | `https://open.bigmodel.cn/api/paas/v4` | `Z_AI_API_KEY` |
| `mimo` | openai | `https://api.xiaomimimo.com/v1` | `MIMO_API_KEY` |
| `minimax` | openai | `https://api.minimaxi.com/v1` | `MINIMAX_API_KEY` |
| `baidu-qianfan` | openai | `https://qianfan.baidubce.com/v2` | `QIANFAN_API_KEY` |
Any other provider name is treated as custom and must be configured under
`custom_providers` with at least `url` and `protocol`.
## Environment variable reference
| Variable | Purpose |
|---|---|
| `OCR_LLM_URL` | Endpoint URL — same shape as `llm.url`. |
| `OCR_LLM_TOKEN` | API key — same as `llm.auth_token`. |
| `OCR_LLM_MODEL` | Model name. |
| `OCR_LLM_AUTH_HEADER` | Auth header name (`x-api-key`, `authorization`, or `bearer`). Anthropic-only; same as `llm.auth_header`. Defaults to `authorization` when unset. |
| `OCR_USE_ANTHROPIC` | Unset → Anthropic protocol (default). Set to `true` / `1` / `yes` (case-insensitive) → Anthropic. Set to anything else (`false`, `0`, `no`, typos, …) → OpenAI. |
| `ANTHROPIC_BASE_URL` | Claude Code-compatible base URL. |
| `ANTHROPIC_AUTH_TOKEN` | Claude Code-compatible API key. |
| `ANTHROPIC_MODEL` | Claude Code-compatible model. |
| `OCR_ENABLE_TELEMETRY` | `1` to enable telemetry from env. |
| `OTEL_SERVICE_NAME` | Override service name in spans/metrics. |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector address — also forces the exporter to `otlp`. |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP transport protocol (`grpc`, `http/protobuf`, or `http/json`). Defaults to `grpc`. |
| `OCR_CONTENT_LOGGING` | `1` to include prompts/responses in telemetry events. |
Per-provider API keys (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`,
`DASHSCOPE_API_KEY`, …) are used as a fallback when a built-in provider's
`api_key` field is unset. See the [Built-in providers](#built-in-providers)
table for each provider's env var name.
## Why `extra_body` exists
Some hosted providers add non-standard fields to the request body (for
example, Bedrock-style `thinking`, vendor-specific `temperature_strategy`,
streaming options). `llm.extra_body` is merged into every outgoing request,
so you can ship those fields without patching the source.
```bash
ocr config set llm.extra_body '{"thinking":{"type":"enabled","budget_tokens":2048}}'
```
## Choosing a language
The `language` key controls one thing only: a directive appended to every
system-role message in the review and `ocr llm test` prompts. The exact
string injected is:
```
\n\nAlways respond in <language>.
```
- *Unset* or empty — treated as `English`.
- `Chinese`, `English`, or any other string — passed through verbatim.
There is no language switching on built-in rule docs. The files embedded
under `internal/config/rules/rule_docs/` are loaded by fixed filename and
are mostly written in Chinese (with `default.md` as an English exception);
they appear in the prompt as-is regardless of the `language` setting. When
`language` is `English`, the prompt therefore contains an English directive
on top of mostly-Chinese rule text — strong models honour the directive and
produce English comments, weaker models may emit mixed output.
`language` has no environment-variable, CLI-flag, or per-project override —
the only place it can be set is the global `~/.opencodereview/config.json`,
via [`ocr config set`](#ocr-config-set--managing-opencodereviewconfigjson):
```bash
ocr config set language English
```
If you need fully English rule text, supply your own rules via `--rule`,
`<repo>/.opencodereview/rule.json`, or `~/.opencodereview/rule.json` (see
[Review Rules](../review-rules/#priority-chain)).
## Per-project vs. global config
The CLI itself is configured globally (`~/.opencodereview/config.json`) —
there is no project-local LLM config. **Review rules** *are* per-project,
however; see [Review Rules](../review-rules/#priority-chain).
## See Also
- [QuickStart](../quickstart/) — minimal setup and first review.
- [CLI Reference](../cli-reference/) — every flag the review command accepts.
- [Telemetry](../telemetry/) — how to wire up OTLP / console exporters.

View file

@ -0,0 +1,220 @@
---
title: Contributing
sidebar:
order: 13
---
OCR is open source under the Apache-2.0 license. Bug reports, doc fixes,
and code contributions are all welcome. This page is a quick reference;
the canonical version lives in
[`CONTRIBUTING.md`](https://github.com/alibaba/open-code-review/blob/main/CONTRIBUTING.md).
## Ways to contribute
You don't have to write Go to be useful:
- **Bug reports** — open a [GitHub issue](https://github.com/alibaba/open-code-review/issues/new/choose)
with reproduction steps.
- **Feature requests** — start a thread in
[Discussions](https://github.com/alibaba/open-code-review/discussions/categories/ideas)
or open a feature-request issue.
- **Docs** — typo fixes, missing examples, broken links — these PRs
often merge fastest.
- **Reviewing other PRs** — comments from non-maintainers help reduce
reviewer load.
- **Code** — bug fixes, performance work, new features.
## Local development setup
### Prerequisites
- [Go ≥ 1.25](https://go.dev/dl/)
- [Git](https://git-scm.com/)
- [Make](https://www.gnu.org/software/make/)
### Getting the source
```bash
# Fork on GitHub, then:
git clone https://github.com/<your-username>/open-code-review.git
cd open-code-review
git remote add upstream https://github.com/alibaba/open-code-review.git
make build # writes dist/opencodereview
make test # LC_ALL=C go test -v -race -count=1 ./...
```
> The `upstream` remote is read-only. Push to `origin` (your fork) and
> open PRs from there.
### Running your local build
```bash
./dist/opencodereview review --preview
```
For convenience, drop a symlink at `~/bin/ocr-dev` pointing at
`dist/opencodereview` so you can invoke `ocr-dev` from any repo.
### Make targets
| Target | What it does |
|---|---|
| `make build` | Build for current platform → `dist/opencodereview`. |
| `make build-darwin-amd64` | Cross-compile for macOS Intel. |
| `make build-darwin-arm64` | Cross-compile for macOS Apple Silicon. |
| `make build-linux-amd64` | Cross-compile for Linux x86_64. |
| `make build-linux-arm64` | Cross-compile for Linux ARM64. |
| `make build-windows-amd64` | Cross-compile for Windows x86_64. |
| `make build-windows-arm64` | Cross-compile for Windows ARM64. |
| `make build-all` | All six cross-compiled binaries (linux/darwin/windows × amd64/arm64). |
| `make sha256sum` | Generate `sha256sum.txt` for build artifacts. |
| `make dist` | `clean → build-all → sha256sum`. What CI runs. |
| `make test` | Run tests with race detector. |
| `make clean` | Remove `dist/`. |
## Branching and commit conventions
### Branch prefixes
| Prefix | Purpose |
|---|---|
| `feat/` | New feature |
| `fix/` | Bug fix |
| `docs/` | Documentation only |
| `refactor/` | Refactor with no behaviour change |
| `test/` | Test-only changes |
| `chore/` | Build / CI / tooling |
```bash
git checkout main
git pull upstream main
git checkout -b feat/anthropic-streaming
```
### Commit messages
[Conventional Commits](https://www.conventionalcommits.org/) format:
```
<type>(<scope>): <short summary>
[optional body explaining the why]
```
Examples:
```
feat(agent): add support for custom tool definitions
fix(llm): handle timeout errors in Anthropic API calls
docs(readme): clarify endpoint resolution priority
refactor(viewer): extract task-card rendering into helper
```
The same format is used for **PR titles** so they show up cleanly in the
generated changelog.
## Project layout
```
open-code-review/
├── cmd/opencodereview/ # CLI entry point — flag parsing, dispatch
├── internal/
│ ├── agent/ # Review agent logic, sub-agent dispatch
│ ├── config/ # Template, rules, allowlist, embedded JSON
│ ├── diff/ # Git diff parsing, three modes
│ ├── gitcmd/ # Git subprocess runner
│ ├── llm/ # LLM client (Anthropic & OpenAI), endpoint resolver
│ ├── model/ # Data structs (LlmComment, Diff, …)
│ ├── pathutil/ # Path utilities
│ ├── release/ # Release-notes generation
│ ├── session/ # JSONL session writer
│ ├── stdout/ # Quiet-able stdout writer
│ ├── suggestdiff/ # Suggestion diff rendering
│ ├── telemetry/ # OpenTelemetry config + helpers
│ ├── tool/ # Tool registry + provider impls
│ └── viewer/ # Embedded HTTP UI
├── pages/ # WebUI marketing page (separate React app)
├── plugins/ # Claude Code slash command
├── extensions/ # Editor extensions (VS Code)
├── examples/ # CI recipes (GitHub Actions, GitLab CI)
├── skills/ # Agent SDK skill manifest
├── scripts/ # NPM postinstall + cross-build scripts
├── npm/ # Per-platform optional dependency packages
└── bin/ # NPM wrapper (Node)
```
Most contributions touch `internal/agent/`, `internal/tool/`, or
`internal/llm/`. The CLI surface in `cmd/opencodereview/` is
intentionally thin — flag parsing then dispatch to the agent package.
## Code quality checks
Before opening a PR:
```bash
go fmt ./...
go vet ./...
make test # race-enabled, runs in CI on every push
make build # smoke test the binary builds
```
CI runs the same set on every push; nothing surprising.
## Adding new tools
A tool has two parts:
1. **JSON definition** in
[`internal/config/toolsconfig/tools.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/toolsconfig/tools.json):
the name, description, and JSON-schema parameters the LLM sees.
2. **Go provider** registered in `internal/tool/definitions.go` with
the actual implementation.
Both have to be present for a new tool name to work. See [Tools](../tools/)
for the existing six and treat them as templates.
## Adding new rule patterns
Edit `internal/config/rules/system_rules.json` to map a new glob to a
rule doc, and add the corresponding markdown under
`internal/config/rules/rule_docs/`. Rule docs are single-file per
pattern (English). The `language` config only appends a directive to the
system prompt instructing the model to respond in that language; it does
not switch rule-doc files.
## PR process
1. **Open an issue first for big changes.** Aligning on the approach
beats discovering misalignment in code review.
2. **One logical change per PR.** If you have two unrelated fixes,
submit two PRs.
3. **Update tests.** Behaviour changes need test coverage — `make test`
has to pass.
4. **Update docs.** If the change affects flags, config keys, or the
review pipeline, update both this docs site (in [`docs/`](https://github.com/alibaba/open-code-review))
and any relevant inline help.
5. **Fill in the PR template.** A maintainer will review, usually
within a few business days.
## Contributor License Agreement (CLA)
The project requires the Alibaba Open Source CLA. The first time you
open a PR, a bot will post a link — sign electronically (takes a
minute). Subsequent PRs don't require re-signing.
## First contribution?
Look for issues labeled
[`good first issue`](https://github.com/alibaba/open-code-review/labels/good%20first%20issue)
or [`help wanted`](https://github.com/alibaba/open-code-review/labels/help%20wanted).
Most are small, self-contained, and have enough context in the issue
description to get started.
## See Also
- [Architecture](../architecture/) — the mental model you'll need
before touching `internal/agent/`.
- [Tools](../tools/) — what the existing tools look like.
- Full contributing guide:
[CONTRIBUTING.md](https://github.com/alibaba/open-code-review/blob/main/CONTRIBUTING.md)

View file

@ -0,0 +1,329 @@
---
title: FAQ
sidebar:
order: 14
---
Common errors, surprises, and "is this supposed to do that?" questions.
If your problem isn't here, open a
[GitHub issue](https://github.com/alibaba/open-code-review/issues) with
the steps you ran and the full output.
## Configuration & startup
### `no valid LLM endpoint configured`
```
no valid LLM endpoint configured; one of OCR_LLM_URL/OCR_LLM_TOKEN/OCR_LLM_MODEL,
~/.opencodereview/config.json, or ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/
ANTHROPIC_MODEL must be set
```
OCR ran the four-source resolution chain ([Configuration](../configuration/#endpoint-resolution))
and didn't find a complete `(URL, token, model)` triple. Either:
- Run `ocr config set llm.url …` / `llm.auth_token …` / `llm.model …`
to populate `~/.opencodereview/config.json`, **or**
- Export `OCR_LLM_URL` / `OCR_LLM_TOKEN` / `OCR_LLM_MODEL`, **or**
- Export `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` /
`ANTHROPIC_MODEL` if you already use Claude Code.
Then `ocr llm test` to verify connectivity before retrying the review.
### `ocr llm test` shows the wrong source
OCR uses the **first** complete triple, not the last. So if your
config file has all three llm.* keys, env vars are ignored. To make
env wins, either delete the config keys (`rm` the file or unset by
hand) or use `ocr config set` to switch to the new values.
### 401 / 403 from `ocr llm test`
The token is missing scope, expired, or wrong vendor. Anthropic and
OpenAI use different auth headers and different URL shapes — make sure
`llm.use_anthropic` matches the URL you're pointing at:
- Anthropic: URL ends `/v1/messages`, `use_anthropic=true`.
- OpenAI / OpenAI-compatible: URL ends `/v1/chat/completions`,
`use_anthropic=false`.
### `not a git repository`
`ocr review` runs `git diff` (and `git ls-files` for untracked files)
against the current directory. If you're not inside a Git working tree,
it exits early. Either `cd` into a repo, or pass `--repo /path/to/repo`.
## Filtering & rules
### My file isn't being reviewed
Run `ocr review --preview` (no LLM cost). The output lists every
candidate file with the **reason** it was kept or dropped:
```
src/foo.go modified
src/foo_test.go modified (excluded: user_exclude)
node_modules/lib.js added (excluded: default_path)
imgs/logo.png binary (excluded: unsupported_ext)
```
The five exclusion reasons map to gates in the
[file filter](../review-rules/#how-files-are-filtered):
| Reason | Fix |
|---|---|
| `binary` | Nothing to do — binary files have no reviewable text. |
| `user_exclude` | Remove the pattern from your `exclude` list. |
| `unsupported_ext` | Add the extension to your `include` list to bypass the allowlist gate. |
| `default_path` | Add the file to `include` — that overrides built-in test-file exclude patterns. |
| `deleted` | Nothing to do — there's no new content to review. |
### My custom rule isn't firing
Run `ocr rules check <file-path>`. It prints the **layer** and
**glob pattern** that matched, end-to-end:
```
File: src/api/UserHandler.go
Source: Project (.opencodereview/rule.json)
Pattern: src/api/**/*.go
Rule: …
```
If the layer is wrong (e.g., showing "System built-in" when you expected
your project rule), most likely the **declaration order** matters — the
first matching pattern wins. Move your more-specific rule earlier in the
`rules` array, or fix the glob.
### Brace expansion isn't working
`bmatcuk/doublestar/v4` supports `{ts,tsx}` braces. If they're not
matching, check for stray spaces — `{ts, tsx}` with a space silently
fails to match `tsx`.
## Reviews
### A file shows zero comments — was it actually reviewed?
Open the [Session Viewer](../viewer/) (`ocr viewer`), find the session,
and look at the file's `main_task` lane:
- Tool calls present + ends in `task_done` → reviewed cleanly.
- Tool calls present + ends mid-loop → look for an error card.
- No `main_task` cards at all → the file was filtered out before review;
see [Filtering & rules](#filtering--rules) above.
### Comments have `start_line: 0` and `end_line: 0`
OCR couldn't anchor the comment to a precise line in the diff. Two
common causes:
- The model paraphrased `existing_code` instead of copying it verbatim
from the diff. The model is told not to, but it sometimes does.
- The diff had unusual formatting (CRLF, mixed tabs/spaces) that broke
the sliding-window match.
The comment is still real — it just wasn't placed automatically. Most
agent integrations (the SKILL, the Claude Code plugin) read the
`existing_code` field and locate the spot in the file themselves.
### Token threshold exceeded
```
[ocr] WARNING: prompt tokens (94000) exceed 80% of max_tokens(58888) for src/big.sql
```
The initial prompt for that file (rule + diff + change-files list) was
already past 80 % of `MAX_TOKENS = 58888` before the model could even
respond. OCR skips the file and continues — you'll see this in
`warnings` in JSON mode too.
Mitigations:
- Add the file to your `exclude` list if it's autogenerated.
- Split a large refactor into smaller commits.
- Use `--commit` mode for a series of small commits rather than
reviewing them all at once via workspace mode.
### Plan phase took forever and the file is small
Run `ocr review --preview` first. If the file's `lines.changed` is
above `PLAN_MODE_LINE_THRESHOLD` (default **50**), the plan phase runs.
That's by design — large diffs benefit from a planning pass. To skip
it for a single review, run with a smaller diff, or temporarily edit
the embedded template (advanced; you'll need to override `--tools`).
### "Max tool requests reached"
```
[ocr] Max tool requests reached for src/foo.go.
```
The model spent 30 (`MAX_TOOL_REQUEST_TIMES`) tool-use rounds without
calling `task_done`. Comments emitted up to that point are still
collected and rendered. If this happens on most files, the issue is
usually one of:
- Model isn't great at following the "call `task_done` when finished"
instruction. Switch to a stronger model (e.g., Claude Opus).
- A tool keeps erroring and the model keeps retrying. Look at the
session JSONL — if the same tool result repeats, that's why.
- The file is genuinely large or context-heavy and 30 rounds isn't
enough. Raise or lower the cap with `--max-tools <n>` (e.g.,
`--max-tools 40` for more, `--max-tools 15` for fewer). Values 19
are clamped up to 10; `0` (the default) uses the template default of
30.
### Some sub-agents fail; the run still exits 0
By design. OCR isolates per-file failures so one bad file doesn't kill
a 20-file review. The aggregate exit code is `0` if *anything*
succeeded; only a fully-failed run (zero successful sub-agents) exits
non-zero. Check the `warnings` array in JSON mode or stderr in text
mode to see which files failed.
### CI run is much slower than local
Two usual suspects:
- **Model rate limits** — under throttling, the LLM client backs off
and retries. Lower `--concurrency` (e.g., to `4`) so you don't hit
the limit in the first place.
- **Cold cache** — if your provider supports prompt caching, the first
run after deploy doesn't benefit from it. Subsequent runs in the
same window are faster.
## Output & integration
### `--audience agent` still has progress lines
Make sure you're not seeing **stderr**. Progress messages occasionally
go to stderr (warnings, errors). The clean stdout that `--audience
agent` guarantees is *parser-friendly* — to suppress everything,
redirect: `ocr review --audience agent 2>/dev/null`.
### JSON output is `{ "files_reviewed": 0, "comments": [] }`
Workspace had no eligible files. This is intentional — the explicit
shape lets callers distinguish "nothing to review" from "no findings
found in the reviewed files". A normal review with zero comments
produces a regular empty array `[]` instead.
### Where do session JSONLs live?
```
~/.opencodereview/sessions/<path-encoded-repo-path>/<session-id>.jsonl
```
The repo path is encoded by replacing `/` and `\` with `-` and `:` with
`_` (e.g. `/Users/foo/my-repo``Users-foo-my-repo`). Browse sessions
with `ocr viewer`. Delete the directory to wipe history; OCR regenerates
the encoded path on the next run.
## Performance & cost
### How can I tell what tokens cost what?
Enable telemetry:
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter console
ocr review
```
LLM calls don't get their own spans — they're recorded as metrics
instead. Watch `ocr.llm.tokens_used` (counter, labelled `model` +
`type`), `ocr.llm.requests_total` (counter, labelled `model` +
`status`), and `ocr.llm.request_duration_seconds` (histogram, labelled
`model`). The console exporter prints these aggregates inline. For
dashboards, switch to the OTLP exporter and ship to your metrics
stack — see [Telemetry](../telemetry/).
### Why are my reviews so expensive?
Common levers:
- Plan phase is on for files ≥ 50 lines. It costs an extra LLM call
per file. Lowering the threshold reduces cost; raising it improves
small-PR speed.
- `MAX_TOOL_REQUEST_TIMES = 30` is generous. A model that uses every
round will produce a longer (more tokens) conversation than one that
finishes in 3 rounds. Stronger models tend to finish faster.
Conversely, if you raised it with `--max-tools` to fight "max tool
requests reached", expect cost per file to grow roughly linearly.
- Memory compression itself is an LLM call. Long subtasks pay for
compression rounds in addition to review rounds.
### How do I reduce LLM calls?
- Add an `include` list so OCR doesn't review files you don't care
about.
- Set `--concurrency` lower if your account has burst-mode pricing.
- Pass `--background` — better context up-front sometimes lets the
model finish without `file_read` / `code_search` round-trips.
## Privacy & security
### Does OCR send my code anywhere?
OCR sends your **diffs** (and optional read-tool snippets) to whatever
LLM endpoint you configured. Nothing else leaves your machine —
session JSONLs and rule files are local-only.
If telemetry is enabled, the `content_logging` flag is plumbed through
the config layer but currently gates **no** code path — prompt and
response content is never exported to your collector regardless of the
flag's value. Treat it as reserved. Leave it `false` in production. See
[Telemetry](../telemetry/#content-logging) for details.
### Can I redact secrets before they're sent to the LLM?
Not built-in. The recommended workflow:
1. Don't commit secrets to your repo (the usual rule).
2. Add files known to contain hash material to `exclude`.
3. Use `git diff --no-textconv` filters or pre-commit redaction to keep
secrets out of diffs.
A "redaction rule" feature is on the roadmap; track
[the issue tracker](https://github.com/alibaba/open-code-review/issues).
## Misc
### Where's the changelog?
[GitHub Releases](https://github.com/alibaba/open-code-review/releases)
— each release has notes generated from Conventional Commits.
### Does OCR support non-Git VCS?
No. The diff providers shell out to `git`. SVN / Mercurial / etc. would
need new providers; an issue for Hg support is open
[here](https://github.com/alibaba/open-code-review/issues).
### Why is the binary called `opencodereview` but the CLI is `ocr`?
The static binary published in releases is named after the project
(`opencodereview`); the NPM wrapper installs it as `ocr` for
ergonomics. If you build from source you get `dist/opencodereview`
copy it to `ocr` on your `$PATH`.
### How do I uninstall?
```bash
npm uninstall -g @alibaba-group/open-code-review # NPM install
sudo rm /usr/local/bin/ocr # binary install
rm -rf ~/.opencodereview # all state
```
OCR doesn't write outside `~/.opencodereview` (apart from the binary
download via NPM), so removing that directory wipes history, config,
and per-user rules.
## See Also
- [Configuration](../configuration/) — LLM endpoint resolution and config keys.
- [Review Rules](../review-rules/) — the file filter and rule resolution chain.
- [Session Viewer](../viewer/) — inspect past review sessions.
- [Telemetry](../telemetry/) — token usage and LLM metrics.

View file

@ -0,0 +1,183 @@
---
title: Installation
sidebar:
order: 4
---
There are four supported ways to install the `ocr` CLI. They all produce
the same binary — pick whichever fits your environment.
## NPM (recommended)
```bash
npm install -g @alibaba-group/open-code-review
```
The NPM package ships a small wrapper script (`bin/ocr.js`) plus a
[postinstall hook](https://github.com/alibaba/open-code-review/blob/main/scripts/install.js)
that:
1. Detects your platform (`darwin-amd64`, `darwin-arm64`, `linux-amd64`,
`linux-arm64`, `windows-amd64`, `windows-arm64`).
2. Downloads the matching binary from GitHub Releases.
3. Verifies it (when checksum data is present) and places it next to the
wrapper.
If a platform-specific npm package (e.g. `@alibaba-group/ocr-darwin-arm64`)
is installed as an optional dependency, the binary is used directly and the
download is skipped.
When you run `ocr`, the wrapper just `exec`s the downloaded binary, so the
overhead is effectively zero after first run.
### Updating
```bash
npm update -g @alibaba-group/open-code-review
# or pin a specific version:
npm install -g @alibaba-group/open-code-review@<version>
```
### Uninstalling
```bash
npm uninstall -g @alibaba-group/open-code-review
```
## GitHub Release binary
If you don't want Node.js, grab the static binary directly from the
[releases page](https://github.com/alibaba/open-code-review/releases):
```bash
# macOS (Apple Silicon)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# macOS (Intel)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux x86_64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux ARM64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Windows (AMD64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-amd64.exe
# Windows (ARM64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-arm64.exe
```
Each release also publishes `sha256sum.txt` next to the binaries so you can
verify integrity:
```bash
curl -LO https://github.com/alibaba/open-code-review/releases/latest/download/sha256sum.txt
shasum -a 256 -c sha256sum.txt --ignore-missing
```
## Install script (curl | sh)
A convenience installer that wraps the GitHub Release binary download
(with checksum verification) — handy for CI base images and headless
machines:
```bash
curl -fsSL https://raw.githubusercontent.com/alibaba/open-code-review/main/install.sh | sh
```
It honours two environment variables:
| Variable | Default | Purpose |
|---|---|---|
| `OCR_INSTALL_DIR` | `/usr/local/bin` | Where to place the `ocr` binary. |
| `OCR_VERSION` | latest release | Pin a specific release tag (e.g. `v1.2.3`). |
The script supports `darwin` and `linux` on `amd64` / `arm64`; for
Windows, use the [GitHub Release binary](#github-release-binary) or
[NPM](#npm-recommended) path instead.
## Build from source
You only need this path if you're hacking on OCR or running on a platform
without a pre-built binary.
### Prerequisites
- [Go ≥ 1.25](https://go.dev/dl/)
- [Git](https://git-scm.com/)
- [Make](https://www.gnu.org/software/make/)
### Build
```bash
git clone https://github.com/alibaba/open-code-review.git
cd open-code-review
make build # writes dist/opencodereview
sudo cp dist/opencodereview /usr/local/bin/ocr
```
### Build for another platform
```bash
make build-linux-amd64
make build-linux-arm64
make build-darwin-amd64
make build-darwin-arm64
make build-windows-amd64 # Windows (x86_64)
make build-windows-arm64 # Windows (ARM64)
make build-all # all six at once
make sha256sum # also produce sha256sum.txt
```
`make dist` runs `clean → build-all → sha256sum` and writes a `VERSION`
file alongside the binaries — that's exactly what the release pipeline
runs.
### Run tests
```bash
make test # LC_ALL=C go test -v -race -count=1 ./...
```
## Verifying the install
Wherever you got the binary from:
```bash
ocr version # prints version + git commit + build date
ocr --help # top-level usage
ocr review --help # full review-command flag list
```
If you see a "command not found" error, double-check that the install
location is on your `$PATH`:
```bash
which ocr
echo $PATH
```
## Where OCR stores state
| Path | What it holds |
|---|---|
| `~/.opencodereview/config.json` | LLM endpoint, language, telemetry config (managed by `ocr config set`). |
| `~/.opencodereview/rule.json` | Optional global review rules. |
| `~/.opencodereview/sessions/<encoded-repo-path>/<session-id>.jsonl` | Streaming JSONL transcript of every review session, used by `ocr viewer`. |
| `~/.opencodereview/{last-update-check,update.lock,update-available}` | NPM wrapper's background update-check state. The wrapper polls for a newer release (every ~18 min by default) and prints an upgrade hint. Disable with `OCR_NO_UPDATE=1`, or tune the interval with `OCR_UPDATE_INTERVAL` (seconds). Not written by the static binary. |
| `<repo>/.opencodereview/rule.json` | Optional per-project review rules — safe to commit. |
OCR never writes outside `~/.opencodereview/` (besides the transient binary
download via NPM). Removing the directory is a clean uninstall.
## See Also
- [QuickStart](../quickstart/) — configure an LLM and run your first review.
- [Configuration](../configuration/) — every env var and config key OCR honors.
- [Contributing](../contributing/) — build from source, run tests, and hack on OCR.

View file

@ -0,0 +1,66 @@
---
title: Integrations
sidebar:
order: 12
---
OCR is a CLI; it composes with anything that can spawn a process. This
section covers the first-class ways to wire it into agentic workflows
and CI, with one page per integration method.
## Why these particular integrations?
OCR's `--audience agent` mode is purpose-built for being driven by
another agent: stdout carries only the JSON / final summary, no
progress UI. That makes three composition patterns natural:
1. **Agent skill** — register OCR as a skill the calling agent can
invoke (e.g., the Anthropic Agent SDK).
2. **Command (Claude Code plugin)** — install the bundled command so
`/open-code-review:review` runs `ocr review` end-to-end. Also works
in any other agent that supports a Claude-Code-style command
convention.
3. **Direct subprocess** — any framework that can call `subprocess.run`
(LangChain tool, custom shell, CI step) just shells out.
You can mix and match. The skill and plugin both end up calling the
same binary.
## Pick a pattern
| Method | Best when | Page |
|---|---|---|
| Agent skill | You're building on the Anthropic Agent SDK or another framework that consumes a `SKILL.md`. | [Agent Skill](agent-skill/) |
| Command (Claude Code plugin) | You use Claude Code (or any agent with a Claude-Code-style command convention) and want `/open-code-review:review` to do the right thing. | [CommandClaude Code Plugin](claude-code/) |
| Direct subprocess | You need to call OCR from a custom script, LangChain tool, or non-Anthropic agent. | [Direct Subprocess](subprocess/) |
| CI/CD | You want OCR to run on every PR or pre-commit. | [CI/CD](ci/) |
## What about MCP?
OCR doesn't expose a Model Context Protocol server today. The intended
integration surface is "agent calls CLI", which is simpler and avoids
the long-running-process issues an MCP server would introduce. If your
agent platform requires MCP specifically, wrap the CLI with a thin
shim — a 30-line Node script that exposes a single `review` tool is
enough.
## Tips that apply to every pattern
- **Always pass `--audience agent`** when the caller is non-human.
Otherwise progress lines pollute the parsed output.
- **Always pass `--background`** when you have PR / requirement
context. Quality gain is large, cost is one tool argument.
- **Set `--concurrency`** lower in CI (`--concurrency 4`) to stay below
vendor rate limits. Default is 8.
- **Prefer `--from origin/main --to HEAD`** in CI over `--commit HEAD`
— the merge-base computation excludes unrelated changes that landed
on `main` since the branch was cut.
- **Keep `OCR_LLM_TOKEN` out of stdout/logs.** OCR doesn't print it,
but a misconfigured shell may. Use CI secret masking.
## See Also
- [CLI Reference](../cli-reference/) — every flag the review command
takes.
- [Configuration](../configuration/) — env vars and config keys.
- [QuickStart](../quickstart/) — minimal setup for a first review.

View file

@ -0,0 +1,119 @@
---
title: Agent Skill
sidebar:
order: 1
---
Register OCR as a callable skill so an agent framework can invoke it
with the right flags, prerequisite checks, and triage rubric — without
you re-deriving any of that on the calling side.
## What ships in the repo
The repo ships a SKILL manifest at
[`skills/open-code-review/SKILL.md`](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md).
It declares OCR as a callable skill, with prerequisite checks, an
invocation workflow, and a comment-triage rubric (High/Medium/Low).
## Install
### Option 1: `npx skills add` (recommended)
Run from inside the project where you want the skill available:
```bash
npx skills add alibaba/open-code-review --skill open-code-review
```
This pulls the manifest from the
[skills registry](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md)
and drops it into the project so any coding agent that respects the
skills convention picks it up on the next invocation. Re-run the
command to update the skill to the latest version.
> **Prerequisite:** the skill will install the `ocr` CLI itself the
> first time it runs (via `npm install -g @alibaba-group/open-code-review`)
> if the binary isn't on `PATH` — see [What the skill does](#what-the-skill-does)
> below. You **do** need an LLM configured up front; the skill cannot
> do that for you and will stop and ask. See [Configuration](../../configuration/).
### Option 2: Manual copy (system-wide)
If you'd rather install the skill globally instead of per-project, copy
the folder into your skills directory:
```bash
mkdir -p ~/.claude/skills
cp -R /path/to/open-code-review/skills/open-code-review ~/.claude/skills/
```
This makes the skill available to every project on the machine.
## What the skill does
The SKILL.md is a prompt: when the calling agent loads it, the agent
itself executes the steps. End-to-end, a single `/open-code-review`
(or equivalent) request unfolds like this:
1. **Prerequisite check.** Run `which ocr` to confirm the CLI is on
`PATH`, then `ocr llm test` to confirm an LLM is reachable.
2. **Auto-install the CLI if missing.** If `which ocr` reports
"NOT INSTALLED", the agent runs
`npm install -g @alibaba-group/open-code-review` and continues. No
user prompt — this is treated as a routine setup step.
3. **Stop and ask if no LLM is configured.** If `ocr llm test` fails,
the agent will *not* invent credentials. It shows the user the two
supported options (environment variables or `ocr config set …`) and
waits for the user to provide an API key.
4. **Extract business context.** Inspect the review target (commits,
branch, working copy) and synthesise a short `--background` string.
5. **Run the review.** Invoke
`ocr review --audience agent --background "…" [--commit | --from/--to]`,
picking flags based on whether the user asked to review the working
copy, a specific commit, or a branch range.
6. **Classify and report.** Group the JSON comments into **High** /
**Medium** / **Low** using the rubric in SKILL.md (bugs and
security issues are High; nitpicks and likely false positives are
silently dropped), then render a Markdown summary.
7. **Fix on request.** If the user said "review **and** fix" (or
similar), apply safe fixes to High/Medium items inline; otherwise
ask before touching the code.
The full prompt — including the exact triage rubric, output template,
and gotchas — lives in
[`skills/open-code-review/SKILL.md`](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md).
Edit your local copy if you want to tighten any of the above (e.g.,
flip the default to always-ask before fixing).
## Anthropic Agent SDK
Point your SDK init at the installed skill path:
```python
from anthropic_agent_sdk import Agent
agent = Agent(
skill_paths=["/path/to/open-code-review/skills/open-code-review"],
)
agent.run("Review my staged changes — focus on race conditions.")
```
The SDK loads the SKILL.md prompt and the agent executes the workflow
described in [What the skill does](#what-the-skill-does) — including
the `npm install` fallback and the prompt-for-credentials step if no
LLM is configured.
## Other agent frameworks
Any framework with a "register external skill" surface can ingest the
SKILL.md — it's just markdown with frontmatter. If your framework
expects a different schema, the markdown body is still useful as a
prompt template.
## See Also
- [CommandClaude Code Plugin](../claude-code/) — the
slash-command flavor of the same skill.
- [Direct Subprocess](../subprocess/) — bypass the manifest and call
the CLI yourself.

View file

@ -0,0 +1,459 @@
---
title: CI/CD
sidebar:
order: 4
---
Run OCR on every Pull Request or Merge Request. The upstream repo
ships two ready-made pipelines you copy and configure — one for
GitHub Actions, one for GitLab CI. Both are thin wrappers around the
core command from [Direct Subprocess](../subprocess/).
## How CI/CD integration works
Every recipe on this page follows the same pattern — the GitHub
Actions and GitLab CI sections below are just the concrete
implementations of it:
1. **Trigger on a PR / MR event.** A new pull request, an updated
merge request, or a manual `/open-code-review` comment kicks off
the job.
2. **Install `ocr`** in the runner, typically
`npm install -g @alibaba-group/open-code-review`. The runner is
ephemeral, so this happens on every run.
3. **Configure the LLM** from CI secrets via `ocr config set`
(endpoint, token, model). There is no persisted
`~/.opencodereview` to fall back on.
4. **Run the review in range mode** with machine-readable output, so
stdout is a clean JSON envelope:
```bash
ocr review \
--from "origin/<base-branch>" \
--to "origin/<head-branch>" \
--format json \
--audience agent
```
`--format json` gives a parseable payload; `--audience agent`
suppresses progress lines. See the
[JSON shape](../subprocess/#json-shape) for the envelope every
recipe consumes.
5. **Parse the JSON** and walk `comments[]`.
6. **Post comments back** to the PR / MR via the provider's review
API. Entries without valid line info (file-level findings) are
folded into a summary note instead of being posted inline; the
posting step also falls back to a plain summary comment if the
inline-batch API rejects the request.
Two kinds of credentials are always in play: the **LLM credentials**
OCR uses to generate findings, and a **PR/MR write token** the
posting step uses to comment back. The GitHub recipe gets the latter
for free via `GITHUB_TOKEN`; GitLab recommends an explicit
`GITLAB_API_TOKEN`, but the built-in `CI_JOB_TOKEN` is used as a
fallback for fork MRs (it can post discussions via `/discussions`) —
a dedicated token is recommended for reliability.
## GitHub Actions
The upstream workflow lives at
[`examples/github_actions/ocr-review.yml`](https://github.com/alibaba/open-code-review/blob/main/examples/github_actions/ocr-review.yml).
### What it does
- Triggers on `pull_request_target` (`opened`) **and** `issue_comment` events
whose body starts with `/open-code-review` or `@open-code-review`
the latter lets reviewers re-run OCR on demand by commenting on a PR.
(`pull_request_target` is used instead of `pull_request` so that
secrets are available even for PRs opened from forks; OCR only reads
the diff and does not execute code from the PR.)
- Installs OCR via `npm install -g @alibaba-group/open-code-review`,
writes config with `ocr config set`, then runs the core command in
branch-range mode.
- Parses the JSON envelope and posts each finding as an inline review
comment via the GitHub Pull Request Review API. Comments without
line info are folded into the summary body. If batch submission
fails, it falls back to posting comments one-by-one and surfaces
statistics in a summary comment.
### Install
Drop the workflow into your repo:
```bash
mkdir -p .github/workflows
curl -o .github/workflows/ocr-review.yml \
https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/github_actions/ocr-review.yml
```
### Required secrets
Set under **Settings → Secrets and variables → Actions**:
| Secret | Required | Description |
|---|---|---|
| `OCR_LLM_URL` | Yes | LLM API endpoint (e.g. `https://api.openai.com/v1/chat/completions`). |
| `OCR_LLM_AUTH_TOKEN` | Yes | Authentication token for the LLM API. This CI secret is passed to `ocr config set llm.auth_token`. (OCR's direct env var is `OCR_LLM_TOKEN`, not `OCR_LLM_AUTH_TOKEN`.) |
| `OCR_LLM_MODEL` | No | Model name. No default — must be set explicitly. |
| `OCR_LLM_USE_ANTHROPIC` | No | Set to `true` for Anthropic Claude models. |
`GITHUB_TOKEN` is auto-provided; the workflow declares
`pull-requests: write` so it can post review comments.
> The workflow also runs
> `ocr config set llm.extra_body '{"thinking": {"type": "disabled"}}'`
> at startup, which turns off thinking-mode requests for
> compatibility across LLM providers that don't support that field.
> Remove the line if your provider needs thinking-mode left on.
### Customization
All of the following are edits to the workflow file you just copied
(`.github/workflows/ocr-review.yml`).
#### Background context
`--background` is the single highest-leverage flag — see the
[tips that apply to every pattern](../#tips-that-apply-to-every-pattern).
Feed the PR title (works especially well when titles follow a
semantic convention like `feat(auth): add OAuth2 support`):
```yaml
- name: Run OCR review
run: |
ocr review \
--background "${{ github.event.pull_request.title }}" \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}" \
--format json --audience agent
```
#### Custom rules
Pass a project-specific rule file with `--rule`:
```yaml
- name: Run OCR review
run: |
ocr review --rule ./my-rules.json \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}"
```
See [Review Rules](../../review-rules/) for the schema.
#### Concurrency
The default is 8 parallel per-file sub-agents. Lower it on large PRs
to stay under your LLM provider's rate limits:
```yaml
- name: Run OCR review
run: |
ocr review --concurrency 5 \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}"
```
#### Trigger pattern
The default workflow triggers on PR **opened** and on PR comments
beginning with `/open-code-review` or `@open-code-review`. Two common
adjustments:
Run on more PR lifecycle events (e.g., re-review when new commits
are pushed):
```yaml
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
```
Use a different comment keyword:
```yaml
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& startsWith(github.event.comment.body, '/review'))
```
The `github.event.issue.pull_request` check ensures the comment is
on a PR, not a regular issue.
#### Pin the OCR version
The default workflow installs the latest published version. To pin:
```yaml
- name: Install OpenCodeReview
run: npm install -g @alibaba-group/open-code-review@1.0.0
```
#### Post under a GitHub App identity
By default, review comments come from `github-actions[bot]`. To post
under a branded bot like `OpenCodeReview Bot`, swap `GITHUB_TOKEN`
for a GitHub App installation token.
1. **Create the app** at *Settings → Developer settings → GitHub
Apps → New GitHub App*. Disable the webhook (not needed for this
use case). Under *Repository permissions* grant:
- **Pull requests**: Read and write
- **Contents**: Read-only (for fetching diffs)
- **Metadata**: Read-only (required)
2. **Generate a private key** from the app settings page and download
the `.pem` file. Note the **App ID** from the same page.
3. **Install the app** on the repositories you want OCR to review.
The Installation ID appears in the post-install URL, e.g.
`https://github.com/settings/installations/12345` → ID is `12345`.
4. **Add three secrets** under *Settings → Secrets and variables →
Actions*:
| Secret | Value |
|---|---|
| `GITHUB_APP_ID` | The App ID. |
| `GITHUB_APP_PRIVATE_KEY` | Full contents of the `.pem` file, including the `-----BEGIN RSA PRIVATE KEY-----` and `-----END RSA PRIVATE KEY-----` lines. |
| `GITHUB_APP_INSTALLATION_ID` | The Installation ID. |
5. **Mint a token and use it** in the comment-posting step:
```yaml
- name: Get GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GITHUB_APP_ID }}
private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
- name: Post review comments to PR
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
# ...existing post script...
```
Reviews will now appear as posted by your app's name instead of
`github-actions[bot]`.
### Troubleshooting
| Symptom | Cause / Fix |
|---|---|
| `Cannot find merge-base` | The checkout step used a shallow clone, but range-mode review needs full history. The upstream workflow sets `fetch-depth: 0` on `actions/checkout` — preserve that setting if you edit the file. |
| `Failed to parse OCR output` | `OCR_LLM_URL` or `OCR_LLM_AUTH_TOKEN` is missing or wrong. Re-check the values under *Settings → Secrets and variables → Actions*. |
| Review comments land on the wrong lines | Usually means the diff shifted between the moment the review started and when comments were posted. The posting script falls back to a plain issue comment in that case — no action needed. |
> **Note.** The `OCR_DEBUG` env var is **not currently implemented**
> in OCR — setting `OCR_DEBUG: "1"` has no effect. It's documented
> here in case it is wired up later. For verbose output today, inspect
> the raw review JSON and stderr that the workflow writes to
> `/tmp/ocr-result.json` and `/tmp/ocr-stderr.log` (see troubleshooting
> below), or run `ocr review` locally.
## GitLab CI
The upstream pipeline lives at
[`examples/gitlab_ci/.gitlab-ci.yml`](https://github.com/alibaba/open-code-review/blob/main/examples/gitlab_ci/.gitlab-ci.yml).
### What it does
- Triggers on `merge_requests` events (all MR events — creation,
updates, reopen).
- Runs in a `node:20` image, installs OCR, configures it via
`ocr config set`, then runs the core command in MR diff mode.
- Parses the JSON envelope with an inlined Python script and posts
each finding as a GitLab Discussion (inline on the diff), using
the MR's `versions` endpoint to compute correct `base_sha` /
`start_sha` / `head_sha` for accurate positioning. Falls back to
regular MR notes for any comment that can't be posted inline, and
closes with a summary note.
### Install
Drop the pipeline into your repo root:
```bash
curl -o .gitlab-ci.yml \
https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/gitlab_ci/.gitlab-ci.yml
```
If you already have a `.gitlab-ci.yml` and want to keep it, vendor
the recipe to a different path and pull it in with `include:`:
```yaml
include:
- local: 'ci/ocr-review.gitlab-ci.yml'
```
### Required CI/CD variables
Set under **Settings → CI/CD → Variables**:
| Variable | Required | Masked | Description |
|---|---|---|---|
| `OCR_LLM_URL` | Yes | No | LLM API endpoint URL. |
| `OCR_LLM_AUTH_TOKEN` | Yes | Yes | API authentication token. This CI variable is passed to `ocr config set llm.auth_token`. (OCR's direct env var is `OCR_LLM_TOKEN`, not `OCR_LLM_AUTH_TOKEN`.) |
| `OCR_LLM_MODEL` | No | No | Model name. No default — must be set explicitly. |
| `GITLAB_API_TOKEN` | No | Yes | Project / personal / group access token with `api` scope. Optional — the built-in `CI_JOB_TOKEN` is used as a fallback when this is absent (e.g. for fork MRs). A dedicated `GITLAB_API_TOKEN` is recommended for reliability. |
> GitLab rejects variables shorter than 8 characters, so
> `llm.use_anthropic` is hardcoded to `false` in the pipeline. To use
> Anthropic Claude models, edit the script directly.
> The pipeline also runs
> `ocr config set llm.extra_body '{"thinking": {"type": "disabled"}}'`
> at startup, which turns off thinking-mode requests for
> compatibility across LLM providers that don't support that field.
> Remove the line if your provider needs thinking-mode left on.
> **Quick bot-naming tip.** For Project Access Tokens and Group
> Access Tokens, the token's **name** is what appears next to MR
> discussions. Naming the token `OpenCodeReview Bot` is a fast way
> to brand the reviewer without setting up anything else — handy
> when you don't need the more durable service-account setup
> documented under [Post under a service account identity](#post-under-a-service-account-identity).
### Customization
All of the following are edits to the `.gitlab-ci.yml` you just
copied.
#### Background context
Pass the MR title to `--background` — especially useful when titles
follow a semantic convention like `feat(auth): add OAuth2 support`:
```yaml
script:
- |
ocr review \
--background "$CI_MERGE_REQUEST_TITLE" \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}" \
--format json --audience agent
```
#### Custom rules and concurrency
Same flags as the GitHub Actions recipe — pass `--rule` for a
project-specific rule file, and `--concurrency` to throttle parallel
sub-agents (default 8):
```yaml
script:
- |
ocr review --rule ./my-rules.json --concurrency 5 \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}"
```
See [Review Rules](../../review-rules/) for the rule schema.
#### Pin the OCR version
```yaml
script:
- npm install -g @alibaba-group/open-code-review@1.0.0
```
#### Avoid re-reviewing on every push
`only: [merge_requests]` triggers on **every** MR update, which can
burn a lot of LLM tokens on long-running MRs. GitLab has no native
"only on creation" event, so the recommended pattern is to detect
existing OCR notes before running the review and bail out if any are
found. Replace the `ocr review` invocation with a Python wrapper:
```python
import json, os, sys, urllib.request
GITLAB_URL = os.environ.get("CI_SERVER_URL", "https://gitlab.com")
PROJECT_ID = os.environ["CI_PROJECT_ID"]
MR_IID = os.environ["CI_MERGE_REQUEST_IID"]
API_TOKEN = os.environ["GITLAB_API_TOKEN"]
url = (
f"{GITLAB_URL}/api/v4/projects/{PROJECT_ID}"
f"/merge_requests/{MR_IID}/notes?per_page=100"
)
req = urllib.request.Request(url, headers={"PRIVATE-TOKEN": API_TOKEN})
with urllib.request.urlopen(req) as resp:
notes = json.loads(resp.read().decode())
if any("OpenCodeReview" in n.get("body", "") for n in notes):
print("OCR already reviewed this MR. Skipping to save tokens.")
sys.exit(0)
# ...otherwise call `ocr review ...` as usual and write the JSON to
# the file the posting step expects.
```
To force a re-review after this, delete the previous OCR notes from
the MR — the next pipeline run will see no OCR notes and proceed.
#### Self-hosted GitLab
No code change needed. The posting script reads `CI_SERVER_URL`
(which GitLab sets automatically on every runner), so it talks to
your own instance out of the box. Just make sure
`GITLAB_API_TOKEN` is issued by your self-hosted instance, not
`gitlab.com`.
#### Post under a service account identity
By default, review discussions appear under whichever user owns
`GITLAB_API_TOKEN`. Swap in a project-scoped service account for a
branded bot identity like `OpenCodeReview Bot`.
1. **Create the service account** at *Project → Settings → Service
Accounts → New service account*. The name you pick (e.g.
`OpenCodeReview Bot`) is what appears next to MR discussions.
2. **Invite it to the project** at *Settings → Members → Invite
member*. Search for the service-account name and assign
`Developer` or `Maintainer` — both have the permissions needed
to post discussions.
3. **Issue an access token** at *Settings → Service Accounts → (the
account) → Add new token*. Required scope: `api`. Copy the token
immediately — GitLab only shows it once.
4. **Swap the token value** at *Settings → CI/CD → Variables*
replace the existing `GITLAB_API_TOKEN` value with the service
account's token (keep the variable name the same).
Discussions are now posted under the service account name instead
of the user who originally created the token.
### Troubleshooting
| Symptom | Cause / Fix |
|---|---|
| `Cannot find merge-base` | The runner used a shallow clone. The upstream pipeline sets `GIT_DEPTH: 0` to force a full clone — preserve that setting if you edit the file. |
| `API error 403` when posting | `GITLAB_API_TOKEN` is missing the `api` scope, isn't a member of the project, or — on self-hosted — was issued by a different instance. Reissue with `api` scope and re-add it under *Settings → CI/CD → Variables*. |
| `Failed to parse OCR output` | `OCR_LLM_URL` or `OCR_LLM_AUTH_TOKEN` is wrong. Re-check the values under *Settings → CI/CD → Variables*. |
| Inline comments land on the wrong lines | GitLab requires exact SHA matching for inline discussions; the posting script fetches `versions` metadata to get the right `base_sha` / `start_sha` / `head_sha`. If a finding still can't be anchored, it falls back to a plain MR note. |
The pipeline writes raw review JSON to `/tmp/ocr-result.json` and
stderr to `/tmp/ocr-stderr.log`. Cat them in a debug step to inspect
what OCR returned:
```yaml
script:
- cat /tmp/ocr-result.json
- cat /tmp/ocr-stderr.log
```
## See Also
- [Direct Subprocess](../subprocess/) — the JSON shape both pipelines
consume, useful when writing your own CI script from scratch.
- [Configuration](../../configuration/) — every env var and config
key OCR honors.

View file

@ -0,0 +1,119 @@
---
title: CommandClaude Code Plugin
sidebar:
order: 2
---
Install the bundled command so OCR runs end-to-end inside
[Claude Code](https://docs.anthropic.com/en/docs/claude-code) — review
the diff, classify findings, and automatically apply fixes for the
ones worth adopting.
## What ships in the repo
The repo ships a Claude Code plugin under
[`plugins/open-code-review/`](https://github.com/alibaba/open-code-review/tree/main/plugins/open-code-review).
The command prompt itself lives at
[`plugins/open-code-review/commands/review.md`](https://github.com/alibaba/open-code-review/blob/main/plugins/open-code-review/commands/review.md)
and is the source of truth for the workflow described below.
## Install
### Option 1: Plugin marketplace (recommended)
Run these two commands **inside Claude Code**:
```bash
/plugin marketplace add alibaba/open-code-review
/plugin install open-code-review@open-code-review
```
This registers the `/open-code-review:review` slash command and keeps
it updateable through `/plugin`.
### Option 2: Copy the command file directly
If you'd rather skip the plugin marketplace, drop the command file
straight into `.claude/commands/`. This registers as `/open-code-review`
(without the `:review` suffix).
**Project-level** (commit alongside the repo so the team shares it):
```bash
mkdir -p .claude/commands
curl -o .claude/commands/open-code-review.md \
https://raw.githubusercontent.com/alibaba/open-code-review/main/plugins/open-code-review/commands/review.md
```
**User-level** (available in every project on the machine):
```bash
mkdir -p ~/.claude/commands
curl -o ~/.claude/commands/open-code-review.md \
https://raw.githubusercontent.com/alibaba/open-code-review/main/plugins/open-code-review/commands/review.md
```
### Other agents with command support
The command file is plain markdown with a single frontmatter field —
nothing about it is Claude-Code-specific. If your agent supports a
similar **command** convention (markdown prompts loaded as invokable
commands from a directory), the file-copy recipe above is the install
path: drop `open-code-review.md` into whichever directory your agent
reads commands from, and invoke it the way your agent invokes
commands. The prompt body is agent-agnostic — it just tells the model
which `ocr` flags to pick and how to triage the output.
> **Prerequisite:** the command will install the `ocr` CLI itself the
> first time it runs (via `npm install -g @alibaba-group/open-code-review`)
> if the binary isn't on `PATH`. You **do** need an LLM configured up
> front — the command will fail if `ocr llm test` can't reach one. See
> [Configuration](../../configuration/).
## Use
In Claude Code, invoke the command by name. Use `/open-code-review:review`
if you installed via the plugin marketplace, or `/open-code-review` if
you copied the file directly:
```
/open-code-review:review
/open-code-review:review review this PR against main
/open-code-review:review focus on race conditions in commit abc123
```
The prompt parses your request and picks the right `ocr review` flags:
no arguments → workspace mode (staged + unstaged + untracked), mention
of a commit → `--commit`, mention of a branch range → `--from` / `--to`.
You can also pass OCR flags through directly (e.g.
`/open-code-review:review --commit abc123` or `--from main --to feature`).
## What the command does
The command prompt is short — three steps:
1. **Run the review.** Invoke `ocr review --audience agent` with the
flags inferred from your request (plus an optional `--background`
when you've described requirement context). If the `ocr` binary
isn't on `PATH`, the command auto-installs it via
`npm i -g @alibaba-group/open-code-review` and continues. Output is
captured with a 5-minute timeout.
2. **Filter and evaluate.** Classify each comment as **High** /
**Medium** / **Low**. Low-confidence comments (likely false
positives, nitpicks, lacking context) are dropped silently; the
rest are displayed.
3. **Fix.** Automatically apply fixes for the High/Medium items worth
adopting. Unlike the [Agent Skill](../agent-skill/), this command
**auto-fixes by default** — it's the right surface for a "review
and clean up" workflow, not a "show me a diff" workflow.
If you want the command to ask before touching code, or to tighten the
triage rubric, edit your local copy of the prompt. Claude Code
re-reads commands on every invocation, so no restart is needed.
## See Also
- [Agent Skill](../agent-skill/) — the SDK-level equivalent; same
underlying CLI, different defaults (asks before fixing).
- [Direct Subprocess](../subprocess/) — bypass the slash command and
call the CLI yourself.

View file

@ -0,0 +1,171 @@
---
title: Direct Subprocess
sidebar:
order: 3
---
Shell out to `ocr` and parse the JSON. This is the lowest-level
integration path — every other method on this site ultimately reduces
to it. The [Agent Skill](../agent-skill/) and [Command](../claude-code/)
methods are prompt templates that tell a calling agent to do exactly
this; the [CI/CD](../ci/) recipes are GitHub Actions and GitLab CI
pipelines that do the same thing from a script — no orchestrating
agent, just subprocess invocation, JSON parsing, and posting comments
back to the PR / MR. Use this page directly when you're calling OCR
from a custom script, a LangChain tool, or any other framework that
isn't already covered.
## Bash
```bash
result=$(ocr review --format json --audience agent)
status=$(echo "$result" | jq -r '.status')
total=$(echo "$result" | jq '.comments | length')
echo "Status: $status — $total comments"
echo "$result" | jq -r '.comments[] | "\(.path):\(.start_line) — \(.content)"'
```
## Python
```python
import json, subprocess
proc = subprocess.run(
["ocr", "review", "--format", "json", "--audience", "agent",
"--from", "origin/main", "--to", "HEAD",
"--background", pr_description],
capture_output=True, text=True, check=True,
)
data = json.loads(proc.stdout)
for c in data["comments"]:
if c["start_line"] > 0:
post_line_comment(c["path"], c["start_line"], c["content"])
```
## JSON shape
OCR emits a single top-level **object** (not a bare array). Here is a
complete `success` envelope with one finding:
```json
{
"status": "success",
"summary": {
"files_reviewed": 1,
"comments": 1,
"total_tokens": 12770,
"input_tokens": 12450,
"output_tokens": 320,
"elapsed": "9s"
},
"comments": [
{
"path": "internal/cache/store.go",
"content": "Concurrent map access without a lock — wrap reads and writes with `sync.RWMutex` to avoid a race on the shared cache.",
"start_line": 42,
"end_line": 47,
"existing_code": "func (s *Store) Get(k string) string {\n return s.m[k]\n}",
"suggestion_code": "func (s *Store) Get(k string) string {\n s.mu.RLock()\n defer s.mu.RUnlock()\n return s.m[k]\n}",
"thinking": "The struct exposes `m map[string]string` without a guarding mutex, and Get/Set are called from concurrent request handlers."
}
]
}
```
### Top-level fields
| Field | Type | Always present | Notes |
|---|---|---|---|
| `status` | string | Yes | One of `success`, `completed_with_warnings`, `completed_with_errors`, `skipped`. |
| `message` | string | No | Short human-readable summary. Set on empty / skipped runs, e.g. `"No comments generated. Looks good to me."`. |
| `summary` | object | No | Run aggregates. Present on completed runs; omitted on `skipped`. Fields below. |
| `comments` | array | Yes | Possibly empty. Per-comment schema below. |
| `warnings` | array | No | Present only when one or more sub-agents failed or were skipped. Schema below. |
### Summary shape (`summary`)
| Field | Type | Notes |
|---|---|---|
| `files_reviewed` | int | Number of files that survived all filters and were sent to the model. |
| `comments` | int | Total comments emitted across all files (matches `comments.length`). |
| `total_tokens` | int | Sum of prompt + completion tokens across every LLM call in the run. |
| `input_tokens` | int | Prompt tokens (including cache-read tokens) across every LLM call. |
| `output_tokens` | int | Completion tokens (including cache-write tokens) across every LLM call. |
| `cache_read_tokens` | int | Total cache-read tokens across every LLM call. Omitted (`omitempty`) when zero. |
| `cache_write_tokens` | int | Total cache-write tokens across every LLM call. Omitted (`omitempty`) when zero. |
| `elapsed` | string | Wall-clock duration rounded to whole seconds, formatted by Go's `time.Duration.String()` (e.g. `"1m12s"`). |
### Per-comment fields (`comments[]`)
| Field | Type | Always present | Notes |
|---|---|---|---|
| `path` | string | Yes | Repo-relative file path. |
| `content` | string | Yes | The review comment, in Markdown. |
| `start_line` | int | Yes | First line of the affected range. A value `< 1` means the comment has no line anchor (file-level) — fold these into the summary instead of trying to post them inline. |
| `end_line` | int | Yes | Last line of the affected range. Equal to `start_line` for single-line comments. |
| `existing_code` | string | No | Original code snippet to be replaced. Omitted for advisory comments with no diff. |
| `suggestion_code` | string | No | Proposed replacement for `existing_code`. Always paired with `existing_code` when present. |
| `thinking` | string | No | Model's reasoning trail. Useful for triage / debugging; safe to drop before displaying to humans. |
### Warnings shape (`warnings[]`)
A run where some files were skipped or failed looks like this:
```json
{
"status": "completed_with_errors",
"message": "Some files could not be reviewed due to errors.",
"comments": [],
"warnings": [
{
"file": "src/very_long_file.go",
"message": "diff size exceeds 80% of MAX_TOKENS; skipped",
"type": "token_threshold_exceeded"
},
{
"file": "src/broken.py",
"message": "sub-agent failed: context deadline exceeded",
"type": "subtask_error"
}
]
}
```
| Field | Type | Notes |
|---|---|---|
| `file` | string | Repo-relative path of the file that triggered the warning. |
| `message` | string | Short human-readable description. |
| `type` | string | Stable kind for filtering. Currently emitted: `subtask_error` (a sub-agent run failed) and `token_threshold_exceeded` (diff too large for the model). |
When `warnings` contains at least one `subtask_error`, `status` is
`completed_with_errors`; otherwise it's `completed_with_warnings`.
### No severity / priority field
OCR does **not** emit a `severity` or `priority` field. The
High/Medium/Low triage you see in the [Agent Skill](../agent-skill/)
and [Command](../claude-code/) docs is added by the calling agent
after it receives the raw comments — don't try to `jq '.comments[].severity'`,
it won't exist.
## Empty-result handling
A workspace with **no eligible files** is reported via `status`, so
callers can distinguish "nothing changed" from "no findings":
```json
{
"status": "skipped",
"message": "No supported files changed.",
"comments": []
}
```
Always check `status == "skipped"` before declaring "all clean".
## See Also
- [CI/CD](../ci/) — ready-made GitHub Actions and pre-commit recipes
built on top of subprocess invocation.
- [Agent Skill](../agent-skill/) — when the caller is an Anthropic
SDK agent rather than a plain script.

View file

@ -0,0 +1,139 @@
---
title: Overview
sidebar:
order: 2
---
## What is Open Code Review?
Open Code Review (**OCR** for short, distinct from Optical Character
Recognition) is an AI-powered code review CLI distributed as the
[`@alibaba-group/open-code-review`](https://www.npmjs.com/package/@alibaba-group/open-code-review)
NPM package and as standalone Go binaries. The CLI binary is named `ocr`.
In a single command (`ocr review`) it:
1. Resolves a Git diff — workspace, branch range, or single commit.
2. Filters the changed files using both system defaults and any user rules.
3. Spawns one **per-file sub-agent** for each changed file, in parallel.
4. Each sub-agent runs an LLM tool-use loop, optionally preceded by a
**plan phase** for larger diffs.
5. The model calls `code_comment` to record findings, optionally `file_read`,
`code_search`, `file_find`, `file_read_diff` to gather context, and
`task_done` when finished.
6. OCR resolves each comment to exact line numbers, runs an optional
re-positioning pass for any comments that didn't match cleanly, and
prints (or JSON-emits) the final list.
## The problem with general-purpose agents
If you've used a general-purpose coding agent (Claude Code with a Skill,
Cursor, Cline, etc.) for code review, you've likely run into:
- **Incomplete coverage** — on larger changesets the agent quietly cuts
corners, reviewing only some files.
- **Position drift** — comments don't line up with the code they refer to;
line numbers and file paths drift off target.
- **Unstable quality** — natural-language Skills are hard to debug, and
output quality fluctuates with minor prompt edits.
The root cause: a purely language-driven architecture lacks **hard
constraints** on the review process.
## Core design: deterministic engineering × agent
OCR's core philosophy is to combine **deterministic engineering** with an
**agent** — each handling what it does best.
### Deterministic engineering — hard constraints
For steps that *must not go wrong*, engineering logic — not the model —
guarantees correctness:
- **Precise file selection** — a [five-gate filter](../review-rules/#how-files-are-filtered)
decides exactly which files are reviewed, with explicit `include`/`exclude`
controls.
- **Smart file bundling** — related files (e.g., `message_en.properties` and
`message_zh.properties`) can be grouped into a single review unit. Each
bundle runs as a sub-agent with isolated context — divide and conquer that
stays stable on very large changesets and naturally supports concurrent
review.
- **Fine-grained rule matching** — review rules are matched per file path
with first-match-wins, keeping the model's attention sharply focused and
eliminating noise. Template-based matching is more stable than purely
language-driven rule guidance.
- **External positioning and reflection modules** — independent comment
positioning ([`internal/diff/relocation.go`](https://github.com/alibaba/open-code-review/blob/main/internal/diff/relocation.go))
and re-location passes systematically improve both location and content
accuracy.
### Agent — dynamic decision-making
The agent's strengths are concentrated where they matter most:
- **Scenario-tuned prompts** — prompt templates deeply optimized for code
review, improving effectiveness while reducing token consumption (see
[`internal/config/template/task_template.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/template/task_template.json)).
- **Scenario-tuned toolset** — distilled from analysis of tool-call traces in
large-scale production data (call-frequency distributions, per-tool
repetition rates, the impact of each tool on the overall call chain). The
result is a purpose-built set of [six tools](../tools/) that is more stable
and predictable than a generic agent toolkit.
## How the pipeline fits together
```mermaid
flowchart TD
Start["<b>ocr review --from main --to feature</b>"]
S1["<b>1. Resolve LLM endpoint</b><br/>config / env / shell rc"]
S2["<b>2. Load diffs from git</b><br/>workspace / commit / range"]
S3["<b>3. Filter files</b><br/>binary → user_exclude → user_include<br/>→ ext allowlist → default path"]
S4["<b>4. Drop diffs > 80% of MAX_TOKENS</b>"]
S5["<b>5. Dispatch per-file sub-agents</b> (concurrent)<br/><br/>For each file:<br/>&nbsp;&nbsp;a. Plan phase (if changed lines ≥ 50)<br/>&nbsp;&nbsp;b. Main loop: LLM → tool calls → … → task_done<br/>&nbsp;&nbsp;c. code_comment results collected (async via worker pool)<br/><br/>Memory compression triggers when context<br/>exceeds 60 % (async) or 80 % (sync) of MAX_TOKENS."]
S6["<b>6. Resolve line numbers</b><br/>from <code>existing_code</code> against diffs.<br/>Re-locate via LLM if needed."]
S7["<b>7. Emit text or JSON output</b><br/>(and persist session to disk)"]
Start --> S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
```
## Project layout
```
open-code-review/
├── cmd/opencodereview/ # CLI entry point: dispatch, flags, commands
├── internal/
│ ├── agent/ # Per-file sub-agent loop + memory compression
│ ├── config/
│ │ ├── allowlist/ # Default file-extension allowlist & exclusions
│ │ ├── rules/ # Layered rule resolver, system rule docs
│ │ ├── template/ # Plan / main / memory_compression prompts
│ │ ├── testconnection/ # Built-in `ocr llm test` task
│ │ └── toolsconfig/ # Tool definitions sent to the model
│ ├── diff/ # Git diff parsing, hunk math, relocation
│ ├── gitcmd/ # Git subprocess runner
│ ├── llm/ # Anthropic + OpenAI protocols, retries, BPE tokens
│ ├── model/ # Diff / Comment data structures
│ ├── pathutil/ # Path utilities
│ ├── release/ # Release-notes generation
│ ├── session/ # JSONL persistence of every review session
│ ├── stdout/ # Quiet-able stdout writer for `--audience agent`
│ ├── suggestdiff/ # Build "Apply suggestion" diffs
│ ├── telemetry/ # OpenTelemetry spans, metrics, exporters
│ ├── tool/ # The six built-in tools + comment collector
│ └── viewer/ # `ocr viewer` — local web UI for past sessions
├── pages/ # React-based marketing landing page (separate)
├── plugins/ # Claude Code plugin manifest + commands
├── extensions/ # Editor extensions (VS Code)
├── examples/ # CI recipes (GitHub Actions, GitLab CI)
├── skills/ # Generic agent Skill manifest
├── scripts/ # NPM install/update helpers, publish scripts
├── npm/ # Per-platform optional dependency packages
└── bin/ # NPM wrapper that shells out to the binary
```
## See Also
- [QuickStart](../quickstart/) — install and run your first review.
- [Architecture](../architecture/) — the agent loop, plan phase, and memory compression.
- [CLI Reference](../cli-reference/) — every flag and sub-command.
- [Integrations](../integrations/) — call OCR from Claude Code or any agent.

View file

@ -0,0 +1,242 @@
---
title: QuickStart
sidebar:
order: 3
---
Install OCR, point it at any LLM that speaks the Anthropic Messages API or
the OpenAI Chat Completions API, and run your first code review.
## Prerequisites
- A working **Git** install — OCR drives Git as a subprocess to read diffs.
- An **API key** for an Anthropic-compatible or OpenAI-compatible LLM.
- One of:
- **Node.js ≥ 18** (recommended; minimum supported: Node 14 — installs via NPM).
- Or just `curl` + `chmod` to drop the static binary into `$PATH`.
- Or **Go ≥ 1.25** if you prefer to build from source.
## Step 1 — Install the CLI
### Option A: NPM (recommended)
```bash
npm install -g @alibaba-group/open-code-review
```
The NPM package installs a small wrapper that downloads the right binary for
your OS / architecture on install (via a postinstall hook). If the binary is
missing at run time, the wrapper errors out rather than downloading. After
install, you have a global `ocr` command:
```bash
ocr --version
```
### Option B: GitHub Release binary
Pick the binary for your platform from the
[releases page](https://github.com/alibaba/open-code-review/releases) and
drop it into your `$PATH`:
```bash
# macOS (Apple Silicon)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# macOS (Intel)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux x86_64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux ARM64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Windows (AMD64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-amd64.exe
# Windows (ARM64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-arm64.exe
```
### Option C: Build from source
```bash
git clone https://github.com/alibaba/open-code-review.git
cd open-code-review
make build
sudo cp dist/opencodereview /usr/local/bin/ocr
```
> See the [Installation](../installation/) page for details on each option,
> including how the NPM wrapper resolves the platform binary.
## Step 2 — Configure an LLM
OCR will refuse to run a review until it can resolve a complete LLM
endpoint (URL + token + model). It searches four sources in priority order:
1. `~/.opencodereview/config.json`
2. OCR-specific environment variables (`OCR_LLM_*`)
3. Claude Code environment variables (`ANTHROPIC_*`)
4. `export ANTHROPIC_*` lines parsed out of your shell rc files
(`~/.zshrc`, `~/.bashrc`, `~/.bash_profile`, `~/.profile`)
### Quickest path: `ocr config set`
```bash
ocr config set llm.url https://api.anthropic.com/v1/messages
ocr config set llm.auth_token sk-ant-xxxxxxxxxx
ocr config set llm.model claude-opus-4-6
ocr config set llm.use_anthropic true
```
These values are persisted to `~/.opencodereview/config.json`.
### Alternative: environment variables
Highest priority — useful in CI / containers where you don't want a config
file on disk:
```bash
export OCR_LLM_URL=https://api.anthropic.com/v1/messages
export OCR_LLM_TOKEN=sk-ant-xxxxxxxxxx
export OCR_LLM_MODEL=claude-opus-4-6
export OCR_USE_ANTHROPIC=true # default true; set false for OpenAI protocol
```
### Already using Claude Code?
OCR transparently picks up the same vars Claude Code uses, so no extra setup:
```bash
export ANTHROPIC_BASE_URL=https://api.anthropic.com
export ANTHROPIC_AUTH_TOKEN=sk-ant-xxxxxxxxxx
export ANTHROPIC_MODEL=claude-opus-4-6
```
If `ANTHROPIC_BASE_URL` lacks a versioned path, OCR appends `/v1/messages`
automatically.
### Using an OpenAI-compatible endpoint?
Set `llm.use_anthropic` to `false` (or `OCR_USE_ANTHROPIC=false`):
```bash
ocr config set llm.url https://api.openai.com/v1/chat/completions
ocr config set llm.auth_token sk-xxxxxxxxxx
ocr config set llm.model gpt-4o
ocr config set llm.use_anthropic false
```
> See [Configuration](../configuration/) for the full key reference,
> including `llm.extra_body` for vendor-specific request fields and
> `language` for switching review-comment language.
## Step 3 — Test connectivity
```bash
ocr llm test
```
Expected output (model name varies):
```
Source: OCR config file
URL: https://api.anthropic.com/v1/messages
Model: claude-opus-4-6
Hello! …
```
If you instead get an error like `no valid LLM endpoint configured`, recheck
the config keys above. A 401 / 403 means the token is wrong or expired.
## Step 4 — Run your first review
Move into any Git repository and run:
```bash
cd path/to/your-repo
# Workspace mode — reviews staged + unstaged + untracked changes (default)
ocr review
# Branch range — reviews `main..feature-branch`
ocr review --from main --to feature-branch
# Single commit — reviews the diff that commit introduced
ocr review --commit abc123
```
You should see a stream of progress lines, finishing with one or more review
comments per file.
> Workspace mode includes **untracked** files. If you only want to review
> what you've staged, `git add` selectively beforehand.
> The three modes above are the basics. See [CLI Reference](../cli-reference/)
> for the complete list of `ocr review` flags — concurrency tuning, output
> format, audience mode, background context, and more — plus every other
> sub-command (`config`, `rules`, `llm test`, `viewer`).
### Want to see what *would* be reviewed first?
```bash
ocr review --preview # workspace
ocr review -c abc123 -p # commit
```
`--preview` runs every filter step but never calls the LLM, so it's free.
It prints the file list with each file's status (`added` / `modified` /
`deleted` / `renamed` / `binary`) and, for excluded files, the reason
(`binary`, `unsupported_ext`, `default_path`, `user_exclude`, `deleted`).
### JSON output for tooling
```bash
ocr review --format json --audience agent > review.json
```
- `--format json` emits a machine-readable array of comments, each with
`path`, `content`, `start_line`, `end_line`, `existing_code`,
`suggestion_code` and optional `thinking`.
- `--audience agent` suppresses the human-friendly progress UI so the only
thing on stdout is the JSON / final summary — exactly what an upstream
agent or CI script wants.
## Step 5 — Review the results
Each comment includes:
| Field | Meaning |
|---|---|
| `path` | File that the comment is about. |
| `content` | The review comment itself, in the configured `language`. |
| `start_line` / `end_line` | Line range in the **new** version of the file. Both `0` means OCR couldn't precisely position the comment — the issue is real but you'll need to find the exact spot yourself. |
| `existing_code` | The snippet from the diff the comment refers to. Used internally for line resolution; useful when `start_line` is `0`. |
| `suggestion_code` | Optional fix snippet. |
| `thinking` | Optional model reasoning. Only present for some models. |
## Step 6 — Inspect a past session
Every review is persisted to `~/.opencodereview/sessions/...` as a
JSONL transcript. Browse them in a local web UI:
```bash
ocr viewer # http://localhost:5483
ocr viewer --addr :3000
```
> See [Session Viewer](../viewer/) for the full UI tour.
## See Also
- [CLI Reference](../cli-reference/) — every sub-command, flag, and output mode.
- [Review Rules](../review-rules/) — customize what gets reviewed.
- [Integrations](../integrations/) — embed OCR in Claude Code, an Agent skill, or CI.
- [Telemetry](../telemetry/) — ship traces and metrics over OTLP.
- [FAQ](../faq/) — known errors and remedies.

View file

@ -0,0 +1,261 @@
---
title: Review Rules
sidebar:
order: 7
---
Rules tell OCR **what to focus on** when reviewing each file. They live
in JSON files at three layers, plus an embedded system default that ships
with the binary.
## Priority chain
OCR resolves rules using a **four-layer priority chain**. For each file
path, the layers are tried in order; the first matching pattern wins.
| Priority | Source | Path | Notes |
|---|---|---|---|
| 1 (highest) | `--rule` flag | user-specified | CLI override; always wins when supplied. |
| 2 | Project config | `<repoDir>/.opencodereview/rule.json` | Per-project rules — safe to commit. |
| 3 | Global config | `~/.opencodereview/rule.json` | User-wide preferences. |
| 4 (lowest) | System default | embedded `system_rules.json` | Built-in rules covering common languages. |
If a higher-priority layer's file doesn't exist, it's silently skipped —
not an error. So a project that never adds `.opencodereview/rule.json`
just falls through to the global / system layers.
The system layer is **always** present (it ships in the binary), so there
is always *some* rule resolved.
## Rule file format (layers 13)
```json
{
"include": ["src/**/*.{ts,tsx}", "src/**/*.go"],
"exclude": ["**/*.test.ts", "**/generated/**"],
"rules": [
{
"path": "src/api/**/*.go",
"rule": "All exported handlers must validate request bodies before use."
},
{
"path": "**/*mapper*.xml",
"rule": "Check SQL for injection risks, parameter errors, and missing closing tags."
}
]
}
```
Three independent fields:
- `include` — optional. Glob patterns that *bypass* built-in default
exclude patterns (test-file exclusions — see below). It is not a
whitelist: files not matching any `include` pattern still proceed
through the `unsupported_ext` and `default_path` checks and may still
be reviewed.
- `exclude` — optional. Glob patterns for files OCR must *not* review.
Highest precedence within the filter.
- `rules` — array of `{path, rule}` entries, evaluated **in declaration
order**. The first `path` whose glob matches the file determines the
prompt OCR sends to the model for that file.
### Glob features
OCR uses [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4)
for matching:
- `*` — match any characters except `/`.
- `**` — match across directory boundaries (`src/**/*.go` covers any
depth).
- `{a,b,c}` — brace expansion. `*.{ts,tsx,js,jsx}` is expanded to four
patterns and matched in turn.
- `?` — match a single character.
- `[abc]` — character class.
> Patterns are matched **case-insensitively** (file path is lowercased
> before matching). When in doubt, use `ocr rules check <path>` to confirm.
## How files are filtered
The filter is a five-gate algorithm in
[`internal/agent/preview.go`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/preview.go).
For each diff, OCR asks:
1. **`binary`** — Is the file binary? Excluded.
2. **`user_exclude`** — Does the path match any user `exclude` pattern?
Excluded.
3. **`user_include`** — If the user defined `include`, does the path
match? If yes, **kept immediately** (bypasses the `unsupported_ext`
and `default_path` gates below).
4. **`unsupported_ext`** — Is the file extension in the
[allowlist](https://github.com/alibaba/open-code-review/blob/main/internal/config/allowlist/supported_file_types.json)?
Excluded if not.
5. **`default_path`** — Does the path match a built-in test-file exclude
pattern (`**/*_test.go`, `**/*.test.{js,jsx,ts,tsx}`, `**/*_spec.rb`,
…)? Excluded.
Files that survive all five gates are sent to the LLM. A `deleted`
reason (not a gate — it's computed separately in `Preview()`) marks
files whose new path is `/dev/null`; there's no new content to review.
Use `ocr review --preview` to print the result of this filter without
spending a token.
### Default path exclusions
The built-in exclude list (see
[`internal/config/allowlist/default_exclude_patterns.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/allowlist/default_exclude_patterns.json))
matches test-file patterns:
- `**/*_test.go`
- `**/src/test/java/**/*.java`
- `**/src/test/**/*.kt`
- `**/*.test.{js,jsx,ts,tsx}`
- `**/*.spec.{js,jsx,ts,tsx}`
- `**/__tests__/**`
- `**/test/**/*_test.py`
- `**/tests/**/*_test.py`
- `**/*_test.py`
- `**/*_spec.rb`
- `**/spec/**/*_spec.rb`
- `**/*Test.java`
- `**/*Tests.java`
- `**/*_test.rs`
- `**/oh_modules/**`
- `**/*.test.ets`
Noisy-directory filtering (`vendor/`, `node_modules/`, `target/`, …)
happens earlier, at the diff level in
[`internal/diff/git.go`](https://github.com/alibaba/open-code-review/blob/main/internal/diff/git.go),
before the per-file filter runs.
To **review** a file that matches one of these test-file patterns, add
it to the user `include` list — that overrides the default-path gate.
## Rule resolution per file
After filtering decides a file *will* be reviewed, OCR picks the rule
text the agent should follow:
1. Try `--rule` (custom) layer in declaration order.
2. Try `<repo>/.opencodereview/rule.json` in declaration order.
3. Try `~/.opencodereview/rule.json` in declaration order.
4. Fall back to the embedded system rule layer.
The embedded `system_rules.json` ships with these patterns (in order):
| Pattern | Rule doc |
|---|---|
| `**/*.properties` | `properties.md` — i18n / configuration files. |
| `**/*{mapper,dao}*.xml` | `mapper_dao_xml.md` — MyBatis-style mapper SQL. |
| `**/pom.xml` | `pom_xml.md` — Maven dependencies. |
| `**/build.gradle` | `build_gradle.md` — Gradle dependencies. |
| `**/package.json` | `package_json.md` — NPM dependencies / scripts. |
| `**/Cargo.toml` | `cargo_toml.md` — Rust manifest. |
| `**/*.{json,json5}` | `json.md` — generic JSON (also matches `.json5`). |
| `.github/workflows/**/*.{yaml,yml}` | `github_workflows.md` — GitHub Actions workflow YAML. |
| `.github/**/*.{yaml,yml}` | `github_config.md` — other `.github` config YAML. |
| `**/*.{yaml,yml}` | `yaml.md` |
| `**/*.java` | `java.md` |
| `**/*.ets` | `arkts.md` — ArkTS / HarmonyOS. |
| `**/*.{ts,js,tsx,jsx}` | `ts_js_tsx_jsx.md` |
| `**/*.{kt}` | `kotlin.md` |
| `**/*.rs` | `rust.md` |
| `**/*.{cpp,cc,hpp}` | `cpp.md` |
| `**/*.c` | `c.md` |
| *(fallback)* | `default.md` |
The resolved rule body becomes the `{{system_rule}}` placeholder in the
plan and main task prompts.
## Inspecting which rule wins: `ocr rules check`
```bash
$ ocr rules check src/main/java/com/example/UserService.java
File: src/main/java/com/example/UserService.java
Source: System built-in
Pattern: **/*.java
Rule:
────────────────────────────────────────
…contents of java.md…
────────────────────────────────────────
```
```bash
$ ocr rules check --rule custom.json src/main/resources/mapper/UserMapper.xml
File: src/main/resources/mapper/UserMapper.xml
Source: Custom (--rule)
Pattern: **/*mapper*.xml
Rule:
────────────────────────────────────────
…contents of your custom rule…
────────────────────────────────────────
```
Use this whenever a rule isn't behaving the way you expected — it tells
you the **layer** and the **pattern** that won.
## Recipes
### Project-level: enforce a coding standard
Save as `<repo>/.opencodereview/rule.json` and commit:
```json
{
"rules": [
{
"path": "src/api/**/*.go",
"rule": "Every public handler must `defer tx.Rollback()` immediately after starting a transaction."
},
{
"path": "**/*mapper*.xml",
"rule": "Check SQL for injection risks, missing parameter binding, and unclosed XML tags."
}
]
}
```
### Project-level: skip generated code, focus on src
```json
{
"include": ["src/**/*.{ts,tsx,js,jsx}"],
"exclude": ["**/*.gen.ts", "**/generated/**"]
}
```
With `include` set, files inside `src/` are kept even if they'd otherwise
be dropped by a built-in default exclude pattern (e.g., a test file).
Files outside `src/` still go through the normal ext / default checks —
`include` is a bypass, not a whitelist.
### Per-PR override
```bash
ocr review --rule ./.review-rules-only-for-this-pr.json
```
Bypasses both the project and global layers — handy when a single PR
needs a totally different review checklist (e.g., security-only review).
### Global personal preferences
Put them at `~/.opencodereview/rule.json` so every repo on your machine
inherits them:
```json
{
"rules": [
{
"path": "**/*.{ts,tsx,js,jsx}",
"rule": "Always check for unhandled promise rejections; warn on `// eslint-disable` without a reason comment."
}
]
}
```
## See Also
- [CLI Reference](../cli-reference/) — `ocr review --rule`, `--preview`, and `ocr rules check`.
- [Configuration](../configuration/) — config file locations and the layered resolution chain.
- [Architecture](../architecture/) — how the resolved rule feeds the agent prompt.

View file

@ -0,0 +1,281 @@
---
title: Telemetry
sidebar:
order: 11
---
OCR ships with first-class **OpenTelemetry** support. Every review run
produces structured spans, metrics, and events. Wired up to a collector,
the data is enough to answer "what did the agent spend time on?",
"which models cost what?", and "why did this run fail?".
## Overview
Telemetry is **off by default**. Once enabled, OCR exports:
- **Spans** — three pipeline-level spans (`review.run`, `diff.parse`,
`subtask.execute.<file>`) plus one short-lived `event.*` span per
decision-point event.
- **Metrics** — aggregated counts and histograms for review duration,
files reviewed, comments generated, LLM requests / tokens / latency,
and tool calls / latency.
- **Events** — discrete in-span events like `plan.skipped`,
`token.threshold.exceeded`, `review.started`.
Two exporters are supported:
| Exporter | When to use |
|---|---|
| `console` | Personal use / debugging. Pretty-prints spans to stdout. |
| `otlp` | System integration. Sends to any OTLP-compatible collector (Jaeger, Tempo, OTel Collector, Datadog Agent, …). |
## Enabling telemetry
Like the LLM endpoint, telemetry is configured by either persistent
config or environment variables — env wins on conflict.
### Config-file approach
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter otlp
ocr config set telemetry.otlp_endpoint localhost:4317
ocr config set telemetry.content_logging false
```
The result in `~/.opencodereview/config.json`:
```json
{
"telemetry": {
"enabled": true,
"exporter": "otlp",
"otlp_endpoint": "localhost:4317",
"content_logging": false
}
}
```
### Environment-variable approach
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 # implies exporter=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc # default. NOTE: only grpc is currently
# implemented; http/protobuf and http/json
# are accepted but not yet wired up.
export OTEL_SERVICE_NAME=open-code-review-prod # optional; default: open-code-review
export OCR_CONTENT_LOGGING=0 # reserved / currently a no-op (see Content logging)
```
Setting `OTEL_EXPORTER_OTLP_ENDPOINT` also forces `exporter=otlp`
useful for one-off `OTEL_EXPORTER_OTLP_ENDPOINT=… ocr review` runs.
## What gets exported
### Spans
The full span tree for a review:
```
review.run
├── diff.parse
├── event.review.started (decision-point event)
├── subtask.execute.<file1>
│ ├── event.plan.skipped (when changes are below threshold)
│ ├── event.plan.failed (when plan phase errored)
│ ├── event.token.threshold.exceeded (when prompt > 80% of max_tokens)
│ └── event.subtask.error (when the subtask errored)
├── subtask.execute.<file2>
└── …
```
LLM round trips and tool executions are **not** emitted as separate
spans — they show up only in metrics (see below). Decision-point events
fire as short-lived `event.<name>` spans attached to the current
context.
Each span carries useful attributes:
| Span | Key attributes |
|---|---|
| `review.run` | `error` (set when the run failed) |
| `diff.parse` | `files.changed`, `lines.inserted`, `lines.deleted` |
| `subtask.execute.<file>` | `file.path`, `lines.changed`, `lines.inserted`, `lines.deleted` |
| `event.review.started` | `file.count`, `review.count`, `repo.dir` |
| `event.plan.skipped` | `file.path`, `lines.changed`, `threshold` |
| `event.plan.failed` | `file.path`, `message` |
| `event.token.threshold.exceeded` | `file.path`, `tokens`, `max_tokens` |
| `event.subtask.error` | `file.path`, `error` |
### Metrics
OCR records numeric metrics via the OTel meter — counts and histograms
the collector aggregates downstream:
| Metric | Type | Unit | Labels |
|---|---|---|---|
| `ocr.review.duration_seconds` | histogram | `s` | — |
| `ocr.files_reviewed_total` | counter | — | — |
| `ocr.comments_generated_total` | counter | — | — |
| `ocr.llm.requests_total` | counter | — | `model`, `status` (`ok` / `error`) |
| `ocr.llm.request_duration_seconds` | histogram | `s` | `model` |
| `ocr.llm.tokens_used` | counter | — | `model`, `type` (currently always `total`) |
| `ocr.tool.calls_total` | counter | — | `tool.name`, `status` (`ok` / `error`) |
| `ocr.tool.execution_duration_seconds` | histogram | `s` | `tool.name` |
### Events
Events fire as short-lived `event.<name>` spans at decision points.
The full list:
| Event | Meaning |
|---|---|
| `review.started` | Diffs loaded; we know how many files we'll review. |
| `no.files.changed` | The diff resolved to zero files. |
| `plan.skipped` | A file was below `PLAN_MODE_LINE_THRESHOLD`. |
| `plan.failed` | The plan phase errored; main loop ran without a plan. |
| `token.threshold.exceeded` | Initial prompt tokens > 80 % of `MAX_TOKENS`; file skipped. |
| `subtask.error` | A per-file subtask errored — emitted with `Error` span status. |
Use these to alert on degraded review quality long before a user
notices.
## Content logging
Telemetry exports the **shape** of LLM traffic (counts, durations,
statuses) but **never** the actual prompts or responses. OCR makes no
attempt to attach LLM message content to spans or events — the data
that leaves the process is the metric / event schema documented above
and nothing else.
The `content_logging` config key (and `OCR_CONTENT_LOGGING=1` env
override) is plumbed through the config layer but currently does **not**
gate any code path that emits prompt content. Treat the flag as
reserved.
If you need to inspect what was sent to or returned from the LLM, use
the local JSONL transcripts that the [Session Viewer](../viewer/)
reads. Those live entirely on disk under `~/.opencodereview/` and are
never shipped to the collector.
## Recipes
### Console exporter for local debugging
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter console
ocr review --commit HEAD
```
Spans print to stdout in human-readable form. Pipe through `less` to
read a long run.
### OTel Collector with Tempo + Prometheus
```yaml
# otel-collector-config.yaml
receivers:
otlp:
protocols: { grpc: { endpoint: 0.0.0.0:4317 } }
exporters:
otlp/tempo:
endpoint: tempo:4317
tls: { insecure: true }
prometheus:
endpoint: 0.0.0.0:9464
service:
pipelines:
traces: { receivers: [otlp], exporters: [otlp/tempo] }
metrics: { receivers: [otlp], exporters: [prometheus] }
```
Then in your shell:
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
ocr review --from main --to feature/branch
```
Open Tempo → search by `service.name=open-code-review` → click any
trace to see the full span tree.
### Datadog
The Datadog Agent's OTLP receiver speaks OTLP/gRPC by default:
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
export OTEL_SERVICE_NAME=open-code-review
```
Spans show up under APM with the service name; LLM metrics show up
under Metrics with the labels above.
### CI run, results in your dashboard
Inject the env in your pipeline step:
```yaml
- name: Code review
env:
OCR_LLM_URL: ${{ secrets.OCR_LLM_URL }}
OCR_LLM_TOKEN: ${{ secrets.OCR_LLM_TOKEN }}
OCR_LLM_MODEL: claude-opus-4-6
OCR_ENABLE_TELEMETRY: "1"
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OTEL_COLLECTOR_URL }}
OTEL_SERVICE_NAME: open-code-review-ci
run: ocr review --from origin/main --to HEAD --audience agent
```
The `OTEL_SERVICE_NAME` separates CI traces from human dev runs.
## Resolution priority
When OCR builds the final telemetry config:
1. Defaults (`enabled=false`, `exporter=console`, no endpoint).
2. `telemetry.*` keys from `~/.opencodereview/config.json`.
3. Environment variables (highest priority, **overrides** the file).
So you can leave `telemetry.enabled=false` in the config and flip it
per-run with `OCR_ENABLE_TELEMETRY=1`.
## Sampling and overhead
OCR exports **everything**. There is no sampling configuration; OTel's
sampling is the responsibility of your collector. For a typical review
run that's:
- 1 `review.run` span + 1 `diff.parse` span + 1 `subtask.execute.<file>`
span per reviewed file + 1 short-lived `event.*` span per
decision-point event.
- A 10-file PR produces ~1525 spans total. LLM round trips and tool
calls add to the metric counters but do not create extra spans.
The export is **batched and asynchronous** — telemetry doesn't block
the review loop. If the collector is unreachable, OCR logs a warning
and continues; the review still produces its normal output.
## Troubleshooting
| Symptom | Likely cause |
|---|---|
| Nothing exported | `OCR_ENABLE_TELEMETRY` / `telemetry.enabled` is unset. The default is **off**. |
| OTLP works locally, fails in prod | OCR currently only implements OTLP/gRPC — `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf` (or `http/json`) is accepted but not yet wired up, so switching it won't help. Verify the endpoint and that the collector is listening for gRPC. |
| Spans show but no metrics | Some collectors only enable the traces pipeline by default; add a `metrics` pipeline in the config. |
| Prompts missing from spans | OCR never attaches prompt content to telemetry — see [Content logging](#content-logging). Inspect transcripts via [Session Viewer](../viewer/) instead. |
## See Also
- [Configuration](../configuration/) — full key reference for the
`telemetry.*` namespace.
- [Architecture](../architecture/) — what each span actually
measures.
- [OpenTelemetry docs](https://opentelemetry.io/docs/) — collector
setup and exporters.

View file

@ -0,0 +1,389 @@
---
title: Tools
sidebar:
order: 9
---
OCR ships with **six built-in tools** the LLM can call during a review.
This page documents each tool's purpose, input schema, and example
input/output. The full machine-readable definitions live in
[`internal/config/toolsconfig/tools.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/toolsconfig/tools.json).
## Tool availability per phase
Each tool declares whether it's exposed during the **plan phase**, the
**main task**, or both:
| Tool | Plan | Main | Purpose |
|---|---|---|---|
| `task_done` | ✗ | ✓ | Signal "I'm finished" — terminates the loop. |
| `code_comment` | ✗ | ✓ | Emit a review comment with line range + suggestion. |
| `file_read` | ✗ | ✓ | Read a slice of a file from the post-change snapshot. |
| `file_read_diff` | ✓ | ✓ | Read another file's diff to confirm a cross-file concern. |
| `file_find` | ✓ | ✓ | Locate files by filename keyword. |
| `code_search` | ✓ | ✓ | Grep across the repo (literal or regex). |
`task_done` and `code_comment` are intentionally **not** available
during the plan phase: planning is read-only.
> **Context tools are read-only context, not comment targets.** The
> `main_task` prompt explicitly forbids commenting on findings in
> *other* files. `file_read`, `file_read_diff`, `file_find`, and
> `code_search` exist so the model can understand the current file's
> diff better — any issue spotted while gathering that context is
> ignored by design. Cross-file concerns surface as comments only when
> they're observable from the **current file's diff**.
To override the tool registry, pass `--tools <path>` to a JSON file with
the same shape as the embedded one. This lets you disable a tool, edit
a description, or add a new tool backed by an existing provider.
## `task_done`
Terminate the main loop.
```json
{
"name": "task_done",
"input": { "state": "DONE" }
}
```
| Field | Required | Meaning |
|---|---|---|
| `state` | yes | `DONE` (default) or `FAILED`. `FAILED` is for "I literally cannot use the available tools to do this" — almost never the right choice. |
When the agent sees `task_done`, it stops calling the LLM and starts
processing accumulated `code_comment` calls. `task_done` returns
immediately (before the result is recorded in the session log), so the
`state` value is accepted but **not** persisted — it doesn't affect exit
codes either.
## `code_comment`
Emit one or more review comments. Each comment is anchored to a code
snippet (`existing_code`) so OCR can compute line numbers automatically.
### Schema
```json
{
"name": "code_comment",
"input": {
"path": "string — optional, override the file path for this comment",
"comments": [
{
"content": "string — the comment in the configured language",
"existing_code": "string — snippet from the diff to anchor on",
"suggestion_code": "string — optional fix snippet",
"thinking": "string — optional, the model's reasoning for this comment"
}
]
}
}
```
`comments` is an array, so the model can emit several comments in one
tool call. `content` and `existing_code` are required; `suggestion_code`
is optional but encouraged. `path` is a top-level optional override —
if omitted, the agent injects the file currently under review. The
agent also injects `path` automatically when the model leaves it out, so
the model rarely needs to set it explicitly. `thinking` (per-comment)
captures the model's reasoning and is preserved on the comment but not
shown in the final review output.
> **`thinking` is a runtime-only field.** OCR parses and stores it, but
> it is deliberately **not** listed in the `code_comment` schema
> advertised to the model in `tools.json` (only `content`,
> `existing_code`, and `suggestion_code` are). Stronger models that
> emit a `thinking` block anyway will have it persisted; most won't send
> it, which is fine.
### Anchoring algorithm
OCR walks the diff looking for the text in `existing_code` using a
**dynamic sliding window**. The match tries, in order:
1. **Hunk new-side** — a run of consecutive **context + added** lines
(not deleted-only, not unchanged-only), yielding new-file line
numbers. If that fails, OCR retries the **hunk old-side** — context +
deleted lines — yielding old-file line numbers.
2. **Full new-file scan** — if no hunk matched, OCR scans the entire
post-change file content line-by-line for consecutive matches
(`resolveFromFileContent`).
3. **Re-location task** — if text matching still fails on a non-trivial
diff, OCR runs the `RE_LOCATION_TASK` prompt asking the model to
re-anchor the snippet.
Matching is **whitespace-insensitive**: lines are trimmed and diff
`+`/`-` markers stripped before comparison, so indentation need not
match exactly. As a last resort the comment is delivered with
`start_line=0`, telling the user "the issue is real but you'll need to
find the spot yourself".
### Example
```json
{
"comments": [
{
"content": "`tx.Rollback()` is never deferred — early returns leak the transaction.",
"existing_code": "tx, err := db.Begin()\nif err != nil {\n return err\n}",
"suggestion_code": "tx, err := db.Begin()\nif err != nil {\n return err\n}\ndefer tx.Rollback()"
}
]
}
```
## `file_read`
Read a range of lines from a file in its **post-change** form.
### Schema
```json
{
"name": "file_read",
"input": {
"file_path": "src/foo.go",
"start_line": 10,
"end_line": 80
}
}
```
| Field | Required | Default | Notes |
|---|---|---|---|
| `file_path` | yes | — | Path relative to the repo root. |
| `start_line` | no | `1` | 1-indexed. |
| `end_line` | no | end of file | Inclusive. |
### Output
```
File: src/foo.go (Total lines: 220)
IS_TRUNCATED: false
LINE_RANGE: 10-80
10|package foo
11|
12|import (
13| "fmt"
```
Each content line is prefixed with its 1-indexed line number and a `|`
separator so the model can quote line numbers precisely in subsequent
`code_comment` calls.
### Limits
- **500 lines max per call.** Larger ranges are truncated, `IS_TRUNCATED:
true` is set, and a trailing `Note: Results truncated to 500 lines.
Please narrow your line range.` is appended.
- Reads only the **modified version** of the file. To see the old
version, use `file_read_diff`.
When the model needs surrounding context (for a comment about a
function it can only see in the diff), it should compute the range
from the diff hunk header `@@ -x,y +m,n @@` — typically `m-50` to
`m+n+50`.
## `file_read_diff`
Read the diff for one or more *other* files in the same change set —
useful when a comment hinges on whether a related file was updated.
### Schema
```json
{
"name": "file_read_diff",
"input": {
"path_array": ["src/api/handler.go", "src/db/queries.go"]
}
}
```
### Output
```
==== FILE: src/api/handler.go ====
--- a/src/api/handler.go
+++ b/src/api/handler.go
@@ -10,1 +10,2 @@
- old line
+ new line 1
+ new line 2
==== FILE: src/db/queries.go ====
@@ -5,1 +5,1 @@
- query := "SELECT *"
+ query := "SELECT id"
```
If a path isn't in the change set, that entry is silently omitted. If
**none** of the requested paths are in the change set the tool returns
`Error: diff not found for the requested paths`; an empty `path_array`
returns `Error: no files found`.
## `file_find`
Find files in the repo by filename keyword (substring match).
### Schema
```json
{
"name": "file_find",
"input": {
"query_name": "UserService",
"case_sensitive": false
}
}
```
| Field | Required | Default | Notes |
|---|---|---|---|
| `query_name` | yes | — | Substring matched against each file's **basename** (the part after the last `/`), not the full path. |
| `case_sensitive` | no | `false` | Set to `true` for exact-case matching. |
The candidate set comes from `git ls-files --cached --others
--exclude-standard` in workspace mode, or `git ls-tree -r --name-only
<ref>` in range / commit mode. Extensionless files are skipped except
for `Makefile`, `Dockerfile`, `LICENSE`, `Vagrantfile`, `Containerfile`.
### Output
A newline-separated list of paths:
```
src/main/java/com/example/UserService.java
src/test/java/com/example/UserServiceTest.java
src/main/java/com/example/internal/UserServiceImpl.java
```
When no file matches (or `query_name` is blank), the tool returns the
literal string `// The file was not found`.
### Limits
Returns up to **100** matches; excess is silently truncated. If the
model needs broader search, it should fall through to `code_search`.
## `code_search`
Full-text search across the repo. Backed by `git grep`, so it
understands `pathspec` syntax and respects `.gitignore`.
### Schema
```json
{
"name": "code_search",
"input": {
"search_text": "TODO|FIXME",
"file_patterns": ["*.go", ":(exclude)vendor/"],
"case_sensitive": false,
"use_perl_regexp": true
}
}
```
| Field | Required | Default | Notes |
|---|---|---|---|
| `search_text` | yes | — | Literal string or PCRE pattern (see `use_perl_regexp`). |
| `file_patterns` | no | whole repo | Array of pathspec entries. Use `:(exclude)pat` to subtract. |
| `case_sensitive` | no | `false` | — |
| `use_perl_regexp` | no | `false` | When `true`, `search_text` is treated as a regex. |
### Output
Results are grouped by file. Each group starts with `File: <path>` and
`Match lines: <n>`, followed by one `line|content` line per hit:
```
File: path/to/example.java
Match lines: 2
433| String name = toolRequest.get().getName();
438| logToolRequest(newPath, tool, toolRequest.get());
File: path/to/other.java
Match lines: 1
22| var req = new ToolRequest();
```
When there are no matches, the tool returns the literal string
`No matches found`.
### Pathspec cookbook
| Goal | `file_patterns` |
|---|---|
| Single file | `["src/main.go"]` |
| All Go files | `["*.go"]` |
| All Go except tests | `["*.go", ":(exclude)*_test.go"]` |
| Only one directory | `["src/api/"]` |
| Multiple types, no vendor | `["*.go", "*.ts", ":(exclude)vendor/", ":(exclude)node_modules/"]` |
### Limits
- Caps matches at **100 per file** via `git grep --max-count 100`, so
total output across many files can exceed 100. When the per-file cap
is hit the output is prefixed with `Note: The results have been
truncated. Only showing first 100 results.`.
- Empty / whitespace-only `search_text` returns `Error: search_text is
blank` instead of expanding to every line.
- Searches the **current working tree** in workspace mode, or the
resolved target ref in range / commit mode (the `FileReader.Ref` is
passed as a positional argument to `git grep`).
## Tool execution & errors
Tools execute synchronously inside the agent loop, with two exceptions:
- `code_comment` is dispatched to the **CommentWorkerPool** so the loop
doesn't block on line-resolution + reflection.
- `task_done` short-circuits — it returns immediately without invoking
any provider.
When a tool errors (network failure, malformed args, file not found),
the result is delivered to the model as a regular tool result with text
like `"Error: file not found: src/missing.go"`. The model then decides
whether to retry, ask for a different file, or call `task_done`.
If a tool name isn't in the registry, OCR returns the constant
`tool.NotAvailableMsg` rather than crashing. This makes runtime tool
disabling (via `--tools`) safe.
## Customizing tools
Two paths to extend:
### 1. Disable a tool
Copy `tools.json`, drop the entry you don't want, then run:
```bash
ocr review --tools ./my-tools.json
```
For example, if you want a "comment-only" reviewer that never reads
extra context, keep only `code_comment` and `task_done`.
### 2. Re-describe a tool
Keep the `name` (the providers are looked up by name internally) but
change the `description` to nudge the model. This is the easiest way to
inject project-specific guidance — e.g., "When using `file_read`,
always read at least 30 lines around the change."
> Adding **new** tool *names* requires Go-side wiring; see
> `internal/tool/definitions.go` and the providers under
> `internal/tool/`. The JSON file alone can't add new behaviour.
## See Also
- [Architecture](../architecture/) — how the agent loop drives tools.
- [Review Rules](../review-rules/) — what the LLM is told to focus on.
- [Session Viewer](../viewer/) — see exactly which tools fired in past
reviews.

View file

@ -0,0 +1,182 @@
---
title: Session Viewer
sidebar:
order: 10
---
`ocr viewer` is a small embedded HTTP server that renders past review
sessions in a browser-friendly UI. No external dependencies — sessions
are read directly from the JSONL files OCR writes to disk during every
review.
## Launching
```bash
ocr viewer # binds localhost:5483
ocr viewer --addr :3000 # bind to all interfaces on port 3000
ocr viewer --addr 0.0.0.0:8080 # bind on all interfaces
```
The default address is `localhost:5483`. The server holds the foreground
`Ctrl+C` stops it. Sessions are scanned lazily from
`~/.opencodereview/sessions/` on each request, so a review running in
another terminal shows up the moment its JSONL file appears.
> **DNS-rebinding protection.** The viewer checks the `Host` header
> against a loopback allowlist (`localhost`, `127.0.0.1`, `::1`). A
> concrete bind host (e.g. `--addr 192.168.1.10:5483`) is added
> automatically, but **wildcard** binds (`:3000`, `0.0.0.0`, `::`) are
> not — reaching the UI from a LAN IP or hostname then returns
> `forbidden host`. To expose a wildcard bind, set
> `OCR_VIEWER_ALLOWED_HOSTS` to a comma-separated list of allowed
> hostnames (e.g. `OCR_VIEWER_ALLOWED_HOSTS=box.local,192.168.1.10`).
## Three pages
The viewer has three URLs:
| URL | What you see |
|---|---|
| `/` | List of all repositories that have sessions on disk. |
| `/r/{repo}` | List of sessions for one repository, newest first. |
| `/r/{repo}/{sessionID}` | Full detail for a single session. |
`{repo}` is a path-encoded string (separators `/` and `\` replaced with
`-`, colons replaced with `_` — the same encoding used to name the
on-disk directories). You don't usually type this — you click through.
### `/` — Repository list
For each repo with at least one session you see the repo path, the
total session count, and the most recent activity timestamp.
### `/r/{repo}` — Session list for one repo
For each session: ID (a UUID), branch name (when OCR was able to
detect it), review mode, model, file count, duration, and a started-at
timestamp.
### `/r/{repo}/{sessionID}` — Session detail
The detail page is the interesting one. It shows:
1. **Header** — diff range, model, branch, total tokens, run duration.
2. **File group** — one block per reviewed file. Inside each file, five
"task type" lanes:
| Task type | When it appears |
|---|---|
| `plan_task` | The plan phase ran (file ≥ `PLAN_MODE_LINE_THRESHOLD`). |
| `main_task` | Every file. The main review loop. |
| `review_filter_task` | The post-review comment-filtering pass ran for this file. |
| `memory_compression_task` | The active+compress zone exceeded 60 % / 80 % budget. |
| `re_location_task` | A `code_comment` couldn't be anchored, fallback re-location ran. |
Each lane is a horizontal strip of **task cards** — one per LLM round
trip. Cards are coloured by task type so you can see at a glance which
phases dominated the run.
## What's in a task card
Click a task card to expand. Each card has:
- a **header row** — request number, model badge, a token badge
(`P:` prompt / `C:` completion, plus `CR:` / `CW:` cache read/write
when present), a duration badge, and an error badge when the round
failed;
- **Response** — the raw assistant response, including any reasoning /
`thinking` blocks;
- **Tool calls** — each tool invocation with arguments + the result that
was returned (collapsible).
The full message list sent to the model and the in-scope tool
definitions are **not** rendered in the card UI; if you need them,
inspect the JSONL transcript directly (the `messages` field on each
`llm_request` record).
## Use cases
The viewer is designed around three workflows:
### "Why did the model say that?"
Open a comment in your terminal output, locate the file in the viewer,
and walk down its `main_task` lane. The card whose **tool calls**
include the `code_comment` you care about is the round that produced
it. The card's Response shows the model's reasoning; for the exact
prompt + context the model was sent, open the `llm_request` record for
that request number in the JSONL transcript (its `messages` field).
### "Why was this file silent?"
A file with **no comments** is a successful review only if the model
*deliberately* called `task_done`. If the lane shows tool calls but no
`code_comment`, that's an intentional clean review. If the lane ends in
an error card, it's a failure dressed up as silence — surface it as a
warning.
### "What did compression keep / drop?"
The `memory_compression_task` lane shows every compression round.
Inside, the Response pane has the resulting summary; the rendered XML
of the compress zone that was fed in lives in the round's
`llm_request` `messages` in the JSONL transcript. Useful when debugging
a "the model forgot earlier context" complaint — you can see whether
compression dropped the relevant detail.
## Storage layout on disk
The viewer reads from:
```
~/.opencodereview/sessions/
└── <path-encoded-repo-path>/
└── <session-id>.jsonl
```
Each line in the JSONL file is one event:
```json
{"type": "llm_request", "filePath": "src/foo.go", "taskType": "main_task", "request_no": 1, "messages": [{"role": "user", "content": "Review this diff…"}], "timestamp": "2026-06-02T10:15:23Z"}
{"type": "llm_response", "filePath": "src/foo.go", "taskType": "main_task", "model": "claude-sonnet-4-6", "content": "Found 2 issues…", "duration_ms": 8421, "usage": {"prompt_tokens": 12450, "completion_tokens": 320}}
{"type": "tool_call", "filePath": "src/foo.go", "tool_name": "file_read", "arguments": "{\"file_path\":\"src/foo.go\",\"start_line\":1,\"end_line\":50}", "result": "File: src/foo.go (Total lines: 220)\nIS_TRUNCATED: false\nLINE_RANGE: 1-50\n1|package foo…", "ok": true, "duration_ms": 14}
```
Lines are append-only — a partial JSONL means a session was killed
mid-run, and the viewer renders what it has.
To free disk space, delete entire session files; the viewer regenerates
its index on the next request.
## Privacy
The JSONL transcripts contain **everything** sent to and received from
the LLM, including any code that was in the diff. They live entirely on
your machine inside `~/.opencodereview/`. OCR does not upload them
anywhere.
If your reviews include code you wouldn't want stored long-term,
either:
- delete the session files periodically, or
- redirect `--audience agent --format json` output to a transient pipe
in CI and run with a temporary `HOME` so the JSONL never persists.
The OpenTelemetry exporter is a separate concern — see
[Telemetry](../telemetry/) for how to keep prompt content out of
exported traces.
## When the viewer is *not* the right tool
- For programmatic post-processing (CI, dashboards), use
`ocr review --format json --audience agent`. The viewer renders for
humans, not machines.
- For grepping across many sessions, use `jq` on the JSONL files
directly. There's no search box in the UI yet.
## See Also
- [Architecture](../architecture/) — what those five task types
actually do under the hood.
- [Tools](../tools/) — the tool calls you'll see in `main_task`
cards.

View file

@ -0,0 +1,177 @@
/* Docs content index — imports all markdown files and provides a lookup by slug + language */
// English docs
import enOverview from './en/overview.md';
import enQuickstart from './en/quickstart.md';
import enInstallation from './en/installation.md';
import enConfiguration from './en/configuration.md';
import enCliReference from './en/cli-reference.md';
import enReviewRules from './en/review-rules.md';
import enArchitecture from './en/architecture.md';
import enTools from './en/tools.md';
import enViewer from './en/viewer.md';
import enTelemetry from './en/telemetry.md';
import enIntegrations from './en/integrations.md';
import enAgentSkill from './en/integrations/agent-skill.md';
import enClaudeCode from './en/integrations/claude-code.md';
import enSubprocess from './en/integrations/subprocess.md';
import enCicd from './en/integrations/ci.md';
import enContributing from './en/contributing.md';
import enFaq from './en/faq.md';
// Chinese docs
import zhOverview from './zh/overview.md';
import zhQuickstart from './zh/quickstart.md';
import zhInstallation from './zh/installation.md';
import zhConfiguration from './zh/configuration.md';
import zhCliReference from './zh/cli-reference.md';
import zhReviewRules from './zh/review-rules.md';
import zhArchitecture from './zh/architecture.md';
import zhTools from './zh/tools.md';
import zhViewer from './zh/viewer.md';
import zhTelemetry from './zh/telemetry.md';
import zhIntegrations from './zh/integrations.md';
import zhAgentSkill from './zh/integrations/agent-skill.md';
import zhClaudeCode from './zh/integrations/claude-code.md';
import zhSubprocess from './zh/integrations/subprocess.md';
import zhCicd from './zh/integrations/ci.md';
import zhContributing from './zh/contributing.md';
import zhFaq from './zh/faq.md';
export type DocSlug =
| 'overview'
| 'quickstart'
| 'installation'
| 'configuration'
| 'cli-reference'
| 'review-rules'
| 'architecture'
| 'tools'
| 'viewer'
| 'telemetry'
| 'integrations'
| 'agent-skill'
| 'claude-code'
| 'subprocess'
| 'cicd'
| 'contributing'
| 'faq';
const enDocs: Record<DocSlug, string> = {
'overview': enOverview,
'quickstart': enQuickstart,
'installation': enInstallation,
'configuration': enConfiguration,
'cli-reference': enCliReference,
'review-rules': enReviewRules,
'architecture': enArchitecture,
'tools': enTools,
'viewer': enViewer,
'telemetry': enTelemetry,
'integrations': enIntegrations,
'agent-skill': enAgentSkill,
'claude-code': enClaudeCode,
'subprocess': enSubprocess,
'cicd': enCicd,
'contributing': enContributing,
'faq': enFaq,
};
const zhDocs: Record<DocSlug, string> = {
'overview': zhOverview,
'quickstart': zhQuickstart,
'installation': zhInstallation,
'configuration': zhConfiguration,
'cli-reference': zhCliReference,
'review-rules': zhReviewRules,
'architecture': zhArchitecture,
'tools': zhTools,
'viewer': zhViewer,
'telemetry': zhTelemetry,
'integrations': zhIntegrations,
'agent-skill': zhAgentSkill,
'claude-code': zhClaudeCode,
'subprocess': zhSubprocess,
'cicd': zhCicd,
'contributing': zhContributing,
'faq': zhFaq,
};
const docsMap: Record<string, Record<DocSlug, string>> = {
en: enDocs,
zh: zhDocs,
ja: enDocs, // fallback to English for Japanese
};
/**
* Strip YAML frontmatter from markdown content
*/
function stripFrontmatter(md: string): string {
if (md.startsWith('---')) {
const end = md.indexOf('---', 3);
if (end !== -1) {
return md.slice(end + 3).trim();
}
}
return md;
}
/**
* Get raw content for a slug in the given language, with English fallback.
*/
function getRawContent(slug: DocSlug, language: string): string {
const langDocs = docsMap[language] || docsMap.en;
return langDocs[slug] || enDocs[slug] || '';
}
/**
* Get the markdown content for a given doc slug and language.
* Falls back to English if the language is not found.
*/
export function getDocContent(slug: DocSlug, language: string): string {
return stripFrontmatter(getRawContent(slug, language));
}
/**
* Get the title from frontmatter
*/
export function getDocTitle(slug: DocSlug, language: string): string {
const raw = getRawContent(slug, language);
if (raw.startsWith('---')) {
const end = raw.indexOf('---', 3);
if (end !== -1) {
const fm = raw.slice(3, end);
const match = fm.match(/title:\s*(.+)/);
if (match) return match[1].trim();
}
}
return slug;
}
/**
* Search across all docs for a query string. Returns matching slugs with context.
*/
export function searchDocs(query: string, language: string): { slug: DocSlug; title: string; snippet: string }[] {
if (!query.trim()) return [];
const langDocs = docsMap[language] || docsMap.en;
const results: { slug: DocSlug; title: string; snippet: string }[] = [];
const lowerQuery = query.toLowerCase();
const slugs = Object.keys(langDocs) as DocSlug[];
for (const slug of slugs) {
const raw = langDocs[slug] || enDocs[slug] || '';
const content = stripFrontmatter(raw);
const lowerContent = content.toLowerCase();
const idx = lowerContent.indexOf(lowerQuery);
if (idx !== -1) {
// Extract snippet around match
const start = Math.max(0, idx - 30);
const end = Math.min(content.length, idx + query.length + 60);
let snippet = content.slice(start, end).replace(/[#*_`\[\]()]/g, '').replace(/\n/g, ' ').trim();
if (start > 0) snippet = '...' + snippet;
if (end < content.length) snippet = snippet + '...';
const title = getDocTitle(slug, language);
results.push({ slug, title, snippet });
}
}
return results;
}

4
pages/src/content/docs/md.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
declare module '*.md' {
const content: string;
export default content;
}

View file

@ -0,0 +1,328 @@
---
title: 架构
sidebar:
order: 8
---
从你按下回车到 JSON 落在终端,`ocr review` 内部实际如何运作的导览。旨在帮你
建立足够的心智模型,以调试行为、调优参数,并有把握地阅读源码。
## 高层流水线
```mermaid
flowchart TD
A["<b>ocr review</b>"]
B["<b>bootstrap</b><br/><span style='font-size:0.85em'>Resolve LLM endpoint (config → env → rc files)<br/>Load template, tool registry, system rules</span>"]
C["<b>diff provider</b><br/><span style='font-size:0.85em'>git diff / ls-files / show — produce []model.Diff<br/>Modes: Workspace · Commit · Range</span>"]
D["<b>filter & rules</b><br/><span style='font-size:0.85em'>5-gate filter (preview.go) — drop binaries,<br/>excluded paths, unsupported extensions. Pick rule per file.</span>"]
E["<b>subtask dispatch</b><br/><span style='font-size:0.85em'>For every diff in parallel (concurrency=N):<br/>Plan phase (optional) → Main loop → Comments</span>"]
F["<b>output writer</b><br/><span style='font-size:0.85em'>Synchronous line-resolution & review-filter; renders text<br/>or JSON depending on --format / --audience.</span>"]
A --> B --> C --> D --> E --> F
```
编排逻辑位于
[`internal/agent/`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/)
包,分布在四个文件:`agent.go`(主循环与分发)、`compression.go`(记忆压缩)、
`preview.go`(文件过滤)和 `util.go`(辅助)。两个入口点值得关注:`Agent.Run`
(流水线顶部)和 `Agent.dispatchSubtasks`per-file 扇出)。
## diff provider
`internal/diff/git.go` 定义了一个 `Provider` 结构,其未导出字段 `mode`(类型为
`Mode`,一个 `int` 枚举)选择与 CLI 参数对应的三种模式之一:
| 模式 | 触发方式 | 返回内容 |
|---|---|---|
| `Workspace` | 无参数 | staged + unstaged + untracked 变更 |
| `Commit` | `--commit <sha>` / `-c <sha>` | `<sha>` 引入的变更(经 `git show <sha>`,等价于 `<sha>^..<sha>` diff |
| `Range` | `--from <a> --to <b>` | `merge-base(a, b)..b` |
每个 diff 携带old/new path、old/new hunk、插入/删除计数、二进制标志、重命名
检测。`DiffContextLines` 固定为 **3**——与 Git 默认一致。
untracked 文件从磁盘读取并作为整文件新增处理,以便 commit 前评审。
## 五重门文件过滤
diff 加载后,每个文件经过
[`whyExcluded`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/preview.go)。
该函数返回以下之一:
```
binary — file is binary
user_exclude — matched a pattern in your `exclude` list
unsupported_ext — extension is not in supported_file_types.json
default_path — matched a built-in test-file exclude pattern
```
……或文件被保留时返回空。`deleted` **不**由 `whyExcluded` 返回;它在 `Preview()`
中随后计算——当一个被保留文件的 diff 报告 `IsDeleted` 时。各门按以下顺序执行:
1. `binary`——二进制文件先被丢弃。
2. `user_exclude`——你项目的 `exclude` 总是优先。
3. `user_include`——若配置了 include 模式**且**文件匹配其一,立即保留
(返回空),绕过下面的 `unsupported_ext``default_path` 门。
4. `unsupported_ext` 按扩展名白名单过滤。
5. `default_path` 是最后一道门:匹配内置**测试文件**排除模式
`**/*_test.go``**/*.test.{js,jsx,ts,tsx}``**/__tests__/**`
`**/*_test.py``**/*_spec.rb``**/*.test.ets`……)。每个模式都以
`**/` 作为根前缀。
噪声目录过滤(`vendor/``node_modules/``target/`……)发生在更早的阶段,
位于 diff-provider 层,通过 `internal/diff/git.go` 中的 `providerDirIgnoreDirs`
列表——这些目录的 diff 被解析后由 `filterDiffs` 剔除,永远不会到达 per-file
过滤器。
运行 `ocr review --preview` 可不花 token 查看完整过滤结果。完整算法见
[评审规则](../review-rules/#how-files-are-filtered)。
## per-file 子任务plan + main
对每个通过过滤的文件OCR 启动一个子 agent。每个子 agent 在自己的 goroutine
中运行,受 `--concurrency`(默认 **8**)约束,并有独立的 LLM 消息缓冲区。
一个子任务最多有**两个阶段**
### 阶段 1——Plan可选
```go
threshold := template.PlanModeLineThreshold // 50
changeLines := d.Insertions + d.Deletions
if changeLines < threshold { skip plan }
```
对小 diffplan 只会增加延迟、没有价值因此被静默跳过main 循环直接运行。对较大
diffOCR 做一次**单次** `PLAN_TASK` LLM 调用——不发送 `Tools` 字段,因此模型
在 plan 期间不能调用工具。只读工具子集(`code_search``file_read_diff`
`file_find`——`tools.json``plan_task` 标志为 `true` 的那三个)作为纯文本
通过 `{{plan_tools}}` 占位符(由 `formatToolDefs` 渲染)嵌入,
让模型知道后续可用什么。模型返回一份清单,作为 main prompt 中的
`{{plan_guidance}}`
### 阶段 2——main 循环
main 循环组装 `MAIN_TASK` prompt与模型展开工具调用对话。完整工具集在
plan 阶段工具基础上加 **`task_done`**、**`code_comment`** 和
**`file_read`**——完整清单见[工具](../tools/)。
```
loop up to MAX_TOOL_REQUEST_TIMES (default 30):
response = llm.complete(messages, tools)
if response.toolCalls is empty:
nudge model with "You did not successfully call any tools.
Please try again or use task_done if finished."
continue
for each call: execute → collect result
if any call was task_done: break
addNextMessage(...) # may trigger compression
```
循环有五个退出条件:
1. 调用了 `task_done`
2. `MAX_TOOL_REQUEST_TIMES` 耗尽。
3. 连续 3 轮未产生有效工具结果(`maxConsecutiveEmptyRounds = 3`)。
4. context 被取消。
5. `addNextMessage` 返回 false——压缩无法把消息缓冲区压回警告阈值以下。
无论哪种情况,已收集的 `code_comment` 调用都成为评审评论。
## 记忆压缩
长的工具调用循环最终会溢出上下文窗口。OCR 用**三分区**策略管理,触发于
`MAX_TOKENS = 58888` 定义的 token 预算:
| 阈值 | 常量 | 动作 |
|---|---|---|
| MAX_TOKENS 的 60 % | `tokenSoftThreshold` | 启动**异步**后台压缩;当前循环不中断继续。 |
| MAX_TOKENS 的 80 % | `tokenWarningThreshold` | 在发送下一个请求前**同步**运行压缩。 |
### 三个区
```mermaid
flowchart LR
subgraph messages["messages"]
direction LR
F["<b>frozen</b><br/>first 2 msgs<br/>(system +<br/>initial user)"]
C["<b>compress</b><br/>summarized<br/>into one<br/>user msg"]
A["<b>active</b><br/>K most recent<br/>complete<br/>rounds"]
end
F --- C --- A
```
一“轮”是一条 assistant 消息加上其后跟随的工具结果消息。`partitionMessages`
从末尾向前遍历轮次,保留能装入 `(0.80 × MAX_TOKENS) - reservedTokens` 的尽可能
多的轮。更早的内容成为 **compress 区**
compress 区被渲染为 XML`MEMORY_COMPRESSION_TASK` prompt 交给模型;返回的
摘要被追加到原始 user 消息内,包在 `<previous_review_summary>` 标签里。
压缩后:`messages = frozen[2] + compressed_user_msg + active`
```go
// compression.go
func (a *Agent) runCompression(ctx context.Context, msgs []llm.Message, filePath string) ([]llm.Message, error) {
part := partitionMessages(msgs, a.args.Template.MaxTokens, 0)
contextXML := buildMessageXML(msgs[part.frozenEnd:part.compressEnd])
// … call MEMORY_COMPRESSION_TASK …
rebuilt[1] = llm.NewTextMessage(role, currentText+
"\n\n<previous_review_summary>\n"+rawSummary+"\n</previous_review_summary>")
for i := part.compressEnd; i < len(msgs); i++ {
rebuilt = append(rebuilt, msgs[i])
}
return rebuilt, nil
}
```
### 异步 vs 同步
异步路径让 main 循环在后台压缩运行时继续产出工具调用;当下一次 token 检查发生
时,已就绪的摘要会通过 `tryApplyPendingCompression` 应用。若比例在异步任务完成前越过
警告阈值,循环会停顿并同步运行 `runCompression`——保证下一个请求总是装得下。
## 评论处理流水线
每个 `code_comment` 工具调用产出一条或多条原始评论。它们经过一个
**CommentWorkerPool**(固定大小 goroutine 池),使主工具调用循环永不阻塞在
后处理上:
1. **行解析**worker 内)——`existing_code` 用滑动窗口算法与 diff 匹配以计算
精确的 `start_line` / `end_line`。匹配失败则两者默认为 `0`——`0` 行范围是
“未锚定”评论的隐式信号,用户需手动定位(没有存储标志;下游消费者检查
`start_line == 0`)。
2. **重新定位任务** *(可选回退)*——当行解析在较复杂的 diff 上失败时OCR 运行
`RE_LOCATION_TASK` prompt请模型重新锚定片段。对改写过的 `existing_code`
字符串有用。
3. **评审过滤**——main 循环结束后worker 池排空),`REVIEW_FILTER_TASK` LLM
调用对照 diff 检查收集到的评论,移除可证明为错的评论。此处错误被记录并忽略。
4. **第二轮行解析**——`Agent.Run` 返回后,顶层命令对完整评论集重跑
`diff.ResolveLineNumbers`(见 `cmd/opencodereview/review_cmd.go`),以捕获
`existing_code` 跨多文件或被重新定位步骤更新的评论。
5. **渲染**——按 `--format` 渲染为 text 或 JSON。
## token 预算守卫
在调用 LLM 之前OCR 先做一个 fail-fast 检查:
```go
tokenLimit := MaxTokens * 4 / 5 // 80 %
if countMessagesTokens(messages) > tokenLimit {
record warning "token_threshold_exceeded"
return nil // skip this file
}
```
这会在巨大 diff自动生成的 lock 文件、触及数千行的重构)耗费请求之前把它们拦截下来。
被跳过的文件作为非致命警告在 stdout 报告,并加入 JSON `warnings` 数组。
第二个检查在 `filterLargeDiffs` 中运行:若 diff 单独超过 `MAX_TOKENS` 的 80 %
它在 per-file 分发器启动前就被过滤掉。
## 模板与占位符
`internal/config/template/task_template.json` 含**五个 prompt**
| Key | 用途 |
|---|---|
| `PLAN_TASK` | plan 阶段——产出清单。 |
| `MAIN_TASK` | main 评审循环——发出 `code_comment` 调用。 |
| `MEMORY_COMPRESSION_TASK` | 摘要 compress 区。 |
| `REVIEW_FILTER_TASK` | 循环后移除可证明为错评论的流程。 |
| `RE_LOCATION_TASK` | 为 `existing_code` 无法匹配的评论重新锚定。 |
每个 prompt 是一个 `{role, prompt_file}` 引用列表,指向模板目录中的 `.md` 文件
(如 `{"role": "system", "prompt_file": "main_task_system.md"}`)。加载时
`resolveConversation` 把这些文件读入内存中的 `{role, content}` 消息,随后模板
占位符按文件解析:
| 占位符 | 替换为 |
|---|---|
| `{{system_rule}}` | 从四层链解析出的规则正文。 |
| `{{change_files}}` | PR 中其他每个变更文件的状态 + 路径。 |
| `{{diff}}` | 本文件的 diff原始 `git diff` 输出)。 |
| `{{current_file_path}}` | 本文件的新路径。 |
| `{{plan_guidance}}` | plan 阶段的输出plan 被跳过时移除。 |
| `{{plan_tools}}` | plan 阶段工具定义的纯文本(由 `formatToolDefs` 渲染),用于 `PLAN_TASK` system prompt。 |
| `{{requirement_background}}` | `--background` 参数内容。 |
| `{{current_system_date_time}}` | 运行的本地时间戳,格式 `YYYY-MM-DD HH:MM`(无秒或时区)。 |
| `{{context}}` | (仅压缩)要摘要的 XML 渲染消息。 |
| `{{path}}` | 文件路径,用于 `REVIEW_FILTER_TASK`。 |
| `{{comments}}` | 累积的评论JSON用于 `REVIEW_FILTER_TASK`。 |
占位符替换位于
[`agent.go`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/agent.go)。
模板本身不是 CLI 覆盖——要修改 prompt你需要编辑
[`task_template.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/template/task_template.json)
并重新构建。`--tools` 参数是*工具注册表*覆盖(它替换 `internal/config/toolsconfig`
消费的 JSON不是模板覆盖——见[工具](../tools/#customizing-tools)。
> **占位符语法注意。** 以上所有占位符都使用双花括号
> `{{…}}` 语法,*除了* `RE_LOCATION_TASK`,它替换单花括号
> 的 `{diff}``{existing_code}``{suggestion_content}`
> (见 `internal/diff/relocation.go`)。
## 持久化
每次评审以 JSONL 写入磁盘:
```
~/.opencodereview/sessions/<encoded-repo-path>/<session-id>.jsonl
```
仓库路径**不**做 base64 编码;`encodeRepoPath`(在
`internal/session/persist.go`)把 `/``\` 替换为 `-``:` 替换为 `_`,使路径
对文件系统安全。
每行是一个事件:发送的 prompt、LLM 响应、工具调用、工具结果、发出的评论等。
Web UI`ocr viewer`)直接读这些文件——没有数据库,只有 append-only 日志。UI
导览与事件 schema 见[会话查看器](../viewer/)。
## 遥测
启用遥测后agent 发出三个流水线级 span`review.run` 包裹整个作业、
`diff.parse` 包裹 diff 加载、每个被评审文件一个 `subtask.execute.<file>`),加上
每个决策点一个短生命周期的 `event.<name>` span`plan.skipped`
`token.threshold.exceeded``subtask.error`……。LLM 往返和工具调用仅作为
metrics 记录——不作为 span。prompt 与响应内容**绝不**附加到遥测;
`OCR_CONTENT_LOGGING` 标志已接入但目前是死代码。完整 schema 见[遥测](../telemetry/)。
## 哪些*不*自动化
一些决策有意保持手动:
- **端点发现没有回退。** 若你的 config + env + rc 文件给不出完整的
`(URL, token, model)` 三元组OCR 以非零码退出,而非猜测。
- **子 agent 失败被隔离,不重试。** 一个失败文件产生一条警告;其余继续。重试
属于包裹它的 CI 流水线,而非 agent。
- **无跨文件推理。** 每个文件在它自己的 LLM 对话中评审。跨文件问题通过
`file_read_diff` / `code_search` 工具调用,而非共享上下文。那些*其他*文件中
的发现也禁止作为评论目标——`main_task` prompt 指示模型仅将上下文工具用于
理解,并忽略在当前 diff 之外文件中出现的问题。
这些选择让运行**按文件确定性**,并让成本可预测。
## 源码地图
若你想对照阅读:
| 关注点 | 文件 |
|---|---|
| 顶层命令分发 | `cmd/opencodereview/main.go` |
| `review` 参数解析 | `cmd/opencodereview/flags.go` |
| agent 编排与压缩 | `internal/agent/`agent.go、compression.go、util.go |
| 文件过滤 / 预览 | `internal/agent/preview.go` |
| diff 加载Git 模式) | `internal/diff/git.go` |
| 规则解析链 | `internal/config/rules/system_rules.go` |
| 工具注册表与实现 | `internal/tool/` |
| LLM 端点解析器 | `internal/llm/resolver.go` |
| 会话 JSONL 写入器 | `internal/session/persist.go` |
| Web 查看器 | `internal/viewer/server.go` |
构建与测试说明见[贡献](../contributing/)。
## 另见
- [工具](../tools/)——agent 循环调用的六种工具。
- [评审规则](../review-rules/)——按文件的规则文本如何解析。
- [会话查看器](../viewer/)——检查此流水线写出的转录。

View file

@ -0,0 +1,376 @@
---
title: CLI 参考
sidebar:
order: 6
---
每个 `ocr` 子命令、参数与退出行为的完整参考。
## 全局用法
```text
OpenCodeReview - AI-Powered Code Review CLI
Usage:
ocr [command]
Commands:
review, r Start a code review
rules Inspect and debug review rules
config Manage configuration settings
llm LLM utility commands
viewer Start the WebUI session viewer
version Show version information
Examples:
ocr review --from master --to dev Review diff range
ocr review --commit abc123 Review a single commit
ocr config provider Interactive provider setup
ocr config model Interactive model selection
ocr config set llm.model opus-4-6 Set a config value
ocr llm test Test LLM connectivity
ocr llm providers List built-in providers
ocr version Show version info
Use "ocr review -h" for more information about review.
Use "ocr rules -h" for more information about rules.
Use "ocr config" for more information about config.
Use "ocr llm" for more information about LLM utilities.
GitHub: https://github.com/alibaba/open-code-review
```
## 命令总览
| 命令 | 别名 | 作用 |
|---|---|---|
| `ocr review` | `ocr r` | 运行代码评审并输出评论。 |
| `ocr rules check <file>` | — | 显示某文件路径适用哪条规则及其来源。 |
| `ocr config set <key> <value>` | — | 将一个配置值持久化到 `~/.opencodereview/config.json`。 |
| `ocr config unset custom_providers.<name>` | — | 删除一个自定义 provider若它是当前启用的则清空启用的 `provider`/`model`)。 |
| `ocr config provider` | — | 交互式 provider 配置 TUI。 |
| `ocr config model` | — | 交互式 model 选择 TUI。 |
| `ocr llm test` | — | 发送一条简短 chat 请求以验证配置的端点。 |
| `ocr llm providers` | — | 列出所有内置 LLM provider。 |
| `ocr viewer` | — | 启动用于历史评审会话的本地 Web UI`localhost:5483`)。 |
| `ocr version` | — | 打印版本、commit、平台、构建日期与 GitHub URL。 |
`ocr``ocr -h` 打印顶层用法。每个子命令也接受 `-h` / `--help`
## `ocr review`
主命令。解析 Git diff分发 per-file 子 agent收集评审评论并打印。
### 概要
```text
ocr review [flags]
ocr r [flags] (alias)
```
若不传任何参数OCR 以**工作区模式**运行——评审当前目录所在仓库中所有 staged +
unstaged + untracked 变更。
### 参数
| 参数 | 简写 | 默认 | 说明 |
|---|---|---|---|
| `--repo <path>` | — | 当前目录 | Git 仓库根。 |
| `--from <ref>` | — | — | diff 起始 ref`main`)。 |
| `--to <ref>` | — | — | diff 结束 ref`feature-branch`)。设置后 OCR 计算 `merge-base(from, to)..to`。 |
| `--commit <sha>` | `-c` | — | 评审单个 commit相对其父。 |
| `--preview` | `-p` | `false` | 运行过滤流水线但跳过 LLM。打印文件列表与排除原因。 |
| `--format <fmt>` | `-f` | `text` | `text`(人类可读)或 `json`(机器可读的评论数组)。 |
| `--audience <who>` | — | `human` | `human` 流式输出进度行;`agent` 静默 stdout只打印最终摘要 / JSON。 |
| `--background <text>` | `-b` | — | 注入 plan + main prompt 的可选需求 / 业务上下文。 |
| `--concurrency <n>` | — | `8` | 并行评审的最大文件数。 |
| `--timeout <minutes>` | — | `10` | 每文件截止时间。`0` 关闭超时。 |
| `--rule <path>` | — | — | 自定义 JSON 评审规则文件路径。覆盖项目级与全局 `rule.json`。 |
| `--max-tools <n>` | — | 模板默认 | 每文件最大工具调用轮数。`0` 用模板默认(`30`19 会被上调到 `10`;任何 `≥ 10` 的值都覆盖模板默认(即使小于 `30`)。 |
| `--model <name>` | — | — | 为本次评审覆盖已解析出的 LLM model`claude-opus-4-6`)。 |
| `--max-git-procs <n>` | — | `16` | 并发 git 子进程的最大数。 |
| `--tools <path>` | — | 内嵌 | 自定义 JSON 工具配置文件路径。覆盖内嵌工具定义。 |
> 模式参数互斥:传 `--from`/`--to`,或 `--commit`,或都不传(工作区模式)。
> 混用会直接报错。
### 模式
#### 工作区模式(默认)
```bash
ocr review
```
OCR 从两条 git 命令组装工作树变更:
- 通过 `git diff HEAD` 获取已跟踪变更staged + unstaged 合并对比 `HEAD`
若为空则回退到 `git diff --staged`
- 通过 `git ls-files --others --exclude-standard` 获取 untracked 文件,从磁盘
读取并作为整文件新增处理
这通常是 commit 前你想要的。如需更小的范围,请选择性暂存。
#### 区间模式
```bash
ocr review --from main --to feature-branch
```
OCR 计算 `merge-base(main, feature-branch)..feature-branch`,因此你只看到
feature 分支*引入*的 diff——而非分支切出后落到 `main` 上的无关变更。
#### Commit 模式
```bash
ocr review --commit abc123
ocr review -c abc123
```
评审 `git show abc123` 产生的 diff即该 commit 引入的变更)。
### 输出
#### Text默认`--audience human`
评审运行时流式输出进度行,随后每条评论一个块(带 `path:start-end` 的暗色
Unicode 分隔头、按 100 列折行的评论正文,以及(存在时)建议替换的彩色内联
diff。运行结束时 stdout 末尾打印一份摘要:
```
[ocr] 17 file(s) changed, reviewing 9 in /path/to/repo
[ocr] Skipping image.png — filtered by path/extension rules
[ocr] ▶ file_read "src/foo.go"
[ocr] ✔ file_read (12ms)
[ocr] Plan completed for src/foo.go
─── src/foo.go:42-47 ───
Concurrent map access without a lock — wrap with sync.RWMutex.
- m[k] = v
+ mu.Lock(); defer mu.Unlock(); m[k] = v
[ocr] Summary: 9 file(s) reviewed, 14 comment(s), ~21344 token(s) used (input: ~18012, output: ~3332), 1m12s elapsed
```
#### Textagent`--audience agent`
评论输出相同,但通过一个内部可静默的 stdout writer 屏蔽进度行
[`internal/stdout`](https://github.com/alibaba/open-code-review/blob/main/internal/stdout/stdout.go))。
在 CI / 流水线中交给另一个 agent 时使用。
#### JSON
```bash
ocr review --format json --audience agent
```
```json
{
"status": "success",
"summary": {
"files_reviewed": 9,
"comments": 1,
"total_tokens": 21344,
"input_tokens": 18012,
"output_tokens": 3332,
"elapsed": "1m12s"
},
"comments": [
{
"path": "src/foo.go",
"content": "Concurrent map access without a lock — wrap with sync.RWMutex.",
"start_line": 42,
"end_line": 47,
"existing_code": "m[k] = v",
"suggestion_code": "mu.Lock(); defer mu.Unlock(); m[k] = v",
"thinking": "Looking at line 42, the map …"
}
]
}
```
顶层字段:
| 字段 | 说明 |
|---|---|
| `status` | `success``completed_with_warnings``completed_with_errors``skipped`。 |
| `message` | 可选。人类可读摘要,如 `"No comments generated. Looks good to me."`。 |
| `summary` | 可选。运行聚合:`files_reviewed``comments``total_tokens``input_tokens``output_tokens``cache_read_tokens`omitempty`cache_write_tokens`omitempty`elapsed``skipped` 运行时省略。 |
| `comments` | 总是存在,可能为空。每条评论的字段如上例。 |
| `warnings` | 可选。当一个或多个子 agent 失败时存在;每条描述受影响文件与错误。 |
当没有文件可评审时JSON 模式会发一个 `skipped` 外壳,以便调用方区分“无变更”
与“无发现”:
```json
{
"status": "skipped",
"message": "No supported files changed.",
"comments": []
}
```
### 退出码
| 码 | 含义 |
|---|---|
| `0` | 评审完成(可能零评论,可能有非致命警告)。 |
| `1` | 致命错误——参数错误、无法解析 LLM 端点、所有 per-file 子 agent 失败等。错误文本打印到 stderr。 |
非致命警告(单个子 agent 失败、某文件超过 token 阈值等内联打印JSON 模式下
会加入 `warnings` 数组。
## `ocr rules`
规则自查。只有一个子命令:
```text
ocr rules check [flags] <file-path>
Flags:
--repo <path> Git repository root (default: current dir)
--rule <path> Path to a custom rule JSON file
```
对给定文件路径OCR 会:
1. 遍历四层规则链custom → project → global → system
2. 取第一条匹配。
3. 打印**来源层**、匹配的 **glob 模式**,以及解析出的**规则文本**。
```bash
$ ocr rules check src/main/java/com/example/Foo.java
File: src/main/java/com/example/Foo.java
Source: System built-in
Pattern: **/*.java
Rule:
────────────────────────────────────────
<contents of internal/config/rules/rule_docs/java.md>
────────────────────────────────────────
```
可用于排查“为什么我的自定义规则没触发?”——完整的优先级说明见
[评审规则](../review-rules/)。
## `ocr config`
将 key 持久化到 `~/.opencodereview/config.json`,并提供交互式配置 TUI。四个
子命令:
```text
ocr config set <key> <value>
ocr config unset custom_providers.<name> Delete a custom provider
ocr config provider Interactive provider setup
ocr config model Interactive model selection
```
- **`set`**——非交互式写入单个配置值。
- **`unset`**——删除一个自定义 provider。仅支持
`custom_providers.<name>`。若删除的是当前启用的 provider`provider`
`model` 被清空(运行 `ocr config provider` 重新选择)。
- **`provider`**——启动交互式 provider 配置 TUI无额外参数非交互式请用
`ocr config set provider <name>`)。
- **`model`**——启动交互式 model 选择 TUI无额外参数非交互式请用
`ocr config set model <name>`)。
完整的 key 参考、schema 与示例见[配置](../configuration/)。
## `ocr llm`
LLM 工具命令。两个子命令:
```text
ocr llm <sub-command>
Sub-commands:
test Send a test conversation to the configured LLM model
providers List all built-in LLM providers
```
### `ocr llm test`
```text
ocr llm test
```
以与 `ocr review` 完全相同的方式解析 LLM 端点,从
[`internal/config/testconnection/task.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/testconnection/task.json)
发送一条预置 chat 请求,并打印:
```
Source: <which strategy was used>
URL: <endpoint URL>
Model: <effective model>
<the model's reply>
✓ Connection test successful
```
非零退出意味着端点未完整配置,或请求失败(网络 / 鉴权 / 模型错误)。错误信息
会指明具体是哪一种。
### `ocr llm providers`
```text
ocr llm providers
```
以三列表格列出每个内置 LLM provider
```
Built-in providers:
NAME PROTOCOL BASE URL
---- -------- --------
anthropic anthropic https://api.anthropic.com
```
随后是一条提示,可用 `ocr config provider` 交互式配置,或用
`ocr config set provider <name>` 非交互式配置。
## `ocr viewer`
```text
ocr viewer [flags]
Flags:
--addr <address> listen address (default: localhost:5483)
Examples:
ocr viewer # start on default port
ocr viewer --addr :3000 # bind to all interfaces on port 3000
```
启动一个内嵌 HTTP 服务器,读取 `~/.opencodereview/sessions/...`,并以浏览器友好的 UI 渲染历史评审会话。见[会话查看器](../viewer/)。
## `ocr version`
```text
ocr version
ocr --version
ocr -V
```
打印构建时写入的版本信息、短 Git commit存在时、平台
`<GOOS>/<GOARCH>`)、构建日期(存在时),以及 GitHub URL
`https://github.com/alibaba/open-code-review`)。
## 提示与注意
- `--audience agent` **并不**隐含 `--format json`。两者控制不同的事——屏蔽 UI
vs 结构化载荷。需要二者兼得时组合使用。
- `--background` 是提升评审质量最有效的参数之一——从其他 agent 调用时,始终传入
需求 / PR 描述。
- 某文件 diff 单独超过 `MAX_TOKENS` 的 80%(默认 `58888`)时,会在调用 LLM 前
被丢弃。这会记录日志但不会使运行失败。
- 当某文件变更行数低于 `PLAN_MODE_LINE_THRESHOLD``50`plan 阶段会被
**自动跳过**
## 另见
- [快速开始](../quickstart/)——安装并完成首次评审。
- [配置](../configuration/)——参数背后的环境变量与 config key。
- [评审规则](../review-rules/)——`--rule` 参数与规则解析。
- [集成](../integrations/)——从 agent 与 CI 调用 `ocr review`

View file

@ -0,0 +1,272 @@
---
title: 配置
sidebar:
order: 5
---
## 端点解析
`ocr review``ocr llm test` 运行时,它会按顺序尝试四个来源,
并使用第一个能给出完整 `(URL, token, model)` 三元组的来源:
| 优先级 | 来源 | 读取内容 |
|---|---|---|
| 1 | `~/.opencodereview/config.json` | 若设置了 `provider`,则通过 `providers`/`custom_providers` 映射解析provider 优先;见[内置 provider](#built-in-providers))。仅当未设置 provider 时才回退到遗留的 `llm` 段。 |
| 2 | OCR 环境变量 | `OCR_LLM_URL``OCR_LLM_TOKEN``OCR_LLM_MODEL``OCR_USE_ANTHROPIC``OCR_LLM_AUTH_HEADER`。 |
| 3 | Claude Code 环境变量 | `ANTHROPIC_BASE_URL``ANTHROPIC_AUTH_TOKEN``ANTHROPIC_MODEL`。 |
| 4 | Shell rc 文件 | 从 `~/.zshrc``~/.bashrc``~/.bash_profile``~/.profile` 中解析出的 `export ANTHROPIC_*=…` 行。 |
对于 Claude Code 风格的来源,若 `ANTHROPIC_BASE_URL` 缺少带版本的路径
`/v1/...`OCR 会自动追加 `/v1/messages`
如果没有一种策略能给出完整三元组OCR 会以如下信息退出:
```
no valid LLM endpoint configured; one of OCR_LLM_URL/OCR_LLM_TOKEN/OCR_LLM_MODEL,
~/.opencodereview/config.json, or ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/
ANTHROPIC_MODEL must be set
```
> 解析在第一个**报错**的来源处停止,而不仅仅是第一个为空的来源。尤其要注意:
> 如果 `config.json` 中设置了 `provider` 但该条目配置有误(未知的 provider 名、
> 缺少 `api_key` 且无环境变量回退、缺少 `model`、自定义 provider 缺少
> `url`/`protocol`OCR 会以该错误退出,并**不会**继续回退到 OCR 环境变量、
> Claude Code 或 rc 文件来源。要切换到基于环境变量的配置,请先取消
> 设置 `provider` key。
> 来源优先级意味着当配置文件已完整填充时,**环境变量不会覆盖任何值**。要让
> 环境变量生效,要么从 `~/.opencodereview/config.json` 删除相关 `llm.*` key
> 要么用 `ocr config set` 切换到新值。
## `ocr config set` ——管理 `~/.opencodereview/config.json`
```bash
ocr config set <key> <value>
```
`config set` 通过 key/value 对修改文件,并做具备 schema 感知的解析。交互式 TUI
命令 `ocr config provider``ocr config model` 也写入同一个文件(见
[交互式设置](#interactive-setup--ocr-config-provider--ocr-config-model))。识别的
key
| Key | 类型 | 说明 |
|---|---|---|
| `provider` | string | 设置当前 provider内置名或自定义。切换 provider 会清空 model。 |
| `model` | string | 为当前 provider 设置 model存在 provider 条目下;若无 provider 则存到顶层 `model`)。 |
| `providers.<name>.<field>` | varies | 内置 provider 的按字段设置:`api_key``url``protocol``model``models``auth_header``extra_body`。 |
| `custom_providers.<name>.<field>` | varies | 同上字段用于自定义非内置provider。自定义 provider 至少要设置 `url``protocol`。 |
| `llm.url` | string | 端点 URL。Anthropic 用完整的 Messages URL`https://api.anthropic.com/v1/messages`。OpenAI 兼容则用 chat-completions URL。 |
| `llm.auth_token` | string | API key。以 `Authorization: Bearer …` 发送OpenAI遗留 Anthropic 路径默认也是 `Authorization: Bearer …`(预设 `anthropic` provider 改为默认 `x-api-key`)。仅在显式设置 `llm.auth_header` 时才用 `x-api-key`。 |
| `llm.auth_header` | string | Auth header 名(`x-api-key``authorization``bearer`)。仅 Anthropic 用;某些需要 `x-api-key` 的 Anthropic 设置必需。 |
| `llm.model` | string | 模型名。`[<数字>m]` 后缀会被自动去除。 |
| `llm.use_anthropic` | boolean | `true`(默认)→ Anthropic Messages 协议。`false` → OpenAI Chat Completions。 |
| `llm.extra_body` | JSON object | 厂商专属的请求字段,合并进每次 chat 请求体。示例:`'{"thinking":{"type":"disabled"}}'`。 |
| `language` | string | 转发为追加到 system prompt 的指令;未设置时默认 `English`。见[选择语言](#choosing-a-language)。 |
| `telemetry.enabled` | boolean | OpenTelemetry 导出的总开关。默认关闭。 |
| `telemetry.exporter` | string | `console``otlp`。 |
| `telemetry.otlp_endpoint` | string | OTLP collector 地址(如 `localhost:4317`)。 |
| `telemetry.content_logging` | boolean | 在导出的事件数据中包含 LLM prompt / 响应。 |
示例:
```bash
ocr config set llm.url https://api.anthropic.com/v1/messages
ocr config set llm.auth_token sk-ant-xxxxxxxxxx
ocr config set llm.model claude-opus-4-6
ocr config set llm.use_anthropic true
ocr config set llm.extra_body '{"thinking":{"type":"disabled"}}'
ocr config set language English
ocr config set telemetry.enabled true
ocr config set telemetry.exporter otlp
ocr config set telemetry.otlp_endpoint localhost:4317
# 基于 provider 的设置(推荐)
ocr config set provider anthropic
ocr config set model claude-opus-4-6
ocr config set providers.anthropic.api_key "$ANTHROPIC_API_KEY"
# 自定义非内置provider
ocr config set provider my-gateway
ocr config set custom_providers.my-gateway.url https://gateway.internal.com/v1
ocr config set custom_providers.my-gateway.protocol openai
ocr config set custom_providers.my-gateway.model llama-3-70b
ocr config set custom_providers.my-gateway.api_key "$MY_API_KEY"
```
布尔值接受 Go `strconv.ParseBool` 接受的任何形式(`true``false``1``0`
`t``f`……)。`llm.extra_body` 必须是合法 JSON。
## 文件 schema 参考
执行上述命令后,`~/.opencodereview/config.json` 形如:
```json
{
"llm": {
"url": "https://api.anthropic.com/v1/messages",
"auth_token": "sk-ant-xxxxxxxxxx",
"auth_header": "x-api-key",
"model": "claude-opus-4-6",
"use_anthropic": true,
"extra_body": {
"thinking": { "type": "disabled" }
}
},
"language": "English",
"telemetry": {
"enabled": true,
"exporter": "otlp",
"otlp_endpoint": "localhost:4317"
}
}
```
基于 provider 的形式使用 `provider``model``providers`
`custom_providers`,而非遗留的 `llm` 块:
```json
{
"provider": "anthropic",
"model": "claude-opus-4-6",
"providers": {
"anthropic": {
"api_key": "sk-ant-xxxxxxxxxx",
"model": "claude-opus-4-6"
}
},
"custom_providers": {
"my-gateway": {
"url": "https://gateway.internal.com/v1",
"protocol": "openai",
"model": "llama-3-70b",
"models": ["llama-3-70b", "llama-3-8b"],
"api_key": "gw-xxxxxxxxxx",
"auth_header": "authorization"
}
},
"language": "English"
}
```
当设置了 `provider` 时,由 `providers`/`custom_providers` 映射驱动解析;该配置下
遗留的 `llm` 段被忽略。
你也可以手动编辑此文件,但下次写入时 `ocr config set` 会以 `" "` 缩进
重新序列化。
## 交互式设置——`ocr config provider` / `ocr config model`
为免去手动键入 key 来选择 provider 和 modelOCR 提供两个交互式 Bubble Tea TUI
二者同样会修改 `~/.opencodereview/config.json`
```bash
ocr config provider
ocr config model
```
- `ocr config provider`——选择内置或自定义 provider并输入 URL / protocol /
API key / model 的交互式 TUI。选择会保存到 config并自动运行 `ocr llm test`
验证端点。对于内置 provider若未直接输入API key 可从该 provider 的环境变量
读取(见[内置 provider](#built-in-providers))。若选择手动配置,则改为填充遗留的
`llm.*` 块。
- `ocr config model`——从当前 provider 的预设列表,以及
`providers.<name>.models` / `custom_providers.<name>.models` 下用户添加的
model 中选择模型的交互式 TUI。需要先设置 provider`ocr config provider`)。
## 内置 provider
以下 provider 随 OCR 发布。每个都有预设的 `BaseURL``Protocol`,以及
(如适用)一个 API key 环境变量,在 `providers.<name>.api_key` 未设置时作为
回退。
| 名称 | Protocol | Base URL | API key 环境变量 |
|---|---|---|---|
| `anthropic` | anthropic | `https://api.anthropic.com` | `ANTHROPIC_API_KEY` |
| `openai` | openai | `https://api.openai.com/v1` | `OPENAI_API_KEY` |
| `dashscope` | openai | `https://dashscope.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_API_KEY` |
| `dashscope-tokenplan` | openai | `https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1` | `DASHSCOPE_TOKENPLAN_KEY` |
| `volcengine` | openai | `https://ark.cn-beijing.volces.com/api/v3` | `ARK_API_KEY` |
| `deepseek` | openai | `https://api.deepseek.com` | `DEEPSEEK_API_KEY` |
| `tencent-tokenhub` | openai | `https://tokenhub.tencentmaas.com/v1` | `TENCENT_TOKENHUB_API_KEY` |
| `hy-tokenplan` | openai | `https://api.lkeap.cloud.tencent.com/plan/v3` | `TENCENT_HUNYUAN_TOKENPLAN_KEY` |
| `kimi` | openai | `https://api.moonshot.cn/v1` | `MOONSHOT_API_KEY` |
| `z-ai` | openai | `https://open.bigmodel.cn/api/paas/v4` | `Z_AI_API_KEY` |
| `mimo` | openai | `https://api.xiaomimimo.com/v1` | `MIMO_API_KEY` |
| `minimax` | openai | `https://api.minimaxi.com/v1` | `MINIMAX_API_KEY` |
| `baidu-qianfan` | openai | `https://qianfan.baidubce.com/v2` | `QIANFAN_API_KEY` |
任何其他 provider 名都被视为自定义,必须在 `custom_providers` 下配置,且至少
要有 `url``protocol`
## 环境变量参考
| 变量 | 用途 |
|---|---|
| `OCR_LLM_URL` | 端点 URL——与 `llm.url` 同形。 |
| `OCR_LLM_TOKEN` | API key——与 `llm.auth_token` 相同。 |
| `OCR_LLM_MODEL` | 模型名。 |
| `OCR_LLM_AUTH_HEADER` | Auth header 名(`x-api-key``authorization``bearer`)。仅 Anthropic`llm.auth_header` 相同。未设置时默认 `authorization`。 |
| `OCR_USE_ANTHROPIC` | 未设置 → Anthropic 协议(默认)。设为 `true` / `1` / `yes`(不区分大小写)→ Anthropic。设为其他值`false``0``no`、拼写错误……)→ OpenAI。 |
| `ANTHROPIC_BASE_URL` | Claude Code 兼容的 base URL。 |
| `ANTHROPIC_AUTH_TOKEN` | Claude Code 兼容的 API key。 |
| `ANTHROPIC_MODEL` | Claude Code 兼容的 model。 |
| `OCR_ENABLE_TELEMETRY` | `1` 表示从环境变量启用遥测。 |
| `OTEL_SERVICE_NAME` | 覆盖 span/metric 中的 service name。 |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector 地址——同时强制 exporter 为 `otlp`。 |
| `OTEL_EXPORTER_OTLP_PROTOCOL` | OTLP 传输协议(`grpc``http/protobuf``http/json`)。默认 `grpc`。 |
| `OCR_CONTENT_LOGGING` | `1` 表示在遥测事件中包含 prompt/响应。 |
各 provider 的 API key`ANTHROPIC_API_KEY``OPENAI_API_KEY`
`DASHSCOPE_API_KEY`……)在内置 provider 的 `api_key` 字段未设置时作为回退。
各 provider 的环境变量名见[内置 provider](#built-in-providers)表。
## 为什么有 `extra_body`
一些托管 provider 会在请求体中加入非标准字段(例如 Bedrock 风格的 `thinking`
厂商专属的 `temperature_strategy`、流式选项)。`llm.extra_body` 会被合并进每个
发出的请求,因此你无需改源码即可发送这些字段。
```bash
ocr config set llm.extra_body '{"thinking":{"type":"enabled","budget_tokens":2048}}'
```
## 选择语言
`language` key 只控制一件事:追加到评审和 `ocr llm test` prompt 中每条
system-role 消息的一条指令。注入的精确字符串是:
```
\n\nAlways respond in <language>.
```
- *未设置*或为空——按 `English` 对待。
- `Chinese``English` 或任何其他字符串——原样透传。
内置 rule docs 不支持语言切换。`internal/config/rules/rule_docs/` 下嵌入的文件按
固定文件名加载,多数以中文撰写(`default.md` 是英文例外);无论 `language`
如何设置,它们都原样出现在 prompt 中。因此当 `language` 设为 `English`prompt
里会是一条英文指令叠加大段中文 rule 文本——强模型会遵从指令产出英文评论,
弱模型可能输出中英混杂的内容。
`language` 没有环境变量、CLI 参数或项目级覆盖——唯一能设置它的地方是全局
`~/.opencodereview/config.json`,通过
[`ocr config set`](#ocr-config-set--managing-opencodereviewconfigjson)
```bash
ocr config set language English
```
如果你需要纯英文 rule 文本,请通过 `--rule``<repo>/.opencodereview/rule.json`
`~/.opencodereview/rule.json` 提供自己的规则(见
[评审规则](../review-rules/#priority-chain))。
## 项目级 vs 全局配置
CLI 本身是全局配置的(`~/.opencodereview/config.json`)——没有项目级 LLM 配置。
但**评审规则**是项目级的;见[评审规则](../review-rules/#priority-chain)。
## 另见
- [快速开始](../quickstart/)——最小化设置与首次评审。
- [CLI 参考](../cli-reference/)——review 命令接受的每个参数。
- [遥测](../telemetry/)——如何接入 OTLP / console exporter。

View file

@ -0,0 +1,201 @@
---
title: 贡献
sidebar:
order: 13
---
OCR 是 Apache-2.0 许可下的开源项目。欢迎 bug 报告、文档修复与代码贡献。本页为
快速参考;权威版本位于
[`CONTRIBUTING.md`](https://github.com/alibaba/open-code-review/blob/main/CONTRIBUTING.md)。
## 贡献方式
不写 Go 也能帮上忙:
- **Bug 报告**——开一个带复现步骤的
[GitHub issue](https://github.com/alibaba/open-code-review/issues/new/choose)。
- **功能请求**——在
[Discussions](https://github.com/alibaba/open-code-review/discussions/categories/ideas)
开帖或开 feature-request issue。
- **文档**——错别字、缺失示例、失效链接——这些 PR 通常最快被合并。
- **评审其他 PR**——非维护者的评论有助于减轻评审者负担。
- **代码**——bug 修复、性能优化、新功能。
## 本地开发设置
### 前置条件
- [Go ≥ 1.25](https://go.dev/dl/)
- [Git](https://git-scm.com/)
- [Make](https://www.gnu.org/software/make/)
### 获取源码
```bash
# Fork on GitHub, then:
git clone https://github.com/<your-username>/open-code-review.git
cd open-code-review
git remote add upstream https://github.com/alibaba/open-code-review.git
make build # writes dist/opencodereview
make test # LC_ALL=C go test -v -race -count=1 ./...
```
> `upstream` remote 是只读的。推送到 `origin`(你的 fork并从那里发起 PR。
### 运行本地构建
```bash
./dist/opencodereview review --preview
```
为方便起见,在 `~/bin/ocr-dev` 放一个指向 `dist/opencodereview` 的符号链接,即可在
任意仓库调用 `ocr-dev`
### Make target
| Target | 作用 |
|---|---|
| `make build` | 为当前平台构建 → `dist/opencodereview`。 |
| `make build-darwin-amd64` | 交叉编译 macOS Intel。 |
| `make build-darwin-arm64` | 交叉编译 macOS Apple Silicon。 |
| `make build-linux-amd64` | 交叉编译 Linux x86_64。 |
| `make build-linux-arm64` | 交叉编译 Linux ARM64。 |
| `make build-windows-amd64` | 交叉编译 Windows x86_64。 |
| `make build-windows-arm64` | 交叉编译 Windows ARM64。 |
| `make build-all` | 全部六个交叉编译二进制linux/darwin/windows × amd64/arm64。 |
| `make sha256sum` | 为构建产物生成 `sha256sum.txt`。 |
| `make dist` | `clean → build-all → sha256sum`。CI 运行的内容。 |
| `make test` | 带 race 检测运行测试。 |
| `make clean` | 删除 `dist/`。 |
## 分支与提交约定
### 分支前缀
| 前缀 | 用途 |
|---|---|
| `feat/` | 新功能 |
| `fix/` | Bug 修复 |
| `docs/` | 仅文档 |
| `refactor/` | 无行为变更的重构 |
| `test/` | 仅测试变更 |
| `chore/` | 构建 / CI / 工具 |
```bash
git checkout main
git pull upstream main
git checkout -b feat/anthropic-streaming
```
### 提交信息
[Conventional Commits](https://www.conventionalcommits.org/) 格式:
```
<type>(<scope>): <short summary>
[optional body explaining the why]
```
示例:
```
feat(agent): add support for custom tool definitions
fix(llm): handle timeout errors in Anthropic API calls
docs(readme): clarify endpoint resolution priority
refactor(viewer): extract task-card rendering into helper
```
**PR 标题**也用相同格式,以便在生成的 changelog 中整洁显示。
## 项目结构
```
open-code-review/
├── cmd/opencodereview/ # CLI 入口——参数解析、分发
├── internal/
│ ├── agent/ # 评审 agent 逻辑、子 agent 分发
│ ├── config/ # 模板、规则、白名单、内嵌 JSON
│ ├── diff/ # Git diff 解析、三种模式
│ ├── gitcmd/ # Git 子进程运行器
│ ├── llm/ # LLM clientAnthropic 与 OpenAI、端点解析器
│ ├── model/ # 数据结构LlmComment、Diff……
│ ├── pathutil/ # 路径工具
│ ├── release/ # Release notes 生成
│ ├── session/ # JSONL 会话写入器
│ ├── stdout/ # 可静音的 stdout writer
│ ├── suggestdiff/ # 建议 diff 渲染
│ ├── telemetry/ # OpenTelemetry 配置 + 辅助
│ ├── tool/ # 工具注册表 + provider 实现
│ └── viewer/ # 内嵌 HTTP UI
├── pages/ # WebUI 营销页(独立 React app
├── plugins/ # Claude Code slash 命令
├── extensions/ # 编辑器扩展VS Code
├── examples/ # CI 配方GitHub Actions、GitLab CI
├── skills/ # Agent SDK skill manifest
├── scripts/ # NPM postinstall + 跨平台构建脚本
├── npm/ # 各平台 optional dependency 包
└── bin/ # NPM wrapperNode
```
多数贡献触及 `internal/agent/``internal/tool/``internal/llm/`
`cmd/opencodereview/` 中的 CLI 层有意保持精简——参数解析后分发到 agent 包。
## 代码质量检查
开 PR 前:
```bash
go fmt ./...
go vet ./...
make test # race-enabled, runs in CI on every push
make build # smoke test the binary builds
```
CI 在每次推送时运行同一套,不会有意外。
## 添加新工具
一个工具有两部分:
1. [`internal/config/toolsconfig/tools.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/toolsconfig/tools.json)
中的 **JSON 定义**name、description 与 LLM 看到的 JSON-schema 参数。
2. 在 `internal/tool/definitions.go` 注册的 **Go provider**,含实际实现。
两者都存在,新工具名才能工作。现有六个见[工具](../tools/),可当作模板。
## 添加新规则模式
编辑 `internal/config/rules/system_rules.json` 把新 glob 映射到规则文档,并在
`internal/config/rules/rule_docs/` 下添加对应 markdown。规则文档按模式一个文件存放
(英文)。`language` 配置只在 system prompt 追加一条指示模型以该语言响应的指令;
它不会切换 rule-doc 文件。
## PR 流程
1. **大改动先开 issue。** 提前对齐方向,好过在代码评审时才发现分歧。
2. **每个 PR 一个逻辑变更。** 若有两个无关修复,提两个 PR。
3. **更新测试。** 行为变更需测试覆盖——`make test` 必须通过。
4. **更新文档。** 若变更影响参数、config key 或评审流水线,同时更新本文档站
(在 [`docs/`](https://github.com/alibaba/open-code-review))与任何相关内联帮助。
5. **填写 PR 模板。** 维护者会评审,通常几个工作日内。
## 贡献者许可协议CLA
本项目要求 Alibaba Open Source CLA。首次开 PR 时会有 bot 贴链接——电子签署
(一分钟)。后续 PR 无需重签。
## 首次贡献?
找标了
[`good first issue`](https://github.com/alibaba/open-code-review/labels/good%20first%20issue)
或 [`help wanted`](https://github.com/alibaba/open-code-review/labels/help%20wanted)
的 issue。多数体量小且自包含issue 描述里有足够上下文,便于上手。
## 另见
- [架构](../architecture/)——修改 `internal/agent/` 前你需要的心智模型。
- [工具](../tools/)——现有工具长什么样。
- 完整贡献指南:
[CONTRIBUTING.md](https://github.com/alibaba/open-code-review/blob/main/CONTRIBUTING.md)

View file

@ -0,0 +1,283 @@
---
title: FAQ
sidebar:
order: 14
---
常见错误、意外与“这应该这样吗?”的问题。若你的问题不在此处,开一个带运行步骤
与完整输出的 [GitHub issue](https://github.com/alibaba/open-code-review/issues)。
## 配置与启动
### `no valid LLM endpoint configured`
```
no valid LLM endpoint configured; one of OCR_LLM_URL/OCR_LLM_TOKEN/OCR_LLM_MODEL,
~/.opencodereview/config.json, or ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/
ANTHROPIC_MODEL must be set
```
OCR 走完了四来源解析链([配置](../configuration/#endpoint-resolution))但没
找到完整的 `(URL, token, model)` 三元组。要么:
- 运行 `ocr config set llm.url …` / `llm.auth_token …` / `llm.model …` 填充
`~/.opencodereview/config.json`**或**
- 导出 `OCR_LLM_URL` / `OCR_LLM_TOKEN` / `OCR_LLM_MODEL`**或**
- 若你已在用 Claude Code导出 `ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN` /
`ANTHROPIC_MODEL`
然后 `ocr llm test` 验证连通性再重试评审。
### `ocr llm test` 显示错误的来源
OCR 取**第一个**完整三元组,而非最后一个。因此若配置文件已有全部三个 llm.*
key环境变量会被忽略。要让环境变量生效删除配置 key删除文件或手动 unset
`ocr config set` 切换到新值。
### `ocr llm test` 返回 401 / 403
token 缺少 scope、已过期或厂商不匹配。Anthropic 与 OpenAI 用不同的 auth header 与
URL 格式——确保 `llm.use_anthropic` 与你指向的 URL 相匹配:
- AnthropicURL 以 `/v1/messages` 结尾,`use_anthropic=true`
- OpenAI / OpenAI 兼容URL 以 `/v1/chat/completions` 结尾,
`use_anthropic=false`
### `not a git repository`
`ocr review` 对当前目录运行 `git diff`(以及对 untracked 文件的 `git ls-files`)。
若你不在 Git 工作树内,它会提前退出。要么 `cd` 进仓库,要么传 `--repo /path/to/repo`
## 过滤与规则
### 我的文件没被评审
运行 `ocr review --preview`(无 LLM 成本)。输出列出每个候选文件及其被保留或
丢弃的**原因**
```
src/foo.go modified
src/foo_test.go modified (excluded: user_exclude)
node_modules/lib.js added (excluded: default_path)
imgs/logo.png binary (excluded: unsupported_ext)
```
五种排除原因对应[文件过滤](../review-rules/#how-files-are-filtered)中的门:
| 原因 | 修复 |
|---|---|
| `binary` | 无需处理——二进制文件无可评审文本。 |
| `user_exclude` | 从你的 `exclude` 列表移除该模式。 |
| `unsupported_ext` | 把扩展名加入你的 `include` 列表以绕过白名单门。 |
| `default_path` | 把文件加入 `include`——那会覆盖内置测试文件排除模式。 |
| `deleted` | 无需处理——没有新内容可评审。 |
### 我的自定义规则没触发
运行 `ocr rules check <file-path>`。它会完整打印匹配的**层**与 **glob 模式**
```
File: src/api/UserHandler.go
Source: Project (.opencodereview/rule.json)
Pattern: src/api/**/*.go
Rule: …
```
若层不对(如期望项目规则却显示 "System built-in"),多半是**声明顺序**问题——
首条匹配模式生效。把更具体的规则在 `rules` 数组里前移,或修正 glob。
### 花括号展开不工作
`bmatcuk/doublestar/v4` 支持 `{ts,tsx}` 花括号。若不匹配,检查多余空格——
`{ts, tsx}` 带空格会静默地无法匹配 `tsx`
## 评审
### 某文件显示零评论——它真的被评审了吗?
打开[会话查看器](../viewer/)`ocr viewer`),找到会话,看该文件的
`main_task` 泳道:
- 有工具调用 + 以 `task_done` 结束 → 干净评审。
- 有工具调用 + 循环中途结束 → 找错误卡片。
- 完全没有 `main_task` 卡片 → 文件评审前被过滤;见上方[过滤与规则](#filtering--rules)。
### 评论的 `start_line: 0``end_line: 0`
OCR 无法把评论锚定到 diff 中的精确行。两个常见原因:
- 模型改写了 `existing_code` 而非从 diff 原样复制。模型被告知不要这样做,但偶尔
仍会如此。
- diff 有异常格式CRLF、tab/空格混用)破坏了滑动窗口匹配。
评论仍是真实的——只是没被自动放置。多数 agent 集成SKILL、Claude Code
plugin`existing_code` 字段并自行在文件中定位。
### Token threshold exceeded
```
[ocr] WARNING: prompt tokens (94000) exceed 80% of max_tokens(58888) for src/big.sql
```
该文件的初始 prompt规则 + diff + change-files 列表)在模型能响应之前就已超过
`MAX_TOKENS = 58888` 的 80 %。OCR 跳过该文件并继续——JSON 模式下你也会在
`warnings` 中看到。
缓解:
- 若是自动生成的,把文件加入 `exclude` 列表。
- 把大重构拆成更小的 commit。
- 对一系列小 commit 用 `--commit` 模式,而非一次性工作区模式评审。
### plan 阶段花了很久而文件很小
先运行 `ocr review --preview`。若文件的 `lines.changed` 超过
`PLAN_MODE_LINE_THRESHOLD`(默认 **50**plan 阶段会运行。这是有意为之——大
diff 能从 plan 中受益。要为单次评审跳过它,用更小 diff 运行,或临时编辑内嵌模板
(高级;需覆盖 `--tools`)。
### "Max tool requests reached"
```
[ocr] Max tool requests reached for src/foo.go.
```
模型花了 30`MAX_TOOL_REQUEST_TIMES`)轮工具调用却没调 `task_done`。到那时为
止发出的评论仍被收集并渲染。若多数文件都这样,问题通常是:
- 模型不擅长遵循“完成后调 `task_done`”指令。换更强模型(如 Claude Opus
- 某工具持续报错而模型持续重试。看会话 JSONL——若同一工具结果重复即是原因。
- 文件确实大或上下文重30 轮不够。用 `--max-tools <n>` 调高或调低
(如 `--max-tools 40` 更多,`--max-tools 15` 更少。19 会被上调到 10
`0`(默认)用模板默认 30。
### 一些子 agent 失败;运行仍以 0 退出
有意为之。OCR 隔离 per-file 失败,使一个有问题的文件不会拖垮 20 文件的评审。只要*有*
成功的,聚合退出码就是 `0`;仅当完全失败(零成功子 agent才非零退出。查看 JSON
模式的 `warnings` 数组或文本模式的 stderr看哪些文件失败了。
### CI 运行比本地慢得多
两个常见原因:
- **模型速率限制**——限流下 LLM client 退避并重试。调低 `--concurrency`
(如 `4`)以免一开始就触限。
- **冷缓存**——若 provider 支持 prompt 缓存,部署后首次运行无法受益。同一窗口内
后续运行更快。
## 输出与集成
### `--audience agent` 仍有进度行
确认你看到的不是 **stderr**。进度消息偶尔会到 stderr警告、错误。`--audience
agent` 保证的干净 stdout 是*对解析器友好的*——要屏蔽一切,重定向:
`ocr review --audience agent 2>/dev/null`
### JSON 输出是 `{ "files_reviewed": 0, "comments": [] }`
工作区没有合格文件。这是有意为之——显式形状让调用方区分“无可评审内容”与“已评审
文件中无发现”。零评论的正常评审产出的是普通空数组 `[]`
### 会话 JSONL 在哪?
```
~/.opencodereview/sessions/<path-encoded-repo-path>/<session-id>.jsonl
```
仓库路径通过把 `/``\` 替换为 `-``:` 替换为 `_` 编码
(如 `/Users/foo/my-repo``Users-foo-my-repo`)。用 `ocr viewer` 浏览会话。
删除该目录清除历史OCR 在下次运行时重新生成编码路径。
## 性能与成本
### 怎么知道哪些 token 花了多少?
启用遥测:
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter console
ocr review
```
LLM 调用没有自己的 span——它们记为 metric。关注 `ocr.llm.tokens_used`
counter`model` + `type`)、`ocr.llm.requests_total`counter`model`
+ `status`)、`ocr.llm.request_duration_seconds`histogram`model`)。
console exporter 会内联打印这些聚合。如需仪表盘,切换到 OTLP exporter 并发到你的
metrics 体系——见[遥测](../telemetry/)。
### 为什么我的评审这么贵?
常见因素:
- 文件 ≥ 50 行时 plan 阶段开启。它每文件多一次 LLM 调用。降低阈值可减少成本;升高
阈值可提升小 PR 的速度。
- `MAX_TOOL_REQUEST_TIMES = 30` 很宽松。用满轮数的模型会产出比 3 轮就完成的模型
更长(更多 token的对话。更强模型倾向于更快完成。反过来若你为应对 "max tool
requests reached" 用 `--max-tools` 调高,预期每文件成本大致线性增长。
- 记忆压缩本身是一次 LLM 调用。较长的子任务除评审轮外,还要为压缩轮付费。
### 如何减少 LLM 调用?
- 添加 `include` 列表,使 OCR 不评审你不关心的文件。
- 若你的账户有 burst-mode 计价,调低 `--concurrency`
- 传 `--background`——更充分的前期上下文有时能让模型无需 `file_read` /
`code_search` 往返即可完成。
## 隐私与安全
### OCR 会把我的代码发到别处吗?
OCR 把你的 **diff**(及可选 read-tool 片段)发到你配置的 LLM 端点。其余任何内容都不
离开你的机器——会话 JSONL 与规则文件仅存于本地。
若启用遥测,`content_logging` 标志已接入配置层但目前**不**控制任何代码路径——
无论该标志值如何prompt 与响应内容绝不导出到你的 collector。请视为保留位。生产
环境保持 `false`。详情见[遥测](../telemetry/#content-logging)。
### 我能在发给 LLM 前脱敏 secret 吗?
非内置功能。推荐工作流:
1. 不要把 secret 提交到仓库(常规规则)。
2. 把已知含敏感信息的文件加入 `exclude`
3. 用 `git diff --no-textconv` 过滤器或 pre-commit 脱敏,使 secret 不进入 diff。
“脱敏规则”功能在路线图上;关注
[issue 跟踪器](https://github.com/alibaba/open-code-review/issues)。
## 杂项
### changelog 在哪?
[GitHub Releases](https://github.com/alibaba/open-code-review/releases)
——每个 release 都附带从 Conventional Commits 生成的 notes。
### OCR 支持非 Git VCS 吗?
不支持。diff provider 通过 shell 调用 `git`。SVN / Mercurial 等需要新的 providerHg 支持的
issue 已在[](https://github.com/alibaba/open-code-review/issues)开放。
### 为什么二进制叫 `opencodereview` 而 CLI 是 `ocr`
release 中发布的静态二进制以项目命名(`opencodereview`NPM wrapper 为了便于使用而安装为 `ocr`。从源码构建得到 `dist/opencodereview`——复制为 `$PATH` 上的
`ocr`
### 如何卸载?
```bash
npm uninstall -g @alibaba-group/open-code-review # NPM install
sudo rm /usr/local/bin/ocr # binary install
rm -rf ~/.opencodereview # all state
```
OCR 不在 `~/.opencodereview` 之外写入NPM 下载二进制除外),因此删除该目录即可
清除历史、配置与每用户规则。
## 另见
- [配置](../configuration/)——LLM 端点解析与 config key。
- [评审规则](../review-rules/)——文件过滤器与规则解析链。
- [会话查看器](../viewer/)——查看历史评审会话。
- [遥测](../telemetry/)——token 用量与 LLM 指标。

View file

@ -0,0 +1,177 @@
---
title: 安装
sidebar:
order: 4
---
安装 `ocr` CLI 有四种受支持的方式。它们产出的都是同一个二进制——按你的
环境选择即可。
## NPM推荐
```bash
npm install -g @alibaba-group/open-code-review
```
NPM 包附带一个小的 wrapper 脚本(`bin/ocr.js`)和一个
[postinstall hook](https://github.com/alibaba/open-code-review/blob/main/scripts/install.js)
它会:
1. 探测你的平台(`darwin-amd64``darwin-arm64``linux-amd64`
`linux-arm64``windows-amd64``windows-arm64`)。
2. 从 GitHub Releases 下载匹配的二进制。
3. (当存在校验和数据时)验证它,并放到 wrapper 旁边。
如果某个平台专属 npm 包(如 `@alibaba-group/ocr-darwin-arm64`)作为
optional dependency 被安装,则直接使用该二进制,跳过下载。
运行 `ocr`wrapper 只是 `exec` 下载好的二进制,因此首次运行后实际开销
为零。
### 更新
```bash
npm update -g @alibaba-group/open-code-review
# 或固定到某个版本:
npm install -g @alibaba-group/open-code-review@<version>
```
### 卸载
```bash
npm uninstall -g @alibaba-group/open-code-review
```
## GitHub Release 二进制
如果你不想装 Node.js可直接从
[releases 页面](https://github.com/alibaba/open-code-review/releases)获取
静态二进制:
```bash
# macOS (Apple Silicon)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# macOS (Intel)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux x86_64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux ARM64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Windows (AMD64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-amd64.exe
# Windows (ARM64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-arm64.exe
```
每个 release 还会在二进制旁发布 `sha256sum.txt`,供你校验完整性:
```bash
curl -LO https://github.com/alibaba/open-code-review/releases/latest/download/sha256sum.txt
shasum -a 256 -c sha256sum.txt --ignore-missing
```
## 安装脚本curl | sh
一个便捷安装器,封装了 GitHub Release 二进制下载(带校验)——适合 CI 基础
镜像和无界面环境:
```bash
curl -fsSL https://raw.githubusercontent.com/alibaba/open-code-review/main/install.sh | sh
```
它识别两个环境变量:
| 变量 | 默认值 | 用途 |
|---|---|---|
| `OCR_INSTALL_DIR` | `/usr/local/bin` | 放置 `ocr` 二进制的位置。 |
| `OCR_VERSION` | 最新 release | 固定到某个 release tag`v1.2.3`)。 |
该脚本支持 `darwin``linux``amd64` / `arm64`Windows 请改用
[GitHub Release 二进制](#github-release-binary)或 [NPM](#npm-recommended)
方式。
## 从源码构建
仅当你要修改 OCR 本身,或在某个没有预编译二进制的平台上运行时才需要此方式。
### 前置条件
- [Go ≥ 1.25](https://go.dev/dl/)
- [Git](https://git-scm.com/)
- [Make](https://www.gnu.org/software/make/)
### 构建
```bash
git clone https://github.com/alibaba/open-code-review.git
cd open-code-review
make build # 产出 dist/opencodereview
sudo cp dist/opencodereview /usr/local/bin/ocr
```
### 为其他平台构建
```bash
make build-linux-amd64
make build-linux-arm64
make build-darwin-amd64
make build-darwin-arm64
make build-windows-amd64 # Windows (x86_64)
make build-windows-arm64 # Windows (ARM64)
make build-all # 一次性构建全部六个
make sha256sum # 同时产出 sha256sum.txt
```
`make dist` 会运行 `clean → build-all → sha256sum`,并在二进制旁写入一个
`VERSION` 文件——这正是 release 流水线执行的步骤。
### 运行测试
```bash
make test # LC_ALL=C go test -v -race -count=1 ./...
```
## 验证安装
无论二进制来自哪里:
```bash
ocr version # 打印版本 + git commit + 构建日期
ocr --help # 顶层用法
ocr review --help # 完整的 review 命令参数列表
```
如果出现 "command not found" 错误,请确认安装位置在你的 `$PATH` 上:
```bash
which ocr
echo $PATH
```
## OCR 在哪里存放状态
| 路径 | 存放内容 |
|---|---|
| `~/.opencodereview/config.json` | LLM 端点、语言、遥测配置(由 `ocr config set` 管理)。 |
| `~/.opencodereview/rule.json` | 可选的全局评审规则。 |
| `~/.opencodereview/sessions/<encoded-repo-path>/<session-id>.jsonl` | 每次评审会话的流式 JSONL 转录,供 `ocr viewer` 使用。 |
| `~/.opencodereview/{last-update-check,update.lock,update-available}` | NPM wrapper 的后台更新检查状态。wrapper 会轮询是否有更新的 release默认约每 18 分钟一次)并打印升级提示。用 `OCR_NO_UPDATE=1` 禁用,或用 `OCR_UPDATE_INTERVAL`(秒)调整间隔。静态二进制不写入这些文件。 |
| `<repo>/.opencodereview/rule.json` | 可选的项目级评审规则——可安全提交。 |
OCR 永远不会写入 `~/.opencodereview/` 之外(除 NPM 临时下载二进制外)。
删除该目录即可完成干净的卸载。
## 另见
- [快速开始](../quickstart/)——配置 LLM 并完成首次评审。
- [配置](../configuration/)——OCR 接受的每个环境变量与 config key。
- [贡献](../contributing/)——从源码构建、跑测试并参与开发。

View file

@ -0,0 +1,56 @@
---
title: 集成
sidebar:
order: 12
---
OCR 是一个 CLI能与任何能派生进程的环境组合。本节涵盖将其接入 agentic
工作流和 CI 的主要方式,每种集成方式一页。
## 为什么是这些集成?
OCR 的 `--audience agent` 模式专为被另一个 agent 驱动而设计stdout 只携带
JSON / 最终摘要,无进度 UI。这让三种组合方式顺理成章
1. **Agent skill**——把 OCR 注册为调用方 agent 可调用的 skill如 Anthropic
Agent SDK
2. **CommandClaude Code plugin**——安装打包的命令,使
`/open-code-review:review` 端到端运行 `ocr review`。在任何其他支持
Claude-Code 风格命令约定的 agent 中也可用。
3. **Direct subprocess**——任何能调 `subprocess.run` 的框架LangChain 工具、
自定义 shell、CI 步骤)直接通过 shell 调用。
你可以混搭。skill 和 plugin 最终调用的都是同一个二进制。
## 选择模式
| 方式 | 最适合 | 页面 |
|---|---|---|
| Agent skill | 你基于 Anthropic Agent SDK 或其他消费 `SKILL.md` 的框架构建。 | [Agent Skill](agent-skill/) |
| CommandClaude Code plugin | 你用 Claude Code或任何有 Claude-Code 风格命令约定的 agent希望 `/open-code-review:review` 做正确的事。 | [CommandClaude Code Plugin](claude-code/) |
| Direct subprocess | 你需要从自定义脚本、LangChain 工具或非 Anthropic agent 调用 OCR。 | [Direct Subprocess](subprocess/) |
| CI/CD | 你希望 OCR 在每个 PR 或 pre-commit 时运行。 | [CI/CD](ci/) |
## MCP 怎么办?
OCR 目前不暴露 Model Context Protocol server。预期的集成方式是“agent 调用 CLI”
更简单,且能避免 MCP server 引入的长期运行进程问题。如果你的 agent 平台特别
要求 MCP用一个薄 shim 包裹 CLI——一个 30 行的 Node 脚本暴露单个 `review`
工具就够了。
## 适用于所有模式的提示
- **始终传 `--audience agent`**,当调用方不是人时。否则进度行会污染待解析的输出。
- **有 PR / 需求上下文时始终传 `--background`**。质量提升显著,成本只是一个工具
参数。
- **CI 中把 `--concurrency` 调低**`--concurrency 4`)以免触发厂商速率限制。默认 8。
- **CI 中优先 `--from origin/main --to HEAD`** 而非 `--commit HEAD`——merge-base
计算排除分支切出后落到 `main` 上的无关变更。
- **让 `OCR_LLM_TOKEN` 远离 stdout/logs。** OCR 不打印它,但配置不当的 shell
可能泄露。使用 CI secret 掩码。
## 另见
- [CLI 参考](../cli-reference/)——review 命令的每个参数。
- [配置](../configuration/)——环境变量与 config key。
- [快速开始](../quickstart/)——首次评审的最小化设置。

View file

@ -0,0 +1,102 @@
---
title: Agent Skill
sidebar:
order: 1
---
把 OCR 注册为可调用的 skill使 agent 框架能以正确的参数、前置检查与分级标准
调用它——无需你在调用侧重新推导这些。
## 仓库里有什么
仓库在
[`skills/open-code-review/SKILL.md`](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md)
提供 SKILL manifest。它把 OCR 声明为可调用 skill含前置检查、调用工作流与
评论分级标准High/Medium/Low
## 安装
### 方式 1`npx skills add`(推荐)
在希望 skill 可用的项目内运行:
```bash
npx skills add alibaba/open-code-review --skill open-code-review
```
这从
[skills registry](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md)
拉取 manifest 并放入项目,使任何尊重 skills 约定的编码 agent 在下次调用时加载
它。重新运行该命令以更新 skill 到最新版本。
> **前置条件:** 首次运行时 skill 会自行安装 `ocr` CLI
> (通过 `npm install -g @alibaba-group/open-code-review`),前提是二进制不在
> `PATH` 上——见[skill 做什么](#what-the-skill-does)。你**确实**需要预先配置好
> LLMskill 无法替你完成,会停下来询问。见[配置](../../configuration/)。
### 方式 2手动复制系统级
若想全局安装 skill 而非按项目,把文件夹复制进你的 skills 目录:
```bash
mkdir -p ~/.claude/skills
cp -R /path/to/open-code-review/skills/open-code-review ~/.claude/skills/
```
这使 skill 在机器上每个项目可用。
## skill 做什么
SKILL.md 是一个 prompt当调用方 agent 加载它时,由 agent 自身执行步骤。一次
完整的 `/open-code-review`(或等价)请求流程如下展开:
1. **前置检查。** 运行 `which ocr` 确认 CLI 在 `PATH` 上,再 `ocr llm test`
确认 LLM 可达。
2. **CLI 缺失则自动安装。**`which ocr` 报告 "NOT INSTALLED"agent 运行
`npm install -g @alibaba-group/open-code-review` 并继续。不提示用户——这被视为
常规设置步骤。
3. **无 LLM 配置则停下询问。**`ocr llm test` 失败agent *不会* 编造凭证。
它向用户展示两种受支持的方式(环境变量或 `ocr config set …`)并等待用户提供
API key。
4. **提取业务上下文。** 检查评审目标commit、分支、工作副本并生成一个简短的
`--background` 字符串。
5. **运行评审。** 调用
`ocr review --audience agent --background "…" [--commit | --from/--to]`
根据用户是要评审工作副本、特定 commit 还是分支区间来选择参数。
6. **分类与报告。** 用 SKILL.md 中的标准把 JSON 评论分为 **High** /
**Medium** / **Low**bug 与安全问题为 High吹毛求疵与疑似误报被静默丢弃
再渲染 Markdown 摘要。
7. **按需修复。** 若用户说“评审**并**修复”(或类似),对 High/Medium 项内联
应用安全修复;否则修改代码前先询问。
完整 prompt——包括确切分级标准、输出模板与注意事项——位于
[`skills/open-code-review/SKILL.md`](https://github.com/alibaba/open-code-review/blob/main/skills/open-code-review/SKILL.md)。
如想收紧上述任一项(比如把默认行为改为修复前总先询问),编辑你本地副本。
## Anthropic Agent SDK
把你的 SDK init 指向已安装的 skill 路径:
```python
from anthropic_agent_sdk import Agent
agent = Agent(
skill_paths=["/path/to/open-code-review/skills/open-code-review"],
)
agent.run("Review my staged changes — focus on race conditions.")
```
SDK 加载 SKILL.md prompt由 agent 执行[skill 做什么](#what-the-skill-does)中
所述工作流——包括 `npm install` 回退与无 LLM 配置时提示输入凭证的步骤。
## 其他 agent 框架
任何有“注册外部 skill”接口的框架都能摄入 SKILL.md——它只是带 frontmatter 的
markdown。若你的框架期望不同 schemamarkdown 正文仍可用作 prompt 模板。
## 另见
- [CommandClaude Code Plugin](../claude-code/)——同一 skill 的
slash-command 版本。
- [Direct Subprocess](../subprocess/)——绕过 manifest自行调用 CLI。

View file

@ -0,0 +1,406 @@
---
title: CI/CD
sidebar:
order: 4
---
在每个 Pull Request 或 Merge Request 上运行 OCR。上游仓库提供两条现成流水线
你复制并配置即可——一条 GitHub Actions一条 GitLab CI。两者都是
[Direct Subprocess](../subprocess/)中核心命令的薄包装。
## CI/CD 集成如何工作
本页每条配方都遵循同一模式——下面的 GitHub Actions 与 GitLab CI 章节只是它的
具体实现:
1. **在 PR / MR 事件上触发。** 新建 pull request、更新的 merge request或手动
`/open-code-review` 评论触发作业。
2. **在 runner 中安装 `ocr`**,通常是
`npm install -g @alibaba-group/open-code-review`。runner 是临时的,因此每次
运行都发生。
3. **从 CI secret 经 `ocr config set` 配置 LLM**端点、token、model。没有持久
`~/.opencodereview` 可回退。
4. **以区间模式运行评审**,输出机器可读,使 stdout 是干净的 JSON 外壳:
```bash
ocr review \
--from "origin/<base-branch>" \
--to "origin/<head-branch>" \
--format json \
--audience agent
```
`--format json` 给出可解析载荷;`--audience agent` 屏蔽进度行。每条配方消费的
外壳见 [JSON 结构](../subprocess/#json-shape)。
5. **解析 JSON** 并遍历 `comments[]`
6. **通过 provider 的 review API 把评论回贴到 PR / MR。** 无有效行信息的条目
(文件级发现)合并到摘要备注而非内联张贴;若内联批量 API 拒绝请求,张贴步骤也
回退为普通摘要评论。
始终涉及两类凭据OCR 用来生成发现的 **LLM 凭据**,以及张贴步骤用来回贴评论的
**PR/MR 写 token**。GitHub 配方通过 `GITHUB_TOKEN` 自动提供后者GitLab 建议显式
配置 `GITLAB_API_TOKEN`,但对 fork MR 会回退使用内置 `CI_JOB_TOKEN`(它可通过
`/discussions` 发起讨论)——为可靠性推荐使用专用 token。
## GitHub Actions
上游工作流位于
[`examples/github_actions/ocr-review.yml`](https://github.com/alibaba/open-code-review/blob/main/examples/github_actions/ocr-review.yml)。
### 它做什么
- 在 `pull_request_target``opened`**和** `issue_comment` 事件上触发,后者正文
`/open-code-review``@open-code-review` 开头——后者让评审者通过在 PR 上
评论按需重跑 OCR。`pull_request_target` 而非 `pull_request`,使即便从
fork 提交的 PR 也能用上 secretOCR 只读 diff不执行 PR 中的代码。)
- 通过 `npm install -g @alibaba-group/open-code-review` 安装 OCR
`ocr config set` 写配置,再以分支区间模式运行核心命令。
- 解析 JSON 外壳并通过 GitHub Pull Request Review API 把每条发现作为内联评审评论
张贴。无行信息的评论合并到摘要正文。若批量提交失败,回退为逐条张贴,并在摘要
评论中呈现统计。
### 安装
把工作流放进你的仓库:
```bash
mkdir -p .github/workflows
curl -o .github/workflows/ocr-review.yml \
https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/github_actions/ocr-review.yml
```
### 必需 secret
**Settings → Secrets and variables → Actions** 下设置:
| Secret | 必需 | 说明 |
|---|---|---|
| `OCR_LLM_URL` | 是 | LLM API 端点(如 `https://api.openai.com/v1/chat/completions`)。 |
| `OCR_LLM_AUTH_TOKEN` | 是 | LLM API 的认证 token。此 CI secret 传给 `ocr config set llm.auth_token`OCR 的直接环境变量是 `OCR_LLM_TOKEN`,不是 `OCR_LLM_AUTH_TOKEN`。) |
| `OCR_LLM_MODEL` | 否 | 模型名。无默认——必须显式设置。 |
| `OCR_LLM_USE_ANTHROPIC` | 否 | Anthropic Claude 模型设为 `true`。 |
`GITHUB_TOKEN` 自动提供;工作流声明 `pull-requests: write` 以便张贴评审评论。
> 工作流启动时还会运行
> `ocr config set llm.extra_body '{"thinking": {"type": "disabled"}}'`
> 为不支持该字段的 LLM provider 关闭 thinking-mode 请求。若你的 provider 需保留
> thinking-mode删除该行。
### 定制
以下都是对你刚复制的工作流文件
`.github/workflows/ocr-review.yml`)的编辑。
#### 背景上下文
`--background` 是效果最显著的单一参数——见
[适用于所有模式的提示](../#tips-that-apply-to-every-pattern)。
传入 PR 标题(当标题遵循 `feat(auth): add OAuth2 support` 这样的语义约定时,效果
更好):
```yaml
- name: Run OCR review
run: |
ocr review \
--background "${{ github.event.pull_request.title }}" \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}" \
--format json --audience agent
```
#### 自定义规则
`--rule` 传入项目专属规则文件:
```yaml
- name: Run OCR review
run: |
ocr review --rule ./my-rules.json \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}"
```
schema 见[评审规则](../../review-rules/)。
#### 并发
默认 8 个并行 per-file 子 agent。大 PR 上调低,以免触发 LLM provider 速率限制:
```yaml
- name: Run OCR review
run: |
ocr review --concurrency 5 \
--from "origin/${{ github.base_ref }}" \
--to "origin/${{ github.head_ref }}"
```
#### 触发模式
默认工作流在 PR **opened** 时以及以 `/open-code-review`
`@open-code-review` 开头的 PR 评论时触发。两种常见调整:
在更多 PR 生命周期事件上运行(如推送新 commit 时复审):
```yaml
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
```
使用不同评论关键字:
```yaml
if: |
github.event_name == 'pull_request' ||
(github.event_name == 'issue_comment'
&& github.event.issue.pull_request
&& startsWith(github.event.comment.body, '/review'))
```
`github.event.issue.pull_request` 检查确保评论在 PR 上而非普通 issue 上。
#### 固定 OCR 版本
默认工作流安装最新发布版本。固定:
```yaml
- name: Install OpenCodeReview
run: npm install -g @alibaba-group/open-code-review@1.0.0
```
#### 以 GitHub App 身份发布
默认评审评论来自 `github-actions[bot]`。要以 `OpenCodeReview Bot` 这类自定义品牌的 bot 发布,把 `GITHUB_TOKEN` 换成 GitHub App installation token。
1. 在 *Settings → Developer settings → GitHub Apps → New GitHub App* **创建
app**。禁用 webhook此用例不需要。在 *Repository permissions* 授予:
- **Pull requests**Read and write
- **Contents**Read-only用于取 diff
- **Metadata**Read-only必需
2. 从 app 设置页**生成私钥**并下载 `.pem` 文件。记下同页的 **App ID**
3. 把 app **安装**到你想 OCR 评审的仓库。Installation ID 出现在安装后 URL 中,
`https://github.com/settings/installations/12345` → ID 为 `12345`
4. 在 *Settings → Secrets and variables → Actions* 下**添加三个 secret**
| Secret | 值 |
|---|---|
| `GITHUB_APP_ID` | App ID。 |
| `GITHUB_APP_PRIVATE_KEY` | `.pem` 文件全部内容,含 `-----BEGIN RSA PRIVATE KEY-----``-----END RSA PRIVATE KEY-----` 行。 |
| `GITHUB_APP_INSTALLATION_ID` | Installation ID。 |
5. 在评论张贴步骤中**生成并使用 token**
```yaml
- name: Get GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.GITHUB_APP_ID }}
private-key: ${{ secrets.GITHUB_APP_PRIVATE_KEY }}
- name: Post review comments to PR
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
# ...existing post script...
```
评审现在会以你 app 的名字而非 `github-actions[bot]` 发布。
### 故障排查
| 症状 | 原因 / 修复 |
|---|---|
| `Cannot find merge-base` | checkout 步骤用了浅克隆,但区间模式评审需要完整历史。上游工作流在 `actions/checkout` 上设 `fetch-depth: 0`——编辑文件时保留该设置。 |
| `Failed to parse OCR output` | `OCR_LLM_URL``OCR_LLM_AUTH_TOKEN` 缺失或错误。在 *Settings → Secrets and variables → Actions* 下复查值。 |
| 评审评论落到错误行 | 通常意味着评审开始到评论张贴之间 diff 发生了偏移。张贴脚本此时回退为普通 issue 评论——无需处理。 |
> **注意。** `OCR_DEBUG` 环境变量目前在 OCR 中**未实现**——设置
> `OCR_DEBUG: "1"` 无效。此处记录以备将来接入。当前若需详细输出,可检查工作流写
> 到 `/tmp/ocr-result.json``/tmp/ocr-stderr.log` 的原始评审 JSON 和 stderr
> (见下方故障排查),或本地运行 `ocr review`
## GitLab CI
上游流水线位于
[`examples/gitlab_ci/.gitlab-ci.yml`](https://github.com/alibaba/open-code-review/blob/main/examples/gitlab_ci/.gitlab-ci.yml)。
### 它做什么
- 在 `merge_requests` 事件上触发(所有 MR 事件——创建、更新、重开)。
- 在 `node:20` 镜像中运行,安装 OCR通过 `ocr config set` 配置,再以 MR diff 模式
运行核心命令。
- 用内联 Python 脚本解析 JSON 外壳,把每条发现作为 GitLab Discussion在 diff
上内联)张贴,用 MR 的 `versions` 端点计算正确的 `base_sha` / `start_sha` /
`head_sha` 以精确定位。对无法内联张贴的评论回退为普通 MR note并以摘要 note
收尾。
### 安装
把流水线放进仓库根:
```bash
curl -o .gitlab-ci.yml \
https://raw.githubusercontent.com/alibaba/open-code-review/main/examples/gitlab_ci/.gitlab-ci.yml
```
若已有 `.gitlab-ci.yml` 并想保留,把配方放到其他路径并用 `include:`
引入:
```yaml
include:
- local: 'ci/ocr-review.gitlab-ci.yml'
```
### 必需 CI/CD 变量
**Settings → CI/CD → Variables** 下设置:
| 变量 | 必需 | 掩码 | 说明 |
|---|---|---|---|
| `OCR_LLM_URL` | 是 | 否 | LLM API 端点 URL。 |
| `OCR_LLM_AUTH_TOKEN` | 是 | 是 | API 认证 token。此 CI 变量传给 `ocr config set llm.auth_token`OCR 的直接环境变量是 `OCR_LLM_TOKEN`,不是 `OCR_LLM_AUTH_TOKEN`。) |
| `OCR_LLM_MODEL` | 否 | 否 | 模型名。无默认——必须显式设置。 |
| `GITLAB_API_TOKEN` | 否 | 是 | 带 `api` scope 的 project / personal / group access token。可选——缺失时回退使用内置 `CI_JOB_TOKEN`(如对 fork MR。为可靠性推荐专用 `GITLAB_API_TOKEN`。 |
> GitLab 拒绝短于 8 字符的变量,因此流水线中 `llm.use_anthropic` 硬编码为
> `false`。要用 Anthropic Claude 模型,直接编辑脚本。
> 流水线启动时还会运行
> `ocr config set llm.extra_body '{"thinking": {"type": "disabled"}}'`
> 为不支持该字段的 LLM provider 关闭 thinking-mode 请求。若你的 provider 需保留
> thinking-mode删除该行。
> **快速 bot 命名提示。** 对 Project Access Token 和 Group Access Token
> token 的**名字**会出现在 MR 讨论旁。把 token 命名为 `OpenCodeReview Bot`
> 即可让评审讨论带上品牌名,无需额外设置——当你不需要
> [以服务账号身份发布](#post-under-a-service-account-identity)中记录的更持久
> 服务账号设置时很方便。
### 定制
以下都是对你刚复制的 `.gitlab-ci.yml` 的编辑。
#### 背景上下文
把 MR 标题传给 `--background`——当标题遵循 `feat(auth): add OAuth2 support`
这样的语义约定时,效果更好:
```yaml
script:
- |
ocr review \
--background "$CI_MERGE_REQUEST_TITLE" \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}" \
--format json --audience agent
```
#### 自定义规则与并发
与 GitHub Actions 配方相同的参数——`--rule` 传项目专属规则文件,
`--concurrency` 限制并行子 agent默认 8
```yaml
script:
- |
ocr review --rule ./my-rules.json --concurrency 5 \
--from "origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME" \
--to "${CI_COMMIT_SHA}"
```
规则 schema 见[评审规则](../../review-rules/)。
#### 固定 OCR 版本
```yaml
script:
- npm install -g @alibaba-group/open-code-review@1.0.0
```
#### 避免每次推送都复审
`only: [merge_requests]` 在**每次** MR 更新时触发,对长生命周期 MR 会消耗大量
LLM token。GitLab 无原生“仅在创建时”事件,因此推荐模式是运行评审前检测已有
OCR note若有则跳过。把 `ocr review` 调用替换为 Python wrapper
```python
import json, os, sys, urllib.request
GITLAB_URL = os.environ.get("CI_SERVER_URL", "https://gitlab.com")
PROJECT_ID = os.environ["CI_PROJECT_ID"]
MR_IID = os.environ["CI_MERGE_REQUEST_IID"]
API_TOKEN = os.environ["GITLAB_API_TOKEN"]
url = (
f"{GITLAB_URL}/api/v4/projects/{PROJECT_ID}"
f"/merge_requests/{MR_IID}/notes?per_page=100"
)
req = urllib.request.Request(url, headers={"PRIVATE-TOKEN": API_TOKEN})
with urllib.request.urlopen(req) as resp:
notes = json.loads(resp.read().decode())
if any("OpenCodeReview" in n.get("body", "") for n in notes):
print("OCR already reviewed this MR. Skipping to save tokens.")
sys.exit(0)
# ...otherwise call `ocr review ...` as usual and write the JSON to
# the file the posting step expects.
```
要在此之后强制复审,从 MR 删除之前的 OCR note——下次流水线运行会看不到 OCR
note便会继续。
#### 自托管 GitLab
无需改代码。张贴脚本读 `CI_SERVER_URL`GitLab 在每个 runner 上自动设置),
因此开箱即可与你自己的实例通信。只需确保 `GITLAB_API_TOKEN` 由你的自托管实例签发,
而非 `gitlab.com`
#### 以服务账号身份发布
默认评审讨论出现在 `GITLAB_API_TOKEN` 所属用户名下。改用项目级服务账号,即可获得
`OpenCodeReview Bot` 这类自定义品牌的 bot 身份。
1. 在 *Project → Settings → Service Accounts → New service account* **创建服务
账号**。你选的名字(如 `OpenCodeReview Bot`)会出现在 MR 讨论旁。
2. 在 *Settings → Members → Invite member* **邀请它到项目**。搜索服务账号名并
分配 `Developer``Maintainer`——两者都有张贴讨论所需权限。
3. 在 *Settings → Service Accounts →(该账号)→ Add new token* **签发 access
token**。所需 scope`api`。立即复制 token——GitLab 只显示一次。
4. 在 *Settings → CI/CD → Variables* **替换 token 值**——用服务账号的 token
替换现有 `GITLAB_API_TOKEN` 值(变量名保持不变)。
讨论现在以服务账号名而非最初创建 token 的用户名发布。
### 故障排查
| 症状 | 原因 / 修复 |
|---|---|
| `Cannot find merge-base` | runner 用了浅克隆。上游流水线设 `GIT_DEPTH: 0` 强制完整克隆——编辑文件时保留该设置。 |
| 张贴时 `API error 403` | `GITLAB_API_TOKEN``api` scope、不是项目成员或——自托管时——由不同实例签发。以 `api` scope 重签并在 *Settings → CI/CD → Variables* 下重新添加。 |
| `Failed to parse OCR output` | `OCR_LLM_URL``OCR_LLM_AUTH_TOKEN` 错误。在 *Settings → CI/CD → Variables* 下复查值。 |
| 内联评论落到错误行 | GitLab 内联讨论要求精确 SHA 匹配;张贴脚本取 `versions` 元数据以得到正确的 `base_sha` / `start_sha` / `head_sha`。若某条发现仍无法锚定,回退为普通 MR note。 |
流水线把原始评审 JSON 写到 `/tmp/ocr-result.json`stderr 写到
`/tmp/ocr-stderr.log`。可在 debug 步骤中 cat 它们,检查 OCR 返回了什么:
```yaml
script:
- cat /tmp/ocr-result.json
- cat /tmp/ocr-stderr.log
```
## 另见
- [Direct Subprocess](../subprocess/)——两条流水线消费的 JSON 结构,从头写
CI 脚本时有用。
- [配置](../../configuration/)——OCR 接受的每个环境变量与 config key。

View file

@ -0,0 +1,102 @@
---
title: CommandClaude Code Plugin
sidebar:
order: 2
---
安装打包的命令,使 OCR 在 [Claude Code](https://docs.anthropic.com/en/docs/claude-code)
内端到端运行——评审 diff、分类发现并自动应用值得采纳的修复。
## 仓库里有什么
仓库在
[`plugins/open-code-review/`](https://github.com/alibaba/open-code-review/tree/main/plugins/open-code-review)
下提供 Claude Code plugin。命令 prompt 本体位于
[`plugins/open-code-review/commands/review.md`](https://github.com/alibaba/open-code-review/blob/main/plugins/open-code-review/commands/review.md)
是下述工作流的权威依据。
## 安装
### 方式 1plugin marketplace推荐
在 **Claude Code 内**运行这两条命令:
```bash
/plugin marketplace add alibaba/open-code-review
/plugin install open-code-review@open-code-review
```
这会注册 `/open-code-review:review` slash 命令,并保持可通过 `/plugin` 更新。
### 方式 2直接复制命令文件
若想跳过 plugin marketplace把命令文件直接放进 `.claude/commands/`。这会注册为
`/open-code-review`(无 `:review` 后缀)。
**项目级**(随仓库提交,团队共享):
```bash
mkdir -p .claude/commands
curl -o .claude/commands/open-code-review.md \
https://raw.githubusercontent.com/alibaba/open-code-review/main/plugins/open-code-review/commands/review.md
```
**用户级**(机器上每个项目可用):
```bash
mkdir -p ~/.claude/commands
curl -o ~/.claude/commands/open-code-review.md \
https://raw.githubusercontent.com/alibaba/open-code-review/main/plugins/open-code-review/commands/review.md
```
### 其他支持命令的 agent
命令文件是带单个 frontmatter 字段的纯 markdown——没有任何 Claude Code 专有
内容。如果你的 agent 支持类似的 **command** 约定(从目录加载为可调用命令的
markdown prompt上面的文件复制方法就是安装路径`open-code-review.md`
放进你的 agent 读取命令的目录,按你的 agent 调用命令的方式调用它。prompt 正文
与 agent 无关——它只告诉模型选哪些 `ocr` 参数以及如何分级输出。
> **前置条件:** 首次运行时命令会自行安装 `ocr` CLI
> (通过 `npm install -g @alibaba-group/open-code-review`),前提是二进制不在
> `PATH` 上。你**确实**需要预先配置好 LLM——若 `ocr llm test` 连不上,命令会
> 失败。见[配置](../../configuration/)。
## 使用
在 Claude Code 中按名调用命令。通过 plugin marketplace 安装的用
`/open-code-review:review`,直接复制文件的用 `/open-code-review`
```
/open-code-review:review
/open-code-review:review review this PR against main
/open-code-review:review focus on race conditions in commit abc123
```
prompt 解析你的请求并选择正确的 `ocr review` 参数:无参数 → 工作区模式
staged + unstaged + untracked提到 commit → `--commit`,提到分支区间 →
`--from` / `--to`。你也可以直接透传 OCR 参数
(如 `/open-code-review:review --commit abc123``--from main --to feature`)。
## 命令做什么
命令 prompt 很短——三步:
1. **运行评审。** 用从你请求推断的参数调用 `ocr review --audience agent`
(描述了需求上下文时加可选 `--background`)。若 `ocr` 二进制不在 `PATH`
命令通过 `npm i -g @alibaba-group/open-code-review` 自动安装并继续。输出在 5
分钟超时内捕获。
2. **过滤与评估。** 把每条评论分为 **High** / **Medium** / **Low**。低置信
(疑似误报、吹毛求疵、缺上下文)评论被静默丢弃;其余展示。
3. **修复。** 对值得采纳的 High/Medium 项自动应用修复。与
[Agent Skill](../agent-skill/) 不同,此命令**默认自动修复**——它是“评审并
清理”工作流的合适选择,而非“给我看 diff”工作流。
若你想让命令在修改代码前先询问,或收紧分级标准,编辑你本地的 prompt 副本。Claude
Code 每次调用都重新读取命令,因此无需重启。
## 另见
- [Agent Skill](../agent-skill/)——SDK 级等价物;同一个底层 CLI不同默认值
(修复前先询问)。
- [Direct Subprocess](../subprocess/)——绕过 slash 命令,自行调用 CLI。

View file

@ -0,0 +1,164 @@
---
title: Direct Subprocess
sidebar:
order: 3
---
通过 shell 调用 `ocr` 并解析 JSON。这是最低层的集成路径——本站其他方式最终都归结
于它。[Agent Skill](../agent-skill/) 与 [Command](../claude-code/) 方式是告诉
调用方 agent 去做这件事的 prompt 模板;[CI/CD](../ci/) 配方是从脚本做同样事情的
GitHub Actions 和 GitLab CI 流水线——不涉及编排 agent只有子进程调用、JSON 解析、
把评论回贴到 PR / MR。当你从自定义脚本、LangChain 工具或任何其他尚未覆盖的
框架调用 OCR 时,直接用本页。
## Bash
```bash
result=$(ocr review --format json --audience agent)
status=$(echo "$result" | jq -r '.status')
total=$(echo "$result" | jq '.comments | length')
echo "Status: $status — $total comments"
echo "$result" | jq -r '.comments[] | "\(.path):\(.start_line) — \(.content)"'
```
## Python
```python
import json, subprocess
proc = subprocess.run(
["ocr", "review", "--format", "json", "--audience", "agent",
"--from", "origin/main", "--to", "HEAD",
"--background", pr_description],
capture_output=True, text=True, check=True,
)
data = json.loads(proc.stdout)
for c in data["comments"]:
if c["start_line"] > 0:
post_line_comment(c["path"], c["start_line"], c["content"])
```
## JSON 结构
OCR 发出单个顶层**对象**(不是裸数组)。下面是一个带一条发现的完整 `success`
外壳:
```json
{
"status": "success",
"summary": {
"files_reviewed": 1,
"comments": 1,
"total_tokens": 12770,
"input_tokens": 12450,
"output_tokens": 320,
"elapsed": "9s"
},
"comments": [
{
"path": "internal/cache/store.go",
"content": "Concurrent map access without a lock — wrap reads and writes with `sync.RWMutex` to avoid a race on the shared cache.",
"start_line": 42,
"end_line": 47,
"existing_code": "func (s *Store) Get(k string) string {\n return s.m[k]\n}",
"suggestion_code": "func (s *Store) Get(k string) string {\n s.mu.RLock()\n defer s.mu.RUnlock()\n return s.m[k]\n}",
"thinking": "The struct exposes `m map[string]string` without a guarding mutex, and Get/Set are called from concurrent request handlers."
}
]
}
```
### 顶层字段
| 字段 | 类型 | 总是存在 | 说明 |
|---|---|---|---|
| `status` | string | 是 | `success``completed_with_warnings``completed_with_errors``skipped` 之一。 |
| `message` | string | 否 | 简短人类可读摘要。在空运行或跳过时设置,如 `"No comments generated. Looks good to me."`。 |
| `summary` | object | 否 | 运行聚合。完成运行时存在;`skipped` 时省略。字段见下。 |
| `comments` | array | 是 | 可能为空。每条评论 schema 见下。 |
| `warnings` | array | 否 | 仅当一个或多个子 agent 失败或被跳过时存在。schema 见下。 |
### summary 结构(`summary`
| 字段 | 类型 | 说明 |
|---|---|---|
| `files_reviewed` | int | 通过所有过滤并发给模型的文件数。 |
| `comments` | int | 跨所有文件发出的评论总数(与 `comments.length` 一致)。 |
| `total_tokens` | int | 运行中每次 LLM 调用的 prompt + completion token 之和。 |
| `input_tokens` | int | 各次 LLM 调用的 prompt token含缓存读 token。 |
| `output_tokens` | int | 各次 LLM 调用的 completion token含缓存写 token。 |
| `cache_read_tokens` | int | 各次 LLM 调用的缓存读 token 总数。为零时省略(`omitempty`)。 |
| `cache_write_tokens` | int | 各次 LLM 调用的缓存写 token 总数。为零时省略(`omitempty`)。 |
| `elapsed` | string | 挂钟时长,取整到整秒,由 Go 的 `time.Duration.String()` 格式化(如 `"1m12s"`)。 |
### 每条评论字段(`comments[]`
| 字段 | 类型 | 总是存在 | 说明 |
|---|---|---|---|
| `path` | string | 是 | 仓库相对文件路径。 |
| `content` | string | 是 | 评审评论Markdown。 |
| `start_line` | int | 是 | 受影响范围的首行。值 `< 1` 表示评论无行锚点(文件级)——应把这些合并到摘要中,而非尝试内联张贴。 |
| `end_line` | int | 是 | 受影响范围的末行。单行评论时与 `start_line` 相等。 |
| `existing_code` | string | 否 | 要被替换的原始代码片段。对于无 diff 的建议性评论则省略。 |
| `suggestion_code` | string | 否 | `existing_code` 的提议替换。存在时总是与 `existing_code` 配对。 |
| `thinking` | string | 否 | 模型推理轨迹。对分级 / 调试有用;展示给用户前可安全丢弃。 |
### warnings 结构(`warnings[]`
一个跳过或部分文件失败的运行形如:
```json
{
"status": "completed_with_errors",
"message": "Some files could not be reviewed due to errors.",
"comments": [],
"warnings": [
{
"file": "src/very_long_file.go",
"message": "diff size exceeds 80% of MAX_TOKENS; skipped",
"type": "token_threshold_exceeded"
},
{
"file": "src/broken.py",
"message": "sub-agent failed: context deadline exceeded",
"type": "subtask_error"
}
]
}
```
| 字段 | 类型 | 说明 |
|---|---|---|
| `file` | string | 触发警告的文件的仓库相对路径。 |
| `message` | string | 简短人类可读描述。 |
| `type` | string | 用于过滤的稳定类型。当前发出:`subtask_error`(子 agent 运行失败)和 `token_threshold_exceeded`diff 对模型来说过大)。 |
`warnings` 含至少一个 `subtask_error` 时,`status`
`completed_with_errors`;否则为 `completed_with_warnings`
### 无 severity / priority 字段
OCR **不**发出 `severity``priority` 字段。你在 [Agent Skill](../agent-skill/)
和 [Command](../claude-code/) 文档中看到的 High/Medium/Low 分级是调用方 agent
收到原始评论后添加的——不要尝试 `jq '.comments[].severity'`,它不存在。
## 空结果处理
**没有合格文件**的工作区通过 `status` 报告,以便调用方区分“无变更”与“无发现”:
```json
{
"status": "skipped",
"message": "No supported files changed.",
"comments": []
}
```
断定“全部干净”之前,始终检查 `status == "skipped"`
## 另见
- [CI/CD](../ci/)——在子进程调用之上构建的现成 GitHub Actions 与 pre-commit
配方。
- [Agent Skill](../agent-skill/)——当调用方是 Anthropic SDK agent 而非普通
脚本时。

View file

@ -0,0 +1,125 @@
---
title: 概览
sidebar:
order: 2
---
## 什么是 Open Code Review
Open Code Review简称 **OCR**,区别于光学字符识别 Optical Character
Recognition是一个 AI 驱动的代码评审 CLI
[`@alibaba-group/open-code-review`](https://www.npmjs.com/package/@alibaba-group/open-code-review)
NPM 包和独立的 Go 二进制形式发布。CLI 二进制名为 `ocr`
只需一条命令(`ocr review`),它会:
1. 解析 Git diff——工作区、分支区间或单个 commit。
2. 结合系统默认规则与用户规则对变更文件进行过滤。
3. 为每个变更文件并行启动一个 **per-file 子 agent**
4. 每个子 agent 运行一个 LLM 工具调用循环;对于较大的 diff可选地先执行
**plan 阶段**
5. 模型调用 `code_comment` 记录发现,可选地调用 `file_read`
`code_search``file_find``file_read_diff` 收集上下文,完成后调用
`task_done`
6. OCR 将每条评论解析到精确的行号,对未能精确匹配的评论运行可选的重新定位
流程,并打印(或以 JSON 输出)最终列表。
## 通用 agent 的问题
如果你用过通用编码 agentClaude Code 的 Skill、Cursor、Cline 等)做代码
评审,很可能遇到过:
- **覆盖不全**——在较大的变更集上agent 会悄悄偷工减料,只评审部分文件。
- **位置漂移**——评论与它所指的代码对不上;行号和文件路径偏离目标。
- **质量不稳定**——自然语言 Skill 难以调试,输出质量随 prompt 的微小改动
而波动。
根本原因:纯语言驱动的架构缺乏对评审流程的 **硬约束**
## 核心设计:确定性工程 × agent
OCR 的核心理念是把 **确定性工程****agent** 结合——各自做自己最擅长的事。
### 确定性工程——硬约束
对于那些 *绝不能出错* 的步骤,由工程逻辑(而非模型)保证正确性:
- **精确的文件选择**——一个[五重门过滤](../review-rules/#how-files-are-filtered)
决定到底评审哪些文件,并提供显式的 `include`/`exclude` 控制。
- **智能文件打包**——相关文件(如 `message_en.properties`
`message_zh.properties`)可以合并为一个评审单元。每个包作为独立上下文交给
子 agent 运行——分而治之,在超大变更集上依然稳定,并天然支持并发评审。
- **细粒度规则匹配**——评审规则按文件路径匹配,首条匹配生效,让模型的注意力
高度聚焦并消除噪声。基于模板的匹配比纯语言驱动的规则引导更稳定。
- **外部定位与反思模块**——独立的评论定位
[`internal/diff/relocation.go`](https://github.com/alibaba/open-code-review/blob/main/internal/diff/relocation.go)
与重新定位流程,系统地提升位置与内容的准确性。
### Agent——动态决策
agent 的优势集中在最关键的地方:
- **场景化调优的 prompt**——针对代码评审场景深度调优的 prompt 模板,在降低 token
消耗的同时提升效果(见
[`internal/config/template/task_template.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/template/task_template.json))。
- **场景化调优的工具集**——从大规模生产数据的工具调用 trace 分析中提炼而来
(调用频次分布、单工具重复率、每个工具对整体调用链的影响)。最终得到一套
专用 [六工具](../tools/) 集,比通用 agent 工具包更稳定、更可预测。
## 流水线如何衔接
```mermaid
flowchart TD
Start["<b>ocr review --from main --to feature</b>"]
S1["<b>1. Resolve LLM endpoint</b><br/>config / env / shell rc"]
S2["<b>2. Load diffs from git</b><br/>workspace / commit / range"]
S3["<b>3. Filter files</b><br/>binary → user_exclude → user_include<br/>→ ext allowlist → default path"]
S4["<b>4. Drop diffs > 80% of MAX_TOKENS</b>"]
S5["<b>5. Dispatch per-file sub-agents</b> (concurrent)<br/><br/>For each file:<br/>&nbsp;&nbsp;a. Plan phase (if changed lines ≥ 50)<br/>&nbsp;&nbsp;b. Main loop: LLM → tool calls → … → task_done<br/>&nbsp;&nbsp;c. code_comment results collected (async via worker pool)<br/><br/>Memory compression triggers when context<br/>exceeds 60 % (async) or 80 % (sync) of MAX_TOKENS."]
S6["<b>6. Resolve line numbers</b><br/>from <code>existing_code</code> against diffs.<br/>Re-locate via LLM if needed."]
S7["<b>7. Emit text or JSON output</b><br/>(and persist session to disk)"]
Start --> S1 --> S2 --> S3 --> S4 --> S5 --> S6 --> S7
```
## 项目结构
```
open-code-review/
├── cmd/opencodereview/ # CLI 入口:分发、参数、命令
├── internal/
│ ├── agent/ # 每文件子 agent 循环 + 记忆压缩
│ ├── config/
│ │ ├── allowlist/ # 默认文件扩展名白名单与排除项
│ │ ├── rules/ # 分层规则解析器、系统规则文档
│ │ ├── template/ # plan / main / memory_compression prompt
│ │ ├── testconnection/ # 内置 `ocr llm test` 任务
│ │ └── toolsconfig/ # 发送给模型的工具定义
│ ├── diff/ # Git diff 解析、hunk 数学、重新定位
│ ├── gitcmd/ # Git 子进程运行器
│ ├── llm/ # Anthropic + OpenAI 协议、重试、BPE token
│ ├── model/ # diff / 评论 数据结构
│ ├── pathutil/ # 路径工具
│ ├── release/ # Release notes 生成
│ ├── session/ # 每次评审会话的 JSONL 持久化
│ ├── stdout/ # `--audience agent` 下可静音的 stdout writer
│ ├── suggestdiff/ # 构建 "Apply suggestion" diff
│ ├── telemetry/ # OpenTelemetry span、metrics、exporter
│ ├── tool/ # 六个内置工具 + 评论收集器
│ └── viewer/ # `ocr viewer`——历史会话的本地 Web UI
├── pages/ # 基于 React 的营销落地页(独立)
├── plugins/ # Claude Code 插件清单 + 命令
├── extensions/ # 编辑器扩展VS Code
├── examples/ # CI 配方GitHub Actions、GitLab CI
├── skills/ # 通用 agent Skill 清单
├── scripts/ # NPM 安装/更新助手、发布脚本
├── npm/ # 各平台 optional dependency 包
└── bin/ # NPM wrappershell 调用二进制
```
## 另见
- [快速开始](../quickstart/)——安装并完成首次评审。
- [架构](../architecture/)——agent 循环、plan 阶段与记忆压缩。
- [CLI 参考](../cli-reference/)——每个参数与子命令。
- [集成](../integrations/)——从 Claude Code 或任意 agent 调用 OCR。

View file

@ -0,0 +1,235 @@
---
title: 快速开始
sidebar:
order: 3
---
安装 OCR连接到任意支持 Anthropic Messages API 或 OpenAI Chat Completions API
的 LLM然后运行你的第一次代码评审。
## 前置条件
- 一个可用的 **Git** 安装——OCR 以子进程方式驱动 Git 读取 diff。
- 一个兼容 Anthropic 或 OpenAI 的 LLM 的 **API key**
- 以下之一:
- **Node.js ≥ 18**(推荐;最低支持 Node 14——通过 NPM 安装)。
- 或仅用 `curl` + `chmod` 把静态二进制放进 `$PATH`
- 或 **Go ≥ 1.25**,如果你偏好从源码构建。
## 第 1 步——安装 CLI
### 方式 ANPM推荐
```bash
npm install -g @alibaba-group/open-code-review
```
NPM 包安装一个小的 wrapper它在安装时通过 postinstall hook为你的
操作系统 / 架构下载正确的二进制。如果运行时二进制缺失wrapper 会报错而
不会去下载。安装后,你得到一个全局 `ocr` 命令:
```bash
ocr --version
```
### 方式 BGitHub Release 二进制
从 [releases 页面](https://github.com/alibaba/open-code-review/releases)
选择对应平台的二进制,放进你的 `$PATH`
```bash
# macOS (Apple Silicon)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# macOS (Intel)
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-darwin-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux x86_64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-amd64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Linux ARM64
curl -Lo ocr https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-linux-arm64
chmod +x ocr && sudo mv ocr /usr/local/bin/ocr
# Windows (AMD64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-amd64.exe
# Windows (ARM64)
curl -Lo ocr.exe https://github.com/alibaba/open-code-review/releases/latest/download/opencodereview-windows-arm64.exe
```
### 方式 C从源码构建
```bash
git clone https://github.com/alibaba/open-code-review.git
cd open-code-review
make build
sudo cp dist/opencodereview /usr/local/bin/ocr
```
> 各安装方式的详情见 [安装](../installation/),包括 NPM wrapper 如何解析
> 平台二进制。
## 第 2 步——配置 LLM
在能解析出一个完整的 LLM 端点URL + token + model之前OCR 会拒绝运行
评审。它按以下优先级顺序搜索四个来源:
1. `~/.opencodereview/config.json`
2. OCR 专属环境变量(`OCR_LLM_*`
3. Claude Code 环境变量(`ANTHROPIC_*`
4. 从你的 shell rc 文件(`~/.zshrc``~/.bashrc``~/.bash_profile`
`~/.profile`)中解析出的 `export ANTHROPIC_*`
### 最快路径:`ocr config set`
```bash
ocr config set llm.url https://api.anthropic.com/v1/messages
ocr config set llm.auth_token sk-ant-xxxxxxxxxx
ocr config set llm.model claude-opus-4-6
ocr config set llm.use_anthropic true
```
这些值会持久化到 `~/.opencodereview/config.json`
### 替代方式:环境变量
优先级最高——适合不想在磁盘上留配置文件的 CI / 容器:
```bash
export OCR_LLM_URL=https://api.anthropic.com/v1/messages
export OCR_LLM_TOKEN=sk-ant-xxxxxxxxxx
export OCR_LLM_MODEL=claude-opus-4-6
export OCR_USE_ANTHROPIC=true # 默认 true设为 false 走 OpenAI 协议
```
### 已经在用 Claude Code
OCR 会自动读取 Claude Code 使用的同一批变量,无需额外配置:
```bash
export ANTHROPIC_BASE_URL=https://api.anthropic.com
export ANTHROPIC_AUTH_TOKEN=sk-ant-xxxxxxxxxx
export ANTHROPIC_MODEL=claude-opus-4-6
```
如果 `ANTHROPIC_BASE_URL` 缺少带版本的路径OCR 会自动追加
`/v1/messages`
### 使用 OpenAI 兼容端点?
`llm.use_anthropic` 设为 `false`(或 `OCR_USE_ANTHROPIC=false`
```bash
ocr config set llm.url https://api.openai.com/v1/chat/completions
ocr config set llm.auth_token sk-xxxxxxxxxx
ocr config set llm.model gpt-4o
ocr config set llm.use_anthropic false
```
> 完整的 key 参考见 [配置](../configuration/),包括用于厂商专属请求字段
> 的 `llm.extra_body`,以及用于切换评审评论语言的 `language`
## 第 3 步——测试连通性
```bash
ocr llm test
```
预期输出(模型名会有所不同):
```
Source: OCR config file
URL: https://api.anthropic.com/v1/messages
Model: claude-opus-4-6
Hello! …
```
如果反而报出 `no valid LLM endpoint configured` 这类错误,请重新检查上面的
配置 key。401 / 403 表示 token 错误或已过期。
## 第 4 步——运行第一次评审
进入任意 Git 仓库并运行:
```bash
cd path/to/your-repo
# 工作区模式——评审 staged + unstaged + untracked 变更(默认)
ocr review
# 分支区间——评审 `main..feature-branch`
ocr review --from main --to feature-branch
# 单个 commit——评审该 commit 引入的 diff
ocr review --commit abc123
```
你会看到持续输出的进度信息,最后每个文件出现一条或多条评审评论。
> 工作区模式包含 **untracked** 文件。如果你只想评审已暂存的内容,请先用
> `git add` 选择性暂存。
> 以上三种是基础用法。`ocr review` 的完整参数(并发调优、输出格式、
> audience 模式、背景上下文等)及其他所有子命令(`config``rules`
> `llm test``viewer`)见 [CLI 参考](../cli-reference/)。
### 想先看看 *会* 评审什么?
```bash
ocr review --preview # 工作区
ocr review -c abc123 -p # commit
```
`--preview` 运行每个过滤步骤但绝不调用 LLM因此不消耗任何 token。它打印文件列表
及每个文件的状态(`added` / `modified` / `deleted` / `renamed` / `binary`
对于被排除的文件还会给出原因(`binary``unsupported_ext``default_path`
`user_exclude``deleted`)。
### 给工具用的 JSON 输出
```bash
ocr review --format json --audience agent > review.json
```
- `--format json` 输出一个机器可读的评论数组,每条含 `path``content`
`start_line``end_line``existing_code``suggestion_code` 和可选的
`thinking`
- `--audience agent` 屏蔽人性化的进度 UI让 stdout 只剩 JSON / 最终
摘要——正是上游 agent 或 CI 脚本所需。
## 第 5 步——查看结果
每条评论包含:
| 字段 | 含义 |
|---|---|
| `path` | 该评论所针对的文件。 |
| `content` | 评审评论本身,使用配置的 `language`。 |
| `start_line` / `end_line` | 文件 **新** 版本中的行范围。两者都为 `0` 表示 OCR 无法精确定位评论——问题是真实的,但需自行定位到准确位置。 |
| `existing_code` | 评论所指的 diff 片段。内部用于行解析;在 `start_line``0` 时有用。 |
| `suggestion_code` | 可选的修复片段。 |
| `thinking` | 可选的模型推理。仅部分模型存在。 |
## 第 6 步——查看历史会话
每次评审都会以 JSONL 转录形式持久化到
`~/.opencodereview/sessions/...`。在本地 Web UI 中浏览它们:
```bash
ocr viewer # http://localhost:5483
ocr viewer --addr :3000
```
> 完整 UI 介绍见 [会话查看器](../viewer/)。
## 另见
- [CLI 参考](../cli-reference/)——每个子命令、参数与输出模式。
- [评审规则](../review-rules/)——自定义评审内容。
- [集成](../integrations/)——把 OCR 嵌入 Claude Code、Agent skill 或 CI。
- [遥测](../telemetry/)——经 OTLP 上报 trace 与 metrics。
- [FAQ](../faq/)——已知错误与对策。

View file

@ -0,0 +1,241 @@
---
title: 评审规则
sidebar:
order: 7
---
规则告诉 OCR 评审每个文件时**应关注什么**。它们存放在三层的 JSON 文件中,
外加随二进制发布的一个内嵌系统默认规则。
## 优先级链
OCR 用一条**四层优先级链**解析规则。对每个文件路径,按序尝试各层;第一个匹配
的模式生效。
| 优先级 | 来源 | 路径 | 说明 |
|---|---|---|---|
| 1最高 | `--rule` 参数 | 用户指定 | CLI 覆盖;只要提供就总是生效。 |
| 2 | 项目配置 | `<repoDir>/.opencodereview/rule.json` | 项目级规则——可安全提交。 |
| 3 | 全局配置 | `~/.opencodereview/rule.json` | 用户级偏好。 |
| 4最低 | 系统默认 | 内嵌 `system_rules.json` | 覆盖常见语言的内置规则。 |
若更高优先级层的文件不存在,会被静默跳过——不是错误。因此从未添加
`.opencodereview/rule.json` 的项目会直接落到全局 / 系统层。
系统层**始终**存在(随二进制发布),因此总会解析出*某个*规则。
## 规则文件格式(层 13
```json
{
"include": ["src/**/*.{ts,tsx}", "src/**/*.go"],
"exclude": ["**/*.test.ts", "**/generated/**"],
"rules": [
{
"path": "src/api/**/*.go",
"rule": "All exported handlers must validate request bodies before use."
},
{
"path": "**/*mapper*.xml",
"rule": "Check SQL for injection risks, parameter errors, and missing closing tags."
}
]
}
```
三个独立字段:
- `include`——可选。glob 模式,用于*绕过*内置的默认排除模式(测试文件排除——见
下文)。它不是白名单:不匹配任何 `include` 模式的文件仍会经过
`unsupported_ext``default_path` 检查,可能仍被评审。
- `exclude`——可选。OCR 不予评审的文件 glob 模式。过滤中优先级最高。
- `rules`——`{path, rule}` 条目数组,按**声明顺序**求值。第一个 `path` glob
匹配该文件的条目,决定 OCR 发给模型的 prompt。
### glob 能力
OCR 用 [`bmatcuk/doublestar/v4`](https://pkg.go.dev/github.com/bmatcuk/doublestar/v4)
做匹配:
- `*`——匹配除 `/` 外的任意字符。
- `**`——跨目录边界匹配(`src/**/*.go` 覆盖任意深度)。
- `{a,b,c}`——花括号展开。`*.{ts,tsx,js,jsx}` 展开为四个模式并依次匹配。
- `?`——匹配单个字符。
- `[abc]`——字符类。
> 模式匹配**不区分大小写**(匹配前文件路径会被小写化)。不确定时用
> `ocr rules check <path>` 确认。
## 文件如何被过滤
过滤是一个五重门算法,位于
[`internal/agent/preview.go`](https://github.com/alibaba/open-code-review/blob/main/internal/agent/preview.go)。
对每个 diffOCR 依次问:
1. **`binary`**——文件是二进制吗?排除。
2. **`user_exclude`**——路径匹配任何用户 `exclude` 模式吗?排除。
3. **`user_include`**——若用户定义了 `include`,路径匹配吗?若是,**立即保留**
(绕过下面的 `unsupported_ext``default_path` 门)。
4. **`unsupported_ext`**——文件扩展名在
[白名单](https://github.com/alibaba/open-code-review/blob/main/internal/config/allowlist/supported_file_types.json)
里吗?不在则排除。
5. **`default_path`**——路径匹配某个内置测试文件排除模式
`**/*_test.go``**/*.test.{js,jsx,ts,tsx}``**/*_spec.rb`……)吗?排除。
通过全部五重门的文件才发给 LLM。`deleted` 原因(不是门——它在 `Preview()`
单独计算)标记新路径为 `/dev/null` 的文件;没有新内容可评审。用
`ocr review --preview` 可在不花 token 的情况下打印此过滤结果。
### 默认路径排除
内置排除列表(见
[`internal/config/allowlist/default_exclude_patterns.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/allowlist/default_exclude_patterns.json)
匹配测试文件模式:
- `**/*_test.go`
- `**/src/test/java/**/*.java`
- `**/src/test/**/*.kt`
- `**/*.test.{js,jsx,ts,tsx}`
- `**/*.spec.{js,jsx,ts,tsx}`
- `**/__tests__/**`
- `**/test/**/*_test.py`
- `**/tests/**/*_test.py`
- `**/*_test.py`
- `**/*_spec.rb`
- `**/spec/**/*_spec.rb`
- `**/*Test.java`
- `**/*Tests.java`
- `**/*_test.rs`
- `**/oh_modules/**`
- `**/*.test.ets`
噪声目录过滤(`vendor/``node_modules/``target/`……)发生在更早的阶段,位于
[`internal/diff/git.go`](https://github.com/alibaba/open-code-review/blob/main/internal/diff/git.go)
的 diff 层,先于 per-file 过滤运行。
要**评审**一个匹配这些测试文件模式的文件,把它加入用户 `include` 列表——那会
覆盖 default-path 门。
## 每文件的规则解析
过滤决定某文件*将被*评审后OCR 选择 agent 应遵循的规则文本:
1. 按声明顺序试 `--rule`custom层。
2. 按声明顺序试 `<repo>/.opencodereview/rule.json`
3. 按声明顺序试 `~/.opencodereview/rule.json`
4. 回退到内嵌系统规则层。
内嵌 `system_rules.json` 自带这些模式(按序):
| 模式 | 规则文档 |
|---|---|
| `**/*.properties` | `properties.md`——i18n / 配置文件。 |
| `**/*{mapper,dao}*.xml` | `mapper_dao_xml.md`——MyBatis 风格 mapper SQL。 |
| `**/pom.xml` | `pom_xml.md`——Maven 依赖。 |
| `**/build.gradle` | `build_gradle.md`——Gradle 依赖。 |
| `**/package.json` | `package_json.md`——NPM 依赖 / 脚本。 |
| `**/Cargo.toml` | `cargo_toml.md`——Rust manifest。 |
| `**/*.{json,json5}` | `json.md`——通用 JSON也匹配 `.json5`)。 |
| `.github/workflows/**/*.{yaml,yml}` | `github_workflows.md`——GitHub Actions 工作流 YAML。 |
| `.github/**/*.{yaml,yml}` | `github_config.md`——其他 `.github` 配置 YAML。 |
| `**/*.{yaml,yml}` | `yaml.md` |
| `**/*.java` | `java.md` |
| `**/*.ets` | `arkts.md`——ArkTS / HarmonyOS。 |
| `**/*.{ts,js,tsx,jsx}` | `ts_js_tsx_jsx.md` |
| `**/*.{kt}` | `kotlin.md` |
| `**/*.rs` | `rust.md` |
| `**/*.{cpp,cc,hpp}` | `cpp.md` |
| `**/*.c` | `c.md` |
| *(fallback)* | `default.md` |
解析出的规则正文成为 plan 和 main task prompt 中 `{{system_rule}}` 占位符的内容。
## 查看哪条规则生效:`ocr rules check`
```bash
$ ocr rules check src/main/java/com/example/UserService.java
File: src/main/java/com/example/UserService.java
Source: System built-in
Pattern: **/*.java
Rule:
────────────────────────────────────────
…contents of java.md…
────────────────────────────────────────
```
```bash
$ ocr rules check --rule custom.json src/main/resources/mapper/UserMapper.xml
File: src/main/resources/mapper/UserMapper.xml
Source: Custom (--rule)
Pattern: **/*mapper*.xml
Rule:
────────────────────────────────────────
…contents of your custom rule…
────────────────────────────────────────
```
当某条规则未按预期生效时用它——它会显示生效的**层**与**模式**。
## 配方
### 项目级:强制编码规范
保存为 `<repo>/.opencodereview/rule.json` 并提交:
```json
{
"rules": [
{
"path": "src/api/**/*.go",
"rule": "Every public handler must `defer tx.Rollback()` immediately after starting a transaction."
},
{
"path": "**/*mapper*.xml",
"rule": "Check SQL for injection risks, missing parameter binding, and unclosed XML tags."
}
]
}
```
### 项目级:跳过生成代码,聚焦 src
```json
{
"include": ["src/**/*.{ts,tsx,js,jsx}"],
"exclude": ["**/*.gen.ts", "**/generated/**"]
}
```
设置 `include` 后,`src/` 内的文件即使本会被内置默认排除模式(如测试文件)剔除
也会被保留。`src/` 之外的文件仍走正常的 ext / default 检查——`include` 是绕过机制,
不是白名单。
### 按 PR 覆盖
```bash
ocr review --rule ./.review-rules-only-for-this-pr.json
```
同时绕过项目层与全局层——当单个 PR 需要完全不同的评审清单(如仅安全评审)时
很方便。
### 全局个人偏好
放到 `~/.opencodereview/rule.json`,你机器上每个仓库都会继承:
```json
{
"rules": [
{
"path": "**/*.{ts,tsx,js,jsx}",
"rule": "Always check for unhandled promise rejections; warn on `// eslint-disable` without a reason comment."
}
]
}
```
## 另见
- [CLI 参考](../cli-reference/)——`ocr review --rule``--preview``ocr rules check`
- [配置](../configuration/)——config 文件位置与分层解析链。
- [架构](../architecture/)——解析出的规则如何馈入 agent prompt。

View file

@ -0,0 +1,258 @@
---
title: 遥测
sidebar:
order: 11
---
OCR 自带一流的 **OpenTelemetry** 支持。每次评审运行产出结构化的 span、
metric 和 event。接入 collector 后这些数据足以回答“agent 把时间花在哪了?”、
“各模型成本如何?”、“这次运行为什么失败?”。
## 概览
遥测**默认关闭**。启用后OCR 导出:
- **Span**——三个流水线级 span`review.run``diff.parse`
`subtask.execute.<file>`)外加每个决策点事件一个短生命周期的 `event.*` span。
- **Metric**——评审时长、评审文件数、生成评论数、LLM 请求 / token / 延迟、
工具调用 / 延迟的聚合计数与直方图。
- **Event**——span 内离散事件,如 `plan.skipped`
`token.threshold.exceeded``review.started`
支持两种 exporter
| Exporter | 何时使用 |
|---|---|
| `console` | 个人使用 / 调试。把 span 格式化打印到 stdout。 |
| `otlp` | 系统集成。发送到任何 OTLP 兼容 collectorJaeger、Tempo、OTel Collector、Datadog Agent……。 |
## 启用遥测
与 LLM 端点一样,遥测可通过持久化 config 或环境变量配置——冲突时环境变量优先。
### 配置文件方式
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter otlp
ocr config set telemetry.otlp_endpoint localhost:4317
ocr config set telemetry.content_logging false
```
`~/.opencodereview/config.json` 中的结果:
```json
{
"telemetry": {
"enabled": true,
"exporter": "otlp",
"otlp_endpoint": "localhost:4317",
"content_logging": false
}
}
```
### 环境变量方式
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317 # implies exporter=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc # default. NOTE: only grpc is currently
# implemented; http/protobuf and http/json
# are accepted but not yet wired up.
export OTEL_SERVICE_NAME=open-code-review-prod # optional; default: open-code-review
export OCR_CONTENT_LOGGING=0 # reserved / currently a no-op (see Content logging)
```
设置 `OTEL_EXPORTER_OTLP_ENDPOINT` 也会强制 `exporter=otlp`——适合一次性的
`OTEL_EXPORTER_OTLP_ENDPOINT=… ocr review` 运行。
## 导出什么
### Span
一次评审的完整 span 树:
```
review.run
├── diff.parse
├── event.review.started (decision-point event)
├── subtask.execute.<file1>
│ ├── event.plan.skipped (when changes are below threshold)
│ ├── event.plan.failed (when plan phase errored)
│ ├── event.token.threshold.exceeded (when prompt > 80% of max_tokens)
│ └── event.subtask.error (when the subtask errored)
├── subtask.execute.<file2>
└── …
```
LLM 往返和工具执行**不**作为单独 span 发出——它们只出现在 metric见下中。
决策点事件作为短生命周期的 `event.<name>` span 附着到当前 context。
每个 span 携带有用属性:
| Span | 关键属性 |
|---|---|
| `review.run` | `error`(运行失败时设置) |
| `diff.parse` | `files.changed``lines.inserted``lines.deleted` |
| `subtask.execute.<file>` | `file.path``lines.changed``lines.inserted``lines.deleted` |
| `event.review.started` | `file.count``review.count``repo.dir` |
| `event.plan.skipped` | `file.path``lines.changed``threshold` |
| `event.plan.failed` | `file.path``message` |
| `event.token.threshold.exceeded` | `file.path``tokens``max_tokens` |
| `event.subtask.error` | `file.path``error` |
### Metric
OCR 通过 OTel meter 记录数值 metric——计数与直方图由 collector 在下游聚合:
| Metric | 类型 | 单位 | 标签 |
|---|---|---|---|
| `ocr.review.duration_seconds` | histogram | `s` | — |
| `ocr.files_reviewed_total` | counter | — | — |
| `ocr.comments_generated_total` | counter | — | — |
| `ocr.llm.requests_total` | counter | — | `model``status``ok` / `error` |
| `ocr.llm.request_duration_seconds` | histogram | `s` | `model` |
| `ocr.llm.tokens_used` | counter | — | `model``type`(当前总是 `total` |
| `ocr.tool.calls_total` | counter | — | `tool.name``status``ok` / `error` |
| `ocr.tool.execution_duration_seconds` | histogram | `s` | `tool.name` |
### Event
事件在决策点作为短生命周期的 `event.<name>` span 触发。完整列表:
| 事件 | 含义 |
|---|---|
| `review.started` | diff 已加载;我们知道将评审多少文件。 |
| `no.files.changed` | diff 解析出零文件。 |
| `plan.skipped` | 某文件低于 `PLAN_MODE_LINE_THRESHOLD`。 |
| `plan.failed` | plan 阶段出错main 循环无 plan 运行。 |
| `token.threshold.exceeded` | 初始 prompt token > `MAX_TOKENS` 的 80 %;文件被跳过。 |
| `subtask.error` | 某 per-file 子任务出错——以 `Error` span 状态发出。 |
借此可在用户察觉之前,及早发现评审质量退化并告警。
## 内容日志
遥测导出 LLM 流量的**形状**(计数、时长、状态),但**绝不**导出实际 prompt 或
响应。OCR 不尝试把 LLM 消息内容附加到 span 或 event——离开进程的数据仅限于上面
记录的 metric / event schema别无其他。
`content_logging` config key`OCR_CONTENT_LOGGING=1` 环境覆盖)已接入配置层,
但目前**不**控制任何发出 prompt 内容的代码路径。请将该标志视为保留位。
如需检查发给 LLM 或从 LLM 返回的内容,使用[会话查看器](../viewer/)读取的本地
JSONL 转录。它们完全存在于 `~/.opencodereview/` 下的磁盘上,绝不发往 collector。
## 配方
### 本地调试用 console exporter
```bash
ocr config set telemetry.enabled true
ocr config set telemetry.exporter console
ocr review --commit HEAD
```
span 以人类可读形式打印到 stdout。可通过管道传给 `less` 查看长运行输出。
### OTel Collector + Tempo + Prometheus
```yaml
# otel-collector-config.yaml
receivers:
otlp:
protocols: { grpc: { endpoint: 0.0.0.0:4317 } }
exporters:
otlp/tempo:
endpoint: tempo:4317
tls: { insecure: true }
prometheus:
endpoint: 0.0.0.0:9464
service:
pipelines:
traces: { receivers: [otlp], exporters: [otlp/tempo] }
metrics: { receivers: [otlp], exporters: [prometheus] }
```
然后在 shell 中:
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
ocr review --from main --to feature/branch
```
打开 Tempo → 按 `service.name=open-code-review` 搜索 → 点击任意 trace 看完整
span 树。
### Datadog
Datadog Agent 的 OTLP receiver 默认使用 OTLP/gRPC
```bash
export OCR_ENABLE_TELEMETRY=1
export OTEL_EXPORTER_OTLP_ENDPOINT=localhost:4317
export OTEL_SERVICE_NAME=open-code-review
```
span 以该 service name 出现在 APM 下LLM metric 带上述标签出现在 Metrics 下。
### CI 运行,结果进入仪表盘
在流水线步骤中注入环境变量:
```yaml
- name: Code review
env:
OCR_LLM_URL: ${{ secrets.OCR_LLM_URL }}
OCR_LLM_TOKEN: ${{ secrets.OCR_LLM_TOKEN }}
OCR_LLM_MODEL: claude-opus-4-6
OCR_ENABLE_TELEMETRY: "1"
OTEL_EXPORTER_OTLP_ENDPOINT: ${{ vars.OTEL_COLLECTOR_URL }}
OTEL_SERVICE_NAME: open-code-review-ci
run: ocr review --from origin/main --to HEAD --audience agent
```
`OTEL_SERVICE_NAME` 可把 CI trace 与人工开发运行的 trace 区分开。
## 解析优先级
OCR 构建最终遥测配置时:
1. 默认(`enabled=false``exporter=console`、无 endpoint
2. `~/.opencodereview/config.json``telemetry.*` key。
3. 环境变量(最高优先级,**覆盖**文件)。
因此你可以在 config 中保留 `telemetry.enabled=false`,按运行用
`OCR_ENABLE_TELEMETRY=1` 开启。
## 采样与开销
OCR 导出**一切**。没有采样配置OTel 的采样是 collector 的责任。对一次典型评审
运行:
- 1 个 `review.run` span + 1 个 `diff.parse` span + 每个被评审文件 1 个
`subtask.execute.<file>` span + 每个决策点事件 1 个短生命周期的 `event.*` span。
- 10 文件的 PR 总共约 1525 个 span。LLM 往返和工具调用增加 metric 计数但不创建
额外 span。
导出是**批量且异步**的——遥测不阻塞评审循环。若 collector 不可达OCR 记录警告
并继续;评审仍会产出正常输出。
## 故障排查
| 症状 | 可能原因 |
|---|---|
| 什么都没导出 | `OCR_ENABLE_TELEMETRY` / `telemetry.enabled` 未设置。默认**关闭**。 |
| OTLP 本地可用,生产失败 | OCR 当前仅实现 OTLP/gRPC——`OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`(或 `http/json`)被接受但未接入,切换也无济于事。请验证 endpoint 以及 collector 是否在监听 gRPC。 |
| span 显示但无 metric | 一些 collector 默认只启用 traces pipeline在配置中加 `metrics` pipeline。 |
| span 中缺 prompt | OCR 绝不把 prompt 内容附加到遥测——见[内容日志](#content-logging)。改用[会话查看器](../viewer/)检查转录。 |
## 另见
- [配置](../configuration/)——`telemetry.*` 命名空间的完整 key 参考。
- [架构](../architecture/)——每个 span 实际度量什么。
- [OpenTelemetry 文档](https://opentelemetry.io/docs/)——collector 设置与 exporter。

View file

@ -0,0 +1,353 @@
---
title: 工具
sidebar:
order: 9
---
OCR 内置 **六个工具**,供 LLM 在评审过程中调用。本页记录每个工具的用途、输入
schema 与示例输入/输出。完整的机器可读定义位于
[`internal/config/toolsconfig/tools.json`](https://github.com/alibaba/open-code-review/blob/main/internal/config/toolsconfig/tools.json)。
## 各阶段工具可用性
每个工具声明它在 **plan 阶段**、**main task** 还是两者中都可用:
| 工具 | Plan | Main | 用途 |
|---|---|---|---|
| `task_done` | ✗ | ✓ | 表示“我完成了”——终止循环。 |
| `code_comment` | ✗ | ✓ | 发出一条带行范围 + 建议的评审评论。 |
| `file_read` | ✗ | ✓ | 读取变更后快照中某文件的一段。 |
| `file_read_diff` | ✓ | ✓ | 读取另一文件的 diff 以确认跨文件关切。 |
| `file_find` | ✓ | ✓ | 按文件名关键字定位文件。 |
| `code_search` | ✓ | ✓ | 全仓 grep字面量或正则。 |
`task_done``code_comment` 在 plan 阶段有意**不可用**plan 是只读的。
> **上下文工具是只读上下文,不是评论目标。** `main_task` prompt 明确禁止对
> *其他*文件中的发现发表评论。`file_read``file_read_diff``file_find`
> `code_search` 的存在是为了让模型更好地理解当前文件的 diff——收集该上下文时
> 发现的任何问题按设计都会被忽略。跨文件关切只有在**当前文件 diff**中可观测
> 时,才会作为评论出现。
要覆盖工具注册表,传入一个与内嵌定义同形的 JSON 文件路径 `--tools <path>`
借此可以禁用工具、编辑描述,或基于已有 provider 添加新工具。
## `task_done`
终止 main 循环。
```json
{
"name": "task_done",
"input": { "state": "DONE" }
}
```
| 字段 | 必需 | 含义 |
|---|---|---|
| `state` | 是 | `DONE`(默认)或 `FAILED``FAILED` 表示“我确实无法用可用工具完成此事”——几乎从不是正确的选择。 |
agent 看到 `task_done` 后,停止调用 LLM 并开始处理已累积的 `code_comment`
调用。`task_done` 立即返回(在结果记入会话日志之前),因此 `state` 值会被接受
但**不**持久化——它也不影响退出码。
## `code_comment`
发出一条或多条评审评论。每条评论锚定到一个代码片段(`existing_code`),以便
OCR 自动计算行号。
### Schema
```json
{
"name": "code_comment",
"input": {
"path": "string — optional, override the file path for this comment",
"comments": [
{
"content": "string — the comment in the configured language",
"existing_code": "string — snippet from the diff to anchor on",
"suggestion_code": "string — optional fix snippet",
"thinking": "string — optional, the model's reasoning for this comment"
}
]
}
}
```
`comments` 是数组,因此模型可以在一次工具调用中发出多条评论。`content`
`existing_code` 必需;`suggestion_code` 可选但建议提供。`path` 是顶层可选覆盖——
省略时agent 会注入当前评审的文件。即便模型省略agent 也会自动注入 `path`,因此
模型极少需要显式设置。`thinking`(按评论)捕获模型推理,保留在评论上,但不会
在最终评审输出中显示。
> **`thinking` 是运行时专属字段。** OCR 会解析并存储它,但有意**不**把它列入
> 给模型的 `code_comment` schema`tools.json` 中只有 `content`
> `existing_code``suggestion_code`)。更强模型若仍发出 `thinking` 块,也会
> 被持久化;多数模型不会发,没问题。
### 锚定算法
OCR 用一个**动态滑动窗口**在 diff 中查找 `existing_code` 文本。匹配按序尝试:
1. **hunk 新侧**——一段连续的 **context + added** 行(不是仅有 deleted、也不是仅有
unchanged得到新文件行号。若失败OCR 重试 **hunk 旧侧**——context +
deleted 行——得到旧文件行号。
2. **全新文件扫描**——若无 hunk 匹配OCR 对整个变更后文件逐行扫描连续匹配
`resolveFromFileContent`)。
3. **重新定位任务**——若文本匹配在较复杂的 diff 上仍失败OCR 运行
`RE_LOCATION_TASK` prompt请模型重新锚定片段。
匹配**对空白不敏感**:比较前会 trim 行并去除 diff 的 `+`/`-` 标记,因此缩进
无需精确一致。作为最后手段,评论会以 `start_line=0` 交付,告诉用户“问题是真实的,但需自行定位”。
### 示例
```json
{
"comments": [
{
"content": "`tx.Rollback()` is never deferred — early returns leak the transaction.",
"existing_code": "tx, err := db.Begin()\nif err != nil {\n return err\n}",
"suggestion_code": "tx, err := db.Begin()\nif err != nil {\n return err\n}\ndefer tx.Rollback()"
}
]
}
```
## `file_read`
读取文件**变更后**形式的一段行。
### Schema
```json
{
"name": "file_read",
"input": {
"file_path": "src/foo.go",
"start_line": 10,
"end_line": 80
}
}
```
| 字段 | 必需 | 默认 | 说明 |
|---|---|---|---|
| `file_path` | 是 | — | 相对仓库根的路径。 |
| `start_line` | 否 | `1` | 从 1 开始索引。 |
| `end_line` | 否 | 文件末尾 | 含端点。 |
### 输出
```
File: src/foo.go (Total lines: 220)
IS_TRUNCATED: false
LINE_RANGE: 10-80
10|package foo
11|
12|import (
13| "fmt"
```
每行内容以 1 起始行号和 `|` 分隔符为前缀,以便模型在后续 `code_comment` 调用中
精确引用行号。
### 限制
- **每次调用最多 500 行。** 更大范围会被截断,置 `IS_TRUNCATED: true`,并追加
`Note: Results truncated to 500 lines. Please narrow your line range.`
- 只读取文件的**修改后版本**。要看旧版本,用 `file_read_diff`
当模型需要周围上下文(对某个只能在 diff 中看到的函数发表评论时),应从 diff
hunk 头 `@@ -x,y +m,n @@` 计算范围——通常 `m-50``m+n+50`
## `file_read_diff`
读取同一变更集中一个或多个*其他*文件的 diff——当评论取决于某相关文件是否被
更新时有用。
### Schema
```json
{
"name": "file_read_diff",
"input": {
"path_array": ["src/api/handler.go", "src/db/queries.go"]
}
}
```
### 输出
```
==== FILE: src/api/handler.go ====
--- a/src/api/handler.go
+++ b/src/api/handler.go
@@ -10,1 +10,2 @@
- old line
+ new line 1
+ new line 2
==== FILE: src/db/queries.go ====
@@ -5,1 +5,1 @@
- query := "SELECT *"
+ query := "SELECT id"
```
若某路径不在变更集中,该条目被静默省略。若请求的路径**都不**在变更集中,工具
返回 `Error: diff not found for the requested paths`;空的 `path_array` 返回
`Error: no files found`
## `file_find`
按文件名关键字(子串匹配)在仓库中查找文件。
### Schema
```json
{
"name": "file_find",
"input": {
"query_name": "UserService",
"case_sensitive": false
}
}
```
| 字段 | 必需 | 默认 | 说明 |
|---|---|---|---|
| `query_name` | 是 | — | 与每个文件的 **basename**(最后一个 `/` 之后的部分)做子串匹配,而非全路径。 |
| `case_sensitive` | 否 | `false` | 设为 `true` 做精确大小写匹配。 |
候选集在工作区模式下来自 `git ls-files --cached --others --exclude-standard`
在区间 / commit 模式下来自 `git ls-tree -r --name-only <ref>`。无扩展名的文件
被跳过,但 `Makefile``Dockerfile``LICENSE``Vagrantfile`
`Containerfile` 例外。
### 输出
以换行分隔的路径列表:
```
src/main/java/com/example/UserService.java
src/test/java/com/example/UserServiceTest.java
src/main/java/com/example/internal/UserServiceImpl.java
```
当没有文件匹配(或 `query_name` 为空)时,工具返回字面字符串
`// The file was not found`
### 限制
最多返回 **100** 条匹配;超出被静默截断。若模型需要更广搜索,应改用
`code_search`
## `code_search`
全仓全文搜索。由 `git grep` 驱动,因此理解 `pathspec` 语法并遵循
`.gitignore`
### Schema
```json
{
"name": "code_search",
"input": {
"search_text": "TODO|FIXME",
"file_patterns": ["*.go", ":(exclude)vendor/"],
"case_sensitive": false,
"use_perl_regexp": true
}
}
```
| 字段 | 必需 | 默认 | 说明 |
|---|---|---|---|
| `search_text` | 是 | — | 字面量字符串或 PCRE 模式(见 `use_perl_regexp`)。 |
| `file_patterns` | 否 | 全仓 | pathspec 条目数组。用 `:(exclude)pat` 做排除。 |
| `case_sensitive` | 否 | `false` | — |
| `use_perl_regexp` | 否 | `false` | 为 `true` 时,`search_text` 作为正则处理。 |
### 输出
结果按文件分组。每组以 `File: <path>``Match lines: <n>` 开头,随后每条命中
一行 `line|content`
```
File: path/to/example.java
Match lines: 2
433| String name = toolRequest.get().getName();
438| logToolRequest(newPath, tool, toolRequest.get());
File: path/to/other.java
Match lines: 1
22| var req = new ToolRequest();
```
无匹配时,工具返回字面字符串 `No matches found`
### pathspec 速查
| 目标 | `file_patterns` |
|---|---|
| 单个文件 | `["src/main.go"]` |
| 所有 Go 文件 | `["*.go"]` |
| 除测试外所有 Go | `["*.go", ":(exclude)*_test.go"]` |
| 仅一个目录 | `["src/api/"]` |
| 多种类型,排除 vendor | `["*.go", "*.ts", ":(exclude)vendor/", ":(exclude)node_modules/"]` |
### 限制
- 通过 `git grep --max-count 100` 把每文件命中数上限设为 **100**,因此跨多文件的
总输出可能超过 100。触及每文件上限时输出前会加
`Note: The results have been truncated. Only showing first 100 results.`
- 空 / 仅空白的 `search_text` 返回 `Error: search_text is blank`,而不是展开成
每一行。
- 工作区模式搜索**当前工作树**,区间 / commit 模式搜索解析出的目标 ref
`FileReader.Ref` 作为位置参数传给 `git grep`)。
## 工具执行与错误
工具在 agent 循环内同步执行,两个例外:
- `code_comment` 派发到 **CommentWorkerPool**,使循环不阻塞在行解析 + 反思上。
- `task_done` 短路——立即返回,不调用任何 provider。
工具出错时(网络失败、参数格式错误、文件未找到),结果作为常规工具结果交付给模型,
文本形如 `"Error: file not found: src/missing.go"`。模型再决定是重试、换文件,
还是调 `task_done`
若工具名不在注册表中OCR 返回常量 `tool.NotAvailableMsg` 而不是崩溃。这使得
(通过 `--tools`)运行时禁用工具是安全的。
## 自定义工具
两种扩展方式:
### 1. 禁用工具
复制 `tools.json`,删掉不想要的条目,然后运行:
```bash
ocr review --tools ./my-tools.json
```
例如,想要一个从不读额外上下文的“仅评论”评审器,只保留 `code_comment`
`task_done`
### 2. 重新描述工具
保留 `name`provider 内部按 name 查找)但更改 `description` 以引导模型。这是
注入项目专属指引最简单的方式——如“使用 `file_read` 时,始终读取变更附近至少
30 行。”
> 添加**新**工具*名*需要在 Go 侧接入;见 `internal/tool/definitions.go`
> `internal/tool/` 下的 provider。仅靠 JSON 文件无法添加新行为。
## 另见
- [架构](../architecture/)——agent 循环如何驱动工具。
- [评审规则](../review-rules/)——告知 LLM 关注什么。
- [会话查看器](../viewer/)——查看过去评审中到底触发了哪些工具。

View file

@ -0,0 +1,151 @@
---
title: 会话查看器
sidebar:
order: 10
---
`ocr viewer` 是一个小型内嵌 HTTP 服务器,以浏览器友好的 UI 渲染历史评审会话。
无外部依赖——会话直接从 OCR 在每次评审期间写入磁盘的 JSONL 文件读取。
## 启动
```bash
ocr viewer # binds localhost:5483
ocr viewer --addr :3000 # bind to all interfaces on port 3000
ocr viewer --addr 0.0.0.0:8080 # bind on all interfaces
```
默认地址是 `localhost:5483`。服务器在前台运行——`Ctrl+C` 停止。会话在每次请求时
`~/.opencodereview/sessions/` 惰性扫描,因此另一个终端里运行的评审一旦其
JSONL 文件出现就会显示。
> **DNS-rebinding 防护。** 查看器会对照 loopback 白名单
> `localhost``127.0.0.1``::1`)检查 `Host` 头。具体的绑定主机
> (如 `--addr 192.168.1.10:5483`)会自动加入,但**通配**绑定
> `:3000``0.0.0.0``::`)不会——此时从 LAN IP 或主机名访问 UI 会返回
> `forbidden host`。要让通配绑定可被访问,设置
> `OCR_VIEWER_ALLOWED_HOSTS` 为逗号分隔的允许主机名列表
> (如 `OCR_VIEWER_ALLOWED_HOSTS=box.local,192.168.1.10`)。
## 三个页面
查看器有三个 URL
| URL | 看到内容 |
|---|---|
| `/` | 磁盘上有会话的所有仓库列表。 |
| `/r/{repo}` | 单个仓库的会话列表,最新在前。 |
| `/r/{repo}/{sessionID}` | 单个会话的完整详情。 |
`{repo}` 是一个路径编码字符串(分隔符 `/``\` 替换为 `-`、冒号替换为
`_`——与磁盘目录命名相同的编码)。通常你不会手动输入它——而是点击进入。
### `/`——仓库列表
对每个至少有一条会话的仓库,显示仓库路径、总会话数和最近活动时间戳。
### `/r/{repo}`——单仓库会话列表
对每个会话ID一个 UUID、分支名OCR 能检测到时)、评审模式、模型、文件数、
时长和开始时间戳。
### `/r/{repo}/{sessionID}`——会话详情
详情页是最有用的那个。它显示:
1. **头部**——diff 范围、模型、分支、总 token、运行时长。
2. **文件分组**——每个被评审文件一个块。每个文件内,五条“任务类型”泳道:
| 任务类型 | 何时出现 |
|---|---|
| `plan_task` | 运行了 plan 阶段(文件 ≥ `PLAN_MODE_LINE_THRESHOLD`)。 |
| `main_task` | 每个文件。主评审循环。 |
| `review_filter_task` | 为该文件运行了评审后评论过滤流程。 |
| `memory_compression_task` | active+compress 区超过 60 % / 80 % 预算。 |
| `re_location_task` | 某条 `code_comment` 无法锚定,回退重新定位运行。 |
每条泳道是**任务卡片**的水平条带——每个 LLM 往返一张。卡片按任务类型着色,让你
一眼看出哪些阶段主导了运行。
## 任务卡片里有什么
点击任务卡片展开。每张卡片有:
- 一行**头部**——请求号、模型徽章、token 徽章(`P:` prompt / `C:` completion
存在时还显示 `CR:` / `CW:` 缓存读写)、时长徽章,以及该轮失败时的错误徽章;
- **Response**——原始 assistant 响应,包括任何推理 / `thinking` 块;
- **Tool calls**——每个工具调用及其参数 + 返回结果(可折叠)。
发给模型的完整消息列表和作用域内工具定义**不**在卡片 UI 中渲染;如需要,可直接
检查 JSONL 转录(每条 `llm_request` 记录的 `messages` 字段)。
## 使用场景
查看器围绕三个工作流设计:
### “模型为什么这么说?”
在终端输出中打开一条评论,在查看器中定位该文件,沿着它的 `main_task` 泳道向下查看。
**工具调用**中包含你关心的 `code_comment` 的那张卡片,就是产出它的那一轮。卡片的
Response 显示模型推理;要确切知道发给模型的 prompt + 上下文,在 JSONL 转录中
打开该请求号的 `llm_request` 记录(其 `messages` 字段)。
### “这个文件为什么静默?”
一个**无评论**的文件,只有当模型*主动*调用 `task_done` 时才是成功评审。若泳道
显示工具调用但无 `code_comment`,那是模型主动给出的干净评审。若泳道以错误卡片结束,那是
伪装成静默的失败——应作为警告处理。
### “压缩保留 / 丢弃了什么?”
`memory_compression_task` 泳道显示每次压缩轮。其中Response 窗格有结果摘要;
被压缩的 compress 区渲染出的 XML 在该轮 `llm_request``messages`JSONL 转录中)。
排查“模型忘了早前上下文”这类反馈时有用——你能看到压缩是否丢弃了相关细节。
## 磁盘存储布局
查看器读取:
```
~/.opencodereview/sessions/
└── <path-encoded-repo-path>/
└── <session-id>.jsonl
```
JSONL 文件每行是一个事件:
```json
{"type": "llm_request", "filePath": "src/foo.go", "taskType": "main_task", "request_no": 1, "messages": [{"role": "user", "content": "Review this diff…"}], "timestamp": "2026-06-02T10:15:23Z"}
{"type": "llm_response", "filePath": "src/foo.go", "taskType": "main_task", "model": "claude-sonnet-4-6", "content": "Found 2 issues…", "duration_ms": 8421, "usage": {"prompt_tokens": 12450, "completion_tokens": 320}}
{"type": "tool_call", "filePath": "src/foo.go", "tool_name": "file_read", "arguments": "{\"file_path\":\"src/foo.go\",\"start_line\":1,\"end_line\":50}", "result": "File: src/foo.go (Total lines: 220)\nIS_TRUNCATED: false\nLINE_RANGE: 1-50\n1|package foo…", "ok": true, "duration_ms": 14}
```
行是 append-only——不完整的 JSONL 意味着会话在运行中被中断,查看器会渲染已写入的
内容。
要释放磁盘空间,删除整个会话文件;查看器在下次请求时重建索引。
## 隐私
JSONL 转录包含发给 LLM 和从 LLM 收到的**一切**,包括 diff 中的任何代码。它们
完全存在于你机器的 `~/.opencodereview/` 内。OCR 不会把它们上传到任何地方。
如果你的评审包含你不想长期存储的代码,可以:
- 定期删除会话文件,或
- 在 CI 中把 `--audience agent --format json` 输出重定向到临时管道,并用临时
`HOME` 运行,使 JSONL 不会被持久化。
OpenTelemetry exporter 是另一回事——如何让 prompt 内容不进入导出 trace 见
[遥测](../telemetry/)。
## 查看器*不*适用时
- 程序化后处理CI、仪表盘`ocr review --format json --audience agent`
查看器为人渲染,不为机器。
- 如需跨多会话 grep直接对 JSONL 文件用 `jq`。UI 中暂无搜索框。
## 另见
- [架构](../architecture/)——那五种任务类型在底层实际做什么。
- [工具](../tools/)——你在 `main_task` 卡片中会看到的工具调用。

View file

@ -258,11 +258,37 @@ export const en: TranslationKeys = {
'docs.mcpFieldArgsDesc': 'Command-line arguments passed to the server', 'docs.mcpFieldArgsDesc': 'Command-line arguments passed to the server',
'docs.mcpFieldEnvDesc': 'Environment variables in KEY=VALUE format', 'docs.mcpFieldEnvDesc': 'Environment variables in KEY=VALUE format',
'docs.mcpFieldToolsDesc': 'Allowed tool names; if empty, all tools from the server are available', 'docs.mcpFieldToolsDesc': 'Allowed tool names; if empty, all tools from the server are available',
'docs.mcpFieldSetupDesc': 'A shell command to run before starting the server, such as building an index (5-minute timeout)', 'docs.mcpFieldSetupDesc': 'A shell command to run before starting the server, such as building an index',
'docs.mcpNote': 'If an MCP tool name conflicts with a built-in tool, it will be skipped with a warning.', 'docs.mcpNote': 'If an MCP tool name conflicts with a built-in tool, it will be skipped with a warning. The setup command has a 5-minute timeout.',
'docs.mcpExample': 'Example: Add CodeGraph for Code Structure Analysis', 'docs.mcpExample': 'Example: Add CodeGraph for Code Structure Analysis',
'docs.envTitle': 'Claude Code Integration', 'docs.envTitle': 'Claude Code Integration',
'docs.envDesc': 'If you are already a Claude Code user with the following environment variables configured, Open Code Review will recognize them automatically — no extra configuration needed:', 'docs.envDesc': 'If you are already a Claude Code user with the following environment variables configured, Open Code Review will recognize them automatically — no extra configuration needed:',
'docs.envNote': 'You can also use <code>ocr config</code> to override or supplement these settings.', 'docs.envNote': 'You can also use <code>ocr config</code> to override or supplement these settings.',
'docs.copy': 'Copy', 'docs.copy': 'Copy',
// Docs Sidebar
'docs.sidebar.gettingStarted': 'Getting Started',
'docs.sidebar.userGuide': 'User Guide',
'docs.sidebar.overview': 'Overview',
'docs.sidebar.quickstart': 'QuickStart',
'docs.sidebar.installation': 'Installation',
'docs.sidebar.configuration': 'Configuration',
'docs.sidebar.cliReference': 'CLI Reference',
'docs.sidebar.reviewRules': 'Review Rules',
'docs.sidebar.architecture': 'Architecture',
'docs.sidebar.tools': 'Tools',
'docs.sidebar.viewer': 'Session Viewer',
'docs.sidebar.telemetry': 'Telemetry',
'docs.sidebar.integrations': 'Integrations',
'docs.sidebar.agentSkill': 'Agent Skill',
'docs.sidebar.claudeCode': 'Command (Claude Code)',
'docs.sidebar.subprocess': 'Direct Subprocess',
'docs.sidebar.cicd': 'CI/CD',
'docs.sidebar.contributing': 'Contributing',
'docs.sidebar.faq': 'FAQ',
'docs.search.placeholder': 'Search...',
'docs.search.noResults': 'No results found',
'docs.search.hint.select': 'Select',
'docs.search.hint.open': 'Open',
'docs.search.hint.close': 'Close',
}; };

View file

@ -260,11 +260,37 @@ export const ja: TranslationKeys = {
'docs.mcpFieldArgsDesc': 'server に渡すコマンドライン引数', 'docs.mcpFieldArgsDesc': 'server に渡すコマンドライン引数',
'docs.mcpFieldEnvDesc': 'KEY=VALUE 形式の環境変数', 'docs.mcpFieldEnvDesc': 'KEY=VALUE 形式の環境変数',
'docs.mcpFieldToolsDesc': '許可するツール名。空の場合は server が提供するすべてのツールを使用できます', 'docs.mcpFieldToolsDesc': '許可するツール名。空の場合は server が提供するすべてのツールを使用できます',
'docs.mcpFieldSetupDesc': 'server 起動前に実行する shell コマンド。例:インデックスの構築(タイムアウト 5 分)', 'docs.mcpFieldSetupDesc': 'server 起動前に実行する shell コマンド。例:インデックスの構築',
'docs.mcpNote': 'MCP ツール名が組み込みツールと競合する場合、そのツールは警告付きでスキップされます。', 'docs.mcpNote': 'MCP ツール名が組み込みツールと競合する場合、そのツールは警告付きでスキップされます。setup コマンドのタイムアウトは 5 分です。',
'docs.mcpExample': '例CodeGraph を追加してコード構造を分析', 'docs.mcpExample': '例CodeGraph を追加してコード構造を分析',
'docs.envTitle': 'Claude Code 統合', 'docs.envTitle': 'Claude Code 統合',
'docs.envDesc': 'すでに Claude Code ユーザーで以下の環境変数を設定済みの場合、Open Code Review は自動的に認識します。追加設定は不要です:', 'docs.envDesc': 'すでに Claude Code ユーザーで以下の環境変数を設定済みの場合、Open Code Review は自動的に認識します。追加設定は不要です:',
'docs.envNote': '<code>ocr config</code> を使用してこれらの設定を上書きまたは補完することもできます。', 'docs.envNote': '<code>ocr config</code> を使用してこれらの設定を上書きまたは補完することもできます。',
'docs.copy': 'コピー', 'docs.copy': 'コピー',
// Docs Sidebar
'docs.sidebar.gettingStarted': '入門ガイド',
'docs.sidebar.userGuide': '利用ガイド',
'docs.sidebar.overview': '概要',
'docs.sidebar.quickstart': 'クイックスタート',
'docs.sidebar.installation': 'インストール',
'docs.sidebar.configuration': '設定',
'docs.sidebar.cliReference': 'CLI リファレンス',
'docs.sidebar.reviewRules': 'レビュールール',
'docs.sidebar.architecture': 'アーキテクチャ',
'docs.sidebar.tools': 'ツール',
'docs.sidebar.viewer': 'セッションビューア',
'docs.sidebar.telemetry': 'テレメトリ',
'docs.sidebar.integrations': '統合',
'docs.sidebar.agentSkill': 'Agent Skill',
'docs.sidebar.claudeCode': 'CommandClaude Code',
'docs.sidebar.subprocess': 'Direct Subprocess',
'docs.sidebar.cicd': 'CI/CD',
'docs.sidebar.contributing': 'コントリビュート',
'docs.sidebar.faq': 'FAQ',
'docs.search.placeholder': '検索...',
'docs.search.noResults': '結果が見つかりません',
'docs.search.hint.select': '選択',
'docs.search.hint.open': '開く',
'docs.search.hint.close': '閉じる',
}; };

View file

@ -260,11 +260,37 @@ export const zh: TranslationKeys = {
'docs.mcpFieldArgsDesc': '传递给 server 的命令行参数', 'docs.mcpFieldArgsDesc': '传递给 server 的命令行参数',
'docs.mcpFieldEnvDesc': 'KEY=VALUE 格式的环境变量', 'docs.mcpFieldEnvDesc': 'KEY=VALUE 格式的环境变量',
'docs.mcpFieldToolsDesc': '允许使用的工具名称;为空时可使用该 server 提供的全部工具', 'docs.mcpFieldToolsDesc': '允许使用的工具名称;为空时可使用该 server 提供的全部工具',
'docs.mcpFieldSetupDesc': '启动 server 前运行的 shell 命令,例如构建索引(超时 5 分钟)', 'docs.mcpFieldSetupDesc': '启动 server 前运行的 shell 命令,例如构建索引',
'docs.mcpNote': '如果 MCP 工具名称与内置工具冲突,该工具将被跳过并显示警告。', 'docs.mcpNote': '如果 MCP 工具名称与内置工具冲突,该工具将被跳过并显示警告。setup 命令的超时时间为 5 分钟。',
'docs.mcpExample': '示例:添加 CodeGraph 进行代码结构分析', 'docs.mcpExample': '示例:添加 CodeGraph 进行代码结构分析',
'docs.envTitle': 'Claude Code 集成', 'docs.envTitle': 'Claude Code 集成',
'docs.envDesc': '如果您已经是 Claude Code 用户并配置了以下环境变量Open Code Review 将自动识别——无需额外配置:', 'docs.envDesc': '如果您已经是 Claude Code 用户并配置了以下环境变量Open Code Review 将自动识别——无需额外配置:',
'docs.envNote': '您也可以使用 <code>ocr config</code> 覆盖或补充这些设置。', 'docs.envNote': '您也可以使用 <code>ocr config</code> 覆盖或补充这些设置。',
'docs.copy': '复制', 'docs.copy': '复制',
// Docs Sidebar
'docs.sidebar.gettingStarted': '入门指南',
'docs.sidebar.userGuide': '使用指南',
'docs.sidebar.overview': '概览',
'docs.sidebar.quickstart': '快速开始',
'docs.sidebar.installation': '安装',
'docs.sidebar.configuration': '配置',
'docs.sidebar.cliReference': 'CLI 参考',
'docs.sidebar.reviewRules': '评审规则',
'docs.sidebar.architecture': '架构',
'docs.sidebar.tools': '工具',
'docs.sidebar.viewer': '会话查看器',
'docs.sidebar.telemetry': '遥测',
'docs.sidebar.integrations': '集成',
'docs.sidebar.agentSkill': 'Agent Skill',
'docs.sidebar.claudeCode': 'CommandClaude Code',
'docs.sidebar.subprocess': 'Direct Subprocess',
'docs.sidebar.cicd': 'CI/CD',
'docs.sidebar.contributing': '贡献',
'docs.sidebar.faq': 'FAQ',
'docs.search.placeholder': '搜索...',
'docs.search.noResults': '未找到结果',
'docs.search.hint.select': '选择',
'docs.search.hint.open': '打开',
'docs.search.hint.close': '关闭',
}; };

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,242 @@
/* Docs Markdown dark theme styles — matching DocsPage design */
.docs-markdown {
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Segoe UI', Roboto, sans-serif;
font-size: 14px;
line-height: 24px;
color: rgba(255, 255, 255, 0.7);
}
.docs-markdown h1 {
font-size: 28px;
font-weight: 700;
color: #FFFFFF;
margin: 0 0 24px 0;
line-height: 36px;
}
.docs-markdown h2 {
font-size: 20px;
font-weight: 600;
color: #FFFFFF;
margin: 40px 0 16px 0;
line-height: 28px;
}
.docs-markdown h3 {
font-size: 16px;
font-weight: 600;
color: #FFFFFF;
margin: 28px 0 12px 0;
line-height: 24px;
}
.docs-markdown h4 {
font-size: 14px;
font-weight: 600;
color: rgba(255, 255, 255, 0.9);
margin: 20px 0 8px 0;
line-height: 22px;
}
.docs-markdown p {
margin: 0 0 12px 0;
color: rgba(255, 255, 255, 0.6);
line-height: 24px;
}
.docs-markdown a {
color: #2BDE5E;
text-decoration: none;
transition: opacity 0.2s;
}
.docs-markdown a:hover {
opacity: 0.8;
text-decoration: underline;
}
/* Inline code */
.docs-markdown code {
font-family: 'Menlo', 'Monaco', 'Consolas', monospace;
font-size: 13px;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 4px;
padding: 2px 6px;
color: rgba(255, 255, 255, 0.85);
}
/* Code blocks */
.docs-markdown pre {
background: #000000;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 6px;
padding: 4px 16px;
margin: 0 0 16px 0;
overflow-x: auto;
display: flex;
align-self: stretch;
justify-content: space-between;
align-items: center;
}
.docs-markdown pre code {
background: none;
border: none;
padding: 0;
font-size: 13px;
line-height: 22px;
color: rgba(255, 255, 255, 0.8);
white-space: pre;
display: block;
flex: 1;
min-width: 0;
}
/* Lists */
.docs-markdown ul,
.docs-markdown ol {
margin: 0 0 16px 0;
padding-left: 24px;
}
.docs-markdown li {
margin-bottom: 6px;
color: rgba(255, 255, 255, 0.6);
line-height: 24px;
}
.docs-markdown li > ul,
.docs-markdown li > ol {
margin-top: 6px;
margin-bottom: 0;
}
/* Tables */
.docs-markdown table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
margin: 0 0 16px 0;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.16);
}
.docs-markdown thead th {
background: rgba(255, 255, 255, 0.04);
font-size: 13px;
font-weight: 500;
color: rgba(255, 255, 255, 0.6);
text-align: left;
padding: 12px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.docs-markdown thead th:first-child {
border-top-left-radius: 8px;
}
.docs-markdown thead th:last-child {
border-top-right-radius: 8px;
}
.docs-markdown tbody td {
font-size: 13px;
color: rgba(255, 255, 255, 0.6);
padding: 12px 16px;
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
vertical-align: top;
}
.docs-markdown tbody tr:last-child td {
border-bottom: none;
}
.docs-markdown tbody tr:last-child td:first-child {
border-bottom-left-radius: 8px;
}
.docs-markdown tbody tr:last-child td:last-child {
border-bottom-right-radius: 8px;
}
/* Blockquotes */
.docs-markdown blockquote {
margin: 0 0 16px 0;
padding: 12px 16px;
border-left: 2px solid #2BDE5E;
background: rgba(255, 255, 255, 0.06);
border-radius: 0 6px 6px 0;
}
.docs-markdown blockquote p {
margin: 0;
color: rgba(255, 255, 255, 0.6);
font-size: 13px;
}
.docs-markdown blockquote p + p {
margin-top: 8px;
}
/* Horizontal rule */
.docs-markdown hr {
border: none;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin: 32px 0;
}
/* Strong / bold */
.docs-markdown strong {
color: rgba(255, 255, 255, 0.9);
font-weight: 600;
}
/* Em / italic */
.docs-markdown em {
font-style: italic;
color: rgba(255, 255, 255, 0.7);
}
/* Images */
.docs-markdown img {
max-width: 100%;
border-radius: 8px;
margin: 8px 0;
}
/* Mermaid code blocks — hide raw source (will be replaced by rendered SVG) */
.docs-markdown pre code.language-mermaid {
display: none;
}
/* Rendered mermaid diagrams */
.docs-markdown .mermaid-rendered {
margin: 16px 0;
padding: 24px 16px;
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
overflow-x: auto;
display: flex;
justify-content: center;
}
.docs-markdown .mermaid-rendered svg {
max-width: 100%;
height: auto;
}
/* Scrollbar styling for code blocks */
.docs-markdown pre::-webkit-scrollbar {
height: 6px;
}
.docs-markdown pre::-webkit-scrollbar-track {
background: transparent;
}
.docs-markdown pre::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
border-radius: 3px;
}

View file

@ -0,0 +1,10 @@
/**
* Shared utility to generate heading IDs from text.
* Used by both extractHeadings (DocsPage TOC) and MarkdownRenderer (heading renderer)
* to ensure consistent anchor IDs.
*/
export function generateHeadingId(text: string): string {
// Strip HTML tags first (from marked output), then strip markdown formatting chars
const plain = text.replace(/<[^>]+>/g, '').replace(/[`*_\[\]()]/g, '').trim();
return plain.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, '-').replace(/^-|-$/g, '');
}

View file

@ -36,6 +36,10 @@ module.exports = {
{ {
test: /\.(png|jpg|jpeg|gif)$/, test: /\.(png|jpg|jpeg|gif)$/,
type: 'asset/resource' type: 'asset/resource'
},
{
test: /\.md$/,
type: 'asset/source'
} }
] ]
}, },