20 KiB
Output policy and invocation reporting
Status: initial planning. Message categories, all four output modes, uniform treatment of help and version, message prefixes, and OD-1's flag grammar and scope (including independent Progress control) are selected. OD-2's unified reporting direction is selected. Exact API signatures, failure presentation, retention, and delivery mechanics remain open. Implementation has not started.
Purpose
Provide consistent handling of --quiet, --verbose, --debug, and
--progress across application commands and optional integrations. Reduce
repeated output classification, diagnostic presentation, and delivery bookkeeping
where CommandKit can provide a meaningful benefit.
Ordinary Go writers and formatting functions already make application output straightforward. Design reporting conveniences around the shared output policy and invocation lifecycle. Applications continue to own their report content, domain behavior, and operation outcomes.
Current baseline
Streams and Invocation expose caller-selected output
and diagnostic writers. Outcome and Diagnostic carry
operation causes, diagnostic writers, and delivery state. Handlers can return
Failure(err) or attach diagnostics to an outcome for presentation after the
handler returns. MessageDiagnostic currently writes the supplied message with
a newline; severity does not add a label automatically.
CommandKit coordinates final diagnostic delivery and retains delivery failures separately from the operation cause. Custom handlers can also write directly to the supplied streams and retain responsibility for their output. The existing outcome and channel ownership decisions are recorded in ADR 0005.
Integration adapters already translate upstream diagnostic records into CommandKit diagnostics while preserving native rendering and source context. Some adapters wrap multiple records in one diagnostic writer and classify the batch by its highest severity. This matters when designing selective visibility.
There is currently no shared verbosity policy or invocation reporter API.
Selected output policy
Distinguish substantive application output from diagnostic message categories:
- Substantive output (Normal): The requested report or command result.
- Error: The invocation failed and will return a non-zero status code.
- Warning: Something went wrong or deserves attention, but the invocation can still succeed. A recovered problem is a warning.
- Info: Informational context and useful explanation, such as which profile was selected or a final operation summary.
- Progress: Streaming updates about work underway, such as items processed.
- Debug: Troubleshooting detail, including intermediate decisions and execution-path information.
Debug mode adds troubleshooting detail to verbose output:
| Output category | --quiet |
No flag | --verbose |
--debug |
|---|---|---|---|---|
| Substantive application output | Suppressed | Shown | Shown | Shown |
| Error | Shown | Shown | Shown | Shown |
| Warning | Suppressed | Suppressed | Shown | Shown |
| Info | Suppressed | Suppressed | Shown | Shown |
| Debug | Suppressed | Suppressed | Suppressed | Shown |
| Progress, without an explicit setting | Suppressed | Suppressed | Suppressed | Shown |
Info belongs in verbose mode; debug includes verbose messages. Normal output remains a substantive result category, separate from diagnostic severity. The category of a message determines its visibility; stream destination alone cannot distinguish a warning from an error.
Progress is distinct from Info and is controlled separately as specified below. Applications and integrations are not required to generate messages in any category. Enabling a category adds no output when there are no messages in it; other output remains subject to its own visibility rules.
Error presentation is tied to invocation failure. Prefer deriving error
diagnostics from a failed outcome, which may carry detailed explanations. Avoid
an unrestricted Error(...) reporting method that permits an error message
followed by a successful outcome. The application still controls process exit
under the existing outcome contract; the returned outcome must consistently
represent the failure and its non-zero status.
Apply the same policy to help and version. There are no command-specific exceptions for successful output:
app help --quiet # succeeds silently
app version --quiet # succeeds silently
Usage and operational failures still report their errors in quiet mode. The feature must distinguish successful output suppression from failure diagnostic delivery.
Message prefixes and channels
Use WARN:, DEBUG:, and ERROR: prefixes for simple warning, debug, and error
messages respectively. Substantive output and Info messages have no prefix.
For multiline diagnostics, prefix the opening line and preserve the structured
detail. Integration adapters coordinate native diagnostic rendering to avoid
duplicate severity labels or loss of upstream formatting.
Streaming Progress messages use the diagnostic channel so they do not corrupt substantive output such as JSON or YAML. Progress presentation details remain part of the consumer API and delivery design below.
Selected flag grammar and scope
Mode selection and resets
Model quiet, ordinary, verbose, and debug as mutually exclusive presentation modes. Each invocation starts in ordinary mode. Process flag occurrences in invocation order; the last enabled mode wins. Repetition does not increment verbosity, and debug is selected explicitly.
Accept presence and the existing explicit boolean syntax, including
--quiet=true and --quiet=false. A false occurrence clears its mode only if
that mode is currently selected, returning to ordinary mode. It does not restore
an earlier selection or disable a different mode. Every occurrence is converted;
invalid boolean values remain usage errors even when a later flag would replace
the selection.
| Flag sequence | Final mode |
|---|---|
--quiet --verbose |
Verbose |
--debug --quiet |
Quiet |
--verbose --verbose |
Verbose |
--verbose --verbose=false |
Ordinary |
--quiet --verbose=false |
Quiet |
--verbose --quiet --quiet=false |
Ordinary |
--quiet=false --debug |
Debug |
Keep verbosity a presentation choice: a quiet invocation still performs its requested operation and returns the ordinary outcome. Apply the policy through CommandKit-managed reporting and streams. Output written directly to process streams or external destinations remains application-owned.
Progress selection
--progress enables Progress without enabling Info, Warning, or Debug messages.
--progress=false explicitly disables it. Accept the existing boolean syntax;
repeated canonical or aliased progress settings use the last value. Retain
whether a setting was explicitly supplied, separately from its boolean value.
Determine effective Progress visibility from the final presentation mode and explicit progress setting:
- Quiet mode always suppresses Progress.
- Otherwise, an explicit progress setting controls visibility.
- Without an explicit setting, debug enables Progress; ordinary and verbose modes leave it disabled.
An explicit progress setting overrides the mode's default regardless of its position relative to mode flags. It does not select or reset the presentation mode. Debug includes verbose detail and defaults Progress on; verbose alone does not enable Progress.
| Flags | Result |
|---|---|
--verbose |
Warning and Info, without Progress. |
--verbose --progress |
Warning, Info, and Progress. |
--debug |
Warning, Info, Debug, and Progress. |
--debug --progress=false or --progress=false --debug |
Debug detail without Progress. |
--quiet --progress or --progress --quiet |
Only failure diagnostics. |
--debug --verbose |
Verbose detail without Progress, absent an explicit progress setting. |
Passing --progress to an application that emits no Progress messages adds no
visible output. It does not suppress that application's substantive results or
require a progress implementation.
Placement, availability, and aliases
Core supplies --quiet, --verbose, --debug, and --progress for application
commands, integrations, the root, canonical groups, and alias-only discovery help.
Consumers do not register them individually. Preserve the existing placement
grammar: flags follow the complete command path and may be interspersed with
operands after that path. For example:
app config show --quiet
app export destination --verbose
app help config show --quiet
app version --quiet
Options before or between command-path components are outside this grammar.
Tokens consumed as values and tokens following -- retain their ordinary
meaning.
Reserve the four long spellings. Applications may add -q, -v, or -d
through the alias API; no built-in short spellings are selected. An application
flag declaration that defines a reserved long name fails installation with a
collision error. Aliases may target these flags but may not overload canonical
spellings. Apply the existing collision and atomic installation rules, including
later registration.
Aliased and canonical occurrences participate in the same ordering. With
-q → --quiet, app export --verbose -q selects quiet mode. Document the newly
reserved canonical names as a compatibility change.
Routing and parsing failures
Determine the mode through the normal parser before execution or successful help/report presentation. Do not add a separate verbosity scanner. This keeps token interpretation consistent and avoids reinterpreting values or operands.
Routing and parsing failures report their errors regardless of verbosity. For
example, app export --quiet --unknown still reports the unknown flag. Error
visibility follows the selected output policy independently of how the mode is
determined; avoiding a separate scanner is an implementation simplification.
CommandKit's own diagnostics
Use the shared reporting system for CommandKit's invocation diagnostics so parser, integration, and application messages follow the same visibility and delivery policy. Installation errors occur before an invocation exists and continue to be returned to the application without automatic printing.
Supported repetition and last-occurrence overrides are routine decisions.
Repeated singleton flags, including --verbose --verbose, may produce debug
detail; repetition alone does not warrant a warning. Repeatable flags retain
their intended accumulation behavior. Add warnings for concrete concerns an
operator can act on, such as a deprecated spelling if deprecation is introduced.
Unknown flags, invalid values, and invalid command paths remain invocation
failures with error diagnostics.
Start debug instrumentation with routing, alias resolution, verbosity selection, and invocation completion. Other useful candidates can be added where they explain a consequential decision:
| Event | Useful detail |
|---|---|
| Command-alias resolution | Entered path, intermediate redirects, and final canonical path. |
| Flag-alias resolution | Entered spelling and canonical flag name. |
| Scoped alias selection | A command-scoped mapping took precedence over an application-wide mapping. |
| Verbosity selection | A later occurrence selected a different mode; for --verbose --debug, debug wins because it appears last. |
| Help-route selection | A canonical command or alias-only discovery context was selected. |
| Repeated singleton flag | A later occurrence replaced an earlier occurrence, without printing their values. |
| Input preparation | Canonical input validation completed and the handler is starting. |
| Invocation completion | Canonical command, outcome classification, and status. |
Use the final selected mode to present earlier parser decisions. Collect the
relevant events during parsing and apply visibility after mode selection:
--verbose --debug shows the selection explanation, while --debug --quiet
suppresses it. This collection of parser events does not require whole-run
output buffering.
Debug messages should explain operator-relevant decisions independently of incidental implementation steps. Use command paths, flag names, and decision descriptions rather than raw argument or configuration-value dumps. Upstream libraries retain ownership of their diagnostic detail and disclosure rules.
Tentative direction
Streaming and failure diagnostics
The proposed approach applies visibility when output is emitted. A verbose or debug run may show informational detail before subsequently failing. A run with Progress enabled may stream updates before failing, and an ordinary run may write part of its substantive output before failing. Previously written bytes cannot be withdrawn. Whole-run buffering solely to make failed runs contain only error output is not part of the proposed direction.
Keep errors understandable in every mode, including source locations and related explanatory context. Unrelated warnings and informational messages remain subject to the selected mode. Retain suppressed diagnostics for programmatic inspection rather than equating suppression with loss of evidence. The exact representation of retained and delivered diagnostics remains open.
Selected consumer reporting API direction
Provide one reporting facility per invocation for substantive output, immediate messages, and end-of-run diagnostics. The consumer API is chosen for its intended long-term design; with no current consumers, preserving existing API shapes carries no weight in this decision.
The facility exposes interfaces appropriate to each need:
| Need | Selected direction | Illustrative shape |
|---|---|---|
| Substantive results | An ordinary io.Writer, usable by encoders and native report writers. |
inv.Reporter().Output() |
| Immediate messages | Categorized methods applying visibility and presentation automatically. | Info, Warn, Debug, Progress |
| End-of-run diagnostics | An explicit deferred view sharing the invocation's reporting state. | report.AtEnd().Warn(message) |
| Rich upstream diagnostics | Typed submission preserving severity, context, and native rendering. | Exact API remains open. |
These method names illustrate the selected direction; exact names and signatures remain to be finalized. Progress is streaming output, distinct from deferred informational summaries. Applications need not implement categories they do not use or check verbosity before reporting.
The reporter owns presentation: output visibility, progress settings, prefixes, ordering, and delivery bookkeeping. Its views share invocation state, allowing already-emitted messages, deferred messages, suppression, and write failures to be coordinated in one place. Helpers may receive the reporter or a narrow application-defined interface. Domain libraries can continue returning diagnostic data for the handler to submit, independently of CommandKit.
The returned outcome owns the operation result: success, failure, or an
application domain classification. CommandKit presents a failed outcome through
the same reporting facility. Error explanations must be explicitly associated
with the failure they describe; an unrestricted Error(...) method that permits
success afterward is outside this direction. Typed upstream diagnostic submission
must preserve that relationship as well.
Use the reporter as the common diagnostic submission path, replacing the earlier
proposal to split submission between a reporter and Outcome.Diagnostics.
Whether the execution result exposes retained diagnostic observations is an
OD-3 decision, separate from how handlers submit messages. Caller-selected
destinations belong at the execution boundary; the handler-facing API routes
output through the reporter instead of requiring a parallel raw-stream API.
Whether reporting methods return write errors, retain them for finalization,
or expose an explicit delivery-error accessor remains open under OD-4. Standard
writer operations remain compatible with io.Writer. The final API design will
consolidate these choices with the selected consumer-facing structure.
Upstream diagnostic ownership
ConfigKit and PromptKit remain authoritative for their diagnostic semantics, severity, source context, and disclosure rules. Integration adapters expose those capabilities through the shared CommandKit presentation policy.
Support consistent reporting of ConfigKit errors and warnings as its adopted public API evolves. This roadmap does not assume that a future warning API already exists or prescribe its upstream shape. Preserve useful native diagnostic detail and supported error identity, and coordinate returned errors with diagnostics that already explain them.
Debug output follows the same upstream disclosure and safe-rendering contracts. Enabling debug does not authorize secret disclosure or bypass owning-library rules.
Work needed
- Settle the open decisions with examples covering ordinary application output, immediate and deferred diagnostics, failure after partial output, help and version, and integration diagnostics in every mode. Include applications that emit no Progress messages and the distinction between Info and Progress.
- Review current diagnostic batching, parsing, help, outcome presentation, and
writer ownership to identify the smallest shared changes. Include invocation
failures that occur before a handler receives its
Invocation. - Finalize the unified reporting API and flag contracts, including interactions with command and flag aliases, following OD-1 and OD-2 above. Replace split handler submission paths and adapt core and integration callers. Account for flag collisions and document changed public contracts.
- Implement the selected behavior across core and the integrations. Preserve upstream rendering and semantics while applying the chosen visibility policy.
- Verify classification, flag selection, suppression, retained diagnostics, ordering, duplicate prevention, Error/non-zero-outcome consistency, debug visibility, and write-failure handling with focused behavioral tests and maintained consumer examples. Cover mode resets, mixed canonical/alias ordering, help and discovery contexts, and core parser events presented according to the final mode. Cover explicit progress overrides, debug's progress default, quiet suppression, progress aliases, message prefixes, and preservation of native multiline diagnostics.
- Update GoDoc, the CLI reference, consumer guidance, and release documentation when implemented. Review whether any resulting architectural change needs an ADR; exact reporting APIs and flag syntax remain API/CLI contracts.
Open Decisions
OD-3: Failure presentation and diagnostic retention
How does the API enforce that Error diagnostics accompany a failed invocation with a non-zero status, including application domain outcomes? How should failure explanations and the operation outcome be associated to avoid inconsistent caller-supplied combinations? How are recovered upstream problems represented as warnings while preserving their native context? Which contextual records belong to an error explanation? How are mixed upstream diagnostic batches filtered while preserving useful native rendering? What is retained when presentation is suppressed, and how is duplicate presentation prevented without hiding unrelated failures? How do adapters apply the selected prefixes to mixed or already-labelled native diagnostics? What presentation should streaming Progress use?
OD-4: Delivery lifecycle and quiet execution
How are immediate and deferred messages ordered and finalized, including when the operation fails? How do callers observe and react to write failures? What delivery state represents intentional suppression? Does quiet mode discard rendered bytes or skip report rendering, and how does that choice preserve operation semantics and failure detection? Which streaming behavior becomes part of the public contract? Are initial Progress messages ordinary streamed lines, or is terminal redraw behavior needed? How do progress delivery failures and retention interact with other messages? No progress-bar, redraw, or terminal-specific API is selected yet.