Document invocation reporting contracts
This commit is contained in:
@@ -8,13 +8,17 @@ modules, so applications choose which upstream libraries they add.
|
||||
The core captures reusable command declarations, validates installation batches
|
||||
before changing an application's registry, converts typed inputs before
|
||||
constructing them, and returns outcomes without exiting the process. Its
|
||||
[consumer guide](docs/consumers/guide.md) covers declaration and invocation.
|
||||
[consumer guide](docs/consumers/guide.md) covers declaration, invocation
|
||||
reporting, and migration. Each handler receives one invocation reporter for
|
||||
substantive output, categorized messages, and terminal explanations; `Execute`
|
||||
returns delivery observations with the outcome. The [CLI reference](docs/cli.md)
|
||||
defines the shared visibility policy.
|
||||
|
||||
## Modules
|
||||
|
||||
| Module | Package | Provides |
|
||||
| --- | --- | --- |
|
||||
| `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` | Typed commands, bindings, execution, outcomes, help, and version. |
|
||||
| `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` | Typed commands, bindings, execution reporting and outcomes, help, and version. |
|
||||
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig` | `ckconfig` | ConfigKit-backed `config` scopes, including configuration source evidence. |
|
||||
| `gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt` | `ckprompt` | PromptKit engine configuration/comparison, five catalogs, named inspection, requirements discovery, concrete request validation, and native smoke tests. |
|
||||
|
||||
|
||||
3
doc.go
3
doc.go
@@ -1,6 +1,7 @@
|
||||
// Package commandkit provides the core instance-owned CLI dispatcher, typed
|
||||
// command declarations, application-owned command and flag aliases, input
|
||||
// bindings, and invocation outcomes. New and App.Register accept canonical
|
||||
// bindings, invocation-scoped reporting, and outcomes with separate operation
|
||||
// causes and presentation observations. New and App.Register accept canonical
|
||||
// Command declarations and RoutingRule declarations together. Optional
|
||||
// integration modules provide ConfigKit and PromptKit-specific commands;
|
||||
// importing those modules does not install their commands.
|
||||
|
||||
112
docs/cli.md
112
docs/cli.md
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,10 +14,11 @@ import (
|
||||
|
||||
// Execute parses and dispatches one invocation. args excludes the executable
|
||||
// name. Supplied values are converted before command input construction; help
|
||||
// does not construct caller inputs or run acquisition callbacks. CommandKit
|
||||
// writes diagnostics to streams.Err and leaves process exit to the caller. A
|
||||
// nil context returns an operational failure, and nil streams are replaced with
|
||||
// io.Discard.
|
||||
// does not construct caller inputs or run acquisition callbacks. It applies the
|
||||
// parsed reporting selection, finalizes invocation presentation, and returns
|
||||
// the resulting Outcome without exiting the process. Supplied streams remain
|
||||
// caller-owned; nil destinations are replaced with io.Discard. A nil context
|
||||
// returns an operational failure.
|
||||
func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outcome {
|
||||
current := newExecution(a, ctx, streams)
|
||||
if a == nil {
|
||||
|
||||
19
identity.go
19
identity.go
@@ -18,8 +18,11 @@ type Identity struct {
|
||||
Revision string
|
||||
}
|
||||
|
||||
// Streams contains the output destinations associated with a command
|
||||
// invocation.
|
||||
// Streams contains the caller-owned output destinations associated with an
|
||||
// invocation. Out receives substantive results; Err receives messages,
|
||||
// terminal explanations, usage guidance, and CommandKit diagnostics. Nil
|
||||
// destinations are replaced with io.Discard. CommandKit does not close or flush
|
||||
// supplied writers.
|
||||
type Streams struct {
|
||||
// Out receives help and command reports.
|
||||
Out io.Writer
|
||||
@@ -37,10 +40,11 @@ func normalizeStreams(streams Streams) Streams {
|
||||
return streams
|
||||
}
|
||||
|
||||
// Invocation provides execution context and reporting to a command handler.
|
||||
// For command aliases, EnteredPath and CanonicalPath expose the route the
|
||||
// caller used and the selected canonical command path. Both omit the
|
||||
// executable name, flags, and operands.
|
||||
// Invocation provides execution context and invocation-scoped reporting to a
|
||||
// command handler. Execute finalizes its reporter before returning. For command
|
||||
// aliases, EnteredPath and CanonicalPath expose the route the caller used and
|
||||
// the selected canonical command path. Both omit the executable name, flags,
|
||||
// and operands.
|
||||
type Invocation struct {
|
||||
// Context carries caller cancellation and request-scoped values.
|
||||
Context context.Context
|
||||
@@ -50,7 +54,8 @@ type Invocation struct {
|
||||
canonicalPath string
|
||||
}
|
||||
|
||||
// Reporter returns the reporting facility for this invocation.
|
||||
// Reporter returns the reporting facility supplied for this invocation. A
|
||||
// manually constructed Invocation has no reporter.
|
||||
func (inv Invocation) Reporter() *Reporter { return inv.reporter }
|
||||
|
||||
// EnteredPath returns the command path used to invoke the selected command,
|
||||
|
||||
@@ -3,5 +3,7 @@
|
||||
// through the core commandkit package. Applications can install CommandKit
|
||||
// RoutingRule declarations alongside it through commandkit.New or
|
||||
// App.Register. Source reports preserve ConfigKit's destination, placement,
|
||||
// and version-check evidence; importing ckconfig alone installs no commands.
|
||||
// and version-check evidence. Invocation reporting applies CommandKit's shared
|
||||
// visibility and delivery policy; provider warnings remain independent of
|
||||
// failure explanations. Importing ckconfig alone installs no commands.
|
||||
package ckconfig
|
||||
|
||||
@@ -4,5 +4,7 @@
|
||||
// canonical commandkit.Command declarations. Applications can install
|
||||
// commandkit.RoutingRule aliases alongside them through commandkit.New or
|
||||
// App.Register. Importing ckprompt installs neither family; callers compose its
|
||||
// declarations with core and ckconfig explicitly.
|
||||
// declarations with core and ckconfig explicitly. Provider diagnostics remain
|
||||
// passive until the owning command submits them through the invocation
|
||||
// reporter, separately from any terminal explanation.
|
||||
package ckprompt
|
||||
|
||||
58
outcome.go
58
outcome.go
@@ -6,7 +6,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Severity classifies a nonterminal diagnostic.
|
||||
// Severity classifies a nonterminal provider diagnostic. Terminal failure
|
||||
// context belongs in an Outcome explanation instead.
|
||||
type Severity string
|
||||
|
||||
const (
|
||||
@@ -48,13 +49,18 @@ const (
|
||||
DeliveryIncomplete
|
||||
)
|
||||
|
||||
// ErrReporterClosed is returned when a reporting handle is used after invocation finalization.
|
||||
// ErrReporterClosed is returned when a reporting handle is used after
|
||||
// invocation finalization.
|
||||
var ErrReporterClosed = errors.New("reporter is closed")
|
||||
|
||||
// Detail renders optional content to the supplied writer.
|
||||
// Detail renders optional content to the writer supplied by the reporter.
|
||||
// Use that writer only during the callback. A callback must not submit another
|
||||
// message, queue deferred work, or start another rendering operation through
|
||||
// the same invocation's reporter.
|
||||
type Detail func(io.Writer) error
|
||||
|
||||
// Explanation contains terminal content for a failed outcome.
|
||||
// Explanation contains terminal content for a failed outcome. CommandKit
|
||||
// presents it during invocation finalization, including in quiet mode.
|
||||
type Explanation struct {
|
||||
// Message is the literal opening text.
|
||||
Message string
|
||||
@@ -62,7 +68,9 @@ type Explanation struct {
|
||||
Details []Detail
|
||||
}
|
||||
|
||||
// PresentationResult records delivery observations for the two output channels.
|
||||
// PresentationResult records output and diagnostic delivery observations
|
||||
// populated by CommandKit during invocation finalization. It is not a message
|
||||
// submission path or a transcript.
|
||||
type PresentationResult struct {
|
||||
// Output records delivery to the result destination.
|
||||
Output DeliveryResult
|
||||
@@ -71,6 +79,9 @@ type PresentationResult struct {
|
||||
}
|
||||
|
||||
// DeliveryResult records rendering and writing errors for one destination.
|
||||
// The state distinguishes no submitted content, intentionally suppressed
|
||||
// content, completed delivery, and incomplete delivery. RenderError and
|
||||
// WriteError retain the first error of their kind for that destination.
|
||||
type DeliveryResult struct {
|
||||
// State summarizes content delivery to the destination.
|
||||
State DeliveryState
|
||||
@@ -80,7 +91,9 @@ type DeliveryResult struct {
|
||||
WriteError error
|
||||
}
|
||||
|
||||
// Diagnostic is passive nonterminal message data returned by a provider.
|
||||
// Diagnostic is passive nonterminal message data returned by a provider. It
|
||||
// does not emit output or select an invocation outcome; the owning integration
|
||||
// submits it through the invocation reporter.
|
||||
//
|
||||
// Write renders the diagnostic to a supplied writer when the owning integration
|
||||
// submits it through the invocation reporter. A nil Write function has no detail.
|
||||
@@ -91,7 +104,9 @@ type Diagnostic struct {
|
||||
Write Detail
|
||||
}
|
||||
|
||||
// Acquired pairs a provider value with independent diagnostics and failure context.
|
||||
// Acquired pairs a provider value with independent diagnostics and failure
|
||||
// context. The owning command chooses how to submit each diagnostic and whether
|
||||
// to attach the explanation to a failed outcome.
|
||||
type Acquired[T any] struct {
|
||||
// Value is the value returned by the acquisition callback.
|
||||
Value T
|
||||
@@ -101,11 +116,16 @@ type Acquired[T any] struct {
|
||||
Explanation Explanation
|
||||
}
|
||||
|
||||
// Outcome is the classified result of an operation.
|
||||
// Outcome is the classified result of an operation. Constructing or returning
|
||||
// an outcome does not itself write output; Execute presents its explanation
|
||||
// during finalization.
|
||||
//
|
||||
// Cause records an optional operation error without selecting the outcome or
|
||||
// generating operator-facing text. Presentation records delivery observations
|
||||
// separately. Invocation coordination treats a zero Outcome as success with status 0.
|
||||
// separately. Invocation coordination treats a zero Outcome as success with
|
||||
// status 0. Presentation failures change a success or usage outcome to
|
||||
// operational status 1; a domain outcome keeps its classification and nonzero
|
||||
// status, while a zero domain status becomes 1.
|
||||
type Outcome struct {
|
||||
// Kind classifies the result.
|
||||
Kind OutcomeKind
|
||||
@@ -126,28 +146,38 @@ func Success() Outcome {
|
||||
return Outcome{Kind: OutcomeSuccess}
|
||||
}
|
||||
|
||||
// Failure returns an operational outcome with status 1 and a terminal explanation.
|
||||
// Failure returns an operational outcome with status 1 and a terminal
|
||||
// explanation. The message and details are presented once when the outcome is
|
||||
// returned from an invocation; an optional operation error can be attached with
|
||||
// WithCause and is not rendered automatically.
|
||||
func Failure(message string, details ...Detail) Outcome {
|
||||
return Outcome{Kind: OutcomeOperational, Status: 1, Explanation: Explanation{Message: message, Details: cloneDetails(details)}}
|
||||
}
|
||||
|
||||
// Usage returns a usage outcome with status 2 and a terminal explanation.
|
||||
// Usage returns a usage outcome with status 2 and a terminal explanation. The
|
||||
// message and details are presented when the outcome is returned from an
|
||||
// invocation; an optional cause is preserved separately with WithCause.
|
||||
func Usage(message string, details ...Detail) Outcome {
|
||||
return Outcome{Kind: OutcomeUsage, Status: 2, Explanation: Explanation{Message: message, Details: cloneDetails(details)}}
|
||||
}
|
||||
|
||||
// Domain returns an application-domain outcome with the supplied name and status.
|
||||
// Domain returns an application-domain outcome with the supplied name and
|
||||
// status. The application owns the classification and status decision.
|
||||
func Domain(name string, status int) Outcome {
|
||||
return Outcome{Kind: OutcomeDomain, Status: status, Domain: name}
|
||||
}
|
||||
|
||||
// WithExplanation returns a copy of the outcome with the supplied terminal explanation.
|
||||
// WithExplanation returns a copy of the outcome with the supplied terminal
|
||||
// explanation, replacing any existing explanation. CommandKit presents it once
|
||||
// during finalization, including in quiet mode.
|
||||
func (o Outcome) WithExplanation(message string, details ...Detail) Outcome {
|
||||
o.Explanation = Explanation{Message: message, Details: cloneDetails(details)}
|
||||
return o
|
||||
}
|
||||
|
||||
// WithCause returns a copy of the outcome with the supplied optional cause.
|
||||
// WithCause returns a copy of the outcome with the supplied optional operation
|
||||
// cause. The cause is preserved for caller inspection and does not select an
|
||||
// outcome or generate operator-facing text.
|
||||
func (o Outcome) WithCause(cause error) Outcome {
|
||||
o.Cause = cause
|
||||
return o
|
||||
|
||||
53
reporter.go
53
reporter.go
@@ -60,16 +60,24 @@ type deferredMessage struct {
|
||||
details []Detail
|
||||
}
|
||||
|
||||
// Reporter coordinates presentation and delivery for one invocation.
|
||||
// Reporter coordinates presentation and delivery for one invocation. The
|
||||
// invocation supplies it; consumers do not construct or finalize it. Reporting
|
||||
// operations on one reporter are safe concurrently, although concurrent
|
||||
// submissions have no predetermined order. Rendering callbacks must not
|
||||
// reenter the same reporter, and their supplied writer is valid only while the
|
||||
// callback runs.
|
||||
//
|
||||
// The invocation supplies the reporter. Consumers use Output for substantive
|
||||
// command results and the reporting methods for categorized messages.
|
||||
// Consumers use Output for substantive command results and the reporting
|
||||
// methods for categorized messages. Delivery and rendering failures are
|
||||
// retained for Outcome.Presentation even when a caller ignores an immediate
|
||||
// method's returned error.
|
||||
type Reporter struct {
|
||||
state *reportingState
|
||||
}
|
||||
|
||||
// DeferredReporter queues messages for delivery during invocation finalization.
|
||||
// Copies share the same invocation reporting state.
|
||||
// Copies share the same invocation reporting state. Submissions made after
|
||||
// finalization return ErrReporterClosed.
|
||||
type DeferredReporter struct {
|
||||
state *reportingState
|
||||
}
|
||||
@@ -126,7 +134,10 @@ func (r *Reporter) applySelection(selection commandmeta.ReportingSelection) {
|
||||
state.progressSet = selection.ProgressSet
|
||||
}
|
||||
|
||||
// Output returns a managed writer for substantive command results.
|
||||
// Output returns a managed writer for substantive command results. Writes are
|
||||
// serialized with other operations through this reporter, and failures are
|
||||
// recorded in Outcome.Presentation. The writer returns ErrReporterClosed after
|
||||
// invocation finalization.
|
||||
func (r *Reporter) Output() io.Writer {
|
||||
if r == nil || r.state == nil {
|
||||
return managedOutputWriter{}
|
||||
@@ -142,6 +153,10 @@ func (w managedOutputWriter) Write(p []byte) (int, error) {
|
||||
}
|
||||
|
||||
// RenderOutput coordinates a complete substantive report rendering operation.
|
||||
// The callback receives a writer usable only during the callback and must not
|
||||
// reenter this reporter. In quiet mode, the callback still runs against a
|
||||
// discard destination so rendering failures are retained without writing to
|
||||
// the caller's output destination.
|
||||
func (r *Reporter) RenderOutput(write func(io.Writer) error) error {
|
||||
if r == nil || r.state == nil {
|
||||
return ErrReporterClosed
|
||||
@@ -168,27 +183,33 @@ func (r *Reporter) RenderOutput(write func(io.Writer) error) error {
|
||||
return finishRenderLocked(state, destinationOutput, renderErr, writer.writeError)
|
||||
}
|
||||
|
||||
// Info submits an informational message.
|
||||
// Info submits an informational message to the diagnostic destination. Its
|
||||
// visibility follows the selected reporting mode.
|
||||
func (r *Reporter) Info(message string, details ...Detail) error {
|
||||
return r.submitMessage(categoryInfo, message, details)
|
||||
}
|
||||
|
||||
// Warn submits a warning message.
|
||||
// Warn submits a warning message to the diagnostic destination. Its visibility
|
||||
// follows the selected reporting mode.
|
||||
func (r *Reporter) Warn(message string, details ...Detail) error {
|
||||
return r.submitMessage(categoryWarning, message, details)
|
||||
}
|
||||
|
||||
// Debug submits a debug message.
|
||||
// Debug submits a debug message to the diagnostic destination. Its visibility
|
||||
// follows the selected reporting mode.
|
||||
func (r *Reporter) Debug(message string, details ...Detail) error {
|
||||
return r.submitMessage(categoryDebug, message, details)
|
||||
}
|
||||
|
||||
// Progress submits a progress message.
|
||||
// Progress submits a progress message to the diagnostic destination. Its
|
||||
// visibility follows the selected mode and explicit progress setting.
|
||||
func (r *Reporter) Progress(message string, details ...Detail) error {
|
||||
return r.submitMessage(categoryProgress, message, details)
|
||||
}
|
||||
|
||||
// AtEnd returns a view for messages delivered after the handler returns.
|
||||
// AtEnd returns a view for messages delivered after the handler returns and
|
||||
// before terminal explanation delivery. The deferred view shares this
|
||||
// invocation's state.
|
||||
func (r *Reporter) AtEnd() DeferredReporter {
|
||||
if r == nil {
|
||||
return DeferredReporter{}
|
||||
@@ -196,17 +217,23 @@ func (r *Reporter) AtEnd() DeferredReporter {
|
||||
return DeferredReporter{state: r.state}
|
||||
}
|
||||
|
||||
// Info queues an informational message for invocation finalization.
|
||||
// Info queues an informational message for invocation finalization. The
|
||||
// submission error is returned immediately; rendering and delivery errors are
|
||||
// reflected in the final outcome.
|
||||
func (r DeferredReporter) Info(message string, details ...Detail) error {
|
||||
return r.enqueue(categoryInfo, message, details)
|
||||
}
|
||||
|
||||
// Warn queues a warning message for invocation finalization.
|
||||
// Warn queues a warning message for invocation finalization. The submission
|
||||
// error is returned immediately; rendering and delivery errors are reflected in
|
||||
// the final outcome.
|
||||
func (r DeferredReporter) Warn(message string, details ...Detail) error {
|
||||
return r.enqueue(categoryWarning, message, details)
|
||||
}
|
||||
|
||||
// Debug queues a debug message for invocation finalization.
|
||||
// Debug queues a debug message for invocation finalization. The submission
|
||||
// error is returned immediately; rendering and delivery errors are reflected in
|
||||
// the final outcome.
|
||||
func (r DeferredReporter) Debug(message string, details ...Detail) error {
|
||||
return r.enqueue(categoryDebug, message, details)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user