spec: 0101 normalize CLI usage-message casing (boss-signed)

Single house style for every hand-rolled usage line: 'Usage: aura <verb> ...'
(clap's capital prefix + program name), 10 literal edits + the one
casing-sensitive test pin in lockstep. Refusal diagnostics, the conforming
run_args_from site, clap output, and the fieldtests corpus stay byte-identical.
Grounding-check PASS is the autonomous signature; scope-fork decisions are
recorded on the issue.

refs #179
This commit is contained in:
2026-07-02 12:31:48 +02:00
parent 8814e29597
commit 7a386dc9bb
@@ -0,0 +1,210 @@
# Normalize CLI usage-message casing — Design Spec
**Date:** 2026-07-02
**Status:** Draft — awaiting sign-off (grounding-check gate; /boss auto-sign)
**Authors:** orchestrator + Claude
## Goal
Erase the three-way inconsistency in the aura CLI's hand-rolled usage
messages (issue #179). After the clap adoption (#175), the binary speaks
three styles at once: clap's own parse errors and one hand-rolled site say
`Usage:` (capital), six hand-rolled sites say `usage:` (lowercase), and
four hand-rolled closures carry no prefix word at all. This cycle
normalizes every hand-rolled usage line to the single house style clap
already speaks — **cosmetic only, no behaviour change**: exit codes,
emission channels (stderr), and control flow are byte-for-byte preserved;
only the usage-string literals and their casing-sensitive test pins move,
in lockstep.
Decision log (house-style shape, bare-closure inclusion, refusal
exclusion, no de-dup refactor): issue #179, comment "Design
reconciliation (specify)".
## Architecture
### The house style (normative)
One rule: **every hand-rolled usage line reads `Usage: aura <verb> …`**
capital `Usage:` prefix and the program name included in the grammar,
exactly the shape of the two already-conforming producers:
- clap itself (derive API, `#[command(name = "aura")]`) renders
`Usage: aura <verb> [OPTIONS]` on parse errors and `--help`;
- the one already-capital hand-rolled site, the built-in `run` branch
(`run_args_from`, `main.rs:4012`): `"Usage: aura run [--harness …] …"`.
The emission convention around the literal is untouched: hand-rolled
sites keep printing through the existing `eprintln!("aura: {…}")` /
baked-in `aura: ` prefix, so the final stderr line reads
`aura: Usage: aura <verb> …` — which is already today's output shape of
the conforming `run_args_from` path. Exit codes keep the #175 partition
(usage = 2, runtime = 1) at every touched site.
### Scope boundary
**In scope (10 literal edits + 1 test-pin edit):** the 6 lowercase
`usage:` literals, the 4 bare usage closures, and the single
casing-sensitive test pin.
**Out of scope (bytes preserved verbatim):**
- The already-conforming site `run_args_from` (`main.rs:4012`) and all
clap-generated output.
- Every aura-specific refusal/diagnostic in the dispatch area — e.g.
`"no recorded geometry for symbol …"`, `"walkforward <blueprint.json>
requires >= 1 --axis to re-fit per window"`, the six `generalize`
refusals, `"cost flags require an R-evaluator harness …"`,
`"--harness is not valid with a blueprint file"`, `"--list-axes lists
axes and takes no other flags"`, `"run/mc requires a closed blueprint
…"`. A refusal is a diagnostic, not a usage line; clap itself keeps
the two apart (`error: …` vs the `Usage:` block).
- The duplicated run-blueprint usage literal (defined in `run_data_from`
at `main.rs:3991`, verbatim inline copy in `dispatch_run` at
`main.rs:4187`): both copies are normalized identically, but no shared
constant is extracted — a de-dup refactor exceeds the cosmetic
contract.
- Historical fieldtest corpus text quoting an old usage line
(`fieldtests/milestone-cost-model-graph/FINDINGS.md`) — the corpus is
a tracked record, never retro-edited.
- Rustdoc/comment prose saying "usage error" (no colon, not a message).
## Concrete code shapes
### Worked user-facing example (the acceptance evidence)
Before (bare closure, built-in sweep):
```console
$ aura sweep --strategy bogus
aura: sweep [--strategy <sma|momentum|r-sma|r-breakout|r-meanrev>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--name <n> | --trace <n>] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--channel <csv>] [--window <csv>] [--band-k <csv>]
$ echo $?
2
```
After (house style; exit code unchanged):
```console
$ aura sweep --strategy bogus
aura: Usage: aura sweep [--strategy <sma|momentum|r-sma|r-breakout|r-meanrev>] [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--name <n> | --trace <n>] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--channel <csv>] [--window <csv>] [--band-k <csv>]
$ echo $?
2
```
And a lowercase site (mc, loaded-blueprint branch):
```console
# before
aura: usage: mc <blueprint.json> --seeds <n> [--name <n>]
# after
aura: Usage: aura mc <blueprint.json> --seeds <n> [--name <n>]
```
### Normative literal edits (before → after, exact bytes)
All in `crates/aura-cli` sources. Only the leading bytes of each literal
change; the grammar tails (`…` below) are preserved verbatim from the
current tree.
| # | Site | Before (leading bytes) | After (leading bytes) |
|---|------|------------------------|-----------------------|
| 1 | `graph_construct.rs:191` (`introspect_cmd`) | `"aura: usage: aura graph introspect …"` | `"aura: Usage: aura graph introspect …"` |
| 2 | `main.rs:3991` (`run_data_from`) | `"usage: aura run <blueprint.json> …"` | `"Usage: aura run <blueprint.json> …"` |
| 3 | `main.rs:4187` (`dispatch_run`, inline copy of #2) | `"aura: usage: aura run <blueprint.json> …"` | `"aura: Usage: aura run <blueprint.json> …"` |
| 4 | `main.rs:4272` (`dispatch_runs`) | `"aura: usage: aura runs family <id> [rank <metric>]"` | `"aura: Usage: aura runs family <id> [rank <metric>]"` |
| 5 | `main.rs:4465` (`dispatch_mc`, blueprint) | `"usage: mc <blueprint.json> …"` | `"Usage: aura mc <blueprint.json> …"` |
| 6 | `main.rs:4503` (`dispatch_mc`, built-in) | `"usage: mc [--name <n>\|--trace <n>] \| mc --strategy r-sma …"` | `"Usage: aura mc [--name <n>\|--trace <n>] \| aura mc --strategy r-sma …"` |
| 7 | `main.rs:4288` (`dispatch_sweep`, blueprint) | `"sweep <blueprint.json> --axis …"` | `"Usage: aura sweep <blueprint.json> --axis …"` |
| 8 | `main.rs:4346` (`dispatch_sweep`, built-in) | `"sweep [--strategy …"` | `"Usage: aura sweep [--strategy …"` |
| 9 | `main.rs:4386` (`dispatch_walkforward`, blueprint) | `"walkforward <blueprint.json> --axis …"` | `"Usage: aura walkforward <blueprint.json> --axis …"` |
| 10 | `main.rs:4424` (`dispatch_walkforward`, built-in) | `"walkforward [--strategy …"` | `"Usage: aura walkforward [--strategy …"` |
Site #6 carries two grammar alternatives separated by ` | `; **both**
gain the program name (`… | aura mc --strategy r-sma …`), so the line
stays internally consistent.
### Test-pin edit (lockstep, exactly one casing-sensitive pin)
`crates/aura-cli/tests/cli_run.rs:1409` (test `mc_rejects_real_flag`, pinning
site #6):
```rust
// before
assert!(err.contains("usage"), );
// after
assert!(err.contains("Usage"), );
```
(The exact assertion text at that line is preserved except for the
pinned substring's casing; if the assertion carries a message argument,
it is left untouched.)
All other pins are casing-neutral and stay byte-identical, verified
against the current suite: `cli_run.rs:116` and `:669` already require
capital `Usage` (satisfied by site `main.rs:4012` and by clap,
respectively — both unedited); `cli_run.rs:1444/1445` pin the verbs
`sweep`/`walkforward` and the flag `--real` (still substrings after the
prefix is prepended); `cli_run.rs:3192/3628/3876` pin out-of-scope
refusal texts (unedited); `tests/graph_construct.rs:294-297` pins only
the exit code and an echo of the bogus node name.
## Components
| File | Change |
|------|--------|
| `crates/aura-cli/src/main.rs` | 9 usage-literal edits (sites #2#10) |
| `crates/aura-cli/src/graph_construct.rs` | 1 usage-literal edit (site #1) |
| `crates/aura-cli/tests/cli_run.rs` | 1 pin flip (`:1409`, lowercase `usage``Usage`) |
No new files, no signature changes, no dependency changes.
## Data flow
Unchanged. Every touched literal keeps its current path to the user:
defined in its dispatch branch (as `&str`, `String` closure, or inline
`eprintln!`), printed to **stderr** with the `aura: ` program prefix,
followed by `exit(2)`. No emission moves between stdout/stderr, no exit
code changes, no message is added or removed — only bytes inside
existing literals change.
## Error handling
The #175 exit-code partition is the invariant to preserve: usage errors
exit 2, runtime failures exit 1. All twelve edited sites are on exit-2
paths today and stay there. The suite's exit-code pins
(`exit_codes_partition_usage_two_from_runtime_one`, the per-command
rejection tests) run unmodified except for the single casing pin above.
## Testing strategy
No new tests: the change is cosmetic and the existing suite already pins
the touched surface. The gate is the suite itself:
1. `cargo build --workspace` clean.
2. `cargo test --workspace` green with **exactly one** test-file edit
(the `cli_run.rs:1409` casing flip). Any other failing pin means an
edit strayed outside the normative table — fix the edit, not the pin.
3. `cargo clippy --workspace --all-targets -- -D warnings` clean.
4. Literal grep gates over `crates/aura-cli/src/`:
- `grep -rn '"usage:' crates/aura-cli/src/` → 0 hits;
- `grep -rn 'aura: usage:' crates/aura-cli/src/` → 0 hits;
- every hand-rolled usage literal now starts `Usage: aura ` (or
`aura: Usage: aura ` where the program prefix is baked into the
literal).
5. Smoke: `aura sweep --strategy bogus` prints the house-style line to
stderr and exits 2 (the worked example above).
## Acceptance criteria
- [ ] All 10 normative literal edits applied exactly as tabled (11
grammar alternatives counting site #6's two; sites #2/#3 remain
verbatim twins).
- [ ] `cli_run.rs:1409` pins capital `Usage`; no other test bytes change.
- [ ] Grep gates (Testing strategy #4) pass: no lowercase `usage:`
literal survives in `crates/aura-cli/src/`.
- [ ] `cargo build` / `cargo test --workspace` / clippy `-D warnings`
all green.
- [ ] Exit codes unchanged at every touched site (usage = 2).
- [ ] Out-of-scope surfaces byte-identical: refusal diagnostics,
`run_args_from` literal, clap output, fieldtests corpus, README /
design-ledger prose.