# G-051 cmdinfo truthful cmdtype for doc-only pseudo-commands and space-form docid prefix parity Status: achieved 2026-07-13 Scope: src/modules/punk/ns-999999.0a1.0.tm (cmdinfo, cmd_traverse), src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test, src/tests/modules/punk/ns/testsuites/ns/cmdflow.test Goal: cmdinfo reports a distinct cmdtype (e.g 'doconly') when resolution lands on a punk::args id with no corresponding real command instead of today's 'notfound', and the space-delimited docid jump accepts the same word prefixes the parser accepts - via the shared punk::args::choiceword_match resolver, not a second matching rule - so 'i string is tr' documents what 'string is tr' actually executes. Acceptance: the pinned GAP tests flip: cmdhelp_GAP_pseudo_command_cmdtype_notfound and cmdhelp_GAP_string_is_true_pseudo report the new cmdtype with the docid unchanged, cmdhelp_GAP_spaceform_docid_prefix_not_honoured and cmdhelp_GAP_string_is_prefix_not_honoured resolve the child docid from a prefix exactly when parse accepts that prefix (honouring -choiceprefix, -choiceprefixdenylist, -choiceprefixreservelist and -choicealiases per G-040 parity); consumers of cmdinfo's cmdtype (cmdhelp, synopsis, eg) handle the new value with no behaviour change for real commands; cmdflow.test and the non-GAP cmdhelp.test tests pass unchanged. ## Context Two related defects in the doc-lookup walk, characterized 2026-07-10: **Pseudo-command cmdtype.** A space-delimited punk::args id can document a "command" below the real implementation level - e.g. `::tcl::string::is true` documents a level below the `::tcl::string::is` proc (the class words are arguments, not subcommands). cmdinfo resolves such docids correctly but reports `cmdtype notfound` because cmdwhich finds no real command at the multiword path (cmdinfo's final cmdwhich is on the space-joined finalcommand). Consumers cannot distinguish "genuinely no such command" from "documentation-only level below a real command". Pins: cmdhelp_GAP_pseudo_command_cmdtype_notfound (fixture), cmdhelp_GAP_string_is_true_pseudo (real tclcore docs, constraint have_tclcoredocs). The documented-method oo case shows the same shape: `cmdinfo $obj docmeth` reports cmdtype notfound with docid " docmeth" (see cmdhelp_oo_documented_method - its docid assertion holds through this goal; whether the method case adopts the new cmdtype or a method-specific one is a design decision for this goal, coordinated with G-052). **Space-form docid prefix parity.** The jump to a space-delimited child docid (cmd_traverse: `punk::args::id_exists "$origin [lindex $args $i]"`) requires the exact word. The parser accepts a unique choice prefix for the same word (G-040 landed choiceword_match parity for the choiceinfo/subhelp path, but the space-form id_exists jump happens BEFORE choice matching and never sees prefixes). Real-world effect: Tcl's `string is tr 1` works, but 'i string is tr' stays at the parent docid. Pins: cmdhelp_GAP_spaceform_docid_prefix_not_honoured (fixture), cmdhelp_GAP_string_is_prefix_not_honoured (real tclcore docs). ## Approach - Introduce a distinct cmdtype value (candidate: `doconly`) returned by cmdinfo when the final resolution has an empty/notfound cmdwhich but a non-empty docid whose id maps below a real command. Audit consumers (cmdhelp, synopsis, eg, anything switching on cmdtype) - most treat docid as authoritative already, so the change should be additive. - For prefix parity: when the exact-word space-form lookup fails and the current docid's definition has a choices-bearing leader for the subcommand word, resolve the word via punk::args::choiceword_match FIRST (the shared resolver - honouring -choiceprefix, denylist, reservelist, aliases) and retry the space-form lookup with the resolved word. No second matching rule: if parse would reject the prefix, the walk must too (the G-040 parity contract, already pinned by cmdflow.test). ## Notes - The cmdinfo result shape (keys origin cmdtype args_resolved args_remaining docid stack) is pinned by cmdhelp_cmdinfo_result_shape - the new cmdtype is a value change on an existing key, not a shape change. - 'string is' has a second wrinkle out of scope here: its per-class ids are generated from the parent's -choicelabels at tclcore load time; prefix parity must come from the walk, not from generating extra prefix ids. ## Progress ### 2026-07-13 implemented (punk::ns 0.4.0, punk::lib 0.4.1) - acceptance complete - doconly cmdtype: cmdinfo classifies cmdwhich-notfound + non-empty docid as 'doconly' after the curried-alias retry. Design decision (anticipated in Context): the TclOO documented-method case (" docmeth" docids) ADOPTS doconly rather than a method-specific value - G-052 may refine when it takes up method-level autodef; cmdhelp_oo_documented_method's docid assertion holds unchanged. - Consumers audited: cmdhelp (docid-driven; its origintype switch only branches on 'script'; doconly implies docid non-empty so the undocumented fallback is unreachable for it), synopsis and eg (docid-driven), cmdtrace (tests only for 'proc'), punk::help topic fallback (uses cmdwhich whichtype, not cmdinfo), punk::lib tclscript analysis (doconly accepted alongside notfound at the subcommand-walk stop condition and the bucketing switch - identical behaviour preserved, the 'string is xdigit' comment there anticipated exactly this). - Space-form prefix parity: cmd_traverse's exact-word space-form jump gains a choiceword_match retry (current level's choices-bearing first leader; the shared G-040 resolver, no second matching rule) resolving the canonical word before the id_exists retest; resolvedargs records the canonical, mirroring parse normalization. - GAP flips (renamed): cmdhelp_pseudo_command_cmdtype_doconly, cmdhelp_spaceform_docid_prefix_honoured (extended with ambiguous/unknown-word stays-at-parent guard), cmdhelp_string_is_true_pseudo_doconly, cmdhelp_string_is_prefix_honoured. - Live verification (punk902z src, Tcl 9.0.2): 'i string is tr' resolves docid ::tcl::string::is true (doconly) and renders byte-identical to 'i string is true' except the failure banner echoing the typed words ("for string is tr" vs "for string is true" - the -caller echo of user input, considered correct); 'string is bool' -> boolean; ambiguous 'd' and unknown 'zz' stay at the parent docid exactly as parse rejects them. - Suites (tclsh 9.0.3): punk::ns 53/53 (cmdflow + non-GAP cmdhelp unchanged), punk::args 193 pass / 1 pre-existing skip, punk::lib 35/35; full source-tree suite 827 total, 813 pass, 13 skip, 1 fail = exec-14.3 only (known pre-existing core-test baseline) - zero regressions.