Fixes CI lint failures by adding appropriate nolint directives for: - G204 (subprocess with variable) - git commands with trusted inputs - G304 (file inclusion via variable) - paths from internal helpers - G302/G306 (file permissions) - .gitignore needs 0644 - errcheck (unchecked return values) - fmt.Fprint* to stdout/stderr - unparam (unused parameters) - reserved for future use
157 lines
4.5 KiB
Go
157 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/steveyegge/beads/internal/types"
|
|
"github.com/steveyegge/beads/internal/ui"
|
|
)
|
|
|
|
// runPollutionCheck runs detailed test pollution detection
|
|
// This integrates the detect-pollution command functionality into doctor.
|
|
//
|
|
//nolint:unparam // path reserved for future use
|
|
func runPollutionCheck(_ string, clean bool, yes bool) {
|
|
// Ensure we have a store initialized (uses direct mode, no daemon support yet)
|
|
if err := ensureDirectMode("pollution check requires direct mode"); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
ctx := rootCtx
|
|
|
|
// Get all issues
|
|
allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error fetching issues: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Detect pollution (reuse detectTestPollution from detect_pollution.go)
|
|
polluted := detectTestPollution(allIssues)
|
|
|
|
if len(polluted) == 0 {
|
|
if !jsonOutput {
|
|
fmt.Println("No test pollution detected!")
|
|
} else {
|
|
outputJSON(map[string]interface{}{
|
|
"polluted_count": 0,
|
|
"issues": []interface{}{},
|
|
})
|
|
}
|
|
return
|
|
}
|
|
|
|
// Categorize by confidence
|
|
highConfidence := []pollutionResult{}
|
|
mediumConfidence := []pollutionResult{}
|
|
|
|
for _, p := range polluted {
|
|
if p.score >= 0.9 {
|
|
highConfidence = append(highConfidence, p)
|
|
} else {
|
|
mediumConfidence = append(mediumConfidence, p)
|
|
}
|
|
}
|
|
|
|
if jsonOutput {
|
|
result := map[string]interface{}{
|
|
"polluted_count": len(polluted),
|
|
"high_confidence": len(highConfidence),
|
|
"medium_confidence": len(mediumConfidence),
|
|
"issues": []map[string]interface{}{},
|
|
}
|
|
|
|
for _, p := range polluted {
|
|
result["issues"] = append(result["issues"].([]map[string]interface{}), map[string]interface{}{
|
|
"id": p.issue.ID,
|
|
"title": p.issue.Title,
|
|
"score": p.score,
|
|
"reasons": p.reasons,
|
|
"created_at": p.issue.CreatedAt,
|
|
})
|
|
}
|
|
|
|
outputJSON(result)
|
|
return
|
|
}
|
|
|
|
// Human-readable output
|
|
fmt.Printf("Found %d potential test issues:\n\n", len(polluted))
|
|
|
|
if len(highConfidence) > 0 {
|
|
fmt.Printf("High Confidence (score ≥ 0.9):\n")
|
|
for _, p := range highConfidence {
|
|
fmt.Printf(" %s: %q (score: %.2f)\n", p.issue.ID, p.issue.Title, p.score)
|
|
for _, reason := range p.reasons {
|
|
fmt.Printf(" - %s\n", reason)
|
|
}
|
|
}
|
|
fmt.Printf(" (Total: %d issues)\n\n", len(highConfidence))
|
|
}
|
|
|
|
if len(mediumConfidence) > 0 {
|
|
fmt.Printf("Medium Confidence (score 0.7-0.9):\n")
|
|
for _, p := range mediumConfidence {
|
|
fmt.Printf(" %s: %q (score: %.2f)\n", p.issue.ID, p.issue.Title, p.score)
|
|
for _, reason := range p.reasons {
|
|
fmt.Printf(" - %s\n", reason)
|
|
}
|
|
}
|
|
fmt.Printf(" (Total: %d issues)\n\n", len(mediumConfidence))
|
|
}
|
|
|
|
if !clean {
|
|
fmt.Printf("Run 'bd doctor --check=pollution --clean' to delete these issues (with confirmation).\n")
|
|
return
|
|
}
|
|
|
|
// Confirmation prompt
|
|
if !yes {
|
|
fmt.Printf("\nDelete %d test issues? [y/N] ", len(polluted))
|
|
var response string
|
|
_, _ = fmt.Scanln(&response)
|
|
if strings.ToLower(response) != "y" {
|
|
fmt.Println("Canceled.")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Backup to JSONL before deleting
|
|
backupPath := ".beads/pollution-backup.jsonl"
|
|
if err := backupPollutedIssues(polluted, backupPath); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error backing up issues: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Backed up %d issues to %s\n", len(polluted), backupPath)
|
|
|
|
// Delete issues
|
|
fmt.Printf("\nDeleting %d issues...\n", len(polluted))
|
|
deleted := 0
|
|
for _, p := range polluted {
|
|
if err := deleteIssue(ctx, p.issue.ID); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error deleting %s: %v\n", p.issue.ID, err)
|
|
continue
|
|
}
|
|
deleted++
|
|
}
|
|
|
|
// Schedule auto-flush
|
|
markDirtyAndScheduleFlush()
|
|
|
|
fmt.Printf("%s Deleted %d test issues\n", ui.RenderPass("✓"), deleted)
|
|
fmt.Printf("\nCleanup complete. To restore, run: bd import %s\n", backupPath)
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(doctorCmd)
|
|
doctorCmd.Flags().BoolVar(&perfMode, "perf", false, "Run performance diagnostics and generate CPU profile")
|
|
doctorCmd.Flags().BoolVar(&checkHealthMode, "check-health", false, "Quick health check for git hooks (silent on success)")
|
|
doctorCmd.Flags().StringVarP(&doctorOutput, "output", "o", "", "Export diagnostics to JSON file")
|
|
doctorCmd.Flags().StringVar(&doctorCheckFlag, "check", "", "Run specific check in detail (e.g., 'pollution')")
|
|
doctorCmd.Flags().BoolVar(&doctorClean, "clean", false, "For pollution check: delete detected test issues")
|
|
doctorCmd.Flags().BoolVar(&doctorDeep, "deep", false, "Validate full graph integrity")
|
|
}
|