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 f19b271642
ci / test (pull_request) Successful in 1m54s
ci / publish (pull_request) Has been skipped
feat. rebuild service in Go
Rebuild the Gitea Codex review bot from the product contract with a Go HTTP service, durable SQL queue, typed Gitea client, isolated runner, and deployment updates.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-12 21:51:01 +02:00

180 lines
6.2 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 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 {
raw := strings.TrimSpace(cmd.Raw)
intent := raw
if at := strings.IndexAny(raw, " \t\r\n"); at >= 0 {
intent = strings.TrimSpace(raw[at:])
}
if at := strings.IndexAny(intent, " \t\r\n"); at >= 0 {
intent = strings.TrimSpace(intent[at:])
}
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.\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.IncludeTests, tests, cmd.Full)
}
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 = fmt.Sprintf("## Codex Review\n\nVerdict: `%s`\nConfidence: `%.2f`\n\n%s", result.Verdict, result.Confidence, result.Summary)
if len(result.Findings) == 0 {
body += "\n\nNo blocking issues found."
} else {
body += "\n\nFindings:"
for i, f := range result.Findings {
suggestion := "n/a"
if f.Suggestion != nil && *f.Suggestion != "" {
suggestion = *f.Suggestion
}
body += fmt.Sprintf("\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)
}
}
} else if len(result.Findings) > 0 {
body += "\n\n---\n\n### Structured Findings\n\n"
for i, f := range result.Findings {
body += fmt.Sprintf("%d. `%s:%d-%d` (%s)\n %s\n %s\n\n", i+1, f.File, f.LineStart, f.LineEnd, f.Severity, f.Title, f.Body)
}
}
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 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
}