# G-053 punk::args range-valued -multiple: occurrence arity with strict duplicate handling Status: proposed Scope: src/modules/punk/args-999999.0a1.0.tm (spec compiler, parse, arg_error/synopsis renderers), src/tests/modules/punk/args/testsuites/args/ Goal: -multiple accepts a {min max} occurrence range (mirroring -choicemultiple; max -1 unbounded) alongside the legacy booleans - so a definition can declare "at most once, repeat is an error" ({0 1}) or bounded repetition ({2 4}) instead of choosing between silent last-wins (0) and unbounded collection (1) - with boolean semantics preserved exactly, including the prepend-defaults/last-wins override idiom. Acceptance: parse raises a usage-style arity error naming the argument for occurrences outside a declared range; boolean -multiple 0/1 behaviour is unchanged (full existing punk::args suite passes untouched); the -optional/range-min reconciliation rule is documented and enforced at define time; the usage table Multi column and synopsis reflect declared ranges; -multipleunique/-multipleuniqueset compose with max>1 ranges unchanged; characterization tests cover the new forms and the value-shape rule. ## Context Boolean `-multiple` conflates three axes: 1. **occurrence arity** - how many times the argument may be supplied 2. **overflow policy** - what happens beyond the limit: silent replace (legacy `-multiple 0` last-wins) or error 3. **value shape** - scalar vs list-of-occurrences The motivating incident (2026-07-10): runtests.tcl's `-include-paths` was a non-multiple list option, so repeated `-include-paths` flags silently last-won - quietly narrowing a test run while reporting green, and defeating even a recorded memory note about the gotcha. Repeatable accumulating flags are the dominant convention in shell-facing CLIs (gcc -I, curl -H, rsync --exclude), so this misuse recurs. runtests was fixed by making that option `-multiple 1`, but the general fix for "repeat should be an error" has no expression today. A separate `-duplicates deny|replace` policy flag was considered and rejected: with `-multiple 1` a duplicates policy is meaningless (duplicates ARE the collected payload), so the flag's validity would depend on another flag's setting, and it would blur into the existing `-multipleunique`/`-multipleuniqueset` territory. punk::args already has the shape precedent in its own vocabulary: `-choicemultiple` is a `{min max}` pair, not a boolean. ## Approach `-multiple` accepts a boolean (legacy, semantics preserved exactly) or a `{min max}` range (max -1 = unbounded): - `-multiple 0` - legacy: single-valued, repeats silently replace (last wins), scalar shape. Unchanged - this preserves the prepend-defaults override idiom `punk::args::parse [list -flag default {*}$userargs]`. - `-multiple 1` - legacy: unbounded collection, list shape. Unchanged. - `-multiple {0 1}` - at most once; a second occurrence is a parse (arity) error. This is the "duplicates deny" case. - `-multiple {2 4}`, `{1 -1}` etc - bounded/lower-bounded repetition, collected. Design decisions to settle (record here when made): - **Value shape for range forms**: lean - `max == 1` forms stay scalar (they are strict single-valued variants of legacy 0); `max > 1` or `-1` yield the occurrence list. Whatever is chosen must be pinned by the characterization tests. - **-optional vs range-min reconciliation**: lean - `-optional` governs presence, the range governs occurrence count when present; contradictory combinations (e.g. range min >= 1 with -optional 1 intended as "required") rejected at define time with a clear message. - **Hot-path canonicalization**: `-multiple` is truth-tested in many parse/render sites; a raw `"0 1"` value would fail expr boolean coercion. The spec compiler should canonicalize once into internal min/max/policy fields (alongside the existing ARG_INFO/ARG_CHECKS structures) so runtime checks stay cheap. Display benefits: the usage table's Multi column and the synopsis `?arg...?` rendering gain meaningful bounded-repetition forms (e.g. "0-1", "2-4"). ## Notes - `-multipleunique` / `-multipleuniqueset` remain the uniqueness knobs and only make sense for max > 1; they compose with ranges unchanged. - Related: G-045 (authoring ergonomics), G-046 (parse-time performance - the canonicalization must not regress the hot path). - The runtests `-include-paths` fix (repeatable, accumulate, single-list form still accepted) shipped independently on 2026-07-10 and does not depend on this goal. - Archived-goal references in this file: G-046 achieved 2026-07-10 (goals/archive/G-046-punkargs-deferred-help-and-fixes.md).