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 }