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