Browse Source

G-166 achieved: cmdinfo/cmdhelp surface recognised-but-unavailable landings distinctly

Activated at user direction and achieved in the same session. On Tcl 8.6,
'i string is dict' (a class the runtime lacks) and 'i string is true' (a real
class) were indistinguishable to both eye and machine: cmdinfo reported
cmdtype 'doconly' with identical key sets, and cmdhelp's advisory parse of an
argument tail succeeded against the virtual docid, so 'i string is dict 5'
rendered exactly like a usable command line. Documentation REACHABILITY for
unavailable words is G-073's intent; the INDISTINGUISHABILITY was emergent -
nothing structural told a consumer the documented word is rejected here.

punk::ns 0.10.0
  (a) cmdinfo gains an 'unavailable' key on EVERY result - empty, or the
  canonical -choiceunavailable name the resolution addressed. It is a SECOND
  AXIS beside cmdtype, never a cmdtype value: cmdtype answers what kind of
  executable thing resolution landed on (both 'string is dict' and
  'string is true' stay 'doconly'), availability answers whether the resolved
  word is selectable in this runtime/context. Overloading cmdtype would have
  broken every consumer switching on cmdtype eq "doconly", and the two only
  co-occur incidentally. cmd_traverse's yielded/returned records carry a sixth
  element for the attribution; cmdinfo keeps the final landing's value.
  Population is nearly free - the level's choices-bearing first leader is
  resolved once per level via the shared punk::args::choiceword_match, and both
  the exact space-form landing and the prefix retry consult that one result
  (the G-051 branch was restructured, not duplicated).

  (b) PREFIX-LANDING 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 - the conservative non-resolution was not kept. Encoding
  availability as resolve-vs-don't-resolve conflated it with addressing and
  made addressing inconsistent: 'i string is tr' resolved but 'i string is dic'
  did not, while exact 'true' and 'dict' both did. Now every form of address
  that names a documented word resolves it, and the availability key plus the
  render marking carry the rejection story parse reports for the same word.
  The choice traverse's G-040 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 marks such subjects in every return form from one shared wording
  (_cmdhelp_unavailable_marking, mirroring punk::args'
  private::unavailable_choice_msg so a user meeting both reads one story):
  -return dict carries the key, table/string render the message below the usage
  under the non-info scheme, -return text leads with an 'UNAVAILABLE:' line.
  The marking takes precedence over BOTH the ok-parse info render and any parse
  failure message - the subject not existing here is the more fundamental
  report, and it is the one a parse against a virtual id cannot make. Parse
  status still passes through, so argument positions keep their marking.

punk::args::moduledoc::tclcore 0.4.1 - on a runtime lacking a curated forward
class, that class's per-class virtual id LEADS its help with the unavailability
statement (runtime patchlevel + the rejected call shape), then the unchanged
static description. The static version note existed but sat at the END of the
prose, so a reader met the full class documentation before learning the class
does not exist here. Runtimes where the class is live generate the id unchanged.

Tests: cmdflow.test gains the flowunavail / flowunavail_nodoc fixtures and 5
pins (key always present; exact landing attributed with cmdtype unchanged; the
prefix decision pinned against parse's choiceunavailable rejection;
choice-traverse attribution without addressing; attribution surviving an
argument tail). cmdhelp.test gains the helpstrua fixture and 5 pins (3 local
render, 2 tclcore-gated real-world) plus the two DELIBERATE key-list flips
(cmdhelp_cmdinfo_result_shape, cmdhelp_return_dict_valid). tclcoreparity.test
gains the virtual-id leading-note pin. All new pins are live-derived, so the
modern arm asserts the same shapes with an empty key.

Verified: cmdflow 25/25 + cmdhelp 50/50 on native 8.6.10 (THE AFFECTED ARM,
unavailable={dict}), 8.7a6 and 9.0.5; full punk/args subtree 341 total / 338
passed / 3 intended skips / 0 failed on all three; full punk/ns 125 green on
9.0.5 and green on 8.6/8.7 apart from two nsprimitives failures verified
PRE-EXISTING on both (confirmed with the module changes stashed); broad
src/tests/modules/*** sweep on 9.0.5 - 1257 tests, 108 files, 0 failed.
Consumer audit: every in-tree cmdinfo consumer reads named keys via dict get,
so the added key is inert for all of them. goals_lint and architecture_lint
clean.

Project 0.56.2 -> 0.57.0 (user-visible shell behaviour) with CHANGELOG entry.
Goal flipped to achieved and archived; four follow-ons recorded (to G-050,
G-044, G-055, G-167) with reference-sweep pointers pushed into those goals'
Notes, and the G-167 back-pointer added at activation - G-166 landing first
settles the marking-vocabulary ownership the two goals share. Also corrected
two pre-existing stale claims in src/tests/modules/AGENTS.md that this work
falsified: tclcoreparity's "version-note labels conditional on class presence"
(stale since G-073) and its "under 8.6 run the file directly via a plain
tclkit" note (stale since native 8.6 became a supported runner 2026-07-21).

Staged deliberately as this change-set only: the concurrent G-172 zig
CPU-floor lane (GOALS.md entry, goals/G-172-*.md, the four build.zig files)
is left uncommitted in the tree.

Claude-Session: https://claude.ai/code/session_01Y5vEJUZkD4p8jmEFEBVhLi
Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
f103b90c3a
  1. 18
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 9
      goals/G-044-repl-command-completion.md
  5. 9
      goals/G-050-synopsis-validity-marking.md
  6. 10
      goals/G-055-tclcore-regen-workflow.md
  7. 84
      goals/G-166-cmdinfo-unavailable-surfacing.md
  8. 22
      goals/G-167-punkargs-version-delta-availability.md
  9. 188
      goals/archive/G-166-cmdinfo-unavailable-surfacing.md
  10. 2
      punkproject.toml
  11. 14
      src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm
  12. 3
      src/modules/punk/args/moduledoc/tclcore-buildversion.txt
  13. 209
      src/modules/punk/ns-999999.0a1.0.tm
  14. 3
      src/modules/punk/ns-buildversion.txt
  15. 4
      src/tests/modules/AGENTS.md
  16. 29
      src/tests/modules/punk/args/testsuites/args/tclcoreparity.test
  17. 91
      src/tests/modules/punk/ns/testsuites/ns/cmdflow.test
  18. 110
      src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test

18
CHANGELOG.md

@ -5,6 +5,24 @@ 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.57.0] - 2026-08-06
- Recognised-but-unavailable documentation landings are now surfaced distinctly
at the shell (G-166). On Tcl 8.6, `i string is dict` and `i string is true`
were machine- and eye-indistinguishable: both reported `cmdtype doconly` with
identical key sets, and `i string is dict 5` rendered exactly like a usable
command line. `punk::ns::cmdinfo` gains an `unavailable` result key (empty, or
the canonical `-choiceunavailable` name the resolution addressed) as a second
axis beside `cmdtype`, and `punk::ns::cmdhelp` marks such subjects visibly in
every return form - the dict form carries the key, table/string render the
marking below the usage, and `-return text` leads with an `UNAVAILABLE:` line.
- `i <cmd> <prefix>` landing uniquely on an unavailable name now resolves that
name's documentation (marked) instead of resolving nothing - `i string is dic`
on 8.6 documents the dict class. Addressing documentation and word
availability are separate axes; only the latter says the word is unusable.
- The `string is <class>` documentation for a class this runtime lacks now leads
with the unavailability statement instead of ending with it.
## [0.56.2] - 2026-08-06
- `make.tcl bake`/`bakehouse` now end with a `BAKE SUMMARY` block: a counts

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-166 [achieved 2026-08-06] cmdinfo/cmdhelp surface recognised-but-unavailable landings distinctly → detail: goals/archive/G-166-cmdinfo-unavailable-surfacing.md
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo result key, cmd_traverse landing attribution, cmdhelp render marking + advisory-parse presentation); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (virtual ids lead with the unavailability note on affected runtimes); src/tests/modules/punk/ns/testsuites/ns/ (cmdinfo/cmdhelp pins incl the deliberate key-list flip); src/tests/modules/punk/args/testsuites/args/tclcoreparity.test (8.6-arm pins)
Acceptance: on a runtime where the tclcore model declares dict unavailable (Tcl 8.6), cmdinfo for 'string is dict' (direct and decomposed invocation forms) reports the new availability key carrying 'dict' while 'string is true' reports it empty, with cmdtype remaining 'doconly' for both (the command-kind axis is not overloaded); the key is present on every cmdinfo result and the cmdhelp.test cmdinfo key-list pin is flipped deliberately; cmdhelp output for an unavailable subject is visibly distinguished in table, text and dict return forms (the dict form carries the key; table/text carry a marking naming the unavailability), and an argument tail such as 'string is dict 5' no longer renders as a cleanly usable command line; the walk's unique-prefix landing on an unavailable name ('i string is dic' on 8.6) has a decided, pinned behaviour consistent with the marking (either resolving the virtual docid WITH the marking, or documented-conservative non-resolution); tclcore virtual ids for unavailable classes lead their rendered help with the unavailability statement on affected runtimes (first description line, not buried mid-prose) while modern runtimes render those ids unchanged; definitions without -choiceunavailable and existing cmdinfo/cmdhelp consumers behave unchanged (full punk/ns and punk/args suites green apart from deliberately flipped pins); the enacted G-073 Follow-on lines carry => goal G-166 dispositions. (Achieved: punk::ns 0.10.0 - cmdinfo 'unavailable' key on every result as a SECOND axis beside cmdtype (both 'string is dict' and 'string is true' stay doconly on 8.6), cmd_traverse records carrying a sixth attribution element; prefix-landing DECIDED as resolve-the-virtual-docid-marked rather than conservative non-resolution, since addressing and availability are separate axes and non-resolution made addressing inconsistent ('i string is tr' resolved, 'i string is dic' did not) - the choice traverse's G-040 parse-parity claim untouched, an unavailable word still identifies no subcommand there; cmdhelp marking via one shared wording in every return form, taking precedence over both the ok-parse info render and any parse failure message so 'i string is dict 5' no longer renders info-scheme-clean; tclcore moduledoc 0.4.1 leads an unavailable class's virtual-id help with the unavailability statement, modern runtimes unchanged; cmdflow.test 25/25 + cmdhelp.test 50/50 on 8.6.10 (affected arm), 8.7a6 and 9.0.5, full punk/args subtree 341/338/3 skips/0 fail on all three, full punk/ns 125 green on 9.0.5 and green on 8.6/8.7 apart from two verified-pre-existing nsprimitives failures, plus a 1257-test src/tests/modules/*** sweep on 9.0.5 with 0 failures; punkshell 0.57.0.)
### G-170 [achieved 2026-08-06] Launcher help depth: form narrowing, parse-true dry-run, actionable form hints → detail: goals/archive/G-170-launcher-help-depth.md
Scope: src/vfs/_config/punkboot_core.tcl (help arm, + synced copies via established channels); punkexe launch definitions wherever homed after G-168/G-169 (formhint un-suppression); src/tests/shell/testsuites/punkexe/launcherhelp.test
Acceptance: 'help script oneliner' (and an index form) renders only that form's usage; a non-form trailing word on a passthrough subcommand keeps today's ignored behaviour (prepend-help pinned unchanged); for a parse-true subcommand the dry-run's accept/reject agrees with dispatch on the same words (parity pinned); the form-word-vs-scriptfile collision rule (a script literally named 'oneliner') is decided and documented; the multi-form definitions' -formhint suppression flips to real templates and the hint labels render them; every increment has a working degraded/plain twin; all pinned in launcherhelp.test. (Achieved: punkboot core 0.7.0 - launcher_show_help tail depth returning the exit code; narrowing = exact form name or decimal ordinal via 'usage -form' (G-150 shape - all-form synopsis, selected form's argument table); dry-run make.tcl-style with the punk::args::parse_report landing report (guarded for older snapshots), parse-true = buildinfo + project parse=1 (parity by construction) and script/tclsh gated on the G-168 fence witness so an unfenced moduledoc vintage keeps the historic ignored tail; collision rule decided - form word wins the single-word position, a like-named file stays reachable as ./oneliner (the G-168 ./-e rule); -formhint "" flipped to ' <punkexe> help <sub> %formname%' in both homes (app-punkscript 1.3 script def, punkexe moduledoc 0.9.0 tclsh def) with deliberately no trailing ellipsis (appending words flips into the dry-run interpretation, not a continuation); the -encoding LIMIT inherited as the sanctioned two-form diagnosis; launcherhelp.test 38/38 (13 new pins incl parity, vintage-witness and PUNKBOOT_PLAIN twins) + args subtree 337/3/0 on tclsh90 AND tclsh87; punkexe subtree at recorded baseline.)

4
GOALS.md

@ -394,10 +394,6 @@ Detail: goals/G-163-unix-exit-output-drain.md
### G-166 [proposed] cmdinfo/cmdhelp surface recognised-but-unavailable landings distinctly
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo result key, cmd_traverse landing attribution, cmdhelp render marking + advisory-parse presentation); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (virtual ids lead with the unavailability note on affected runtimes); src/tests/modules/punk/ns/testsuites/ns/ (cmdinfo/cmdhelp pins incl the deliberate key-list flip); src/tests/modules/punk/args/testsuites/args/tclcoreparity.test (8.6-arm pins)
Detail: goals/G-166-cmdinfo-unavailable-surfacing.md
### G-167 [proposed] punk::args version-delta availability: unavailable @forms for shape deltas, unavailable option names for flag deltas
Scope: src/modules/punk/args-999999.0a1.0.tm (form selection/candidacy availability filter + tailored raises, option-scan unavailable-name recognition, synopsis/table renderers - annotated form lines, unavailable option rows, automated other-shapes note, formstatus 'available' field, -form display-vs-parse split); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp -return text form-section heading annotation; form narrowing inherits the availability filter); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (adoption on curated real 8.6-vs-9 deltas); src/tests/modules/punk/args/testsuites/args/ (new suite + formviability/tclcoreparity extensions)
Detail: goals/G-167-punkargs-version-delta-availability.md

9
goals/G-044-repl-command-completion.md

@ -96,6 +96,15 @@ arginfo principle: introspection must not run commands to elicit usage).
help-depth arms (form narrowing, parse-true dry-run) built the per-form
navigation surface whose candidacy data this goal's completion consumer
later consumes.
- G-166 (achieved 2026-08-06 - goals/archive/G-166-cmdinfo-unavailable-surfacing.md)
left this goal an open follow-on: completion over choices should CONSUME the
cmdinfo 'unavailable' key (and the -choiceunavailable pool behind it) rather
than re-deriving availability, so recognised-but-unavailable words
display-but-do-not-complete consistently with what parse accepts. Note the
addressing-vs-availability split G-166 settled: a unique prefix of an
unavailable name now RESOLVES that name's documentation (marked), so a
completion consumer must read the availability key rather than infer
unusability from a failed doc lookup.
- xtal reference: https://tarray.magicsplat.com/xtal_lang.html - an alternative syntax
for Tcl (tarray project); an interactive xtal subshell is the concrete
language-subshell scenario the provider seam must not preclude.

9
goals/G-050-synopsis-validity-marking.md

@ -47,3 +47,12 @@ string").
autodef definitions render through the same synopsis machinery this goal makes
validity-aware - generated method docs get goodarg/badarg marking too when both
land.
- G-166 (achieved 2026-08-06 - goals/archive/G-166-cmdinfo-unavailable-surfacing.md)
recorded the sibling display-marking decision and, by landing first, OWNS the
marking vocabulary this goal reuses: "'<name>' is a recognised name here, but
is not available in this runtime/context"
(punk::ns::_cmdhelp_unavailable_marking, mirroring punk::args'
private::unavailable_choice_msg), with the machine axis named 'unavailable'
on the cmdinfo result. It also left this goal an open follow-on: punk::ns::synopsis
does NOT surface the availability axis, so 's string is dict' on Tcl 8.6 still
renders the synopsis of a subject the runtime cannot run, unmarked.

10
goals/G-055-tclcore-regen-workflow.md

@ -106,6 +106,16 @@ other commands including the multi-form ::after.
session work of 2026-07-10; the C ground truth for the option-window semantics is
StringIsCmd in generic/tclCmdMZ.c (objv[2..objc-2] option loop - the final word
is unconditionally str).
- G-166 (achieved 2026-08-06 - goals/archive/G-166-cmdinfo-unavailable-surfacing.md)
left this goal an open follow-on for the workflow guidance, alongside the
G-073 forward-class pattern already dispositioned here: a forward class the
running runtime LACKS gets a conditional per-runtime prefix on its per-class
virtual id, so the generated help LEADS with the unavailability statement
(runtime patchlevel + the rejected call shape), then a blank line, then the
unchanged static description. The static version note alone is not enough -
it sat at the END of the prose, so a reader met the full class documentation
before learning the class does not exist there. Runtimes where the class is
live must generate the id unchanged (tclcore moduledoc 0.4.1).
- Probing policy: define-time/verification probing is limited to safe,
side-effect-free invocations of pure builtins (deliberately-invalid calls
harvesting error metadata). This does not relax cmdhelp's rule of never calling

84
goals/G-166-cmdinfo-unavailable-surfacing.md

@ -1,84 +0,0 @@
# G-166 cmdinfo/cmdhelp surface recognised-but-unavailable landings distinctly
Status: proposed
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo result key, cmd_traverse landing attribution, cmdhelp render marking + advisory-parse presentation); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (virtual ids lead with the unavailability note on affected runtimes); src/tests/modules/punk/ns/testsuites/ns/ (cmdinfo/cmdhelp pins incl the deliberate key-list flip); src/tests/modules/punk/args/testsuites/args/tclcoreparity.test (8.6-arm pins)
Goal: cmdinfo/cmdhelp surface recognised-but-unavailable landings: cmdinfo gains an availability key (empty, or the canonical unavailable word) populated wherever resolution addresses a -choiceunavailable word (exact space-form docid landings and the choice traverse), cmdhelp marks such subjects visibly in all return forms and its advisory parse no longer presents them as cleanly usable, the tclcore virtual ids lead with the unavailability note on affected runtimes, and the doc walk's treatment of a unique prefix landing on an unavailable name (currently: resolves no documentation) is decided and pinned within this goal - with the enacted G-073 follow-on items dispositioned accordingly.
Acceptance: on a runtime where the tclcore model declares dict unavailable (Tcl 8.6), cmdinfo for 'string is dict' (direct and decomposed invocation forms) reports the new availability key carrying 'dict' while 'string is true' reports it empty, with cmdtype remaining 'doconly' for both (the command-kind axis is not overloaded); the key is present on every cmdinfo result and the cmdhelp.test cmdinfo key-list pin is flipped deliberately; cmdhelp output for an unavailable subject is visibly distinguished in table, text and dict return forms (the dict form carries the key; table/text carry a marking naming the unavailability), and an argument tail such as 'string is dict 5' no longer renders as a cleanly usable command line; the walk's unique-prefix landing on an unavailable name ('i string is dic' on 8.6) has a decided, pinned behaviour consistent with the marking (either resolving the virtual docid WITH the marking, or documented-conservative non-resolution); tclcore virtual ids for unavailable classes lead their rendered help with the unavailability statement on affected runtimes (first description line, not buried mid-prose) while modern runtimes render those ids unchanged; definitions without -choiceunavailable and existing cmdinfo/cmdhelp consumers behave unchanged (full punk/ns and punk/args suites green apart from deliberately flipped pins); the enacted G-073 Follow-on lines carry => goal G-166 dispositions.
## Context
Found 2026-08-05 immediately after G-073 achieved (user probes): on Tcl 8.6,
'i ::tcl::string::is dict' (unavailable class) and 'i ::tcl::string::is true'
(real class) produce indistinguishable machine shapes - cmdinfo reports
cmdtype 'doconly' with identical key sets for both, and cmdhelp's advisory
parse of an argument tail ('string is dict 5') succeeds against the virtual id
so the render presents the unavailable class exactly like a usable one. The
only difference is the "(class not present in Tcl 8.6)" sentence mid-way
through the description prose.
Half of this is G-073's intent: documentation REACHABILITY for unavailable
words (via exact space-form virtual docids) is an acceptance requirement, and
doc addressing has always been deliberately broader than parse acceptance
(per-class ids like 'string is true' document argument words that were never
subcommands). The INDISTINGUISHABILITY is emergent, not specified: nothing
structural tells a consumer - human or machine - that the documented word is
rejected by this runtime's parse.
Axis decision (assessed 2026-08-05, to be confirmed in the work): a NEW
cmdinfo key, not a cmdtype refinement such as 'doconly_unavailable'. cmdtype
answers "what kind of executable thing did resolution land on"; availability
is a property of the resolved word at its level. Today they co-occur with
doconly only incidentally (unavailable entries never have real commands behind
them), and overloading the value breaks consumers switching on
cmdtype eq "doconly". The two-axes distinction is recorded in the cmd_traverse
choice-traverse comment (ns 0.9.3).
## Approach
- New cmdinfo result key (spelling decided in the work; candidate
'unavailable' - empty string normally, the canonical unavailable word when
the resolution addressed one). Population is nearly free: the choice
traverse already holds the resolver's matchinfo (its 'unavailable' key,
punk::args 0.16.0), and the exact space-form id landing branches can consult
the level's choices-bearing arginfo the same way the space-form prefix-parity
branch (G-051, achieved 2026-07-13 - see
goals/archive/G-051-cmdinfo-pseudo-and-prefix.md) already does
(Dict_getdef -choiceunavailable + membership check).
- cmdhelp derives the render marking from the same data: a visible
unavailability statement in table/text forms (warn styling; placement decided
in the work - title line marker and/or a leading line), the key in -return
dict, and the advisory-parse presentation adjusted so an accepted parse
against an unavailable subject's virtual id is not shown as a cleanly usable
command line.
- tclcore generator: on runtimes where a forward class is unavailable, the
per-class virtual id's help LEADS with the unavailability statement (the
static description follows); modern runtimes generate the id unchanged.
- Prefix-landing decision: either the walk resolves a unique prefix landing to
the virtual docid and relies on the marking to carry the rejection story
(parse gives the tailored "resolves to 'dict'" report for the same word), or
the current conservative non-resolution is kept and pinned as deliberate.
Decided in the work; the G-073 follow-on line flips accordingly.
## Notes
- Related: G-073 (achieved 2026-08-05, archived -
goals/archive/G-073-punkargs-unavailable-choices.md) - this goal enacts its
open follow-on on doc-walk prefix landings and resolves the
indistinguishability its acceptance did not specify; the -choiceunavailable
key, resolver indication and tailored parse rejection this goal consumes all
landed there (punk::args 0.16.0, tclcore moduledoc 0.4.0, ns 0.9.3).
- Related: G-050 (punk::ns::synopsis argument-validity marking and
status-aware returns) - sibling display-marking surface; whichever lands
second should reuse the first's marking vocabulary/styling decisions.
- Related: G-044 (completion/hinting) - a future consumer: completion over
choices should be able to exclude or annotate unavailable words from the
same data rather than re-deriving.
- Related: G-055 (tclcore moduledoc regeneration workflow) - the tclcore
virtual-id leading-note change lands in the moduledoc G-055's workflow
governs; its guidance gains the forward-class pattern per the G-073
follow-on already dispositioned => goal G-055.
- Overlap survey 2026-08-05: goals_xref paths src/modules/punk/ns-999999.0a1.0.tm
- G-044/G-050/G-052/G-075/G-085 surfaced; G-052 (TclOO autodef) and G-085
(cmdtrace) are same-file unrelated concerns; G-075 ((package) ids) is
sibling docid territory with no word-availability interaction - deliberate
non-overlap.

22
goals/G-167-punkargs-version-delta-availability.md

@ -83,14 +83,20 @@ deltas use -choiceunavailable; forms are reserved for true shape deltas.
- Related: G-073 (achieved 2026-08-05 - goals/archive/G-073-punkargs-unavailable-choices.md) -
the foundation: concept, resolver indication, tailored-message wording and
display vocabulary this goal extends to option names and forms.
- Related: G-166 (proposed) - sibling surfacing goal: word-level cmdinfo
availability key + cmdhelp marking. This goal's machine surface is the
formstatus 'available' field; the two must share marking vocabulary and
styling, and whichever lands first owns it. (G-149, formerly named here as
a third owner candidate, achieved 2026-08-05 without introducing any
form-line marking vocabulary - its hint labels are the LEFT label column
of the Synopsis cell, composing freely with form-line annotations - so
ownership is between this goal and G-166.)
- Related: G-166 (achieved 2026-08-06 - goals/archive/G-166-cmdinfo-unavailable-surfacing.md) -
sibling surfacing goal: word-level cmdinfo availability key + cmdhelp
marking. This goal's machine surface is the formstatus 'available' field.
G-166 LANDED FIRST AND THEREFORE OWNS the marking vocabulary: reuse its
wording - "'<name>' is a recognised name here, but is not available in this
runtime/context" (punk::ns::_cmdhelp_unavailable_marking, itself mirroring
punk::args' private::unavailable_choice_msg) - for form and option
annotations rather than minting a second phrasing, and reuse its machine
vocabulary too: the cmdinfo key is named 'unavailable' and carries the
canonical name (empty when not applicable), always present on the result.
(G-149, formerly named here as a third owner candidate, achieved 2026-08-05
without introducing any form-line marking vocabulary - its hint labels are
the LEFT label column of the Synopsis cell, composing freely with form-line
annotations.)
- Related: G-152 (achieved 2026-08-02 - goals/archive/G-152-punkargs-form-viability-verdict.md) -
the formstatus consumer contract the 'available' field extends additively.
- Related: G-041 (achieved - goals/archive/G-041-punkargs-form-matching.md) -

188
goals/archive/G-166-cmdinfo-unavailable-surfacing.md

@ -0,0 +1,188 @@
# G-166 cmdinfo/cmdhelp surface recognised-but-unavailable landings distinctly
Status: achieved 2026-08-06
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo result key, cmd_traverse landing attribution, cmdhelp render marking + advisory-parse presentation); src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm (virtual ids lead with the unavailability note on affected runtimes); src/tests/modules/punk/ns/testsuites/ns/ (cmdinfo/cmdhelp pins incl the deliberate key-list flip); src/tests/modules/punk/args/testsuites/args/tclcoreparity.test (8.6-arm pins)
Goal: cmdinfo/cmdhelp surface recognised-but-unavailable landings: cmdinfo gains an availability key (empty, or the canonical unavailable word) populated wherever resolution addresses a -choiceunavailable word (exact space-form docid landings and the choice traverse), cmdhelp marks such subjects visibly in all return forms and its advisory parse no longer presents them as cleanly usable, the tclcore virtual ids lead with the unavailability note on affected runtimes, and the doc walk's treatment of a unique prefix landing on an unavailable name (currently: resolves no documentation) is decided and pinned within this goal - with the enacted G-073 follow-on items dispositioned accordingly.
Acceptance: on a runtime where the tclcore model declares dict unavailable (Tcl 8.6), cmdinfo for 'string is dict' (direct and decomposed invocation forms) reports the new availability key carrying 'dict' while 'string is true' reports it empty, with cmdtype remaining 'doconly' for both (the command-kind axis is not overloaded); the key is present on every cmdinfo result and the cmdhelp.test cmdinfo key-list pin is flipped deliberately; cmdhelp output for an unavailable subject is visibly distinguished in table, text and dict return forms (the dict form carries the key; table/text carry a marking naming the unavailability), and an argument tail such as 'string is dict 5' no longer renders as a cleanly usable command line; the walk's unique-prefix landing on an unavailable name ('i string is dic' on 8.6) has a decided, pinned behaviour consistent with the marking (either resolving the virtual docid WITH the marking, or documented-conservative non-resolution); tclcore virtual ids for unavailable classes lead their rendered help with the unavailability statement on affected runtimes (first description line, not buried mid-prose) while modern runtimes render those ids unchanged; definitions without -choiceunavailable and existing cmdinfo/cmdhelp consumers behave unchanged (full punk/ns and punk/args suites green apart from deliberately flipped pins); the enacted G-073 Follow-on lines carry => goal G-166 dispositions.
## Context
Found 2026-08-05 immediately after G-073 achieved (user probes): on Tcl 8.6,
'i ::tcl::string::is dict' (unavailable class) and 'i ::tcl::string::is true'
(real class) produce indistinguishable machine shapes - cmdinfo reports
cmdtype 'doconly' with identical key sets for both, and cmdhelp's advisory
parse of an argument tail ('string is dict 5') succeeds against the virtual id
so the render presents the unavailable class exactly like a usable one. The
only difference is the "(class not present in Tcl 8.6)" sentence mid-way
through the description prose.
Half of this is G-073's intent: documentation REACHABILITY for unavailable
words (via exact space-form virtual docids) is an acceptance requirement, and
doc addressing has always been deliberately broader than parse acceptance
(per-class ids like 'string is true' document argument words that were never
subcommands). The INDISTINGUISHABILITY is emergent, not specified: nothing
structural tells a consumer - human or machine - that the documented word is
rejected by this runtime's parse.
Axis decision (assessed 2026-08-05, to be confirmed in the work): a NEW
cmdinfo key, not a cmdtype refinement such as 'doconly_unavailable'. cmdtype
answers "what kind of executable thing did resolution land on"; availability
is a property of the resolved word at its level. Today they co-occur with
doconly only incidentally (unavailable entries never have real commands behind
them), and overloading the value breaks consumers switching on
cmdtype eq "doconly". The two-axes distinction is recorded in the cmd_traverse
choice-traverse comment (ns 0.9.3).
## Approach
- New cmdinfo result key (spelling decided in the work; candidate
'unavailable' - empty string normally, the canonical unavailable word when
the resolution addressed one). Population is nearly free: the choice
traverse already holds the resolver's matchinfo (its 'unavailable' key,
punk::args 0.16.0), and the exact space-form id landing branches can consult
the level's choices-bearing arginfo the same way the space-form prefix-parity
branch (G-051, achieved 2026-07-13 - see
goals/archive/G-051-cmdinfo-pseudo-and-prefix.md) already does
(Dict_getdef -choiceunavailable + membership check).
- cmdhelp derives the render marking from the same data: a visible
unavailability statement in table/text forms (warn styling; placement decided
in the work - title line marker and/or a leading line), the key in -return
dict, and the advisory-parse presentation adjusted so an accepted parse
against an unavailable subject's virtual id is not shown as a cleanly usable
command line.
- tclcore generator: on runtimes where a forward class is unavailable, the
per-class virtual id's help LEADS with the unavailability statement (the
static description follows); modern runtimes generate the id unchanged.
- Prefix-landing decision: either the walk resolves a unique prefix landing to
the virtual docid and relies on the marking to carry the rejection story
(parse gives the tailored "resolves to 'dict'" report for the same word), or
the current conservative non-resolution is kept and pinned as deliberate.
Decided in the work; the G-073 follow-on line flips accordingly.
## Notes
- Related: G-073 (achieved 2026-08-05, archived -
goals/archive/G-073-punkargs-unavailable-choices.md) - this goal enacts its
open follow-on on doc-walk prefix landings and resolves the
indistinguishability its acceptance did not specify; the -choiceunavailable
key, resolver indication and tailored parse rejection this goal consumes all
landed there (punk::args 0.16.0, tclcore moduledoc 0.4.0, ns 0.9.3).
- Related: G-050 (punk::ns::synopsis argument-validity marking and
status-aware returns) - sibling display-marking surface; whichever lands
second should reuse the first's marking vocabulary/styling decisions.
- Related: G-044 (completion/hinting) - a future consumer: completion over
choices should be able to exclude or annotate unavailable words from the
same data rather than re-deriving.
- Related: G-055 (tclcore moduledoc regeneration workflow) - the tclcore
virtual-id leading-note change lands in the moduledoc G-055's workflow
governs; its guidance gains the forward-class pattern per the G-073
follow-on already dispositioned => goal G-055.
- Overlap survey 2026-08-05: goals_xref paths src/modules/punk/ns-999999.0a1.0.tm
- G-044/G-050/G-052/G-075/G-085 surfaced; G-052 (TclOO autodef) and G-085
(cmdtrace) are same-file unrelated concerns; G-075 ((package) ids) is
sibling docid territory with no word-availability interaction - deliberate
non-overlap.
- Related: G-167 (proposed) - back-pointer added at activation 2026-08-06
(activation-freshness survey, the top-ranked pair at 2.21; G-167 already
points here): its @form/option-name availability layers are the shape-delta
sibling of this goal's word-level availability. Marking-vocabulary ownership
is settled by this goal landing first - G-167's form/option annotations reuse
the wording established here ("is a recognised name here, but is not
available in this runtime/context", shared with punk::args'
private::unavailable_choice_msg) rather than minting a second phrasing.
- Activation-freshness survey 2026-08-06 (goals_xref score G-166): no other
material relationship found. The remaining unlinked pairs are lexical - G-072
/ G-083 / G-084 share the tclcore moduledoc and tclcoreparity.test paths as
punk::args definition-authoring goals with no word-availability interaction
(deliberate non-overlap, as recorded for G-075 above); G-047 / G-066 / G-063
/ G-163 / G-056 / G-016 / G-048 / G-158 / G-110 / G-148 / G-061 / G-069 score
on shared vocabulary only, with zero path intersection.
## Progress
- 2026-08-06 (activated and achieved, one session): punk::ns 0.10.0 +
punk::args::moduledoc::tclcore 0.4.1 (source modules); punkshell 0.57.0.
Key spelling as proposed: 'unavailable' - matching punk::args'
choiceword_match result-key name so the two layers share one word.
AXIS DECISION CONFIRMED as drafted: a new cmdinfo key, not a cmdtype
refinement. cmdtype still answers what kind of executable thing resolution
landed on ('string is dict' and 'string is true' both stay 'doconly' on 8.6);
'unavailable' answers whether the resolved word is selectable here. The key
is present on EVERY cmdinfo result (empty when not applicable), so consumers
never have to test for its existence.
Mechanism: cmd_traverse's yielded/returned records gained a sixth element
carrying the level's availability attribution; cmdinfo keeps the FINAL
record's value (the availability of the landing it reports). Population is
as cheap as predicted - the level's choices-bearing first leader is resolved
once per level with punk::args::choiceword_match and BOTH the exact
space-form landing and the prefix retry consult that one result (the G-051
branch was restructured rather than duplicated).
- PREFIX-LANDING DECISION (enacting the G-073 follow-on): a unique prefix
landing on an unavailable name RESOLVES that name's virtual docid, attributed
- the conservative non-resolution was not kept. Rationale recorded at the
code site: documentation ADDRESSING and word AVAILABILITY are separate axes,
which is this goal's whole thesis; encoding availability as
resolve-vs-don't-resolve conflated them and made addressing inconsistent
('i string is tr' resolved but 'i string is dic' did not, while exact
'string is true' and 'string is dict' both did). Now every form of address
that names a documented word resolves it, and the availability key plus
cmdhelp's marking carry the rejection story parse reports for the same word.
The choice traverse's G-040 parse-parity claim is deliberately untouched: an
unavailable word still identifies no SUBCOMMAND there - it is only attributed
(the path taken when the unavailable name has no space-form virtual docid).
- cmdhelp marking: one wording, one home
(punk::ns::_cmdhelp_unavailable_marking), used by every return form - the
-return dict form carries the key, table/string render the message below
the usage under the non-info scheme, -return text leads with an
'UNAVAILABLE:' line (it is the machine/LLM-facing surface). The marking takes
precedence over BOTH the ok-parse info render and any parse failure message:
the subject not existing here is the more fundamental report, and it is the
one a parse against a virtual id cannot make - which is exactly why
'i string is dict 5' previously rendered info-scheme-clean (the advisory
parse of '5' SUCCEEDS against the virtual id). The parse status is still
passed through, so argument positions keep their marking under the
non-info scheme. Wording mirrors punk::args'
private::unavailable_choice_msg; the runtime-specific WHY is deliberately
not repeated in punk::ns - it belongs to the definition, and the subject's
own help now leads with it.
- tclcore: the per-class virtual id of a forward class this runtime lacks
leads its help with the unavailability statement (naming the runtime
patchlevel and the rejected call shape), a blank line, then the unchanged
static description. Runtimes where the class is live generate the id
unchanged - the unavailable set is empty there, so the arm is unreachable
rather than merely inactive.
- Verification (runtests.tcl, three runtimes): punk/ns cmdflow.test 25/25 and
cmdhelp.test 50/50 on native Tcl 8.6.10
(bin/runtime/win32-ix86/tclsh8.6.10-luck-zip.exe - THE AFFECTED ARM,
unavailable={dict}), 8.7a6 (c:/tcl87) and 9.0.5; full punk/ns subtree 125
tests green on 9.0.5, and on 8.6/8.7 green apart from the two PRE-EXISTING
nsprimitives failures (nseval_ifexists_weird, nstree_raw_pins - the
weird-namespace ':::' resolution divergence that 9.x does not share),
confirmed identical with the module changes stashed on BOTH runtimes. Full
punk/args subtree 341 total / 338 passed / 3 intended skips / 0 failed on
8.6.10, 8.7a6 and 9.0.5. Broad regression sweep: the whole
src/tests/modules/*** tree on 9.0.5 - 1257 tests, 108 files, 0 failed. New pins: cmdflow.test flowunavail (per-choice virtual
docids) + flowunavail_nodoc (attribution without addressing) fixtures with 5
tests (key always present and empty by default; exact landing attributed with
cmdtype unchanged; the prefix-landing decision pinned against parse's
choiceunavailable rejection; choice-traverse attribution; attribution
surviving an argument tail); cmdhelp.test helpstrua fixture with 3 render
tests (dict key, argument-tail-not-cleanly-usable incl the info-scheme
suppression, string+text marking) plus 2 tclcore-gated real-world tests
('string is dict' vs 'string is true' direct AND decomposed forms, and the
'i string is dict 5' arm) - all live-derived so the modern arm asserts the
same shapes with an empty key; tclcoreparity.test gains the virtual-id
leading-note pin. The two deliberate key-list flips landed as drafted
(cmdhelp_cmdinfo_result_shape, cmdhelp_return_dict_valid).
- Consumer audit: every in-tree cmdinfo consumer reads named keys via
'dict get' (punk::ns synopsis/eg/forms/corp/cmdtrace/cmdhelp,
punk::args ensemble_subcommands_definition and its usage resolution,
scriptlib/developer/whatis.tcl and nslist.tcl) - none positional, so the
added key is inert for all of them.
## Follow-ons
Follow-on: punk::ns::synopsis does not surface the availability axis - 's string is dict' on 8.6 renders the synopsis of a subject the runtime cannot run, unmarked (deliberately out of scope: the synopsis marking surface is G-050's, and this goal established the vocabulary G-050 reuses) => goal G-050
Follow-on: completion/hinting should consume the cmdinfo availability key (and the -choiceunavailable pool behind it) rather than re-deriving, so unavailable words display-but-do-not-complete => goal G-044
Follow-on: the tclcore leading-note pattern (conditional per-runtime help prefix on a forward class's virtual id) belongs in the moduledoc authoring workflow guidance alongside the G-073 forward-class pattern already dispositioned there => goal G-055
Follow-on: G-167's @form/option-name availability surfacing reuses the marking vocabulary this goal established rather than minting a second phrasing => goal G-167

2
punkproject.toml

@ -1,6 +1,6 @@
[project]
name = "punkshell"
version = "0.56.2"
version = "0.57.0"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -

14
src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm

@ -10074,7 +10074,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#set string_class_choices [dict get [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end] -choices]
set string_class_choicelabels [dict get [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end] -choicelabels]
dict for {sclass slabel} $string_class_choicelabels {
punk::args::define [string map [list %sc% $sclass %slabel% $slabel] {
#G-166: the virtual ids for forward classes THIS runtime lacks lead their help
#with the unavailability statement. The static class description already carries
#a version annotation, but it sits at the END of the prose (dict's "(class not
#present in Tcl 8.6)" is the twelfth line) - a reader of 'i string is dict' on 8.6
#met the full class documentation before learning the class does not exist here.
#Runtimes that HAVE the class generate the id unchanged (the loop's condition is
#empty there, since string_is_unavailable is computed per-runtime above).
if {$sclass in $string_is_unavailable} {
set sclass_help " NOT AVAILABLE in this Tcl\n runtime ([info patchlevel]).\n Recognised forward class:\n the name is known and its\n prefix reserved here, but\n 'string is $sclass' is\n rejected by this\n interpreter.\n\n$slabel"
} else {
set sclass_help $slabel
}
punk::args::define [string map [list %sc% $sclass %slabel% $sclass_help] {
@id -id "::tcl::string::is %sc%"
@cmd -name "Built-in: string is %sc%" -help\
{%slabel%}

3
src/modules/punk/args/moduledoc/tclcore-buildversion.txt

@ -1,6 +1,7 @@
0.4.0
0.4.1
#First line must be a semantic version number
#all other lines are ignored.
#0.4.1 - G-166: on a runtime that LACKS a curated forward class, that class's per-class virtual id now LEADS its help with the unavailability statement ('NOT AVAILABLE in this Tcl runtime (<patchlevel>). Recognised forward class: ... but 'string is dict' is rejected by this interpreter.'), followed by a blank line and the unchanged static description. The static description already carried the version note - but at its END ('(class not present in Tcl 8.6)' is its twelfth line), so a reader of 'i string is dict' on 8.6 met the full class documentation before learning the class does not exist there. Runtimes where the class is live generate the id unchanged (the unavailable set is empty on 8.7/9.x). Pinned by tclcoreparity.test tclcoreparity_stringis_unavailable_virtualid_leads_with_note (live-derived: affected arm on 8.6, unchanged arm on 8.7/9.x).
#0.4.0 - G-073 'string is' forward-class adoption: a curated forward-class list (dict; 'unicode' deliberately excluded - unreleased-8.7-only, removed in Tcl 9) is diffed against the harvested live class set at define time, and classes this runtime lacks are declared -choiceunavailable (punk::args 0.16.0 - the require gains that floor). Effect on 8.6: dict displays among the classes under the Unavailable group with its static description ("class not present in Tcl 8.6"), 'string is dict' is rejected with the tailored choiceunavailable error naming that note, 'string is di' is ambiguous (deliberately stricter than real 8.6, which accepts 'di' as digit - preparing users for 9.x where dict makes it genuinely ambiguous; recorded as a user-sanctioned parity exemption in tclcoreparity.test, full words parity-true), and 'i string is dict' documents the class via its per-class virtual id (the choicelabels build and the virtual-id loop now cover unavailable classes from the same static descriptions). Modern runtimes (8.7/9.x - dict live) compute an empty unavailable list and behave identically to 0.3.4.
#0.3.4 - G-074: the documented cancelid/cancelscript overlap on ::after cancel is sanctioned via the new @form -overlapallowed key (punk::args 0.12.0) - punk::args::formcheck now reports it as an acknowledged (sanctioned) structural overlap rather than an actionable finding, leaving ::after with zero unsanctioned findings. Parse behaviour unchanged: an id-shaped 'after cancel' word still raises multipleformmatches (the runtime-liveness ambiguity real Tcl resolves by trying the id first - 0.3.0 record). ::lseq deliberately NOT sanctioned: its range/start_count and range/count overlaps are type-weakness findings (the expr-typed end slot swallows the 'count'/'by' discriminator words) - kept visible pending an expr syntax-validating type (G-069/G-070 territory, G-055 operand-typing record).
#0.3.3 - removed the parse-inert @dynamic tags from ::split, ::array and ::join (user-directed; identified by the 0.3.2 sweep). None has round-2 substitution content, and their ${...} placeholders are all display-field styling/examples - the definitions now resolve as ordinary static definitions with display expansion cached per raw definition (previously @dynamic forced display re-expansion on every render). No bad-@dynamic warnings remain across the module's 462 registered ids; renders and parses verified unchanged for all three.

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

@ -4652,7 +4652,25 @@ y" {return quirkykeyscript}
-summary\
"Subcommand resolution of ensemble-like tree of commands."\
-help\
"Return a dict with command resolution info for ensemble-like tree of commands with subcommands"
"Return a dict with command resolution info for ensemble-like tree of commands with subcommands
Result keys:
origin - the resolved command (or the docid path for a
documentation-only landing)
cmdtype - what KIND of executable thing resolution landed on
(proc, native, alias, ensemble, doconly, notfound...)
unavailable - (G-166) empty string, or the canonical
-choiceunavailable name the resolution addressed.
A SEPARATE axis from cmdtype: it describes the
resolved word's availability in this
runtime/context, not the kind of thing found.
Non-empty means the landing is documentation the
current runtime cannot actually run - e.g
'string is dict' on Tcl 8.6.
args_resolved - the command words consumed by resolution
args_remaining- the trailing words resolution did not consume
docid - the punk::args definition id documenting the landing
stack - per-level record of the traversal"
@leaders -min 0 -max 0
@opts
-form -default * -help\
@ -4683,8 +4701,9 @@ y" {return quirkykeyscript}
set commands [list]
set consumed_args [list]
set docid ""
set unavailable "" ;#G-166: availability attribution of the FINAL landing (each record overwrites)
while {$final == 0} {
lassign [$reduce $origin] final origin consumed remainingargs docid
lassign [$reduce $origin] final origin consumed remainingargs docid unavailable
#if {$final != 1} {
if {[string match (autodef)* $origin]} {
set origin [string range $origin 9 end]
@ -4715,8 +4734,19 @@ y" {return quirkykeyscript}
#genuinely unknown command. The docid remains authoritative for display.
set cmdtype doconly
}
return [list origin $origin cmdtype $cmdtype args_resolved [list [lindex $commands 0] {*}$consumed_args] args_remaining $remainingargs docid $docid stack $stack]
#G-166: 'unavailable' is deliberately a SECOND axis beside cmdtype - cmdtype
#answers what kind of executable thing resolution landed on, availability
#answers whether the resolved word is selectable in this runtime/context.
#Overloading cmdtype (e.g a 'doconly_unavailable' value) would break every
#consumer switching on cmdtype eq "doconly", and the two only co-occur
#incidentally (an unavailable name has no real command behind it today).
return [list origin $origin cmdtype $cmdtype unavailable $unavailable args_resolved [list [lindex $commands 0] {*}$consumed_args] args_remaining $remainingargs docid $docid stack $stack]
}
#Each record yielded/returned by cmd_traverse is:
# [list <code> <origin> <resolvedargs> <remainingargs> <docid> <unavailable>]
#<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.
proc cmd_traverse {ns formid args} {
set autodefined [dict create]
#puts "cmd_traverse args: $args yielding: [info coroutine]"
@ -4746,7 +4776,7 @@ y" {return quirkykeyscript}
set origin $which ;#Flip our traversal to be on the documented 'which' rather than the actual origin
if {$whichtype eq "alias"} {
#*documented* alias
return [list 1 $origin {} [lrange $args 1 end] $docid]
return [list 1 $origin {} [lrange $args 1 end] $docid {}]
}
}
}
@ -4773,10 +4803,10 @@ y" {return quirkykeyscript}
#} else {
if {$docid ne "" && ![llength [lrange $args 1 end]]} {
return [list 0a $origin {} {} $docid]
return [list 0a $origin {} {} $docid {}]
}
set origin [yield [list 0 $origin {} [lrange $args 1 end] $docid]]
set origin [yield [list 0 $origin {} [lrange $args 1 end] $docid {}]]
set whichinfo [namespace eval $ns [list punk::ns::cmdwhich $cmd]]
set origin [dict get $whichinfo origin]
set origintype [dict get $whichinfo origintype]
@ -4867,7 +4897,7 @@ y" {return quirkykeyscript}
}
#}
if {[llength $args] == 1} {
return [list 2 $origin $resolvedargs {} $docid]
return [list 2 $origin $resolvedargs {} $docid {}]
}
set terminate 0
for {set i 1} {$i < [llength $args]} {incr i} {
@ -4894,19 +4924,18 @@ y" {return quirkykeyscript}
set docid_exists 0
set eparams [list]
set a_spaceform ""
if {[punk::args::id_exists "$origin [lindex $args $i]"]} {
set a_spaceform [lindex $args $i]
} elseif {$docid ne "" && [punk::args::id_exists $docid]} {
#G-051 space-form docid prefix parity: no space-form id exists for the
#exact word - if the current level's definition has a choices-bearing
#first leader, resolve the word with the same shared resolver argument
#parsing uses (punk::args::choiceword_match - honouring -choiceprefix,
#-nocase, -choicealiases, -choiceprefixdenylist,
#-choiceprefixreservelist) and retry the space-form lookup with the
#canonical word - so 'i string is tr' lands on the documentation for
#what 'string is tr' actually executes. No second matching rule: a word
#parse would reject resolves nothing here either, and a canonical with
#no space-form id falls through to the normal per-level handling.
set a_unavailable "" ;#G-166 availability attribution for this level's landing
set a_word [lindex $args $i]
#G-051 space-form docid prefix parity / G-166 availability attribution:
#resolve the word against the current level's choices-bearing first leader
#with the same shared resolver argument parsing uses
#(punk::args::choiceword_match - honouring -choiceprefix, -nocase,
#-choicealiases, -choiceprefixdenylist, -choiceprefixreservelist and
#-choiceunavailable). Computed once here because BOTH the exact-word
#space-form landing and the prefix retry consult it: the exact landing only
#for the availability verdict, the prefix retry for the canonical word too.
set lvl_matchinfo ""
if {$docid ne "" && [punk::args::id_exists $docid]} {
set pf_spec [punk::args::get_spec $docid]
set pf_fid [lindex [dict get $pf_spec form_names] 0]
set pf_leaders [dict get $pf_spec FORMS $pf_fid LEADER_NAMES]
@ -4917,7 +4946,7 @@ y" {return quirkykeyscript}
lappend pf_allchoices {*}$pf_members
}
if {[llength $pf_allchoices]} {
set pf_matchinfo [punk::args::choiceword_match [lindex $args $i]\
set lvl_matchinfo [punk::args::choiceword_match $a_word\
[punk::args::system::Dict_getdef $pf_arginfo -nocase 0]\
$pf_allchoices\
[punk::args::system::Dict_getdef $pf_arginfo -choicealiases {}]\
@ -4926,15 +4955,46 @@ y" {return quirkykeyscript}
[punk::args::system::Dict_getdef $pf_arginfo -choiceprefixreservelist {}]\
[punk::args::system::Dict_getdef $pf_arginfo -choiceunavailable {}]\
]
if {[dict get $pf_matchinfo matched]} {
set pf_canonical [dict get $pf_matchinfo canonical]
if {$pf_canonical ne [lindex $args $i] && [punk::args::id_exists "$origin $pf_canonical"]} {
set a_spaceform $pf_canonical
}
}
}
}
}
if {[punk::args::id_exists "$origin $a_word"]} {
set a_spaceform $a_word
#G-166: the exact word may address a recognised-but-unavailable name.
#Its virtual docid exists by G-073 design (documentation REACHABILITY
#for unavailable words), so resolution succeeds - but the landing is
#attributed so consumers can tell it from a usable one.
if {$lvl_matchinfo ne "" && [dict get $lvl_matchinfo unavailable]} {
set a_unavailable [dict get $lvl_matchinfo canonical]
}
} elseif {$lvl_matchinfo ne "" && [dict get $lvl_matchinfo matched]} {
#retry the space-form lookup with the canonical word - so 'i string is
#tr' lands on the documentation for what 'string is tr' actually
#executes. No second matching rule: a word parse would reject resolves
#nothing here either, and a canonical with no space-form id falls
#through to the normal per-level handling.
set pf_canonical [dict get $lvl_matchinfo canonical]
if {$pf_canonical ne $a_word && [punk::args::id_exists "$origin $pf_canonical"]} {
set a_spaceform $pf_canonical
}
} elseif {$lvl_matchinfo ne "" && [dict get $lvl_matchinfo unavailable]} {
#G-166 DECISION (enacting the G-073 follow-on): a unique prefix landing
#on an unavailable name resolves that name's virtual docid, marked -
#it no longer conservatively resolves nothing. Rationale: documentation
#ADDRESSING and word AVAILABILITY are separate axes (the whole point of
#the attribution added here). Encoding availability as
#resolve-vs-don't-resolve conflated them and made addressing
#inconsistent - 'string is tr' resolved but 'string is dic' did not,
#while exact 'string is true' and 'string is dict' both did. Now every
#form of address that names a documented word resolves it, and the
#availability key (plus cmdhelp's marking) carries the rejection story
#that parse reports for the same word.
set pf_canonical [dict get $lvl_matchinfo canonical]
if {[punk::args::id_exists "$origin $pf_canonical"]} {
set a_spaceform $pf_canonical
set a_unavailable $pf_canonical
}
}
if {$a_spaceform ne ""} {
set a $a_spaceform
#review - tests?
@ -4966,7 +5026,7 @@ y" {return quirkykeyscript}
#review - get_spec needs to resolve if @dynamic
#we don't really need the spec if we have no queryargs
if {![llength $queryargs]} {
return [list X $origin $resolvedargs $queryargs_untested $docid]
return [list X $origin $resolvedargs $queryargs_untested $docid $a_unavailable]
}
@ -5063,7 +5123,7 @@ y" {return quirkykeyscript}
#ledit queryargs_untested 0 0
#jjj
#continue
return [list 3 $origin $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid]
return [list 3 $origin $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid $a_unavailable]
break
}
#G-040: resolve the subcommand word with the same shared resolver argument
@ -5078,9 +5138,10 @@ y" {return quirkykeyscript}
#doc-only ids without implying the word parses - that is how per-class ids
#like 'string is true' have always documented argument words, and how an
#UNAVAILABLE word's virtual id ('string is dict' on 8.6) documents since
#G-073. Consequence: cmdinfo classes both landings 'doconly' with no
#availability distinction - surfacing that (cmdinfo axis / render marking)
#is deliberately not decided here.
#G-073. G-166 settled the surfacing: the addressing branches above resolve
#such landings and cmdinfo reports them on its own 'unavailable' axis,
#while THIS traverse keeps its parity claim intact - an unavailable word
#still identifies no subcommand here, it is only attributed below.
set ct_matchinfo [punk::args::choiceword_match $q\
[punk::args::system::Dict_getdef $arginfo -nocase 0]\
$allchoices\
@ -5093,7 +5154,14 @@ y" {return quirkykeyscript}
if {![dict get $ct_matchinfo matched]} {
#no match under parse rules (covers: unknown word, ambiguous prefix,
#reserved word, denied prefix, and non-exact word when -choiceprefix 0)
return [list 4 $origin $resolvedargs $queryargs_untested $docid]
#G-166: a landing on a recognised-but-unavailable name is still not a
#subcommand match, but it IS attributed - this is the path taken when
#the unavailable name has no space-form virtual docid to address.
set ct_unavailable ""
if {[dict get $ct_matchinfo unavailable]} {
set ct_unavailable [dict get $ct_matchinfo canonical]
}
return [list 4 $origin $resolvedargs $queryargs_untested $docid $ct_unavailable]
break
}
set resolved_q [dict get $ct_matchinfo canonical]
@ -5191,10 +5259,10 @@ y" {return quirkykeyscript}
#punk::args::update_definitions [list [namespace qualifiers $mapped_subcmd]]
if {[llength $queryargs_untested] == 0} {
return [list 6 $mapped_subcmd $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid]
return [list 6 $mapped_subcmd $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid {}]
}
set origin [yield [list 0 $mapped_subcmd $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid]]
set origin [yield [list 0 $mapped_subcmd $resolvedargs [list {*}$eparams {*}$queryargs_untested] $docid {}]]
#set resolvedargs [list]
#incr i [expr {-1 * [llength $resolvedargs]+1}] ;#wrong e.g test trace add execution blah enterstep cmd
@ -5223,22 +5291,22 @@ y" {return quirkykeyscript}
break ;#out of foreach q $queryargs ...
} else {
#test with: i namespace which -v x
return [list 7 $origin $resolvedargs $queryargs_untested $prevdocid]
return [list 7 $origin $resolvedargs $queryargs_untested $prevdocid {}]
}
} ;#end loop foreach q $queryargs lname $leadernames_matched
} else {
#??
#puts stderr "cmdinfo.cmd_traverse returning 8 origin: $origin resolved: $resolvedargs remaining: [lrange $args $i end] docid: $docid"
return [list 8 $origin $resolvedargs [lrange $args $i end] $docid]
return [list 8 $origin $resolvedargs [lrange $args $i end] $docid $a_unavailable]
}
} else {
#puts stderr "origin $origin not documented"
return [list 9 $origin {} [lrange $args $i end] ""]
return [list 9 $origin {} [lrange $args $i end] "" {}]
}
}
#REVIEW!!!
#puts stderr "cmd_traverse 10 $origin $resolvedargs $queryargs_untested $docid - review"
return [list 10 $origin $resolvedargs $queryargs_untested $docid]
return [list 10 $origin $resolvedargs $queryargs_untested $docid $a_unavailable]
}
@ -5494,12 +5562,18 @@ y" {return quirkykeyscript}
} -help\
"Return form of the usage information.
'dict' returns a dict with keys origin, docid, cmdtype,
args_remaining and parsestatus - where parsestatus is the
parse-status structure of the supplied argument words against
the resolved definition (see punk::args::parse_status for the
documented structure; empty if the command is undocumented).
unavailable, args_remaining and parsestatus - where parsestatus
is the parse-status structure of the supplied argument words
against the resolved definition (see punk::args::parse_status for
the documented structure; empty if the command is undocumented).
Its per-argument statuses distinguish a fully-valid, an invalid
and an incomplete argument set machine-parsably.
'unavailable' (G-166) is empty, or the canonical
-choiceunavailable name the resolution addressed - a subject the
current runtime/context documents but cannot run (e.g
'string is dict' on Tcl 8.6). The other return forms carry the
same fact as a visible marking, and never present such a subject
as a cleanly usable command line.
'text' returns plain text with no ANSI codes and no table
layout, rendering the argument section of every command form
(or of each form in an explicit -form selection) in a single
@ -5542,6 +5616,17 @@ y" {return quirkykeyscript}
form (no words, an ambiguous match, or words no form
accepts) keep the whole-command render."
}
#G-166: the single wording for cmdhelp's recognised-but-unavailable marking, shared
#by the table and text return forms so every surface says the same thing. Vocabulary
#deliberately mirrors punk::args' tailored parse rejection
#(private::unavailable_choice_msg, G-073) - a user who meets both reads one story.
#The runtime-specific WHY is not repeated here: it belongs to the definition, and the
#unavailable subject's own help leads with it (the tclcore virtual ids do since G-166).
proc _cmdhelp_unavailable_marking {name} {
#(wording stays position-neutral: the table/string renderers place the message
#below the usage, the text form leads with it)
return "'$name' is a recognised name here, but is not available in this runtime/context - this command line is not usable here and the usage shown is reference documentation only."
}
proc cmdhelp {args} {
set nscaller [uplevel 1 [list ::tcl::namespace::current]]
lassign [dict values [punk::args::parse $args -cache 1 withid ::punk::ns::cmdhelp]] leaders opts values received
@ -5592,6 +5677,7 @@ y" {return quirkykeyscript}
if {$docid eq ""} {
return "Undocumented command [dict get $dinfo origin]. Type: [dict get $dinfo cmdtype]"
}
set unavail [dict get $dinfo unavailable] ;#G-166 (the dict form always carries it)
set spec [punk::args::get_spec $docid]
set pstatus [dict get $dinfo parsestatus]
if {$opt_form ne "*"} {
@ -5632,6 +5718,11 @@ y" {return quirkykeyscript}
}
set result [string trimright $result \n]
}
if {$unavail ne ""} {
#G-166: mark the plain-text form too - it is the machine/LLM-facing
#surface, so the marking leads rather than trailing the usage block.
set result "UNAVAILABLE: [_cmdhelp_unavailable_marking $unavail]\n$result"
}
if {$opt_grepstr ne ""} {
if {[llength $opt_grepstr] == 1} {
set result [punk::ansi::grepstr --ignore-case -return all [lindex $opt_grepstr 0] $result]
@ -5686,9 +5777,14 @@ y" {return quirkykeyscript}
if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme]
}
return [dict create origin $rootorigin docid $rootdoc cmdtype $rootorigintype args_remaining $queryargs parsestatus $pstatus]
}
if {[dict get $pstatus ok]} {
return [dict create origin $rootorigin docid $rootdoc cmdtype $rootorigintype unavailable [dict get $testinfo unavailable] args_remaining $queryargs parsestatus $pstatus]
}
#G-166: an unavailable subject never renders as a cleanly usable
#command line - see the matching site in the main cmdhelp body.
set unavail [dict get $testinfo unavailable]
if {$unavail ne ""} {
set result [punk::args::arg_error [_cmdhelp_unavailable_marking $unavail] [punk::args::get_spec $rootdoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {[dict get $pstatus ok]} {
#show usage - with goodargs marked
if {!$scheme_received} {
dict set nextopts -scheme info
@ -5753,6 +5849,7 @@ y" {return quirkykeyscript}
set origindoc [dict get $cinfo docid]
set args_remaining [dict get $cinfo args_remaining]
set origintype [dict get $cinfo cmdtype]
set unavail [dict get $cinfo unavailable] ;#G-166
switch -- $origintype {
script {
@ -5800,9 +5897,21 @@ y" {return quirkykeyscript}
if {$scheme_received} {
dict set pstatus scheme [dict get $opts -scheme]
}
return [dict create origin $origin docid $origindoc cmdtype $origintype args_remaining $args_remaining parsestatus $pstatus]
}
if {[dict get $pstatus ok]} {
return [dict create origin $origin docid $origindoc cmdtype $origintype unavailable $unavail args_remaining $args_remaining parsestatus $pstatus]
}
#G-166: a recognised-but-unavailable subject is never presented as cleanly
#usable. The advisory parse can succeed against the subject's virtual id
#(the id documents the word, and an argument tail like 'string is dict 5'
#satisfies it) - so an ok parse would otherwise switch to the 'info' scheme
#and mark the words good, rendering exactly like a usable command line. The
#unavailability marking takes precedence over BOTH the ok-parse info render
#and any parse failure message: the subject not existing here is the more
#fundamental report, and it is the one a parse against a virtual id cannot
#make. The parse status is still passed through, so argument positions keep
#their marking under the (non-info) scheme.
if {$unavail ne ""} {
set result [punk::args::arg_error [_cmdhelp_unavailable_marking $unavail] [punk::args::get_spec $origindoc] {*}$nextopts -aserror 0 -parsestatus $pstatus]
} elseif {[dict get $pstatus ok]} {
#show usage - with goodargs marked
if {!$scheme_received} {
dict set nextopts -scheme info
@ -5821,7 +5930,7 @@ y" {return quirkykeyscript}
return $result
} else {
if {$opt_return eq "dict"} {
return [dict create origin $origin docid "" cmdtype $origintype args_remaining $args_remaining parsestatus {}]
return [dict create origin $origin docid "" cmdtype $origintype unavailable $unavail args_remaining $args_remaining parsestatus {}]
}
return "Undocumented command $origin. Type: $origintype"
}

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

@ -1,6 +1,7 @@
0.9.3
0.10.0
#First line must be a semantic version number
#all other lines are ignored.
#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.
#0.9.2 - documentation-only PUNKARGS update: cmdhelp's 'subcommand' argument help now states the multi-form narrowing contract explicitly (argument words resolving to exactly one form narrow the usage display to that form's argument table, marked in the synopsis; no words, an ambiguous match, or words no form accepts keep the whole-command render). No behaviour change - the G-041 advisory-parse candidacy already implements it; G-150 pins the contract in cmdhelp.test against the now selection-sound punk::auto_exec::hash declaration (auto_exec 0.1.1).
#0.9.1 - get_ns_dicts fix: packagetails/packageprefixes initialization moved inside the per-namespace loop - with multiple matched namespaces (path globs, trailing **) every dict after the first carried the union of all earlier namespaces' entries, mis-marking non-package children in nslist/n// underline styling and in the returned data; a child with several deeper packages was also appended to packageprefixes once per matching package (now once). Single-namespace queries (the nslist.test tier A pins) unaffected; the multi-namespace reset is pinned in nslist.test (nslist_classification_multins_package_buckets_reset). Documentation-only PUNKARGS added for get_ns_dicts (documents the returned per-namespace dict keys and -allbelow/-nspathcommands; parsing stays manual) and nslist_dict (caller-relative wrapper). Motivated by exposing the dict data layer to agent tooling: scriptlib/developer/nslist.tcl backing the new tcl-nslist agent skill.

4
src/tests/modules/AGENTS.md

File diff suppressed because one or more lines are too long

29
src/tests/modules/punk/args/testsuites/args/tclcoreparity.test

@ -210,6 +210,35 @@ namespace eval ::testspace {
}\
-result [list 1 1 1 1]
#added 2026-08-06 (agent, G-166) - a forward class's per-class virtual id LEADS its
#help with the unavailability statement on runtimes that lack the class. Before this,
#'i string is dict' on 8.6 presented the full class documentation and only mentioned
#the version boundary in the twelfth line of the static description. Live-derived:
#the affected arm runs on 8.6, the unchanged arm on 8.7/9.x.
test tclcoreparity_stringis_unavailable_virtualid_leads_with_note {the virtual id of a forward class this runtime lacks leads its help with the unavailability statement; where the class is live the id is generated unchanged} -constraints have_tclcoredocs -setup $common -body {
set classes [live_classes]
set raw [join [punk::args::raw_def "::tcl::string::is dict"]
]
set posnote [string first "NOT AVAILABLE in this Tcl" $raw]
set posdesc [string first "Any proper dict structure" $raw]
#the static description is the documentation source on every runtime
lappend result [expr {$posdesc >= 0}]
if {"dict" in $classes} {
#modern runtime: the class is live - no unavailability statement at all
lappend result no-note [expr {$posnote < 0}]
} else {
#the affected arm: the statement is present and PRECEDES the description
lappend result has-note [expr {$posnote >= 0 && $posnote < $posdesc}]
}
#the statement names the runtime and the rejected call shape (affected arm only)
if {"dict" ni $classes} {
lappend result [string match "*'string is dict' is*" $raw]
} else {
lappend result 1
}
} -cleanup {
} -result [list 1 [expr {"dict" in [live_classes] ? "no-note" : "has-note"}] 1 1]
#--- ::after cancel-id discrimination (user-directed 2026-07-13; probe record in
#--- goals/G-055) - the cancelid/info forms' id is typed by the id shape harvested
#--- from the running interpreter, so 'after cancel <non-id>' resolves to the

91
src/tests/modules/punk/ns/testsuites/ns/cmdflow.test

@ -110,6 +110,47 @@ namespace eval ::testspace {
@values -min 0 -max 0
}
#G-166 fixtures: a parent declaring a recognised-but-unavailable choice
#(-choiceunavailable, G-073) with per-choice space-form virtual docids, so the
#available and the unavailable word are addressed through the SAME exact-id branch
#of the walk - the two differ only on the availability axis.
proc flowunavail {args} {}
punk::args::define {
@id -id ::testspace::flowunavail
@cmd -name testspace::flowunavail -summary "parent unavail" -help "parent unavail"
@leaders -min 1 -max 1
subcmd -choices {alpha beta} -choiceunavailable {gamma} -choicelabels {
gamma "not provided by this build"
}
@values -min 0 -max 1
item -type string -optional 1
}
punk::args::define {
@id -id "::testspace::flowunavail alpha"
@cmd -name "testspace::flowunavail alpha" -summary "alpha class" -help "alpha class"
@values -min 0 -max 1
item -type string -optional 1
}
punk::args::define {
@id -id "::testspace::flowunavail gamma"
@cmd -name "testspace::flowunavail gamma" -summary "gamma class" -help "gamma class"
@values -min 0 -max 1
item -type string -optional 1
}
#as flowunavail but the unavailable name has NO space-form virtual docid - the walk's
#choice traverse is then the surface that meets it (G-166 attribution without addressing)
proc flowunavail_nodoc {args} {}
punk::args::define {
@id -id ::testspace::flowunavail_nodoc
@cmd -name testspace::flowunavail_nodoc -summary "parent unavail nodoc" -help "parent unavail nodoc"
@leaders -min 1 -max 1
subcmd -choices {alpha beta} -choiceunavailable {gamma} -choiceinfo {
alpha {{doctype punkargs} {subhelp ::testspace::flowsub_alpha}}
}
@values -min 0 -max 0
}
#undocumented command whose subcommand is documented via a space-delimited id
proc flowgap {args} {}
punk::args::define {
@ -261,6 +302,56 @@ namespace eval ::testspace {
}\
-result [list alpha alpha ::testspace::flowsub_alpha ::testspace::flowsub_alpha]
#--- recognised-but-unavailable landings (G-166) -------------------------------------------
#added 2026-08-06 (agent, G-166) - cmdinfo's availability axis and the walk's
#addressing decision for unavailable names
test cmdinfo_unavailable_key_present_and_empty {every cmdinfo result carries the availability key; it is empty for definitions with no -choiceunavailable} -setup $common -body {
set cinfo [punk::ns::cmdinfo ::testspace::flowcmd]
lappend result [dict exists $cinfo unavailable] [dict get $cinfo unavailable]
set cinfo [punk::ns::cmdinfo ::testspace::flowparent alpha]
lappend result [dict exists $cinfo unavailable] [dict get $cinfo unavailable]
} -cleanup {
} -result [list 1 {} 1 {}]
test cmdinfo_unavailable_exact_landing {an exact unavailable word resolves its virtual docid and is attributed; the available sibling resolves identically with an empty key and both stay cmdtype doconly} -setup $common -body {
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail gamma]
lappend result [dict get $cinfo docid] [dict get $cinfo cmdtype] [dict get $cinfo unavailable]
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail alpha]
lappend result [dict get $cinfo docid] [dict get $cinfo cmdtype] [dict get $cinfo unavailable]
} -cleanup {
} -result [list {::testspace::flowunavail gamma} doconly gamma {::testspace::flowunavail alpha} doconly {}]
#The G-166 decision on the G-073 follow-on: a unique prefix landing on an unavailable
#name RESOLVES its virtual docid (marked) rather than conservatively resolving nothing.
#Addressing and availability are separate axes; parse still rejects the same word.
test cmdinfo_unavailable_prefix_landing_resolves_marked {a unique prefix of an unavailable name resolves the same virtual docid with the same attribution, while parse rejects the word as choiceunavailable} -setup $common -body {
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail gam]
lappend result [dict get $cinfo docid] [dict get $cinfo unavailable]
#parse side: the word is rejected with the tailored unavailability class
set ps [punk::args::parse_status {gam} withid ::testspace::flowunavail]
lappend result [dict get $ps ok] [dict get $ps failureclass]
#a prefix of an AVAILABLE choice keeps resolving as before, unattributed
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail al]
lappend result [dict get $cinfo docid] [dict get $cinfo unavailable]
} -cleanup {
} -result [list {::testspace::flowunavail gamma} gamma 0 choiceunavailable {::testspace::flowunavail alpha} {}]
test cmdinfo_unavailable_choicetraverse_attribution {an unavailable name with no virtual docid resolves no documentation (the traverse's parse-parity claim holds) but is still attributed} -setup $common -body {
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail_nodoc gamma]
lappend result [dict get $cinfo docid] [dict get $cinfo args_remaining] [dict get $cinfo unavailable]
#an unknown word on the same fixture is not attributed
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail_nodoc zzz]
lappend result [dict get $cinfo docid] [dict get $cinfo args_remaining] [dict get $cinfo unavailable]
} -cleanup {
} -result [list ::testspace::flowunavail_nodoc gamma gamma ::testspace::flowunavail_nodoc zzz {}]
test cmdinfo_unavailable_survives_argument_tail {trailing argument words do not clear the attribution - the landing is still the unavailable subject} -setup $common -body {
set cinfo [punk::ns::cmdinfo ::testspace::flowunavail gamma extra]
lappend result [dict get $cinfo docid] [dict get $cinfo args_remaining] [dict get $cinfo unavailable]
} -cleanup {
} -result [list {::testspace::flowunavail gamma} extra gamma]
#--- cmdhelp smoke ------------------------------------------------------------------------
test cmdhelp_string_smoke {cmdhelp -return string renders usage for a documented proc and a subhelp subcommand}\

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

@ -205,6 +205,33 @@ namespace eval ::testspace {
str -type string
}
#G-166: the same model carrying a recognised-but-unavailable class (-choiceunavailable,
#G-073) with its own space-form virtual docid - 'gone' is documented and addressable
#but rejected by parse, exactly as tclcore's 'string is dict' is on Tcl 8.6
proc helpstrua {args} {}
punk::args::define {
@id -id ::testspace::helpstrua
@cmd -name testspace::helpstrua -summary "helpstrua parent" -help "helpstrua parent"
@leaders -min 1 -max 1
subcmd -choices {is compare} -choiceunavailable {gone} -choicelabels {
gone "not present in this build"
}
@values -min 0 -max -1
arg -optional 1 -multiple 1
}
punk::args::define {
@id -id "::testspace::helpstrua is"
@cmd -name "testspace::helpstrua is" -summary "helpstrua is summary" -help "helpstrua is"
@values -min 1 -max 1
str -type string
}
punk::args::define {
@id -id "::testspace::helpstrua gone"
@cmd -name "testspace::helpstrua gone" -summary "helpstrua gone summary" -help "helpstrua gone"
@values -min 1 -max 1
str -type string
}
#TclOO instance with one documented and one undocumented method (the
#punk::ansi::class::class_ansi rendertest/checksum situation)
oo::class create ::testspace::HelpClass {
@ -240,7 +267,7 @@ namespace eval ::testspace {
}\
-cleanup {
}\
-result [list {origin cmdtype args_resolved args_remaining docid stack} proc ::testspace::helpfix]
-result [list {origin cmdtype unavailable args_resolved args_remaining docid stack} proc ::testspace::helpfix]
#--- scheme selection --------------------------------------------------------------------
@ -410,7 +437,7 @@ namespace eval ::testspace {
}\
-cleanup {
}\
-result [list {origin docid cmdtype args_remaining parsestatus} ::testspace::helpfix proc 1 valid info ok ok 0 unparsed]
-result [list {origin docid cmdtype unavailable args_remaining parsestatus} ::testspace::helpfix proc 1 valid info ok ok 0 unparsed]
test cmdhelp_return_dict_invalid {an invalid argument set (choice violation) returns ok 0/invalid/error with the offending argument status bad}\
-setup $common -body {
@ -512,6 +539,85 @@ namespace eval ::testspace {
}\
-result [list 1 {::tcl::string::is true} {}]
#--- recognised-but-unavailable subjects (G-166) --------------------------------------------
#added 2026-08-06 (agent, G-166) - the marking that keeps an unavailable subject from
#reading like a usable command line, across all three return forms
test cmdhelp_unavailable_return_dict_carries_key {the dict return form carries the availability key for an unavailable subject and leaves it empty for an available sibling} -setup $common -body {
set d [punk::ns::cmdhelp -return dict ::testspace::helpstrua gone hello]
lappend result [dict get $d docid] [dict get $d unavailable]
set d [punk::ns::cmdhelp -return dict ::testspace::helpstrua is hello]
lappend result [dict get $d docid] [dict get $d unavailable]
} -cleanup {
} -result [list {::testspace::helpstrua gone} gone {::testspace::helpstrua is} {}]
test cmdhelp_unavailable_argument_tail_not_cleanly_usable {a valid argument tail against an unavailable subject renders the unavailability marking and NOT the info scheme, while the available sibling renders info-scheme-clean} -setup $common -body {
variable INFOBORDER
#the advisory parse of 'hello' SUCCEEDS against the virtual id - without the
#G-166 marking this rendered exactly like a usable command line
set ps [punk::args::parse_status {hello} withid "::testspace::helpstrua gone"]
lappend result [dict get $ps ok]
set out [punk::ns::cmdhelp ::testspace::helpstrua gone hello]
lappend result [has_sgr_with $out $INFOBORDER]
lappend result [string match {*is a recognised name here, but is not available*} [punk::ansi::ansistrip $out]]
#available control: same shape, clean info render, no marking
set out [punk::ns::cmdhelp ::testspace::helpstrua is hello]
lappend result [has_sgr_with $out $INFOBORDER]
lappend result [string match {*is a recognised name here, but is not available*} [punk::ansi::ansistrip $out]]
} -cleanup {
} -result [list 1 0 1 1 0]
test cmdhelp_unavailable_string_and_text_forms_marked {the string and text return forms both name the unavailability; the text form leads with it} -setup $common -body {
set out [punk::ansi::ansistrip [punk::ns::cmdhelp -return string ::testspace::helpstrua gone hello]]
lappend result [string match {*is a recognised name here, but is not available*} $out]
set out [punk::ns::cmdhelp -return text ::testspace::helpstrua gone hello]
lappend result [string match {UNAVAILABLE: 'gone' is a recognised name here*} $out]
#available control - no marking, and the text form still leads with COMMAND:
set out [punk::ns::cmdhelp -return text ::testspace::helpstrua is hello]
lappend result [string match {*UNAVAILABLE:*} $out] [string match {COMMAND:*} $out]
} -cleanup {
} -result [list 1 1 0 1]
#added 2026-08-06 (agent, G-166) - real-world arm: on a runtime where the tclcore model
#declares dict unavailable (Tcl 8.6) the two 'string is <class>' landings that were
#machine-indistinguishable are now told apart by the availability key alone - cmdtype
#stays doconly for both, so the command-kind axis is not overloaded. Live-derived, so
#the modern arm asserts the same shape with an empty key.
test cmdhelp_string_is_unavailable_class_attributed {'string is dict' and 'string is true' share cmdtype doconly and differ only on the availability key; the prefix landing agrees with the exact word} -constraints have_tclcoredocs -setup $common -body {
#live-derived: dict is unavailable exactly where this runtime lacks the class
set spec [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end]
set declared [expr {[dict exists $spec -choiceunavailable] ? [dict get $spec -choiceunavailable] : ""}]
set expect [expr {"dict" in $declared ? "dict" : ""}]
set cinfo [punk::ns::cmdinfo ::string is dict]
lappend result [dict get $cinfo cmdtype] [expr {[dict get $cinfo unavailable] eq $expect}]
set cinfo [punk::ns::cmdinfo ::string is true]
lappend result [dict get $cinfo cmdtype] [dict get $cinfo unavailable]
#decomposed invocation form (the ensemble walked word by word) agrees
set cinfo [punk::ns::cmdinfo ::tcl::string::is dict]
lappend result [expr {[dict get $cinfo unavailable] eq $expect}]
#'i string is dic' - the G-166 decision: the unique prefix resolves the same
#virtual docid, attributed (it previously resolved no documentation on 8.6)
set cinfo [punk::ns::cmdinfo ::string is dic]
lappend result [dict get $cinfo docid] [expr {[dict get $cinfo unavailable] eq $expect}]
} -cleanup {
} -result [list doconly 1 doconly {} 1 {::tcl::string::is dict} 1]
test cmdhelp_string_is_unavailable_class_argument_tail_marked {'i string is dict 5' does not render as a cleanly usable command line where dict is unavailable} -constraints have_tclcoredocs -setup $common -body {
variable INFOBORDER
set spec [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end]
set declared [expr {[dict exists $spec -choiceunavailable] ? [dict get $spec -choiceunavailable] : ""}]
set is_unavail [expr {"dict" in $declared}]
set out [punk::ns::cmdhelp ::string is dict 5]
set marked [string match {*is a recognised name here, but is not available*} [punk::ansi::ansistrip $out]]
#marked exactly where the class is unavailable; info scheme suppressed there
lappend result [expr {$marked == $is_unavail}]
lappend result [expr {$is_unavail ? ![has_sgr_with $out $INFOBORDER] : 1}]
#the always-available control is never marked
set out [punk::ns::cmdhelp ::string is true 5]
lappend result [string match {*is a recognised name here, but is not available*} [punk::ansi::ansistrip $out]]
} -cleanup {
} -result [list 1 1 0]
#--- TclOO methods (G-052) -----------------------------------------------------------------
test cmdhelp_oo_documented_method {a documented method on an instance resolves to its class-level docid and gets goodarg marking}\

Loading…
Cancel
Save