ctl-core 0.5.0

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` feature): binary and mounted names,
  recursive commands, aliases, visibility, arguments, flags, Usage KDL,
  optional audience notes, and shared MiniJinja fragments for skill versions,
  mounted invocation, and command inventories.

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, `app` composes
the lifecycle, and `surface` adds Clap/Usage/Serde/MiniJinja operator-document
extraction. Prefer explicit feature sets when a consumer needs less than the
complete chassis.

`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.

## 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.
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`.