package cmd import ( "flag" "fmt" "sort" "strings" "git.noodles.cam/claude-skills/marketplace/cli/internal/issue" ) func init() { register(&Command{ Name: "check", Group: GroupIssue, Args: "[…]", Short: "validate issues against the canonical format", Long: `The same check the sync layer runs before it pushes anything, available on its own so a local-only issue can be held to the format without a tracker being involved. Errors mean malformed; warnings mean it deviates from its type's template or its graph looks suspect. An unticked checkbox is neither: work not done yet is the normal state of a perfectly well-formed issue. Exit status is 1 when anything has errors, which is what makes this usable in a hook or a CI step.`, Examples: []Example{ {"kettle check", "every issue in the store"}, {"kettle check wire-sqlc-appclick", "one issue"}, {"kettle check --quiet", "exit status only"}, {"kettle check --strict", "treat warnings as errors"}, }, Setup: func(fs *flag.FlagSet) func([]string) error { quiet := fs.Bool("quiet", false, "exit status only, print nothing") strict := fs.Bool("strict", false, "treat warnings as errors") out := storeFlag(fs) return func(args []string) error { root, err := storeRoot(*out) if err != nil { return err } if err := issue.StoreError(root); err != nil { return err } issues, err := issue.LoadAll(root) if err != nil { return err } ids := args if len(ids) == 0 { for id := range issues { ids = append(ids, id) } sort.Strings(ids) } known := map[string]bool{} for id := range issues { known[id] = true } for _, id := range ids { if !known[id] { return Fail("no issue %q in %s", id, root) } } bad := 0 for _, id := range ids { errs, warns := issue.Validate(issues[id], known) if *strict { errs, warns = append(errs, warns...), nil } if len(errs) > 0 { bad++ } if *quiet { continue } if len(errs) == 0 && len(warns) == 0 { fmt.Printf("ok %s\n", id) continue } for _, e := range errs { fmt.Printf("ERROR %s: %s\n", id, e) } for _, w := range warns { fmt.Printf("warn %s: %s\n", id, w) } } for _, c := range issue.FindCycles(issue.Graph(issues)) { bad++ if !*quiet { fmt.Printf("ERROR cycle: %s\n", strings.Join(c, " -> ")) } } if !*quiet { fmt.Printf("%d issue(s) checked, %d with errors\n", len(ids), bad) } if bad > 0 { return SilentError{Code: 1} } return nil } }, }) }