mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-04 04:23:02 +00:00
316 lines
8.2 KiB
Go
316 lines
8.2 KiB
Go
package review
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"github.com/yourorg/devour/internal/quality"
|
|
)
|
|
|
|
type ReviewPacket struct {
|
|
Generated time.Time `json:"generated"`
|
|
ProjectPath string `json:"project_path"`
|
|
Language string `json:"language"`
|
|
Scorecard *quality.Scorecard `json:"scorecard"`
|
|
Findings []FindingReview `json:"findings"`
|
|
Context ReviewContext `json:"context"`
|
|
Questions []ReviewQuestion `json:"questions"`
|
|
}
|
|
|
|
type FindingReview struct {
|
|
ID string `json:"id"`
|
|
Type string `json:"type"`
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
File string `json:"file"`
|
|
Line int `json:"line"`
|
|
Severity quality.Severity `json:"severity"`
|
|
Score int `json:"score"`
|
|
Status quality.Status `json:"status"`
|
|
NeedsReview bool `json:"needs_review"`
|
|
Context string `json:"context"`
|
|
Metadata map[string]string `json:"metadata"`
|
|
}
|
|
|
|
type ReviewContext struct {
|
|
TotalFiles int `json:"total_files"`
|
|
TotalLOC int `json:"total_loc"`
|
|
FindingsByDim map[string]int `json:"findings_by_dimension"`
|
|
TopIssues []string `json:"top_issues"`
|
|
Trends map[string]string `json:"trends"`
|
|
}
|
|
|
|
type ReviewQuestion struct {
|
|
ID string `json:"id"`
|
|
Category string `json:"category"`
|
|
Question string `json:"question"`
|
|
Options []string `json:"options,omitempty"`
|
|
}
|
|
|
|
type PacketGenerator struct {
|
|
dataDir string
|
|
}
|
|
|
|
func NewPacketGenerator(dataDir string) *PacketGenerator {
|
|
return &PacketGenerator{dataDir: dataDir}
|
|
}
|
|
|
|
func (g *PacketGenerator) Generate(findings []quality.Finding, scorecard *quality.Scorecard, lang string) (*ReviewPacket, error) {
|
|
packet := &ReviewPacket{
|
|
Generated: time.Now(),
|
|
ProjectPath: g.dataDir,
|
|
Language: lang,
|
|
Scorecard: scorecard,
|
|
Findings: g.convertFindings(findings),
|
|
Context: g.buildContext(findings),
|
|
Questions: g.generateQuestions(findings),
|
|
}
|
|
|
|
return packet, nil
|
|
}
|
|
|
|
func (g *PacketGenerator) convertFindings(findings []quality.Finding) []FindingReview {
|
|
var reviews []FindingReview
|
|
|
|
for _, f := range findings {
|
|
if f.Status != quality.StatusOpen {
|
|
continue
|
|
}
|
|
|
|
review := FindingReview{
|
|
ID: f.ID,
|
|
Type: f.Type,
|
|
Title: f.Title,
|
|
Description: f.Description,
|
|
File: f.File,
|
|
Line: f.Line,
|
|
Severity: f.Severity,
|
|
Score: f.Score,
|
|
Status: f.Status,
|
|
NeedsReview: f.Severity >= quality.SeverityT3,
|
|
Metadata: f.Metadata,
|
|
}
|
|
|
|
review.Context = g.generateContext(f)
|
|
reviews = append(reviews, review)
|
|
}
|
|
|
|
return reviews
|
|
}
|
|
|
|
func (g *PacketGenerator) generateContext(f quality.Finding) string {
|
|
switch f.Type {
|
|
case "complexity", "complexity_ast":
|
|
return "This function may be difficult to maintain. Consider if it can be simplified or broken down."
|
|
case "duplication":
|
|
return "Similar code exists elsewhere. Consider extracting common functionality."
|
|
case "dead_code":
|
|
return "This code appears unused. Verify before removing - it may be called via reflection or external tools."
|
|
case "security":
|
|
return "Potential security concern. Review carefully and consider security implications."
|
|
case "import_cycle":
|
|
return "Circular dependency detected. This can cause initialization issues and makes code harder to understand."
|
|
default:
|
|
return "Review this finding and decide if it needs addressing."
|
|
}
|
|
}
|
|
|
|
func (g *PacketGenerator) buildContext(findings []quality.Finding) ReviewContext {
|
|
byDim := make(map[string]int)
|
|
var topIssues []string
|
|
|
|
for _, f := range findings {
|
|
if f.Status == quality.StatusOpen {
|
|
dim := g.classifyDimension(f)
|
|
byDim[dim]++
|
|
}
|
|
}
|
|
|
|
topCount := 0
|
|
for _, f := range findings {
|
|
if f.Status == quality.StatusOpen && topCount < 5 {
|
|
topIssues = append(topIssues, fmt.Sprintf("%s: %s", f.Type, f.Title))
|
|
topCount++
|
|
}
|
|
}
|
|
|
|
return ReviewContext{
|
|
FindingsByDim: byDim,
|
|
TopIssues: topIssues,
|
|
Trends: make(map[string]string),
|
|
}
|
|
}
|
|
|
|
func (g *PacketGenerator) classifyDimension(f quality.Finding) string {
|
|
switch f.Type {
|
|
case "complexity", "complexity_ast":
|
|
return "Code Quality"
|
|
case "duplication":
|
|
return "Duplication"
|
|
case "dead_code", "unused_import", "unused":
|
|
return "File Health"
|
|
case "security":
|
|
return "Security"
|
|
case "naming":
|
|
return "Naming Quality"
|
|
case "import_cycle":
|
|
return "Architecture"
|
|
default:
|
|
return "Other"
|
|
}
|
|
}
|
|
|
|
func (g *PacketGenerator) generateQuestions(findings []quality.Finding) []ReviewQuestion {
|
|
var questions []ReviewQuestion
|
|
|
|
hasDupes := false
|
|
hasComplex := false
|
|
hasDead := false
|
|
|
|
for _, f := range findings {
|
|
if f.Status != quality.StatusOpen {
|
|
continue
|
|
}
|
|
switch f.Type {
|
|
case "duplication":
|
|
hasDupes = true
|
|
case "complexity", "complexity_ast":
|
|
hasComplex = true
|
|
case "dead_code":
|
|
hasDead = true
|
|
}
|
|
}
|
|
|
|
if hasDupes {
|
|
questions = append(questions, ReviewQuestion{
|
|
ID: "dupe_strategy",
|
|
Category: "duplication",
|
|
Question: "How should duplicated code be consolidated?",
|
|
Options: []string{
|
|
"Extract to shared utility",
|
|
"Keep separate (different use cases)",
|
|
"Refactor to common interface",
|
|
},
|
|
})
|
|
}
|
|
|
|
if hasComplex {
|
|
questions = append(questions, ReviewQuestion{
|
|
ID: "complexity_strategy",
|
|
Category: "complexity",
|
|
Question: "What's the best approach for complex functions?",
|
|
Options: []string{
|
|
"Break into smaller functions",
|
|
"Introduce helper types",
|
|
"Accept current complexity",
|
|
},
|
|
})
|
|
}
|
|
|
|
if hasDead {
|
|
questions = append(questions, ReviewQuestion{
|
|
ID: "dead_code_strategy",
|
|
Category: "maintenance",
|
|
Question: "Should unused code be removed?",
|
|
Options: []string{
|
|
"Remove if truly unused",
|
|
"Keep for future use",
|
|
"Mark as deprecated",
|
|
},
|
|
})
|
|
}
|
|
|
|
questions = append(questions, ReviewQuestion{
|
|
ID: "priority",
|
|
Category: "planning",
|
|
Question: "Which area should be prioritized for improvement?",
|
|
Options: []string{
|
|
"Security issues first",
|
|
"Complexity reduction",
|
|
"Dead code cleanup",
|
|
"Architecture improvements",
|
|
},
|
|
})
|
|
|
|
return questions
|
|
}
|
|
|
|
func (g *PacketGenerator) Save(packet *ReviewPacket, filename string) error {
|
|
reviewDir := filepath.Join(g.dataDir, "review")
|
|
if err := os.MkdirAll(reviewDir, 0755); err != nil {
|
|
return fmt.Errorf("failed to create review directory: %w", err)
|
|
}
|
|
|
|
path := filepath.Join(reviewDir, filename)
|
|
data, err := json.MarshalIndent(packet, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal packet: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(path, data, 0644); err != nil {
|
|
return fmt.Errorf("failed to write packet: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *PacketGenerator) Load(filename string) (*ReviewPacket, error) {
|
|
path := filepath.Join(g.dataDir, "review", filename)
|
|
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read packet: %w", err)
|
|
}
|
|
|
|
var packet ReviewPacket
|
|
if err := json.Unmarshal(data, &packet); err != nil {
|
|
return nil, fmt.Errorf("failed to parse packet: %w", err)
|
|
}
|
|
|
|
return &packet, nil
|
|
}
|
|
|
|
func (g *PacketGenerator) ImportReview(filename string, responses map[string]string) error {
|
|
_, err := g.Load(filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
findingsPath := filepath.Join(g.dataDir, "quality", "status.json")
|
|
data, err := os.ReadFile(findingsPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read findings: %w", err)
|
|
}
|
|
|
|
var state struct {
|
|
Findings []quality.Finding `json:"findings"`
|
|
}
|
|
if err := json.Unmarshal(data, &state); err != nil {
|
|
return fmt.Errorf("failed to parse findings: %w", err)
|
|
}
|
|
|
|
for _, f := range state.Findings {
|
|
if response, ok := responses[f.ID]; ok {
|
|
if f.Metadata == nil {
|
|
f.Metadata = make(map[string]string)
|
|
}
|
|
f.Metadata["review_response"] = response
|
|
f.Metadata["reviewed_at"] = time.Now().Format(time.RFC3339)
|
|
}
|
|
}
|
|
|
|
updatedData, err := json.MarshalIndent(state, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal updated findings: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(findingsPath, updatedData, 0644); err != nil {
|
|
return fmt.Errorf("failed to write updated findings: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|