The root cause of DF-005 was a flag collision plus a dead entry point: the generated leaf declared -o/--output as a format flag (generator.go:454), but the wired-in run handler executeAndPrint never read it (raw JSON dumped for every format); meanwhile the root persistent -o/--output file path flag collided with that shorthand, and its handler ExecuteAndFormat was never wired in — dead code. The fix (implemented and verified at /tmp/engine, module engine):
The root cause of DF-005 was a flag collision plus a dead entry point: the generated leaf declared -o/--output as a format flag (generator.go:454), but the wired-in run handler executeAndPrint never read it (raw JSON dumped for every format); meanwhile the root persistent -o/--output file path flag collided with that shorthand, and its handler ExecuteAndFormat was never wired in — dead code. The fix (implemented and verified at /tmp/engine, module engine):
1. pkg/generator/generator.go:454 — leaf wiring now goes to the real handler:
// DF-005 fix: leaf commands are wired to engine.ExecuteAndFormat,
// which reads the -o/--output format flag, honors --raw, honors the
// root --output-file flag, and emits no per-invocation stderr noise.
RunE: e.ExecuteAndFormat,
// ...and the flag the handler now consumes (generator.go:454):
cmd.Flags().StringP("output", "o", "table", "output format: table, json, yaml (default table)")
cmd.Flags().Bool("raw", false, "dump the raw response body without formatting")
2. pkg/engine/engine.go — executeAndPrint reads the flags; ExecuteAndFormat is the wired entry point (no more dead code, no stderr noise):
func (e *Engine) executeAndPrint(cmd *cobra.Command, args []string) error {
body, err := e.fetcher.Fetch(strings.Join(args, " "))
if err != nil {
return err
}
format, err := cmd.Flags().GetString(FormatFlag) // leaf -o/--output, default table
if err != nil {
format = "table"
}
raw, _ := cmd.Flags().GetBool(RawFlag)
out, err := printer.Format(body, format, raw)
if err != nil {
return err
}
if path, _ := cmd.Flags().GetString(OutputFileFlag); path != "" {
return os.WriteFile(path, out, 0o644)
}
_, err = cmd.OutOrStdout().Write(out)
return err
}
// ExecuteAndFormat is the run handler wired into generated leaf commands.
func (e *Engine) ExecuteAndFormat(cmd *cobra.Command, args []string) error {
return e.executeAndPrint(cmd, args)
}
3. pkg/printer/printer.go (new) — table via text/tabwriter, json, yaml via gopkg.in/yaml.v3, --raw bypass, non-JSON raw fallback:
func Format(body []byte, format string, raw bool) ([]byte, error) {
if raw {
return body, nil
}
var v interface{}
if err := json.Unmarshal(body, &v); err != nil {
return body, nil // non-JSON body: raw fallback
}
switch format {
case "table":
if out, ok := table(v); ok {
return out, nil
}
return body, nil // scalars / arrays of scalars: raw fallback
case "json":
out, err := json.MarshalIndent(v, "", " ")
if err != nil {
return nil, err
}
return append(out, '\n'), nil
case "yaml":
return yaml.Marshal(v)
default:
return nil, fmt.Errorf("unsupported output format %q (supported: table, json, yaml)", format)
}
}
table() union-sorts keys across the JSON objects and renders with tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0); empty arrays render nothing.
4. cmd/root.go — root flag renamed, shorthand dropped (collision eliminated):
// DF-005 fix: renamed from -o/--output (which collided with the leaf
// format flag) to --output-file with no shorthand; the handler now honors it.
root.PersistentFlags().String("output-file", "", "write output to this file instead of stdout")
Reproduced the buggy behavior first, then verified the fix. Before — identical raw JSON for every invocation, stderr noise, and -o out.txt (intended file path) silently consumed by the leaf format flag:
$ ./engine-cli users -o json # -> raw JSON, not formatted
$ ./engine-cli users --output yaml # -> raw JSON, not YAML
$ ./engine-cli users -o out.txt # collision: no out.txt created
$ ./engine-cli users 2>err.txt # stderr: "engine: executing ..." noise
After — the CLI behaves correctly end-to-end:
$ ./engine-cli users # table (default), stderr bytes: 0
id name role
1 alice admin
2 bob dev
$ ./engine-cli users -o json # indented JSON
$ ./engine-cli users --output yaml# YAML
$ ./engine-cli users --raw # raw body verbatim
$ ./engine-cli users -o json --output-file out.json # stdout empty, file written
$ ./engine-cli users --output-file out.txt # persistent flag works before subcommand too
$ ./engine-cli users -o xml # Error: unsupported output format "xml" (exit 1)
21 automated tests, all passing (go test ./..., go vet, go test -race clean; gofmt clean):
pkg/printer (10): exact table alignment, sparse rows (missing cells), single object, empty array → empty, scalar/array-of-scalars → raw fallback, exact indented JSON, exact YAML, --raw byte-identical, non-JSON body → raw fallback for all formats, unknown format → error.cmd (11): default table + zero stderr bytes (noise removed), -o json, --output yaml long form, --raw, --output-file writes file with empty stdout, --output-file combined with -o json, non-JSON body fallback through the full CLI, unknown format error, root no longer defines output and --output-file has no shorthand, leaf keeps -o with default table plus --raw, and both flags resolve independently on one invocation (no shadowing).Edge cases covered: non-JSON bodies (all formats fall back to raw), --raw on non-JSON, empty arrays, sparse/missing cells, JSON scalars and arrays of scalars, flag placement before/after the subcommand, shorthand removal on root, and the exact generator.go:454 flag declaration retained.
JSON: {"problem_class":"go-cobra-cli-flag-collision","model":"deepseek-v4-flash","result":"passed","tests":21}
Solved by Pi Agent (deepseek-v4-flash).