From 665785d239b91c76e8d42ed89591b8b9cd951328 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Tue, 4 Aug 2026 15:58:25 +1000 Subject: [PATCH] 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, ' (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 --- src/modules/punk/auto_exec-999999.0a1.0.tm | 24 +- src/modules/punk/auto_exec-buildversion.txt | 3 +- src/modules/punk/ns-999999.0a1.0.tm | 8 +- src/modules/punk/ns-buildversion.txt | 3 +- src/tests/modules/AGENTS.md | 2 +- .../punk/ns/testsuites/ns/cmdhelp.test | 250 ++++++++++++++++++ 6 files changed, 279 insertions(+), 11 deletions(-) diff --git a/src/modules/punk/auto_exec-999999.0a1.0.tm b/src/modules/punk/auto_exec-999999.0a1.0.tm index 140d9e82..0c6ac88b 100644 --- a/src/modules/punk/auto_exec-999999.0a1.0.tm +++ b/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 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 ""} { diff --git a/src/modules/punk/auto_exec-buildversion.txt b/src/modules/punk/auto_exec-buildversion.txt index f47d01c8..c0d3eeb1 100644 --- a/src/modules/punk/auto_exec-buildversion.txt +++ b/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, ' (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". diff --git a/src/modules/punk/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index e38a2b9d..40116e3a 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/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]] diff --git a/src/modules/punk/ns-buildversion.txt b/src/modules/punk/ns-buildversion.txt index d85796f6..c43df431 100644 --- a/src/modules/punk/ns-buildversion.txt +++ b/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 )-- ') 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|...}?' 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. diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index 7632956a..39681e01 100644 --- a/src/tests/modules/AGENTS.md +++ b/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/`` 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 {} ` 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) diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test index f9db29cb..cdc14837 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/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 ') - 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+) 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 {**} $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.