Browse Source

G-143 achieved: make.tcl multi-form tool/buildsuite definitions + per-action help depth

tool/buildsuite/help are multi-form punk::args definitions (one @form per
action, literal single-choice action leaders + choicelabels): 'help tool' /
'help buildsuite' render one synopsis line per action; 'make.tcl help
<subcommand> <action>' - equivalently '<subcommand> <action> -help', or a
0-based form index ('help tool 2') - renders that action's single-form
usage; unknown actions and out-of-range indexes are pointed punk::args
errors (exit 1). tool dispatch parses through the definition (unknown
actions/flags: exit-1 usage errors). User-approved deviation: the declared
positional model puts options before tool names ('tool build -test 0
<name> ...'); docs/agent guidance updated to match and a flag-shaped tool
name earns a stderr hint; the PUNKBOOT_PLAIN degraded scan keeps the
historic flag-anywhere parse. buildsuite driver-arg passthrough unchanged.
G-144 consumer follow-through: all 17 per-subcommand @form -synopsis
overrides retired (automatic @cmd -name bracket-notation synopses;
top-level make.tcl override kept deliberately). Pinned by new
punkexe/maketclhelp.test (12 tests); full punkexe subtree green (114
tests, 0 fail). Goal flipped to achieved and archived; punkshell 0.40.0.

Assisted-by: harness=claude; primary-model=claude-opus-5[1m]; api-location=anthropic.com
master
Julian Noble 1 week ago
parent
commit
6711302b5c
  1. 18
      CHANGELOG.md
  2. 4
      GOALS-archive.md
  3. 4
      GOALS.md
  4. 9
      goals/archive/G-143-maketcl-multiform-help.md
  5. 2
      punkproject.toml
  6. 2
      src/AGENTS.md
  7. 251
      src/make.tcl
  8. 303
      src/tests/shell/testsuites/punkexe/maketclhelp.test
  9. 17
      src/tests/shell/testsuites/punkexe/maketcltool.test

18
CHANGELOG.md

@ -5,6 +5,24 @@ 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.0] - 2026-08-01
- make.tcl help depth (G-143): the tool and buildsuite subcommands are multi-form
punk::args definitions (one @form per action with a literal action leader), so
'make.tcl help tool'/'help buildsuite' render one synopsis line per action, and
'make.tcl help <subcommand> <action>' - equivalently '<subcommand> <action> -help',
with a 0-based form index accepted in place of the action word - renders that
action's single-form usage; unknown actions are pointed punk::args usage errors
(exit 1). tool dispatch now parses through the definition: unknown actions and
flags are usage errors (exit 1), and the declared positional model puts options
before tool names ('make.tcl tool build -test 0 <name> ...' - the historic
flag-anywhere order is no longer accepted; a misplaced flag earns a stderr hint,
and the PUNKBOOT_PLAIN degraded scan keeps the historic parse). buildsuite
driver-arg passthrough is unchanged. Per-subcommand explicit @form -synopsis
overrides retired in favour of the G-144 automatic bracket-notation synopses
(top-level make.tcl line kept). Pinned by
src/tests/shell/testsuites/punkexe/maketclhelp.test.
## [0.39.6] - 2026-08-01
- punk::args 0.13.0 (G-144): auto-generated synopses for `(script)`-prefixed

4
GOALS-archive.md

@ -278,3 +278,7 @@ Acceptance: a definition with @id -id (script)::x::sub and @cmd -name 'make.tcl
### G-145 [achieved 2026-08-01] Remnant-free piped usage tables: root-cause split ANSI fragments → detail: goals/archive/G-145-piped-usage-ansi-remnants.md
Scope: src/make.tcl (::punkboot::ansistrip transform); src/modules/textblock-999999.0a1.0.tm + src/modules/punk/ansi-999999.0a1.0.tm (ANSI-aware cell/width handling as implicated); src/tests/shell/testsuites/punkexe/maketclcolour.test (remnant pin)
Acceptance: piped 'tclsh src/make.tcl help <subject>' for every declared subject plus bare 'make.tcl -help' yields output with no ESC bytes and no CSI-remnant substrings (regex {\[[0-9;:]*m}; the help corpus carries no such literal text), pinned in maketclcolour.test; the root cause and chosen fix layer are recorded in this file; existing colour-policy pins (tty/forced/nocolor modes) pass unchanged.
### G-143 [achieved 2026-08-01] make.tcl help depth: multi-form tool/buildsuite definitions and per-action usage → detail: goals/archive/G-143-maketcl-multiform-help.md
Scope: src/make.tcl (::punkboot::argdoc definitions + help dispatch); src/tests/shell/testsuites/punkexe/ (new piped help characterization test)
Acceptance: 'make.tcl help tool' shows per-action synopsis lines (list; info; build with -test and toolnames; test with toolnames) instead of the single conflated line; 'make.tcl help tool build' and 'make.tcl help buildsuite build' render that form's usage table and an unknown action subject produces a punk::args usage error (exit 1); all currently-working tool invocations behave unchanged while 'make.tcl tool <unknown-action>' and unknown flags produce punk::args usage errors (exit 1); buildsuite driver-arg forwarding is unchanged (an arbitrary flag after 'buildsuite build <suite>' still reaches the driver untouched); PUNKBOOT_PLAIN=1 degraded dispatch still handles the same invocations via the plain scan; the behaviours are pinned by a piped characterization test under src/tests/shell/testsuites/punkexe/. (Achieved with a user-approved deviation 2026-08-01: the historic tool flag-anywhere argument order is not preserved - the declared positional model is options-before-names ('tool build -test 0 <name> ...') with docs/agent guidance updated to match; the PUNKBOOT_PLAIN degraded handler keeps the historic parse. The help action additionally accepts a 0-based form index. Detail in the archived file's Progress section.)

4
GOALS.md

@ -394,10 +394,6 @@ Detail: goals/G-141-libfetch-target-selection.md
Scope: punkbin artifact repo (external c:/repo/jn/punkbin - layout contract, src/build_sha1sums.tcl, per-tier listing manifests); src/scriptapps/bin/ (punk-runtime payloads); bin/punk-runtime.cmd (regenerated output) + bin/AGENTS.md; src/tests/shell/testsuites/binscripts/; src/make.tcl (libfetch coordination)
Detail: goals/G-142-punkbin-listing-manifests.md
### G-143 [proposed] make.tcl help depth: multi-form tool/buildsuite definitions and per-action usage
Scope: src/make.tcl (::punkboot::argdoc definitions + help dispatch); src/tests/shell/testsuites/punkexe/ (new piped help characterization test)
Detail: goals/G-143-maketcl-multiform-help.md
### G-146 [proposed] Fossil binary-glob seeding for generated projects (initial commit completes)
Scope: src/project_layouts/ (.fossil-settings payloads); src/make.tcl (thin-layout sync, if derivation chosen); src/modules/punk/mix/commandset/project-999999.0a1.0.tm (generation-time fossil commit path)
Detail: goals/G-146-fossil-binary-glob-seeding.md

9
goals/G-143-maketcl-multiform-help.md → goals/archive/G-143-maketcl-multiform-help.md

@ -1,6 +1,6 @@
# G-143 make.tcl help depth: multi-form tool/buildsuite definitions and per-action usage
Status: proposed
Status: achieved 2026-08-01
Scope: src/make.tcl (::punkboot::argdoc definitions + help dispatch); src/tests/shell/testsuites/punkexe/ (new piped help characterization test)
Goal: The tool and buildsuite subcommands are declared as multi-form punk::args definitions (one @form per action with a literal action leader and per-form opts/values) so that full-subcommand help renders one synopsis line per action - making it explicit which actions accept <toolname> and -test where today's single line conflates them - 'make.tcl help <subcommand> <action>' renders that action's single-form usage, tool dispatch parses through the definition (pointed punk::args usage errors for unknown actions and flags), and buildsuite keeps its passthrough driver-args contract (forms serve its help/synopsis accuracy only).
Acceptance: 'make.tcl help tool' shows per-action synopsis lines (list; info; build with -test and toolnames; test with toolnames) instead of the single conflated line; 'make.tcl help tool build' and 'make.tcl help buildsuite build' render that form's usage table and an unknown action subject produces a punk::args usage error (exit 1); all currently-working tool invocations behave unchanged while 'make.tcl tool <unknown-action>' and unknown flags produce punk::args usage errors (exit 1); buildsuite driver-arg forwarding is unchanged (an arbitrary flag after 'buildsuite build <suite>' still reaches the driver untouched); PUNKBOOT_PLAIN=1 degraded dispatch still handles the same invocations via the plain scan; the behaviours are pinned by a piped characterization test under src/tests/shell/testsuites/punkexe/.
@ -22,4 +22,11 @@ G-030 (achieved 2026-07-14 - goals/archive/G-030-maketcl-punkargs.md) made make.
- Related: G-144 (achieved 2026-08-01 - see goals/archive/G-144-punkargs-synopsis-name-fallback.md) - auto-synopsis @cmd -name fallback shipped in punk::args 0.13.0: (script)-prefixed ids now auto-render per-form synopsis lines with the declared @cmd -name in bracket notation, so this goal's multi-form definitions need no @form -synopsis overrides for the name, and make.tcl's existing hand-written overrides become optional (G-144 recorded their retirement as this goal's consumer follow-through, or a small standalone edit if this goal is not active).
- Related: G-025 (proposed) - the future version-report subcommand should be born to these conventions (carried from G-030's notes).
- Overlap survey 2026-08-01: goals_xref paths src/make.tcl scores no intersects (make.tcl is a df-damped ubiquitous anchor); manual index sweep - the live make.tcl-scoped goals (G-005 G-006 G-012 G-019 G-023 G-026 G-027 G-028 G-029 G-063 G-065 G-067) are build/vendor mechanics with no help/dispatch overlap; punkexe-test-scoped goals (G-077 G-131 G-141) unrelated.
- Activation freshness re-run 2026-08-01 (goals_xref score G-143): no new related goals since drafting - the strongest unlinked pairs (G-085 argdoc key, G-021/G-108 test key, G-083/G-072/G-075/G-055/G-053 punkargs term noise) are unrelated mechanisms; G-077's one-directional pair was already named in the drafting survey. No new Related lines required.
- Archived-goal references in this file: G-030 achieved 2026-07-14 (goals/archive/G-030-maketcl-punkargs.md); G-045 achieved 2026-07-12 (goals/archive/G-045-punkargs-authoring-ergonomics.md); G-104 achieved 2026-07-25 (goals/archive/G-104-maketcl-buildsuite-surface.md) - buildsuite surface whose passthrough contract this preserves; G-126 achieved 2026-07-27 (goals/archive/G-126-punkzip-accelerator.md) - origin of the tool subcommand.
## Progress
- 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).
- 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.39.6"
version = "0.40.0"
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): `tclsh src/make.tcl help ?subcommand?` or `<subcommand> -help` shows tabled usage; invalid arguments produce punk::args usage errors. 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 `::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, explicit `@form -synopsis` — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing); 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? ?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.
- 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.

251
src/make.tcl

@ -2610,13 +2610,18 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " $scriptname buildsuite list|info|build ?<suitename>? ?driver-args ...?" \n
append h " - list the defined buildsuites under src/buildsuites (zig runtime factory), show a suite's configured" \n
append h " detail (sources/zig pin/products from its sources.config), or run its driver forwarding the args" \n \n
append h " $scriptname tool list|info|build|test ?<toolname> ...? ?-test 0|1?" \n
append h " $scriptname tool list ?<toolname> ...?" \n
append h " $scriptname tool info <toolname> ..." \n
append h " $scriptname tool build ?-test 0|1? ?<toolname> ...?" \n
append h " $scriptname tool test ?<toolname> ...?" \n
append h " - vendored first-party build tools under src/tools (zig source; G-126): list/detail them, run their" \n
append h " test suites, or build (test-gated) and install to <projectdir>/bin" \n
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?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help)" \n \n
append h " $scriptname help ?subcommand? ?action?" \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 " 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
@ -3234,7 +3239,11 @@ 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'."
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."
buildsuite
"
Surface for the defined buildsuites under src/buildsuites (zig-built
@ -3278,16 +3287,19 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
tool info <name> ...
Per-tool detail: paths, provenance records, toolchain
resolution.
tool build ?<name> ...? ?-test 0|1?
tool build ?-test 0|1? ?<name> ...?
Resolve a zig satisfying the tool's declared floor, run the
'zig build test' gate (default on; the gate is the EXIT CODE -
expected warnings on stderr do not fail it), build ReleaseSafe
and install to <projectdir>/bin. Nothing is installed when the
gate or the build fails. With no name, all tools are
gate or the build fails. Options precede the tool names
(punk::args positional model); with no name, all tools are
processed.
tool test ?<name> ...?
Run each tool's 'zig build test' only.
'make.tcl help tool <action>' shows a single action's usage table.
Toolchain resolution: PUNK_ZIG=<path-to-zig-or-its-folder>
overrides; otherwise <projectdir>/bin/tools/zig* is scanned and
the LOWEST release satisfying the floor wins (deterministic as
@ -3404,7 +3416,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES bakehouse]}" -&
-help -&
{${[dict get $HELPTEXTS bakehouse]}}
@form -synopsis "make.tcl bakehouse ?-k? ?-dirty-abort 1|0? ?-confirm 0|1?"
@opts
${$OPT_FORCEKILL}
${$OPT_DIRTYABORT_ON}
@ -3417,7 +3428,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES packages]}" -&
-help -&
{${[dict get $HELPTEXTS packages]}}
@form -synopsis "make.tcl packages ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3429,7 +3439,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES modules]}" -&
-help -&
{${[dict get $HELPTEXTS modules]}}
@form -synopsis "make.tcl modules ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3441,7 +3450,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES libs]}" -&
-help -&
{${[dict get $HELPTEXTS libs]}}
@form -synopsis "make.tcl libs ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3453,7 +3461,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES bake]}" -&
-help -&
{${[dict get $HELPTEXTS bake]}}
@form -synopsis "make.tcl bake ?-k? ?-dirty-abort? ?-confirm 0|1? ?kitname ...?"
@opts
${$OPT_FORCEKILL}
${$OPT_DIRTYABORT}
@ -3472,7 +3479,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES bakelist]}" -&
-help -&
{${[dict get $HELPTEXTS bakelist]}}
@form -synopsis "make.tcl bakelist ?kitname ...?"
@values -min 0 -max -1
kitname -type string -optional 1 -multiple 1${$KITNAME_CHOICEPART} -help -&
"Configured kit output(s) to report on - a kit name, or @<groupname>
@ -3486,7 +3492,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES vfslibs]}" -&
-help -&
{${[dict get $HELPTEXTS vfslibs]}}
@form -synopsis "make.tcl vfslibs ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3498,7 +3503,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES bin]}" -&
-help -&
{${[dict get $HELPTEXTS bin]}}
@form -synopsis "make.tcl bin ?-k? ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_FORCEKILL}
${$OPT_DIRTYABORT}
@ -3511,7 +3515,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES vendorupdate]}" -&
-help -&
{${[dict get $HELPTEXTS vendorupdate]}}
@form -synopsis "make.tcl vendorupdate ?-confirm 0|1?"
@opts
${$OPT_CONFIRM}
@values -min 0 -max 0
@ -3522,7 +3525,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES libfetch]}" -&
-help -&
{${[dict get $HELPTEXTS libfetch]}}
@form -synopsis "make.tcl libfetch ?-serverurl <url>? ?-trust-server? ?-force?"
@opts
${$OPT_SERVERURL}
${$OPT_TRUSTSERVER}
@ -3535,7 +3537,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES bootsupport]}" -&
-help -&
{${[dict get $HELPTEXTS bootsupport]}}
@form -synopsis "make.tcl bootsupport ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3547,7 +3548,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES vfscommonupdate]}" -&
-help -&
{${[dict get $HELPTEXTS vfscommonupdate]}}
@form -synopsis "make.tcl vfscommonupdate ?-dirty-abort? ?-confirm 0|1?"
@opts
${$OPT_DIRTYABORT}
${$OPT_CONFIRM}
@ -3559,7 +3559,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES info]}" -&
-help -&
{${[dict get $HELPTEXTS info]}}
@form -synopsis "make.tcl info ?-confirm 0|1?"
@opts
${$OPT_CONFIRM}
@values -min 0 -max 0
@ -3570,7 +3569,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES check]}" -&
-help -&
{${[dict get $HELPTEXTS check]}}
@form -synopsis "make.tcl check"
@opts
@values -min 0 -max 0
}
@ -3580,7 +3578,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES projectversion]}" -&
-help -&
{${[dict get $HELPTEXTS projectversion]}}
@form -synopsis "make.tcl projectversion"
@opts
@values -min 0 -max 0
}
@ -3590,7 +3587,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES workflow]}" -&
-help -&
{${[dict get $HELPTEXTS workflow]}}
@form -synopsis "make.tcl workflow"
@opts
@values -min 0 -max 0
}
@ -3602,7 +3598,6 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
"Run the punk shell (repl) using bootsupport libraries.
All arguments after the shell subcommand are passed through to the
repl untouched (they are not parsed or validated by make.tcl)."
@form -synopsis "make.tcl shell ?arg ...?"
@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -&
"Arguments passed through to the punk repl."
@ -3613,11 +3608,28 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES buildsuite]}" -&
-help -&
{${[dict get $HELPTEXTS buildsuite]}}
@form -synopsis "make.tcl buildsuite list|info|build ?<suitename>? ?driver-args ...?"
@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -&
"Action (list|info|build), suite name, and - for build - driver
arguments forwarded to the suite's suite.tcl untouched."
@form -form {list info build}
@form -form list
@leaders -min 1 -max 1
action -choices {list} -choicelabels -&
{list "Discover the suites (directories carrying a suite.tcl driver) with their one-line descriptions"}
@values -min 0 -max 0
@form -form info
@leaders -min 1 -max 1
action -choices {info} -choicelabels -&
{info "Show a suite's configured detail: description, source records, zig pin, products, doc pointers"}
@values -min 1 -max 1
suitename -type string -optional 0 -help -&
"Suite name (a directory under src/buildsuites carrying a suite.tcl driver)."
@form -form build
@leaders -min 1 -max 1
action -choices {build} -choicelabels -&
{build "Run the suite's driver ('suite.tcl build'), forwarding trailing args untouched"}
@values -min 1 -max -1
suitename -type string -optional 0 -help -&
"Suite to build."
driverarg -type any -optional 1 -multiple 1 -help -&
"Driver arguments forwarded to suite.tcl untouched (e.g -tclbranch/-refresh/-steps)."
}
punk::args::define {
@ -3626,11 +3638,38 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES tool]}" -&
-help -&
{${[dict get $HELPTEXTS tool]}}
@form -synopsis "make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"
@form -form {list info build test}
@form -form list
@leaders -min 1 -max 1
action -choices {list} -choicelabels -&
{list "Discover the tools with version, zig floor, install state and vendored commit; report the resolved toolchain"}
@values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -&
"Action (list|info|build|test), tool name(s), and - for build -
the -test 0|1 gate flag. Parsed by the handler."
toolname -type string -optional 1 -multiple 1 -&
-help "Validated against the configured tools (list reports all regardless)."
@form -form info
@leaders -min 1 -max 1
action -choices {info} -choicelabels -&
{info "Per-tool detail: paths, provenance records, toolchain resolution"}
@values -min 1 -max -1
toolname -type string -optional 0 -multiple 1 -&
-help "Tool(s) to describe (directory names under src/tools)."
@form -form build
@leaders -min 1 -max 1
action -choices {build} -choicelabels -&
{build "Resolve zig, run the test gate, build ReleaseSafe and install to <projectdir>/bin"}
@opts
-test -type boolean -default 1 -help -&
"Run the 'zig build test' gate before building (default 1). The gate is the exit code."
@values -min 0 -max -1
toolname -type string -optional 1 -multiple 1 -&
-help "Tool(s) to build; with no name, all tools are processed."
@form -form test
@leaders -min 1 -max 1
action -choices {test} -choicelabels -&
{test "Run each tool's 'zig build test' only"}
@values -min 0 -max -1
toolname -type string -optional 1 -multiple 1 -&
-help "Tool(s) to test; with no name, all tools are processed."
}
punk::args::define {
@ -3639,11 +3678,13 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
-summary "${[dict get $SUMMARIES help]}" -&
-help -&
{${[dict get $HELPTEXTS help]}}
@form -synopsis "make.tcl help ?subcommand?"
@values -min 1 -max 1
@values -min 1 -max 2
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."
}
#top-level definition: the subcommand table rendered by 'make.tcl' / 'make.tcl -help'
variable SUBGROUPS {
@ -3664,16 +3705,21 @@ 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>' or 'make.tcl <subcommand> -help' shows a
subcommand's own usage. Interactive y/n confirmations can be driven
non-interactively with the -confirm flag declared on the relevant
subcommands.}
'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.}
punk::args::define {
@id -id "(script)::punkboot"
@cmd -name "make.tcl" -&
-summary "punkshell project build tool (punk boot)" -&
-help -&
{${$TOPLEVEL_HELP}}
#sole remaining explicit synopsis override (G-143 retired the per-subcommand
#ones per G-144's @cmd -name auto-synopsis): the auto render would show the
#subcommand leader as mandatory - this line states the bare-invocation and
#per-subcommand-flags reality the declaration cannot.
@form -synopsis "make.tcl ?subcommand? ?flags?"
@leaders -min 1 -max 1
subcommand -type string -optional 0 -choicecolumns 1 -&
@ -3708,6 +3754,7 @@ if {$::punkboot::punkargs_ok && [package provide punk::ansi] ne "" && [package p
set do_help 0
set help_subject ""
set help_action ""
set help_exitcode 0
#defaults for the option-derived variables (fallback scan and passthrough subcommands rely on these)
set ::punkboot::opt_forcekill 0
@ -3743,10 +3790,26 @@ if {$::punkboot::punkargs_ok} {
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]
}
}
} 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 ""
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
}
} elseif {$subcommand eq "shell"} {
#declared passthrough: everything after 'shell' goes to the repl unparsed
set ::punkboot::command shell
@ -3756,9 +3819,29 @@ if {$::punkboot::punkargs_ok} {
set ::punkboot::command buildsuite
set ::punkboot::bs_args $subargs
} elseif {$subcommand eq "tool"} {
#declared passthrough: the handler parses action/names/-test itself (G-126)
#G-143: tool is declared multi-form (one @form per action) and dispatch
#parses through the definition - unknown actions and unknown flags in
#option position produce pointed punk::args usage errors (exit 1). The
#definition's positional model puts -test before the tool names
#('make.tcl tool build -test 0 <name> ...'); the historic flag-anywhere
#order is not carried over (docs updated). Bare 'tool' selects the list
#form. The degraded plain scan keeps the historic manual tail parse
#(handler-side, exit-2 surface preserved there).
if {![llength $subargs]} {set subargs [list list]}
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::tool} argd]} {
puts stderr $argd
exit 1
}
set ::punkboot::command tool
set ::punkboot::tool_args $subargs
set ::punkboot::tool_action [dict get $argd form]
set ::punkboot::tool_names [list]
if {[dict exists $argd values toolname]} {
set ::punkboot::tool_names [dict get $argd values toolname]
}
set ::punkboot::tool_dotest 1
if {[dict exists $argd opts -test]} {
set ::punkboot::tool_dotest [dict get $argd opts -test]
}
} else {
if {[catch {punk::args::parse $subargs -errorstyle $::punkboot::errstyle withid (script)::punkboot::$subcommand} argd]} {
puts stderr $argd
@ -3871,7 +3954,32 @@ if {$do_help} {
if {$help_subject ne ""} {
set usage_id (script)::punkboot::$help_subject
}
if {![catch {punk::args::usage $usage_id} usage_out]} {
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).
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]
}
if {$fmatch eq ""} {
if {[catch {punk::args::usage -form $help_action $usage_id} usage_err]} {
puts stderr $usage_err
} else {
puts stderr "make.tcl: no action '$help_action' for '$help_subject' (actions: $form_names)"
}
exit 1
}
set help_action $fmatch
lappend usage_args -form $help_action
}
if {![catch {punk::args::usage {*}$usage_args $usage_id} usage_out]} {
puts stdout $usage_out
set availability_note [::punkboot::punkboot_availability_note]
if {$availability_note ne ""} {
@ -5126,30 +5234,40 @@ if {$::punkboot::command eq "tool"} {
#fetch guidance. Build caches (.zig-cache/zig-out) are deliberately left in
#place between builds for rebuild speed (both git- and fossil-ignored).
set tool_root [file join $sourcefolder tools]
if {![info exists ::punkboot::tool_args]} {set ::punkboot::tool_args [list]}
set targs $::punkboot::tool_args
set tool_action [lindex $targs 0]
if {$tool_action eq ""} {set tool_action list}
#args after the action: tool names plus an optional '-test 0|1' flag (build)
set tool_dotest 1
set tool_names [list]
set _i 1
while {$_i < [llength $targs]} {
set a [lindex $targs $_i]
if {$a eq "-test"} {
incr _i
set v [lindex $targs $_i]
if {![string is boolean -strict $v]} {
puts stderr "make.tcl tool: -test requires a boolean value (got '$v')"
exit 2
if {$::punkboot::punkargs_ok} {
#G-143: action/names/-test already parsed through the multi-form definition
#at dispatch (pointed punk::args usage errors for unknown actions/flags).
set tool_action $::punkboot::tool_action
set tool_names $::punkboot::tool_names
set tool_dotest $::punkboot::tool_dotest
} else {
#degraded plain scan: the handler's historic manual tail parse (its unknown
#action / bad -test exit-2 surface is preserved in this mode)
if {![info exists ::punkboot::tool_args]} {set ::punkboot::tool_args [list]}
set targs $::punkboot::tool_args
set tool_action [lindex $targs 0]
if {$tool_action eq ""} {set tool_action list}
#args after the action: tool names plus an optional '-test 0|1' flag (build)
set tool_dotest 1
set tool_names [list]
set _i 1
while {$_i < [llength $targs]} {
set a [lindex $targs $_i]
if {$a eq "-test"} {
incr _i
set v [lindex $targs $_i]
if {![string is boolean -strict $v]} {
puts stderr "make.tcl tool: -test requires a boolean value (got '$v')"
exit 2
}
set tool_dotest [expr {bool($v)}]
} else {
lappend tool_names $a
}
set tool_dotest [expr {bool($v)}]
} else {
lappend tool_names $a
incr _i
}
incr _i
unset -nocomplain _i a v
}
unset -nocomplain _i a v
proc ::punkboot::tool_dirs {tool_root} {
set dirs [list]
@ -5297,6 +5415,9 @@ if {$::punkboot::command eq "tool"} {
set selected [list]
foreach n $tool_names {
if {![dict exists $byname $n]} {
if {[string match -* $n]} {
puts stderr "tool: '$n' looks like a flag - options precede the tool names (e.g 'make.tcl tool build -test 0 <name> ...')"
}
puts stderr "tool '$n' not found under $tool_root - configured tools: [dict keys $byname]"
exit 2
}
@ -5340,7 +5461,7 @@ if {$::punkboot::command eq "tool"} {
}
puts stdout ""
puts stdout " - 'make.tcl tool info <name>' shows a tool's detail"
puts stdout " - 'make.tcl tool build ?<name> ...? ?-test 0|1?' builds (test-gated) and installs to bin/"
puts stdout " - 'make.tcl tool build ?-test 0|1? ?<name> ...?' builds (test-gated) and installs to bin/"
puts stdout " - zig is OPTIONAL for punkshell builds: packages/bake never require this step"
exit 0
}
@ -5446,7 +5567,7 @@ if {$::punkboot::command eq "tool"} {
}
default {
puts stderr "unknown tool action '$tool_action' - expected list|info|build|test"
puts stderr "usage: make.tcl tool list|info|build|test ?<toolname> ...? ?-test 0|1?"
puts stderr "usage: make.tcl tool list|info|build|test ?-test 0|1? ?<toolname> ...?"
exit 2
}
}

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

@ -0,0 +1,303 @@
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).
#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 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
# - '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
# precede tool names in the definition's positional model - the historic
# flag-anywhere order is deliberately not carried over)
# - 'buildsuite build suite_tcl90 -bogusxyz': the arbitrary flag reaches the
# driver untouched (the driver's own unknown-option error, not a usage error)
# - PUNKBOOT_PLAIN=1 degrade: same invocations handled by the plain scan (tool
# list exit 0; tool frobnicate keeps the historic exit-2 surface; plain help
# text carries the per-action lines)
#Driven through PLAIN tclsh (the acceptance's named invocation
#'tclsh src/make.tcl help ...'): the working tree's make.tcl + bootsupport
#punk::args are exercised directly. Kit-hosted rendering ('<punkexe> script
#src/make.tcl help tool') shows the raw (script) ids until the next routine
#vfscommonupdate+bake refreshes the kit's preloaded punk::args past 0.13.0
#(the documented punk-exe-hosted provenance-mixing hazard; the G-144 (script)
#name fallback is 0.13.0-era). The kit-hosted dispatch surfaces that do not
#depend on name rendering are pinned in maketcltool.test.
#
#Resolved lazily in the test body. Skipped (constraint tclshavailable) if no
#tclsh is on PATH.
namespace eval ::testspace {
namespace import ::tcltest::*
variable testdir [file dirname [file normalize [info script]]]
#<projectroot>/src/tests/shell/testsuites/punkexe -> 5 levels up to <projectroot>
variable projectroot [file normalize [file join $testdir .. .. .. .. ..]]
variable maketcl [file join $projectroot src make.tcl]
testConstraint tclshavailable [expr {[auto_execok tclsh] ne ""}]
variable maketcl_run_timeout_ms 60000
variable runstate
array set runstate {}
proc maketcl_run_read {chan} {
variable runstate
append runstate(output) [read $chan]
if {[chan eof $chan]} {
chan event $chan readable {}
set runstate(done) eof
}
}
#Run 'tclsh src/make.tcl <subcommand...>' with output captured through a pipe
#(stdin half-closed for immediate EOF - make.tcl must never wait on stdin for
#these invocations). envoverrides is a dict: name value pairs applied to ::env
#for the child (a value of "" means unset). Saved values are restored after
#the run. Returns dict: timedout 0|1, exitcode <int|"">, output <combined
#stdout+stderr>.
proc maketcl_run {cmdargs {envoverrides {}}} {
variable runstate
variable maketcl_run_timeout_ms
variable maketcl
array unset runstate
set runstate(output) ""
set runstate(done) ""
set saved [dict create]
dict for {evar eval_} $envoverrides {
if {[info exists ::env($evar)]} {
dict set saved $evar [list 1 $::env($evar)]
} else {
dict set saved $evar [list 0 {}]
}
if {$eval_ eq ""} {
unset -nocomplain ::env($evar)
} else {
set ::env($evar) $eval_
}
}
try {
set runcmd [list [auto_execok tclsh] $maketcl {*}$cmdargs 2>@1]
set chan [open |$runcmd r+]
chan configure $chan -blocking 0 -translation binary
catch {chan close $chan write} ;#no stdin for the child - immediate EOF
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]]
chan event $chan readable [list [namespace current]::maketcl_run_read $chan]
while {$runstate(done) eq ""} {
vwait [namespace current]::runstate(done)
}
after cancel $timerid
set timedout [expr {$runstate(done) eq "timeout"}]
set exitcode ""
if {$timedout} {
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]}
catch {chan close $chan}
} else {
chan configure $chan -blocking 1
if {[catch {chan close $chan} errdata errdict]} {
set exitcode [lindex [dict get $errdict -errorcode] end]
} else {
set exitcode 0
}
}
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)]
} finally {
dict for {evar restore} $saved {
lassign $restore existed oldval
if {$existed} {
set ::env($evar) $oldval
} else {
unset -nocomplain ::env($evar)
}
}
}
}
proc esc_count {text} {
return [regexp -all {\x1b} $text]
}
#added 2026-08-01 (agent, G-143)
test maketcl_help_tool_multiform {piped 'make.tcl help tool': exit 0, ESC-free, one synopsis line per action with i -form hints and the action choicelabel} -constraints {tclshavailable} -body {
set r [maketcl_run {help tool}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count $out]
lappend result synlist [regexp {make\.tcl tool list \[toolname\]\.\.\.} $out]
lappend result syninfo [regexp {make\.tcl tool info toolname \[toolname\]\.\.\.} $out]
lappend result synbuild [regexp {make\.tcl tool build \[-test <bool>\] \[toolname\]\.\.\.} $out]
lappend result syntest [regexp {make\.tcl tool test \[toolname\]\.\.\.} $out]
lappend result formhints [regexp { i -form 0 } $out]
lappend result choicelabel [regexp {Discover the tools with version, zig floor, install state} $out]
set result
} -result {timedout 0 exitcode 0 esc 0 synlist 1 syninfo 1 synbuild 1 syntest 1 formhints 1 choicelabel 1}
#added 2026-08-01 (agent, G-143)
test maketcl_help_tool_build_form {'make.tcl help tool build' renders the build form's argument table (-test opt, toolname, build choicelabel)} -constraints {tclshavailable} -body {
set r [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 esc [esc_count $out]
lappend result testopt [regexp {\?-test\?} $out]
lappend result toolname [regexp {\?toolname\.\.\.\?} $out]
lappend result choicelabel [regexp {Resolve zig, run the test gate, build ReleaseSafe and install} $out]
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}]
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 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]
set result
} -result {timedout 0 exitcode 0 sameasbuildword 1 timedout2 0 exitcode2 1 formerr2 1 range2 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 {
set r [maketcl_run {help buildsuite}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count $out]
lappend result synlist [regexp {make\.tcl buildsuite list} $out]
lappend result syninfo [regexp {make\.tcl buildsuite info suitename} $out]
lappend result synbuild [regexp {make\.tcl buildsuite build suitename \[driverarg\]\.\.\.} $out]
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 {
set r [maketcl_run {help buildsuite build}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result suitename [regexp {Suite to build\.} $out]
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}
#added 2026-08-01 (agent, G-143)
test maketcl_help_unknown_action {'make.tcl help <subcommand> <unknown-action>' exits 1 with a pointed punk::args -form error naming the allowed 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]
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]
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 {
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 result
} -result {timedout 0 exitcode 0 synbuild 1 testopt 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 {
set r [maketcl_run {help}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count $out]
lappend result subcmd [regexp {subcommand} $out]
lappend result bakehouse [regexp {bakehouse} $out]
lappend result toolrow [regexp {tool} $out]
set result
} -result {timedout 0 exitcode 0 esc 0 subcmd 1 bakehouse 1 toolrow 1}
#added 2026-08-01 (agent, G-143)
test maketcl_tool_dispatch_usage_errors {'make.tcl tool <unknown-action>' and an option-position unknown flag produce pointed punk::args usage errors (exit 1)} -constraints {tclshavailable} -body {
set r [maketcl_run {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 noform [regexp {No form of the command matches the supplied arguments} $out]
set r2 [maketcl_run {tool build -bogus punkzip}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result noform2 [regexp {No form of the command matches the supplied arguments} $out2]
set result
} -result {timedout 0 exitcode 1 noform 1 timedout2 0 exitcode2 1 noform2 1}
#added 2026-08-01 (agent, G-143)
test maketcl_tool_misplaced_flag_hint {'tool build punkzip -test 0' (historic flag-anywhere order) exits 2 with the options-precede-names guidance} -constraints {tclshavailable} -body {
set r [maketcl_run {tool build punkzip -test 0}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result hint [regexp {looks like a flag - options precede the tool names} $out]
lappend result notfound [regexp {tool '-test' not found under} $out]
set result
} -result {timedout 0 exitcode 2 hint 1 notfound 1}
#added 2026-08-01 (agent, G-143)
test maketcl_buildsuite_driver_forwarding {'buildsuite build <suite> <arbitrary-flag>' forwards the flag to the driver untouched (the driver's own unknown-option error, not a punk::args usage error)} -constraints {tclshavailable} -body {
set r [maketcl_run {buildsuite build suite_tcl90 -bogusxyz}]
set out [dict get $r output]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result fwdline [regexp {with forwarded args: -bogusxyz} $out]
lappend result drivererr [regexp {suite_tcl90 ERROR: unknown option '-bogusxyz'} $out]
lappend result nousageerr [regexp {No form of the command matches} $out]
set result
} -result {timedout 0 exitcode 1 fwdline 1 drivererr 1 nousageerr 0}
#added 2026-08-01 (agent, G-143)
test maketcl_plain_degrade {PUNKBOOT_PLAIN=1 degraded dispatch handles the same invocations via the plain scan (tool list exit 0; unknown action keeps the historic exit-2 surface; plain help carries the per-action lines)} -constraints {tclshavailable} -body {
set r [maketcl_run {tool list} {PUNKBOOT_PLAIN 1}]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result header [regexp {name\s+version\s+zig-floor} [dict get $r output]]
set r2 [maketcl_run {tool frobnicate} {PUNKBOOT_PLAIN 1}]
set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result historic [regexp {unknown tool action 'frobnicate' - expected list\|info\|build\|test} $out2]
set r3 [maketcl_run {help tool} {PUNKBOOT_PLAIN 1}]
set out3 [dict get $r3 output]
lappend result timedout3 [dict get $r3 timedout] exitcode3 [dict get $r3 exitcode]
lappend result plainlist [regexp {make\.tcl tool list \?<toolname> \.\.\.\?} $out3]
lappend result plainbuild [regexp {make\.tcl tool build \?-test 0\|1\? \?<toolname> \.\.\.\?} $out3]
set result
} -result {timedout 0 exitcode 0 header 1 timedout2 0 exitcode2 2 historic 1 timedout3 0 exitcode3 0 plainlist 1 plainbuild 1}
cleanupTests
}
namespace delete ::testspace

17
src/tests/shell/testsuites/punkexe/maketcltool.test

@ -12,7 +12,8 @@ package require tcltest
# pass on machines without a 0.16 toolchain)
# - 'tool info punkzip' exits 0 and reports the PROVENANCE.md-sourced records
# (upstream, full 40-hex vendored commit) and the provenance file path
# - 'tool <unknown-action>' exits 2 naming the action and the expected set
# - 'tool <unknown-action>' exits 1 with a pointed punk::args usage error naming
# the candidate forms (G-143: dispatch parses through the multi-form definition)
# - 'tool info <unknown-name>' exits 2 naming the tool and the configured names
#The build/test actions are deliberately NOT exercised here (they need a zig
#toolchain and minutes of compile time); the tree's own 'zig build test' is the
@ -128,15 +129,21 @@ namespace eval ::testspace {
set result
} -result {timedout 0 exitcode 0 toolline 1 upstream 1 commit 1 provfile 1 floor 1}
#added 2026-07-27 (agent, G-126)
test maketcl_tool_unknown_action {tool with an unknown action exits 2 naming it and the expected actions} -constraints {punkexeavailable} -body {
#added 2026-07-27 (agent, G-126); updated 2026-08-01 (agent, G-143) - dispatch now
#parses through the multi-form definition: an unknown action is a pointed
#punk::args noformmatch usage error (exit 1) instead of the handler's exit 2
test maketcl_tool_unknown_action {tool with an unknown action exits 1 with a punk::args noformmatch usage error naming the candidate forms} -constraints {punkexeavailable} -body {
set r [maketcl_run {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 named [regexp {unknown tool action 'frobnicate' - expected list\|info\|build\|test} $out]
lappend result noform [regexp {No form of the command matches the supplied arguments} $out]
lappend result finfo [regexp {form 'info':} $out]
lappend result flist [regexp {form 'list':} $out]
lappend result fbuild [regexp {form 'build':} $out]
lappend result ftest [regexp {form 'test':} $out]
set result
} -result {timedout 0 exitcode 2 named 1}
} -result {timedout 0 exitcode 1 noform 1 finfo 1 flist 1 fbuild 1 ftest 1}
#added 2026-07-27 (agent, G-126)
test maketcl_tool_unknown_name {tool info with an unknown tool name exits 2 listing the configured tools} -constraints {punkexeavailable} -body {

Loading…
Cancel
Save