Browse Source

make.tcl help: pure dry-run + received-args report (G-143 follow-up)

User-directed: the bare-action carve-out is removed - 'make.tcl help
buildsuite build' now falls through to the punk::args diagnosis like any
other incomplete line (exit 1). Empirically the error table carries the
form's argument rows alongside the all-forms synopsis and per-form
reasons, so the diagnosis IS the form documentation and the carve-out
bought nothing but an exit code. Accepted lines gain a one-line
received-args report after the usage table, built from the parse
result's 'received' key (defaulted opts omitted):
'dry-run: line accepted (form build) - action = build | -test = 0 |
toolname = punkzip'. This makes punk::args' value-swallow visible
('kitname = punk91 -confirm 0' for the out-of-order line) - an interim
make.tcl-side clue until punk::args grows an annotated success render.
bake/bakelist kitname -choicerestricted 0 confirmed deliberate
(discoverability-only choices; handler validation authoritative;
define-time mapvfs parse best-effort). maketclhelp.test pins updated;
help/tool/colour suites 20/20. punkshell 0.40.3.

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

13
CHANGELOG.md

@ -5,6 +5,19 @@ 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.3] - 2026-08-02
- make.tcl help dry-run purified (G-143 follow-up, user-directed): the 0.40.2
bare-action carve-out is removed - 'make.tcl help buildsuite build' now falls
through to the punk::args diagnosis like any other incomplete line (exit 1;
the error table carries the form's argument rows and names the missing
suitename, so the error IS the form documentation). Accepted lines gain a
one-line received-args report after the usage table ('dry-run: line accepted
(form build) - action = build | -test = 0 | toolname = punkzip'), which makes
punk::args' value-swallow visible ('kitname = punk91 -confirm 0') - an
interim make.tcl-side clue until punk::args grows an annotated success
render.
## [0.40.2] - 2026-08-01
- make.tcl help is now a dry-run of the supplied command line (G-143 follow-up,

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

@ -31,4 +31,5 @@ G-030 (achieved 2026-07-14 - goals/archive/G-030-maketcl-punkargs.md) made make.
- 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). Superseded same day by 0.40.2 (below).
- 2026-08-01 (second same-day refinement, user-directed): help is now a full DRY-RUN - the words parse through the subject's declaration via punk::args form auto-selection exactly as dispatch would parse them; accepted lines render the matched form's usage, rejected lines emit the subcommand's own punk::args noformmatch diagnosis (all-forms synopsis + per-form reasons, stderr, exit 1), with one carve-out: a BARE action word/prefix renders its form directly since forms with required values ('buildsuite build' needs a suitename) would fail a strict dry-run - the 0.40.1 leading-word-only selection and its option-first whole-usage fallback are superseded (the parse error table is itself usage documentation, and help verdicts mirror dispatch verdicts exactly). Empirical punk::args facts recorded from the probes: flag-like words at/after the first value position are consumed as VALUES (so 'bake punk91 -confirm 0' and 'tool build -test' PARSE, swallowing the flag-like word - only leader-level mistakes and option-position unknowns fail); per-arg -regexprepass/-regexprefail ARE honoured during form matching, so '-regexprefail {^-}' on a plain string value makes a flag-led line select the flag form uniquely (verified: without it 'Ambiguous arguments ... match more than one form'; punk::auto_exec::hash exhibits exactly this on bare parse withid, its handler pre-dispatching around it) - candidate fixes for auto_exec/cmdhelp narrowing noted to the user, not applied here.
- 2026-08-02 (third refinement, user-directed - dry-run purified, 0.40.3): the bare-action carve-out removed - 'help buildsuite build' falls through to the punk::args diagnosis like any incomplete line (exit 1; empirically the error table carries the form's argument rows, so it IS the form documentation - the 0.40.2 test failure that motivated the carve-out had all content pins passing, only the exit code differed). Accepted lines gained a one-line received-args report after the table ('dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip'), built from the parse result's 'received' key (defaulted opts omitted), which makes the value-swallow visible ('kitname = punk91 -confirm 0') - an interim make.tcl-side clue until punk::args grows an annotated success render (candidate enhancement: success-side equivalent of the arg_error received-args highlighting). bake/bakelist kitname choices confirmed deliberate as -choicerestricted 0 (in-tree comment: discoverability-only; handler validation authoritative; define-time mapvfs parse is best-effort in a catch and may yield empty choices).
- 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.2"
version = "0.40.3"
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? ?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 and DRY-RUNS it through that subcommand's declaration: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. A BARE action word (or unambiguous prefix) renders that form's usage directly, even where the real command requires further values (`help buildsuite build`). Note punk::args' positional model: flag-like words at/after the first value position parse as VALUES, so such lines are accepted and render usage cleanly (declaration-level passthroughs - shell args, buildsuite driver args - always parse clean); 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.
- 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 and DRY-RUNS it through that subcommand's declaration: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. There is deliberately no bare-action carve-out: an action word alone is dry-run too, so a form with required values reports them via the diagnosis (`help buildsuite build` exits 1 naming the missing suitename - the error table carries the form's argument rows, so it IS the form documentation). An accepted line is confirmed with a one-line received-args report after the table (`dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip`); punk::args' positional model consumes flag-like words at/after the first value position as VALUES, and the report makes that visible (`kitname = punk91 -confirm 0`). Declaration-level passthroughs (shell args, buildsuite driver args) always parse clean; 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.

81
src/make.tcl

@ -3246,10 +3246,13 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
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') - while a line it would reject shows the
same usage error the subcommand itself would produce (synopsis plus
per-form reasons, exit 1). A bare action word always renders that
action's usage, even where the real command needs more arguments
('make.tcl help buildsuite build')."
same usage error the subcommand itself would produce (the error
table carries the forms' synopsis, argument rows and per-form
reasons; exit 1). An accepted line is confirmed with a one-line
report of where each word landed ('kitname = punk91 -confirm 0'
reveals flag-like words consumed as values); an action word alone
is dry-run too, so a form needing more arguments reports them via
the usage error ('make.tcl help buildsuite build')."
buildsuite
"
Surface for the defined buildsuites under src/buildsuites (zig-built
@ -3964,43 +3967,61 @@ if {$do_help} {
set usage_id (script)::punkboot::$help_subject
}
set usage_args [list]
set accepted_note ""
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') and help is a DRY-RUN of
#it: the words parse through the subject's declaration exactly as
#('make.tcl help tool build -test 0 punkzip') and help is a pure DRY-RUN
#of it: the words parse through the subject's declaration exactly as
#dispatch would parse them (punk::args form auto-selection - literal
#action leaders, choice prefixes). A line the subcommand would accept
#renders the matched form's single-form usage (whole usage for
#single-form subjects); a line it would reject gets the same pointed
#punk::args diagnosis dispatch would give - synopsis plus per-form
#reasons - on stderr with exit 1, never a plain-help fallback. Note
#punk::args' positional model: flag-like words at or after the first
#value position are consumed as values, so such lines still render
#usage cleanly (and declaration-level passthroughs - shell args,
#buildsuite driver args - parse clean by design).
#One carve-out: a BARE action word (or unambiguous prefix) asks about
#that form rather than claiming a runnable line - forms with required
#values ('buildsuite build' needs a suitename) would fail a strict
#dry-run, so a single form-matching word renders the form directly.
set form_names [punk::args::forms $usage_id]
set fmatch ""
if {[llength $help_words] == 1 && [llength $form_names] > 1} {
set fmatch [tcl::prefix::match -error "" $form_names [lindex $help_words 0]]
#single-form subjects) plus a one-line report of where each received
#word landed; a line it would reject gets the same pointed punk::args
#diagnosis dispatch would give on stderr with exit 1 - the error table
#carries the all-forms synopsis, per-form reasons and argument rows,
#so it IS the form documentation. Deliberately no bare-action
#carve-out: 'help buildsuite build' shows the build form via the
#diagnosis that a suitename is still required. Note punk::args'
#positional model: flag-like words at or after the first value
#position are consumed as VALUES - the accepted-line report makes
#that visible ('kitname = punk91 -confirm 0'); declaration-level
#passthroughs (shell args, buildsuite driver args) parse clean by
#design.
if {[catch {punk::args::parse $help_words -errorstyle $::punkboot::errstyle withid $usage_id} argd]} {
puts stderr $argd
exit 1
}
if {$fmatch ne ""} {
lappend usage_args -form $fmatch
} else {
if {[catch {punk::args::parse $help_words -errorstyle $::punkboot::errstyle withid $usage_id} argd]} {
puts stderr $argd
exit 1
}
if {[llength $form_names] > 1} {
lappend usage_args -form [dict get $argd form]
set form_names [punk::args::forms $usage_id]
if {[llength $form_names] > 1} {
lappend usage_args -form [dict get $argd form]
}
#interim 'where did my words land' clue (punk::args has no annotated
#success render yet): name = value pairs for RECEIVED args only
#(defaulted opts omitted), so a swallowed flag-like word shows up
#against the argument that consumed it.
set recnames [list]
foreach {rn ri} [dict get $argd received] {
if {$rn ni $recnames} {lappend recnames $rn}
}
set parts [list]
foreach section {leaders opts values} {
foreach {an av} [dict get $argd $section] {
if {$an in $recnames} {lappend parts "$an = $av"}
}
}
set accepted_note "dry-run: line accepted"
if {[llength $form_names] > 1} {
append accepted_note " (form [dict get $argd form])"
}
if {[llength $parts]} {
append accepted_note " - [join $parts { | }]"
}
}
if {![catch {punk::args::usage {*}$usage_args $usage_id} usage_out]} {
puts stdout $usage_out
if {$accepted_note ne ""} {
puts stdout $accepted_note
}
set availability_note [::punkboot::punkboot_availability_note]
if {$availability_note ne ""} {
puts stdout $availability_note

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

@ -14,8 +14,9 @@ package require tcltest
#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 build': the build form's own argument table (-test opt + toolname);
# 'help buildsuite build': suitename-required diagnosis carrying the form's rows
# (pure dry-run - see below)
# - 'help <subject> <cmdline...>': help DRY-RUNS the subject's own command line
# through its declaration - 'help tool build -test 0 punkzip' renders the build
# form (punk::args form auto-selection; flag-like words at/after the first
@ -24,9 +25,12 @@ package require tcltest
# while a line the subcommand would reject ('help tool -test 0 build',
# 'help tool 2') exits 1 with the same punk::args noformmatch diagnosis
# dispatch gives (its table carries the all-forms synopsis, so the error is
# itself usage documentation); carve-out: a BARE action word renders its form
# directly even where the real command needs more values ('help buildsuite
# build' - the build form requires a suitename a strict dry-run would demand)
# itself usage documentation); no bare-action carve-out: 'help buildsuite
# build' (suitename required) exits 1 with that same diagnosis, whose table
# still carries the build form's argument rows; accepted lines are confirmed
# with a received-args report ('dry-run: line accepted (form build) - action =
# build | ...'), which also reveals flag-like words consumed as values
# ('kitname = punk91 -confirm 0')
# - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with the
# punk::args noformmatch diagnosis naming the candidate forms
# - 'tool build -help': the <subcommand> <action> -help route renders the form
@ -172,23 +176,28 @@ 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 refinement) - help dry-runs the subject's own
#command line through its declaration: accepted lines render the matched form's
#usage (flag-like words at/after the first value position parse as VALUES per
#the punk::args positional model, so 'bake punk91 -confirm 0' is accepted);
#rejected lines (option-first, numeric form index, unknown flags in option
#position) exit 1 with the noformmatch diagnosis, whose table carries the
#all-forms synopsis - the error is itself usage documentation
test maketcl_help_cmdline_selection {'make.tcl help tool build -test 0 punkzip' renders exactly the build form; accepted single-form lines render whole usage; rejected lines exit 1 with the self-documenting noformmatch diagnosis} -constraints {tclshavailable} -body {
#added 2026-08-01 (agent, G-143 refinement); updated 2026-08-02 (agent) - help
#dry-runs the subject's own command line through its declaration: accepted
#lines render the matched form's usage plus a received-args report (flag-like
#words at/after the first value position parse as VALUES per the punk::args
#positional model, so 'bake punk91 -confirm 0' is accepted and the report
#reveals the swallow); rejected lines (option-first, numeric form index,
#unknown flags in option position) exit 1 with the noformmatch diagnosis,
#whose table carries the all-forms synopsis - the error is itself usage
#documentation
test maketcl_help_cmdline_selection {'make.tcl help tool build -test 0 punkzip' renders the build form + received-args report; accepted single-form lines render whole usage with the value-swallow visible; rejected lines exit 1 with the self-documenting noformmatch diagnosis} -constraints {tclshavailable} -body {
set r [maketcl_run {help tool build -test 0 punkzip}]
set rword [maketcl_run {help tool build}]
set out [dict get $r output]
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]}]
lappend result synbuild [regexp {make\.tcl tool build \[-test <bool>\] \[toolname\]\.\.\.} $out]
lappend result accepted [regexp {dry-run: line accepted \(form build\)} $out]
lappend result landed [regexp {toolname = punkzip} $out]
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 bakeusage2 [regexp {make\.tcl bake \[-k\]} $out2]
lappend result swallow2 [regexp {kitname = punk91 -confirm 0} $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]
@ -199,7 +208,7 @@ namespace eval ::testspace {
lappend result timedout4 [dict get $r4 timedout] exitcode4 [dict get $r4 exitcode]
lappend result noform4 [regexp {No form of the command matches the supplied arguments} $out4]
set result
} -result {timedout 0 exitcode 0 sameasbuildword 1 timedout2 0 exitcode2 0 bakeusage2 1 timedout3 0 exitcode3 1 noform3 1 synindiag3 1 timedout4 0 exitcode4 1 noform4 1}
} -result {timedout 0 exitcode 0 synbuild 1 accepted 1 landed 1 timedout2 0 exitcode2 0 bakeusage2 1 swallow2 1 timedout3 0 exitcode3 1 noform3 1 synindiag3 1 timedout4 0 exitcode4 1 noform4 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 {
@ -214,8 +223,10 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 0 esc 0 synlist 1 syninfo 1 synbuild 1}
#added 2026-08-01 (agent, G-143)
test maketcl_help_buildsuite_build_form {'make.tcl help buildsuite build' renders the build form's argument table (suitename + driverarg)} -constraints {tclshavailable} -body {
#added 2026-08-01 (agent, G-143); updated 2026-08-02 (agent) - pure dry-run: the
#build form requires a suitename, so the bare action word yields the punk::args
#diagnosis (exit 1) whose table still carries the form's argument rows
test maketcl_help_buildsuite_build_form {'make.tcl help buildsuite build' (incomplete - suitename required) exits 1 with the diagnosis carrying the build form's argument rows} -constraints {tclshavailable} -body {
set r [maketcl_run {help buildsuite build}]
set out [dict get $r output]
set result [list]
@ -224,7 +235,7 @@ namespace eval ::testspace {
lappend result driverarg [regexp {\?driverarg\.\.\.\?} $out]
lappend result fwdnote [regexp {Driver arguments forwarded to suite\.tcl untouched} $out]
set result
} -result {timedout 0 exitcode 0 suitename 1 driverarg 1 fwdnote 1}
} -result {timedout 0 exitcode 1 suitename 1 driverarg 1 fwdnote 1}
#added 2026-08-01 (agent, G-143); updated 2026-08-01 (agent) - help now dry-runs
#the line through the declaration, so the error is the same punk::args
@ -244,8 +255,10 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 1 noform 1 flist 1 fbuild 1 timedout2 0 exitcode2 1 noform2 1}
#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
#added 2026-08-01 (agent, G-143); extended 2026-08-01, updated 2026-08-02 (agent) -
#appending -help to a full command line ('tool build -test 0 punkzip -help')
#routes identically; the received-args reports differ per line, so the full
#variant pins content rather than byte-equality
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]
@ -254,10 +267,12 @@ namespace eval ::testspace {
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}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result samefull [expr {[dict get $r2 output] eq $out}]
lappend result synbuild2 [regexp {make\.tcl tool build \[-test <bool>\] \[toolname\]\.\.\.} $out2]
lappend result landed2 [regexp {toolname = punkzip} $out2]
set result
} -result {timedout 0 exitcode 0 synbuild 1 testopt 1 timedout2 0 exitcode2 0 samefull 1}
} -result {timedout 0 exitcode 0 synbuild 1 testopt 1 timedout2 0 exitcode2 0 synbuild2 1 landed2 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