feat: integrate detect-pollution into bd doctor --check=pollution (bd-kff0)
- Add --check flag to doctor for specific check modes - Add --check=pollution to run detailed pollution detection - Add --clean flag to delete detected test issues - Mark detect-pollution command as hidden (deprecated) - Update fix messages to point to new doctor --check=pollution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,15 +12,17 @@ import (
|
||||
"github.com/steveyegge/beads/internal/ui"
|
||||
)
|
||||
|
||||
// showDetectPollutionDeprecationHint shows a hint about bd doctor consolidation (bd-bqcc)
|
||||
// showDetectPollutionDeprecationHint shows a hint about bd doctor consolidation (bd-kff0)
|
||||
func showDetectPollutionDeprecationHint() {
|
||||
fmt.Fprintln(os.Stderr, ui.RenderMuted("💡 Tip: 'bd doctor' now detects test pollution in the Metadata section"))
|
||||
fmt.Fprintln(os.Stderr, ui.RenderMuted("💡 Tip: Use 'bd doctor --check=pollution' instead (this command is deprecated)"))
|
||||
}
|
||||
|
||||
var detectPollutionCmd = &cobra.Command{
|
||||
Use: "detect-pollution",
|
||||
GroupID: "maint",
|
||||
Short: "Detect and optionally clean test issues from database",
|
||||
Use: "detect-pollution",
|
||||
GroupID: "maint",
|
||||
Hidden: true, // bd-kff0: deprecated, use 'bd doctor --check=pollution' instead
|
||||
Deprecated: "use 'bd doctor --check=pollution' instead",
|
||||
Short: "Detect and optionally clean test issues from database",
|
||||
Long: `Detect test issues that leaked into production database using pattern matching.
|
||||
|
||||
This command finds issues that appear to be test data based on:
|
||||
|
||||
162
cmd/bd/doctor.go
162
cmd/bd/doctor.go
@@ -19,6 +19,7 @@ import (
|
||||
"github.com/steveyegge/beads/internal/beads"
|
||||
"github.com/steveyegge/beads/internal/configfile"
|
||||
"github.com/steveyegge/beads/internal/syncbranch"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
"github.com/steveyegge/beads/internal/ui"
|
||||
)
|
||||
|
||||
@@ -56,6 +57,8 @@ var (
|
||||
doctorFixChildParent bool // bd-cuek: opt-in fix for child→parent deps
|
||||
perfMode bool
|
||||
checkHealthMode bool
|
||||
doctorCheckFlag string // bd-kff0: run specific check (e.g., "pollution")
|
||||
doctorClean bool // bd-kff0: for pollution check, delete detected issues
|
||||
)
|
||||
|
||||
// ConfigKeyHintsDoctor is the config key for suppressing doctor hints
|
||||
@@ -98,6 +101,10 @@ Export Mode (--output):
|
||||
Save diagnostics to a JSON file for historical analysis and bug reporting.
|
||||
Includes timestamp and platform info for tracking intermittent issues.
|
||||
|
||||
Specific Check Mode (--check):
|
||||
Run a specific check in detail. Available checks:
|
||||
- pollution: Detect and optionally clean test issues from database
|
||||
|
||||
Examples:
|
||||
bd doctor # Check current directory
|
||||
bd doctor /path/to/repo # Check specific repository
|
||||
@@ -108,7 +115,9 @@ Examples:
|
||||
bd doctor --fix --fix-child-parent # Also fix child→parent deps (opt-in)
|
||||
bd doctor --dry-run # Preview what --fix would do without making changes
|
||||
bd doctor --perf # Performance diagnostics
|
||||
bd doctor --output diagnostics.json # Export diagnostics to file`,
|
||||
bd doctor --output diagnostics.json # Export diagnostics to file
|
||||
bd doctor --check=pollution # Show potential test issues
|
||||
bd doctor --check=pollution --clean # Delete test issues (with confirmation)`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Use global jsonOutput set by PersistentPreRun
|
||||
|
||||
@@ -137,6 +146,19 @@ Examples:
|
||||
return
|
||||
}
|
||||
|
||||
// Run specific check if --check flag is set (bd-kff0)
|
||||
if doctorCheckFlag != "" {
|
||||
switch doctorCheckFlag {
|
||||
case "pollution":
|
||||
runPollutionCheck(absPath, doctorClean, doctorYes)
|
||||
return
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Error: unknown check %q\n", doctorCheckFlag)
|
||||
fmt.Fprintf(os.Stderr, "Available checks: pollution\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Run diagnostics
|
||||
result := runDiagnostics(absPath)
|
||||
|
||||
@@ -457,7 +479,7 @@ func applyFixList(path string, fixes []doctorCheck) {
|
||||
continue
|
||||
case "Test Pollution":
|
||||
// No auto-fix: test cleanup requires user review
|
||||
fmt.Printf(" ⚠ Run 'bd detect-pollution' to review and clean test issues\n")
|
||||
fmt.Printf(" ⚠ Run 'bd doctor --check=pollution' to review and clean test issues\n")
|
||||
continue
|
||||
case "Git Conflicts":
|
||||
// No auto-fix: git conflicts require manual resolution
|
||||
@@ -1081,9 +1103,145 @@ func printDiagnostics(result doctorResult) {
|
||||
}
|
||||
}
|
||||
|
||||
// runPollutionCheck runs detailed test pollution detection (bd-kff0)
|
||||
// This integrates the detect-pollution command functionality into doctor.
|
||||
func runPollutionCheck(path 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 (bd-9cc)")
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ func CheckTestPollution(path string) DoctorCheck {
|
||||
Status: "warning",
|
||||
Message: fmt.Sprintf("%d potential test issue(s) detected", count),
|
||||
Detail: "Test issues may have leaked into production database",
|
||||
Fix: "Run 'bd detect-pollution' to review and clean test issues",
|
||||
Fix: "Run 'bd doctor --check=pollution' to review and clean test issues",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user