fix(doctor): detect status mismatches between DB and JSONL (GH#885)

When bd sync fails mid-operation, the local JSONL can become stale while
the SQLite database has the correct state. Previously, bd doctor only
checked count and timestamp differences, missing cases where counts match
but issue statuses differ.

This adds content-level comparison to CheckDatabaseJSONLSync that:
- Compares issue statuses between DB and JSONL
- Samples up to 500 issues for performance on large databases
- Reports detailed mismatches (shows up to 3 examples)
- Suggests 'bd export' to fix the stale JSONL

Example detection:
  Status mismatch: 1 issue(s) have different status in DB vs JSONL
  Status mismatches detected:
    test-1: DB=closed, JSONL=open

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
obsidian
2026-01-04 11:30:26 -08:00
committed by Steve Yegge
parent 5229b3e90d
commit 1a0ad09e23
2 changed files with 180 additions and 1 deletions

View File

@@ -161,6 +161,30 @@ func TestCheckDatabaseJSONLSync(t *testing.T) {
},
expectedStatus: "warning",
},
{
// GH#885: Status mismatch detection
name: "status mismatch - same count different status",
setup: func(t *testing.T, dir string) {
// Create database with issue status "closed"
dbPath := setupTestDatabase(t, dir)
db, _ := sql.Open("sqlite3", dbPath)
defer db.Close()
// Add config table for prefix check (required by CheckDatabaseJSONLSync)
_, _ = db.Exec(`CREATE TABLE IF NOT EXISTS config (key TEXT PRIMARY KEY, value TEXT)`)
_, _ = db.Exec(`INSERT INTO config (key, value) VALUES ('issue_prefix', 'test')`)
_, _ = db.Exec(`INSERT INTO issues (id, title, status) VALUES ('test-1', 'Test Issue', 'closed')`)
// Create JSONL with same issue but status "open" (stale JSONL)
jsonlPath := filepath.Join(dir, ".beads", "issues.jsonl")
content := `{"id":"test-1","title":"Test Issue","status":"open"}
`
if err := os.WriteFile(jsonlPath, []byte(content), 0600); err != nil {
t.Fatalf("failed to create JSONL: %v", err)
}
},
expectedStatus: "warning",
expectMessage: "Status mismatch: 1 issue(s) have different status in DB vs JSONL",
},
}
for _, tt := range tests {