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:
@@ -166,12 +166,26 @@ var createCmd = &cobra.Command{
|
||||
estimatedMinutes = &est
|
||||
}
|
||||
|
||||
// Validate template if --validate flag is set
|
||||
// Validate template based on --validate flag or config
|
||||
validateTemplate, _ := cmd.Flags().GetBool("validate")
|
||||
if validateTemplate {
|
||||
// Explicit --validate flag: fail on error
|
||||
if err := validation.ValidateTemplate(types.IssueType(issueType), description); err != nil {
|
||||
FatalError("%v", err)
|
||||
}
|
||||
} else {
|
||||
// Check validation.on-create config (bd-t7jq)
|
||||
validationMode := config.GetString("validation.on-create")
|
||||
if validationMode == "error" || validationMode == "warn" {
|
||||
if err := validation.ValidateTemplate(types.IssueType(issueType), description); err != nil {
|
||||
if validationMode == "error" {
|
||||
FatalError("%v", err)
|
||||
} else {
|
||||
// warn mode: print warning but proceed
|
||||
fmt.Fprintf(os.Stderr, "%s %v\n", ui.RenderWarn("⚠"), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Use global jsonOutput set by PersistentPreRun
|
||||
|
||||
@@ -287,6 +287,11 @@ Use --merge to merge the sync branch back to main branch.`,
|
||||
}
|
||||
}
|
||||
|
||||
// Template validation before export (bd-t7jq)
|
||||
if err := validateOpenIssuesForSync(ctx); err != nil {
|
||||
FatalError("%v", err)
|
||||
}
|
||||
|
||||
fmt.Println("→ Exporting pending changes to JSONL...")
|
||||
if err := exportToJSONL(ctx, jsonlPath); err != nil {
|
||||
FatalError("exporting: %v", err)
|
||||
|
||||
@@ -10,8 +10,11 @@ import (
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/steveyegge/beads/internal/config"
|
||||
"github.com/steveyegge/beads/internal/rpc"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
"github.com/steveyegge/beads/internal/ui"
|
||||
"github.com/steveyegge/beads/internal/validation"
|
||||
)
|
||||
|
||||
// exportToJSONL exports the database to JSONL format
|
||||
@@ -179,3 +182,63 @@ func exportToJSONL(ctx context.Context, jsonlPath string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateOpenIssuesForSync validates all open issues against their templates
|
||||
// before export, based on the validation.on-sync config setting.
|
||||
// Returns an error if validation.on-sync is "error" and issues fail validation.
|
||||
// Prints warnings if validation.on-sync is "warn".
|
||||
// Does nothing if validation.on-sync is "none" (default).
|
||||
func validateOpenIssuesForSync(ctx context.Context) error {
|
||||
validationMode := config.GetString("validation.on-sync")
|
||||
if validationMode == "none" || validationMode == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure store is active
|
||||
if err := ensureStoreActive(); err != nil {
|
||||
return fmt.Errorf("failed to initialize store for validation: %w", err)
|
||||
}
|
||||
|
||||
// Get all issues (excluding tombstones) and filter to open ones
|
||||
allIssues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get issues for validation: %w", err)
|
||||
}
|
||||
|
||||
// Filter to only open issues (not closed, not tombstones)
|
||||
var issues []*types.Issue
|
||||
for _, issue := range allIssues {
|
||||
if issue.Status != types.StatusClosed && issue.Status != types.StatusTombstone {
|
||||
issues = append(issues, issue)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate each issue
|
||||
var warnings []string
|
||||
for _, issue := range issues {
|
||||
if err := validation.LintIssue(issue); err != nil {
|
||||
warnings = append(warnings, fmt.Sprintf("%s: %v", issue.ID, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(warnings) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Report based on mode
|
||||
if validationMode == "error" {
|
||||
fmt.Fprintf(os.Stderr, "%s Validation failed for %d issue(s):\n", ui.RenderFail("✗"), len(warnings))
|
||||
for _, w := range warnings {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", w)
|
||||
}
|
||||
return fmt.Errorf("template validation failed: %d issues missing required sections (set validation.on-sync: none or warn to proceed)", len(warnings))
|
||||
}
|
||||
|
||||
// warn mode: print warnings but proceed
|
||||
fmt.Fprintf(os.Stderr, "%s Validation warnings for %d issue(s):\n", ui.RenderWarn("⚠"), len(warnings))
|
||||
for _, w := range warnings {
|
||||
fmt.Fprintf(os.Stderr, " - %s\n", w)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user