From f103b90c3a99b399e93a0ad181f1b2e595c06f5e Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 6 Aug 2026 18:29:58 +1000 Subject: [PATCH] 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 --- CHANGELOG.md | 18 ++ GOALS-archive.md | 4 + GOALS.md | 4 - goals/G-044-repl-command-completion.md | 9 + goals/G-050-synopsis-validity-marking.md | 9 + goals/G-055-tclcore-regen-workflow.md | 10 + goals/G-166-cmdinfo-unavailable-surfacing.md | 84 ------- ...167-punkargs-version-delta-availability.md | 22 +- .../G-166-cmdinfo-unavailable-surfacing.md | 188 ++++++++++++++++ punkproject.toml | 2 +- .../args/moduledoc/tclcore-999999.0a1.0.tm | 14 +- .../args/moduledoc/tclcore-buildversion.txt | 3 +- src/modules/punk/ns-999999.0a1.0.tm | 209 +++++++++++++----- src/modules/punk/ns-buildversion.txt | 3 +- src/tests/modules/AGENTS.md | 4 +- .../args/testsuites/args/tclcoreparity.test | 29 +++ .../punk/ns/testsuites/ns/cmdflow.test | 91 ++++++++ .../punk/ns/testsuites/ns/cmdhelp.test | 110 ++++++++- 18 files changed, 659 insertions(+), 154 deletions(-) delete mode 100644 goals/G-166-cmdinfo-unavailable-surfacing.md create mode 100644 goals/archive/G-166-cmdinfo-unavailable-surfacing.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f85e265..a6b81885 100644 --- a/CHANGELOG.md +++ b/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 ` 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 ` 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 diff --git a/GOALS-archive.md b/GOALS-archive.md index 688ac559..bc0b364e 100644 --- a/GOALS-archive.md +++ b/GOALS-archive.md @@ -20,6 +20,10 @@ Do not edit archived records here except to fix a broken path. Archived detail f ## Archived goals +### G-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 ' help %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.) diff --git a/GOALS.md b/GOALS.md index 0a74c1c3..ba052f30 100644 --- a/GOALS.md +++ b/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 diff --git a/goals/G-044-repl-command-completion.md b/goals/G-044-repl-command-completion.md index 3a24e9d6..91529f9c 100644 --- a/goals/G-044-repl-command-completion.md +++ b/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. diff --git a/goals/G-050-synopsis-validity-marking.md b/goals/G-050-synopsis-validity-marking.md index debc6c8e..03d04655 100644 --- a/goals/G-050-synopsis-validity-marking.md +++ b/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: "'' 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. diff --git a/goals/G-055-tclcore-regen-workflow.md b/goals/G-055-tclcore-regen-workflow.md index 2d9ab6b4..2b6937d9 100644 --- a/goals/G-055-tclcore-regen-workflow.md +++ b/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 diff --git a/goals/G-166-cmdinfo-unavailable-surfacing.md b/goals/G-166-cmdinfo-unavailable-surfacing.md deleted file mode 100644 index 76910ae7..00000000 --- a/goals/G-166-cmdinfo-unavailable-surfacing.md +++ /dev/null @@ -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. diff --git a/goals/G-167-punkargs-version-delta-availability.md b/goals/G-167-punkargs-version-delta-availability.md index d0075c89..d3a62882 100644 --- a/goals/G-167-punkargs-version-delta-availability.md +++ b/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 - "'' 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) - diff --git a/goals/archive/G-166-cmdinfo-unavailable-surfacing.md b/goals/archive/G-166-cmdinfo-unavailable-surfacing.md new file mode 100644 index 00000000..604c05a8 --- /dev/null +++ b/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 diff --git a/punkproject.toml b/punkproject.toml index f07b9e0b..4373ffc3 100644 --- a/punkproject.toml +++ b/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 - diff --git a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm b/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm index 204e7381..1dfbeb25 100644 --- a/src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm +++ b/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%} diff --git a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt b/src/modules/punk/args/moduledoc/tclcore-buildversion.txt index 784aa3ae..9f8cbfbd 100644 --- a/src/modules/punk/args/moduledoc/tclcore-buildversion.txt +++ b/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 (). 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. diff --git a/src/modules/punk/ns-999999.0a1.0.tm b/src/modules/punk/ns-999999.0a1.0.tm index c8e0973d..26818649 100644 --- a/src/modules/punk/ns-999999.0a1.0.tm +++ b/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 ] + # (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" } diff --git a/src/modules/punk/ns-buildversion.txt b/src/modules/punk/ns-buildversion.txt index 0ecac665..53329246 100644 --- a/src/modules/punk/ns-buildversion.txt +++ b/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. diff --git a/src/tests/modules/AGENTS.md b/src/tests/modules/AGENTS.md index bcc4dd7e..e11bfdca 100644 --- a/src/tests/modules/AGENTS.md +++ b/src/tests/modules/AGENTS.md @@ -43,10 +43,10 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/` - `commandstack/` — commandstack (cooperative command renaming) tests (`testsuites/commandstack/commandstack.test`, 2026-08-03 - characterisation suite + the G-160 hygiene-pass contract at commandstack 0.6.0 + the 0.7.0 convenience removal forms + the 0.7.1 reload-contract state guards (G-160 follow-ons, 2026-08-04); usage-driven from punk::packagepreference/packagetrace/packagesuppress/punk-auto_execok/punk::nav::fs-cd): record shape as a contract (token first/renamer second dict key order for the lsearch -index 1/-index 3 convention, trailing `did_rename` 0|1, `{implementation {} did_rename 0}` no-rename signal), COMMANDSTACKNEXT/COMMANDSTACKNEXT_ORIGINAL delegation + the `commandstack::next` helper (caller-context parity with the manual uplevel convention pinned), unique+monotonic per-(renamer,command) tokenids (same-renamer re-renames chain and are removable by exact token, third rename succeeds), multi-renamer stacking with removal in any order (bottom-removal re-linking), builtin renames (next_implementor `original`), remove_rename's three argument forms + unknown-renamer errors, the 0.7.0 convenience removal forms (pop_rename - command form pops topmost-for-renamer and returns the removed record, bare form searches live stacks with a multi-command ambiguity error; remove_renamer - all of a renamer's entries across live stacks with correct re-linking, Rename_stack-parked stacks skipped, removed records returned keyed by command; restore_original - whole-stack unwind to the original returning the record count, deliberately registering stack-evidenced renamers so it survives a known_renamers reset while the renamer-explicit forms keep the gate; all silent with debug off), the token->implementation map get_next_command resolves through (map/stack consistency pinned across rename/remove; parked stacks keep dispatching), channel discipline (silent full cycle with debug off; warnings only under debug), debug argument validation, -renamer misplacement errors, get_stack raw-key-first retrieval of Rename_stack-parked records + Rename_stack 1/0 returns, Delete_stack live-record guard (errors; empty/missing return 1), get_IMPLEMENTOR classification incl builtin-where-cmdtype-exists (dynamic expectation - undetermined on 8.6), lib::split_body round-trip, lib::splitx, show_stack fallback render, the reload contract (a module re-source preserves ALL state - stacks/token counters/token map/known_renamers/debug each info-exists guarded; delegation and exact-token removal keep working after reload), the help overview, and lazy punk::args registration of the PUNKARGS docs. Behavioural tests run in fresh child interps per test (module sourced by path relative to the test file; a ::puts shim captures module output for silence/warning assertions and keeps runner output clean); descriptions are single-line per the tcltestrun banner-parsing style guidance in src/tests/AGENTS.md (a hard contract until G-161 made the parser multi-line tolerant). Green on tclsh90 (9.0.3) and punk86 (8.6) - `punkcheck/` — punkcheck module tests (install, summarize_install_resultdict, installtrack) - `punk/ansi/` — punk::ansi tests (`testsuites/ansi/`): ansistrip/ansimerge, plus characterization of the ANSI-at-position mechanisms (`ansistring.test`: INDEX/INDEXCODE/INDEXCHAR/RANGE/INSERT grapheme indexing with SGR-prefix merging, INDEXCOLUMNS/COLUMNINDEX double-wide column mapping, trim/VIEW; extended 2026-08-05 by the G-151 pre-modification coverage survey - VIEW -lf 0/1/2 single-line-ization modes + always-on NUL + silent unknown-option tolerance (load-bearing for overtype's '-nul 1' sites) + C1/zero-width visuals, RANGE end-relative/clamping plus the merged-code-stack re-emission and trailing-code-drop semantics a truncate primitive must respect, COUNT-vs-length combining-accent divergence, NEW object basics, and the KNOWN-DEFICIENCY pin that VIEWCODES/VIEWSTYLE raise invalid-command in a plain punk::ansi interp (bare 'a+'/'a' interactive-alias dependency; pinned in a fresh child interp); plus the TRUNCATE primitive pins landed with G-151 (punk::ansi 0.2.0) - fits-unchanged byte identity incl styled fixtures, grapheme-exact capping with marker budget participation and marker-alone truncation, SGR-reset-before-marker on styled prefixes, CJK/combining-cluster grapheme safety, and strict option errors (deliberately unlike VIEW's pinned silent tolerance)), code splitting invariants (`ta.test`: detect/detectcode distinction, split_codes/split_codes_single/split_at_codes shapes and round-trip) and single-code/effective-state semantics (`codetype.test`: is_sgr_reset/has_sgr_leadingreset, has_any/all_effective, sgr_merge, sequence_type classify), grepstr characterization (`grepstr.test`: return modes incl summarydict (linemap pinned as always-present - the -help says -n-only, reconciliation deferred to the planned hygiene pass), exact highlight SGR wrapping, -n line numbering, invert + empty-highlight strip, -C context/breaks, capture groups, and the tab deficiency: warns once per call on stderr, single-pass tab line survives - the multi-pass mangling is pinned at consumer level in punk/ns corp.test), and untabify characterization (`untabify.test`: -stops int/list/terminal, -with spaces/unicode/custom-pair, multiline, errors, plus the EXPERIMENTAL -plastic elastic-tabstop mode deliberately pinned-as-interim and retained for possible repl editbuf use). Console queries (get_tabstops/get_size + punk::console::tabwidth) are mocked per the overtype renderline.test pattern - they emit live terminal queries that block/error headless. ANSI codes in these tests are literal escape strings so results are colour-state independent -- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms (incl the 2026-08-05 candidacy fencing/fall-through primitive pins ahead of G-168: -regexprefail on optional value slots is form-fatal with no re-landing, unknown-option/dangling-option/option-value-fence failures fall through to sibling forms as successful parses, and the documented per-word limit - a complete option+value+file line double-matches a fenced scriptfile form and a dash-tolerant stdin form), registered-namespace lazy definition loading (`docpackages.test`, G-169 pre-work: inert registration/scan-vs-load split, id_exists never triggers loading, real_id/usage lazy resolution incl tag-prefixed script-level id families, duplicate-definer last-loaded-wins characterization), the G-168 launch-definition model parity (`punkexemodel.test`, punkexe moduledoc 0.7.0, id loading updated for the G-169 handover - the script id resolves via app-punkscript-docs from src/lib (auto_path derived from the test file location) while the moduledoc carries the core-owned ids: script/tclsh selection matrices against the real (script)::punkexe ids with the real-side dispatch/app oracle recorded in goals/archive/G-168, the bare-'-e' viable-incomplete verdict, the ./-e fence message, -encoding fall-through statuses, and the sanctioned complete-'-encoding' multipleformmatches LIMIT pins; the formcheck.test punkexe GAPs flipped to discriminated/sanctioned/selection-sound pins in the same arc), the G-151 landing report (`parsereport.test`, punk::args 0.18.0 parse_report: the canonical flag-like-word-consumed-as-VALUE attribution row, parsed-result vs words+withid entry parity, machine dict shape with declaration-section row order and absent-optional row omission, received xN multiplicity for solos/-multiple opts, type-aware always-marked elision at the default width plus the caller > @cmd -reportvaluewidth > built-in width cascade and never-elided dict returns, VIEW-style single-line-ization of control-bearing values, aliased-optionset storage-key row bridging, bordered-table/tableobject renders, words-form failure errorcode parity with parse, and the registered two-form definition's own render), rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure) +- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms (incl the 2026-08-05 candidacy fencing/fall-through primitive pins ahead of G-168: -regexprefail on optional value slots is form-fatal with no re-landing, unknown-option/dangling-option/option-value-fence failures fall through to sibling forms as successful parses, and the documented per-word limit - a complete option+value+file line double-matches a fenced scriptfile form and a dash-tolerant stdin form), registered-namespace lazy definition loading (`docpackages.test`, G-169 pre-work: inert registration/scan-vs-load split, id_exists never triggers loading, real_id/usage lazy resolution incl tag-prefixed script-level id families, duplicate-definer last-loaded-wins characterization), the G-168 launch-definition model parity (`punkexemodel.test`, punkexe moduledoc 0.7.0, id loading updated for the G-169 handover - the script id resolves via app-punkscript-docs from src/lib (auto_path derived from the test file location) while the moduledoc carries the core-owned ids: script/tclsh selection matrices against the real (script)::punkexe ids with the real-side dispatch/app oracle recorded in goals/archive/G-168, the bare-'-e' viable-incomplete verdict, the ./-e fence message, -encoding fall-through statuses, and the sanctioned complete-'-encoding' multipleformmatches LIMIT pins; the formcheck.test punkexe GAPs flipped to discriminated/sanctioned/selection-sound pins in the same arc), the G-151 landing report (`parsereport.test`, punk::args 0.18.0 parse_report: the canonical flag-like-word-consumed-as-VALUE attribution row, parsed-result vs words+withid entry parity, machine dict shape with declaration-section row order and absent-optional row omission, received xN multiplicity for solos/-multiple opts, type-aware always-marked elision at the default width plus the caller > @cmd -reportvaluewidth > built-in width cascade and never-elided dict returns, VIEW-style single-line-ization of control-bearing values, aliased-optionset storage-key row bridging, bordered-table/tableobject renders, words-form failure errorcode parity with parse, and the registered two-form definition's own render), rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix; version-note labels appear when the class is live OR is a declared forward unavailable class - dict labels on every runtime since G-073, unicode keeps the live-only rule; the G-073 forward-class adoption invariants and the USER-SANCTIONED 'di' prefix-strictness exemption (the model is deliberately stricter than real 8.6, full words parity-true); and the G-166 pin that an unavailable class's per-class virtual id LEADS its help with the unavailability statement on runtimes lacking the class while modern runtimes generate it unchanged. All expectations are derived from the running interpreter, green on 8.6/8.7/9.0 through runtests.tcl on each - native Tcl 8.6 has been a supported runner interpreter since 2026-07-21) - `punk/nav/ns/` — punk::nav::ns tests (`testsuites/nav/navns.test`): the n/ n// n/// navigation state machine (ns/ transitions absolute/relative/glob-no-nav, failed-nav state preservation, quad-colon normalization, v-form content selection, ensemble annotation) and the ::punk::nav::ns::ns_current variable contract the repl/codethread/subshell seeding all consume; display content is covered in punk/ns nslist.test - `punk/repl/` — punk::repl tests (`testsuites/repl/`): opunk console backend integration (`consolebackends.test`) and repl current-namespace retention (`nscurrent.test`: real codethread via repl::init driven by synchronous runscript sends - inscope evaluation of ns_current, retention across submissions, n/-navigation retained, auto-create-with-notice for missing namespaces, the 2026-07-14 stray-namespace seeding fix pinned behaviourally plus a source-text guard on repl::start's inline template; the end-to-end piped subshell session is covered at shell level by shell/testsuites/punkexe/shellnavns.test - which found the first-subshell shared-code-interp asymmetry and the piped-inscope gap recorded there) -- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the flipped nslist_types_default pin - bare nslist without -types displays all member types since punk::ns 0.7.1 fixed the braced-literal -types default), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); remaining GAP pins for pseudo-command cmdtype + space-form docid prefixes (G-051, real `string is` pins behind the have_tclcoredocs constraint), TclOO undocumented-method fallback (G-052), and synopsis marking absence (G-050); plus the G-150 flag-led form-narrowing + selection-soundness pins against punk::auto_exec::hash - bare-parse selection contract (incl the dash-led-name noformmatch cost), no-word/unknown-flag/ambiguity whole-render fallbacks, and hash runtime-behaviour-unchanged), and name/path primitive characterization (`nsprimitives.test`: string pins for nsparts/nsprefix/nstail/nsjoin/nsjoinall incl weird colon-run (`:::`) splitting, the trailing-colon parse ambiguity (`::x:` + `y` joins to the same string as `::x` + `:y` and reparses leading-colon-greedy), and prefix/tail/join round-trip and its absolutizing exceptions (the original twin-divergence pins for nsparts1/nsprefix1/nsprefix_orig/nstail1/nstail_orig served as safe-deletion evidence and were removed with the twins in the punk::ns 0.7.0 hygiene pass - divergence record in this file's git history, commit 0c7168a1); plus nseval fq-requirement/create-on-eval/evaluator-proc caching, the native-vs-punk `p:::x` resolution divergence (native namespace eval reaches child `x`, nseval creates/reaches literal `:x`), nseval_ifexists no-create + error propagation on plain and genuinely weird namespaces, nsexists/nschildren/nstree_raw weird-ns and relative-resolution pins, globmatchns `*`/`**`/`?` semantics (incl `*` matching a single inner colon - a formerly stale 'should be fixed' comment above nsglob_as_re was corrected in the 0.7.0 hygiene pass), and nspath_to_absolute/nspath_here_absolute caller-resolution pins; the nsjoinall error-message wart pin ('nsjoin:' prefix) flipped when punk::ns 0.7.1 fixed it) +- `punk/ns/` — punk::ns tests (`testsuites/ns/`): cmdwhich/cmdinfo/cmd_traverse doc-lookup flow (`cmdflow.test`, G-040 parity; plus the G-166 availability axis on the flowunavail/flowunavail_nodoc fixtures - the `unavailable` cmdinfo key present on every result, exact and unique-prefix landings on a `-choiceunavailable` name resolving that name's virtual docid ATTRIBUTED rather than resolving nothing, the choice traverse attributing without addressing when no virtual docid exists, and cmdtype staying unchanged throughout since availability is a second axis, never a cmdtype value), n/ display machinery characterization (`nslist.test`: tier A get_ns_dicts classification buckets as the machine contract for display reworks - incl package tail/prefix derivation, alias edge cases, usageinfo scan-dependence; tier B per-element layout-agnostic marking - underline/underdouble/underdotted namespace package styles, command type tag colours, exported/imported markers, the punkargs doc icon; tier C REWORK-flagged pins of the current hardcoded 2-col/4-col layout and nspath subtables, to flip deliberately with the planned punk-tables/width-responsive rework; plus the flipped nslist_types_default pin - bare nslist without -types displays all member types since punk::ns 0.7.1 fixed the braced-literal -types default), corp proc-retrieval and syntax/untabify interplay (`corp.test`: name edge cases, -ranges/-n line handling, basic-highlight ansistrip equivalence, -untabify spaces/unicode tab-free output, the KNOWN-DEFICIENCY pin for default -untabify none on tabbed bodies - grepstr warns per pass and brace overlays mangle tabbed lines, deterministic under mocked console tabstops - and a ::tcl::CopyDirectory -untabify spaces smoke test; precursor coverage for the planned punk::ns hygiene pass), cmdtrace characterization (`cmdtrace.test`: -pause 0 non-interactive runs, linedict line-mark keys for flat and 2-word-form nested switches as correct-mark guards, and GAP pins for the upstream nested-switch mismark - core.tcl-lang.org tktview 5d5b1052280c976ea3d4, arm bodies whose split-list index lands on a literal switch-command word report container-relative lines; mark tests gated on have_tclcoredocs because cmdtrace's arm-offset correction parses against the ::switch argdoc; plus the fixed-canary asserting punk::lib::check::has_tclbug_nestedswitch_tracelines still reports the bug - a live behavioural probe, so a fixed Tcl release fails the canary first and triggers the documented flip workflow), cmdhelp usage-rendering integration (`cmdhelp.test`: scheme selection, goodarg/badarg marking incl type/allocation failures, goodchoice highlighting of supplied/default choice words, alias path, cmdinfo result shape, queried-command failure attribution, and `-return dict` parse-status returns (G-049 - its GAP pins flipped 2026-07-10); the flipped G-051 pins for pseudo-command cmdtype (`doconly`) + space-form docid prefixes (real `string is` pins behind the have_tclcoredocs constraint); the G-166 unavailability marking on the helpstrua fixture and on the real `string is dict` subject - the `-return dict` key, the table/string marking with the info scheme suppressed so a valid argument tail no longer renders as a cleanly usable command line, and the `-return text` leading `UNAVAILABLE:` line (the two deliberate key-list flips - `cmdhelp_cmdinfo_result_shape`, `cmdhelp_return_dict_valid` - landed with it); remaining GAP pins for TclOO undocumented-method fallback (G-052) and synopsis marking absence (G-050); plus the G-150 flag-led form-narrowing + selection-soundness pins against punk::auto_exec::hash - bare-parse selection contract (incl the dash-led-name noformmatch cost), no-word/unknown-flag/ambiguity whole-render fallbacks, and hash runtime-behaviour-unchanged), and name/path primitive characterization (`nsprimitives.test`: string pins for nsparts/nsprefix/nstail/nsjoin/nsjoinall incl weird colon-run (`:::`) splitting, the trailing-colon parse ambiguity (`::x:` + `y` joins to the same string as `::x` + `:y` and reparses leading-colon-greedy), and prefix/tail/join round-trip and its absolutizing exceptions (the original twin-divergence pins for nsparts1/nsprefix1/nsprefix_orig/nstail1/nstail_orig served as safe-deletion evidence and were removed with the twins in the punk::ns 0.7.0 hygiene pass - divergence record in this file's git history, commit 0c7168a1); plus nseval fq-requirement/create-on-eval/evaluator-proc caching, the native-vs-punk `p:::x` resolution divergence (native namespace eval reaches child `x`, nseval creates/reaches literal `:x`), nseval_ifexists no-create + error propagation on plain and genuinely weird namespaces, nsexists/nschildren/nstree_raw weird-ns and relative-resolution pins, globmatchns `*`/`**`/`?` semantics (incl `*` matching a single inner colon - a formerly stale 'should be fixed' comment above nsglob_as_re was corrected in the 0.7.0 hygiene pass), and nspath_to_absolute/nspath_here_absolute caller-resolution pins; the nsjoinall error-message wart pin ('nsjoin:' prefix) flipped when punk::ns 0.7.1 fixed it) - `punk/mix/` — punk::mix::cli tests (prune helpers, punkcheck virtual sources), punk::mix::commandset::repo fossil move/rename characterization tests (`testsuites/repo/`, FOSSIL_HOME-isolated; GAP-marked tests pin behaviour G-022 will change), punk::mix::commandset::loadedlib tests (`testsuites/loadedlib/libsearch.test`: 'dev lib.search' match semantics via -return list — wrap-glob default, =exact prefix, case rules, explicit globs, version aggregation — plus the loadedlib 0.2.0 contract: deep discovery by default (deep .tm modules found without -refresh, registration persists), -refresh = genuine re-scan (epoch incr + rediscovery picks up .tm files added to already-scanned dirs), and highlight working without the shell-global a+ alias; shared provisioned child interp sourcing the source-tree libunknown directly — see the file's ORDERING NOTE), and the MULTISHELL polyglot build machinery (`testsuites/scriptwrap/`, split 2026-07-19 per G-092 so no single file dominates the -jobs parallel floor - tests moved verbatim: `multishell.test` = scriptset wrap via the punk.multishell.cmd template with structure/LF-only pins plus platform-gated execution smoke (cmd.exe→powershell payload on windows, sh payload on unix or via the `wsllinux` capability constraint from `src/tests/testsupport/wslprobe.tcl` - staged to the WSL distro's native filesystem, G-059); `multishell_wrapverify.test` = checkfile 512-byte label validation of a fresh wrap; `multishell_wrapdeterminism.test` = byte-identical re-wrap pin; `runtimecmd_checkfile.test` = checkfile + LF contract of the committed bin/runtime.cmd; `runtimecmd_roundtrip.test` = the runtime scriptset round-trip byte-identity pin) - `punk/lib/` — punk::lib tests (`testsuites/lib/`): range/index/parse/compat/interp_sync utilities, G-058 static-baseline seeding (`staticseed.test`: interp_sync_package_paths/snapshot_package_paths propagate a simulated ::punkboot static baseline and seed `load {} ` ifneeded mappings; no-op without a baseline), and the repl command-completeness engine (`commandcomplete.test`: punk::lib::system::incomplete pending-opener stacks - the info-complete quoting quirk progression (`set x "{*}{"` standalone vs in-proc-body), single openers, tabs, escapes, incomplete<->info-complete parity property; pre-repl-refactor characterization, see goals/G-044 detail preserve-list) - `punk/packagepreference/` — punk::packagepreference tests (`testsuites/packagepreference/`): G-058 static-vs-bundled policy (`staticpolicy.test`: require of a baseline package triggers the index scan before resolution so a newer bundled copy wins, static beats older bundled, exact requires of bundled versions stay reachable, missing static mappings get seeded), and the install re-install guard (`installguard.test`, G-160: second install returns 0 silently via the record `renamer` key - the historical `rename` read threw; uninstall/re-install cycle gets the next unique tokenid; fresh child interps source the SOURCE-TREE commandstack + packagepreference by path so no bootsupport/kit snapshot can answer) diff --git a/src/tests/modules/punk/args/testsuites/args/tclcoreparity.test b/src/tests/modules/punk/args/testsuites/args/tclcoreparity.test index 7b73e40f..a22f0a42 100644 --- a/src/tests/modules/punk/args/testsuites/args/tclcoreparity.test +++ b/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 ' resolves to the diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test b/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test index 651f21b7..97fa4696 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdflow.test +++ b/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}\ diff --git a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test b/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test index 0b17780e..b555fa59 100644 --- a/src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test +++ b/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 ' 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}\