Browse Source

G-150: auto_exec 0.1.1 selection-sound hash declaration + ns 0.9.2 cmdhelp narrowing docs + pins

- auto_exec 0.1.1: hash's show_or_set name value carries -regexprefail {^-} - a bare punk::args::parse of a flag-led line now selects its flag form uniquely instead of 'Ambiguous arguments ... show_or_set, <flag form>' (show_or_set swallowed flag-like words as plain names), so cmdhelp 'i hash -r' narrows to the rehash form with no handler involvement; runtime behaviour unchanged (the handler's first-word switch already rejected dash-led names; at the declaration level they now need an explicit flag form - accepted cost). Help form tightened (user direction): -anyopts dropped so --help must lead, and @values -flagvalues 1 (punk::args 0.15.0) makes trailing words of any shape consume as ignored values - a flag-led line no longer marks the help form viable via adhoc-option seating. Handler fix (user-landed): the multi-name -t missing-name branch printed "$hash: nm: not found" (undefined variable) - now "hash: $nm: not found"
- ns 0.9.2: documentation-only - cmdhelp's subcommand argdoc now states the multi-form narrowing contract explicitly (no code change; the G-041 advisory-parse candidacy already narrowed on a unique selection)
- cmdhelp.test: G-150 pins - flag-led narrowing on -r/-d, bare-parse selection incl -t/plain/empty lines, the dash-led-name noformmatch cost, no-word/unknown-flag/ambiguity whole-render fallbacks, runtime-unchanged hash behaviour, help-form narrowing and dashword swallow, and the no-false-viable-help pin; plus a GAP pin of the valmin-reservation false negative in the G-152 viability verdict (owned by G-164, proposed) - 45/45 green
- src/tests/modules/AGENTS.md: index clause for the new coverage
Assisted-by: harness=opencode; primary-model=openrouter/moonshotai/kimi-k3; api-location=openrouter.ai
master
Julian Noble 6 days ago
parent
commit
665785d239
  1. 24
      src/modules/punk/auto_exec-999999.0a1.0.tm
  2. 3
      src/modules/punk/auto_exec-buildversion.txt
  3. 8
      src/modules/punk/ns-999999.0a1.0.tm
  4. 3
      src/modules/punk/ns-buildversion.txt
  5. 2
      src/tests/modules/AGENTS.md
  6. 250
      src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

24
src/modules/punk/auto_exec-999999.0a1.0.tm

@ -119,9 +119,15 @@ tcl::namespace::eval punk::auto_exec {
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
name -type string -multiple 1 -optional 1 -default {} -regexprefail {^-} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
If no names are provided, then all autoexec commands in the hash table will be shown.
Names may not begin with a dash: a flag-led line selects a flag form
instead (-r/-t/-d/--help), mirroring the handler's first-word dispatch.
(G-150: the constraint makes a bare punk::args::parse of a flag-led line
select its flag form uniquely instead of reporting an ambiguous match
with this form, so declaration-driven consumers such as 'i hash -r'
form narrowing work without handler involvement.)"
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
@ -156,13 +162,17 @@ tcl::namespace::eval punk::auto_exec {
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
@opts -min 1 -max 1
--help -type none -optional 0 -help\
"Display usage information for this command."
"Display usage information for this command.
Must be the first word after 'hash'; trailing words are
consumed as ignored values."
@values -min 0 -max -1
@values -min 0 -max -1 -flagvalues 1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
"Additional arguments that are ignored when --help is used.
Dash-led words are consumed as values here (not treated
as options - @values -flagvalues 1)."
}]
}
@ -221,7 +231,7 @@ tcl::namespace::eval punk::auto_exec {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
puts stderr "hash: $nm: not found"
}
}
if {$result ne ""} {

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

@ -1,3 +1,4 @@
0.1.0
0.1.1
#First line must be a semantic version number
#all other lines are ignored.
#0.1.1 - G-150: hash's show_or_set name value carries -regexprefail {^-}, making the declaration selection-sound: a bare punk::args::parse of a flag-led line now selects its flag form uniquely instead of reporting 'Ambiguous arguments ... show_or_set, <flag form>' (show_or_set swallowed flag-like words as plain names). Unblocks declaration-driven consumers - cmdhelp 'i hash -r' narrows to the rehash form with no handler involvement. Runtime behaviour unchanged: the handler's first-word switch already rejected dash-led names ('invalid option'); at the declaration level such a name now requires an explicit flag form (accepted cost). Same-day refinements (user direction): (a) help form simplified - -anyopts dropped so --help must lead, and @values -flagvalues 1 (the new punk::args 0.15.0 capability, added for this) makes trailing words of any shape - dash-led included - consume as ignored values (a flag-led line no longer marks the help form viable via adhoc-option seating; the arbitrary-flag doc row is gone); (b) handler fix (user-landed) - the multi-name -t missing-name branch printed "$hash: nm: not found" (undefined variable $hash, literal 'nm:') - now "hash: $nm: not found".

8
src/modules/punk/ns-999999.0a1.0.tm

@ -5522,7 +5522,13 @@ y" {return quirkykeyscript}
definition and reflected in the usage display: a complete
valid set renders with the 'info' scheme and the received
arguments marked, an incomplete or invalid set renders with
the 'error' scheme."
the 'error' scheme.
On a flat multi-form command the advisory parse also selects
the displayed form: argument words resolving to exactly one
form narrow the usage display to that form's argument table
(marked in the synopsis), while lines selecting no single
form (no words, an ambiguous match, or words no form
accepts) keep the whole-command render."
}
proc cmdhelp {args} {
set nscaller [uplevel 1 [list ::tcl::namespace::current]]

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

@ -1,6 +1,7 @@
0.9.1
0.9.2
#First line must be a semantic version number
#all other lines are ignored.
#0.9.2 - documentation-only PUNKARGS update: cmdhelp's 'subcommand' argument help now states the multi-form narrowing contract explicitly (argument words resolving to exactly one form narrow the usage display to that form's argument table, marked in the synopsis; no words, an ambiguous match, or words no form accepts keep the whole-command render). No behaviour change - the G-041 advisory-parse candidacy already implements it; G-150 pins the contract in cmdhelp.test against the now selection-sound punk::auto_exec::hash declaration (auto_exec 0.1.1).
#0.9.1 - get_ns_dicts fix: packagetails/packageprefixes initialization moved inside the per-namespace loop - with multiple matched namespaces (path globs, trailing **) every dict after the first carried the union of all earlier namespaces' entries, mis-marking non-package children in nslist/n// underline styling and in the returned data; a child with several deeper packages was also appended to packageprefixes once per matching package (now once). Single-namespace queries (the nslist.test tier A pins) unaffected; the multi-namespace reset is pinned in nslist.test (nslist_classification_multins_package_buckets_reset). Documentation-only PUNKARGS added for get_ns_dicts (documents the returned per-namespace dict keys and -allbelow/-nspathcommands; parsing stays manual) and nslist_dict (caller-relative wrapper). Motivated by exposing the dict data layer to agent tooling: scriptlib/developer/nslist.tcl backing the new tcl-nslist agent skill.
#0.9.0 - cmdhelp -return text: plain-text renderer for machine/LLM consumption - no ANSI codes, no table layout, and the argument section of EVERY command form (or of each form in an explicit -form selection) rendered in a single output, unlike the interactive displays which render only the G-041 best-matching form's argument table ('i -return text after' shows all six after forms' argument sections under one common header). Implementation: resolution is delegated to the existing -return dict recursion (aliases/ensembles/docid/form candidacy all inherited); each form's argument section renders via arg_error -return string -scheme nocolour restricted with -form, is ansistripraw'd, and multiform outputs join the per-form sections (headed ' --ARGUMENTS (form <name>)-- ') below the common header+synopsis emitted once. Argument words still restrict the rendered forms per G-041 semantics ('i -return text after cancel xx' renders the matched cancelscript form only); no advisory-parse error preamble is included (the text form is documentation, not validation - use -return dict for parse status). -grepstr applies to the final text as for other return forms. Supported by the punk::args 0.12.6 arg_error changes (message below usage once; single-line 'name TYPE:... ?DEFAULT:...? ?MULTI:yes?' argument rows in the string renderer). Tests: cmdhelp.test cmdhelp_return_text_all_forms / _argword_form_selection / _single_form_plain_marker.
#0.8.1 - synopsis: fixed 'list element in braces followed by "?" instead of space' error when rendering a command whose definition carries a custom @form -synopsis that is not a valid Tcl list (e.g Tcl manpage style '?-form {int|<formname>...}?' as ::punk::args::parse formerly used - 's parse' errored). The command-head replacement treated each synopsis line as a Tcl list (lreplace); non-list lines now take a textual fallback that replaces the leading word(s) via index walking and passes the tail through verbatim. Pinned in punk/args synopsis.test (synopsis_form_custom_override_nonlist) since that suite already exercises the punk::ns::synopsis path.

2
src/tests/modules/AGENTS.md

@ -46,7 +46,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms, rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`<type>` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure)
- `punk/nav/ns/` — punk::nav::ns tests (`testsuites/nav/navns.test`): the n/ n// n/// navigation state machine (ns/ transitions absolute/relative/glob-no-nav, failed-nav state preservation, quad-colon normalization, v-form content selection, ensemble annotation) and the ::punk::nav::ns::ns_current variable contract the repl/codethread/subshell seeding all consume; display content is covered in punk/ns nslist.test
- `punk/repl/` — punk::repl tests (`testsuites/repl/`): opunk console backend integration (`consolebackends.test`) and repl current-namespace retention (`nscurrent.test`: real codethread via repl::init driven by synchronous runscript sends - inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for missing namespaces, the 2026-07-14 stray-namespace seeding fix pinned behaviourally plus a source-text guard on repl::start's inline template; the end-to-end piped subshell session is covered at shell level by shell/testsuites/punkexe/shellnavns.test - which found the first-subshell shared-code-interp asymmetry and the piped-inscope gap recorded there)
- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the flipped nslist_types_default pin - bare nslist without -types displays all member types since punk::ns 0.7.1 fixed the braced-literal -types default), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); remaining GAP pins for pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050)), and name/path primitive characterization (`nsprimitives.test`: string pins for nsparts/nsprefix/nstail/nsjoin/nsjoinall incl weird colon-run (`:::`) splitting, the trailing-colon parse ambiguity (`::x:` + `y` joins to the same string as `::x` + `:y` and reparses leading-colon-greedy), and prefix/tail/join round-trip and its absolutizing exceptions (the original twin-divergence pins for nsparts1/nsprefix1/nsprefix_orig/nstail1/nstail_orig served as safe-deletion evidence and were removed with the twins in the punk::ns 0.7.0 hygiene pass - divergence record in this file's git history, commit 0c7168a1); plus nseval fq-requirement/create-on-eval/evaluator-proc caching, the native-vs-punk `p:::x` resolution divergence (native namespace eval reaches child `x`, nseval creates/reaches literal `:x`), nseval_ifexists no-create + error propagation on plain and genuinely weird namespaces, nsexists/nschildren/nstree_raw weird-ns and relative-resolution pins, globmatchns `*`/`**`/`?` semantics (incl `*` matching a single inner colon - a formerly stale 'should be fixed' comment above nsglob_as_re was corrected in the 0.7.0 hygiene pass), and nspath_to_absolute/nspath_here_absolute caller-resolution pins; the nsjoinall error-message wart pin ('nsjoin:' prefix) flipped when punk::ns 0.7.1 fixed it)
- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the flipped nslist_types_default pin - bare nslist without -types displays all member types since punk::ns 0.7.1 fixed the braced-literal -types default), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); remaining GAP pins for pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050); plus the G-150 flag-led form-narrowing + selection-soundness pins against punk::auto_exec::hash - bare-parse selection contract (incl the dash-led-name noformmatch cost), no-word/unknown-flag/ambiguity whole-render fallbacks, and hash runtime-behaviour-unchanged), and name/path primitive characterization (`nsprimitives.test`: string pins for nsparts/nsprefix/nstail/nsjoin/nsjoinall incl weird colon-run (`:::`) splitting, the trailing-colon parse ambiguity (`::x:` + `y` joins to the same string as `::x` + `:y` and reparses leading-colon-greedy), and prefix/tail/join round-trip and its absolutizing exceptions (the original twin-divergence pins for nsparts1/nsprefix1/nsprefix_orig/nstail1/nstail_orig served as safe-deletion evidence and were removed with the twins in the punk::ns 0.7.0 hygiene pass - divergence record in this file's git history, commit 0c7168a1); plus nseval fq-requirement/create-on-eval/evaluator-proc caching, the native-vs-punk `p:::x` resolution divergence (native namespace eval reaches child `x`, nseval creates/reaches literal `:x`), nseval_ifexists no-create + error propagation on plain and genuinely weird namespaces, nsexists/nschildren/nstree_raw weird-ns and relative-resolution pins, globmatchns `*`/`**`/`?` semantics (incl `*` matching a single inner colon - a formerly stale 'should be fixed' comment above nsglob_as_re was corrected in the 0.7.0 hygiene pass), and nspath_to_absolute/nspath_here_absolute caller-resolution pins; the nsjoinall error-message wart pin ('nsjoin:' prefix) flipped when punk::ns 0.7.1 fixed it)
- `punk/mix/` — punk::mix::cli tests (prune helpers, punkcheck virtual sources), punk::mix::commandset::repo fossil move/rename characterization tests (`testsuites/repo/`, FOSSIL_HOME-isolated; GAP-marked tests pin behaviour G-022 will change), punk::mix::commandset::loadedlib tests (`testsuites/loadedlib/libsearch.test`: 'dev lib.search' match semantics via -return list — wrap-glob default, =exact prefix, case rules, explicit globs, version aggregation — plus the loadedlib 0.2.0 contract: deep discovery by default (deep .tm modules found without -refresh, registration persists), -refresh = genuine re-scan (epoch incr + rediscovery picks up .tm files added to already-scanned dirs), and highlight working without the shell-global a+ alias; shared provisioned child interp sourcing the source-tree libunknown directly — see the file's ORDERING NOTE), and the MULTISHELL polyglot build machinery (`testsuites/scriptwrap/`, split 2026-07-19 per G-092 so no single file dominates the -jobs parallel floor - tests moved verbatim: `multishell.test` = scriptset wrap via the punk.multishell.cmd template with structure/LF-only pins plus platform-gated execution smoke (cmd.exe→powershell payload on windows, sh payload on unix or via the `wsllinux` capability constraint from `src/tests/testsupport/wslprobe.tcl` - staged to the WSL distro's native filesystem, G-059); `multishell_wrapverify.test` = checkfile 512-byte label validation of a fresh wrap; `multishell_wrapdeterminism.test` = byte-identical re-wrap pin; `runtimecmd_checkfile.test` = checkfile + LF contract of the committed bin/runtime.cmd; `runtimecmd_roundtrip.test` = the runtime scriptset round-trip byte-identity pin)
- `punk/lib/` — punk::lib tests (`testsuites/lib/`): range/index/parse/compat/interp_sync utilities, G-058 static-baseline seeding (`staticseed.test`: interp_sync_package_paths/snapshot_package_paths propagate a simulated ::punkboot static baseline and seed `load {} <prefix>` ifneeded mappings; no-op without a baseline), and the repl command-completeness engine (`commandcomplete.test`: punk::lib::system::incomplete pending-opener stacks - the info-complete quoting quirk progression (`set x "{*}{"` standalone vs in-proc-body), single openers, tabs, escapes, incomplete<->info-complete parity property; pre-repl-refactor characterization, see goals/G-044 detail preserve-list)
- `punk/packagepreference/` — punk::packagepreference tests (`testsuites/packagepreference/`): G-058 static-vs-bundled policy (`staticpolicy.test`: require of a baseline package triggers the index scan before resolution so a newer bundled copy wins, static beats older bundled, exact requires of bundled versions stay reachable, missing static mappings get seeded), and the install re-install guard (`installguard.test`, G-160: second install returns 0 silently via the record `renamer` key - the historical `rename` read threw; uninstall/re-install cycle gets the next unique tokenid; fresh child interps source the SOURCE-TREE commandstack + packagepreference by path so no bootsupport/kit snapshot can answer)

250
src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

@ -3,6 +3,7 @@ package require tcltest
package require punk::args
package require punk::ns
package require punk::ansi
package require punk::auto_exec
#Characterization of punk::ns::cmdhelp usage rendering ('i <cmd> <args...>') - added
#2026-07-10 ahead of G-049..G-052: scheme selection (error scheme for a failed parse of
@ -16,6 +17,12 @@ package require punk::ansi
#The marking primitives themselves (punk::args::arg_error -parsedargs/-badarg/-scheme)
#are pinned in src/tests/modules/punk/args/testsuites/args/usagemarking.test - this file
#pins that cmdhelp drives them correctly.
#G-150 (2026-08-04): flag-led form narrowing on the selection-sound punk::auto_exec::hash
#declaration (its show_or_set name value carries -regexprefail {^-}), the bare-parse
#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).
#
#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.
@ -781,5 +788,248 @@ namespace eval ::testspace {
-cleanup {
}\
-result [list 1 1 0]
#--- G-150: flag-led form narrowing on a selection-sound definition ----------------------
#added 2026-08-04 (agent, G-150) - auto_exec::hash's show_or_set name value carries
#-regexprefail {^-}, so a bare punk::args parse of a flag-led line selects the flag form
#uniquely (previously multipleformmatches show_or_set+<flag form>) and cmdhelp's G-041
#candidacy narrows the render to that form with no hash-handler involvement; no-word,
#unknown-flag and ambiguous lines keep the whole-command render, and hash's runtime
#behaviour is unchanged (a dash-led name now needs an explicit flag form - accepted cost).
test cmdhelp_multiform_flagled_narrowing_rehash {a flag-led line narrows the usage display to the flag's form (definition-driven, no handler involvement)}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash -r]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus form]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash -r]]
lappend result [string match {*Clear autoexec commands*} $out]
lappend result [string match {*One or more autoexec command names to set*} $out]
lappend result [string match {*Ambiguous*} $out]
set txt [punk::ns::cmdhelp -return text ::punk::auto_exec::hash -r]
lappend result [string match {*--ARGUMENTS (form 'rehash')--*} $txt]
lappend result [string match {*--ARGUMENTS (form 'show_or_set')--*} $txt]
}\
-cleanup {
}\
-result [list 1 rehash 1 0 0 1 0]
test cmdhelp_multiform_flagled_narrowing_delete {a flag-led line with a name value narrows to the delete form only}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash -d foo]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus form]
set txt [punk::ns::cmdhelp -return text ::punk::auto_exec::hash -d foo]
lappend result [string match {*--ARGUMENTS (form 'delete')--*} $txt]
lappend result [string match {*--ARGUMENTS (form 'show_or_set')--*} $txt]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash -d foo]]
lappend result [string match {*Delete specified autoexec commands*} $out]
lappend result [string match {*One or more autoexec command names to set*} $out]
}\
-cleanup {
}\
-result [list 1 delete 1 0 1 0]
test auto_exec_hash_bare_parse_selects_flag_forms {bare punk::args::parse withid selects the flag-led forms uniquely (definition-driven, no hash-handler involvement)}\
-setup $common -body {
lappend result [dict get [punk::args::parse {-r} withid ::punk::auto_exec::hash] form]
lappend result [dict get [punk::args::parse {-d foo} withid ::punk::auto_exec::hash] form]
lappend result [dict get [punk::args::parse {-t foo} withid ::punk::auto_exec::hash] form]
lappend result [dict get [punk::args::parse {plainname} withid ::punk::auto_exec::hash] form]
lappend result [dict get [punk::args::parse {} withid ::punk::auto_exec::hash] form]
}\
-cleanup {
}\
-result [list rehash delete test show_or_set show_or_set]
test auto_exec_hash_dashled_name_no_longer_plain {a dash-led word no longer seats as a plain show_or_set name - bare parse reports noformmatch with the show_or_set failure classed regexprefail (the accepted cost: such names need an explicit flag form)}\
-setup $common -body {
catch {punk::args::parse {-weird} withid ::punk::auto_exec::hash} msg opts
set ec [dict get $opts -errorcode]
lappend result [string match {*noformmatch*} $ec]
lappend result [string match {*show_or_set*regexprefail*} $ec]
}\
-cleanup {
}\
-result [list 1 1]
test cmdhelp_multiform_nowords_whole_render_retained {a no-word invocation keeps the whole-command render: every form's synopsis line and no failure or ambiguity message}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus form]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash]]
lappend result [string match {*hash -r*} $out]
lappend result [string match {*hash -t *} $out]
lappend result [string match {*hash -d *} $out]
lappend result [string match {*hash --help *} $out]
lappend result [string match {*hash \[name\]*} $out]
lappend result [string match {*Ambiguous*} $out]
lappend result [string match {*No form of the command matches*} $out]
}\
-cleanup {
}\
-result [list 1 show_or_set 1 1 1 1 1 0 0]
test cmdhelp_multiform_unknown_flag_noformmatch_whole_render {an unknown flag-led line keeps the whole-command render with the noformmatch diagnosis (the constraint stopped it seating as a plain name)}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash -bogus]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus failureclass]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash -bogus]]
lappend result [string match {*No form of the command matches*} $out]
lappend result [string match {*Ambiguous*} $out]
}\
-cleanup {
}\
-result [list 0 noformmatch 1 0]
#ambiguous-by-design multiform fixture: {-x} fully matches both forms
proc ambfix {args} {}
punk::args::define {
@id -id ::testspace::ambfix
@cmd -name testspace::ambfix -summary "ambfix summary" -help "ambfix ambiguous multiform fixture."
@form -form flagged
@opts
-x -type none
@values -min 0 -max 0
@form -form plain
@values -min 0 -max -1
word -type string -multiple 1 -optional 1
}
test cmdhelp_multiform_ambiguity_whole_render {a line matching more than one form keeps the whole-command render with the ambiguity named (multipleformmatches route)}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::testspace::ambfix -x]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus failureclass]
lappend result [lsort [dict keys [dict get $d parsestatus formstatus]]]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::testspace::ambfix -x]]
lappend result [string match {*Ambiguous*} $out]
lappend result [expr {[string match {*flagged*} $out] && [string match {*plain*} $out]}]
}\
-cleanup {
}\
-result [list 0 multipleformmatches {flagged plain} 1 1]
test auto_exec_hash_runtime_behaviour_unchanged {hash runtime behaviour is unchanged by the declaration constraint: bare show, -t single and multi, -d, -r, and the dash-led invalid-option error}\
-setup {
set result ""
#reseed the process-global hash table with deterministic entries (saved and
#restored - the testinterp caches real lookups in ::auto_execs as it runs).
#NOTE: qualify ::auto_execs explicitly - this script runs at namespace level
#in ::testspace where 'global' is a no-op.
set saved_auto_execs [array get ::auto_execs]
unset -nocomplain ::auto_execs
array set ::auto_execs [list foo /bin/foo bar /bin/bar neg {}]
}\
-body {
lappend result [lsort [split [punk::auto_exec::hash] \n]]
lappend result [punk::auto_exec::hash -t foo]
lappend result [punk::auto_exec::hash -t foo bar]
punk::auto_exec::hash -d foo
lappend result [info exists ::auto_execs(foo)] [info exists ::auto_execs(bar)]
punk::auto_exec::hash -r
lappend result [array exists ::auto_execs]
#a dash-led first word still hits the handler's invalid-option dispatch (also
#emits the expected one-line stderr note 'hash: -bogus: invalid option') - the
#show_or_set constraint did not move the runtime behaviour
catch {punk::auto_exec::hash -bogus} msg
lappend result [string match {hash: usage:*} $msg]
set result
}\
-cleanup {
unset -nocomplain ::auto_execs
array set ::auto_execs $saved_auto_execs
}\
-result [list {/bin/bar /bin/foo} /bin/foo {foo /bin/foo
bar /bin/bar} 0 1 0 1]
#added 2026-08-04 (agent, G-150) - help-form tightening (user direction): -anyopts dropped
#from the help form, --help must lead, trailing words consumed as ignored values; a
#flag-led line no longer marks the help form viable (was the only "(viable" form for
#'i hash -t' via the adhoc-option seating); plus the regression pin for the user-landed
#fix of the handler's multi-name -t missing-name message (was: can't read "hash").
test cmdhelp_multiform_help_form_narrowing {--help leads and selects the help form uniquely; trailing words are consumed as ignored values and the arbitrary-flag doc row is gone}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash --help foo bar]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus form]
set txt [punk::ns::cmdhelp -return text ::punk::auto_exec::hash --help]
lappend result [string match {*--ARGUMENTS (form 'help')--*} $txt]
lappend result [string match {*--ARGUMENTS (form 'show_or_set')--*} $txt]
lappend result [string match {*<arbitrary-flag>*} $txt]
}\
-cleanup {
}\
-result [list 1 help 1 0 0]
#added 2026-08-04 (agent, G-150) - the help form's @values -flagvalues 1 (punk::args
#0.15.0) makes dash-led trailing words after --help seat as ignored values
test cmdhelp_multiform_help_form_dashwords_swallowed {dash-led trailing words after --help are consumed as ignored values via @values -flagvalues (no invalidoption)}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash --help -t foo]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus form]
set txt [punk::ns::cmdhelp -return text ::punk::auto_exec::hash --help -t foo]
lappend result [string match {*--ARGUMENTS (form 'help')--*} $txt]
lappend result [string match {*--ARGUMENTS (form 'show_or_set')--*} $txt]
}\
-cleanup {
}\
-result [list 1 help 1 0]
test cmdhelp_multiform_flagled_line_help_not_viable {a flag-led incomplete line no longer marks the help form viable once it stops accepting adhoc options (the old adhoc-viable pointer was wrong); show_or_set/rehash/delete are genuinely not viable}\
-setup $common -body {
set d [punk::ns::cmdhelp -return dict ::punk::auto_exec::hash -t]
lappend result [dict get $d parsestatus ok] [dict get $d parsestatus failureclass]
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::punk::auto_exec::hash -t]]
lappend result [string match {*No form of the command matches*} $out]
foreach f {show_or_set rehash delete help} {
lappend result [string match "*form '$f': (not viable)*" $out]
}
set result
}\
-cleanup {
}\
-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
#{-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)}\
-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)
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]
}\
-cleanup {
}\
-result [list test 1 0]
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 {
set result ""
#deterministic reseed of the process-global hash table (see the
#runtime_behaviour_unchanged test for the ::auto_execs qualification note)
set saved_auto_execs [array get ::auto_execs]
unset -nocomplain ::auto_execs
array set ::auto_execs [list foo /bin/foo]
}\
-body {
lappend result [punk::auto_exec::hash -t foo missingname]
lappend result [info exists ::auto_execs(foo)]
set result
}\
-cleanup {
unset -nocomplain ::auto_execs
array set ::auto_execs $saved_auto_execs
}\
-result [list {foo /bin/foo} 1]
}
tcltest::cleanupTests ;#needed to produce test summary line.

Loading…
Cancel
Save