Retire completed shell completion roadmaps

This commit is contained in:
2026-11-03 14:47:36 +00:00
parent 0acbbf45ad
commit 3934038b98
4 changed files with 3 additions and 1281 deletions

View File

@@ -1,187 +0,0 @@
# Shell completion
Status: the selected initial scope is implemented and validated. Canonical
suggestions and alias-aware interpretation are available for
Bash, Zsh, and Fish through explicitly installed completion support.
## Purpose
Provide useful shell completion for core-only applications and applications with
optional integrations. Reuse installed command declarations and CommandKit's
routing rules so consumers need little completion-specific wiring.
Completion helps an operator construct an invocation. It does not execute the
selected application command. Preserve application ownership of runtime behavior
and upstream ownership of configuration and prompt semantics.
## Planning baseline
Reviewed baseline: `2301872` (`Stop flag interpretation at unknown flags`).
- Core captures declarations and builds a routing snapshot containing canonical
commands and separate application aliases.
- [Neutral metadata](../../internal/commandmeta/metadata.go) describes command
names, descriptions, flags, boolean/value distinctions, repetition, required
flags, and operand cardinality. It has no completion choices or path hints.
- The [Cobra adapter](../../internal/cobra/parse.go) constructs a backend tree
for parsing and help. CommandKit owns execution and presentation outside that
tree; enabling Cobra's default completion command alone is insufficient.
- [Alias routing](../../route.go) resolves complete command paths and coordinates
canonical parsing with entered spellings. Completion must also handle partial
words and an empty current word.
- Invocation reporting controls ordinary output and diagnostics. Shell completion
queries need machine-readable replies independent of flags in the unfinished
command line.
- The core module uses Cobra v1.10.1 and pflag v1.0.9. Cobra supplies script
generators and completion callbacks for Bash, Zsh, Fish, and PowerShell.
See its [versioned completion documentation](https://github.com/spf13/cobra/blob/v1.10.1/site/content/completions/_index.md).
[ADR 0013](../adr/0013-application-alias-routing.md) owns the separation between
alias routes and canonical commands. [ADR 0014](../adr/0014-invocation-reporting.md)
owns invocation reporting. This roadmap defines completion within those
boundaries; it does not establish a replacement command model.
## Target experience
An operator generates and loads a shell integration script, then receives
suggestions for installed commands, applicable canonical flags, and declared
value choices or paths. The selected script-generation grammar is:
```text
app completion bash
app completion zsh
app completion fish
```
Consumers explicitly install a core completion declaration through the existing
installation API. The completion namespace is reserved only when installed.
Keep the machine query hidden from ordinary help, with collision validation for
its selected path. Installation must not replace an application-owned command.
Script generation writes to the caller-selected result stream. Shell setup
instructions explain loading the script; automatic modification of shell startup
files is outside the initial implementation.
The initial MVP suggests canonical command names and flag spellings. Completed aliases
already entered by the operator are interpreted to identify the canonical target.
For example, with `jobs` routing to `workspace jobs`, completing after
`app jobs ` can suggest the canonical children of `workspace jobs`.
Completion should preserve the text the operator has already entered.
Matching partial canonical names, resolving completed aliases, and interpreting
an alias-only discovery prefix are distinct cases. Initially, an alias-only
prefix with no canonical target offers no command continuations. For example,
if `legacy inspect` is an alias but `legacy` has no canonical target,
`app legacy <TAB>` offers no command continuations. After the complete alias is
entered, completion uses its canonical target. Applicable flag suggestions can
still follow the existing discovery context.
Suggested continuations must form a valid next step when inserted into the
entered route. Do not append canonical words that would create an invalid route.
Alias discovery may be omitted from the initial MVP; this is a scope choice,
not a permanent limitation of CommandKit. Suggesting alias names or alias-path
continuations remains [potential future work](deferred.md#alias-completion-and-discovery).
### Flag suggestions
Initially suppress already supplied singleton flags from flag-name suggestions.
Canonical and aliased occurrences count as the same flag. Keep repeatable flags
available. Keep all four reporting flags (`--quiet`, `--verbose`, `--debug`, and
`--progress`) available because later occurrences can intentionally change or
reset their settings.
Suggestion filtering leaves accepted syntax unchanged. Operators can still type
singleton flags again under the existing repetition rules. If an operator has
already entered a repeated flag, continue completing its value according to its
hint even when its name would have been omitted from suggestions.
## Selected initial scope
Provide canonical command and flag suggestions, descriptions where supported by
the selected shells, alias-aware interpretation, static value choices, and
file/directory hints. Apply the same declaration facilities to application-owned
commands and optional integrations.
Support Bash, Zsh, and Fish using the existing backend generators, with validation
in each shell. PowerShell is outside the initial supported shell set because
there is no current consumer need; this does not preclude future support.
Static suggestions should come from captured metadata. Completion should not
invoke ordinary handlers, input/state factories, or acquisition callbacks to
obtain it. Application startup and declaration-factory effects remain owned by
the consumer, just as they do for help.
Dynamic discovery of configured prompt names, remote resources, or other live
values is a possible later extension. Deferring it avoids selecting provider
lifetimes and latency behavior before a concrete consumer need; it is not a
permanent prohibition on dynamic completion.
## Ownership and implementation approach
Core owns completion context, alias interpretation, and neutral suggestions.
The internal backend adapter owns Cobra-specific types, directives, script
emission, and protocol details. Prefer the existing backend facilities where
compatible with CommandKit's grammar. Verify the adapter approach with a small
end-to-end prototype before fixing private interfaces.
A completion request identifies completed words and the current partial word,
including an empty word after a space. Interpret only what is needed to locate
suggestions. Ordinary required-value validation and input conversion must not
reject a partially constructed invocation before completion can help fill it.
Reuse token and flag interpretation helpers where practical without changing
normal execution grammar or treating incomplete input as an executable command.
Account for command-local flags, aliases, consumed values, `--`, attached values,
boolean assignments, short clusters, repeatable flags, and operand positions.
Keep provenance sufficient to return a replacement for the word being edited.
Completion candidates are suggestions, not an additional input validator.
When both an operand and a flag are valid, use the operand's hint for an
empty current word after a space. Offer flag names when the operator starts
the word with `-` in a context where flags are accepted.
Value choices and path hints belong in CommandKit-owned public metadata. Carry
them through declaration capture and neutral metadata to the adapter. Arbitrary
converter functions cannot reliably reveal their accepted values. Adding a list
of suggestions must not silently restrict the values a converter accepts.
Provide declarative static choices with optional descriptions, file/directory
hints, and an explicit way to request no value suggestions. Unannotated values
default to no value suggestions; applicable commands and flags still complete.
Filename completion requires a path hint. This keeps unrelated filenames out of
identifier and numeric completions. The implementation plan specifies how hints
fit into existing binding declarations.
Initially use one hint strategy per value position: static choices, files,
directories, or no value suggestions. Optional choice descriptions belong to the
static-choice strategy. Combined choices and filesystem suggestions can be
considered later if consumers need them; the initial API need not define their
merging or deduplication behavior.
Verify the same strategy works for separated and attached flag values, such as
`app serve --config ./par<TAB>` and `app serve --config=./par<TAB>`. For the
attached form, preserve `--config=` and complete its value correctly. Validate
the resulting command line in each supported shell.
Keep ordinary script generation under the existing reporting policy. A hidden
shell query is a protocol exchange: flags in the command being completed are
request data, so `--quiet` must not erase its reply and `--debug` must not append
ordinary diagnostics to it. When an unrecognized or incomplete command line
leaves no inferable completion context, return a valid empty reply that disables filename fallback. Reserve
protocol errors for failures in the query itself. Specify their wire behavior
and output ownership in the implementation plan. A completion request must not
accidentally run the target command.
Hidden protocol names are implementation details unless deliberately published.
## Delivery plan
The [implementation plan](implementation.md) owns the sprint sequence, API and
protocol design, and validation steps. The target includes behavioral coverage
and shell-level validation through the public consumer path in Bash, Zsh, and
Fish.
## Deferred scope
Alias discovery remains a potential extension beyond the MVP. Dynamic discovery
remains deferred until a concrete consumer need justifies its callback,
cancellation, error-handling, and provider-lifetime design.

View File

@@ -27,7 +27,7 @@ relevant ADRs.
### Alias completion and discovery
The [shell-completion roadmap](completion.md) selects an initial MVP with
The delivered [shell completion feature](../cli.md#shell-completion) provides
canonical suggestions and alias-aware interpretation. Potential future work
includes suggesting command and flag alias spellings and valid continuations of
alias-only prefixes. For example, completion could suggest `inspect` after
@@ -45,9 +45,8 @@ These capabilities remain outside the delivered scope and require a concrete
operator or consumer need and an appropriate ownership/behavior decision:
- JSON reports and output-file management or reloadable export.
- Shell-completion extensions beyond the initial scope in the
[completion roadmap](completion.md), including alias discovery described above
and dynamic discovery of configured or remote values.
- Shell completion extensions, including alias discovery described above and
dynamic discovery of configured or remote values.
- Configuration mutation, generic value assignment, and exhaustive field-origin
inventories.
- Credential diagnostics beyond PromptKit's native smoke checks and additional

View File

@@ -1,345 +0,0 @@
# Shell completion implementation plan
Status: Stages 1–8 complete.
Implement the [shell completion roadmap](completion.md) from baseline
`2301872`. Follow the stages in order in one sprint. Intermediate stages may
leave unfinished wiring or a noncompiling tree; validate the assembled result
before calling the sprint complete. Use the existing ownership boundaries in
[ADR 0013](../adr/0013-application-alias-routing.md),
[ADR 0014](../adr/0014-invocation-reporting.md), and the
[architecture policy](../policy/architecture.md). Apply the
[testing policy](../policy/testing.md) to integration assertions. Keep protocol
names, candidate ordering, and backend mechanics private.
## Stage 1: Prove the shell transport
Status: complete.
1. Use the Cobra version pinned in the root module. In a disposable prototype,
generate scripts with `GenBashCompletionV2(w, true)`,
`GenZshCompletion(w)`, and `GenFishCompletion(w, true)`. Give the generator
a minimal root whose name is the installed executable basename. Do not
install Cobra's completion command or execute its completion parser.
2. Supply a fixture executable that handles the generated scripts' hidden
request (`__complete` with these generator settings). Verify the actual
arguments and replies using each script in its shell. The request carries
the words through the cursor, including the partial word as its last
argument; preserve an explicitly empty last argument. Check what each
script does with the prefix of an attached value before deciding how to
encode its replacement. Cover `--config=./par`, separated path values,
spaces, an empty no-file reply, directory-only completion, and a partial
short cluster. Verify the edited line, not only the printed candidate list.
3. Use Cobra's line protocol: candidate lines with optional tab-separated
descriptions, followed by the colon-prefixed directive integer and newline.
Use the adapter's Cobra directive constants, not hardcoded numbers in core.
Map command/flag/static choices and no candidates to NoFileComp, file hints
to native file completion, and directory hints to FilterDirs. If a generated
script cannot express the selected behavior as emitted, make the smallest
adapter-owned script adjustment that preserves it and add a shell regression.
In particular, verify attached-value prefix reconstruction and Fish's
filesystem handling. Do not narrow the supported contexts to fit a script.
4. Record the observed argument and replacement conventions in the sprint
evidence. Carry forward only the adapter and fixtures needed for the final
implementation; do not commit disposable prototype machinery.
Completion: generated scripts using Cobra v1.10.1 and exercised them through
interactive Bash 5.3.9, Zsh 5.9, and Fish 4.2.1 with a fixture that records the
query and the executed edited line. In the tested attached-value cases the
query includes the entered flag and value; a static candidate contains the value
alone, and the shell script preserves the flag prefix. Explicit empty words,
quoted prior arguments, short clusters, file and directory hints, and no-file
replies were exercised. Zsh's generated word splitting lost the attached flag
context when a path contained an escaped space. Fish split quoted prior words,
passed escapes in current words, and treated directory-only replies as ordinary
file completion. The adapter adjusts those scripts and preserves the first `=`
as the flag/value separator. The opt-in `TestCompletionShells` regression checks
actual edited lines for these cases. Run it with Bash, Zsh, Fish, Python pexpect,
and Bash completion installed using `COMMANDKIT_COMPLETION_SHELLS=1
GOWORK=off GOTOOLCHAIN=local go test -count=1 -run '^TestCompletionShells$'
./internal/cobra`. Zsh and Fish were extracted into a disposable directory for
this run; the focused adapter suite and the full interactive matrix pass.
## Stage 2: Capture completion hints
Status: complete.
1. Add the following public API in the root package. Add
`Completion CompletionHint` to both `FlagMeta` and `ArgumentMeta`;
existing binding constructors and their signatures stay in place.
```go
type CompletionChoice struct {
Value string
Description string
}
type CompletionHint struct { /* private representation */ }
func CompleteChoices(choices ...CompletionChoice) CompletionHint
func CompleteFiles() CompletionHint
func CompleteDirectories() CompletionHint
func NoCompletion() CompletionHint
```
2. Represent the four strategies as a private discriminator and choice list.
The zero hint and `NoCompletion()` are equivalent. Copy choice slices
when constructing the hint. Capture the hint through ordinary bindings,
repeatable bindings, and `CaptureFlags`; bridge it through
`bridgeFlags` and `bridgeArguments` to neutral metadata. Use neutral
strategy and choice types in `internal/commandmeta`; backend types do not
cross the adapter boundary.
3. Keep hints independent of binding identity, requiredness, defaults,
repetition, and converters. Static choices are literal prefix matches and
do not constrain accepted values. Empty choices mean no suggestions, not
file fallback. Use ordinary boolean literals `true` and `false` for
the built-in boolean assignment case; an application boolean binding
without a hint has no value suggestions.
4. Verify caller mutation of the original choice slice cannot change captured
suggestions, repeated/captured bindings keep hints, and values absent from
a suggestion list still reach the existing converter.
Completion: added the public choice and hint constructors, carried hints through
flag and operand bindings into neutral metadata, and supplied explicit boolean
choices for built-in help and reporting flags. Focused tests cover copied
choices, repeated and captured bindings, the empty-choice strategy, independent
bridge copies, and execution of values outside the suggested choices. Root,
internal adapter, and neutral metadata package tests pass with `GOWORK=off` and
`GOTOOLCHAIN=local`.
## Stage 3: Install completion as an ordinary declaration
Status: complete.
1. Add `func Completion(executable string) Command`. The parameter is the
executable basename the operator types, independent of the human-readable
`Identity.Name`. Use a root-only `completion` group with three runnable
children, `bash`, `zsh`, and `fish`. Use the existing declaration and
registration API (`New(..., Completion("app"))` or
`app.Register(Completion("app"))`). Keep script-specific name handling
inside completion; do not change validation of ordinary application identity.
2. Carry a private completion-installation marker with the declaration and
derive query availability from the committed routing snapshot. Use the
existing registration candidate to validate collisions in either order,
including aliases and same-batch declarations. Reserve the query's first
token only while installed: a canonical command or alias beginning with
that token would be shadowed by raw query dispatch. Reuse existing
command/alias collision rules for `completion`. Do not represent the
query as a canonical node, alias target, or public help entry.
3. Generate scripts in the internal adapter using the transport established
in Stage 1. Surface generator errors through ordinary presentation.
Use `Reporter.RenderOutput` for script generation and delivery, so
`app completion bash --quiet` follows normal reporting. Document the
basename expected by the generated scripts; handle unsupported generator
names at the completion boundary.
4. Verify opt-in behavior, root-only placement, help visibility, batch
rollback, late installation, and absence of new reserved names for an app
that does not install completion.
Completion: added the root-only completion declaration and three script
commands, with generator output delivered through the invocation reporter.
Registration validates hidden-query collisions in either order, and the routing
snapshot reflects committed installation. Tests cover help, quiet output,
delivery failure, invalid basenames, opt-in behavior, collisions, rollback,
nesting, and late installation. Fish variable names are adjusted to support
dotted executable basenames; interactive checks of those names pass in Bash,
Zsh, and Fish. Root and adapter package tests pass with `GOWORK=off` and
`GOTOOLCHAIN=local`.
## Stage 4: Resolve the entered command context
Status: complete.
1. Add a private core completion request that separates completed words from
the current word. Call `resolveEnteredRoute` on completed words only;
use the snapshot's canonical metadata, resolved flag-alias context, and
discovery context. Keep entered words unchanged. For canonical routes
with a runnable operand owner, use the existing canonical prefix length
to distinguish its trailing operands/options from command words.
2. Suggest canonical child names matching the partial word while the input
is still in command-path position. At a node with both children and
operands, matching children take precedence; if there are no matching
children, use the operand context when available. Check insertion against
normal routing: resolve completed entered words plus the candidate and
retain it only if it reaches the intended canonical child. Do not treat a
partial alias as already entered. After a flag or operand has ended the
command path, do not suggest children.
3. An alias-only discovery prefix provides no command continuations, but can
supply applicable canonical flags using discovery metadata and alias
mappings. Unknown routes with no valid context yield no candidates.
At root, use the installed visible commands, including built-ins and
integrations. Exclude the protocol endpoint everywhere.
4. Add context tests for root/group/runnable paths, a partial canonical
child, a complete alias and alias chain, an alias-only prefix, a canonical
child shadowed in the entered route, and flags beginning before the
complete command path. Use existing routing rules as the oracle.
Completion: added private request context resolution using completed words and
the committed routing snapshot. Canonical child candidates are checked by
resolving the entered route with the candidate inserted; discovery-only and
unknown contexts do not invent command continuations. Tests cover canonical
and aliased routes, chains, scoped and discovery flag aliases, shadowed
children, runnable operand metadata, and paths ended by flags or operands.
The root package suite passes with `GOWORK=off` and `GOTOOLCHAIN=local`.
## Stage 5: Interpret flags and locate value positions
Status: complete.
1. Walk the completed tail using the same canonical and alias spellings and
flag token rules as normal parsing. Factor shared token classification
from `internal/cobra/flags.go` into neutral helpers where needed; keep
backend parsing and failure wording at their current owner. The completion
walk records supplied canonical flags, consumed operands, whether options
ended, and any pending value flag. It does not run Cobra dispatch,
converters, required checks, input factories, or providers.
2. A pending value takes precedence even if the current word starts with
a dash. For long equals forms, explicit boolean assignments, and short
value flags in clusters, separate the value prefix from the entered
spelling and separator. Preserve that spelling, including an alias.
For a partially typed short cluster still accepting flag members, use its
canonical short spellings for continuations; do not rewrite earlier members.
A standalone `--` ends flags unless consumed as a value. After it,
dash-looking words are operands. An unknown completed option that makes
further interpretation ambiguous yields no suggestions.
3. For flag-name suggestions, match canonical spellings and suppress already
supplied singletons by canonical identity, including occurrences through
aliases. Keep repeatable flags and the four reporting flags eligible.
When a repeated singleton is typed anyway, still complete its value.
4. For an operand, use the same cardinality allocation as `allocateOperands`
in `binding.go` without required validation: include the current word
as a placeholder, determine which binding receives that final position,
and use its hint. Extract the cardinality calculation if necessary to avoid
a second allocation policy. Excess operands receive no suggestions.
When both operand and flag positions are available, an empty word uses
the operand hint and an option-looking partial word requests flags.
5. Test pending/attached/empty values, short clusters, scoped aliases and
unavailable targets, repeats, booleans, option-looking values, interspersed
operands, terminators, and optional/repeated operands before a required
suffix. Keep existing normal execution tests green at the sprint boundary.
Completion: shared neutral token classification and flag lookup with normal
parsing, and extracted operand count allocation for execution and completion.
The completion walk records consumed flags and operands, locates pending or
attached values, preserves entered prefixes, and filters canonical flag names
by supplied identity. Tests cover aliases, short clusters, repeated flags,
boolean assignments, option-looking values, terminators, interspersed operands,
and operand cardinality. Root, adapter, and neutral metadata package tests pass
with `GOWORK=off` and `GOTOOLCHAIN=local`.
## Stage 6: Assemble candidates and deliver query replies
Status: complete.
1. Produce a neutral result of candidate replacements, optional descriptions,
or a filesystem strategy with value context. Keep prefix preservation
distinct from matching; the adapter translates the neutral replacement
according to the shell conventions verified in Stage 1. Do not interpret
choice values as shell source. Normalize description line breaks and omit
candidates the line protocol cannot represent rather than changing what
an inserted value means. Do not add execution-time validation for these
presentation limitations.
2. In `Execute`, after normal receiver/context checks and before ordinary
routing, recognize the exact raw query token only on an installed snapshot.
Treat all following tokens as data. Split off the final word; a missing
current-word argument is a malformed transport request, distinct from an
empty string. Use the current snapshot on each query, including after
later registration. Do not enter normal parsing, reporting selection,
input preparation, or handler execution for this path.
3. Write the adapter response only to normalized `Streams.Out`, with
managed output delivery in ordinary mode. A valid query with no inferable
context returns success and a NoFileComp reply. For a malformed request
or query construction failure, return an operational outcome and, if the
output is still usable, emit an error directive with no candidates.
Explanations go to diagnostics, never into the reply. Preserve write
failure evidence through normal presentation bookkeeping; do not retry
a broken output stream. No generated script should consume ordinary
usage guidance or debug output as a candidate.
4. Test query dispatch via `App.Execute`, including explicit empty last word,
no-context reply, malformed request, writer failure, hidden discovery,
later registration, and target words containing `--quiet` and `--debug`.
Use counter fixtures to prove target handlers, converters, factories,
and providers are untouched. Verify no special dispatch without installation.
Completion: connected the installed query before normal routing and reporting
selection, assembled neutral candidates and filesystem strategies, and routed
protocol output through managed reporting. Invalid transport requests return an
error directive and a separate diagnostic; write failures remain presentation
evidence. The adapter preserves short attached prefixes through a private cue
consumed by the generated scripts, while retaining literal choice values and
normalizing descriptions. Query tests cover empty and malformed requests,
aliases and discovery, later registration, callback isolation, reporting flags
as data, and writer failure. Root and adapter package suites and the full
interactive Bash, Zsh, and Fish transport matrix pass.
## Stage 7: Annotate maintained commands and examples
Status: complete.
1. Annotate static format/scope flags where their choices already come from
fixed command metadata or literals. Use the same values as the existing
version, config, and PromptKit command implementations; omit scope hints
that would advertise values not supported by that installed declaration.
Mark existing config/overlay filename flags as files where the binding
takes a filename. Leave prompt IDs, dynamic selectors, and composite
assignments unannotated. Do not infer domain semantics or load providers.
2. Add explicit installation to the maintained core and composed examples.
Demonstrate a static choice, a path hint, and an already-entered alias
with no new runtime dependency. Include an integration-level check
that installed hints reach completion without acquisition.
3. Update GoDoc, the CLI reference, and consumer/example instructions with
actual syntax, basename selection, manual script loading for each shell,
and examples of hints. Keep the wire protocol private. Once delivered,
update the feature status and keep alias discovery and dynamic discovery
in deferred work.
Completion: annotated built-in version, configuration, and PromptKit formats,
supported configuration scopes, and filename selections. The core and composed
examples install completion and demonstrate choices, paths, and entered
aliases. Integration tests verify scope availability and completion without
input, state, acquisition, comparison, engine, or client callbacks. GoDoc, CLI,
consumer, example, and roadmap documentation now describe the delivered scope;
alias and dynamic discovery remain deferred. Root and both integration package
tests pass in the local workspace, and changed Markdown links and whitespace
checks pass.
## Stage 8: Validate the assembled sprint
Status: complete.
1. Exercise the generated scripts in Bash, Zsh, and Fish using a built fixture
executable, a disposable directory with filenames and directories containing
spaces, and isolated shell configuration. Verify actual completion output
and replacement behavior. Cover canonical commands, an entered alias,
static choices and descriptions, no fallback, file/directory hints in
separated and attached values, and the empty operand/leading-dash rule.
Use a PTY or a shell's native completion test interface where appropriate.
Syntax checking alone is insufficient. Record shell versions and the
commands or harness used so the evidence is reproducible.
2. Run the root and both optional module suites under the local workspace
procedure in [development.md](../development.md), plus the root suite
with `GOWORK=off`. Keep shell-dependent checks outside the default Go
suite unless the existing environment provides those shells; an absent
shell is missing acceptance evidence, not a passing check.
3. Verify no backend types in public declarations, no reverse optional-module
dependency, and no changes to ordinary routing, flag acceptance, or report
behavior outside the selected completion scope. Review documentation links,
formatting, and the final diff. Record the actual validation results.
Completion: the opt-in `TestCompletionConsumerInShells` builds a CommandKit
fixture, generates its scripts, and verifies edited invocations and displayed
descriptions in interactive Bash 5.3.9, Zsh 5.9, and Fish 4.2.1. The fixture
uses isolated shell configuration and paths containing spaces. Run it with
Python pexpect, Bash completion, and the three shells available using
`COMMANDKIT_COMPLETION_SHELLS=1 GOTOOLCHAIN=local go test -count=1 -run
'^TestCompletionConsumerInShells$' .`. Zsh and Fish were extracted to a
disposable directory for this run; their executable, library, Zsh module, and
function paths were supplied through `PATH`, `LD_LIBRARY_PATH`,
`COMPLETION_ZSH_MODULE_PATH`, and `COMPLETION_ZSH_FUNCTION_ROOT`, respectively.
The adapter's interactive
transport matrix and additional short-equals and Unicode-basename checks also
pass in all three shells. The root and both optional module suites pass in the
local workspace, as does the root suite with `GOWORK=off`. Public GoDoc and
dependency checks show no exposed backend types or reverse optional-module
dependency. Existing execution suites, changed documentation links, formatting,
and diff whitespace checks pass. The local workspace file is ignored.

View File

@@ -1,745 +0,0 @@
# Shell completion audit remediation
Status: all eight shell completion findings addressed. The completed
invocation reporting audit is retained below as historical evidence.
## Completion review scope
Reviewed `10a799a..0450ecf` against the [completion roadmap](completion.md)
and [implementation plan](implementation.md). Core captures static hints,
resolves entered routes against the installed snapshot, and chooses candidates
without dispatching the target. The internal Cobra adapter generates the scripts
and translates replies; optional modules contribute hints through core metadata.
The opt-in installation, hidden dispatch, and reporting boundary are in place.
The principal gaps are in the transport and its public compatibility effects.
Evidence includes source review, a temporary public consumer using the maintained
fixture's shape, interactive shell reproductions, and a compile probe. No
implementation changes were made in this review.
### 1. Keep shell completion data out of Zsh evaluation
Priority: P1. Status: complete.
- [x] Completed: Bash and Zsh pass literal query argv without evaluation; Zsh uses direct argument-safe completion calls. Quoted and escaped Tab-only regressions and the existing Bash/Zsh adapter matrices pass.
[Zsh script adjustment](../../internal/cobra/completion.go#L146) leaves the
generator's `eval _describe ... $flagPrefix` in place. The generator builds
`flagPrefix` from a regex match on the current word. Thus a quoted literal value
can become executable shell source when the operator presses Tab. In an
interactive Zsh session with the generated script, typing
`probe run --format '-$(touch AUDIT_TAB_ONLY)=x'` and pressing Tab, **without
pressing Enter**, created the sentinel in a disposable directory. The value was
being entered for a separate flag, so it did not even have an attached prefix.
Replace evaluation of word-derived text with argument-safe calls. Pass prefix
options as separate arguments and use the actual parsed value context. Review
other retained script evaluation paths for the same data/code boundary, including
query argv construction; preserve ordinary shell completion semantics without
executing literal data. Add an interactive regression that presses Tab without
Enter and verifies no side effect, with quoted and escaped input.
### 2. Use parser-derived replacement context in every shell
Priority: P2. Status: complete.
- [x] Completed: All three scripts use the reply’s parsed attachment prefix. Interactive checks cover literal option-looking values, operands after the terminator, and existing attached/static/path cases. The root suite and full adapter shell matrix passed.
[Core interpretation](../../completion_flags.go) correctly prioritizes a
pending value and honors `--`. But [reply encoding and script
adjustments](../../internal/cobra/completion.go) omit explicit prefix metadata
for equals forms and let the scripts infer an attached flag from the word's
shape. That inference cannot distinguish an option from a separate value or
operand that happens to contain a dash and equals sign.
Reproduced with a static choice `--tag=chosen` on the value flag `--format`.
For `probe run --format --tag=ch<TAB>`, the hidden query returned the correct
literal choice. Bash and Zsh left `--tag=ch` unchanged; Fish inserted
`--tag=--tag=chosen`. These are edited invocation results, not merely candidate
list differences.
Carry the parser's replacement context to the shell, including an explicit
distinction between whole-word replacement and replacement after an attached
prefix. Account for Bash's native word-break handling without treating it as
CommandKit grammar. Exercise actual edited argv for separate option-looking
values, operands after `--`, and real attached flags, including aliases and path
hints. Coordinate with finding 1 so the same context drives safe shell calls.
### 3. Separate transport control records from literal candidates
Priority: P2. Status: complete.
- [x] Completed: Successful replies frame replacement metadata in a dedicated first line. Bash and Zsh no longer interpret ActiveHelp-like candidate text. Interactive checks pass for literal control-like choices, attached choices, and empty replies.
[CompletionReply](../../internal/cobra/completion.go#L19) emits literal choices
in a stream where generated scripts also recognize Cobra's `_activeHelp_ `
prefix and CommandKit's private short-attachment marker. Control and candidate
records are not distinguished unambiguously. For example, a positional choice
`_activeHelp_ choice` appears in the query response, but interactive Bash
completion of `probe run _act<TAB>` leaves `_act` unchanged because the script
interprets the choice as a control record.
Disable control features that CommandKit does not use and use unambiguous
framing or escaping for necessary metadata. Prefer coordinating this work with
finding 2 to avoid another content-dependent prefix heuristic. Preserve
representable literal choices rather than reserving additional public value
prefixes. Test an edited invocation with marker-like values and ensure a
control record cannot appear as a candidate.
### 4. Preserve comparability of public metadata
Priority: P2. Status: complete.
- [x] Completed: Choice hints now point to privately owned copied data. Compile checks cover comparison and map keys for both metadata types; the root suite passes.
[CompletionHint](../../completion.go#L62) embeds a slice by value. Adding it
to [FlagMeta and ArgumentMeta](../../command.go#L93) makes both existing exported
types noncomparable, even when an application never opts into completion.
A public compile probe now rejects both `map[commandkit.FlagMeta]int{}` and
`commandkit.ArgumentMeta{} == commandkit.ArgumentMeta{}`. Before the sprint
these structs contained only comparable fields. The chosen metadata field
addition does not require this extra compatibility break.
Keep the hint representation comparable, for example with a private pointer to
immutable copied choice data. Preserve copying and the zero hint. Verify that
ordinary metadata can again be compared and used as map keys. Do not promise
semantic equality between separately constructed nonzero choice hints.
### 5. Document the Bash initialization prerequisite
Priority: P3. Status: complete.
- [x] Completed: CLI setup, examples, and validation guidance now explain Bash completion initialization. The harness accepts a package-specific init path; both required helper functions were verified in clean Bash.
The [CLI instructions](../cli.md#shell-completion) tell operators to source the
generated Bash script without mentioning Bash completion helpers. The
[interactive fixture](../../internal/cobra/testdata/completion_terminal.py)
sources those helpers first. In an otherwise clean interactive Bash, sourcing
only the generated script and completing `probe ru` produced
`_get_comp_words_by_ref: command not found` and left the word uncompleted.
The path branches also use `_filedir`.
Document how to initialize the Bash completion package before sourcing the
script, and align the maintained examples and validation instructions. There
is no need to bundle a replacement Bash completion library.
## Completion interaction review
The follow-up reviewed the new completion path against registration snapshots,
command and scoped flag aliases, ordinary argument allocation, reporting, and
the maintained integration declarations. A local build of the composed example
returned `text` and `yaml` through the existing `pk` and `--fmt` aliases,
even with `--quiet --debug` in the target words. The equivalent ordinary
inspection with `--quiet` stayed silent. ConfigKit completion included
`app` and `promptkit` for diff, and additionally `all` for validate, matching
the installed capabilities. No new discrepancy was found in these paths.
The transport and public metadata findings were open at that review.
### 6. Supply the existing PromptKit smoke kinds as operand hints
Priority: P3. Status: complete.
- [x] Completed: The smoke operand suggests the existing native kinds and all. Composed tests verify canonical and alias hints without loading an engine or calling smoke or inference; the ckprompt suite passes.
The [smoke command](../../integrations/ckprompt/smoke_command.go#L141) still
declares `KIND` without a hint. In the maintained composed consumer, queries
after `promptkit test ` and the existing `smoke ` alias return only the no-file
directive, including for the partial word `re`. The same consumer successfully
runs `smoke reachability --campaign spring --prompt briefing --profile spring`
using its local fixture. Operators get static format suggestions on the command
but none for its required, documented selector.
Attach a static operand hint using the existing native kind constants and
CommandKit's `all` aggregate selector. Preserve upstream ownership of accepted
kinds and existing execution behavior; the hint must not become an allowlist.
Cover the canonical command and the maintained alias without starting a smoke
test or loading an engine for completion.
## Completion algorithmic review
Reviewed the installed metadata, route selection, flag and operand walk,
candidate construction, and reply encoding by source inspection. The flag walk
advances through the input rather than rescanning earlier tokens. Operand
allocation is linear in the number of bindings, and choice filtering and reply
encoding are linear in the candidates and text they inspect or emit. Stage 4
expressly requires checking each suggested child against ordinary alias routing;
this review does not propose removing or replacing that check. No additional
correctness defect or newly introduced quadratic algorithm was identified within
the remaining scope. The six existing findings were open at that review.
### 7. Look up canonical completion metadata without scanning siblings
Priority: P3. Status: complete.
- [x] Completed: Canonical snapshot nodes now hold their neutral metadata. Completion uses one map lookup per path component; root, nested and alias context checks and a late-registration leaf query pass.
[completionCommand](../../completion_context.go) searches the metadata child
slice at every component of an already resolved canonical path. For depth
`D` and sibling counts `B_i` at those levels, the lookup can take
`O(sum(B_i))` name comparisons. Even querying a single selected leaf in a wide
group can scan every sibling, after routing already found the canonical path.
This is distinct from enumerating child suggestions, where visiting children
can be necessary.
Consider indexing neutral metadata by canonical path or associating the
metadata with the existing canonical nodes when building the snapshot. A
name-indexed walk then takes expected `O(D)` map lookups. Building and storing
a separate index would cost `O(N)` entries for `N` installed nodes; prefer
reusing existing tree storage where practical. Preserve registration snapshot
refresh and the selected candidate insertion checks.
### 8. Avoid sorting resolved flag aliases on every completion query
Priority: P3. Status: complete.
- [x] Completed: Completion builds its lookup records directly from unique resolved aliases in linear time. Existing help ordering is unchanged; scoped and discovery completion checks pass.
[resolveCompletionContext](../../completion_context.go) calls
[neutralRecords](../../flag_aliases.go) for the selected canonical or discovery
context. For `A` resolved aliases, this builds and sorts a key slice in
`O(A log A)` comparisons, then builds a record slice. The completion flag
walker immediately indexes those records by spelling. Source spellings are
already unique in this resolved context, so sorted input contributes neither
precedence nor candidate ordering to the lookup.
Build completion's lookup from the resolved mappings in expected `O(A)`
time, or reuse a snapshot-owned lookup if that avoids duplicate allocation
without complicating installation. Preserve existing sorted records where
display order matters and preserve scoped precedence and target availability.
This is a source-level opportunity; no latency or allocation threshold is
proposed.
## Shell completion remediation validation
The root and both optional module suites passed in the local workspace, and
the root suite passed with `GOWORK=off`. Both opt-in interactive completion
suites passed with Bash 5.3.9, Zsh 5.9, and Fish 4.2.1. They cover literal
Tab-only input, actual edited arguments, attached and whole-word completion,
path hints, descriptions, and control-like literal choices. The ignored
workspace and module dependency versions were preserved.
---
## Historical invocation reporting audit remediation
Status: remediation complete; all 7 findings addressed.
## Review scope
Reviewed the reporting implementation from `2a1096b` through `7c035cc` against
the [feature roadmap](reporting.md), [implementation plan](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/230187296fae5253cb1773f16c2f1d1a39499ed0/docs/roadmap/implementation.md),
[ADR 0014](../adr/0014-invocation-reporting.md),
[ADR 0005](../adr/0005-invocation-outcomes.md), and the
[architecture policy](../policy/architecture.md).
The intended ownership is substantially implemented: core owns invocation
reporting, ordered presentation selection, coordinated delivery, and finalization;
the internal Cobra adapter supplies neutral parsing evidence; optional
integrations own native adaptation and command presentation. Providers remain
passive, and ckprompt depends on ckconfig without a reverse dependency.
The findings below concern missing presentation and error evidence within that
architecture. They do not require a different public architecture or automatic
rendering of application causes. No separate unrequested registration restriction,
concurrent-dispatch guarantee, or upstream-domain responsibility was identified.
Evidence includes source and test review, maintained example invocations, and
temporary public-API audit consumers outside the repository. The audit did not
modify implementation code or add tests, and did not rerun the full test suites.
The follow-up interaction audit at `1e532cd` reviewed reporting against existing
alias contexts, typed bindings, scope/input/state preparation, cancellation,
native reports, and maintained consumers. Representative successful invocations
covered command and scoped flag aliases with explicit zero and repeated values,
quiet help/version, aggregate configuration YAML, quiet comparison, concrete
request validation, and offline aggregate smoke testing. Findings 4 and 5 record
the additional gaps; findings 1–3 were still open at that review and were not
duplicated below.
The algorithmic audit at `ee76b0f` examined the reporting-specific parser passes,
delivery bookkeeping, deferred storage, native diagnostic partitioning, and
integration report coordination. It used source-level complexity analysis, not
benchmarks or real-world performance measurements. Selected algorithms were
excluded from reconsideration, including synchronous serialized reporting, FIFO
deferred delivery, rendering rich content under quiet mode, fresh backend trees,
and the chosen configuration/smoke report preparation boundaries. Findings 6 and
7 are implementation opportunities within those decisions.
No newly introduced quadratic-time algorithm was identified in the reviewed
paths. In particular:
- Reporting selection is linear in the number of flag occurrences. There are
four fixed reporting flags and at most two additional reverse searches for
selection provenance; these do not create a quadratic pass.
- Deferred insertion is amortized constant time plus callback-list copying;
draining is linear in queued messages and details, excluding callback/I/O
costs. The avoidable storage and handoff costs are described in finding 6.
- Native diagnostic snapshotting and partitioning are linear in source records,
diagnostic records, and the total related-location entries. Nested copying of
related locations is not quadratic in the actual input size.
- Explanation combination uses amortized slice appends. Configuration comparison
has two sides, and aggregate smoke execution has four selected operations;
neither introduces an unbounded nested traversal through all prior results.
The algorithmic audit found no additional ordinary correctness issue beyond
findings 1–5. It made no implementation changes and ran no tests or benchmarks.
## 1. Preserve native PromptKit operation failure explanations
Priority: P2. Status: complete.
- [x] Completed: bundled native operation failures now supply terminal explanations
without printing arbitrary application causes. Explicit provider context and
independent warnings retain their presentation paths. Native-error forwarding
and single-presentation regressions pass in ordinary and quiet modes.
Validation: `GOTOOLCHAIN=local go test ./...` in `integrations/ckprompt`.
### Finding and evidence
[WithRequestValidation and FromEngine](../../integrations/ckprompt/adapters.go)
carry native operation errors back to their callers, but their acquired terminal
explanations contain only configuration-loading diagnostic records.
[runRequestValidation](../../integrations/ckprompt/validation_command.go) and
[definitionFailure](../../integrations/ckprompt/command.go) then retain the native
error solely as `Cause` and present a generic operation heading when no acquired
explanation exists. Native request-validation and lookup failures consequently
lose their useful explanation at the CLI boundary.
Reproduced in the maintained composed example:
```sh
go run . promptkit requirements validate briefing --campaign spring
```
Its entire diagnostic is:
```text
ERROR: Unable to validate the PromptKit request for prompt "briefing".
```
A separate public-API consumer using the bundled validation adapter confirmed
that the native error contains `missing required input artifact: topic`, while
the acquisition has neither an explanation heading nor a detail renderer. The
cause survives, but the operator cannot see the missing input. The maintained
`promptkit show prompt missing --campaign spring --quiet` invocation likewise
omits the native lookup explanation.
This falls short of useful access to native capabilities and the roadmap's
requirement to keep failures understandable. Separating causes from presentation
does not prohibit an integration from deliberately adapting native failure
content into an explanation. Existing native-failure tests primarily check
classification and cause identity while discarding the diagnostic destination,
so they do not protect this handoff.
### Required work
- At the bundled PromptKit adapter/command boundary, deliberately provide the
relevant native operation failure explanation, using the owning API's public
presentation and disclosure rules. Cover validation and the other native
inspection/selection paths affected by the same migration.
- Preserve the original cause independently and preserve an explicitly supplied
provider explanation. Keep independent warnings separate and avoid presenting
the same native failure through two paths.
- Keep core's cause-neutral behavior. Do not solve this by printing arbitrary
`Outcome.Cause` values or treating every application-owned loader/provider
error as safe operator text.
### Acceptance evidence
Use controlled upstream failure evidence to verify that CommandKit forwards
useful native failure content into the terminal explanation in ordinary and
quiet modes, with cause identity retained and unrelated warnings still hidden
where selected. Check explicit provider explanations for single presentation.
These are adapter/presentation assertions, not tests of whether PromptKit
correctly detects missing artifacts or invalid selections.
## 2. Retain independent rendering errors joined with write errors
Priority: P2. Status: complete.
- [x] Completed: joined callback errors retain independent rendering evidence,
while plain and wrapped sink errors remain write failures. Output and detail
regressions pass, including joined destination errors.
Validation: `GOTOOLCHAIN=local go test ./...` in the repository root.
### Finding and evidence
In [reporter.go](../../reporter.go), `recordRendererErrorLocked` drops the entire
callback error whenever `errors.Is(renderErr, writeErr)` is true. That test also
matches `errors.Join(independentRenderErr, writeErr)`: the callback error can
contain an independent rendering failure as well as the propagated sink failure.
The shared helper serves `RenderOutput`, message details, and terminal details.
Dropping the whole joined error loses evidence both from the immediate returned
error and from `Presentation.*.RenderError`.
A public-API audit consumer used a failing result writer and this callback:
```go
_, writeErr := io.WriteString(w, "result")
return errors.Join(renderErr, writeErr)
```
The invocation failed as expected and retained the sink error, but
`errors.Is(returnedError, renderErr)` was false and the final output `RenderError`
was nil. This contradicts the selected retention of independently returned
rendering failures. The current separation test covers returning a standalone
rendering error after a failed write, but not a callback that already joins them.
### Required work
- Distinguish a callback merely propagating its managed-writer failure from a
callback carrying additional independent error evidence. Do not discard the
entire returned error just because its chain also contains the sink error.
- Preserve the independent callback error in the immediate error result and
bounded rendering observations. Retain the first write error separately and
continue avoiding further writes to the failed destination.
- Keep ordinary propagation of a write error from being misclassified as an
independently discovered rendering failure. No transcript or new public
error-classification API is needed.
### Acceptance evidence
Add a focused joined-error regression at the shared reporting boundary. Verify
`errors.Is` access to both failures in the returned error, rendering evidence in
`RenderError`, and sink evidence in `WriteError`. Preserve existing coverage for
plain/wrapped write-error propagation and first-error retention. Exercise a
detail-rendering path as needed to establish that the shared fix applies there.
## 3. Explain failures introduced by CommandKit presentation
Priority: P2. Status: complete.
- [x] Completed: presentation failures that turn success into failure now retain
a core-owned rendering/delivery explanation. Supplied explanations and causes,
generic handler fallback, quiet rendering, domain status, and failed-sink
behavior remain covered. Validation: `GOTOOLCHAIN=local go test ./...` at root.
### Finding and evidence
[Reporter.finalize](../../reporter.go) changes an otherwise successful outcome
to failure when presentation failed, then invokes the same
`ensureFallbackExplanation` used for an unexplained failed handler outcome.
Consequently, a CommandKit-invoked renderer failure or a result-write failure
produces only:
```text
ERROR: Command failed.
```
The diagnostic destination may be entirely healthy. The caller can inspect
`Presentation`, but the operator is not told that rendering or delivery failed.
This also affects successful domain outcomes changed to non-zero status.
Public-API consumers reproduced the generic explanation after both a callback
that failed before writing and a rejected result write. The corresponding
reporter and execution tests currently assert this generic text, so they preserve
the gap rather than the selected distinction.
[Stage 4](https://forge.maximumdirect.net/base-layer/commandkit/src/commit/230187296fae5253cb1773f16c2f1d1a39499ed0/docs/roadmap/implementation.md#stage-4-implement-deferred-delivery-and-finalization)
requires a useful core-owned explanation when presentation turns success into
failure. The roadmap assigns handling of reporting failures to CommandKit and
distinguishes healthy diagnostic destinations from failed result destinations.
The generic fallback remains appropriate for a failed handler outcome that
supplies no explanation; it does not explain the presentation failure core
itself discovered.
### Required work
- When presentation changes a successful outcome into failure and no explanation
was supplied, attach and retain an explanation identifying the failed
presentation operation, such as result rendering or result delivery. Present
it through a healthy diagnostic destination.
- Keep the original optional cause unchanged and preserve supplied explanations.
An informative core-owned heading is sufficient; this does not require
automatically exposing callback error strings or application causes.
- Preserve the selected outcome/status table, exact generic fallback for
otherwise unexplained failed outcomes, and explicit native explanations,
including a non-nil renderer that emits no bytes.
- Do not retry a failed diagnostic destination, redirect into substantive output,
replay explanation callbacks, or introduce recursive reporting-failure messages.
### Acceptance evidence
Update focused tests so a successful operation with an output-rendering or
output-writing failure receives a retained presentation-specific explanation
through working diagnostics, including quiet rendering failure and a successful
domain outcome. Preserve evidence for supplied explanations/causes, generic
fallback on unexplained handler failure, and diagnostic-sink failure without
retry. Assert the meaningful failure category rather than inventing a new
public exact-wording guarantee.
## 4. Restore cancellation checks between report preparation and delivery
Priority: P2. Status: complete.
- [x] Completed: restored preparation-before-delivery cancellation checks for
PromptKit catalogs, definitions, requirements, and validation, and buffered
configuration diff/sources/YAML explanation reports. Cancellation retains its
operation cause without becoming a rendering failure; native streaming and
smoke-report behavior remain unchanged. Controlled preparation/serialization
checks cover ordinary and quiet modes. Validation: module suites pass in
`integrations/ckconfig` and `integrations/ckprompt`.
### Finding and evidence
Before the reporting migration, PromptKit's `writePromptReport` checked the
invocation context after a report had been prepared and before writing it.
The migrated [renderProfileReport](../../integrations/ckprompt/command.go)
formats its text report inside `RenderOutput`, writes it immediately, and
returns success without that intervening cancellation check. The surrounding
inspection command checks cancellation only before entering report preparation.
A deterministic public-API consumer supplied a profile inspection with a
controlled YAML marshaler in `EffectiveModelParams.ExtraParams`. The marshaler
cancelled the invocation context and returned a valid value while the text
report was being prepared. This uses the ordinary report serialization boundary;
it does not reenter the reporter or require concurrency or an external service.
Observed results for `promptkit show profile demo --format text`:
| Mode | Context at completion | Status | Cause matches cancellation | Report bytes |
| --- | --- | --- | --- | --- |
| Ordinary | `context.Canceled` | `0` | No | `277` |
| Quiet | `context.Canceled` | `0` | No | `0` |
Cancellation occurred before the first destination write. Previously, the shared
post-preparation check would have returned operational failure with the
cancellation cause and left the report destination untouched. The migration also
removed similar post-preparation checks from buffered configuration report paths,
including [config diff](../../integrations/ckconfig/diff.go). Its current
post-render cancellation branch runs only when rendering already returned an
error, so successful preparation does not preserve the old checkpoint.
The plan requires preserving existing cancellation behavior outside the selected
reporting changes. Quiet suppression is not intended to turn this cancellation
into success. Existing cancellation tests cover acquisition and write failures,
but do not cover cancellation during successful report preparation.
### Required work
- Restore the integration-owned cancellation checkpoint after report preparation
and before delivery in migrated paths that previously had it. Review the
removed `writePromptReport` callers and the buffered configuration paths rather
than patching only the demonstrated profile-text case.
- Preserve supported cancellation identity in the operation outcome, while
retaining reporter ownership of rendering and destination failures. Apply the
same operation decision under quiet mode.
- Keep this change bounded to the existing preparation/delivery behavior. Do not
introduce a core-wide rule that rejects every cancelled application invocation,
interrupt caller writers, withdraw delivered bytes, or promise cancellation
after delivery has already begun. New whole-run buffering is unnecessary.
### Acceptance evidence
Use a controlled serialization/preparation callback to cancel before the first
report write. Assert operational failure, `errors.Is` access to
`context.Canceled`, and no report delivery, including quiet mode. This verifies
CommandKit coordination, not upstream serialization or domain behavior. Preserve
existing coverage for cancellation during acquisition, partial output, and
caller-owned writes that have already started.
## 5. Apply reporting-flag aliases in unresolved-route parse contexts
Priority: P2. Status: complete.
- [x] Completed: unresolved routes obtain aliases from the context selected by
normal parsing, through neutral internal metadata. Root and nested-group
canonical/alias comparisons, quiet failure visibility, scoped precedence,
unchanged unresolved identity, and factory non-execution pass.
Validation: `GOTOOLCHAIN=local go test ./...` at the repository root.
### Finding and evidence
[compileFlagAliases](../../flag_aliases.go) now resolves application-wide aliases
to the new reporting flags in root and group contexts. However,
[execution.prepareSession](../../execute.go) supplies aliases to the parser only
for `routeCanonical` and `routeDiscovery`. For an unresolved command path it
supplies none, even when the normal parser selects the root or a known group
whose reporting flags and alias mappings are available.
With an installed application-wide `FlagAlias("-d", "--debug")`, a public-API
consumer reproduced:
| Invocation | Observed diagnostics |
| --- | --- |
| `app missing --debug` | Debug route/selection detail, then the unknown-command usage failure. |
| `app missing -d` | `unknown shorthand flag: 'd' in -d`, with no Debug detail. |
| `app group missing --debug` | Debug detail, then the group operand/command usage failure. |
| `app group missing -d` | `unknown shorthand flag: 'd' in -d`, with no Debug detail. |
| `app group run -d` | Successful command execution with Debug detail. |
The spelling registered for a reporting flag is thus treated as unknown on the
failure paths where operators may need debugging. Canonical mode selection on
these paths already works. This conflicts with the
[CLI alias contract](../cli.md#application-owned-aliases): an application-wide
alias is effective wherever its canonical target is available. The new reporting
flags make these existing parse-context gaps observable for mode selection.
Current reporting parse tests cover a canonical reporting flag after a missing
command and aliases on valid routes, but not their combination.
### Required work
- Supply the alias mapping applicable to the context used by normal parsing
when the full entered command path does not resolve. Cover root and known
group parse contexts, preserving existing scope/precedence rules where they
apply.
- Keep ordered canonical/alias occurrence handling in the normal parser. Do not
add a separate reporting-flag scanner or enable unrelated aliases whose
canonical target is unavailable.
- Preserve the original routing failure, handler/factory non-execution, and
entered-path usage behavior. Finding a parse context must not manufacture a
successfully resolved canonical command identity for the invalid route.
### Acceptance evidence
Compare canonical and application-wide aliased reporting flags on unresolved
root and nested-group paths. They should select the same mode and retain the
same underlying command-path failure rather than invent an unknown-flag error.
Include a quiet alias followed by another parse error, without suppressing the
terminal usage explanation. Retain scoped-alias precedence, unavailable-target,
terminator/value handling, and discovery-context coverage at their existing
owners.
## 6. Avoid retaining deferred messages that need no presentation work
Priority: P3. Status: complete.
- [x] Completed: renderer-free hidden deferred messages record suppression
without queue retention. Finalization detaches the queue without an array
copy and delivers captured callback lists without recopying them. Hidden rich
callbacks, FIFO order, no-content handling, capture, and closure remain covered.
Validation: root module suite and focused reporter race checks pass.
### Finding and complexity
[DeferredReporter.enqueue](../../reporter.go) unconditionally stores the category,
heading, and copied detail list. Visibility is checked only when `finalize`
drains the queue through `submitMessage`. Consequently, a long-running handler
that submits many simple deferred Debug messages in ordinary mode, or simple
Info/Warning messages in quiet mode, retains all of them despite there being
no output or renderer work to perform.
Let `M` be the number of these renderer-free hidden messages and `B` the bytes
of otherwise unreferenced distinct heading text. Reporter storage can grow as
`Θ(M + B)` until finalization. Finalization then makes a second `Θ(M)` queue
array copy before visiting and suppressing every entry. This is linear, not
quadratic, but the additional reporter storage for this case can be `O(1)`.
The `M` public submissions still require `Θ(M)` total call work; this is not a
claim that the whole invocation can become sublinear.
### Refactoring opportunity
Under the current execution flow, reporting selection is applied before handlers
and providers run, with no public operation to change it mid-handler. For a
hidden deferred submission with no non-nil detail callback, consider recording
the eventual suppression observation without retaining the heading or queue
entry. Preserve the existing no-content treatment of an empty heading with no
renderer. This removes the avoidable `Θ(M + B)` retention and its later sweep
for renderer-free suppressed messages.
Retain rich hidden submissions: their callbacks must still execute at the
selected deferred time against discard so rendering failures are preserved.
Retain visible messages in FIFO order. Closed-reporter checks must still run
before accepting or discarding any submission. This optimization does not add
a public immutability guarantee for future mode APIs or change the selected
emission-time visibility rules.
Independently, `finalize` can detach the reporter-owned queue under its existing
mutex instead of copying its array and then clearing the original reference.
That makes queue handoff `O(1)` instead of `Θ(M)` extra copying; actual visible/
rich message delivery remains linear. An internal delivery path can also avoid
recopying callback lists already captured by `enqueue`, while preserving the
selected defensive copy when first accepting caller-owned lists.
These opportunities preserve the chosen queue and synchronous delivery model.
They do not justify message caps, dropping visible messages, skipping rich
renderers, or a background delivery subsystem.
### Review evidence
The retention and copies follow directly from `enqueue`, the array copy at the
start of `finalize`, and `submitMessage`'s later visibility check. If selected,
use focused behavior checks for suppression observations, FIFO delivery,
callback capture, hidden renderer failures, and late submissions. Source-level
storage analysis is sufficient; no benchmark or allocation threshold is proposed.
## 7. Stop normalizing an argument tail that parsing cannot reach
Priority: P3. Status: complete.
- [x] Completed: interpretation returns the first fatal unknown-flag prefix,
retaining earlier canonical/alias occurrences and short-cluster members.
Temporary slices grow with that prefix; unreachable values and spellings are
no longer normalized or copied. Existing error precedence, values, and
terminator coverage passes. Validation: root module suite passes.
### Finding and complexity
In [interpretFlagTokens](../../internal/cobra/flags.go), the first unknown long
flag or unknown short-cluster member sets `recordingOccurrences = false`, but
interpretation continues through the remaining argv. Later recognized flags
still populate spelling lists and construct canonical `--name=value` strings.
[Session.Parse](../../internal/cobra/parse.go) then calls the backend, which
fails at the earlier unknown flag without consuming that normalized tail.
The adapter does not enable unknown-flag whitelisting.
For example, after `--unknown`, a later valid `--message=LARGE_VALUE` still
causes the value bytes to be copied into a new canonical argument, even though
no parser, converter, or handler reaches that value. The recent occurrence
tracking correctly excludes the tail from selection evidence but retains this
pre-existing unnecessary normalization work.
Let `F` and `A` be the flag/alias table sizes, `P` the token/byte processing cost
through the first fatal unknown flag, and `T` the remaining normalization work,
including copied value bytes. With expected constant-time map lookups, the helper
currently takes `O(F + A + P + T)` time on that failure path. It can take
`O(F + A + P)` by stopping at the failing token and growing temporary arrays
with the prefix instead of preallocating for all argv. The existing backend can
still produce the same failure from that prefix. This is an early-failure
improvement, not a quadratic-time finding or a change to the successful parse's
necessary linear work.
### Refactoring opportunity
Consider ending interpretation immediately after preserving the first unknown
flag or failing short-cluster suffix. Preserve any earlier valid occurrences and
their spellings, including recognized members preceding a bad short member.
Let the backend retain its existing failure classification, wording, and
precedence; do not replace normal parsing with a reporting-specific scanner.
This can also remove the repeated `recordingOccurrences` guards needed solely
because the current implementation continues past the failure.
The helper currently preallocates both output and occurrence slices from total
argv length, so an early return alone does not eliminate its `O(argc)` upfront
array allocation and initialization. To obtain the prefix-bounded helper cost,
grow those arrays with the interpreted prefix rather than reserving for the
unreachable tail. This changes allocation strategy, not accepted input sizes.
Other routing/parser steps still copy argv string headers, so this refactoring
does not make the entire invocation prefix-linear. It specifically removes
avoidable tail interpretation, spelling storage, and large-value byte copies.
There is no need to reconsider the selected backend or fresh-tree strategy.
### Review evidence
The unknown-flag branches and continued spelling/string construction establish
the extra work statically. Existing prefix-order, missing-value precedence,
short-cluster, and terminator/value tests provide relevant behavior coverage if
this is selected. Preserve the backend error and consumed occurrence prefix on
early failure; no timing threshold or real-world performance data is needed.
## Final remediation validation
All seven items have completion markers and focused regression evidence. The
following checks pass for the assembled changes:
- `GOTOOLCHAIN=local go test ./...` at the repository root and in each of
`integrations/ckconfig` and `integrations/ckprompt`, including maintained examples.
- `GOWORK=off GOTOOLCHAIN=local go test ./...` at the repository root.
- `GOTOOLCHAIN=local go test -race -run '^TestReporter|^TestHidden' .` at root.
- Go formatting, staged/unstaged whitespace review, and local document-link review.
The ignored local workspace is preserved. No dependency upgrade, release,
publication, or change to upstream domain behavior was part of this work.