ctl-core 0.6.1

Shared clap chassis for the *ctl CLIs
Documentation
# Unified ctl presentation

## Decision

Typed domain data is the source of every command presentation. ctl-core owns
the Rust CLI chassis and rendering framework once; consumer CLIs own domain
models and domain-specific information hierarchy.

The public boundary is semantic:

```text
Clap graph ──→ App ──→ domain handler ──→ Result model
                                   ┌──────────┴──────────┐
                                   │                     │
                             serde JSON             Present
                                                     Document
                                             pretty or colorless
```

## Ownership

ctl-core owns:

- Usage mounts, optional consumer enrichment through `App::usage_spec`,
  pre-parse short-circuits, help, Clap parsing, exit codes, and stream selection;
- `OutputArgs`, `FormatArgs`, `FormatLong`, `ColorLong`, `ColorMode`, `OutputFormat`, and quiet behavior;
- `Document`, `Text`, `Fields`, `Table`, `Section`, `Notice`, `Rule`, and
  unwrapped verbatim blocks that remove terminal controls, bidi controls, and
  Unicode line separators while preserving other Unicode formatting;
- semantic roles and the one visual theme;
- width detection, wrapping (including Usage lines), table layout, ANSI policy,
  stdout, and stderr;
- JSON emission and generic error envelopes.

A consumer owns:

- Clap-derived domain verbs and parameters;
- serializable request, result, notice, and domain error types;
- the adapter that maps one result to semantic document nodes;
- operator prose in its skill and installed instructions.

A domain handler never prints, constructs a terminal table, reads terminal
width, or selects pretty versus JSON.

## Engine boundary

Comfy Table is a private Rust table and wrapping engine. Anstyle and Anstream
are private style and stream engines. Their types cannot appear in ctl-core's
public semantic API or in a consumer crate.

This boundary is deliberate. Comfy Table is mature, width-aware, and tested.
The broad Rust ports of Python Rich remain young and API-unstable. The facade
lets ctl-core replace an engine later without changing domain models or
consumer render adapters.

Do not implement Unicode width, ANSI stripping, table sizing, or terminal
capability detection from scratch.

## Automatic width policy

An automatically detected TTY or `COLUMNS` width reserves two columns before
wrapping, stacking, help, fields, and table layout. This default protects hosts
that indent tool output or whose frame consumes the rightmost column. The configured
floor is 20 when the detected width permits it. A narrower terminal caps that
floor, so ctl-core never claims columns the terminal does not have.

When neither a terminal nor `COLUMNS` gives a width, as in agent and CI
captures where every stream is piped, ctl-core lays out to a fallback width of
80. `fallback_width(Some(n))` changes it and `fallback_width(None)` renders
tables at their natural width. The fallback is separate from the minimum,
which applies only to a detected width.

`CTL_CORE_COLUMN_BUFFER` replaces the default buffer. Library consumers have
full control through `RenderOptions`, `View`, and `App`:
`automatic_width_buffer(n)` overrides the environment, including zero to disable
buffering; `automatic_width_buffer_envs(&[...])` replaces the ordered names so a
consumer can add aliases or disable lookup with an empty slice; and
`minimum_automatic_width(n)` replaces the default preferred floor of 20, still
capped by the detected terminal width; and `fallback_width(..)` sets or disables
the undetected-width fallback. Option-aware help
entry points accept the same `RenderOptions`. An explicit `width(n)` is always
exact and ignores every automatic-width setting.

## Default look

Records render borderless with keys right-aligned (`RecordStyle::KeysRight`).
Lists keep the full grid (`ListStyle::Grid`) with rows together
(`RowSeparation::None`). JSON is indented by two spaces (`JsonLayout::Pretty`).
Identifiers render bold in the terminal foreground (`Role::Id`). Each is an
option on `RenderOptions`, `View`, and `App` (`styles`, `json_layout`), so a
consumer that needs the box or compact JSON sets it.

## Output law

One serializable model feeds all modes:

1. JSON serializes the model directly, writes one newline to stdout, and never
   contains ANSI.
2. Pretty asks `Present` for a semantic document and renders it with color
   policy `auto` or `always`.
3. Colorless renders the same document with color policy `never`; it does not
   maintain a second layout.
4. Human errors use stderr. JSON failures use stdout so machine consumers read
   one stream.
5. Quiet suppresses successful human output only. It never hides errors or
   JSON.

## Help law

Clap derive types are the command grammar. ctl-core extracts help from that
graph and renders it through the same document renderer. No consumer maintains
a second help parameter model or a local terminal-width implementation.

## Fluent boundary

Consumer presentation code composes meaning:

```rust,ignore
Document::new()
    .heading("status")
    .fields(
        Fields::new()
            .row("active", result.active.as_deref().unwrap_or("none"))
            .row("queued", result.queued.to_string()),
    )
    .table(
        Table::new(["id", "title"])
            .token_column(0)
            .row([result.id.as_str(), result.title.as_str()]),
    )
    .notice(Notice::new(NoticeLevel::Warning, "one stale row"))
```

It does not choose borders, spacing, ANSI styles, width, or streams.

## Operator surface

Clap is also the source for committed operator documents. `Surface` extracts
binary and mounted names, package metadata, recursive commands, aliases,
visibility, locally declared argument and flag metadata, and mounted Usage KDL.
That metadata and its validation methods require only `surface-model`;
`surface-serde` adds serialization without a template engine, and
`surface-templates` adds MiniJinja. `surface-model` still inherits Serde from
`usage-lib`, whose KDL model requires it; that is not ctl-core serialization.
The existing `surface` feature remains the compatibility aggregate.
Ancestor globals stay normalized on their declaring command and appear
separately as each descendant's `inherited_arguments`. Templates can render the
effective flag set without losing declaration provenance. It records hidden
commands for verification while the shared command-inventory fragment renders
visible commands only. `Surface::note` carries optional skill- or
instruction-specific prose when Clap's `about` addresses another audience.

ctl-core ships three MiniJinja fragments:

- `ctl/version.md.jinja` renders a skill frontmatter version line;
- `ctl/invocation.md.jinja` renders mounted examples and the no-`--` rule;
- `ctl/commands.md.jinja` renders the visible top-level command inventory.

A consumer template imports those fragments and keeps its own domain prose.
Tests render with the consumer's Clap type and byte-compare the result with the
committed skill or installed instructions. Adding or renaming a Clap command
therefore changes `Surface` and fails the committed-render test until the
operator document moves with it. No token scan or copied command list remains.

## Migration order

1. Publish the ctl-core presentation kernel.
2. Migrate verctl, which already has typed report models and uses ctl-core
   `View`.
3. Migrate forkctl, retaining its protocol types while deleting local help,
   view, layout, and terminal dependencies.
4. Migrate qctl handlers from direct printing to typed results, then add shared
   pretty, colorless, and JSON views.
5. Pin the released `surface` feature in each consumer, render its skill and
   instructions from shared fragments plus domain prose, and delete copied verb
   scans and invocation rules.

Each consumer pins a released ctl-core version. No path dependencies connect
repositories.