feat(doctor): add prefix mismatch detection check (gt-17wdl)
Add a new 'prefix-mismatch' check to gt doctor that detects when the prefix configured in rigs.json differs from what routes.jsonl actually uses for a rig's beads. This can happen when: - deriveBeadsPrefix() generates a different prefix than what's in the DB - Someone manually edited rigs.json with the wrong prefix - Beads were initialized before auto-derive existed with a different prefix The check is fixable: running 'gt doctor --fix' will update rigs.json to match the actual prefixes from routes.jsonl. Includes comprehensive tests for: - No routes (nothing to check) - No rigs.json (nothing to check) - Matching prefixes (OK) - Mismatched prefixes (Warning) - Fix functionality 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
cf1eac8521
commit
637df1d289
@@ -99,3 +99,219 @@ func TestBeadsDatabaseCheck_PopulatedDatabase(t *testing.T) {
|
||||
t.Errorf("expected StatusOK for populated db, got %v", result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPrefixMismatchCheck(t *testing.T) {
|
||||
check := NewPrefixMismatchCheck()
|
||||
|
||||
if check.Name() != "prefix-mismatch" {
|
||||
t.Errorf("expected name 'prefix-mismatch', got %q", check.Name())
|
||||
}
|
||||
|
||||
if !check.CanFix() {
|
||||
t.Error("expected CanFix to return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMismatchCheck_NoRoutes(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := NewPrefixMismatchCheck()
|
||||
ctx := &CheckContext{TownRoot: tmpDir}
|
||||
|
||||
result := check.Run(ctx)
|
||||
|
||||
if result.Status != StatusOK {
|
||||
t.Errorf("expected StatusOK for no routes, got %v", result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMismatchCheck_NoRigsJson(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix":"gt-","path":"gastown/mayor/rig"}`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := NewPrefixMismatchCheck()
|
||||
ctx := &CheckContext{TownRoot: tmpDir}
|
||||
|
||||
result := check.Run(ctx)
|
||||
|
||||
if result.Status != StatusOK {
|
||||
t.Errorf("expected StatusOK when no rigs.json, got %v", result.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMismatchCheck_Matching(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
mayorDir := filepath.Join(tmpDir, "mayor")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with gt- prefix
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix":"gt-","path":"gastown/mayor/rig"}`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create rigs.json with matching gt prefix
|
||||
rigsPath := filepath.Join(mayorDir, "rigs.json")
|
||||
rigsContent := `{
|
||||
"version": 1,
|
||||
"rigs": {
|
||||
"gastown": {
|
||||
"git_url": "https://github.com/example/gastown",
|
||||
"beads": {
|
||||
"prefix": "gt"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
if err := os.WriteFile(rigsPath, []byte(rigsContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := NewPrefixMismatchCheck()
|
||||
ctx := &CheckContext{TownRoot: tmpDir}
|
||||
|
||||
result := check.Run(ctx)
|
||||
|
||||
if result.Status != StatusOK {
|
||||
t.Errorf("expected StatusOK for matching prefixes, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMismatchCheck_Mismatch(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
mayorDir := filepath.Join(tmpDir, "mayor")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with gt- prefix
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix":"gt-","path":"gastown/mayor/rig"}`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create rigs.json with WRONG prefix (ga instead of gt)
|
||||
rigsPath := filepath.Join(mayorDir, "rigs.json")
|
||||
rigsContent := `{
|
||||
"version": 1,
|
||||
"rigs": {
|
||||
"gastown": {
|
||||
"git_url": "https://github.com/example/gastown",
|
||||
"beads": {
|
||||
"prefix": "ga"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
if err := os.WriteFile(rigsPath, []byte(rigsContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := NewPrefixMismatchCheck()
|
||||
ctx := &CheckContext{TownRoot: tmpDir}
|
||||
|
||||
result := check.Run(ctx)
|
||||
|
||||
if result.Status != StatusWarning {
|
||||
t.Errorf("expected StatusWarning for prefix mismatch, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
|
||||
if len(result.Details) != 1 {
|
||||
t.Errorf("expected 1 detail, got %d", len(result.Details))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrefixMismatchCheck_Fix(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
mayorDir := filepath.Join(tmpDir, "mayor")
|
||||
if err := os.MkdirAll(beadsDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.MkdirAll(mayorDir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create routes.jsonl with gt- prefix
|
||||
routesPath := filepath.Join(beadsDir, "routes.jsonl")
|
||||
routesContent := `{"prefix":"gt-","path":"gastown/mayor/rig"}`
|
||||
if err := os.WriteFile(routesPath, []byte(routesContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create rigs.json with WRONG prefix (ga instead of gt)
|
||||
rigsPath := filepath.Join(mayorDir, "rigs.json")
|
||||
rigsContent := `{
|
||||
"version": 1,
|
||||
"rigs": {
|
||||
"gastown": {
|
||||
"git_url": "https://github.com/example/gastown",
|
||||
"beads": {
|
||||
"prefix": "ga"
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
if err := os.WriteFile(rigsPath, []byte(rigsContent), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
check := NewPrefixMismatchCheck()
|
||||
ctx := &CheckContext{TownRoot: tmpDir}
|
||||
|
||||
// First verify there's a mismatch
|
||||
result := check.Run(ctx)
|
||||
if result.Status != StatusWarning {
|
||||
t.Fatalf("expected mismatch before fix, got %v", result.Status)
|
||||
}
|
||||
|
||||
// Fix it
|
||||
if err := check.Fix(ctx); err != nil {
|
||||
t.Fatalf("Fix() failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify it's now fixed
|
||||
result = check.Run(ctx)
|
||||
if result.Status != StatusOK {
|
||||
t.Errorf("expected StatusOK after fix, got %v: %s", result.Status, result.Message)
|
||||
}
|
||||
|
||||
// Verify rigs.json was updated
|
||||
data, err := os.ReadFile(rigsPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cfg, err := loadRigsConfig(rigsPath)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to load fixed rigs.json: %v (content: %s)", err, data)
|
||||
}
|
||||
if cfg.Rigs["gastown"].BeadsConfig.Prefix != "gt" {
|
||||
t.Errorf("expected prefix 'gt' after fix, got %q", cfg.Rigs["gastown"].BeadsConfig.Prefix)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user