This repository has been archived on 2026-07-17. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
gitea-codex/internal/review/review.go
T
Space-Banane 85c0e735dc
ci / test (pull_request) Successful in 21s
ci / publish (pull_request) Has been skipped
fix. harden Go review flow
Fix runner bootstrap and auth handling, preserve queued SHAs, make event/job acceptance atomic, fence stale runs, correct retries and prompts, add fake end-to-end coverage, and fix deployment defaults.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-12 22:20:48 +02:00

217 lines
6.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package review
import (
"encoding/json"
"fmt"
"strings"
"gopkg.in/yaml.v3"
"gitea-codex-bot/internal/domain"
)
type rawConfig struct {
Enabled *bool `yaml:"enabled"`
Review struct {
DefaultMode string `yaml:"default_mode"`
MaxDiffBytes int `yaml:"max_diff_bytes"`
IncludeTests bool `yaml:"include_tests"`
Focus []string `yaml:"focus"`
} `yaml:"review"`
Ignore []string `yaml:"ignore"`
}
func ParseRepoConfig(text string) (domain.RepoReviewConfig, error) {
cfg := domain.DefaultRepoReviewConfig()
cfg.Configured = true
var raw rawConfig
if err := yaml.Unmarshal([]byte(text), &raw); err != nil {
return domain.RepoReviewConfig{}, err
}
if raw.Enabled != nil {
cfg.Enabled = *raw.Enabled
}
if raw.Review.DefaultMode != "" {
cfg.DefaultMode = strings.ToLower(strings.TrimSpace(raw.Review.DefaultMode))
}
if raw.Review.MaxDiffBytes > 0 {
cfg.MaxDiffBytes = raw.Review.MaxDiffBytes
}
cfg.IncludeTests = raw.Review.IncludeTests
if raw.Review.Focus != nil {
cfg.Focus = boundedStrings(raw.Review.Focus, 32, 200)
}
cfg.Ignore = boundedStrings(raw.Ignore, 128, 500)
return cfg, nil
}
func MissingRepoConfig() domain.RepoReviewConfig {
cfg := domain.DefaultRepoReviewConfig()
cfg.Configured = false
return cfg
}
func ApplyServerMaxDiff(cfg *domain.RepoReviewConfig, serverMax int) {
if serverMax <= 0 {
return
}
if cfg.MaxDiffBytes <= 0 || cfg.MaxDiffBytes > serverMax {
cfg.MaxDiffBytes = serverMax
}
}
func boundedStrings(input []string, maxItems, maxLen int) []string {
out := make([]string, 0, min(len(input), maxItems))
for _, item := range input {
item = strings.TrimSpace(item)
if item != "" && len(item) <= maxLen {
out = append(out, item)
}
if len(out) == maxItems {
break
}
}
return out
}
func ResolveMode(cmd *domain.ParsedCommand, cfg domain.RepoReviewConfig) {
if cmd.Name == "review" && !cmd.ModeExplicit {
if cfg.DefaultMode == "full" || cfg.DefaultMode == "summary" || cfg.DefaultMode == "security" || cfg.DefaultMode == "performance" || cfg.DefaultMode == "tests" {
cmd.Mode = cfg.DefaultMode
} else {
cmd.Mode = "summary"
}
}
}
func BuildPrompt(cmd domain.ParsedCommand, cfg domain.RepoReviewConfig, pr domain.PullRequestContext) string {
intent := commandIntent(cmd.Raw)
if intent == "" {
intent = "review this pull request and report introduced issues."
}
focus := strings.Join(cfg.Focus, ", ")
if focus == "" {
focus = "correctness, security, maintainability"
}
ignore := strings.Join(cfg.Ignore, ", ")
if ignore == "" {
ignore = "(none)"
}
tests := "Do not run tests, benchmarks, or other executables. Review changes statically unless explicitly asked."
if cmd.Mode == "tests" || cfg.IncludeTests {
tests = "Tests may be executed for this run because tests mode/include_tests is explicitly enabled."
}
return fmt.Sprintf("review: %s\nReview only issues introduced by this PR.\nCompare exactly these commits: base `%s` ... head `%s`.\nUse local git data from this checkout; do not review unrelated history.\nRequested mode: %s.\nFocus areas: %s.\nIgnore patterns: %s.\nMaximum diff bytes: %d.\nInclude tests setting: %t.\n%s\nFull review requested: %t.\nReturn strict JSON matching the provided output schema.", intent, pr.BaseSHA, pr.HeadSHA, cmd.Mode, focus, ignore, cfg.MaxDiffBytes, cfg.IncludeTests, tests, cmd.Full)
}
func commandIntent(raw string) string {
raw = strings.TrimSpace(raw)
if raw == "" {
return ""
}
aliasEnd := strings.IndexAny(raw, " \t\r\n")
if aliasEnd < 0 {
return ""
}
remainder := strings.TrimSpace(raw[aliasEnd:])
commandEnd := strings.IndexAny(remainder, " \t\r\n")
if commandEnd < 0 {
return ""
}
return strings.TrimSpace(remainder[commandEnd:])
}
func ValidateResult(result domain.ReviewResult) error { return result.Validate() }
func DecodeResult(data []byte) (domain.ReviewResult, error) {
var result domain.ReviewResult
dec := json.NewDecoder(strings.NewReader(string(data)))
dec.DisallowUnknownFields()
if err := dec.Decode(&result); err != nil {
return domain.ReviewResult{}, err
}
if err := result.Validate(); err != nil {
return domain.ReviewResult{}, err
}
return result, nil
}
func FormatQueueAck(sha string) string {
return fmt.Sprintf("👀 Codex review queued for commit `%s`.", first(sha, 7))
}
func FormatCooldownAck(seconds int) string {
return fmt.Sprintf("⏳ Cooldown active. Please wait %ds before requesting another review on this PR.", seconds)
}
func FormatDisabledAck() string {
return "🚫 Review is disabled by `.codex-review.yml` for this repository."
}
func FormatUnsupportedAck(name string) string {
return fmt.Sprintf("⚠️ Command `@codex %s` is not enabled on this repository.", name)
}
func FormatResultComment(sha string, result domain.ReviewResult, configured bool) string {
marker := fmt.Sprintf("<!-- codex-review:head_sha=%s -->", sha)
body := strings.TrimSpace(result.MarkdownComment)
if body == "" {
body = fallbackDetails(result)
} else if len(result.Findings) > 0 {
body += "\n\n---\n\n" + structuredDetails(result)
}
if result.Meta != nil && result.Meta.Model != "" {
body += fmt.Sprintf("\n_Note: model `%s`, input `%d`, output `%d`, total `%d` tokens used._", result.Meta.Model, result.Meta.Usage.InputTokens, result.Meta.Usage.OutputTokens, result.Meta.Usage.TotalTokens)
}
if !configured {
body += "\n\n> ️.codex-review.yml is not configured"
}
if strings.HasPrefix(body, "<!-- codex-review:head_sha=") {
lines := strings.SplitN(body, "\n", 2)
if len(lines) == 2 {
body = marker + "\n" + lines[1]
}
} else {
body = marker + "\n" + body
}
return body
}
func fallbackDetails(result domain.ReviewResult) string {
body := fmt.Sprintf("## Codex Review\n\nVerdict: `%s`\nConfidence: `%.2f`\n\n%s", result.Verdict, result.Confidence, result.Summary)
if len(result.Findings) == 0 {
return body + "\n\nNo blocking issues found."
}
return body + "\n\n" + structuredFindings(result.Findings)
}
func structuredDetails(result domain.ReviewResult) string {
return fmt.Sprintf("### Structured Findings\n\nVerdict: `%s`\nConfidence: `%.2f`\n\n%s\n\n%s", result.Verdict, result.Confidence, result.Summary, structuredFindings(result.Findings))
}
func structuredFindings(findings []domain.Finding) string {
var body strings.Builder
body.WriteString("Findings:")
for i, f := range findings {
suggestion := "n/a"
if f.Suggestion != nil && *f.Suggestion != "" {
suggestion = *f.Suggestion
}
fmt.Fprintf(&body, "\n\n%d. `%s:%d-%d` (%s)\n %s\n %s\n Suggestion: %s", i+1, f.File, f.LineStart, f.LineEnd, f.Severity, f.Title, f.Body, suggestion)
}
return body.String()
}
func FailureComment(sha, errText string) string {
return fmt.Sprintf("⚠️ Codex review run failed after queueing.\n\n- Commit: `%s`\n- Error: `%s`\n\nPlease rerun `@codex rerun` after checking worker logs.", first(sha, 7), first(strings.Join(strings.Fields(errText), " "), 500))
}
func first(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}