edit in main.go at line 4
edit in main.go at line 41
+ if errors.Is(err, cmd.ErrHelpRequested) {
+ os.Exit(0)
+ }
replacement in cmd/pattern.go at line 79
[4.1056087]→[4.1056087:1056106](∅→∅),
[4.1056106]→[3.4159:4171](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/pattern.go at line 144
[4.1058172]→[4.1058172:1058191](∅→∅),
[4.1058191]→[3.4511:4523](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/location.go at line 86
[4.1063821]→[4.1063821:1063840](∅→∅),
[4.1063840]→[3.5182:5194](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/location.go at line 132
[4.20028]→[4.20028:20047](∅→∅),
[4.20047]→[3.5546:5558](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/import.go at line 22
[4.1070277]→[4.1070277:1070303](∅→∅) − runImportBulk(args[1:])
+ return runImportBulk(args[1:])
replacement in cmd/import.go at line 24
[4.1070317]→[4.1070317:1070343](∅→∅) − runImportFile(args[1:])
+ return runImportFile(args[1:])
replacement in cmd/import.go at line 26
[4.1070359]→[4.1070359:1070387](∅→∅) − runImportFolder(args[1:])
+ return runImportFolder(args[1:])
replacement in cmd/import.go at line 28
[4.1070405]→[4.1070405:1070435](∅→∅) − runImportSegments(args[1:])
+ return runImportSegments(args[1:])
replacement in cmd/import.go at line 30
[4.1070457]→[4.1070457:1070491](∅→∅) − runImportUnstructured(args[1:])
+ return runImportUnstructured(args[1:])
edit in cmd/import.go at line 35
[4.1070609]→[3.6462:6474](∅→∅) replacement in cmd/import.go at line 68
[4.1072745]→[4.1072745:1072781](∅→∅) − func runImportBulk(args []string) {
+ func runImportBulk(args []string) error {
replacement in cmd/import.go at line 85
[4.1073596]→[4.1073596:1073609](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/import.go at line 89
[4.1073641]→[4.3374:3472](∅→∅) − requireFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--csv", *csvPath, "--log", *logPath)
+ if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--csv", *csvPath, "--log", *logPath); err != nil {
+ return err
+ }
edit in cmd/import.go at line 114
[4.1074667]→[4.1074667:1074712](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
replacement in cmd/import.go at line 116
[4.1074818]→[4.1074818:1074839](∅→∅) replacement in cmd/import.go at line 118
[4.1074843]→[4.1074843:1074856](∅→∅) + return fmt.Errorf("bulk import: %w", err)
replacement in cmd/import.go at line 121
[4.1074860]→[4.1074860:1074879](∅→∅) + return printJSON(output)
replacement in cmd/import.go at line 140
[4.1075604]→[4.1075604:1075640](∅→∅) − func runImportFile(args []string) {
+ func runImportFile(args []string) error {
replacement in cmd/import.go at line 158
[4.1076492]→[4.1076492:1076505](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/import.go at line 162
[4.1076537]→[4.3473:3606](∅→∅) − requireFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--file", *filePath)
+ if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--file", *filePath); err != nil {
+ return err
+ }
replacement in cmd/import.go at line 182
[4.1077352]→[4.1077352:1077410](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("import file: %w", err)
replacement in cmd/import.go at line 185
[4.1077414]→[4.1077414:1077433](∅→∅) + return printJSON(output)
replacement in cmd/import.go at line 207
[4.1078230]→[4.1078230:1078268](∅→∅) − func runImportFolder(args []string) {
+ func runImportFolder(args []string) error {
replacement in cmd/import.go at line 226
[4.1079247]→[4.1079247:1079260](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/import.go at line 230
[4.1079292]→[4.3607:3744](∅→∅) − requireFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath)
+ if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath); err != nil {
+ return err
+ }
edit in cmd/import.go at line 254
[4.1080233]→[4.1080233:1080278](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
replacement in cmd/import.go at line 256
[4.1080355]→[4.1080355:1080376](∅→∅) replacement in cmd/import.go at line 258
[4.1080380]→[4.1080380:1080393](∅→∅) + return fmt.Errorf("import folder: %w", err)
replacement in cmd/import.go at line 261
[4.1080397]→[4.1080397:1080416](∅→∅) + return printJSON(output)
replacement in cmd/import.go at line 302
[4.1082018]→[4.1082018:1082058](∅→∅) − func runImportSegments(args []string) {
+ func runImportSegments(args []string) error {
replacement in cmd/import.go at line 332
[4.1083881]→[4.1083881:1083894](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/import.go at line 336
[4.1083926]→[4.3745:3909](∅→∅) − requireFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath, "--mapping", *mappingPath)
+ if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--location", *locationID, "--cluster", *clusterID, "--folder", *folderPath, "--mapping", *mappingPath); err != nil {
+ return err
+ }
replacement in cmd/import.go at line 367
[4.1085253]→[4.1085253:1085300](∅→∅) − fmt.Fprintf(os.Stderr, "\nError: %v\n", err)
+ fmt.Fprintf(os.Stderr, "\n")
replacement in cmd/import.go at line 370
[4.1085404]→[4.1085404:1085425](∅→∅) replacement in cmd/import.go at line 372
[4.1085429]→[4.1085429:1085442](∅→∅) + return fmt.Errorf("import segments: %w", err)
replacement in cmd/import.go at line 381
[4.1085840]→[4.1085840:1085859](∅→∅) + return printJSON(output)
replacement in cmd/import.go at line 399
[4.1086489]→[4.1086489:1086533](∅→∅) − func runImportUnstructured(args []string) {
+ func runImportUnstructured(args []string) error {
replacement in cmd/import.go at line 419
[4.1087722]→[4.1087722:1087735](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/import.go at line 423
[4.1087767]→[4.3910:3995](∅→∅) − requireFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--folder", *folderPath)
+ if err := checkFlags(fs, "--db", *dbPath, "--dataset", *datasetID, "--folder", *folderPath); err != nil {
+ return err
+ }
replacement in cmd/import.go at line 446
[4.1088598]→[4.1088598:1088656](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("import unstructured: %w", err)
replacement in cmd/import.go at line 449
[4.1088660]→[4.1088660:1088679](∅→∅) + return printJSON(output)
replacement in cmd/import.go at line 452
[4.1088682]→[4.1088682:1088706](∅→∅) − func printJSON(v any) {
+ func printJSON(v any) error {
replacement in cmd/import.go at line 456
[4.1088805]→[4.1088805:1088879](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/import.go at line 458
replacement in cmd/dataset.go at line 68
[4.1094818]→[4.1094818:1094837](∅→∅),
[4.1094837]→[3.7222:7234](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/dataset.go at line 122
[4.1096702]→[4.1096702:1096721](∅→∅),
[4.1096721]→[3.7465:7477](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
edit in cmd/common.go at line 7
replacement in cmd/common.go at line 62
[3.8467]→[3.8467:8857](∅→∅),
[3.8857]→[4.5094:5098](∅→∅),
[4.5094]→[4.5094:5098](∅→∅) − // requireFlags checks that the given flag values are non-empty strings.
− // If any are empty, prints usage and exits.
− // Each pair is (flagName, flagValue) — e.g. requireFlags(fs, "--db", *dbPath, "--id", *id)
− func requireFlags(fs *flag.FlagSet, pairs ...string) {
− var missing []string
− for i := 0; i < len(pairs); i += 2 {
− if pairs[i+1] == "" {
− missing = append(missing, pairs[i])
− }
+ // mustValue returns the value for a flag that requires an argument, advancing i by 2.
+ // Exits the program if the value is missing (unrecoverable CLI error).
+ func mustValue(args []string, i *int, flag string) string {
+ if *i+1 >= len(args) {
+ fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flag)
+ os.Exit(1)
replacement in cmd/common.go at line 69
[4.5101]→[4.5101:5212](∅→∅) − if len(missing) > 0 {
− fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
− fs.Usage()
+ v := args[*i+1]
+ *i += 2
+ return v
+ }
+
+ // mustIntValue returns the integer value for a flag, validating range [lo,hi].
+ // Exits on missing value, parse error, or out-of-range.
+ func mustIntValue(args []string, i *int, flag string, lo, hi int) int {
+ val := mustValue(args, i, flag)
+ v, err := strconv.Atoi(val)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %s must be an integer\n", flag)
edit in cmd/common.go at line 83
+ if v < lo || v > hi {
+ fmt.Fprintf(os.Stderr, "Error: %s must be between %d and %d\n", flag, lo, hi)
+ os.Exit(1)
+ }
+ return v
replacement in cmd/cluster.go at line 80
[4.1101905]→[4.1101905:1101924](∅→∅),
[4.1101924]→[3.9228:9240](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/cluster.go at line 142
[4.1103923]→[4.1103923:1103942](∅→∅),
[4.1103942]→[3.9525:9537](∅→∅) − printJSON(output)
− return nil
+ return printJSON(output)
replacement in cmd/calls_push_certainty.go at line 43
[4.1106185]→[4.24670:24728](∅→∅) − // parsePushCertaintyArgs parses CLI arguments into flags
+ // parsePushCertaintyArgs parses CLI arguments into flags.
+ // Uses mustValue for flag-value extraction (exits on missing value).
replacement in cmd/calls_push_certainty.go at line 52
[4.1106265]→[4.24819:24861](∅→∅) − f.folder = requireValue(arg, args, &i)
+ f.folder = mustValue(args, &i, "--folder")
replacement in cmd/calls_push_certainty.go at line 54
[4.1106425]→[4.24862:24902](∅→∅) − f.file = requireValue(arg, args, &i)
+ f.file = mustValue(args, &i, "--file")
replacement in cmd/calls_push_certainty.go at line 56
[4.1106583]→[4.24903:24945](∅→∅) − f.filter = requireValue(arg, args, &i)
+ f.filter = mustValue(args, &i, "--filter")
replacement in cmd/calls_push_certainty.go at line 58
[4.1106746]→[4.24946:24989](∅→∅) − f.species = requireValue(arg, args, &i)
+ f.species = mustValue(args, &i, "--species")
replacement in cmd/calls_push_certainty.go at line 70
[2.1180]→[2.1180:1224](∅→∅) − f.location = requireValue(arg, args, &i)
+ f.location = mustValue(args, &i, "--location")
edit in cmd/calls_push_certainty.go at line 75
[4.1107837]→[4.1107837:1107899](∅→∅) − fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n\n", arg)
edit in cmd/calls_push_certainty.go at line 76
+ fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n", arg)
edit in cmd/calls_push_certainty.go at line 81
[4.25199]→[4.25199:25201](∅→∅),
[4.25201]→[4.1107949:1107950](∅→∅),
[4.1107949]→[4.1107949:1107950](∅→∅),
[4.1107950]→[4.25202:25483](∅→∅) − }
−
− // requireValue returns the next argument after a flag, or exits with an error
− func requireValue(flag string, args []string, i *int) string {
− if *i+1 >= len(args) {
− fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flag)
− os.Exit(1)
− }
− v := args[*i+1]
− *i += 2
− return v
replacement in cmd/calls_push_certainty.go at line 83
[4.25808]→[4.25808:25938](∅→∅) − // validatePushCertaintyFlags checks flag combinations and exits on error
− func validatePushCertaintyFlags(f pushCertaintyFlags) {
+ // validatePushCertaintyFlags checks flag combinations
+ func validatePushCertaintyFlags(f pushCertaintyFlags) error {
edit in cmd/calls_push_certainty.go at line 86
[4.25975]→[4.1107983:1108064](∅→∅),
[4.1107983]→[4.1107983:1108064](∅→∅) − fmt.Fprintf(os.Stderr, "Error: missing required flag: --folder or --file\n\n")
replacement in cmd/calls_push_certainty.go at line 87
[4.1108092]→[4.1108092:1108105](∅→∅) + return fmt.Errorf("missing required flag: --folder or --file")
edit in cmd/calls_push_certainty.go at line 90
[4.25999]→[4.1108127:1108207](∅→∅),
[4.1108127]→[4.1108127:1108207](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night and --day are mutually exclusive\n\n")
replacement in cmd/calls_push_certainty.go at line 91
[4.1108235]→[4.1108235:1108248](∅→∅) + return fmt.Errorf("--night and --day are mutually exclusive")
edit in cmd/calls_push_certainty.go at line 94
[2.1270]→[2.1270:1343](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
replacement in cmd/calls_push_certainty.go at line 95
[4.1108407]→[4.1108407:1108420](∅→∅) + return fmt.Errorf("--night/--day requires --location")
edit in cmd/calls_push_certainty.go at line 97
replacement in cmd/calls_push_certainty.go at line 109
[4.26414]→[4.26414:26458](∅→∅) − func runCallsPushCertainty(args []string) {
+ func runCallsPushCertainty(args []string) error {
replacement in cmd/calls_push_certainty.go at line 111
[4.26493]→[4.26493:26524](∅→∅) − validatePushCertaintyFlags(f)
+ if err := validatePushCertaintyFlags(f); err != nil {
+ return err
+ }
edit in cmd/calls_push_certainty.go at line 117
[4.1108482]→[4.1108482:1108527](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
replacement in cmd/calls_push_certainty.go at line 118
[4.1108656]→[4.1108656:1108669](∅→∅) + return fmt.Errorf("loading config: %w", err)
replacement in cmd/calls_push_certainty.go at line 121
[4.1108706]→[4.1108706:1108801](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s is missing \"classify.reviewer\"\n", cfgPath)
− os.Exit(1)
+ return fmt.Errorf("%s is missing \"classify.reviewer\"", cfgPath)
edit in cmd/calls_push_certainty.go at line 129
[2.1410]→[2.1410:1426](∅→∅) replacement in cmd/calls_push_certainty.go at line 131
[2.1504]→[2.1504:1564](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("parsing location: %w", err)
replacement in cmd/calls_push_certainty.go at line 151
[4.1109203]→[4.1109203:1109261](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("push-certainty: %w", err)
replacement in cmd/calls_push_certainty.go at line 167
[4.1109669]→[4.1109669:1109743](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls_push_certainty.go at line 169
replacement in cmd/calls_propagate.go at line 77
[4.1113392]→[4.1113392:1113432](∅→∅) − func runCallsPropagate(args []string) {
+ func runCallsPropagate(args []string) error {
replacement in cmd/calls_propagate.go at line 105
[4.1115300]→[4.1115300:1115313](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
edit in cmd/calls_propagate.go at line 109
[4.1115356]→[4.1115356:1115441](∅→∅) − fmt.Fprintf(os.Stderr, "Error: exactly one of --file or --folder is required\n\n")
replacement in cmd/calls_propagate.go at line 110
[4.1115454]→[4.1115454:1115467](∅→∅) + return fmt.Errorf("exactly one of --file or --folder is required")
replacement in cmd/calls_propagate.go at line 113
[4.1115471]→[4.1115471:1115797](∅→∅) − missing := []string{}
− if *from == "" {
− missing = append(missing, "--from")
− }
− if *to == "" {
− missing = append(missing, "--to")
− }
− if *species == "" {
− missing = append(missing, "--species")
− }
− if len(missing) > 0 {
− fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
− fs.Usage()
− os.Exit(1)
+ if err := checkFlags(fs, "--from", *from, "--to", *to, "--species", *species); err != nil {
+ return err
replacement in cmd/calls_propagate.go at line 128
[4.1116057]→[4.1116057:1116126](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s\n", result.Error)
− os.Exit(1)
+ return fmt.Errorf("propagate: %s", result.Error)
replacement in cmd/calls_propagate.go at line 131
[4.1116175]→[4.1116175:1116251](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
replacement in cmd/calls_propagate.go at line 133
[4.1116255]→[4.1116255:1116264](∅→∅) replacement in cmd/calls_propagate.go at line 143
[4.1116452]→[4.1116452:1116519](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s\n", result.Error)
− os.Exit(1)
+ return fmt.Errorf("propagate: %s", result.Error)
replacement in cmd/calls_propagate.go at line 153
[4.1117001]→[4.1117001:1117075](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls_propagate.go at line 155
edit in cmd/calls_modify.go at line 60
[4.25244]→[4.25244:25246](∅→∅),
[4.25246]→[4.1120957:1120958](∅→∅),
[4.1120957]→[4.1120957:1120958](∅→∅),
[4.1120958]→[4.25247:25545](∅→∅) − }
−
− // requireFlagValue extracts the value for a flag that requires an argument.
− // Exits on missing value.
− func requireFlagValue(args []string, i int, flagName string) string {
− if i+1 >= len(args) {
− fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flagName)
− os.Exit(1)
− }
− return args[i+1]
edit in cmd/calls_modify.go at line 63
+ // Uses mustValue for flag-value extraction (exits on missing value).
replacement in cmd/calls_modify.go at line 71
[4.1121057]→[4.25697:25746](∅→∅),
[4.25746]→[4.1121185:1121195](∅→∅),
[4.1121185]→[4.1121185:1121195](∅→∅) − ma.file = requireFlagValue(args, i, "--file")
− i += 2
+ ma.file = mustValue(args, &i, "--file")
replacement in cmd/calls_modify.go at line 73
[4.1121217]→[4.25747:25804](∅→∅),
[4.25804]→[4.1121353:1121363](∅→∅),
[4.1121353]→[4.1121353:1121363](∅→∅) − ma.reviewer = requireFlagValue(args, i, "--reviewer")
− i += 2
+ ma.reviewer = mustValue(args, &i, "--reviewer")
replacement in cmd/calls_modify.go at line 75
[4.1121383]→[4.25805:25858](∅→∅),
[4.25858]→[4.1121515:1121525](∅→∅),
[4.1121515]→[4.1121515:1121525](∅→∅) − ma.filter = requireFlagValue(args, i, "--filter")
− i += 2
+ ma.filter = mustValue(args, &i, "--filter")
replacement in cmd/calls_modify.go at line 77
[4.1121546]→[4.25859:25914](∅→∅),
[4.25914]→[4.1121680:1121690](∅→∅),
[4.1121680]→[4.1121680:1121690](∅→∅) − ma.segment = requireFlagValue(args, i, "--segment")
− i += 2
+ ma.segment = mustValue(args, &i, "--segment")
replacement in cmd/calls_modify.go at line 79
[4.1121711]→[4.25915:25970](∅→∅),
[4.25970]→[4.1121845:1121855](∅→∅),
[4.1121845]→[4.1121845:1121855](∅→∅) − ma.species = requireFlagValue(args, i, "--species")
− i += 2
+ ma.species = mustValue(args, &i, "--species")
replacement in cmd/calls_modify.go at line 81
[4.1121878]→[4.25971:26022](∅→∅) − val := requireFlagValue(args, i, "--certainty")
+ val := mustValue(args, &i, "--certainty")
edit in cmd/calls_modify.go at line 89
[4.26100]→[4.1122177:1122187](∅→∅),
[4.1122177]→[4.1122177:1122187](∅→∅) replacement in cmd/calls_modify.go at line 93
[4.1122256]→[4.26124:26179](∅→∅),
[4.26179]→[4.1122390:1122400](∅→∅),
[4.1122390]→[4.1122390:1122400](∅→∅) − ma.comment = requireFlagValue(args, i, "--comment")
− i += 2
+ ma.comment = mustValue(args, &i, "--comment")
edit in cmd/calls_modify.go at line 99
[4.1122539]→[4.1122539:1122602](∅→∅) − fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n\n", arg)
edit in cmd/calls_modify.go at line 100
+ fmt.Fprintf(os.Stderr, "Error: unknown flag: %s\n", arg)
replacement in cmd/calls_modify.go at line 110
[4.26256]→[4.26256:26297](∅→∅) − func validateModifyArgs(ma modifyArgs) {
+ func validateModifyArgs(ma modifyArgs) error {
edit in cmd/calls_modify.go at line 128
[4.1123050]→[4.1123050:1123125](∅→∅) − fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
replacement in cmd/calls_modify.go at line 129
[4.1123146]→[4.1123146:1123159](∅→∅) + return fmt.Errorf("missing required flags: %v", missing)
replacement in cmd/calls_modify.go at line 132
[4.26460]→[4.1123231:1123319](∅→∅),
[4.1123231]→[4.1123231:1123319](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --certainty must be between 0 and 100\n")
− os.Exit(1)
+ return fmt.Errorf("--certainty must be between 0 and 100")
edit in cmd/calls_modify.go at line 134
replacement in cmd/calls_modify.go at line 153
[4.27299]→[4.27299:27336](∅→∅) − func RunCallsModify(args []string) {
+ func RunCallsModify(args []string) error {
replacement in cmd/calls_modify.go at line 155
[4.27365]→[4.27365:27389](∅→∅) + if err := validateModifyArgs(ma); err != nil {
+ return err
+ }
replacement in cmd/calls_modify.go at line 174
[4.1123647]→[4.1123647:1123714](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s\n", result.Error)
− os.Exit(1)
+ return fmt.Errorf("modify: %s", result.Error)
edit in cmd/calls_modify.go at line 179
replacement in cmd/calls_detect_anomalies.go at line 60
[4.1126708]→[4.1126708:1126754](∅→∅) − func runCallsDetectAnomalies(args []string) {
+ func runCallsDetectAnomalies(args []string) error {
replacement in cmd/calls_detect_anomalies.go at line 71
[4.1126922]→[4.1126922:1127002](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --folder requires a value\n")
− os.Exit(1)
+ return fmt.Errorf("--folder requires a value")
replacement in cmd/calls_detect_anomalies.go at line 78
[4.1127083]→[4.1127083:1127162](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --model requires a value\n")
− os.Exit(1)
+ return fmt.Errorf("--model requires a value")
replacement in cmd/calls_detect_anomalies.go at line 85
[4.1127261]→[4.1127261:1127342](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --species requires a value\n")
− os.Exit(1)
+ return fmt.Errorf("--species requires a value")
replacement in cmd/calls_detect_anomalies.go at line 92
[4.1127452]→[4.1127452:1127466](∅→∅) replacement in cmd/calls_detect_anomalies.go at line 97
[4.1127571]→[4.1127571:1127585](∅→∅) + return fmt.Errorf("unknown flag: %s", arg)
edit in cmd/calls_detect_anomalies.go at line 102
[4.1127612]→[4.1127612:1127672](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --folder is required\n\n")
replacement in cmd/calls_detect_anomalies.go at line 103
[4.1127702]→[4.1127702:1127715](∅→∅) + return fmt.Errorf("--folder is required")
edit in cmd/calls_detect_anomalies.go at line 106
[4.1127740]→[4.1127740:1127814](∅→∅) − fmt.Fprintf(os.Stderr, "Error: at least 2 --model values required\n\n")
replacement in cmd/calls_detect_anomalies.go at line 107
[4.1127844]→[4.1127844:1127857](∅→∅) + return fmt.Errorf("at least 2 --model values required")
replacement in cmd/calls_detect_anomalies.go at line 116
[4.1128006]→[4.1128006:1128064](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("detect-anomalies: %w", err)
replacement in cmd/calls_detect_anomalies.go at line 127
[4.1128441]→[4.1128441:1128515](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls_detect_anomalies.go at line 129
replacement in cmd/calls_clip_labels.go at line 14
[4.1128727]→[4.1128727:1128768](∅→∅) − func runCallsClipLabels(args []string) {
+ func runCallsClipLabels(args []string) error {
replacement in cmd/calls_clip_labels.go at line 45
[4.1130841]→[4.1130841:1130854](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/calls_clip_labels.go at line 48
[4.1130858]→[4.1130858:1131075](∅→∅) − if *folder == "" {
− fmt.Fprintf(os.Stderr, "Error: --folder is required\n\n")
− fs.Usage()
− os.Exit(1)
− }
− if *mapping == "" {
− fmt.Fprintf(os.Stderr, "Error: --mapping is required\n\n")
− fs.Usage()
− os.Exit(1)
+ if err := checkFlags(fs, "--folder", *folder, "--mapping", *mapping); err != nil {
+ return err
replacement in cmd/calls_clip_labels.go at line 74
[4.1131825]→[4.1131825:1131883](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("clip-labels: %w", err)
replacement in cmd/calls_clip_labels.go at line 100
[4.1132957]→[4.1132957:1133031](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls_clip_labels.go at line 102
replacement in cmd/calls_clip.go at line 82
[4.1460]→[4.1460:1473](∅→∅) + *certainty = -1 // sentinel: will be filtered by tools layer
replacement in cmd/calls_clip.go at line 101
[4.1140316]→[4.28055:28158](∅→∅) − // validateClipFlags checks required flags and flag combinations
− func validateClipFlags(f clipFlags) {
+ // validateClipFlags checks required flags and flag combinations.
+ // Returns an error instead of exiting.
+ func validateClipFlags(f clipFlags) error {
edit in cmd/calls_clip.go at line 115
[4.1140600]→[4.1140600:1140675](∅→∅) − fmt.Fprintf(os.Stderr, "Error: missing required flags: %v\n\n", missing)
replacement in cmd/calls_clip.go at line 116
[4.1140694]→[4.1140694:1140707](∅→∅) + return fmt.Errorf("missing required flags: %v", missing)
edit in cmd/calls_clip.go at line 119
[4.28264]→[4.1140730:1140810](∅→∅),
[4.1140730]→[4.1140730:1140810](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night and --day are mutually exclusive\n\n")
replacement in cmd/calls_clip.go at line 120
[4.1140829]→[4.1140829:1140842](∅→∅) + return fmt.Errorf("--night and --day are mutually exclusive")
edit in cmd/calls_clip.go at line 123
[4.3166]→[4.3166:3239](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
replacement in cmd/calls_clip.go at line 124
[4.1140992]→[4.1140992:1141005](∅→∅) + return fmt.Errorf("--night/--day requires --location")
edit in cmd/calls_clip.go at line 126
replacement in cmd/calls_clip.go at line 141
[4.28849]→[4.28849:28884](∅→∅) − func RunCallsClip(args []string) {
+ func RunCallsClip(args []string) error {
replacement in cmd/calls_clip.go at line 143
[4.28910]→[4.28910:28932](∅→∅) + if err := validateClipFlags(f); err != nil {
+ return err
+ }
replacement in cmd/calls_clip.go at line 169
[4.1141558]→[4.1141558:1141571](∅→∅) + return fmt.Errorf("clip: %w", err)
edit in cmd/calls_clip.go at line 175
edit in cmd/calls_classify.go at line 6
[4.1141729]→[4.1141729:1141740](∅→∅) edit in cmd/calls_classify.go at line 77
+ }
+
+ // mustUniqueValue is like mustValue but exits if the flag was already set.
+ func mustUniqueValue(args []string, i *int, flag, current string) string {
+ if current != "" {
+ fmt.Fprintf(os.Stderr, "Error: %s can only be specified once\n", flag)
+ os.Exit(1)
+ }
+ return mustValue(args, i, flag)
replacement in cmd/calls_classify.go at line 89
[4.8303]→[4.8303:8329](∅→∅) − // Exits on parse errors.
+ // Uses mustValue for flag-value extraction (exits on missing value).
replacement in cmd/calls_classify.go at line 99
[4.1145612]→[4.8429:8479](∅→∅),
[4.8479]→[4.1145744:1145754](∅→∅),
[4.1145744]→[4.1145744:1145754](∅→∅) − a.folder = a.requireValue(args, i, "--folder")
− i += 2
+ a.folder = mustValue(args, &i, "--folder")
replacement in cmd/calls_classify.go at line 101
[4.1145772]→[4.8480:8526](∅→∅),
[4.8526]→[4.1145900:1145910](∅→∅),
[4.1145900]→[4.1145900:1145910](∅→∅) − a.file = a.requireValue(args, i, "--file")
− i += 2
+ a.file = mustValue(args, &i, "--file")
replacement in cmd/calls_classify.go at line 103
[4.1145930]→[4.8527:8593](∅→∅),
[4.8593]→[4.1146178:1146188](∅→∅),
[4.1146178]→[4.1146178:1146188](∅→∅) − a.filter = a.requireUniqueValue(args, i, "--filter", a.filter)
− i += 2
+ a.filter = mustUniqueValue(args, &i, "--filter", a.filter)
replacement in cmd/calls_classify.go at line 105
[4.1146209]→[4.8594:8663](∅→∅),
[4.8663]→[4.1146461:1146471](∅→∅),
[4.1146461]→[4.1146461:1146471](∅→∅) − a.species = a.requireUniqueValue(args, i, "--species", a.species)
− i += 2
+ a.species = mustUniqueValue(args, &i, "--species", a.species)
replacement in cmd/calls_classify.go at line 107
[4.1146494]→[4.8664:8731](∅→∅),
[4.8731]→[4.1146892:1146902](∅→∅),
[4.1146892]→[4.1146892:1146902](∅→∅) − a.certainty = a.requireIntRange(args, i, "--certainty", 0, 100)
− i += 2
+ a.certainty = mustIntValue(args, &i, "--certainty", 0, 100)
replacement in cmd/calls_classify.go at line 109
[4.8751]→[4.8751:8812](∅→∅),
[4.8812]→[4.1147282:1147292](∅→∅),
[4.1147282]→[4.1147282:1147292](∅→∅) − a.sample = a.requireIntRange(args, i, "--sample", 1, 100)
− i += 2
+ a.sample = mustIntValue(args, &i, "--sample", 1, 100)
replacement in cmd/calls_classify.go at line 111
[4.8830]→[4.8830:8880](∅→∅),
[4.9024]→[4.9024:9034](∅→∅) − a.gotoFile = a.requireValue(args, i, "--goto")
− i += 2
+ a.gotoFile = mustValue(args, &i, "--goto")
replacement in cmd/calls_classify.go at line 113
[2.2349]→[2.2349:2421](∅→∅),
[2.2421]→[4.1147760:1147770](∅→∅),
[4.9113]→[4.1147760:1147770](∅→∅),
[4.1147760]→[4.1147760:1147770](∅→∅) − a.location = a.requireUniqueValue(args, i, "--location", a.location)
− i += 2
+ a.location = mustUniqueValue(args, &i, "--location", a.location)
edit in cmd/calls_classify.go at line 133
[4.9210]→[4.9210:9436](∅→∅),
[4.9436]→[4.1148720:1148736](∅→∅),
[4.1148720]→[4.1148720:1148736](∅→∅),
[4.1148736]→[4.9437:9457](∅→∅),
[4.9457]→[4.1148736:1148737](∅→∅),
[4.1148736]→[4.1148736:1148737](∅→∅),
[4.1148737]→[4.9458:9727](∅→∅),
[4.9727]→[4.1148902:1148918](∅→∅),
[4.1148902]→[4.1148902:1148918](∅→∅),
[4.1148918]→[4.9728:9768](∅→∅),
[4.9768]→[4.1148918:1148919](∅→∅),
[4.1148918]→[4.1148918:1148919](∅→∅),
[4.1148919]→[4.9769:10084](∅→∅),
[4.10084]→[4.1149041:1149057](∅→∅),
[4.1149041]→[4.1149041:1149057](∅→∅),
[4.1149057]→[4.10085:10188](∅→∅),
[4.10188]→[4.1149208:1149224](∅→∅),
[4.1149208]→[4.1149208:1149224](∅→∅),
[4.1149224]→[4.10189:10201](∅→∅),
[4.10201]→[4.1149224:1149225](∅→∅),
[4.1149224]→[4.1149224:1149225](∅→∅) − // requireValue returns the next argument or exits if missing.
− func (classifyArgs) requireValue(args []string, i int, flag string) string {
− if i+1 >= len(args) {
− fmt.Fprintf(os.Stderr, "Error: %s requires a value\n", flag)
− os.Exit(1)
− }
− return args[i+1]
− }
−
− // requireUniqueValue is like requireValue but exits if the flag was already set.
− func (a classifyArgs) requireUniqueValue(args []string, i int, flag, current string) string {
− if current != "" {
− fmt.Fprintf(os.Stderr, "Error: %s can only be specified once\n", flag)
− os.Exit(1)
− }
− return a.requireValue(args, i, flag)
− }
−
− // requireIntRange parses the next arg as int and validates range [lo,hi].
− func (a classifyArgs) requireIntRange(args []string, i int, flag string, lo, hi int) int {
− val := a.requireValue(args, i, flag)
− v, err := strconv.Atoi(val)
− if err != nil {
− fmt.Fprintf(os.Stderr, "Error: %s must be an integer\n", flag)
− os.Exit(1)
− }
− if v < lo || v > hi {
− fmt.Fprintf(os.Stderr, "Error: %s must be between %d and %d\n", flag, lo, hi)
− os.Exit(1)
− }
− return v
− }
−
replacement in cmd/calls_classify.go at line 134
[4.10542]→[4.10542:10577](∅→∅) − func (a classifyArgs) validate() {
+ func (a classifyArgs) validate() error {
replacement in cmd/calls_classify.go at line 136
[4.10633]→[4.10633:10723](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --sample requires --certainty to be set\n")
− os.Exit(1)
+ return fmt.Errorf("--sample requires --certainty to be set")
edit in cmd/calls_classify.go at line 139
[4.10763]→[4.10763:10844](∅→∅) − fmt.Fprintf(os.Stderr, "Error: missing required flag: --folder or --file\n\n")
replacement in cmd/calls_classify.go at line 140
[4.10867]→[4.1149694:1149707](∅→∅),
[4.1149694]→[4.1149694:1149707](∅→∅) + return fmt.Errorf("missing required flag: --folder or --file")
edit in cmd/calls_classify.go at line 143
[4.10891]→[4.10891:10971](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night and --day are mutually exclusive\n\n")
replacement in cmd/calls_classify.go at line 144
[4.10994]→[4.10994:11007](∅→∅) + return fmt.Errorf("--night and --day are mutually exclusive")
edit in cmd/calls_classify.go at line 147
[2.2467]→[2.2467:2540](∅→∅) − fmt.Fprintf(os.Stderr, "Error: --night/--day requires --location\n\n")
replacement in cmd/calls_classify.go at line 148
[4.11169]→[4.1149854:1149867](∅→∅),
[4.1149854]→[4.1149854:1149867](∅→∅) + return fmt.Errorf("--night/--day requires --location")
edit in cmd/calls_classify.go at line 150
replacement in cmd/calls_classify.go at line 154
[4.11250]→[4.11250:11399](∅→∅) − // the converted []tools.KeyBinding slice. Exits on validation errors.
− func validateBindings(cfg *utils.Config, cfgPath string) []tools.KeyBinding {
+ // the converted []tools.KeyBinding slice. Returns an error on validation errors.
+ func validateBindings(cfg *utils.Config, cfgPath string) ([]tools.KeyBinding, error) {
replacement in cmd/calls_classify.go at line 160
[4.1150088]→[4.1150088:1150202](∅→∅) − fmt.Fprintf(os.Stderr, "Error: binding key %q in %s must be a single character\n", key, cfgPath)
− os.Exit(1)
+ return nil, fmt.Errorf("binding key %q in %s must be a single character", key, cfgPath)
replacement in cmd/calls_classify.go at line 163
[4.1150270]→[4.1150270:1150389](∅→∅) − fmt.Fprintf(os.Stderr,
− "Error: binding key %q in %s is reserved by the TUI for %s — pick a different key.\n",
+ return nil, fmt.Errorf("binding key %q in %s is reserved by the TUI for %s — pick a different key",
edit in cmd/calls_classify.go at line 165
[4.1150416]→[4.1150416:1150430](∅→∅) replacement in cmd/calls_classify.go at line 173
[4.1150759]→[4.1150759:1150865](∅→∅) − fmt.Fprintf(os.Stderr,
− "Error: secondary_bindings key %q in %s has no matching primary binding\n",
+ return nil, fmt.Errorf("secondary_bindings key %q in %s has no matching primary binding",
edit in cmd/calls_classify.go at line 175
[4.1150890]→[4.1150890:1150904](∅→∅) replacement in cmd/calls_classify.go at line 178
[4.1150956]→[4.1150956:1151063](∅→∅) − fmt.Fprintf(os.Stderr,
− "Error: secondary_bindings[%q] key %q in %s must be a single character\n",
+ return nil, fmt.Errorf("secondary_bindings[%q] key %q in %s must be a single character",
edit in cmd/calls_classify.go at line 180
[4.1151092]→[4.1151092:1151107](∅→∅) replacement in cmd/calls_classify.go at line 182
[4.1151175]→[4.1151175:1151311](∅→∅) − fmt.Fprintf(os.Stderr,
− "Error: secondary_bindings[%q] key %q in %s is reserved by the TUI for %s — pick a different key.\n",
+ return nil, fmt.Errorf("secondary_bindings[%q] key %q in %s is reserved by the TUI for %s — pick a different key",
edit in cmd/calls_classify.go at line 184
[4.1151349]→[4.1151349:1151364](∅→∅) replacement in cmd/calls_classify.go at line 186
[4.1151385]→[4.1151385:1151481](∅→∅) − fmt.Fprintf(os.Stderr,
− "Error: secondary_bindings[%q][%q] in %s has empty calltype\n",
+ return nil, fmt.Errorf("secondary_bindings[%q][%q] in %s has empty calltype",
edit in cmd/calls_classify.go at line 188
[4.1151510]→[4.1151510:1151525](∅→∅) replacement in cmd/calls_classify.go at line 192
[4.11404]→[4.11404:11421](∅→∅) replacement in cmd/calls_classify.go at line 196
[4.11484]→[4.11484:11523](∅→∅) − func RunCallsClassify(args []string) {
+ func RunCallsClassify(args []string) error {
replacement in cmd/calls_classify.go at line 198
[4.11553]→[4.11553:11567](∅→∅) + if err := a.validate(); err != nil {
+ return err
+ }
edit in cmd/calls_classify.go at line 205
[4.11701]→[4.11701:11746](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
replacement in cmd/calls_classify.go at line 206
[4.11875]→[4.11875:11888](∅→∅) + return fmt.Errorf("loading config: %w", err)
replacement in cmd/calls_classify.go at line 211
[4.11952]→[4.11952:12047](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s is missing \"classify.reviewer\"\n", cfgPath)
− os.Exit(1)
+ return fmt.Errorf("%s is missing \"classify.reviewer\"", cfgPath)
replacement in cmd/calls_classify.go at line 214
[4.12088]→[4.12088:12207](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %s is missing \"classify.bindings\" (need at least one key)\n", cfgPath)
− os.Exit(1)
+ return fmt.Errorf("%s is missing \"classify.bindings\" (need at least one key)", cfgPath)
replacement in cmd/calls_classify.go at line 217
[4.12211]→[4.12211:12256](∅→∅) − bindings := validateBindings(&cfg, cfgPath)
+ bindings, err := validateBindings(&cfg, cfgPath)
+ if err != nil {
+ return err
+ }
edit in cmd/calls_classify.go at line 229
[2.2648]→[2.2648:2664](∅→∅) replacement in cmd/calls_classify.go at line 231
[2.2742]→[2.2742:2802](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("parsing location: %w", err)
replacement in cmd/calls_classify.go at line 262
[4.1152435]→[4.1152435:1152493](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("loading data files: %w", err)
replacement in cmd/calls_classify.go at line 278
[4.1152965]→[4.1152965:1152978](∅→∅) replacement in cmd/calls_classify.go at line 284
[4.1153115]→[4.1153115:1153173](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("TUI error: %w", err)
edit in cmd/calls_classify.go at line 286
replacement in cmd/calls_classify.go at line 293
[4.1153334]→[4.1153334:1153432](∅→∅) − fmt.Fprintf(os.Stderr, "Error: invalid bind format: %s (expected key=value)\n", s)
− os.Exit(1)
+ panic(fmt.Sprintf("invalid bind format: %s (expected key=value)", s))
edit in cmd/calls.go at line 11
+
+ // ErrHelpRequested is returned when --help is used, signalling normal exit.
+ var ErrHelpRequested = fmt.Errorf("help requested")
replacement in cmd/calls.go at line 24
[4.1154074]→[4.1154074:1154104](∅→∅),
[4.1154104]→[3.9634:9647](∅→∅) − runCallsFromPreds(args[1:])
− return nil
+ return runCallsFromPreds(args[1:])
replacement in cmd/calls.go at line 26
[4.1154124]→[4.1154124:1154154](∅→∅),
[4.1154154]→[3.9648:9661](∅→∅) − runCallsFromBirda(args[1:])
− return nil
+ return runCallsFromBirda(args[1:])
replacement in cmd/calls.go at line 28
[4.1154174]→[4.1154174:1154204](∅→∅),
[4.1154204]→[3.9662:9675](∅→∅) − runCallsFromRaven(args[1:])
− return nil
+ return runCallsFromRaven(args[1:])
replacement in cmd/calls.go at line 30
[4.1154225]→[4.1154225:1154256](∅→∅),
[4.1154256]→[3.9676:9689](∅→∅) − runCallsShowImages(args[1:])
− return nil
+ return runCallsShowImages(args[1:])
replacement in cmd/calls.go at line 32
[4.1154274]→[4.1154274:1154303](∅→∅),
[4.1154303]→[3.9690:9703](∅→∅) − RunCallsClassify(args[1:])
− return nil
+ return RunCallsClassify(args[1:])
replacement in cmd/calls.go at line 34
[4.1154317]→[4.1154317:1154342](∅→∅),
[4.1154342]→[3.9704:9717](∅→∅) − RunCallsClip(args[1:])
− return nil
+ return RunCallsClip(args[1:])
replacement in cmd/calls.go at line 36
[4.1154358]→[4.1154358:1154385](∅→∅),
[4.1154385]→[3.9718:9731](∅→∅) − RunCallsModify(args[1:])
− return nil
+ return RunCallsModify(args[1:])
replacement in cmd/calls.go at line 38
[4.1154409]→[4.1154409:1154443](∅→∅),
[4.1154443]→[3.9732:9745](∅→∅) − runCallsPushCertainty(args[1:])
− return nil
+ return runCallsPushCertainty(args[1:])
replacement in cmd/calls.go at line 40
[4.1154469]→[4.1154469:1154505](∅→∅),
[4.1154505]→[3.9746:9759](∅→∅) − runCallsDetectAnomalies(args[1:])
− return nil
+ return runCallsDetectAnomalies(args[1:])
replacement in cmd/calls.go at line 42
[4.1154524]→[4.1154524:1154554](∅→∅),
[4.1154554]→[3.9760:9773](∅→∅) − runCallsPropagate(args[1:])
− return nil
+ return runCallsPropagate(args[1:])
replacement in cmd/calls.go at line 44
[4.1154573]→[4.1154573:1154603](∅→∅),
[4.1154603]→[3.9774:9787](∅→∅) − runCallsSummarise(args[1:])
− return nil
+ return runCallsSummarise(args[1:])
replacement in cmd/calls.go at line 46
[4.1154624]→[4.1154624:1154655](∅→∅),
[4.1154655]→[3.9788:9801](∅→∅) − runCallsClipLabels(args[1:])
− return nil
+ return runCallsClipLabels(args[1:])
replacement in cmd/calls.go at line 103
[4.1158049]→[4.1158049:1158089](∅→∅) − func runCallsFromPreds(args []string) {
+ func runCallsFromPreds(args []string) error {
replacement in cmd/calls.go at line 137
[4.1160335]→[4.1160335:1160348](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/calls.go at line 141
[4.1160380]→[4.1160380:1160484](∅→∅) − if *csvPath == "" {
− fmt.Fprintf(os.Stderr, "Error: --csv is required\n\n")
− fs.Usage()
− os.Exit(1)
+ if err := checkFlags(fs, "--csv", *csvPath); err != nil {
+ return err
replacement in cmd/calls.go at line 150
[4.1160639]→[4.1160639:1160874](∅→∅) − fmt.Fprintf(os.Stderr, "Error: Could not parse filter from filename. Use --filter flag.\n")
− fmt.Fprintf(os.Stderr, "Expected format: prefix_filter_date.csv (e.g., predsST_opensoundscape-kiwi-1.2_2025-11-12.csv)\n")
− os.Exit(1)
+ return fmt.Errorf("could not parse filter from filename. Use --filter flag. Expected format: prefix_filter_date.csv (e.g., predsST_opensoundscape-kiwi-1.2_2025-11-12.csv)")
replacement in cmd/calls.go at line 183
[4.1161875]→[4.1161875:1161933](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("from-preds: %w", err)
replacement in cmd/calls.go at line 200
[4.1162441]→[4.1162441:1162515](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls.go at line 202
replacement in cmd/calls.go at line 206
[4.1162586]→[4.1162586:1162627](∅→∅) − func runCallsShowImages(args []string) {
+ func runCallsShowImages(args []string) error {
replacement in cmd/calls.go at line 226
[4.1163716]→[4.1163716:1163729](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/calls.go at line 230
[4.1163761]→[4.1163761:1163867](∅→∅) − if *filePath == "" {
− fmt.Fprintf(os.Stderr, "Error: --file is required\n\n")
− fs.Usage()
− os.Exit(1)
+ if err := checkFlags(fs, "--file", *filePath); err != nil {
+ return err
replacement in cmd/calls.go at line 249
[4.1164260]→[4.1164260:1164318](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("show-images: %w", err)
edit in cmd/calls.go at line 253
replacement in cmd/calls.go at line 279
[4.1165463]→[4.1165463:1165503](∅→∅) − func runCallsFromBirda(args []string) {
+ func runCallsFromBirda(args []string) error {
replacement in cmd/calls.go at line 303
[4.1166866]→[4.1166866:1166879](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
edit in cmd/calls.go at line 308
[4.1166971]→[4.1166971:1167048](∅→∅) − fmt.Fprintf(os.Stderr, "Error: Either --folder or --file is required\n\n")
replacement in cmd/calls.go at line 309
[4.1167061]→[4.1167061:1167074](∅→∅) + return fmt.Errorf("either --folder or --file is required")
replacement in cmd/calls.go at line 339
[4.1167813]→[4.1167813:1167871](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("from-birda: %w", err)
replacement in cmd/calls.go at line 355
[4.1168397]→[4.1168397:1168471](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls.go at line 357
replacement in cmd/calls.go at line 383
[4.1169510]→[4.1169510:1169550](∅→∅) − func runCallsFromRaven(args []string) {
+ func runCallsFromRaven(args []string) error {
replacement in cmd/calls.go at line 408
[4.1170993]→[4.1170993:1171006](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
edit in cmd/calls.go at line 413
[4.1171098]→[4.1171098:1171175](∅→∅) − fmt.Fprintf(os.Stderr, "Error: Either --folder or --file is required\n\n")
replacement in cmd/calls.go at line 414
[4.1171188]→[4.1171188:1171201](∅→∅) + return fmt.Errorf("either --folder or --file is required")
replacement in cmd/calls.go at line 444
[4.1171939]→[4.1171939:1171997](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("from-raven: %w", err)
replacement in cmd/calls.go at line 460
[4.1172521]→[4.1172521:1172595](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls.go at line 462
replacement in cmd/calls.go at line 509
[4.1174533]→[4.1174533:1174573](∅→∅) − func runCallsSummarise(args []string) {
+ func runCallsSummarise(args []string) error {
replacement in cmd/calls.go at line 536
[4.1176290]→[4.1176290:1176303](∅→∅) + return fmt.Errorf("parsing arguments: %w", err)
replacement in cmd/calls.go at line 540
[4.1176335]→[4.1176335:1176441](∅→∅) − if *folder == "" {
− fmt.Fprintf(os.Stderr, "Error: --folder is required\n\n")
− fs.Usage()
− os.Exit(1)
+ if err := checkFlags(fs, "--folder", *folder); err != nil {
+ return err
replacement in cmd/calls.go at line 557
[4.1176746]→[4.1176746:1176804](∅→∅) − fmt.Fprintf(os.Stderr, "Error: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("summarise: %w", err)
replacement in cmd/calls.go at line 571
[4.1177368]→[4.1177368:1177442](∅→∅) − fmt.Fprintf(os.Stderr, "Error encoding output: %v\n", err)
− os.Exit(1)
+ return fmt.Errorf("encoding output: %w", err)
edit in cmd/calls.go at line 573
edit in CHANGELOG.md at line 4
+
+ ## [2026-05-11] Stream 3c complete: os.Exit elimination in cmd/
+
+ Completed the conversion of all cmd/ subcommand functions from void+os.Exit
+ to error-return pattern. This extends the Stream 3c work that previously
+ converted core CRUD + utility commands.
+
+ **os.Exit count in cmd/:** 172 → 13 (remaining are in CLI arg-parsing helpers:
+ `mustValue`, `mustIntValue`, `mustUniqueValue` — these handle truly fatal
+ syntax errors like missing flag values, duplicate flags, unknown flags)
+
+ **All business logic errors** now flow through main.go's single exit point.
+
+ **New shared helpers in `cmd/common.go`:**
+ - `mustValue(args, &i, flag)` — returns next arg, advances i, exits on missing
+ - `mustIntValue(args, &i, flag, lo, hi)` — returns validated int, exits on error
+ - `mustUniqueValue(args, &i, flag, current)` — exits if flag already set
+ - `checkFlags(fs, pairs...)` — returns error if any string flags are empty
+ - `checkNonZeroFlags(fs, pairs...)` — returns error if any int flags are zero
+ - Removed `requireFlags` (replaced by `checkFlags` everywhere)
+
+ **Converted functions** (void→error-return):
+ - `RunCalls` dispatcher + all subcommand handlers
+ - `RunCallsClassify`, `RunCallsClip`, `RunCallsModify`
+ - `runCallsFromPreds`, `runCallsFromBirda`, `runCallsFromRaven`
+ - `runCallsShowImages`, `runCallsSummarise`
+ - `runCallsClipLabels`, `runCallsPropagate`
+ - `runCallsDetectAnomalies`, `runCallsPushCertainty`
+ - `RunImport` dispatcher + all subcommand handlers
+ - `runImportBulk`, `runImportFile`, `runImportFolder`
+ - `runImportSegments`, `runImportUnstructured`
+ - `printJSON` now returns error
+
+ **main.go:** Added `ErrHelpRequested` sentinel — `--help` exits with code 0
+ via `errors.Is(err, cmd.ErrHelpRequested)`.
edit in CHANGELOG.md at line 40
+ **Complexity reductions:**
+ - `parseClassifyArgs`: 22 → 13 (extracted `mustIntValue`, `mustUniqueValue`)
+ - `parseModifyArgs`: 21 → 9 (extracted `mustValue`, `mustIntValue`)
+ - `parsePushCertaintyArgs`: 17 → 9 (extracted `mustValue`)
+