Files
commandkit/docs/roadmap/implementation.md

52 KiB
Raw Blame History

Initial CommandKit implementation plan

Status: implementation underway; Stages 1-13 complete and Stage 14 pending.

Date: 2026-10-07.

Purpose and instructions for the implementation agent

Implement the selected end state in the feature roadmap. 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 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 and the agent policy. 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 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: complete. 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. 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.

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.

Completion: added the module and resolved dependency checksums, root identity and outcome declarations, dependency attribution, and current development commands. Verified with go test ./..., go mod verify, and git diff --check -- '*.md' COPYRIGHT.

Stage 2: implement the instance registry and command declarations

Status: complete. Prerequisite: Stage 1.

Implement App, opaque Command, metadata, group composition, and registration in root files such as app.go, command.go, and registry.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.

Completion: added typed command declarations, group composition, root help and version reservations, and atomic per-App registration. Verified with go test ./....

Stage 3: implement typed bindings and input assembly

Status: complete. Prerequisite: Stage 2.

Add root factories in binding.go using these signatures:

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.

Completion evidence: binding behavior tests and go test ./... pass. Input factories remain deferred until callers build a prepared input; help-mode preparation parses supplied values without enforcing omitted required values.

Stage 4: implement the internal Cobra adapter

Status: complete. 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.
  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 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.

Completion evidence: internal adapter and root-to-adapter integration tests cover command routing, local flags, interspersed operands, --, boolean presence and explicit false, repeated raw values, help parsing, group failures, and fresh parse state. go test ./... passes; the root package's direct imports include only the internal adapter and not Cobra or pflag.

Stage 5: implement invocation and diagnostic delivery

Status: complete. 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.

Completion evidence: external-package tests exercise real parsing and dispatch, typed state freshness, usage and input-check causes, domain outcomes, warning-only success, completed diagnostic suppression, checked short writes, and combined operation/delivery failures. go test ./... passes. Report-writing implementations will set ReportIncomplete when introduced with the standard reports.

Stage 6: complete help and version

Status: complete. 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.

Completion evidence: external tests cover root, group, runnable-parent, and exact-path help; help without required inputs; version text/YAML and invalid selectors; and checked report-write failures. Pure build-info tests cover main, dependency, tagged replacement, local replacement, development, and missing module cases. go test ./... passes; README and CLI reference describe the implemented boundary.

Stage 7: implement configuration suites, scopes, and selector preflight

Status: complete. Prerequisite: Stage 6.

Add the following root contracts in config.go/selectors.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.

Completion evidence: added typed configuration suite, scope, selector, view, and request contracts; validates scope IDs, capability metadata, selector bindings, and family collisions. config scopes reports installed capability intersections in stable text/YAML inventories without creating a runtime context or acquiring configuration. Added selector preflight and per-scope context projection for later operation handlers, including explicit empty/false/zero values and clearing inapplicable bound fields. go test ./... passes; the CLI reference describes scope discovery. Configuration operation handlers remain sequenced in Stages 8-11.

Stage 8: integrate ConfigKit and implement validation

Status: complete. Prerequisite: Stage 7.

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.

Completion evidence: added the ConfigKit scope adapter and grouped diagnostic writer. The adapter exposes native effective/source/explanation facets, captures success and LoadError diagnostics, and preserves loader errors. config validate now installs for declared scopes, preflights all requested capabilities, acquires in stable order, and emits success records incrementally in text or YAML. Checks cover ConfigKit defaults, overlay, recorded resolution, absent/empty selections, warning success, load-error identity and single presentation, aggregate stop behavior, and report/diagnostic short writes. go test ./... passes; README, development orientation, and CLI reference describe the implemented boundary.

Stage 9: implement native effective YAML display

Status: complete. 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.

Completion evidence: installed config show when a scope declares it, with YAML as its only format. Single-scope output passes through the native effective writer; aggregate output acquires and checks every view before emitting framed native documents in scope order. Existing markers, leading comments and YAML directives are retained, including explicit document termination where needed between directive-bearing documents. Checks cover native single-scope bytes, aggregate document decoding and values, invalid formats, acquisition failure with empty output, and incomplete delivery after a writer failure. go test ./... passes; README, development orientation, and CLI reference describe the feature.

Stage 10: implement source inventory reports

Status: complete. 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.

Completion evidence: installed config sources for scopes declaring the capability. The command reads the acquired source facet once and renders explicit snake-case YAML DTOs or a quoted text inventory, preserving source order, empty and zero values, synthetic paths, and import locations. Checks use a real ConfigKit filesystem root, import, overlay, and mapping underlay; they verify the YAML field types and import correlation, captured acquisition, empty inventory, text quoting, unsupported aggregate preflight, and partial writer failure. go test ./... passes; README, development orientation, and CLI reference describe the report.

Stage 11: implement path explanation reports

Status: complete. 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.

Completion evidence: installed single-scope config explain with required JSON Pointer --path, text as the native writer output, and explicit YAML DTOs for snapshots, origins, history, inheritance, and secret references. Malformed pointers return usage before context creation or acquisition; unknown paths are operational failures checked before native writing. Checks cover root and escaped paths, inherited and historical values, ordered resolution dependencies, secret redaction and name-only references, exact native text, missing/invalid/unknown paths, and writer failure. go test ./... passes; README, development orientation, and CLI reference describe the feature.

Stage 12: integrate the PromptKit configuration scope

Status: complete. Prerequisite: Stage 11.

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.

Completion evidence: promptkit_scope_test.go exercises a real in-memory PromptKit engine, native reports, aggregate order, lazy discovery, and preserved PromptKit/ConfigKit error identities. go test ./... passes.

Stage 13: implement named prompt/profile inspection

Status: complete. Prerequisite: Stage 12.

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.

Completion evidence: promptkit_test.go exercises real captured prompt and profile definitions, both report formats, inheritance and provider parameters, selection errors, optional capabilities, and help without engine construction. go test ./... passes.

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.