diff --git a/README.md b/README.md index 34005bb..7704cc7 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/docs/development.md b/docs/development.md index d74a8ca..d3ebb3c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -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. diff --git a/docs/roadmap/01-cli-plan.md b/docs/roadmap/01-cli-plan.md index 0c10a67..5f33170 100644 --- a/docs/roadmap/01-cli-plan.md +++ b/docs/roadmap/01-cli-plan.md @@ -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 diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md new file mode 100644 index 0000000..9f51ebf --- /dev/null +++ b/docs/roadmap/implementation.md @@ -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. diff --git a/docs/roadmap/initial.md b/docs/roadmap/initial.md index 974eb14..c78b75e 100644 --- a/docs/roadmap/initial.md +++ b/docs/roadmap/initial.md @@ -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.