Files
commandkit/docs/roadmap/implementation.md

301 lines
18 KiB
Markdown
Raw Normal View History

2026-11-03 03:32:18 +00:00
# Shell completion implementation plan
Status: Stages 1–5 complete. Stage 6 is next.
2026-11-03 03:32:18 +00:00
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
2026-11-03 03:45:47 +00:00
Status: complete.
2026-11-03 03:32:18 +00:00
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.
2026-11-03 03:45:47 +00:00
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.
2026-11-03 03:32:18 +00:00
## Stage 2: Capture completion hints
Status: complete.
2026-11-03 03:32:18 +00:00
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`.
2026-11-03 03:32:18 +00:00
## Stage 3: Install completion as an ordinary declaration
Status: complete.
2026-11-03 03:32:18 +00:00
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`.
2026-11-03 03:32:18 +00:00
## Stage 4: Resolve the entered command context
2026-11-03 03:51:26 +00:00
Status: complete.
2026-11-03 03:32:18 +00:00
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.
2026-11-03 03:51:26 +00:00
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`.
2026-11-03 03:32:18 +00:00
## Stage 5: Interpret flags and locate value positions
Status: complete.
2026-11-03 03:32:18 +00:00
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`.
2026-11-03 03:32:18 +00:00
## Stage 6: Assemble candidates and deliver query replies
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.
## Stage 7: Annotate maintained commands and examples
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.
## Stage 8: Validate the assembled sprint
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.