ctl-core 0.6.1

Shared clap chassis for the *ctl CLIs
Documentation
# ctl-core

Shared Rust CLI chassis and presentation kernel for the `*ctl` CLIs
(`verctl`, `qctl`, `forkctl`) and `state-sync`. **Not a command.** Import as
`ctl_core`.

This repo's queue is [`tasks.yaml`](tasks.yaml) (`CTC-###`).

## What this crate owns

- `-h` / `--help` and `-V` / `--version` (never `disable_help_flag`)
- Shared flags: `-c`/`--color`, `-f`/`--format`, `-n`/`--dry-run`/`--preview`,
  `-q`/`--quiet`, plus `--foo` / `--no-foo` negation helpers
- `App`: pre-parse hooks, Usage, help, parsing, execution, streams, errors, and
  exit codes
- `Document` and the fluent semantic primitives used by help and command output
- Pretty / JSON / colorless `View` over one serializable domain model
- The only terminal theme, width detection, wrapping, and table configuration
- Wire envelope: `Envelope` / `ErrorBody` / `SCHEMA_VERSION`
- Mise Usage spec (`usage` feature): `--usage-spec[=BIN]`, `mount_line`, and
  `App::usage_spec` enrichment, so consumers run `mise run q status` with no
  `--`. Forkctl completion remains a composable `App::before_parse` hook.
- Clap-derived `Surface` (`surface-model` feature): binary and mounted names,
  recursive commands, aliases, visibility, arguments, flags, Usage KDL, and
  optional audience notes. `surface-serde` adds serialization without a template
  engine; `surface-templates` adds shared MiniJinja fragments; `surface` remains
  the compatibility aggregate enabling all three.

Domain verbs and result types stay in each CLI. Domain handlers return data and
never print, inspect the terminal, choose a view, or construct engine tables.
Comfy Table, Anstyle, and Anstream are private ctl-core implementation details;
none of their types may cross the public semantic boundary.

Boolean pairs that are domain-specific (`--pr` / `--no-pr`) stay in the CLI.
Use clap `overrides_with` both ways and `warn_opposites` so the last flag wins
without silence.

## Cargo features

Features are tree-shaking. `document` carries no terminal engine. `render` adds
the private engine, `view` adds JSON, `help` adds Clap help, and `app` composes
the lifecycle. `surface-model` adds only Clap/Usage operator metadata;
`surface-serde` adds ctl-core's direct Serde edge without a template engine;
`surface-templates` adds MiniJinja; `surface` preserves the full legacy aggregate.
`surface-model` still inherits Serde from `usage-lib`, whose KDL model requires
it; that is not ctl-core serialization. Prefer the narrowest feature set a
consumer needs. Never add serialization or rendering behavior to a lower tier.

`features = ["usage"]` does not pull `help`. Do not add `help` only to make the
prelude compile.

Crate docs (`src/lib.rs` + `document_features`) are authoritative for the
feature graph. The GitHub README is not rustdoc.

## Library control

ctl-core supplies sane defaults, never hidden policy. Every automatic behavior
that can change rendered output has a public library override and an explicit
disable path. Environment-backed defaults expose their canonical name and let a
consumer replace the ordered name list with its own aliases or disable lookup.
An explicit render width is exact and bypasses automatic-width buffering.

The automatic terminal-width buffer defaults to two columns and reads
`CTL_CORE_COLUMN_BUFFER`; the automatic minimum defaults to 20. When no width is
detected, output lays out to a fallback width of 80, and `fallback_width(None)`
disables it. `RenderOptions`, `View`, and `App` expose the buffer, ordered
environment names, minimum, and fallback.
Buffer zero disables subtraction; an empty environment slice disables lookup.
Explicit widths remain exact. Public option-aware help entry points keep direct
help callers under the same policy.

## View contract

1. **Models first.** Each command returns a serializable result; the view does
   not own domain data.
2. **One model, every mode.** `Present` maps that model to a semantic `Document`.
   JSON serializes the model directly. Colorless renders the same document.
3. **Semantic composition only.** Consumers use `Fields`, `Table`, `Section`,
   `Notice`, `Text`, and `Document::verbatim`; they never choose borders, ANSI,
   width, or streams. Verbatim is for preformatted Markdown or protocol text
   whose line boundaries must survive rendering. It removes terminal controls,
   bidi controls, and Unicode line separators; preserves other Unicode format
   characters; then normalizes trailing newlines to the document.
4. **Quiet is human-success only.** It never hides JSON or errors.
5. **Help is a document.** Clap remains the grammar, and help uses the same
   renderer as command output. Usage lines wrap without splitting tokens.

See [`docs/presentation.md`](docs/presentation.md). `kv` / `grid`, the `go`
wrapper, and string-render traits are migration surfaces, not the destination.

## Strings

Multiline Rust is `indoc!` / `formatdoc!` / `writedoc!` / `printdoc!` /
`eprintdoc!` / `concatdoc!` (re-exported from this crate). **No `concat!`.**
**No escaped `\n` in a document.** Leave a raw `\n` only when that *is* the
test.

## Comments

Doc comments carry the why, on the item. Inline `//` prose inside a function
body is litter.

## Operator documents

Consumer skills and installed instructions are committed rendered artifacts.
Their source templates import `ctl/version.md.jinja`,
`ctl/invocation.md.jinja`, or `ctl/commands.md.jinja` and retain domain prose.
Tests build `Surface` from the consumer's Clap type and byte-compare the render
with the committed file. Do not restore token scans, copied command lists, or a
second no-`--` rule.

## Consumers

Migrate one consumer at a time by pinning a released ctl-core version, never a
path dependency. Verctl goes first, then forkctl retains its protocol while
deleting its local view/help/layout, then qctl turns direct printing into typed
results. The migration is tracked as `CTC-010` here and `QCTL-008` in qctl.

## Declared input (queued)

`CTC-008` moves the shared "read, parse shape, validate once, complain in the
repo's own words" layer here so each CLI stops owning a drifting copy. Do that
row before any CLI-side "move my schema.rs" follow-up.

## Release

Human writes `.changeset/*.md` on the same PR that ships the behaviour. Never
hand-edit versions or CHANGELOG. Declarations live in [`.ctl/ver.yaml`](.ctl/ver.yaml).
`prepare` runs `cargo update --workspace` and stages `Cargo.lock` because CI
packages with `--locked`. Prove the lane by shipping through it (`CTC-009`).

## Checks

```sh
mise run verify
```

`verify` is format, clippy, nextest, doc-tests, cargo-deny licenses/bans/sources, and cargo-machete.
Locally, `.miserc.toml` adds the `mbx` env (`mise.mbx.toml`), which routes
Cargo through mr-boxington so worktrees share one build store. CI does not
load it.
Do not `&&` those in a new task; `depends` is the mise form. Those cargo
invocations share `target/`; the package-cache lock serializes them. Do not
invent extra `CARGO_TARGET_DIR` trees to hide that. Advisories (and yanked
crates) are `mise run deny:advisories` on CI only, so a pre-push `verify`
does not need the network. An unfixable RUSTSEC or yank is listed in
`deny.toml` `[advisories].ignore` with a reason, not by weakening `yanked`
or `unmaintained`.

## Git

Conventional commits. lefthook. No `--no-verify`. Branch `type/number-desc`.
Always open a PR — never push to `main`.