CLI --help/--version does not meet POSIX/GNU/clig.dev conventions (hand-rolled parser) #175

Closed
opened 2026-07-01 15:05:23 +02:00 by Brummel · 7 comments
Owner

Context

The aura CLI parses argv entirely by hand (no clap/argh/structopt in
crates/aura-cli/Cargo.toml), so nothing supplies the conventional --help /
--version behaviour for free. A read-only audit against the three relevant
convention layers found the foundation clean (stream/exit discipline, argument
syntax) but the help experience itself well short of standard.

This issue is the self-contained basis for a decoupled session. Line anchors
below are a snapshot at the time of the audit (main around ebebc24) and will
drift — re-locate by symbol: the USAGE / COST_FLAGS_NOTE consts and the
args.iter().any(|a| a == "--help" || a == "-h") short-circuit in fn main
(crates/aura-cli/src/main.rs), plus the per-subcommand usage() closures.

The standard (three layers)

  • POSIX Utility Conventions (IEEE 1003.1, ch. 12, Guidelines 1–14) — argument
    syntax only (-o value, clustering, -- terminator, - = stdin, operand
    order). Says almost nothing about help text.
  • GNU Coding Standards ("Command-Line Interfaces") — --help and
    --version are required; --help → stdout, exit 0; options list, description,
    bug-report footer; --foo=value; long-option abbreviation.
  • De-facto modern (clig.dev / docopt / man-pages(7) / argparse-BSD) — for a
    subcommand tool: scoped subcommand help, tool help <sub>, an Options
    section (one flag per line), SYNOPSIS notation, exit 2 = usage error vs 1 =
    runtime failure.

aura is a git/cargo-style subcommand tool, so the third layer is the most
relevant yardstick.

Already compliant — do NOT regress

  • Stream/exit discipline is exemplary: --help/-h → stdout, exit 0
    (main.rs:4038-4041); every error → aura: {msg} on stderr; results → stdout;
    never mixed.
  • -h aliases --help correctly (any argv position, overrides other args,
    suppresses the run).
  • SYNOPSIS notation is correct ([optional], <placeholder>, | alternatives).
  • POSIX syntax basics: space-separated long-option values everywhere;
    multi-value as one comma token (G8); binary name aura (G1/G2); per-utility
    positional operands (run <file.json>, chart <name>).

Deviations (verified), by severity

High

  1. --help is a single dense one-line usage: blob with no Options section.
    All ~13 invocation forms crammed on one line joined by |; no flag gets its
    own line + description. Only the three cost flags have prose
    (COST_FLAGS_NOTE). — main.rs:4014-4019 (clig.dev help-options-section).
  2. No scoped subcommand help. The --help check (main.rs:4038) fires
    before the subcommand match (:4042) and always prints the global blob,
    so aura sweep --help, aura mc --help, … are byte-identical. (clig.dev
    help-subcmd-scoped).
  3. No --version / -V. GNU MUST. aura --version hits the catch-all
    (_ =>, :4186) → USAGE on stderr, exit 2. The version exists
    (version.workspace = true0.1.0) but is never surfaced. (GNU
    support-version).

Medium

  1. The rich per-subcommand usage() strings (sweep :1668, walkforward :1790,
    generalize :1718 — listing all flags) are reachable only on a parse
    error
    (stderr, exit 2), never via --help. The detail is in the binary; the
    explicit help request routes past it. (Exception: run shares its text with
    the global path, incl. COST_FLAGS_NOTE.)
  2. No aura help <subcmd> verb — aura help sweep is a usage error.
  3. No subcommand overview with one-line summaries (commands appear only as
    leading synopsis tokens).
  4. No --flag=value (GNU equals) form — exact string match; --real=EURUSD errors.
  5. Exit code 2 is overloaded for runtime failures (I/O, "no local data",
    run_dispatch errors), not just usage errors — and inconsistently:
    append_family write failure → 1 (:1926), TraceStore::write failure (same
    class) → 2 (:202).

Low

  • No program description / usage examples in help.
  • Bare aura (no args) errors (exit 2) instead of concise help.
  • Usage errors dump the full USAGE instead of "bad arg + --help pointer".
  • -- end-of-options terminator not recognized.
  • No long-option abbreviation.
  • - (stdin) not accepted as an operand where stdin is read.
  • usage: prefix inconsistent across subcommands (sweep/walkforward/generalize
    omit it).
  • reproduce <bad-id> → exit 2 vs runs family <bad-id> → exit 0 for the same
    "unknown id".

Highest-leverage direction (for the session to spec, not a fixed plan)

  • Move the --help handling after subcommand recognition and print that
    subcommand's usage() on stdout (exit 0) — the scoped detail texts already
    exist, they are just wired to the error path. Bare aura/aura --help keeps
    the top-level overview.
  • Add a --version (and -V) arm surfacing CARGO_PKG_VERSION (+ optional
    AURA_COMMIT), stdout, exit 0.
  • Consider whether to keep hand-rolling or adopt a parser crate; if hand-rolled,
    an Options section per subcommand + a top-level Commands list closes most of the
    gap. A crate would need a C16 per-case dependency review.

Constraints / notes

  • Engine invariant 8 (frozen deploy artifact) is unaffected — this is
    research-side CLI only.
  • Audit was strictly read-only; a verifier built/ran aura for empirical checks
    (target/ artifacts only, no source change).
  • Substantive rationale for the eventual design choice belongs in the ledger /
    the cycle spec, per project discipline.
## Context The `aura` CLI parses argv entirely by hand (no `clap`/`argh`/`structopt` in `crates/aura-cli/Cargo.toml`), so nothing supplies the conventional `--help` / `--version` behaviour for free. A read-only audit against the three relevant convention layers found the *foundation* clean (stream/exit discipline, argument syntax) but the *help experience itself* well short of standard. This issue is the self-contained basis for a decoupled session. Line anchors below are a snapshot at the time of the audit (`main` around `ebebc24`) and will drift — re-locate by symbol: the `USAGE` / `COST_FLAGS_NOTE` consts and the `args.iter().any(|a| a == "--help" || a == "-h")` short-circuit in `fn main` (`crates/aura-cli/src/main.rs`), plus the per-subcommand `usage()` closures. ## The standard (three layers) - **POSIX Utility Conventions** (IEEE 1003.1, ch. 12, Guidelines 1–14) — argument *syntax* only (`-o value`, clustering, `--` terminator, `-` = stdin, operand order). Says almost nothing about help *text*. - **GNU Coding Standards** ("Command-Line Interfaces") — `--help` **and** `--version` are required; `--help` → stdout, exit 0; options list, description, bug-report footer; `--foo=value`; long-option abbreviation. - **De-facto modern** (clig.dev / docopt / man-pages(7) / argparse-BSD) — for a subcommand tool: *scoped* subcommand help, `tool help <sub>`, an Options section (one flag per line), SYNOPSIS notation, exit 2 = usage error vs 1 = runtime failure. aura is a git/cargo-style subcommand tool, so the third layer is the most relevant yardstick. ## Already compliant — do NOT regress - Stream/exit discipline is exemplary: `--help`/`-h` → stdout, exit 0 (`main.rs:4038-4041`); every error → `aura: {msg}` on stderr; results → stdout; never mixed. - `-h` aliases `--help` correctly (any argv position, overrides other args, suppresses the run). - SYNOPSIS notation is correct (`[optional]`, `<placeholder>`, `|` alternatives). - POSIX syntax basics: space-separated long-option values everywhere; multi-value as one comma token (G8); binary name `aura` (G1/G2); per-utility positional operands (`run <file.json>`, `chart <name>`). ## Deviations (verified), by severity ### High 1. **`--help` is a single dense one-line `usage:` blob with no Options section.** All ~13 invocation forms crammed on one line joined by `|`; no flag gets its own line + description. Only the three cost flags have prose (`COST_FLAGS_NOTE`). — `main.rs:4014-4019` (clig.dev *help-options-section*). 2. **No scoped subcommand help.** The `--help` check (`main.rs:4038`) fires *before* the subcommand `match` (`:4042`) and always prints the global blob, so `aura sweep --help`, `aura mc --help`, … are byte-identical. (clig.dev *help-subcmd-scoped*). 3. **No `--version` / `-V`.** GNU MUST. `aura --version` hits the catch-all (`_ =>`, `:4186`) → USAGE on stderr, exit 2. The version exists (`version.workspace = true` → `0.1.0`) but is never surfaced. (GNU *support-version*). ### Medium 4. The rich per-subcommand `usage()` strings (sweep `:1668`, walkforward `:1790`, generalize `:1718` — listing *all* flags) are reachable **only on a parse error** (stderr, exit 2), never via `--help`. The detail is in the binary; the explicit help request routes past it. (Exception: `run` shares its text with the global path, incl. `COST_FLAGS_NOTE`.) 5. No `aura help <subcmd>` verb — `aura help sweep` is a usage error. 6. No subcommand overview with one-line summaries (commands appear only as leading synopsis tokens). 7. No `--flag=value` (GNU equals) form — exact string match; `--real=EURUSD` errors. 8. Exit code 2 is overloaded for runtime failures (I/O, "no local data", `run_dispatch` errors), not just usage errors — and inconsistently: `append_family` write failure → 1 (`:1926`), `TraceStore::write` failure (same class) → 2 (`:202`). ### Low - No program description / usage examples in help. - Bare `aura` (no args) errors (exit 2) instead of concise help. - Usage errors dump the full USAGE instead of "bad arg + `--help` pointer". - `--` end-of-options terminator not recognized. - No long-option abbreviation. - `-` (stdin) not accepted as an operand where stdin is read. - `usage:` prefix inconsistent across subcommands (sweep/walkforward/generalize omit it). - `reproduce <bad-id>` → exit 2 vs `runs family <bad-id>` → exit 0 for the same "unknown id". ## Highest-leverage direction (for the session to spec, not a fixed plan) - Move the `--help` handling *after* subcommand recognition and print that subcommand's `usage()` on stdout (exit 0) — the scoped detail texts already exist, they are just wired to the error path. Bare `aura`/`aura --help` keeps the top-level overview. - Add a `--version` (and `-V`) arm surfacing `CARGO_PKG_VERSION` (+ optional `AURA_COMMIT`), stdout, exit 0. - Consider whether to keep hand-rolling or adopt a parser crate; if hand-rolled, an Options section per subcommand + a top-level Commands list closes most of the gap. A crate would need a C16 per-case dependency review. ## Constraints / notes - Engine invariant 8 (frozen deploy artifact) is unaffected — this is research-side CLI only. - Audit was strictly read-only; a verifier built/ran `aura` for empirical checks (`target/` artifacts only, no source change). - Substantive rationale for the eventual design choice belongs in the ledger / the cycle spec, per project discipline.
Brummel added the feature label 2026-07-01 15:05:23 +02:00
Author
Owner

Cycle discovery — swarm triage (autonomous, /boss)

A synthetic-user swarm (minimalist, power-user, maintainer, skeptic,
off-ledger wildcard) populated the five load-bearing forks this cycle forces.
Convergent forks are decided on cited ground and recorded below; the two
divergent direction forks are routed to the user.

Decided on cited ground

Primary consumer = LLM / automation (all five stances converge). C14
(docs/design/INDEX.md:1057) names the CLI "the primary surface for the LLM
and automation; the human drives the visual face." This frames scope:
affordances that serve the machine consumer (scoped structured help,
--version provenance, consistent exit codes) are in; human-only niceties
(prose usage examples, long-option abbreviation, --as-stdin for humans)
are deprioritized.

Convergent scope core — all five stances agree the cycle delivers at least:

  • Scoped subcommand helpaura <sub> --help prints that subcommand's own
    usage on stdout, exit 0. The detailed per-subcommand texts already exist in
    the binary but are wired only to the parse-error path (stderr, exit 2); the
    fix is a near-free re-route (move the --help short-circuit after
    subcommand recognition). Wrinkle the fix must resolve: run/sweep/mc
    each carry two disjoint grammars under one subcommand token (a
    .json-blueprint branch and the built-in/--strategy branch), so
    "print that subcommand's usage" must decide which grammar aura run --help
    shows.
  • --version / -V — a GNU MUST; the version exists (0.1.0,
    version.workspace = true) but is never surfaced. Also machine-relevant
    (provenance), so it survives the LLM-first reframe.

Exit-code inconsistency is a real, isolated bug; the full re-partition is
deferred.
Four of five stances agree: the same write-failure class exits
inconsistently — append_family write failure → exit 1 (main.rs:1926) vs
TraceStore::write failure → exit 2 (main.rs:202). A genuine defect to fix.
The broader "exit 2 = usage error / exit 1 = runtime failure" re-partition
(deviation #8) is NOT folded in: crates/aura-cli/tests/cli_run.rs pins
exit 2 for runtime "no local data" failures (:142/:166/:195/:234), so the
re-partition is a breaking change to the observable/automation contract and to
~46 exit-2 assertions — a deliberate ledger decision, not a refactor.

Test-contract renegotiation is in scope, not free. The do-not-regress
suite already pins current behaviour: per_subcommand_help_is_uniform_stdout_exit_zero
(cli_run.rs:673, the #131 pin) asserts --help is uniform across
subcommands — in direct tension with the scoped-help goal — and
run_help_surfaces_cost_flag_units_note (:702) pins cost-note substrings any
single-source change must carry through. Whoever specs this budgets editing
pinned contracts, not just adding new tests.

Routed to the user (direction forks the swarm could not ground)

Fork A — parser architecture: keep the hand-rolled argv parser vs adopt a
parser crate (clap-class).
The swarm split with no citable common ground.
Power-user + maintainer: the 5902-line hand-roll (60 usage() sites, growing
every cycle a subcommand is added) is exactly the carry C16
(INDEX.md:1132-1139, "hand-rolling what a vetted standard crate already does
is the anti-pattern") names; the CLI is research-side, not the frozen deploy
artifact (invariant 8), so the dependency cost is only a dev-loop/compile tax,
and a derive-based parser yields scoped help, --version, --flag=value, and
completions from one declaration. Skeptic: a help-text defect is not a parser
defect (the issue itself calls the argument-syntax foundation "exemplary"), so
rewriting a working 5902-line parser is risk with no payoff. Minimalist keeps
hand-rolled but marks the keep-vs-adopt call itself ungrounded. C16 cuts both
ways (it discourages hand-rolling a crate's job, but mandates a deliberate
per-case weigh, no blanket admission), and the blast radius differs radically:
keep = small additive change + rewire; adopt = rewrite of a 5902-line parser +
renegotiation of a 3633-line pinned test surface. A per-case C16 dependency
review the user owns.

Fork B — audience-inversion tension: human clig.dev conformance vs a
machine-first surface.
The wildcard surfaced, on cited ground (C14
INDEX.md:1057), that #175 measures aura against clig.dev — a human at a
terminal — while the ledger ranks that audience second. The reframe: rather
than furnish a human --help experience, invest in a machine-first surface —
structured/JSON help or a command manifest the LLM consumes (a pattern already
latent in graph introspect --vocabulary, crates/aura-cli/src/graph_construct.rs),
typed/structured errors, and aura run - reading a generated blueprint from
stdin (today run/sweep/mc gate the blueprint on is_file(), so an
op-script generator must write a temp file first — main.rs ~4055/~4118/~4157,
the rest.first().filter(...) operand gate). A project-direction question
overlapping the #157/C21 op-script trajectory; only the user can set whether
this cycle stays human-convention-compliance or turns machine-first.

Orchestrator recommendation (for the user to ratify or override)

  • Fork A → keep the hand-rolled parser this cycle, adding a structured
    per-subcommand Options section + a top-level Commands list; treat a
    clap migration as its own C16-review refactor cycle if wanted, not huckepack
    on a help-text fix. Rationale: the scoped-help core is near-free in the
    existing parser, whereas a clap rewrite's payoff (carry reduction) is
    decoupled from this cycle's help/version goal and carries a large
    test-contract renegotiation.
  • Fork B → keep this cycle scoped to human/GNU convention compliance (the
    convergent core + an Options section), and file the machine-first surface
    (JSON/manifest help, stdin op-scripts) as a separate forward item on the
    #157/C21 track. Rationale: the convergent core already serves the machine
    consumer; the reframe is a distinct feature line, not a help-convention fix.

Both recommendations are the orchestrator's; the two forks stay the user's
call until ratified.

## Cycle discovery — swarm triage (autonomous, `/boss`) A synthetic-user swarm (minimalist, power-user, maintainer, skeptic, off-ledger wildcard) populated the five load-bearing forks this cycle forces. Convergent forks are decided on cited ground and recorded below; the two divergent direction forks are routed to the user. ### Decided on cited ground **Primary consumer = LLM / automation (all five stances converge).** C14 (`docs/design/INDEX.md:1057`) names the CLI "the primary surface for the LLM and automation; the human drives the visual face." This frames scope: affordances that serve the machine consumer (scoped structured help, `--version` provenance, consistent exit codes) are in; human-only niceties (prose usage examples, long-option abbreviation, `-`-as-stdin *for humans*) are deprioritized. **Convergent scope core — all five stances agree the cycle delivers at least:** - *Scoped subcommand help* — `aura <sub> --help` prints that subcommand's own usage on stdout, exit 0. The detailed per-subcommand texts already exist in the binary but are wired only to the parse-error path (stderr, exit 2); the fix is a near-free re-route (move the `--help` short-circuit after subcommand recognition). Wrinkle the fix must resolve: `run`/`sweep`/`mc` each carry two disjoint grammars under one subcommand token (a `.json`-blueprint branch and the built-in/`--strategy` branch), so "print that subcommand's usage" must decide which grammar `aura run --help` shows. - *`--version` / `-V`* — a GNU MUST; the version exists (0.1.0, `version.workspace = true`) but is never surfaced. Also machine-relevant (provenance), so it survives the LLM-first reframe. **Exit-code inconsistency is a real, isolated bug; the full re-partition is deferred.** Four of five stances agree: the same write-failure class exits inconsistently — `append_family` write failure → exit 1 (`main.rs:1926`) vs `TraceStore::write` failure → exit 2 (`main.rs:202`). A genuine defect to fix. The broader "exit 2 = usage error / exit 1 = runtime failure" re-partition (deviation #8) is NOT folded in: `crates/aura-cli/tests/cli_run.rs` pins exit 2 for *runtime* "no local data" failures (:142/:166/:195/:234), so the re-partition is a breaking change to the observable/automation contract and to ~46 exit-2 assertions — a deliberate ledger decision, not a refactor. **Test-contract renegotiation is in scope, not free.** The do-not-regress suite already pins current behaviour: `per_subcommand_help_is_uniform_stdout_exit_zero` (`cli_run.rs:673`, the #131 pin) asserts `--help` is *uniform* across subcommands — in direct tension with the scoped-help goal — and `run_help_surfaces_cost_flag_units_note` (:702) pins cost-note substrings any single-source change must carry through. Whoever specs this budgets editing pinned contracts, not just adding new tests. ### Routed to the user (direction forks the swarm could not ground) **Fork A — parser architecture: keep the hand-rolled argv parser vs adopt a parser crate (clap-class).** The swarm split with no citable common ground. Power-user + maintainer: the 5902-line hand-roll (60 `usage()` sites, growing every cycle a subcommand is added) is exactly the carry C16 (`INDEX.md:1132-1139`, "hand-rolling what a vetted standard crate already does is the anti-pattern") names; the CLI is research-side, not the frozen deploy artifact (invariant 8), so the dependency cost is only a dev-loop/compile tax, and a derive-based parser yields scoped help, `--version`, `--flag=value`, and completions from one declaration. Skeptic: a help-text defect is not a parser defect (the issue itself calls the argument-syntax foundation "exemplary"), so rewriting a working 5902-line parser is risk with no payoff. Minimalist keeps hand-rolled but marks the keep-vs-adopt call itself `ungrounded`. C16 cuts both ways (it discourages hand-rolling a crate's job, but mandates a deliberate per-case weigh, no blanket admission), and the blast radius differs radically: keep = small additive change + rewire; adopt = rewrite of a 5902-line parser + renegotiation of a 3633-line pinned test surface. A per-case C16 dependency review the user owns. **Fork B — audience-inversion tension: human clig.dev conformance vs a machine-first surface.** The wildcard surfaced, on cited ground (C14 `INDEX.md:1057`), that #175 measures aura against clig.dev — a human at a terminal — while the ledger ranks that audience second. The reframe: rather than furnish a human `--help` experience, invest in a machine-first surface — structured/JSON help or a command manifest the LLM consumes (a pattern already latent in `graph introspect --vocabulary`, `crates/aura-cli/src/graph_construct.rs`), typed/structured errors, and `aura run -` reading a generated blueprint from stdin (today `run`/`sweep`/`mc` gate the blueprint on `is_file()`, so an op-script generator must write a temp file first — `main.rs` ~4055/~4118/~4157, the `rest.first().filter(...)` operand gate). A project-direction question overlapping the #157/C21 op-script trajectory; only the user can set whether this cycle stays human-convention-compliance or turns machine-first. ### Orchestrator recommendation (for the user to ratify or override) - **Fork A → keep the hand-rolled parser this cycle**, adding a structured per-subcommand Options section + a top-level Commands list; treat a clap migration as its own C16-review refactor cycle if wanted, not huckepack on a help-text fix. Rationale: the scoped-help core is near-free in the existing parser, whereas a clap rewrite's payoff (carry reduction) is decoupled from this cycle's help/version goal and carries a large test-contract renegotiation. - **Fork B → keep this cycle scoped to human/GNU convention compliance** (the convergent core + an Options section), and file the machine-first surface (JSON/manifest help, stdin op-scripts) as a separate forward item on the #157/C21 track. Rationale: the convergent core already serves the machine consumer; the reframe is a distinct feature line, not a help-convention fix. Both recommendations are the orchestrator's; the two forks stay the user's call until ratified.
Author
Owner

Fork decisions ratified by the user (2026-07-01)

Following the swarm triage in the prior comment, the two direction forks are
settled by the user and the cycle is paused on a coordination blocker.
Recorded here so a later resumption picks up from the tracker.

Fork B — human/GNU convention compliance (not the machine-first reframe)

User: "wir bleiben bei gnu-compliance" ("we stay with GNU compliance"),
2026-07-01. The cycle targets human / GNU / clig.dev convention compliance.
The wildcard's machine-first reframe (JSON/manifest help, typed errors,
aura run - stdin op-scripts) is NOT part of this cycle — a distinct feature
line to file forward on the #157/C21 op-script track if wanted.

Fork A — adopt a parser crate (clap)

User: "ich tendiere zu clap" ("I lean toward clap"), 2026-07-01, chosen over
the orchestrator's keep-hand-rolled recommendation. Premise correction that
informed the choice: the "5902-line parser" framing (carried into the swarm
prompts, hence its keep-vs-adopt reasoning) was wrong — main.rs is 5902 lines
TOTAL (the whole CLI binary). The actual argument parsing is ~under 1000 lines
(14 small parse_* fns + the dispatch match); the bulk is ~3000 lines of
sim-harness execution (run_*/emit_*/render_*) plus 1757 lines of inline
tests. clap was chosen knowing the real parsing surface is modest; the
justification is C16 (INDEX.md:1132-1139, use the vetted standard crate) plus
one declarative source yielding scoped help, --version, --flag=value, and
completions — resolving the whole convergent core in one restructure rather
than piecemeal additive fixes. The CLI is research-side (invariant 8), so
clap's dependency/build surface is a dev-loop tax, not a frozen-artifact tax;
the per-case C16 review is to be documented in the spec/ledger.

Design points the spec must carry (clap-specific)

  • Dual-grammar subcommands. run/sweep/mc each carry two disjoint
    grammars under one token — a .json-blueprint branch vs the
    built-in/--strategy branch, today discriminated by
    rest.first().filter(|a| a.ends_with(".json") && is_file()). clap-derive
    does not model this natively; it needs a deliberate solution (external
    positional + manual post-dispatch, or nested subcommands). Plus the nested
    positionals runs family <id> rank <metric> and the graph build /
    graph introspect sub-subcommands.
  • Exit-code split (deviation #8) returns to the table. clap uses exit 2 for
    usage errors by default, which makes the clean "2 = usage / 1 = runtime"
    partition easier — but that is the re-partition previously deferred as a
    contract break against the exit-2-runtime pins in cli_run.rs
    (:142/:166/:195/:234). Because clap changes the error surface regardless, the
    spec decides whether to take the partition now or preserve the old
    exit-2-runtime codes. Orchestrator leaning: take it, since clap breaks the
    surface anyway.
  • Test-contract renegotiation. clap generates its own help/usage/error
    text; the exactly-pinned "usage" stderr substrings and exit codes across
    cli_run.rs (part of the 1757 test lines) must be renegotiated — including
    per_subcommand_help_is_uniform_stdout_exit_zero (:673, the #131 pin, in
    direct tension with the scoped-help goal) and
    run_help_surfaces_cost_flag_units_note (:702).

Blocker — paused, waiting on the parallel main session

User: "wir warten" ("we wait"), 2026-07-01. A separate session is actively
committing to main, touching crates/aura-cli/src/main.rs (e.g. bd2003e,
aura sweep <bp.json> --list-axes). A clap rewrite restructures the whole
parsing layer of that same file, so running it in parallel would collide at
landing time (two large restructures of one block). The cycle is paused until
the parallel session releases aura-cli. On resumption: branch fresh from the
then-current main (do NOT build on the stale a78a055 worktree base) so the
clap restructure captures all current subcommands/flags (incl. --list-axes)
with no post-hoc conflict.

Status: design settled, ready for spec production, blocked on the parallel
main-session releasing aura-cli.

## Fork decisions ratified by the user (2026-07-01) Following the swarm triage in the prior comment, the two direction forks are settled by the user and the cycle is paused on a coordination blocker. Recorded here so a later resumption picks up from the tracker. ### Fork B — human/GNU convention compliance (not the machine-first reframe) User: "wir bleiben bei gnu-compliance" ("we stay with GNU compliance"), 2026-07-01. The cycle targets human / GNU / clig.dev convention compliance. The wildcard's machine-first reframe (JSON/manifest help, typed errors, `aura run -` stdin op-scripts) is NOT part of this cycle — a distinct feature line to file forward on the #157/C21 op-script track if wanted. ### Fork A — adopt a parser crate (clap) User: "ich tendiere zu clap" ("I lean toward clap"), 2026-07-01, chosen over the orchestrator's keep-hand-rolled recommendation. Premise correction that informed the choice: the "5902-line parser" framing (carried into the swarm prompts, hence its keep-vs-adopt reasoning) was wrong — `main.rs` is 5902 lines TOTAL (the whole CLI binary). The actual argument parsing is ~under 1000 lines (14 small `parse_*` fns + the dispatch `match`); the bulk is ~3000 lines of sim-harness execution (`run_*`/`emit_*`/`render_*`) plus 1757 lines of inline tests. clap was chosen knowing the real parsing surface is modest; the justification is C16 (`INDEX.md:1132-1139`, use the vetted standard crate) plus one declarative source yielding scoped help, `--version`, `--flag=value`, and completions — resolving the whole convergent core in one restructure rather than piecemeal additive fixes. The CLI is research-side (invariant 8), so clap's dependency/build surface is a dev-loop tax, not a frozen-artifact tax; the per-case C16 review is to be documented in the spec/ledger. ### Design points the spec must carry (clap-specific) - **Dual-grammar subcommands.** `run`/`sweep`/`mc` each carry two disjoint grammars under one token — a `.json`-blueprint branch vs the built-in/`--strategy` branch, today discriminated by `rest.first().filter(|a| a.ends_with(".json") && is_file())`. clap-derive does not model this natively; it needs a deliberate solution (external positional + manual post-dispatch, or nested subcommands). Plus the nested positionals `runs family <id> rank <metric>` and the `graph build` / `graph introspect` sub-subcommands. - **Exit-code split (deviation #8) returns to the table.** clap uses exit 2 for usage errors by default, which makes the clean "2 = usage / 1 = runtime" partition easier — but that is the re-partition previously deferred as a contract break against the exit-2-runtime pins in `cli_run.rs` (:142/:166/:195/:234). Because clap changes the error surface regardless, the spec decides whether to take the partition now or preserve the old exit-2-runtime codes. Orchestrator leaning: take it, since clap breaks the surface anyway. - **Test-contract renegotiation.** clap generates its own help/usage/error text; the exactly-pinned `"usage"` stderr substrings and exit codes across `cli_run.rs` (part of the 1757 test lines) must be renegotiated — including `per_subcommand_help_is_uniform_stdout_exit_zero` (:673, the #131 pin, in direct tension with the scoped-help goal) and `run_help_surfaces_cost_flag_units_note` (:702). ### Blocker — paused, waiting on the parallel main session User: "wir warten" ("we wait"), 2026-07-01. A separate session is actively committing to `main`, touching `crates/aura-cli/src/main.rs` (e.g. `bd2003e`, `aura sweep <bp.json> --list-axes`). A clap rewrite restructures the whole parsing layer of that same file, so running it in parallel would collide at landing time (two large restructures of one block). The cycle is paused until the parallel session releases `aura-cli`. On resumption: branch fresh from the then-current `main` (do NOT build on the stale a78a055 worktree base) so the clap restructure captures all current subcommands/flags (incl. `--list-axes`) with no post-hoc conflict. Status: design settled, ready for spec production, blocked on the parallel main-session releasing `aura-cli`.
Author
Owner

Exit-code fork resolved (2026-07-01)

The one remaining open design point — how to resolve the exit-code overload
(deviation #8) once clap forces the error surface to change — is settled by the
user: "Natürlich A." ("Of course A."), 2026-07-01.

Option A — clean partition: exit 2 = usage error, exit 1 = runtime failure.
The runtime failures that today exit 2 (no local data, I/O, dispatch errors —
cli_run.rs:142/166/195/234) move to exit 1; clap supplies exit 2 for usage
errors natively. The exit-2-runtime test pins are renegotiated as part of the
clap restructure (they must be touched anyway). Chosen over Option B (leave
runtime failures on exit 2, overload preserved) because clap changes the error
surface regardless, making this the moment to heal the semantics rather than
carry the overload through the rewrite.

Note: adopting clap also absorbs several Low-tier deviations for free —
--flag=value (GNU equals form), the -- end-of-options terminator, and
long-option abbreviation are native clap behaviour, so they need no separate
spec work. --as-stdin (a Low deviation) is NOT clap-automatic (it is app
logic replacing the is_file() gate) and belongs to the deferred machine-first
track (Fork B), out of this cycle.

All load-bearing forks for this cycle are now settled. Status unchanged: design
settled, ready for spec production, blocked on the parallel main-session
releasing aura-cli.

## Exit-code fork resolved (2026-07-01) The one remaining open design point — how to resolve the exit-code overload (deviation #8) once clap forces the error surface to change — is settled by the user: "Natürlich A." ("Of course A."), 2026-07-01. **Option A — clean partition: exit 2 = usage error, exit 1 = runtime failure.** The runtime failures that today exit 2 (no local data, I/O, dispatch errors — `cli_run.rs:142/166/195/234`) move to exit 1; clap supplies exit 2 for usage errors natively. The exit-2-runtime test pins are renegotiated as part of the clap restructure (they must be touched anyway). Chosen over Option B (leave runtime failures on exit 2, overload preserved) because clap changes the error surface regardless, making this the moment to heal the semantics rather than carry the overload through the rewrite. Note: adopting clap also absorbs several Low-tier deviations for free — `--flag=value` (GNU equals form), the `--` end-of-options terminator, and long-option abbreviation are native clap behaviour, so they need no separate spec work. `-`-as-stdin (a Low deviation) is NOT clap-automatic (it is app logic replacing the `is_file()` gate) and belongs to the deferred machine-first track (Fork B), out of this cycle. All load-bearing forks for this cycle are now settled. Status unchanged: design settled, ready for spec production, blocked on the parallel main-session releasing `aura-cli`.
Author
Owner

Design decisions derived at spec time (specify, 2026-07-01)

Spec docs/specs/0098-cli-clap-gnu-compliance.md written. Beyond the
user-ratified forks (clap / GNU-compliance / exit-code Option A), the spec
resolves three derived design decisions:

  • Dual-grammar → clap mapping. run/sweep/walkforward/mc each carry two
    grammars under one token (the .ends_with(".json") && is_file()
    discriminator, a lockstep quartet at main.rs:4249/4297/4334/4376). Decision:
    one clap args struct per such subcommand with an optional [blueprint]
    positional + the union of both branches' flags, and a post-parse dispatch on
    the same is_file() predicate that selects the branch and validates flag
    applicability (unexpected-branch flags → usage error, exit 2). Derived:
    preserves the exact current surface; the alternative (renaming into distinct
    subcommands, e.g. run-blueprint) would break the surface and the pinned
    tests, contradicting Fork B (convention compliance, not redesign).
  • Exit-code classification = parse-time vs run-time. Usage errors (exit 2):
    clap parse failures + aura's argument-structure validations clap cannot
    express (--from/--to without --real, cost flags without an R-harness,
    generalize's required knobs, dual-grammar flag applicability). Runtime
    failures (exit 1): failures after a valid parse (no local data, no geometry,
    no recorded run/family, tap-not-found, name collision, persist write
    failure). reproduce-diverged stays exit 1 (a semantic verdict = runtime
    class). Derived directly from Option A's semantics.
  • Two-iteration split. Iteration 1 = clap migration + scoped help +
    --version, behaviour-preserving on exit codes (parse errors 2, domain refusals
    stay 2, reproduce 1). Iteration 2 = the exit-code split (domain refusals 2→1,
    normalize the lone generalize persist inconsistency at main.rs:1924 vs
    1878/2268). Derived: the two iterations' test-pin surfaces are largely
    disjoint (parser-path usage pins vs domain-refusal exit pins), so the split is
    clean and iteration 1 ships the scoped-help + version core reviewably before
    the exit-code contract change.

The spec covers the full cycle; iteration 1 (clap migration + scoped help +
version) is the shippable first slice, iteration 2 the exit-code split.

## Design decisions derived at spec time (specify, 2026-07-01) Spec `docs/specs/0098-cli-clap-gnu-compliance.md` written. Beyond the user-ratified forks (clap / GNU-compliance / exit-code Option A), the spec resolves three derived design decisions: - **Dual-grammar → clap mapping.** run/sweep/walkforward/mc each carry two grammars under one token (the `.ends_with(".json") && is_file()` discriminator, a lockstep quartet at main.rs:4249/4297/4334/4376). Decision: one clap args struct per such subcommand with an optional `[blueprint]` positional + the union of both branches' flags, and a post-parse dispatch on the same is_file() predicate that selects the branch and validates flag applicability (unexpected-branch flags → usage error, exit 2). Derived: preserves the exact current surface; the alternative (renaming into distinct subcommands, e.g. `run-blueprint`) would break the surface and the pinned tests, contradicting Fork B (convention compliance, not redesign). - **Exit-code classification = parse-time vs run-time.** Usage errors (exit 2): clap parse failures + aura's argument-structure validations clap cannot express (--from/--to without --real, cost flags without an R-harness, generalize's required knobs, dual-grammar flag applicability). Runtime failures (exit 1): failures after a valid parse (no local data, no geometry, no recorded run/family, tap-not-found, name collision, persist write failure). reproduce-diverged stays exit 1 (a semantic verdict = runtime class). Derived directly from Option A's semantics. - **Two-iteration split.** Iteration 1 = clap migration + scoped help + --version, behaviour-preserving on exit codes (parse errors 2, domain refusals stay 2, reproduce 1). Iteration 2 = the exit-code split (domain refusals 2→1, normalize the lone generalize persist inconsistency at main.rs:1924 vs 1878/2268). Derived: the two iterations' test-pin surfaces are largely disjoint (parser-path usage pins vs domain-refusal exit pins), so the split is clean and iteration 1 ships the scoped-help + version core reviewably before the exit-code contract change. The spec covers the full cycle; iteration 1 (clap migration + scoped help + version) is the shippable first slice, iteration 2 the exit-code split.
Author
Owner

Iteration 1 shipped (clap migration) — 2026-07-01

Iteration 1 of the clap adoption landed (feat(0098), commit 366170a on the cycle
branch): the hand-rolled parser is replaced by a clap derive parser — scoped
<sub> --help, --version/-V, a per-flag Options section, --flag=value, the
-- terminator, and long-option abbreviation. Exit codes behaviour-preserved
(the split is iteration 2). Execution layer untouched; the dual-grammar
subcommands (run/sweep/walkforward/mc) are mapped via an optional [blueprint]
positional + a post-parse is_file() dispatch, each built-in handler re-asserting
a stray-positional guard (pinned by two new E2E tests). Full workspace suite +
clippy green.

Derived decision (orchestrator, post-loop): long-option abbreviation. Spec
acceptance criterion 4 claimed clap absorbs it "for free"; it is actually opt-in
(clap infer_long_args). Two ratified decisions conflict on this low-tier item —
Fork B (GNU compliance; getopt_long abbreviates) vs the LLM/automation-first
weighting (an automation caller never abbreviates; human-only niceties
deprioritized). Resolved to DELIVER, on a cost tiebreaker: enabling it is a
single root attribute that propagates to subcommands (not per-struct work), so
the effort is proportionate and GNU compliance is met exactly as the spec
states. Pinned by a test (--harn -> --harness).

Carry-on: error-message casing is now mixed ("Usage:" from clap beside preserved
lowercase "usage:" aura messages) — a deliberate quality-hold (the casings are
pinned by renegotiated/preserved tests); cosmetic, a candidate for the
iteration-2 exit-code-split cleanup.

Iteration 2 (the exit-code split: domain refusals 2->1, normalize the generalize
persist inconsistency) remains open.

## Iteration 1 shipped (clap migration) — 2026-07-01 Iteration 1 of the clap adoption landed (feat(0098), commit 366170a on the cycle branch): the hand-rolled parser is replaced by a clap derive parser — scoped `<sub> --help`, `--version`/`-V`, a per-flag Options section, `--flag=value`, the `--` terminator, and long-option abbreviation. Exit codes behaviour-preserved (the split is iteration 2). Execution layer untouched; the dual-grammar subcommands (run/sweep/walkforward/mc) are mapped via an optional `[blueprint]` positional + a post-parse is_file() dispatch, each built-in handler re-asserting a stray-positional guard (pinned by two new E2E tests). Full workspace suite + clippy green. Derived decision (orchestrator, post-loop): long-option abbreviation. Spec acceptance criterion 4 claimed clap absorbs it "for free"; it is actually opt-in (clap `infer_long_args`). Two ratified decisions conflict on this low-tier item — Fork B (GNU compliance; getopt_long abbreviates) vs the LLM/automation-first weighting (an automation caller never abbreviates; human-only niceties deprioritized). Resolved to DELIVER, on a cost tiebreaker: enabling it is a single root attribute that propagates to subcommands (not per-struct work), so the effort is proportionate and GNU compliance is met exactly as the spec states. Pinned by a test (`--harn` -> `--harness`). Carry-on: error-message casing is now mixed ("Usage:" from clap beside preserved lowercase "usage:" aura messages) — a deliberate quality-hold (the casings are pinned by renegotiated/preserved tests); cosmetic, a candidate for the iteration-2 exit-code-split cleanup. Iteration 2 (the exit-code split: domain refusals 2->1, normalize the generalize persist inconsistency) remains open.
Author
Owner

Iteration 2 classification decisions (planner, 2026-07-01)

Iteration 2 (the exit-code split) plan written (0099). The spec's
"parse-time vs run-time" rule under-specified 6 boundary cases (surfaced by the
post-migration exit-site recon); resolved via a sharper derived principle:

Attribution principle: exit 2 (usage) = something about the COMMAND-LINE
arguments is wrong or inapplicable — including the content of an argv-named file
(the blueprint path is itself an argument). exit 1 (runtime) = the command line
was well-formed but the ENVIRONMENT / recorded state it needs is missing, or
piped STDIN data is bad.

Boundary resolutions:

  • Malformed / open / wrong-kind blueprint FILE (argv-named .json) -> usage 2
    (the file is an argument; the malformed/open-blueprint pins stay Some(2)).
    Only STDIN op-script content + stdin-read I/O (graph build/introspect) ->
    runtime 1.
  • graph introspect --node <unknown> -> usage 2 (invalid argv flag value).
  • Applicability refusals (walkforward + unsupported strategy, mc + open
    blueprint, macd + --real) -> usage 2 (the command is inapplicable; the user
    fixes the invocation).
  • Unknown / malformed --axis name -> usage 2 (argument fault); a genuine
    mid-run compute fault (window-too-short) -> runtime 1.
  • Invalid --metric / rank-metric value -> usage 2 (invalid argv value); a
    post-run aggregate failure -> runtime 1.

Flips 2->1 (runtime, the spec's explicit list): no local data, no recorded
geometry, no recorded run/family, "run has no tap named", trace-name collision,
family/trace persist-write failures (normalizing the lone generalize
inconsistency -- all persist -> 1 together), missing/corrupt content-addressed
store state (the reproduce path), chart-read failures. reproduce-diverged stays

  1. ~8 domain-refusal test pins flip Some(2)->Some(1) (message substrings
    unchanged); 3 hidden match/guard arms fixed; a partition property test added.

Casing normalization ("Usage:"/"usage:"/bare) deliberately deferred -- cosmetic,
high pin-churn, no functional gain; left for a future polish issue.

## Iteration 2 classification decisions (planner, 2026-07-01) Iteration 2 (the exit-code split) plan written (0099). The spec's "parse-time vs run-time" rule under-specified 6 boundary cases (surfaced by the post-migration exit-site recon); resolved via a sharper derived principle: **Attribution principle:** exit 2 (usage) = something about the COMMAND-LINE arguments is wrong or inapplicable — including the content of an argv-named file (the blueprint path is itself an argument). exit 1 (runtime) = the command line was well-formed but the ENVIRONMENT / recorded state it needs is missing, or piped STDIN data is bad. Boundary resolutions: - Malformed / open / wrong-kind blueprint FILE (argv-named .json) -> usage 2 (the file is an argument; the malformed/open-blueprint pins stay Some(2)). Only STDIN op-script content + stdin-read I/O (graph build/introspect) -> runtime 1. - `graph introspect --node <unknown>` -> usage 2 (invalid argv flag value). - Applicability refusals (walkforward + unsupported strategy, mc + open blueprint, macd + --real) -> usage 2 (the command is inapplicable; the user fixes the invocation). - Unknown / malformed `--axis` name -> usage 2 (argument fault); a genuine mid-run compute fault (window-too-short) -> runtime 1. - Invalid `--metric` / rank-metric value -> usage 2 (invalid argv value); a post-run aggregate failure -> runtime 1. Flips 2->1 (runtime, the spec's explicit list): no local data, no recorded geometry, no recorded run/family, "run has no tap named", trace-name collision, family/trace persist-write failures (normalizing the lone generalize inconsistency -- all persist -> 1 together), missing/corrupt content-addressed store state (the reproduce path), chart-read failures. reproduce-diverged stays 1. ~8 domain-refusal test pins flip Some(2)->Some(1) (message substrings unchanged); 3 hidden match/guard arms fixed; a partition property test added. Casing normalization ("Usage:"/"usage:"/bare) deliberately deferred -- cosmetic, high pin-churn, no functional gain; left for a future polish issue.
Author
Owner

Cycle closed — drift-clean (audit 0099), 2026-07-01

Both iterations of the clap adoption shipped and the cycle-close audit is
drift-clean:

  • iter 1 (clap migration, 366170a): scoped aura <sub> --help,
    --version/-V, a per-flag Options section, --flag=value / -- /
    long-option abbreviation.
  • iter 2 (exit-code split, fa42bf3): the usage=2 / runtime=1 partition
    (attribution principle), the lone generalize persist-inconsistency normalized.
  • audit (27f0f8e): architect drift-clean — invariant 8 / C16 engine-domain
    separation preserved (clap confined to aura-cli, a leaf binary the frozen
    artifact cannot pick up); C14 automation-face served, execution layer
    untouched. The exit-code partition + clap admission were lifted into the C14
    ledger realization note before the ephemeral spec was removed.

Deferred forward: #179 (error-message casing normalization, idea); the
machine-first help surface stays on the #157/C21 track (Fork B settled this
cycle as human/GNU convention compliance).

The six commits sit on the worktree-cli-help branch, unpushed (the /boss run
does not push autonomously). This issue closes automatically on push/merge (the
audit commit carries closes #175); until then it stays open pending review.

## Cycle closed — drift-clean (audit 0099), 2026-07-01 Both iterations of the clap adoption shipped and the cycle-close audit is drift-clean: - **iter 1** (clap migration, 366170a): scoped `aura <sub> --help`, `--version`/`-V`, a per-flag Options section, `--flag=value` / `--` / long-option abbreviation. - **iter 2** (exit-code split, fa42bf3): the usage=2 / runtime=1 partition (attribution principle), the lone generalize persist-inconsistency normalized. - **audit** (27f0f8e): architect drift-clean — invariant 8 / C16 engine-domain separation preserved (clap confined to `aura-cli`, a leaf binary the frozen artifact cannot pick up); C14 automation-face served, execution layer untouched. The exit-code partition + clap admission were lifted into the C14 ledger realization note before the ephemeral spec was removed. Deferred forward: #179 (error-message casing normalization, `idea`); the machine-first help surface stays on the #157/C21 track (Fork B settled this cycle as human/GNU convention compliance). The six commits sit on the `worktree-cli-help` branch, unpushed (the `/boss` run does not push autonomously). This issue closes automatically on push/merge (the audit commit carries `closes #175`); until then it stays open pending review.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Brummel/Aura#175