diff --git a/crates/aura-cli/src/main.rs b/crates/aura-cli/src/main.rs index 5f316e9..b6c28ce 100644 --- a/crates/aura-cli/src/main.rs +++ b/crates/aura-cli/src/main.rs @@ -467,10 +467,12 @@ fn build_comparison_chart_data( /// run has no such tap (refuse-don't-guess). Used by the `--tap` flag on the /// single-run chart path; without `--tap` the single-run page is unchanged. fn filter_to_tap(data: ChartData, tap: &str) -> Result { - let series: Vec = data.series.into_iter().filter(|s| s.name == tap).collect(); - if series.is_empty() { - return Err(format!("run has no tap named '{tap}'")); + if !data.series.iter().any(|s| s.name == tap) { + // List the valid taps so the user can correct a typo (#131) — refuse, but help. + let available: Vec<&str> = data.series.iter().map(|s| s.name.as_str()).collect(); + return Err(format!("run has no tap named '{tap}' (available: {})", available.join(", "))); } + let series: Vec = data.series.into_iter().filter(|s| s.name == tap).collect(); let mut meta = data.meta; meta.taps = vec![tap.to_string()]; Ok(ChartData { xs: data.xs, series, meta }) @@ -1966,6 +1968,13 @@ fn main() { // so an unexpected trailing token falls through to the usage-error path rather // than masquerading as a successful run (#16 strict reading). let args: Vec = std::env::args().skip(1).collect(); + // A `--help` / `-h` anywhere prints usage to stdout and exits 0, uniformly across + // subcommands (#131): `aura chart --help` shows usage instead of erroring + // "unexpected argument", and the bare `aura --help` is the same affordance. + if args.iter().any(|a| a == "--help" || a == "-h") { + println!("{USAGE}"); + return; + } match args.iter().map(String::as_str).collect::>().as_slice() { ["run", rest @ ..] => match parse_run_args(rest) { Ok(args) => match run_dispatch(args) { @@ -2012,7 +2021,6 @@ fn main() { ["runs", "families"] => runs_families(), ["runs", "family", id] => runs_family(id, None), ["runs", "family", id, "rank", metric] => runs_family(id, Some(metric)), - ["--help"] | ["-h"] => println!("{USAGE}"), _ => { eprintln!("aura: {USAGE}"); std::process::exit(2); diff --git a/crates/aura-cli/tests/cli_run.rs b/crates/aura-cli/tests/cli_run.rs index b92d485..c155268 100644 --- a/crates/aura-cli/tests/cli_run.rs +++ b/crates/aura-cli/tests/cli_run.rs @@ -489,6 +489,12 @@ fn chart_tap_nonexistent_on_run_refuses_with_exit_2() { stderr.contains("run has no tap named 'nope'"), "expected the no-such-tap refusal message, got: {stderr}" ); + // #131: the refusal lists the valid taps so the user can correct the typo + // (the demo run's taps are `equity` and `exposure`). + assert!( + stderr.contains("available:") && stderr.contains("equity") && stderr.contains("exposure"), + "the refusal should enumerate the available taps, got: {stderr}" + ); let _ = std::fs::remove_dir_all(&cwd); } @@ -557,6 +563,36 @@ fn help_flag_prints_usage_to_stdout_and_exits_zero() { ); } +/// Property (#131): `--help`/`-h` is UNIFORM across subcommands — `aura --help` +/// prints usage to stdout and exits 0 for every subcommand, not only the bare `aura +/// --help`. Pre-fix, `aura chart --help` errored with "unexpected chart argument" +/// (exit 2) while `aura run --help` leaked usage to stderr; this pins the uniform +/// success-path help affordance for all subcommands. +#[test] +fn per_subcommand_help_is_uniform_stdout_exit_zero() { + for sub in ["run", "chart", "sweep", "walkforward", "mc", "graph", "runs"] { + for flag in ["--help", "-h"] { + let out = Command::new(BIN).args([sub, flag]).output().expect("spawn aura --help"); + assert_eq!( + out.status.code(), + Some(0), + "`aura {sub} {flag}` should exit 0, got: {:?}", + out.status + ); + let stdout = String::from_utf8(out.stdout).expect("utf-8 stdout"); + assert!( + stdout.contains("usage"), + "`aura {sub} {flag}` should print usage to stdout, got: {stdout:?}" + ); + assert!( + out.stderr.is_empty(), + "`aura {sub} {flag}` should not write to stderr, got: {:?}", + String::from_utf8_lossy(&out.stderr) + ); + } + } +} + /// Property: `aura graph` emits a single self-contained HTML page — the /// interactive pin-graph viewer with the deterministic model inlined and the /// vendored Graphviz-WASM + pan-zoom inlined (no remote fetch). Driven through