Files
Aura/docs/specs/0101-normalize-cli-usage-casing.md
T
Brummel 7a386dc9bb 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
2026-07-02 12:31:48 +02:00

9.8 KiB
Raw Blame History

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):

$ 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):

$ 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):

# 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):

// 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 usageUsage)

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.