// Copyright 2026 MarketAlly. All rights reserved. // SPDX-License-Identifier: MIT package ai import ( "context" "code.gitcaddy.com/server/v3/models/db" "code.gitcaddy.com/server/v3/modules/timeutil" ) func init() { db.RegisterModel(new(ErrorPattern)) db.RegisterModel(new(WorkflowTelemetry)) } // ErrorPattern represents a known CI/CD error pattern with its solution type ErrorPattern struct { ID int64 `xorm:"pk autoincr"` Pattern string `xorm:"VARCHAR(255) NOT NULL INDEX"` PatternRegex string `xorm:"TEXT"` RunnerType string `xorm:"VARCHAR(50) INDEX"` ProjectType string `xorm:"VARCHAR(100) INDEX"` Framework string `xorm:"VARCHAR(100)"` ErrorMessage string `xorm:"TEXT"` Diagnosis string `xorm:"TEXT"` Solution string `xorm:"TEXT"` SolutionDiff string `xorm:"TEXT"` OccurrenceCount int `xorm:"DEFAULT 1"` SuccessCount int `xorm:"DEFAULT 0"` CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"` } // TableName returns the table name for ErrorPattern func (ErrorPattern) TableName() string { return "error_pattern" } // WorkflowTelemetry records workflow run results for compatibility tracking type WorkflowTelemetry struct { ID int64 `xorm:"pk autoincr"` RunnerID int64 `xorm:"INDEX"` RunnerName string `xorm:"VARCHAR(255) INDEX"` JobID int64 `xorm:"INDEX"` WorkflowName string `xorm:"VARCHAR(255)"` ProjectType string `xorm:"VARCHAR(100) INDEX"` Framework string `xorm:"VARCHAR(100)"` Target string `xorm:"VARCHAR(100)"` Status string `xorm:"VARCHAR(20) INDEX"` ErrorPatternID int64 `xorm:"INDEX"` DurationSeconds int CreatedUnix timeutil.TimeStamp `xorm:"created INDEX"` } // TableName returns the table name for WorkflowTelemetry func (WorkflowTelemetry) TableName() string { return "workflow_telemetry" } // GetErrorPatterns returns matching error patterns func GetErrorPatterns(ctx context.Context, pattern, runnerType, projectType string) ([]*ErrorPattern, error) { patterns := make([]*ErrorPattern, 0, 10) sess := db.GetEngine(ctx).OrderBy("occurrence_count DESC") if pattern != "" { sess = sess.Where("pattern LIKE ?", "%"+pattern+"%") } // Handle special "_any" filter for patterns with empty runner_type if runnerType == "_any" { sess = sess.And("runner_type = ?", "") } else if runnerType != "" { sess = sess.And("runner_type = ?", runnerType) } if projectType != "" { sess = sess.And("project_type = ?", projectType) } return patterns, sess.Limit(50).Find(&patterns) } // GetErrorPatternByID returns an error pattern by ID func GetErrorPatternByID(ctx context.Context, id int64) (*ErrorPattern, error) { ep := &ErrorPattern{} has, err := db.GetEngine(ctx).ID(id).Get(ep) if err != nil { return nil, err } if !has { return nil, nil } return ep, nil } // CreateOrUpdateErrorPattern creates or updates an error pattern func CreateOrUpdateErrorPattern(ctx context.Context, ep *ErrorPattern) error { existing := &ErrorPattern{} has, err := db.GetEngine(ctx). Where("pattern = ? AND runner_type = ? AND project_type = ?", ep.Pattern, ep.RunnerType, ep.ProjectType). Get(existing) if err != nil { return err } if has { existing.OccurrenceCount++ if ep.Solution != "" { existing.Solution = ep.Solution existing.SolutionDiff = ep.SolutionDiff existing.Diagnosis = ep.Diagnosis } if ep.ErrorMessage != "" && existing.ErrorMessage == "" { existing.ErrorMessage = ep.ErrorMessage } _, err = db.GetEngine(ctx).ID(existing.ID).AllCols().Update(existing) return err } _, err = db.GetEngine(ctx).Insert(ep) return err } // IncrementSuccessCount marks a solution as successful func IncrementSuccessCount(ctx context.Context, id int64) error { _, err := db.GetEngine(ctx).Exec( "UPDATE error_pattern SET success_count = success_count + 1, updated_unix = ? WHERE id = ?", timeutil.TimeStampNow(), id) return err } // RecordTelemetry records a workflow run result func RecordTelemetry(ctx context.Context, t *WorkflowTelemetry) error { _, err := db.GetEngine(ctx).Insert(t) return err } // GetCompatibilityMatrix returns what works on which runners func GetCompatibilityMatrix(ctx context.Context, projectType string) ([]map[string]any, error) { results := make([]map[string]any, 0) // Use XORM's native query which returns []map[string][]byte sql := "SELECT runner_name, framework, target, " + "SUM(CASE WHEN status = 'success' THEN 1 ELSE 0 END) as successes, " + "SUM(CASE WHEN status = 'failure' THEN 1 ELSE 0 END) as failures, " + "COUNT(*) as total " + "FROM workflow_telemetry WHERE 1=1" if projectType != "" { sql += " AND project_type = ?" } sql += " GROUP BY runner_name, framework, target ORDER BY total DESC" var rows []map[string][]byte var err error if projectType != "" { rows, err = db.GetEngine(ctx).Query(sql, projectType) } else { rows, err = db.GetEngine(ctx).Query(sql) } if err != nil { return nil, err } for _, row := range rows { successes := parseInt(row["successes"]) failures := parseInt(row["failures"]) total := parseInt(row["total"]) successRate := float64(0) if total > 0 { successRate = float64(successes) / float64(total) * 100 } results = append(results, map[string]any{ "runner_name": string(row["runner_name"]), "framework": string(row["framework"]), "target": string(row["target"]), "successes": successes, "failures": failures, "total": total, "success_rate": successRate, }) } return results, nil } func parseInt(b []byte) int64 { if len(b) == 0 { return 0 } var n int64 for _, c := range b { if c >= '0' && c <= '9' { n = n*10 + int64(c-'0') } } return n }