Document invocation reporting contracts

This commit is contained in:
2026-10-26 03:57:11 +00:00
parent a6c0fb5b0d
commit 96aa6b5ca0
15 changed files with 345 additions and 71 deletions

View File

@@ -66,6 +66,93 @@ version: dev
revision: ""
```
## Reporting
CommandKit applies one shared reporting policy to application handlers,
integrations, help, version, and CommandKit's own invocation diagnostics. The
caller passes result and diagnostic writers to `App.Execute`; substantive
results go to `Streams.Out`, and categorized messages, terminal explanations,
and usage guidance go to `Streams.Err`.
### Modes and visibility
Each invocation starts in ordinary mode. `--quiet`, `--verbose`, and `--debug`
are mutually exclusive; the last enabled occurrence selects the mode. They
accept presence or an explicit boolean value. A `false` occurrence clears the
selected mode only when it names the current mode, returning to ordinary mode;
it does not restore an earlier selection. Invalid boolean values are usage
errors, even if a later flag would select another mode.
| Category | `--quiet` | No mode flag | `--verbose` | `--debug` |
| --- | --- | --- | --- | --- |
| Substantive result | Suppressed | Shown | Shown | Shown |
| Terminal failure | Shown | Shown | Shown | Shown |
| Warning | Suppressed | Suppressed | Shown | Shown |
| Info | Suppressed | Suppressed | Shown | Shown |
| Debug | Suppressed | Suppressed | Suppressed | Shown |
| Progress, unless enabled explicitly | Suppressed | Suppressed | Suppressed | Shown |
`--progress` independently enables Progress; `--progress=false` disables it.
Both accept the existing boolean syntax, and the last occurrence wins. An
explicit setting overrides the mode default, except quiet always suppresses
Progress. Without an explicit setting, debug enables Progress; ordinary and
verbose modes leave it disabled. Progress does not enable Info, Warning, or
Debug.
### Placement and aliases
Reporting flags are available on application commands, the root, canonical
groups, help, version, and alias-only discovery help. Place them after the
complete command path; they may be interspersed with operands after that path.
For example:
```text
example export --quiet
example config show --verbose
example promptkit test all --debug --progress=false
example help config show --quiet
example version --quiet
```
Reporting flags do not go before or between command-path components. Tokens
consumed as values and tokens after `--` retain their normal meaning.
The reserved long spellings are `--quiet`, `--verbose`, `--debug`, and
`--progress`; application declarations cannot reuse them. There are no built-in
short spellings. Applications may define aliases such as `-q → --quiet`,
`-v → --verbose`, and `-d → --debug`; canonical and aliased occurrences share
the same ordering. Thus `example export --verbose -q` selects quiet mode.
### Channels and messages
Substantive reports use the result destination. Warnings, Info, Progress,
Debug, terminal explanations, and usage guidance use the diagnostic destination.
Simple Warning, Debug, and terminal failure messages begin with `WARN:`,
`DEBUG:`, and `ERROR:` respectively. Info, Progress, and substantive results
have no prefix. For multiline messages, the prefix appears on the opening line;
native structured details keep their own formatting without duplicate labels.
Progress is streamed as unprefixed lines; terminal redraw and progress bars are
not provided. A failed outcome with no message or detail uses the fallback
`ERROR: Command failed.`.
Quiet mode suppresses successful help and version output, as it does other
substantive results. Usage guidance and routing, parsing, and operational
failures remain visible in every mode. A recovered problem is reported only if
the application chooses to submit a Warning; an error cause by itself does not
create a message.
### Presentation failures
CommandKit continues rendering substantive output under quiet mode through a
discard destination. This suppresses writes to the caller's result writer while
still detecting report preparation, encoding, or rendering failures. An
intentional suppression is not a delivery failure. A failure while rendering or
writing requested content is retained in the returned `Outcome` and makes an
otherwise successful or usage invocation operational with status `1`. A domain
classification and its nonzero status are preserved; a zero-status domain
outcome becomes status `1` when presentation fails. Earlier bytes already
written remain visible.
## Registered command syntax
Options are parsed after the complete command path and may be interspersed with
@@ -223,8 +310,8 @@ interrupt the native value. Existing native markers and leading comments are
retained; documents without a marker receive one. Scope labels containing line
control characters are quoted to keep them on one comment line. A provider
failure leaves the output stream empty. A rendering or output failure may leave
an aggregate prefix, and the outcome reports
incomplete report delivery.
an aggregate prefix; `Outcome.Presentation.Output` records incomplete delivery
and the destination or rendering error.
### List configuration sources
@@ -390,8 +477,9 @@ again.
The complete report is prepared before writing. Report preparation failures and
cancellation observed before report delivery starts leave CommandKit's report
output empty. Once writing starts, CommandKit does not interrupt the supplied
writer or roll back delivered bytes. A write error or short write uses the
existing incomplete-report delivery outcome and can leave partial output.
writer or roll back delivered bytes. A write error or short write is recorded
as incomplete delivery in `Outcome.Presentation.Output` and can leave partial
output.
## PromptKit definition inspection
@@ -607,8 +695,9 @@ execution. See PromptKit's
Builder, engine-loading, and native validation errors use the operational path
(status `1`) with supported error identity and acquired diagnostics retained.
Malformed syntax, missing operands, and unsupported formats use usage status
`2`. Successful diagnostics and report-delivery failures follow the common
delivery contract.
`2`. Successful diagnostics use the common reporting policy. Report delivery
failures are recorded in `Outcome.Presentation.Output`, separately from the
operation cause.
### PromptKit catalogs
@@ -802,8 +891,8 @@ outcome. Operation errors use operational status `1`; aggregate runs continue
after individual errors, retain their reports and ordered error text, and join
causes for supported `errors.Is`/`errors.As` access. Cancellation stops later
operations and preserves results already acquired. Report encoding or delivery
failures use the common incomplete-report delivery outcome and retain operation
causes where applicable. Syntax, operand, unsupported format, and conflicting
failures are recorded in `Outcome.Presentation.Output`, separately from
operation causes. Syntax, operand, unsupported format, and conflicting
namespace selection are usage status `2`.
Native configuration, selection, and diagnostic meanings remain owned by
@@ -816,4 +905,9 @@ and [engine API](https://gitea.maximumdirect.net/eric/promptkit/src/commit/9178f
CommandKit usage failures use status `2`; operational failures use status `1`.
Successful operations use status `0`. Application handlers may return a domain
classification and status of their choice, which `Execute` preserves. The caller
chooses how to map an outcome to process exit.
chooses how to map an outcome to process exit. A rendering or delivery failure
changes a successful or usage outcome to operational status `1`. A domain
outcome keeps its classification and nonzero status; a zero-status domain
outcome becomes `1` if presentation fails. The returned `Outcome.Presentation`
records output and diagnostic delivery observations separately from its
operation `Cause`.

View File

@@ -64,7 +64,9 @@ func Greeting() commandkit.Command {
),
},
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
_, _ = fmt.Fprintln(invocation.Streams.Out, input.Count.Value)
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
},
})
@@ -85,6 +87,49 @@ values convert before input construction. A command's `NewInput` creates input
only when execution reaches it; help does not construct caller input or run
provider callbacks.
## Invocation reporting
`Invocation.Reporter()` supplies the reporter for the current handler. Use
`Output()` or `RenderOutput()` for substantive results; use `Warn`, `Info`,
`Progress`, and `Debug` for categorized messages. Queue a summary with
`AtEnd().Info` when it belongs after the handler returns. The
[CLI reference](../cli.md#reporting) owns flag grammar, visibility, prefixes,
and status behavior.
Return terminal text with `Failure(message, details...)`, `Usage(message,
details...)`, or `WithExplanation` on a domain outcome. Attach an optional Go
error separately with `WithCause`. A cause is preserved for inspection, but it
does not choose the outcome or become user-facing text. Rendering callbacks
write to their supplied writer only while the callback runs; they must not
submit another message or start another rendering operation through the same
reporter.
```go
Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome {
if _, err := fmt.Fprintln(invocation.Reporter().Output(), input.Count.Value); err != nil {
return commandkit.Failure("Could not write the greeting.").WithCause(err)
}
return commandkit.Success()
}
```
After `App.Execute`, inspect the returned `Outcome`. Its `Cause` retains the
operation error; `Presentation.Output` and `Presentation.Diagnostics` report
delivery state and the first rendering or write errors for their destinations.
CommandKit does not retain a message transcript. Reporter operations for one
invocation are safe concurrently, but concurrent submission order is not
specified. The caller owns the writers passed to `Execute`; CommandKit does not
close them.
### Migrating from the previous reporting API
| Previous API | Current API |
| --- | --- |
| `invocation.Streams.Out` / `.Err` | `invocation.Reporter().Output()` and categorized reporter methods. `Streams` remains the argument to `App.Execute`. |
| `Failure(err)` / `Usage(err)` | `Failure("operator-facing explanation").WithCause(err)` / `Usage("usage explanation").WithCause(err)`. Choose the explanation deliberately; the cause alone is not printed. |
| `Outcome.Diagnostics` or terminal `SeverityError` | Submit independent messages through reporter methods. Put failure context in `Explanation`; provider `Acquired.Diagnostics` remains passive data for the owning integration to submit. |
| `ReportIncomplete`, `DiagnosticDelivery`, or `DeliveryError` | Inspect `Outcome.Presentation.Output` and `.Diagnostics`, including each `DeliveryResult.State`, `RenderError`, and `WriteError`. |
## Application-owned aliases
Pass commands and aliases together as declarations. Integration factories can
@@ -257,10 +302,11 @@ application-owned `session restore` command.
## Outcomes and ownership
Core classifies usage and operational failures while preserving application
domain outcomes and supported error identity. Report and diagnostic delivery
errors remain inspectable separately from the operation cause. The caller owns
process startup, signal handling, and exit mapping; `Execute` never exits the
process.
domain outcomes and supported error identity. Applications choose domain
classification and status. A terminal explanation is presented separately from
the optional operation cause, while report and diagnostic delivery observations
are available in `Outcome.Presentation`. The caller owns process startup,
signal handling, and exit mapping; `Execute` never exits the process.
Go declarations and GoDoc own exact exported contracts. ConfigKit owns
configuration decoding, validation, and provenance. PromptKit owns prompt

View File

@@ -2,8 +2,9 @@
CommandKit is distributed as a core Go module and two optional integration
modules. Core owns captured declarations, typed input preparation, installation,
dispatch, outcomes, help, and version. `integrations/ckconfig` owns ConfigKit
scope adaptation and reports; `integrations/ckprompt` owns PromptKit engine,
dispatch, invocation reporting and outcomes, help, and version.
`integrations/ckconfig` owns ConfigKit scope adaptation and reports;
`integrations/ckprompt` owns PromptKit engine,
definition inspection, requirements discovery, and concrete request validation.
The maintained [consumer guide](consumers/guide.md) links to the three maintained
consumers.
@@ -53,6 +54,7 @@ For application-owned aliases, use the
| Task | Read |
| --- | --- |
| Implemented CLI behavior | [CLI reference](cli.md) |
| Invocation reporting | [CLI reporting contract](cli.md#reporting), [consumer guide](consumers/guide.md#invocation-reporting), core GoDoc, [reporting roadmap](roadmap/reporting.md), and [ADR 0014](adr/0014-invocation-reporting.md) |
| Application-owned aliases | [CLI reference](cli.md#application-owned-aliases), [Go declarations and GoDoc](../command.go), [consumer guide](consumers/guide.md#application-owned-aliases), and [ADR 0013](adr/0013-application-alias-routing.md) |
| Public API usage and standard-suite wiring | [Consumer guide](consumers/guide.md), [ckconfig package](../integrations/ckconfig/doc.go), [ckprompt package](../integrations/ckprompt/doc.go), and applicable ADRs |
| Dispatcher, registration, or typed inputs | [Command declarations](../command.go), [bindings](../binding.go), [invocation](../execute.go), and [ADR 0001](adr/0001-command-dispatch-and-inputs.md), refined by [ADR 0011](adr/0011-captured-declarations-and-installation.md) |

View File

@@ -10,7 +10,8 @@ this page inventories the implemented owners.
The root facade owns captured canonical command declarations, application-owned
routing rules, binding conversion, invocation coordination, the per-App
registries, standard help/version commands, and outcome/diagnostic delivery.
registries, standard help/version commands, invocation reporting, and outcome
finalization.
Its dependency graph has no ConfigKit or PromptKit integration dependency.
Applications provide typed callbacks and install routing rules alongside the
complete canonical command tree through the same batch API.
@@ -25,6 +26,30 @@ successful registration invalidates the snapshot for a later invocation. The
root package owns app input and callback execution, not provider configuration
semantics.
Each execution creates one reporter backed by the caller's result and diagnostic
writers. The Cobra adapter returns parsed values, flag spellings, and reporting
flag occurrences. Core reduces those occurrences in entered order after route
resolution, applies the final mode and progress selection, and presents any
collected parser events through that selection. The selected mode therefore
applies to command execution, help, and version; routing and parsing failures
remain visible in every mode.
The reporter owns invocation-scoped visibility, message formatting, managed
report rendering, delivery observations, and finalization. It presents queued
messages after the handler returns, then a failed outcome's explanation, and
closes its handles before `Execute` returns. It records rendering and write
errors separately for result and diagnostic destinations; the returned
`Outcome.Presentation` exposes those observations independently from the
operation cause. The reporter does not retain a transcript and does not close
or flush caller-owned writers.
Internally, one reporting-state mutex guards visibility, queued messages,
destination state, and delivery errors while serializing reporter operations
and writes. Rendering callbacks execute within that coordination boundary, so
they must not recursively submit or render through the same reporter. This is
an implementation detail supporting the callback contract; the mutex does not
coordinate writes made outside the reporter.
## `internal/alias` and `internal/commandpath`
`internal/commandpath` provides shared path parsing and token operations used by

View File

@@ -2,9 +2,11 @@
## Summary
CommandKit v0.3.0 adds application-owned command and flag aliases. This
pre-1.0 minor release also updates the installation declaration type and
Invocation struct shape; review the migration guidance before upgrading.
CommandKit v0.3.0 adds application-owned command and flag aliases and shared
invocation reporting. This pre-1.0 minor release also updates the installation
declaration type and Invocation struct shape, replaces the error-first outcome
and diagnostic-delivery APIs, and reserves reporting flag names; review the
migration guidance before upgrading.
## Changes
@@ -17,6 +19,14 @@ Invocation struct shape; review the migration guidance before upgrading.
`EnteredPath` and `CanonicalPath`.
- Interpret a canonical short value flag such as `-c=` as an explicitly
supplied empty value, consistent with `--color=` and `-c ""`.
- Obtain an invocation-scoped `Reporter` from each handler's `Invocation` for
substantive output, Warning/Info/Progress/Debug messages, deferred messages,
and managed report rendering. The shared `--quiet`, `--verbose`, `--debug`,
and `--progress` flags apply across core and integration commands.
- Return terminal explanation text with `Failure`, `Usage`, or
`Outcome.WithExplanation`; attach an optional operation error separately with
`Outcome.WithCause`. Returned `Outcome.Presentation` records output and
diagnostic delivery observations without changing the operation cause.
## Compatibility and migration
@@ -29,10 +39,26 @@ values whose parameter type is `...Command`, including wrappers assigned from
`New` or `Register`, must update their signature to `...Declaration`.
Adding private route fields to `Invocation` means positional struct literals no
longer compile. Use keyed fields, for example
`commandkit.Invocation{Context: ctx, Streams: streams}`, when constructing an
Invocation outside command execution. CommandKit populates the route fields for
executed handlers.
longer compile. `Invocation` no longer contains `Streams`; `App.Execute` supplies
the reporter and route paths to handlers. `Streams` remains the argument to
`App.Execute`, where the caller owns the result and diagnostic writers.
Handlers that used `invocation.Streams.Out` or `.Err` should use
`invocation.Reporter().Output()` or the categorized reporting methods. Replace
`Failure(err)` and `Usage(err)` with an operator-facing explanation and an
optional `.WithCause(err)`; causes alone are not rendered. Replace
`Outcome.Diagnostics` submissions and terminal `SeverityError` records with
reporter messages and an `Outcome` explanation. Provider-returned
`Acquired.Diagnostics` remain passive data for the owning integration to
submit. Replace `ReportIncomplete`, `DiagnosticDelivery`, and `DeliveryError`
with `Outcome.Presentation.Output` and `.Diagnostics`, whose `DeliveryResult`
contains a state plus first render and write errors.
The long flags `--quiet`, `--verbose`, `--debug`, and `--progress` are reserved
for CommandKit reporting and cannot be declared by applications. Application
aliases may target them. See the current [CLI reporting contract](../cli.md#reporting)
and [consumer migration guide](../consumers/guide.md#invocation-reporting) for
their behavior and API examples.
The consumer minimum remains Go 1.26.0 and the maintainer release toolchain
remains Go 1.26.8. Core v0.3.0 must be published before preparing dependent

View File

@@ -1,6 +1,6 @@
# Invocation reporting implementation plan
Status: Stages 1–18 complete. Stage 19 is next.
Status: Stages 1–19 complete. Stage 20 is next.
## Objective and authority
@@ -719,7 +719,7 @@ on either optional integration module.
## Stage 19: Update canonical documentation and compatibility guidance
Status: pending.
Status: complete.
1. Update public Go declarations/GoDoc for exact reporter, outcome, provider,
lifecycle, cause, and presentation-observation contracts. Describe callback
@@ -749,6 +749,15 @@ Validation: review prose against implemented behavior, check local links and
fragments, and run the Markdown whitespace checks from the developer guide.
Current contract documentation must no longer present the removed APIs as valid.
Completion: updated exported GoDoc, the CLI reference, consumer migration
guidance, README, integration package documentation, developer guide, internal
overview, and the unpublished core v0.3.0 note. Current contracts direct callers
to the invocation reporter and keep operation causes separate from explanations
and presentation observations. Reviewed the prose against the implementation
and selected reporting policy; `go doc` checks, local Markdown link and fragment
checks, and both Markdown whitespace checks pass. Historical integration release
notes, accepted ADRs, and deferred-work records remain unchanged.
## Stage 20: Validate the complete end state and finish
Status: pending.

View File

@@ -12,8 +12,8 @@ diagnostic boundary, fallback explanations, and rendering callback boundary
below are also selected. Architectural preparation is complete:
[ADR 0014](../adr/0014-invocation-reporting.md) is accepted, and the
[architecture policy](../policy/architecture.md) is aligned. Implementation has
not started; implementation, validation, and maintained consumer examples remain.
The [implementation plan](implementation.md) owns stage order and execution
started. Stages 1–19 are complete; final validation remains. The
[implementation plan](implementation.md) owns stage order and execution
instructions.
## Purpose