f19b271642
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>
181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
package domain
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type JobStatus string
|
|
|
|
const (
|
|
JobQueued JobStatus = "queued"
|
|
JobRunning JobStatus = "running"
|
|
JobSucceeded JobStatus = "succeeded"
|
|
JobFailed JobStatus = "failed"
|
|
JobSkipped JobStatus = "skipped"
|
|
)
|
|
|
|
type RunStatus string
|
|
|
|
const (
|
|
RunRunning RunStatus = "running"
|
|
RunSucceeded RunStatus = "succeeded"
|
|
RunFailed RunStatus = "failed"
|
|
RunSkipped RunStatus = "skipped"
|
|
)
|
|
|
|
type ParsedCommand struct {
|
|
Name string
|
|
Alias string
|
|
Raw string
|
|
Mode string
|
|
ModeExplicit bool
|
|
Full bool
|
|
Arguments []string
|
|
}
|
|
|
|
func (c ParsedCommand) IsReview() bool { return c.Name == "review" || c.Name == "rerun" }
|
|
|
|
type RepoReviewConfig struct {
|
|
Configured bool
|
|
Enabled bool
|
|
DefaultMode string
|
|
MaxDiffBytes int
|
|
IncludeTests bool
|
|
Focus []string
|
|
Ignore []string
|
|
}
|
|
|
|
func DefaultRepoReviewConfig() RepoReviewConfig {
|
|
return RepoReviewConfig{Configured: true, Enabled: true, DefaultMode: "full", MaxDiffBytes: 200000, Focus: []string{"correctness", "security", "maintainability"}}
|
|
}
|
|
|
|
type PullRequestContext struct {
|
|
Repo string
|
|
PRNumber int
|
|
BaseRef string
|
|
BaseSHA string
|
|
HeadRef string
|
|
HeadSHA string
|
|
CloneURL string
|
|
BaseCloneURL string
|
|
HeadCloneURL string
|
|
HTMLURL string
|
|
IsFork bool
|
|
}
|
|
|
|
type Finding struct {
|
|
Severity string `json:"severity"`
|
|
File string `json:"file"`
|
|
LineStart int `json:"line_start"`
|
|
LineEnd int `json:"line_end"`
|
|
Title string `json:"title"`
|
|
Body string `json:"body"`
|
|
Suggestion *string `json:"suggestion"`
|
|
}
|
|
|
|
type Usage struct {
|
|
InputTokens int `json:"input_tokens,omitempty"`
|
|
OutputTokens int `json:"output_tokens,omitempty"`
|
|
TotalTokens int `json:"total_tokens,omitempty"`
|
|
}
|
|
|
|
type ReviewMeta struct {
|
|
Source string `json:"source,omitempty"`
|
|
Model string `json:"model,omitempty"`
|
|
Usage Usage `json:"usage,omitempty"`
|
|
}
|
|
|
|
type ReviewResult struct {
|
|
Verdict string `json:"verdict"`
|
|
Confidence float64 `json:"confidence"`
|
|
Summary string `json:"summary"`
|
|
MarkdownComment string `json:"markdown_comment"`
|
|
Findings []Finding `json:"findings"`
|
|
Meta *ReviewMeta `json:"_meta,omitempty"`
|
|
}
|
|
|
|
func (r ReviewResult) Validate() error {
|
|
if r.Verdict != "correct" && r.Verdict != "has_issues" {
|
|
return errors.New("invalid verdict")
|
|
}
|
|
if r.Confidence < 0 || r.Confidence > 1 {
|
|
return errors.New("confidence must be between 0 and 1")
|
|
}
|
|
if len(r.Summary) > 20000 || len(r.MarkdownComment) > 50000 {
|
|
return errors.New("review text is too long")
|
|
}
|
|
if len(r.Findings) > 200 {
|
|
return errors.New("too many findings")
|
|
}
|
|
for i, f := range r.Findings {
|
|
if f.Severity != "low" && f.Severity != "medium" && f.Severity != "high" && f.Severity != "critical" {
|
|
return errors.New("invalid finding severity")
|
|
}
|
|
if f.File == "" || len(f.File) > 1000 || f.LineStart < 1 || f.LineEnd < f.LineStart {
|
|
return errors.New("invalid finding location")
|
|
}
|
|
if len(f.Title) > 2000 || len(f.Body) > 20000 {
|
|
return errors.New("finding text is too long")
|
|
}
|
|
if f.Suggestion != nil && len(*f.Suggestion) > 20000 {
|
|
return errors.New("suggestion is too long")
|
|
}
|
|
_ = i
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type WebhookEvent struct {
|
|
EventName string
|
|
DeliveryID string
|
|
Repo string
|
|
PRNumber int
|
|
HeadSHA string
|
|
CommentID int64
|
|
CommentBody string
|
|
Sender string
|
|
PayloadSHA256 string
|
|
}
|
|
|
|
type Job struct {
|
|
ID int64
|
|
Repo string
|
|
PRNumber int
|
|
HeadSHA string
|
|
TriggerCommentID int64
|
|
TriggerCommentBody string
|
|
Command string
|
|
CommandArgs string
|
|
RequestedBy string
|
|
Status JobStatus
|
|
LastError string
|
|
ResultJSON []byte
|
|
CreatedAt time.Time
|
|
UpdatedAt time.Time
|
|
StartedAt *time.Time
|
|
FinishedAt *time.Time
|
|
}
|
|
|
|
type ReviewRun struct {
|
|
ID int64
|
|
JobID int64
|
|
Status RunStatus
|
|
ContainerID string
|
|
ResultJSON []byte
|
|
Error string
|
|
StartedAt time.Time
|
|
FinishedAt *time.Time
|
|
}
|
|
|
|
func (j Job) AttemptNumber() int { return 1 }
|
|
|
|
// ReviewRunner isolates all process/container execution from orchestration.
|
|
type ReviewRunner interface {
|
|
Run(context.Context, PullRequestContext, ParsedCommand, RepoReviewConfig) (ReviewResult, error)
|
|
}
|
|
|
|
func NormalizeRepo(repo string) string { return strings.TrimSpace(repo) }
|