From 6cc7e8751c643522aade4a5703a3d47027b20caf Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Sun, 9 Aug 2026 14:01:56 +1000 Subject: [PATCH] punk::ns 0.11.1: G-174 cmd_traverse word accounting - doc-lookup walk never drops argument words (project 0.66.3) The walk behind cmdinfo dropped all but the last trailing word for commands whose definition's first form is zero-arg with no leaders (the punk::ansi::a? class): the empty-leader position fell through to the outer per-word loop, whose exhaustion return reported only the last tail snapshot - dropped words landed in neither args_resolved nor args_remaining, so cmdhelp's advisory parse cleanly mis-narrowed 'i a? web green' to the loose sample form with no failure signal anywhere. - cmd_traverse: the empty-leader position returns new lane 11 with the unconsumed tail (remaining shape mirrors the code-3 lane); word-accounting contract stated at the record comment (conservation; sanctioned exceptions: ensemble-parameter re-presentation, alias/script currying; first-form-only inspection recorded as a limit); stale code-7 witness comment corrected ('i namespace which -v x' actually terminates in the code-8 lane). - cmdflow.test: traverse_final/word_accounting helpers, fixtures flowmultiform (a?-shaped) / flowmultilead / flownomap / flowplainlead, lane-code + conservation battery across return lanes 0a,2,X,3,4,6,7,8,9,10,11 (code-7 and code-10 witnessed for the first time), multi-word no-drop pins, the real-world a? witness, and the first-form-only recorded-limit pin. - cmdhelp.test: 'a? web green' -return dict (multipleformmatches, formstatus keys {web sample}) and -return text (web's argument section before sample's) presentation pins, plus the punk::ns::synopsis underline-marking pin. - Witness: pre-fix, 8 of the 9 new tests fail (5 cmdflow + 3 cmdhelp); post-fix punk/ns subtree green on canonical tclsh90s (138 pass, 2 pre-existing gated skips), cmdflow+cmdhelp green on tclsh86ts (86/86). - G-174 activated on user direction, flipped achieved 2026-08-09 and archived (entry to GOALS-archive.md, detail to goals/archive/ with Follow-ons); suite-index DOX updated; project 0.66.3 + CHANGELOG entry. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 11 + GOALS-archive.md | 4 + GOALS.md | 4 - .../G-174-cmdtraverse-word-accounting.md | 89 ++++++- punkproject.toml | 2 +- src/modules/punk/ns-999999.0a1.0.tm | 29 ++- src/modules/punk/ns-buildversion.txt | 3 +- src/tests/modules/AGENTS.md | 2 +- .../punk/ns/testsuites/ns/cmdflow.test | 230 ++++++++++++++++++ .../punk/ns/testsuites/ns/cmdhelp.test | 63 +++++ 10 files changed, 426 insertions(+), 11 deletions(-) rename goals/{ => archive}/G-174-cmdtraverse-word-accounting.md (57%) diff --git a/CHANGELOG.md b/CHANGELOG.md index d32f2c47..3d020f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,17 @@ 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.66.3] - 2026-08-09 + +- Command help doc-lookup no longer drops argument words (G-174, punk::ns + 0.11.1): for commands whose definition's first form is zero-arg with no + leaders (e.g `punk::ansi::a?`), the cmdinfo walk silently discarded all but + the last trailing word before the help system's form narrowing ran, so + `i a? web green` presented the loose sample form instead of the genuine + web/sample ambiguity. The walk now conserves the whole unconsumed tail: + `i a? web green` renders web's argument table first with both matching + forms marked, and `s a? web green` underlines the same forms. + ## [0.66.2] - 2026-08-09 - make.tcl dry-run help (G-151 follow-through): an accepted 'make.tcl help diff --git a/GOALS-archive.md b/GOALS-archive.md index 443f22ee..1e4306ee 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-174 [achieved 2026-08-09] cmd_traverse word accounting - the doc-lookup walk never drops argument words → detail: goals/archive/G-174-cmdtraverse-word-accounting.md +Scope: src/modules/punk/ns-999999.0a1.0.tm (cmd_traverse/cmdinfo doc-lookup walk, cmdhelp advisory parse + synopsis consumers); src/tests/modules/punk/ns/testsuites/ns/{cmdflow,cmdhelp}.test (conservation characterisation + consumer pins); src/modules/punk/ansi-999999.0a1.0.tm (punk::ansi::a? argdoc - real-world witness, edits only if the sample-form tightening decision lands) +Acceptance: characterisation tests in cmdflow.test assert the conservation invariant (args_resolved plus args_remaining account for every supplied trailing word, each word in exactly one) across the walk's return lanes - including an a?-shaped fixture (plain proc, multiform definition, zero-arg first form) with multi-word tails, plus witnesses for the previously unwitnessed code-7 and code-10 return lanes; punk::ns::cmdhelp for 'a? web green' runs its advisory parse on the full tail and presents the web/sample multipleformmatches ambiguity (web's argument table rendered first, both forms marked in the synopsis) rather than a clean sample-only render, and punk::ns::synopsis marks the same forms for the same words; the first-form-only inspection (fid = first of form_names) is either extended or pinned as a recorded limit with its own witness, with the decision recorded in this file; the punk::ns suite passes under the canonical runtests interpreter with no regressions in cmdflow/cmdhelp. + ### G-176 [achieved 2026-08-08] commandstack doc integration - stack-tied overrider argdocs, override hints, and the libunknown package-override migration → detail: goals/archive/G-176-commandstack-doc-integration.md Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.3.1.tm (package-override migration to commandstack + flagship epoch doc - fixed-version file, renamed 0.2.4 -> 0.3.0 -> 0.3.1 across increments 2 and 4); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins) Acceptance: characterisation tests of the CURRENT libunknown override landed before any behaviour change and stayed green byte-identically through the migration (epoch single-arg/indexed forms + error arms, the forget arm, abbreviations, delegation, re-init guard, packagepreference interop in both install orders) with only marked install-shape pins flipping at the migration; libunknown installs its ::package override via commandstack::rename_command with a stack-query guard and no ::package:: residue; rename_command -punkargs docs are queryable while the record lives (incl renames performed BEFORE punk::args loads - pinned in a bare child) and are removed by remove_rename/pop_rename/remove_renamer/restore_original alike; cmdhelp shows an override-in-place notice (cmdinfo 'overridden' second-axis key, no hard commandstack dependency) enumerating record-attached doc ids; 'i package epoch' resolves the libunknown-attached doc and the how-to is described in commandstack::help + the rename_command argdoc; punk/libunknown, commandstack, punk/ns and punk/packagepreference suites pass under the canonical runtests interpreter. diff --git a/GOALS.md b/GOALS.md index 9b79e545..272dc5cb 100644 --- a/GOALS.md +++ b/GOALS.md @@ -396,10 +396,6 @@ Detail: goals/G-167-punkargs-version-delta-availability.md Scope: src/make.tcl (bake/bakehouse/vfslibs - VFSPAYLOAD + PACKAGES phases, kit assembly, new explicit materialize/tidy commands); src/make.tcl sync_layouts phase (the mint-side tracked-path write) with its targets src/project_layouts/ and src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/ plus the workflow_text TERMINOLOGY/DIAGRAM 1b stage-verb assignment; src/vfs/ (payload declarations, _targets staging generalization, README.md + AGENTS.md precedence spec); src/_bake (assembly staging); bin/packages (lib-tier consumption); .gitignore + .fossil-settings/ignore-glob (staging ignore coverage); src/tests/shell/testsuites/punkexe/ (kill-probe + precedence characterization); src/runtime/mapvfs.toml (kit-entry runtime-content mask declarations - 2026-08-08 amendment) Detail: goals/G-171-bake-writes-land-untracked.md -### G-174 [proposed] cmd_traverse word accounting - the doc-lookup walk never drops argument words -Scope: src/modules/punk/ns-999999.0a1.0.tm (cmd_traverse/cmdinfo doc-lookup walk, cmdhelp advisory parse + synopsis consumers); src/tests/modules/punk/ns/testsuites/ns/{cmdflow,cmdhelp}.test (conservation characterisation + consumer pins); src/modules/punk/ansi-999999.0a1.0.tm (punk::ansi::a? argdoc - real-world witness, edits only if the sample-form tightening decision lands) -Detail: goals/G-174-cmdtraverse-word-accounting.md - ### G-175 [proposed] @dynamic resolve multiplicity - one render, one resolution (verdict + optional landing) Scope: src/modules/punk/args-999999.0a1.0.tm (resolve @dynamic cache-skip, by-id entry points get_spec/parse_status/arg_error/synopsis); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp render pipeline - the four independent by-id fetches); src/tests/modules/punk/args/testsuites/args/dynamic.test + src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (once-per-render counter pins + cross-render freshness pins, if the landing arm is taken) Detail: goals/G-175-punkargs-dynamic-resolve-multiplicity.md diff --git a/goals/G-174-cmdtraverse-word-accounting.md b/goals/archive/G-174-cmdtraverse-word-accounting.md similarity index 57% rename from goals/G-174-cmdtraverse-word-accounting.md rename to goals/archive/G-174-cmdtraverse-word-accounting.md index 3ff6dd88..27ebff5d 100644 --- a/goals/G-174-cmdtraverse-word-accounting.md +++ b/goals/archive/G-174-cmdtraverse-word-accounting.md @@ -1,6 +1,6 @@ # G-174 cmd_traverse word accounting - the doc-lookup walk never drops argument words -Status: proposed +Status: achieved 2026-08-09 Scope: src/modules/punk/ns-999999.0a1.0.tm (cmd_traverse/cmdinfo doc-lookup walk, cmdhelp advisory parse + synopsis consumers); src/tests/modules/punk/ns/testsuites/ns/{cmdflow,cmdhelp}.test (conservation characterisation + consumer pins); src/modules/punk/ansi-999999.0a1.0.tm (punk::ansi::a? argdoc - real-world witness, edits only if the sample-form tightening decision lands) Goal: the doc-lookup walk behind punk::ns::cmdinfo has a stated, characterised word-accounting contract - every supplied trailing word lands in exactly one of args_resolved or args_remaining - so args_remaining-sensitive consumers (cmdhelp's advisory parse and G-041 form narrowing, synopsis form marking) always operate on the full unconsumed tail, and a command whose definition's first form is zero-arg (the punk::ansi::a? class) no longer has its literal discriminator words silently dropped before form narrowing runs. Acceptance: characterisation tests in cmdflow.test assert the conservation invariant (args_resolved plus args_remaining account for every supplied trailing word, each word in exactly one) across the walk's return lanes - including an a?-shaped fixture (plain proc, multiform definition, zero-arg first form) with multi-word tails, plus witnesses for the previously unwitnessed code-7 and code-10 return lanes; punk::ns::cmdhelp for 'a? web green' runs its advisory parse on the full tail and presents the web/sample multipleformmatches ambiguity (web's argument table rendered first, both forms marked in the synopsis) rather than a clean sample-only render, and punk::ns::synopsis marks the same forms for the same words; the first-form-only inspection (fid = first of form_names) is either extended or pinned as a recorded limit with its own witness, with the decision recorded in this file; the punk::ns suite passes under the canonical runtests interpreter with no regressions in cmdflow/cmdhelp. @@ -116,3 +116,90 @@ defect lives precisely in the uncovered lanes. multipleformmatches with per-form formstatus (all-valid keys) and noformmatch with ranked -formerrors; cmdhelp consumes them correctly. No punk::args change is expected under this goal. +- Related (activation overlap survey 2026-08-09, goals_xref score G-174): + G-050 (punk::ns::synopsis argument-validity marking) - the synopsis marking + consumer this goal feeds the full tail; G-174 pins WHICH forms are marked, + G-050 owns richer validity marking. G-175 (@dynamic resolve multiplicity) - + the same cmdhelp render pipeline and by-id fetches the advisory parse + exercises. G-052 (TclOO method autodef) - sibling doc-walk/cmdhelp fallback + surface pinned in the same suites. +- DECISION (fix arm, 2026-08-09): the minimal empty-leader-return landed - the + annotated `#todo - return?` break now returns the unconsumed tail as new lane + code 11, remaining shape `[list {*}$eparams {*}$queryargs_untested]` mirroring + the sibling code-3 lane. Subcommand-walk eligibility tightening (requiring a + choices-bearing leader) was NOT needed: record-level probing showed every + other walk exit already conserved, and tightening would have re-routed the + characterised code-3 lane through a different return. cmdinfo's driving loop + tests only `final == 0`, so the new nonzero lane code is transparent to it. +- DECISION (first-form-only inspection, 2026-08-09): pinned as a recorded limit. + Witness: cmdflow.test cmdtraverse_firstformonly_recorded_limit on the + flowmultilead fixture (zero-arg first form plus a LATER form carrying a + choices-bearing subcommand leader with a subhelp mapping) - the walk never + consults the later form, the word stays conserved in args_remaining at the + parent docid. Cross-form traversal discrimination remains a possible follow-on + goal; flipping that pin is the signal it landed. The limit is also stated in + the word-accounting contract comment above cmd_traverse. +- DECISION (ansi-side, 2026-08-09): a?'s 'sample' form stays string-loose - the + marked-ambiguity presentation IS the G-041 multipleformmatches contract. No + punk::ansi edit. +- The code-7 lane's stale source comment ('test with: i namespace which -v x') + was corrected at the site: that invocation actually terminates in the code-8 + lane (::tcl::namespace::which's autodef first form carries values, so the + subcommand walk is never entered at that level) - verified by record-level + probing 2026-08-09. The code-7 witness is the purpose-built flownomap fixture + (a choice the resolver matches but no choiceinfo mapping or space-form id + serves). +- Observed corner, deliberately not addressed here (recorded as an open + follow-on): with nonempty eparams, the code-3/code-11 remaining shape prepends + the already-consumed -ensembleparameter words while resolvedargs also holds + them - double-listing beyond the sanctioned descent re-presentation. Reachable + only by a hand-authored definition placing a choice-less (or absent) leader + after -ensembleparameter leaders in a no-opts-no-values first form; no shipped + definition has the shape. + +## Progress + +2026-08-09 activated (user direction) and landed in one increment (punk::ns +0.11.1, project 0.66.3): + +- Fix: cmd_traverse's empty-leader position returns lane 11 with the unconsumed + tail; the word-accounting contract is stated at the record-shape comment above + cmd_traverse (conservation, canonical spellings in resolvedargs, verbatim tail + in remainingargs, the two sanctioned exceptions - ensemble-parameter + re-presentation and alias/script currying - and the first-form-only limit). +- Characterisation, cmdflow.test G-174 section: traverse_final (drives the + coroutine exactly as cmdinfo's reducer loop, exposing the final record's lane + code) and word_accounting (cmdinfo-level counts + verbatim-suffix check) + helpers; fixtures flowmultiform (a?-shaped: zero-arg first form, literal-leader + form, literal-first-value form, loose string form), flowmultilead + (first-form-only limit), flownomap (code-7), flowplainlead (code-3); the + lane-code + conservation battery across return lanes + 0a,2,X,3,4,6,7,8,9,10,11 - code-7 and code-10 witnessed for the first time; + multi-word no-drop pins (2- and 3-word tails, both discriminator shapes); + single-word by-contract pin (lane 11, no longer last-snapshot luck); the + real-world a? witness ('web green'/'term pastel' whole-tail args_remaining); + the first-form-only recorded-limit pin. +- Characterisation, cmdhelp.test G-174 section: 'a? web green' -return dict + (args_remaining={web green}, ok=0, failureclass=multipleformmatches, + formstatus keys {web sample}); -return text (web's --ARGUMENTS section renders + before sample's, no other form sections); punk::ns::synopsis underline-marks + exactly {web sample} of the six forms. +- Defect witness (pre-fix runs of the new tests, canonical tclsh90s): 8 of the 9 + new tests fail against the unfixed walk (5 cmdflow + 3 cmdhelp); the code-7 + witness passes both sides - that lane was conserved all along, merely + unwitnessed. Pre-fix signature: cmdinfo a? web green -> args_remaining={green} + ('web' in neither list, final record code=10 consumed={} remaining={green}); + post-fix: {web green} via lane 11. +- Verification: full punk/ns subtree green under the canonical zig-built + tclsh90s (140 tests: 138 pass, 2 pre-existing gated skips, 0 fail - no + regressions in cmdflow/cmdhelp); cmdflow+cmdhelp green under tclsh86ts + (86/86, the 8.6 leg); testbody_lint clean over the ns testsuite dir (7 files, + 140 tests, 0 findings). + +Acceptance satisfied in full; flipped achieved 2026-08-09. + +## Follow-ons + +Follow-on: cross-form traversal discrimination - extend cmd_traverse's first-form-only inspection (fid = first of form_names) so choices-bearing subcommand leaders on later forms are consulted during the walk; flip cmdflow.test cmdtraverse_firstformonly_recorded_limit when it lands => open +Follow-on: tighten punk::ansi::a?'s loose 'sample' form so discriminator words do not ambiguate => declined 2026-08-09 (the marked-ambiguity presentation is the G-041 multipleformmatches contract - drafting-time default confirmed at landing) +Follow-on: eparam double-listing corner - with nonempty eparams the code-3/code-11 remaining shape prepends already-consumed -ensembleparameter words beyond the sanctioned descent re-presentation (hand-authored -ensembleparameter-then-choiceless-leader first forms only; no shipped definition has the shape) => open diff --git a/punkproject.toml b/punkproject.toml index 51661ba2..36048e71 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.66.2" +version = "0.66.3" 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/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index f6b35e3f..5766564d 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/src/modules/punk/ns-999999.0a1.0.tm @@ -4788,6 +4788,15 @@ y" {return quirkykeyscript} # (G-166) is the canonical -choiceunavailable name this level's #resolution addressed, or empty. cmdinfo keeps the FINAL record's value - the #availability of the landing it reports. + #Word accounting contract (G-174): across the walk, every supplied trailing + #word lands in exactly one of resolvedargs/remainingargs (resolvedargs holds + #canonical spellings of the consumed leading words, remainingargs the verbatim + #unconsumed tail). Sanctioned exceptions: a consumed -ensembleparameter word is + #deliberately RE-PRESENTED in remainingargs so the next level's definition can + #parse it, and alias/script currying expands the word list itself mid-walk. + #The subcommand walk inspects only the definition's FIRST form (fid below) - + #a recorded limit; cross-form traversal discrimination is a possible follow-on. + #Pinned by src/tests/modules/punk/ns/testsuites/ns/cmdflow.test (G-174 section). proc cmd_traverse {ns formid args} { set autodefined [dict create] #puts "cmd_traverse args: $args yielding: [info coroutine]" @@ -5112,8 +5121,16 @@ y" {return quirkykeyscript} #puts "===> queryargs:$queryargs lnames:$leadernames_matched" #usually we expect only one entry in leadernames (except for -ensembleparameter cases) if {$lname eq ""} { - #todo - return? - break + #G-174: leader positions exhausted with query words left - the + #inspected form has no (further) leaders, e.g a zero-arg first + #form like punk::ansi::a?'s sgr_overview. Return the unconsumed + #tail (same remaining shape as the code-3 lane). The former bare + #'break' fell through to the outer per-word loop, which + #re-snapshots the tail each iteration and whose exhaustion + #return reports only the LAST snapshot - every earlier word was + #silently dropped, landing in neither resolvedargs nor the + #remaining tail. + return [list 11 $origin $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid $a_unavailable] } set arginfo [dict get $spec FORMS $fid ARG_INFO $lname] set allchoices [list] @@ -5331,7 +5348,13 @@ y" {return quirkykeyscript} break ;#out of foreach q $queryargs ... } else { - #test with: i namespace which -v x + #a word the choice resolver matched but no docid serves it - no + #choiceinfo mapping and no space-form id. G-174: witnessed by + #cmdflow.test cmdtraverse_code7_witness_unmapped_choice via the + #flownomap fixture. The probe this comment historically named - + #'i namespace which -v x' - actually terminates in the code-8 + #lane: ::tcl::namespace::which's autodef first form carries + #values, so the subcommand walk is never entered at that level. return [list 7 $origin $resolvedargs $queryargs_untested $prevdocid {}] } } ;#end loop foreach q $queryargs lname $leadernames_matched diff --git a/src/modules/punk/ns-buildversion.txt b/src/modules/punk/ns-buildversion.txt index c79abd3d..bbe74b09 100644 --- a/src/modules/punk/ns-buildversion.txt +++ b/src/modules/punk/ns-buildversion.txt @@ -1,6 +1,7 @@ -0.11.0 +0.11.1 #First line must be a semantic version number #all other lines are ignored. +#0.11.1 - G-174 cmd_traverse word accounting: the doc-lookup walk behind cmdinfo no longer silently drops trailing words for commands whose definition's FIRST form is zero-arg with no leaders (the punk::ansi::a? class). The empty-leader position in the subcommand walk (the annotated '#todo - return?' break) now returns the unconsumed tail as new lane code 11 (remaining shape mirroring the code-3 lane) instead of falling back to the outer per-word loop, whose exhaustion return reported only the LAST tail snapshot - with N trailing words, N-1 landed in NEITHER args_resolved NOR args_remaining. Consumers operating on args_remaining now see the full unconsumed tail: cmdhelp's advisory parse for 'i a? web green' reports the genuine web/sample multipleformmatches ambiguity (web's argument table first, both forms marked) instead of a clean sample-only render, and punk::ns::synopsis marks the same forms. Word accounting contract stated at the cmd_traverse record comment: every supplied trailing word lands in exactly one of resolvedargs/remainingargs (sanctioned exceptions: -ensembleparameter re-presentation, alias/script currying). First-form-only inspection (fid = first of form_names) pinned as a recorded limit - cross-form traversal discrimination is a possible follow-on. Pinned by cmdflow.test's G-174 section (lane-code + conservation battery incl the previously unwitnessed code-7/code-10 lanes, a?-shaped fixtures) and cmdhelp.test's G-174 section (dict/text ambiguity presentation, synopsis form marking). #0.11.0 - G-176 increment 4: cmdinfo gains an 'overridden' result key - a second axis beside cmdtype/unavailable (G-166 style) reporting the LIVE commandstack override state of the base command: one {renamer docids } dict per stack record (bottom-up), docids being the punk::args ids of doc blocks the record attached via 'rename_command -punkargs' (derived with punk::args::rawdef_id). Guarded live query (info commands on commandstack::get_stack) - empty list when commandstack is not loaded, never cached so it is correct as stacks change. cmdhelp surfaces it: the table/string renders lead with a plain "note: '' is currently overridden/extended via commandstack by: (override-attached docs: '')" line, -return text leads with an OVERRIDDEN: line (below UNAVAILABLE when both apply - the G-166 leading-line convention), -return dict carries the 'overridden' key on all its return shapes, and the undocumented-command fallback carries the note too. The cmdhelp.test cmdinfo/dict key-list pins flipped deliberately with this key (as they did for G-166's 'unavailable'); new pins cover the documented-command note incl the docs clause + dict axis, and the doc-less-proc autodef render leading with the note, the -return text OVERRIDDEN: line and the note clearing when the override unwinds. #0.10.0 - G-166 recognised-but-unavailable landings are surfaced distinctly. (a) cmdinfo gains an 'unavailable' result key on EVERY result - empty, or the canonical -choiceunavailable name (punk::args 0.16.0) the resolution addressed. It is a SECOND axis beside cmdtype, never a cmdtype value: cmdtype still answers what kind of executable thing was found ('string is dict' and 'string is true' on Tcl 8.6 both stay 'doconly'), availability answers whether the resolved word is selectable in this runtime/context. cmd_traverse's yielded/returned records carry a sixth element for it; cmdinfo keeps the final landing's value. (b) DECIDED (enacting the G-073 doc-walk follow-on): a unique PREFIX landing on an unavailable name now resolves that name's virtual docid, attributed - previously it conservatively resolved nothing, which made addressing inconsistent ('i string is tr' resolved but 'i string is dic' did not, while exact 'dict' and 'true' both did). The choice traverse's parse-parity claim is untouched: an unavailable word still identifies no SUBCOMMAND there, it is only attributed (the path taken when the name has no space-form virtual docid). (c) cmdhelp surfaces it in every return form: -return dict carries the key, table/string render the marking below the usage, -return text leads with an 'UNAVAILABLE:' line - and an unavailable subject is never presented as cleanly usable, so 'i string is dict 5' no longer renders info-scheme-clean just because the advisory parse of '5' succeeds against the virtual id. Wording (punk::ns::_cmdhelp_unavailable_marking) mirrors punk::args' tailored parse rejection; the runtime-specific why stays in the definition. Definitions without -choiceunavailable behave unchanged. Tests: cmdflow.test gains the flowunavail/flowunavail_nodoc fixtures + 5 pins, cmdhelp.test gains the helpstrua fixture + 5 pins (3 local, 2 real-world tclcore-gated) and the two deliberate key-list flips (cmdhelp_cmdinfo_result_shape, cmdhelp_return_dict_valid). #0.9.3 - G-073 doc-walk parity: both cmd_traverse choiceword_match call sites (the G-051 space-form prefix-parity branch and the choice-traverse subcommand resolution) now pass the argument's -choiceunavailable list (punk::args 0.16.0 8th resolver argument), so the doc walk honours recognised-but-unavailable names identically to parsing: a prefix shadowed into ambiguity by an unavailable name resolves no documentation, and an unavailable word is not accepted as a choice (its documentation remains reachable via the exact space-form virtual docid, checked before the choice traverse - e.g 'i string is dict' on Tcl 8.6 via the tclcore moduledoc 0.4.0 per-class ids). No behaviour change for definitions without the key. diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index f1abb192..e280efb8 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 (incl the 2026-08-08 validation.test dash-word pins: the starved-values dash-led seating rule - no -- marker needed when word supply equals required-values demand - and dash-led -type regexp values, whose validator's 'regexp -about' needed its -- guard), choices/choicegroups, forms (incl the 2026-08-05 candidacy fencing/fall-through primitive pins ahead of G-168: -regexprefail on optional value slots is form-fatal with no re-landing, unknown-option/dangling-option/option-value-fence failures fall through to sibling forms as successful parses, and the documented per-word limit - a complete option+value+file line double-matches a fenced scriptfile form and a dash-tolerant stdin form), the ensemble_subcommands_definition generator (`ensembledef.test`: lazy-argdoc ordering - the generator loads registered-but-unloaded subcommand argdocs before its id checks; plus the 2026-08-08 unqualified-ensemble-name normalization pin - a relative multi-component name is qualified in the caller's context, zero update_definitions stderr warnings and snippet parity with the qualified call), registered-namespace lazy definition loading (`docpackages.test`, G-169 pre-work: inert registration/scan-vs-load split, id_exists never triggers loading, real_id/usage lazy resolution incl tag-prefixed script-level id families, duplicate-definer last-loaded-wins characterization), the G-168 launch-definition model parity (`punkexemodel.test`, punkexe moduledoc 0.7.0, id loading updated for the G-169 handover - the script id resolves via app-punkscript-docs from src/lib (auto_path derived from the test file location) while the moduledoc carries the core-owned ids: script/tclsh selection matrices against the real (script)::punkexe ids with the real-side dispatch/app oracle recorded in goals/archive/G-168, the bare-'-e' viable-incomplete verdict, the ./-e fence message, -encoding fall-through statuses, and the sanctioned complete-'-encoding' multipleformmatches LIMIT pins; the formcheck.test punkexe GAPs flipped to discriminated/sanctioned/selection-sound pins in the same arc), the G-151 landing report (`parsereport.test`, punk::args 0.18.0 parse_report: the canonical flag-like-word-consumed-as-VALUE attribution row, parsed-result vs words+withid entry parity, machine dict shape with declaration-section row order and absent-optional row omission, received xN multiplicity for solos/-multiple opts, type-aware always-marked elision at the default width plus the caller > @cmd -reportvaluewidth > built-in width cascade and never-elided dict returns, VIEW-style single-line-ization of control-bearing values, aliased-optionset storage-key row bridging plus the G-084 storage-key attribution pin (value-parsekey and renamed-option rows attribute received; a shared-parsekey group member row keeps the bridged value with Source default - the recorded remaining gap), bordered-table/tableobject renders, words-form failure errorcode parity with parse, and the registered two-form definition's own render), 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, and the G-084 unique-storage-key attribution pin - argstatus reports received/value through a value's -parsekey or a renamed option's declared parsekey), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus the G-084 settled pins (2026-08-07): value -parsekey rename support, @values-line -parsekey rejection, leader -parsekey define-time rejection, last-defined-member defaults precedence, cross-member -multiple define-time rejection (allowed only when ALL members share one @opts -parsekeymode error group; a {0 1} range replace-shape stays allowed) and value-parsekey integrity (duplicate/dash-led value parsekeys rejected at define time); `testsuites/dev/parsekey-knownbugs.test` holds no disabled pins (retained as the future punkargsKnownBug home); the parsekey/optname collision GAP is flipped (G-083, 2026-08-07: -parsekey colliding with a distinct defined arg's name is now a resolve error unless that arg shares the parsekey), and the G-083 argument-relations define-time vocabulary is pinned in `relations.test` (per-arg -conflicts target validation, @opts -parsekeymode override|error storage and the requires-group/bad-value/requires-parsekey resolve errors), plus its increment-2 parse-time enforcement (optionconflict raise for -conflicts violations and -parsekeymode error co-occurrence, defaults-never-conflict, cross-group conflicts, parse_status invalid classification), and its increment-3 usage/synopsis rendering and lsearch moduledoc adoption (conflicts-with hints appended to per-arg help rows, -parsekeymode error groups annotated 'mutually exclusive' in group headers with override groups unannotated, synopsis one-line form carrying no conflict detail; and the lsearch tclcore moduledoc modelling -sorted/-glob and -bisect/-all incompatibilities via per-arg -conflicts while -glob/-regexp stay last-wins and the 'punk::args fixes required' caveat is dropped))), the G-053 range-valued -multiple occurrence arity (`multipleranges.test`, punk::args 0.22.0: {min max} range canonicalisation into a per-form MULTIPLE_RANGES companion dict while the stored -multiple boolean keeps its hot-path list-collect meaning so legacy 0/1 stay byte-unchanged, the -optional/range-min reconciliation resolve error, the {0 1}/{2 4}/{1 -1} scalar-vs-list value shapes, parse-time occurrencecount enforcement with min-under-supply suppressed in the G-152 viability probe and classified incomplete (pure exhaustion) while over-max is invalid, the usage-table Multi column showing 0-1/2-4/1+ with the synopsis ?arg? at-most-once ellipsis distinction, and -multipleunique composition; plus the allocation-participation pins (directed work 2026-08-08, punk::args 0.25.0): bounded ranges cap greedy leader/value collection at max occurrences (incl the cap-beats-type-screen case), min-occurrence-aware reservations keep a later required ranged argument fed (single and pair clauses, the leaders-side scan cap and split floor), genuine over-supply reports the pointed occurrence limit at the overflow site, legacy boolean greed unchanged), longopt style-distinction and mash-edge characterisation (`longopts.test`, 2026-08-07 gap-fill: the gnu (`--flag=` inline-only; spaced/solo usage = badoptionformat) vs plain (`--flag` spaced-only; inline `=` = invalidoption) vs mixed (`-f|--file|--file=` both forms incl longopt prefix abbreviation) definition-grammar distinctions, first-`=`-only value splitting and empty inline values, the two `=`-member resolve-time definition errors, single-dash `-flag=value` never split at the `=`, and `@opts -any 1` adhoc passthrough where `--flag=value` splits but `-flag=value` stays a whole adhoc flagname expecting a value; the same gap-fill added to `mashopts.test`: unknown-letter-in-mash invalidoption rejection and the `-any` interplay - defined flags still mash while undefined mash-lookalike tokens are adhoc flags, never mash attempts; error-shape pins use the first three -errorcode elements), 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 appear when the class is live OR is a declared forward unavailable class - dict labels on every runtime since G-073, unicode keeps the live-only rule; the G-073 forward-class adoption invariants and the USER-SANCTIONED 'di' prefix-strictness exemption (the model is deliberately stricter than real 8.6, full words parity-true); and the G-166 pin that an unavailable class's per-class virtual id LEADS its help with the unavailability statement on runtimes lacking the class while modern runtimes generate it unchanged. All expectations are derived from the running interpreter, green on 8.6/8.7/9.0 through runtests.tcl on each - native Tcl 8.6 has been a supported runner interpreter since 2026-07-21) - `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; plus the G-166 availability axis on the flowunavail/flowunavail_nodoc fixtures - the `unavailable` cmdinfo key present on every result, exact and unique-prefix landings on a `-choiceunavailable` name resolving that name's virtual docid ATTRIBUTED rather than resolving nothing, the choice traverse attributing without addressing when no virtual docid exists, and cmdtype staying unchanged throughout since availability is a second axis, never a cmdtype value), 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); the flipped G-051 pins for pseudo-command cmdtype (`doconly`) + space-form docid prefixes (real `string is` pins behind the have_tclcoredocs constraint); the G-166 unavailability marking on the helpstrua fixture and on the real `string is dict` subject - the `-return dict` key, the table/string marking with the info scheme suppressed so a valid argument tail no longer renders as a cleanly usable command line, and the `-return text` leading `UNAVAILABLE:` line (the two deliberate key-list flips - `cmdhelp_cmdinfo_result_shape`, `cmdhelp_return_dict_valid` - landed with it); the G-176 override-in-place hint (2026-08-08: cmdinfo's live `overridden` second axis - commandstack rename records for the base command with renamers + `-punkargs`-attached doc ids - and cmdhelp's leading "note: '' is currently overridden/extended via commandstack by: ..." line incl the docs clause, the `-return text` OVERRIDDEN: leading line, the dict key on all return shapes, the doc-less-proc autodef render leading with the note, and the note clearing when the override unwinds; the same two key-list pins flipped again with the new key, and fixtures unwind via restore_original in -cleanup); remaining GAP pins for 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/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity; plus the G-166 availability axis on the flowunavail/flowunavail_nodoc fixtures - the `unavailable` cmdinfo key present on every result, exact and unique-prefix landings on a `-choiceunavailable` name resolving that name's virtual docid ATTRIBUTED rather than resolving nothing, the choice traverse attributing without addressing when no virtual docid exists, and cmdtype staying unchanged throughout since availability is a second axis, never a cmdtype value; plus the G-174 word-accounting contract (2026-08-09): the traverse_final/word_accounting helpers - direct coroutine drive exposing the walk's final-record lane code, and cmdinfo-level counts + verbatim-suffix conservation - a lane-code + conservation battery across return lanes 0a,2,X,3,4,6,7,8,9,10,11 (code-7 and code-10 witnessed for the first time; the sanctioned ensemble-parameter re-presentation and alias/script currying stay out of the helper's scope), the a?-shaped flowmultiform fixture's multi-word no-drop pins (lane 11 - the pre-fix walk dropped all but the last trailing word for zero-arg-first-form definitions), the flownomap code-7 witness, the real-world punk::ansi::a? whole-tail pins, and the first-form-only-inspection RECORDED-LIMIT pin on flowmultilead - flip it if cross-form traversal discrimination ever lands), 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); the flipped G-051 pins for pseudo-command cmdtype (`doconly`) + space-form docid prefixes (real `string is` pins behind the have_tclcoredocs constraint); the G-166 unavailability marking on the helpstrua fixture and on the real `string is dict` subject - the `-return dict` key, the table/string marking with the info scheme suppressed so a valid argument tail no longer renders as a cleanly usable command line, and the `-return text` leading `UNAVAILABLE:` line (the two deliberate key-list flips - `cmdhelp_cmdinfo_result_shape`, `cmdhelp_return_dict_valid` - landed with it); the G-176 override-in-place hint (2026-08-08: cmdinfo's live `overridden` second axis - commandstack rename records for the base command with renamers + `-punkargs`-attached doc ids - and cmdhelp's leading "note: '' is currently overridden/extended via commandstack by: ..." line incl the docs clause, the `-return text` OVERRIDDEN: leading line, the dict key on all return shapes, the doc-less-proc autodef render leading with the note, and the note clearing when the override unwinds; the same two key-list pins flipped again with the new key, and fixtures unwind via restore_original in -cleanup); remaining GAP pins for 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; plus the G-174 zero-arg-first-form ambiguity presentation (2026-08-09): 'a? web green' -return dict carrying the full tail with the web/sample multipleformmatches parse status, -return text rendering web's --ARGUMENTS section before sample's with no other form sections, and punk::ns::synopsis underline-marking exactly the matching forms - the pre-fix walk fed the advisory parse only the tail's last word, which cleanly single-matched the loose sample form), 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/cmdflow.test b/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test index 97fa4696..35bbf86e 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test +++ b/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test @@ -528,5 +528,235 @@ namespace eval ::testspace { -cleanup { }\ -result [list 1] + + #--- G-174: cmd_traverse word accounting (conservation invariant) ------------------------- + #The walk's contract: every supplied trailing word lands in exactly one of + #args_resolved or args_remaining - args_resolved holds canonical spellings of the + #consumed leading words, args_remaining the verbatim unconsumed tail. Sanctioned + #exceptions, out of scope for the conservation helper: ensemble-parameter + #re-presentation - a consumed -ensembleparameter word deliberately repeats in + #args_remaining, pinned by ensparam_traverse_to_documented_sub above - and + #alias/script currying, which expands the walk's word list itself. + #Before the G-174 fix, a command whose definition's FIRST form was zero-arg with no + #leaders - the punk::ansi::a? class - dropped all but the LAST trailing word: the + #per-word outer loop re-snapshotted the tail each iteration and its exhaustion + #return reported only the last snapshot. The walk now returns the unconsumed tail + #from the leader-exhausted position - lane code 11. + + #added 2026-08-09 (agent, G-174) - helpers + fixtures for the word-accounting contract + #drive punk::ns::cmd_traverse exactly as cmdinfo's reducer loop does - including the + #autodef-prefix strip on resumed origins - and report the FINAL record. The walk's + #return lane code is not surfaced through cmdinfo, so lane witnesses need the raw record. + proc traverse_final {args} { + set r ::testspace::g174_reducer + catch {rename $r ""} + coroutine $r ::punk::ns::cmd_traverse [namespace current] * {*}$args + set final 0 + set origin "" + set rec {} + while {$final == 0} { + set rec [$r $origin] + lassign $rec final origin + if {[string match (autodef)* $origin]} { + set origin [string range $origin 9 end] + } + } + return $rec + } + #cmdinfo-level conservation check. Returns: counts-ok suffix-ok + # counts-ok: consumed words plus remaining words account for the whole supplied tail + # suffix-ok: args_remaining is verbatim the unconsumed trailing words of the tail + #consumed words are compared by count only - resolution records canonical spellings. + proc word_accounting {cmdwords} { + set cinfo [punk::ns::cmdinfo {*}$cmdwords] + set tail [lrange $cmdwords 1 end] + set consumed [lrange [dict get $cinfo args_resolved] 1 end] + set remaining [dict get $cinfo args_remaining] + set counts_ok [expr {([llength $consumed] + [llength $remaining]) == [llength $tail]}] + if {[llength $remaining]} { + set suffix [lrange $tail end-[expr {[llength $remaining]-1}] end] + } else { + set suffix {} + } + set suffix_ok [expr {$suffix eq $remaining}] + return [list $counts_ok $suffix_ok] + } + + #a?-shaped fixture: plain proc, multiform definition, zero-arg FIRST form, then a + #literal-leader form and a literal-first-value form (the two shapes punk::ansi::a? + #carries its discriminator literals in), then a loose string form that ambiguates. + proc flowmultiform {args} {} + punk::args::define { + @id -id ::testspace::flowmultiform + @cmd -name testspace::flowmultiform -summary "multiform zero-arg-first" -help "a?-shaped multiform fixture" + @form -form "overview" + @values -form "overview" -min 0 -max 0 + + @form -form "lead" + @leaders -form "lead" -min 1 -max 1 + lead -type literal(lead) -help "literal leader discriminator" + @values -form "lead" -min 0 -max -1 + panel -type string -optional 1 -multiple 1 -choices {red green blue} + + @form -form "val" + @values -form "val" -min 1 -max -1 + val -type literal(val) -help "literal first-value discriminator" + panel -type string -optional 1 -multiple 1 -choices {red green blue} + + @form -form "loose" + @values -form "loose" -min 1 -max -1 + word -type string -optional 0 -multiple 1 + } + + #zero-arg first form PLUS a later form carrying a choices-bearing subcommand leader - + #the first-form-only inspection limit witness (see cmdtraverse_firstformonly_recorded_limit) + proc flowmultilead {args} {} + punk::args::define { + @id -id ::testspace::flowmultilead + @cmd -name testspace::flowmultilead -summary "zero-arg first form then subcommand form" -help "first-form-only inspection limit fixture" + @form -form "overview" + @values -form "overview" -min 0 -max 0 + + @form -form "subs" + @leaders -form "subs" -min 1 -max 1 + subcmd -choices {alpha} -choiceinfo { + alpha {{doctype punkargs} {subhelp ::testspace::flowsub_alpha}} + } + @values -form "subs" -min 0 -max 0 + } + + #as flowparent but one choice has no choiceinfo mapping and no space-form id - the + #code-7 lane witness (a word the choice resolver MATCHES but no docid serves) + proc flownomap {args} {} + punk::args::define { + @id -id ::testspace::flownomap + @cmd -name testspace::flownomap -summary "parent nomap" -help "parent with an unmapped choice" + @leaders -min 1 -max 1 + subcmd -choices {alpha nomap} -choiceinfo { + alpha {{doctype punkargs} {subhelp ::testspace::flowsub_alpha}} + } + @values -min 0 -max 0 + } + + #no-opts-no-values first form whose single leader has NO choices - the code-3 lane + proc flowplainlead {args} {} + punk::args::define { + @id -id ::testspace::flowplainlead + @cmd -name testspace::flowplainlead -summary "plain leader" -help "leader without choices is not walkable as a subcommand" + @leaders -min 1 -max 1 + lead -type string + @values -min 0 -max 0 + } + + #added 2026-08-09 (agent, G-174) - conservation invariant across the walk's return lanes. + #One battery entry per return lane: final-record lane code plus the cmdinfo-level + #conservation verdict. Lane codes are the first element of the walk's record - a + #diagnostic vocabulary, pinned here so a routing change is a visible flip. + test cmdtraverse_lane_conservation_battery {each walk return lane conserves the supplied tail - final lane code plus counts-ok suffix-ok per probe}\ + -setup $common -body { + foreach {label probe expected_code} { + docid-notail {::testspace::flowcmd} 0a + single-notfound {::testspace::no_such_cmd} 2 + spaceform-exhaust {::testspace::flowgap deep} X + choiceless-leader {::testspace::flowplainlead hello there} 3 + unknown-word {::testspace::flowparent zzz} 4 + subhelp-landing {::testspace::flowparent alpha} 6 + unmapped-choice {::testspace::flownomap nomap} 7 + optsvals-form {::testspace::flowcmd x y} 8 + undoc-tail {::testspace::no_such_cmd x} 9 + eparam-exhaust {::testspace::paramens ctxA} 10 + zeroarg-firstform {::testspace::flowmultiform val green} 11 + } { + set code [lindex [traverse_final {*}$probe] 0] + lappend result [list $label $code {*}[word_accounting $probe]] + } + set result + }\ + -cleanup { + }\ + -result [list\ + {docid-notail 0a 1 1}\ + {single-notfound 2 1 1}\ + {spaceform-exhaust X 1 1}\ + {choiceless-leader 3 1 1}\ + {unknown-word 4 1 1}\ + {subhelp-landing 6 1 1}\ + {unmapped-choice 7 1 1}\ + {optsvals-form 8 1 1}\ + {undoc-tail 9 1 1}\ + {eparam-exhaust 10 1 1}\ + {zeroarg-firstform 11 1 1}\ + ] + + #added 2026-08-09 (agent, G-174) - the defect class: multi-word tails on a zero-arg-first-form + #command. Before the fix args_remaining held only the LAST word (earlier words in neither + #list); the walk now returns the whole unconsumed tail. + test cmdtraverse_zeroargfirst_multiword_no_drop {multi-word tails on a zero-arg-first-form command land whole in args_remaining}\ + -setup $common -body { + set cinfo [punk::ns::cmdinfo ::testspace::flowmultiform val green] + lappend result [dict get $cinfo args_remaining] [dict get $cinfo docid] + set cinfo [punk::ns::cmdinfo ::testspace::flowmultiform lead red] + lappend result [dict get $cinfo args_remaining] + set cinfo [punk::ns::cmdinfo ::testspace::flowmultiform val green blue] + lappend result [dict get $cinfo args_remaining] + }\ + -cleanup { + }\ + -result [list {val green} ::testspace::flowmultiform {lead red} {val green blue}] + + test cmdtraverse_zeroargfirst_singleword {a single trailing word still lands in args_remaining - via the leader-exhausted return, no longer by last-snapshot luck}\ + -setup $common -body { + set rec [traverse_final ::testspace::flowmultiform val] + lappend result [lindex $rec 0] [lindex $rec 3] + }\ + -cleanup { + }\ + -result [list 11 val] + + #added 2026-08-09 (agent, G-174) - the real-world witness from the goal: the walk no longer + #narrows 'i a? web green' to the loose sample form by dropping 'web' before the advisory parse + test cmdinfo_realworld_ansi_aq_no_drop {punk::ansi::a? discriminator words survive to args_remaining for form narrowing}\ + -setup $common -body { + set cinfo [punk::ns::cmdinfo punk::ansi::a? web green] + lappend result [dict get $cinfo args_remaining] + set cinfo [punk::ns::cmdinfo punk::ansi::a? term pastel] + lappend result [dict get $cinfo args_remaining] + }\ + -cleanup { + }\ + -result [list {web green} {term pastel}] + + #added 2026-08-09 (agent, G-174) - code-7 lane detail: the choice resolver matches the word + #but no docid serves it (no choiceinfo mapping, no space-form id) - the walk stops at the + #parent's docid with the matched word unconsumed; a mapped sibling still descends + test cmdtraverse_code7_witness_unmapped_choice {a matched choice with no mapping returns lane 7 with the word unconsumed and the parent docid}\ + -setup $common -body { + set rec [traverse_final ::testspace::flownomap nomap] + lappend result [lindex $rec 0] [lindex $rec 3] [lindex $rec 4] + set cinfo [punk::ns::cmdinfo ::testspace::flownomap nomap] + lappend result [dict get $cinfo docid] [dict get $cinfo args_remaining] + set cinfo [punk::ns::cmdinfo ::testspace::flownomap alpha] + lappend result [dict get $cinfo docid] + }\ + -cleanup { + }\ + -result [list 7 nomap ::testspace::flownomap ::testspace::flownomap nomap ::testspace::flowsub_alpha] + + #added 2026-08-09 (agent, G-174) - first-form-only inspection pinned as a RECORDED LIMIT + #(G-174 decision): the walk inspects only the definition's FIRST form, so a subcommand + #leader on a LATER form is never consulted - the word stays unconsumed at the parent + #docid (conserved) instead of descending to the later form's subhelp docid. Cross-form + #traversal discrimination is a possible follow-on goal; flipping this pin is the signal + #that it landed. + test cmdtraverse_firstformonly_recorded_limit {a choices-bearing leader on a later form is not consulted - the walk stays at the parent with the word conserved in args_remaining}\ + -setup $common -body { + set rec [traverse_final ::testspace::flowmultilead alpha] + lappend result [lindex $rec 0] + set cinfo [punk::ns::cmdinfo ::testspace::flowmultilead alpha] + lappend result [dict get $cinfo docid] [dict get $cinfo args_remaining] + }\ + -cleanup { + }\ + -result [list 11 ::testspace::flowmultilead alpha] } 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 fdb004c4..cdc61486 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test @@ -1211,5 +1211,68 @@ bar /bin/bar} 0 1 0 1] rename ::testspace::ovbare "" }\ -result [list 1 0 1 0] + + #--- G-174: zero-arg-first-form multiform - full-tail advisory parse presentation --------- + #punk::ansi::a? is the real-world subject: its FIRST form (sgr_overview) is zero-arg, + #which routed cmd_traverse into the subcommand walk where the empty leader list + #dropped all but the last trailing word. cmdhelp's advisory parse then saw only the + #tail's last word, which cleanly single-matched the loose 'sample' form - so + #'i a? web green' rendered sample's usage with no failure signal anywhere. With the + #walk conserving the tail (cmdflow.test G-174 section), the advisory parse runs on + #the full tail and reports the genuine web/sample multipleformmatches ambiguity - + #the marked-ambiguity presentation IS the G-041 contract (the a? sample form + #deliberately stays string-loose - G-174 decision). + + #added 2026-08-09 (agent, G-174) + test cmdhelp_zeroargfirst_ambiguity_dict {cmdhelp -return dict for 'a? web green' carries the full tail and the web/sample multipleformmatches parse status}\ + -setup $common -body { + set d [punk::ns::cmdhelp -return dict ::punk::ansi::a? web green] + lappend result [dict get $d args_remaining] + set ps [dict get $d parsestatus] + lappend result [dict get $ps ok] [dict get $ps failureclass] + lappend result [dict keys [dict get $ps formstatus]] + }\ + -cleanup { + }\ + -result [list {web green} 0 multipleformmatches {web sample}] + + #added 2026-08-09 (agent, G-174) + test cmdhelp_zeroargfirst_ambiguity_text {cmdhelp -return text for 'a? web green' renders web's argument table first with sample's following - both matching forms, no others}\ + -setup $common -body { + set txt [punk::ns::cmdhelp -return text ::punk::ansi::a? web green] + set positions {} + foreach f {sgr_overview term tk web x11 sample} { + set pos [string first "--ARGUMENTS (form '$f')--" $txt] + if {$pos >= 0} {lappend positions $f $pos} + } + lappend result [dict keys $positions] + lappend result [expr {[dict get $positions web] < [dict get $positions sample]}] + }\ + -cleanup { + }\ + -result [list {web sample} 1] + + #added 2026-08-09 (agent, G-174) - punk::ns::synopsis marks the same forms for the same + #words (the args_remaining-sensitive marking consumer beside cmdhelp's advisory parse). + #Marking is underline-wrapping of the matching forms' synopsis lines; non-comment + #synopsis lines map to form names in declaration order. + test synopsis_zeroargfirst_marks_matching_forms {punk::ns::synopsis for 'a? web green' underlines exactly the web and sample form lines}\ + -setup $common -body { + set syn [punk::ns::synopsis -return full ::punk::ansi::a? web green] + set UL [punk::ansi::a+ underline] + set formnames [punk::args::forms ::punk::ansi::a?] + lappend result $formnames + set marked {} + set fidx 0 + foreach line [split $syn \n] { + if {[string range $line 0 1] in {"# " "##"}} {continue} + if {[string first $UL $line] == 0} {lappend marked [lindex $formnames $fidx]} + incr fidx + } + lappend result $marked [expr {$fidx == [llength $formnames]}] + }\ + -cleanup { + }\ + -result [list {sgr_overview term tk web x11 sample} {web sample} 1] } tcltest::cleanupTests ;#needed to produce test summary line.