forked from Gitlink/gitlink-cli
247 lines
7.8 KiB
Go
247 lines
7.8 KiB
Go
package workflow
|
|
|
|
func ScoreHealth(input HealthInput, lang string) HealthResult {
|
|
lang = normalizeLang(lang)
|
|
|
|
metrics := make([]HealthMetric, 0, 8)
|
|
notes := []ScoringNote{}
|
|
recommendations := []string{}
|
|
|
|
issueMetric := scoreIssueBacklog(input, lang)
|
|
metrics = append(metrics, issueMetric)
|
|
if issueMetric.Score < issueMetric.MaxScore {
|
|
recommendations = append(recommendations, message(lang, "rec_reduce_issues"))
|
|
}
|
|
|
|
prMetric := scorePRBacklog(input, lang)
|
|
metrics = append(metrics, prMetric)
|
|
if prMetric.Score < prMetric.MaxScore {
|
|
recommendations = append(recommendations, message(lang, "rec_reduce_prs"))
|
|
}
|
|
|
|
recentMetric, recentNote := scoreRecentActivity(input, lang)
|
|
metrics = append(metrics, recentMetric)
|
|
if recentNote.Note != "" {
|
|
notes = append(notes, recentNote)
|
|
}
|
|
if recentMetric.Score < recentMetric.MaxScore/2 {
|
|
recommendations = append(recommendations, message(lang, "rec_restore_activity"))
|
|
}
|
|
|
|
releaseMetric, releaseNote := scoreReleaseStatus(input, lang)
|
|
metrics = append(metrics, releaseMetric)
|
|
if releaseNote.Note != "" {
|
|
notes = append(notes, releaseNote)
|
|
}
|
|
if releaseMetric.Score < releaseMetric.MaxScore/2 {
|
|
recommendations = append(recommendations, message(lang, "rec_release"))
|
|
}
|
|
|
|
docMetric := scoreDocumentation(input, lang)
|
|
metrics = append(metrics, docMetric)
|
|
if docMetric.Score < docMetric.MaxScore {
|
|
recommendations = append(recommendations, message(lang, "rec_docs"))
|
|
}
|
|
|
|
licenseMetric := scoreLicenseAndContributing(input, lang)
|
|
metrics = append(metrics, licenseMetric)
|
|
if licenseMetric.Score < licenseMetric.MaxScore {
|
|
recommendations = append(recommendations, message(lang, "rec_license"))
|
|
}
|
|
|
|
agentMetric, agentNote := scoreAgentReadiness(input, lang)
|
|
metrics = append(metrics, agentMetric)
|
|
if agentNote.Note != "" {
|
|
notes = append(notes, agentNote)
|
|
}
|
|
if agentMetric.Score < agentMetric.MaxScore/2 {
|
|
recommendations = append(recommendations, message(lang, "rec_agent"))
|
|
}
|
|
|
|
ciMetric, ciNote := scoreCIStatus(input, lang)
|
|
metrics = append(metrics, ciMetric)
|
|
if ciNote.Note != "" {
|
|
notes = append(notes, ciNote)
|
|
}
|
|
|
|
total := 0
|
|
maxTotal := 0
|
|
for _, metric := range metrics {
|
|
total += metric.Score
|
|
maxTotal += metric.MaxScore
|
|
}
|
|
|
|
healthScore := 0
|
|
if maxTotal > 0 {
|
|
healthScore = clampInt(total*100/maxTotal, 100)
|
|
}
|
|
if len(recommendations) == 0 {
|
|
recommendations = append(recommendations, message(lang, "rec_maintain"))
|
|
}
|
|
|
|
return HealthResult{
|
|
Repository: input.Repository,
|
|
HealthScore: healthScore,
|
|
RiskLevel: riskLevel(healthScore),
|
|
Metrics: metrics,
|
|
Recommendations: uniqueStrings(recommendations),
|
|
ScoringNotes: notes,
|
|
}
|
|
}
|
|
|
|
func scoreIssueBacklog(input HealthInput, lang string) HealthMetric {
|
|
score := 20
|
|
score -= minInt(input.StaleIssues*3, 14)
|
|
if input.OpenIssues > 50 {
|
|
score -= 8
|
|
} else if input.OpenIssues > 20 {
|
|
score -= 5
|
|
} else if input.OpenIssues > 10 {
|
|
score -= 2
|
|
}
|
|
score = clampInt(score, 20)
|
|
|
|
status := "good"
|
|
reason := message(lang, "health_issue_backlog_good")
|
|
if score < 14 {
|
|
status = "attention"
|
|
reason = message(lang, "health_issue_backlog_attention")
|
|
}
|
|
return HealthMetric{Name: "issue_backlog_and_response", Status: status, Score: score, MaxScore: 20, Reason: reason}
|
|
}
|
|
|
|
func scorePRBacklog(input HealthInput, lang string) HealthMetric {
|
|
score := 20
|
|
score -= minInt(input.StalePRs*5, 15)
|
|
if input.OpenPRs > 20 {
|
|
score -= 8
|
|
} else if input.OpenPRs > 10 {
|
|
score -= 5
|
|
} else if input.OpenPRs > 5 {
|
|
score -= 2
|
|
}
|
|
score = clampInt(score, 20)
|
|
|
|
status := "good"
|
|
reason := message(lang, "health_pr_backlog_good")
|
|
if score < 14 {
|
|
status = "attention"
|
|
reason = message(lang, "health_pr_backlog_attention")
|
|
}
|
|
return HealthMetric{Name: "pr_backlog_and_merge_state", Status: status, Score: score, MaxScore: 20, Reason: reason}
|
|
}
|
|
|
|
func scoreRecentActivity(input HealthInput, lang string) (HealthMetric, ScoringNote) {
|
|
if !input.RecentActivityKnown {
|
|
return HealthMetric{Name: "recent_activity", Status: "unknown", Score: 8, MaxScore: 15, Reason: message(lang, "health_recent_unknown")}, ScoringNote{Metric: "recent_activity", Note: message(lang, "health_recent_unknown")}
|
|
}
|
|
if input.RecentActivityDays <= 7 {
|
|
return HealthMetric{Name: "recent_activity", Status: "good", Score: 15, MaxScore: 15, Reason: "recent activity within 7 days"}, ScoringNote{}
|
|
}
|
|
if input.RecentActivityDays <= 30 {
|
|
return HealthMetric{Name: "recent_activity", Status: "attention", Score: 10, MaxScore: 15, Reason: "recent activity within 30 days"}, ScoringNote{}
|
|
}
|
|
if input.RecentActivityDays <= 90 {
|
|
return HealthMetric{Name: "recent_activity", Status: "attention", Score: 6, MaxScore: 15, Reason: "recent activity older than 30 days"}, ScoringNote{}
|
|
}
|
|
return HealthMetric{Name: "recent_activity", Status: "risk", Score: 2, MaxScore: 15, Reason: "recent activity older than 90 days"}, ScoringNote{}
|
|
}
|
|
|
|
func scoreReleaseStatus(input HealthInput, lang string) (HealthMetric, ScoringNote) {
|
|
if !input.ReleaseKnown {
|
|
return HealthMetric{Name: "release_status", Status: "unknown", Score: 8, MaxScore: 15, Reason: message(lang, "health_release_unknown")}, ScoringNote{Metric: "release_status", Note: message(lang, "health_release_unknown")}
|
|
}
|
|
if input.HasRecentRelease {
|
|
return HealthMetric{Name: "release_status", Status: "good", Score: 15, MaxScore: 15, Reason: "recent release found"}, ScoringNote{}
|
|
}
|
|
return HealthMetric{Name: "release_status", Status: "risk", Score: 4, MaxScore: 15, Reason: "no recent release found"}, ScoringNote{}
|
|
}
|
|
|
|
func scoreDocumentation(input HealthInput, lang string) HealthMetric {
|
|
score := 0
|
|
if input.HasReadme {
|
|
score += 7
|
|
}
|
|
if input.HasContributing {
|
|
score += 3
|
|
}
|
|
status := "attention"
|
|
reason := message(lang, "rec_docs")
|
|
if score == 10 {
|
|
status = "good"
|
|
reason = "README and contribution guidance are present"
|
|
}
|
|
return HealthMetric{Name: "documentation", Status: status, Score: score, MaxScore: 10, Reason: reason}
|
|
}
|
|
|
|
func scoreLicenseAndContributing(input HealthInput, lang string) HealthMetric {
|
|
score := 0
|
|
if input.HasLicense {
|
|
score += 6
|
|
}
|
|
if input.HasContributing {
|
|
score += 4
|
|
}
|
|
status := "attention"
|
|
reason := message(lang, "rec_license")
|
|
if score == 10 {
|
|
status = "good"
|
|
reason = "LICENSE and CONTRIBUTING are present"
|
|
}
|
|
return HealthMetric{Name: "license_and_contributing", Status: status, Score: score, MaxScore: 10, Reason: reason}
|
|
}
|
|
|
|
func scoreAgentReadiness(input HealthInput, lang string) (HealthMetric, ScoringNote) {
|
|
if !input.AgentReadinessKnown {
|
|
return HealthMetric{Name: "agent_readiness", Status: "unknown", Score: 5, MaxScore: 10, Reason: message(lang, "health_agent_unknown")}, ScoringNote{Metric: "agent_readiness", Note: message(lang, "health_agent_unknown")}
|
|
}
|
|
score := clampInt(input.AgentReadinessScore, 10)
|
|
status := "attention"
|
|
if score >= 8 {
|
|
status = "good"
|
|
} else if score < 4 {
|
|
status = "risk"
|
|
}
|
|
return HealthMetric{Name: "agent_readiness", Status: status, Score: score, MaxScore: 10, Reason: "agent readiness score provided"}, ScoringNote{}
|
|
}
|
|
|
|
func scoreCIStatus(input HealthInput, lang string) (HealthMetric, ScoringNote) {
|
|
if !input.CIKnown {
|
|
return HealthMetric{Name: "ci_status", Status: "unknown", Score: 0, MaxScore: 0, Reason: message(lang, "health_ci_unknown")}, ScoringNote{Metric: "ci_status", Note: message(lang, "health_ci_unknown")}
|
|
}
|
|
if input.CIPassing {
|
|
return HealthMetric{Name: "ci_status", Status: "good", Score: 0, MaxScore: 0, Reason: "CI status is passing"}, ScoringNote{}
|
|
}
|
|
return HealthMetric{Name: "ci_status", Status: "risk", Score: 0, MaxScore: 0, Reason: "CI status is failing"}, ScoringNote{}
|
|
}
|
|
|
|
func riskLevel(score int) string {
|
|
switch {
|
|
case score >= 85:
|
|
return "low"
|
|
case score >= 65:
|
|
return "medium"
|
|
case score >= 40:
|
|
return "high"
|
|
default:
|
|
return "critical"
|
|
}
|
|
}
|
|
|
|
func clampInt(value, maxValue int) int {
|
|
if value < 0 {
|
|
return 0
|
|
}
|
|
if value > maxValue {
|
|
return maxValue
|
|
}
|
|
return value
|
|
}
|
|
|
|
func minInt(a int, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|