mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-04-15 07:16:34 +00:00
* created roadmap and yaml claude agent
* Update roadmap.md
* feat: Clean advanced analysis implementation - core agents, engine, artifacts
* Remove unrelated files - keep only advanced analysis implementation
* fix: Fix goroutine leak in hosted agent rate limiter
- Added stop channel and stopped flag to RateLimiter struct
- Modified replenishTokens to listen for stop signal and exit cleanly
- Added Stop() method to gracefully shutdown rate limiter
- Added Stop() method to HostedAgent to cleanup rate limiter on shutdown
Fixes cursor bot issue: Rate Limiter Goroutine Leak
* fix: Fix analyzer config and model validation bugs
Bug 1: Analyzer Config Missing File Path
- Added filePath to DeploymentStatus analyzer config in convertAnalyzerToSpec
- Sets namespace-specific path (cluster-resources/deployments/{namespace}.json)
- Falls back to generic path (cluster-resources/deployments.json) if no namespace
- Fixes LocalAgent.analyzeDeploymentStatus backward compatibility
Bug 2: HealthCheck Fails Model Validation
- Changed Ollama model validation from prefix match to exact match
- Prevents false positives where llama2:13b would match request for llama2:7b
- Ensures agent only reports healthy when exact model is available
Both fixes address cursor bot reported issues and maintain backward compatibility.
* fixing lint errors
* fixing lint errors
* adding CLI flags
* fix: resolve linting errors for CI
- Remove unnecessary nil check in host_kernel_configs.go (len() for nil slices is zero)
- Remove unnecessary fmt.Sprintf() calls in ceph.go for static strings
- Apply go fmt formatting fixes
Fixes failing lint CI check
* fix: resolve CI failures in build-test workflow and Ollama tests
1. Fix GitHub Actions workflow logic error:
- Replace problematic contains() expression with explicit job result checks
- Properly handle failure and cancelled states for each job
- Prevents false positive failures in success summary job
2. Fix Ollama agent parseLLMResponse panics:
- Add proper error handling for malformed JSON in LLM responses
- Return error when JSON is found but invalid (instead of silent fallback)
- Add error when no meaningful content can be parsed from response
- Prevents nil pointer dereference in test assertions
Fixes failing build-test/success and build-test/test CI checks
* fix: resolve all CI failures and cursor bot issues
1. Fix disable-ollama flag logic bug:
- Remove disable-ollama from advanced analysis trigger condition
- Prevents unintended advanced analysis mode when no agents registered
- Allows proper fallback to legacy analysis
2. Fix diff test consistency:
- Update test expectations to match function behavior (lines with newlines)
- Ensures consistency between streaming and non-streaming diff paths
3. Fix Ollama agent error handling:
- Add proper error return for malformed JSON in LLM responses
- Add meaningful content validation for markdown parsing
- Prevents nil pointer panics in test assertions
4. Fix analysis engine mock agent:
- Mock agent now processes and returns results for all provided analyzers
- Fixes test expectation mismatch (expected 8 results, got 1)
Resolves all failing CI checks: lint, test, and success workflow logic
---------
Co-authored-by: Noah Campbell <noah.edward.campbell@gmail.com>
680 lines
18 KiB
Go
680 lines
18 KiB
Go
package artifacts
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
|
)
|
|
|
|
// SummaryGenerator generates summary artifacts
|
|
type SummaryGenerator struct{}
|
|
|
|
func (g *SummaryGenerator) Generate(ctx context.Context, result *analyzer.AnalysisResult) (*Artifact, error) {
|
|
summary := struct {
|
|
Overview analyzer.AnalysisSummary `json:"overview"`
|
|
TopIssues []*analyzer.AnalyzerResult `json:"topIssues"`
|
|
Categories map[string]int `json:"categories"`
|
|
Agents []analyzer.AgentMetadata `json:"agents"`
|
|
Recommendations []string `json:"recommendations"`
|
|
GeneratedAt time.Time `json:"generatedAt"`
|
|
}{
|
|
Overview: result.Summary,
|
|
Categories: g.categorizeResults(result.Results),
|
|
Agents: result.Metadata.Agents,
|
|
TopIssues: g.getTopIssues(result.Results, 10),
|
|
Recommendations: g.generateRecommendations(result),
|
|
GeneratedAt: time.Now(),
|
|
}
|
|
|
|
data, err := json.MarshalIndent(summary, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artifact := &Artifact{
|
|
Name: "summary.json",
|
|
Type: "summary",
|
|
Format: "json",
|
|
ContentType: "application/json",
|
|
Size: int64(len(data)),
|
|
Content: data,
|
|
Metadata: ArtifactMetadata{
|
|
CreatedAt: time.Now(),
|
|
Generator: "SummaryGenerator",
|
|
Version: "1.0.0",
|
|
Tags: []string{"summary", "overview"},
|
|
},
|
|
}
|
|
|
|
return artifact, nil
|
|
}
|
|
|
|
func (g *SummaryGenerator) Name() string {
|
|
return "Summary Generator"
|
|
}
|
|
|
|
func (g *SummaryGenerator) Description() string {
|
|
return "Generates high-level summary artifacts from analysis results"
|
|
}
|
|
|
|
func (g *SummaryGenerator) categorizeResults(results []*analyzer.AnalyzerResult) map[string]int {
|
|
categories := make(map[string]int)
|
|
for _, result := range results {
|
|
if result.Category != "" {
|
|
categories[result.Category]++
|
|
}
|
|
}
|
|
return categories
|
|
}
|
|
|
|
func (g *SummaryGenerator) getTopIssues(results []*analyzer.AnalyzerResult, limit int) []*analyzer.AnalyzerResult {
|
|
var failedResults []*analyzer.AnalyzerResult
|
|
for _, result := range results {
|
|
if result.IsFail {
|
|
failedResults = append(failedResults, result)
|
|
}
|
|
}
|
|
|
|
sort.Slice(failedResults, func(i, j int) bool {
|
|
return failedResults[i].Confidence > failedResults[j].Confidence
|
|
})
|
|
|
|
if len(failedResults) > limit {
|
|
return failedResults[:limit]
|
|
}
|
|
return failedResults
|
|
}
|
|
|
|
func (g *SummaryGenerator) generateRecommendations(result *analyzer.AnalysisResult) []string {
|
|
var recommendations []string
|
|
|
|
if result.Summary.FailCount > 0 {
|
|
recommendations = append(recommendations,
|
|
fmt.Sprintf("Address %d failed checks to improve system health", result.Summary.FailCount))
|
|
}
|
|
|
|
if result.Summary.WarnCount > result.Summary.PassCount {
|
|
recommendations = append(recommendations,
|
|
"Review warning conditions to prevent potential issues")
|
|
}
|
|
|
|
categories := g.categorizeResults(result.Results)
|
|
for category, count := range categories {
|
|
if count >= 5 {
|
|
recommendations = append(recommendations,
|
|
fmt.Sprintf("Focus attention on %s category (%d issues)", category, count))
|
|
}
|
|
}
|
|
|
|
return recommendations
|
|
}
|
|
|
|
// InsightsGenerator generates insights and correlation artifacts
|
|
type InsightsGenerator struct{}
|
|
|
|
func (g *InsightsGenerator) Generate(ctx context.Context, result *analyzer.AnalysisResult) (*Artifact, error) {
|
|
insights := struct {
|
|
KeyFindings []string `json:"keyFindings"`
|
|
Patterns []Pattern `json:"patterns"`
|
|
Correlations []analyzer.Correlation `json:"correlations"`
|
|
Trends []Trend `json:"trends"`
|
|
Recommendations []RemediationInsight `json:"recommendations"`
|
|
GeneratedAt time.Time `json:"generatedAt"`
|
|
}{
|
|
KeyFindings: g.extractKeyFindings(result.Results),
|
|
Patterns: g.identifyPatterns(result.Results),
|
|
Correlations: result.Metadata.Correlations,
|
|
Trends: g.analyzeTrends(result.Results),
|
|
Recommendations: g.generateRemediationInsights(result.Remediation),
|
|
GeneratedAt: time.Now(),
|
|
}
|
|
|
|
data, err := json.MarshalIndent(insights, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artifact := &Artifact{
|
|
Name: "insights.json",
|
|
Type: "insights",
|
|
Format: "json",
|
|
ContentType: "application/json",
|
|
Size: int64(len(data)),
|
|
Content: data,
|
|
Metadata: ArtifactMetadata{
|
|
CreatedAt: time.Now(),
|
|
Generator: "InsightsGenerator",
|
|
Version: "1.0.0",
|
|
Tags: []string{"insights", "patterns", "correlations"},
|
|
},
|
|
}
|
|
|
|
return artifact, nil
|
|
}
|
|
|
|
func (g *InsightsGenerator) Name() string {
|
|
return "Insights Generator"
|
|
}
|
|
|
|
func (g *InsightsGenerator) Description() string {
|
|
return "Generates insights, patterns, and correlation artifacts"
|
|
}
|
|
|
|
func (g *InsightsGenerator) extractKeyFindings(results []*analyzer.AnalyzerResult) []string {
|
|
var findings []string
|
|
|
|
for _, result := range results {
|
|
if result.IsFail && result.Confidence > 0.8 {
|
|
findings = append(findings, result.Message)
|
|
}
|
|
}
|
|
|
|
if len(findings) > 10 {
|
|
findings = findings[:10]
|
|
}
|
|
|
|
return findings
|
|
}
|
|
|
|
func (g *InsightsGenerator) identifyPatterns(results []*analyzer.AnalyzerResult) []Pattern {
|
|
var patterns []Pattern
|
|
|
|
// Pattern: Multiple failures in same category
|
|
categoryFailures := make(map[string]int)
|
|
for _, result := range results {
|
|
if result.IsFail && result.Category != "" {
|
|
categoryFailures[result.Category]++
|
|
}
|
|
}
|
|
|
|
for category, count := range categoryFailures {
|
|
if count >= 3 {
|
|
patterns = append(patterns, Pattern{
|
|
Type: "category-failure-cluster",
|
|
Description: fmt.Sprintf("Multiple failures in %s category", category),
|
|
Count: count,
|
|
Confidence: 0.8,
|
|
})
|
|
}
|
|
}
|
|
|
|
// Pattern: Agent-specific issues
|
|
agentFailures := make(map[string]int)
|
|
for _, result := range results {
|
|
if result.IsFail && result.AgentName != "" {
|
|
agentFailures[result.AgentName]++
|
|
}
|
|
}
|
|
|
|
for agent, count := range agentFailures {
|
|
if count >= 2 {
|
|
patterns = append(patterns, Pattern{
|
|
Type: "agent-failure-pattern",
|
|
Description: fmt.Sprintf("Multiple failures detected by %s agent", agent),
|
|
Count: count,
|
|
Confidence: 0.7,
|
|
})
|
|
}
|
|
}
|
|
|
|
return patterns
|
|
}
|
|
|
|
func (g *InsightsGenerator) analyzeTrends(results []*analyzer.AnalyzerResult) []Trend {
|
|
// Placeholder for trend analysis
|
|
// In a real implementation, this would compare with historical data
|
|
totalResults := len(results)
|
|
failedResults := 0
|
|
for _, result := range results {
|
|
if result.IsFail {
|
|
failedResults++
|
|
}
|
|
}
|
|
|
|
var direction string
|
|
var confidence float64
|
|
|
|
failureRate := float64(failedResults) / float64(totalResults)
|
|
if failureRate < 0.1 {
|
|
direction = "stable"
|
|
confidence = 0.8
|
|
} else if failureRate < 0.3 {
|
|
direction = "stable"
|
|
confidence = 0.6
|
|
} else {
|
|
direction = "degrading"
|
|
confidence = 0.7
|
|
}
|
|
|
|
return []Trend{
|
|
{
|
|
Category: "overall",
|
|
Direction: direction,
|
|
Confidence: confidence,
|
|
Description: fmt.Sprintf("System health appears %s based on current analysis", direction),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (g *InsightsGenerator) generateRemediationInsights(steps []analyzer.RemediationStep) []RemediationInsight {
|
|
var insights []RemediationInsight
|
|
|
|
// Group by category and generate insights
|
|
categories := make(map[string][]analyzer.RemediationStep)
|
|
for _, step := range steps {
|
|
category := step.Category
|
|
if category == "" {
|
|
category = "general"
|
|
}
|
|
categories[category] = append(categories[category], step)
|
|
}
|
|
|
|
for category, categorySteps := range categories {
|
|
highPriorityCount := 0
|
|
automatableCount := 0
|
|
|
|
for _, step := range categorySteps {
|
|
if step.Priority >= 8 {
|
|
highPriorityCount++
|
|
}
|
|
if step.IsAutomatable {
|
|
automatableCount++
|
|
}
|
|
}
|
|
|
|
var impact, effort string
|
|
if highPriorityCount > len(categorySteps)/2 {
|
|
impact = "high"
|
|
} else {
|
|
impact = "medium"
|
|
}
|
|
|
|
if automatableCount > len(categorySteps)/2 {
|
|
effort = "low"
|
|
} else {
|
|
effort = "medium"
|
|
}
|
|
|
|
insights = append(insights, RemediationInsight{
|
|
Category: category,
|
|
Priority: highPriorityCount,
|
|
Impact: impact,
|
|
Effort: effort,
|
|
Description: fmt.Sprintf("%d steps in %s category, %d high priority",
|
|
len(categorySteps), category, highPriorityCount),
|
|
})
|
|
}
|
|
|
|
return insights
|
|
}
|
|
|
|
// RemediationGenerator generates remediation guide artifacts
|
|
type RemediationGenerator struct{}
|
|
|
|
func (g *RemediationGenerator) Generate(ctx context.Context, result *analyzer.AnalysisResult) (*Artifact, error) {
|
|
guide := struct {
|
|
Summary string `json:"summary"`
|
|
PriorityActions []analyzer.RemediationStep `json:"priorityActions"`
|
|
Categories map[string][]analyzer.RemediationStep `json:"categories"`
|
|
Prerequisites []string `json:"prerequisites"`
|
|
Automation AutomationGuide `json:"automation"`
|
|
GeneratedAt time.Time `json:"generatedAt"`
|
|
}{
|
|
Summary: g.generateRemediationSummary(result.Remediation),
|
|
PriorityActions: g.getPriorityActions(result.Remediation, 5),
|
|
Categories: g.categorizeRemediationSteps(result.Remediation),
|
|
Prerequisites: g.identifyPrerequisites(result.Remediation),
|
|
Automation: g.generateAutomationGuide(result.Remediation),
|
|
GeneratedAt: time.Now(),
|
|
}
|
|
|
|
data, err := json.MarshalIndent(guide, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artifact := &Artifact{
|
|
Name: "remediation-guide.json",
|
|
Type: "remediation",
|
|
Format: "json",
|
|
ContentType: "application/json",
|
|
Size: int64(len(data)),
|
|
Content: data,
|
|
Metadata: ArtifactMetadata{
|
|
CreatedAt: time.Now(),
|
|
Generator: "RemediationGenerator",
|
|
Version: "1.0.0",
|
|
Tags: []string{"remediation", "guide", "actions"},
|
|
},
|
|
}
|
|
|
|
return artifact, nil
|
|
}
|
|
|
|
func (g *RemediationGenerator) Name() string {
|
|
return "Remediation Generator"
|
|
}
|
|
|
|
func (g *RemediationGenerator) Description() string {
|
|
return "Generates detailed remediation guide artifacts"
|
|
}
|
|
|
|
func (g *RemediationGenerator) generateRemediationSummary(steps []analyzer.RemediationStep) string {
|
|
if len(steps) == 0 {
|
|
return "No remediation steps required"
|
|
}
|
|
|
|
automatable := 0
|
|
highPriority := 0
|
|
|
|
for _, step := range steps {
|
|
if step.IsAutomatable {
|
|
automatable++
|
|
}
|
|
if step.Priority >= 8 {
|
|
highPriority++
|
|
}
|
|
}
|
|
|
|
return fmt.Sprintf("Found %d remediation steps: %d high priority, %d automatable",
|
|
len(steps), highPriority, automatable)
|
|
}
|
|
|
|
func (g *RemediationGenerator) getPriorityActions(steps []analyzer.RemediationStep, limit int) []analyzer.RemediationStep {
|
|
sorted := make([]analyzer.RemediationStep, len(steps))
|
|
copy(sorted, steps)
|
|
|
|
sort.Slice(sorted, func(i, j int) bool {
|
|
return sorted[i].Priority > sorted[j].Priority
|
|
})
|
|
|
|
if len(sorted) > limit {
|
|
return sorted[:limit]
|
|
}
|
|
return sorted
|
|
}
|
|
|
|
func (g *RemediationGenerator) categorizeRemediationSteps(steps []analyzer.RemediationStep) map[string][]analyzer.RemediationStep {
|
|
categories := make(map[string][]analyzer.RemediationStep)
|
|
|
|
for _, step := range steps {
|
|
category := step.Category
|
|
if category == "" {
|
|
category = "general"
|
|
}
|
|
categories[category] = append(categories[category], step)
|
|
}
|
|
|
|
return categories
|
|
}
|
|
|
|
func (g *RemediationGenerator) identifyPrerequisites(steps []analyzer.RemediationStep) []string {
|
|
var prerequisites []string
|
|
|
|
categoryPrereqs := map[string]string{
|
|
"infrastructure": "Admin access to cluster nodes",
|
|
"networking": "Network configuration permissions",
|
|
"storage": "Storage admin permissions",
|
|
"security": "Security policy modification rights",
|
|
}
|
|
|
|
categories := make(map[string]bool)
|
|
for _, step := range steps {
|
|
if step.Category != "" {
|
|
categories[step.Category] = true
|
|
}
|
|
}
|
|
|
|
for category := range categories {
|
|
if prereq, exists := categoryPrereqs[category]; exists {
|
|
prerequisites = append(prerequisites, prereq)
|
|
}
|
|
}
|
|
|
|
return prerequisites
|
|
}
|
|
|
|
func (g *RemediationGenerator) generateAutomationGuide(steps []analyzer.RemediationStep) AutomationGuide {
|
|
automatable := 0
|
|
manual := 0
|
|
|
|
for _, step := range steps {
|
|
if step.IsAutomatable {
|
|
automatable++
|
|
} else {
|
|
manual++
|
|
}
|
|
}
|
|
|
|
var scripts []Script
|
|
if automatable > 0 {
|
|
scripts = append(scripts, Script{
|
|
Name: "automated-remediation.sh",
|
|
Description: "Automated remediation script for detected issues",
|
|
Language: "bash",
|
|
Content: g.generateRemediationScript(steps),
|
|
Prerequisites: []string{"kubectl", "admin access", "bash"},
|
|
})
|
|
}
|
|
|
|
return AutomationGuide{
|
|
AutomatableSteps: automatable,
|
|
ManualSteps: manual,
|
|
Scripts: scripts,
|
|
}
|
|
}
|
|
|
|
func (g *RemediationGenerator) generateRemediationScript(steps []analyzer.RemediationStep) string {
|
|
script := `#!/bin/bash
|
|
# Automated Remediation Script
|
|
# Generated by Troubleshoot Analysis Engine
|
|
|
|
set -e
|
|
|
|
echo "Starting automated remediation..."
|
|
|
|
`
|
|
|
|
for i, step := range steps {
|
|
if step.IsAutomatable && step.Command != "" {
|
|
script += fmt.Sprintf(`
|
|
# Step %d: %s
|
|
echo "Executing: %s"
|
|
if %s; then
|
|
echo "✅ Step %d completed successfully"
|
|
else
|
|
echo "❌ Step %d failed - manual intervention required"
|
|
fi
|
|
|
|
`, i+1, step.Description, step.Description, step.Command, i+1, i+1)
|
|
}
|
|
}
|
|
|
|
script += `
|
|
echo "Automated remediation completed. Please review any failed steps manually."
|
|
`
|
|
|
|
return script
|
|
}
|
|
|
|
// CorrelationGenerator generates correlation matrix artifacts
|
|
type CorrelationGenerator struct{}
|
|
|
|
func (g *CorrelationGenerator) Generate(ctx context.Context, result *analyzer.AnalysisResult) (*Artifact, error) {
|
|
correlations := g.buildCorrelationMatrix(result.Results)
|
|
|
|
data, err := json.MarshalIndent(correlations, "", " ")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artifact := &Artifact{
|
|
Name: "correlations.json",
|
|
Type: "correlations",
|
|
Format: "json",
|
|
ContentType: "application/json",
|
|
Size: int64(len(data)),
|
|
Content: data,
|
|
Metadata: ArtifactMetadata{
|
|
CreatedAt: time.Now(),
|
|
Generator: "CorrelationGenerator",
|
|
Version: "1.0.0",
|
|
Tags: []string{"correlations", "relationships"},
|
|
},
|
|
}
|
|
|
|
return artifact, nil
|
|
}
|
|
|
|
func (g *CorrelationGenerator) Name() string {
|
|
return "Correlation Generator"
|
|
}
|
|
|
|
func (g *CorrelationGenerator) Description() string {
|
|
return "Generates correlation matrix and relationship artifacts"
|
|
}
|
|
|
|
func (g *CorrelationGenerator) buildCorrelationMatrix(results []*analyzer.AnalyzerResult) map[string]interface{} {
|
|
correlations := make(map[string]interface{})
|
|
|
|
// Namespace-based correlations
|
|
namespaceFailures := make(map[string][]string)
|
|
namespaceWarnings := make(map[string][]string)
|
|
|
|
for _, result := range results {
|
|
if result.InvolvedObject != nil && result.InvolvedObject.Namespace != "" {
|
|
namespace := result.InvolvedObject.Namespace
|
|
if result.IsFail {
|
|
namespaceFailures[namespace] = append(namespaceFailures[namespace], result.Title)
|
|
} else if result.IsWarn {
|
|
namespaceWarnings[namespace] = append(namespaceWarnings[namespace], result.Title)
|
|
}
|
|
}
|
|
}
|
|
|
|
correlations["namespace_failures"] = namespaceFailures
|
|
correlations["namespace_warnings"] = namespaceWarnings
|
|
|
|
// Category-based correlations
|
|
categoryCorrelations := make(map[string]map[string]int)
|
|
for _, result := range results {
|
|
if result.Category != "" {
|
|
if categoryCorrelations[result.Category] == nil {
|
|
categoryCorrelations[result.Category] = make(map[string]int)
|
|
}
|
|
|
|
status := "unknown"
|
|
if result.IsPass {
|
|
status = "pass"
|
|
} else if result.IsWarn {
|
|
status = "warn"
|
|
} else if result.IsFail {
|
|
status = "fail"
|
|
}
|
|
|
|
categoryCorrelations[result.Category][status]++
|
|
}
|
|
}
|
|
|
|
correlations["category_status_distribution"] = categoryCorrelations
|
|
|
|
// Agent-based correlations
|
|
agentResults := make(map[string]map[string]int)
|
|
for _, result := range results {
|
|
if result.AgentName != "" {
|
|
if agentResults[result.AgentName] == nil {
|
|
agentResults[result.AgentName] = make(map[string]int)
|
|
}
|
|
|
|
if result.IsPass {
|
|
agentResults[result.AgentName]["pass"]++
|
|
} else if result.IsWarn {
|
|
agentResults[result.AgentName]["warn"]++
|
|
} else if result.IsFail {
|
|
agentResults[result.AgentName]["fail"]++
|
|
}
|
|
}
|
|
}
|
|
|
|
correlations["agent_performance"] = agentResults
|
|
|
|
// Confidence correlations
|
|
confidenceRanges := map[string]int{
|
|
"high (>0.8)": 0,
|
|
"medium (0.5-0.8)": 0,
|
|
"low (<0.5)": 0,
|
|
"unspecified": 0,
|
|
}
|
|
|
|
for _, result := range results {
|
|
if result.Confidence > 0.8 {
|
|
confidenceRanges["high (>0.8)"]++
|
|
} else if result.Confidence > 0.5 {
|
|
confidenceRanges["medium (0.5-0.8)"]++
|
|
} else if result.Confidence > 0 {
|
|
confidenceRanges["low (<0.5)"]++
|
|
} else {
|
|
confidenceRanges["unspecified"]++
|
|
}
|
|
}
|
|
|
|
correlations["confidence_distribution"] = confidenceRanges
|
|
|
|
return correlations
|
|
}
|
|
|
|
// GeneratorRegistry manages all artifact generators
|
|
type GeneratorRegistry struct {
|
|
generators map[string]ArtifactGenerator
|
|
}
|
|
|
|
// NewGeneratorRegistry creates a new generator registry
|
|
func NewGeneratorRegistry() *GeneratorRegistry {
|
|
registry := &GeneratorRegistry{
|
|
generators: make(map[string]ArtifactGenerator),
|
|
}
|
|
|
|
// Register default generators
|
|
registry.RegisterGenerator("summary", &SummaryGenerator{})
|
|
registry.RegisterGenerator("insights", &InsightsGenerator{})
|
|
registry.RegisterGenerator("remediation", &RemediationGenerator{})
|
|
registry.RegisterGenerator("correlations", &CorrelationGenerator{})
|
|
|
|
return registry
|
|
}
|
|
|
|
// RegisterGenerator registers a new generator
|
|
func (r *GeneratorRegistry) RegisterGenerator(name string, generator ArtifactGenerator) {
|
|
r.generators[name] = generator
|
|
}
|
|
|
|
// GetGenerator gets a generator by name
|
|
func (r *GeneratorRegistry) GetGenerator(name string) (ArtifactGenerator, bool) {
|
|
generator, exists := r.generators[name]
|
|
return generator, exists
|
|
}
|
|
|
|
// GenerateArtifact generates an artifact using the specified generator
|
|
func (r *GeneratorRegistry) GenerateArtifact(ctx context.Context, generatorName string, result *analyzer.AnalysisResult) (*Artifact, error) {
|
|
generator, exists := r.GetGenerator(generatorName)
|
|
if !exists {
|
|
return nil, fmt.Errorf("no generator found with name: %s", generatorName)
|
|
}
|
|
|
|
return generator.Generate(ctx, result)
|
|
}
|
|
|
|
// ListGenerators returns all available generator names
|
|
func (r *GeneratorRegistry) ListGenerators() []string {
|
|
var names []string
|
|
for name := range r.generators {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names)
|
|
return names
|
|
}
|