Adopt shared policies and record CommandKit architecture

This commit is contained in:
2026-10-06 16:32:59 +00:00
parent 42d9b361cb
commit 88fb3f19b0
21 changed files with 3116 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
# ADR 0001: Own dispatch through an explicit registry and typed inputs
## Status
Accepted.
## Date
2026-10-06
## Context
Audita, Narratio, Notarius, and Scriptorium repeat configuration inspection,
routing, parsing, help, and error handling. Their command vocabulary and syntax
have diverged. CommandKit aims to provide one implementation of standard
inspection, consistent vocabulary and mechanics, and less application
infrastructure. Application migrations are expected to make breaking changes.
Applications have genuine domain commands, such as Narratio's `session restore`,
that remain outside the standard suite. Merely forwarding standard families from
separate application dispatchers would leave custom-command mechanics and root
help dependent on each application's implementation.
Input handling also needs a common boundary. Raw argv handlers or string-based
flag retrieval leave repeated parsing and input assembly with applications.
Presence matters for configuration overrides: omitted values differ from
explicitly supplied empty strings, false, and zero.
## Decision
### Ownership and namespace structure
CommandKit owns the dispatcher, shared parsing and presence tracking, help,
syntax diagnostics, and standard-family handlers and outcomes. Applications own
custom command names, domain meaning, validation, effects, artifacts, and
operational outcomes.
Use namespace/resource-first syntax for the standard suite. The `config` family
and an installed `promptkit` family belong to CommandKit. PromptKit definition
inspection uses the single `promptkit` namespace; root `prompt` and `profile`
names remain available to applications. This provides a clear family ownership
boundary without spreading a namespace across root verbs.
Applications register commands outside reserved families. They cannot replace
standard handlers or add private operations underneath a reserved family.
Standard-family context selectors and typed overrides use the defined input
extension mechanism rather than introducing application-owned operations there.
Applications retain `main`, signal handling, context creation, stream selection,
runtime construction, and final process exit. CommandKit returns invocation
outcomes and does not terminate the process. Configuration schemas, acquisition,
domain resolution and validation, and provider construction remain application
integration responsibilities. ConfigKit and PromptKit retain their own
configuration, provenance, safe inspection, and native rendering semantics.
### Explicit registry and capability installation
Each explicitly constructed application instance owns a registry. Applications
register individual commands and composable groups. There is no package-global
registry or import-time `init` registration.
Definitions provide paths, descriptions, flags and defaults, repetition and
presence metadata, operand requirements, and execution handlers. One definition
supplies routing, parsing, and help. Duplicate paths and attempts to replace
reserved commands return registration errors; order does not select a winner.
Help and version are installed automatically using supplied application identity
and build metadata. Configuration and PromptKit families are installed when the
application supplies their providers. Supplying a capability installs its complete
standard family, not a collection requiring manual operation-by-operation wiring.
Registering providers does not execute them. Construction may diagnose missing
or invalid wiring without reading configuration, resolving credentials, or
building engines. Invalid configuration is an invocation-time failure. Command
declarations, help, and version remain available independently of application
initialization. The selected handler acquires dependencies when it needs them.
Do not install placeholder commands. Omitting the configuration provider does
not make a partial integration a complete core adopter. PromptKit is optional;
its selected family is installed as a complete capability. The CLI roadmap owns
the proposed command inventory and the remaining choices for that standard.
### Typed inputs with explicit bindings
Use application-defined input structs for custom commands and CommandKit-defined
inputs for standard handlers. Explicit registration binds flags and operands to
their input fields. The declarations also supply parsing and help metadata.
Handlers do not retrieve primitive values by flag name or parse the complete argv.
Use ordinary typed fields where omission has no separate meaning. Where it does,
keep supplied/not-supplied state beside the value in a presence-aware field.
Defaults do not mark input as explicitly supplied. Preserve explicit empty,
false, and zero values.
Create fresh input storage per invocation. Registered commands must not capture
one mutable input permanently or retain prior values, collections, or presence.
This decision addresses lifetime without establishing concurrent dispatch.
Supply context and stream access separately from CLI inputs. Input declarations
and binding setup must not construct execution dependencies. Applications perform
domain validation and may interpret parsed domain values or supply a custom value
parser through the binding system.
The public factory, binding, registration, and handler signatures and presence
type names remain API design choices. No illustrative declaration establishes
an exact public API.
### Outcomes and domain behavior
Resolve the command and handle help/version before loading application state.
For execution, allocate fresh inputs, apply shared parsing and operand checks,
then invoke the typed handler. Return its invocation outcome to the application.
Shared usage errors and standard-family outcomes belong to CommandKit. Custom
handlers must communicate application-specific outcomes without silent generic
reclassification. For example, Scriptorium output validation and Notarius artifact
outcomes remain application concerns. Do not classify domain errors by matching
message strings or print usage unconditionally for runtime failures.
Common mechanics do not require a common success-report format for custom
commands. The application owns its artifact and operational output contracts.
Exact result/error interfaces and unresolved grammar choices are separate design
work, not approvals implied by this decision.
## Alternatives considered
### Dispatcher and ownership
| Alternative | Analysis |
| --- | --- |
| Application-owned dispatcher forwarding standard families | Small integration surface and convenient migration, but custom syntax, root help, and errors still need cross-dispatcher coordination; removes less infrastructure. |
| Standard definitions mounted into an application's framework | Reuses routing infrastructure, but application configuration can preserve inconsistent behavior outside mounted families. Internal framework reuse remains compatible with this decision. |
| Shared metadata/help with separate routing | Unifies discovery but adds synchronization among metadata, routing, and parsing. One dispatcher avoids it. |
| Both full dispatch and embeddable family handlers | Serves established external CLI architectures but adds integration surfaces and maintenance. Add embedding only for a concrete need. |
| Raw-argument handlers for custom commands | Eases migration but leaves parsing and help responsibilities with applications. A future transition adapter needs explicit limitations and is not full adoption. |
| Universal application runtime framework | Could absorb startup and operational services, but expands beyond the demonstrated CLI boundary and domain ownership. |
| Verb-first standard paths | Groups actions but distributes namespace ownership among individual routes. Namespace/resource-first paths keep family ownership clear. |
| Reserve root `prompt` and `profile` | Provides short resource paths but consumes application namespaces. A single `promptkit` family gives the library one ownership boundary. |
### Registration
| Alternative | Analysis |
| --- | --- |
| Declarative tree passed to construction | Makes assembly visible together; larger applications still need group composition. May become a convenience over the same registry. |
| Constructor options for every command/group | Compact setup, but excessive options can obscure commands versus application settings. Option conventions remain API design work. |
| Global registry populated by `init` | Less entry-point code, but hidden import effects, shared mutable state, and unclear ownership. Explicit instances avoid those costs. |
### Handler inputs
| Alternative | Analysis |
| --- | --- |
| Parsed-value accessor object | Uniform handlers, but string-based lookup, runtime type errors, and repeated assembly remain. |
| Individual typed parameters | Clear for small commands; grows into long signatures and adapter machinery. Typed structs scale more naturally. |
| Reflection and struct tags | Compact declarations, but adds a tag language, field-discovery rules, and runtime declaration validation. Explicit bindings keep declarations visible. |
| Typed command objects | Can organize complex handlers; must separate fresh input from dependency and execution lifetimes. Applications may use objects without making them mandatory. |
| Generated bindings/adapters | Can combine compact authoring and typing, but adds tooling and generated-file maintenance without demonstrated need. |
| Raw arguments | Simple routing integration, but returns common parsing and help to applications. |
## Consequences
One command system removes repeated routing, parser setup, help assembly, and
shared error plumbing as well as standard inspection handlers. Domain handlers
and their declarations remain recognizable application code.
The public registration surface is larger than family delegation, and explicit
bindings require connecting declarations to the correct fields. Those costs
support consistent mechanics without requiring reflection or generated code.
Shared parsing defects can affect every adopter. Consumer and backend integration
evidence must protect selected semantics, including ownership conflicts, help
without configuration, presence, fresh input, cancellation, and domain outcomes.
Intentional migration changes require coordinated application documentation and
subprocess-consumer updates.
The [architecture policy](../policy/architecture.md) owns normative boundaries.
The [dispatcher roadmap](../roadmap/02-dispatch.md) owns implementation status and
pending API design. The [CLI roadmap](../roadmap/01-cli-plan.md) retains command
proposals and their approval status. [ADR 0002](0002-cobra-backend.md) selects the
backend, and [ADR 0003](0003-public-facade.md) defines its public/internal boundary.

View File

@@ -0,0 +1,95 @@
# ADR 0002: Use Cobra and pflag as the initial internal CLI backend
## Status
Accepted.
## Date
2026-10-06
## Context
CommandKit's selected dispatcher model needs nested routing, flags and operands,
generated help, usage diagnostics, and invocation execution. Reimplementing this
generic infrastructure would add work beyond the suite-specific inspection and
consistency behavior that motivates the library.
Cobra and urfave/cli v3 were the finalists. The latter's destination bindings and
duplicate controls offered a close fit for the explicit-input model. The author
strongly preferred Cobra because of its broader adoption. Familiarity and the
availability of contributors who know the framework are useful maintenance
considerations across several related applications.
The selection was based on documentation and API review. Ecosystem adoption is
the selection rationale, not evidence that every backend default is an appropriate
CommandKit contract.
## Decision
Use Cobra with pflag as the sole initial CLI backend. CommandKit translates its
registrations into backend command trees, flag bindings, and execution callbacks.
Reuse their routing, option parsing, help, context, and stream mechanisms and add
adaptation where selected CommandKit semantics require it.
CommandKit retains its own public registration API, typed inputs, standard
families, and observable contracts. Applications do not construct Cobra commands
or use pflag getters in handlers. [ADR 0003](0003-public-facade.md) isolates the
backend behind an internal adapter and root-owned public contracts.
Applications retain process lifecycle and domain execution. ConfigKit and
PromptKit retain configuration and inspection semantics. Framework selection
does not transfer those responsibilities to Cobra or CommandKit.
Build fresh backend command/flag state and input storage per invocation. A fresh
command tree constructed from registered definitions is the initial approach.
Tree construction must not load configuration or create application runtime.
Implement one concrete backend. There is no initial public backend selector or
pluggable framework. A future replacement can change internal implementation
while preserving the public API and selected behavioral contracts.
## Alternatives considered
| Alternative | Advantages | Reason not selected |
| --- | --- | --- |
| urfave/cli v3 | Destination bindings for flags and named operands, presence queries, and duplicate controls fit the chosen input model closely. | Credible alternative; Cobra's broader adoption is the preferred maintenance tradeoff. |
| pflag alone | Established parsing with a smaller integration surface. | Leaves more routing, help integration, and command infrastructure to implement. |
| Kong | Compact typed-struct and tag-driven declarations. | Its main declaration model differs from the explicit-binding API; adaptation reduces its benefit. |
| Standard `flag` alone | Familiar variable binding and custom value parsing. | First-operand stopping requires adaptation for the intended syntax, with more custom command infrastructure. |
| Expose Cobra as the public API | Less adapter code and the full framework surface. | Couples consumers to backend structures and framework-specific configuration. |
| Interchangeable backends immediately | Future integration flexibility. | Adds abstraction and behavior surfaces without a current requirement. |
## Consequences
The framework supplies established generic CLI machinery. CommandKit still owns
registration conflicts, reserved families, typed bindings and presence, standard
help and diagnostics, provider integration, and selected reports and outcomes.
The adapter must accommodate explicit empty/false/zero values, fresh state,
selected repetition and operand rules, and help without configuration. Backend
features do not expand the standard automatically: for example, completion
commands remain deferred unless deliberately selected.
Backend error handling must preserve application outcomes, avoid duplicate
diagnostics, and return control to the application's entry point. Native YAML
inspection and multi-document scope display remain integration work using the
owning libraries. Choosing a framework introduces no JSON report requirement.
Exact dependency versions and attribution are selected when dependencies are
incorporated. Meaningful integration evidence must exercise the real adapter
against selected contracts. A dependency update or replacement can change CLI
behavior even with unchanged Go signatures and is subject to compatibility policy.
The [architecture policy](../policy/architecture.md) owns normative boundaries;
the [backend roadmap](../roadmap/03-backend.md) owns implementation tasks and status.
[ADR 0001](0001-command-dispatch-and-inputs.md) records the dispatcher and input
model. Remaining command and grammar proposals belong to the CLI roadmap.
Reviewed primary references from the selection:
- [Cobra API](https://pkg.go.dev/github.com/spf13/cobra).
- [Cobra command and help guide](https://github.com/spf13/cobra/blob/main/site/content/user_guide.md).
- [Cobra flag bindings](https://cobra.dev/docs/how-to-guides/working-with-flags/).
- [pflag syntax and extension points](https://github.com/spf13/pflag).
- [urfave/cli v3 API](https://pkg.go.dev/github.com/urfave/cli/v3).

View File

@@ -0,0 +1,109 @@
# ADR 0003: Own public contracts through a root facade and internal CLI adapter
## Status
Accepted.
## Date
2026-10-06
## Context
CommandKit needs room to change implementation while consumers retain stable
registration, input, and invocation contracts. Exposing backend assembly or
extension structures would let applications depend on details that maintainers
may later replace.
ConfigKit, PromptKit, and ReleaseKit demonstrate facades that own public types and
delegate through private state and explicit conversion. ConfigKit also translates
upstream YAML parser structures into an independent internal document model.
ReleaseKit has a second facade for a distinct lightweight runtime responsibility;
CommandKit has no corresponding requirement for another public package initially.
The Cobra selection is compatible with dependency isolation. Public API
independence needs a genuine contract boundary, and keeping backend structures
out of shared internal representations also makes future replacement easier.
## Decision
Use the module root `commandkit` as the sole initial supported public facade.
Define CommandKit's public named types there and expose only selected operations,
extension contracts, and documented methods. Handles keep implementation state in
private fields.
Implementation packages belong under Go's `internal/` boundary. Public code
forwards inward with explicit wrappers and conversions; internal production
packages do not import the root. Shared internal contracts live inside that
boundary. Additional public packages require a deliberate decision based on a
durable consumer responsibility.
Do not alias public types to internal or backend types, anonymously embed those
types to promote methods, or expose implementation bridge methods. Exported
fields, signatures, variables, callbacks, interface methods, constraints, and
method sets must not reveal internal or Cobra/pflag structures. Loosely typed
values and returned errors must not provide indirect backend-object access.
Standard contracts such as `context.Context` and `io.Writer` remain appropriate.
ConfigKit or PromptKit public types may be selected as deliberate integration
dependencies. Application input types remain application-owned. Go declarations
and GoDoc own exact exported contracts.
Confine Cobra/pflag types and operations to an internal adapter. Registry logic,
standard handlers, and shared definitions use CommandKit-owned representations.
The adapter translates paths, descriptions, bindings, presence, callbacks, and
selected help/outcome semantics into backend machinery. Do not reproduce Cobra's
complete API through renamed wrappers.
Typed input factories and handlers can cross the facade through private callbacks
without internal packages importing public declarations. Preserve fresh input and
backend state per invocation. Introduce narrow internal interfaces where actual
engine/adapter interaction justifies them; exact package names and splits remain
implementation choices.
Use one concrete backend initially. There is no public backend interface, plugin
registry, or requirement for a second implementation. Replacement is a future
maintainer change.
Translate backend-owned syntax failures into CommandKit diagnostics and outcomes.
Preserve useful public identity for application handler errors and cancellation
according to the invocation contract. Explicit conversions must preserve value
ownership, callback lifetime, presence, and supported error semantics.
## Alternatives considered
| Alternative | Analysis |
| --- | --- |
| Keep all machinery in the public root | Less forwarding, but harder consumer navigation and easier accidental public expansion. |
| Alias or embed implementation types | Less conversion code, but couples public type identity, fields, or method sets to internal choices. |
| Hide Cobra only behind the public facade | Hides types from applications but allows backend structures to spread through internal logic, complicating replacement. |
| Mirror Cobra's API with wrappers | Removes dependency names while preserving its conceptual coupling and compatibility surface. |
| Public packages for registry, bindings, backend, and other mechanisms | Adds supported imports and assembly contracts without a distinct consumer responsibility. |
| Selectable/pluggable backends immediately | Adds selection and extension machinery before a concrete need exists. |
## Consequences
Consumers depend on deliberate root contracts while internal algorithms,
collaborators, and package boundaries can evolve. Useful application registration
capabilities still need a proportionate API; minimizing accidental exports must
not recreate repetitive consumer infrastructure.
Explicit forwarding and conversion require maintenance. Type identity, ownership,
method sets, callback lifetime, and error semantics must be protected by evidence,
not inferred from package relocation.
Compiled public API checks protect aliases, fields, signatures, constraints, and
promoted methods. Import checks keep the backend in its adapter and internal
production code independent of root. External-package tests protect consumer
semantics through the real adapter. ConfigKit and ReleaseKit provide precedents
for these checks without requiring their full validation workflows.
Backend replacement can preserve the public Go API but must also preserve selected
CLI behavior or follow the release policy for intentional incompatibilities.
Backend defaults do not independently establish new CommandKit contracts.
The [architecture policy](../policy/architecture.md) owns normative boundaries.
The [facade roadmap](../roadmap/04-facade.md) owns implementation status, package
layout illustrations, and pending work. [ADR 0001](0001-command-dispatch-and-inputs.md)
records the dispatcher/input model, and [ADR 0002](0002-cobra-backend.md) selects
the initial backend. The CLI roadmap retains individual command approval status.