feat(config): add validation.on-create and validation.on-sync config options (bd-t7jq)

Add .beads/config.yaml support for template validation settings:
- validation.on-create: warn|error|none (default: none)
- validation.on-sync: warn|error|none (default: none)

When set to "warn", issues missing required sections (based on type) show
warnings but operations proceed. When set to "error", operations fail.

Implementation:
- Add validation keys to YamlOnlyKeys in yaml_config.go
- Add defaults in config.go
- Wire up bd create to check validation.on-create config
- Wire up bd sync to run validation before export
- Add tests for config loading
- Update CONFIG.md documentation

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
topaz
2026-01-01 19:31:12 -08:00
committed by Steve Yegge
parent b73085962c
commit 65fb0c6d77
7 changed files with 164 additions and 2 deletions

View File

@@ -114,6 +114,14 @@ func Initialize() error {
// Create command defaults
v.SetDefault("create.require-description", false)
// Validation configuration defaults (bd-t7jq)
// Values: "warn" | "error" | "none"
// - "none": no validation (default, backwards compatible)
// - "warn": validate and print warnings but proceed
// - "error": validate and fail on missing sections
v.SetDefault("validation.on-create", "none")
v.SetDefault("validation.on-sync", "none")
// Git configuration defaults (GH#600)
v.SetDefault("git.author", "") // Override commit author (e.g., "beads-bot <beads@example.com>")
v.SetDefault("git.no-gpg-sign", false) // Disable GPG signing for beads commits

View File

@@ -786,3 +786,61 @@ func TestConfigSourceConstants(t *testing.T) {
t.Errorf("SourceFlag = %q, want \"flag\"", SourceFlag)
}
}
func TestValidationConfigDefaults(t *testing.T) {
// Isolate from environment variables
restore := envSnapshot(t)
defer restore()
// Initialize config
if err := Initialize(); err != nil {
t.Fatalf("Initialize() returned error: %v", err)
}
// Test validation.on-create default is "none"
if got := GetString("validation.on-create"); got != "none" {
t.Errorf("GetString(validation.on-create) = %q, want \"none\"", got)
}
// Test validation.on-sync default is "none"
if got := GetString("validation.on-sync"); got != "none" {
t.Errorf("GetString(validation.on-sync) = %q, want \"none\"", got)
}
}
func TestValidationConfigFromFile(t *testing.T) {
// Create a temporary directory for config file
tmpDir := t.TempDir()
// Create a config file with validation settings
configContent := `
validation:
on-create: error
on-sync: warn
`
beadsDir := filepath.Join(tmpDir, ".beads")
if err := os.MkdirAll(beadsDir, 0750); err != nil {
t.Fatalf("failed to create .beads directory: %v", err)
}
configPath := filepath.Join(beadsDir, "config.yaml")
if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
t.Fatalf("failed to write config file: %v", err)
}
// Change to tmp directory
t.Chdir(tmpDir)
// Initialize viper
if err := Initialize(); err != nil {
t.Fatalf("Initialize() returned error: %v", err)
}
// Test that validation settings are loaded correctly
if got := GetString("validation.on-create"); got != "error" {
t.Errorf("GetString(validation.on-create) = %q, want \"error\"", got)
}
if got := GetString("validation.on-sync"); got != "warn" {
t.Errorf("GetString(validation.on-sync) = %q, want \"warn\"", got)
}
}

View File

@@ -54,6 +54,11 @@ var YamlOnlyKeys = map[string]bool{
// Create command settings
"create.require-description": true,
// Validation settings (bd-t7jq)
// Values: "warn" | "error" | "none"
"validation.on-create": true,
"validation.on-sync": true,
}
// IsYamlOnlyKey returns true if the given key should be stored in config.yaml
@@ -65,7 +70,7 @@ func IsYamlOnlyKey(key string) bool {
}
// Check prefix matches for nested keys
prefixes := []string{"routing.", "sync.", "git.", "directory.", "repos.", "external_projects."}
prefixes := []string{"routing.", "sync.", "git.", "directory.", "repos.", "external_projects.", "validation."}
for _, prefix := range prefixes {
if strings.HasPrefix(key, prefix) {
return true