Add doctor package with: - Check interface for implementing health checks - CheckContext for passing context to checks - CheckResult and CheckStatus types - Report with summary and pretty printing - Doctor runner with Run() and Fix() methods - BaseCheck and FixableCheck for easy check implementation - CLI command: gt doctor [--fix] [--verbose] [--rig <name>] Built-in checks will be added in: - gt-f9x.5: Town-level checks (config, state, mail, rigs) - gt-f9x.6: Rig-level checks (refinery, clones, gitignore) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
119 lines
2.8 KiB
Go
119 lines
2.8 KiB
Go
package doctor
|
|
|
|
// Doctor manages and executes health checks.
|
|
type Doctor struct {
|
|
checks []Check
|
|
}
|
|
|
|
// NewDoctor creates a new Doctor with no registered checks.
|
|
func NewDoctor() *Doctor {
|
|
return &Doctor{
|
|
checks: make([]Check, 0),
|
|
}
|
|
}
|
|
|
|
// Register adds a check to the doctor's check list.
|
|
func (d *Doctor) Register(check Check) {
|
|
d.checks = append(d.checks, check)
|
|
}
|
|
|
|
// RegisterAll adds multiple checks to the doctor's check list.
|
|
func (d *Doctor) RegisterAll(checks ...Check) {
|
|
d.checks = append(d.checks, checks...)
|
|
}
|
|
|
|
// Checks returns the list of registered checks.
|
|
func (d *Doctor) Checks() []Check {
|
|
return d.checks
|
|
}
|
|
|
|
// Run executes all registered checks and returns a report.
|
|
func (d *Doctor) Run(ctx *CheckContext) *Report {
|
|
report := NewReport()
|
|
|
|
for _, check := range d.checks {
|
|
result := check.Run(ctx)
|
|
// Ensure check name is populated
|
|
if result.Name == "" {
|
|
result.Name = check.Name()
|
|
}
|
|
report.Add(result)
|
|
}
|
|
|
|
return report
|
|
}
|
|
|
|
// Fix runs all checks with auto-fix enabled where possible.
|
|
// It first runs the check, then if it fails and can be fixed, attempts the fix.
|
|
func (d *Doctor) Fix(ctx *CheckContext) *Report {
|
|
report := NewReport()
|
|
|
|
for _, check := range d.checks {
|
|
result := check.Run(ctx)
|
|
if result.Name == "" {
|
|
result.Name = check.Name()
|
|
}
|
|
|
|
// Attempt fix if check failed and is fixable
|
|
if result.Status != StatusOK && check.CanFix() {
|
|
err := check.Fix(ctx)
|
|
if err == nil {
|
|
// Re-run check to verify fix worked
|
|
result = check.Run(ctx)
|
|
if result.Name == "" {
|
|
result.Name = check.Name()
|
|
}
|
|
// Update message to indicate fix was applied
|
|
if result.Status == StatusOK {
|
|
result.Message = result.Message + " (fixed)"
|
|
}
|
|
} else {
|
|
// Fix failed, add error to details
|
|
result.Details = append(result.Details, "Fix failed: "+err.Error())
|
|
}
|
|
}
|
|
|
|
report.Add(result)
|
|
}
|
|
|
|
return report
|
|
}
|
|
|
|
// BaseCheck provides a base implementation for checks that don't support auto-fix.
|
|
// Embed this in custom checks to get default CanFix() and Fix() implementations.
|
|
type BaseCheck struct {
|
|
CheckName string
|
|
CheckDescription string
|
|
}
|
|
|
|
// Name returns the check name.
|
|
func (b *BaseCheck) Name() string {
|
|
return b.CheckName
|
|
}
|
|
|
|
// Description returns the check description.
|
|
func (b *BaseCheck) Description() string {
|
|
return b.CheckDescription
|
|
}
|
|
|
|
// CanFix returns false by default.
|
|
func (b *BaseCheck) CanFix() bool {
|
|
return false
|
|
}
|
|
|
|
// Fix returns an error indicating this check cannot be auto-fixed.
|
|
func (b *BaseCheck) Fix(ctx *CheckContext) error {
|
|
return ErrCannotFix
|
|
}
|
|
|
|
// FixableCheck provides a base implementation for checks that support auto-fix.
|
|
// Embed this and override CanFix() to return true, and implement Fix().
|
|
type FixableCheck struct {
|
|
BaseCheck
|
|
}
|
|
|
|
// CanFix returns true for fixable checks.
|
|
func (f *FixableCheck) CanFix() bool {
|
|
return true
|
|
}
|