Skip to content

fix: steer a flag written before its subcommand and document the import-event tie-break - #90

Merged
hdkiller merged 2 commits into
developfrom
codex/flag-order-steer
Sep 10, 2026
Merged

hdkiller merged 2 commits into
developfrom
codex/flag-order-steer

Conversation

@hdkiller

Copy link
Copy Markdown
Contributor

Summary

Two first-run findings. A long flag written before the subcommand that takes it is now told where it belongs instead of being refused as merely unexpected, and receipt add's tie-break for import events that share an imported_at is written down.

--archive and --json are defined per subcommand rather than globally, so openpapir --archive <root> case list and openpapir --json capabilities are the likeliest flag-order mistakes, and the parser on its own said no more than that the token was unexpected.

What changed

  • crates/openpapir-cli/src/usage.rs: the usage walker now classifies the rejected argument rather than only filtering it. A long flag the parser called unexpected counts as misplaced when either it was written before the subcommand the walk went on to recognise and that command defines it, or no recognised command defines it while a command below the deepest one does. The walk records the index of the first recognised subcommand so "before the subcommand" is a fact about the raw arguments rather than a guess. Both forms then say the argument belongs after the subcommand: the JSON form as the refusal's message, with the new details.placement value after_subcommand beside details.argument; the human form as one line after the parser's own usage text.
  • The name comes from the command definition, so only a name this build declares is ever echoed; the value written beside the flag never reaches either form. The envelope's shape, its usage.arguments code, its command and the exit code 2 are unchanged. A flag written where a flag belongs and refused anyway is unaffected: no placement, and when only another subcommand defines it, no argument either.
  • crates/openpapir-cli/tests/contract.rs: two new tests pin the steer, one per output form, over five invocations including --archive=<root> and a flag written before a nested subcommand. Both assert a marker planted in the caller's value never appears. One existing case moved: --json -- import --archive now carries argument json, because --json really was written before the subcommand, and its place in the "never echoed" table is taken by case list --title x, a flag another subcommand defines written where a flag belongs.
  • crates/openpapir-cli/src/usage.rs unit tests: the subtree lookup and the boundary each get a test.
  • crates/openpapir-cli/tests/property.rs: the privacy assertion is untouched. Added alongside it: details.placement, when present, holds the one value the contract defines and names an argument.
  • crates/openpapir-cli/tests/golden.rs: the two usage cases moved into their own usage_cases() so archive_cases() stays under the line limit; no case changed.
  • docs/architecture.md: the receipt add section now states that imported_at is recorded to the second, so two imports of the same bytes inside one second tie; the tie goes to the lowest identifier, which is deterministic but is not necessarily the earlier import or the one an earlier import reported, and --import-event is the only way to name one explicitly. Behaviour is unchanged. The usage-refusal section documents the steer.
  • docs/error-contract.md: placement added to the details key list with its single value, and the usage.arguments entry rewritten for the new case.
  • CHANGELOG.md: two bullets at the top of Fixed.

Goldens changed

  • Added tests/golden/usage.flag-order/ (--archive <root> case list), with its row in tests/golden/README.md. Neither the human nor the JSON form holds the archive root. No existing golden changed.

Verification

  • ./scripts/check.sh && cargo build --release --locked && cargo run --locked -p openpapir-cli -- capabilities --json, all green.
  • cargo clippy --workspace --all-targets --locked --target x86_64-pc-windows-msvc -- -D warnings, clean.
  • cargo +1.88 check --workspace --all-targets --locked, clean.
  • cargo llvm-cov --workspace --locked --fail-under-lines 90: 97.31% lines.
  • Rebased onto origin/develop immediately before pushing.

Notes and exceptions

  • docs/guide.md is deliberately untouched: another change is regenerating it. Its step 6 sentence, which teaches that the identifier import printed is the one receipt add echoes, may want the tie-break note once that regeneration lands.
  • crates/openpapir-cli/skills/openpapir/SKILL.md is deliberately untouched. Its description of the parser refusal stays accurate, and every example in it already writes the flag after the subcommand.

@hdkiller
hdkiller merged commit 26edaa2 into develop Sep 10, 2026
14 checks passed
@hdkiller
hdkiller deleted the codex/flag-order-steer branch September 10, 2026 14:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant