47 KiB
Initial CommandKit implementation plan
Status: implementation underway; Stages 1-4 complete and Stages 5-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.
- Create
go.modforgitea.maximumdirect.net/eric/CommandKit, withgo 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, andgo.yaml.in/yaml/v4 v4.0.0-rc.6. Resolve the actual graph and maintaingo.sum. Keep required transitive dependencies; do not use adjacent checkouts or committed local replacements. - 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.
- Add
doc.go,identity.go, andoutcome.gowith 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
- Commands have one path component as
Name; composition supplies the prefix.Groupcreates a non-runnable parent.WithChildrenreturns 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. - 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.
- 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. - 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.
- 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
configandpromptkitownership 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. NewInput == nilmeans 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.CheckInputis 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]
- 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. - 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.
- Use
strconvscalar parsers,time.ParseDuration, or caller domain parsers; consumers can pass standard parsers directly. BoolFlag uses presence=true and explicit=true|falseconversion throughstrconv.ParseBool. ValueFlag is available for bool-like domain values needing an ordinary value argument. - 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.
- 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.
- 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
-hhelp 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.
- Create
internal/cobrawith 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. - 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. - 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.
- 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. - 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.
- Install local
--help/-hmetadata;--help=falseis 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. - 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: 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.
- 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.
- 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.
- 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.ErrShortWriteand 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. - 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.
- On diagnostic write failure, stop delivery, set DeliveryIncomplete, and retain
that error in DeliveryError; use
errors.Joinwhen 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. - Keep the operation Cause when diagnostics subsequently fail. Do not substitute
cancellation with a generic wrapper that loses
context.Canceledor 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. - 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.
- Complete automatically installed
helpandversion. Root bare invocation,help [COMMAND…], root/selected--help/-h, and non-runnable groups render metadata help. Resolvehelpoperands as an exact registered path. Required execution input is not needed. Unknown path/flag is usage failure. - 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.
versionaccepts only--scope app|commandkit(default app) and--format text|yaml(default text), plus help. It has no operands. Empty application Version rendersdev; empty Revision remains empty. Text isNAME VERSION\n, adding(REVISION)before the newline when provided.- 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 isCommandKit; do not turn a replacement filesystem path into its display name. A missing/versionless/local replacement isdev, 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. - 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:
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
- 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.
- Registration checks unique nonempty scope IDs, reserves
allas 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. - Install
config scopesimmediately. 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. - Use a private standard input type containing Selection, scope, format, path
presence, and C.
--config/--overlayare singleton presence-aware strings on acquisition-bearing commands.--scopedefaults app;--formatuses each command's selected default. Scopes discovery accepts format/help only. - 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.
- 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.
- 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.
allis 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.config scopes --format yamluses{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.
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
- 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.
- Capture result Diagnostics/Sources on success. On load error, use
errors.Asfor public*configkit.LoadErrorto 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. - 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.WriteDiagnosticsonce 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. - 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.
- Text success record:
SCOPE: valid\n. YAML is onescopesblock sequence of{name, valid: true}records, appended as each completes. Emit the initialscopes: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.
- 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. - 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.
- 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: NAMElabel 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. - 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.
- Implement single-scope
config sources, default text with yaml alternative. Read the captured SourceReader once. Never reload sources for this report. - Use private explicit DTOs and snake-case YAML fields. Root shape is
{scope, sources}. Each source hasid,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[]. - 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.
- Implement single-scope
config explain --path POINTER. Presence is required; explicitly empty is the root. Parse with publicconfigkit.ParsePathbefore 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. - 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. - 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.
- Snapshot:
- 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.
func NewPromptKitScope[C any](
load func(context.Context, Selection, C) (*promptkit.Engine, error),
) (ConfigScope[C], error)
- Return scope name
promptkitand 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. - 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.
- 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.
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
- 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.
- Direct suite declarations can supply either callback. Install only corresponding
promptkit show prompt/profilechildren. 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. - 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--scopeis installed for definition commands. Selector target ispromptkit; empty Scopes or a list containing promptkit applies, independent of a config scope registry. Other explicit targets are miswired metadata at registration. - 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.
- 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. - Profile YAML keys:
profile_id,effective_model_params,api_key_required. Effective target keys arebackend_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. - 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.
- 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 customsession 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.
- 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.
- 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.
- Complete substantive GoDoc,
docs/cli.md, consumer guidance and the two examples, anddocs/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. - 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.
- 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.
- 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.