-multiple now accepts a {min max} range (max -1 = unbounded) alongside the
legacy booleans 0/1, so a definition can declare at most once ({0 1}, a second
occurrence is a parse error), bounded repetition ({2 4}), or one-or-more
({1 -1}) instead of choosing between silent last-wins (0) and unbounded
collection (1).
The spec compiler canonicalises once at resolve: the stored -multiple becomes
the computed boolean (list-shape collect: true for max>1 or -1, false for
legacy 0 and max==1) so every existing collect-vs-replace / scalar-vs-list /
leader-value-single-multiple hot-path truth-test stays correct, and the range
companions (min/max/maxbounded) live in a separate per-form MULTIPLE_RANGES
dict - NOT in ARG_INFO, so they do not ride along when ARG_INFO is round-tripped
as a spec via resolved_def copyfrom. Legacy 0/1 and boolean strings
(true/false/yes/no) are coerced to the boolean and stay byte-unchanged (no
MULTIPLE_RANGES entry for unlimited cases, so {0 -1} is equivalent to legacy 1).
Resolve validation: max positive or -1, min <= max, and the -optional/range-min
reconciliation (non-zero min forces presence, contradicts -optional -> reject
with a clear message; declare -optional 0). The value-shape rule: max==1 forms
stay scalar, max>1 or -1 yield the occurrence list.
Parse enforcement: a new PUNKARGS VALIDATION occurrencecount failure class
(payload <argname> count <n> min <m> | max <m>) fires in a single post-loop pass
per section (opts/leaders/values) via a private::multiple_range_enforce
helper. Over-max is a hard contradiction (fires in both normal and
viability-probe modes; parse_status_classify maps it to invalid); under-min is
pure end-of-input exhaustion (SUPPRESSED in the G-152 viability probe via the
viabilitycheck arg, classified incomplete so a viable form reports incomplete
not invalid). Legacy -multiple 1 required with 0 occurrences still reports
trailingvaluecount (byte-unchanged, not occurrencecount).
Rendering: the usage-table Multi column reflects the range (0-1 / 2-4 / 1+ for
unbounded-with-floor; the greencheck stays for legacy 1), the string renderer
emits MULTI:0-1 etc., and the synopsis distinguishes at-most-once (?arg?, no
ellipsis) from repeating (arg...). -multipleunique/-multipleuniqueset compose
with max>1 ranges unchanged. define -help documents the range form and the
-optional/range-min rule.
New testsuite multipleranges.test (28 tests: 13 define-time
canonicalisation+validation, 12 parse-time enforcement incl parse_status
verdicts and a legacy-required-still-trailingvaluecount guard, 3 rendering).
Legacy untouched by default confirmed: full punk/args suite 399/0 (3 skipped),
punk/ns 125/125. All G-053 acceptance criteria met; goal flipped to achieved
2026-08-08 and archived (detail -> goals/archive/, index -> GOALS-archive.md,
reference sweep applied to G-072/G-084, deferred G-084 cross-member -multiple
collection decision recorded as a Follow-on => goal G-084).
Project version 0.59.0 -> 0.60.0 (minor: backward-compatible behaviour
addition). punk::args module 0.21.0 -> 0.22.0.
Assisted-by: harness=pi; primary-model=huggingface/zai-org/GLM-5.2; api-location=huggingface.co