package quality import ( "fmt" "sort" ) type NarrativeGenerator struct { targetScore int } func NewNarrativeGenerator(targetScore int) *NarrativeGenerator { if targetScore <= 0 { targetScore = 95 } return &NarrativeGenerator{targetScore: targetScore} } func (g *NarrativeGenerator) Generate(findings []Finding, scorecard *Scorecard, history []StateSnapshot) *Narrative { phase := g.determinePhase(findings, scorecard) headline := g.generateHeadline(phase, scorecard) dimensions := g.analyzeDimensions(findings) actions := g.generateActions(findings, phase) strategy := g.generateStrategy(findings, dimensions) tools := g.generateTools(findings) debt := g.analyzeDebt(findings, scorecard) strictTarget := g.calculateStrictTarget(scorecard) reminders := g.generateReminders(findings, history) riskFlags := g.identifyRisks(findings, history) return &Narrative{ Phase: phase, Headline: headline, Dimensions: dimensions, Actions: actions, Strategy: strategy, Tools: tools, Debt: debt, Milestone: g.generateMilestone(phase, scorecard), WhyNow: g.explainWhyNow(phase, findings), RiskFlags: riskFlags, StrictTarget: strictTarget, Reminders: reminders, } } func (g *NarrativeGenerator) determinePhase(findings []Finding, scorecard *Scorecard) string { openCount := 0 t4Count := 0 t3Count := 0 for _, f := range findings { if f.Status == StatusOpen { openCount++ if f.Severity == SeverityT4 { t4Count++ } else if f.Severity == SeverityT3 { t3Count++ } } } if openCount == 0 { return "maintenance" } if t4Count > 0 { return "critical" } if t3Count > 5 || openCount > 20 { return "debt_reduction" } if openCount > 5 { return "cleanup" } return "polish" } func (g *NarrativeGenerator) generateHeadline(phase string, scorecard *Scorecard) string { switch phase { case "maintenance": return "Codebase is healthy! Focus on preventing new debt." case "critical": return fmt.Sprintf("Critical issues detected (%d strict score). Address T4 findings first.", scorecard.StrictScore) case "debt_reduction": return fmt.Sprintf("Significant technical debt (%d open issues). Systematic cleanup recommended.", scorecard.TotalScore) case "cleanup": return fmt.Sprintf("Minor issues detected (%d open). Quick wins available.", scorecard.TotalScore) default: return fmt.Sprintf("Codebase in good shape (%d open issues).", scorecard.TotalScore) } } func (g *NarrativeGenerator) analyzeDimensions(findings []Finding) *NarrativeDimensions { dimensionScores := make(map[Dimension][]Finding) for _, f := range findings { if f.Status == StatusOpen { dim := g.classifyDimension(f) dimensionScores[dim] = append(dimensionScores[dim], f) } } var lowest []*DimensionInfo var biggestGap []*DimensionInfo var stagnant []*DimensionInfo for dim, dimFindings := range dimensionScores { info := &DimensionInfo{ Name: string(dim), Issues: len(dimFindings), } impact := 0 for _, f := range dimFindings { impact += f.Score * int(f.Severity) } info.Impact = float64(impact) lowest = append(lowest, info) } sort.Slice(lowest, func(i, j int) bool { return lowest[i].Impact > lowest[j].Impact }) if len(lowest) > 5 { lowest = lowest[:5] } return &NarrativeDimensions{ LowestDimensions: lowest, BiggestGapDimensions: biggestGap, StagnantDimensions: stagnant, } } func (g *NarrativeGenerator) classifyDimension(f Finding) Dimension { switch f.Type { case "complexity", "complexity_ast": return DimensionCodeQuality case "duplication", "dupes": return DimensionDuplication case "dead_code", "unused_import", "unused": return DimensionFileHealth case "security": return DimensionSecurity case "naming": return DimensionNamingQuality case "import_cycle", "cycles": return DimensionAbstractionFit default: return DimensionCodeQuality } } func (g *NarrativeGenerator) generateActions(findings []Finding, phase string) []string { var actions []string t1AutoFixable := 0 t2Quick := 0 t3Judgment := 0 t4Major := 0 for _, f := range findings { if f.Status != StatusOpen { continue } switch f.Severity { case SeverityT1: t1AutoFixable++ case SeverityT2: t2Quick++ case SeverityT3: t3Judgment++ case SeverityT4: t4Major++ } } if t4Major > 0 { actions = append(actions, fmt.Sprintf("Address %d T4 (major refactor) issues - these require architectural changes", t4Major)) } if t3Judgment > 0 { actions = append(actions, fmt.Sprintf("Review %d T3 (needs judgment) issues - decide if they need fixing", t3Judgment)) } if t1AutoFixable > 0 { actions = append(actions, fmt.Sprintf("Run auto-fixer for %d T1 (auto-fixable) issues", t1AutoFixable)) } if t2Quick > 0 { actions = append(actions, fmt.Sprintf("Quick manual fixes available for %d T2 issues", t2Quick)) } if len(actions) == 0 { actions = append(actions, "No immediate actions required - maintain code quality") } return actions } func (g *NarrativeGenerator) generateStrategy(findings []Finding, dimensions *NarrativeDimensions) *NarrativeStrategy { autoFixable := 0 total := 0 for _, f := range findings { if f.Status == StatusOpen { total++ if f.Severity == SeverityT1 { autoFixable++ } } } var recommendation string var coverage float64 if total > 0 { coverage = float64(autoFixable) / float64(total) * 100 } if coverage > 50 { recommendation = "Use auto-fixers first, then address remaining issues manually" } else if autoFixable > 0 { recommendation = "Start with auto-fixers for quick wins, then prioritize by impact" } else { recommendation = "Prioritize by severity and impact, starting with T4 issues" } return &NarrativeStrategy{ FixerLeverage: &FixerLeverage{ AutoFixableCount: autoFixable, TotalCount: total, Coverage: coverage, Recommendation: recommendation, }, CanParallelize: len(findings) > 3, Hint: g.generateHint(findings), } } func (g *NarrativeGenerator) generateHint(findings []Finding) string { for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT1 { return "T1 issues can be auto-fixed with 'devour quality fix'" } } for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT4 { return "T4 issues require planning - consider creating a dedicated branch" } } return "Focus on one category at a time for best results" } func (g *NarrativeGenerator) generateTools(findings []Finding) *NarrativeTools { fixers := []interface{}{} for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT1 { fixers = append(fixers, map[string]string{ "name": f.Type, "description": fmt.Sprintf("Fix %s issues", f.Type), }) } } return &NarrativeTools{ Fixers: fixers, Plan: &PlanTool{ Command: "devour quality plan", Description: "Generate prioritized action plan", }, Badge: &BadgeTool{ Generated: true, InReadme: false, Path: "scorecard.png", }, } } func (g *NarrativeGenerator) analyzeDebt(findings []Finding, scorecard *Scorecard) *NarrativeDebt { wontfixCount := 0 for _, f := range findings { if f.Status == StatusWontfix { wontfixCount++ } } var worstDimension string var worstGap float64 dimensionImpact := make(map[string]float64) for _, f := range findings { if f.Status == StatusOpen { dim := string(g.classifyDimension(f)) dimensionImpact[dim] += float64(f.Score * int(f.Severity)) } } for dim, impact := range dimensionImpact { if impact > worstGap { worstGap = impact worstDimension = dim } } return &NarrativeDebt{ OverallGap: float64(scorecard.StrictScore), WontfixCount: wontfixCount, WorstDimension: worstDimension, WorstGap: worstGap, Trend: "stable", } } func (g *NarrativeGenerator) calculateStrictTarget(scorecard *Scorecard) *StrictTarget { gap := float64(scorecard.StrictScore) / float64(g.targetScore) * 100 var state string var warning *string switch { case gap >= 100: state = "at_target" case gap >= 80: state = "near_target" case gap >= 50: state = "in_progress" w := "Significant gap to target - consider focused effort" warning = &w default: state = "needs_work" w := "Large gap to target - prioritize high-impact fixes" warning = &w } return &StrictTarget{ Target: float64(g.targetScore), Current: float64(scorecard.StrictScore), Gap: gap, State: state, Warning: warning, } } func (g *NarrativeGenerator) generateReminders(findings []Finding, history []StateSnapshot) []string { var reminders []string autoFixable := 0 for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT1 { autoFixable++ } } if autoFixable > 0 { reminders = append(reminders, fmt.Sprintf("%d auto-fixable issues available - use 'devour quality fix'", autoFixable)) } if len(history) > 0 { latest := history[len(history)-1] if latest.Findings == len(findings) { reminders = append(reminders, "No progress since last scan - consider tackling a specific category") } } return reminders } func (g *NarrativeGenerator) identifyRisks(findings []Finding, history []StateSnapshot) []string { var risks []string t4Count := 0 for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT4 { t4Count++ } } if t4Count > 3 { risks = append(risks, fmt.Sprintf("High number of T4 issues (%d) indicates architectural debt", t4Count)) } if len(history) >= 3 { trend := 0 for i := len(history) - 3; i < len(history); i++ { trend += history[i].Findings } avg := trend / 3 if len(findings) > int(float64(avg)*1.2) { risks = append(risks, "Finding count is trending upward - debt is accumulating") } } return risks } func (g *NarrativeGenerator) generateMilestone(phase string, scorecard *Scorecard) string { switch phase { case "maintenance": return "Maintain current quality level" case "critical": return "Reduce T4 issues to zero" case "debt_reduction": return fmt.Sprintf("Reduce strict score below %d", g.targetScore) case "cleanup": return "Clear all T1 and T2 issues" default: return "Continue quality improvement" } } func (g *NarrativeGenerator) explainWhyNow(phase string, findings []Finding) string { for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT4 { return "T4 issues compound over time - addressing them early prevents architectural decay" } } t1Count := 0 for _, f := range findings { if f.Status == StatusOpen && f.Severity == SeverityT1 { t1Count++ } } if t1Count > 5 { return "Quick wins available - auto-fixers can clear low-hanging fruit in minutes" } return "Consistent small improvements compound into significant quality gains" }