Files
commandkit/docs/roadmap/implementation.md

45 KiB
Raw Blame History

Application-owned aliases implementation plan

Status: in progress; Stages 1–10 completed.

Authority, baseline, and scope

Implement Application-owned aliases, governed by ADR 0013, ADR 0011, and the architecture policy. The feature roadmap owns the selected behavior; this document owns the work sequence and implementation choices. Follow the agent, testing, and documentation policies. Read the developer guide first.

The inspected baseline is commit f3b8a129d99d04230f998d0327ffad05f6002d7a. Core v0.2.0, ckconfig v0.1.0, and ckprompt v0.1.0 have been published. New and Register currently accept ...Command; there are no routing-rule declarations. Invocation currently contains context and streams only. The Cobra adapter selects canonical commands, parses their flags, and renders help. Core owns typed conversion, input construction, dispatch, and outcomes.

This sprint delivers command and flag alias declarations, joint installation, resolution, invocation identity, help, diagnostics, examples, and documentation. It includes the selected canonical short-option empty-value correction. It does not implement operand synonyms, preset arguments, shell completion, new upstream operations, a replacement backend, or release publication.

How to execute the plan

  • Perform Stage 1 through Stage 17 in order. Each stage is sized as a separate Luna implementation prompt. Add its completion evidence here and commit each completed nonempty unit under the agent policy.
  • Inspect the current tree before editing; preserve unrelated work. Prefer the code knowledge graph for code discovery, falling back when it is insufficient.
  • Reuse current behavior and tests where they express accepted contracts. Replace mechanics when needed; do not add compatibility wrappers around the old installation signatures or a second application parsing lifecycle.
  • Stages include the focused tests needed for their work. Run those during implementation, not during this planning pass. Do not make later stages depend on unverified termination or token-interpretation assumptions.
  • Keep tests about CommandKit adaptation, presentation, and lifetimes. Upstream libraries own their domain tests. Use local callbacks and fixtures; no live providers or credentials are needed.
  • Preserve eager declaration capture, reusable declarations, namespace ownership, binding identity, presence, wrapped causes, and existing outcome delivery. Do not add concurrent-dispatch, arbitrary deep-copy, performance, or public ordering guarantees.
  • New private records and helpers described below are implementation choices. Their names and exact organization can change without altering public behavior. Do not expose them through the facade or add backend selection APIs.
  • Keep published release records unchanged. Local source verification does not authorize creating tags, pushing, or publishing another release.

Implementation decisions used by the stages

Owners and data flow

Owner Work
Root commandkit package Public declarations, retained errors, candidate installation, alias records, routing coordination, invocation identity, typed conversion, and outcome presentation.
New internal/commandpath package Shared path representation and token/path operations used by core, neutral metadata, and the adapter. It has no backend or optional-library imports.
New internal/alias package Private command-prefix lookup and termination analysis over paths; no handlers, application factories, or backend types. Keep its model limited to the selected routing rules.
internal/commandmeta Neutral canonical metadata and the routing, spelling-provenance, and help-view records actually needed across the adapter boundary.
internal/cobra Canonical backend sessions, alias-aware flag interpretation, and rendering selected help views without installing alias command nodes.
Optional integrations Continue returning canonical Command declarations and owning their providers. Only source migrations, focused composition evidence, and maintained consumer changes are needed here.

Core retains canonical commands and routing rules separately. A candidate installation compiles relationships against the complete prospective registry, then publishes them together. Derived lookup metadata is reusable within an App; parse state and entered-route provenance remain invocation-local.

Use the same private route lookup for execution, explicit help-target lookup, and discovery-help views. It returns a canonical command identity or a distinct alias-prefix discovery context, plus entered/canonical paths and remaining arguments. Keep lookup independent of execution and rendering. This is current routing/help machinery, not a completion API.

Public declarations and invalid values

Implement the roadmap's signatures:

type Declaration interface { commandkitDeclaration() }

func New(identity Identity, declarations ...Declaration) (*App, error)
func (a *App) Register(declarations ...Declaration) error

func Alias(path, target string) RoutingRule
func FlagAlias(spelling, target string) RoutingRule
func FlagAliasFor(commandPath, spelling, target string) RoutingRule

func (inv Invocation) EnteredPath() string
func (inv Invocation) CanonicalPath() string

Command and opaque RoutingRule implement the private marker. Their factories capture values and retain errors; alias factories do not return errors or run callbacks. Keep Group, RootGroup, and CommandSpec.Children command-only. WithError and NoCommand keep their current command contracts; do not add an absence rule or a general extension/plugin declaration kind.

A nil declaration, invalid zero Command, and zero RoutingRule are installation errors, not no-ops. Handle nil pointers without panics; normalize non-nil pointers to the supplied opaque declaration types to their values. Dispatch only the supported CommandKit declaration kinds, without reflection or calling application-defined assembly callbacks. NoCommand remains the explicit absence case, with retained errors checked before it is skipped.

An identical normalized routing rule can be contributed repeatedly. It is idempotent, including across calls to Register. Do not extend that exception to duplicate canonical command declarations. Identity uses declared kind, source, target, and scope, not merely the final resolved endpoint: different declared targets remain competing mappings even if they reach the same command or binding. Invalid later registration leaves both registries and derived metadata intact.

Store invocation paths privately as normalized strings, populated by dispatch. Accessors on an otherwise caller-created zero invocation return empty strings. The original context and stream fields remain usable in keyed literals.

Paths and provenance

Parse declaration paths on whitespace once at capture. Reuse canonical one-token name validation; do not introduce a stricter name alphabet. Path rendering, prefix comparison, normalized keys, and exact backend lookup use shared helpers. Never split or trim already-tokenized argv elements.

Route tokens retain their association with the original argv prefix as aliases replace command-path components. This is needed to identify the entered path without guessing from the length of the final canonical path. For smoke all with smoke → promptkit test, the entered command path is smoke, the canonical path is promptkit test, and all is still an operand. For pk list bundles, both original list and bundles belong to the entered path when its resolved command is promptkit list collections.

Only the leading command portion participates in command rewriting. A flag or -- stops command-path lookup. Canonical command structure and operand metadata establish where trailing non-option tokens become operands. Longest explicit rules beneath an alternate spelling still take precedence as selected in the roadmap; do not reserve all operand space beneath an alternate spelling.

Exact termination analysis

Use a finite pushdown analysis of the deterministic longest-prefix rewriter. This is private installation machinery. It avoids rejecting valid consuming repetitions or accepting a growing loop that never repeats an entire path. Keep it isolated so its correctness can be established before dispatch uses it.

The implementation has two parts, split across Stages 2 and 3:

  1. Compile a prefix trie into a finite-state stack machine. The top of the stack is the beginning of the unresolved path. Lookup remembers the deepest matching rule, inspects only the finite source-prefix length, and restores any inspected suffix when replacing that source with its target. A failed longer match selects the saved shorter match; it does not invent a route. No match halts rewriting. End-of-path and the boundary before flags are terminal markers, never command components that a rule can consume.
  2. The finite stack alphabet contains literal tokens used by rules, an OTHER equivalence class for tokens not used by any rule, and an end marker. Preserve original arbitrary tokens in the runtime resolver; this quotient is only for proving termination. All alias sources are nonempty.
  3. Normalize transitions to (p,A) → (q,w) with w of length zero, one, or two, using finite helper states for longer pushes. Remember which original rules participate so an error can identify mappings. The compiler's lookup and restoration phases must be acyclic; only completing a rule can return to the lookup start. The bottom/end marker is never popped or duplicated.
  4. Compute the finite least fixed point Pop(p,A,q): starting in control p with top A, the machine can remove that symbol and finish in q without inspecting the stack below it. Seed pop transitions. A one-symbol push uses one existing pop summary; a two-symbol push composes two summaries in left-to-right order. Retain witness information, not copies of arbitrary stack contents.
  5. Build a graph of heads (control, top-symbol). A transition pushing B gives an edge to (q,B). One pushing BC gives an edge to (q,B) and, for every Pop(q,B,r), to (r,C). A pop transition alone gives no edge. These edges summarize nonempty paths that never pop below their starting stack level. A directed cycle, including a self-edge, witnesses a repeatable computation and hence nontermination.
  6. Check the model's scope explicitly: installation is checking all finite command-token suffixes, not just each bare source or target. For this compiler, helper phases cannot run forever without returning to the lookup start, and every finite token stack at that start is a possible input. Therefore a repeatable computation must correspond to a possible alias invocation; do not reject an abstract cycle introduced only by an invalid end-marker configuration. Restrict marker transitions accordingly and record this reasoning with the implementation tests.

For the fixed-point step, a rule (p,A) → (r,BC) adds Pop(p,A,q) only when both Pop(r,B,s) and Pop(s,C,q) hold for some s. This distinction matters: the head-graph edge to (s,C) requires only the first summary and represents continuing at C, rather than having removed it.

Do not substitute an ordinary graph of alias declarations, a decreasing-length rule, a maximum chain length, a fixed suffix-enumeration depth, or a timeout for this analysis. Use a straightforward finite fixed point and cycle detector; no external model-checker dependency or public automaton abstraction is needed. Bounded examples are regression evidence, not the proof of universal termination. If implementation exposes a flaw in the compiler or analysis, correct that isolated stage before proceeding; do not weaken the selected alias contract.

After termination succeeds, resolve each declared target through the same longest-prefix semantics and require an exact canonical command/group endpoint, with no injected operand tokens. Validate literal canonical reachability using the complete candidate command tree and its argument bounds, without invoking value parsers or application checks. Follow canonical child selection before treating unmatched tokens as operands. Do not reject a route solely because a runnable ancestor exists: it must actually intercept that command's declared operand space or a canonical route.

Flag resolution and parsing

Normalize a flag declaration into a long or individual one-byte short spelling, without a value. Reuse the existing canonical spelling rules, allowing built-in help as a destination. Do not interpret clusters or attached values in a rule declaration. A command-scoped path must be an exact canonical path, not an alias or an operand-bearing invocation.

For each canonical command/group and the application root, form effective mappings from application-wide rules plus command-local overrides. Include canonical --help and -h. Resolve chains with canonical spellings as endpoints, apply availability to the final endpoint, and validate active source collisions separately. Application-wide missing destinations stay inactive; explicit scoped missing destinations are errors. Detect mapping cycles, including in reusable global chains, and recompute relationships when registration changes the context. Discovery-help contexts have core help flags and global rules only.

Keep the flag-name/value interpretation in the internal adapter. Use one metadata-driven token pass for canonical and alias spellings, including mixed short clusters. Normalize reached spellings to canonical backend flags, retain their original occurrence spellings, and pass values/operands unchanged. Cobra/pflag remain the backend; this pass adapts the selected token grammar and does not convert typed application values. Do not independently scan or rewrite tokens in core as a second flag parser.

Once a value-taking flag consumes an attached suffix or the next token, neither that value nor anything following -- is searched for aliases. Explicit boolean values require =; a bare boolean contributes true. Normalize -c= as an explicitly empty value, and -v= as an empty value for boolean conversion, including when these are canonical short spellings. Keep occurrence order within each canonical binding and raw values available for the existing typed converter; do not add a cross-binding converter-order guarantee.

Retain private per-occurrence spelling provenance through conversion. An internal conversion error can carry a canonical binding/occurrence index and wrap the original cause so presentation can identify the entered spelling. Do not rerun converters or stringify and replace their errors. Preserve CaptureFlags/FlagValues binding identity and application-input semantics.

Stage 1: Introduce shared command-path primitives

Work

  1. Add internal/commandpath with capture parsing, component validation, equality/prefix operations, normalized keys, formatting, and owned token copies at capture boundaries. Distinguish a root lookup path from a declaration path: root lookup is valid; an empty alias declaration path is invalid.
  2. Move or delegate the existing command-name check to the shared validator. Migrate canonical bridge paths, exact help lookup, and core path formatting where they currently join/split the same paths repeatedly.
  3. Keep argv as tokens throughout. Do not change canonical routing, public API, command names, or name restrictions in this refactor.

Evidence: focused path tests for whitespace-normalized declarations, literal quotes/backslashes, invalid option components, empty declarations, root lookup, and a single argv token containing whitespace. Existing routing and help tests remain green.

Completed: Added shared command-path parsing, validation, relations, keys, formatting, and token-copy helpers. Core command validation and path diagnostics, plus adapter route comparison and exact help lookup, now use the shared package. Verified with go test ./internal/commandpath ./internal/cobra ..

Stage 2: Model deterministic prefix rewriting

Work

  1. Add private normalized command-rule records and longest-prefix lookup under internal/alias, using Stage 1 paths. A simple trie is sufficient.
  2. Implement the finite stack-machine compiler described above, including saved shorter matches, mismatch restoration, token equivalence classes, end-marker handling, and normalized short push transitions.
  3. Add a small test interpreter for the compiled machine and compare finite cases with the direct longest-prefix resolver. Do not expose this interpreter or use a test step bound as the production validity decision.
  4. Keep compiler phase transitions explicit so lookup/restoration cannot create an internal loop unrelated to completing alias rewrites.

Evidence: chains; overlap chosen by token count rather than character count; target expansion; group suffix preservation; shorter fallback after a failed longer token match; unknown tokens; empty suffixes. The compiler and direct resolver agree on these terminating cases.

Completed: Added the independent longest-prefix resolver and compiled its trie into a finite stack machine with literal and OTHER symbols, end and flag-boundary markers, shorter-match fallback, restoration, and normalized pushes. A test-only interpreter matches direct resolution for the recorded finite cases. Verified with go test ./internal/alias.

Stage 3: Prove and test termination validation

Work

  1. Implement pop-summary saturation, head-graph construction, and cycle detection from the implementation decisions. Return useful participating rule context for a rejection. Document why the abstraction neither misses growing loops nor invents invalid marker loops.
  2. Cover direct cycles and a → b extra, b → a, whose paths grow without repeating. Cover a suffix-dependent cycle using a → b, b → group, b x → a x: bare a terminates but a x does not.
  3. Accept consuming repetitions: with canonical group group, a → group and group again → a must accept a again again and consume both suffix tokens before ending at group.
  4. Cover longest-match suppression of a potential shorter-rule loop: with a → b x, b → a, b x → group, actual resolution terminates. A naive rule-dependency cycle detector would incorrectly reject it.
  5. Include a valid chain longer than any tempting small fixed hop limit and vary rule registration order. Keep no artificial public alias-count limit.

Complete when: the exact finite analysis is justified and the focused cases distinguish it from conservative declaration-cycle or bounded simulation checks. Do not integrate an approximate validator and defer correctness.

Completed: Added worklist saturation for finite pop summaries, head-graph construction, cycle detection, and errors naming participating mappings. Terminal-marker transitions preserve the unique bottom marker, and tests verify markers cannot be popped. Direct, growing, and suffix-dependent loops are rejected; consuming repetition, longest-match suppression, and 80-rule chains in either registration order are accepted. Verified with go test ./internal/alias -count=1.

Stage 4: Add captured declaration types and factories

Work

  1. Add Declaration, opaque RoutingRule, the three alias factories, and private captured rule records with retained construction errors.
  2. Capture and normalize command paths and flag spellings immediately. Preserve the existing Command capture, WithError, and absence behavior.
  3. Change New/Register to ...Declaration, normalizing supported values and pointers at the installation boundary. Keep group children command-only.
  4. Convert internal []Command expansions used for installation into explicit declaration slices where necessary, including built-in assembly and affected consumers/tests. Do not add a bundle API or generalize Group.

Evidence: a core-only public consumer can capture mixed declarations; invalid paths/spellings are reported at installation; nil and zero declarations are handled as selected; canonical command-only calls continue to work. No factories, loaders, or handlers run during capture.

Completed: Added the opaque Declaration and RoutingRule types, command and flag alias factories, eager normalized capture with retained issues, and value/pointer normalization at New and Register. Built-in and consumer installation slices now use declarations explicitly. Verified with root and both integration module suites under the local workspace.

Stage 5: Install canonical commands and command aliases atomically

Work

  1. Replace the current root-command-only pending map with a candidate containing existing/new canonical commands and separate normalized routing records. Reuse existing canonical placement and namespace checks.
  2. Validate retained errors, exact canonical-source collisions, competing rule mappings, prefix termination, exact resolved targets, and canonical operand interception before publishing the candidate. Same-batch target order is irrelevant. Missing targets and operand-bearing destinations are errors.
  3. Accept identical normalized mappings, including repeated contributions across registration calls. Revalidate existing aliases when a new canonical batch changes reachability. Invalidate derived snapshots only on accepted changes.
  4. Preserve old registry/help behavior after every failed batch. Aliases inside an installed standard-family spelling remain routing records and never bypass canonical root-only placement or merge command trees.

Evidence: both registration orders; retained wrapped errors; duplicates; missing/forward targets; existing and same-batch collisions; canonical children versus runnable operand space; valid longer aliases beneath r → run; failed registration after a snapshot was used. Explicitly reject promptkit test full → promptkit test all under a test KIND fixture.

Completed: Replaced direct registry mutation with a candidate that validates the complete canonical tree and normalized routing rules before publishing. Alias chains and termination, missing or operand-bearing targets, canonical source and operand collisions, duplicate idempotence, later command conflicts, and failed-batch preservation are covered. Verified with go test ./... -count=1.

Stage 6: Compile effective flag aliases during installation

Work

  1. Extend the candidate with global and canonical-command-scoped flag rules. Build effective mappings from neutral canonical flag metadata and core help spellings, without preparing application inputs.
  2. Resolve chains, apply scoped precedence, check final availability and active canonical-source collisions, and reject cycles. Handle long-to-short, short-to-long, and canonical short endpoints consistently.
  3. Accept globals whose destinations are currently absent. Recompute all affected contexts when later commands arrive; reject the whole batch if a newly active global now overloads a canonical spelling.
  4. Add the root and group contexts; support the restricted help context for alias-only prefixes. Keep command-scoped aliases local to their canonical command rather than implicitly inheriting through descendants.

Evidence: different-target duplicates versus identical duplicates; explicit unknown scopes; inactive globals; scoped overrides inside a chain; cycles; --colour → --color terminating at canonical --color even with an inactive global --color → --missing; collision after --missing becomes available; root/group help targets and failed-batch preservation.

Completed: Installation candidates now compile effective global and canonical-command-scoped flag aliases from captured canonical metadata and built-in help spellings. Chain resolution applies scope precedence, tracks inactive global destinations, detects cycles, and rejects newly active canonical-source collisions without publishing failed candidates. Root, group, command, and alias-prefix discovery contexts retain only their applicable spellings. Verified with root and both integration module suites using the local workspace.

Stage 7: Resolve entered routes to canonical commands

Work

  1. Add alias lookup records to the App snapshot without adding alias nodes or runners to the canonical command tree. Reuse the validated longest-prefix semantics and Stage 1 paths.
  2. Implement command/group rewriting with token provenance, remaining argv, canonical selection, and a distinct discovery-prefix result. Stop path matching at flag/terminator boundaries and canonical operands.
  3. Retain the original entered endpoint and final canonical endpoint, not intermediate chain names. Do not use the final path length to slice original argv. Unknown paths retain the entered route without a fabricated target.
  4. Ensure a failed longer explicit mapping never retries a shorter mapping. Centralize this lookup for execution and help consumers in later stages.

Evidence: collections → list collections → promptkit list collections; pk → promptkit composed with promptkit list bundles; longest explicit pk list bundles override; smoke all retaining its operand; r full choosing an explicit longer route while canonical run full remains unchanged; flags/values/space-containing argv tokens never becoming command components.

Completed: Routing snapshots now retain canonical path metadata and the separate command-alias resolver. Execution resolves command prefixes before parsing, tracks the original input-token origins through alias chains, and preserves the remaining argv. Canonical operands, flags, terminators, unknown paths, and alias-only discovery prefixes receive distinct route results. Verified with root and both integration module suites using the local workspace.

Stage 8: Integrate parser-aware flag aliasing and short grammar

Work

  1. Pass the selected canonical/discovery context's effective spellings to the internal adapter through neutral records. Keep Cobra/pflag imports internal.
  2. Implement the single alias-aware token interpretation pass described above. Canonical and aliased short flags use the same cluster logic. Preserve raw values, explicit empties, --, and interspersed operands.
  3. Feed canonical backend flags without invoking a typed parser twice. Collect original spelling provenance per occurrence alongside canonical raw values.
  4. Correct canonical -c= and boolean -v= at this same boundary; do not patch aliases alone or pre-rewrite every token beginning with a dash in core.

Evidence: every token example in the roadmap's flag sections; mixed canonical/alias clusters; -cvq consuming vq; -qv=false; explicit empty values for long, short, and aliased spellings; missing values; unknown cluster members; --message --hlep as a value; --message=--hlep; post--- operands.

Completed: The routed flag context now reaches the adapter through neutral flag-alias records. One adapter pass interprets canonical and alternate long and short spellings, preserves option values and terminator operands, and feeds only canonical flags to pflag. Raw values retain per-occurrence source spellings. Canonical short empty values now follow the same long-form boundary. Verified with root and both integration module suites using the local workspace.

Stage 9: Wire execution and invocation identity

Work

  1. Use the route result to select the existing canonical payload and its input preparation. Populate both Invocation accessors for ordinary and alias invocations, including callbacks reached by private built-in plumbing.
  2. Replace the current directly constructed Invocation in the typed runner with the coordinated context so metadata reaches handlers without changing their signature. Retain caller context and streams.
  3. Keep canonical parsing/conversion, required checks, input/state lifetimes, CaptureFlags, provider acquisition, and domain outcome classification on their existing paths. No handler delegates to another alias handler.
  4. Keep failed routing, invalid flags, and help from constructing application input or running acquisition. Preserve the existing distinction that help still converts supplied values but skips execution-required checks.

Evidence: a canonical handler observes equal endpoints directly and distinct endpoints through aliases; operands are absent from both path strings; alias and canonical forms reach the same binding/callback once; repeated calls and declaration reuse do not leak invocation values; zero Invocation accessors are empty. Existing cancellation and domain outcome checks remain green.

Completed: Invocation now retains the entered and canonical route paths alongside the caller's context and streams. Public typed handlers and private callbacks receive the same resolved identity. Tests cover canonical and alias calls with operands, repeated calls, declaration reuse across Apps, private callback plumbing, and zero-value accessors. Verified with the root, ckconfig, and ckprompt suites under the local workspace.

Stage 10: Add alias-aware help and discovery views

Work

  1. Route help COMMAND... targets through the shared metadata lookup, despite those tokens being operands of the built-in help command. Do not globally rewrite arbitrary operands to obtain this special help-target behavior. Keep the help command's invocation identity separate from its requested view: help list collections invokes canonical help, while rendering the entered target list collections and its resolved canonical command.
  2. Coordinate ALIAS --help, bare canonical groups, and aliases to groups with the same help selection. Render canonical inputs with entered usage paths and a canonical-target annotation when the paths differ.
  3. List explicit root aliases separately from canonical commands. Group views include relevant aliases separately from children, accounting for more-specific overrides. Do not enumerate every possible composed shortcut or chain hop.
  4. Implement help for alias-only prefixes without canonical nodes or namespaces. Bare incomplete prefixes are usage failures pointing to help. Global help flag aliases work on these views; descendant flags do not become available.
  5. Add only effective flag aliases to each canonical input's help, resolving chains to endpoints and describing the canonical input once. Keep lookup records independent of rendering and provider callbacks.

Evidence: root/group/alias/discovery help; help list collections and list collections --hlep; help promptkit list showing bundles; group shortcut help with a more-specific destination override; inactive/scoped flag visibility; no input/state factories or providers called. Preserve existing help output-delivery handling.

Completed: Help targets and --help routes now use shared command-alias resolution. Canonical help views render entered usage paths with canonical target annotations; root and group views list applicable declared aliases and effective flag aliases. Alias-only prefixes render discovery help through help PREFIX or global help spellings, while bare incomplete prefixes return a usage failure pointing to help. Verified help selection, scoped/global visibility, more-specific group overrides, and input/run callback lifetimes with root and both integration module suites under the local workspace.

Stage 11: Preserve entered spellings in errors and delivery outcomes

Work

  1. Thread flag occurrence provenance into private conversion-error context. Identify the actual failing occurrence, including an earlier invalid value followed by a valid overwrite. Wrap rather than replace application causes.
  2. Update usage presentation to use entered command paths, identify a resolved canonical target, and preserve the target's diagnostic content. An unresolved route does not acquire an invented canonical label.
  3. Use existing usage/operational/domain classifications and diagnostic-delivery ownership. Route aliases must not duplicate already delivered diagnostics. Preserve writer errors, short writes, cause identity, and incomplete reports.
  4. Check alias-related help/usage presentation against failed and successful registration so a stale snapshot does not mislabel the selected operation.

Evidence: malformed aliased scalar and repeatable occurrences; mixed spellings; wrapped errors.Is/errors.As; usage paths with canonical annotation; unknown routes and flags; cancellation; output and diagnostic failures. Assert semantic fragments and identities, not entire backend wording snapshots.

Stage 12: Verify registration and routing interactions end to end

Work

  1. Extend public external-package tests with command-only and simulated root-only family fixtures. Exercise construction, later registration, retained declaration errors, reuse across Apps, and failed-batch recovery.
  2. Verify canonical invocations retain their declared operand space in both installation orders, while longer mappings beneath alternate spellings work. Cover optional, required, and repeated operand metadata without calling domain value converters at installation.
  3. Verify same-batch alias chains and globals see the entire candidate, and later command additions trigger full relationship validation. Check cached lookup/help before and after successful and failed registration.
  4. Include a core-only consumer proving alias infrastructure needs no optional integration imports. Keep alias records out of the canonical handler tree.

Evidence: run the focused external registration, routing, and help suites. Fill missing cross-boundary cases rather than duplicating the private algorithm test inventory. No concurrent-dispatch contract is introduced.

Stage 13: Verify typed bindings and integration composition

Work

  1. Cover singleton last-occurrence behavior, conversion of every supplied occurrence, repeatable ordering, explicit false/zero/empty presence, required checks, and fresh invocation state with mixed flag spellings.
  2. Verify captured selector bindings still reach selected typed integration inputs. Test a command-scoped flag alias reached through a command shortcut, and global help on standard families, without rechecking upstream semantics.
  3. Add focused ckprompt composition cases for actual installed collection and named-inspection commands, plus the valid smoke → promptkit test shortcut. Use local callbacks; smoke tests should assert forwarded kind and returned report/outcome, not perform network requests.
  4. Reject the invalid smoke operand-synonym mapping against the actual promptkit test KIND declaration. Preserve ordinary canonical calls in the same application. Use a small ckconfig alias case for selector forwarding.

Evidence: root binding suites and affected integration suites under the local workspace. Do not claim nested modules' pinned core v0.2.0 graph validates new alias APIs. See Stage 17 for independent local-source graph verification.

Stage 14: Update maintained consumer examples

Work

  1. Extend examples/core with application command shortcuts, a group shortcut, a global help typo alias, and a scoped flag alias. Demonstrate a mixed []Declaration collection and the two Invocation path accessors with small application-owned behavior.
  2. Extend the composed ckprompt consumer with list collections → promptkit list collections, promptkit list bundles → promptkit list collections, and pk → promptkit. Add scoped --fmt → --format for named prompt inspection and --hlep → --help globally. Keep canonical suite loading and domain commands unchanged; install aliases alongside the complete canonical declarations.
  3. Add the valid smoke command-path shortcut if useful for the existing local smoke fixture. Do not represent all as a child command or alias target.
  4. Update example tests to show aliases reaching canonical operations, help without acquisition, and ordinary canonical commands still working. Keep outputs local and deterministic.

Evidence: root and composed-example tests pass against the new local core; the examples remain complete, copyable artifacts. Explain local source testing and forthcoming core dependency adoption in their developer-facing guidance; do not claim published core v0.2.0 has alias support.

Stage 15: Reconcile public and internal documentation

Work

  1. Add GoDoc for Declaration, RoutingRule, factories, installation changes, and Invocation accessors. Explain eager capture, factory errors retained for installation, nil/zero handling, duplicate rules, and canonical scope paths.
  2. Update docs/cli.md with implemented alias routing, command/operand boundaries, flag scope/availability, short grammar and empty-value correction, entered-path diagnostics, and help/discovery behavior. Preserve canonical upstream command and report contracts.
  3. Update the consumer guide and example READMEs for []Declaration, direct Command calls, slice conversion, and explicit root installation of rules. Include a minimal real integration composition example and links to the maintained complete consumers.
  4. Update the internal overview and applicable architecture provisions for the separate canonical/routing layers and joint installation. ADR 0013 already authorizes this architecture; do not rewrite accepted decisions or add an ADR for incidental algorithm or presentation details.
  5. Update developer navigation to the roadmap/plan while work is active and preserve deferred.md. Keep shell completion as future direction only.

Evidence: local links and fragments resolve; volatile behavior has its canonical owner; no future alias APIs are described as existing before their implementation is present. Prose checks and relevant examples agree.

Stage 16: Record compatibility impact for the next core release

Work

  1. Prepare the next core minor release note, docs/releases/v0.3.0.md, with the product title, short summary, migration guidance, and required immutable licensing footer under the release procedure. Do not edit published notes.
  2. Explain the ...Command to ...Declaration change, especially []Command expansion and function-value signature changes; command/group factories still return Command. Note private Invocation path storage's impact on positional struct literals and recommend keyed construction when needed.
  3. Explain command/flag aliases and the explicit short-option empty-value correction. Go 1.26.0 and the current upstream/backend selections need not change for this feature.
  4. Keep publication and real integration dependency bumps separate. The next core must be published before dependent module release preparation downloads that version and records actual sums, as required by docs/release.md. Do not invent core v0.3.0 sums, commit local replacements, or rewrite the integrations' existing v0.1.0 release notes. Report this release-preparation dependency in the final implementation handoff.

Evidence: note explains consumer action and candidate-local link paths and footer are correct. No release launcher, remote push, or tag operation occurs as part of implementing this plan.

Stage 17: Verify the complete target and finish

Work

  1. Format changed Go sources and run the complete deterministic root suite and both integration suites using the local three-module workspace described by the developer guide. Use the documented Go 1.26.8 maintainer toolchain; this feature does not raise the consumer minimum.
  2. Root independent validation uses GOWORK=off and its actual module graph. For nested modules needing unpublished local core APIs in tests/examples, use disposable copies of each go.mod/go.sum through -modfile, with a local core replacement; replace ckconfig with its local source for composed verification where needed. Run GOWORK=off module suites against those copies. Permit only disposable metadata to change and inspect it separately. This verifies local source composition, not published-version availability.
  3. Leave committed dependency constraints and genuine sums intact until their separately authorized release preparation. Do not run nested suites against old published core and count failures as feature failures or count old code's success as evidence for new aliases. Do not publish dependencies to make this implementation sprint's checks pass.
  4. Confirm core still has no optional-library dependency, backend imports stay internal, integration factories remain canonical Command factories, and alias lookup requires no application/provider initialization.
  5. Audit every roadmap behavior against the evidence index below. Verify metadata paths, help views, errors, and failed-registration recovery, not only successful alias execution. Reuse existing focused evidence rather than creating an additional redundant test framework or release gate.
  6. Run prose whitespace and local-link checks; inspect all diffs and intended files. Mark stages complete with actual evidence, update the feature roadmap's status, commit remaining intended work, and report validation plus any release-preparation steps still required. Preserve completed planning records until cleanup is requested.

After selecting the documented toolchain and preparing the existing dependency cache, the independent graph checks can be performed from the repository root as follows. The workspace checks above are still needed; these commands exercise each module's own metadata with only the indicated local source substitutions. The temporary-file updates accommodate unpublished source without changing tracked version requirements or synthesizing published checksums.

(
    set -eu
    commandkit_alias_repo=$(pwd)
    commandkit_alias_verify=$(mktemp -d /tmp/commandkit-alias-verify.XXXXXX)
    trap 'rm -rf "$commandkit_alias_verify"' EXIT
    export GOTOOLCHAIN=local GOWORK=off
    export GOPROXY=off GONOPROXY=none GOSUMDB=off

    go test -mod=readonly ./...

    cp integrations/ckconfig/go.mod "$commandkit_alias_verify/ckconfig.mod"
    cp integrations/ckconfig/go.sum "$commandkit_alias_verify/ckconfig.sum"
    go mod edit -modfile="$commandkit_alias_verify/ckconfig.mod" \
        -replace="gitea.maximumdirect.net/eric/CommandKit=$commandkit_alias_repo"
    (cd integrations/ckconfig &&
        go test -mod=mod -modfile="$commandkit_alias_verify/ckconfig.mod" ./...)

    cp integrations/ckprompt/go.mod "$commandkit_alias_verify/ckprompt.mod"
    cp integrations/ckprompt/go.sum "$commandkit_alias_verify/ckprompt.sum"
    go mod edit -modfile="$commandkit_alias_verify/ckprompt.mod" \
        -replace="gitea.maximumdirect.net/eric/CommandKit=$commandkit_alias_repo"
    go mod edit -modfile="$commandkit_alias_verify/ckprompt.mod" \
        -replace="gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig=$commandkit_alias_repo/integrations/ckconfig"
    (cd integrations/ckprompt &&
        go test -mod=mod -modfile="$commandkit_alias_verify/ckprompt.mod" ./...)
)

If an offline command reports an absent dependency, prepare that dependency separately and rerun the affected check. Do not turn an offline suite into a network-enabled test run or silently substitute older published implementation. These local-source graph checks do not add a permanent release gate.

Complete when: the accepted feature works through the public consumer boundary, the selected checks pass against local source, documentation matches the implementation, and no unresolved feature decisions or implementation work remain. Publishing and dependent-version availability are separate operations.

Target evidence index

Accepted behavior Primary stages
Public factories, capture, absence/zero/nil handling, slices 1, 4, 5, 12, 15, 16
Exact chains, longest matches, group composition, universal termination 2, 3, 5, 7, 12
Canonical ownership, operand boundaries, no preset operands 5, 7, 12, 13
Same-batch and later installation, duplicates, rollback 5, 6, 12
Global/scoped flag aliases, chains, endpoints, availability, collisions 6, 8, 12, 13
Values, operands, clusters, empty values, presence, repeat order 8, 9, 11, 13
Entered/canonical identity and ordinary lifecycle 7, 9, 11, 13
Root/group/alias-prefix help, effective flag help, target-aware usage 7, 10, 11, 12
Error identity, cancellation, report/diagnostic delivery 9, 11, 13
Real consumer composition and module boundaries 12, 13, 14, 17
Future-completion lookup separation without completion delivery 7, 10, 15
Migration, current references, release-note preparation 14, 15, 16, 17