Browse Source

G-049..G-053 proposed: parse-status model, synopsis marking, cmdinfo doconly+prefix parity, oo method autodef, range-valued -multiple

Five user-approved proposed goals with detail files, grounded in the 2026-07-10
characterization work (GAP pins in usagemarking.test/cmdhelp.test map to these):

- G-049 punk::args parse-status data model with machine-parsable cmdhelp returns:
  documented per-argument status structure consumed by both arg_error renderers,
  cmdhelp -return dict; fixes badarg coverage (type/allocation failures), scheme
  honoured on failure path, caller attribution, and the stateful shared
  colour-array scheme handling (nocolour fallthrough + dash-spelling leak).
- G-050 synopsis argument-validity marking and status-aware returns: 's' gains the
  goodarg/badarg marking via the G-049 structure; replaces the curried-alias
  excess-args length arithmetic with a parse against the resolved definition.
- G-051 cmdinfo truthful cmdtype for doc-only pseudo-commands + space-form docid
  prefix parity via the shared choiceword_match resolver ('i string is tr').
- G-052 TclOO method-level autodef: undocumented methods get an (autodef) from the
  introspected parameter list instead of the class-summary-only fallback.
- G-053 punk::args range-valued -multiple: {min max} occurrence ranges alongside
  the legacy booleans, expressing "at most once, repeat is an error" without a
  separate duplicates flag (mirrors the -choicemultiple precedent; motivated by
  the runtests -include-paths last-wins incident).

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
ae72dffd91
  1. 30
      GOALS.md
  2. 80
      goals/G-049-punkargs-parse-status-model.md
  3. 44
      goals/G-050-synopsis-validity-marking.md
  4. 54
      goals/G-051-cmdinfo-pseudo-and-prefix.md
  5. 54
      goals/G-052-oo-method-autodef.md
  6. 67
      goals/G-053-punkargs-multiple-ranges.md

30
GOALS.md

@ -338,3 +338,33 @@ Scope: src/modules/textblock-999999.0a1.0.tm (textblock::table proc + PUNKARGS,
Detail: goals/G-048-textblock-table-punkargs.md Detail: goals/G-048-textblock-table-punkargs.md
Goal: textblock::table parses its arguments via punk::args::parse (replacing the unvalidated dict merge at L6397), with its PUNKARGS definition covering both table-wrapper-specific options (-return, -rows, -headers) and the constructor passthrough options - the latter sourced by referencing punk::args definition blocks authored inline on the textblock::class::table class methods (the pattern used for render_to_input_line and rendertest in punk::ansi::class::class_ansi: lappend PUNKARGS [list { @id ... }] immediately before the method, punk::args::parse $args withid "..." inside the method body), so the constructor itself parses via punk::args and its documented option set is the single source of truth that textblock::table's PUNKARGS references rather than a parallel hand-typed list - retiring the "more options available - argument definition is incomplete" caveat and closing the -return -choiceprefix documentation/parsing mismatch (item #8 from the punk::args -choices audit) as a side effect. Goal: textblock::table parses its arguments via punk::args::parse (replacing the unvalidated dict merge at L6397), with its PUNKARGS definition covering both table-wrapper-specific options (-return, -rows, -headers) and the constructor passthrough options - the latter sourced by referencing punk::args definition blocks authored inline on the textblock::class::table class methods (the pattern used for render_to_input_line and rendertest in punk::ansi::class::class_ansi: lappend PUNKARGS [list { @id ... }] immediately before the method, punk::args::parse $args withid "..." inside the method body), so the constructor itself parses via punk::args and its documented option set is the single source of truth that textblock::table's PUNKARGS references rather than a parallel hand-typed list - retiring the "more options available - argument definition is incomplete" caveat and closing the -return -choiceprefix documentation/parsing mismatch (item #8 from the punk::args -choices audit) as a side effect.
Acceptance: textblock::class::table's constructor carries an inline punk::args define block (lappend PUNKARGS, @id naming the class+method) and parses its args via punk::args::parse withid, replacing the current manual switch at L466; textblock::table's PUNKARGS references the constructor's documented options (via @id reference, a shared fragment, or a documented include mechanism - the chosen mechanism recorded in the detail file) so table's definition is complete without hand-duplicating the constructor's option list; an invalid -return value (e.g. -return tab) produces a punk::args usage error instead of silently passing through; valid inputs reach textblock::class::table new with behaviour parity to the current dict merge; the -choiceprefix 0 on -return is honoured by construction (punk::args::parse enforces it); the "NOTE: more options available - argument definition is incomplete" comment is removed; existing textblock test suites pass. Acceptance: textblock::class::table's constructor carries an inline punk::args define block (lappend PUNKARGS, @id naming the class+method) and parses its args via punk::args::parse withid, replacing the current manual switch at L466; textblock::table's PUNKARGS references the constructor's documented options (via @id reference, a shared fragment, or a documented include mechanism - the chosen mechanism recorded in the detail file) so table's definition is complete without hand-duplicating the constructor's option list; an invalid -return value (e.g. -return tab) produces a punk::args usage error instead of silently passing through; valid inputs reach textblock::class::table new with behaviour parity to the current dict merge; the -choiceprefix 0 on -return is honoured by construction (punk::args::parse enforces it); the "NOTE: more options available - argument definition is incomplete" comment is removed; existing textblock test suites pass.
### G-049 [proposed] punk::args parse-status data model with machine-parsable cmdhelp returns
Scope: src/modules/punk/args-999999.0a1.0.tm (arg_error, parse error dispatch, colour-scheme handling), src/modules/punk/ns-999999.0a1.0.tm (cmdhelp), src/tests/modules/punk/args/testsuites/args/usagemarking.test, src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test
Detail: goals/G-049-punkargs-parse-status-model.md
Goal: the information behind usage-display argument marking (which supplied arguments validated, which argument failed and why, which scheme applies) exists as a documented parse-status structure produced from a parse attempt and consumed by both arg_error renderers, and punk::ns::cmdhelp can return it machine-parsably via -return dict - replacing the transient goodargs/badarg locals, the badarg gaps in non-choice validation failures, and the stateful shared colour-array scheme handling.
Acceptance: cmdhelp -return dict distinguishes an incomplete, a fully-valid and an invalid argument set via per-argument statuses (received/ok/bad + overall scheme/message/form) with the structure documented; the table and string renderers derive their marking from that same structure, with rendered output unchanged except where the pinned GAP tests flip: badarg marking covers type/allocation failures not just choice violations (cmdhelp_GAP_no_badarg_marking_for_failed_typed_value), an explicit -scheme is honoured on the parse-failure path (cmdhelp_GAP_explicit_scheme_ignored_on_failure), the failure message names the queried command instead of cmdhelp's internal parse source line (cmdhelp_GAP_errormsg_leaks_internal_source), and scheme rendering no longer depends on or mutates shared colour state - the documented -scheme choice value 'nocolour' takes effect and repeated renders of the same call are identical regardless of prior scheme renders (usagemarking_GAP_scheme_nocolour_renders_with_leftover_colours, usagemarking_GAP_dash_nocolour_leaks_into_shared_array, usagemarking_GAP_dash_nocolour_leak_affects_later_info_render); all non-GAP characterization tests in usagemarking.test and cmdhelp.test pass unchanged.
### G-050 [proposed] punk::ns::synopsis argument-validity marking and status-aware returns
Scope: src/modules/punk/ns-999999.0a1.0.tm (synopsis), src/modules/punk/args-999999.0a1.0.tm (synopsis renderer), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (synopsis pins)
Detail: goals/G-050-synopsis-validity-marking.md
Goal: synopsis ('s') marks supplied argument words for validity the way cmdhelp's usage display does (goodarg/badarg styles via the G-049 parse-status structure), offers the same status machine-parsably in its dict return, and replaces the curried-alias excess-args length arithmetic (the 's pse' REVIEW note) with a parse against the resolved definition.
Acceptance: 's <cmd> <valid args>' renders the supplied words marked as validated and 's <cmd> <invalid args>' marks the offending word, while output with no argument words supplied is unchanged (the synopsis_GAP_no_argument_validity_marking pin flips); the dict return carries per-argument status; the curried-alias substitution behaviour (synopsis_curried_alias_shows_braced_target pin) is resolved to a documented presentation derived from parsing rather than list-length arithmetic; existing synopsis output tests (args testsuite synopsis.test and test::punk::args) pass; depends on G-049 for the status structure; G-044's completion/hint display is a consumer of the same status API (cross-reference, not a dependency).
### G-051 [proposed] cmdinfo truthful cmdtype for doc-only pseudo-commands and space-form docid prefix parity
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo, cmd_traverse), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test, src/tests/modules/punk/ns/testsuites/ns/cmdflow.test
Detail: goals/G-051-cmdinfo-pseudo-and-prefix.md
Goal: cmdinfo reports a distinct cmdtype (e.g 'doconly') when resolution lands on a punk::args id with no corresponding real command instead of today's 'notfound', and the space-delimited docid jump accepts the same word prefixes the parser accepts - via the shared punk::args::choiceword_match resolver, not a second matching rule - so 'i string is tr' documents what 'string is tr' actually executes.
Acceptance: the pinned GAP tests flip: cmdhelp_GAP_pseudo_command_cmdtype_notfound and cmdhelp_GAP_string_is_true_pseudo report the new cmdtype with the docid unchanged, cmdhelp_GAP_spaceform_docid_prefix_not_honoured and cmdhelp_GAP_string_is_prefix_not_honoured resolve the child docid from a prefix exactly when parse accepts that prefix (honouring -choiceprefix, -choiceprefixdenylist, -choiceprefixreservelist and -choicealiases per G-040 parity); consumers of cmdinfo's cmdtype (cmdhelp, synopsis, eg) handle the new value with no behaviour change for real commands; cmdflow.test and the non-GAP cmdhelp.test tests pass unchanged.
### G-052 [proposed] TclOO method-level autodef documentation
Scope: src/modules/punk/ns-999999.0a1.0.tm (generate_autodef oo branches, cmd_traverse), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test
Detail: goals/G-052-oo-method-autodef.md
Goal: an undocumented method on a tcl::oo object or class gets an (autodef) punk::args definition generated from its introspected parameter list (info object call + method definition arglists - the machinery generate_autodef already uses for its class summary), so 'i $obj <method> <args...>' shows method-level usage with good/bad argument marking instead of only the class summary with the method word highlighted - explicitly documented methods continue to win.
Acceptance: the cmdhelp_GAP_oo_undocumented_method_class_summary_only pin flips: cmdinfo on an undocumented method resolves a method-level (autodef) docid with the trailing words in args_remaining, and bogus trailing arguments render error-scheme output instead of an info-scheme class summary; documented-method behaviour (cmdhelp_oo_documented_method) is unchanged; instance methods, class-defined methods and mixin/superclass-inherited methods resolve (constructor/'new' signatures at minimum characterized, in-scope or explicitly deferred in the detail file); the "-choiceprefix 0 ... methods must be specified in full always? - review" question on the class-summary method choicelist is resolved and documented either way; cmdflow.test and cmdhelp.test pass.
### G-053 [proposed] punk::args range-valued -multiple: occurrence arity with strict duplicate handling
Scope: src/modules/punk/args-999999.0a1.0.tm (spec compiler, parse, arg_error/synopsis renderers), src/tests/modules/punk/args/testsuites/args/
Detail: goals/G-053-punkargs-multiple-ranges.md
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.

80
goals/G-049-punkargs-parse-status-model.md

@ -0,0 +1,80 @@
# G-049 punk::args parse-status data model with machine-parsable cmdhelp returns
Status: proposed
Scope: src/modules/punk/args-999999.0a1.0.tm (arg_error, parse error dispatch, colour-scheme handling), src/modules/punk/ns-999999.0a1.0.tm (cmdhelp), src/tests/modules/punk/args/testsuites/args/usagemarking.test, src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test
Acceptance: see GOALS.md index entry (canonical).
## Context
The interactive usage display ('i <cmd> <args...>' via punk::ns::cmdhelp) marks supplied
arguments for validity: a fully-valid argument set renders with the info scheme (grey
border) and received arguments struck through in green; a failed parse renders with the
error scheme (yellow border) and - for some failure classes - the offending argument in
red. This behaviour was characterized 2026-07-10 in usagemarking.test (arg_error
primitives) and cmdhelp.test (integration), and several defects were pinned as GAP tests:
- **badarg coverage is uneven**: only `choiceviolation` failures populate `-badarg` in the
validation error dispatch, so only choice violations get the red row marking. A value
failing its `-type` check surfaces as `missingrequiredvalue` (the value is never
assigned, so allocation fails first) with NO badarg - 'i cmd v1 x x' with int-typed
values names the failing argument in the message but marks nothing.
(cmdhelp_GAP_no_badarg_marking_for_failed_typed_value)
- **explicit -scheme is ignored on the failure path**: cmdhelp passes -scheme through to
arg_error only on the success path and the tableobject failure branch; for
-return table/string failures it returns the parse error message rendered with the
default error scheme. (cmdhelp_GAP_explicit_scheme_ignored_on_failure)
- **caller attribution leaks internals**: at top call depth (the interactive case) the
failure message names cmdhelp's own parse call as raw source text
(`punk::args::parse $args_remaining -cache 0 ...`) instead of the queried command;
from nested contexts it attributes correctly. (cmdhelp_GAP_errormsg_leaks_internal_source)
- **scheme colour handling is stateful**: arg_error's scheme normalization switch matches
"", "-nocolor", "-nocolour" (with dash) for the nocolour arm, so the DOCUMENTED choice
value `nocolour` falls through to the "na" scheme - no override merge - and renders
with whatever colours the most recent info/error render left in the shared
arg_error_CLR array. The dash spelling does merge, and because scheme merges write the
shared array in place, its strike-only goodarg/badarg overrides then leak into all
subsequent renders until a colour on/off flip forces an array reload.
(usagemarking_GAP_scheme_nocolour_renders_with_leftover_colours,
usagemarking_GAP_dash_nocolour_leaks_into_shared_array,
usagemarking_GAP_dash_nocolour_leak_affects_later_info_render)
Meanwhile the good/bad/scheme information itself exists only as transient locals inside
arg_error (goodargs list derived from -parsedargs 'received', badarg string, scheme name)
and as colour in the output. Nothing machine-readable reports "these supplied words
validated, this one failed, for this reason". cmdhelp's -return choices are
string/table/tableobject only.
## Approach
1. Define a parse-status structure (dict) produced from a parse attempt against a
definition: per-argument entries (name, received value/position, status
ok|bad|unparsed, failure class when bad) plus overall fields (scheme, message, form).
Success and failure of punk::args::parse both map onto it (failure via the PUNKARGS
VALIDATION errorcode payload - which should carry -badarg for type/allocation
failures, not just choice violations).
2. Make arg_error's table and string renderers consume the structure (replacing the
goodargs/badarg locals), resolving colours per-render from the scheme arrays instead
of merging into the shared array - fixing the nocolour choice and the leakage as a
side effect.
3. Add `-return dict` to punk::ns::cmdhelp returning the structure alongside resolution
info (origin/docid/cmdtype from cmdinfo), and pass the explicit -scheme through the
failure path.
4. Fix caller attribution for the usage-display path (the message should name the
queried command; related to the G-046 'Bad number of leading values...' rewording
acceptance item - coordinate, don't duplicate).
## Notes
- G-050 (synopsis marking) and G-044 (repl completion/hinting) consume this structure;
land the structure first.
- Characterization baseline: usagemarking.test (14 tests) and cmdhelp.test (19 tests),
both green on Tcl 9.0.3 and 8.7 as of 2026-07-10. GAP-prefixed tests pin the defects
above and flip with this goal.
- Preserved surface includes the goodchoice highlighting (reverse video on the choice
word matching an argument's value-in-effect - supplied choice for received arguments,
default choice for arguments the parse filled from -default; only when a parse result
is supplied). The status structure should carry value-in-effect per argument so the
renderers keep this without reaching back into the raw parse result.
- The scheme discrimination trick used by the tests (info border SGR params are the only
scheme-unique style; error border shares params with info title) is itself a hint that
scheme styling deserves a small documented palette rather than ad-hoc per-key overrides.

44
goals/G-050-synopsis-validity-marking.md

@ -0,0 +1,44 @@
# G-050 punk::ns::synopsis argument-validity marking and status-aware returns
Status: proposed
Scope: src/modules/punk/ns-999999.0a1.0.tm (synopsis), src/modules/punk/args-999999.0a1.0.tm (synopsis renderer), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (synopsis pins)
Acceptance: see GOALS.md index entry (canonical).
## Context
'i <cmd> <args...>' (cmdhelp) marks supplied argument words for validity; 's <cmd>
<args...>' (synopsis) does not - its output is identical whether the trailing words are
valid, invalid or absent (pinned 2026-07-10: synopsis_GAP_no_argument_validity_marking).
The user-facing intent is parity: the synopsis line(s) should show which supplied words
validated (goodarg style) and where the first problem is (badarg style), with the same
info/error scheme signalling as the usage table.
Separately, synopsis's handling of curried aliases is length-arithmetic based: the
`excess` computation (ns-999999.0a1.0.tm, "This isn't quite right.. e.g see: s pse"
REVIEW comment) counts args_remaining vs supplied words and lreplaces that many leading
synopsis words with resolved_args. For a curried alias the substituted command renders
as the braced alias target (e.g `{::ns::realcmd curriedarg} ...`) rather than the alias
name (pinned: synopsis_curried_alias_shows_braced_target).
## Approach
Consume the G-049 parse-status structure: parse the trailing words against the resolved
definition (same docid resolution as cmdhelp via cmdinfo), then render the synopsis with
per-word marking derived from the status, and expose the status in the dict return.
Replacing the excess-args arithmetic with an actual parse against the resolved definition
is the same change - the parse knows how many words the definition consumed and how the
curried words map onto leaders.
Also worth addressing while in there: the existing in-source note that treating the
synopsis line as a Tcl list for lreplace is risky with ANSI content ("todo - consider
always using 'punk::args::synopsis -return dict' and operating on that list to rebuild
string").
## Notes
- Depends on G-049 (status structure). G-044's hint display is a future consumer of the
same API - keep the marking function reusable outside synopsis/cmdhelp rendering.
- Existing string-shape tests: args testsuite synopsis.test and the installed
test::punk::args synopsis tests ("must be no leading space" convention) - marking must
be ANSI-only so ansistripped output stays stable, or those tests get a documented
update.

54
goals/G-051-cmdinfo-pseudo-and-prefix.md

@ -0,0 +1,54 @@
# G-051 cmdinfo truthful cmdtype for doc-only pseudo-commands and space-form docid prefix parity
Status: proposed
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo, cmd_traverse), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test, src/tests/modules/punk/ns/testsuites/ns/cmdflow.test
Acceptance: see GOALS.md index entry (canonical).
## Context
Two related defects in the doc-lookup walk, characterized 2026-07-10:
**Pseudo-command cmdtype.** A space-delimited punk::args id can document a "command"
below the real implementation level - e.g. `::tcl::string::is true` documents a level
below the `::tcl::string::is` proc (the class words are arguments, not subcommands).
cmdinfo resolves such docids correctly but reports `cmdtype notfound` because cmdwhich
finds no real command at the multiword path (cmdinfo's final cmdwhich is on the
space-joined finalcommand). Consumers cannot distinguish "genuinely no such command" from
"documentation-only level below a real command".
Pins: cmdhelp_GAP_pseudo_command_cmdtype_notfound (fixture),
cmdhelp_GAP_string_is_true_pseudo (real tclcore docs, constraint have_tclcoredocs).
The documented-method oo case shows the same shape: `cmdinfo $obj docmeth` reports
cmdtype notfound with docid "<class> docmeth" (see cmdhelp_oo_documented_method - its
docid assertion holds through this goal; whether the method case adopts the new cmdtype
or a method-specific one is a design decision for this goal, coordinated with G-052).
**Space-form docid prefix parity.** The jump to a space-delimited child docid
(cmd_traverse: `punk::args::id_exists "$origin [lindex $args $i]"`) requires the exact
word. The parser accepts a unique choice prefix for the same word (G-040 landed
choiceword_match parity for the choiceinfo/subhelp path, but the space-form id_exists
jump happens BEFORE choice matching and never sees prefixes). Real-world effect:
Tcl's `string is tr 1` works, but 'i string is tr' stays at the parent docid.
Pins: cmdhelp_GAP_spaceform_docid_prefix_not_honoured (fixture),
cmdhelp_GAP_string_is_prefix_not_honoured (real tclcore docs).
## Approach
- Introduce a distinct cmdtype value (candidate: `doconly`) returned by cmdinfo when the
final resolution has an empty/notfound cmdwhich but a non-empty docid whose id maps
below a real command. Audit consumers (cmdhelp, synopsis, eg, anything switching on
cmdtype) - most treat docid as authoritative already, so the change should be additive.
- For prefix parity: when the exact-word space-form lookup fails and the current docid's
definition has a choices-bearing leader for the subcommand word, resolve the word via
punk::args::choiceword_match FIRST (the shared resolver - honouring -choiceprefix,
denylist, reservelist, aliases) and retry the space-form lookup with the resolved word.
No second matching rule: if parse would reject the prefix, the walk must too (the G-040
parity contract, already pinned by cmdflow.test).
## Notes
- The cmdinfo result shape (keys origin cmdtype args_resolved args_remaining docid stack)
is pinned by cmdhelp_cmdinfo_result_shape - the new cmdtype is a value change on an
existing key, not a shape change.
- 'string is' has a second wrinkle out of scope here: its per-class ids are generated
from the parent's -choicelabels at tclcore load time; prefix parity must come from the
walk, not from generating extra prefix ids.

54
goals/G-052-oo-method-autodef.md

@ -0,0 +1,54 @@
# G-052 TclOO method-level autodef documentation
Status: proposed
Scope: src/modules/punk/ns-999999.0a1.0.tm (generate_autodef oo branches, cmd_traverse), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test
Acceptance: see GOALS.md index entry (canonical).
## Context
For a tcl::oo object, generate_autodef builds a class-summary (autodef) definition: a
`method` leader whose choices are the public methods, with choiceinfo doctype markers
(objectmethod/classmethod/coremethod) and subhelp links for methods that have explicit
punk::args docs (the id convention is "<location> <method>", e.g.
"::punk::ansi::class::class_ansi rendertest"). A documented method therefore resolves and
renders with full argument marking.
An UNDOCUMENTED method does not: 'i $obj checksum' shows only the class summary with the
method word highlighted, and because the class-summary definition ends with
`@values -unnamed true`, ANY trailing words parse successfully - 'i $obj checksum bogus1
bogus2' renders an info-scheme (all good) class summary. Pinned 2026-07-10:
cmdhelp_GAP_oo_undocumented_method_class_summary_only (cmdtype ooobject, docid
(autodef)<obj>, args_remaining carries the method + bogus words, info scheme + goodarg
on the method row).
TclOO is fully introspectable: `info object call $obj $method` locates the
implementation, and `info class definition $class $method` / `info object definition`
yield the parameter list. generate_autodef already walks `info object call` for the
choiceinfo markers, and already has a partial single-method arglist path (the oodef
switch handling 1/2-element parameter entries, defaults, and trailing 'args') - it is
just not wired up as a general method-level (autodef) generation step in the doc walk.
## Approach
- When cmd_traverse consumes a method word on an oo object/class and no explicit
"<location> <method>" id exists, generate "(autodef)<location> <method>" from the
introspected parameter list (same element mapping as the existing proc autodef:
name / {name default} / trailing args -> -multiple 1 -optional 1) and resolve to it,
leaving subsequent words as args_remaining for parse marking.
- Location follows the same rules as the existing choiceinfo id convention (object-local
methods keyed by the object, class methods by the class, inherited by the defining
class) so explicit docs and autodefs share the lookup key.
- Decide and document the class-summary `-choiceprefix 0` question ("methods must be
specified in full always? - review" at the method choicelist): whatever the answer,
parse and doc walk must agree (G-040 parity).
- Constructor/'new' on classes: characterize at minimum; generating an autodef from
`info class constructor` is the natural extension - explicitly defer if not landed.
## Notes
- punk::ansi::class::class_ansi (rendertest / render_to_input_line) is the documented-
method reference pattern; the fixture in cmdhelp.test mirrors it (HelpClass docmeth
documented, plainmeth undocumented).
- Filters, private methods and `unknown` handlers are marked in choiceinfo today but out
of scope for autodef generation.
- Coordinate the cmdtype reported for method resolutions with G-051's doconly decision.

67
goals/G-053-punkargs-multiple-ranges.md

@ -0,0 +1,67 @@
# 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/
Acceptance: see GOALS.md index entry (canonical).
## 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.
Loading…
Cancel
Save