feat: full file context + patterns-repo support
Major improvements to review quality: 1. Full file context: fetch complete content of all modified files from the PR branch and include as reference. This eliminates false-positive "missing import" findings since the model sees the entire file. 2. Patterns repo: new --patterns-repo / PATTERNS_REPO flag fetches language idiom files from a separate Gitea repo (e.g. rodin/elixir-patterns) and includes them as review criteria. 3. Multi-file patterns: --patterns-files / PATTERNS_FILES accepts comma-separated file paths to fetch from the patterns repo. New API methods: - GetPullRequestFiles: list changed files in a PR - GetFileContentRef: fetch file content from a specific branch/ref Prompt changes: - BuildSystemPrompt now accepts (conventions, patterns) - BuildUserPrompt now accepts fileContext parameter - File context displayed before diff for model reference - Patterns presented as "review criteria" in system prompt Composite action updated with patterns-repo and patterns-files inputs.
This commit is contained in:
+18
-10
@@ -6,15 +6,14 @@ import (
|
||||
)
|
||||
|
||||
// BuildSystemPrompt constructs the system prompt for the LLM reviewer.
|
||||
func BuildSystemPrompt(conventions string) string {
|
||||
func BuildSystemPrompt(conventions, patterns string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString("You are an expert code reviewer. Review the provided pull request diff carefully.\n\n")
|
||||
sb.WriteString("IMPORTANT CONTEXT:\n")
|
||||
sb.WriteString("- You are reviewing a DIFF, not the complete file. Code not shown in the diff already exists in the repository.\n")
|
||||
sb.WriteString("- Imports, type definitions, functions, and other declarations that do not appear in the diff are already present in the file.\n")
|
||||
sb.WriteString("- Do NOT flag missing imports, missing type definitions, or undefined references unless the diff itself introduces a new usage without a corresponding addition in the same diff.\n")
|
||||
sb.WriteString("- Only flag issues with code that is actually being ADDED or MODIFIED in this diff.\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 correctness, idiomatic code, potential bugs, and design issues.\n")
|
||||
sb.WriteString("2. Consider the CI status — if CI has failed, that is an automatic REQUEST_CHANGES regardless of code quality.\n")
|
||||
@@ -40,17 +39,20 @@ func BuildSystemPrompt(conventions string) string {
|
||||
sb.WriteString("- Be thorough but fair. Don't nitpick style unless it impacts readability significantly.\n")
|
||||
sb.WriteString("- Line numbers should reference the new file line numbers from the diff headers.\n")
|
||||
sb.WriteString("- If the diff is empty or trivial (only formatting/whitespace), APPROVE with no findings.\n")
|
||||
sb.WriteString("- Never flag 'missing imports' or 'undefined' errors for symbols that could exist in the unchanged portions of the file.\n")
|
||||
|
||||
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\nThe repository has the following coding conventions that should be respected:\n\n%s\n", 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()
|
||||
}
|
||||
|
||||
// BuildUserPrompt constructs the user message with PR context.
|
||||
func BuildUserPrompt(title, description, diff string, ciPassed bool, ciDetails string) string {
|
||||
func BuildUserPrompt(title, description, diff, fileContext string, ciPassed bool, ciDetails string) string {
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(fmt.Sprintf("## Pull Request: %s\n\n", title))
|
||||
@@ -69,7 +71,13 @@ func BuildUserPrompt(title, description, diff string, ciPassed bool, ciDetails s
|
||||
sb.WriteString(fmt.Sprintf("CI Details: %s\n", ciDetails))
|
||||
}
|
||||
|
||||
sb.WriteString("\n### Diff\n\n")
|
||||
if fileContext != "" {
|
||||
sb.WriteString("\n### Full File Context (modified files)\n\n")
|
||||
sb.WriteString(fileContext)
|
||||
sb.WriteString("\n")
|
||||
}
|
||||
|
||||
sb.WriteString("\n### Diff (changes to review)\n\n")
|
||||
sb.WriteString("```diff\n")
|
||||
sb.WriteString(diff)
|
||||
sb.WriteString("\n```\n")
|
||||
|
||||
+47
-6
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
func TestBuildSystemPrompt_NoConventions(t *testing.T) {
|
||||
prompt := BuildSystemPrompt("")
|
||||
prompt := BuildSystemPrompt("", "")
|
||||
|
||||
if !strings.Contains(prompt, "expert code reviewer") {
|
||||
t.Error("expected system prompt to mention code reviewer role")
|
||||
@@ -18,7 +18,7 @@ func TestBuildSystemPrompt_NoConventions(t *testing.T) {
|
||||
|
||||
func TestBuildSystemPrompt_WithConventions(t *testing.T) {
|
||||
conventions := "- Use stdlib only\n- No panics\n"
|
||||
prompt := BuildSystemPrompt(conventions)
|
||||
prompt := BuildSystemPrompt(conventions, "")
|
||||
|
||||
if !strings.Contains(prompt, "coding conventions") {
|
||||
t.Error("expected conventions section")
|
||||
@@ -29,7 +29,7 @@ func TestBuildSystemPrompt_WithConventions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildUserPrompt_Basic(t *testing.T) {
|
||||
prompt := BuildUserPrompt("Fix bug", "Fixes the crash", "diff content here", true, "all checks passed")
|
||||
prompt := BuildUserPrompt("Fix bug", "Fixes the crash", "diff content here", "", true, "all checks passed")
|
||||
|
||||
if !strings.Contains(prompt, "Fix bug") {
|
||||
t.Error("expected PR title")
|
||||
@@ -46,7 +46,7 @@ func TestBuildUserPrompt_Basic(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildUserPrompt_CIFailed(t *testing.T) {
|
||||
prompt := BuildUserPrompt("Add tests", "", "some diff", false, "lint: failed")
|
||||
prompt := BuildUserPrompt("Add tests", "", "some diff", "", false, "lint: failed")
|
||||
|
||||
if !strings.Contains(prompt, "FAILED") {
|
||||
t.Error("expected CI status FAILED")
|
||||
@@ -57,7 +57,7 @@ func TestBuildUserPrompt_CIFailed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBuildUserPrompt_NoDescription(t *testing.T) {
|
||||
prompt := BuildUserPrompt("Quick fix", "", "diff", true, "")
|
||||
prompt := BuildUserPrompt("Quick fix", "", "diff", "", true, "")
|
||||
|
||||
if strings.Contains(prompt, "### Description") {
|
||||
t.Error("should not contain Description header when body is empty")
|
||||
@@ -66,7 +66,7 @@ func TestBuildUserPrompt_NoDescription(t *testing.T) {
|
||||
|
||||
func TestBuildUserPrompt_DiffIncluded(t *testing.T) {
|
||||
diff := "+func Hello() string {\n+\treturn \"hello\"\n+}"
|
||||
prompt := BuildUserPrompt("Greeting", "Add greeting func", diff, true, "")
|
||||
prompt := BuildUserPrompt("Greeting", "Add greeting func", diff, "", true, "")
|
||||
|
||||
if !strings.Contains(prompt, "```diff") {
|
||||
t.Error("expected diff fence")
|
||||
@@ -75,3 +75,44 @@ func TestBuildUserPrompt_DiffIncluded(t *testing.T) {
|
||||
t.Error("expected diff content in prompt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemPrompt_WithPatterns(t *testing.T) {
|
||||
patterns := "## Naming: use snake_case for functions"
|
||||
prompt := BuildSystemPrompt("", patterns)
|
||||
if !strings.Contains(prompt, "Language Patterns") {
|
||||
t.Error("expected patterns section header")
|
||||
}
|
||||
if !strings.Contains(prompt, "snake_case") {
|
||||
t.Error("expected patterns content")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildSystemPrompt_WithBoth(t *testing.T) {
|
||||
conventions := "Run mix format before commit"
|
||||
patterns := "Use pipe operator for transformations"
|
||||
prompt := BuildSystemPrompt(conventions, patterns)
|
||||
if !strings.Contains(prompt, "Repository Conventions") {
|
||||
t.Error("expected conventions section")
|
||||
}
|
||||
if !strings.Contains(prompt, "Language Patterns") {
|
||||
t.Error("expected patterns section")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUserPrompt_WithFileContext(t *testing.T) {
|
||||
fileContext := "--- main.go ---\npackage main\n"
|
||||
prompt := BuildUserPrompt("Fix", "desc", "diff here", fileContext, true, "")
|
||||
if !strings.Contains(prompt, "Full File Context") {
|
||||
t.Error("expected file context section")
|
||||
}
|
||||
if !strings.Contains(prompt, "package main") {
|
||||
t.Error("expected file content in prompt")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildUserPrompt_WithoutFileContext(t *testing.T) {
|
||||
prompt := BuildUserPrompt("Fix", "desc", "diff here", "", true, "")
|
||||
if strings.Contains(prompt, "Full File Context") {
|
||||
t.Error("should not include file context section when empty")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user