d7d5151a1f
CI / test (pull_request) Successful in 15s
CI / review (/openai/v1, gpt-4.1, gpt41, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (/anthropic/v1, claude-sonnet-4-6, sonnet, anthropic, SONNET_REVIEW_TOKEN) (pull_request) Failing after 17s
CI / review (/openai/v1, gpt-4.1-mini, gpt41-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 16s
CI / review (/openai/v1, gpt-5-mini, gpt5-mini, openai, GPT_REVIEW_TOKEN) (pull_request) Failing after 14s
CI / review (/openai/v1, gpt-5, security, openai, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m28s
CI / review (/openai/v1, gpt-5, gpt, openai, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m41s
Implement role-based review personas that provide specialized review focus: - Security: vulnerabilities, auth, secrets, injection attacks - Architect: design patterns, code organization, API contracts - Docs: documentation quality, API clarity, error messages Changes: - Add persona loading from JSON files and embedded built-ins - Add --persona and --persona-file CLI flags (mutually exclusive) - Add BuildPersonaSystemPrompt for persona-specific prompts - Add FormatMarkdownWithDisplay for persona display names - Update action.yml with persona and persona-file inputs - Add comprehensive tests for all new functionality - Document personas in README with examples The persona system replaces the generic 'You are an expert code reviewer' prompt with domain-specific identity, focus areas, ignore list, and severity calibration. This reduces redundancy between multiple reviewers and catches domain-specific issues that generic reviewers miss. Closes #51
117 lines
4.5 KiB
Go
117 lines
4.5 KiB
Go
package review
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// BuildPersonaSystemPrompt constructs a system prompt from a persona definition.
|
|
// This replaces BuildSystemBase when a persona is provided.
|
|
func BuildPersonaSystemPrompt(p *Persona) string {
|
|
var sb strings.Builder
|
|
|
|
// Identity section
|
|
sb.WriteString(p.Identity)
|
|
sb.WriteString("\n\n")
|
|
|
|
// Focus section
|
|
if len(p.Focus) > 0 {
|
|
sb.WriteString("## Focus Areas\n\n")
|
|
sb.WriteString("Concentrate your review on:\n")
|
|
for _, f := range p.Focus {
|
|
sb.WriteString(fmt.Sprintf("- %s\n", f))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
// Ignore section
|
|
if len(p.Ignore) > 0 {
|
|
sb.WriteString("## Explicitly Out of Scope\n\n")
|
|
sb.WriteString("Do NOT comment on:\n")
|
|
for _, i := range p.Ignore {
|
|
sb.WriteString(fmt.Sprintf("- %s\n", i))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
// Severity calibration
|
|
if p.Severity.Major != "" || p.Severity.Minor != "" || p.Severity.Nit != "" {
|
|
sb.WriteString("## Severity Calibration\n\n")
|
|
sb.WriteString("Use these severity definitions for YOUR domain:\n")
|
|
if p.Severity.Major != "" {
|
|
sb.WriteString(fmt.Sprintf("- **MAJOR**: %s\n", p.Severity.Major))
|
|
}
|
|
if p.Severity.Minor != "" {
|
|
sb.WriteString(fmt.Sprintf("- **MINOR**: %s\n", p.Severity.Minor))
|
|
}
|
|
if p.Severity.Nit != "" {
|
|
sb.WriteString(fmt.Sprintf("- **NIT**: %s\n", p.Severity.Nit))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
// Output format instructions (same as base, but with persona context)
|
|
sb.WriteString("## Review Instructions\n\n")
|
|
sb.WriteString("CONTEXT:\n")
|
|
sb.WriteString("- You will receive the full content of modified files for reference, followed by the diff showing what changed.\n")
|
|
sb.WriteString("- The diff shows ONLY what was added/removed. The full file content provides complete context.\n")
|
|
sb.WriteString("- Focus your review on the CHANGES (the diff), using the full files for context.\n\n")
|
|
sb.WriteString("Your task:\n")
|
|
sb.WriteString("1. Review the diff for issues within YOUR focus areas only.\n")
|
|
sb.WriteString("2. Consider the CI status — if CI has failed, that is an automatic REQUEST_CHANGES regardless of code quality.\n")
|
|
sb.WriteString("3. Output your review as structured JSON (and ONLY JSON, no markdown fences or other text).\n\n")
|
|
sb.WriteString("Output format:\n")
|
|
sb.WriteString("{\n")
|
|
sb.WriteString(" \"verdict\": \"APPROVE\" or \"REQUEST_CHANGES\",\n")
|
|
sb.WriteString(" \"summary\": \"Brief overall assessment (1-3 sentences)\",\n")
|
|
sb.WriteString(" \"findings\": [\n")
|
|
sb.WriteString(" {\n")
|
|
sb.WriteString(" \"severity\": \"MAJOR\" or \"MINOR\" or \"NIT\",\n")
|
|
sb.WriteString(" \"file\": \"path/to/file\",\n")
|
|
sb.WriteString(" \"line\": <line number from the diff>,\n")
|
|
sb.WriteString(" \"finding\": \"Description of the issue\"\n")
|
|
sb.WriteString(" }\n")
|
|
sb.WriteString(" ],\n")
|
|
sb.WriteString(" \"recommendation\": \"Full recommendation text explaining your verdict\"\n")
|
|
sb.WriteString("}\n\n")
|
|
sb.WriteString("Rules:\n")
|
|
sb.WriteString("- If there are any MAJOR findings → verdict must be REQUEST_CHANGES\n")
|
|
sb.WriteString("- If there are no MAJOR findings → verdict should be APPROVE\n")
|
|
sb.WriteString("- If CI has failed → verdict must be REQUEST_CHANGES with a finding noting the CI failure\n")
|
|
sb.WriteString("- Only report findings within your focus areas. Ignore everything else.\n")
|
|
sb.WriteString("- Line numbers should reference the new file line numbers from the diff headers.\n")
|
|
sb.WriteString("- If the diff has no changes relevant to your focus areas, APPROVE with no findings.\n")
|
|
|
|
// Custom output format if provided
|
|
if p.OutputFormat != "" {
|
|
sb.WriteString("\n\n## Additional Output Guidelines\n\n")
|
|
sb.WriteString(p.OutputFormat)
|
|
}
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
// BuildSystemPromptWithPersona constructs the full system prompt, using either
|
|
// a persona or the default generic prompt.
|
|
func BuildSystemPromptWithPersona(persona *Persona, conventions, patterns string) string {
|
|
var base string
|
|
if persona != nil {
|
|
base = BuildPersonaSystemPrompt(persona)
|
|
} else {
|
|
base = BuildSystemBase()
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString(base)
|
|
|
|
if patterns != "" {
|
|
sb.WriteString(fmt.Sprintf("\n\n## Language Patterns & Idioms\n\nUse the following patterns as review criteria. Code that violates these established patterns is a finding:\n\n%s\n", patterns))
|
|
}
|
|
|
|
if conventions != "" {
|
|
sb.WriteString(fmt.Sprintf("\n\n## Repository Conventions\n\nThe repository has the following coding conventions that must be respected:\n\n%s\n", conventions))
|
|
}
|
|
|
|
return sb.String()
|
|
}
|