Scaffolder command-authoring parity¶
Status: APPROVED — two related GTB-parity gaps from the v0.22.0 audit
(Phase 2, §A). Both are scaffolder command-authoring polish with no open
decisions. Lifts the explicit deferral in
commands/generate/command.rs:14 and mirrors GTB
(internal/cmd/generate/command.go, internal/generator/validate.go).
Part 1 — richer generate command metadata¶
rtb generate command today accepts only <name> [--about TEXT] and a
single flat segment (command.rs:1-20 documents the deferral). GTB
supports the full command metadata. Add, mirroring the already-shipped
generate flag --shorthand (flag.rs:40):
rtb generate command <name> [--about TEXT] [--long TEXT]
[--short <char>] [--aliases a,b,c]
[--parent <command-path>] [--args <spec>]
--short <char>→.short_flag('x')/ single-char alias (validated as one ASCII alnum, likeflag --shorthand).--aliases a,b,c→CommandSpec::aliases(each validated as a command name; deduped; not colliding with reserved names or the primary).--parent <command-path>→ nested command (the deferred multi- segment path); validated segment-by-segment (validate.rsalready allows reserved words in nested position,:246), depth-capped atMAX_COMMAND_PATH_DEPTH.--long TEXT→ long help;--args <spec>→ positional/arg scaffolding (a simplename:typelist rendered into the generated command's clap derive; richer arg modelling stays incremental).- These thread into the generated command's marker-region render +
manifest
commands:entry so regenerate preserves them.
Part 2 — Rust-keyword command-name rejection¶
GTB rejects Go keywords as command names (validate.go:117,
token.IsKeyword) because a command name becomes a generated module/ident.
RTB's validate.rs has RESERVED_NAMES (framework + scaffolder verbs +
help) but no Rust-keyword guard — a command named match, move,
use, fn, type, impl, etc. would collide with generated identifiers.
Add a RUST_KEYWORDS set (strict + reserved + 2018 keywords) and reject a
top-level command name that is a Rust keyword, with a clear diagnostic
naming the collision. (Nested segments map to path strings, not idents, so
the guard is top-level-name-scoped, consistent with the existing
reserved-name rule at validate.rs:151.)
strict: as break const continue crate else enum extern false fn for if
impl in let loop match mod move mut pub ref return self Self
static struct super trait true type unsafe use where while
reserved: abstract become box do final macro override priv typeof unsized
virtual yield try
2018: async await dyn
Testing (TDD)¶
- Unit (Part 1):
--shortone-char validation;--aliasesparse/dedup/ reserved-collision reject;--parentnested-path validation + depth cap; generated render contains.short_flag/aliases/nested module; manifest round-trips the metadata. - Unit (Part 2): each Rust keyword rejected as a top-level name with the keyword-collision diagnostic; the same word accepted as a nested segment; a non-keyword reserved word still rejected by the existing rule.
- E2E (
assert_cmd):generate command deploy --short d --aliases dep --parent clusterproduces a nestedcluster deploycommand thatcargo checks and exposes the alias;generate command matchis rejected.
Out of scope¶
- Full positional/flag arg modelling beyond a simple
name:typelist (incremental). - Reserved-word handling for flags (flags already validated separately).