Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ ocr review \

The `--format json` and `--audience agent` flags output machine-readable results suitable for parsing in CI scripts.

Each finding in the JSON output may include two optional structured fields so CI
integrations can sort, group, or gate builds without re-parsing comment text:

| Field | Allowed values | Notes |
|-------|----------------|-------|
| `category` | `bug`, `security`, `performance`, `maintainability`, `test`, `style`, `documentation`, `other` | Classifies the kind of issue. |
| `severity` | `critical`, `high`, `medium`, `low`, `info` | Indicates the importance of the issue. |

Both fields are optional and emitted only when the model populates them, so existing
consumers that ignore them are unaffected (the keys are omitted entirely when empty).

See the [`examples/`](./examples/) directory for integration examples:

- [`github_actions/`](./examples/github_actions/) — GitHub Actions integration example
Expand Down
9 changes: 9 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,15 @@ ocr review \

`--format json` 和 `--audience agent` 参数输出适合 CI 脚本解析的机器可读结果。

JSON 输出中的每条评审结果可包含两个可选的结构化字段,便于 CI 集成在无需解析评论文本的情况下排序、分组或卡点构建:

| 字段 | 允许的取值 | 说明 |
|------|-----------|------|
| `category` | `bug`、`security`、`performance`、`maintainability`、`test`、`style`、`documentation`、`other` | 标识问题的类别。 |
| `severity` | `critical`、`high`、`medium`、`low`、`info` | 标识问题的严重程度。 |

这两个字段均为可选,仅在模型填充时才会输出,因此忽略它们的现有消费方不受影响(字段为空时会被完全省略)。

集成示例请参见 [`examples/`](./examples/) 目录:

- [`github_actions/`](./examples/github_actions/) — GitHub Actions 集成示例
Expand Down
18 changes: 18 additions & 0 deletions cmd/opencodereview/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ func renderComment(comment model.LlmComment) {

fmt.Printf("\n\033[2m─── %s:%d-%d ───\033[0m\n", comment.Path, comment.StartLine, comment.EndLine)

if badge := buildBadge(comment); badge != "" {
fmt.Printf("%s\n", badge)
}

if comment.Content != "" {
for _, ln := range wrapByRunes(comment.Content, 100) {
fmt.Printf("%s\n", ln)
Expand All @@ -81,6 +85,20 @@ func renderComment(comment model.LlmComment) {
fmt.Println()
}

// buildBadge renders a compact "[severity:high] [category:security]" prefix line
// for a comment. It returns an empty string when both fields are absent, so existing
// text output is unchanged for findings without structured metadata.
func buildBadge(comment model.LlmComment) string {
var parts []string
if comment.Severity != "" {
parts = append(parts, fmt.Sprintf("[severity:%s]", comment.Severity))
}
if comment.Category != "" {
parts = append(parts, fmt.Sprintf("[category:%s]", comment.Category))
}
return strings.Join(parts, " ")
}

// printDiffLine renders a single diff line with colored prefix and background on content.
func printDiffLine(prefix, content, fgColor, bgColor string) {
fmt.Printf("%s%s%s %s%s\033[0m\n", fgColor+bgColor, prefix, "\033[0m"+bgColor, content, "\033[0m")
Expand Down
2 changes: 1 addition & 1 deletion internal/config/template/task_template.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"messages": [
{
"role": "system",
"content": "## Role\nYou are a code review assistant developed by Alibaba. You are skilled at code review in the software development process and are responsible for providing professional review feedback for code changes that are about to be submitted. Your feedback perfectly combines detailed analysis with contextual explanations.\nYou are working in an IDE with editor concepts for open files and an integrated terminal. The user's developed code is stored in the IDE's staging area.\nBefore users commit staged code to remote repositories, they will send you tasks to help them complete the process successfully. Each time a user sends a task, it will be placed in <user_task>, and you will use <tool> to interact with the real world when executing tasks.\nPlease keep your responses concise and objective.\n\n## Capabilities\n- Think step by step progressively.\n- First understand the code changes to be reviewed. Code changes are provided in Unified Diff format, where lines starting with `-` indicate deleted code, lines starting with `+` indicate added code, consecutive `-` and `+` lines represent modified code, and other lines represent unchanged code.\n- Be objective and neutral, make judgments based on facts and logic, avoid subjective assumptions. When the context is unclear, use tools to obtain contextual information rather than judging based on assumptions.\n- For the current code changes, provide feedback opinions, pointing out areas for improvement or potential issues. Focus on issues in newly added code.\n- Avoid commenting on correct code or unchanged code.\n- Avoid commenting on deleted code; deleted code serves only as reference context.\n- Focus on clarity, practicality, and comprehensiveness.\n- Use developer-friendly terminology and analogies in explanations.\n- Focus primarily on the actual code logic and functionality. Avoid commenting on or providing feedback about non-functional elements such as code comments, tool-generated indicators (like @Generated annotations), or other metadata, unless the user explicitly requests you to review these elements.\n\n## Strict Focus Rules\n- Context tools are for understanding purposes only. Findings from other files must NOT become the subject of your comments.\n- If you discover a potential issue in another file while gathering context, ignore it — your task is limited to the current diffs.\n\n## Reply limit\n- If the current code review task is complete, call `task_done` to end the task.\n- If a code issue has been identified and confirmed, call the `code_comment` tool to provide feedback.\n- If additional context is needed to confirm the issue, call the appropriate context tool."
"content": "## Role\nYou are a code review assistant developed by Alibaba. You are skilled at code review in the software development process and are responsible for providing professional review feedback for code changes that are about to be submitted. Your feedback perfectly combines detailed analysis with contextual explanations.\nYou are working in an IDE with editor concepts for open files and an integrated terminal. The user's developed code is stored in the IDE's staging area.\nBefore users commit staged code to remote repositories, they will send you tasks to help them complete the process successfully. Each time a user sends a task, it will be placed in <user_task>, and you will use <tool> to interact with the real world when executing tasks.\nPlease keep your responses concise and objective.\n\n## Capabilities\n- Think step by step progressively.\n- First understand the code changes to be reviewed. Code changes are provided in Unified Diff format, where lines starting with `-` indicate deleted code, lines starting with `+` indicate added code, consecutive `-` and `+` lines represent modified code, and other lines represent unchanged code.\n- Be objective and neutral, make judgments based on facts and logic, avoid subjective assumptions. When the context is unclear, use tools to obtain contextual information rather than judging based on assumptions.\n- For the current code changes, provide feedback opinions, pointing out areas for improvement or potential issues. Focus on issues in newly added code.\n- Avoid commenting on correct code or unchanged code.\n- Avoid commenting on deleted code; deleted code serves only as reference context.\n- Focus on clarity, practicality, and comprehensiveness.\n- Use developer-friendly terminology and analogies in explanations.\n- Focus primarily on the actual code logic and functionality. Avoid commenting on or providing feedback about non-functional elements such as code comments, tool-generated indicators (like @Generated annotations), or other metadata, unless the user explicitly requests you to review these elements.\n- When reporting an issue with the `code_comment` tool, set its `category` field to one of: bug, security, performance, maintainability, test, style, documentation, other; and set its `severity` field to one of: critical, high, medium, low, info. Choose the value that best fits the finding so consumers can sort and filter results.\n\n## Strict Focus Rules\n- Context tools are for understanding purposes only. Findings from other files must NOT become the subject of your comments.\n- If you discover a potential issue in another file while gathering context, ignore it — your task is limited to the current diffs.\n\n## Reply limit\n- If the current code review task is complete, call `task_done` to end the task.\n- If a code issue has been identified and confirmed, call the `code_comment` tool to provide feedback.\n- If additional context is needed to confirm the issue, call the appropriate context tool."
},
{
"role": "user",
Expand Down
8 changes: 8 additions & 0 deletions internal/config/toolsconfig/tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
"suggestion_code": {
"type": "string",
"description": "Corresponding suggested code snippet, maintaining consistent code style."
},
"category": {
"type": "string",
"description": "Issue category. One of: bug, security, performance, maintainability, test, style, documentation, other."
},
"severity": {
"type": "string",
"description": "Issue severity. One of: critical, high, medium, low, info."
}
},
"required": [
Expand Down
6 changes: 6 additions & 0 deletions internal/model/review.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ type LlmComment struct {
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
Thinking string `json:"thinking,omitempty"`
// Category is the optional finding classification. Allowed values:
// bug, security, performance, maintainability, test, style, documentation, other.
Category string `json:"category,omitempty"`
// Severity is the optional finding importance. Allowed values:
// critical, high, medium, low, info.
Severity string `json:"severity,omitempty"`
}

// CodeReviewResult holds raw LLM-generated review suggestion for a code segment.
Expand Down
6 changes: 6 additions & 0 deletions internal/tool/code_comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ func ParseComments(args map[string]any) ([]model.LlmComment, string) {
if thinking, ok := obj["thinking"].(string); ok {
cm.Thinking = thinking
}
if category, ok := obj["category"].(string); ok {
cm.Category = category
}
if severity, ok := obj["severity"].(string); ok {
cm.Severity = severity
}
if path, ok := args["path"].(string); ok {
cm.Path = path
}
Expand Down
108 changes: 108 additions & 0 deletions internal/tool/code_comment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package tool

import (
"encoding/json"
"strings"
"testing"

"github.com/open-code-review/open-code-review/internal/model"
)

// TestParseComments_CategorySeverityParsed verifies that the optional structured
// category and severity fields are read off each comment object when present.
func TestParseComments_CategorySeverityParsed(t *testing.T) {
args := map[string]any{
"path": "main.go",
"comments": []any{
map[string]any{
"content": "Potential nil pointer dereference.",
"existing_code": "x := *p",
"category": "bug",
"severity": "high",
},
},
}

comments, errMsg := ParseComments(args)
if errMsg != "" {
t.Fatalf("unexpected error message: %s", errMsg)
}
if len(comments) != 1 {
t.Fatalf("expected 1 comment, got %d", len(comments))
}
if got := comments[0].Category; got != "bug" {
t.Errorf("category = %q, want %q", got, "bug")
}
if got := comments[0].Severity; got != "high" {
t.Errorf("severity = %q, want %q", got, "high")
}
}

// TestParseComments_CategorySeverityOptional verifies that a finding without the
// new fields is still accepted and leaves them zero-valued (backward compatible).
func TestParseComments_CategorySeverityOptional(t *testing.T) {
args := map[string]any{
"path": "main.go",
"comments": []any{
map[string]any{
"content": "Consider renaming for clarity.",
"existing_code": "a := 1",
},
},
}

comments, errMsg := ParseComments(args)
if errMsg != "" {
t.Fatalf("unexpected error message: %s", errMsg)
}
if len(comments) != 1 {
t.Fatalf("expected 1 comment, got %d", len(comments))
}
if comments[0].Category != "" {
t.Errorf("category = %q, want empty", comments[0].Category)
}
if comments[0].Severity != "" {
t.Errorf("severity = %q, want empty", comments[0].Severity)
}
}

// TestLlmComment_JSONOmitsEmptyCategorySeverity verifies that omitempty drops the
// keys entirely when unset, so existing JSON consumers are unaffected.
func TestLlmComment_JSONOmitsEmptyCategorySeverity(t *testing.T) {
cm := model.LlmComment{
Path: "main.go",
Content: "no metadata",
}
b, err := json.Marshal(cm)
if err != nil {
t.Fatalf("marshal: %v", err)
}
out := string(b)
if strings.Contains(out, "category") {
t.Errorf("expected no category key, got %s", out)
}
if strings.Contains(out, "severity") {
t.Errorf("expected no severity key, got %s", out)
}
}

// TestLlmComment_JSONSerializesCategorySeverity verifies the fields serialize when set.
func TestLlmComment_JSONSerializesCategorySeverity(t *testing.T) {
cm := model.LlmComment{
Path: "main.go",
Content: "sql injection",
Category: "security",
Severity: "critical",
}
b, err := json.Marshal(cm)
if err != nil {
t.Fatalf("marshal: %v", err)
}
out := string(b)
if !strings.Contains(out, `"category":"security"`) {
t.Errorf("expected category in output, got %s", out)
}
if !strings.Contains(out, `"severity":"critical"`) {
t.Errorf("expected severity in output, got %s", out)
}
}