Plan staged CommandKit implementation

This commit is contained in:
2026-10-07 03:55:36 +00:00
parent 0f6ced42ef
commit e5c361d2cb
5 changed files with 811 additions and 36 deletions

View File

@@ -15,7 +15,7 @@ implementation is available yet.
- [Dispatcher implementation roadmap](docs/roadmap/02-dispatch.md)
- [Backend implementation roadmap](docs/roadmap/03-backend.md)
- [Public facade implementation roadmap](docs/roadmap/04-facade.md)
- [Policy adoption record and follow-up](docs/roadmap/policy.md)
- [Policy provenance](docs/policy/meta.md)
CommandKit is licensed under the [LGPL v3.0 or later](COPYRIGHT).

View File

@@ -46,7 +46,7 @@ canonical documentation owners.
| Backend integration | [Backend roadmap](roadmap/03-backend.md) and [ADR 0002](adr/0002-cobra-backend.md) |
| Public API or internal adapter boundaries | [Facade roadmap](roadmap/04-facade.md), [ADR 0003](adr/0003-public-facade.md), and the architecture policy |
| Licensing or incorporated material | [Project grant](../COPYRIGHT), [license texts](../LICENSE), and [attribution](oss/attribution.md) |
| Policy provenance and adoption follow-up | [Policy provenance](policy/meta.md) and [adoption record and follow-up](roadmap/policy.md) |
| Policy provenance | [Policy provenance](policy/meta.md) |
Read all applicable rows for cross-cutting work. These roadmaps describe future
implementation; their illustrative Go types and package layouts are not APIs.

View File

@@ -11,6 +11,11 @@ breaking changes. The decisions recorded below are accepted; other recommendatio
remain proposed. Implementation details and exact Go API declarations remain
future work.
The ADRs record architectural choices and their rationale. Detailed selection,
acquisition coordination, report formatting, and diagnostic-delivery mechanics
below remain proposals unless explicitly marked accepted. Their presence here
does not establish additional contracts in the ADRs.
### Accepted planning decisions
- Use namespace/resource first: `app config show` and `app promptkit show`.
@@ -212,8 +217,9 @@ available names in the diagnostic; there is no fallback to `app`.
Scopes use stable semantic names rather than executable names such as `notarius`.
Additional independently meaningful configuration scopes declare their supported
inspection operations and applicable providers. CommandKit validates declared
wiring and coordinates explicit dependencies, reusing compatible provider results
within an invocation. Scope inventory and help report the supported operations.
wiring. This roadmap proposes coordinating explicit dependencies and reusing
compatible provider results within an invocation; the coordination contract
remains API design work. Scope inventory and help report the supported operations.
An unsupported scope-operation selection receives a usage diagnostic before
acquisition. `all` retains its all-scope meaning and checks coverage for the
requested operation. [ADR 0009](../adr/0009-configuration-scopes.md) owns the model.
@@ -259,8 +265,8 @@ When `--config` is omitted, CommandKit invokes the registered acquisition callba
with that absence preserved. It exposes supplied discovery, environment, and
default-only metadata in help. Sharing acquisition code with ordinary execution is
recommended consumer practice; execution loading and filesystem discovery remain
application choices. [ADR 0010](../adr/0010-configuration-selection.md) owns the
parsing and propagation contract.
application choices. The detailed selection and propagation rules in this section
remain proposals for integration API design.
`--overlay` always selects a configuration overlay, never a PromptKit execution
profile. Follow ConfigKit's naming and resolution semantics: omission, an empty
@@ -358,7 +364,8 @@ implements acquisition, composition, defaults, decoding, and domain resolution
according to its public result contract. The PromptKit adapter requests upstream
configuration validation with all-definition coverage. Missing upstream support
is a dependency of that capability, rather than the entire integration.
[ADR 0008](../adr/0008-inspection-lifecycle.md) owns this boundary.
The acquisition lifecycle and dependency coordination described here remain
proposals for provider API design.
CommandKit's validation handler requests the declared configuration-validation
operation and writes its result. Request preparation, execution, and operational
@@ -670,8 +677,9 @@ CommandKit release version. `--format yaml` changes presentation of the selected
identity. Version subjects are separate from configuration scopes. CommandKit
uses build metadata with acquisition callbacks left uninvoked.
Future component identities can use registered version metadata.
[ADR 0004](../adr/0004-standard-inspection-surface.md) records version selection.
Registration of future component identities remains an API design proposal.
[ADR 0004](../adr/0004-standard-inspection-surface.md) records version subjects;
this roadmap retains the selected command syntax.
Applications supply build identity, with tagged Go build information usable when
available. An unversioned development build reports `dev`; unknown revision or

View File

@@ -0,0 +1,777 @@
# Initial CommandKit implementation plan
Status: ready for implementation; all stages pending.
Date: 2026-10-07.
## Purpose and instructions for the implementation agent
Implement the selected end state in [the feature roadmap](initial.md). Read this
whole plan before starting. Complete Stage 1, then each subsequent stage in numeric
order. Each stage is sized as a separate Luna implementation assignment. Later
stages depend on the completed earlier stages, even where their feature IDs overlap.
This document owns implementation steps and the concrete design needed to execute
them. The feature roadmap owns the target and user intent. The accepted ADRs and
[architecture policy](../policy/architecture.md) control architectural boundaries.
The older numbered roadmaps are evidence; their conflicting or unselected
proposals do not override this plan or the accepted decisions.
Before each stage, follow [development orientation](../development.md) and the
[agent policy](../policy/agents.md). Read applicable documentation, testing, and
OSS policy sections. Inspect the current implementation and previous stage work;
use the prescribed knowledge-graph discovery workflow for code. Keep changes
focused on the assigned stage. Document implemented behavior when it lands.
The implementation assignments include the behavioral evidence listed below.
Use the [testing policy](../policy/testing.md) to avoid redundant assertions or
tests of incidental private structure. Use real collaborators where deterministic;
use controlled providers/writers for failures. The default suite is offline and
uses no real credentials or model requests. Once the module exists, format touched
Go files and run the relevant tests, then `go test ./...` for the completed stage.
Dependency/toolchain preparation can happen beforehand; tests themselves must not
require network access. No coverage threshold, race/platform matrix, scanner,
publisher, or consumer migration is introduced by this plan.
For each completed stage, report its deliverables, checks, and remaining work.
Apply the local commit policy to completed units. Update this document's stage
status as work completes; retain deferred work in the feature roadmap. A stage is
complete when its implemented boundary and checks are complete, not when the
entire final library already exists. Early stages may establish declarations that
later stages use, but must compile and must not claim later behavior is implemented.
## Baseline and execution boundaries
At planning time, CommandKit contains documentation, policies, and nine accepted
ADRs, with no Go module or production Go code. Initial delivery uses ConfigKit
v0.5.0 and PromptKit v0.12.0. Their inspected commits and consumer reuse candidates
are recorded in the feature roadmap. Those sibling checkouts are review evidence;
the implementation uses released public APIs and module dependencies.
Create exactly the two selected production packages: root `commandkit` and
`internal/cobra`. Production file names below are suggested organization, not
compatibility contracts. Root public named types retain their own declarations
and private implementation state. No root aliases, embedding, loose bridge
objects, or returned backend errors expose Cobra/pflag or internal types.
Code in the adapter may use neutral bridge types exported within `internal/cobra`;
these types are not aliased into the public API. The root imports the adapter; the
adapter never imports the root. Keep registry policy, typed input construction,
standard handlers, providers, and reports in the root.
The implementation reuses owning writers and public records. Write the small
integration glue from those contracts. Treat Notarius/Scriptorium code as behavior
and field-inventory evidence; this plan requires no source copy. If a substantive
copy becomes desirable, resolve its actual rights and record source revision,
changes, and notices under the OSS policy before copying. Do not infer a change
from GPL application grants to CommandKit's LGPL grant from common ownership.
Defer `config diff`, PromptKit catalogs/listing, JSON reports, shell completion,
output-file management, selectable backends, general DI/plugin machinery,
concurrent-dispatch guarantees, new library families, migrations, and publication.
Do not install placeholders or claim capabilities for deferred features.
## Stage inventory and feature coverage
| Stage | Deliverable | Feature IDs |
| --- | --- | --- |
| 1 | Module, identity/outcome primitives, initial dependency attribution | API-01, OUT-01 |
| 2 | Instance registry and composable command declarations | API-01, REG-01 |
| 3 | Typed flag/operand bindings, defaults, and presence | INPUT-01 |
| 4 | Internal Cobra routing/parsing bridge | BACK-01, INPUT-01 |
| 5 | Public invocation and diagnostic/delivery coordination | INV-01, OUT-01 |
| 6 | Complete generated help and version commands | HELP-01, VER-01 |
| 7 | Typed configuration suites, scopes, selectors, discovery | INT-01, CFG-01 |
| 8 | ConfigKit scope helper and validation | INT-01, CFG-02 |
| 9 | Native and aggregate YAML display | CFG-03 |
| 10 | Source inventory reports | CFG-04 |
| 11 | Safe path explanation reports | CFG-05 |
| 12 | PromptKit configuration scope | PK-01 |
| 13 | Named prompt/profile inspection, including ExtraParams | PK-02, PK-03 |
| 14 | Public examples, consumer fit, documentation and completion audit | All included IDs |
## Stage 1: establish the module and outcome primitives
**Status:** pending. **Prerequisite:** repository orientation.
1. Create `go.mod` for `gitea.maximumdirect.net/eric/CommandKit`, with `go 1.25.5`.
Use the roadmap's initial pins: ConfigKit v0.5.0, PromptKit v0.12.0,
Cobra v1.10.1, pflag v1.0.9, and `go.yaml.in/yaml/v4 v4.0.0-rc.6`.
Resolve the actual graph and maintain `go.sum`. Keep required transitive
dependencies; do not use adjacent checkouts or committed local replacements.
2. Add applicable dependency notices/license texts when incorporating those
dependencies, following [the OSS policy](../policy/oss.md). Use exact imported
commits and actual upstream notices; assess relevant transitive components from
the resolved graph. Do not add fictional entries or generic legal conclusions.
3. Add `doc.go`, `identity.go`, and `outcome.go` with the root-owned types below.
Update development commands to their implemented boundary once the module exists.
The following declarations specify public API shapes; implement bodies and GoDoc
in their owning stages. They are not files to copy verbatim with missing bodies.
```go
type Identity struct {
Name, Version, Revision string
}
type Streams struct { Out, Err io.Writer }
type Invocation struct {
Context context.Context
Streams Streams
}
type OutcomeKind string
// Constants: OutcomeSuccess="success", OutcomeUsage="usage",
// OutcomeOperational="operational", OutcomeDomain="domain".
type DeliveryState string
// Constants: DeliveryNone="none", DeliveryComplete="complete",
// DeliveryIncomplete="incomplete".
type Diagnostic struct {
Severity configkit.Severity
Write func(io.Writer) error
}
type Outcome struct {
Kind OutcomeKind
Status int
Domain string
Cause error
DeliveryError error
DiagnosticDelivery DeliveryState
ReportIncomplete bool
Diagnostics []Diagnostic
}
func Success() Outcome
func Usage(cause error) Outcome
func Failure(cause error) Outcome
func Domain(name string, status int, cause error) Outcome
func MessageDiagnostic(severity configkit.Severity, message string) Diagnostic
```
Use the public ConfigKit severity type deliberately rather than introducing a
parallel severity hierarchy. Diagnostic writers are caller/root functions, never
backend objects. Their output goes through Stage 5's single presentation path.
`Success`, `Usage`, and `Failure` set kinds/statuses to success/0, usage/2, and
operational/1. `Domain` preserves the supplied domain name, status, and cause.
`Cause` is the operation's error; `DeliveryError` records writer failures separately
and may reference the same error when writing itself caused the operation failure.
Callers inspect these errors with `errors.Is`/`errors.As`; Outcome need not implement
`error`. Empty diagnostic delivery state normalizes to `DeliveryNone`. A zero
Outcome normalizes to success/0 when no classification was supplied; do not infer
domain meaning from numeric status. The explicit domain classification survives
invocation coordination.
**Checks:** compilation; focused outcome-helper checks for classification/status
and cause identity. No invocation or inspection behavior is claimed yet.
## Stage 2: implement the instance registry and command declarations
**Status:** pending. **Prerequisite:** Stage 1.
Implement `App`, opaque `Command`, metadata, group composition, and registration
in root files such as `app.go`, `command.go`, and `registry.go`.
```go
type App struct { /* private state */ }
type Command struct { /* private declaration */ }
type Supplied[T any] struct { Value T; Set bool }
type FlagMeta struct { Name, Short, Usage string; Required bool }
type ArgumentMeta struct { Name, Usage string }
type Flag[I any] struct { /* private binding declaration */ }
type Argument[I any] struct { /* private binding declaration */ }
type CommandSpec[I any] struct {
Name, Summary, Description string
NewInput func() I
Flags []Flag[I]
Arguments []Argument[I]
CheckInput func(I) error
Run func(Invocation, I) Outcome
}
func New(identity Identity, commands ...Command) (*App, error)
func NewCommand[I any](spec CommandSpec[I]) (Command, error)
func Group(name, summary string, children ...Command) (Command, error)
func WithChildren(parent Command, children ...Command) (Command, error)
func (a *App) Register(commands ...Command) error
```
1. Commands have one path component as `Name`; composition supplies the prefix.
`Group` creates a non-runnable parent. `WithChildren` returns a new declaration
retaining the parent's optional Run and bindings. Children are relative to that
parent. A runnable parent and its children can both be invoked.
2. Keep declarations independent of invocation state. Retain callbacks without
invoking NewInput, setters, handlers, or acquisition while registering. Copy
declarative slices as needed so registration owns its routing metadata; this
does not deep-copy caller closure state or arbitrary input objects.
3. Validate routable declarations: nonempty single-token command names, unique
sibling paths/flag names/short spellings, valid operand bounds, and required
parser/setter wiring. A command token must not be option syntax or `--`, since
those tokens delimit routing. Do not impose a lowercase vocabulary policy on
custom command names. Scope identifiers have their separate Stage 7 rules.
4. Reject duplicate paths; do not merge user registrations based on order.
Register a complete group with its children. Validate a registration batch
before attaching it so a rejected standard-family install leaves no half-tree.
5. New requires a nonempty identity Name and accepts optional initial command
registrations. It reserves and creates metadata for automatic root help/version;
Stage 6 supplies their complete handlers. Standard installers claim `config` and
`promptkit` ownership as their families are installed. Custom commands cannot
replace standard commands or add children under installed owned families.
A pre-existing custom tree at the same family path makes installation fail.
6. `NewInput == nil` means a fresh zero I. A supplied factory is invoked only for
execution after successful parse/help handling, and can initialize unbound
runtime fields. CommandKit overwrites bound fields from fresh parsed state and
defaults. Shared objects deliberately supplied by the factory stay caller-owned.
`CheckInput` is declared input-combination validation and returns usage failure;
domain validation belongs in Run or acquisition.
Define the private binding storage needed by Stage 3, but add no public factories
with unimplemented bodies. Complete public type ownership without an extra engine
package or exported bridge methods.
**Checks:** metadata construction does not invoke callbacks; independent App
registries; duplicate nested paths; runnable-parent composition; registration errors
leave the previous inventory intact. Execution remains Stage 5 work.
## Stage 3: implement typed bindings and input assembly
**Status:** pending. **Prerequisite:** Stage 2.
Add root factories in `binding.go` using these signatures:
```go
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]
```
1. A nil scalar default is the zero V; defaults never imply Set. With no explicit
repeat occurrence, use a fresh copy of defaults and Set=false. Once supplied,
repeated values replace the default collection and accumulate in argv order.
Explicit empty, false, and zero values set Set=true. Ordinary fields can use a
setter that assigns only `.Value`.
2. Singleton occurrences are all converted; each later successful occurrence
replaces the previous value. A malformed earlier occurrence remains a
conversion failure. Repeat conversion is one occurrence per value; do not apply
pflag's CSV/slice interpretation to a domain string containing commas.
3. Use `strconv` scalar parsers, `time.ParseDuration`, or caller domain parsers;
consumers can pass standard parsers directly. BoolFlag uses presence=true and
explicit `=true|false` conversion through `strconv.ParseBool`. ValueFlag is
available for bool-like domain values needing an ordinary value argument.
4. Parse into fresh typed binding values before constructing I. This lets help
inspect provided flag syntax without invoking NewInput. After help/parse checks,
allocate a fresh I, call NewInput if supplied, and apply typed setters. There is
no handler lookup of raw flags by name and no generic reflective field mapper.
5. ValueArgument has minimum 1 or 0 and maximum 1. RepeatArgument uses supplied
bounds; max=-1 means unbounded. Allocate operands in declaration order, greedily
within each maximum while reserving the minimum required by later arguments.
This also defines multiple/optional variable-length declarations without an
incidental final-variadic-only restriction. Reject unsatisfied total bounds.
6. Metadata exposes defaults, requiredness, repetition, and operand counts in
help. Render typed defaults for display without exposing backend flag objects.
A custom one-byte Short spelling is optional; standard options use long names
except the accepted `-h` help spelling. Names/spellings cannot collide with local help.
**Checks:** focused binding behavior for omission, explicit zero/false/empty,
last-wins, repeated order and comma-containing strings, defaults, positional
allocation, caller parse errors, and fresh collections/input state. Adapter-level
integration of these cases belongs to Stage 4/5; avoid duplicating every case.
## Stage 4: implement the internal Cobra adapter
**Status:** pending. **Prerequisite:** Stage 3.
1. Create `internal/cobra` with neutral tree/flag/operand declarations and fresh
per-run binding factories. A bridge declaration contains path metadata,
descriptions, local flag metadata, and conversion callbacks. A parse result
contains the selected path, help request, operands, and neutral failure data.
Backend-owned failures return message data, not backend error objects. A
caller-parser cause is retained separately where supplied.
2. Build a fresh backend tree and pflag value storage per invocation. Split argv
before its first option or `--`. Give only that leading word sequence to Cobra
Find; combine its unconsumed operands with the original tail for the selected
command's ParseFlags call. Set a neutral Args callback so root metadata, rather
than backend legacy operand policy, validates argument counts. Cobra resolves
children until a non-child operand; never resume routing after an option or
operand. A runnable parent's remaining words are its operands. A non-runnable
group with an unknown trailing command gets a usage failure.
3. This boundary adaptation avoids Cobra's ordinary flag-stripping search selecting
a child beyond an earlier option. Do not feed the tail into a second tree-wide
Execute search or write a parallel tree router. Root coordination invokes the
neutral handler after parse. The relevant machinery is in [Cobra's pinned source](https://github.com/spf13/cobra/blob/v1.10.1/command.go).
4. Adapt pflag Values to the Stage 3 conversions, boolean no-value behavior,
presence, and repeated-value rules. Enable interspersed operands; `--` ends
parsing. The [pinned pflag parser](https://github.com/spf13/pflag/blob/v1.0.9/flag.go)
is the tokenizer/conversion mechanism. Do not add a second general flag parser.
5. Keep flags local to resolved commands. Group flags are available when that
group runs; do not invent inherited/global standard flags. Reject options
before a later path component instead of silently moving them to the child.
6. Install local `--help`/`-h` metadata; `--help=false` is an ordinary execution
request. Parse supplied flags for help, but bypass missing execution-required
flags/operands and CheckInput. Unknown flags and invalid help paths remain
usage failures. Enforce supplied operand maxima and conversions where applicable.
7. Suppress automatic backend diagnostics/usage printing. Configure backend
streams/context explicitly; do not allow backend defaults to select process
streams. Do not initialize completion commands or implicit version flags.
Do not route application outcomes through Cobra errors. Callback errors remain
root-owned. Translate only failures created by backend parsing; preserve caller
parser causes without scanning their object graphs.
**Checks:** real adapter behavior for `config show --scope app`, rejection of
`config --scope app show`, nested runnable parents, interspersed operands, `--`,
unknown flags, boolean spellings, last-wins/repetition, help with missing required
inputs, and fresh consecutive parses. Confirm the root does not import Cobra/pflag.
## Stage 5: implement invocation and diagnostic delivery
**Status:** pending. **Prerequisite:** Stage 4.
Add `func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outcome`.
It consumes argv without the executable name. Use the supplied nonnil context;
replace nil streams with `io.Discard`, so the library never silently picks process
streams. An invalid nil context returns operational failure rather than panicking
through a bundled handler. Do not create signals or terminate the process.
1. Route/parse, handle metadata help requests, then apply execution requirements,
construct typed I, run CheckInput, and invoke Run with Invocation. A command
without Run requests group help. Stage 6 completes help/version rendering.
2. Adapt backend-owned failures into root-owned usage errors with message and
optional caller-parser cause. Do not unwrap a pflag/Cobra error into Cause.
Preserve input-check/handler/acquisition causes when those stages supply them.
3. Finalize outcomes in one root presentation path. Emit Diagnostics in their
order to Err; MessageDiagnostic writes one message with checked delivery.
Use a small checked-writer wrapper for CommandKit report/diagnostic paths that
converts a short write with nil error to `io.ErrShortWrite` and retains writer
failures. Preserve the caller's writers for custom-handler output; no general
callback-output interception is required.
An error-severity Diagnostic declares that the returned cause already has a
presentation; root does not inspect rendered text to infer this. If Cause exists
and no such diagnostic was supplied, append one cause diagnostic.
Do not repeat a caller outcome marked DeliveryComplete. Do not infer that
callbacks never print: their output remains their responsibility.
4. Usage failures append selected/root usage guidance to Err. Operational failures
do not dump usage. Help/version and standard reports use Out. Report failures
set ReportIncomplete and retain the writer cause/delivery error.
5. On diagnostic write failure, stop delivery, set DeliveryIncomplete, and retain
that error in DeliveryError; use `errors.Join` when multiple delivery failures
need preservation. Standard outcomes become operational/status 1. Explicit
application-domain kind/name/status stay supplied by the handler, with delivery
failure separately available to the application's final status mapping.
6. Keep the operation Cause when diagnostics subsequently fail. Do not substitute
cancellation with a generic wrapper that loses `context.Canceled` or deadline
identity. Standard handlers check cancellation at their operation/acquisition
boundaries and pass the context on; no universal goroutine cancellation runner
or interruption guarantee for arbitrary callbacks is introduced.
7. Repeated Execute calls construct all parsed state anew. Do not add execution
locking or claim concurrent dispatch from this property.
**Checks:** public external-package invocations using real adapter; custom domain
status 2 remains domain rather than usage; context/writer causes remain inspectable;
warning-only success; diagnostic failure after operation failure retains both;
report failure marks incomplete delivery; no duplicate root diagnostics; no process
exit. Include a consecutive run that omits previously supplied flags/operands.
## Stage 6: complete help and version
**Status:** pending. **Prerequisite:** Stage 5.
1. Complete automatically installed `help` and `version`. Root bare invocation,
`help [COMMAND…]`, root/selected `--help`/`-h`, and non-runnable groups render
metadata help. Resolve `help` operands as an exact registered path. Required
execution input is not needed. Unknown path/flag is usage failure.
2. Generate descriptions, usage, child inventory, flags/defaults/counts, and later
selector applicability from the single registry declaration. Sort sibling
command names for readable stable discovery. Keep alignment/wrapping details
out of compatibility promises. Use backend usage metadata inside the adapter,
render into a buffer using the returned-error Usage method, then write through
a checked root report path. Avoid UsageString/CheckErr convenience paths that
can invoke backend fatal handling; translate a rendering failure into neutral
failure data. A broken Out yields delivery failure rather than backend printing.
3. `version` accepts only `--scope app|commandkit` (default app) and
`--format text|yaml` (default text), plus help. It has no operands.
Empty application Version renders `dev`; empty Revision remains empty. Text is
`NAME VERSION\n`, adding ` (REVISION)` before the newline when provided.
4. Resolve embedded CommandKit metadata from `runtime/debug.ReadBuildInfo`:
inspect Main when the module itself is the main module, otherwise the matching
dependency; use Replace when present to determine the effective version.
Component Identity.Name is `CommandKit`; do not turn a replacement filesystem
path into its display name. A missing/versionless/local replacement is `dev`,
not the replaced tag.
A dependency tag does not supply a revision; leave that field empty rather
than borrowing the executable's VCS revision. Caller application identity stays
caller-supplied. Keep build-info projection as a small private pure helper for
controlled checks. Do not introduce a separate integer version or inventory.
5. Version YAML is `{name, version, revision}` with explicit keys. Configure the
common encoder for checked writes and later report DTOs. Unknown scope/format
is usage failure before any provider call.
**Checks:** constructor/help/version invoke no NewInput or acquisition; invalid
config cannot disable these paths; group/runnable-parent help matches registration;
help stream failure is operational; default version contains only app identity;
controlled build info covers tagged/development/replacement component selection.
Create/update the substantive help/version portion of `docs/cli.md` now.
## Stage 7: implement configuration suites, scopes, and selector preflight
**Status:** pending. **Prerequisite:** Stage 6.
Add the following root contracts in `config.go`/`selectors.go`:
```go
type Selection struct {
Config Supplied[string]
Overlay Supplied[string]
}
type ConfigOperation string
// Constants: ConfigValidate="validate", ConfigShow="show",
// ConfigSources="sources", ConfigExplain="explain".
type ConfigRequest struct { Operation ConfigOperation; 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 ConfigView struct {
Effective EffectiveWriter
Sources SourceReader
Explanation ExplanationReader
}
type Acquired[T any] struct { Value T; Diagnostics []Diagnostic }
type ConfigScope[C any] struct {
Name, Summary string
Operations []ConfigOperation
Acquire func(context.Context, ConfigRequest, C) (Acquired[ConfigView], error)
}
type Selector[C any] struct { Binding Flag[C]; Scopes []string }
type ConfigSuite[C any] struct {
NewContext func() C
Selectors []Selector[C]
CheckInput func(Selection, C) error
Scopes []ConfigScope[C]
}
func RegisterConfig[C any](app *App, suite ConfigSuite[C]) error
```
1. A successful Acquire attests required load/owning validation for the requested
operation. Validation needs no ConfigView interface. Other operations require
only their relevant nonnil view facet; missing advertised facets are operational
provider failures after acquisition. Optional facets avoid dummy implementations.
Acquired.Diagnostics is meaningful on both success and error.
2. Registration checks unique nonempty scope IDs, reserves `all` as the aggregate
selection, requires Acquire when operations are declared, and validates known
operation metadata. A metadata-only scope can omit Acquire. Preserve
exact IDs without trimming/case folding or adding a new lexical naming policy.
Deduplicate repeated capability declarations. No comparison type is installed.
3. Install `config scopes` immediately. Assemble other handlers as their Stage 8–11
implementations land, from the union of declared capabilities. Keep declared
capabilities distinct from installed operations during these intermediate stages;
discovery/help list their intersection, so they never claim a later handler exists.
Final help and discovery expose exactly the enabled operations. A second config
suite install is a registration collision; compose scopes into one declaration
beforehand.
4. Use a private standard input type containing Selection, scope, format, path
presence, and C. `--config`/`--overlay` are singleton presence-aware strings on
acquisition-bearing commands. `--scope` defaults app; `--format` uses each
command's selected default. Scopes discovery accepts format/help only.
5. Each Selector.Binding is explicit typed flag metadata/setters for C. Empty
Scopes means all scopes in that suite; otherwise validate referenced scope IDs.
Reject collisions with standard local flags. Explain applicability in help.
An explicitly supplied selector applicable to none of the selected scopes is
usage failure. Aggregate selection routes it only to declared applicable scopes.
6. Parse selector values once. Preflight format, scope existence, operation coverage,
supplied applicability, pointer syntax when relevant, and declared input checks
before acquisition. For CheckInput, supply a zero-based typed context with all
parsed selectors; do not invoke NewContext for usage failures or help.
7. After preflight, call NewContext once (or use zero C). For each selected scope,
copy that base context and apply its applicable typed binding values/defaults;
use absent zero values for inapplicable bound fields. Unbound cache/runtime
fields from the base are retained. This supports an invocation-local cache
supplied by the consumer without a CommandKit cache/dependency graph.
Typed binding setters should assign their target fields; CommandKit does not
inspect arbitrary context objects or closure effects.
8. `all` is legal only for validate/show and covers every registered scope. Check
all capabilities before calling NewContext/Acquire. Other operations select
one scope. Default app remains app even when absent, yielding useful usage
choices. Order app, promptkit, then other IDs alphabetically.
9. `config scopes --format yaml` uses `{scopes: [{name, operations}]}`; operation
order is validate, show, sources, explain, filtered per scope. Text lists one
scope per line with its supported operations. An empty scope collection gives
an empty inventory; it does not invent app support.
**Checks:** metadata-only discovery, partial scope metadata, order/defaults,
unsupported all before callbacks, explicit/omitted selector applicability,
typed empty/false/zero context selectors, and factories uninvoked on help/usage.
Use a small custom scope fixture; owning library helpers arrive next.
## Stage 8: integrate ConfigKit and implement validation
**Status:** pending. **Prerequisite:** Stage 7.
```go
func NewConfigKitScope[C, T any](name, summary string,
load func(context.Context, Selection, C) (*configkit.Result[T], error),
) (ConfigScope[C], error)
func ConfigKitDiagnostics(scope string, sources []configkit.SourceInfo,
records []configkit.Diagnostic) []Diagnostic
```
1. The scope factory requires a nonnil loader, declares the four initial ConfigKit
operations, and wraps the public result as the relevant view facets. Load once
for the selected operation. The callback owns Definition, imports, source/
filesystem/discovery construction, Overlay mapping, overrides, domain resolution,
and validation. Preserve supplied Config/Overlay absence and empty values.
2. Capture result Diagnostics/Sources on success. On load error, use `errors.As`
for public `*configkit.LoadError` to capture diagnostics/sources and retain the
original error. Do not import engine packages or reconstruct withheld causes.
A nil successful result is an operational provider failure.
3. ConfigKitDiagnostics returns a grouped Diagnostic whose severity is error when
any owning record is error, otherwise warning. Its writer emits a scope header
and source-ID legend using quoted labels/paths, then calls public
`configkit.WriteDiagnostics` once for the supplied records. Retain canonical
record order and check every legend/write result. Empty records need no group.
Stage 5 suppresses an extra generic cause message when this error group already
presents the failure. Do not deduplicate unrelated equal-looking diagnostics.
4. Implement validate after preflight. Acquire scopes in selected order, record
their diagnostics, and emit a success record immediately after each succeeds.
Stop on the first operational or writer failure. Do not acquire later scopes
after that failure. NewContext/cache reuse follows Stage 7; no second validation
engine is introduced.
5. Text success record: `SCOPE: valid\n`. YAML is one `scopes` block sequence of
`{name, valid: true}` records, appended as each completes. Emit the initial
`scopes:` header only with the first successful record; no fake false record or
success summary on first-scope failure. A later failure may leave a valid YAML
successful-prefix report; the returned outcome is incomplete. This differs
intentionally from show's all-before-output acquisition rule.
**Checks:** real ConfigKit defaults/overlay/recorded resolution; absent versus
empty overlay callback input; warnings permit validation success; structured load
errors retain public identity and render once; all validates in order and stops
on failure. Report/diagnostic short writes retain their writer errors.
## Stage 9: implement native effective YAML display
**Status:** pending. **Prerequisite:** Stage 8.
1. Implement `config show`; its only initial format is yaml (default and explicit).
Single scope writes Effective.WriteEffective directly to Out with no envelope,
scope header, re-marshalling, or parsing of runtime Result.Config.
2. For all, preflight availability, acquire every selected view, and finish required
provider validation/facet checks before any Out bytes. If a later acquisition
fails, return its diagnostic outcome with an empty report stream.
3. After acquisition, render each native document into an ordinary bytes buffer
and compose it in order. This is framing, not a configuration projection. If
the native document already has an opening `---`, retain it and insert the
`# scope: NAME` label after it. Preserve any leading comments/directives. If it
lacks a marker, write `---`, the label, then the native bytes. Add a terminating
newline only where necessary to separate documents. Keep labels on one comment
line (quote IDs containing line controls); do not alter native payload ordering
or scalar/null/container semantics. No native-document size cap is introduced.
4. A rendering failure can leave earlier aggregate documents; preparation guaranteed
acquisition completeness, not atomic delivery. Out failures mark incomplete
report delivery and retain causes. Do not retry a broken output stream or write
a success footer. Diagnostics remain independently directed to Err.
**Checks:** compare single-scope bytes to public native writer output; decode
aggregate YAML with the pinned YAML dependency and verify document count/order,
labels and native values. Cover an already marked native document, missing/null/
empty structures, a second acquisition failure with no prefix, and a writer failure
after output begins. These protect selected behavior, not encoder whitespace.
## Stage 10: implement source inventory reports
**Status:** pending. **Prerequisite:** Stage 9.
1. Implement single-scope `config sources`, default text with yaml alternative.
Read the captured SourceReader once. Never reload sources for this report.
2. Use private explicit DTOs and snake-case YAML fields. Root shape is
`{scope, sources}`. Each source has `id`, `label`, `path`, `layer`, `kind`,
`underlay_index`, `imported_from`. A location is `{source_id, line, column}`;
imported_from is null when absent. Retain owning scalar zero/empty values and
source record order. An empty source collection is `[]`.
3. Text emits a scope heading, then one labeled source record with the same fields
and optional import location. Quote free-form labels/paths for readability.
Do not promise column alignment or invent source IDs/file paths for synthetic
inputs. Use the common checked writer/encoder path.
**Checks:** real ConfigKit root/import/overlay/synthetic records, source order,
one captured acquisition, YAML field types and import correlation, empty inventory,
unsupported all usage preflight, writer failure. Update the source-report CLI
reference with the implemented shape.
## Stage 11: implement path explanation reports
**Status:** pending. **Prerequisite:** Stage 10.
1. Implement single-scope `config explain --path POINTER`. Presence is required;
explicitly empty is the root. Parse with public `configkit.ParsePath` before
acquisition. Invalid pointer is usage/status 2; a well-formed unknown current/
historical path is operational/status 1. Check Explain's bool before calling
WriteExplanation; empty native output is not successful lookup evidence.
2. Text uses native WriteExplanation. YAML uses private DTOs with root fields
`scope`, `path`, `effective`, `origin`, `history`, `secret_reference`.
Keep value fragments as strings; do not parse them into a different value tree.
3. Define nested DTOs precisely from the reviewed public API:
- Snapshot: `{present, redacted, yaml}`; preserve false, empty, and null-fragment
distinctions rather than omitting fields.
- Origin: `{layer, locations, operation, inheritance}`. Locations use Stage 10's
DTO. Inheritance records contain `{from, to, reference, locations}` with paths
serialized through Path.String(). Empty locations/inheritance are `[]`.
- History entry: `{before, after, origin, reasons, dependencies}`. Reasons and
canonical pointer dependencies retain owning order; empty collections are `[]`.
- Secret reference: null when absent, otherwise `{name}` containing the owner's
environment-variable name, never its resolved value.
4. Preserve all public inheritance/provenance data; do not blindly marshal Path's
private representation. Keep owning redaction on effective and historical values.
**Checks:** real known effective, historical deletion, root, escaped pointer, and
inherited values; malformed versus unknown outcomes; redacted before/after and
reference-name preservation; YAML snapshot strings and inheritance pointers;
native text equivalence; writer failure. Update explanation CLI documentation.
## Stage 12: integrate the PromptKit configuration scope
**Status:** pending. **Prerequisite:** Stage 11.
```go
func NewPromptKitScope[C any](
load func(context.Context, Selection, C) (*promptkit.Engine, error),
) (ConfigScope[C], error)
```
1. Return scope name `promptkit` and its four available operations. Require a
nonnil callback; invoke it only for a selected operation. Application code
supplies engine sources/options and may derive them from its app view/context.
2. Use Engine.InspectConfiguration to obtain public ConfigurationInspection once.
Expose it through EffectiveWriter, SourceReader, and ExplanationReader; successful
engine acquisition/inspection supplies validation. Capture public source/
diagnostic records with the Stage 8 helper. A nil returned engine is operational
failure; preserve public ErrInvalidConfig and ConfigKit LoadError identities
where supplied through the error chain.
3. Reuse configuration handlers/report DTOs exactly; no duplicate PromptKit config
YAML renderer or shadow schema. Register this scope independently of definition
commands. No prompt/model execution, credential lookup, or model availability
probe is added by bundled inspection handlers.
**Checks:** real public NewEngine with synthetic local/in-memory sources; native
YAML/source/explanation equivalence, invalid engine config and warning delivery,
all display with app first/promptkit second, configuration-only installation,
caller context/selection propagation, and metadata discovery without engine load.
Fixtures do not depend on provider services or sibling repository runtime files.
## Stage 13: implement named prompt/profile inspection
**Status:** pending. **Prerequisite:** Stage 12.
```go
type PromptKitSuite[C any] struct {
NewContext func() C
Selectors []Selector[C]
CheckInput func(Selection, C) error
InspectPrompt func(context.Context, Selection, C, string, string) (Acquired[*promptkit.PromptInspection], error)
InspectProfile func(context.Context, Selection, C, string) (Acquired[*promptkit.ProfileInspection], error)
}
func NewPromptKitSuite[C any](
load func(context.Context, Selection, C) (*promptkit.Engine, error),
) (PromptKitSuite[C], error)
func RegisterPromptKit[C any](app *App, suite PromptKitSuite[C]) error
```
1. Suite factories require a nonnil engine loader and supply both named callbacks.
Call it once for the selected command, capture engine configuration diagnostics,
then use public InspectPrompt or InspectProfile with the caller context. Carry
successful acquisition warnings even if the named lookup fails. Retain error
identities from both acquisition and inspection without invoking execution APIs.
2. Direct suite declarations can supply either callback. Install only corresponding
`promptkit show prompt`/`profile` children. The entire installed promptkit family
stays owned. A suite with neither callback installs no definition commands or
namespace and returns success; it does not require caller feature-toggle
bookkeeping. Configuration scope installation remains independent.
3. Accept ID as one required operand. Prompt alone has singleton `--version`
(default empty). Both accept standard Config/Overlay selection, context-selector
bindings, and `--format text|yaml` (text default), plus help. No `--scope` is
installed for definition commands. Selector target is `promptkit`; empty Scopes
or a list containing promptkit applies, independent of a config scope registry.
Other explicit targets are miswired metadata at registration.
4. Reuse Stage 7's input/context creation and input-check rules. Missing ID is
usage; a supplied blank/unknown/ambiguous selection reaching the owning API is
an operational inspection result, not a new CommandKit ID grammar. Pass version
omission as empty and leave owning selection rules in PromptKit.
5. Prompt YAML keys: `prompt_id`, `prompt_version`, `prompt_hash`,
`default_profile_id`, `inputs`, `output_contract`. Inputs retain declared order
with `{name, required, content_type, description}`; empty input list is `[]`.
Output contract contains `{format, validation_mode, schema_path, repair_attempts}`.
The hash is opaque; do not validate its format or length.
6. Profile YAML keys: `profile_id`, `effective_model_params`, `api_key_required`.
Effective target keys are `backend_id`, `endpoint`, `model`, `temperature`,
`max_tokens`, `top_p`, `timeout_seconds`, `service_tier`, `reasoning_effort`,
`api_key_env`, `extra_params`. Preserve owning zero values; do not infer provider
defaults from zero. Include ExtraParams without redaction, filtering, or
omission: it contains provider settings and needs no upstream enhancement for
this feature. Preserve nested maps/sequences/scalars, including null values.
A nil ExtraParams map is represented as an empty mapping for this report.
7. Text presents the same named fields with labeled sections. Use the shared YAML
encoder for nested provider parameters within that presentation rather than
inventing JSON output or flattening values. Quote free-form scalar labels where
needed. Text presentation is readable; YAML provides the defined machine shape.
**Checks:** real captured definitions and backend/profile inheritance, exact and
omitted prompt version, opaque hash/order, unknown IDs, ambiguity/cancellation
causes, credential-reference/requirement fields, nested ExtraParams in both
formats, nil/empty parameters, independently installed prompt/profile capability,
definition-only registration, and help without engine construction. Assert no
handler enters generation rather than exercising paid/network execution paths.
## Stage 14: substantiate consumer fit and complete documentation
**Status:** pending. **Prerequisite:** Stage 13.
1. Add complete maintained examples using only the public root API and published
upstream packages. Use two ordinary example directories (separate Go main
packages are demonstration consumers, not additional public library packages):
- `examples/simple`: synthetic typed app configuration, one ConfigKit loader
and scope helper, plus a typed custom command with presence/repetition.
- `examples/composed`: app/PromptKit scopes, definition suite, typed campaign
selection, and custom `session restore`. ConfigSuite.NewContext constructs
an invocation-local cache in an unbound field; applicable typed selectors
populate scope contexts sharing that cache. The callback composes app → engine
loading. Definition suite constructs its own invocation-local context.
2. Factor example domain loading into ordinary consumer functions reused by its
execution/inspection demonstration. This is a recommended consumer pattern,
not a new policy governing every application. Keep schemas, campaign/session
meaning, side effects, domain output/status mapping, and executable main with
the example consumer. Use minimal synthetic content, no production workspaces.
3. Verify the examples remove manual standard parsers/dispatchers/report code.
Root generic factories must remain practical for a normal input struct and
composable group. Share internal standard-input helpers across suites rather
than requiring the consumer to rebuild them. Do not solve callback boilerplate
by adding a runtime/DI framework or another public package.
4. Complete substantive GoDoc, `docs/cli.md`, consumer guidance and the two examples,
and `docs/internal/overview.md`. CLI docs own standard syntax/report/status
contracts; GoDoc owns exact symbols/lifecycle; consumer docs own walkthroughs.
Link owning-library schemas; do not copy their configuration references.
5. Update README/development to actual implemented behavior and commands. Check
attribution against incorporated dependencies/material. Do not create empty
integration docs or release tooling. Leave the selected deferred targets visible
in the feature roadmap. Mark implementation status against the included IDs.
6. Audit the complete target: supported operation metadata, independent capability
subsets, default/app/all/order behavior, typed grammar and repeated invocations,
native preservation, all-before-output acquisition, reports/diagnostics/errors,
both public consumer examples, and root/adapter dependency/type boundaries.
Inspect actual exported declarations and imports; avoid a broad architecture
scanner as a substitute for this focused boundary review.
7. Finish with formatted source, the complete offline suite, link/document checks,
and an ordinary diff review. Confirm no sibling replacement/internal import,
deferred-feature placeholder, new public package, or app-domain policy slipped
into the implementation. Record completed stage status and checks, then commit
the completed unit under the local policy. Publishing remains separate work.
**Completion evidence:** each included roadmap feature has observable behavior
and its appropriate checks; examples compile and use the real public dispatcher;
default tests do not require real services/credentials; documents describe only
implemented contracts. The final report identifies the commits, checks, and
deferred capabilities without claiming downstream migrations or release completion.

View File

@@ -1,7 +1,6 @@
# Initial CommandKit feature roadmap
Status: selected initial implementation target; ready for staged implementation
planning.
Status: selected initial implementation target; unimplemented.
Date: 2026-10-07.
@@ -9,9 +8,9 @@ Date: 2026-10-07.
This document defines the selected end state of the initial library: packages,
features, integration contracts, dependencies, and completion evidence. It is
prepared using [the agreed checklist](initial-plan.md). It does not sequence
implementation tasks or describe existing Go code. CommandKit currently has no Go
module or dispatcher implementation.
prepared using [the agreed checklist](initial-plan.md). Implementation steps belong
in [the implementation plan](implementation.md). This document describes the target
state; CommandKit currently has no Go module or dispatcher implementation.
The [architecture policy](../policy/architecture.md) and nine accepted ADRs control
this roadmap. The delivery scope, typed API model, selector/report behavior, and
@@ -122,10 +121,11 @@ containing the new public APIs and a revised capability assessment.
**Owner:** root. **Prerequisites:** registry and invocation contracts below.
Expose an application handle with private state and a small deliberate method
set. Use construction from application identity and registry options,
registration methods for groups/standard suites, and invocation with context,
arguments, and caller streams. Keep runtime dependencies in handler closures or a
separate typed execution dependency parameter; they are not parsed CLI fields.
set. Use construction from application identity and explicit initial command
registrations, registration methods for groups/standard suites, and invocation
with context, arguments, and caller streams. Keep runtime dependencies in handler
closures or a separate typed execution dependency parameter; they are not parsed
CLI fields.
Construction automatically installs help and version from identity/build metadata.
Provider registration assembles the other standard families' supported operations.
@@ -135,9 +135,8 @@ registration. Public named CommandKit types live in the root. Public ConfigKit
and PromptKit types are deliberate integration dependencies where useful.
Use generic package functions/factories with explicit typed binding setters and
composable groups. Go methods cannot declare their own type parameters, so generic
entry points belong in package functions/factories. Exact names/signatures and
captured-view assembly will be specified in the staged plan using this selected
model before implementation begins.
entry points belong in package functions/factories. Typed declarations and captured
views retain their selected ownership and supported public value/error semantics.
**Completion evidence:** an external consumer uses only the root facade to
register and execute commands; public signatures, callbacks, fields, constraints,
@@ -246,9 +245,8 @@ application-owned inspection-context input. It installs the standard input
bindings once, plus explicit typed application selector bindings, and scope
registrations with acquisition callbacks. A simple consumer uses an empty context
type. A complex consumer supplies campaign or other domain selection as typed
fields. Specify exact assembly in the staged plan using generic suite helpers and
small capabilities; do not require a universal untyped service container or handler
access to parser objects.
fields. Generic suite helpers and small capabilities provide this assembly without
a universal untyped service container or handler access to parser objects.
Each scope declares supported operations and obtains a captured view only on an
invocation that needs it. Use small operation-specific interfaces/callbacks:
@@ -568,9 +566,9 @@ Two maintained public examples should substantiate infrastructure reduction:
`session restore` group/handler. Standard paths and report composition remain
CommandKit-owned while campaign/session meaning stays with the application.
Review the declaration and callback burden against the existing handlers. If
these examples require the consumer to recreate dispatch, standard input parsing,
or standard report projection, revisit the API before making the Luna plan.
The declaration and callback burden should demonstrate infrastructure reduction
against the existing handlers. Consumers should be able to use these examples
without recreating dispatch, standard input parsing, or standard report projection.
## Explicit deferrals
@@ -586,7 +584,7 @@ source-release tooling is planned under the release policy when preparing a
releasable candidate. Do not create placeholder publisher commands or a binary
build matrix.
## Completion and implementation-plan handoff
## Completion criteria
The initial target is complete when the included feature IDs have their stated
observable behavior and evidence, and the selected roadmap choices are reflected
@@ -612,11 +610,3 @@ Maintain these substantive documents/artifacts as implementation lands:
change that incorporates it, under the OSS policy.
- Contributor commands and orientation updates once the Go module and validation
commands actually exist; preserve existing policy ownership.
The delivery scope, typed API/outcome model, full profile coverage, and
selector/validation behavior have been selected. Translate included IDs into
ordered tasks with prerequisites, precise API/CLI deliverables, and completion
checks. Specify exact Go declarations, binding helpers, captured-view assembly,
and stream placement in that staged plan using the selected model. Carry deferred
IDs forward without scaffolding them. The plan should make the selected public
contracts concrete enough that the implementation agent need not invent them.