# G-051 cmdinfo truthful cmdtype for doc-only pseudo-commands and space-form docid prefix parity Status: proposed 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.