diff --git a/cmd/bd/autoimport.go b/cmd/bd/autoimport.go index 31c93fd9..8f2c1219 100644 --- a/cmd/bd/autoimport.go +++ b/cmd/bd/autoimport.go @@ -143,10 +143,28 @@ func checkGitForIssues() (int, string, string) { return 0, "", "" } -// localConfig represents the subset of config.yaml we need for auto-import. +// localConfig represents the subset of config.yaml we need for auto-import and no-db detection. // Using proper YAML parsing handles edge cases like comments, indentation, and special characters. type localConfig struct { SyncBranch string `yaml:"sync-branch"` + NoDb bool `yaml:"no-db"` +} + +// isNoDbModeConfigured checks if no-db: true is set in config.yaml. +// Uses proper YAML parsing to avoid false matches in comments or nested keys. +func isNoDbModeConfigured(beadsDir string) bool { + configPath := filepath.Join(beadsDir, "config.yaml") + data, err := os.ReadFile(configPath) // #nosec G304 - config file path from beadsDir + if err != nil { + return false + } + + var cfg localConfig + if err := yaml.Unmarshal(data, &cfg); err != nil { + return false + } + + return cfg.NoDb } // getLocalSyncBranch reads sync-branch from the local config.yaml file. diff --git a/cmd/bd/autoimport_test.go b/cmd/bd/autoimport_test.go index 31c1ec63..c20df43a 100644 --- a/cmd/bd/autoimport_test.go +++ b/cmd/bd/autoimport_test.go @@ -299,6 +299,85 @@ func TestBoolToFlag(t *testing.T) { } } +func TestIsNoDbModeConfigured(t *testing.T) { + tests := []struct { + name string + configYAML string + createFile bool + want bool + }{ + { + name: "no config.yaml exists", + createFile: false, + want: false, + }, + { + name: "config.yaml without no-db key", + configYAML: "issue-prefix: test\nauthor: testuser\n", + createFile: true, + want: false, + }, + { + name: "no-db: true", + configYAML: "no-db: true\n", + createFile: true, + want: true, + }, + { + name: "no-db: false", + configYAML: "no-db: false\n", + createFile: true, + want: false, + }, + { + name: "no-db in comment should not match", + configYAML: "# no-db: true\nissue-prefix: test\n", + createFile: true, + want: false, + }, + { + name: "no-db nested under section should not match", + configYAML: "settings:\n no-db: true\n", + createFile: true, + want: false, + }, + { + name: "no-db with other config", + configYAML: "issue-prefix: bd\nno-db: true\nauthor: steve\n", + createFile: true, + want: true, + }, + { + name: "empty file", + configYAML: "", + createFile: true, + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + beadsDir := filepath.Join(tmpDir, ".beads") + if err := os.MkdirAll(beadsDir, 0755); err != nil { + t.Fatalf("Failed to create beads dir: %v", err) + } + + if tt.createFile { + configPath := filepath.Join(beadsDir, "config.yaml") + if err := os.WriteFile(configPath, []byte(tt.configYAML), 0644); err != nil { + t.Fatalf("Failed to write config.yaml: %v", err) + } + } + + got := isNoDbModeConfigured(beadsDir) + if got != tt.want { + t.Errorf("isNoDbModeConfigured() = %v, want %v", got, tt.want) + } + }) + } +} + func TestGetLocalSyncBranch(t *testing.T) { tests := []struct { name string diff --git a/cmd/bd/doctor.go b/cmd/bd/doctor.go index 377d8054..045077e1 100644 --- a/cmd/bd/doctor.go +++ b/cmd/bd/doctor.go @@ -854,16 +854,8 @@ func checkDatabaseVersion(path string) doctorCheck { if jsonlPath != "" { // JSONL exists but no database - check if this is no-db mode or fresh clone - // Check config.yaml for no-db: true - configPath := filepath.Join(beadsDir, "config.yaml") - isNoDbMode := false - // #nosec G304 -- configPath is constructed from beadsDir which is in .beads/ - if configData, err := os.ReadFile(configPath); err == nil { - // Simple check for no-db: true in config.yaml - isNoDbMode = strings.Contains(string(configData), "no-db: true") - } - - if isNoDbMode { + // Use proper YAML parsing to detect no-db mode (bd-r6k2) + if isNoDbModeConfigured(beadsDir) { return doctorCheck{ Name: "Database", Status: statusOK, diff --git a/cmd/bd/main.go b/cmd/bd/main.go index 1ca17fc2..04a48226 100644 --- a/cmd/bd/main.go +++ b/cmd/bd/main.go @@ -296,7 +296,6 @@ var rootCmd = &cobra.Command{ beadsDir := beads.FindBeadsDir() if beadsDir != "" { jsonlPath := filepath.Join(beadsDir, "issues.jsonl") - configPath := filepath.Join(beadsDir, "config.yaml") // Check if JSONL exists and config.yaml has no-db: true jsonlExists := false @@ -304,11 +303,8 @@ var rootCmd = &cobra.Command{ jsonlExists = true } - isNoDbMode := false - // configPath is safe: constructed from filepath.Join(beadsDir, hardcoded name) - if configData, err := os.ReadFile(configPath); err == nil { //nolint:gosec - isNoDbMode = strings.Contains(string(configData), "no-db: true") - } + // Use proper YAML parsing to detect no-db mode (bd-r6k2) + isNoDbMode := isNoDbModeConfigured(beadsDir) // If JSONL-only mode is configured, auto-enable it if jsonlExists && isNoDbMode {