diff --git a/CHANGELOG.md b/CHANGELOG.md index 206d7a1d..ce002153 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ 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.2] - 2026-08-01 + +- make.tcl help is now a dry-run of the supplied command line (G-143 follow-up, + user-directed): 'make.tcl help ?arg ...?' (and ' + ?arg ...? -help') parses the words through the subcommand's punk::args + declaration exactly as dispatch would - an accepted line renders the matched + action's single-form usage ('help tool build -test 0 punkzip', prefix words + like 'help tool bu' included), while a rejected line (unknown action, + option-first line, unknown flag in option position) emits the same punk::args + noformmatch diagnosis dispatch gives - all-forms synopsis plus per-form + reasons - on stderr with exit 1. One carve-out: a bare action word (or + unambiguous prefix) renders that form's usage directly even where the real + command requires further values ('help buildsuite build'). This replaces + 0.40.1's leading-word-only selection and its option-first whole-usage + fallback: the parse error table is itself usage documentation, and help + verdicts otherwise mirror dispatch verdicts exactly. + ## [0.40.1] - 2026-08-01 - make.tcl help refinement (G-143 follow-up, user-directed): 'make.tcl help diff --git a/goals/archive/G-143-maketcl-multiform-help.md b/goals/archive/G-143-maketcl-multiform-help.md index 3fd14a28..eddf99ef 100644 --- a/goals/archive/G-143-maketcl-multiform-help.md +++ b/goals/archive/G-143-maketcl-multiform-help.md @@ -29,5 +29,6 @@ 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 ' and ' -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 ...', 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 ?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). +- 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 ?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. - 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). diff --git a/punkproject.toml b/punkproject.toml index eda190c3..929b10e6 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.40.1" +version = "0.40.2" 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/AGENTS.md b/src/AGENTS.md index 029907f5..af97df95 100644 --- a/src/AGENTS.md +++ b/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 ` ?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 ...`, 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 ` ?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 ...`, 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/` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk//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/`, `lib_tcl/` 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` 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/`, no deploy, and the previously deployed `bin/` 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./` or `tcl./` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `/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 `-` 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. diff --git a/src/make.tcl b/src/make.tcl index 4fce9524..a9aa5df2 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -2620,8 +2620,9 @@ proc ::punkboot::punkboot_gethelp {args} { append h " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n append h " $scriptname help ?subcommand? ?arg ...?" \n append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname -help);" \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 " extra words are the subcommand's own command line, dry-run through its declaration - an accepted" \n + append h " line shows the matched action's usage (tool, buildsuite), a rejected line that subcommand's own" \n + append h " usage error (tabled help only)" \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 @@ -3240,13 +3241,15 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO " Show tabled usage for make.tcl as a whole, or for a single subcommand. 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." + dry-runs it through the subcommand's declaration: a line the + subcommand would accept shows the matched action's 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') - 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')." buildsuite " Surface for the defined buildsuites under src/buildsuites (zig-built @@ -3686,10 +3689,10 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO -choices {${[dict keys $SUMMARIES]}} -& -help "Subcommand to show usage for." 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." + "The subject's own arguments, as they would be typed. The line + is dry-run through the subject's declaration: an accepted line + shows the matched action's usage (tool, buildsuite), a rejected + line shows the subject's own usage error." } #top-level definition: the subcommand table rendered by 'make.tcl' / 'make.tcl -help' variable SUBGROUPS { @@ -3711,12 +3714,12 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO General usage: make.tcl ?flags? 'make.tcl help ?arg ...?' or 'make.tcl - ?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.} + ?arg ...? -help' shows a subcommand's own usage - the words are + dry-run through the subcommand's declaration, so an accepted + command line shows the matched action's usage (tool, buildsuite) + and a rejected one shows that subcommand's own usage error. + 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" -& @@ -3963,30 +3966,37 @@ if {$do_help} { set usage_args [list] 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. + #('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 + #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 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 $w0 $usage_id} usage_err]} { - puts stderr $usage_err - } else { - puts stderr "make.tcl: no action '$w0' for '$help_subject' (actions: $form_names)" - } + set fmatch "" + if {[llength $help_words] == 1 && [llength $form_names] > 1} { + set fmatch [tcl::prefix::match -error "" $form_names [lindex $help_words 0]] + } + 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 } - lappend usage_args -form $fmatch + if {[llength $form_names] > 1} { + lappend usage_args -form [dict get $argd form] + } } } if {![catch {punk::args::usage {*}$usage_args $usage_id} usage_out]} { diff --git a/src/tests/shell/testsuites/punkexe/maketclhelp.test b/src/tests/shell/testsuites/punkexe/maketclhelp.test index cfc0cd54..64a2d960 100644 --- a/src/tests/shell/testsuites/punkexe/maketclhelp.test +++ b/src/tests/shell/testsuites/punkexe/maketclhelp.test @@ -5,25 +5,30 @@ package require tcltest #single-choice action leader + -choicelabels), so full-subcommand help renders #one synopsis line per action, 'make.tcl help ?arg ...?' (and #' ?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). +#DRY-RUNS it through the declaration (punk::args form auto-selection): accepted +#lines render the matched form's usage, rejected lines emit the same punk::args +#diagnosis dispatch would give (exit 1), 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 ': 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 +# - 'help ': 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 +# value position parse as values, so subtly-wrong-but-accepted lines still +# render usage: 'help bake punk91 -confirm 0' renders bake's whole usage), +# 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) +# - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with the +# punk::args noformmatch diagnosis naming the candidate forms # - 'tool build -help': the -help route renders the form # - 'tool frobnicate' / 'tool build -bogus punkzip': exit 1 punk::args usage errors # - 'tool build punkzip -test 0': exit 2 with the misplaced-flag guidance (options @@ -167,13 +172,14 @@ 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 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 { + #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 { set r [maketcl_run {help tool build -test 0 punkzip}] set rword [maketcl_run {help tool build}] set result [list] @@ -186,11 +192,14 @@ namespace eval ::testspace { 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]}] + lappend result noform3 [regexp {No form of the command matches the supplied arguments} $out3] + lappend result synindiag3 [expr {[regexp {make\.tcl tool list } $out3] && [regexp {make\.tcl tool test } $out3]}] set r4 [maketcl_run {help tool 2}] + set out4 [dict get $r4 output] 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 0 allforms3 1 timedout4 0 exitcode4 1} + } -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} #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 { @@ -217,22 +226,23 @@ namespace eval ::testspace { set result } -result {timedout 0 exitcode 0 suitename 1 driverarg 1 fwdnote 1} - #added 2026-08-01 (agent, G-143) - test maketcl_help_unknown_action {'make.tcl help ' exits 1 with a pointed punk::args -form error naming the allowed forms} -constraints {tclshavailable} -body { + #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 + #noformmatch diagnosis dispatch gives (was a -form invalid-value error) + test maketcl_help_unknown_action {'make.tcl help ' exits 1 with the punk::args noformmatch diagnosis naming the candidate forms} -constraints {tclshavailable} -body { set r [maketcl_run {help tool frobnicate}] set out [dict get $r output] set result [list] lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] - lappend result formerr [regexp {invalid value for option -form} $out] - lappend result named [regexp {Received 'frobnicate'} $out] - lappend result allowed [regexp {one of 'list info build test'} $out] + lappend result noform [regexp {No form of the command matches the supplied arguments} $out] + lappend result flist [regexp {form 'list':} $out] + lappend result fbuild [regexp {form 'build':} $out] set r2 [maketcl_run {help buildsuite frobnicate}] 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 allowed2 [regexp {one of 'list info build'} $out2] + lappend result noform2 [regexp {No form of the command matches the supplied arguments} $out2] set result - } -result {timedout 0 exitcode 1 formerr 1 named 1 allowed 1 timedout2 0 exitcode2 1 formerr2 1 allowed2 1} + } -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