Browse Source

Launcher/make.tcl single help mechanism (user direction; core 0.5.0, 0.53.0)

All -help/--help flag interception withdrawn from the kit launcher and
make.tcl: the leading 'help' word is the ONE help entry (the punkshell
'i <cmd> ...' idiom). It felt like too many ways to do the same thing
and was already colliding - the experimental shellspy processor's own
-help/--help handling was shadowed by the dispatch intercept; it is
reachable again.

Launcher (boot core 0.4.0 -> 0.5.0, moduledoc::punkexe 0.6.0,
moduledoc::punkboot 0.3.0):
- 'help <subcommand> ?arg ...?' accepts and IGNORES trailing words, so
  'help' can be prepended to an existing command line ('punk9_beta help
  tclsh somefile somearg' now shows the tclsh usage instead of
  confusingly falling back to the top-level table).
- Subject validation parses ONLY the subject word against a new
  required-subject clone (script)::punkexe.launcherhelpsubject
  (resolved_def -antiglobs {arg} -override subject -optional 0): the
  display-shape clone let punk::args SKIP a mistyped optional subject
  into the unconstrained arg row, silently rendering the top table
  where a tabled choice error is wanted (exit 1).
- A leading '-help' falls under the unknown-first-argument refusal
  (stderr usage answers it, exit 1); '<subcommand> -help' is the
  subcommand's own argument: tclsh regains exception-free stock dash
  parity, buildinfo refuses it like any argument (exit 2), a
  parse-declared registration rejects it like any undeclared flag.

make.tcl: the wants_help subargs scan ('<subcommand> ... -help') and
the top-level/degraded-scan help-flag branches are removed, along with
the help_flags variable - 'make.tcl bakelist -help' now fails the
kitname choice gate exactly like 'make.tcl bakelist nonexistant', and
'make.tcl -help' the subcommand choice gate (exit 1), the usage error
itself carrying the documentation. 'make.tcl help <sub> <cmdline>'
dry-run behaviour unchanged.

Tests re-pinned to the revised contract (launcherhelp.test with a
kit_nodashhelp probe gate + trailing-args coverage, maketclhelp.test,
maketclcolour.test); punkexe subtree 152/148/4skip/0fail against
freshly baked zipfs + metakit kits; core copies byte-identical across
master/_vfscommon/layout/modpod-template channels.

Claude-Session: https://claude.ai/code/session_01Y1diJnhjUxKgEG6EwYAzxj
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
ac19df2a24
  1. 4
      ARCHITECTURE.md
  2. 20
      CHANGELOG.md
  3. 2
      bin/AGENTS.md
  4. 2
      punkproject.toml
  5. 2
      src/AGENTS.md
  6. 77
      src/make.tcl
  7. 21
      src/modules/punk/args/moduledoc/punkboot-999999.0a1.0.tm
  8. 3
      src/modules/punk/args/moduledoc/punkboot-buildversion.txt
  9. 119
      src/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm
  10. 3
      src/modules/punk/args/moduledoc/punkexe-buildversion.txt
  11. 16
      src/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl
  12. 200
      src/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl
  13. 200
      src/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl
  14. 140
      src/tests/shell/testsuites/punkexe/launcherhelp.test
  15. 7
      src/tests/shell/testsuites/punkexe/maketclcolour.test
  16. 38
      src/tests/shell/testsuites/punkexe/maketclhelp.test
  17. 2
      src/vfs/AGENTS.md
  18. 16
      src/vfs/_config/project_main.tcl
  19. 166
      src/vfs/_config/punkboot_core.tcl

4
ARCHITECTURE.md

@ -42,12 +42,12 @@ app package from src/lib/ (app-punkshell, app-punkscript, app-repl, app-shellsp
## Boot and launch chain ## Boot and launch chain
- **Kit anatomy.** Each `src/vfs/*.vfs` folder is a runtime payload; `src/runtime/mapvfs.toml` (G-024, tomlish-parsed; deprecated `mapvfs.config` line format still readable) maps payload folders to platform runtimes under `bin/runtime/<platform>/` as named kit outputs, with named groups and generative version-named schemes; an optional sibling `src/vfs/<name>.vfs.toml` declares payload packages materialized INTO the folder with drop-in-wins precedence (G-115 - spec: `src/vfs/README.md`); `src/vfs/_vfscommon.vfs/` is a generated merge of common libraries (never hand-edited); `src/vfs/_config/` holds the entry scripts. Sources: `src/vfs/AGENTS.md`, `src/runtime/AGENTS.md`. - **Kit anatomy.** Each `src/vfs/*.vfs` folder is a runtime payload; `src/runtime/mapvfs.toml` (G-024, tomlish-parsed; deprecated `mapvfs.config` line format still readable) maps payload folders to platform runtimes under `bin/runtime/<platform>/` as named kit outputs, with named groups and generative version-named schemes; an optional sibling `src/vfs/<name>.vfs.toml` declares payload packages materialized INTO the folder with drop-in-wins precedence (G-115 - spec: `src/vfs/README.md`); `src/vfs/_vfscommon.vfs/` is a generated merge of common libraries (never hand-edited); `src/vfs/_config/` holds the entry scripts. Sources: `src/vfs/AGENTS.md`, `src/runtime/AGENTS.md`.
- **Entry point.** `src/vfs/_config/punk_main.tcl` (thin main sourcing the shared boot core - G-031). An optional first argument is a dash-delimited package mode composed of tokens from `minted`, `os`, `src`, `internal` (`internal` is always appended when absent; `minted` was named `dev` pre-0.43.0), optionally scoped with the `proj:` prefix (G-033: `minted`/`src` resolve against the project containing the cwd - walk-up to the nearest git/fossil repo root with a punkshell-style src tree - instead of the executable's own project; discovery is always reported, never a silent rebind). The next argument is the subcommand: `shell`, `script`, `punk`, `tclsh`, `shellspy`, `buildinfo`, `help`. A non-subcommand first argument naming an existing file (or `lib:*` reference) is treated as a script invocation; a first argument of exactly `-e` runs the following argument as a Tcl one-liner (G-077, achieved - handled as `script -e`; tool-style kits keep `-e` routed to their own processor); anything else is refused with usage (G-032); no arguments at all means interactive shell. Subcommands map to app packages under `src/lib/` (`app-punkshell`, `app-punkscript`, `app-repl`, `app-shellspy`). The launch surface self-documents through punk::args (G-032, achieved semantics in boot core 0.4.0): `help`/`-help`/`<subcommand> -help` render tabled usage from the moduledoc definitions plus project registrations (`::punkboot::project_subcommand_info` - summaries, definition ids, optional declared parse), degrading to a plain subcommand list when that stack is unavailable. Source: `src/vfs/AGENTS.md`. - **Entry point.** `src/vfs/_config/punk_main.tcl` (thin main sourcing the shared boot core - G-031). An optional first argument is a dash-delimited package mode composed of tokens from `minted`, `os`, `src`, `internal` (`internal` is always appended when absent; `minted` was named `dev` pre-0.43.0), optionally scoped with the `proj:` prefix (G-033: `minted`/`src` resolve against the project containing the cwd - walk-up to the nearest git/fossil repo root with a punkshell-style src tree - instead of the executable's own project; discovery is always reported, never a silent rebind). The next argument is the subcommand: `shell`, `script`, `punk`, `tclsh`, `shellspy`, `buildinfo`, `help`. A non-subcommand first argument naming an existing file (or `lib:*` reference) is treated as a script invocation; a first argument of exactly `-e` runs the following argument as a Tcl one-liner (G-077, achieved - handled as `script -e`; tool-style kits keep `-e` routed to their own processor); anything else is refused with usage (G-032); no arguments at all means interactive shell. Subcommands map to app packages under `src/lib/` (`app-punkshell`, `app-punkscript`, `app-repl`, `app-shellspy`). The launch surface self-documents through punk::args (G-032; single-mechanism revision 2026-08-05 in boot core 0.5.0): `help ?subcommand? ?arg ...?` - the only help entry, trailing words accepted so `help` can be prepended to a command line - renders tabled usage from the moduledoc definitions plus project registrations (`::punkboot::project_subcommand_info` - summaries, definition ids, optional declared parse), degrading to a plain subcommand list when that stack is unavailable; help FLAGS are never intercepted (a leading `-help` earns the refusal, `<subcommand> -help` belongs to the subcommand). Source: `src/vfs/AGENTS.md`.
- **Package modes pick module provenance.** `internal` uses module snapshots baked into the kit at bake time; `minted` loads the mint output trees at repo-root `modules/` and `lib/`; `src` loads the unbuilt working tree (`src/modules`, `src/lib`, `src/bootsupport`, `src/vendormodules`) with `package prefer latest` so magic-version dev modules (`999999.0a1.0`) beat stamped snapshots. `src` mode is the standard way to verify working-tree changes without a rebuild. Source: `bin/AGENTS.md` "Launch package modes". - **Package modes pick module provenance.** `internal` uses module snapshots baked into the kit at bake time; `minted` loads the mint output trees at repo-root `modules/` and `lib/`; `src` loads the unbuilt working tree (`src/modules`, `src/lib`, `src/bootsupport`, `src/vendormodules`) with `package prefer latest` so magic-version dev modules (`999999.0a1.0`) beat stamped snapshots. `src` mode is the standard way to verify working-tree changes without a rebuild. Source: `bin/AGENTS.md` "Launch package modes".
- **Payload mount derivation (G-129, achieved).** The boot keys zipfs presence on `tcl::zipfs::mount` and derives where the executable's attached archive actually mounted from the mount table itself (the entry whose archive file is the executable; fallback: the mount containing the boot script) instead of assuming `tcl::zipfs::root` + `//zipfs:/app`. Modern runtimes still answer `//zipfs:/app`; the androwish/undroidwish 8.6 backport has no `root` command, mounts at the executable's own path, and boots the same payload through its `app/main.tcl` in-archive hook (`src/vfs/punk8win.vfs` carries fauxlinks for both conventions). A mounted archive the boot cannot attribute is reported on stderr rather than silently yielding no internal paths. The derivation procs stay defined post-boot (`::punkboot::zipfs_kit_mountbase`). Sources: `src/vfs/AGENTS.md`, `bin/AGENTS.md` "Kit-wrappable runtime requirements", `src/tests/shell/testsuites/punkexe/kitmountpoint.test`. - **Payload mount derivation (G-129, achieved).** The boot keys zipfs presence on `tcl::zipfs::mount` and derives where the executable's attached archive actually mounted from the mount table itself (the entry whose archive file is the executable; fallback: the mount containing the boot script) instead of assuming `tcl::zipfs::root` + `//zipfs:/app`. Modern runtimes still answer `//zipfs:/app`; the androwish/undroidwish 8.6 backport has no `root` command, mounts at the executable's own path, and boots the same payload through its `app/main.tcl` in-archive hook (`src/vfs/punk8win.vfs` carries fauxlinks for both conventions). A mounted archive the boot cannot attribute is reported on stderr rather than silently yielding no internal paths. The derivation procs stay defined post-boot (`::punkboot::zipfs_kit_mountbase`). Sources: `src/vfs/AGENTS.md`, `bin/AGENTS.md` "Kit-wrappable runtime requirements", `src/tests/shell/testsuites/punkexe/kitmountpoint.test`.
- **Static package capture (G-058, achieved).** Boot probe-loads the runtime's statically linked packages in a throwaway interp and records what each provides; every interp the shell fabricates (boot, codethread, shellthread workers) seeds `package ifneeded <name> <ver> {load {} <prefix>}` from that record, and `punk::packagepreference` resolves static-vs-bundled version-aware. Sources: `src/vfs/AGENTS.md`, `goals/archive/G-058-static-runtime-packages.md`. - **Static package capture (G-058, achieved).** Boot probe-loads the runtime's statically linked packages in a throwaway interp and records what each provides; every interp the shell fabricates (boot, codethread, shellthread workers) seeds `package ifneeded <name> <ver> {load {} <prefix>}` from that record, and `punk::packagepreference` resolves static-vs-bundled version-aware. Sources: `src/vfs/AGENTS.md`, `goals/archive/G-058-static-runtime-packages.md`.
- **`script` subcommand (G-015, achieved).** The lean non-interactive path: default punk shell module/alias environment, no shellfilter stacks/transforms, honest exit codes, launch plumbing emits nothing on stdout/stderr (exec-style callers see only the script's own output). Supports `lib:<name>` scriptlib resolution; a first argument of exactly `-e` runs the second argument as a Tcl one-liner (G-077, achieved - `::argv0` is `-e`, trailing args in `::argv`, a usage error when the script argument is missing, stdin never read by the form itself, non-empty final result echoed), also reachable as top-level `<punkexe> -e` via a boot-core reclassification ahead of the unknown-first-arg refusal. App package: `src/lib/app-punkscript/`. - **`script` subcommand (G-015, achieved).** The lean non-interactive path: default punk shell module/alias environment, no shellfilter stacks/transforms, honest exit codes, launch plumbing emits nothing on stdout/stderr (exec-style callers see only the script's own output). Supports `lib:<name>` scriptlib resolution; a first argument of exactly `-e` runs the second argument as a Tcl one-liner (G-077, achieved - `::argv0` is `-e`, trailing args in `::argv`, a usage error when the script argument is missing, stdin never read by the form itself, non-empty final result echoed), also reachable as top-level `<punkexe> -e` via a boot-core reclassification ahead of the unknown-first-arg refusal. App package: `src/lib/app-punkscript/`.
- **`tclsh` subcommand (G-118, achieved).** Plain-tclsh emulation with NO punk modules loaded: stock argument-form parity (`-encoding name file`; any other leading `-` argument means no script, everything stays in `::argv`; no `-e` one-liner - deliberate stock parity; the punk-level one-liner lives at the top level and as `script -e` (G-077, achieved 2026-08-04 - goals/archive/G-077-punkexe-dash-e-oneliner.md)), `lib:` refused with a pointer at `script`, missing script file is a clean exit 1. On piperepl-patched runtimes (the punk family runtimes - `src/buildsuites/suite_tcl90/patches/`) the C-level machinery publishes `::tclsh` launch state (`istty`, `dorepl`, `evalinput`, `inputbuffer`, `reopened`) allowing piped input to hand over to a live console repl at eof; runtimes without the machinery evaluate piped stdin whole-buffer with honest exit codes and fail fast on console stdin. The subcommand-family contract (tclsh/script/shell/punk/shellspy/buildinfo/help) is documented as punk::args definitions in `src/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm` (ids `(script)::punkexe` + per-subcommand; view via `punk::args::usage`), which the G-032 launcher help wiring renders and parses from at dispatch time (a help flag as tclsh's SOLE argument is the one deliberate stock-parity exception). Pipe-testable behaviour pinned by `src/tests/shell/testsuites/punkexe/tclshcmd.test` and `launcherhelp.test`. - **`tclsh` subcommand (G-118, achieved).** Plain-tclsh emulation with NO punk modules loaded: stock argument-form parity (`-encoding name file`; any other leading `-` argument means no script, everything stays in `::argv`; no `-e` one-liner - deliberate stock parity; the punk-level one-liner lives at the top level and as `script -e` (G-077, achieved 2026-08-04 - goals/archive/G-077-punkexe-dash-e-oneliner.md)), `lib:` refused with a pointer at `script`, missing script file is a clean exit 1. On piperepl-patched runtimes (the punk family runtimes - `src/buildsuites/suite_tcl90/patches/`) the C-level machinery publishes `::tclsh` launch state (`istty`, `dorepl`, `evalinput`, `inputbuffer`, `reopened`) allowing piped input to hand over to a live console repl at eof; runtimes without the machinery evaluate piped stdin whole-buffer with honest exit codes and fail fast on console stdin. The subcommand-family contract (tclsh/script/shell/punk/shellspy/buildinfo/help) is documented as punk::args definitions in `src/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm` (ids `(script)::punkexe` + per-subcommand; view via `punk::args::usage`), which the G-032 launcher help wiring renders and parses from at dispatch time (`help tclsh` for the usage; stock dash parity is exception-free since the 2026-08-05 single-mechanism revision). Pipe-testable behaviour pinned by `src/tests/shell/testsuites/punkexe/tclshcmd.test` and `launcherhelp.test`.
- **`bin/` launch surface.** Built kits plus polyglot `.cmd` utilities (`punk-runtime`, `punk-getzig`, `punk-tclargs`, `dtplite`) generated from `src/scriptapps/` sources by the `punk::mix` scriptwrap machinery - outputs are never hand-edited. `bin/punk-runtime.cmd` fetches/lists/selects plain runtimes under `bin/runtime/<platform>/` from the punkbin artifact server with sha1 verification. Source: `bin/AGENTS.md`. - **`bin/` launch surface.** Built kits plus polyglot `.cmd` utilities (`punk-runtime`, `punk-getzig`, `punk-tclargs`, `dtplite`) generated from `src/scriptapps/` sources by the `punk::mix` scriptwrap machinery - outputs are never hand-edited. `bin/punk-runtime.cmd` fetches/lists/selects plain runtimes under `bin/runtime/<platform>/` from the punkbin artifact server with sha1 verification. Source: `bin/AGENTS.md`.
## Interp and thread topology ## Interp and thread topology

20
CHANGELOG.md

@ -5,6 +5,26 @@ 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` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.53.0] - 2026-08-05
- Single help mechanism (user direction; boot core 0.5.0, moduledoc::punkexe
0.6.0, moduledoc::punkboot 0.3.0): all -help/--help flag interception is
withdrawn from the kit launcher and make.tcl - the leading `help` word is
the one help entry (the punkshell `i <cmd> ...` idiom). `<punkexe> help
<subcommand> ?arg ...?` now accepts and ignores trailing words, so `help`
can simply be prepended to an existing command line (previously
`help tclsh somefile somearg` confusingly fell back to the top-level
table). Help flags are ordinary arguments everywhere: a leading `-help`
earns the unknown-first-argument refusal (whose stderr usage answers it,
exit 1), `tclsh -help` regains full stock dash parity (the sole-argument
carve-out is gone), `buildinfo -help` is refused like any argument
(exit 2), the experimental shellspy processor's own -help/--help handling
is reachable again, and `make.tcl <subcommand> ... -help` fails whatever
punk::args gate it meets (e.g. `bakelist -help` = the same choice-gate
rejection as `bakelist nonexistant`) with the usage error itself carrying
the documentation. launcherhelp.test and maketclhelp.test re-pinned to
the revised contract.
## [0.52.0] - 2026-08-04 ## [0.52.0] - 2026-08-04
- G-165 driver-invariant kit assembly: every make.tcl zip-kit bake now emits - G-165 driver-invariant kit assembly: every make.tcl zip-kit bake now emits

2
bin/AGENTS.md

@ -97,7 +97,7 @@ platform-specific difference left in examples is path separator style
### Launch package modes (built punk shells) ### Launch package modes (built punk shells)
The first argument to a built punk shell may be a dash-delimited package mode composed of tokens from `minted`, `os`, `src`, `internal` (e.g. `punksys src`, `punk902z minted-os`), optionally scoped with the `proj:` prefix (e.g. `punk91 proj:internal-src`). (`minted` was named `dev` before punkshell 0.43.0 - clean rename, no alias: old kits accept only `dev`, current kits only `minted`.) `internal` is the default and is always appended when absent. A first argument that is not a valid mode list is treated as a subcommand instead (a `proj:` prefix on a non-mode string is not consumed either). Implementation: `src/vfs/_config/punk_main.tcl` (search `all_package_modes`). The full packagemode contract (block meanings, ordering, proj: examples) is the punk::args documentation at id `(script)::punkexe` (module `punk::args::moduledoc::punkexe`), and the launch surface is self-documenting in every current kit (G-032): `<punkexe> help ?subcommand?`, `<punkexe> -help` or `<punkexe> <subcommand> -help` render tabled usage (with the packagemode contract as the leader row) - degrading to a plain subcommand list where the punk::args stack is unavailable. An unknown first argument that names no existing file (and no `lib:*` scriptlib reference) is refused with that usage rather than silently attempted as a script. A first argument of exactly `-e` instead runs the following argument as a Tcl one-liner (G-077; equivalently `<punkexe> script -e <script> ?arg ...?`). The first argument to a built punk shell may be a dash-delimited package mode composed of tokens from `minted`, `os`, `src`, `internal` (e.g. `punksys src`, `punk902z minted-os`), optionally scoped with the `proj:` prefix (e.g. `punk91 proj:internal-src`). (`minted` was named `dev` before punkshell 0.43.0 - clean rename, no alias: old kits accept only `dev`, current kits only `minted`.) `internal` is the default and is always appended when absent. A first argument that is not a valid mode list is treated as a subcommand instead (a `proj:` prefix on a non-mode string is not consumed either). Implementation: `src/vfs/_config/punk_main.tcl` (search `all_package_modes`). The full packagemode contract (block meanings, ordering, proj: examples) is the punk::args documentation at id `(script)::punkexe` (module `punk::args::moduledoc::punkexe`), and the launch surface is self-documenting in every current kit (G-032; single-mechanism revision 2026-08-05): `<punkexe> help ?subcommand? ?arg ...?` renders tabled usage (with the packagemode contract as the leader row; trailing words after the subject are accepted and ignored, so `help` can be prepended to an existing command line) - degrading to a plain subcommand list where the punk::args stack is unavailable. Help FLAGS are never intercepted: `-help` after a subcommand is that subcommand's own argument, and a leading `-help` falls under the refusal below. An unknown first argument that names no existing file (and no `lib:*` scriptlib reference) is refused with that usage rather than silently attempted as a script. A first argument of exactly `-e` instead runs the following argument as a Tcl one-liner (G-077; equivalently `<punkexe> script -e <script> ?arg ...?`).
`src` mode is the one that matters for verifying working-tree changes: `src` mode is the one that matters for verifying working-tree changes:

2
punkproject.toml

@ -1,6 +1,6 @@
[project] [project]
name = "punkshell" name = "punkshell"
version = "0.52.0" version = "0.53.0"
license = "BSD-2-Clause" license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell" url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof - #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 make.tcl 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 run proceeds; piped/agent runs skip it. `tclsh src/make.tcl check` reports the current src provenance status and what the producing commands would do. - Provenance warnings (dirty-src gate + vendorupdate source-project check) print with a plain column-0 `PROVENANCE-WARNING:` token (grep for it in captured make.tcl 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 run proceeds; piped/agent runs skip it. `tclsh src/make.tcl check` reports the current src provenance status and what the producing 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`). - 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 release workflow (the TERMINOLOGY stage-verb key, 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 make.tcl 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 TERMINOLOGY / 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. - `tclsh src/make.tcl workflow` prints an embedded ASCII data-flow overview of the release workflow (the TERMINOLOGY stage-verb key, 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 make.tcl 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 TERMINOLOGY / RELEASE SEQUENCE / DIAGRAM / KEY-NOTES / MAINTENANCE structure; verify by running `tclsh src/make.tcl workflow` (and once under `PUNKBOOT_PLAIN=1`). Adding/changing a subcommand also means: SUMMARIES + HELPTEXTS + its braced `punk::args::define` block (+ SUBGROUPS) in `::punkboot::argdoc`, `known_commands`, the plain `punkboot_gethelp` text, and a project-version bump (make.tcl interface is product surface per root AGENTS.md). Layout make.tcl copies pick the change up via the thin-layout sync step in any `make.tcl modules`/`libs`/`packages`/`bakehouse` run - do not hand-sync them.
- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or `<subcommand> ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. Bare `make.tcl help` (and `make.tcl`/`-help`) renders a rich top-level overview (2026-08-02, the `i info` style): per-subcommand cells carrying the one-line summary plus that subcommand's own auto-generated synopsis line(s) - per-form lines for the multi-form subcommands - grouped by SUBGROUPS (rich `-choicelabels` built by `::punkboot::argdoc::overview_labels` from `punk::args::synopsis` per id, hung on the separate `(script)::punkboot.overview` id so dispatch never pays the label cost; the lean `(script)::punkboot` id remains the dispatch surface). `-choicecolumns` is 1 for now - the long one-line summaries make a 2-column layout too wide without table CELL WRAPPING; revisit `-choicecolumns 2` when that lands (user decision 2026-08-02; the `## FORM` header lines are likewise deliberately dropped from the cells). tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and help accepts the subcommand's own COMMAND LINE after the subject and DRY-RUNS it through that subcommand's declaration: `make.tcl help tool build -test 0 punkzip` (equivalently that command line with `-help` appended) parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. There is deliberately no bare-action carve-out: an action word alone is dry-run too, so a form with required values reports them via the diagnosis (`help buildsuite build` exits 1 naming the missing suitename - the error table carries the form's argument rows, so it IS the form documentation). An accepted line is confirmed with a one-line received-args report after the table (`dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip`); punk::args' positional model consumes flag-like words at/after the first value position as VALUES where the value arg is unconstrained (tool's toolname), and the report makes such swallows visible - value args with restricted choices (bake/bakelist kitnames as of 2026-08-02) instead reject the flag-like word at the choice gate, exactly as dispatch does. Declaration-level passthroughs (shell args, buildsuite driver args) always parse clean; numeric form indexes are deliberately not an interface. tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions. - make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved; single-mechanism revision 2026-08-05, user direction): `tclsh src/make.tcl help ?subcommand? ?arg ...?` shows tabled usage - the leading `help` word is the ONLY help entry; help FLAGS (-help/--help/-h//?) are ordinary arguments that fail whatever gate they meet (top level: the subcommand choice violation; tool option position: noformmatch; a value position with restricted choices: the choice gate; degraded scan: Unknown flag + help exit 1), with the usage error itself carrying the documentation. Invalid arguments produce punk::args usage errors. Bare `make.tcl help` (and bare `make.tcl`) renders a rich top-level overview (2026-08-02, the `i info` style): per-subcommand cells carrying the one-line summary plus that subcommand's own auto-generated synopsis line(s) - per-form lines for the multi-form subcommands - grouped by SUBGROUPS (rich `-choicelabels` built by `::punkboot::argdoc::overview_labels` from `punk::args::synopsis` per id, hung on the separate `(script)::punkboot.overview` id so dispatch never pays the label cost; the lean `(script)::punkboot` id remains the dispatch surface). `-choicecolumns` is 1 for now - the long one-line summaries make a 2-column layout too wide without table CELL WRAPPING; revisit `-choicecolumns 2` when that lands (user decision 2026-08-02; the `## FORM` header lines are likewise deliberately dropped from the cells). 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` parses via punk::args form auto-selection and renders the build form's usage, while a line the subcommand would reject (unknown action word, option-first line, unknown flag in option position) gets the same pointed punk::args noformmatch diagnosis dispatch gives - synopsis plus per-form reasons - on stderr with exit 1, never a plain-help fallback. There is deliberately no bare-action carve-out: an action word alone is dry-run too, so a form with required values reports them via the diagnosis (`help buildsuite build` exits 1 naming the missing suitename - the error table carries the form's argument rows, so it IS the form documentation). An accepted line is confirmed with a one-line received-args report after the table (`dry-run: line accepted (form build) - action = build | -test = 0 | toolname = punkzip`); punk::args' positional model consumes flag-like words at/after the first value position as VALUES where the value arg is unconstrained (tool's toolname), and the report makes such swallows visible - value args with restricted choices (bake/bakelist kitnames as of 2026-08-02) instead reject the flag-like word at the choice gate, exactly as dispatch does. Declaration-level passthroughs (shell args, buildsuite driver args) always parse clean; numeric form indexes are deliberately not an interface. tool DISPATCH parses through its definition: unknown actions/flags are punk::args usage errors (exit 1), and the declared positional model puts options before the tool names (`make.tcl tool build -test 0 <name> ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions.
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk/<layout>/src/{make.tcl,build.tcl}` + bootsupport manifest copies + the inert `gitignore.in` payload copies refreshed from the root `.gitignore` - G-012), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns. - 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/_bake/<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`) bakes 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 `BAKE-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`. - **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/_bake/<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`) bakes 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 `BAKE-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 `BAKE-WARNING`s and the kit still bakes 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 rebakes. **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. - **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BAKE-WARNING`s and the kit still bakes 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 rebakes. **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.

77
src/make.tcl

@ -198,7 +198,6 @@ namespace eval ::punkboot {
variable scriptfolder [file normalize [file dirname [info script]]] variable scriptfolder [file normalize [file dirname [info script]]]
variable foldername [file tail $scriptfolder] variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list] variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable help_flags [list -help --help /? -h]
variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate libfetch bootsupport vfscommonupdate projectversion workflow buildsuite tool] variable known_commands [list bakehouse packages modules libs bake bakelist vfslibs bin info check shell vendorupdate libfetch bootsupport vfscommonupdate projectversion workflow buildsuite tool]
} }
@ -2588,7 +2587,7 @@ proc ::punkboot::punkboot_gethelp {args} {
set scriptname [file tail [info script]] set scriptname [file tail [info script]]
append h "Usage:" \n append h "Usage:" \n
append h "" \n append h "" \n
append h " $scriptname -help or $scriptname --help or $scriptname /? or just $scriptname" \n append h " $scriptname help or just $scriptname" \n
append h " - This help." \n \n append h " - This help." \n \n
append h " $scriptname bakehouse ?-k? ?-dirty-abort 1|0?" \n append h " $scriptname bakehouse ?-k? ?-dirty-abort 1|0?" \n
append h " - consumer run from a clean checkout: mints the packages (modules + libs) then bakes kit/zipkit executables to <projectdir>/bin" \n append h " - consumer run from a clean checkout: mints the packages (modules + libs) then bakes kit/zipkit executables to <projectdir>/bin" \n
@ -2656,7 +2655,7 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " - zig is OPTIONAL: packages/bake never require this step; without a suitable toolchain the state is" \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 " reported and build/test exit nonzero with fetch guidance (bin/punk-getzig.cmd)" \n \n
append h " $scriptname help ?subcommand? ?arg ...?" \n append h " $scriptname help ?subcommand? ?arg ...?" \n
append h " - show usage for $scriptname or one of its subcommands (equivalent: $scriptname <subcommand> -help);" \n append h " - show usage for $scriptname or one of its subcommands (the single help mechanism);" \n
append h " extra words are the subcommand's own command line, dry-run through its declaration - an accepted" \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 " 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 " usage error (tabled help only)" \n \n
@ -3079,7 +3078,9 @@ set scriptargs $::argv
# G-030: make.tcl dogfoods punk::args. # G-030: make.tcl dogfoods punk::args.
# Subcommands and their options are declared as punk::args definitions ((script)::punkboot and # Subcommands and their options are declared as punk::args definitions ((script)::punkboot and
# (script)::punkboot::<subcommand>) and parsed with punk::args::parse - 'make.tcl', # (script)::punkboot::<subcommand>) and parsed with punk::args::parse - 'make.tcl',
# 'make.tcl help ?subcommand?' and 'make.tcl <subcommand> -help' render tabled usage, and invalid # 'make.tcl help ?subcommand? ?arg ...?' renders tabled usage (the single help mechanism -
# help FLAGS like -help/--help are ordinary arguments since 2026-08-05: they fail whatever
# gate they meet, the usage error itself being documentation), and invalid
# arguments produce punk::args usage errors. # arguments produce punk::args usage errors.
# Degrade rule (hard requirement - see goals/archive/G-030-maketcl-punkargs.md): punk::args is # Degrade rule (hard requirement - see goals/archive/G-030-maketcl-punkargs.md): punk::args is
# guarded. When it (or a definition feature it needs) is unavailable from bootsupport, dispatch # guarded. When it (or a definition feature it needs) is unavailable from bootsupport, dispatch
@ -3309,14 +3310,18 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
update contract for keeping it in step with make.tcl behaviour." update contract for keeping it in step with make.tcl behaviour."
help help
" "
Show tabled usage for make.tcl as a whole, or for a single subcommand. Show tabled usage for make.tcl as a whole, or for a single
After the subject, accepts that subcommand's own command line and subcommand. This leading 'help' word is the SINGLE help
dry-runs it through the subcommand's declaration: a line the mechanism: there is no -help flag handling anywhere (a
subcommand would accept shows the matched action's usage - 'make.tcl -help/--help in a command line is an ordinary argument that
help tool build -test 0 punkzip' shows the tool build form, as does fails whatever gate it meets, and that usage error is itself
appending -help to the command line itself ('make.tcl tool build the documentation).
-test 0 punkzip -help') - while a line it would reject shows the After the subject, accepts that subcommand's own command line
same usage error the subcommand itself would produce (the error and 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 - while a line it would reject shows the same
usage error the subcommand itself would produce (the error
table carries the forms' synopsis, argument rows and per-form table carries the forms' synopsis, argument rows and per-form
reasons; exit 1). An accepted line is confirmed with a one-line reasons; exit 1). An accepted line is confirmed with a one-line
report of where each word landed ('kitname = punk91 -confirm 0' report of where each word landed ('kitname = punk91 -confirm 0'
@ -3803,11 +3808,12 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO
'bin' and 'modules' folders at the same level as 'src'. 'bin' and 'modules' folders at the same level as 'src'.
General usage: make.tcl <subcommand> ?flags? General usage: make.tcl <subcommand> ?flags?
'make.tcl help <subcommand> ?arg ...?' or 'make.tcl <subcommand> 'make.tcl help <subcommand> ?arg ...?' shows a subcommand's own
?arg ...? -help' shows a subcommand's own usage - the words are usage (the single help mechanism - there is no -help flag) -
dry-run through the subcommand's declaration, so an accepted the words after the subject are dry-run through the
command line shows the matched action's usage (tool, buildsuite) subcommand's declaration, so an accepted command line shows
and a rejected one shows that subcommand's own usage error. 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 Interactive y/n confirmations can be driven non-interactively
with the -confirm flag declared on the relevant subcommands.} with the -confirm flag declared on the relevant subcommands.}
punk::args::define { punk::args::define {
@ -3912,21 +3918,22 @@ set ::punkboot::opt_kitnames [list] ;#G-121: requested kit names for bake/bak
if {$::punkboot::punkargs_ok} { if {$::punkboot::punkargs_ok} {
set first [lindex $scriptargs 0] set first [lindex $scriptargs 0]
if {![llength $scriptargs] || $first in $::punkboot::help_flags} { if {![llength $scriptargs]} {
set do_help 1 set do_help 1
} else { } else {
#validate/resolve the subcommand word via the top-level definition #validate/resolve the subcommand word via the top-level definition
#(allows unambiguous prefixes; unknown subcommands get a punk::args usage error) #(allows unambiguous prefixes; unknown subcommands get a punk::args
#usage error - INCLUDING help flags like -help/--help since the
#2026-08-05 single-mechanism revision: the leading 'help' word and
#the bare invocation are the help surface, and a '-help' anywhere
#else is an ordinary argument that fails whatever gate it meets,
#with the usage error itself carrying the documentation)
if {[catch {punk::args::parse [list $first] -errorstyle $::punkboot::errstyle withid (script)::punkboot} argd]} { if {[catch {punk::args::parse [list $first] -errorstyle $::punkboot::errstyle withid (script)::punkboot} argd]} {
puts stderr $argd puts stderr $argd
exit 1 exit 1
} }
set subcommand [dict get $argd leaders subcommand] set subcommand [dict get $argd leaders subcommand]
set subargs [lrange $scriptargs 1 end] set subargs [lrange $scriptargs 1 end]
set wants_help 0
foreach h $::punkboot::help_flags {
if {$h in $subargs} {set wants_help 1 ; break}
}
if {$subcommand eq "help"} { if {$subcommand eq "help"} {
set do_help 1 set do_help 1
if {[llength $subargs]} { if {[llength $subargs]} {
@ -3943,17 +3950,6 @@ if {$::punkboot::punkargs_ok} {
set help_subject [dict get $argd values subject] set help_subject [dict get $argd values subject]
set help_words [lrange $subargs 1 end] set help_words [lrange $subargs 1 end]
} }
} elseif {$wants_help} {
set do_help 1
set help_subject $subcommand
#'<subcommand> ?arg ...? -help' - the same contract as
#'make.tcl help <subcommand> ?arg ...?': the remaining words are the
#subcommand's command line, with the help flag itself removed
#(wherever it appeared).
set help_words [list]
foreach w $subargs {
if {$w ni $::punkboot::help_flags} {lappend help_words $w}
}
} elseif {$subcommand eq "shell"} { } elseif {$subcommand eq "shell"} {
#declared passthrough: everything after 'shell' goes to the repl unparsed #declared passthrough: everything after 'shell' goes to the repl unparsed
set ::punkboot::command shell set ::punkboot::command shell
@ -4046,13 +4042,12 @@ if {$::punkboot::punkargs_ok} {
set ::punkboot::opt_confirm [expr {bool($v)}] set ::punkboot::opt_confirm [expr {bool($v)}]
} }
default { default {
if {$a in $::punkboot::help_flags} { #help flags are not special (2026-08-05 single-mechanism
set do_help 1 #revision): they land here as unknown flags, and the
} else { #resulting help display carries exit 1
puts stderr "Unknown flag: $a\n" puts stderr "Unknown flag: $a\n"
set do_help 1 set do_help 1
set help_exitcode 1 set help_exitcode 1
}
} }
} }
} else { } else {

21
src/modules/punk/args/moduledoc/punkboot-999999.0a1.0.tm

@ -116,11 +116,12 @@ tcl::namespace::eval punk::args::moduledoc::punkboot {
summary one-line summary shown beside the name in the summary one-line summary shown beside the name in the
top-level 'help' table top-level 'help' table
argsid punk::args definition id documenting the argsid punk::args definition id documenting the
subcommand: 'help <name>' and '<name> -help' subcommand: 'help <name>' renders it. Without an
render it. Without an argsid, '<name> -help' argsid, 'help <name>' shows the summary line.
stays FULL passthrough to the handler (the app (The launcher never intercepts a subcommand's own
may do its own -help) and 'help <name>' shows arguments - help flags included, 2026-08-05
the summary line. single-mechanism revision - so an app may do its
own -help handling regardless.)
package package to require (guarded) before resolving package package to require (guarded) before resolving
argsid - typically a moduledoc-style package argsid - typically a moduledoc-style package
carrying the definition carrying the definition
@ -139,14 +140,16 @@ tcl::namespace::eval punk::args::moduledoc::punkboot {
unknownfirst 'script' treats a non-subcommand first unknownfirst 'script' treats a non-subcommand first
argument that names an existing file (or a argument that names an existing file (or a
lib:* scriptlib reference) as a script lib:* scriptlib reference) as a script
invocation and refuses anything else with invocation, runs a first argument of exactly
'-e' as the G-077 one-liner, and refuses
anything else - help flags included - with
usage (default; the refusal is G-032 and usage (default; the refusal is G-032 and
degrades to always-treat-as-script without degrades to always-treat-as-script without
punk::args), or the name of a known punk::args), or the name of a known
subcommand to receive the whole arglist subcommand to receive the whole arglist
(tool-style kits - these also keep help (tool-style kits; in every kit the 'help'
FLAGS routed to that subcommand; the 'help' word is the launch-surface help entry -
word remains the launch-surface entry) help FLAGS are never intercepted)
All dicts default to empty: an undeclared thin main yields All dicts default to empty: an undeclared thin main yields
exactly the punkshell launch behaviour." exactly the punkshell launch behaviour."

3
src/modules/punk/args/moduledoc/punkboot-buildversion.txt

@ -1,5 +1,6 @@
0.2.0 0.3.0
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.3.0 - single-mechanism revision (2026-08-05, user direction): help-flag interception withdrawn from the launcher - registration argsid text now names 'help <name>' as the only launcher-rendered route (apps own their -help handling); unknownfirst text covers the G-077 '-e' one-liner and states help flags are never intercepted
#0.2.0 - G-032 registration surface extension documented: project_subcommand_info (summary/argsid/package/parse) in the registration definition + thin-main obligations; built-in list gains help; launcher help/flag semantics (unknownfirst=script gate, argsid-gated -help interception) recorded #0.2.0 - G-032 registration surface extension documented: project_subcommand_info (summary/argsid/package/parse) in the registration definition + thin-main obligations; built-in list gains help; launcher help/flag semantics (unknownfirst=script gate, argsid-gated -help interception) recorded
#0.1.0 - initial (G-031 runtime-queryable boot-core API doc): (script)::punkboot overview + thin-main obligations, (script)::punkboot::registration (project_subcommands / launch_defaults), live definitions for the persistent ::punkboot procs (zipfs_mount_pairs, zipfs_kit_mountbase, path_within, proj_root_find); boot_state surface documented #0.1.0 - initial (G-031 runtime-queryable boot-core API doc): (script)::punkboot overview + thin-main obligations, (script)::punkboot::registration (project_subcommands / launch_defaults), live definitions for the persistent ::punkboot procs (zipfs_mount_pairs, zipfs_kit_mountbase, path_within, proj_root_find); boot_state surface documented

119
src/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm

@ -38,8 +38,9 @@
#[para] buildinfo, help (G-118 item 10). The launcher help/parse wiring (G-032) #[para] buildinfo, help (G-118 item 10). The launcher help/parse wiring (G-032)
#[para] renders and parses from them at dispatch time: the boot core's help #[para] renders and parses from them at dispatch time: the boot core's help
#[para] surface clones (script)::punkexe / (script)::punkexe::help per kit with #[para] surface clones (script)::punkexe / (script)::punkexe::help per kit with
#[para] the live subcommand set, and per-subcommand ids back '<subcommand> #[para] the live subcommand set, and per-subcommand ids back the 'help
#[para] -help' and the declared argument gates. Id convention follows the #[para] <subcommand>' renders and the declared argument gates (the single help
#[para] mechanism - -help flag forms withdrawn 2026-08-05). Id convention follows the
#[para] make.tcl precedent for script-level surfaces: (script)::punkexe and #[para] make.tcl precedent for script-level surfaces: (script)::punkexe and
#[para] (script)::punkexe::<subcommand>, with explicit @form -synopsis overrides #[para] (script)::punkexe::<subcommand>, with explicit @form -synopsis overrides
#[para] since a constructed id is not an invocable command name. #[para] since a constructed id is not an invocable command name.
@ -115,20 +116,24 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
the refusal degrades to the historic treat-as-script the refusal degrades to the historic treat-as-script
behaviour when punk::args is unavailable in the kit/context). behaviour when punk::args is unavailable in the kit/context).
Help (G-032): 'help' or '-help' as the first non-mode Help (G-032; single mechanism since 2026-08-05): 'help' as
argument renders this usage; 'help <subcommand>' or the first non-mode argument renders this usage;
'<subcommand> -help' renders a subcommand's own usage 'help <subcommand> ?arg ...?' renders a subcommand's own
(see the 'help' choice below for the carve-outs). A kit usage - words after the subject are accepted and ignored,
whose thin main routes unknown first arguments to a project so 'help' can simply be prepended to an existing command
subcommand (a tool-style kit) keeps routing help FLAGS to line. There is deliberately NO -help flag handling: a
that subcommand - its launch surface stays reachable via the leading '-help' falls under the unknown-first-argument rule
'help' subcommand word. Project-registered subcommands above (its refusal shows this usage on stderr), and
appear in the rendered table by registration alone, with '-help' after a subcommand is that subcommand's own
the summaries and definition ids their registrations argument (tclsh keeps full stock dash parity; shellspy's
declare (see punk::args::moduledoc::punkboot's registration processor handles -help/--help/help itself).
definition). These built-in launch behaviours are subject Project-registered subcommands appear in the rendered table
to the thin main's declared launch defaults - the rendered by registration alone, with the summaries and definition
per-kit table is the authority for a given executable." ids their registrations declare (see
punk::args::moduledoc::punkboot's registration definition).
These built-in launch behaviours are subject to the thin
main's declared launch defaults - the rendered per-kit
table is the authority for a given executable."
@form -synopsis "<punkexe> ?packagemode? ?subcommand? ?arg ...?" @form -synopsis "<punkexe> ?packagemode? ?subcommand? ?arg ...?"
@leaders -min 0 -max 2 @leaders -min 0 -max 2
packagemode -type string -optional 1 -default internal -help -& packagemode -type string -optional 1 -default internal -help -&
@ -212,10 +217,10 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
lives on the punk-level launch surfaces instead (G-077): use lives on the punk-level launch surfaces instead (G-077): use
'<punkexe> -e <script> ?arg ...?' or '<punkexe> script -e '<punkexe> -e <script> ?arg ...?' or '<punkexe> script -e
<script> ?arg ...?', never this subcommand. <script> ?arg ...?', never this subcommand.
G-032 help carve-out (the single deliberate exception): a help The parity has no help-flag exception (the 2026-08-05
flag (-help/--help/-h//?) as the SOLE argument renders this single-mechanism revision withdrew the earlier sole-argument
usage and exits 0; the same flag accompanied by ANY further '-help' carve-out): use '<punkexe> help tclsh' for this
argument keeps the stock all-args-to-::argv semantics above. usage.
A scriptfile of the form 'lib:name' is refused with a pointer to A scriptfile of the form 'lib:name' is refused with a pointer to
the 'script' subcommand: scriptlib resolution is a punk the 'script' subcommand: scriptlib resolution is a punk
@ -299,10 +304,10 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
#Sibling subcommands: summary-level contracts. These are declared #Sibling subcommands: summary-level contracts. These are declared
#PASSTHROUGH surfaces (G-032 decision): their arguments belong to the #PASSTHROUGH surfaces (G-032 decision): their arguments belong to the
#script/app they launch, so dispatch never parses them - only the #script/app they launch, so dispatch never parses NOR intercepts them
#first-argument help flag is intercepted (see each -help body). #(-help flag interception withdrawn 2026-08-05 - 'help <subcommand>'
#buildinfo below is the dispatch-parsed subcommand (exact @values #is the help route). buildinfo below is the dispatch-parsed
#contract enforced through punk::args). #subcommand (exact @values contract enforced through punk::args).
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list { lappend PUNKARGS [list {
@id -id (script)::punkexe::script @id -id (script)::punkexe::script
@ -336,9 +341,9 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
same one-liner is available at the top level as same one-liner is available at the top level as
'<punkexe> -e <script> ?arg ...?'. '<punkexe> -e <script> ?arg ...?'.
A help flag (-help/--help/-h//?) as the FIRST argument renders Help flags are ordinary script arguments in every position
this usage and exits 0 (G-032); in any later position it is an (no interception - 2026-08-05 single-mechanism revision):
ordinary script argument." use '<punkexe> help script' for this usage."
@form -form {file} -synopsis "<punkexe> script ?scriptfile|lib:name? ?arg ...?" @form -form {file} -synopsis "<punkexe> script ?scriptfile|lib:name? ?arg ...?"
@values -min 0 -max -1 @values -min 0 -max -1
scriptfile -type string -optional 1 -help -& scriptfile -type string -optional 1 -help -&
@ -366,8 +371,8 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
arguments, a script can be run first with the interactive arguments, a script can be run first with the interactive
shell maintained afterwards. This is also the default when the shell maintained afterwards. This is also the default when the
executable is launched with no arguments at all. executable is launched with no arguments at all.
A help flag (-help etc) as the FIRST argument renders this All arguments pass through untouched; use '<punkexe> help
usage and exits 0 (G-032)." shell' for this usage."
@form -synopsis "<punkexe> shell ?arg ...?" @form -synopsis "<punkexe> shell ?arg ...?"
@values -min 0 -max -1 @values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -& arg -type any -optional 1 -multiple 1 -help -&
@ -386,8 +391,8 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
stdin are supported (e.g: echo 'puts hello' | <punkexe> punk) stdin are supported (e.g: echo 'puts hello' | <punkexe> punk)
and stderr is kept clean of launch chatter so exec-style and stderr is kept clean of launch chatter so exec-style
callers do not misreport failure. callers do not misreport failure.
A help flag (-help etc) as the FIRST argument renders this All arguments pass through untouched; use '<punkexe> help
usage and exits 0 (G-032)." punk' for this usage."
@form -synopsis "<punkexe> punk ?arg ...?" @form -synopsis "<punkexe> punk ?arg ...?"
@values -min 0 -max -1 @values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -& arg -type any -optional 1 -multiple 1 -help -&
@ -401,11 +406,10 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
"Experimental shellspy command-line processor." -& "Experimental shellspy command-line processor." -&
-help -& -help -&
"Pass all arguments to the experimental shellspy command-line "Pass all arguments to the experimental shellspy command-line
processor (app-shellspy). Note: 'shellspy' is only this launch processor (app-shellspy) - including help flags: the
subcommand - it is not the project name (the project is processor handles -help/--help/help itself. Note: 'shellspy'
punkshell). is only this launch subcommand - it is not the project name
A help flag (-help etc) as the FIRST argument renders this (the project is punkshell)."
usage and exits 0 (G-032)."
@form -synopsis "<punkexe> shellspy ?arg ...?" @form -synopsis "<punkexe> shellspy ?arg ...?"
@values -min 0 -max -1 @values -min 0 -max -1
arg -type any -optional 1 -multiple 1 -help -& arg -type any -optional 1 -multiple 1 -help -&
@ -443,11 +447,10 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
stamp_present=0 marks an unstamped kit; fields of absent stamp_present=0 marks an unstamped kit; fields of absent
notions are omitted rather than invented. notions are omitted rather than invented.
Arguments are refused: exit 2 with a one-line refusal plus Arguments are refused - help flags included: exit 2 with a
this usage as a punk::args error table on stderr (G-032; the one-line refusal plus this usage as a punk::args error table
table degrades away when punk::args is unavailable). A help on stderr (G-032; the table degrades away when punk::args is
flag (-help etc) as the first argument renders this usage and unavailable). Use '<punkexe> help buildinfo' for this usage.
exits 0.
In-process equivalent (no exec): the 'buildinfo' shell command In-process equivalent (no exec): the 'buildinfo' shell command
/ punk::buildinfo::report." / punk::buildinfo::report."
@ -468,25 +471,26 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
-summary -& -summary -&
"Show usage for the launch surface or one of its subcommands (G-032)." -& "Show usage for the launch surface or one of its subcommands (G-032)." -&
-help -& -help -&
"Render launch usage on stdout and exit 0 (G-032). "Render launch usage on stdout and exit 0 (G-032). This is
the launcher's SINGLE help mechanism (the punkshell
'i <cmd> ...' idiom; -help flag forms were withdrawn
2026-08-05 - help flags are never intercepted anywhere in
the dispatch).
With no argument: the top-level table - the package modes and With no argument: the top-level table - the package modes and
every subcommand this kit's launch surface carries (the every subcommand this kit's launch surface carries (the
built-in family plus project-registered subcommands, each built-in family plus project-registered subcommands, each
with its one-line summary). with its one-line summary).
With a subcommand argument: that subcommand's own usage. For With a subcommand argument: that subcommand's own usage.
a project-registered subcommand this is the punk::args Words after the subject are accepted and ignored, so 'help'
definition id its registration declares; a registration can simply be prepended to an existing command line
without one gets its registered summary line (such a ('<punkexe> help tclsh somefile somearg' shows the tclsh
subcommand may implement its own -help handling - the usage). For a project-registered subcommand the rendered
launcher does not intercept its arguments). usage is the punk::args definition id its registration
declares; a registration without one gets its registered
'<punkexe> -help' renders the same top-level table on kits summary line (such a subcommand may implement its own -help
whose unknown-first-argument default is 'script'; handling - the launcher does not touch its arguments).
'<punkexe> <subcommand> -help' renders the subcommand usage
(see the top-level definition for the tool-style-kit and
tclsh carve-outs).
Degradation (G-030 doctrine): when punk::args, these Degradation (G-030 doctrine): when punk::args, these
definitions, or the rendering stack are unavailable in the definitions, or the rendering stack are unavailable in the
@ -497,12 +501,15 @@ tcl::namespace::eval punk::args::moduledoc::punkexe {
The subject choices below are the built-in family; in a live The subject choices below are the built-in family; in a live
kit the valid (and prefix-resolvable) choices are that kit's kit the valid (and prefix-resolvable) choices are that kit's
actual subcommand set." actual subcommand set."
@form -synopsis "<punkexe> help ?subcommand?" @form -synopsis "<punkexe> help ?subcommand? ?arg ...?"
@values -min 0 -max 1 @values -min 0 -max -1
subject -type string -optional 1 -choices {tclsh script shell punk shellspy buildinfo help} -help -& subject -type string -optional 1 -choices {tclsh script shell punk shellspy buildinfo help} -help -&
"Subcommand to show usage for. In a kit the valid choices are "Subcommand to show usage for. In a kit the valid choices are
the live launch surface: the built-ins plus any the live launch surface: the built-ins plus any
project-registered subcommands." project-registered subcommands."
arg -type any -optional 1 -multiple 1 -help -&
"Accepted and ignored (reserved): the subject's own command
line may be left in place when prepending 'help'."
}] }]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
} }

3
src/modules/punk/args/moduledoc/punkexe-buildversion.txt

@ -1,6 +1,7 @@
0.5.0 0.6.0
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.
#0.6.0 - single-mechanism revision (2026-08-05, user direction): all -help flag documentation withdrawn - 'help ?subcommand? ?arg ...?' is the sole help entry (trailing words accepted/ignored - the prepend-help idiom); (script)::punkexe::help gains the arg row + max -1; tclsh parity stated exception-free; per-subcommand -help paragraphs replaced with 'help <sub>' pointers; buildinfo refuses help flags like any argument
#0.5.0 - G-077 -e one-liner forms documented: (script)::punkexe::script gains the oneliner @form + -help paragraph; top-level (script)::punkexe documents the '-e' first-argument reclassification and gains its oneliner @form; (script)::punkexe::tclsh boundary text now points at the live forms #0.5.0 - G-077 -e one-liner forms documented: (script)::punkexe::script gains the oneliner @form + -help paragraph; top-level (script)::punkexe documents the '-e' first-argument reclassification and gains its oneliner @form; (script)::punkexe::tclsh boundary text now points at the live forms
#0.4.0 - G-032 launcher help/parse wiring live: help subcommand documented ((script)::punkexe::help + choices entry); top-level unknown-first-arg refusal rule + help discovery documented; per-subcommand first-arg -help notes (tclsh sole-arg carve-out); buildinfo argument-refusal shape; passthrough-vs-parsed decision recorded #0.4.0 - G-032 launcher help/parse wiring live: help subcommand documented ((script)::punkexe::help + choices entry); top-level unknown-first-arg refusal rule + help discovery documented; per-subcommand first-arg -help notes (tclsh sole-arg carve-out); buildinfo argument-refusal shape; passthrough-vs-parsed decision recorded
#0.3.0 - buildinfo subcommand documented (G-025): (script)::punkexe::buildinfo definition + choices entry; stamp/live/source distinction + in-shell equivalent noted #0.3.0 - buildinfo subcommand documented (G-025): (script)::punkexe::buildinfo definition + choices entry; stamp/live/source distinction + in-shell equivalent noted

16
src/project_layouts/vendor/punk/project-0.1/src/vfs/_config/project_main.tcl vendored

@ -16,8 +16,9 @@
# punk::args::moduledoc::punkexe package for the documented launch surface), # punk::args::moduledoc::punkexe package for the documented launch surface),
# module and library path setup, punk::libunknown, and the default dispatch # module and library path setup, punk::libunknown, and the default dispatch
# (tclsh/shellspy/punk/shell/script/buildinfo/help) with its G-032 help # (tclsh/shellspy/punk/shell/script/buildinfo/help) with its G-032 help
# surface ('<kit> help', '-help', '<subcommand> -help' - tabled usage # surface ('<kit> help ?subcommand? ?arg ...?' - the single help mechanism:
# listing your registered subcommands, degrading to a plain list). # tabled usage listing your registered subcommands, degrading to a plain
# list; help FLAGS are never intercepted).
# #
#WHAT TO EDIT #WHAT TO EDIT
# Only the ==CUSTOMIZE== blocks below. Everything else is contract plumbing. # Only the ==CUSTOMIZE== blocks below. Everything else is contract plumbing.
@ -58,11 +59,12 @@ apply { args {
namespace eval ::punkboot {variable project_subcommands [dict create]} namespace eval ::punkboot {variable project_subcommands [dict create]}
#Optional per-name help/parse metadata (G-032): with this declared, your #Optional per-name help/parse metadata (G-032): with this declared, your
#subcommands appear in '<kit> help' with summaries, 'help <name>' / #subcommands appear in '<kit> help' with summaries, 'help <name>' renders
#'<name> -help' render your punk::args definition, and parse 1 validates #your punk::args definition, and parse 1 validates arguments through it
#arguments through it before your handler runs (usage errors tabled, #before your handler runs (usage errors tabled, exit 1). All keys
#exit 1). All keys optional; a name without an entry keeps full #optional; a name without an entry keeps full passthrough semantics (the
#passthrough semantics. Contract detail: punk::args::moduledoc::punkboot #launcher never touches a subcommand's own arguments - help flags
#included). Contract detail: punk::args::moduledoc::punkboot
#registration definition. #registration definition.
# namespace eval ::punkboot {variable project_subcommand_info [dict create \ # namespace eval ::punkboot {variable project_subcommand_info [dict create \
# mytool {summary "Frob the widgets in a directory." \ # mytool {summary "Frob the widgets in a directory." \

200
src/project_layouts/vendor/punk/project-0.1/src/vfs/_config/punkboot_core.tcl vendored

@ -11,13 +11,17 @@
#package_mode parsing (minted|os|internal|src with the proj: scope prefix, #package_mode parsing (minted|os|internal|src with the proj: scope prefix,
#G-033), module-path and auto_path assembly, punk::libunknown, src-mode #G-033), module-path and auto_path assembly, punk::libunknown, src-mode
##modpod registration, then the default subcommand dispatch ##modpod registration, then the default subcommand dispatch
#(tclsh/shellspy/punk/shell/script/buildinfo/help) extended by the thin #(tclsh/shellspy/punk/shell/script/buildinfo/help plus the G-077 top-level
#'-e' one-liner reclassification to 'script -e') extended by the thin
#main's declared project subcommands (G-031 registration model - see the #main's declared project subcommands (G-031 registration model - see the
#dispatch section near the end of this file). The launch surface documents #dispatch section near the end of this file). The launch surface documents
#itself via punk::args (G-032): '<punkexe> help ?subcommand?', '-help' and #itself via punk::args (G-032; -help flag forms withdrawn 2026-08-05):
#'<subcommand> -help' render tabled usage from the moduledoc::punkexe #'<punkexe> help ?subcommand? ?arg ...?' - the single help mechanism -
#definitions plus the live registrations, degrading to a plain subcommand #renders tabled usage from the moduledoc::punkexe definitions plus the live
#list when that stack is unavailable. #registrations, degrading to a plain subcommand list when that stack is
#unavailable. Help flags (-help etc) are never intercepted: a leading one
#falls under the unknown-first-argument refusal (which shows usage on
#stderr), and after a subcommand they are the subcommand's own arguments.
# #
#The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #The package_mode surface (documented in full by punk::args::moduledoc::punkexe):
#an optional FIRST launch argument of the form minted|os|internal|src or any #an optional FIRST launch argument of the form minted|os|internal|src or any
@ -51,7 +55,7 @@
namespace eval ::punkboot { namespace eval ::punkboot {
#boot-core identity (G-031; stamped into kits + reported by the G-025 #boot-core identity (G-031; stamped into kits + reported by the G-025
#buildinfo surfaces) #buildinfo surfaces)
variable core_version 0.3.0 variable core_version 0.5.0
} }
if {![info exists ::punkboot::launch_args]} { if {![info exists ::punkboot::launch_args]} {
namespace eval ::punkboot [list variable launch_args $::argv] namespace eval ::punkboot [list variable launch_args $::argv]
@ -1522,23 +1526,25 @@ if {![info exists ::punkboot::launch_defaults]} {
namespace eval ::punkboot [list variable boot_state $boot_state_dict] namespace eval ::punkboot [list variable boot_state $boot_state_dict]
#--------------------------------------------------------------- #---------------------------------------------------------------
#Launcher help + declared-parse machinery (G-032) #Launcher help + declared-parse machinery (G-032; -help flag forms
#withdrawn 2026-08-05 by user direction - single mechanism)
#The launch surface documents itself through punk::args (definition ids #The launch surface documents itself through punk::args (definition ids
#from the punk::args::moduledoc::punkexe family): #from the punk::args::moduledoc::punkexe family) via ONE mechanism, the
# <punkexe> help ?subcommand? tabled usage; the top-level table lists #leading 'help' word (the punkshell 'i <cmd> ...' idiom):
# built-ins AND project-registered # <punkexe> help tabled top-level usage; the table
# subcommands with summaries # lists built-ins AND project-registered
# <punkexe> -help same as bare 'help' - only on kits whose # subcommands with summaries
# unknownfirst default is 'script' (a # <punkexe> help <subcommand> ?arg ...?
# tool-style kit routes flags to its own # the subcommand's tabled usage; words
# processor; its surface stays reachable # after the subject are accepted and
# via the 'help' word) # ignored, so 'help' can be prepended
# <punkexe> <subcommand> -help the subcommand's tabled usage when -help # to any existing command line
# is its FIRST argument (tclsh: only when #There is deliberately NO -help/--help flag handling anywhere in the
# -help is the SOLE argument - stock #dispatch: a leading help-flag word falls under the unknown-first-argument
# parity keeps every other dash form in #rule (whose refusal shows this usage anyway, stderr exit 1), and
# ::argv; project subcommands: only when #'<subcommand> -help' belongs to the subcommand's own arguments (full
# the registration declares an argsid) #passthrough - e.g app-shellspy handles -help/--help/help itself; a
#parse-declared subcommand rejects it like any undeclared flag).
#Everything is guarded and lazy (the G-030 degradation doctrine): nothing #Everything is guarded and lazy (the G-030 degradation doctrine): nothing
#here loads punk::args on a normal boot/dispatch path. When punk::args or #here loads punk::args on a normal boot/dispatch path. When punk::args or
#the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same
@ -1553,7 +1559,6 @@ if {![info exists ::punkboot::launch_defaults]} {
#(script)::punkexe::help for subject validation. #(script)::punkexe::help for subject validation.
#--------------------------------------------------------------- #---------------------------------------------------------------
namespace eval ::punkboot { namespace eval ::punkboot {
variable help_flags [list -help --help -h /?]
variable launch_builtin_subcommands [list] variable launch_builtin_subcommands [list]
variable launch_subcommands [list] variable launch_subcommands [list]
variable launcher_punkargs_state "" ;#"" unprobed | ok | plain variable launcher_punkargs_state "" ;#"" unprobed | ok | plain
@ -1679,8 +1684,8 @@ if {![info exists ::punkboot::launch_defaults]} {
proc launcher_helpdef_id {} { proc launcher_helpdef_id {} {
#define (script)::punkexe.launcherhelp: the moduledoc #define (script)::punkexe.launcherhelp: the moduledoc
#(script)::punkexe::help definition with the subject choices #(script)::punkexe::help definition with the subject choices
#replaced by the live subcommand set, so 'help <name>' validates #replaced by the live subcommand set - the DISPLAY clone rendered
#(and prefix-resolves) against what this kit actually offers. #by 'help help'.
set id (script)::punkexe.launcherhelp set id (script)::punkexe.launcherhelp
set existing "" set existing ""
catch {set existing [punk::args::raw_def $id]} catch {set existing [punk::args::raw_def $id]}
@ -1698,11 +1703,37 @@ if {![info exists ::punkboot::launch_defaults]} {
} }
return $id return $id
} }
proc launcher_helpsubjectdef_id {} {
#define (script)::punkexe.launcherhelpsubject: the VALIDATION
#clone the help arm parses the subject word against - subject
#REQUIRED with the live choices, and the display definition's
#ignored trailing-arg row suppressed (-antiglobs). Without this
#the display shape lets punk::args SKIP a mistyped optional
#subject into the unconstrained arg row, silently rendering the
#top-level table where a tabled choice error is wanted.
set id (script)::punkexe.launcherhelpsubject
set existing ""
catch {set existing [punk::args::raw_def $id]}
if {$existing ne ""} {
return $id
}
if {[catch {
set ov [dict create \
@id [list -id $id] \
subject [list -optional 0 -choices [launcher_live_choices]] \
]
punk::args::define [punk::args::resolved_def -antiglobs {arg} -override $ov (script)::punkexe::help]
}]} {
return ""
}
return $id
}
proc launcher_plain_help {} { proc launcher_plain_help {} {
#degraded help: plain subcommand list built without punk::args #degraded help: plain subcommand list built without punk::args
set exebase [file rootname [file tail [info nameofexecutable]]] set exebase [file rootname [file tail [info nameofexecutable]]]
set lines [list] set lines [list]
lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?" lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?"
lappend lines " $exebase ?packagemode? -e <script> ?arg ...? (one-liner - same as 'script -e', G-077)"
lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal)," lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal),"
lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)" lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)"
lappend lines " subcommands:" lappend lines " subcommands:"
@ -1793,6 +1824,7 @@ if {![info exists ::punkboot::launch_defaults]} {
puts stderr "known subcommands: [join [launcher_plain_choices] {, }]" puts stderr "known subcommands: [join [launcher_plain_choices] {, }]"
} }
puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation" puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation"
puts stderr "for a one-liner use '$exebase -e <script> ?arg ...?' (equivalently '$exebase script -e <script> ?arg ...?')"
return 1 return 1
} }
} }
@ -1838,53 +1870,44 @@ if {![info exists ::punkboot::launch_defaults]} {
set subcommand [lindex $arglist 0] set subcommand [lindex $arglist 0]
if {$subcommand in $known_subcommands} { if {$subcommand in $known_subcommands} {
set subcommand_arglist [lrange $arglist 1 end] set subcommand_arglist [lrange $arglist 1 end]
#G-032: a help flag as the subcommand's FIRST argument renders that #G-032 (revised 2026-08-05): a subcommand's arguments are entirely its
#subcommand's usage. tclsh: only when it is the SOLE argument (stock #own - there is no first-argument help-flag interception. Use
#parity keeps every other leading-dash form - including '-help' with #'<punkexe> help <subcommand>' for a subcommand's usage; '-help' after
#further arguments - in ::argv); project subcommands: only when the #a subcommand reaches the app/script unchanged (tclsh keeps full stock
#registration declares an argsid (a handler-only registration keeps #dash parity; app-shellspy handles -help/--help/help itself; a
#full passthrough - the application may do its own -help handling). #parse-declared subcommand rejects it like any undeclared flag).
if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} {
set do_subhelp 0
if {$subcommand eq "tclsh"} {
if {[llength $subcommand_arglist] == 1} {
set do_subhelp 1
}
} elseif {$subcommand in $builtin_subcommands} {
set do_subhelp 1
} elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} {
set do_subhelp 1
}
if {$do_subhelp} {
::punkboot::launcher_show_help $subcommand
exit 0
}
}
} else { } else {
set subcommand_arglist $arglist set subcommand_arglist $arglist
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
#G-032: a help flag in first position is a launch-surface help #G-077: a first argument of exactly '-e' is the one-liner form -
#request on kits whose unknown-first default is 'script'. #reclassify to 'script' with the WHOLE arglist ('-e' included) as
#Tool-style kits (unknownfirst names a project subcommand) keep #its arguments, so the script subcommand's own '-e' handling runs
#routing flags to their processor - their surface stays #it. This sits ahead of the unknown-first-arg refusal below, which
#discoverable via the 'help' subcommand word. #would otherwise reject '-e' as naming no existing file. Only
if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} { #script-default kits intercept it - a tool-style kit keeps '-e'
::punkboot::launcher_show_help #routed to its own processor, and '<punkexe> tclsh -e ...' keeps
exit 0 #stock parity (the tclsh subcommand never reaches this path).
} #A leading help FLAG (-help etc) is deliberately NOT special
set subcommand $default_unknownfirst #(2026-08-05 single-mechanism revision): on script-default kits it
if {$subcommand eq "script"} { #falls to the refusal below, whose stderr usage answers it anyway;
#G-032 unknown-first-arg reclassification rule (recorded in #tool-style kits route it to their processor.
#goals/G-032-launcher-punkargs.md): reclassify to 'script' if {$subcommand eq "-e" && $default_unknownfirst eq "script"} {
#only when the argument plausibly names a script - an set subcommand script
#existing file path, or a lib:* scriptlib reference. Anything } else {
#else is refused with usage on stderr (exit 1); when set subcommand $default_unknownfirst
#punk::args is unavailable the refusal degrades to the if {$subcommand eq "script"} {
#historic always-reclassify behaviour. #G-032 unknown-first-arg reclassification rule (recorded in
set unknown_word [lindex $subcommand_arglist 0] #goals/G-032-launcher-punkargs.md): reclassify to 'script'
if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} { #only when the argument plausibly names a script - an
if {[::punkboot::launcher_unknown_first_error $unknown_word]} { #existing file path, or a lib:* scriptlib reference. Anything
exit 1 #else is refused with usage on stderr (exit 1); when
#punk::args is unavailable the refusal degrades to the
#historic always-reclassify behaviour.
set unknown_word [lindex $subcommand_arglist 0]
if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} {
if {[::punkboot::launcher_unknown_first_error $unknown_word]} {
exit 1
}
} }
} }
} }
@ -2068,11 +2091,11 @@ if {![info exists ::punkboot::launch_defaults]} {
#exit code; no repl fallthrough (G-015-compatible). #exit code; no repl fallthrough (G-015-compatible).
#G-032: the argument contract ((script)::punkexe::buildinfo, #G-032: the argument contract ((script)::punkexe::buildinfo,
#@values -min 0 -max 0) is enforced through punk::args when #@values -min 0 -max 0) is enforced through punk::args when
#available: any argument earns the historic one-line refusal PLUS #available: any argument (including '-help' - no flag
#the tabled usage error on stderr, exit 2 (buildinfo.test pins the #interception, 2026-08-05 revision) earns the historic one-line
#one-liner and the exit code). The empty-argument fast path never #refusal PLUS the tabled usage error on stderr, exit 2
#touches punk::args; a '-help' first argument was already #(buildinfo.test pins the one-liner and the exit code). The
#intercepted at selection. #empty-argument fast path never touches punk::args.
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
puts stderr "buildinfo: takes no arguments" puts stderr "buildinfo: takes no arguments"
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
@ -2089,9 +2112,14 @@ if {![info exists ::punkboot::launch_defaults]} {
exit [punk::buildinfo::main] exit [punk::buildinfo::main]
} }
help { help {
#G-032: launcher help surface. Bare 'help' renders the top-level #G-032: launcher help surface - THE single help mechanism
#tabled usage (built-ins + project-registered subcommands with #(2026-08-05 revision; the punkshell 'i <cmd> ...' idiom). Bare
#summaries); 'help <subcommand>' renders that subcommand's usage. #'help' renders the top-level tabled usage (built-ins +
#project-registered subcommands with summaries);
#'help <subcommand> ?arg ...?' renders that subcommand's usage -
#words after the subject are accepted and ignored, so 'help' can
#be prepended to any existing command line
#('punk9_beta help tclsh somefile somearg' shows the tclsh usage).
#Subject validation parses through the launcherhelp clone when #Subject validation parses through the launcherhelp clone when
#punk::args is available (tabled choice errors on stderr exit 1, #punk::args is available (tabled choice errors on stderr exit 1,
#unambiguous prefixes resolve); degraded mode accepts exact names #unambiguous prefixes resolve); degraded mode accepts exact names
@ -2102,9 +2130,16 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
set helpdef_id [::punkboot::launcher_helpdef_id] set helpdef_id [::punkboot::launcher_helpsubjectdef_id]
if {$helpdef_id ne ""} { if {$helpdef_id ne ""} {
if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { #validate/resolve ONLY the subject word (the make.tcl help
#precedent), against the required-subject validation clone:
#a mistyped subject earns the tabled choice error (exit 1)
#instead of being skipped past to render the top-level
#table; everything after the subject is deliberately
#unparsed (accepted and ignored - it is the subject's own
#command line, any mix of its words).
if {[catch {punk::args::parse [lrange $subcommand_arglist 0 0] -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} {
puts stderr $argd puts stderr $argd
exit 1 exit 1
} }
@ -2114,10 +2149,11 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
} }
#degraded: exact-name subject only #degraded: exact-name subject only; trailing words ignored as in
#the parsed path
set help_subject [lindex $subcommand_arglist 0] set help_subject [lindex $subcommand_arglist 0]
if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { if {$help_subject ni $::punkboot::launch_subcommands} {
puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" puts stderr "help: expected 'help ?subcommand? ?arg ...?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]"
exit 1 exit 1
} }
::punkboot::launcher_show_help $help_subject ::punkboot::launcher_show_help $help_subject

200
src/project_layouts/vendor/punk/project-0.1/src/vfs/_vfscommon.vfs/punkboot/core.tcl vendored

@ -11,13 +11,17 @@
#package_mode parsing (minted|os|internal|src with the proj: scope prefix, #package_mode parsing (minted|os|internal|src with the proj: scope prefix,
#G-033), module-path and auto_path assembly, punk::libunknown, src-mode #G-033), module-path and auto_path assembly, punk::libunknown, src-mode
##modpod registration, then the default subcommand dispatch ##modpod registration, then the default subcommand dispatch
#(tclsh/shellspy/punk/shell/script/buildinfo/help) extended by the thin #(tclsh/shellspy/punk/shell/script/buildinfo/help plus the G-077 top-level
#'-e' one-liner reclassification to 'script -e') extended by the thin
#main's declared project subcommands (G-031 registration model - see the #main's declared project subcommands (G-031 registration model - see the
#dispatch section near the end of this file). The launch surface documents #dispatch section near the end of this file). The launch surface documents
#itself via punk::args (G-032): '<punkexe> help ?subcommand?', '-help' and #itself via punk::args (G-032; -help flag forms withdrawn 2026-08-05):
#'<subcommand> -help' render tabled usage from the moduledoc::punkexe #'<punkexe> help ?subcommand? ?arg ...?' - the single help mechanism -
#definitions plus the live registrations, degrading to a plain subcommand #renders tabled usage from the moduledoc::punkexe definitions plus the live
#list when that stack is unavailable. #registrations, degrading to a plain subcommand list when that stack is
#unavailable. Help flags (-help etc) are never intercepted: a leading one
#falls under the unknown-first-argument refusal (which shows usage on
#stderr), and after a subcommand they are the subcommand's own arguments.
# #
#The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #The package_mode surface (documented in full by punk::args::moduledoc::punkexe):
#an optional FIRST launch argument of the form minted|os|internal|src or any #an optional FIRST launch argument of the form minted|os|internal|src or any
@ -51,7 +55,7 @@
namespace eval ::punkboot { namespace eval ::punkboot {
#boot-core identity (G-031; stamped into kits + reported by the G-025 #boot-core identity (G-031; stamped into kits + reported by the G-025
#buildinfo surfaces) #buildinfo surfaces)
variable core_version 0.3.0 variable core_version 0.5.0
} }
if {![info exists ::punkboot::launch_args]} { if {![info exists ::punkboot::launch_args]} {
namespace eval ::punkboot [list variable launch_args $::argv] namespace eval ::punkboot [list variable launch_args $::argv]
@ -1522,23 +1526,25 @@ if {![info exists ::punkboot::launch_defaults]} {
namespace eval ::punkboot [list variable boot_state $boot_state_dict] namespace eval ::punkboot [list variable boot_state $boot_state_dict]
#--------------------------------------------------------------- #---------------------------------------------------------------
#Launcher help + declared-parse machinery (G-032) #Launcher help + declared-parse machinery (G-032; -help flag forms
#withdrawn 2026-08-05 by user direction - single mechanism)
#The launch surface documents itself through punk::args (definition ids #The launch surface documents itself through punk::args (definition ids
#from the punk::args::moduledoc::punkexe family): #from the punk::args::moduledoc::punkexe family) via ONE mechanism, the
# <punkexe> help ?subcommand? tabled usage; the top-level table lists #leading 'help' word (the punkshell 'i <cmd> ...' idiom):
# built-ins AND project-registered # <punkexe> help tabled top-level usage; the table
# subcommands with summaries # lists built-ins AND project-registered
# <punkexe> -help same as bare 'help' - only on kits whose # subcommands with summaries
# unknownfirst default is 'script' (a # <punkexe> help <subcommand> ?arg ...?
# tool-style kit routes flags to its own # the subcommand's tabled usage; words
# processor; its surface stays reachable # after the subject are accepted and
# via the 'help' word) # ignored, so 'help' can be prepended
# <punkexe> <subcommand> -help the subcommand's tabled usage when -help # to any existing command line
# is its FIRST argument (tclsh: only when #There is deliberately NO -help/--help flag handling anywhere in the
# -help is the SOLE argument - stock #dispatch: a leading help-flag word falls under the unknown-first-argument
# parity keeps every other dash form in #rule (whose refusal shows this usage anyway, stderr exit 1), and
# ::argv; project subcommands: only when #'<subcommand> -help' belongs to the subcommand's own arguments (full
# the registration declares an argsid) #passthrough - e.g app-shellspy handles -help/--help/help itself; a
#parse-declared subcommand rejects it like any undeclared flag).
#Everything is guarded and lazy (the G-030 degradation doctrine): nothing #Everything is guarded and lazy (the G-030 degradation doctrine): nothing
#here loads punk::args on a normal boot/dispatch path. When punk::args or #here loads punk::args on a normal boot/dispatch path. When punk::args or
#the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same
@ -1553,7 +1559,6 @@ if {![info exists ::punkboot::launch_defaults]} {
#(script)::punkexe::help for subject validation. #(script)::punkexe::help for subject validation.
#--------------------------------------------------------------- #---------------------------------------------------------------
namespace eval ::punkboot { namespace eval ::punkboot {
variable help_flags [list -help --help -h /?]
variable launch_builtin_subcommands [list] variable launch_builtin_subcommands [list]
variable launch_subcommands [list] variable launch_subcommands [list]
variable launcher_punkargs_state "" ;#"" unprobed | ok | plain variable launcher_punkargs_state "" ;#"" unprobed | ok | plain
@ -1679,8 +1684,8 @@ if {![info exists ::punkboot::launch_defaults]} {
proc launcher_helpdef_id {} { proc launcher_helpdef_id {} {
#define (script)::punkexe.launcherhelp: the moduledoc #define (script)::punkexe.launcherhelp: the moduledoc
#(script)::punkexe::help definition with the subject choices #(script)::punkexe::help definition with the subject choices
#replaced by the live subcommand set, so 'help <name>' validates #replaced by the live subcommand set - the DISPLAY clone rendered
#(and prefix-resolves) against what this kit actually offers. #by 'help help'.
set id (script)::punkexe.launcherhelp set id (script)::punkexe.launcherhelp
set existing "" set existing ""
catch {set existing [punk::args::raw_def $id]} catch {set existing [punk::args::raw_def $id]}
@ -1698,11 +1703,37 @@ if {![info exists ::punkboot::launch_defaults]} {
} }
return $id return $id
} }
proc launcher_helpsubjectdef_id {} {
#define (script)::punkexe.launcherhelpsubject: the VALIDATION
#clone the help arm parses the subject word against - subject
#REQUIRED with the live choices, and the display definition's
#ignored trailing-arg row suppressed (-antiglobs). Without this
#the display shape lets punk::args SKIP a mistyped optional
#subject into the unconstrained arg row, silently rendering the
#top-level table where a tabled choice error is wanted.
set id (script)::punkexe.launcherhelpsubject
set existing ""
catch {set existing [punk::args::raw_def $id]}
if {$existing ne ""} {
return $id
}
if {[catch {
set ov [dict create \
@id [list -id $id] \
subject [list -optional 0 -choices [launcher_live_choices]] \
]
punk::args::define [punk::args::resolved_def -antiglobs {arg} -override $ov (script)::punkexe::help]
}]} {
return ""
}
return $id
}
proc launcher_plain_help {} { proc launcher_plain_help {} {
#degraded help: plain subcommand list built without punk::args #degraded help: plain subcommand list built without punk::args
set exebase [file rootname [file tail [info nameofexecutable]]] set exebase [file rootname [file tail [info nameofexecutable]]]
set lines [list] set lines [list]
lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?" lappend lines "Usage: $exebase ?packagemode? ?subcommand? ?arg ...?"
lappend lines " $exebase ?packagemode? -e <script> ?arg ...? (one-liner - same as 'script -e', G-077)"
lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal)," lappend lines " packagemode: ordered dash-separated list of internal|minted|os|src (default internal),"
lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)" lappend lines " optionally scoped with the 'proj:' prefix (e.g proj:internal-src)"
lappend lines " subcommands:" lappend lines " subcommands:"
@ -1793,6 +1824,7 @@ if {![info exists ::punkboot::launch_defaults]} {
puts stderr "known subcommands: [join [launcher_plain_choices] {, }]" puts stderr "known subcommands: [join [launcher_plain_choices] {, }]"
} }
puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation" puts stderr "use '$exebase help' for the launch surface, or '$exebase script $word ?arg ...?' to force script interpretation"
puts stderr "for a one-liner use '$exebase -e <script> ?arg ...?' (equivalently '$exebase script -e <script> ?arg ...?')"
return 1 return 1
} }
} }
@ -1838,53 +1870,44 @@ if {![info exists ::punkboot::launch_defaults]} {
set subcommand [lindex $arglist 0] set subcommand [lindex $arglist 0]
if {$subcommand in $known_subcommands} { if {$subcommand in $known_subcommands} {
set subcommand_arglist [lrange $arglist 1 end] set subcommand_arglist [lrange $arglist 1 end]
#G-032: a help flag as the subcommand's FIRST argument renders that #G-032 (revised 2026-08-05): a subcommand's arguments are entirely its
#subcommand's usage. tclsh: only when it is the SOLE argument (stock #own - there is no first-argument help-flag interception. Use
#parity keeps every other leading-dash form - including '-help' with #'<punkexe> help <subcommand>' for a subcommand's usage; '-help' after
#further arguments - in ::argv); project subcommands: only when the #a subcommand reaches the app/script unchanged (tclsh keeps full stock
#registration declares an argsid (a handler-only registration keeps #dash parity; app-shellspy handles -help/--help/help itself; a
#full passthrough - the application may do its own -help handling). #parse-declared subcommand rejects it like any undeclared flag).
if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} {
set do_subhelp 0
if {$subcommand eq "tclsh"} {
if {[llength $subcommand_arglist] == 1} {
set do_subhelp 1
}
} elseif {$subcommand in $builtin_subcommands} {
set do_subhelp 1
} elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} {
set do_subhelp 1
}
if {$do_subhelp} {
::punkboot::launcher_show_help $subcommand
exit 0
}
}
} else { } else {
set subcommand_arglist $arglist set subcommand_arglist $arglist
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
#G-032: a help flag in first position is a launch-surface help #G-077: a first argument of exactly '-e' is the one-liner form -
#request on kits whose unknown-first default is 'script'. #reclassify to 'script' with the WHOLE arglist ('-e' included) as
#Tool-style kits (unknownfirst names a project subcommand) keep #its arguments, so the script subcommand's own '-e' handling runs
#routing flags to their processor - their surface stays #it. This sits ahead of the unknown-first-arg refusal below, which
#discoverable via the 'help' subcommand word. #would otherwise reject '-e' as naming no existing file. Only
if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} { #script-default kits intercept it - a tool-style kit keeps '-e'
::punkboot::launcher_show_help #routed to its own processor, and '<punkexe> tclsh -e ...' keeps
exit 0 #stock parity (the tclsh subcommand never reaches this path).
} #A leading help FLAG (-help etc) is deliberately NOT special
set subcommand $default_unknownfirst #(2026-08-05 single-mechanism revision): on script-default kits it
if {$subcommand eq "script"} { #falls to the refusal below, whose stderr usage answers it anyway;
#G-032 unknown-first-arg reclassification rule (recorded in #tool-style kits route it to their processor.
#goals/G-032-launcher-punkargs.md): reclassify to 'script' if {$subcommand eq "-e" && $default_unknownfirst eq "script"} {
#only when the argument plausibly names a script - an set subcommand script
#existing file path, or a lib:* scriptlib reference. Anything } else {
#else is refused with usage on stderr (exit 1); when set subcommand $default_unknownfirst
#punk::args is unavailable the refusal degrades to the if {$subcommand eq "script"} {
#historic always-reclassify behaviour. #G-032 unknown-first-arg reclassification rule (recorded in
set unknown_word [lindex $subcommand_arglist 0] #goals/G-032-launcher-punkargs.md): reclassify to 'script'
if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} { #only when the argument plausibly names a script - an
if {[::punkboot::launcher_unknown_first_error $unknown_word]} { #existing file path, or a lib:* scriptlib reference. Anything
exit 1 #else is refused with usage on stderr (exit 1); when
#punk::args is unavailable the refusal degrades to the
#historic always-reclassify behaviour.
set unknown_word [lindex $subcommand_arglist 0]
if {!([file exists $unknown_word] || [string match -nocase lib:* $unknown_word])} {
if {[::punkboot::launcher_unknown_first_error $unknown_word]} {
exit 1
}
} }
} }
} }
@ -2068,11 +2091,11 @@ if {![info exists ::punkboot::launch_defaults]} {
#exit code; no repl fallthrough (G-015-compatible). #exit code; no repl fallthrough (G-015-compatible).
#G-032: the argument contract ((script)::punkexe::buildinfo, #G-032: the argument contract ((script)::punkexe::buildinfo,
#@values -min 0 -max 0) is enforced through punk::args when #@values -min 0 -max 0) is enforced through punk::args when
#available: any argument earns the historic one-line refusal PLUS #available: any argument (including '-help' - no flag
#the tabled usage error on stderr, exit 2 (buildinfo.test pins the #interception, 2026-08-05 revision) earns the historic one-line
#one-liner and the exit code). The empty-argument fast path never #refusal PLUS the tabled usage error on stderr, exit 2
#touches punk::args; a '-help' first argument was already #(buildinfo.test pins the one-liner and the exit code). The
#intercepted at selection. #empty-argument fast path never touches punk::args.
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
puts stderr "buildinfo: takes no arguments" puts stderr "buildinfo: takes no arguments"
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
@ -2089,9 +2112,14 @@ if {![info exists ::punkboot::launch_defaults]} {
exit [punk::buildinfo::main] exit [punk::buildinfo::main]
} }
help { help {
#G-032: launcher help surface. Bare 'help' renders the top-level #G-032: launcher help surface - THE single help mechanism
#tabled usage (built-ins + project-registered subcommands with #(2026-08-05 revision; the punkshell 'i <cmd> ...' idiom). Bare
#summaries); 'help <subcommand>' renders that subcommand's usage. #'help' renders the top-level tabled usage (built-ins +
#project-registered subcommands with summaries);
#'help <subcommand> ?arg ...?' renders that subcommand's usage -
#words after the subject are accepted and ignored, so 'help' can
#be prepended to any existing command line
#('punk9_beta help tclsh somefile somearg' shows the tclsh usage).
#Subject validation parses through the launcherhelp clone when #Subject validation parses through the launcherhelp clone when
#punk::args is available (tabled choice errors on stderr exit 1, #punk::args is available (tabled choice errors on stderr exit 1,
#unambiguous prefixes resolve); degraded mode accepts exact names #unambiguous prefixes resolve); degraded mode accepts exact names
@ -2102,9 +2130,16 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
set helpdef_id [::punkboot::launcher_helpdef_id] set helpdef_id [::punkboot::launcher_helpsubjectdef_id]
if {$helpdef_id ne ""} { if {$helpdef_id ne ""} {
if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { #validate/resolve ONLY the subject word (the make.tcl help
#precedent), against the required-subject validation clone:
#a mistyped subject earns the tabled choice error (exit 1)
#instead of being skipped past to render the top-level
#table; everything after the subject is deliberately
#unparsed (accepted and ignored - it is the subject's own
#command line, any mix of its words).
if {[catch {punk::args::parse [lrange $subcommand_arglist 0 0] -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} {
puts stderr $argd puts stderr $argd
exit 1 exit 1
} }
@ -2114,10 +2149,11 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
} }
#degraded: exact-name subject only #degraded: exact-name subject only; trailing words ignored as in
#the parsed path
set help_subject [lindex $subcommand_arglist 0] set help_subject [lindex $subcommand_arglist 0]
if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { if {$help_subject ni $::punkboot::launch_subcommands} {
puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" puts stderr "help: expected 'help ?subcommand? ?arg ...?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]"
exit 1 exit 1
} }
::punkboot::launcher_show_help $help_subject ::punkboot::launcher_show_help $help_subject

140
src/tests/shell/testsuites/punkexe/launcherhelp.test

@ -1,12 +1,17 @@
# -*- tcl -*- # -*- tcl -*-
# Characterization of the G-032 launcher help + declared-parse wiring against built punk kits: # Characterization of the G-032 launcher help + declared-parse wiring against built punk kits
# - '<punkexe> help' / '-help' render the top-level usage listing all subcommands (stdout, exit 0) # (single-mechanism revision 2026-08-05, user direction: the leading 'help' word is the ONLY
# - 'help <subcommand>' and '<subcommand> -help' render per-subcommand usage; unknown subjects # help entry - all -help flag interception withdrawn):
# are tabled usage errors (stderr, exit 1) # - '<punkexe> help' renders the top-level usage listing all subcommands (stdout, exit 0)
# - 'help <subcommand> ?arg ...?' renders per-subcommand usage - trailing words accepted and
# ignored, so 'help' can be prepended to an existing command line; unknown subjects are
# tabled usage errors (stderr, exit 1)
# - help FLAGS are never intercepted: a leading '-help' falls under the unknown-first-argument
# refusal (stderr usage, exit 1); '<subcommand> -help' reaches the subcommand's own
# arguments (tclsh: full stock dash parity, no sole-argument exception; buildinfo: refused
# like any argument, exit 2; parse-declared registration: rejected like any undeclared flag)
# - unknown first argument naming no existing file is refused with usage (stderr, exit 1); # - unknown first argument naming no existing file is refused with usage (stderr, exit 1);
# an existing file still reclassifies to the script subcommand # an existing file still reclassifies to the script subcommand
# - tclsh keeps stock leading-dash parity except a SOLE help flag
# - buildinfo argument rejection carries the tabled usage beside the historic one-liner (exit 2)
# - PUNKBOOT_PLAIN=1 degrades help to the plain subcommand list (still stdout, exit 0) # - PUNKBOOT_PLAIN=1 degrades help to the plain subcommand list (still stdout, exit 0)
# - project-registered subcommands: help appears by registration alone (summary + argsid), # - project-registered subcommands: help appears by registration alone (summary + argsid),
# declared parse gates arguments, handler-only registrations keep full passthrough # declared parse gates arguments, handler-only registrations keep full passthrough
@ -16,7 +21,9 @@
# (PUNK_SHELL_TEST_EXE / punk902z), and a second constraint-gated probe runs the headline # (PUNK_SHELL_TEST_EXE / punk902z), and a second constraint-gated probe runs the headline
# checks against a metakit-container kit (punk86/punksys) when one is deployed. # checks against a metakit-container kit (punk86/punksys) when one is deployed.
# Tests are constrained on the probed kit actually carrying the G-032 core (kit_g032 / # Tests are constrained on the probed kit actually carrying the G-032 core (kit_g032 /
# mkkit_g032) so a stale pre-G-032 bin/ kit skips rather than red-herrings. # mkkit_g032) so a stale pre-G-032 bin/ kit skips rather than red-herrings; the flag-form
# tests additionally probe that the kit is PAST the 2026-08-05 revision (kit_nodashhelp)
# so a boot core still carrying the interception skips rather than fails.
package require tcltest package require tcltest
@ -101,6 +108,22 @@ namespace eval ::testspace {
} }
testConstraint mkkit_g032 $mkkit_g032 testConstraint mkkit_g032 $mkkit_g032
#probe: is the kit PAST the 2026-08-05 single-mechanism revision? A leading
#'-help' then earns the unknown-first refusal (exit 1); the earlier core
#rendered top-level usage (exit 0). Gates the flag-form tests so a
#pre-revision kit skips rather than fails.
proc probe_nodashhelp {exe} {
if {[catch {exe_run $exe -help} pr]} {
return 0
}
expr {[dict get $pr exitcode] == 1}
}
variable kit_nodashhelp 0
if {$kit_g032} {
set kit_nodashhelp [probe_nodashhelp $punkexe]
}
testConstraint kit_nodashhelp $kit_nodashhelp
# -- top-level help forms ---------------------------------------------------------------------- # -- top-level help forms ----------------------------------------------------------------------
test launcherhelp_help_word {bare 'help': top-level usage on stdout, exit 0, nothing on stderr}\ test launcherhelp_help_word {bare 'help': top-level usage on stdout, exit 0, nothing on stderr}\
@ -118,13 +141,22 @@ namespace eval ::testspace {
}\ }\
-result {0 1 1 1 1 1} -result {0 1 1 1 1 1}
test launcherhelp_dashhelp_flag {'-help' first arg: same top-level usage, exit 0}\ #updated 2026-08-05 (agent, user direction) - single-mechanism revision: the
-constraints {punkexeavailable kit_g032} -body { #leading '-help' flag is no longer intercepted; it falls under the
#unknown-first-argument refusal, whose stderr usage answers the request
#anyway (exit 1, stdout empty)
test launcherhelp_dashhelp_flag_refused {'-help' first arg: no interception - unknown-first refusal with usage on stderr, exit 1}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe variable punkexe
set r [exe_run $punkexe -help] set r [exe_run $punkexe -help]
list [dict get $r exitcode] [string match "*packagemode*" [dict get $r stdout]] [expr {[dict get $r stderr] eq ""}] set result [list]
lappend result [dict get $r exitcode]
lappend result [expr {[dict get $r stdout] eq ""}]
lappend result [string match "*unknown subcommand*" [dict get $r stderr]]
lappend result [string match "*packagemode*" [stripansi [dict get $r stderr]]]
set result
}\ }\
-result {0 1 1} -result {1 1 1 1}
test launcherhelp_subject_word {'help buildinfo': the subcommand's usage, exit 0}\ test launcherhelp_subject_word {'help buildinfo': the subcommand's usage, exit 0}\
-constraints {punkexeavailable kit_g032} -body { -constraints {punkexeavailable kit_g032} -body {
@ -142,10 +174,37 @@ namespace eval ::testspace {
}\ }\
-result {0 1} -result {0 1}
test launcherhelp_sub_dashhelp {'script -help': the script subcommand's usage, exit 0}\ #updated 2026-08-05 (agent, user direction) - '<subcommand> -help' is the
-constraints {punkexeavailable kit_g032} -body { #subcommand's OWN argument now: app-punkscript treats '-help' as a script
#designation and errors honestly (nonzero, no launcher usage table)
test launcherhelp_sub_dashhelp_passthrough {'script -help': passes through to app-punkscript (its own error, no launcher usage render, nonzero exit)}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe variable punkexe
set r [exe_run $punkexe script -help] set r [exe_run $punkexe script -help]
set result [list]
lappend result [expr {[dict get $r exitcode] != 0}]
lappend result [expr {![string match "*punkexe script*" [dict get $r stdout]]}]
lappend result [expr {[dict get $r stderr] ne ""}]
set result
}\
-result {1 1 1}
test launcherhelp_subject_trailing_args {'help tclsh somefile somearg': trailing words accepted - the tclsh usage renders (prepend-help idiom)}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe
set r [exe_run $punkexe help tclsh somefile somearg]
set result [list]
lappend result [dict get $r exitcode]
lappend result [string match "*punkexe tclsh*" [dict get $r stdout]]
lappend result [expr {![string match "*packagemode*" [stripansi [dict get $r stdout]]]}]
set result
}\
-result {0 1 1}
test launcherhelp_subject_trailing_flagargs {'help script -e {puts hi}': flag-like trailing words accepted too}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe
set r [exe_run $punkexe help script -e {puts hi}]
list [dict get $r exitcode] [string match "*punkexe script*" [dict get $r stdout]] list [dict get $r exitcode] [string match "*punkexe script*" [dict get $r stdout]]
}\ }\
-result {0 1} -result {0 1}
@ -186,13 +245,16 @@ namespace eval ::testspace {
# -- tclsh stock-parity carve-out -------------------------------------------------------------- # -- tclsh stock-parity carve-out --------------------------------------------------------------
test launcherhelp_tclsh_dashhelp_sole {'tclsh -help' (sole arg) renders the tclsh usage, exit 0}\ #updated 2026-08-05 (agent, user direction) - the sole-argument '-help'
-constraints {punkexeavailable kit_g032} -body { #carve-out is withdrawn: FULL stock dash parity, the flag stays in ::argv
test launcherhelp_tclsh_dashhelp_sole {'tclsh -help' (sole arg): stock parity restored - the flag stays in ::argv, stdin evaluated}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe variable punkexe
set r [exe_run $punkexe tclsh -help] set r [exe_run $punkexe -stdin "puts \[list SOLEDASH \$::argv\]\n" tclsh -help]
list [dict get $r exitcode] [string match "*punkexe tclsh*" [dict get $r stdout]] #note: a single-element ::argv renders unbraced ('SOLEDASH -help')
list [dict get $r exitcode] [string match "*SOLEDASH -help*" [dict get $r stdout]] [expr {![string match "*punkexe tclsh*" [dict get $r stdout]]}]
}\ }\
-result {0 1} -result {0 1 1}
test launcherhelp_tclsh_dashhelp_with_args {'tclsh -help extra': stock parity - all args stay in ::argv}\ test launcherhelp_tclsh_dashhelp_with_args {'tclsh -help extra': stock parity - all args stay in ::argv}\
-constraints {punkexeavailable kit_g032} -body { -constraints {punkexeavailable kit_g032} -body {
@ -217,13 +279,19 @@ namespace eval ::testspace {
}\ }\
-result {2 1 1 1} -result {2 1 1 1}
test launcherhelp_buildinfo_dashhelp {'buildinfo -help' renders usage on stdout, exit 0}\ #updated 2026-08-05 (agent, user direction) - '-help' is an ordinary
-constraints {punkexeavailable kit_g032} -body { #(refused) argument to buildinfo now: same shape as 'buildinfo extra'
test launcherhelp_buildinfo_dashhelp {'buildinfo -help': refused like any argument - one-liner + tabled usage on stderr, exit 2}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe variable punkexe
set r [exe_run $punkexe buildinfo -help] set r [exe_run $punkexe buildinfo -help]
list [dict get $r exitcode] [string match "*punkexe buildinfo*" [dict get $r stdout]] set result [list]
lappend result [dict get $r exitcode]
lappend result [string match "*takes no arguments*" [dict get $r stderr]]
lappend result [expr {[dict get $r stdout] eq ""}]
set result
}\ }\
-result {0 1} -result {2 1 1}
# -- degradation ------------------------------------------------------------------------------- # -- degradation -------------------------------------------------------------------------------
@ -322,14 +390,22 @@ apply { args {
}\ }\
-result {0 1 1} -result {0 1 1}
test launcherhelp_registration_dashhelp {'demo -help' renders the declared definition; handler not invoked}\ #updated 2026-08-05 (agent, user direction) - no argsid-gated '-help'
-constraints {punkexeavailable kit_g032} -body { #interception: for a parse-declared registration '-help' is an undeclared
#flag and fails the declared-parse gate (exit 1, handler not invoked);
#'help demo' remains the help route
test launcherhelp_registration_dashhelp {'demo -help' (parse-declared registration): rejected by the declared-parse gate like any undeclared flag; handler not invoked}\
-constraints {punkexeavailable kit_g032 kit_nodashhelp} -body {
variable punkexe variable punkexe
set fm [fake_main_setup] set fm [fake_main_setup]
set r [exe_run $punkexe tclsh $fm demo -help] set r [exe_run $punkexe tclsh $fm demo -help]
list [dict get $r exitcode] [string match "*fakekit demo*" [dict get $r stdout]] [expr {![string match "*DEMO_HANDLER_RAN*" [dict get $r stdout]]}] set result [list]
lappend result [dict get $r exitcode]
lappend result [expr {![string match "*DEMO_HANDLER_RAN*" [dict get $r stdout]]}]
lappend result [expr {[dict get $r stderr] ne ""}]
set result
}\ }\
-result {0 1 1} -result {1 1 1}
test launcherhelp_registration_parse_pass {declared parse: valid options reach the handler}\ test launcherhelp_registration_parse_pass {declared parse: valid options reach the handler}\
-constraints {punkexeavailable kit_g032} -body { -constraints {punkexeavailable kit_g032} -body {
@ -379,13 +455,19 @@ apply { args {
}\ }\
-result {0 1 1 1 1} -result {0 1 1 1 1}
test launcherhelp_mkkit_subject {metakit kit: 'help script' renders the subcommand usage}\ test launcherhelp_mkkit_subject {metakit kit: 'help script' renders the subcommand usage (trailing words accepted too)}\
-constraints {mkkitavailable mkkit_g032} -body { -constraints {mkkitavailable mkkit_g032} -body {
variable mkkitexe variable mkkitexe
set r [exe_run $mkkitexe help script] set r [exe_run $mkkitexe help script]
list [dict get $r exitcode] [string match "*punkexe script*" [dict get $r stdout]] set result [list]
lappend result [dict get $r exitcode]
lappend result [string match "*punkexe script*" [dict get $r stdout]]
set r2 [exe_run $mkkitexe help script somefile somearg]
lappend result [dict get $r2 exitcode]
lappend result [string match "*punkexe script*" [dict get $r2 stdout]]
set result
}\ }\
-result {0 1} -result {0 1 0 1}
test launcherhelp_mkkit_unknownfirst {metakit kit: unknown non-file first arg refused with usage, exit 1}\ test launcherhelp_mkkit_unknownfirst {metakit kit: unknown non-file first arg refused with usage, exit 1}\
-constraints {mkkitavailable mkkit_g032} -body { -constraints {mkkitavailable mkkit_g032} -body {

7
src/tests/shell/testsuites/punkexe/maketclcolour.test

@ -159,12 +159,15 @@ namespace eval ::testspace {
} -result {timedout 0 exitcode 0 esc 0 policyline 1} } -result {timedout 0 exitcode 0 esc 0 policyline 1}
#added 2026-08-01 (agent, G-145) - remnant-free piped usage tables #added 2026-08-01 (agent, G-145) - remnant-free piped usage tables
test maketcl_colour_help_tables_zero_remnants {piped tclsh src/make.tcl help for every declared subject plus bare help/-help: zero ESC bytes and zero orphan CSI-fragment text} -constraints {tclshavailable} -body { #updated 2026-08-05 (agent, user direction) - the '-help' invocation removed
#with the single-mechanism revision (help flags are ordinary arguments now:
#'make.tcl -help' is a subcommand choice violation, exit 1)
test maketcl_colour_help_tables_zero_remnants {piped tclsh src/make.tcl help for every declared subject plus bare help: zero ESC bytes and zero orphan CSI-fragment text} -constraints {tclshavailable} -body {
#Declared subjects: the dict keys of ::punkboot::argdoc SUMMARIES in src/make.tcl, #Declared subjects: the dict keys of ::punkboot::argdoc SUMMARIES in src/make.tcl,
#pulled verbatim by the help subject arg's -choices. Characterization of the #pulled verbatim by the help subject arg's -choices. Characterization of the
#declared set as of 2026-08-01 - a new subcommand lands in SUMMARIES and here. #declared set as of 2026-08-01 - a new subcommand lands in SUMMARIES and here.
set subjects {bakehouse packages modules libs bake bakelist vfslibs bin vendorupdate libfetch bootsupport vfscommonupdate info check projectversion workflow shell help buildsuite tool} set subjects {bakehouse packages modules libs bake bakelist vfslibs bin vendorupdate libfetch bootsupport vfscommonupdate info check projectversion workflow shell help buildsuite tool}
set invocations [list help -help] set invocations [list help]
foreach subject $subjects {lappend invocations [list help $subject]} foreach subject $subjects {lappend invocations [list help $subject]}
set tclshexe [auto_execok tclsh] set tclshexe [auto_execok tclsh]
set offenders [list] set offenders [list]

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

@ -1,10 +1,12 @@
package require tcltest package require tcltest
#Piped characterization of make.tcl's help depth (goal G-143): the tool and #Piped characterization of make.tcl's help depth (goal G-143; single-mechanism
#revision 2026-08-05, user direction - the leading 'help' word is the ONLY help
#entry, all -help flag handling withdrawn): the tool and
#buildsuite subcommands are declared multi-form (one @form per action with a #buildsuite subcommands are declared multi-form (one @form per action with a
#single-choice action leader + -choicelabels), so full-subcommand help renders #single-choice action leader + -choicelabels), so full-subcommand help renders
#one synopsis line per action, 'make.tcl help <subcommand> ?arg ...?' (and #one synopsis line per action, and 'make.tcl help <subcommand> ?arg ...?'
#'<subcommand> ?arg ...? -help') accepts the subcommand's own command line and #accepts the subcommand's own command line and
#DRY-RUNS it through the declaration (punk::args form auto-selection): accepted #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 #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 #diagnosis dispatch would give (exit 1), tool dispatch parses through the
@ -35,7 +37,9 @@ package require tcltest
# 'help bake punk91' is accepted with 'kitname = punk91' reported # 'help bake punk91' is accepted with 'kitname = punk91' reported
# - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with the # - 'help tool frobnicate' / 'help buildsuite frobnicate': exit 1 with the
# punk::args noformmatch diagnosis naming the candidate forms # punk::args noformmatch diagnosis naming the candidate forms
# - 'tool build -help': the <subcommand> <action> -help route renders the form # - 'tool build -help' / 'make.tcl -help': help flags are ordinary arguments now -
# they fail the gate they meet (noformmatch / subcommand choice violation, exit 1)
# and that usage error is itself the documentation
# - 'tool frobnicate' / 'tool build -bogus punkzip': exit 1 punk::args usage errors # - '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 # - 'tool build punkzip -test 0': exit 2 with the misplaced-flag guidance (options
# precede tool names in the definition's positional model - the historic # precede tool names in the definition's positional model - the historic
@ -262,24 +266,30 @@ namespace eval ::testspace {
set result set result
} -result {timedout 0 exitcode 1 noform 1 flist 1 fbuild 1 timedout2 0 exitcode2 1 noform2 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, updated 2026-08-02 (agent) - #added 2026-08-01 (agent, G-143); re-pinned 2026-08-05 (agent, user direction) -
#appending -help to a full command line ('tool build -test 0 punkzip -help') #the <subcommand> ?arg ...? -help route is withdrawn (single help mechanism:
#routes identically; the received-args reports differ per line, so the full #the leading 'help' word). '-help' is now an ordinary argument: in tool's
#variant pins content rather than byte-equality #option position it is an unknown flag (noformmatch diagnosis, exit 1 - the
test maketcl_tool_subaction_help_route {'make.tcl tool build -help' and 'tool build -test 0 punkzip -help' render the build form's usage (the <subcommand> ?arg ...? -help route)} -constraints {tclshavailable} -body { #error table carries the forms, so it still documents); at/after the first
#value position it is swallowed as a toolname and earns the flag-like-toolname
#guidance (exit 2, as for any flag-shaped name in value position); at the very
#top level it fails the subcommand choice gate (exit 1)
test maketcl_tool_subaction_help_route {'-help' is an ordinary argument: 'tool build -help' noformmatch exit 1; 'tool build -test 0 punkzip -help' flag-like-toolname exit 2; 'make.tcl -help' subcommand choice gate exit 1} -constraints {tclshavailable} -body {
set r [maketcl_run {tool build -help}] set r [maketcl_run {tool build -help}]
set out [dict get $r output] set out [dict get $r output]
set result [list] set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] 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 noform [regexp {No form of the command matches the supplied arguments} $out]
lappend result testopt [regexp {\?-test\?} $out]
set r2 [maketcl_run {tool build -test 0 punkzip -help}] set r2 [maketcl_run {tool build -test 0 punkzip -help}]
set out2 [dict get $r2 output] set out2 [dict get $r2 output]
lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode] lappend result timedout2 [dict get $r2 timedout] exitcode2 [dict get $r2 exitcode]
lappend result synbuild2 [regexp {make\.tcl tool build \[-test <bool>\] \[toolname\]\.\.\.} $out2] lappend result flaghint2 [regexp {looks like a flag - options precede the tool names} $out2]
lappend result landed2 [regexp {toolname = punkzip} $out2] set r3 [maketcl_run {-help}]
set out3 [dict get $r3 output]
lappend result timedout3 [dict get $r3 timedout] exitcode3 [dict get $r3 exitcode]
lappend result choicegate3 [regexp {Received: '-help'} $out3]
set result set result
} -result {timedout 0 exitcode 0 synbuild 1 testopt 1 timedout2 0 exitcode2 0 synbuild2 1 landed2 1} } -result {timedout 0 exitcode 1 noform 1 timedout2 0 exitcode2 2 flaghint2 1 timedout3 0 exitcode3 1 choicegate3 1}
#added 2026-08-01 (agent, G-143); updated 2026-08-02 (agent) - the top level now #added 2026-08-01 (agent, G-143); updated 2026-08-02 (agent) - the top level now
#renders the rich overview id ((script)::punkboot.overview - the 'i info' style): #renders the rich overview id ((script)::punkboot.overview - the 'i info' style):

2
src/vfs/AGENTS.md

@ -17,7 +17,7 @@ VFS (Virtual File System) folders define the runtime payloads that get wrapped i
- `punkdeclare.vfs` + `punkdeclare.vfs.toml` are the G-115 demonstration kit: VCS carries only the boot fauxlink; the whole `lib_tcl9/` binary payload (tcludp vendor tree + tcllibc packages tier) materializes from the declaration and is deliberately ignored in both VCS. Bake with `make.tcl bake -confirm 0 punkdeclare` (bake_default=false keeps it out of full bakes); its smoke-require (`udp`, `tcllibc`) proves the declared payload resolves inside the built artifact. - `punkdeclare.vfs` + `punkdeclare.vfs.toml` are the G-115 demonstration kit: VCS carries only the boot fauxlink; the whole `lib_tcl9/` binary payload (tcludp vendor tree + tcllibc packages tier) materializes from the declaration and is deliberately ignored in both VCS. Bake with `make.tcl bake -confirm 0 punkdeclare` (bake_default=false keeps it out of full bakes); its smoke-require (`udp`, `tcllibc`) proves the declared payload resolves inside the built artifact.
- A `*.vfs` folder baked against a runtime is expected to carry a root-level startup script: an actual `main.tcl`, or a root fauxlink resolving to the name `main.tcl` whose target exists and is a `.tcl` file. `make.tcl` warns when neither is present (column-0 `BAKE-WARNING:` token, ANSI-highlighted, recapped at end of run like provenance warnings) but still bakes - a kit without a startup script is legal. It must NOT carry both, nor two root fauxlinks resolving to `main.tcl`: the bake REFUSES such a kit per-kit under FAILED KITS (G-031 collision gate, predicate `punkboot::utils::vfs_startup_script_report` - merge traversal order would otherwise silently decide the boot script). Overlay-only folders with no runtime mapping (e.g `_vfscommon.vfs`) are not checked. - A `*.vfs` folder baked against a runtime is expected to carry a root-level startup script: an actual `main.tcl`, or a root fauxlink resolving to the name `main.tcl` whose target exists and is a `.tcl` file. `make.tcl` warns when neither is present (column-0 `BAKE-WARNING:` token, ANSI-highlighted, recapped at end of run like provenance warnings) but still bakes - a kit without a startup script is legal. It must NOT carry both, nor two root fauxlinks resolving to `main.tcl`: the bake REFUSES such a kit per-kit under FAILED KITS (G-031 collision gate, predicate `punkboot::utils::vfs_startup_script_report` - merge traversal order would otherwise silently decide the boot script). Overlay-only folders with no runtime mapping (e.g `_vfscommon.vfs`) are not checked.
- Componentized boot (G-031, 0.45.0): the kit main is a THIN project-owned file that declares project subcommands (`::punkboot::project_subcommands` - built-ins tclsh/shellspy/punk/shell/script/buildinfo/help cannot be shadowed) and launch defaults (`noargs`/`unknownfirst`), publishes `::punkboot::launch_args`/`main_script`, then sources the layout-owned boot core: `<vfsroot>/punkboot/core.tcl` in kits (regenerated into `_vfscommon.vfs` from the `_config/punkboot_core.tcl` master by `make.tcl vfscommonupdate`), parent-dir candidate for nested `app/main.tcl`, sibling `punkboot_core.tcl` when a main runs as a plain script from `_config` (the kitmountpoint characterization shape). The core carries the boot machinery and default dispatch and is versioned (`::punkboot::core_version`). Behavioural statements about `punk_main.tcl`/`project_main.tcl` in the bullets below describe machinery that now lives in the boot core they source. - Componentized boot (G-031, 0.45.0): the kit main is a THIN project-owned file that declares project subcommands (`::punkboot::project_subcommands` - built-ins tclsh/shellspy/punk/shell/script/buildinfo/help cannot be shadowed) and launch defaults (`noargs`/`unknownfirst`), publishes `::punkboot::launch_args`/`main_script`, then sources the layout-owned boot core: `<vfsroot>/punkboot/core.tcl` in kits (regenerated into `_vfscommon.vfs` from the `_config/punkboot_core.tcl` master by `make.tcl vfscommonupdate`), parent-dir candidate for nested `app/main.tcl`, sibling `punkboot_core.tcl` when a main runs as a plain script from `_config` (the kitmountpoint characterization shape). The core carries the boot machinery and default dispatch and is versioned (`::punkboot::core_version`). Behavioural statements about `punk_main.tcl`/`project_main.tcl` in the bullets below describe machinery that now lives in the boot core they source.
- Launcher help + declared parse (G-032, core 0.3.0): the dispatch renders and parses through punk::args - `<punkexe> help ?subcommand?` / `-help` / `<subcommand> -help` render tabled usage from the `punk::args::moduledoc::punkexe` ids plus the live registrations (per-kit clones `(script)::punkexe.launcher`/`.launcherhelp`); an unknown first argument naming no existing file (and no `lib:*` reference) is refused with usage (stderr, exit 1) instead of silently reclassifying to `script`; `buildinfo` enforces its no-arguments contract through punk::args (historic one-liner + tabled usage, exit 2). Optional per-name registration metadata `::punkboot::project_subcommand_info` (`summary`/`argsid`/`package`/`parse` - documented by `punk::args::moduledoc::punkboot`) puts project subcommands in the help tables by registration alone and can gate their arguments through a declared definition (usage errors exit 1, handler not invoked). Carve-outs: tool-style kits (`unknownfirst` naming a project subcommand) keep help FLAGS routed to their processor ('help' word remains); `tclsh` intercepts a help flag only as its SOLE argument (stock dash parity); a registration without `argsid` keeps `-help` passthrough to its handler. Everything is lazy + guarded (G-030 doctrine): normal boots never load punk::args, and with the stack unavailable (or `PUNKBOOT_PLAIN=1`) help degrades to a plain subcommand list while parsing degrades to the historic switch semantics. Characterization: `src/tests/shell/testsuites/punkexe/launcherhelp.test`. - Launcher help + declared parse (G-032, core 0.3.0; single-mechanism revision 2026-08-05 in core 0.5.0 - user direction): the dispatch renders and parses through punk::args with ONE help entry, the leading `help` word - `<punkexe> help ?subcommand? ?arg ...?` renders tabled usage from the `punk::args::moduledoc::punkexe` ids plus the live registrations (per-kit clones `(script)::punkexe.launcher`/`.launcherhelp`); words after the subject are accepted and ignored, so `help` can be prepended to an existing command line. Help FLAGS (-help/--help etc) are never intercepted anywhere: a leading one falls under the unknown-first-argument refusal (whose stderr usage answers it, exit 1), and after a subcommand they are the subcommand's own arguments (`tclsh` keeps full stock dash parity; app-shellspy handles -help/--help/help itself; `buildinfo` refuses any argument - one-liner + tabled usage, exit 2; a parse-declared registration rejects them like any undeclared flag). An unknown first argument naming no existing file (no `lib:*` reference, and - G-077 - not `-e`) is refused with usage (stderr, exit 1) instead of silently reclassifying to `script`. Optional per-name registration metadata `::punkboot::project_subcommand_info` (`summary`/`argsid`/`package`/`parse` - documented by `punk::args::moduledoc::punkboot`) puts project subcommands in the help tables by registration alone and can gate their arguments through a declared definition (usage errors exit 1, handler not invoked). Everything is lazy + guarded (G-030 doctrine): normal boots never load punk::args, and with the stack unavailable (or `PUNKBOOT_PLAIN=1`) help degrades to a plain subcommand list while parsing degrades to the historic switch semantics. Characterization: `src/tests/shell/testsuites/punkexe/launcherhelp.test`.
- Top-level `-e` one-liner (G-077, core 0.4.0): on kits whose unknownfirst default is `script`, a first argument of exactly `-e` is reclassified to the `script` subcommand's own `-e` handling ahead of the G-032 unknown-first-arg refusal (the whole arglist, `-e` included, becomes its arguments) - `<punkexe> -e <script> ?arg ...?` runs the code as a one-liner (app-punkscript 1.1: `::argv0` is `-e`, trailing args in `::argv`, errorInfo + exit 1 on error, a usage error when the script argument is missing, stdin never read by the form itself, non-empty final result echoed). Tool-style kits keep `-e` routed to their processor; `<punkexe> tclsh -e ...` keeps deliberate stock parity (G-118). Characterization: `src/tests/shell/testsuites/punkexe/scriptexec.test` (-e section). - Top-level `-e` one-liner (G-077, core 0.4.0): on kits whose unknownfirst default is `script`, a first argument of exactly `-e` is reclassified to the `script` subcommand's own `-e` handling ahead of the G-032 unknown-first-arg refusal (the whole arglist, `-e` included, becomes its arguments) - `<punkexe> -e <script> ?arg ...?` runs the code as a one-liner (app-punkscript 1.1: `::argv0` is `-e`, trailing args in `::argv`, errorInfo + exit 1 on error, a usage error when the script argument is missing, stdin never read by the form itself, non-empty final result echoed). Tool-style kits keep `-e` routed to their processor; `<punkexe> tclsh -e ...` keeps deliberate stock parity (G-118). Characterization: `src/tests/shell/testsuites/punkexe/scriptexec.test` (-e section).
- VFS content must be compatible with the target platform runtime. - VFS content must be compatible with the target platform runtime.
- A root-level `punkshell.ico` in a kit's own custom `.vfs` folder overrides - A root-level `punkshell.ico` in a kit's own custom `.vfs` folder overrides

16
src/vfs/_config/project_main.tcl

@ -16,8 +16,9 @@
# punk::args::moduledoc::punkexe package for the documented launch surface), # punk::args::moduledoc::punkexe package for the documented launch surface),
# module and library path setup, punk::libunknown, and the default dispatch # module and library path setup, punk::libunknown, and the default dispatch
# (tclsh/shellspy/punk/shell/script/buildinfo/help) with its G-032 help # (tclsh/shellspy/punk/shell/script/buildinfo/help) with its G-032 help
# surface ('<kit> help', '-help', '<subcommand> -help' - tabled usage # surface ('<kit> help ?subcommand? ?arg ...?' - the single help mechanism:
# listing your registered subcommands, degrading to a plain list). # tabled usage listing your registered subcommands, degrading to a plain
# list; help FLAGS are never intercepted).
# #
#WHAT TO EDIT #WHAT TO EDIT
# Only the ==CUSTOMIZE== blocks below. Everything else is contract plumbing. # Only the ==CUSTOMIZE== blocks below. Everything else is contract plumbing.
@ -58,11 +59,12 @@ apply { args {
namespace eval ::punkboot {variable project_subcommands [dict create]} namespace eval ::punkboot {variable project_subcommands [dict create]}
#Optional per-name help/parse metadata (G-032): with this declared, your #Optional per-name help/parse metadata (G-032): with this declared, your
#subcommands appear in '<kit> help' with summaries, 'help <name>' / #subcommands appear in '<kit> help' with summaries, 'help <name>' renders
#'<name> -help' render your punk::args definition, and parse 1 validates #your punk::args definition, and parse 1 validates arguments through it
#arguments through it before your handler runs (usage errors tabled, #before your handler runs (usage errors tabled, exit 1). All keys
#exit 1). All keys optional; a name without an entry keeps full #optional; a name without an entry keeps full passthrough semantics (the
#passthrough semantics. Contract detail: punk::args::moduledoc::punkboot #launcher never touches a subcommand's own arguments - help flags
#included). Contract detail: punk::args::moduledoc::punkboot
#registration definition. #registration definition.
# namespace eval ::punkboot {variable project_subcommand_info [dict create \ # namespace eval ::punkboot {variable project_subcommand_info [dict create \
# mytool {summary "Frob the widgets in a directory." \ # mytool {summary "Frob the widgets in a directory." \

166
src/vfs/_config/punkboot_core.tcl

@ -15,10 +15,13 @@
#'-e' one-liner reclassification to 'script -e') extended by the thin #'-e' one-liner reclassification to 'script -e') extended by the thin
#main's declared project subcommands (G-031 registration model - see the #main's declared project subcommands (G-031 registration model - see the
#dispatch section near the end of this file). The launch surface documents #dispatch section near the end of this file). The launch surface documents
#itself via punk::args (G-032): '<punkexe> help ?subcommand?', '-help' and #itself via punk::args (G-032; -help flag forms withdrawn 2026-08-05):
#'<subcommand> -help' render tabled usage from the moduledoc::punkexe #'<punkexe> help ?subcommand? ?arg ...?' - the single help mechanism -
#definitions plus the live registrations, degrading to a plain subcommand #renders tabled usage from the moduledoc::punkexe definitions plus the live
#list when that stack is unavailable. #registrations, degrading to a plain subcommand list when that stack is
#unavailable. Help flags (-help etc) are never intercepted: a leading one
#falls under the unknown-first-argument refusal (which shows usage on
#stderr), and after a subcommand they are the subcommand's own arguments.
# #
#The package_mode surface (documented in full by punk::args::moduledoc::punkexe): #The package_mode surface (documented in full by punk::args::moduledoc::punkexe):
#an optional FIRST launch argument of the form minted|os|internal|src or any #an optional FIRST launch argument of the form minted|os|internal|src or any
@ -52,7 +55,7 @@
namespace eval ::punkboot { namespace eval ::punkboot {
#boot-core identity (G-031; stamped into kits + reported by the G-025 #boot-core identity (G-031; stamped into kits + reported by the G-025
#buildinfo surfaces) #buildinfo surfaces)
variable core_version 0.4.0 variable core_version 0.5.0
} }
if {![info exists ::punkboot::launch_args]} { if {![info exists ::punkboot::launch_args]} {
namespace eval ::punkboot [list variable launch_args $::argv] namespace eval ::punkboot [list variable launch_args $::argv]
@ -1523,23 +1526,25 @@ if {![info exists ::punkboot::launch_defaults]} {
namespace eval ::punkboot [list variable boot_state $boot_state_dict] namespace eval ::punkboot [list variable boot_state $boot_state_dict]
#--------------------------------------------------------------- #---------------------------------------------------------------
#Launcher help + declared-parse machinery (G-032) #Launcher help + declared-parse machinery (G-032; -help flag forms
#withdrawn 2026-08-05 by user direction - single mechanism)
#The launch surface documents itself through punk::args (definition ids #The launch surface documents itself through punk::args (definition ids
#from the punk::args::moduledoc::punkexe family): #from the punk::args::moduledoc::punkexe family) via ONE mechanism, the
# <punkexe> help ?subcommand? tabled usage; the top-level table lists #leading 'help' word (the punkshell 'i <cmd> ...' idiom):
# built-ins AND project-registered # <punkexe> help tabled top-level usage; the table
# subcommands with summaries # lists built-ins AND project-registered
# <punkexe> -help same as bare 'help' - only on kits whose # subcommands with summaries
# unknownfirst default is 'script' (a # <punkexe> help <subcommand> ?arg ...?
# tool-style kit routes flags to its own # the subcommand's tabled usage; words
# processor; its surface stays reachable # after the subject are accepted and
# via the 'help' word) # ignored, so 'help' can be prepended
# <punkexe> <subcommand> -help the subcommand's tabled usage when -help # to any existing command line
# is its FIRST argument (tclsh: only when #There is deliberately NO -help/--help flag handling anywhere in the
# -help is the SOLE argument - stock #dispatch: a leading help-flag word falls under the unknown-first-argument
# parity keeps every other dash form in #rule (whose refusal shows this usage anyway, stderr exit 1), and
# ::argv; project subcommands: only when #'<subcommand> -help' belongs to the subcommand's own arguments (full
# the registration declares an argsid) #passthrough - e.g app-shellspy handles -help/--help/help itself; a
#parse-declared subcommand rejects it like any undeclared flag).
#Everything is guarded and lazy (the G-030 degradation doctrine): nothing #Everything is guarded and lazy (the G-030 degradation doctrine): nothing
#here loads punk::args on a normal boot/dispatch path. When punk::args or #here loads punk::args on a normal boot/dispatch path. When punk::args or
#the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same #the moduledoc definitions are unavailable (or PUNKBOOT_PLAIN=1 - same
@ -1554,7 +1559,6 @@ if {![info exists ::punkboot::launch_defaults]} {
#(script)::punkexe::help for subject validation. #(script)::punkexe::help for subject validation.
#--------------------------------------------------------------- #---------------------------------------------------------------
namespace eval ::punkboot { namespace eval ::punkboot {
variable help_flags [list -help --help -h /?]
variable launch_builtin_subcommands [list] variable launch_builtin_subcommands [list]
variable launch_subcommands [list] variable launch_subcommands [list]
variable launcher_punkargs_state "" ;#"" unprobed | ok | plain variable launcher_punkargs_state "" ;#"" unprobed | ok | plain
@ -1680,8 +1684,8 @@ if {![info exists ::punkboot::launch_defaults]} {
proc launcher_helpdef_id {} { proc launcher_helpdef_id {} {
#define (script)::punkexe.launcherhelp: the moduledoc #define (script)::punkexe.launcherhelp: the moduledoc
#(script)::punkexe::help definition with the subject choices #(script)::punkexe::help definition with the subject choices
#replaced by the live subcommand set, so 'help <name>' validates #replaced by the live subcommand set - the DISPLAY clone rendered
#(and prefix-resolves) against what this kit actually offers. #by 'help help'.
set id (script)::punkexe.launcherhelp set id (script)::punkexe.launcherhelp
set existing "" set existing ""
catch {set existing [punk::args::raw_def $id]} catch {set existing [punk::args::raw_def $id]}
@ -1699,6 +1703,31 @@ if {![info exists ::punkboot::launch_defaults]} {
} }
return $id return $id
} }
proc launcher_helpsubjectdef_id {} {
#define (script)::punkexe.launcherhelpsubject: the VALIDATION
#clone the help arm parses the subject word against - subject
#REQUIRED with the live choices, and the display definition's
#ignored trailing-arg row suppressed (-antiglobs). Without this
#the display shape lets punk::args SKIP a mistyped optional
#subject into the unconstrained arg row, silently rendering the
#top-level table where a tabled choice error is wanted.
set id (script)::punkexe.launcherhelpsubject
set existing ""
catch {set existing [punk::args::raw_def $id]}
if {$existing ne ""} {
return $id
}
if {[catch {
set ov [dict create \
@id [list -id $id] \
subject [list -optional 0 -choices [launcher_live_choices]] \
]
punk::args::define [punk::args::resolved_def -antiglobs {arg} -override $ov (script)::punkexe::help]
}]} {
return ""
}
return $id
}
proc launcher_plain_help {} { proc launcher_plain_help {} {
#degraded help: plain subcommand list built without punk::args #degraded help: plain subcommand list built without punk::args
set exebase [file rootname [file tail [info nameofexecutable]]] set exebase [file rootname [file tail [info nameofexecutable]]]
@ -1841,49 +1870,27 @@ if {![info exists ::punkboot::launch_defaults]} {
set subcommand [lindex $arglist 0] set subcommand [lindex $arglist 0]
if {$subcommand in $known_subcommands} { if {$subcommand in $known_subcommands} {
set subcommand_arglist [lrange $arglist 1 end] set subcommand_arglist [lrange $arglist 1 end]
#G-032: a help flag as the subcommand's FIRST argument renders that #G-032 (revised 2026-08-05): a subcommand's arguments are entirely its
#subcommand's usage. tclsh: only when it is the SOLE argument (stock #own - there is no first-argument help-flag interception. Use
#parity keeps every other leading-dash form - including '-help' with #'<punkexe> help <subcommand>' for a subcommand's usage; '-help' after
#further arguments - in ::argv); project subcommands: only when the #a subcommand reaches the app/script unchanged (tclsh keeps full stock
#registration declares an argsid (a handler-only registration keeps #dash parity; app-shellspy handles -help/--help/help itself; a
#full passthrough - the application may do its own -help handling). #parse-declared subcommand rejects it like any undeclared flag).
if {[lindex $subcommand_arglist 0] in $::punkboot::help_flags} {
set do_subhelp 0
if {$subcommand eq "tclsh"} {
if {[llength $subcommand_arglist] == 1} {
set do_subhelp 1
}
} elseif {$subcommand in $builtin_subcommands} {
set do_subhelp 1
} elseif {[dict exists $::punkboot::project_subcommand_info $subcommand argsid]} {
set do_subhelp 1
}
if {$do_subhelp} {
::punkboot::launcher_show_help $subcommand
exit 0
}
}
} else { } else {
set subcommand_arglist $arglist set subcommand_arglist $arglist
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
#G-032: a help flag in first position is a launch-surface help
#request on kits whose unknown-first default is 'script'.
#Tool-style kits (unknownfirst names a project subcommand) keep
#routing flags to their processor - their surface stays
#discoverable via the 'help' subcommand word.
if {$subcommand in $::punkboot::help_flags && $default_unknownfirst eq "script"} {
::punkboot::launcher_show_help
exit 0
}
#G-077: a first argument of exactly '-e' is the one-liner form - #G-077: a first argument of exactly '-e' is the one-liner form -
#reclassify to 'script' with the WHOLE arglist ('-e' included) as #reclassify to 'script' with the WHOLE arglist ('-e' included) as
#its arguments, so the script subcommand's own '-e' handling runs #its arguments, so the script subcommand's own '-e' handling runs
#it. This sits ahead of the unknown-first-arg refusal below, which #it. This sits ahead of the unknown-first-arg refusal below, which
#would otherwise reject '-e' as naming no existing file. Same #would otherwise reject '-e' as naming no existing file. Only
#carve-out shape as the help flags: only script-default kits #script-default kits intercept it - a tool-style kit keeps '-e'
#intercept it - a tool-style kit keeps '-e' routed to its own #routed to its own processor, and '<punkexe> tclsh -e ...' keeps
#processor, and '<punkexe> tclsh -e ...' keeps stock parity #stock parity (the tclsh subcommand never reaches this path).
#(the tclsh subcommand never reaches this path). #A leading help FLAG (-help etc) is deliberately NOT special
#(2026-08-05 single-mechanism revision): on script-default kits it
#falls to the refusal below, whose stderr usage answers it anyway;
#tool-style kits route it to their processor.
if {$subcommand eq "-e" && $default_unknownfirst eq "script"} { if {$subcommand eq "-e" && $default_unknownfirst eq "script"} {
set subcommand script set subcommand script
} else { } else {
@ -2084,11 +2091,11 @@ if {![info exists ::punkboot::launch_defaults]} {
#exit code; no repl fallthrough (G-015-compatible). #exit code; no repl fallthrough (G-015-compatible).
#G-032: the argument contract ((script)::punkexe::buildinfo, #G-032: the argument contract ((script)::punkexe::buildinfo,
#@values -min 0 -max 0) is enforced through punk::args when #@values -min 0 -max 0) is enforced through punk::args when
#available: any argument earns the historic one-line refusal PLUS #available: any argument (including '-help' - no flag
#the tabled usage error on stderr, exit 2 (buildinfo.test pins the #interception, 2026-08-05 revision) earns the historic one-line
#one-liner and the exit code). The empty-argument fast path never #refusal PLUS the tabled usage error on stderr, exit 2
#touches punk::args; a '-help' first argument was already #(buildinfo.test pins the one-liner and the exit code). The
#intercepted at selection. #empty-argument fast path never touches punk::args.
if {[llength $subcommand_arglist]} { if {[llength $subcommand_arglist]} {
puts stderr "buildinfo: takes no arguments" puts stderr "buildinfo: takes no arguments"
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
@ -2105,9 +2112,14 @@ if {![info exists ::punkboot::launch_defaults]} {
exit [punk::buildinfo::main] exit [punk::buildinfo::main]
} }
help { help {
#G-032: launcher help surface. Bare 'help' renders the top-level #G-032: launcher help surface - THE single help mechanism
#tabled usage (built-ins + project-registered subcommands with #(2026-08-05 revision; the punkshell 'i <cmd> ...' idiom). Bare
#summaries); 'help <subcommand>' renders that subcommand's usage. #'help' renders the top-level tabled usage (built-ins +
#project-registered subcommands with summaries);
#'help <subcommand> ?arg ...?' renders that subcommand's usage -
#words after the subject are accepted and ignored, so 'help' can
#be prepended to any existing command line
#('punk9_beta help tclsh somefile somearg' shows the tclsh usage).
#Subject validation parses through the launcherhelp clone when #Subject validation parses through the launcherhelp clone when
#punk::args is available (tabled choice errors on stderr exit 1, #punk::args is available (tabled choice errors on stderr exit 1,
#unambiguous prefixes resolve); degraded mode accepts exact names #unambiguous prefixes resolve); degraded mode accepts exact names
@ -2118,9 +2130,16 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
if {[::punkboot::launcher_punkargs_ready]} { if {[::punkboot::launcher_punkargs_ready]} {
set helpdef_id [::punkboot::launcher_helpdef_id] set helpdef_id [::punkboot::launcher_helpsubjectdef_id]
if {$helpdef_id ne ""} { if {$helpdef_id ne ""} {
if {[catch {punk::args::parse $subcommand_arglist -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} { #validate/resolve ONLY the subject word (the make.tcl help
#precedent), against the required-subject validation clone:
#a mistyped subject earns the tabled choice error (exit 1)
#instead of being skipped past to render the top-level
#table; everything after the subject is deliberately
#unparsed (accepted and ignored - it is the subject's own
#command line, any mix of its words).
if {[catch {punk::args::parse [lrange $subcommand_arglist 0 0] -errorstyle [::punkboot::launcher_errstyle] withid $helpdef_id} argd]} {
puts stderr $argd puts stderr $argd
exit 1 exit 1
} }
@ -2130,10 +2149,11 @@ if {![info exists ::punkboot::launch_defaults]} {
exit 0 exit 0
} }
} }
#degraded: exact-name subject only #degraded: exact-name subject only; trailing words ignored as in
#the parsed path
set help_subject [lindex $subcommand_arglist 0] set help_subject [lindex $subcommand_arglist 0]
if {[llength $subcommand_arglist] > 1 || $help_subject ni $::punkboot::launch_subcommands} { if {$help_subject ni $::punkboot::launch_subcommands} {
puts stderr "help: expected 'help ?subcommand?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]" puts stderr "help: expected 'help ?subcommand? ?arg ...?' with subcommand one of: [join [::punkboot::launcher_plain_choices] {, }]"
exit 1 exit 1
} }
::punkboot::launcher_show_help $help_subject ::punkboot::launcher_show_help $help_subject

Loading…
Cancel
Save