diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d61e184..c8fe8936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ 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.51.1] - 2026-08-04 + +- punk::args 0.15.1 (G-164): the G-152 form-viability verdict's + valmin-reservation false negative is cured. When the reservation + seats a word that literally names a declared required option into a + required value slot at end-of-input, and the resulting optionmissing + stands in the viability probe (a value word consumed), the probe + re-parses with the stolen word(s) consumed as the options they name + and classes the form viable (incomplete) on pure end-of-input + exhaustion. `i hash -t` now marks the test form "(viable - needs + more arguments)" (show_or_set/rehash/delete/help stay not viable) + and `i hash -d` marks delete viable - the 0.51.0 known issue is + resolved. Runtime seating is unchanged: the trailing-flag-as-value + rule is load-bearing and pinned unchanged, as is the runtime + optionmissing ("missing flags") path; a stolen word naming a + value-TAKING required option stays conservative (invalid). Options + still missing after the re-seating are ordinary exhaustion in the + re-probe. Verdict contract documentation updated in the + parse/parse_status argdoc. Pins: formviability.test +7 tests + (17/17), cmdhelp.test GAP pin + cmdhelp_GAP_multiform_flagled_testform_false_notviable flipped to + cmdhelp_multiform_flagled_testform_viable (45/45); args + ns + subtrees green apart from the 2 pre-existing nsprimitives + environment failures on this box. + ## [0.51.0] - 2026-08-04 - `i ?word ...?` form narrowing for flat multi-form commands diff --git a/GOALS-archive.md b/GOALS-archive.md index 6e738966..2378ce3e 100644 --- a/GOALS-archive.md +++ b/GOALS-archive.md @@ -20,6 +20,10 @@ Do not edit archived records here except to fix a broken path. Archived detail f ## Archived goals +### G-164 [achieved 2026-08-04] punk::args viability probe: alternative-allocation re-probe for reservation-stolen flag words → detail: goals/archive/G-164-viability-alternative-allocation.md +Scope: src/modules/punk/args-999999.0a1.0.tm (G-152 viability probe - private::get_dict_form optional 4th arg); src/tests/modules/punk/args/testsuites/args/formviability.test; src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (GAP flip) +Acceptance: 'i ::punk::auto_exec::hash -t' classes the test form viable (incomplete) while show_or_set/rehash/delete/help remain not viable, and {-d} classes delete viable; no parse RESULT changes anywhere (runtime seating untouched): full args and ns subtrees green apart from the deliberate GAP flip, with flagvalues_plain_then_dash (trailing-flag-as-value) and the runtime 'missing flags' message path pinned unchanged; the GAP pin cmdhelp_GAP_multiform_flagled_testform_false_notviable flips to the viable assertion. + ### G-150 [achieved 2026-08-04] punk::ns cmdhelp form narrowing + selection-sound auto_exec::hash declaration → detail: goals/archive/G-150-cmdhelp-form-narrowing.md Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdhelp); src/modules/punk/auto_exec-999999.0a1.0.tm (hash definition); src/tests/modules/punk/ns/testsuites/ (cmdhelp + auto_exec pins) Acceptance: 'i ::punk::auto_exec::hash -r' renders only the rehash form's usage and 'i ::punk::auto_exec::hash -d ' only the delete form, driven by the definition (no hash-handler involvement); hash's plain name value carries a flag-excluding constraint (-regexprefail {^-} or equivalent) such that bare punk::args::parse withid selects flag-led forms uniquely, with hash's runtime behaviour otherwise unchanged (a name literally starting with '-' now requires explicit -form selection - the accepted cost); ensembles, single-form commands and no-word invocations render as today (existing cmdhelp behaviour pinned unchanged); the behaviours are pinned in the module testsuites. diff --git a/GOALS.md b/GOALS.md index 9e5c4264..1651bcfe 100644 --- a/GOALS.md +++ b/GOALS.md @@ -404,7 +404,3 @@ Detail: goals/G-162-bakelist-kit-freshness.md Scope: src/modules/punk/repl-999999.0a1.0.tm (exit/eof done paths, reader-window blocking-restore discipline); src/modules/punk/console-999999.0a1.0.tm (drain helper, -blocking 0 window sites); src/lib/app-punkshell/punkshell.tcl (final-exit seam); ARCHITECTURE.md (Process exit and shutdown section); src/tests/shell/testsuites/punkexe/ (unix-pty-gated drain pin) Detail: goals/G-163-unix-exit-output-drain.md -### G-164 [proposed] punk::args viability probe: alternative-allocation re-probe for reservation-stolen flag words -Scope: src/modules/punk/args-999999.0a1.0.tm (G-152 viability probe - private::get_dict_form optional 4th arg); src/tests/modules/punk/args/testsuites/args/formviability.test; src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (GAP flip) -Detail: goals/G-164-viability-alternative-allocation.md - diff --git a/goals/G-044-repl-command-completion.md b/goals/G-044-repl-command-completion.md index a6622d4e..5c9cce38 100644 --- a/goals/G-044-repl-command-completion.md +++ b/goals/G-044-repl-command-completion.md @@ -93,7 +93,7 @@ arginfo principle: introspection must not run commands to elicit usage). -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). -- G-164 (proposed 2026-08-04) is the probe-layer answer to the G-152 note's "consumer lookahead" for one class: the valmin-reservation false negative (a required-flag word stolen into a required value slot at end-of-input - e.g 'i hash -t', where optionmissing stands and the test form classes not viable although the completing suffix 'hash -t foo' exists). G-164 re-probes with the stolen word consumed as the option it literally names, granting viable-incomplete on pure exhaustion; runtime seating stays unchanged. Current state documented by the 0.51.0 CHANGELOG known-issue text and the GAP pin cmdhelp_GAP_multiform_flagled_testform_false_notviable (flips under G-164). +- G-164 (achieved 2026-08-04 - goals/archive/G-164-viability-alternative-allocation.md) was the probe-layer answer to the G-152 note's "consumer lookahead" for one class: the valmin-reservation false negative (a required-flag word stolen into a required value slot at end-of-input - e.g 'i hash -t', where optionmissing stood and the test form classed not viable although the completing suffix 'hash -t foo' exists). The probe now re-parses with the stolen word consumed as the option it literally names, granting viable-incomplete on pure exhaustion; runtime seating is unchanged. G-164 recorded the value-TAKING required option conservatism as an open follow-on (a completing suffix exists there too - candidate refinement if this goal's completion consumer needs it) - see goals/archive/G-164-viability-alternative-allocation.md. - 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) diff --git a/goals/G-164-viability-alternative-allocation.md b/goals/archive/G-164-viability-alternative-allocation.md similarity index 53% rename from goals/G-164-viability-alternative-allocation.md rename to goals/archive/G-164-viability-alternative-allocation.md index 2f15232e..17329440 100644 --- a/goals/G-164-viability-alternative-allocation.md +++ b/goals/archive/G-164-viability-alternative-allocation.md @@ -1,6 +1,6 @@ # G-164 punk::args viability probe: alternative-allocation re-probe for reservation-stolen flag words -Status: proposed +Status: achieved 2026-08-04 Scope: src/modules/punk/args-999999.0a1.0.tm (G-152 viability probe - private::get_dict_form optional 4th arg); src/tests/modules/punk/args/testsuites/args/formviability.test; src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (GAP flip) Goal: When the valmin reservation seats a word that literally names a declared required option into a required value slot, and the resulting optionmissing stands in the viability probe (a value word consumed), the probe re-parses with the stolen word(s) consumed as the option they name; pure end-of-input exhaustion after that re-seating grants 'incomplete' (viable). Runtime seating is unchanged - the trailing-flag-as-value rule is load-bearing. Acceptance: 'i ::punk::auto_exec::hash -t' classes the test form viable (incomplete) while show_or_set/rehash/delete/help remain not viable, and {-d} classes delete viable; no parse RESULT changes anywhere (runtime seating untouched): full args and ns subtrees green apart from the deliberate GAP flip, with flagvalues_plain_then_dash (trailing-flag-as-value) and the runtime 'missing flags' message path pinned unchanged; the GAP pin cmdhelp_GAP_multiform_flagled_testform_false_notviable flips to the viable assertion. @@ -15,3 +15,13 @@ Surfaced by the G-150 batch (2026-08-04, punkshell 0.51.0): removing the help fo - Related: G-152 (achieved 2026-08-02 - goals/archive/G-152-punkargs-form-viability-verdict.md) - the suffix-viability verdict layer this refines; its 'deliberately seating-based' note already records that a completion consumer wanting flag-pending-value awareness needs its own lookahead - this goal is that lookahead, at the probe layer. - Related: G-150 (achieved 2026-08-04 - goals/archive/G-150-cmdhelp-form-narrowing.md) - the batch that surfaced the false negative; its cmdhelp.test GAP pin (cmdhelp_GAP_multiform_flagled_testform_false_notviable) flips here. - Overlap survey 2026-08-04: goals_xref paths src/modules/punk/args-999999.0a1.0.tm src/tests/modules/punk/args/testsuites/args/formviability.test src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test - the args family (G-044 G-050 G-053 G-056 G-069 G-072 G-073 G-075 G-083 G-084 G-088 G-149 G-151) via the ubiquitous args-module anchor and the ns family (G-050 G-052 G-075 G-085) via the shared cmdhelp.test anchor; G-044 named above as the consumer, the rest are unrelated parse/render mechanisms. G-152/G-150 (archived) named above. +- Activation freshness re-run 2026-08-04 (goals_xref score G-164, at user-directed activation same day as drafting): no new related goals - the one-directional args/ns-family pairs are the goals already surveyed above; the unlinked pairs (G-158/G-110/G-055/G-019/G-028 et al) are term noise on runtime/load/plain/required. No new Related lines required. + +## Progress + +- 2026-08-04 IMPLEMENTED (punk::args 0.15.1, punkshell 0.51.1): the alternative-allocation re-probe landed in private::get_dict_form. The valmin reservation's option-scan seating site now records the words it seats without an option check (reservation_seated); at the optionmissing raise in probe mode, when reservation-seated words literally name missing required options (exact match against the option's declared spellings), the probe re-parses via a {1 reseat_words} viabilitycheck argument with those words consumed as the options they name (the reservation break is bypassed for exactly those words); a re-seated parse failing only on pure end-of-input exhaustion (or clean) passes the probe. Options still missing after the re-seating are ordinary exhaustion in the re-probe (no value words precede them), so {-t} on a two-required-flag form is viable too. A stolen word naming a value-TAKING required option stays conservative: re-seated, it seats as a value again at end-of-input (the trailing-flag-as-value rule) and the optionmissing stands. Runtime seating untouched - the re-probe runs only inside the probe. Verdict contract documentation updated in the parse argdoc 'Multiform failure contract' and the parse_status argdoc formstatus section. +- 2026-08-04 VERIFIED (native tclsh 8.7a6 via runtests.tcl): acceptance pinned - formviability.test +7 tests (17/17: re-probe grants viable ranked-first with siblings invalid, delete-form {-d} viable, other-missing-options exhaustion, unnamed stolen word stays invalid, value-taking conservatism, runtime seating unchanged (optionmissing single-form / noformmatch multiform), single-form formstatus); the cmdhelp.test GAP pin flipped to cmdhelp_multiform_flagled_testform_viable (45/45). 'i ::punk::auto_exec::hash -t' verified live rendering "form 'test': (viable - needs more arguments)" with show_or_set/rehash/delete/help "(not viable)"; 'i hash -d' renders delete viable. args + ns subtrees green apart from the 2 pre-existing nsprimitives environment failures on this box; full source-tree -jobs 16 run at parity with the recorded pre-existing environment failures (exec.test 5, argparsingtest 5, commandstack 1, modpod roundtrip 1, lib compat lpop 1, nsprimitives 2, repl consolebackends/nscurrent SIGILL 4, zipreader 2, offsetstyle 1 - none args-related; flagvalues_plain_then_dash and the runtime optionmissing message path pinned unchanged). make.tcl modules mint clean (args-0.15.1.tm, args-0.15.0.tm pruned); bootsupport/vfscommon promotion left to the next promotion cycle (per the 0.50.1 precedent). + +## Follow-ons + +Follow-on: a stolen word naming a value-TAKING required option stays conservative (invalid) - re-seated, it seats as a value again at end-of-input (the trailing-flag-as-value rule) although a completing suffix exists (e.g {-dest x foo}); a further probe layer could re-seat with the option's own value also supplied - candidate refinement if the G-044 completion consumer needs it => open diff --git a/punkproject.toml b/punkproject.toml index cd1df5ab..faafecea 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.51.0" +version = "0.51.1" license = "BSD-2-Clause" url = "https://www.gitea1.intx.com.au/jn/punkshell" #packager: declared identity for published artifacts (declarative, not proof - diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index ea3fb682..6cb6dc37 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -6558,6 +6558,16 @@ tcl::namespace::eval punk::args { 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. + Reservation-stolen flag words (G-164): when the valmin reservation + seats a word that literally names a declared required option into + a required value slot at end-of-input, the viability probe + re-parses with the stolen word(s) consumed as the options they + name and grants 'incomplete' on pure exhaustion after that + re-seating (e.g a form requiring flag -t plus a value classes + viable for {-t}, since {-t foo} parses). This re-probe does not + extend to a stolen word naming a value-TAKING required option, + which stays conservative (invalid). Runtime seating is unchanged + in both cases - the trailing-flag-as-value rule is load-bearing. Arguments matching several forms cleanly raise errorcode PUNKARGS VALIDATION {multipleformmatches forms } Consumers (form narrowing, interactive completion) should select @@ -7763,6 +7773,16 @@ tcl::namespace::eval punk::args { {literal(elseif) expr script}): input ending inside such a clause reports invalid even when the partial clause words match. + Reservation-stolen flag words (G-164): when the + valmin reservation seats a word that literally + names a declared required option into a required + value slot at end-of-input, the viability probe + re-parses with the stolen word(s) consumed as the + options they name and grants 'incomplete' on pure + exhaustion after that re-seating (a stolen word + naming a value-TAKING required option stays + conservative). Runtime seating is unchanged - the + trailing-flag-as-value rule is load-bearing. Single-form parses report their one form; when multi-form candidacy ran, every attempted form is reported - partial-arglist consumers (e.g command @@ -9674,11 +9694,32 @@ tcl::namespace::eval punk::args { #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. + #G-164 refinement (alternative-allocation re-probe): when the valmin + #reservation seats a word that literally names a declared required option + #into a required value slot at end-of-input, and the resulting optionmissing + #stands in the probe (a value word was consumed, defeating the position + #guard), the probe re-parses with the stolen word(s) consumed as the options + #they name (a {1 reseat_words} viabilitycheck argument); pure end-of-input + #exhaustion after that re-seating means the words are a valid prefix (a + #completing suffix exists - e.g {-t} on a form requiring flag -t plus a + #value: {-t foo} parses). Runtime seating is unchanged - the re-probe runs + #only inside the probe. A stolen word naming a value-TAKING required option + #stays conservative: re-seated, it seats as a value again at end-of-input + #(the trailing-flag-as-value rule) and the optionmissing stands. #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}} { + #G-164: the probe's alternative-allocation re-probe invokes this proc with a + #2-element viabilitycheck {1 reseat_words} - words the valmin reservation + #stole into required value slots in the primary probe that literally name + #declared required options; on this pass they are consumed as the options + #they name (see the optionmissing site far below). + set reseat_words [list] + if {[llength $viabilitycheck] > 1} { + lassign $viabilitycheck viabilitycheck reseat_words + } #puts "-arg_info->$arg_info" set flagsreceived [list] ;#for checking if required flags satisfied set solosreceived [list] @@ -9750,6 +9791,10 @@ tcl::namespace::eval punk::args { #a word consumed by a later slot leaves its entry unused. set alloc_rejections [dict create] + #G-164: words the valmin reservation seats as values without ever comparing + #them to the declared options (recorded for the probe's re-probe trigger) + set reservation_seated [list] + if {$VAL_MIN eq ""} { set valmin 0 #set VAL_MIN 0 @@ -10147,10 +10192,19 @@ tcl::namespace::eval punk::args { set remaining_args_including_this [expr {[llength $remaining_rawargs] - $i}] #lowest valmin is 0 if {$remaining_args_including_this <= $valmin} { - # if current arg is -- it will pass through as a value here - set arglist [lrange $remaining_rawargs 0 $i-1] - set post_values [lrange $remaining_rawargs $i end] - break + if {[llength $reseat_words] && [lindex $remaining_rawargs $i] in $reseat_words} { + #G-164 re-probe: this word literally names a declared required + #option the reservation stole into a value slot on the primary + #probe pass - fall through to consume it as the option it names + } else { + # if current arg is -- it will pass through as a value here + set arglist [lrange $remaining_rawargs 0 $i-1] + set post_values [lrange $remaining_rawargs $i end] + #G-164: seated as values by the valmin reservation - never + #compared to the declared options + set reservation_seated $post_values + break + } } set a [lindex $remaining_rawargs $i] set a1 [string index $a 0] @@ -11404,6 +11458,45 @@ tcl::namespace::eval punk::args { set api_opt_required [lmap v $OPT_REQUIRED {lindex [split $v |] end}] if {[llength [set missing [punk::args::system::punklib_ldiff $api_opt_required $flagsreceived]]]} { + #G-164 viability probe, alternative-allocation re-probe: this + #optionmissing may be an artifact of the valmin reservation having + #stolen flag words into required value slots at end-of-input (a + #value word was consumed, so the probe's position guard above lets + #the absence stand - e.g {-t} on a form requiring flag -t plus a + #value, where {-t foo} parses). When a missing required option is + #literally named by a reservation-seated word, re-probe with the + #stolen word(s) consumed as the options they name: if the + #re-seated parse then fails only on pure end-of-input exhaustion + #(or not at all), the supplied words ARE a valid prefix of the + #form and the probe passes. Options still missing after the + #re-seating are ordinary exhaustion in the re-probe (no value + #words precede them) - they do not block the pass. Runtime + #seating is unchanged - this runs only inside the probe, and only + #via reservation-seated words (the trailing-flag-as-value rule is + #load-bearing). + if {$viabilitycheck && [llength $reservation_seated]} { + set reseat [list] + foreach m $missing { + set spellings [list] + if {[dict exists $lookup_optset $m]} { + foreach optdef [split [dict get $lookup_optset $m] |] { + lappend spellings [string trimright $optdef =] + } + } else { + lappend spellings $m + } + foreach w $reservation_seated { + if {$w in $spellings} { + lappend reseat $w + break + } + } + } + if {[llength $reseat] && ![catch {private::get_dict_form $argspecs $fid $rawargs [list 1 $reseat]}]} { + #re-seated parse is exhausted-only or clean: valid prefix + return + } + } set full_missing [list] foreach m $missing { if {[dict exists $lookup_optset $m]} { diff --git a/src/modules/punk/args-buildversion.txt b/src/modules/punk/args-buildversion.txt index 55ed3f6f..d7896164 100644 --- a/src/modules/punk/args-buildversion.txt +++ b/src/modules/punk/args-buildversion.txt @@ -1,6 +1,7 @@ -0.15.0 +0.15.1 #First line must be a semantic version number #all other lines are ignored. +#0.15.1 - G-164 viability-probe alternative-allocation re-probe: when the valmin reservation seats a word that literally names a declared required option into a required value slot at end-of-input, and the resulting optionmissing stands in the G-152 viability probe (a value word consumed, defeating the position guard), the probe now re-parses with the stolen word(s) consumed as the options they name (private::get_dict_form's viabilitycheck argument carries {1 reseat_words} on the re-probe pass) and grants 'incomplete' (viable) on pure end-of-input exhaustion after that re-seating. Cures the 'i hash -t' false negative: the test form classes viable since {-t foo} parses; sibling forms stay invalid. Options still missing after the re-seating are ordinary exhaustion in the re-probe. Runtime seating is unchanged - the re-probe runs only inside the probe, only via reservation-seated words (the trailing-flag-as-value rule is load-bearing; flagvalues_plain_then_dash and the runtime optionmissing path pinned unchanged). A stolen word naming a value-TAKING required option stays conservative (invalid) - re-seated, it seats as a value again at end-of-input. Verdict contract documentation updated in the parse/parse_status argdoc. formviability.test gains 7 pins (re-probe, delete-form, other-missing-options exhaustion, unnamed stolen word, value-taking conservatism, runtime-unchanged, single-form formstatus); the cmdhelp.test GAP pin cmdhelp_GAP_multiform_flagled_testform_false_notviable flips to cmdhelp_multiform_flagled_testform_viable. #0.15.0 - @values -flagvalues (G-150 follow-on, user-directed): a @values section may declare -flagvalues 1 - an unmatched dash-led word in option position is then consumed as a trailing value instead of raising invalidoption (declared options still match first; the '--' end-of-options marker is unaffected; off by default). Motivation: '--help'-style forms robustly swallowing ignored trailing words of any shape - previously such a word seated as a value only positionally (at end-of-input via the valmin reservation, or within a bounded valmax) and errored 'Unexpected option' otherwise ('{--help -t foo}' on the unconstrained shape). Mechanism: VAL_FLAGVALUES on the form dict (template default false; @values compiler key, boolean-validated at resolve time) short-circuits the unmatched-dash-word fallback in the parse engine. In-tree consumer: punk::auto_exec::hash's help form. New testsuite flagvalues.test (9 tests: seating matrix, declared-option precedence, off-by-default contrast, optionmissing after value seating, key validation). Documented in the module header notes. #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 '':" 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. diff --git a/src/tests/modules/punk/args/testsuites/args/formviability.test b/src/tests/modules/punk/args/testsuites/args/formviability.test index 6cd66e5b..d3dad490 100644 --- a/src/tests/modules/punk/args/testsuites/args/formviability.test +++ b/src/tests/modules/punk/args/testsuites/args/formviability.test @@ -13,6 +13,14 @@ package require punk::args #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. +#G-164 (2026-08-04): the probe's alternative-allocation re-probe - when the valmin +#reservation seats a word that literally names a declared required option into a +#required value slot at end-of-input and the resulting optionmissing stands in the +#probe (a value word consumed), the probe re-parses with the stolen word(s) +#consumed as the options they name and grants viable-incomplete on pure +#exhaustion. Runtime seating is unchanged (the trailing-flag-as-value rule is +#load-bearing); a stolen word naming a value-TAKING required option stays a +#documented conservatism. namespace eval ::testspace { namespace import ::tcltest::* @@ -48,6 +56,33 @@ namespace eval ::testspace { toolname -type string -optional 1 -multiple 1 } + #added 2026-08-04 (agent, G-164) - hash-shaped multiform fixture (mirrors + #punk::auto_exec::hash's selection-sound declaration): flag-led forms whose + #required -type none flag is followed by required value(s); show_or_set's + #optional name value carries the flag-excluding constraint + punk::args::define { + @id -id ::testspace::vhash + @cmd -name testspace::vhash -summary "hash-shaped multiform" -help "hash-shaped multiform fixture" + @form -form show_or_set + @opts -min 0 -max 0 + @values -min 0 -max -1 + name -type string -optional 1 -multiple 1 -regexprefail {^-} + @form -form rehash + @opts -min 1 -max 1 + -r -type none -optional 0 + @values -min 0 -max 0 + @form -form test + @opts + -t -type none -optional 0 + @values -min 1 -max -1 + name -type string -optional 0 -multiple 1 + @form -form delete + @opts + -d -type none -optional 0 + @values -min 1 -max -1 + name -type string -optional 0 -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 { @@ -242,5 +277,137 @@ namespace eval ::testspace { -cleanup { }\ -result [list 1 1 1 1 1] + + #added 2026-08-04 (agent, G-164) - the valmin reservation seats the lone -t as the + #required name value at end-of-input and the position guard lets optionmissing + #stand; the probe's alternative-allocation re-probe re-parses with the stolen word + #consumed as the option it literally names, granting viable-incomplete on pure + #exhaustion ({-t foo} parses as test). Runtime seating is unchanged - the + #re-probe runs only inside the viability probe. + test formviability_reservation_stolen_flag_reprobed {a flag word the valmin reservation stole into the required value slot is re-probed as the option it literally names: viable (incomplete), ranked first; sibling forms stay invalid}\ + -setup $common -body { + if {[catch {punk::args::parse {-t} -errorstyle minimal withid ::testspace::vhash} 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 test incomplete invalid] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_delete_form_viable {{-d} classes the delete form viable (incomplete) by the same re-probe; the test form stays invalid because -d names no missing option of it}\ + -setup $common -body { + if {[catch {punk::args::parse {-d} -errorstyle minimal withid ::testspace::vhash} 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 delete incomplete invalid] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_other_missing_options_are_exhaustion {required options never supplied remain pure exhaustion in the re-probe - only the reservation-stolen words need naming}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::vtwoflags + @cmd -name testspace::vtwoflags -summary "two required flags + value" -help "two required flags fixture" + @form -form go + @opts + -t -type none -optional 0 + -x -type none -optional 0 + @values -min 1 -max 1 + name -type string -optional 0 + } + #-t stolen, -x never supplied: both satisfiable by appending ({-t -x foo}) + set pstat [punk::args::parse_status {-t} withid ::testspace::vtwoflags] + lappend result [dict get $pstat formstatus go status] + set pstat [punk::args::parse_status {-t -x} withid ::testspace::vtwoflags] + lappend result [dict get $pstat formstatus go status] + #the completing suffix really does parse at runtime + lappend result [dict get [punk::args::parse {-t -x foo} withid ::testspace::vtwoflags] form] + }\ + -cleanup { + punk::args::undefine ::testspace::vtwoflags 1 + }\ + -result [list incomplete incomplete go] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_unnamed_stolen_word_stays_invalid {a reservation-stolen word naming no missing required option grants no re-probe pass - the form is genuinely not viable}\ + -setup $common -body { + set pstat [punk::args::parse_status {-z} withid ::testspace::vhash] + lappend result [dict get $pstat formstatus test status] + }\ + -cleanup { + }\ + -result [list invalid] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_value_taking_option_conservative {a stolen word naming a value-TAKING required option stays invalid - re-seated, it seats as a value again at end-of-input (documented conservatism)}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::vdest + @cmd -name testspace::vdest -summary "required value-taking option + value" -help "value-taking option fixture" + @form -form go + @opts + -dest -type string -optional 0 + @values -min 1 -max 1 + name -type string -optional 0 + } + set pstat [punk::args::parse_status {-dest} withid ::testspace::vdest] + lappend result [dict get $pstat formstatus go status] + }\ + -cleanup { + punk::args::undefine ::testspace::vdest 1 + }\ + -result [list invalid] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_runtime_seating_unchanged {the re-probe is probe-only: runtime parses of the same words keep the engine's own failure classes (optionmissing single-form, noformmatch multiform)}\ + -setup $common -body { + catch {punk::args::parse {-t} -form test withid ::testspace::vhash} msg opts + lappend result [lindex [lindex [dict get $opts -errorcode] 2] 0] + catch {punk::args::parse {-t} -errorstyle minimal withid ::testspace::vhash} msg opts + lappend result [lindex [lindex [dict get $opts -errorcode] 2] 0] + }\ + -cleanup { + }\ + -result [list optionmissing noformmatch] + + #added 2026-08-04 (agent, G-164) + test formviability_reprobe_singleform_formstatus {single-form parse_status: the formstatus record gains the re-probe's viable verdict while the top-level status keeps the engine classification and the runtime parse still raises optionmissing}\ + -setup $common -body { + punk::args::define { + @id -id ::testspace::vsflag + @cmd -name testspace::vsflag -summary "single-form required flag + value" -help "single-form fixture" + @opts + -t -type none -optional 0 + @values -min 1 -max 1 + name -type string -optional 0 + } + set pstat [punk::args::parse_status {-t} withid ::testspace::vsflag] + lappend result [dict get $pstat status] [dict get $pstat formstatus _default status] + catch {punk::args::parse {-t} withid ::testspace::vsflag} msg opts + lappend result [lindex [lindex [dict get $opts -errorcode] 2] 0] + }\ + -cleanup { + punk::args::undefine ::testspace::vsflag 1 + }\ + -result [list incomplete incomplete optionmissing] } tcltest::cleanupTests ;#needed to produce test summary line. diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test index cdc14837..19fe4849 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test @@ -22,7 +22,9 @@ package require punk::auto_exec #selection contract, the whole-render fallbacks (no words / unknown flag / ambiguity) and #hash's unchanged runtime behaviour. The GAP pin #cmdhelp_GAP_multiform_flagled_testform_false_notviable (the valmin-reservation false -#negative in the G-152 viability verdict) is owned by G-164 (proposed). +#negative in the G-152 viability verdict) flipped 2026-08-04 under G-164 to +#cmdhelp_multiform_flagled_testform_viable (the probe's alternative-allocation +#re-probe grants the test form viable-incomplete). # #Assertion approach (as usagemarking.test): expected SGR styles are read from the live #colour arrays and matched by SGR parameter subset - renderers may merge/reorder codes. @@ -989,28 +991,28 @@ bar /bin/bar} 0 1 0 1] -result [list 0 noformmatch 1 1 1 1 1] #added 2026-08-04 (agent, G-150) - GAP pin of a KNOWN FALSE NEGATIVE in the G-152 - #viability verdict: for 'i hash -t' the test form renders "(not viable)" although + #viability verdict: for 'i hash -t' the test form rendered "(not viable)" although #{-t} has a completing suffix ({-t foo} parses as test, pinned below). Mechanism: #the valmin reservation seats the lone -t as the required name value (never compared #to the declared -t option), after which the G-152 position guard (missing-required- #option suppressed only with no value words consumed) lets optionmissing stand. - #Not one of G-152's documented conservatisms. Desired end state: an alternative- - #allocation re-probe in the probe layer (stolen word(s) literally matching the - #missing declared option -> re-probe with them consumed as options, grant - #incomplete on pure exhaustion) - NOT a runtime reservation change, which would - #flip real parses (flagvalues_plain_then_dash pins the trailing-flag-as-value rule). - test cmdhelp_GAP_multiform_flagled_testform_false_notviable {KNOWN FALSE NEGATIVE: the test form renders "(not viable)" for a flag-led incomplete line that has a completing suffix (valmin reservation steals the flag word into the required value slot)}\ + #Flipped 2026-08-04 (agent, G-164): the probe's alternative-allocation re-probe + #re-parses with the reservation-stolen word consumed as the option it literally + #names and grants viable-incomplete on pure end-of-input exhaustion. Runtime + #seating is unchanged (flagvalues_plain_then_dash pins the trailing-flag-as-value + #rule; flagvalues_required_option_still_missing pins the runtime optionmissing). + test cmdhelp_multiform_flagled_testform_viable {the test form renders viable for a flag-led incomplete line with a completing suffix - the G-164 alternative-allocation re-probe cures the valmin-reservation false negative}\ -setup $common -body { #the completing suffix exists: {-t foo} selects the test form lappend result [dict get [punk::args::parse {-t foo} withid ::punk::auto_exec::hash] form] - #...yet the bare prefix renders "(not viable)" today (the pinned-wrong state) + #...and the bare prefix now renders viable for the test form set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash -t]] lappend result [string match {*form 'test': (not viable)*} $out] - lappend result [string match {*form 'test': (viable -*} $out] + lappend result [string match {*form 'test': (viable - needs more arguments)*} $out] }\ -cleanup { }\ - -result [list test 1 0] + -result [list test 0 1] test auto_exec_hash_test_multi_missing_name_message {a multi-name -t with a missing name returns the found entries and does not error (regression pin for the fixed handler message; emits the expected one-line stderr note 'hash: missingname: not found')}\ -setup {