Browse Source

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
master
Julian Noble 18 hours ago
parent
commit
6cc7e8751c
  1. 11
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 89
      goals/archive/G-174-cmdtraverse-word-accounting.md
  5. 2
      punkproject.toml
  6. 29
      src/modules/punk/ns-999999.0a1.0.tm
  7. 3
      src/modules/punk/ns-buildversion.txt
  8. 2
      src/tests/modules/AGENTS.md
  9. 230
      src/tests/modules/punk/ns/testsuites/ns/cmdflow.test
  10. 63
      src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

11
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

4
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.

4
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

89
goals/G-174-cmdtraverse-word-accounting.md → 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

2
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 -

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

@ -4788,6 +4788,15 @@ y" {return quirkykeyscript}
#<unavailable> (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

3
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 <string> docids <list>} 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: '<cmd>' is currently overridden/extended via commandstack by: <renamers> (override-attached docs: '<ids>')" 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.

2
src/tests/modules/AGENTS.md

File diff suppressed because one or more lines are too long

230
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.

63
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.

Loading…
Cancel
Save