Files
commandkit/docs/adr/0003-public-facade.md

5.8 KiB

ADR 0003: Own public contracts through a root facade and internal CLI adapter

Status

Partially superseded by ADR 0010.

ADR 0010 narrows the root facade and public-type ownership decision to the core module and places optional library dependencies in integration modules. The remaining principles continue to apply; this record's decision text is retained.

Date

2026-10-06

Context

CommandKit needs room to change its internal implementation details while consumers retain access to a stable external API. Exposing backend assembly or extension structures would let applications ossify around implementation details that maintainers may later decide to replace.

ConfigKit, PromptKit, and ReleaseKit demonstrate facades that own public types and delegate through private state and explicit conversion. ConfigKit also translates upstream YAML parser structures into an independent internal document model.

The selection of Cobra as an internal dependency is compatible with this decision. Public API independence needs a genuine contract boundary, and keeping backend structures out of shared internal representations also makes future replacement easier.

Decision

Use the module root commandkit as the sole initial supported public facade. Define CommandKit's public named types there and expose only selected operations, extension contracts, and documented methods. Handles keep implementation state in private fields.

Straightforward implementation may live in the root package without expanding its exports. Introduce internal packages, interfaces, and conversions where they simplify implementation or protect the backend boundary. Package organization and the amount of internal layering follow implementation needs. Additional public packages require a deliberate decision based on a durable consumer responsibility.

Do not alias public types to internal or backend types, anonymously embed those types to promote methods, or expose implementation bridge methods. Exported fields, signatures, variables, callbacks, interface methods, constraints, and method sets must not reveal internal or Cobra/pflag structures. CommandKit does not expose its internal backend objects or backend-specific syntax errors through its public API, including through loosely typed values or returned errors. Caller-supplied values and errors retain their documented handling; this boundary does not require inspecting or transforming arbitrary application objects.

Standard contracts such as context.Context and io.Writer remain appropriate. Public types from integration libraries, such as ConfigKit and PromptKit, may be selected as deliberate dependencies. Application input types remain application-owned. Go declarations and GoDoc own exact exported contracts.

Confine Cobra/pflag types and operations to an internal adapter. Registry logic, standard handlers, and shared definitions remain independent of backend structures. The adapter translates paths, descriptions, bindings, presence, callbacks, and selected help/outcome semantics into backend machinery. Do not reproduce Cobra's complete API through renamed wrappers.

Preserve independent parsed state per invocation under ADR 0001. Factories, callbacks, and shared representations remain API and implementation design choices.

Translate backend-owned syntax failures into CommandKit diagnostics and outcomes. Preserve useful public identity for application handler errors and cancellation according to the invocation contract. Any conversions must preserve value ownership, callback lifetime, presence, and supported error semantics.

Alternatives considered

Alternative Analysis
Keep all machinery in the public root Less forwarding, but harder consumer navigation and easier accidental public expansion.
Require a forwarding-only root and separate internal representations Enforces a uniform layering scheme. It can add duplicate types and callback plumbing before those mechanisms simplify implementation.
Alias or embed implementation types Less conversion code, but couples public type identity, fields, or method sets to internal choices.
Hide Cobra only behind the public facade Hides types from applications but allows backend structures to spread through internal logic, complicating replacement.
Mirror Cobra's API with wrappers Removes dependency names while preserving its conceptual coupling and compatibility surface.
Public packages for registry, bindings, backend, and other mechanisms Adds supported imports and assembly contracts without a distinct consumer responsibility.
Selectable/pluggable backends immediately Adds selection and extension machinery before a concrete need exists.

Consequences

Consumers depend on deliberate root contracts while internal algorithms, collaborators, and package boundaries can evolve. Useful application registration capabilities still need a proportionate API; minimizing accidental exports must not recreate repetitive consumer infrastructure.

Forwarding and conversion add maintenance where used. Proportionate checks should protect the public surface, backend isolation, and consumer behavior through the real adapter. The implementation determines which checks provide useful evidence; package relocation alone does not establish those properties.

Backend replacement can preserve the public Go API but must also preserve selected CLI behavior or follow the release policy for intentional incompatibilities. Backend defaults do not independently establish new CommandKit contracts.

The architecture policy owns normative boundaries. ADR 0001 records the dispatcher/input model, and ADR 0002 selects the initial backend.