fix(doctor): UX improvements for diagnostics and daemon (#687)
* fix(doctor): UX improvements for diagnostics and daemon - Add Repo Fingerprint check to detect when database belongs to a different repository (copied .beads dir or git remote URL change) - Add interactive fix for repo fingerprint with options: update repo ID, reinitialize database, or skip - Add visible warning when daemon takes >5s to start, recommending 'bd doctor' for diagnosis - Detect install method (Homebrew vs script) and show only relevant upgrade command - Improve WARNINGS section: - Add icons (⚠ or ✖) next to each item - Color numbers by severity (yellow for warnings, red for errors) - Render entire error lines in red - Sort by severity (errors first) - Fix alignment with checkmarks above - Use heavier fail icon (✖) for better visibility - Add integration and validation tests for doctor fixes * fix(lint): address errcheck and gosec warnings - mol_bond.go: explicitly ignore ephStore.Close() error - beads.go: add nosec for .gitignore file permissions (0644 is standard)
This commit is contained in:
@@ -396,6 +396,103 @@ func CheckDeletionsManifest(path string) DoctorCheck {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckRepoFingerprint validates that the database belongs to this repository.
|
||||
// This detects when a .beads directory was copied from another repo or when
|
||||
// the git remote URL changed. A mismatch can cause data loss during sync.
|
||||
func CheckRepoFingerprint(path string) DoctorCheck {
|
||||
beadsDir := filepath.Join(path, ".beads")
|
||||
|
||||
// Get database path
|
||||
var dbPath string
|
||||
if cfg, err := configfile.Load(beadsDir); err == nil && cfg != nil && cfg.Database != "" {
|
||||
dbPath = cfg.DatabasePath(beadsDir)
|
||||
} else {
|
||||
dbPath = filepath.Join(beadsDir, beads.CanonicalDatabaseName)
|
||||
}
|
||||
|
||||
// Skip if database doesn't exist
|
||||
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusOK,
|
||||
Message: "N/A (no database)",
|
||||
}
|
||||
}
|
||||
|
||||
// Open database
|
||||
db, err := sql.Open("sqlite3", "file:"+dbPath+"?mode=ro")
|
||||
if err != nil {
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusWarning,
|
||||
Message: "Unable to open database",
|
||||
Detail: err.Error(),
|
||||
}
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Get stored repo ID
|
||||
var storedRepoID string
|
||||
err = db.QueryRow("SELECT value FROM metadata WHERE key = 'repo_id'").Scan(&storedRepoID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows || strings.Contains(err.Error(), "no such table") {
|
||||
// Legacy database without repo_id
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusWarning,
|
||||
Message: "Legacy database (no fingerprint)",
|
||||
Detail: "Database was created before version 0.17.5",
|
||||
Fix: "Run 'bd migrate --update-repo-id' to add fingerprint",
|
||||
}
|
||||
}
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusWarning,
|
||||
Message: "Unable to read repo fingerprint",
|
||||
Detail: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// If repo_id is empty, treat as legacy
|
||||
if storedRepoID == "" {
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusWarning,
|
||||
Message: "Legacy database (empty fingerprint)",
|
||||
Detail: "Database was created before version 0.17.5",
|
||||
Fix: "Run 'bd migrate --update-repo-id' to add fingerprint",
|
||||
}
|
||||
}
|
||||
|
||||
// Compute current repo ID
|
||||
currentRepoID, err := beads.ComputeRepoID()
|
||||
if err != nil {
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusWarning,
|
||||
Message: "Unable to compute current repo ID",
|
||||
Detail: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// Compare
|
||||
if storedRepoID != currentRepoID {
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusError,
|
||||
Message: "Database belongs to different repository",
|
||||
Detail: fmt.Sprintf("stored: %s, current: %s", storedRepoID[:8], currentRepoID[:8]),
|
||||
Fix: "Run 'bd migrate --update-repo-id' if URL changed, or 'rm -rf .beads && bd init' if wrong database",
|
||||
}
|
||||
}
|
||||
|
||||
return DoctorCheck{
|
||||
Name: "Repo Fingerprint",
|
||||
Status: StatusOK,
|
||||
Message: fmt.Sprintf("Verified (%s)", currentRepoID[:8]),
|
||||
}
|
||||
}
|
||||
|
||||
// Fix functions
|
||||
|
||||
// FixMigrateTombstones converts legacy deletions.jsonl entries to inline tombstones
|
||||
|
||||
Reference in New Issue
Block a user