diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1bdbdb..1d5148b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.41.1] - 2026-08-02 + +- make.tcl top-level help is a rich overview (user-directed, the repl 'i info' + style): bare 'make.tcl help'/'make.tcl'/'-help' now render per-subcommand + cells carrying the one-line summary plus that subcommand's auto-generated + bracket-notation synopsis line(s) - per-form lines for the multi-form + tool/buildsuite - grouped by the existing subcommand groups in two columns. + Built with stock punk::args machinery (rich -choicelabels generated via + punk::args::synopsis per subcommand id + -choicecolumns 2), hung on a + separate (script)::punkboot.overview id resolved only on the help path so + dispatch pays nothing; the lean top-level definition (now also + -choicecolumns 2 for its unknown-subcommand error render) remains the + dispatch surface, and the plain PUNKBOOT_PLAIN fallback help is unchanged. + ## [0.41.0] - 2026-08-02 - make.tcl bake/bakelist kitnames are declaration-authoritative (user-directed, diff --git a/punkproject.toml b/punkproject.toml index 7b4c8919..b8acdce9 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,6 +1,6 @@ [project] name = "punkshell" -version = "0.41.0" +version = "0.41.1" license = "BSD-2-Clause" url = "https://www.gitea1.intx.com.au/jn/punkshell" #packager: declared identity for published artifacts (declarative, not proof - diff --git a/src/AGENTS.md b/src/AGENTS.md index 1347450c..5ff66261 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -69,7 +69,7 @@ Recovery after a wrong path guess: - Provenance warnings (dirty-src gate + vendorupdate source-project check) print with a plain column-0 `PROVENANCE-WARNING:` token (grep for it in captured build output) and are recapped at the end of the run via make.tcl's wrapped `::exit`. Interactive terminal runs get a 3-second ctrl-c grace countdown before a dirty build proceeds; piped/agent runs skip it. `tclsh src/make.tcl check` reports the current src provenance status and what the build commands would do. - Use `tclsh src/make.tcl vfscommonupdate` to rebuild `_vfscommon.vfs`. The REPLACE confirmation prompts on an interactive terminal; unattended/agent runs must pass `-confirm 0` (with the default `-confirm 1` and a piped/closed stdin, make.tcl aborts fast with guidance instead of reading stdin - do not pipe `y`). - `tclsh src/make.tcl workflow` prints an embedded ASCII data-flow overview of the build/release workflow (release-ready command sequence, source→outputs folder flow, kit assembly, keyed notes). The text lives in `::punkboot::workflow_text` in `src/make.tcl` - embedded rather than a data file so it travels with the make.tcl copies seeded into generated projects. **Update contract (agents):** whenever build data flow changes - a make.tcl subcommand added/removed/repurposed, a source or output folder added or rerouted, a propagation target added, a gate (staleness/provenance/confirm) or deploy behaviour changed - update the workflow text in the same change-set. Keep it plain ASCII, max line width 100, and preserve the RELEASE SEQUENCE / DIAGRAM / KEY-NOTES / MAINTENANCE structure; verify by running `tclsh src/make.tcl workflow` (and once under `PUNKBOOT_PLAIN=1`). Adding/changing a subcommand also means: SUMMARIES + HELPTEXTS + its braced `punk::args::define` block (+ SUBGROUPS) in `::punkboot::argdoc`, `known_commands`, the plain `punkboot_gethelp` text, and a project-version bump (make.tcl interface is product surface per root AGENTS.md). Layout make.tcl copies pick the change up via the thin-layout sync step in any `make.tcl modules`/`libs`/`packages`/`bakehouse` run - do not hand-sync them. -- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or ` ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. tool and buildsuite are multi-form definitions (one @form per action with a literal action leader), so whole-subcommand help renders one synopsis line per action, and help accepts the subcommand's own COMMAND LINE after the subject 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 ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions. +- make.tcl subcommands and flags are punk::args-declared (G-030; help depth G-143, achieved): `tclsh src/make.tcl help ?subcommand? ?arg ...?` or ` ?arg ...? -help` shows tabled usage; invalid arguments produce punk::args usage errors. 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 in two columns (`-choicecolumns 2` + 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). 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 ...`, matching bake's flags-before-names convention; the historic flag-anywhere order is deliberately not accepted - a flag-shaped tool name earns a stderr hint). buildsuite keeps its passthrough driver-args contract - its forms serve help/synopsis accuracy only. The dispatch degrades to plain scan/help when the bootsupport punk::args (or its rendering stack) is stale or unavailable; `PUNKBOOT_PLAIN=1` forces the degraded mode for troubleshooting (the degraded tool handler keeps the historic manual tail parse and exit-2 surface). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclhelp.test`. The `::punkboot::argdoc` definitions are braced file-style blocks using the G-045 authoring mechanisms — `-&` record continuations, tstr placeholders pulling SUMMARIES/HELPTEXTS and the shared `OPT_*` option fragments — with `-help` bodies expanding as deferred display fields so the HELPTEXTS block indentation deliberately renders as a centred Description (no `@normalize` re-basing). Synopses are the automatic punk::args bracket notation via the G-144 `@cmd -name` fallback; explicit `@form -synopsis` overrides were retired 2026-08-01 except the top-level `make.tcl ?subcommand? ?flags?` line (it states the bare-invocation/flags reality the auto render cannot) - do not reintroduce per-subcommand overrides without cause. See src/modules/AGENTS.md "punk::args definition authoring ergonomics" before editing them or authoring similar definitions. - Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl/` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk//src/{make.tcl,build.tcl}` + bootsupport manifest copies + the inert `gitignore.in` payload copies refreshed from the root `.gitignore` - G-012), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl/`, `lib_tcl/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns. - **Boot-precondition gate (G-125).** A bake refuses any kit whose merged `.vfs` supplies no tcl library: the kit is listed under `FAILED KITS` with a reason naming the cause, and NOTHING is written - no `src/_build/`, no deploy, and the previously deployed `bin/` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `init.tcl` in `tcl_library/`, `lib/tcl./` or `tcl./` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `/tcl8.6` (the androwish/undroidwish zipfs backport for 8.6 does this) - plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`. - **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BUILD-WARNING`s and the kit still builds and deploys (unlike the G-125 gate). (a) **Binary-arch scan**, at the same post-merge seam as the gate: every binary library (`*.dll`/`*.so`/`*.dylib`) in the merged tree is classified by header bytes (`punkboot::utils::binary_arch_classify` - PE Machine field, ELF e_machine, Mach-O incl. universal; unclassifiable content is honestly `unknown` and never warned about) against the kit's target platform. Structural and non-executing, so it runs for cross-target kits too (measured ~30-140ms per kit, avg ~93ms across the 12 assembled trees). Libraries under a platform-discriminated subdir - canonical `-` platform-dir names, the universal `macosx` folder, or a recognised vendor spelling (`win-x64` etc; the lists are namespace variables in `punkboot::utils`) - are exempt: multi-arch payloads are legitimate there. First real sweep (2026-07-27) found a true latent instance: `zint.dll` 2.13.0 in `src/vfs/punk8win.vfs/lib_tcl8/` is 32-bit (i386) and can never load in the x64 tcl8 kits (punk86/punkbi/punksys) that carry it - those bakes warn until the payload is fixed. (b) **Smoke-require probe**: a kit declaring packages in `src/runtime/mapvfs.toml` (`smokerequire` key - see `src/runtime/AGENTS.md`) has each one plain-`package require`d INSIDE the freshly built artifact via its tclsh subcommand, which exits cleanly even on runtimes whose full repl teardown is fragile. This is the only check that observes actual package RESOLUTION - the punkluck86 2026-07-27 incident class (a higher-versioned wrong-arch Thread shadowing the runtime's working copy on plain require) is invisible to structural checks because the working file exists too. Failures are recapped naming kit, package and the actual error; cross-target kits skip with a stated reason; undeclared kits run nothing new; the probe only runs when the kit actually rebuilds. **What the checks do NOT guarantee:** statically linked packages, pure-tcl packages whose binary dependency lives elsewhere, and version-preference outcomes are visible only to the smoke probe - and only for the declared package set; the arch scan proves nothing about loadability beyond architecture, and an `unknown` classification is silence, not a pass. Both degrade to a NOTE when the bootsupport `punkboot::utils` snapshot is stale (same guarded require as the G-125 gate); `tclsh src/make.tcl check` reports the scan's ACTIVE/UNAVAILABLE state and the currently declared smoke-require kits. diff --git a/src/make.tcl b/src/make.tcl index 7fe7ebb8..f95547b7 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -3765,11 +3765,52 @@ if {[info exists ::env(PUNKBOOT_PLAIN)] && [string is true -strict $::env(PUNKBO #per-subcommand-flags reality the declaration cannot. @form -synopsis "make.tcl ?subcommand? ?flags?" @leaders -min 1 -max 1 - subcommand -type string -optional 0 -choicecolumns 1 -& + subcommand -type string -optional 0 -choicecolumns 2 -& -choicegroups {${$SUBGROUPS}} -& -choicelabels {${$SUMMARIES}} -& -help "Use 'make.tcl help ' or 'make.tcl -help' for details of a subcommand." } + #Rich top-level overview (2026-08-02, the 'i info' style): each subcommand + #cell carries its one-line summary plus the auto-generated bracket-notation + #synopsis line(s) of that subcommand's OWN definition (per-form lines for + #the multi-form tool/buildsuite), grouped by SUBGROUPS in two columns - + #the same punk::args choices-table machinery punk::ns::cmdhelp uses for + #built-in ensembles. Hung on a SEPARATE id so the label cost (a synopsis + #render per subcommand, a few ms total) is paid only when the top-level + #help actually renders: the lean (script)::punkboot definition above stays + #the dispatch surface, and overview_labels runs at lazy resolve time + #(first usage of this id - deliberately NOT in the capability probe below). + proc ::punkboot::argdoc::overview_labels {} { + variable SUMMARIES + set labels [dict create] + dict for {sub summary} $SUMMARIES { + set lines [list] + if {![catch {punk::args::synopsis -noheader (script)::punkboot::$sub} syn]} { + catch {set syn [punk::ansi::ansistrip $syn]} + foreach ln [split $syn \n] { + #drop '## FORM n ' headers - the synopsis lines are + #self-describing and the cells stay compact + if {[string match "## *" [string trim $ln]] || [string trim $ln] eq ""} continue + lappend lines $ln + } + } + dict set labels $sub "# $summary\n[join $lines \n]" + } + return $labels + } + punk::args::define { + @id -id "(script)::punkboot.overview" + @cmd -name "make.tcl" -& + -summary "punkshell project build tool (punk boot)" -& + -help -& + {${$TOPLEVEL_HELP}} + @form -synopsis "make.tcl ?subcommand? ?flags?" + @leaders -min 1 -max 1 + subcommand -type string -optional 0 -choicecolumns 2 -& + -choicegroups {${$SUBGROUPS}} -& + -choicelabels {${[::punkboot::argdoc::overview_labels]}} -& + -help "Use 'make.tcl help ' or 'make.tcl -help' for details of a subcommand." + } #capability probe: force resolution of representative definitions now, inside #the guarded block - a stale punk::args that accepted the raw text but cannot #resolve a mechanism used here (e.g snapshots predating -& record continuation @@ -3991,8 +4032,10 @@ if {$do_help} { set help_shown 0 if {$::punkboot::punkargs_ok} { #tabled usage via punk::args - degrade to the plain fallback help if the rendering - #stack (punk::ansi/textblock etc) can't produce it - set usage_id (script)::punkboot + #stack (punk::ansi/textblock etc) can't produce it. The bare top level renders + #the rich overview id (per-subcommand summary + synopsis cells); the lean + #(script)::punkboot id remains the dispatch surface. + set usage_id (script)::punkboot.overview if {$help_subject ne ""} { set usage_id (script)::punkboot::$help_subject } diff --git a/src/tests/shell/testsuites/punkexe/maketclhelp.test b/src/tests/shell/testsuites/punkexe/maketclhelp.test index 8615039c..031e6fc3 100644 --- a/src/tests/shell/testsuites/punkexe/maketclhelp.test +++ b/src/tests/shell/testsuites/punkexe/maketclhelp.test @@ -281,18 +281,23 @@ namespace eval ::testspace { set result } -result {timedout 0 exitcode 0 synbuild 1 testopt 1 timedout2 0 exitcode2 0 synbuild2 1 landed2 1} - #added 2026-08-01 (agent, G-143) - test maketcl_help_toplevel_unchanged {bare 'make.tcl help' still renders the top-level subcommand table} -constraints {tclshavailable} -body { + #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): + #per-subcommand cells carry the one-line summary plus that subcommand's own + #auto-generated synopsis line(s), incl. per-form lines for tool/buildsuite, + #grouped in two columns + test maketcl_help_toplevel_overview {bare 'make.tcl help' renders the rich top-level overview: ESC-free, per-subcommand synopsis lines incl. the tool forms} -constraints {tclshavailable} -body { set r [maketcl_run {help}] set out [dict get $r output] set result [list] lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] lappend result esc [esc_count $out] lappend result subcmd [regexp {subcommand} $out] - lappend result bakehouse [regexp {bakehouse} $out] - lappend result toolrow [regexp {tool} $out] + lappend result bakehousesyn [regexp {make\.tcl bakehouse \[-k\] \[-dirty-abort \] \[-confirm \]} $out] + lappend result toolformsyn [regexp {make\.tcl tool build \[-test \] \[toolname\]\.\.\.} $out] + lappend result bsformsyn [regexp {make\.tcl buildsuite build suitename \[driverarg\]\.\.\.} $out] set result - } -result {timedout 0 exitcode 0 esc 0 subcmd 1 bakehouse 1 toolrow 1} + } -result {timedout 0 exitcode 0 esc 0 subcmd 1 bakehousesyn 1 toolformsyn 1 bsformsyn 1} #added 2026-08-01 (agent, G-143) test maketcl_tool_dispatch_usage_errors {'make.tcl tool ' and an option-position unknown flag produce pointed punk::args usage errors (exit 1)} -constraints {tclshavailable} -body {