7d0d3ea885
CI / test (pull_request) Successful in 20s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 39s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m43s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 2m19s
- Add --provider flag (gitea|github) for VCS backend selection - Add --base-url flag for GitHub API endpoint configuration - Rename --gitea-url to --vcs-url with backward-compatible alias - Replace direct gitea.Client usage with vcs.Client interface - Create vcs.Client via factory switch based on --provider value - Implement Reviewer + Identity interfaces on github.Client - Add verdictToEvent() using canonical vcs.ReviewEvent types - Remove review.GiteaEvent() (replaced by verdictToEvent) - GitHub supersede uses DismissReview; Gitea keeps EditComment flow - Add VCS_PROVIDER, VCS_BASE_URL, VCS_URL env var support Closes #82
60 lines
1.9 KiB
Go
60 lines
1.9 KiB
Go
package review
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
// FormatMarkdown formats a ReviewResult into the markdown body for a Gitea review.
|
|
func FormatMarkdown(result *ReviewResult, reviewerName string) string {
|
|
return FormatMarkdownWithDisplay(result, reviewerName, reviewerName)
|
|
}
|
|
|
|
|
|
// FormatMarkdownWithDisplay formats a ReviewResult with separate display name and sentinel name.
|
|
// Note: displayName is not HTML-escaped as Gitea sanitizes rendered Markdown.
|
|
// Persona display names are controlled by repo owners (trusted input).
|
|
// displayName is used for the header title, sentinelName is used for the cleanup sentinel.
|
|
// If displayName is empty, sentinelName is used for both.
|
|
func FormatMarkdownWithDisplay(result *ReviewResult, displayName, sentinelName string) string {
|
|
var sb strings.Builder
|
|
|
|
// Use display name for header, or fall back to sentinel name
|
|
headerName := displayName
|
|
if headerName == "" {
|
|
headerName = sentinelName
|
|
}
|
|
|
|
if headerName != "" {
|
|
title := CapitalizeFirst(headerName)
|
|
sb.WriteString(fmt.Sprintf("# %s Review\n\n", title))
|
|
}
|
|
|
|
sb.WriteString("## Summary\n\n")
|
|
sb.WriteString(result.Summary)
|
|
sb.WriteString("\n\n")
|
|
|
|
if len(result.Findings) > 0 {
|
|
sb.WriteString("## Findings\n\n")
|
|
sb.WriteString("| # | Severity | File | Line | Finding |\n")
|
|
sb.WriteString("|---|----------|------|------|--------|\n")
|
|
|
|
for i, f := range result.Findings {
|
|
sb.WriteString(fmt.Sprintf("| %d | [%s] | `%s` | %d | %s |\n",
|
|
i+1, f.Severity, f.File, f.Line, f.Finding))
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
|
|
sb.WriteString("## Recommendation\n\n")
|
|
sb.WriteString(fmt.Sprintf("**%s** — %s\n", result.Verdict, result.Recommendation))
|
|
|
|
if sentinelName != "" {
|
|
sb.WriteString(fmt.Sprintf("\n---\n*Review by %s*\n", headerName))
|
|
// Hidden sentinel for identifying this bot's reviews during cleanup
|
|
sb.WriteString(fmt.Sprintf("\n<!-- review-bot:%s -->\n", sentinelName))
|
|
}
|
|
|
|
return sb.String()
|
|
}
|