Browse Source

G-152 punk::args 0.14.0: sound suffix-viability verdicts (achieved + archived)

Multiform per-form failure statuses (formstatus records, noformmatch errorcode
classes + -formerrors) are now sound suffix-viability verdicts: 'incomplete'
(viable) iff every supplied word was validly consumed as a prefix of the form
and the failure is pure exhaustion at end-of-input; anything already supplied
contradicting the form reports 'invalid'. Mechanism: viability-probe mode on
private::get_dict_form suppressing only the position-guarded pure-exhaustion
raises (count min-shortfalls, missing-required checks, adhoc option value at
end) while final validation of the consumed words stands as the soundness
backstop; multiform candidacy and parse_status's single-form formstatus record
confirm each classify-'incomplete' failure with one probe re-parse. Rendered
noformmatch form lines marked '(viable - needs more arguments)'/'(not viable)'
after the stable "form '<fid>':" anchor. Consumer contract documented in the
parse ('Multiform failure contract') and parse_status argdocs. Documented
conservatism: input ending inside a multi-member type clause reports invalid.

New testsuite formviability.test (acceptance scenarios, option position
guards, single-form record soundness, clause conservatism, message marking);
forms.test verdict pins deliberately updated (ms/idle incomplete->invalid for
type-screen rejections). punkshell 0.41.3 (patch: refined multiform error
classification/reporting at the shell surface).

Goal G-152 activated (user-directed) and flipped achieved 2026-08-02 with
acceptance verified via runtests (args subtree green; full-suite parity with
stashed baseline); entry archived to GOALS-archive.md, detail file to
goals/archive/ with verification evidence; consumer pointers pushed to
G-044/G-150 notes.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
f4e23418d5
  1. 12
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 1
      goals/G-044-repl-command-completion.md
  5. 1
      goals/G-150-cmdhelp-form-narrowing.md
  6. 10
      goals/archive/G-152-punkargs-form-viability-verdict.md
  7. 2
      punkproject.toml
  8. 180
      src/modules/punk/args-999999.0a1.0.tm
  9. 3
      src/modules/punk/args-buildversion.txt
  10. 14
      src/tests/modules/punk/args/testsuites/args/forms.test
  11. 246
      src/tests/modules/punk/args/testsuites/args/formviability.test

12
CHANGELOG.md

@ -5,6 +5,18 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.41.3] - 2026-08-02
- punk::args 0.14.0 (G-152): multiform parse failures now report a sound
per-form suffix-viability verdict - a form is 'incomplete' (viable) only
when the supplied words are a valid prefix of it and parsing stopped from
exhaustion at end-of-input; words contradicting a form (e.g a wrong action
word) now report it 'invalid' instead of the former false 'incomplete'.
The verdict rides the noformmatch errorcode classes/-formerrors and the
parse_status formstatus records (documented consumer contract in the module
argdoc), and rendered noformmatch reports mark each candidate form line
'(viable - needs more arguments)' or '(not viable)'.
## [0.41.2] - 2026-08-02
- make.tcl top-level overview interim layout (user decision): -choicecolumns

4
GOALS-archive.md

@ -282,3 +282,7 @@ Acceptance: piped 'tclsh src/make.tcl help <subject>' for every declared subject
### G-143 [achieved 2026-08-01] make.tcl help depth: multi-form tool/buildsuite definitions and per-action usage → detail: goals/archive/G-143-maketcl-multiform-help.md
Scope: src/make.tcl (::punkboot::argdoc definitions + help dispatch); src/tests/shell/testsuites/punkexe/ (new piped help characterization test)
Acceptance: 'make.tcl help tool' shows per-action synopsis lines (list; info; build with -test and toolnames; test with toolnames) instead of the single conflated line; 'make.tcl help tool build' and 'make.tcl help buildsuite build' render that form's usage table and an unknown action subject produces a punk::args usage error (exit 1); all currently-working tool invocations behave unchanged while 'make.tcl tool <unknown-action>' and unknown flags produce punk::args usage errors (exit 1); buildsuite driver-arg forwarding is unchanged (an arbitrary flag after 'buildsuite build <suite>' still reaches the driver untouched); PUNKBOOT_PLAIN=1 degraded dispatch still handles the same invocations via the plain scan; the behaviours are pinned by a piped characterization test under src/tests/shell/testsuites/punkexe/. (Achieved with a user-approved deviation 2026-08-01: the historic tool flag-anywhere argument order is not preserved - the declared positional model is options-before-names ('tool build -test 0 <name> ...') with docs/agent guidance updated to match; the PUNKBOOT_PLAIN degraded handler keeps the historic parse. The help action additionally accepts a 0-based form index. Detail in the archived file's Progress section.)
### G-152 [achieved 2026-08-02] punk::args form-failure suffix-viability verdict (completion-grade classification) → detail: goals/archive/G-152-punkargs-form-viability-verdict.md
Scope: src/modules/punk/args-999999.0a1.0.tm (multiform failure classification + noformmatch errorcode/formstatus consumer contract + render marking); src/tests/modules/punk/args/testsuites/args/
Acceptance: against a tool-shaped multiform definition (literal action leaders; the info form requiring a value), parse of {info} reports form info viable and list/build/test non-viable; parse of {frobnicate} reports NO form viable (today's false 'incomplete' on the info form is gone); parse of {build -bogus} reports no form viable; the class vocabulary and errorcode/formstatus shape carrying the verdict are documented as a consumer contract in the module's own argdoc/doc surface; the rendered noformmatch output visibly distinguishes viable form(s) from impossible ones; existing args-suite pins pass unchanged apart from deliberately updated classification pins; the new verdicts are pinned in the args testsuite.

4
GOALS.md

@ -418,10 +418,6 @@ Detail: goals/G-150-cmdhelp-form-narrowing.md
Scope: src/modules/punk/args-999999.0a1.0.tm (parse-result presentation); src/tests/modules/punk/args/testsuites/args/
Detail: goals/G-151-punkargs-annotated-success-render.md
### G-152 [proposed] punk::args form-failure suffix-viability verdict (completion-grade classification)
Scope: src/modules/punk/args-999999.0a1.0.tm (multiform failure classification + noformmatch errorcode/formstatus consumer contract + render marking); src/tests/modules/punk/args/testsuites/args/
Detail: goals/G-152-punkargs-form-viability-verdict.md
### G-153 [active] metakit_punk zig port: fork-as-upstream Mk4tcl for the 8.6 kit container
Scope: external fork repo c:/repo/jn/metakit_punk (gitea jn/metakit - the port itself lands there); src/buildsuites/suite_tcl86/sources.config and src/buildsuites/suite_tcl86/build.zig.zon (ready-to-land metakit records drafted in the detail file; landing them is G-101-era consumption); TEMP_REFERENCE/{metakit_jnorthrup,metakit_pooryorick,KitCreator} (read-only patch/boot-chain reference - machine-local); goals/G-101-tcl86-kit-container-strategy.md (consumer decision record)
Detail: goals/G-153-metakit-punk-zig-port.md

1
goals/G-044-repl-command-completion.md

@ -92,6 +92,7 @@ arginfo principle: introspection must not run commands to elicit usage).
src/tests/modules/punk/ns/testsuites/ns/cmdflow.test (2026-07-08), including ensemble
-parameters handling; the choice semantics by
src/tests/modules/punk/args/testsuites/args/choices.test; forms by forms.test.
- G-152 (achieved 2026-08-02 - goals/archive/G-152-punkargs-form-viability-verdict.md) shipped this goal's core verdict enabler: per-form suffix-viability verdicts ('incomplete' = the form is still satisfiable by appending words, sound by construction) in the noformmatch errorcode classes/-formerrors and the parse_status formstatus records, with the consumer contract documented in the parse/parse_status argdocs. Its Notes record two things a completion consumer may need to extend: the trailing-flag-before-restricted-choice seating nuance (verdict is seating-based; flag-pending-value awareness needs consumer lookahead) and the multi-member-clause conservatism (input ending inside a clause reports invalid).
- Archived-goal references in this file: G-001 achieved 2026-07-11 (goals/archive/G-001-pluggable-console-backends.md); G-040 achieved 2026-07-08 (goals/archive/G-040-punkargs-choicealiases.md); G-041 achieved 2026-07-13 (goals/archive/G-041-punkargs-form-matching.md).
## Repl behaviour preserve-list + testability findings (2026-07-11, user-directed - pre-refactor ordering)

1
goals/G-150-cmdhelp-form-narrowing.md

@ -14,4 +14,5 @@ From the G-143 finalisation sessions (2026-08-01/02 - archived detail file's Pro
- Related: G-044 (proposed) - punk::args-driven interactive completion; form narrowing and completion share the words-to-candidate-forms machinery (see also the noformmatch classes finding recorded in goals/archive/G-143-maketcl-multiform-help.md).
- Related: G-050 (proposed) - punk::ns::synopsis validity marking; sibling punk::ns presentation surface.
- Related: G-143 (achieved 2026-08-01 - goals/archive/G-143-maketcl-multiform-help.md) - the make.tcl help dry-run contract this mirrors at the repl.
- G-152 (achieved 2026-08-02 - goals/archive/G-152-punkargs-form-viability-verdict.md) shipped the suffix-viability verdict this goal's failed/incomplete-line narrowing consumes: formstatus statuses and noformmatch errorcode classes are now sound per-form verdicts, and rendered noformmatch form lines carry '(viable - needs more arguments)' / '(not viable)' markers. Note bootsupport punk::args was still 0.13.0 at G-152 archive time - refresh bootsupport when this goal wants the verdict at the make.tcl surface.
- Overlap survey 2026-08-02: goals_xref paths src/modules/punk/ns-999999.0a1.0.tm src/modules/punk/auto_exec-999999.0a1.0.tm - ns family G-044 G-050 G-052 G-075 G-085 (G-052 autodef docs and G-085 cmdtrace are unrelated mechanisms; G-075 package-id docs tangential); no auto_exec-scoped goals surfaced.

10
goals/G-152-punkargs-form-viability-verdict.md → goals/archive/G-152-punkargs-form-viability-verdict.md

@ -1,6 +1,6 @@
# G-152 punk::args form-failure suffix-viability verdict (completion-grade classification)
Status: proposed
Status: achieved 2026-08-02
Scope: src/modules/punk/args-999999.0a1.0.tm (multiform failure classification + noformmatch errorcode/formstatus consumer contract + render marking); src/tests/modules/punk/args/testsuites/args/
Goal: A multiform parse failure reports a SOUND suffix-viability verdict per candidate form - a form is marked viable if and only if every supplied word was validly consumed as a prefix of that form and the failure is pure exhaustion at end-of-input (nothing already supplied contradicts the form) - exposed through a documented, stable machine-readable contract (the noformmatch errorcode classes / formstatus records) so consumers (cmdhelp narrowing, make.tcl help, G-044 completion) can select the still-satisfiable form, and marked distinguishably in the rendered per-form failure report.
Acceptance: against a tool-shaped multiform definition (literal action leaders; the info form requiring a value), parse of {info} reports form info viable and list/build/test non-viable; parse of {frobnicate} reports NO form viable (today's false 'incomplete' on the info form is gone); parse of {build -bogus} reports no form viable; the class vocabulary and errorcode/formstatus shape carrying the verdict are documented as a consumer contract in the module's own argdoc/doc surface; the rendered noformmatch output visibly distinguishes viable form(s) from impossible ones; existing args-suite pins pass unchanged apart from deliberately updated classification pins; the new verdicts are pinned in the args testsuite.
@ -17,3 +17,11 @@ Raised by user review of 'make.tcl help tool info' output (2026-08-02, during th
- Related: G-074 (achieved 2026-07-13 - goals/archive/G-074-punkargs-multiform-ambiguity-lint.md) - static form-overlap analysis (formcheck); this goal is the RUNTIME per-invocation counterpart over supplied words, sharing the parse_status machinery.
- Overlap survey 2026-08-02: goals_xref paths src/modules/punk/args-999999.0a1.0.tm - the punkargs family as recorded in G-149's survey (G-044 G-050 named above; G-053 G-056 G-069 G-072 G-073 G-075 G-083 G-084 G-088 are unrelated parser/spec/render mechanisms); session siblings G-149/G-151 are render-surface work, this is parse-classification.
- Probe evidence (2026-08-02, bootsupport 0.13.0): {info} -> `classes {incomplete invalid invalid invalid}` over ranked `forms {info list build test}`; {frobnicate} -> identical (the false positive); {build -bogus} -> all invalid. Dev classifier: rank_form_failures orders by form_literal_affinity then incomplete-first.
- Conservatism (documented in both argdoc contracts): input ending INSIDE a multi-member type clause (e.g `coord -type {int int}` supplied one word) reports invalid - clause allocation walks reversed member types and cannot affirm partial words. Candidate refinement if G-044 needs it: a forward partial-member check at the in-loop missing-required raise sites.
- Recorded for G-044 (completion): a trailing DEFINED flag word before an unconstrained optional value is consumed as a VALUE (the engine's trailing-flag-as-value rule), so such lines PARSE (toolname swallows '-test') and no verdict question arises; a trailing flag before a RESTRICTED choice value seats as that value and fails its choice gate -> verdict invalid, even though appending the flag's value word would re-seat it as an option ({bake -confirm} vs {bake -confirm 0}). The verdict is deliberately seating-based per this goal's Goal wording (and the G-143-era declaration-authoritative choice-gate decision); a completion consumer wanting flag-pending-value awareness needs its own lookahead.
- Bootsupport punk::args remains 0.13.0 after this goal: make.tcl help dry-run output gains the verdict marking only when bootsupport is next refreshed (maintenance/G-150-era step; no acceptance dependency - acceptance ran against the src module via runtests).
## Progress
- 2026-08-02 IMPLEMENTED (punk::args 0.14.0, punkshell 0.41.3): verdict computed by a viability-probe mode on the single-form engine (`private::get_dict_form` optional 4th arg) rather than by remapping failure classes. The probe suppresses exactly the pure-exhaustion raises - below-minimum leading/trailing count shortfalls and missing-required leader/option/value checks, each position-guarded (leaders only with no option/value words consumed; options only before value words and any eopts `--`; values always), plus an adhoc option's value lying beyond the supplied words - and lets every contradiction raise stand INCLUDING final validation of the consumed words (the soundness backstop; the engine validates types/choices only AFTER the count/required checks, which is exactly why the raw failure classes could not carry the verdict, and why the G-071 screen asymmetry - basic types screened even for required args, choices deliberately unscreened - made {frobnicate} class identically to {info}). Multiform candidacy confirms each classify-'incomplete' per-form failure with one probe re-parse before recording it (contradiction-classed failures skip the probe); parse_status's single-form formstatus record gets the same confirmation; parse_status's top-level status keeps the engine classification for single-form parses (display-scheme driver) - the split is documented in the argdoc.
- 2026-08-02 VERIFIED (native tclsh 8.7 via runtests.tcl): acceptance scenarios pinned in new `src/tests/modules/punk/args/testsuites/args/formviability.test` (10 tests: {info} -> classes {incomplete invalid invalid invalid} with the viable info form ranked first; {frobnicate} -> all invalid, overall parse_status status now 'invalid'; {build -bogus} -> all invalid; -formerrors/formstatus agreement; success-path verdicts; required-option position guards; single-form formstatus soundness incl the engine-vs-verdict split on a type-screen rejection; clause-partial conservatism; message marking). Contract documented in the parse argdoc ('Multiform failure contract' section: errorcode shape + class vocabulary) and the parse_status argdoc (formstatus verdict semantics). Rendered noformmatch marks each form line '(viable - needs more arguments)' / '(not viable)' after the stable "form '<fid>':" anchor (anchor pinned unchanged). args subtree green (28 files + the new suite); full source-tree run at exact parity with a stashed-baseline run (the same 21 pre-existing environment failures on this box - twapi-less tcl 8.7a6 child SIGILLs in repl consolebackends/nscurrent, core exec.test, argparsingtest, ansi grepstr, lib compat lpop edge, ns nslist/nsprimitives, zip zipreader - none args-related). forms.test verdict pins deliberately updated: formstatus ms/idle for {cancel after#1} 'incomplete'->'invalid' (words failing their type screens are contradictions).

2
punkproject.toml

@ -1,6 +1,6 @@
[project]
name = "punkshell"
version = "0.41.2"
version = "0.41.3"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -

180
src/modules/punk/args-999999.0a1.0.tm

@ -6528,6 +6528,31 @@ tcl::namespace::eval punk::args {
punk::args::define). When -form permits several forms, the form that
auto-selection settled on is reported here, and a formstatus key is added
recording each attempted form's outcome (see punk::args::parse_status).
Multiform failure contract (stable, machine-readable):
when a multi-form candidacy attempt matches no form, the raised error
carries the errorcode
PUNKARGS VALIDATION {noformmatch forms <fidlist> classes <classlist>}
-formerrors <records> -argspecs <spec>
where 'forms' lists the candidate form names ranked best-candidate
first and 'classes' holds one suffix-viability verdict per listed
form (same order) from the vocabulary:
incomplete - VIABLE: every supplied word was validly consumed as
a prefix of that form and parsing stopped only from
exhaustion at end-of-input - appending further
words can still satisfy the form
invalid - NOT viable: something already supplied contradicts
the form - no appended words can satisfy it
-formerrors holds one record per form (same ranked order) with keys
status/failureclass/badarg/message - 'status' is the same verdict.
The verdict is conservative for arguments whose -type is a
multi-member clause: input ending inside such a clause reports
invalid even when the partial clause words match.
Arguments matching several forms cleanly raise errorcode
PUNKARGS VALIDATION {multipleformmatches forms <fidlist>}
Consumers (form narrowing, interactive completion) should select
candidate forms from these channels rather than parsing message
text.
}
@form -form {withid withdef}
@leaders -min 1 -max 1
@ -6934,7 +6959,10 @@ tcl::namespace::eval punk::args {
}
noformmatch {
#G-041 multi-form candidacy: payload is: forms <fids> classes <per-form statuses>
#incomplete when some candidate form needs only more words
#G-152: the classes are sound per-form suffix-viability verdicts (candidacy
#confirms each 'incomplete' by a viability-mode re-parse before raising) -
#overall incomplete therefore means at least one form is still satisfiable
#by appending words
if {"incomplete" in [Dict_getdef $payload classes {}]} {
return incomplete
}
@ -7176,6 +7204,10 @@ tcl::namespace::eval punk::args {
#G-041: order a formfailures dict (fid -> {status .. failureclass .. badarg .. message ..})
#best-candidate first: literal affinity desc, then incomplete before invalid, then the
#incoming (declaration) order. Returns the re-ordered dict.
#order a noformmatch failure's per-form records best-candidate first: leading-literal
#affinity with the supplied words, then status incomplete before invalid, then
#declaration order (stable sort). G-152: status is the confirmed suffix-viability
#verdict, so the class tiebreak ranks still-satisfiable forms above impossible ones.
proc private::rank_form_failures {argspecs rawargs formfailures} {
set scored [list]
dict for {fid finfo} $formfailures {
@ -7676,7 +7708,14 @@ tcl::namespace::eval punk::args {
failing its -type check can also surface as
an allocation shortfall (missingrequiredvalue)
- badarg and the per-argument statuses carry
the specifics in both situations.
the specifics in both situations. For a
single-form parse this is the engine failure
classification, NOT a viability verdict -
read formstatus for that. For a multiform
noformmatch failure incomplete does carry
verdict weight: it means at least one
candidate form is still viable (its
formstatus status is incomplete).
scheme suggested display scheme: info (ok) | error (failure)
message empty | the validation failure message
errorcode empty | the -errorcode of the validation failure with
@ -7695,14 +7734,33 @@ tcl::namespace::eval punk::args {
then incomplete before invalid, then declaration
order).
formstatus dict keyed by candidate form name - each value a
dict with at least the key 'status'
(valid | incomplete | invalid) and for failed
dict with at least the key 'status' and for failed
candidates failureclass, badarg and message.
'status' is a per-form suffix-viability verdict
(stable consumer contract):
valid - the supplied words satisfy the form
incomplete - VIABLE: every supplied word was
validly consumed as a prefix of the
form and parsing stopped only from
exhaustion at end-of-input -
appending further words can still
satisfy the form
invalid - NOT viable: something already
supplied contradicts the form - no
appended words can satisfy it
The verdict is conservative for arguments whose
-type is a multi-member clause (e.g
{literal(elseif) expr script}): input ending
inside such a clause reports invalid even when
the partial clause words match.
Single-form parses report their one form; when
multi-form candidacy ran, every attempted form is
reported - partial-arglist consumers (e.g command
completion/hinting) can read per-form
compatibility from this key.
completion/hinting, cmdhelp form narrowing) can
select the still-satisfiable form(s) from this
key. The raising parse exposes the same verdicts
in its noformmatch errorcode - see
punk::args::parse.
receivednames received argument names normalized to definition
argument names (opt aliases folded to their
-alias|-name optionset)
@ -7848,7 +7906,17 @@ tcl::namespace::eval punk::args {
}
}
default {
set formstatus [dict create $fid [dict create status $status failureclass $failureclass badarg $badarg message $r]]
#G-152: formstatus statuses are suffix-viability verdicts. The overall
#status above keeps the engine failure classification (it drives the
#display scheme), but the per-form record must not report 'incomplete'
#unless the supplied words are a valid prefix of the form - confirm by
#a viability-mode re-parse exactly as multiform candidacy does.
set formverdict $status
if {$formverdict eq "incomplete"
&& [catch {private::get_dict_form $spec_dict $fid $arglist 1}]} {
set formverdict invalid
}
set formstatus [dict create $fid [dict create status $formverdict failureclass $failureclass badarg $badarg message $r]]
}
}
#the -argspecs payload (the whole resolved spec) is display machinery - too bulky
@ -9512,8 +9580,21 @@ tcl::namespace::eval punk::args {
}
set classinfo [lindex $ecode 2]
set failureclass [lindex $classinfo 0]
set formverdict [private::parse_status_classify $failureclass [lrange $classinfo 1 end]]
#G-152: a per-form 'incomplete' is a suffix-viability verdict - it must hold
#only when every supplied word was validly consumed as a prefix of the form
#and the failure is pure exhaustion at end-of-input. The failure class alone
#cannot witness that (count and missing-required classes fire before final
#validation sees the consumed words), so confirm by re-parsing in viability
#mode: exhaustion raises are skipped there while validation of the consumed
#words still applies - a raise means something already supplied contradicts
#the form.
if {$formverdict eq "incomplete"
&& [catch {private::get_dict_form $argspecs $fid $rawargs 1}]} {
set formverdict invalid
}
dict set formfailures $fid [dict create\
status [private::parse_status_classify $failureclass [lrange $classinfo 1 end]]\
status $formverdict\
failureclass $failureclass\
badarg [Dict_getdef [lrange $ecode 3 end] -badarg ""]\
message [lindex [split $fresult \n] 0]\
@ -9547,7 +9628,16 @@ tcl::namespace::eval punk::args {
set classes [lmap finfo [dict values $formfailures] {dict get $finfo status}]
set msg "Bad arguments for %caller%. No form of the command matches the supplied arguments. Candidate forms: $candidate_fids"
dict for {fid finfo} $formfailures {
append msg \n " form '$fid': [dict get $finfo message]"
#G-152: visibly distinguish forms still satisfiable by appending words
#(status incomplete - the confirmed suffix-viability verdict) from
#forms nothing appended can satisfy. Keep the "form '<fid>':" prefix
#intact - consumers/tests anchor on it.
if {[dict get $finfo status] eq "incomplete"} {
set verdicttag "(viable - needs more arguments)"
} else {
set verdicttag "(not viable)"
}
append msg \n " form '$fid': $verdicttag [dict get $finfo message]"
}
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list noformmatch forms $candidate_fids classes $classes] -argspecs $argspecs -formerrors $formfailures]] $msg
}
@ -9562,7 +9652,23 @@ tcl::namespace::eval punk::args {
#body, extracted verbatim from get_dict when multi-form candidacy landed (G-041).
#argspecs is a RESOLVED spec dict (get_dict resolves the deflist in its caller's
#context before selecting forms - nothing here touches the caller's frame).
proc private::get_dict_form {argspecs fid rawargs} {
#
#viabilitycheck (G-152): when 1, run as a suffix-viability probe - the question
#answered is not "do these arguments satisfy the form" but "are these arguments a
#valid PREFIX of the form" (could appending further words still satisfy it). The
#probe suppresses exactly the raises that represent pure exhaustion at
#end-of-input (below-minimum count checks and missing-required checks, each
#guarded by position conditions ensuring appended words could actually reach the
#missing element, and an option's value lying beyond the supplied words) and lets
#every other raise stand - including the final validation of the words actually
#consumed, which is the soundness backstop: a probe raise means something already
#supplied contradicts the form. Callers only test raised-vs-returned; the
#returned dict of a probe run is partial and must be discarded.
#Known conservatism (documented in the parse/parse_status contracts): input
#ending INSIDE a multi-member type clause raises (clause allocation cannot
#affirm the partial words) - such prefixes report non-viable even when the
#partial clause words match.
proc private::get_dict_form {argspecs fid rawargs {viabilitycheck 0}} {
#puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied
set solosreceived [list]
@ -10564,6 +10670,15 @@ tcl::namespace::eval punk::args {
tcl::dict::set arg_checks $a $OPT_CHECKS_DEFAULTS
#assert -type value has llength 1 (multitype clauses not allowed for opts)
if {[tcl::dict::get $argstate $a -type] ne "none"} {
if {$viabilitycheck && $i == $maxidx} {
#G-152 viability probe: the adhoc option's value lies beyond
#the supplied words - pure end-of-input exhaustion. End the
#opts scan; the unfinished flag stays unassigned (required
#checks are position-guarded in probe mode).
set arglist [lrange $remaining_rawargs 0 $i-1]
set post_values {}
break
}
if {[tcl::dict::get $argstate $a -multiple]} {
tcl::dict::lappend opts $a $flagval
if {$a ni $multisreceived} {
@ -11172,14 +11287,29 @@ tcl::namespace::eval punk::args {
}
#-----------------------------------------------------
#G-152 viability probe: a below-minimum count is pure end-of-input exhaustion -
#satisfiable by appending words - only while the parse position can still reach
#that section: the leaders section only when nothing was consumed beyond it (no
#option words, no value words - appended words land after those and cannot fill
#a leader); the trailing values always (values end the argument list and the
#loops above consumed every supplied word). The probe skips those raises so
#final validation can pass verdict on the words actually consumed.
#Above-maximum counts are contradictions in any mode.
if {$viabilitycheck} {
set skip_leadershortfall [expr {![llength $flagsreceived] && ![llength $values]}]
set skip_valueshortfall 1
} else {
set skip_leadershortfall 0
set skip_valueshortfall 0
}
if {$leadermax == -1} {
#only check min
if {$num_leaders < $leadermin} {
if {$num_leaders < $leadermin && !$skip_leadershortfall} {
set msg "Bad number of leading values for %caller%. Got $num_leaders leaders. Expected at least $leadermin"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list leadingvaluecount $num_leaders min $leadermin max $leadermax] -argspecs $argspecs]] $msg
}
} else {
if {$num_leaders < $leadermin || $num_leaders > $leadermax} {
if {($num_leaders < $leadermin && !$skip_leadershortfall) || $num_leaders > $leadermax} {
if {$leadermin == $leadermax} {
set msg "Bad number of leading values for %caller%. Got $num_leaders leaders. Expected exactly $leadermin"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list leadingvaluecount $num_leaders min $leadermin max $leadermax] -argspecs $argspecs]] $msg
@ -11192,12 +11322,12 @@ tcl::namespace::eval punk::args {
if {$valmax == -1} {
#only check min
if {$num_values < $valmin} {
if {$num_values < $valmin && !$skip_valueshortfall} {
set msg "Bad number of trailing values for %caller%. Got $num_values values. Expected at least $valmin"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list trailingvaluecount $num_values min $valmin max $valmax] -argspecs $argspecs]] $msg
}
} else {
if {$num_values < $valmin || $num_values > $valmax} {
if {($num_values < $valmin && !$skip_valueshortfall) || $num_values > $valmax} {
if {$valmin == $valmax} {
set msg "Bad number of trailing values for %caller%. Got $num_values values. Expected exactly $valmin"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list trailingvaluecount $num_values min $valmin max $valmax] -argspecs $argspecs]] $msg
@ -11230,14 +11360,30 @@ tcl::namespace::eval punk::args {
# error "Required value missing for [Get_caller]. missing values $missing marked with -optional false - so must be present"
#}
#for now (2024-06) punk::lib::ldiff is a better compromise across normal/safe interps e.g 0.7/0.8us
if {[llength $LEADER_REQUIRED]} {
#G-152 viability probe: a missing required argument is pure end-of-input
#exhaustion - satisfiable by appending words - only while the parse position
#can still reach its argument class: a required leader only when no option or
#value words followed the leaders; a required option only while no value words
#have begun and no eopts marker (--) has closed the options; a required value
#always (values end the argument list). The probe skips those raises in that
#situation; otherwise the absence stands as a contradiction even in probe mode.
if {$viabilitycheck} {
set skip_leadermissing [expr {![llength $flagsreceived] && ![llength $values]}]
set skip_optionmissing [expr {![llength $values] && "--" ni $flagsreceived}]
set skip_valuemissing 1
} else {
set skip_leadermissing 0
set skip_optionmissing 0
set skip_valuemissing 0
}
if {[llength $LEADER_REQUIRED] && !$skip_leadermissing} {
if {[llength [set missing [punk::args::system::punklib_ldiff $LEADER_REQUIRED $leadernames_received]]]} {
set msg "Required leader missing for %caller%. missing values: '$missing' marked with -optional false - so must be present"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list leadermissing $missing received $leadernames_received] -argspecs $argspecs]] $msg
#arg_error "Required leader missing for [Get_caller]. missing values: '$missing' marked with -optional false - so must be present" $argspecs
}
}
if {[llength $OPT_REQUIRED]} {
if {[llength $OPT_REQUIRED] && !$skip_optionmissing} {
#broken e.g
#punk::args::define {@id -id ::spud} @opts {-x -parsekey -coord -optional 0} {-y -parsekey -coord -optional 0}
#error is:
@ -11259,7 +11405,7 @@ tcl::namespace::eval punk::args {
#arg_error "Required option missing for [Get_caller]. missing flags: '$missing' are marked with -optional false - so must be present " $argspecs
}
}
if {[llength $VAL_REQUIRED]} {
if {[llength $VAL_REQUIRED] && !$skip_valuemissing} {
if {[llength [set missing [punk::args::system::punklib_ldiff $VAL_REQUIRED $valnames_received]]]} {
set msg "Required value missing for %caller%. missing values: '$missing' marked with -optional false - so must be present"
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list valuemissing $missing received $valnames_received] -argspecs $argspecs]] $msg

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.13.0
0.14.0
#First line must be a semantic version number
#all other lines are ignored.
#0.14.0 - G-152 suffix-viability verdict: multiform per-form failure statuses (the 'formstatus' records, the noformmatch errorcode's 'classes' list and its -formerrors records) are now SOUND suffix-viability verdicts - a form reports 'incomplete' (viable) if and only if every supplied word was validly consumed as a prefix of that form and the failure is pure exhaustion at end-of-input; anything already supplied contradicting the form reports 'invalid'. Previously the count/missing-required failure classes granted 'incomplete' before final validation ever saw the consumed words, so a wrong action word landed 'incomplete' on a value-carrying sibling form identically to a genuinely-viable prefix ({frobnicate} vs {info} were indistinguishable), and a word rejected by an allocation type screen (missingrequiredvalue) also classed 'incomplete'. Mechanism: private::get_dict_form gains a viability-probe mode (optional 4th arg) suppressing exactly the pure-exhaustion raises - below-minimum leading/trailing count shortfalls and missing-required leader/option/value checks, each position-guarded so appended words could actually reach the missing element (leaders only with no option/value words consumed; options only before value words and any eopts --; values always), plus an adhoc option's value lying beyond the supplied words - while every contradiction raise stands, including final validation of the consumed words (the soundness backstop). Multiform candidacy and parse_status's single-form formstatus record confirm each classify-'incomplete' failure with one probe re-parse before reporting it. Documented conservatism: input ending INSIDE a multi-member type clause reports invalid (clause allocation cannot affirm the partial words). Rendered noformmatch messages mark each form line '(viable - needs more arguments)' or '(not viable)' after the stable "form '<fid>':" anchor; the existing incomplete-first ranking thereby puts still-satisfiable forms first. Contract documented in the parse ('Multiform failure contract' help section) and parse_status (formstatus/status key) argdocs; parse_status's top-level 'status' for single-form parses keeps the engine classification (display-scheme driver) - only formstatus carries the verdict. New testsuite formviability.test (acceptance scenarios, option-position guards, single-form record soundness, clause conservatism, message marking); forms.test verdict pins deliberately updated (ms/idle 'incomplete'->'invalid' for words failing their type screens).
#0.13.0 - G-144: auto-generated synopses for (script)-prefixed constructed ids now lead with the declared @cmd -name (the invocation name - e.g 'make.tcl modules' for (script)::punkboot::modules) instead of the raw id, across punk::args::synopsis full/summary renders and the usage/arg_error synopsis sections (table and string) that funnel through them; explicit @form -synopsis overrides still win, so make.tcl/punkexe displays are unchanged today (their override retirement is a follow-on). The fallback is gated on the (script) prefix only: (autodef) ids are deliberately excluded - their id-minus-tag IS the invocation (arg_error's existing tag strip surfaces it) while their @cmd -name is a display label ('destroy', 'Object: ::x') that must not lead a synopsis line; (shared)/(package)/(default)/(widgetcommand) doc ids declare no invocation-style -name today and render the id as before (a prefixed id with no -name falls back to the id). Real command ids render byte-identical. Retires the 'open generation deficiency' recorded at 0.12.5 (head-render preference for @cmd -name); define's @form -synopsis directive doc and synopsis -help updated. New synopsis.test pins: script-id full/summary/multiform/surfaces, no-name constructed id, autodef unchanged, real-id byte-identical.
#0.12.7 - update_definitions: tag-prefixed id 'namespaces' ((autodef)::x plus script-level id families like (script)::punkboot / (script)::punkexe - the make.tcl G-030 convention, now also the launch-dispatch doc home per G-118 item 10) are handled first-class in the nslist walk. Previously only the (autodef) tag was stripped; other tags fell through to the unqualified-ns branch, emitting a spurious "warning: update_definitions received unqualified ns: (script)" on stderr for EVERY id query and probing a garbage ::(script)... namespace - and, decisively, the namespace_docpackages association was never consulted with the TAGGED key the scan records, so real_id/usage on a registered-but-not-yet-loaded script-level id returned ""/no-such-id even though get_spec resolved it. Now: docpackages lookup with the original tagged key (loads the documenting package - punk::args::usage works for (script)::punkexe::* ids on first query), then generic tag strip for the real-namespace probes, with a bare/tag-only qualifier skipping them silently. Real unqualified namespaces still warn.
#0.12.6 - arg_error error-message placement (user direction 2026-07-18): the supplied error message and any per-form match detail it carries ("Bad arguments ... Candidate forms ..." + per-form failure lines) now render exactly once, BELOW the usage display, for both the table and string return forms. Previously the block rendered above the usage AND was appended again below it (the historical "put original error at bottom of table too"), duplicating potentially long multi-form detail. The bottom append is also now suppressed when the message is empty (usage-only calls no longer gain a trailing newline + empty line). tableobject returns are unchanged (they never carried the message). Also (same user direction, format explicitly not frozen - no external consumers yet): the string renderer's argument rows are now single-line 'name TYPE:... ?DEFAULT:...? ?MULTI:yes?' - the multi-line table-cell type constraints (-minsize/-maxsize/-range(s), previously line-broken mid-row so DEFAULT:/MULTI: landed glued to the last constraint line, e.g 'i -return string dict filter' arg vars) parenthesize onto the base type as TYPE:list(-minsize 2 -maxsize 2), empty DEFAULT/MULTI fields are omitted instead of rendering empty labels (MULTI shows 'yes' rather than the check glyph), and whitespace-only help blocks no longer emit indented filler lines. The <unnamed>/arbitrary-flag indicator rows get the same DEFAULT-omission. Choices also gain structure in the string renderer (previously a flat join left choice names and multi-line choicelabels at inconsistent depths, machine-indistinguishable once ANSI is stripped - only the table/ANSI prefix highlighting disambiguated): each choice renders as a markdown-subset bullet '- `choice`' at fixed indent 6 (the backticked token may carry a trailing choiceinfo marker glyph), the label's first line follows the closing backtick on the bullet line after ' - ', label continuations indent to 8 (2 past the bullet dash), and groups head their bullets as "Group 'name':". The resulting structural contract for parsers: column-0 lines bearing TYPE: are argument rows, indented lines are documentation, '- `...`' bullets are choices. Table cells unchanged (construction shared, restructure applied at the string-path consumption site). Feeds punk::ns 0.9.0 cmdhelp -return text (LLM-oriented plain text). Pinned in rendering.test (rendering_stringmode_singleline_argrows, rendering_stringmode_choice_bullets); errorselection.test message pins gained leading * (message no longer starts the raised string); cmdhelp.test message pins sample the last line. No API change.

14
src/tests/modules/punk/args/testsuites/args/forms.test

@ -14,6 +14,11 @@ package require punk::ns
#documented. The former GAP pins (forms_parse_default_form0_only_GAP,
#forms_parse_formlist_rejected_GAP) flipped to forms_parse_autoselect and
#forms_parse_formlist_restriction.
#G-152 (2026-08-02): per-form statuses are sound suffix-viability verdicts - a form
#reports 'incomplete' only when the supplied words are a valid prefix of it (pure
#exhaustion at end-of-input); a supplied word contradicting the form (e.g failing an
#allocation type screen) now reports 'invalid'. Verdict pins updated accordingly here;
#dedicated coverage in formviability.test.
namespace eval ::testspace {
namespace import ::tcltest::*
@ -145,6 +150,9 @@ namespace eval ::testspace {
-result [list get set {newvalue myvalue}]
#added 2026-07-13 (agent, G-041)
#verdict pin updated 2026-08-02 (agent, G-152): ms status for {cancel after#1} is
#invalid - 'cancel' fails the ms argument's int screen, so no appended words can
#satisfy that form (was incomplete under the pre-verdict classification)
test forms_parse_autoselect_formstatus {a multi-form auto-selected parse reports every candidate form's status in the result}\
-setup $common -body {
set argd [punk::args::parse {cancel after#1} withid ::testspace::afterish]
@ -162,7 +170,7 @@ namespace eval ::testspace {
}\
-cleanup {
}\
-result [list {ms cancel idle} valid incomplete _default 0]
-result [list {ms cancel idle} valid invalid _default 0]
#added 2026-07-13 (agent, G-041)
test forms_parse_noformmatch {input matching no form raises an error naming each candidate form's failure - best candidate (literal affinity) first}\
@ -243,6 +251,8 @@ namespace eval ::testspace {
-result [list idle cancel noformmatch {cancel idle} 1]
#added 2026-07-13 (agent, G-041)
#verdict pin updated 2026-08-02 (agent, G-152): idle status for {cancel after#1} is
#invalid - 'cancel' fails idle's literal(idle) screen (was incomplete pre-verdict)
test forms_parsestatus_form_autoselect {parse_status reports the auto-selected form, and for noformmatch the best-candidate form with per-form statuses}\
-setup $common -body {
#valid: form key = the matched form; formstatus covers every candidate
@ -259,7 +269,7 @@ namespace eval ::testspace {
}\
-cleanup {
}\
-result [list 1 cancel valid incomplete 0 incomplete noformmatch cancel 0 1]
-result [list 1 cancel valid invalid 0 incomplete noformmatch cancel 0 1]
test forms_synopsis_lists_all_forms {punk::ns::synopsis renders a synopsis line per form}\
-setup $common -body {

246
src/tests/modules/punk/args/testsuites/args/formviability.test

@ -0,0 +1,246 @@
package require tcltest
package require punk::args
#G-152 suffix-viability verdict characterization - added 2026-08-02.
#A multiform parse failure reports a per-form suffix-viability verdict: a form's
#status is 'incomplete' (VIABLE) if and only if every supplied word was validly
#consumed as a prefix of that form and the failure is pure exhaustion at
#end-of-input; anything already supplied contradicting the form yields 'invalid'
#(NOT viable - no appended words can satisfy it). The verdict rides the
#noformmatch errorcode classes, the -formerrors records and the parse_status
#formstatus records (single-form parses included), and the rendered noformmatch
#message marks each form line '(viable - needs more arguments)' or '(not viable)'.
#Contract documentation: the punk::args::parse and punk::args::parse_status
#argdoc help bodies.
namespace eval ::testspace {
namespace import ::tcltest::*
variable common {
set result ""
}
#tool-shaped multiform fixture (the G-152 acceptance shape): literal action
#leaders; the info form requiring a value; the build form carrying an option
#and optional multiple values
punk::args::define {
@id -id ::testspace::vtool
@cmd -name testspace::vtool -summary "tool-shaped multiform" -help "tool-shaped multiform fixture"
@form -form list
@leaders -min 1 -max 1
action -type string -choices {list} -optional 0
@form -form info
@leaders -min 1 -max 1
action -type string -choices {info} -optional 0
@values -min 1 -max 1
toolname -type string -optional 0
@form -form build
@leaders -min 1 -max 1
action -type string -choices {build} -optional 0
@opts
-test -type boolean -default 1
@values -min 0 -max -1
toolname -type string -optional 1 -multiple 1
@form -form test
@leaders -min 1 -max 1
action -type string -choices {test} -optional 0
@values -min 0 -max -1
toolname -type string -optional 1 -multiple 1
}
#added 2026-08-02 (agent, G-152)
test formviability_viable_form_leads {a valid literal leader alone: its form is viable (incomplete), ranked first; every sibling form is invalid}\
-setup $common -body {
if {[catch {punk::args::parse {info} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} {
set classinfo [lindex [dict get $erroropts -errorcode] 2]
lappend result [lindex $classinfo 0]
set payload [lrange $classinfo 1 end]
lappend result [lindex [dict get $payload forms] 0]
set classes [dict get $payload classes]
lappend result [lindex $classes 0] [lsort -unique [lrange $classes 1 end]]
} else {
lappend result UNEXPECTED-parsed
}
set result
}\
-cleanup {
}\
-result [list noformmatch info incomplete invalid]
#added 2026-08-02 (agent, G-152)
test formviability_no_viable_form {a word satisfying no action leader: NO form is viable - the false 'incomplete' on value-carrying forms is gone}\
-setup $common -body {
if {[catch {punk::args::parse {frobnicate} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} {
set classinfo [lindex [dict get $erroropts -errorcode] 2]
lappend result [lindex $classinfo 0]
lappend result [lsort -unique [dict get [lrange $classinfo 1 end] classes]]
} else {
lappend result UNEXPECTED-parsed
}
set result
}\
-cleanup {
}\
-result [list noformmatch invalid]
#added 2026-08-02 (agent, G-152)
test formviability_bad_flag_no_viable_form {a valid action leader followed by an unknown flag: no form is viable}\
-setup $common -body {
if {[catch {punk::args::parse {build -bogus} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} {
set classinfo [lindex [dict get $erroropts -errorcode] 2]
lappend result [lindex $classinfo 0]
lappend result [lsort -unique [dict get [lrange $classinfo 1 end] classes]]
} else {
lappend result UNEXPECTED-parsed
}
set result
}\
-cleanup {
}\
-result [list noformmatch invalid]
#added 2026-08-02 (agent, G-152)
test formviability_empty_input_all_viable {no words supplied: every form is viable}\
-setup $common -body {
set pstat [punk::args::parse_status {} withid ::testspace::vtool]
lappend result [dict get $pstat ok] [dict get $pstat status]
foreach f {list info build test} {
lappend result [dict get $pstat formstatus $f status]
}
set result
}\
-cleanup {
}\
-result [list 0 incomplete incomplete incomplete incomplete incomplete]
#added 2026-08-02 (agent, G-152)
test formviability_formerrors_and_formstatus_agree {the -formerrors errorcode records and the parse_status formstatus carry the same ranked per-form verdicts}\
-setup $common -body {
if {[catch {punk::args::parse {info} -errorstyle minimal withid ::testspace::vtool} errmsg erroropts]} {
set customdict [lrange [dict get $erroropts -errorcode] 3 end]
set formerrors [dict get $customdict -formerrors]
dict for {f finfo} $formerrors {
lappend result $f [dict get $finfo status]
}
} else {
lappend result UNEXPECTED-parsed
}
set pstat [punk::args::parse_status {info} withid ::testspace::vtool]
dict for {f finfo} [dict get $pstat formstatus] {
lappend result [dict get $finfo status]
}
set result
}\
-cleanup {
}\
-result [list info incomplete list invalid build invalid test invalid incomplete invalid invalid invalid]
#added 2026-08-02 (agent, G-152)
test formviability_success_path_verdicts {an auto-selected multiform parse reports sound verdicts for the non-matching forms}\
-setup $common -body {
set argd [punk::args::parse {info punkzip} withid ::testspace::vtool]
lappend result [dict get $argd form]
lappend result [dict get $argd formstatus info status]
lappend result [dict get $argd formstatus build status]
lappend result [dict get $argd formstatus list status]
}\
-cleanup {
}\
-result [list info valid invalid invalid]
#added 2026-08-02 (agent, G-152)
test formviability_required_option_position_guard {a missing required option is viable only while no value words have begun}\
-setup $common -body {
punk::args::define {
@id -id ::testspace::vreqopt
@cmd -name testspace::vreqopt -summary "required-option multiform" -help "required-option multiform fixture"
@form -form go
@leaders -min 1 -max 1
action -type string -choices {go} -optional 0
@opts
-dest -type string -optional 0
@values -min 0 -max -1
extra -type string -optional 1 -multiple 1
@form -form stop
@leaders -min 1 -max 1
action -type string -choices {stop} -optional 0
}
#no value words yet: appending '-dest <val>' can still satisfy the go form
set pstat [punk::args::parse_status {go} withid ::testspace::vreqopt]
lappend result [dict get $pstat formstatus go status]
#a value word has begun: appended flag words would be consumed as values
set pstat [punk::args::parse_status {go sometarget} withid ::testspace::vreqopt]
lappend result [dict get $pstat formstatus go status]
set pstat [punk::args::parse_status {go -dest x} withid ::testspace::vreqopt]
lappend result [dict get $pstat formstatus go status]
}\
-cleanup {
punk::args::undefine ::testspace::vreqopt 1
}\
-result [list incomplete invalid valid]
#added 2026-08-02 (agent, G-152)
test formviability_singleform_formstatus_sound {single-form parse_status: the formstatus record carries the viability verdict while the top-level status keeps the engine classification}\
-setup $common -body {
punk::args::define {
@id -id ::testspace::vsingle
@cmd -name testspace::vsingle -summary "single-form fixture" -help "single-form fixture"
@values -min 2 -max 2
num -type int -optional 0
name -type string -optional 0
}
#valid prefix (5 satisfies num) ending in exhaustion: viable
set pstat [punk::args::parse_status {5} withid ::testspace::vsingle]
lappend result [dict get $pstat status] [dict get $pstat formstatus _default status]
#word contradicts num's int type: engine classifies the allocation
#shortfall incomplete (documented) but the form is NOT viable
set pstat [punk::args::parse_status {notanint} withid ::testspace::vsingle]
lappend result [dict get $pstat status] [dict get $pstat failureclass] [dict get $pstat formstatus _default status]
}\
-cleanup {
punk::args::undefine ::testspace::vsingle 1
}\
-result [list incomplete incomplete incomplete missingrequiredvalue invalid]
#added 2026-08-02 (agent, G-152)
test formviability_clause_partial_conservative {input ending inside a multi-member type clause reports invalid - documented conservatism of the verdict}\
-setup $common -body {
punk::args::define {
@id -id ::testspace::vclausey
@cmd -name testspace::vclausey -summary "clause multiform" -help "multi-member clause fixture"
@form -form pair
@values -min 0 -max -1
coord -type {int int} -optional 0 -multiple 1
@form -form reset
@values -min 1 -max 1
reset -type literal(reset)
}
#{5} could be completed to {5 6} - but clause allocation cannot affirm the
#partial words, so the contract reports invalid (conservative direction)
set pstat [punk::args::parse_status {5} withid ::testspace::vclausey]
lappend result [dict get $pstat formstatus pair status]
#complete clause parses; the counterpart form judges soundly
set pstat [punk::args::parse_status {5 6} withid ::testspace::vclausey]
lappend result [dict get $pstat formstatus pair status] [dict get $pstat formstatus reset status]
}\
-cleanup {
punk::args::undefine ::testspace::vclausey 1
}\
-result [list invalid valid invalid]
#added 2026-08-02 (agent, G-152)
test formviability_message_marks_viability {the rendered noformmatch message visibly distinguishes viable forms from impossible ones per form line}\
-setup $common -body {
catch {punk::args::parse {info} -errorstyle minimal -caller vtool withid ::testspace::vtool} errmsg
lappend result [string match "*form 'info': (viable - needs more arguments)*" $errmsg]
lappend result [string match "*form 'list': (not viable)*" $errmsg]
lappend result [string match "*form 'build': (not viable)*" $errmsg]
lappend result [string match "*form 'test': (not viable)*" $errmsg]
#the form-line anchor consumers/tests rely on stays intact
lappend result [string match "*form 'info':*" $errmsg]
}\
-cleanup {
}\
-result [list 1 1 1 1 1]
}
tcltest::cleanupTests ;#needed to produce test summary line.
Loading…
Cancel
Save