Skip to content

Why PIG Keeps the Cobra Command Layer Flat

The source-layout decision that keeps one top-level command in one cmd file and moves concrete behavior into cli and internal packages.
The source-layout decision that keeps one top-level command in one cmd file and moves concrete behavior into cli and internal packages.

Decision date: 2026-06-30
Status: Active repository architecture.
Current reference: pig command overview and the source repository
Scope: Go source ownership and command registration, not the public command taxonomy itself.

Decision

The cmd package stays flat. One top-level command belongs in one top-level Go file: pg.go, pb.go, pt.go, pe.go, sty.go, do.go, repo.go, and their peers. Even large command trees remain in that entry-point file unless there is an explicit decision to change the layout.

The file may be long, but it should contain Cobra concerns: names, aliases, annotations, flags, argument validation, help, registration, and option mapping. Concrete work belongs in cli/*, internal/*, or another implementation package.

Context

Earlier command growth produced many small files named after subcommands and several parallel implementations of confirmation, structured output, plan rendering, and legacy wrapping. It became difficult to answer simple questions: where is a top-level command registered, which file owns an alias, and whether two helpers implement the same policy.

PIG’s command families are large, but their public grammar is one surface. Keeping that grammar in one place makes review and collision detection easier, while implementation packages remain decomposed by responsibility.

Alternatives considered

  • A directory per command under cmd. Rejected for normal commands because it scatters one public grammar across many packages and encourages business logic near Cobra.
  • One file per subcommand. Rejected because registration, aliases, and inherited flags become hard to audit as one contract.
  • Put everything in cmd. Rejected because tests, reuse, and error handling suffer when operational logic depends on Cobra state.
  • Abstract every repeated line. Rejected because speculative frameworks can hide the command grammar; only stable, cross-command glue should be shared.

Contract

  • cmd/root.go owns root setup, global flags, and top-level registration;
  • cmd/utils.go owns shared command-layer helpers;
  • each normal top-level command has one matching top-level source file and may have one matching test file;
  • Cobra code validates syntax and maps options, but does not perform the operation;
  • reusable confirmation, annotation, structured-output, and plan helpers have one implementation;
  • implementation packages accept ordinary options and return typed results or errors without depending on Cobra globals.

Consequences

Some command files are intentionally large. The trade-off is accepted because the public surface can be reviewed as a unit, while the implementation remains split below it. The rule also reduces file churn when aliases or flags move and gives agents a deterministic starting point.

The boundary is architectural, not cosmetic: a short cmd file that hides business logic in closures is still a violation, while a long file containing only declarative command glue is not.

Verification and evolution

The convention was recorded in 9eb70db, followed by the large command-surface consolidation in fb93602. Guard tests check alias collisions and command registration, while package tests exercise the implementation beneath the Cobra layer.

Current status

This remains the repository rule for new work. Public command documentation belongs on this site; source-layout enforcement remains close to the code so contributors and coding agents encounter it before editing.