Browse Source

make.tcl help takes the subcommand's own command line (G-143 refinement)

User-directed reshape of the help-depth interface: 'make.tcl help
<subcommand> ?arg ...?' (and '<subcommand> ?arg ...? -help') now accepts
the subcommand's command line as typed and renders the usage most
specific to it - 'make.tcl help tool build -test 0 punkzip' shows the
tool build form via its leading action word (the same literal-leader
word punk::args form auto-selection keys on at dispatch). Words after
the action are tolerated and ignored; an option-first line falls back to
the whole subcommand's usage; single-form subjects (e.g 'help bake
punk91 -confirm 0') ignore the words entirely; an unknown action word on
a multi-form subject stays a pointed exit-1 usage error. The 0.40.0
numeric form-index acceptance ('help tool 2') is withdrawn as
unintuitive. Probe recorded: the repl 'i' (punk::ns::cmdhelp) tolerates
trailing argument words but does not form-narrow flat multi-form
commands - make.tcl help now exceeds it there. maketclhelp.test pins
updated (12 tests, all green + tool/colour suites); punkshell 0.40.1.

Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
77485434ef
  1. 15
      CHANGELOG.md
  2. 1
      goals/archive/G-143-maketcl-multiform-help.md
  3. 2
      punkproject.toml
  4. 2
      src/AGENTS.md
  5. 108
      src/make.tcl
  6. 57
      src/tests/shell/testsuites/punkexe/maketclhelp.test

15
CHANGELOG.md

@ -5,6 +5,21 @@ 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.40.1] - 2026-08-01
- make.tcl help refinement (G-143 follow-up, user-directed): 'make.tcl help
<subcommand> ?arg ...?' now accepts the subcommand's own command line and
renders the usage most specific to it - 'make.tcl help tool build -test 0
punkzip' (equivalently the same command line with -help appended) renders the
tool build form via its leading action word; words after the action are
tolerated and ignored, an option-first line falls back to the whole
subcommand's usage, and single-form subjects (e.g 'help bake punk91 -confirm
0') ignore the words entirely. The 0.40.0 numeric form-index acceptance
('help tool 2') is withdrawn as unintuitive - an unknown or numeric action
word remains a pointed exit-1 usage error. This exceeds the repl 'i' alias,
which tolerates trailing argument words but does not form-narrow on flat
multi-form commands.
## [0.40.0] - 2026-08-01
- make.tcl help depth (G-143): the tool and buildsuite subcommands are multi-form

1
goals/archive/G-143-maketcl-multiform-help.md

@ -29,4 +29,5 @@ G-030 (achieved 2026-07-14 - goals/archive/G-030-maketcl-punkargs.md) made make.
- 2026-08-01 (activation session): multi-form tool/buildsuite/help definitions (single-choice action leaders + -choicelabels, per-form opts/values), tool dispatch parse-through, help subject+action routing ('help <sub> <action>' and '<sub> <action> -help'), degraded-mode preservation, and the piped characterization coverage (new maketclhelp.test; maketcltool.test unknown-action pin updated to the exit-1 punk::args surface) landed in the working tree.
- 2026-08-01 (finalisation session): user-approved acceptance deviation recorded - the historic tool flag-anywhere argument order is NOT preserved: the declared positional model is options-before-names ('make.tcl tool build -test 0 <name> ...', matching bake's flags-before-names convention), with docs/agent guidance updated to match (src/AGENTS.md make.tcl bullet; gethelp + HELPTEXTS + tool-list footer + degraded usage line; flag-shaped tool names earn a stderr hint at exit 2). The PUNKBOOT_PLAIN degraded handler keeps the historic flag-anywhere manual parse and exit-2 surface. help action resolution additionally accepts a 0-based form index ('make.tcl help tool 2' renders byte-identically to 'help tool build') - the notation of the rendered ' i -form N ...' hints and of punk::args's own -form error text; out-of-range indexes exit 1 with the punk::args -form error. G-144 consumer follow-through applied: all 17 per-subcommand @form -synopsis overrides retired (automatic @cmd -name bracket-notation synopses; per-subcommand help output captured before/after and diffed - synopsis-line notation changes only, no information lost; check/projectversion/workflow render byte-identical); the top-level 'make.tcl ?subcommand? ?flags?' override is deliberately kept (states the bare-invocation and per-subcommand-flags reality the auto render cannot).
- 2026-08-01 (same-day post-flip refinement, user-directed): the '?action?' subject-plus-action-word help interface (and its numeric form-index variant) was reshaped to the user's intended contract - 'make.tcl help <subcommand> ?arg ...?' accepts the subcommand's own COMMAND LINE ('help tool build -test 0 punkzip'), with the leading action word narrowing multi-form subjects, later words tolerated/ignored, option-first lines falling back to whole-subcommand usage, and single-form subjects ignoring the words; the numeric form index was withdrawn as unintuitive (exit 1 like any unknown action word). Probe recorded: the repl 'i' (punk::ns::cmdhelp) tolerates trailing argument words but does NOT form-narrow flat multi-form commands (verified on punk::auto_exec::hash) - make.tcl help now exceeds it there. Shipped as punkshell 0.40.1; maketclhelp.test pins updated (12 tests).
- Verification at flip (2026-08-01, native tclsh90): runtests shell/testsuites/punkexe full subtree 114 tests / 110 pass / 4 env-gated skips / 0 fail, including maketclhelp.test 12/12 (per-action synopses, form tables incl. the form-index pin, exit-1 unknown action/flag errors, 'tool build -help' route, buildsuite -bogusxyz driver forwarding, PUNKBOOT_PLAIN degrade) and maketcltool.test 4/4. Kit-hosted punk-exe rendering shows raw (script) ids until the next routine vfscommonupdate+bake refreshes the kit's preloaded punk::args past 0.13.0 (documented in the test header; dispatch surfaces pinned kit-hosted in maketcltool.test are name-independent).

2
punkproject.toml

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

2
src/AGENTS.md

@ -69,7 +69,7 @@ Recovery after a wrong path guess:
- Provenance warnings (dirty-src gate + vendorupdate source-project check) print with a plain column-0 `PROVENANCE-WARNING:` token (grep for it in captured build output) and are recapped at the end of the run via make.tcl's wrapped `::exit`. Interactive terminal runs get a 3-second ctrl-c grace countdown before a dirty build proceeds; piped/agent runs skip it. `tclsh src/make.tcl check` reports the current src provenance status and what the build commands would do.
- Use `tclsh src/make.tcl vfscommonupdate` to rebuild `_vfscommon.vfs`. The REPLACE confirmation prompts on an interactive terminal; unattended/agent runs must pass `-confirm 0` (with the default `-confirm 1` and a piped/closed stdin, make.tcl aborts fast with guidance instead of reading stdin - do not pipe `y`).
- `tclsh src/make.tcl workflow` prints an embedded ASCII data-flow overview of the build/release workflow (release-ready command sequence, source→outputs folder flow, kit assembly, keyed notes). The text lives in `::punkboot::workflow_text` in `src/make.tcl` - embedded rather than a data file so it travels with the make.tcl copies seeded into generated projects. **Update contract (agents):** whenever build data flow changes - a make.tcl subcommand added/removed/repurposed, a source or output folder added or rerouted, a propagation target added, a gate (staleness/provenance/confirm) or deploy behaviour changed - update the workflow text in the same change-set. Keep it plain ASCII, max line width 100, and preserve the RELEASE SEQUENCE / DIAGRAM / KEY-NOTES / MAINTENANCE structure; verify by running `tclsh src/make.tcl workflow` (and once under `PUNKBOOT_PLAIN=1`). Adding/changing a subcommand also means: SUMMARIES + HELPTEXTS + its braced `punk::args::define` block (+ SUBGROUPS) in `::punkboot::argdoc`, `known_commands`, the plain `punkboot_gethelp` text, and a project-version bump (make.tcl interface is product surface per root AGENTS.md). Layout make.tcl copies pick the change up via the thin-layout sync step in any `make.tcl modules`/`libs`/`packages`/`bakehouse` run - do not hand-sync them.
- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?action?` or `<subcommand> ?action? -help` shows tabled usage; invalid arguments produce punk::args usage errors. tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and an action word - or the 0-based form index shown in the rendered ' i -form N ...' hints - narrows to that action's single-form usage; an unknown action is a pointed exit-1 usage error, never a plain-help fallback. tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or `<subcommand> ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and help accepts the subcommand's own COMMAND LINE after the subject: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) narrows to the build form's usage via the leading action word - the same literal-leader word punk::args form auto-selection keys on at dispatch. Words after the action are tolerated and ignored (help never validates them), an option-first line falls back to the whole subcommand's usage, single-form subjects ignore the words entirely, and an unknown action word on a multi-form subject is a pointed exit-1 usage error, never a plain-help fallback (numeric form indexes are deliberately not an interface). tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk/<layout>/src/{make.tcl,build.tcl}` + bootsupport manifest copies + the inert `gitignore.in` payload copies refreshed from the root `.gitignore` - G-012), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns.
- **Boot-precondition gate (G-125).** A bake refuses any kit whose merged `.vfs` supplies no tcl library: the kit is listed under `FAILED KITS` with a reason naming the cause, and NOTHING is written - no `src/_build/<kit>`, no deploy, and the previously deployed `bin/<kit>` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `init.tcl` in `tcl_library/`, `lib/tcl<major>.<minor>/` or `tcl<major>.<minor>/` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `<exe>/tcl8.6` (the androwish/undroidwish zipfs backport for 8.6 does this) - plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`.
- **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BUILD-WARNING`s and the kit still builds and deploys (unlike the G-125 gate). (a) **Binary-arch scan**, at the same post-merge seam as the gate: every binary library (`*.dll`/`*.so`/`*.dylib`) in the merged tree is classified by header bytes (`punkboot::utils::binary_arch_classify` - PE Machine field, ELF e_machine, Mach-O incl. universal; unclassifiable content is honestly `unknown` and never warned about) against the kit's target platform. Structural and non-executing, so it runs for cross-target kits too (measured ~30-140ms per kit, avg ~93ms across the 12 assembled trees). Libraries under a platform-discriminated subdir - canonical `<os>-<cpu>` platform-dir names, the universal `macosx` folder, or a recognised vendor spelling (`win-x64` etc; the lists are namespace variables in `punkboot::utils`) - are exempt: multi-arch payloads are legitimate there. First real sweep (2026-07-27) found a true latent instance: `zint.dll` 2.13.0 in `src/vfs/punk8win.vfs/lib_tcl8/` is 32-bit (i386) and can never load in the x64 tcl8 kits (punk86/punkbi/punksys) that carry it - those bakes warn until the payload is fixed. (b) **Smoke-require probe**: a kit declaring packages in `src/runtime/mapvfs.toml` (`smokerequire` key - see `src/runtime/AGENTS.md`) has each one plain-`package require`d INSIDE the freshly built artifact via its tclsh subcommand, which exits cleanly even on runtimes whose full repl teardown is fragile. This is the only check that observes actual package RESOLUTION - the punkluck86 2026-07-27 incident class (a higher-versioned wrong-arch Thread shadowing the runtime's working copy on plain require) is invisible to structural checks because the working file exists too. Failures are recapped naming kit, package and the actual error; cross-target kits skip with a stated reason; undeclared kits run nothing new; the probe only runs when the kit actually rebuilds. **What the checks do NOT guarantee:** statically linked packages, pure-tcl packages whose binary dependency lives elsewhere, and version-preference outcomes are visible only to the smoke probe - and only for the declared package set; the arch scan proves nothing about loadability beyond architecture, and an `unknown` classification is silence, not a pass. Both degrade to a NOTE when the bootsupport `punkboot::utils` snapshot is stale (same guarded require as the G-125 gate); `tclsh src/make.tcl check` reports the scan's ACTIVE/UNAVAILABLE state and the currently declared smoke-require kits.

108
src/make.tcl

@ -2618,10 +2618,10 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " test suites, or build (test-gated) and install to <projectdir>/bin; options precede the tool names" \n
append h " - zig is OPTIONAL: packages/bake never require this step; without a suitable toolchain the state is" \n
append h " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n
append h " $scriptname help ?subcommand? ?action?" \n
append h " $scriptname help ?subcommand? ?arg ...?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help);" \n
append h " with an action word or 0-based form index, that action's single-form usage when the tabled help" \n
append h " is available" \n \n
append h " extra words are the subcommand's own command line - a leading action word narrows the multi-form" \n
append h " subcommands (tool, buildsuite) to that action's usage when the tabled help is available" \n \n
append h " Flags:" \n
append h " -confirm 0|1" \n
append h " - interactive y/n confirmation policy (default 1: prompt when stdin is a terminal, abort fast when it is not)." \n
@ -3239,11 +3239,14 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
help
"
Show tabled usage for make.tcl as a whole, or for a single subcommand.
Equivalent to 'make.tcl <subcommand> -help'. For the multi-form
subcommands (tool, buildsuite) an action word - or the 0-based form
index the synopsis hints display - narrows to that action's
single-form usage: 'make.tcl help tool build', 'make.tcl help tool 2'
and 'make.tcl tool build -help' all render the build form."
After the subject, accepts that subcommand's own command line and
shows the usage most specific to it: for the multi-form subcommands
(tool, buildsuite) the leading action word narrows to that action's
single-form usage - 'make.tcl help tool build -test 0 punkzip' shows
the tool build form, as does appending -help to the command line
itself ('make.tcl tool build -test 0 punkzip -help'). Words after
the action are tolerated and ignored; an option-first line shows
the whole subcommand's usage."
buildsuite
"
Surface for the defined buildsuites under src/buildsuites (zig-built
@ -3678,13 +3681,15 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES help]}" -&
-help -&
{${[dict get $HELPTEXTS help]}}
@values -min 1 -max 2
@values -min 1 -max -1
subject -type string -optional 0 -&
-choices {${[dict keys $SUMMARIES]}} -&
-help "Subcommand to show usage for."
action -type string -optional 1 -help -&
"Action word (or 0-based form index) of a multi-form subcommand
(tool, buildsuite) - shows that action's single-form usage."
arg -type any -optional 1 -multiple 1 -help -&
"The subject's own arguments, as they would be typed. For a
multi-form subcommand (tool, buildsuite) a leading action word
narrows to that action's single-form usage; everything after
it is tolerated and ignored."
}
#top-level definition: the subcommand table rendered by 'make.tcl' / 'make.tcl -help'
variable SUBGROUPS {
@ -3705,11 +3710,13 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
'bin' and 'modules' folders at the same level as 'src'.
General usage: make.tcl <subcommand> ?flags?
'make.tcl help <subcommand> ?action?' or 'make.tcl <subcommand> ?action? -help'
shows a subcommand's own usage (a single action's usage for the
multi-form tool/buildsuite subcommands). Interactive y/n
confirmations can be driven non-interactively with the -confirm
flag declared on the relevant subcommands.}
'make.tcl help <subcommand> ?arg ...?' or 'make.tcl <subcommand>
?arg ...? -help' shows a subcommand's own usage - the words are the
subcommand's own command line, and for the multi-form
tool/buildsuite subcommands a leading action word narrows to that
action's usage. Interactive y/n confirmations can be driven
non-interactively with the -confirm flag declared on the relevant
subcommands.}
punk::args::define {
@id -id "(script)::punkboot"
@cmd -name "make.tcl" -&
@ -3754,7 +3761,7 @@ if {$::punkboot::punkargs_ok && [package provide punk::ansi] ne "" && [package p
set do_help 0
set help_subject ""
set help_action ""
set help_words [list]
set help_exitcode 0
#defaults for the option-derived variables (fallback scan and passthrough subcommands rely on these)
set ::punkboot::opt_forcekill 0
@ -3785,30 +3792,29 @@ if {$::punkboot::punkargs_ok} {
if {$subcommand eq "help"} {
set do_help 1
if {[llength $subargs]} {
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::help} argd]} {
#validate/resolve ONLY the subject word through the help definition
#(choices table + unambiguous prefixes). The tail is the subject's
#own command line as the user would type it - deliberately NOT
#parsed here: it may be any mix of that subcommand's actions,
#options and values, including incomplete or wrong ones, which is
#precisely when help is wanted (G-143 refinement 2026-08-01).
if {[catch {punk::args::parse [lrange $subargs 0 0] -errorstyle $::punkboot::errstyle withid (script)::punkboot::help} argd]} {
puts stderr $argd
exit 1
}
set help_subject [dict get $argd values subject]
if {[dict exists $argd values action]} {
#G-143: 'make.tcl help <subcommand> <action>' renders the action's
#single-form usage (resolved against the subject's forms below)
set help_action [dict get $argd values action]
}
set help_words [lrange $subargs 1 end]
}
} elseif {$wants_help} {
set do_help 1
set help_subject $subcommand
#G-143: '<subcommand> <action> -help' routes to the action's single-form
#usage when the subcommand is declared multi-form (tool, buildsuite) -
#the action candidate is the first non-flag word. Single-form subcommands
#keep the historic whole-subcommand render (trailing words ignored).
set firstword ""
#'<subcommand> ?arg ...? -help' - the same contract as
#'make.tcl help <subcommand> ?arg ...?': the remaining words are the
#subcommand's command line, with the help flag itself removed
#(wherever it appeared).
set help_words [list]
foreach w $subargs {
if {![string match -* $w]} {set firstword $w ; break}
}
if {$firstword ne "" && [llength [punk::args::forms (script)::punkboot::$subcommand]] > 1} {
set help_action $firstword
if {$w ni $::punkboot::help_flags} {lappend help_words $w}
}
} elseif {$subcommand eq "shell"} {
#declared passthrough: everything after 'shell' goes to the repl unparsed
@ -3955,29 +3961,33 @@ if {$do_help} {
set usage_id (script)::punkboot::$help_subject
}
set usage_args [list]
if {$help_action ne ""} {
#G-143: per-action single-form usage. An unknown action word is a pointed
#punk::args usage error (exit 1) - never a plain-help fallback. Form
#names resolve by unambiguous prefix, matching the dispatch convention;
#a 0-based form INDEX is also accepted - the notation punk::args itself
#advertises (the ' i -form N ...' synopsis hints and its -form error
#messages both speak indexes).
if {[llength $help_words]} {
#G-143 (refined 2026-08-01): help_words is the subject's own command line
#('make.tcl help tool build -test 0 punkzip'). For a multi-form subject
#the LEADING word selects that action's single-form usage by unambiguous
#prefix - the same literal-leader word punk::args form auto-selection
#keys on at dispatch. Words after it are the user's in-progress
#arguments: tolerated and ignored, never validated here. A flag-shaped
#leading word selects nothing (an option-first line cannot identify a
#form without option-schema knowledge) and the whole subcommand's usage
#renders. A non-flag leading word matching no action of a multi-form
#subject is a pointed punk::args usage error (exit 1) - the same
#reaction dispatch has to that command line - never a plain-help
#fallback. Single-form subjects ignore the words entirely.
set form_names [punk::args::forms $usage_id]
set fmatch [tcl::prefix::match -error "" $form_names $help_action]
if {$fmatch eq "" && [string is integer -strict $help_action]
&& $help_action >= 0 && $help_action < [llength $form_names]} {
set fmatch [lindex $form_names $help_action]
}
set w0 [lindex $help_words 0]
if {[llength $form_names] > 1 && ![string match -* $w0]} {
set fmatch [tcl::prefix::match -error "" $form_names $w0]
if {$fmatch eq ""} {
if {[catch {punk::args::usage -form $help_action $usage_id} usage_err]} {
if {[catch {punk::args::usage -form $w0 $usage_id} usage_err]} {
puts stderr $usage_err
} else {
puts stderr "make.tcl: no action '$help_action' for '$help_subject' (actions: $form_names)"
puts stderr "make.tcl: no action '$w0' for '$help_subject' (actions: $form_names)"
}
exit 1
}
set help_action $fmatch
lappend usage_args -form $help_action
lappend usage_args -form $fmatch
}
}
if {![catch {punk::args::usage {*}$usage_args $usage_id} usage_out]} {
puts stdout $usage_out

57
src/tests/shell/testsuites/punkexe/maketclhelp.test

@ -3,19 +3,25 @@ package require tcltest
#Piped characterization of make.tcl's help depth (goal G-143): the tool and
#buildsuite subcommands are declared multi-form (one @form per action with a
#single-choice action leader + -choicelabels), so full-subcommand help renders
#one synopsis line per action, 'make.tcl help <subcommand> <action>' (and
#'<subcommand> <action> -help') renders that action's single-form usage, tool
#dispatch parses through the definition (pointed punk::args usage errors,
#exit 1, for unknown actions and option-position unknown flags), and buildsuite
#keeps its passthrough driver-args contract (forms serve help/synopsis only).
#one synopsis line per action, 'make.tcl help <subcommand> ?arg ...?' (and
#'<subcommand> ?arg ...? -help') accepts the subcommand's own command line and
#renders the usage most specific to it (the leading action word narrows to that
#action's single form), tool dispatch parses through the definition (pointed
#punk::args usage errors, exit 1, for unknown actions and option-position
#unknown flags), and buildsuite keeps its passthrough driver-args contract
#(forms serve help/synopsis only).
#Pins:
# - 'help tool' / 'help buildsuite': per-action synopsis lines with i -form hints
# and the action -choicelabels in the choices area; ESC-free (G-113 piped policy)
# - 'help tool build' / 'help buildsuite build': the form's own argument table
# (-test opt + toolname; suitename + driverarg)
# - 'help tool 2' (0-based form index - the notation of the rendered ' i -form N '
# hints and of punk::args's own -form error text): same render as 'help tool
# build'; an out-of-range index exits 1 with the punk::args -form error
# - 'help <subject> <cmdline...>': help accepts the subject's own command line -
# 'help tool build -test 0 punkzip' renders the build form (the leading action
# word selects; later words are tolerated and ignored), 'help bake punk91
# -confirm 0' renders bake's whole usage (single-form subjects ignore the
# words), an option-first line ('help tool -test 0 build') falls back to the
# whole subcommand's usage, and a numeric form index is deliberately NOT an
# interface ('help tool 2' exits 1 like any unknown action word)
# - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with a pointed
# punk::args -form error naming the allowed forms
# - 'tool build -help': the <subcommand> <action> -help route renders the form
@ -161,21 +167,30 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 0 esc 0 testopt 1 toolname 1 choicelabel 1}
#added 2026-08-01 (agent, G-143) - numeric parity with the ' i -form N ...' synopsis
#hints and punk::args's own -form error notation ('Allowed values 0-N')
test maketcl_help_form_index {'make.tcl help tool 2' (0-based form index) renders exactly the build form; out-of-range exits 1 with the punk::args -form error} -constraints {tclshavailable} -body {
set r [maketcl_run {help tool 2}]
#added 2026-08-01 (agent, G-143 refinement) - help accepts the subject's own
#command line: the leading action word narrows (the same literal-leader word
#punk::args form auto-selection keys on at dispatch), later words are tolerated,
#option-first lines fall back to the whole subcommand's usage, single-form
#subjects ignore the words, and a numeric form index is deliberately NOT an
#interface (exits 1 like any unknown action word)
test maketcl_help_cmdline_selection {'make.tcl help tool build -test 0 punkzip' renders exactly the build form; trailing words tolerated; option-first and single-form lines render whole usage; numeric index rejected} -constraints {tclshavailable} -body {
set r [maketcl_run {help tool build -test 0 punkzip}]
set rword [maketcl_run {help tool build}]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result sameasbuildword [expr {[dict get $r output] eq [dict get $rword output]}]
set r2 [maketcl_run {help tool 9}]
set r2 [maketcl_run {help bake punk91 -confirm 0}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result formerr2 [regexp {invalid value for option -form} $out2]
lappend result range2 [regexp {Allowed values 0-3} $out2]
lappend result bakeusage2 [regexp {make\.tcl bake \[-k\]} $out2]
set r3 [maketcl_run {help tool -test 0 build}]
set out3 [dict get $r3 output]
lappend result timedout3 [dict get $r3 timedout] exitcode3 [dict get $r3 exitcode]
lappend result allforms3 [expr {[regexp {make\.tcl tool list } $out3] && [regexp {make\.tcl tool test } $out3]}]
set r4 [maketcl_run {help tool 2}]
lappend result timedout4 [dict get $r4 timedout] exitcode4 [dict get $r4 exitcode]
set result
} -result {timedout 0 exitcode 0 sameasbuildword 1 timedout2 0 exitcode2 1 formerr2 1 range2 1}
} -result {timedout 0 exitcode 0 sameasbuildword 1 timedout2 0 exitcode2 0 bakeusage2 1 timedout3 0 exitcode3 0 allforms3 1 timedout4 0 exitcode4 1}
#added 2026-08-01 (agent, G-143)
test maketcl_help_buildsuite_multiform {piped 'make.tcl help buildsuite': exit 0, one synopsis line per action} -constraints {tclshavailable} -body {
@ -219,16 +234,20 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 1 formerr 1 named 1 allowed 1 timedout2 0 exitcode2 1 formerr2 1 allowed2 1}
#added 2026-08-01 (agent, G-143)
test maketcl_tool_subaction_help_route {'make.tcl tool build -help' renders the build form's usage (the <subcommand> <action> -help route)} -constraints {tclshavailable} -body {
#added 2026-08-01 (agent, G-143); extended 2026-08-01 (agent) - appending -help to
#a full command line ('tool build -test 0 punkzip -help') routes identically
test maketcl_tool_subaction_help_route {'make.tcl tool build -help' and 'tool build -test 0 punkzip -help' render the build form's usage (the <subcommand> ?arg ...? -help route)} -constraints {tclshavailable} -body {
set r [maketcl_run {tool build -help}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result synbuild [regexp {make\.tcl tool build \[-test <bool>\] \[toolname\]\.\.\.} $out]
lappend result testopt [regexp {\?-test\?} $out]
set r2 [maketcl_run {tool build -test 0 punkzip -help}]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result samefull [expr {[dict get $r2 output] eq $out}]
set result
} -result {timedout 0 exitcode 0 synbuild 1 testopt 1}
} -result {timedout 0 exitcode 0 synbuild 1 testopt 1 timedout2 0 exitcode2 0 samefull 1}
#added 2026-08-01 (agent, G-143)
test maketcl_help_toplevel_unchanged {bare 'make.tcl help' still renders the top-level subcommand table} -constraints {tclshavailable} -body {

Loading…
Cancel
Save