polish(cli): normalize usage-message casing to the clap house style

Every hand-rolled usage line now reads 'Usage: aura <verb> ...' — capital
prefix plus the program name, the shape clap itself and the built-in run
branch already spoke. 10 literal edits: 6 lowercase 'usage:' flips (graph
introspect, run blueprint x2, runs family, mc blueprint, mc built-in), 4 bare
closures gain the prefix (sweep/walkforward, blueprint + built-in branches);
the mc built-in line names the program in both |-separated alternatives.
Refusal diagnostics, the conforming run_args_from literal, clap output, and
the fieldtests corpus stay byte-identical; exit codes keep the usage=2 /
runtime=1 partition.

Lockstep pin: the single casing-sensitive assertion (mc --real) flips to
capital 'Usage'. Three additive regression tests pin the house style itself
(bare-closure prefix, both mc alternatives, mc blueprint branch) — the
pre-existing pins checked only verb tokens / a single Usage occurrence;
deviation from the spec's no-new-test-bytes wording recorded on the issue.

Verification: cargo test --workspace rc=0 (cli_run 133 passed), clippy -D
warnings clean, grep gates exact (zero lowercase 'usage:' literals; 11
'Usage: aura' source lines), smoke 'aura sweep --strategy bogus' prints the
house-style line and exits 2.

closes #179
This commit is contained in:
2026-07-02 12:56:23 +02:00
parent ab7f0b0650
commit 9402afe958
3 changed files with 90 additions and 11 deletions
+1 -1
View File
@@ -188,7 +188,7 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd) {
+ cmd.content_id as usize;
if count != 1 {
eprintln!(
"aura: usage: aura graph introspect --vocabulary | --node <T> | --unwired | --content-id"
"aura: Usage: aura graph introspect --vocabulary | --node <T> | --unwired | --content-id"
);
std::process::exit(2);
}
+9 -9
View File
@@ -3988,7 +3988,7 @@ fn is_blueprint_file(arg: &Option<String>) -> Option<&str> {
/// mirroring the old `parse_blueprint_run_args` window guard (`--from`/`--to`
/// require `--real`; empty symbol rejected). Refuses in place (stderr + exit 2).
fn run_data_from(real: Option<&str>, from: Option<i64>, to: Option<i64>) -> RunData {
let usage = "usage: aura run <blueprint.json> [--params <json-cell-array>] [--seed <n>] [--real <SYMBOL> [--from <ms>] [--to <ms>]]";
let usage = "Usage: aura run <blueprint.json> [--params <json-cell-array>] [--seed <n>] [--real <SYMBOL> [--from <ms>] [--to <ms>]]";
match real {
Some(s) if !s.is_empty() => RunData::Real { symbol: s.to_string(), from, to },
Some(_) => {
@@ -4184,7 +4184,7 @@ fn dispatch_run(a: RunCmd) {
|| a.slip_vol_mult.is_some()
|| a.carry_per_cycle.is_some()
{
eprintln!("aura: usage: aura run <blueprint.json> [--params <json-cell-array>] [--seed <n>] [--real <SYMBOL> [--from <ms>] [--to <ms>]]");
eprintln!("aura: Usage: aura run <blueprint.json> [--params <json-cell-array>] [--seed <n>] [--real <SYMBOL> [--from <ms>] [--to <ms>]]");
std::process::exit(2);
}
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
@@ -4269,7 +4269,7 @@ fn dispatch_runs(a: RunsCmd) {
(None, None) => runs_family(&id, None),
(Some("rank"), Some(m)) => runs_family(&id, Some(&m)),
_ => {
eprintln!("aura: usage: aura runs family <id> [rank <metric>]");
eprintln!("aura: Usage: aura runs family <id> [rank <metric>]");
std::process::exit(2);
}
},
@@ -4285,7 +4285,7 @@ fn dispatch_reproduce(a: ReproduceCmd) {
fn dispatch_sweep(a: SweepCmd) {
match is_blueprint_file(&a.blueprint) {
Some(path) => {
let usage = || "sweep <blueprint.json> --axis <name>=<csv> [--axis …] [--name <n> | --trace <n>] [--real <SYM> [--from <ms>] [--to <ms>]]".to_string();
let usage = || "Usage: aura sweep <blueprint.json> --axis <name>=<csv> [--axis …] [--name <n> | --trace <n>] [--real <SYM> [--from <ms>] [--to <ms>]]".to_string();
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
@@ -4343,7 +4343,7 @@ fn dispatch_sweep(a: SweepCmd) {
run_blueprint_sweep(&doc, &axes, &name, persist, DataSource::from_choice(data));
}
None => {
let usage = || "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>]".to_string();
let usage = || "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>]".to_string();
if a.blueprint.is_some() || !a.axis.is_empty() || a.list_axes {
eprintln!("aura: {}", usage());
std::process::exit(2);
@@ -4383,7 +4383,7 @@ fn dispatch_sweep(a: SweepCmd) {
fn dispatch_walkforward(a: WalkforwardCmd) {
match is_blueprint_file(&a.blueprint) {
Some(path) => {
let usage = || "walkforward <blueprint.json> --axis <name>=<csv> [--axis …] [--select <argmax|plateau:mean|plateau:worst>] [--name <n>]".to_string();
let usage = || "Usage: aura walkforward <blueprint.json> --axis <name>=<csv> [--axis …] [--select <argmax|plateau:mean|plateau:worst>] [--name <n>]".to_string();
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
@@ -4421,7 +4421,7 @@ fn dispatch_walkforward(a: WalkforwardCmd) {
run_blueprint_walkforward(&doc, &axes, &name, DataSource::Synthetic, select);
}
None => {
let usage = || "walkforward [--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>] [--select <argmax|plateau:mean|plateau:worst>]".to_string();
let usage = || "Usage: aura walkforward [--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>] [--select <argmax|plateau:mean|plateau:worst>]".to_string();
if a.blueprint.is_some() || !a.axis.is_empty() {
eprintln!("aura: {}", usage());
std::process::exit(2);
@@ -4462,7 +4462,7 @@ fn dispatch_walkforward(a: WalkforwardCmd) {
fn dispatch_mc(a: McCmd) {
match is_blueprint_file(&a.blueprint) {
Some(path) => {
let usage = "usage: mc <blueprint.json> --seeds <n> [--name <n>]";
let usage = "Usage: aura mc <blueprint.json> --seeds <n> [--name <n>]";
let doc = std::fs::read_to_string(path).unwrap_or_else(|e| {
eprintln!("aura: {path}: {e}");
std::process::exit(2);
@@ -4500,7 +4500,7 @@ fn dispatch_mc(a: McCmd) {
run_blueprint_mc(&doc, n_seeds, &name, DataSource::Synthetic);
}
None => {
let usage = || "usage: mc [--name <n>|--trace <n>] | mc --strategy r-sma [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--block-len <n>] [--resamples <n>] [--seed <n>]".to_string();
let usage = || "Usage: aura mc [--name <n>|--trace <n>] | aura mc --strategy r-sma [--real <SYMBOL> [--from <ms>] [--to <ms>]] [--fast <csv>] [--slow <csv>] [--stop-length <csv>] [--stop-k <csv>] [--block-len <n>] [--resamples <n>] [--seed <n>]".to_string();
if a.blueprint.is_some() || a.seeds.is_some() {
eprintln!("aura: {}", usage());
std::process::exit(2);
+80 -1
View File
@@ -1406,7 +1406,7 @@ fn mc_rejects_real_flag_with_usage_exit_2() {
assert_eq!(out.status.code(), Some(2), "mc --real must exit 2; status: {:?}", out.status);
assert!(out.stdout.is_empty(), "mc --real must not emit a run on stdout: {:?}", out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(stderr.contains("usage"), "mc --real stderr must carry usage: {stderr:?}");
assert!(stderr.contains("Usage"), "mc --real stderr must carry usage: {stderr:?}");
// the exclusion is total: no `runs/` registry write either.
assert!(!dir.join("runs").exists(), "mc --real must not start a real run");
@@ -1447,6 +1447,85 @@ fn family_window_flag_without_real_refuses_with_usage_exit_2() {
}
}
/// Property (house style, #179): every hand-rolled usage line reads
/// `Usage: aura <verb> …` — capital `Usage:` plus the program name inside the
/// grammar. `sweep`/`walkforward`'s built-in-branch usage closures were bare
/// (`"sweep [--strategy …]"`, no prefix, no program name) before cycle 0101; a
/// regression that drops the prefix again would NOT be caught by the older
/// `family_window_flag_without_real_refuses_with_usage_exit_2` pin above, which
/// only checks for the verb token and `--real` — not the `Usage: aura` lead-in.
/// An unknown `--strategy` name reaches this same closure on both commands, so
/// it is the smallest trigger for each.
#[test]
fn builtin_branch_usage_lines_lead_with_the_house_style_prefix() {
for (args, verb) in [(&["sweep", "--strategy", "bogus"][..], "sweep"), (&["walkforward", "--strategy", "bogus"][..], "walkforward")] {
let dir = temp_cwd("builtin_usage_prefix");
let out = Command::new(BIN)
.args(args)
.current_dir(&dir)
.output()
.unwrap_or_else(|e| panic!("spawn aura {args:?}: {e}"));
assert_eq!(out.status.code(), Some(2), "`aura {args:?}` must exit 2; status: {:?}", out.status);
let stderr = String::from_utf8_lossy(&out.stderr);
let want = format!("aura: Usage: aura {verb} ");
assert!(stderr.starts_with(&want), "`aura {args:?}` stderr must lead with {want:?}: {stderr:?}");
let _ = std::fs::remove_dir_all(&dir);
}
}
/// Property (house style, #179): the built-in `mc` usage line carries TWO
/// `|`-separated grammar alternatives, and BOTH name the program (`aura mc …`),
/// not just the first. This is the one lockstep-pinned site
/// (`mc_rejects_real_flag_with_usage_exit_2` above), but that pin only checks
/// for a single `"Usage"` occurrence — a regression that fixed only the first
/// alternative (leaving `… | mc --strategy r-sma …` unprefixed) would still
/// pass it. `--seeds` with no blueprint file is the built-in branch's own
/// grammar violation, reaching this exact literal.
#[test]
fn mc_builtin_usage_names_the_program_in_both_alternatives() {
let dir = temp_cwd("mc_usage_both_alts");
let out = Command::new(BIN)
.args(["mc", "--seeds", "3"])
.current_dir(&dir)
.output()
.expect("spawn aura mc --seeds");
assert_eq!(out.status.code(), Some(2), "mc --seeds without a blueprint must exit 2; status: {:?}", out.status);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("Usage: aura mc [--name"),
"first alternative must name the program: {stderr:?}"
);
assert!(
stderr.contains("| aura mc --strategy r-sma"),
"second alternative must ALSO name the program: {stderr:?}"
);
let _ = std::fs::remove_dir_all(&dir);
}
/// Property (house style, #179): `mc`'s loaded-blueprint branch usage line
/// also normalizes to `Usage: aura mc <blueprint.json> …` — before cycle 0101
/// it was fully lowercase and carried no program name (`"usage: mc
/// <blueprint.json> …"`). Reusing `sma_signal.json` (an existing closed
/// blueprint fixture) only to reach the dispatch branch; omitting `--seeds`
/// is this branch's own grammar violation.
#[test]
fn mc_over_a_blueprint_usage_names_the_program() {
let dir = temp_cwd("mc_blueprint_usage_prefix");
let fixture = format!("{}/tests/fixtures/sma_signal.json", env!("CARGO_MANIFEST_DIR"));
let out = Command::new(BIN)
.args(["mc", &fixture])
.current_dir(&dir)
.output()
.expect("spawn aura mc <blueprint.json>");
assert_eq!(out.status.code(), Some(2), "mc <blueprint.json> without --seeds must exit 2; status: {:?}", out.status);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("aura: Usage: aura mc <blueprint.json>"),
"stderr must lead with the house-style, program-named usage: {stderr:?}"
);
let _ = std::fs::remove_dir_all(&dir);
}
/// Property: `aura chart <family>` overlays one series per family member, labelled
/// by member key. The built-in sweep grid is fast∈{2,3} × slow∈{4,5} = 4 members;
/// each member key appears as a series name in the injected AURA_TRACES.