From cc574915ee17e2d738f27874d2c8857aa1f51a47 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 11 Oct 2026 02:13:16 +0000 Subject: [PATCH] Validate independent module boundaries --- README.md | 1 - docs/development.md | 4 +- docs/oss/attribution.md | 13 + docs/roadmap/implementation.md | 44 +- docs/roadmap/modules.md | 299 ---- docs/roadmap/registration-api.md | 1288 ----------------- docs/roadmap/registration-backend.md | 500 ------- integrations/ckconfig/go.mod | 6 + .../ckprompt/examples/composed/README.md | 2 +- integrations/ckprompt/go.mod | 11 + 10 files changed, 61 insertions(+), 2107 deletions(-) delete mode 100644 docs/roadmap/modules.md delete mode 100644 docs/roadmap/registration-api.md delete mode 100644 docs/roadmap/registration-backend.md diff --git a/README.md b/README.md index 085300a..40679a4 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,6 @@ integration. - [Architecture policy](docs/policy/architecture.md) - [Policy provenance](docs/policy/meta.md) - [Deferred capabilities and proposals](docs/roadmap/deferred.md) -- [Registration API design record](docs/roadmap/registration-api.md) CommandKit is licensed under the [LGPL v3.0 or later](COPYRIGHT). diff --git a/docs/development.md b/docs/development.md index 46443db..6885023 100644 --- a/docs/development.md +++ b/docs/development.md @@ -131,9 +131,9 @@ The maintained consumers compile with their owning module suites and run without external services: ```sh -go run ./examples/core greet --count=0 --tag=first --tag second +go run ./examples/core workspace greet --count=0 --tag=first --tag second (cd integrations/ckconfig/examples/simple && go run . config show) -(cd integrations/ckprompt/examples/composed && go run . config validate --scope all --campaign spring) +(cd integrations/ckprompt/examples/composed && go run . config validate --scope all --campaign spring --limit=0) (cd integrations/ckprompt/examples/composed && go run . promptkit show prompt briefing --campaign spring --format yaml) (cd integrations/ckprompt/examples/composed && go run . session restore session-42 --campaign spring) ``` diff --git a/docs/oss/attribution.md b/docs/oss/attribution.md index 9e4c158..6a95764 100644 --- a/docs/oss/attribution.md +++ b/docs/oss/attribution.md @@ -252,3 +252,16 @@ License and notices: [BSD 3-Clause](licenses/BSD-3-Clause.txt) | [LICENSE](https://go.googlesource.com/mod/+/d3398d06de5fa5c71083d3d1c26f2cda73508e0f/LICENSE). [Source](https://go.googlesource.com/mod/+/d3398d06de5fa5c71083d3d1c26f2cda73508e0f). + +## Go tools + +Selected module: `golang.org/x/tools` at `v0.49.0` (ReleaseKit tool graph dependency). + +Copyright (c) 2009 The Go Authors. All rights reserved. + +SPDX-License-Identifier: BSD-3-Clause + +License and notices: [BSD 3-Clause](licenses/BSD-3-Clause.txt) | +[LICENSE](https://go.googlesource.com/tools/+/18332fec72972efbb8ab9881984fec2d8cfc2b58/LICENSE). + +[Source](https://go.googlesource.com/tools/+/18332fec72972efbb8ab9881984fec2d8cfc2b58). diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 3a5e340..676591b 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,18 +1,22 @@ # Implement the optional integration modules and captured registration API -Status: implementation in progress. Stages 1 through 15 are complete; subsequent work follows the ordered roadmap. +Status: implementation complete. Stages 1 through 16 are complete. Execute the stages in order. Each stage is sized as a separate Luna coding-agent assignment. This document authorizes no publication or changes to sibling repositories. ## Scope and authority -Deliver the target in the [module roadmap](modules.md), the exact declarations -and behavior in the [registration API](registration-api.md), and the selected -mechanics in the [registration backend design](registration-backend.md). -Read [development orientation](../development.md) and its applicable policy guide -before implementation. The [architecture policy](../policy/architecture.md) and -accepted ADRs control if a planning instruction conflicts with them. +This report retains the implementation sequence and completion evidence. +Current contracts belong to the [core GoDoc](../../doc.go), +[ckconfig GoDoc](../../integrations/ckconfig/doc.go), +[ckprompt GoDoc](../../integrations/ckprompt/doc.go), +[consumer guide](../consumers/guide.md), [CLI reference](../cli.md), +[internal overview](../internal/overview.md), and +[release procedure](../release.md). Read [development orientation](../development.md) +and its applicable policy guide before related work. The +[architecture policy](../policy/architecture.md) and accepted ADRs control +architecture decisions. The governing decisions include [optional modules](../adr/0010-optional-integration-modules.md), [captured declarations](../adr/0011-captured-declarations-and-installation.md), @@ -51,9 +55,8 @@ Go 1.26.8. The planning review found one module, integration source and examples in the root, ReleaseKit v0.3.0 selected as a Go tool, and a root-only release launcher. -The [backend findings](registration-backend.md#baseline-findings) identify the -parallel installation paths, repeated validation, mixed input/state storage, and -help special case that this sprint replaces. +The selected owners and current mechanics are recorded in the +[internal overview](../internal/overview.md) and current package GoDoc. One factual correction to earlier planning is necessary: core `v0.1.0` is already published on `origin`. The observed annotated tag object is @@ -614,7 +617,7 @@ dependency ownership. 2. Rewrite ckconfig's simple example using `AppCommand`, with no dummy input/state types at its consumer call site. Retain a useful typed application command. 3. Rewrite ckprompt's composed example using the finalized - [three consumer examples](registration-api.md#complete-consumer-registration-examples) + [three consumer examples](../consumers/guide.md) as API evidence: combined app/promptkit configuration scopes, applicable typed selectors, scope-aware checks, pointer input isolation, shared app-owned state, independent named inspection, and `session restore`. @@ -845,11 +848,20 @@ publication wiring are verified with no compatibility scaffolding. pending real dependency/sum preparation. The workspace should contain no unrelated generated files or temporary replacement manifests. -**Completion evidence:** all three module suites and representative consumers -succeed; module/package graph checks prove the requested exclusion boundaries; -current docs and licensing reflect distribution ownership; publication tooling -selects one module through released contracts. Actual publication and upstream -version adoption remain separate from this sprint. +**Completion evidence — complete:** all three module suites pass with the local +workspace, and the five maintained consumer invocations succeed. Workspace-off +tests and three external consumer modules also pass using temporary module files +with local replacements only for unpublished CommandKit modules; package and +module graph checks confirm the intended core, ckconfig, and ckprompt dependency +boundaries. Root tidy is clean, nested manifests were reconciled from temporary +tidy runs, and no future-version checksums or distributed filesystem +replacements were added. Public exports and ownership, installation/input cost +paths, maintained guides, local links, license distribution, release notes, and +the selected-module source-only release wiring were reviewed. Obsolete module, +registration API, and backend planning documents were retired after their current +owners and links were updated. No real publication was attempted. Actual +dependency downloads and checksum preparation remain part of ordered release +preparation after the prerequisite versions are published. ## Release handoff diff --git a/docs/roadmap/modules.md b/docs/roadmap/modules.md deleted file mode 100644 index b80f237..0000000 --- a/docs/roadmap/modules.md +++ /dev/null @@ -1,299 +0,0 @@ -# Optional integration modules - -Status: module boundary accepted in ADR 0010; consumer API and internal backend -design finalized, with both composition questions resolved. The -[staged implementation plan](implementation.md) is complete; no module extraction -has begun. ReleaseKit v0.4.0 provides module-release support; CommandKit adoption -is pending. - -## Purpose and authority - -Make CommandKit useful to consumers that use both ConfigKit and PromptKit, -ConfigKit alone, or neither library. Consumers using only CommandKit's core -should acquire neither ConfigKit nor PromptKit through CommandKit's package or -module dependencies. ConfigKit-only consumers should not acquire PromptKit. - -This roadmap records the targeted end state, selected module layout, and -delivery boundaries. The [implementation plan](implementation.md) owns the ordered -steps and validation needed to reach this target. The finalized -[registration API](registration-api.md) owns the -selected declarations, installation semantics, and representative consumer -examples. The finalized [registration backend design](registration-backend.md) -owns internal structure, shared installation/execution mechanics, and the resolved -composition decisions. These documents describe the implementation target. -[ADR 0010](../adr/0010-optional-integration-modules.md) owns the accepted module -boundary and convenient registration principles; it partially supersedes -[ADR 0003](../adr/0003-public-facade.md), whose remaining principles still apply. -[ADR 0011](../adr/0011-captured-declarations-and-installation.md) records the -finalized registration and invocation-lifetime decisions. The -[architecture policy](../policy/architecture.md) is aligned with those decisions. -GoDoc, the [CLI reference](../cli.md), and the [consumer guide](../consumers/guide.md) describe the prototype baseline; they do -not make its incidental API shapes compatibility requirements for this sprint. - -## Organizing principle: design the final API - -Give the existing prototype implementation zero deference when selecting the -final API, as directed for this transition. Core `v0.1.0` has since been published; -preserve that release and prepare the incompatible replacement as a fresh minor -release under the release policy. Publication does not change the selected API -or require compatibility wrappers for this sprint. The goal is -the best, simplest, most coherent API for the accepted responsibilities and -representative consumers, not the smallest diff or a mechanically relocated -prototype. Accepted architectural decisions and explicitly selected behavior -remain requirements; prototype declarations, helpers, bookkeeping, and tests do -not independently establish contracts. - -Complete the transition in one sprint. No backward-compatibility wrappers, -deprecation period, or migration bridge is needed. Intermediate commits need not -build or pass tests; do not add temporary machinery merely to make them do so. -Validate the complete target state before release. Reuse existing code where it -serves that design, and refactor, replace, or remove it where it does not. - -## Current baseline - -At the start of this roadmap, CommandKit has one Go module and one supported -public root package. It requires ConfigKit v0.5.0 and PromptKit v0.12.0 and -declares Go 1.26.0. Its internal Cobra adapter implements routing and parsing. - -The root combines core command infrastructure with configuration and PromptKit -inspection. Coupling extends beyond constructors: - -- `NewConfigKitScope` accepts ConfigKit results and maps their diagnostics. -- Core `Diagnostic.Severity` currently uses ConfigKit's public severity type; - moving inspection handlers alone will not remove that dependency. -- `SourceReader` returns ConfigKit source records; `ExplanationReader` accepts - ConfigKit paths and returns ConfigKit explanations. -- `PromptKitSuite` callbacks return PromptKit inspection types. -- `NewPromptKitScope` adapts engine configuration into the configuration suite. -- Standard handlers share root-private helpers and state. For example, - configuration registration accesses the application's registry and records - whether the suite has already been installed. - -The [internal overview](../internal/overview.md) describes the implemented -inventory. The maintained examples and integration tests currently belong to the -root module. ReleaseKit v0.3.0 is also selected there as a Go tool dependency; -the [release procedure](../release.md) assumes one module and one version tag. - -Optional command registration currently controls behavior, not dependencies. -Moving integration code into packages sharing the root `go.mod` would separate -compiled imports but retain integration requirements in the module graph. -Go's [module graph rules](https://go.dev/ref/mod#module-graph-pruning) explain -that distinction. - -## Targeted end state - -Provide three consumer modules in this repository with dependencies directed -toward the core. Keep the core at `gitea.maximumdirect.net/eric/CommandKit` and -give each integration its own `go.mod`: - -| Directory | Module path | Package name | -| --- | --- | --- | -| Repository root | `gitea.maximumdirect.net/eric/CommandKit` | `commandkit` | -| `integrations/ckconfig` | `gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig` | `ckconfig` | -| `integrations/ckprompt` | `gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt` | `ckprompt` | - -The `integrations/` grouping keeps optional modules together as their number -grows; the anticipated three to ten integrations are planning context, not a -limit or delivery commitment. Distinct package names avoid requiring local -aliases when these integrations are imported alongside ConfigKit and PromptKit. -Separate module files establish dependency isolation; directory naming alone -does not. The shared repository requires explicit multi-module testing and -publication orchestration. - -| Module | Responsibility | Integration dependencies | -| --- | --- | --- | -| Core | Application construction, command/group registration, typed bindings, dispatch, help/version, common grammar, outcomes, and diagnostics | Neither ConfigKit nor PromptKit | -| ConfigKit integration | Standard configuration family, scope composition, reports, and ConfigKit adapters | Core and ConfigKit; no PromptKit | -| PromptKit integration | Named prompt/profile inspection and the PromptKit configuration-scope adapter | Core, PromptKit, and the ConfigKit integration | - -The core remains an opinionated command library with its existing internal -Cobra boundary. The dependency goal is independence from ConfigKit and PromptKit, -not a standard-library-only implementation. - -Integrations remain maintained parts of CommandKit. They install commands into -the same application through supported public core operations and follow its -grammar, help, invocation, and outcome conventions. Applications retain domain -commands, acquisition, runtime construction, streams, and process exit. - -The selected configuration boundary moves the existing ConfigKit-shaped -contracts with their handlers into the ConfigKit integration. Only ConfigKit -support is needed for now; a generic configuration interface is outside this -migration. The PromptKit integration may depend on the ConfigKit integration, -consistent with PromptKit's own ConfigKit dependency. Document that relationship -in consumer guidance: `ckprompt` depends on `ckconfig`. - -This boundary preserves one configuration suite into which -applications compose `app`, `promptkit`, and other supported scopes. PromptKit -configuration inspection and named-definition inspection remain independently -selectable. A module dependency does not automatically install a command family. - -Core public signatures and implementation imports contain no ConfigKit or -PromptKit types. Integration-specific types and helpers move to their owning -modules. Do not retain root forwarding wrappers that restore those imports. -Shared helpers stay private unless a concrete cross-module interaction needs a -supported contract; extraction is not a reason to expose every existing helper. - -Preserve explicitly selected operator behavior: command vocabulary, selection -and scope semantics, native reports, diagnostics, error identity, outcomes, and -help without provider acquisition. Identify and decide any changes to selected -CLI behavior separately. Do not preserve accidental prototype behavior or Go API -shapes merely because they already exist. - -### Selected API and release principles - -- Use the finalized [uniform registration API](registration-api.md#declaration-and-installation). - Applications and integrations produce captured core command declarations and - install them through core `New` or `App.Register`; construction errors are - retained for installation-time validation. Integration factories provide - complete families and focused conveniences. The API document owns exact - signatures and the [input/shared-state separation](registration-api.md#why-inputs-and-shared-state-have-separate-types). -- Follow the selected [namespace lifetime and reconciliation requirements](registration-api.md#namespace-reconciliation-and-implementation-handoff). - Built-in help/version remain reserved; optional integration roots are owned - when their real family is installed. ADR 0011 and the architecture policy now - record that lifetime; implementation remains pending. -- Use the finalized [internal registration and execution design](registration-backend.md). - Built-ins, application commands, and integration families share one - [installation path](registration-backend.md#one-installation-path-for-every-command) - and typed compiler. Captured metadata and invocation state have distinct owners; - the backend document defines the capture, binding, routing, and presentation - mechanics without adding another integration installation API. -- Apply the resolved [unused-binding validation boundary](registration-backend.md#resolved-unused-binding-validation-oq-1) - and [scope-error handoff](registration-backend.md#resolved-scope-error-handoff-oq-2). - The registration API incorporates the selected runnable-parent and scope-error - attachment declarations. No absence-validation overload is selected. -- Core diagnostics use a small CommandKit-owned severity type. Upstream - diagnostic conversion belongs in the integrations, preserving selected - classification, delivery, and error semantics without copying ConfigKit's - wider data model into the core. -- Version the modules independently according to their own changes and the - [release policy](../policy/release.md#versioning-and-compatibility). Do not - require integration version numbers to match either the core or upstream - dependency versions. Record selected dependencies in `go.mod` and explain - relevant upstream adoption in module release notes. Coordinate publication - when changes span modules; independent versions do not imply an exhaustive - compatibility matrix. - -### Selected multi-module release approach - -Adopt ReleaseKit v0.4.0 with module-release support. This is a planned tooling -upgrade from the current v0.3.0 baseline, not an implemented CommandKit capability. -Use ReleaseKit's public contracts for checking and -publishing one explicitly selected module per invocation; do not build a separate -CommandKit publication engine or temporary compatibility path. - -Use one shared launcher with small per-module profiles. Each profile selects its -repository-relative module directory and product identity; each invocation -supplies that module's independently selected semantic version. ReleaseKit owns -module-tag derivation, selected-module validation, repository publication guards, -and publication outcomes. Root releases use unprefixed tags. Nested releases use -their Go module-subdirectory prefixes, and notes belong at -`/docs/releases/.md`. Source and licensing links use -the complete Git tag, while note titles identify the product and semantic version. - -Run the selected module's standard suite once with workspace mode disabled and -prepared dependencies. Repository cleanliness, branch, candidate commit, and -endpoint guards remain repository-wide. A root-module release does not implicitly -validate independently released nested modules; composition checks are selected -deliberately where needed. Keep successful source-tag publication as completion, -without CI, hosted delivery, proxy-availability, or installation gates. - -CommandKit's preferred shared file inputs are repository-root `COPYRIGHT`, -`LICENSE`, and `docs/oss/attribution.md`, when present, rather than the entire -root `docs/` tree. Use v0.4.0's `validation.shared_files` field to select those -three files explicitly, as documented by ReleaseKit. Shared inputs affect enabled -file checks, not Go suite scope; resolving references does not add surrounding -directories to the inputs. - -Keep module selection and profile lookup repository-relative, module-owned -validation settings and product-command execution module-relative, and diagnostic -paths repository-relative. Use the provided repository context for shared scripts. -The released [profile](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/b96262eb0ce9c8f2eb3e948a5841db2b799bc91b/docs/config.md) -and [CLI](https://gitea.maximumdirect.net/eric/ReleaseKit/src/commit/b96262eb0ce9c8f2eb3e948a5841db2b799bc91b/docs/cli.md) -contracts govern profile fields, command syntax, module context, and shared-file -selection. The implementation plan specifies their adoption; no separate -publication engine is needed. -Nested-module artifact builds, hosted delivery, and batch publication are not -needed for CommandKit's source-only releases. - -## Consumer examples and validation target - -Three maintained public consumers demonstrate the final API: - -| Adoption path | Example home | Required evidence | -| --- | --- | --- | -| Core only | `examples/core` | Typed commands/groups, presence, help/version; no optional-library imports or module requirements | -| ConfigKit only | `integrations/ckconfig/examples/simple` | Focused `AppCommand` convenience and standard config reports; no PromptKit imports or module requirements | -| ConfigKit and PromptKit | `integrations/ckprompt/examples/composed` | Combined configuration scopes, typed applicable selectors, independent scope inputs, shared state, named definition inspection, and an application-owned command | - -Tests, fixtures, and benchmarks belong to the module owning their contracts. -Mixed suites are split by responsibility; root tests and examples cannot -reintroduce optional integration dependencies. Public composition checks belong -in the PromptKit integration. Relevant existing coverage is retained or rewritten -for selected behavior rather than incidental prototype API shapes. - -Validation addresses package imports and each independently selected module -graph, including transitive and tool requirements. Core-only consumers exclude -both owning libraries and integrations; ConfigKit-only consumers exclude -PromptKit and its integration. Root `go test ./...` alone cannot validate nested -modules. The three modules' suites and representative public consumers provide -composition evidence without an exhaustive historical compatibility matrix. - -Local workspace composition supports development; distributed manifests contain -versioned inter-module requirements, without filesystem replacements or committed -workspace files. Workspace-off prepublication checks against local intended -source establish wiring and isolation, not published-version availability. -Released consumers resolve actual independently released versions without -adjacent checkouts or consumer replacements. The implementation plan defines the -prepublication checks and ordered release handoff. - -## Documentation and distribution target - -Implemented GoDoc owns exact exports and error/lifetime semantics. The consumer -guide and READMEs present the final imports and optional-module selection directly; -maintained examples demonstrate it. The CLI reference retains selected operator -behavior, with registration/ownership prose matching the implemented API. Internal -inventory and contributor navigation describe implemented owners and validation. -No separate downstream prototype migration guide is required. - -Each integration distributes a module-local README, project grant, complete -license texts, applicable attribution and required notices, and module-local -release notes. Required licensing material remains available in that module's Go -source distribution rather than only through an outside-module local link. The -existing grant and separately licensed material retain their terms. Repository -attribution can retain factual entries for integration material while module-local -notices identify applicable components. - -Modules use independent versions. The planned core replacement is `v0.2.0`; -ckconfig and ckprompt initially use `v0.1.0`. Their dependency requirements express -that relationship explicitly. The already published root `v0.1.0` and its note -remain intact. If a planned version is occupied before preparation, select a -fresh appropriate version under the release policy. Core publication precedes -ckconfig, which precedes ckprompt; actual released dependencies and corresponding -sums are prepared for each candidate. Source-only publication still completes at -successful tag publication, without proxy-availability or installation gates. - -The release procedure and CommandKit-specific release-policy adaptations describe -selected-module publication, independent versions, semantic-version note paths, -full-tag links and recovery identity, and the root shared-file selection. Release -profiles and launcher use released ReleaseKit contracts; additional delivery -services and permanent migration gates are outside the target. - -## Implementation status - -ADR/architecture alignment, public API design, and internal backend design are -complete. Module extraction, code refactoring, maintained consumers, validation, -current documentation, and multi-module tooling adoption are not implemented. -The [16-stage implementation plan](implementation.md) owns the sequence and -completion evidence. Earlier accepted ADR decision text remains intact. - -## Scope and follow-up - -This work changes packaging and integration boundaries. It does not select a -new backend, concurrent-dispatch contract, dynamic plugin system, or replacement -configuration engine. It does not lower the existing Go minimum. - -Keep the PromptKit v0.13.0 and ConfigKit v0.6.0 adoption identifiable as a separate -change after extraction unless an implementation dependency justifies combining -them. Catalogs and requirement discovery are subsequent integration features. -Semantic comparison retains the upstream-contract dependency recorded in -[deferred work](deferred.md). None is needed to demonstrate module isolation. diff --git a/docs/roadmap/registration-api.md b/docs/roadmap/registration-api.md deleted file mode 100644 index d9d9241..0000000 --- a/docs/roadmap/registration-api.md +++ /dev/null @@ -1,1288 +0,0 @@ -# Unified command registration API - -Status: finalized API design; implementation is pending. The declarations, -consumer examples, and design checks below define the selected target API, -including the additions and clarifications selected in the finalized backend -design. -No implementation is authorized by this document alone. - -## Purpose and authority - -Applications and integrations construct captured command declarations and install -them through the same core API. Installation validates each batch before registry -mutation. Integrations supply complete standard families and focused conveniences -while retaining ownership of their provider semantics. - -This document defines the selected registration API: capture declarations and -any construction errors eagerly, then report errors at installation. The -completed alternatives comparison is retained in Git history. - -[ADR 0001](../adr/0001-command-dispatch-and-inputs.md) owns explicit registration, -typed inputs, and command ownership. -[ADR 0010](../adr/0010-optional-integration-modules.md) owns optional module -boundaries and convenient integration registration. -[ADR 0011](../adr/0011-captured-declarations-and-installation.md) records the -captured-declaration model, optional-family namespace lifetime, and separated -input/shared-state lifetimes. The [architecture policy](../policy/architecture.md) -reflects those boundaries. The [module roadmap](modules.md) tracks extraction -work. The finalized [backend design](registration-backend.md) owns internal -structure and shared implementation mechanics. Its resolved composition choices -are incorporated below; this API design remains a target rather than implemented -behavior. - -Design for the best consumer API without preserving current implementation -signatures or machinery. Existing code can supply evidence and reusable work, -but does not constrain the design. This is future API design, not documentation -of implemented behavior. - -## Selected consumer model - -A consumer can use only the core or add any supported combination of integrations. -Every command reaches the application through core `New` or `Register`, regardless -of whether an application or integration declared it. There is no mandatory -integration interface, application-wide generic input type, or separate -integration installation lifecycle. - -The core remains independent of integration modules and their upstream libraries. -Integrations may depend on one another as documented. Such module dependencies -do not install commands or acquire providers. The selected modules remain -`CommandKit`, `CommandKit/integrations/ckconfig`, and -`CommandKit/integrations/ckprompt`, under the existing repository module prefix. - -Each application command retains its own typed input and domain handler. -Integration suites retain their own typed context and provider declarations. -Both produce the same opaque core `Command` handle. Consumers need not construct -individual standard handlers, duplicate integration adapters, or expose backend -objects to assemble an application. - -## Declaration and installation - -### One declaration model - -A `Command` is a captured declaration, not proof that the declaration is valid -or installed. Ordinary command, group, scope, and suite adapter construction -does not require a separate error check at each call site. - -The uniform construction shape is below. The complete declarations and examples -later in this document provide the supporting fields and semantics for -implementation. - -```go -// Core package: -func NewCommand[I any](spec CommandSpec[I]) Command -func Group(name, summary string, children ...Command) Command - -func New(identity Identity, commands ...Command) (*App, error) -func (app *App) Register(commands ...Command) error -``` - -The initial integrations expose complete-family factories using their own suite -types: - -```go -// In each initial integration package; Suite belongs to that integration: -func Command[I, S any](suite Suite[I, S]) commandkit.Command -``` - -Here `I` is the suite's CLI input and `S` is its shared invocation state. This -selected separation resolves the pointer-isolation issue without a clone hook or -an application-wide type. Future integrations can accept declarations suited to -their resources while returning the same core `Command`; the core does not -prescribe a universal suite. - -Application-owned command factories follow the same declaration pattern. -Application helpers that genuinely perform other fallible work may still return -errors; the design does not suppress those errors or require all application -functions to be infallible. - -### Eager capture, errors reported at installation - -Factories capture declaration metadata and callback references and assemble -neutral command descriptions immediately. Validation that can already be done -may occur then, but its errors remain attached to the declaration for installation -to report. Internally fallible assembly can capture an error without returning it -to the consumer at every construction step. - -Use the selected `WithError(command, err)` core operation to retain integration -construction errors in opaque declarations. Its complete signature and no-op -interaction are specified below. Do not substitute an untyped integration payload, -root-private access, or a deferred preparation lifecycle. - -`New` and `Register` report retained declaration errors and validate the batch, -including bindings, root placement, and collisions, before registry mutation. -A failed `New` yields no usable application. A failed `Register` leaves the -existing registry intact. No rollback guarantee spans separate registration calls. - -Declaration construction, installation, help, and version do not invoke provider -acquisition or application/suite input/state factories. Built-in help may prepare -its own private typed input through the common compiler, without initializing the -target command or invoking its validation/execution callbacks. These paths do not -validate files, credentials, or application configuration. Invalid wiring fails at installation; -an upstream error returned by a valid provider remains an invocation result. - -An originally nil loader remains invalid wiring even if an adapter would wrap -it in a non-nil function. Integration declarations retain enough information to -detect this. An intentionally absent capability is distinct from an invalid -adapter. Errors identify the affected command family and relevant scope, binding, -or capability and preserve supported wrapped error identity; exact prose is not -selected here. - -## Integration composition and conveniences - -Configuration scopes are composed before installation into one configuration -suite. Application and engine scopes can share that suite. Named definition -inspection remains independently selectable: installing an engine configuration -scope does not automatically install definition commands, or vice versa. - -Keep provider declarations integration-owned. Configuration supports ConfigKit -for now; the core does not acquire a universal configuration or suite interface. -The PromptKit integration can use the ConfigKit integration's selection and scope -contracts through its selected dependency. - -Provide the focused `ckconfig.AppCommand(load)` convenience for a context-free -application configuration loader. It declares the standard `app` scope, with a -default description, through the same declaration model as the full suite. It -does not require an unused context argument or `Suite[struct{}]` at the consumer -call site. A loader still receives `context.Context` and configuration selection. -Use the full suite when adding scopes, selectors, custom state, or other metadata. - -Do not initially expose integration-specific `Register` or `RegisterApp` -functions alongside declaration factories. Core registration is the installation -path for all consumers. Additional conveniences should address demonstrated -consumer needs rather than mirror every underlying constructor automatically. - -## Namespaces and empty declarations - -Help and version are reserved by core construction. Optional families claim -their complete root namespace when installed. Before installation, a domain group -may occupy that name; subsequent integration installation then fails with a -collision. The reverse order also rejects a collision. Neither order replaces or -merges an existing group. - -Standard-family declarations carry enforceable root-only placement metadata. -Installing them beneath an ordinary group is an assembly error. Use the same -`Command` type with the selected `RootGroup` constructor specified below. It -represents placement, not authentication of the declaring package. Installed -standard families cannot be extended with private application operations or have -their handlers replaced. - -The selected empty-suite behavior is: - -| Declaration | Installation result | -| --- | --- | -| Configuration suite with no scopes | Declaration error | -| Named configuration scopes with no inspection operations | Install scope discovery with the actual empty capability sets | -| Configuration scopes with supplied operations | Install discovery and supported operations | -| Otherwise valid definition suite with no capabilities | Successful no-op; no namespace or help entry | -| Adapter with a nil loader | Declaration error, even when selected capabilities otherwise appear empty | - -Use an explicit `commandkit.NoCommand()` declaration for intentional absence. -Core installation skips it. A zero-value `Command` remains invalid. Integration -factories retain adapter-construction errors before representing an empty family -as a no-op. Validate bindings needed by generated commands; unused selector -bindings are ignored when they contribute to no generated inspection command. -Integration-owned scope identifiers, applicability references, and enabled-provider -wiring still undergo validation. Ignoring unused bindings does not erase retained -adapter errors. This is the selected [unused-binding resolution](registration-backend.md#resolved-unused-binding-validation-oq-1). -A no-op does not reserve a namespace, and `NoCommand()` needs no validation-bearing -overload or separate public declaration validator. - -## Capture, reuse, and invocation inputs - -Capture scope metadata, capability lists, selector applicability, bindings, and -callback references when declarations are constructed. Copy the declaration -collections needed for those semantics. Later edits to the source suite or its -metadata slices do not change a returned command declaration. - -Declarations may be installed in multiple applications. Registries maintain -independent installed state, and invocations have independent parsed state. -Application callback closures and objects they reference remain application-owned -and may deliberately be shared. These choices do not promise arbitrary deep -copies or concurrent dispatch. - -Use the same typed, presence-aware binding primitives for application commands -and integration inputs. Configuration selectors additionally declare scope -applicability; definition selectors need no configuration-specific wrapper. -Explicit empty, false, and zero values remain distinguishable from omission. - -An omitted `NewInput` or `NewState` supplies the zero value of its respective -type. For pointer types that is nil; do not silently allocate arbitrary pointer -state or prohibit a loader that intentionally accepts nil. Applications supply a -factory when initialized state is needed. A supplied factory returning nil retains -its application-owned meaning. - -Scope-specific parsed values stay separate from shared acquisition state through -the selected `Suite[I, S]`: `NewInput` initializes each selected scope's input, -while `NewState` initializes `S` once for the selected inspection invocation. -Providers receive both explicitly. Pointer inputs are independently initialized, -not shallow copies of a single pointer. The detailed lifetime table below -spells out those calls without requiring arbitrary deep copies or prohibiting -pointer inputs or shared pointer state. - -## Selected core declarations - -The following declarations specify the public registration and binding surface -used by these examples. Opaque types retain unexported representation; these are -API declarations, not an implementation of their bodies. `context` and `io` are -standard-library packages. Outcome semantics remain governed by -[ADR 0005](../adr/0005-invocation-outcomes.md); this pass changes neither status -mapping nor diagnostic delivery policy. - -```go -package commandkit - -import ( - "context" - "io" -) - -type App struct { - // Unexported representation. -} -type Command struct { - // Unexported representation. -} -type Flag[I any] struct { - // Unexported representation. -} -type Argument[I any] struct { - // Unexported representation. -} - -type Identity struct { - Name string - Version string - Revision string -} - -type Streams struct { - Out io.Writer - Err io.Writer -} - -type Invocation struct { - Context context.Context - Streams Streams -} - -type Supplied[T any] struct { - Value T - Set bool -} - -type FlagMeta struct { - Name string - Short string - Usage string - Required bool -} - -type ArgumentMeta struct { - Name string - Usage string -} - -type CommandSpec[I any] struct { - Name string - Summary string - Description string - Children []Command - NewInput func() I - Flags []Flag[I] - Arguments []Argument[I] - CheckInput func(I) error - Run func(Invocation, I) Outcome -} - -func NewCommand[I any](spec CommandSpec[I]) Command -func Group(name, summary string, children ...Command) Command -func RootGroup(name, summary string, children ...Command) Command -func WithError(command Command, err error) Command -func NoCommand() Command - -func New(identity Identity, commands ...Command) (*App, error) -func (app *App) Register(commands ...Command) error -func (app *App) Execute(ctx context.Context, args []string, streams Streams) Outcome - -func ValueFlag[I, V any]( - meta FlagMeta, defaultValue *V, - parse func(string) (V, error), set func(*I, Supplied[V]), -) Flag[I] -func BoolFlag[I any]( - meta FlagMeta, defaultValue bool, set func(*I, Supplied[bool]), -) Flag[I] -func RepeatFlag[I, V any]( - meta FlagMeta, defaults []V, - parse func(string) (V, error), set func(*I, Supplied[[]V]), -) Flag[I] -func ValueArgument[I, V any]( - meta ArgumentMeta, required bool, - parse func(string) (V, error), set func(*I, Supplied[V]), -) Argument[I] -func RepeatArgument[I, V any]( - meta ArgumentMeta, min, max int, - parse func(string) (V, error), set func(*I, Supplied[[]V]), -) Argument[I] - -type Severity string - -const ( - SeverityWarning Severity = "warning" - SeverityError Severity = "error" -) - -type Diagnostic struct { - Severity Severity - Write func(io.Writer) error -} - -// Neutral value/diagnostic envelope; not a universal provider interface. -type Acquired[T any] struct { - Value T - Diagnostics []Diagnostic -} - -type OutcomeKind string - -const ( - OutcomeSuccess OutcomeKind = "success" - OutcomeUsage OutcomeKind = "usage" - OutcomeOperational OutcomeKind = "operational" - OutcomeDomain OutcomeKind = "domain" -) - -type DeliveryState string - -const ( - DeliveryNone DeliveryState = "none" - DeliveryComplete DeliveryState = "complete" - DeliveryIncomplete DeliveryState = "incomplete" -) - -type Outcome struct { - Kind OutcomeKind - Status int - Domain string - Cause error - DeliveryError error - DiagnosticDelivery DeliveryState - ReportIncomplete bool - Diagnostics []Diagnostic -} - -func Success() Outcome -func Failure(cause error) Outcome -``` - -`NewCommand` records invalid bindings or invalid execution wiring as declaration -errors; it does not require a construction error check. An ordinary group may -have no execution handler. Opaque declarations expose no operation to mutate an -installed tree, extract its handlers, or append children after capture. Collections -in `CommandSpec`, groups, and defaults are captured as appropriate; runtime -closure state and diagnostic writer effects remain caller-owned. - -### Runnable parents and captured children - -`CommandSpec.Children` combines a typed handler with child declarations. Capture -its child-handle slice and apply the same immediate composition, collision, and -installation placement checks used by `Group`. A runnable parent follows its -handler and operand requirements on bare invocation; help describes the parent -and children under [ADR 0006](../adr/0006-command-grammar.md). `Group` remains the -convenience for a non-runnable parent. Neither form permits mutation or child -append operations after capture. The [backend design](registration-backend.md#immutable-declaration-records) -uses the same captured representation and installation path for both. - -### Retained errors, placement, and no-ops - -`WithError` returns a new captured declaration retaining a non-nil error alongside -any errors already attached to the declaration or its children. A nil error does -not add an error. Installation reports retained errors with command context and -preserves their supported `errors.Is`/`errors.As` identity. It need not prescribe -an exact aggregate-error type or error text. Wrapping does not turn an invalid -zero declaration into a valid command or erase a root-only marker. - -`RootGroup` has the same eager declaration behavior as `Group`, but carries -root-only placement metadata. Core installation rejects it anywhere below the -root, including inside a wrapper declared with ordinary `Group`. No integration -name allowlist or declaring-package authentication is needed. - -`NoCommand` is an explicit absence marker. Core checks attached errors before -skipping it, so `WithError(NoCommand(), err)` fails rather than hiding `err`. -An unadorned no-op is skipped at the root or among children and contributes no -help entry or reserved namespace. A zero-value `Command` is not this marker and -fails installation. A root-only group with no children remains a real group, -not an absence marker; the integrations select their own empty-suite semantics. - -These operations compose without private-state access or delayed preparation. -An integration can construct an ordinary neutral tree, mark its root, and attach -its own validation error: - -```go -// Schematic integration-owned assembly: every function called here runs eagerly. -func familyDeclaration(children []commandkit.Command, wiringErr error) commandkit.Command { - group := commandkit.RootGroup("config", "Inspect configuration.", children...) - return commandkit.WithError(group, wiringErr) -} -``` - -The actual integration factory validates scope/provider metadata and eagerly -constructs all supported children. Core constructors retain their own binding -errors. There is no exported preparation callback, invocation-time declaration -validation, or root helper that understands `ckconfig.Suite`. - -### Typed flag composition needed by optional modules - -Scopes need to apply the same parsed selector to independently initialized inputs. -The integration cannot inspect a core `Flag`'s private parser or setter. Provide -one narrow typed composition mechanism, rather than exporting those internals: - -```go -// In commandkit: -type FlagValues[I any] struct { - // Unexported representation. -} - -// Non-nil sentinel errors; compare using errors.Is, including through wrapping. -var ( - ErrFlagInput error // Missing required selected flag value. - ErrFlagWiring error // Invalid binding selection or destination. -) - -func CaptureFlags[Outer, I any]( - flags []Flag[I], - target func(*Outer) *FlagValues[I], -) []Flag[Outer] - -func (values FlagValues[I]) ApplyTo(target *I, selected ...Flag[I]) error -``` - -`CaptureFlags` eagerly captures bindings and supplies ordinary outer-command flag -declarations. During an execution invocation those declarations record converted -values, defaults, and presence in the targeted `FlagValues`; they do not initialize -`I` or acquire providers. Nil target callbacks and invalid inner bindings remain -declaration errors. The core's ordinary flag validation still catches duplicate -names and conflicts with standard flags after composition. - -`ApplyTo` delivers only the selected bindings into an already initialized input. -The integration selects those bindings from its captured scope-applicability -metadata. Binding handles carry sufficient identity to match their captured -values without string-based retrieval by handlers. Selecting a binding absent -from the captured set or providing a nil destination pointer is an application/ -integration wiring error. The zero `FlagValues` with no selected bindings is valid. - -Copying a binding handle or capturing it in a command preserves its identity. -Constructing another flag with identical metadata creates a distinct binding; -matching names, parsers, or setters do not make it interchangeable with the -original. Reuse the original handles when selecting values to apply. The same -handle can be captured by separate declarations without sharing invocation values. - -Execution-required checks for captured inner flags are deferred until `ApplyTo` -selects the applicable subset; a required app-only selector must not become -mandatory for a PromptKit-only scope request. Missing required selected values -match `ErrFlagInput` through `errors.Is` and produce usage outcomes. An absent -binding or nil destination matches `ErrFlagWiring` and produces an operational -failure, not an operator usage error. Each error produced by `ApplyTo` identifies -one of these categories and includes relevant flag context without requiring -callers to parse its message. - -The core's `CheckInput` error handling recognizes `ErrFlagWiring` through wrapping -and routes it to the operational path. `ErrFlagInput` and ordinary input-validation -errors use the usage path. Integrations may therefore return an `ApplyTo` error -from their input-validation path, adding family/scope context by wrapping it, -without misclassifying wiring defects. Both failures stop before shared-state -initialization or acquisition. Metadata wiring defects detectable during assembly -remain retained installation errors; these categories do not defer those checks -to execution. Help uses metadata without applying values or requiring execution -inputs. - -Core-owned repeatable-value slices are independent per application of the values; -custom parsed objects and accessor effects retain application ownership. This -API does not deep-copy arbitrary application values or expose backend state. -It is a reusable typed input-composition contract for integrations, not a second -parser, a raw-argv handler API, or an untyped integration payload. - -For example, the private configuration command input can contain -`FlagValues[I]` beside standard selection/scope fields. The integration declares -its selector flags through `CaptureFlags`, resolves selected scopes in its -execution input-validation path, and calls `ApplyTo` for each scope. Definition -inspection applies the whole captured flag set once. This explains the public -seam; private input structs and the exact assembly algorithm remain implementation -choices. - -## Selected configuration integration declarations - -The `ckconfig` package owns the following contracts. Only the four implemented -inspection operations appear here; comparison remains deferred. `config scopes` -is supplied by suite assembly, not an acquisition operation. - -```go -package ckconfig - -import ( - "context" - "io" - - commandkit "gitea.maximumdirect.net/eric/CommandKit" - configkit "gitea.maximumdirect.net/eric/ConfigKit" -) - -type Selection struct { - Config commandkit.Supplied[string] - Overlay commandkit.Supplied[string] -} - -type Operation string - -const ( - Validate Operation = "validate" - Show Operation = "show" - Sources Operation = "sources" - Explain Operation = "explain" -) - -type Request struct { - Operation Operation - Selection Selection -} - -type EffectiveWriter interface { - WriteEffective(io.Writer) error -} - -type SourceReader interface { - Sources() []configkit.SourceInfo -} - -type ExplanationReader interface { - Explain(configkit.Path) (configkit.Explanation, bool) - WriteExplanation(io.Writer, configkit.Path) error -} - -type View struct { - Effective EffectiveWriter - Sources SourceReader - Explanation ExplanationReader -} - -type Scope[I, S any] struct { - Name string - Summary string - Operations []Operation - Acquire func(context.Context, Request, I, S) (commandkit.Acquired[View], error) - // Adapter construction issues are retained in unexported representation. -} - -type Selector[I any] struct { - Binding commandkit.Flag[I] - Scopes []string -} - -type Suite[I, S any] struct { - NewInput func() I - NewState func() S - Selectors []Selector[I] - CheckInput func(scope string, selection Selection, input I) error - Scopes []Scope[I, S] -} - -func FromConfigKit[I, S, T any]( - name, summary string, - load func(context.Context, Selection, I, S) (*configkit.Result[T], error), -) Scope[I, S] - -func WithScopeError[I, S any](scope Scope[I, S], err error) Scope[I, S] - -func Command[I, S any](suite Suite[I, S]) commandkit.Command - -func AppCommand[T any]( - load func(context.Context, Selection) (*configkit.Result[T], error), -) commandkit.Command -``` - -The generic types are local to the suite, not to `App`. `I` contains typed CLI -values; `S` contains invocation state such as a cache. Neither belongs to the core -configuration model because the core has no such model. A scope with no operations -can omit `Acquire` and still be discoverable. A scope declaring operations needs -valid acquisition wiring. Providers supply the relevant native view facets and -validation diagnostics at invocation time, preserving current selected reports -and classifications rather than implementing a second configuration engine. - -An empty selector scope list applies to all registered scopes; a nonempty list -identifies its applicable registered scopes. Unknown scope identifiers are -declaration errors. Bindings used by generated commands undergo core validation, -including standard-flag collisions; unused selector bindings follow the selected -absence/unused-binding rule above. Scope identifiers, capability selection, -defaults, and aggregate availability follow -[ADR 0009](../adr/0009-configuration-scopes.md). Non-applicable selectors are not -applied to a scope's input; this adds no new unused-selector rejection policy. -`CheckInput` receives the registered name of each requested scope alongside its -selection and initialized, filtered input before acquisition. It can distinguish -a selector omitted for an applicable scope from one intentionally not delivered -to another scope, and apply domain rules accordingly without loading configuration. -The definition suite retains its scope-free validation signature; it has no -configuration scope to supply. - -`FromConfigKit` declares the operations supported by the selected ConfigKit -adapter and records the original loader, including an invalid nil loader. -Consumers may explicitly narrow `Operations` before calling `Command`; a -constructor issue is not erased by clearing the operation list. The suite factory -captures those lists and its generated neutral command tree immediately. - -`AppCommand` uses an internal state-free suite with an `app` scope and a plain -application-configuration description. It neither exposes placeholder type -arguments nor demands a forwarding wrapper from the caller. A nil loader becomes -a retained `config`/`app` declaration error. The full suite is used for composition; -there is no incremental scope append operation after installation. - -### Retained scope errors - -`ckconfig.WithScopeError` returns a scope retaining a non-nil construction error -alongside earlier causes, leaving the original scope unchanged. A nil error -preserves the scope. Retained causes preserve supported `errors.Is`/`errors.As` -identity across scope copies and narrowing of `Operations`. The family factory -collects those issues into its command declaration before capability selection -can discard them; no acquisition callback runs to discover a construction error. - -`ckprompt.ConfigScope` uses this public operation to retain original loader errors -without accessing private `ckconfig` state. It otherwise declares the ordinary -scope fields and capabilities. The [scope-error resolution](registration-backend.md#resolved-scope-error-handoff-oq-2) -records the rationale and rejected alternative; the operation adds no new scope -installation lifecycle. - -## Selected PromptKit integration declarations - -The `ckprompt` package uses `ckconfig` selections and scope contracts through its -selected module dependency. Definition selectors are plain typed flags; they do -not use a configuration-scope wrapper. Named prompt and profile inspection remain -the implemented subset; this pass adds no catalog operations. - -```go -package ckprompt - -import ( - "context" - - commandkit "gitea.maximumdirect.net/eric/CommandKit" - "gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig" - promptkit "gitea.maximumdirect.net/eric/promptkit" -) - -type PromptRequest struct { - Selection ckconfig.Selection - ID string - Version commandkit.Supplied[string] -} - -type ProfileRequest struct { - Selection ckconfig.Selection - ID string -} - -type Suite[I, S any] struct { - NewInput func() I - NewState func() S - Selectors []commandkit.Flag[I] - CheckInput func(ckconfig.Selection, I) error - InspectPrompt func(context.Context, PromptRequest, I, S) (commandkit.Acquired[*promptkit.PromptInspection], error) - InspectProfile func(context.Context, ProfileRequest, I, S) (commandkit.Acquired[*promptkit.ProfileInspection], error) - // Adapter construction issues are retained in unexported representation. -} - -func ConfigScope[I, S any]( - load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error), -) ckconfig.Scope[I, S] - -func FromEngine[I, S any]( - load func(context.Context, ckconfig.Selection, I, S) (*promptkit.Engine, error), -) Suite[I, S] - -func Command[I, S any](suite Suite[I, S]) commandkit.Command -``` - -`ConfigScope` declares the supported configuration facets for the `promptkit` -scope; `FromEngine` declares supported named inspection callbacks. Both leave -acquisition untouched until invocation and retain nil-loader errors. Each adapter -maps the owning library's public diagnostics into core-owned `Diagnostic` values. -Definition providers preserve diagnostics even when a named lookup fails. - -Consumers may leave either definition callback nil to omit that capability. -A suite with both callbacks nil contributes no commands and ignores unused -selector bindings. It returns a no-op retaining any adapter-construction errors; -those errors still fail installation. -A nil loader supplied to `FromEngine` is invalid wiring, not missing capability; -setting its callbacks nil does not erase the retained error. For prompt lookup, -the adapter passes the requested version value to PromptKit, with omission using -its existing empty/default selection. The presence field remains available to a -custom inspection callback; this does not redefine upstream version semantics. - -## Input isolation and invocation lifetimes - -### Why inputs and shared state have separate types - -Combining selector storage and a cache in one context type obscures their distinct -lifetimes and can cause scope-specific inputs to alias. Separate input values -could share a cache through references, but would leave input isolation and state -lifetime implicit. The selected `Suite[I, S]` makes the two responsibilities -explicit without requiring arbitrary application graphs to be cloned: - -- `I`: per-scope/per-command CLI input, initialized and populated independently. -- `S`: shared acquisition state for one selected inspection invocation. - -There is no public clone hook and no reflection-based allocation of pointer inputs. -Two type parameters cost some declaration syntax in composed consumers, but make -sharing deliberate and lifetimes predictable. `AppCommand` hides both for the -simple case. This selected separation supports unified construction and -independently owned suites. - -### Factory timing - -The execution rows describe the normal path after applicable conversion and -preflight checks succeed and execution reaches the indicated stage. They do not -require callbacks to run after a failure. An input-application or validation -failure can stop processing before later scopes are initialized; -shared state and acquisition are reached only after all requested input checks -succeed. The no-call rows remain unconditional for CommandKit-owned paths. - -| Path | `NewInput` | Suite `NewState` | Acquisition | -| --- | --- | --- | --- | -| Construct declarations / `New` / `Register` | Not called | Not called | Not called | -| Help, version, `config scopes` | Not called | Not called | Not called | -| Application command execution | Once for the selected command | No suite factory | Caller handler | -| Configuration inspection of one scope | Once for that scope | Once for the inspection invocation | Requested scope | -| Configuration inspection of several scopes | Once per selected scope | Once, shared by selected scopes | Requested scopes, in selected operation order | -| Named definition inspection | Once for the selected command | Once for that invocation | Selected inspection callback | - -The factory counts describe application inputs and suite input/state. Built-in -help may prepare its own private input through the common compiler; it still -leaves the target application's and suites' factories, validation/execution -callbacks, and providers uninvoked. - -Input conversion and scope/capability preflight use metadata without acquisition. -All supplied occurrences on an installed command undergo conversion, including -selectors inapplicable to the requested scope and values supplied with help. -Scope filtering affects value application and required-value checks, not -conversion; an earlier malformed singleton occurrence remains an error even if a -later one is valid. - -For configuration execution, initialize and populate each requested scope input, -then run its `CheckInput`; initialize the shared state before acquisition after -input checks succeed. The input/state callbacks do not run for unselected scopes -or unrelated families. This explicitly separates the former mixed-context role: -`NewState` names the shared acquisition-state factory, distinct from -`context.Context`; it runs once when execution reaches that stage. `NewInput` -initializes scope storage independently. No shared-state callback is silently -called once per scope. - -A missing factory supplies the zero value of its type. A nil pointer input/state -is permitted when the receiving binding callbacks and providers support it. -Applications requiring allocated pointer input supply `NewInput`; those requiring -a cache or other initialized state supply `NewState`. Factories that deliberately -return shared mutable input objects retain caller-owned effects; CommandKit does -not promise to repair those aliases. For scope-independent CLI storage, return -fresh input storage as demonstrated below. Pointer shared state is supported and -is shared deliberately, not copied. - -Typed captured flag values are applied only to applicable inputs. Repeatable -collections owned by CommandKit are independent when materialized into different -inputs. Arbitrary objects supplied by factories, custom parsers, or closures are -not deeply cloned. Providers can retain their input pointer according to their -callback contract without a later scope overwriting it through a shared template. -Subsequent invocations initialize new input/state; registration does not retain -an invocation cache. No concurrent-dispatch guarantee follows from these rules. - -## Complete consumer registration examples - -These examples target the selected APIs, not today's prototype. Each block is a -complete consumer assembly function with imports and input declarations. Domain -loaders and domain execution are supplied as typed function parameters where -appropriate: their bodies are application behavior, not hidden registration -helpers. No constructor error checks or provider acquisition are concealed in -those parameters. The modules and declarations must be implemented before these -blocks can compile against CommandKit itself. - -### 1. Core only: typed commands and a composable group - -```go -package coreexample - -import ( - "fmt" - "strconv" - - commandkit "gitea.maximumdirect.net/eric/CommandKit" -) - -type GreetInput struct { - Count commandkit.Supplied[int] - Name commandkit.Supplied[string] - Enabled commandkit.Supplied[bool] -} - -func App() (*commandkit.App, error) { - countDefault := 1 - nameDefault := "reader" - greet := commandkit.NewCommand(commandkit.CommandSpec[GreetInput]{ - Name: "greet", Summary: "Write a greeting.", - Flags: []commandkit.Flag[GreetInput]{ - commandkit.ValueFlag( - commandkit.FlagMeta{Name: "count", Usage: "Number of greetings."}, - &countDefault, strconv.Atoi, - func(input *GreetInput, value commandkit.Supplied[int]) { - input.Count = value - }, - ), - commandkit.ValueFlag( - commandkit.FlagMeta{Name: "name", Usage: "Greeting recipient."}, - &nameDefault, func(raw string) (string, error) { return raw, nil }, - func(input *GreetInput, value commandkit.Supplied[string]) { - input.Name = value - }, - ), - commandkit.BoolFlag( - commandkit.FlagMeta{Name: "enabled", Usage: "Enable greeting output."}, - true, - func(input *GreetInput, value commandkit.Supplied[bool]) { - input.Enabled = value - }, - ), - }, - CheckInput: func(input GreetInput) error { - if input.Count.Value < 0 { - return fmt.Errorf("count must be nonnegative") - } - return nil - }, - Run: func(invocation commandkit.Invocation, input GreetInput) commandkit.Outcome { - if !input.Enabled.Value { - return commandkit.Success() - } - for i := 0; i < input.Count.Value; i++ { - if _, err := fmt.Fprintf(invocation.Streams.Out, "Hello, %s!\n", input.Name.Value); err != nil { - return commandkit.Failure(err) - } - } - return commandkit.Success() - }, - }) - text := commandkit.Group("text", "Text utilities.", greet) - return commandkit.New(commandkit.Identity{Name: "Example", Version: "dev"}, text) -} -``` - -`NewInput` is omitted, so ordinary command execution starts with a zero-valued -`GreetInput` and applies defaults/presence through bindings. Explicit `--count=0`, -`--name=`, and `--enabled=false` remain supplied. Invalid command or binding -metadata is reported by the single final `New` call, not by `greet` or `text`. -Negative count is instead a usage result from invocation-time input validation. -No integration package or upstream configuration library is imported. - -For a caller that wants to execute the assembled app, the common invocation is: - -```go -outcome := app.Execute(ctx, args, commandkit.Streams{Out: stdout, Err: stderr}) -// The caller owns streams, cancellation, and final process exit/status mapping. -``` - -### 2. Context-free application configuration - -```go -package configexample - -import ( - "context" - - commandkit "gitea.maximumdirect.net/eric/CommandKit" - "gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig" - configkit "gitea.maximumdirect.net/eric/ConfigKit" -) - -type AppConfig struct { - Limit int `yaml:"limit"` -} - -func App( - load func(context.Context, ckconfig.Selection) (*configkit.Result[AppConfig], error), -) (*commandkit.App, error) { - return commandkit.New( - commandkit.Identity{Name: "Example", Version: "dev"}, - ckconfig.AppCommand(load), - ) -} -``` - -The parameter is the complete loader signature. It can be an application's normal -ConfigKit acquisition function directly; no unused context argument, dummy suite, -or forwarding wrapper is required. `load` is not called during construction, -installation, help, or version. An explicitly empty configuration/overlay value -is available to the loader through `Selection` with `Set == true`. - -### 3. Composed application: pointer inputs, shared cache, custom command - -```go -package composedexample - -import ( - "context" - "fmt" - "os" - "strconv" - - commandkit "gitea.maximumdirect.net/eric/CommandKit" - "gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig" - "gitea.maximumdirect.net/eric/CommandKit/integrations/ckprompt" - configkit "gitea.maximumdirect.net/eric/ConfigKit" - promptkit "gitea.maximumdirect.net/eric/promptkit" -) - -type AppConfig struct { - Limit int `yaml:"limit"` -} - -type InspectionInput struct { - Campaign commandkit.Supplied[string] - Limit commandkit.Supplied[int] -} - -// Application-owned cache. Loaders may use Source for shared file reads. -// Source bytes are treated as read-only by this example's loaders. -type RunCache struct { - files map[string][]byte -} - -func (cache *RunCache) Source(ctx context.Context, path string) ([]byte, error) { - if err := ctx.Err(); err != nil { - return nil, err - } - if contents, ok := cache.files[path]; ok { - return contents, nil - } - contents, err := os.ReadFile(path) - if err != nil { - return nil, err - } - cache.files[path] = contents - return contents, nil -} - -type RestoreInput struct { - ID commandkit.Supplied[string] -} - -func App( - loadApp func(context.Context, ckconfig.Selection, *InspectionInput, *RunCache) (*configkit.Result[AppConfig], error), - loadEngine func(context.Context, ckconfig.Selection, *InspectionInput, *RunCache) (*promptkit.Engine, error), - restore func(context.Context, string) commandkit.Outcome, -) (*commandkit.App, error) { - parseString := func(raw string) (string, error) { return raw, nil } - campaignFlag := commandkit.ValueFlag[*InspectionInput, string]( - commandkit.FlagMeta{Name: "campaign", Usage: "Select campaign inputs."}, - nil, parseString, - func(input **InspectionInput, value commandkit.Supplied[string]) { - (*input).Campaign = value - }, - ) - limitFlag := commandkit.ValueFlag[*InspectionInput, int]( - commandkit.FlagMeta{Name: "limit", Usage: "Override the application item limit."}, - nil, strconv.Atoi, - func(input **InspectionInput, value commandkit.Supplied[int]) { - (*input).Limit = value - }, - ) - newInput := func() *InspectionInput { return &InspectionInput{} } - newState := func() *RunCache { - return &RunCache{files: make(map[string][]byte)} - } - - appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApp) - engineScope := ckprompt.ConfigScope(loadEngine) - configCommand := ckconfig.Command(ckconfig.Suite[*InspectionInput, *RunCache]{ - NewInput: newInput, NewState: newState, - CheckInput: func(scope string, selection ckconfig.Selection, input *InspectionInput) error { - // Example application policy; it does not apply to engine inspection. - if scope == "app" && input.Campaign.Set && !input.Limit.Set { - return fmt.Errorf("application campaign inspection requires --limit") - } - return nil - }, - Selectors: []ckconfig.Selector[*InspectionInput]{ - {Binding: campaignFlag, Scopes: []string{"app", "promptkit"}}, - {Binding: limitFlag, Scopes: []string{"app"}}, - }, - Scopes: []ckconfig.Scope[*InspectionInput, *RunCache]{appScope, engineScope}, - }) - - definitions := ckprompt.FromEngine(loadEngine) - definitions.NewInput = newInput - definitions.NewState = newState - definitions.Selectors = []commandkit.Flag[*InspectionInput]{campaignFlag} - definitions.InspectPrompt = nil // Install profiles only; no prompt command. - definitionCommand := ckprompt.Command(definitions) - - restoreCommand := commandkit.NewCommand(commandkit.CommandSpec[RestoreInput]{ - Name: "restore", Summary: "Restore a session.", - Arguments: []commandkit.Argument[RestoreInput]{ - commandkit.ValueArgument( - commandkit.ArgumentMeta{Name: "ID", Usage: "Session identifier."}, - true, parseString, - func(input *RestoreInput, value commandkit.Supplied[string]) { - input.ID = value - }, - ), - }, - Run: func(invocation commandkit.Invocation, input RestoreInput) commandkit.Outcome { - return restore(invocation.Context, input.ID.Value) - }, - }) - session := commandkit.Group("session", "Manage sessions.", restoreCommand) - // Domain callback validation also uses captured errors, without a helper error. - if restore == nil { - session = commandkit.WithError(session, fmt.Errorf("session restore: handler is nil")) - } - return commandkit.New( - commandkit.Identity{Name: "Example", Version: "dev"}, - configCommand, definitionCommand, session, - ) -} -``` - -Domain loaders are passed directly to the adapters, so a nil original loader -cannot be hidden by a forwarding closure. Each signature shows both the isolated -pointer input and shared cache. Loader bodies choose ConfigKit schemas and -override application rules; CommandKit does -not prescribe those application internals. - -For `example config show --scope all --campaign spring --limit=0`: - -1. Capability preflight selects the `app` and `promptkit` scopes. -2. `newInput` produces two distinct pointers; both receive `Campaign.Set == true` - and `Campaign.Value == "spring"`. -3. Only the app input receives `Limit.Set == true` and `Limit.Value == 0`. The - engine input retains its independently initialized, unsupplied limit. -4. `newState` produces one `*RunCache`, passed to both acquisitions. If both - loaders request the same path through `Source`, its bytes are read once within - that invocation. Sharing is application-owned, not a global CommandKit cache. -5. An app loader applies its limit override only when `input.Limit.Set`; omission - preserves its ordinary inherited configuration, while explicit zero overrides - it. `--campaign=` likewise remains an explicitly empty campaign selection. -6. Native reports are presented under the existing `config show` preparation and - delivery contract. No subsequent scope mutates the first input's selector fields. - -For `example promptkit show profile briefing --campaign spring`, definition input -and state are initialized once for that invocation. Configuration scopes are not -acquired. The definition suite does not declare `--limit`, so that flag is not -accepted on the profile command. `session restore` invokes only the domain handler. -Help and scope discovery create neither cache nor pointer input. - -The example's scope-aware validation permits -`example config show --scope promptkit --campaign spring` without `--limit`. -The same campaign selection for `--scope app` requires an explicitly supplied -limit, including zero. An aggregate request missing that limit fails input -validation before shared-state creation or either acquisition. This is the -example application's policy, not a new CommandKit selector requirement. - -`restore` validation illustrates application wiring errors, not a requirement that -CommandKit inspect arbitrary closure internals. The application's declaration -factory records a known nil dependency because it wrapped that callback itself. - -### Independent installation and supported subsets - -Using the declarations assembled above, each of these is a separate assembly -choice, not another installation API: - -```go -// Configuration including engine scope, without named definition inspection: -app, err := commandkit.New(identity, configCommand, session) - -// Named profile inspection, without any config family: -app, err := commandkit.New(identity, definitionCommand, session) - -// Add a complete optional family later through the same atomic batch API: -err := app.Register(definitionCommand) -``` - -These are separate illustrative snippets; they are not successive redeclarations -in one function. Importing `ckprompt` without passing one of its declarations does -not install either surface. A metadata-only configuration scope can be declared -with a name/summary, an empty `Operations` list, and no `Acquire`; it remains -visible under `config scopes`. A full adapter's capabilities can also be narrowed: - -```go -appScope := ckconfig.FromConfigKit("app", "Application settings.", loadApp) -appScope.Operations = []ckconfig.Operation{ckconfig.Validate, ckconfig.Show} -``` - -The suite factory captures this selection and installs only supplied operations -plus discovery. It does not invent comparison or catalog providers. - -## Focused design checks and expected outcomes - -These checks specify observable target behavior, not a requirement to -add an exhaustive new test matrix. They use the declarations above; `identity` -has a valid name. Declaration errors are returned Go errors from installation, -not invocation status codes. Provider failures and ordinary input errors retain -the invocation model. - -### Retained errors are eager evidence, not delayed preparation - -```go -sentinel := errors.New("configuration declaration is incomplete") -declaration := commandkit.WithError(commandkit.NoCommand(), sentinel) -_, err := commandkit.New(identity, declaration) -// err != nil; errors.Is(err, sentinel) is true despite the absence marker. -``` - -This snippet uses standard-library `errors`. `WithError` needs no callback and -invokes no constructor later. A group's retained child errors and placement -metadata survive copying, wrapping, and installation in another app. Nil error -wrapping preserves the underlying declaration, including invalid zero commands. - -### Invalid wiring versus missing capability - -```go -var missingLoader func(context.Context, ckconfig.Selection) (*configkit.Result[AppConfig], error) -_, err := commandkit.New(identity, ckconfig.AppCommand(missingLoader)) -// Installation fails with config/app loader context; no loader runs. - -emptyDefinitions := ckprompt.Command(ckprompt.Suite[struct{}, struct{}]{}) -app, err := commandkit.New(identity, emptyDefinitions) -// Success; no promptkit family appears in help. -``` - -An engine adapter created from a typed nil engine loader fails installation even -if its inspection callbacks are later cleared. In contrast, a manually composed -suite with one non-nil inspection provider and the other absent installs only the -supplied capability. Metadata validation does not require every family operation. -A valid provider that later returns a load/lookup error reports operational status -1 with supported public error identity and its captured diagnostics. - -### Collisions, placement, and per-call atomicity - -```go -existing := commandkit.Group("config", "Application-owned configuration tools.") -app, err := commandkit.New(identity, existing) -// Success while no standard config family is installed under the selected rule. -err = app.Register(ckconfig.AppCommand(load)) -// Collision; the original domain group remains unchanged. - -wrapper := commandkit.Group("tools", "Utilities.", ckconfig.AppCommand(load)) -_, err = commandkit.New(identity, wrapper) -// Root-only placement error, not a new tools/config standard command path. -``` - -Installing the standard family first and then attempting a domain `config` group -also fails. Neither direction merges children or replaces handlers. Installing -`Group("help", ...)` or `Group("version", ...)` always collides with built-ins. -For `app.Register(goodNewCommand, conflictingCommand)`, neither command is added -when the batch fails; commands installed by earlier successful calls remain. -A failed `New` returns no usable app. This is registry atomicity, not rollback -of arbitrary effects inside application callbacks. - -### Empty declarations - -| Input | Expected installation result | -| --- | --- | -| `ckconfig.Command(ckconfig.Suite[struct{}, struct{}]{})` | Error: no configuration scopes | -| One named scope with empty operations and nil acquisition | Success; `config scopes` describes it, no inspection verbs supplied by that scope | -| Definition suite with no callbacks and unused selector bindings | Explicit absence, unused bindings ignored; retained adapter errors still fail | -| `NoCommand()` beside ordinary children | Skipped, no placeholder help item | -| `WithError(NoCommand(), sentinel)` | Error retaining sentinel identity | -| Zero `Command{}` at the root or as a child | Declaration error | -| `RootGroup("config", ..., child)` under an ordinary parent | Placement error | - -An empty definition no-op nested under `Group("tools", ...)` remains absence, -not a misplaced real family. A real empty `RootGroup` is still subject to placement -and collision checks. Configuration and definition factories choose the relevant -empty-family behavior before producing these declarations. - -### Capture and reuse - -```go -scope := ckconfig.FromConfigKit("app", "Application settings.", loadApp) -suite := ckconfig.Suite[*InspectionInput, *RunCache]{ - NewInput: newInput, NewState: newState, - Selectors: []ckconfig.Selector[*InspectionInput]{ - {Binding: campaignFlag, Scopes: []string{"app"}}, - }, - Scopes: []ckconfig.Scope[*InspectionInput, *RunCache]{scope}, -} -declaration := ckconfig.Command(suite) -suite.Scopes[0].Summary = "Changed source metadata" -suite.Scopes[0].Operations = []ckconfig.Operation{ckconfig.Validate} -suite.Selectors[0].Scopes[0] = "another-scope" - -first, firstErr := commandkit.New(identity, declaration) -second, secondErr := commandkit.New(identity, declaration) -// Both installations use the original captured scope, operation, and selector metadata. -``` - -The variables here have the types from the composed example. Original captured -operations remain available, the summary remains unchanged, and the selector -still applies to `app`; the later source edit is not revalidated as though it -were the returned command. Registry and invocation state are independent in -`first` and `second`. `firstErr` and `secondErr` are nil for otherwise valid wiring. -Callback references are shared intentionally, so mutable objects captured inside -those callbacks remain application-owned. Reusing a declaration does not authorize -concurrent execution or clone a callback's object graph. - -### Initialization and scope isolation - -When its preflight and input checks succeed, the composed example's selected -aggregate inspection calls `newInput` twice and `newState` once. It delivers -distinct `*InspectionInput` addresses but the same -`*RunCache` to the two providers. Retaining the app input in a provider does not -expose it to later engine-selector writes. Selecting one scope calls each factory -once; selecting profile inspection initializes only its own invocation. - -Without input selectors, a suite may deliberately use pointer `I` with no -`NewInput` and a loader accepting nil. With selectors whose accessors dereference -that pointer, it supplies the input factory, just as a pointer-shaped application -command does. Without `NewState`, pointer `S` is nil; providers that require a -cache supply the state factory. A fresh parsed-value store does not imply that -arbitrary application factories returning aliases are repaired or deep-copied. - -### Scope validation, flag identity, and error categories - -| Situation | Expected result | -| --- | --- | -| Campaign selected for the example's app scope, with no limit | Scope-aware `CheckInput` returns a usage error; no shared state or acquisition | -| Same campaign selected for only the engine scope | Validation receives scope `promptkit`; the app-only limit rule does not apply | -| A required applicable flag was not supplied | `ApplyTo` returns an error matching `ErrFlagInput`; usage outcome | -| A required flag applies only to an unselected scope | Its requirement is not enforced for the selected scope | -| A copied original binding is selected from its captured values | Identity matches; values apply to the initialized input | -| A newly constructed lookalike binding is selected instead | It is absent from that captured set; error matches `ErrFlagWiring` | -| The destination pointer passed to `ApplyTo` is nil | Error matches `ErrFlagWiring` | -| An `ApplyTo` error is wrapped with family/scope context | `errors.Is` still identifies the category; wiring failures remain operational | -| Input validation fails before later scopes are initialized | Later input factories need not run; `NewState` and acquisitions do not run | - -The flag-required checks here describe binding metadata, while the campaign/limit -relationship is the example's application validation rule. Neither requires -examining error-message text. `ErrFlagInput` and `ErrFlagWiring` classify errors -reported by flag application; they do not replace captured installation errors -or upstream provider errors. - -## Namespace reconciliation and implementation handoff - -The user-selected lifetime is precise: help/version are always reserved; -configuration and PromptKit standard roots are owned only when their real family -is installed. An absent/no-op family reserves nothing. A domain group using an -uninstalled integration name is permitted, but blocks later installation of the -same root with a collision. Root-only placement is independent of that lifetime. - -The documentation alignment is complete: - -| Owner | Recorded decision | -| --- | --- | -| [ADR 0011](../adr/0011-captured-declarations-and-installation.md) | Captured declarations, atomic installation batches, installed-family ownership, root-only placement, separate input/shared-state lifetimes, and scope-aware configuration input validation | -| [ADR 0001 status](../adr/0001-command-dispatch-and-inputs.md#status) | Remains accepted, with refinements recorded in ADR 0011 and its decision text preserved | -| [ADR 0010](../adr/0010-optional-integration-modules.md) | Core/integration dependency boundaries remain in effect; ADR 0011 refines registration and supersedes the earlier prototype migration-guide requirement | -| [Architecture policy](../policy/architecture.md) | Uniform installed-family ownership, optional integration facades, and declaration/input boundaries | -| [Module roadmap](modules.md) | Final API and backend design selected; extraction and release adoption still pending | -| [Backend design](registration-backend.md) | Shared internal pipeline, runnable children, scope-error attachment, unused-binding validation, and both composition-question resolutions | - -The [staged implementation plan](implementation.md) translates this finalized -API, the finalized [backend design](registration-backend.md), and the module -roadmap into ordered implementation assignments. Both composition questions are -resolved; the plan covers eager capture, error retention, neutral bindings, scope -isolation, maintained examples, and release adoption. -The documentation alignment neither implements these changes nor authorizes -implementation by itself. diff --git a/docs/roadmap/registration-backend.md b/docs/roadmap/registration-backend.md deleted file mode 100644 index 4a90536..0000000 --- a/docs/roadmap/registration-backend.md +++ /dev/null @@ -1,500 +0,0 @@ -# Registration and execution internals - -Status: finalized internal design; no implementation has begun. Runnable-command -children, scope-error attachment, and the unused-binding validation resolution are -incorporated into the [registration API](registration-api.md). Both composition -questions below are resolved; the [staged implementation plan](implementation.md) -owns the ordered work and completion evidence. - -## Purpose and authority - -Provide the [registration API](registration-api.md) through one coherent internal -system. Application commands, optional integration families, and built-in core -commands should use the same declaration, installation, routing, and execution -machinery. Command-specific handlers remain different where their responsibilities -are different. - -[ADR 0011](../adr/0011-captured-declarations-and-installation.md) owns the accepted -capture, installation, namespace, and input/state decisions. -[ADR 0010](../adr/0010-optional-integration-modules.md) owns module boundaries, and -the [architecture policy](../policy/architecture.md) governs implementation. -The [module roadmap](modules.md) owns the broader extraction and release work. -This document defines selected internal mechanics and records the resolved -composition decisions. Public declarations and consumer examples belong in the -registration API document; these changes remain unimplemented. - -The prototype has no compatibility priority. Replace its parallel paths and -bookkeeping where they obstruct the selected design. Preserve deliberately -selected grammar, presence, outcomes, reports, and callback lifetimes. Private -names below describe responsibilities rather than required exported symbols or a -mandated file layout. This is design work, not a staged implementation plan. - -## Baseline findings - -The following findings come from the current implementation. They identify reuse -opportunities and structural risks, not measured performance regressions. - -| Evidence | Pain point | Proposed solution | -| --- | --- | --- | -| `New` in [app.go](../../app.go) inserts help/version placeholders; `installBuiltins` in [builtins.go](../../builtins.go) replaces them directly | Built-ins bypass ordinary registry installation and have separate reserved-node bookkeeping | Construct real built-in declarations and install them with caller declarations in one batch | -| `Register` in [registry.go](../../registry.go) already stages roots before committing | Useful atomic installation exists, but privileged writers can bypass it | Make this the sole registry mutation boundary for every command source | -| `RegisterConfig` in [config.go](../../config.go) checks the root namespace and `App.configRegistered` before calling `Register` | The configuration installer duplicates collision policy and carries integration state in the core | Integration factories return root-only declarations; core installation decides collisions | -| `RegisterPromptKit` in [promptkit.go](../../promptkit.go) returns early when both inspection callbacks are nil | An absence shortcut can hide construction or metadata errors | Retain errors before representing absence; installation checks errors before skipping a no-op | -| `Group`, `validateCommandDeclaration`, and `cloneCommands` in [command.go](../../command.go) recursively validate/copy child trees; registration repeats this | Nested assembly can revisit a subtree at each ancestor and again at installation | Capture immutable child handles and local metadata once; perform contextual installation checks in one traversal | -| `normalizeConfigSuite` and `normalizePromptKitSuite` repeat flag-name, short-name, duplicate, and wiring rules | Moving integrations into modules could create several competing implementations of core binding policy | Validate generated command bindings in the core; integrations validate only their own suite semantics | -| `adaptContextSelector` and `contextsForScopes` in [selectors.go](../../selectors.go) apply selectors to mixed context storage, then copy/project it again | Selector storage and shared state have tangled lifetimes; pointer contexts can alias | Capture converted values once; initialize each scope input independently; create shared state after all input checks | -| Configuration `CheckInput` calls preflight, while `runConfigShow` and `runConfigValidation` select scopes again | Metadata selection and scope setup can be repeated between validation and execution | Carry an invocation-local prepared selection from `CheckInput` into `Run` | -| `Execute` in [execute.go](../../execute.go) identifies `help` by its path and asserts its private input type | A built-in name controls a separate execution path | Let the help declaration's typed runner receive a private execution context through the common compiler | -| `bridgeTree` in [parser.go](../../parser.go) caches neutral metadata; [internal/cobra/parse.go](../../internal/cobra/parse.go) creates a session per invocation | The useful metadata/session split should survive the refactor | Share captured metadata, refresh it after registry changes, and keep mutable backend values invocation-local | -| `commandAt` traverses names again after the adapter has already resolved a command | Duplicate route lookup can become another source of dispatch disagreement | Return a private command key from the adapter and resolve the captured runner directly | - -## Selected internal ownership - -Keep most cohesive core machinery private in the root package. A facade does not -require forwarding-only files or duplicate representations for every public type. -Use an internal package where it protects an actual dependency boundary. - -| Owner | Internal responsibility | Does not own | -| --- | --- | --- | -| Core declarations | Captured command records, child handles, local declaration errors, root placement, explicit absence | Registry occupancy, provider acquisition, Cobra objects | -| Core bindings | Binding identity, typed conversion, presence/defaults, converted-value application, binding metadata validation | Scope names, ConfigKit/PromptKit types, application object cloning | -| Core registry | Validate/stage/commit installation batches and invalidate derived metadata | Integration-specific installed flags, command factories, report rendering | -| Core execution | Invoke the selected compiled runner, share the current presentation context, normalize/present outcomes | Application startup, provider schemas, process exit | -| `internal/commandmeta` (planned) | Narrow backend-neutral routing/help DTOs, private command keys, parse result shapes | Public contracts, typed handlers, registry policy, upstream library types | -| `internal/cobra` | Translate neutral metadata into one invocation session; route, collect raw occurrences, render help | Typed value conversion, declaration validation, family ownership, acquisitions | -| `ckconfig` private implementation | Capture scopes, compile applicable selectors, prepare selected inputs, acquire views, perform each configuration operation | Core registry state or private binding access | -| `ckprompt` private implementation | Capture definition providers, adapt engine inspection, prepare definition input/state, render definition reports | A second dispatcher/parser or configuration-suite installation | - -The neutral metadata package is justified by the existing core/adapter exchange: -today its neutral structures live inside `internal/cobra`. Move those structures -as a unit rather than introduce an additional copy of the same DTOs. Only the core -execution coordinator needs to call the concrete adapter. Declaration and binding -logic can depend on the neutral shapes without depending on adapter operations. -This is not a backend-selection interface or a new public package. - -Integrations use supported public core contracts. Do not solve extraction by -letting them import core private assembly helpers. Small local selection-flag or -report-formatting helpers are acceptable; a fourth utility module or a universal -provider framework is not justified merely by a few similar declarations. - -## One installation path for every command - -```mermaid -flowchart TD - A[Application command factories] --> D[Captured core declarations] - B[Integration family factories] --> D - C[Core help and version factories] --> D - D --> V[Shared batch validation and staging] - V --> R[Instance registry commit] - R --> M[Cached neutral routing and help metadata] - M --> S[Fresh invocation adapter session] - S --> E[Selected typed runner and common outcome delivery] -``` - -### Construction and later registration - -Recommend one private `installBatch` operation used by both `New` and -`App.Register`: - -1. `New` validates application identity and creates an unpublished empty app. -2. Core factories construct real help/version declarations, using the same typed - command compiler as application declarations. -3. `New` submits built-ins and caller declarations together to `installBatch`. -4. `App.Register` submits its supplied declarations to that same operation. - -There are no placeholder commands, direct built-in registry writes, privileged -installation switches, or separate family installers. The app exists before its -built-in callbacks are captured, but those callbacks do not execute during -construction. A failed initial batch exposes no usable app. - -Help/version names are occupied by real built-in nodes. Optional family names are -occupied only by installed real roots. The ordinary collision check consequently -implements reservation without a parallel list of known integration names. -Root-only placement is captured metadata, not a check for a particular family -name or the package declaring it. - -### Batch validation and commit - -The shared installer should: - -- Inspect retained errors and zero/absence state. Report attached errors before - skipping an absent declaration, including absence nested in a group. -- Traverse real declarations with their installation path; enforce root placement - and report contextual construction/structural errors. Read captured binding - validation results rather than invoking parsers or factories. -- Check real root names against the existing registry and other roots in the - pending batch. Sibling collisions inside a declaration belong to the same core - structural-validation rules. -- Stage additions in a local pending map. Commit only after the entire batch is - valid. Existing captured nodes need not be cloned to stage new roots. -- Invalidate derived routing/help metadata once after a successful nonempty - commit. A failed batch or an entirely absent batch leaves registry and cached - metadata intact. - -All mutation is confined to the commit portion. Do not implement atomicity through -temporary insertion followed by rollback or through copying the whole existing -registry for every call. This preserves the selected per-call registry guarantee; -it adds no transaction for callback effects, allocation failures, or concurrency. - -Preserve retained causes with ordinary wrapping/joining where needed. Do not -flatten them into strings. The implementation need not promise exhaustive error -enumeration, stable error text, or a public aggregate-error type. - -## Capture once; keep installation state separate - -### Immutable declaration records - -Recommend an opaque `Command` handle referring to a private captured record. -That record contains descriptions, child handles, an optional compiled typed -payload, retained errors, root-only placement, and explicit absence state. - -Copy caller-owned metadata slices at their ownership boundary, including binding -lists, child-handle lists, scope operations, and selector applicability. Once -captured, those records are read-only. `Group` copies its child-handle slice; -it does not recursively clone already captured child records. `WithError` returns -a new wrapper/record retaining the existing content and added cause without -mutating the original handle. `RootGroup` reuses ordinary group capture and adds -placement metadata. A zero handle remains distinguishable from explicit absence. - -This permits declaration reuse without copying entire trees at every assembly -level. It does not clone application values, callbacks, or their object graphs. -Do not write registry paths, app pointers, converted values, or invocation caches -into reusable declarations. Core built-ins capture their own app-specific context -when each app is constructed. - -Keep payload and children independent in the private model. Do not introduce a -new prohibition on runnable commands with children merely because separate leaf -and group constructors are convenient. - -The selected public addition is [children on runnable commands](registration-api.md#runnable-parents-and-captured-children) -through `CommandSpec.Children`. It exposes the behavior described by -[ADR 0006](../adr/0006-command-grammar.md). Capture the child-handle slice using the -same ownership and immediate composition checks as `Group`, and enforce the same -placement rules at installation. `Group` remains a convenience for non-runnable -parents. This addition requires no separate registration mechanism. - -### Divide validation by responsibility and lifetime - -Use one implementation of each core metadata rule: - -- Command/binding constructors capture local metadata and retain local errors. -- Group capture checks its immediate composition without recursively rerunning - every child's local binding validator. -- Installation checks contextual placement and registry/batch collisions while - reporting retained errors through one tree traversal. -- Integrations check scope identifiers, applicability references, declared - operations, adapter issues, and enabled provider wiring. They hand generated - flags and operands to core command construction for generic binding checks. - -This prevents ancestor-by-ancestor validation and copying from growing with both -tree size and nesting depth. Exact allocations remain implementation choices; -do not add a global intern table, reflection-driven compiler, or mutable -"validated" cache shared between applications. - -Core name/short-name/help-reservation rules also check the combined standard and -selector flags on generated commands. Integration code should not reproduce -those rules with handwritten reserved-name lists. Validate bindings used by -generated commands; unused selector bindings in an absent family are ignored, -as clarified in the OQ-1 resolution below. Retained adapter-construction errors -still reach installation. - -## One typed conversion and application mechanism - -### Prepared typed values - -Retain the useful separation in the prototype's `prepareInput`: convert raw -occurrences before initializing input. Refactor its prepared setter closures into -one private typed prepared-value mechanism usable by ordinary bindings and -`CaptureFlags`/`FlagValues`. - -A prepared entry needs original binding identity, supplied state, and a typed -application closure. The closure captures the converted/default value and applies -it to initialized input storage. A repeatable binding copies its core-owned slice -when applying it to another input; it does not clone arbitrary elements. - -Scalar, boolean, repeated-flag, and operand constructors remain distinct where -their parsing and cardinality differ. Their execution glue should feed the same -typed preparation/application machinery, rather than create another conversion -system for integrations. - -`CaptureFlags` adapts each original binding to an outer command binding that stores -the prepared typed entry in `FlagValues`. It does not apply selectors to a shared -template, initialize suite input, or invoke a parser a second time. -`FlagValues.ApplyTo` selects the original identities and applies their prepared -values to the supplied destination. - -All supplied occurrences undergo conversion, including selectors inapplicable to -the selected scope and values supplied alongside help. Scope filtering governs -value application and required-value checks, not conversion. An earlier malformed -singleton occurrence remains an error even if a later occurrence is valid. - -Recommend a private identity token per original binding and an invocation-local -lookup from token to prepared entry. Handle copies preserve identity; lookalike -bindings get a different token. A private nonzero-sized token object avoids -relying on addresses of zero-sized Go objects. No global registry or public ID is -needed. The lookup belongs to the invocation store, not the reusable binding. - -### Required values and errors - -Separate required metadata used for help from the point where execution enforces -it. Ordinary flags enforce their requirement in ordinary input preparation. -Captured inner flags enforce it when `ApplyTo` selects applicable bindings. Do not -erase required metadata or reimplement missing-value policy in each integration. - -Apply the API's existing `ErrFlagInput`/`ErrFlagWiring` categories through one core -classification helper. The command's input-validation path should recognize -wrapped wiring failures as operational; ordinary validation and missing input use -usage outcomes. An integration may wrap those causes with scope/family context -without parsing error messages or inventing another outcome protocol. - -Help still converts supplied occurrences and rejects malformed values through the -same preparation logic, but does not materialize the target command's application -or suite input, invoke its validation or execution callbacks, or enforce its -execution-required fields. Suite state factories and providers also remain -uninvoked. The built-in help command may prepare its own private typed input -through the common compiler. Presence, singleton repetition, and repeated-value -semantics remain those of the finalized API and selected grammar. - -## One compiled execution path, including help - -Recommend that the private typed command compiler produce a prepared invocation -closure. After raw conversion, this closure materializes `I`, applies values, -runs `CheckInput`, and invokes the typed handler. Keep `I` inside that closure -rather than passing an `any` input through separate factory/check/run calls and -repeated type assertions. A heterogeneous registry can store the resulting -non-generic private runner interface/function. - -The private runner receives the current execution context, containing streams, -cancellation, the app, and access to the current help/presentation session. For an -application or integration handler, the compiler passes only the public -`Invocation` and typed input. Built-in help uses a narrow private typed runner -adapter to render targets through that same current execution context. - -That private adapter shares declaration capture, metadata validation, binding -preparation, registry installation, and outcome presentation with every other -command. It changes only the final callback's access to core services. Version -can use ordinary typed handler wiring with captured identity. Do not add a -second built-in compiler or a dispatcher branch keyed on `path == "help"`. - -The adapter's local `--help` mechanism is still common syntax machinery, not a -separately registered application command. Bare root/group help and explicit -help commands use the same presentation service. Cobra must not add hidden -command families independently of captured registrations. - -### Metadata and session lifetime - -Preserve the existing cache/session split: - -- Registry changes invalidate one derived neutral routing/help snapshot. Sorting - and backend metadata projection occur when that snapshot is rebuilt, not on - every parse or help operation. -- A fresh mutable adapter session is built for each execution and reused for - its parsing, help, and usage presentation. Mutable Cobra values are not cached - across invocations. -- Give each snapshot a private key-to-runner table. The adapter returns the - selected neutral command key along with its path/raw values. Core execution - uses the table associated with that session rather than resolving the same - path again through `commandAt`. -- Command keys are meaningful only within their snapshot. Do not expose them, - depend on persistent numbering, or use a newly rebuilt table to interpret a - result from an older session. -- If a callback registers additional commands and subsequent presentation needs - current metadata, refresh the presentation session on demand. Do not reparse - argv or rerun factories, validators, or handlers merely to refresh help. - -This removes duplicated route resolution and preserves useful existing behavior -without promising concurrent dispatch, registration during another goroutine's -execution, or a general snapshot-isolation contract. - -## Integration preparation without parallel execution frameworks - -### Configuration suite - -At family construction, capture a private prepared suite containing ordered scope -records, lookup by scope name, supported operations, discovery metadata, and the -applicable original selector bindings for each scope. Those lists are a natural -output of composing scopes and selectors; no separate mutable membership cache -or public capability framework is needed. - -Each inspection command uses an invocation-local outer input containing standard -selectors, `FlagValues[I]`, and its prepared operation selection. Recommend a -pointer-shaped private outer input initialized by the command's own `NewInput`. -Its `CheckInput` can populate the prepared selection and `Run` receives that same -object. This matters because the public `CheckInput` receives its generic input -by value: assigning a plan to a field in a copied value struct would lose it. -This outer allocation is distinct from the suite's input and shared-state -factories and is not performed on help paths. - -The shared configuration preparation routine should: - -1. Resolve scopes, operation availability, formats, and applicable operand rules - from metadata before suite factories or acquisition. -2. For each selected scope, obtain a fresh `I` through `NewInput` or its zero - value, apply only applicable captured bindings, and call the scope-aware - `CheckInput` with scope identity and selection. -3. Retain the successfully prepared scope/input records in selected order on the - outer invocation input. On failure, return the supported error and stop; - later inputs need not be initialized. -4. In `Run`, after input validation succeeds and execution reaches acquisition, - obtain shared `S` once through `NewState` or its zero value. Pass that state - and each prepared input explicitly to the selected provider. - -Do not recompute selection in every operation's `Run`, put the plan in a shared -declaration closure, project a shallow copy of one input pointer, or clear -non-applicable fields after initialization. Non-applicable bindings should leave -the scope input's own initialized fields untouched. - -Share this preparation across validate/show/sources/explain. Keep acquisition and -report loops operation-owned: `show` acquires all selected views before report -output, while other operations retain their selected timing. A universal -"acquire everything then render" wrapper would alter behavior unnecessarily. -Discovery reads captured metadata without initializing suite input or state. - -`AppCommand` should build the ordinary scope and suite internally and call the -same family factory. It is convenience assembly, not a second config installer, -handler set, or execution cache. - -### PromptKit suite and scope adapter - -Definition commands use the same core captured bindings, typed runner, and outcome -delivery. A small private definition-input helper applies the entire selected -flag set, validates the typed input, and retains it for the runner. It needs no -configuration-scope projection or inherited mixed-context selector model. - -Within `ckprompt`, share engine loading/inspection and diagnostic adaptation -between scope and named-definition adapters where their upstream contract is the -same. Inspect the original loader before wrapping it. Provider lookup failures -still retain diagnostics and error identity. Keep report models and native -configuration views with their proper owners. - -`ConfigScope` constructs a public `ckconfig.Scope` and delegates configuration -command generation to `ckconfig`. It must not construct another config family or -reach into `ckconfig`'s private prepared suite. Its construction-error handoff is -the selected `ckconfig.WithScopeError` operation described below. - -### Share mechanics where ownership supports them - -Core owns outcome classification and diagnostic delivery once; integration -handlers return public outcomes and diagnostic envelopes. Within an integration, -share mundane writing/encoding and provider-error helpers where appropriate, -while preserving each report's actual timing and incomplete-delivery semantics. - -Some small helpers may remain similar between independent modules. Do not expose -generic encoders, upstream severity converters, standard-flag getters, or a -generic pipeline merely to eliminate a few lines. Important duplication to remove -is competing registration, validation, conversion, acquisition-lifetime, and -presentation policy—not every structurally similar field assignment. - -## Efficiency and implementation review evidence - -The intended cost profile is straightforward: - -- Capture local metadata once and share read-only subtrees; avoid recursively - copying/revalidating a child at every ancestor. -- Stage only added roots, with one contextual tree walk and ordinary name - lookups; avoid cloning the existing registry or installing commands one by one - inside a supposedly atomic batch. -- Derive routing/help metadata once per changed registry state, and use the - adapter's selected command key rather than repeating route traversal. -- Convert each supplied occurrence once per invocation. Apply prepared values - only to selected inputs, copying only core-owned repeated collections. -- Prepare scope selection once per invocation and shared state once when - acquisition is reached. No process-global provider cache is introduced. - -These are algorithmic recommendations, not latency/allocation guarantees. Existing -benchmarks can inform the implementation sprint; this audit has not run them or -measured performance. Profile before adding pools, segmented trees, bitsets, -concurrent registries, or mutable backend reuse. - -Focused implementation evidence should demonstrate the common path with all -three command sources: built-ins, application declarations, and integrations. -Include invalid bindings, conflicting roots in either order, an invalid final -member of a batch, nested root-only declarations, absence with retained errors, -source metadata edits after capture, and declaration reuse across apps. -Include runnable parents with children through `CommandSpec.Children`, applying -the same capture, collision, and placement checks as ordinary groups. - -For execution, check wrapped wiring versus input failures, fresh repeatable -collections, pointer-shaped scope inputs with shared state, scope-aware validation, -help without target application/suite factories or providers, and metadata refresh -without callback replay. Check that supplied but inapplicable selectors still -undergo conversion, including on help paths, while the built-in help command can -prepare its own private input through the common compiler. -Evidence for the internal path itself can inspect the shared installer/compiler; -do not turn a private function name or exact allocation count into a public -contract. These are review targets for the future implementation, not a request -to add or run tests during this planning pass. - -## Resolved scope-error handoff (OQ-2) - -The selected `Scope[I, S]` includes private adapter-construction issues owned by -`ckconfig`. Before the selected addition below, `ckprompt.ConfigScope` could not -attach its original nil-loader error through supported public contracts. An -`Acquire == nil` check alone is insufficient: narrowing `Operations` to empty makes a scope -metadata-only, while the original invalid loader must remain an error. Wrapping -the nil loader in a non-nil acquisition closure would incorrectly defer it to -invocation. A core `WithError` only accepts a `Command`, not a scope. - -**Selected:** use the integration-owned [retained scope-error operation](registration-api.md#retained-scope-errors), -`ckconfig.WithScopeError`, declared in the registration API document. -It returns a scope retaining the original issue, preserving supported error -identity. Repeated attachments preserve earlier causes and leave the original -scope unchanged; a nil error preserves the scope. Retained causes survive scope -copies and narrowing of `Operations`. The family factory collects those issues -into its command declaration before capability selection or absence can discard -them. `ConfigScope` uses this for its original loader validation and -otherwise fills the ordinary public scope fields. - -This mirrors the accepted core retained-error model at the boundary that needs -it. It does not add scope installation, expose private representations, or move -PromptKit types into the core. Scope capture still occurs at the existing family -factory; this operation is evidence attachment rather than a new lifecycle. - -**Alternative considered:** give the public scope declaration an explicit -construction-error field, consumed by the family factory alongside provider metadata. This is simple -and permits application-created adapters to supply errors directly. It expands -mutable public representation and requires defining preservation/combination of -multiple errors; the opaque attachment operation keeps those details private. - -## Resolved unused-binding validation (OQ-1) - -**Selected:** empty definition suites contribute no commands, and unused selector -bindings are ignored. Generate commands for the supplied capabilities and validate -the bindings needed by those commands. When neither definition-inspection -capability is enabled, the family contributes no namespace, help entry, or handler. -There is no need to construct validation-only commands or a temporary application -for selector bindings that will not be installed. - -This follows the capability-driven assembly model: selecting no capabilities -naturally produces no commands. Rejecting unused bindings would report defects -that cannot affect routing, parsing, or execution. This clarifies the validation -boundary while preserving ADR 0011's selected empty-suite behavior; it does not -promise exhaustive validation of unused metadata. - -An explicitly invalid adapter construction remains distinct from an omitted -capability. Retain errors such as a nil loader supplied to an adapter even when -its capabilities are subsequently cleared. Return ordinary `NoCommand()` for an -empty definition suite, attaching any retained construction errors through the -existing `WithError`. Installation checks those errors before skipping absence. -No validation-bearing `NoCommand` overload or public declaration-validation helper -is needed. - -The binding rule also applies to configuration selectors unused by any generated -inspection command. Existing configuration-suite semantics remain unchanged: -zero named scopes is an error, while named metadata-only scopes supply the real -`config scopes` discovery command and remain valid. Integration scope identifiers, -applicability references, and enabled-provider wiring retain their own validation; -ignoring unused bindings does not erase retained adapter errors or bypass checks -on commands that are generated. - -This construction-time rule does not skip invocation-time conversion of a supplied -flag on an installed command merely because it is inapplicable to the selected -scope. Conversion and scope-specific application remain separate as described -above. - -**Alternatives considered:** rejecting empty definition suites could catch an -accidental omission, but would make an empty capability selection an error without -a demonstrated need. Validating all unused bindings, through either an extended -absence factory or an explicit validation helper, would catch latent defects but -add public API and validation machinery for commands that do not exist. Neither -alternative is selected. diff --git a/integrations/ckconfig/go.mod b/integrations/ckconfig/go.mod index 4818fc2..32d4df7 100644 --- a/integrations/ckconfig/go.mod +++ b/integrations/ckconfig/go.mod @@ -7,3 +7,9 @@ require ( gitea.maximumdirect.net/eric/ConfigKit v0.5.0 go.yaml.in/yaml/v4 v4.0.0-rc.6 ) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect +) diff --git a/integrations/ckprompt/examples/composed/README.md b/integrations/ckprompt/examples/composed/README.md index 1e42300..5321426 100644 --- a/integrations/ckprompt/examples/composed/README.md +++ b/integrations/ckprompt/examples/composed/README.md @@ -5,7 +5,7 @@ definition inspection, and one application-owned command: ```sh go run . config scopes -go run . config validate --scope all --campaign spring +go run . config validate --scope all --campaign spring --limit=0 go run . promptkit show prompt briefing --campaign spring --format yaml go run . promptkit show profile spring --campaign spring go run . session restore session-42 --campaign spring diff --git a/integrations/ckprompt/go.mod b/integrations/ckprompt/go.mod index e97b6f4..e194795 100644 --- a/integrations/ckprompt/go.mod +++ b/integrations/ckprompt/go.mod @@ -5,6 +5,17 @@ go 1.26.0 require ( gitea.maximumdirect.net/eric/CommandKit v0.2.0 gitea.maximumdirect.net/eric/CommandKit/integrations/ckconfig v0.1.0 + gitea.maximumdirect.net/eric/ConfigKit v0.5.0 gitea.maximumdirect.net/eric/promptkit v0.12.0 go.yaml.in/yaml/v4 v4.0.0-rc.6 ) + +require ( + gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 // indirect + gitea.maximumdirect.net/eric/promptkit-backend-rakestrawhome/v3 v3.20260930.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/text v0.14.0 // indirect +)