Browse Source

G-155 increment A: stage vocabulary landed - mint/promote/bake strict, BAKE-WARNING, _mint/_bake workdirs (payload-frozen)

make.tcl: TERMINOLOGY block in workflow_text + charter now "one mint stage,
two promotion gates, one bake"; release-sequence rows mint/promote/bake;
SUMMARIES/HELPTEXTS/plain-help mint phrasing; SUBGROUPS "mint & bake";
BUILD-WARNING -> BAKE-WARNING (emit+recap+prose, internals renamed);
bakelist nobuild -> nobake + "bake product:"; workdir src/_build -> src/_bake
with guarded get_bake_workdir call (inline fallback for stale punk::mix
snapshots); producing-commands gate wording (also fixed stale project/vfs
alias names in check output); internal vars bakefolder/baking_runtime/
kit_bakedir/bake_vfs etc.

punk::mix::base 0.2.0: get_build_workdir -> get_bake_workdir (src/_bake) with
legacy delegating alias; antipatterns + _mint/_bake; RETIRED the never-called
vfs-cksums quartet (store_vfs_build_cksums called an undefined proc + unset
var - dead code, zero callers repo-wide).
punk::mix::cli 0.6.0: modpod/tarjar staging <srcdir>/_build -> <srcdir>/_mint
(mintfolder), antidir/reserved lists + _mint/_bake, src/_bake running-check,
mint-verb error text, dead 'build' assignment removed;
build_modules_from_source_to_base name KEPT (punkcheck installer identity).
punkboot::utils 0.6.1: argdoc names BAKE-WARNING.

Ignore rules: **/_mint/ + **/_bake/ added in .gitignore and derived
ignore-glob (**/_build/ retained for buildsuites + stale checkouts);
extras|check-ignore verification empty.

Docs swept (root+src AGENTS, ARCHITECTURE incl heading "Mint, bake,
provenance and kits", READMEs, modules/vfs/runtime/bootsupport/buildsuites/
tools AGENTS, tests/modules AGENTS); archived goals + CHANGELOG untouched.
Tests: BAKE-WARNING/nobake/src/_bake pins updated (offsetstyle,
maketclpayloadcheck, maketclbakelist, kitlocations, platform, help,
binaryarch, bootlibrary); punkcheck install.test fixtures untouched (they
characterize punkcheck's own default excludes).

Payload deliberately frozen this increment (no vfs/_config or vfscommon
changes) so the byte-compare bake can prove the plumbing. architecture_lint
clean; workflow renders in tabled + PUNKBOOT_PLAIN modes.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 2 weeks ago
parent
commit
c4908d9bd9
  1. 5
      .fossil-settings/ignore-glob
  2. 4
      .gitignore
  3. 12
      AGENTS.md
  4. 20
      ARCHITECTURE.md
  5. 12
      README.md
  6. 32
      bin/AGENTS.md
  7. 46
      src/AGENTS.md
  8. 6
      src/README.md
  9. 14
      src/bootsupport/AGENTS.md
  10. 2
      src/buildsuites/AGENTS.md
  11. 630
      src/make.tcl
  12. 8
      src/modules/AGENTS.md
  13. 104
      src/modules/punk/mix/base-999999.0a1.0.tm
  14. 3
      src/modules/punk/mix/base-buildversion.txt
  15. 93
      src/modules/punk/mix/cli-999999.0a1.0.tm
  16. 3
      src/modules/punk/mix/cli-buildversion.txt
  17. 2
      src/modules/punkboot/utils-999999.0a1.0.tm
  18. 3
      src/modules/punkboot/utils-buildversion.txt
  19. 12
      src/runtime/AGENTS.md
  20. 2
      src/tests/modules/AGENTS.md
  21. 12
      src/tests/modules/punkboot/utils/testsuites/utils/binaryarch.test
  22. 12
      src/tests/modules/punkboot/utils/testsuites/utils/bootlibrary.test
  23. 14
      src/tests/modules/punkboot/utils/testsuites/utils/offsetstyle.test
  24. 10
      src/tests/shell/testsuites/punkexe/maketclbakelist.test
  25. 12
      src/tests/shell/testsuites/punkexe/maketclkitlocations.test
  26. 4
      src/tests/shell/testsuites/punkexe/maketclpayloadcheck.test
  27. 2
      src/tests/shell/testsuites/punkexe/maketclplatform.test
  28. 4
      src/tools/AGENTS.md
  29. 10
      src/vfs/AGENTS.md

5
.fossil-settings/ignore-glob

@ -32,6 +32,11 @@ _aside
*/_aside
_build
*/_build
#G-155 stage workdirs (see .gitignore): _mint module staging + _bake kit assembly
_mint
*/_mint
_bake
*/_bake
scratch*
*/scratch*

4
.gitignore vendored

@ -32,6 +32,10 @@
/logs/
**/_aside/
**/_build/
#G-155 stage workdirs: src/_bake (kit assembly) + per-srcdir _mint (module staging);
#**/_build/ stays for src/buildsuites/_build (true compile area) and stale checkouts
**/_mint/
**/_bake/
scratch*
#Built documentation

12
AGENTS.md

@ -93,7 +93,7 @@ Default section order:
When the user requests a durable behavior change, record it here or in the relevant child AGENTS.md
- LF line endings are strongly preferred for all files in this repository. Converting a CRLF text file to LF when an edit touches it is correct and welcome - do not preserve CRLF for diff-minimisation. Preserve existing line endings only for files with deliberately mixed/CRLF endings (e.g. line-ending round-trip test data) or when explicitly instructed for a file.
- If the active editor is on a source-derived snapshot, bootstrap copy, or build output path such as `src/bootsupport/`, root `modules/`, root `lib/`, `modules_tcl8/`, `modules_tcl9/`, `lib_tcl8/`, or `lib_tcl9/`, confirm the intended target before editing unless the user explicitly named that path.
- If the active editor is on a source-derived snapshot, bootstrap copy, or generated output path such as `src/bootsupport/`, root `modules/`, root `lib/`, `modules_tcl8/`, `modules_tcl9/`, `lib_tcl8/`, or `lib_tcl9/`, confirm the intended target before editing unless the user explicitly named that path.
- cmd.exe PATH truncation (this machine, and any Windows machine with a heavily populated PATH): cmd.exe truncates a long PATH, so a tool that resolves fine in PowerShell may be "not found" when invoked via `cmd.exe /c`. Use absolute executable paths inside any `cmd /c` command line, and prefer PowerShell-native invocation unless a console host is specifically required (e.g. hidden-console test harnesses). If a tool is missing only under cmd.exe, suspect truncation before absence.
- Windows paths under POSIX shells (WSL, msys/git-bash): a `C:/...` path passed unconverted to a POSIX file command (`mkdir -p`, `cp`, output redirects) is a RELATIVE path - it silently creates a stray directory tree in the cwd whose first component is literally named `C:` (the NTFS-illegal `:` is stored as private-use character U+F03A, so it renders as `C` plus an odd dot/glyph in Explorer, and while the tree contains only empty directories it is invisible to git and fossil). Translate at the boundary (`wslpath` for WSL, `cygpath` for msys - msys auto-conversion does NOT cover paths inside quoted command strings or when MSYS_NO_PATHCONV/MSYS2_ARG_CONV_EXCL is set), and after WSL/msys-driven work glance at the repo root for a stray `C*` entry. (Origin: G-140 WSL smokes leaked an empty temp-dir skeleton into the repo root, found and removed 2026-07-31.)
- Agent-authored text is plain ASCII by default: no em/en dashes, curly quotes, arrow or ellipsis characters, or other typographic Unicode - use ASCII equivalents (" - ", straight quotes, "->", "..."). The rule governs elements the agent generates, with extra force for outward-bound artifacts (ticket drafts, bug reports, emails, commit messages - anything likely to be pasted into an external system), which are verified before handover (e.g. grep for `[^\x00-\x7F]`). The exceptions are illustrative, not a closed list: non-ASCII subject matter (encoding/Unicode/ANSI-art test data, or documentation demonstrating such behaviour), verbatim quotes of existing material, and explicit user request are the common cases, but any good reason qualifies - state the reason when deviating. Content the agent did not author is outside the rule: existing files are never bulk-retrofitted, and while checking non-authored content (e.g. files being committed on the user's behalf) is fine, non-ASCII there is the author's prerogative - if unsure whether it is intentional, stop and ask rather than fix or block.
@ -145,13 +145,13 @@ The punkshell project version is tracked in `punkproject.toml` (`[project] versi
An agent must bump the `punkproject.toml` version as part of its DOX closeout pass whenever its change ships user-visible shell behaviour. This is change-driven, not release-driven — the version stays honest between releases.
- **Patch** — bug fixes, internal refactors, doc-only updates that ship in a build without changing user-facing shell behaviour.
- **Patch** — bug fixes, internal refactors, doc-only updates that ship in baked kits without changing user-facing shell behaviour.
- **Minor** — new shell commands, new launchers, new default modules visible at the REPL, backward-compatible behaviour additions.
- **Major** — removed commands, changed default behaviour, changed launch invocation, breaking changes to the shell's user-facing contract.
Changes confined to tests, build tooling internals, or non-shipped surfaces do not require a bump. When in doubt, bump patch.
Changes confined to tests, make tooling internals, or non-shipped surfaces do not require a bump. When in doubt, bump patch.
The `make.tcl` command interface is part of the product surface: new subcommands, changed subcommand behaviour, or removed subcommands warrant at least a patch bump. Internal refactors of an existing subcommand's implementation that leave its interface and output unchanged stay exempt as build tooling internals.
The `make.tcl` command interface is part of the product surface: new subcommands, changed subcommand behaviour, or removed subcommands warrant at least a patch bump. Internal refactors of an existing subcommand's implementation that leave its interface and output unchanged stay exempt as make tooling internals.
### Changelog
@ -171,12 +171,12 @@ The project version is fully independent of module versions. A module bump (even
## Child DOX Index
- `src/` — Source tree root; editable source code, build scripts, tests, VFS payloads, vendor deps, docs (see src/AGENTS.md)
- `src/` — Source tree root; editable source code, make.tcl tooling, tests, VFS payloads, vendor deps, docs (see src/AGENTS.md)
- `src/modules/` — Main editable module source (see src/modules/AGENTS.md)
- `src/modules/punk/` — Core punk namespace modules (see src/modules/punk/AGENTS.md)
- `src/modules/test/` — Installed-module test packages (see src/modules/test/AGENTS.md)
- `src/modules/opunk/` — Alternative punk namespace, voo-based classes (see src/modules/opunk/AGENTS.md)
- `src/modules/punkcheck/`Build/check system
- `src/modules/punkcheck/`Install/provenance-check system
- `src/modules_tcl8/` — Tcl 8 specific modules (see src/modules_tcl8/AGENTS.md)
- `src/modules_tcl9/` — Tcl 9 specific modules (see src/modules_tcl9/AGENTS.md)
- `src/lib/` — Editable library source (see src/lib/AGENTS.md)

20
ARCHITECTURE.md

@ -18,7 +18,7 @@ Rules that keep it useful instead of rotten:
## The ten-thousand-foot view
Punkshell is an alternative Tcl shell distributed as self-contained executables ("kits"): a zipfs/metakit-aware Tcl runtime wrapped with a virtual filesystem (VFS) payload carrying the punk module set. A kit boots through a single entry script, which selects a package mode (which module sources to trust) and a subcommand (which application to run: interactive shell, non-interactive script runner, stock tclsh emulation, and others). The interactive shell is a REPL in the main interp that evaluates user code in a separate code thread and talks to the terminal through a two-layer console abstraction. Everything is built, tested and released by a Tcl-driven build system (`src/make.tcl`) with file-level provenance tracking (`punkcheck`).
Punkshell is an alternative Tcl shell distributed as self-contained executables ("kits"): a zipfs/metakit-aware Tcl runtime wrapped with a virtual filesystem (VFS) payload carrying the punk module set. A kit boots through a single entry script, which selects a package mode (which module sources to trust) and a subcommand (which application to run: interactive shell, non-interactive script runner, stock tclsh emulation, and others). The interactive shell is a REPL in the main interp that evaluates user code in a separate code thread and talks to the terminal through a two-layer console abstraction. Everything is minted, baked, tested and released by a Tcl-driven make system (`src/make.tcl`) with file-level provenance tracking (`punkcheck`).
```
bin/<kit>.exe = Tcl runtime (zipfs/metakit) + <name>.vfs payload
@ -43,7 +43,7 @@ app package from src/lib/ (app-punkshell, app-punkscript, app-repl, app-shellsp
- **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`. An optional first argument is a dash-delimited package mode composed of tokens from `dev`, `os`, `src`, `internal` (`internal` is always appended when absent), optionally scoped with the `proj:` prefix (G-033: `dev`/`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`. Any other first argument is treated as a script invocation; no arguments at all means interactive shell. Subcommands map to app packages under `src/lib/` (`app-punkshell`, `app-punkscript`, `app-repl`, `app-shellspy`).
- **Package modes pick module provenance.** `internal` uses module snapshots baked into the kit at build time; `dev` loads build outputs from 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; `dev` loads minted outputs from 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`.
- **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. App package: `src/lib/app-punkscript/`.
@ -80,23 +80,23 @@ Two layers with a deliberate dependency direction (the class never depends on th
- **Editable modules** live in `src/modules/` (+ `src/modules_tcl8/`, `src/modules_tcl9/` siblings); filenames carry the magic dev version `999999.0a1.0` with the real semver in a `<name>-buildversion.txt` sidecar, and namespaces mirror directory layout (`punk/ansi/` = `punk::ansi`). `punk::libunknown` is the manually-versioned exception. Source: `src/modules/AGENTS.md`.
- **`punk::args` is both parser and documentation system.** `PUNKARGS`/`argdoc` blocks travel with each proc and power inline usage tables (`i <command>` at the repl); even manually-parsing procs carry documentation-only definitions. Much of the goals-era work has been hardening this system - see the `punkargs` entries across `GOALS.md` and `GOALS-archive.md`.
- **Discovery and modpods.** `punk::libunknown` provides module discovery/registration beyond `tcl::tm` defaults; `#modpod-*` source directories pack into zip-based multi-file `.tm` modules at build time (src mode registers them via an inline boot scanner written in builtins).
- **Bootsupport.** `src/bootsupport/` holds snapshot copies of build-time-critical modules (manifest: `include_modules.config`), analogous to devDependencies. `make.tcl` classifies snapshot staleness for five runtime-critical packages (`punkcheck`, `punk::repo`, `punk::mix`, `punk::tdl`, `punk::args`) as abort/prompt/warn from the version delta. Source: `src/bootsupport/AGENTS.md`.
- **Discovery and modpods.** `punk::libunknown` provides module discovery/registration beyond `tcl::tm` defaults; `#modpod-*` source directories pack into zip-based multi-file `.tm` modules at mint time (src mode registers them via an inline boot scanner written in builtins).
- **Bootsupport.** `src/bootsupport/` holds snapshot copies of make-critical modules (manifest: `include_modules.config`), analogous to devDependencies. `make.tcl` classifies snapshot staleness for five runtime-critical packages (`punkcheck`, `punk::repo`, `punk::mix`, `punk::tdl`, `punk::args`) as abort/prompt/warn from the version delta. Source: `src/bootsupport/AGENTS.md`.
- **opunk layer.** `src/modules/opunk/` explores `voo`-based value-OO reimplementations (objects as plain Tcl values); the `punk::*` modules remain the production implementations.
- **Vendored code.** Third-party packages live in `src/vendormodules/` and `src/vendorlib/` (+ `_tcl8`/`_tcl9` siblings), refreshed by `make.tcl vendorupdate` from config; never hand-edited (enforcement policy is proposed as G-026).
## Build, provenance and kits
## Mint, bake, provenance and kits
- **`src/make.tcl` is the single build entry.** Key subcommands: `modules`/`libs`/`packages` (repo-root build outputs), `vfscommonupdate` (regenerate `_vfscommon.vfs`), `bake` (kit assembly into `bin/`; optional kit names or `@group` selectors confine the run to those kits - G-121/G-024; `bake_default=false` entries build only when selected), `bakelist` (report the configured kit matrix with runtime/vfs presence, deployed state, groups and scheme roles), `vfslibs` (materialize the per-.vfs payload declarations - G-115), `bin`, `bakehouse` (packages + bake for a clean checkout - G-112 rename, achieved; the transitional `project`/`vfs` aliases were removed 2026-08-01), `bootsupport` (snapshot refresh), `vendorupdate`, `check`, `workflow`, `projectversion`. Dispatch and help dogfood `punk::args` (G-030, achieved) and degrade to plain scan/help when bootsupport `punk::args` is stale (`PUNKBOOT_PLAIN=1` forces the degraded mode). Source: `src/AGENTS.md`.
- **`src/make.tcl` is the single make entry.** Stage verbs are strict (TERMINOLOGY block in `make.tcl workflow` - G-155): build = compile (buildsuites/tool), mint = stamp/pack modules from src, promote = the gates, bake = kit assembly. Key subcommands: `modules`/`libs`/`packages` (mint - repo-root output trees), `vfscommonupdate` (regenerate `_vfscommon.vfs`), `bake` (kit assembly into `bin/`; optional kit names or `@group` selectors confine the run to those kits - G-121/G-024; `bake_default=false` entries bake only when selected), `bakelist` (report the configured kit matrix with runtime/vfs presence, deployed state, groups and scheme roles), `vfslibs` (materialize the per-.vfs payload declarations - G-115), `bin`, `bakehouse` (packages + bake for a clean checkout - G-112 rename, achieved; the transitional `project`/`vfs` aliases were removed 2026-08-01), `bootsupport` (snapshot refresh), `vendorupdate`, `check`, `workflow`, `projectversion`. Dispatch and help dogfood `punk::args` (G-030, achieved) and degrade to plain scan/help when bootsupport `punk::args` is stale (`PUNKBOOT_PLAIN=1` forces the degraded mode). Source: `src/AGENTS.md`.
- **Host vs target platform (G-122).** The kit surfaces separate what the driving tclsh IS from what a bake is FOR. Target-keyed: the `bin/runtime/<tier>` store a runtime is read from, `.exe` suffixing of runtimes and kit outputs, presence checks, and the pre-deploy process sweep's tooling (`tasklist`/`taskkill` vs `ps`/`kill`, skipped when the target's processes cannot exist on this host). Host-keyed: copy commands, path handling, filesystem case rules, prompts. The default target is the host canon except for cygwin-family hosts (msys2/cygwin-runtime tclsh - `tcl_platform(platform)` `unix` on windows), which target `win32-x86_64`; a `mapvfs.toml` entry may declare its own target and is then addressed in that platform's tier. `make.tcl check` prints the derivation. Zip-type kits assemble without zipfs in the driving tcl (raw-runtime split + `punk::zip::mkzip` + concatenation), and since G-124 they EXTRACT without it too: `punk::zip` reads a zip - plain, or attached to an executable under either offset convention - with stock Tcl only, so a bake needs neither zipfs nor tcllib to carry a runtime's `tcl_library` into the kit. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punk/platform-999999.0a1.0.tm`, `src/modules/punk/zip-999999.0a1.0.tm`.
- **punkcheck** records every install/delete as events in per-folder `.punkcheck` directories - the basis for skip/copy change detection, superseded-module pruning and provenance. Single OO record lifecycle (G-094, achieved) with atomic saves and advisory event-scoped locking for concurrent writers (G-095, achieved).
- **Provenance gates.** Build/promotion commands warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts.
- **Provenance gates.** Producing commands (mint/promote/bake) warn on uncommitted `src/` changes (column-0 `PROVENANCE-WARNING:` token, `-dirty-abort` for strict mode); `vendorupdate` warns for dirty source-project checkouts.
- **Boot-precondition gate (G-125).** A bake refuses a kit whose merged `.vfs` supplies no tcl library rather than deploying an artifact that cannot initialise: the kit lands in `FAILED KITS` and nothing is written, so the previously deployed `bin/<kit>` survives. Structural and non-executing (so cross-target kits are covered), reading the merged tree rather than the extraction outcome. The predicate is `punkboot::utils::vfs_boot_library_report`, called through the same guarded require as the provenance check so a stale bootsupport snapshot degrades it to a notice; `make.tcl check` reports ACTIVE/UNAVAILABLE. Sources: `src/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Payload/target consistency checks (G-133 + G-134), all advisory.** At the same post-merge seam, a bake classifies each binary library in the merged tree by header (`punkboot::utils::binary_arch_classify` - PE/ELF/Mach-O, honest unknowns) against the kit's target platform; wrong-arch libraries outside platform-discriminated subdirs (canonical `<os>-<cpu>` names, vendor spellings like `win-x64`) earn recapped `BUILD-WARNING`s. After assembly, a kit declaring smoke-require packages (`mapvfs.toml` `smokerequire` key) has each one plain-`package require`d inside the freshly built artifact via its tclsh subcommand - the only check that sees resolution-order defects (wrong-arch version shadowing); cross-target kits skip with a stated reason. The assembled image is also probed for its zip offset convention (G-134, `punkboot::utils::kit_offsetstyle_report` over `punk::zip::archive_info`): a FILE-relative attached payload warns (the pipeline emits archive-relative; the G-128 stamper refuses file-relative by default), no-zip/plain results stay silent. Same guarded-require degradation; `make.tcl check` reports all three. None guarantees statics, pure-tcl packages with binary deps, or version preference beyond the declared smoke set. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Kit icon step (G-057 + G-128).** Every kit a bake builds gets a `<kitname>.resources.toml` sidecar (deployed beside `bin/<kit>`) recording the build-time icon choice - default `src/runtime/punkshell.ico`, overridable by a root `punkshell.ico` in the kit's own custom `.vfs` folder (pre-merge) - with sha256 identity and provenance lifted from the icon's G-135 assetorigin record. win32-target kits get the icon embedded as PE `RT_ICON`/`RT_GROUP_ICON` behind a single seam (`::punkboot::kit_icon_process`): the vendored punkres portable stamper (`src/tools/punkres`, built to `bin/punkres(.exe)` by the make.tcl tool step) is selected when present and works from any build host; otherwise the twapi arm (tcl-sfe mechanism) serves windows hosts with nothing built. Both arms share one semantic (delete all icon/group entries, write ids 1..N; the group is NAMED from the icon file's uppercased rootname - `PUNKSHELL` - with language adopted from the replaced group) and stamp a per-kit copy of the payload-free raw runtime prefix BEFORE payload attach; punkres can also stamp a FINISHED kit post-hoc, preserving an appended zip payload (archive-relative moved verbatim; file-relative refused by default, shifted with a consent flag). Non-PE targets skip as not applicable; a host where neither mechanism serves skips with a combined notice naming the punkres build remedy; the sidecar is written in every case. Sources: `src/AGENTS.md`, `bin/AGENTS.md`, `src/vfs/AGENTS.md`, `src/tools/AGENTS.md`.
- **Payload/target consistency checks (G-133 + G-134), all advisory.** At the same post-merge seam, a bake classifies each binary library in the merged tree by header (`punkboot::utils::binary_arch_classify` - PE/ELF/Mach-O, honest unknowns) against the kit's target platform; wrong-arch libraries outside platform-discriminated subdirs (canonical `<os>-<cpu>` names, vendor spellings like `win-x64`) earn recapped `BAKE-WARNING`s. After assembly, a kit declaring smoke-require packages (`mapvfs.toml` `smokerequire` key) has each one plain-`package require`d inside the freshly built artifact via its tclsh subcommand - the only check that sees resolution-order defects (wrong-arch version shadowing); cross-target kits skip with a stated reason. The assembled image is also probed for its zip offset convention (G-134, `punkboot::utils::kit_offsetstyle_report` over `punk::zip::archive_info`): a FILE-relative attached payload warns (the pipeline emits archive-relative; the G-128 stamper refuses file-relative by default), no-zip/plain results stay silent. Same guarded-require degradation; `make.tcl check` reports all three. None guarantees statics, pure-tcl packages with binary deps, or version preference beyond the declared smoke set. Sources: `src/AGENTS.md`, `src/runtime/AGENTS.md`, `src/modules/punkboot/utils-999999.0a1.0.tm`.
- **Kit icon step (G-057 + G-128).** Every kit a bake produces gets a `<kitname>.resources.toml` sidecar (deployed beside `bin/<kit>`) recording the bake-time icon choice - default `src/runtime/punkshell.ico`, overridable by a root `punkshell.ico` in the kit's own custom `.vfs` folder (pre-merge) - with sha256 identity and provenance lifted from the icon's G-135 assetorigin record. win32-target kits get the icon embedded as PE `RT_ICON`/`RT_GROUP_ICON` behind a single seam (`::punkboot::kit_icon_process`): the vendored punkres portable stamper (`src/tools/punkres`, built to `bin/punkres(.exe)` by the make.tcl tool step) is selected when present and works from any bake host; otherwise the twapi arm (tcl-sfe mechanism) serves windows hosts with nothing built. Both arms share one semantic (delete all icon/group entries, write ids 1..N; the group is NAMED from the icon file's uppercased rootname - `PUNKSHELL` - with language adopted from the replaced group) and stamp a per-kit copy of the payload-free raw runtime prefix BEFORE payload attach; punkres can also stamp a FINISHED kit post-hoc, preserving an appended zip payload (archive-relative moved verbatim; file-relative refused by default, shifted with a consent flag). Non-PE targets skip as not applicable; a host where neither mechanism serves skips with a combined notice naming the punkres build remedy; the sidecar is written in every case. Sources: `src/AGENTS.md`, `bin/AGENTS.md`, `src/vfs/AGENTS.md`, `src/tools/AGENTS.md`.
- **Buildsuites and the kit family.** `src/buildsuites/suite_tcl90/` builds Tcl/Tk/tcllib from source with a pinned zig toolchain and produces the runtime kit family (plain / punk / bi) plus artifact metadata (G-096-G-117 era: see archived goals G-096, G-098, G-102, G-103, G-107); artifacts publish to the punkbin repo that `bin/punk-runtime.cmd` fetches from. Both suites also emit the punkbin LIBRARY tier (G-138, achieved): verified tcllib/tcllibc installs as generation-tagged immutable zips (`lib/allplatforms` + `lib/<target>`, embedded schema-v2 class=library records, punkzip-deterministic) - the `library-artifacts` step in each recipe. The tier's `<target>` axis includes cross-built lanes: suite_tcl90's `tcllibc-linux` step (G-140, achieved) cross-builds the critcl accelerators for linux-x86_64 via critcl driving `zig cc -target` (stubs linkage - no linux host involved), structurally ELF-gated on the build host and consumed by the punk9linux kit payload. Consumption (G-139, achieved): `make.tcl libfetch` materializes declared artifacts into the untracked `bin/packages/<target>` tier with server-trust consent, and the PACKAGES_tcl<N> libs phase + per-.vfs payload declarations with `source_root = "packages"` (G-115) feed the deployed `lib_tcl<N>` trees and kit vfs payloads - the vendored tcllib trees are retired, and embedded records ride into baked kits. `src/buildsuites/suite_tcl86/` is the 8.6 sibling (G-099 + G-100, both achieved): a Tcl 8.6 windows runtime (static + dynamic shells, `tcl86t.dll`, on-disk lib tree - no zipfs, so no self-contained kit) with thread 2.8 + tclvfs + Tk 8.6 + tklib + tcllib(+tcllibc critcl accelerators) companions, gated against the 8.6 core/thread/tclvfs testsuites with tcllib/tklib on a record tier and an opt-in `test-tk`; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrub `TCL<major>_<minor>_TM_PATH` as well as TCLLIBPATH/TCL_LIBRARY/TK_LIBRARY - without it a census measures the machine's module trees. The 8.6 kit container strategy remains in-flux - see "In-flux areas".
- **Project generation.** `dev project.new` composes thin layouts from `src/project_layouts/` (overlay chain with `.anti` deletion markers and `name@base` derivation - G-087, achieved; VCS-config payloads stored inert as `gitignore.in` and materialized to `.gitignore` at composition - G-012) and injects bootsupport modules from the generating shell at generation time.
- **Workflow overview.** `tclsh src/make.tcl workflow` prints the embedded ASCII data-flow overview of the build/release pipeline, with its own update contract in `src/AGENTS.md`. This section deliberately summarises rather than copies it.
- **Workflow overview.** `tclsh src/make.tcl workflow` prints the embedded ASCII data-flow overview of the release pipeline (TERMINOLOGY + mint/promote/bake), with its own update contract in `src/AGENTS.md`. This section deliberately summarises rather than copies it.
## Test harness

12
README.md

@ -11,22 +11,22 @@ Version 0.11.0 (2026-07) — this is **alpha** level software and still highly e
### Getting Started
The project uses a Tcl-based build system (`src/make.tcl`). From the project root:
The project uses a Tcl-based make system (`src/make.tcl`) with strict stage verbs - mint (stamp/package modules from src), promote (the gates), bake (assemble kits); 'build' means compiling binaries (buildsuites/tools). `tclsh src/make.tcl workflow` prints the TERMINOLOGY key and data flow. From the project root:
```
# Fetch a suitable Tcl runtime (cross-platform polyglot wrapper — runs from bash, powershell, or cmd.exe)
./bin/punk-runtime.cmd fetch
# Build modules and libraries into the project root
# Mint modules and libraries into the project root
tclsh src/make.tcl packages
# Full consumer build from a clean checkout (packages + baked kit binaries)
# Full consumer run from a clean checkout (minted packages + baked kit binaries)
tclsh src/make.tcl bakehouse
```
See `src/README.md` for detailed build instructions and `bin/AGENTS.md` for the runtime manager.
See `src/README.md` for detailed make instructions and `bin/AGENTS.md` for the runtime manager.
For evaluating uncommitted source without a full build, a built executable can run directly against the source tree:
For evaluating uncommitted source without minting or baking, a built executable can run directly against the source tree:
```
<punkexe> src shell
@ -139,7 +139,7 @@ For evaluating uncommitted source without a full build, a built executable can r
### Documentation
- `src/README.md` — detailed build instructions
- `src/README.md` — detailed make instructions
- `CHANGELOG.md` — version history and recent changes
- `AGENTS.md` (root) + child AGENTS.md files — contributor guidance and project structure (DOX hierarchy)
- `GOALS.md` — technical goal index with per-goal detail files under `goals/`

32
bin/AGENTS.md

@ -2,7 +2,7 @@
## Purpose
Built punk shell executables (kits with the punk boot layer), assorted build/experiment tooling, and plain runtime kits under `runtime/`. Executables here are build outputs - agents do not hand-edit binaries.
Baked punk shell executables (kits with the punk boot layer), assorted tooling, and plain runtime kits under `runtime/`. Executables here are outputs of the bake and tool-build steps - agents do not hand-edit binaries.
### Generated polyglot .cmd scripts - never edit in place
@ -155,19 +155,19 @@ A `bin/<kit>` that a bake deployed has passed the G-125 boot-precondition gate (
merged vfs supplies a tcl library - a kit failing that is never deployed) and the
G-133 advisory checks have run: a structural binary-arch scan of its payload (a
wrong-arch `.dll`/`.so`/`.dylib` outside a platform-discriminated subdir earned a
recapped `BUILD-WARNING` - advisory, so heed the bake recap before trusting a warned
recapped `BAKE-WARNING` - advisory, so heed the bake recap before trusting a warned
kit) and, when its `src/runtime/mapvfs.toml` entry declares smoke-require packages
and the kit is host-runnable, a plain `package require` of each inside the built
artifact via its tclsh subcommand. The assembled image was also probed for its zip
offset convention (G-134, advisory): a FILE-relative attached payload earned a
recapped `BUILD-WARNING` (the pipeline emits archive-relative; the G-128 stamper
recapped `BAKE-WARNING` (the pipeline emits archive-relative; the G-128 stamper
refuses file-relative by default), while kits with no attached zip (metakit shapes)
probe as `none` and stay silent. What that does NOT guarantee: statically linked
packages and pure-tcl packages with binary dependencies are invisible to the arch
scan; package-resolution outcomes (a wrong-arch higher-versioned copy shadowing a
working one) are visible only to the smoke probe and only for the declared package
set; and an unclassifiable binary is silence, not a pass. A kit whose bake recap
showed no `BUILD-WARNING`s still only proves what those checks measure - not that
showed no `BAKE-WARNING`s still only proves what those checks measure - not that
every payload package loads.
### Cross-target kit outputs (`bin/kits/<platform>/`, G-127)
@ -176,11 +176,11 @@ Kit OUTPUT locations are keyed by each kit's TARGET platform (the mapping's `tar
key - G-122's output half):
- A kit for this host's own default target deploys flat to `bin/<kit>` exactly as
always (build product `src/_build/<kit>`).
- Any other target's kit deploys to `bin/kits/<platform>/<kit>` (build product
`src/_build/kits/<platform>/<kit>`), each tier with its own `.punkcheck` install
always (bake product `src/_bake/<kit>`).
- Any other target's kit deploys to `bin/kits/<platform>/<kit>` (bake product
`src/_bake/kits/<platform>/<kit>`), each tier with its own `.punkcheck` install
ledger and the `<kit>.resources.toml` sidecar beside the kit as usual. `bin/kits/`
rather than `bin/<platform>/` because `bin/runtime/<platform>/` already means build
rather than `bin/<platform>/` because `bin/runtime/<platform>/` already means bake
INPUTS - the two tiers sit side by side as input vs output.
- Same-named kits for DIFFERENT targets therefore coexist (the artifact path is a
pure function of name + target, and `.exe` suffixing follows the target); only a
@ -188,7 +188,7 @@ key - G-122's output half):
- `make.tcl bakelist` marks non-default-target rows with `out=kits/<platform>/` and
its per-kit detail block prints the tiered paths; a kit NAME spanning several
targets selects all of them (bakelist and selective bake alike).
- Everything under `bin/kits/` is a build output covered by the existing `/bin/*`
- Everything under `bin/kits/` is a bake output covered by the existing `/bin/*`
ignore rules in both VCS - never commit from it.
- Relocations at the 2026-07-31 switch: the linux `punkshell902` moved from
`bin/punkshell902` to `bin/kits/linux-x86_64/punkshell902` (intended relocation -
@ -200,9 +200,9 @@ key - G-122's output half):
### Kit resource record sidecar + embedded icon (G-057)
Every kit a bake builds ships with a `bin/<kitname>.resources.toml` text
sidecar (deployed beside the kit; the build copy sits in `src/_build/`).
It records the build-time icon choice for EVERY target, and on win32 targets
Every kit a bake produces ships with a `bin/<kitname>.resources.toml` text
sidecar (deployed beside the kit; the bake copy sits in `src/_bake/`).
It records the bake-time icon choice for EVERY target, and on win32 targets
baked by a capable host the same icon is embedded into the kit executable as
PE RT_ICON/RT_GROUP_ICON resources (stub stamped before the payload is
appended so the vfs payload is never at risk; runtime store originals are
@ -250,10 +250,10 @@ RT_VERSION stamping extends the same file.
and twapi unloadable or a non-windows build host) records `unavailable`
with a combined reason naming both gaps and the punkres build remedy
(`make.tcl tool build punkres` - G-128). In every case the sidecar is still
written and the build completes.
written and the bake completes.
- Byte-stable at unchanged input and host capability: no timestamps, fixed
field order, rewritten only when bytes change. The default icon is part of
the kit's punkcheck source set, so changing it rebuilds kits.
the kit's punkcheck source set, so changing it rebakes kits.
### Runtime fetch/selection (`punk-runtime.cmd`)
@ -262,7 +262,7 @@ RT_VERSION stamping extends the same file.
verification against its `sha1sums.txt` - both the powershell payload on windows and the
bash payload on unix verify; a fetch whose checksum fails leaves only a `.tmp`).
The `<platform>` folders are store TIERS keyed by the platform a runtime is FOR, and the
kit build reads them the same way: `src/make.tcl` picks a tier per kit from that kit's
kit bake reads them the same way: `src/make.tcl` picks a tier per kit from that kit's
target platform, not from the driving tclsh's own personality (G-122 - see
`src/AGENTS.md` and `src/runtime/AGENTS.md`), so a tier holding runtimes for another
platform is a first-class thing to populate here.
@ -314,7 +314,7 @@ payload - `$PunkRuntimeSupportExt`/`Test-PunkRuntimeSupportName` in the ps1,
because the listings are required to agree byte-for-byte; extend both and re-wrap. What a
tier is expected to contain is documented for users in `bin/runtime/README.md`.
Forgot-to-switch-back guard (2026-07-25): `make.tcl bake`/`bakehouse` emit a
BUILD-WARNING (recapped at end of run) when a working runtime they wrap is materialized
BAKE-WARNING (recapped at end of run) when a working runtime they wrap is materialized
from an older revision than an `-r<N>` artifact present beside it - a deliberate
`use <old-rN>` (e.g to exercise `list -remote` row marking) that was never switched
back no longer bakes stale kits silently (see src/AGENTS.md Work Guidance).

46
src/AGENTS.md

@ -2,18 +2,18 @@
## Purpose
The source tree root contains all editable source code, build scripts, test suites, VFS payloads, vendor dependencies, and documentation. This is the primary development area.
The source tree root contains all editable source code, the make.tcl tooling, test suites, VFS payloads, vendor dependencies, and documentation. This is the primary development area.
## Ownership
- The `src/` tree is where agents perform development work.
- Generated/output directories at the project root (`modules/`, `lib/`, `lib_tcl8/`, `lib_tcl9/`, `modules_tcl8/`, `modules_tcl9/`) are build targets — agents must not directly modify them.
- Generated/output directories at the project root (`modules/`, `lib/`, `lib_tcl8/`, `lib_tcl9/`, `modules_tcl8/`, `modules_tcl9/`) are mint output trees — agents must not directly modify them.
- VFS payloads, runtime mappings, vendored dependencies, generated docs, and entry-point scripts have child AGENTS.md files with local ownership rules.
## Local Contracts
- `make.tcl` is the primary build entry point: `tclsh src/make.tcl <command>`.
- The build system handles bootstrap loading, version assignment, module/library packaging, and VFS packaging.
- `make.tcl` is the primary make entry point: `tclsh src/make.tcl <command>`. Stage verbs are strict (see the TERMINOLOGY block in `make.tcl workflow`): build = compile (buildsuites/tool only), mint = stamp/pack modules from src into the projectroot trees, promote = the bootsupport/vfscommonupdate gates, bake = kit assembly + deploy.
- make.tcl handles bootstrap loading, version stamping, module/library minting, and kit-payload packaging.
- Shell launcher entry points include `tclsh src/make.tcl shell`, built launchers under the project `bin/` directory, scripts sourced from `src/scriptapps/`, and executable entry scripts under `src/vfs/_config/`.
- Tcl 8.6+ is required and Tcl 9.0 is supported; gate 9.0-specific behavior behind version checks or existing `punk::lib::compat` helpers.
- Primary target is Windows (`win32-x86_64`); Linux, macOS, and FreeBSD are secondary targets.
@ -59,38 +59,38 @@ Recovery after a wrong path guess:
## Work Guidance
- Run `tclsh src/make.tcl packages` once after cloning to populate generated module and library assets.
- Use `tclsh src/make.tcl bakehouse` for full builds from a clean checkout (packages + bake; refuses uncommitted src by default - `-dirty-abort 0` overrides).
- Use `tclsh src/make.tcl modules` to build just the module packages.
- Use `tclsh src/make.tcl libs` to build just the library packages.
- Use `tclsh src/make.tcl packages` to build both modules and libraries.
- Use `tclsh src/make.tcl bakehouse` for full consumer runs from a clean checkout (mint + bake; refuses uncommitted src by default - `-dirty-abort 0` overrides).
- Use `tclsh src/make.tcl modules` to mint just the module packages.
- Use `tclsh src/make.tcl libs` to mint just the library packages.
- Use `tclsh src/make.tcl packages` to mint both modules and libraries.
- make.tcl colour is terminal-aware (G-113): piped/redirected runs (agent harnesses, CI, log capture) automatically produce fully ESC-free output with no caller action required - an ansistrip channel transform on stdout+stderr guarantees zero ESC bytes for every emitter, including module-side ones (punkcheck summaries, punk::args tables). Interactive terminal runs keep colour (stdout tty probe via the `-winsize` channel option, Tcl 8.7+/9). Precedence: `NO_COLOR` (any value) always suppresses colour; `PUNK_FORCE_COLOR`/`FORCE_COLOR` (value other than 0/false/no/off) re-enables ANSI on piped output; otherwise the probe decides. Tcl 8.6 terminals are auto-detected without dependencies: windows consoles via the console channel's utf-16 encoding signature (`-encoding unicode` - only 8.6 console channels report it; the byte-level strip transform is never pushed onto a utf-16-class channel, which it would corrupt - per-channel push, so `> file` from an 8.6 console wraps stdout only), and unix-class hosts (linux/WSL/mac, plus msys2/cygwin-runtime tclsh builds that report platform unix on windows) via the tty channel signature (real ttys expose `-mode` etc; pipes/files lack them). The force vars remain for exotic environments. stderr follows the stdout decision; `make.tcl shell` pops the transform (repl colour is the shell's own concern). The ansistrip transform also holds split ANSI sequences across write chunks, so piped output carries no orphan sequence-fragment text ('0;1m' beside table borders etc - G-145): the transform must NOT declare the `clear` op in its supported-methods list - the Tcl core delivers clear before EVERY write op (intentional, under-documented core behaviour documented as read-side/seek-scoped), and the former clear handler's drop of the per-channel carry was the fragment source; `finalize` still drops the carry. `tclsh src/make.tcl check` reports the active policy (`colour policy (G-113): mode=...` line; modes tty|forced|nocolor|piped-plain|tcl86-plain). Pinned by `src/tests/shell/testsuites/punkexe/maketclcolour.test`. Agents no longer need to set NO_COLOR for captured runs (setting it remains harmless).
- Use `tclsh src/make.tcl vendorupdate` to refresh vendormodules from config. It warns (non-fatal) for each source project whose fossil/git checkout is dirty - vendored artifacts built from a dirty tree have no committed provenance; commit in the source project to clear the warning (enforcement policy tracked by goal G-026).
- All build/promotion commands (`bakehouse`, `packages`, `modules`, `libs`, `bake`, `vfslibs`, `bin`, `bootsupport`, `vfscommonupdate`) warn similarly when this project's own `src/` has uncommitted fossil/git changes (dirt outside `src/` is ignored). Warn-only by default EXCEPT `bakehouse` (aborts by default; `-dirty-abort 0` overrides); pass `-dirty-abort` elsewhere to make the check aborting. For evaluating uncommitted source without a build, prefer `<builtexe> src` / `<builtexe> src shell`. The check is `punkboot::utils::vcs_dirty_warnings` (optional scope argument) loaded guardedly from bootsupport - if the snapshot is stale/missing the check degrades to a skip notice (but `-dirty-abort` then aborts rather than silently losing the requested strictness).
- 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.
- All producing commands (mint/promote/bake: `bakehouse`, `packages`, `modules`, `libs`, `bake`, `vfslibs`, `bin`, `bootsupport`, `vfscommonupdate`) warn similarly when this project's own `src/` has uncommitted fossil/git changes (dirt outside `src/` is ignored). Warn-only by default EXCEPT `bakehouse` (aborts by default; `-dirty-abort 0` overrides); pass `-dirty-abort` elsewhere to make the check aborting. For evaluating uncommitted source without minting or baking, prefer `<builtexe> src` / `<builtexe> src shell`. The check is `punkboot::utils::vcs_dirty_warnings` (optional scope argument) loaded guardedly from bootsupport - if the snapshot is stale/missing the check degrades to a skip notice (but `-dirty-abort` then aborts rather than silently losing the requested strictness).
- 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`).
- `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.
- `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.
- Uncommitted `make.tcl`-generated outputs may be batched into one commit, regardless of how many `make.tcl` invocations produced them. This covers punkcheck-managed build outputs that are VCS-tracked: `src/bootsupport/`, `_vfscommon.vfs/modules` + `/lib`, declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders, the thin-layout sync copies (`src/project_layouts/vendor/punk/<layout>/src/{make.tcl,build.tcl}` + bootsupport manifest copies + the inert `gitignore.in` payload copies refreshed from the root `.gitignore` - G-012), and the module-shipped layout payload under `src/modules/punk/mix/#modpod-templates-*/templates/project_layouts/` (G-087: synced from `src/project_layouts` - never hand-edit the modpod copy). (Root `modules/`, `lib/`, `modules_tcl<N>/`, `lib_tcl<N>/` are gitignored and never committed.) Each module's old→new version rename is atomic; a regeneration reflects one build-output refresh, not independent hand-edits. This is a carve-out from generic "split aggressively" commit guidance for punkcheck-managed build outputs only — it does not cover user-curated VFS content (`_config/`, per-kit `*.vfs/` outside declared `lib_tcl<N>` subfolders, `_aside/`, `mkzipfix.vfs`, `_vfscommon.vfs/doc`), which commits separately per its own concerns.
- **Boot-precondition gate (G-125).** A bake refuses any kit whose merged `.vfs` supplies no tcl library: the kit is listed under `FAILED KITS` with a reason naming the cause, and NOTHING is written - no `src/_build/<kit>`, no deploy, and the previously deployed `bin/<kit>` is left byte-identical. This is deliberately a failure rather than a warning: the deploy step deletes the old kit before copying the new one, so the pre-gate behaviour replaced a working shell with an artifact that died at startup with `application-specific initialization failed: Cannot find a usable init.tcl`. The check is structural and non-executing (so it covers cross-target kits too): `init.tcl` in `tcl_library/`, `lib/tcl<major>.<minor>/` or `tcl<major>.<minor>/` - the third for runtimes whose archive mounts at the executable's own path rather than `//zipfs:/app`, so `[info library]` is `<exe>/tcl8.6` (the androwish/undroidwish zipfs backport for 8.6 does this) - plus one companion file a real Tcl library carries beside it (`tm.tcl`, `package.tcl`, `auto.tcl`, `clock.tcl`, `history.tcl`, `word.tcl`) or an `encoding/` directory - without that second test the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries would answer for a tcl library it cannot provide. It reads the MERGED tree rather than asking whether extraction ran, so a `.vfs` that legitimately supplies its own tcl library (`src/vfs/punk8_statictwapi.vfs`, `src/vfs/punk9test.vfs`) builds and deploys unchanged. **Remedy when a kit is refused:** the usual cause is that the runtime's own payload could not be extracted - read the recapped `BUILD-WARNING` naming what was tried, then either fix extraction (a driving tclsh with no zipfs needs punk::zip >= 0.2.0 in bootsupport - see the extraction bullet above), point the kit at a runtime that carries a payload, or have the kit's `.vfs` supply a tcl library itself. The predicate is `punkboot::utils::vfs_boot_library_report` called through a guarded require, exactly as the dirty-src provenance check is: a stale or missing bootsupport snapshot degrades the gate to a `NOTE` rather than failing every kit. `tclsh src/make.tcl check` reports whether the gate is `ACTIVE` or `UNAVAILABLE`.
- **Payload/target consistency checks (G-133), both ADVISORY** - warnings are recapped `BUILD-WARNING`s and the kit still builds and deploys (unlike the G-125 gate). (a) **Binary-arch scan**, at the same post-merge seam as the gate: every binary library (`*.dll`/`*.so`/`*.dylib`) in the merged tree is classified by header bytes (`punkboot::utils::binary_arch_classify` - PE Machine field, ELF e_machine, Mach-O incl. universal; unclassifiable content is honestly `unknown` and never warned about) against the kit's target platform. Structural and non-executing, so it runs for cross-target kits too (measured ~30-140ms per kit, avg ~93ms across the 12 assembled trees). Libraries under a platform-discriminated subdir - canonical `<os>-<cpu>` platform-dir names, the universal `macosx` folder, or a recognised vendor spelling (`win-x64` etc; the lists are namespace variables in `punkboot::utils`) - are exempt: multi-arch payloads are legitimate there. First real sweep (2026-07-27) found a true latent instance: `zint.dll` 2.13.0 in `src/vfs/punk8win.vfs/lib_tcl8/` is 32-bit (i386) and can never load in the x64 tcl8 kits (punk86/punkbi/punksys) that carry it - those bakes warn until the payload is fixed. (b) **Smoke-require probe**: a kit declaring packages in `src/runtime/mapvfs.toml` (`smokerequire` key - see `src/runtime/AGENTS.md`) has each one plain-`package require`d INSIDE the freshly built artifact via its tclsh subcommand, which exits cleanly even on runtimes whose full repl teardown is fragile. This is the only check that observes actual package RESOLUTION - the punkluck86 2026-07-27 incident class (a higher-versioned wrong-arch Thread shadowing the runtime's working copy on plain require) is invisible to structural checks because the working file exists too. Failures are recapped naming kit, package and the actual error; cross-target kits skip with a stated reason; undeclared kits run nothing new; the probe only runs when the kit actually rebuilds. **What the checks do NOT guarantee:** statically linked packages, pure-tcl packages whose binary dependency lives elsewhere, and version-preference outcomes are visible only to the smoke probe - and only for the declared package set; the arch scan proves nothing about loadability beyond architecture, and an `unknown` classification is silence, not a pass. Both degrade to a NOTE when the bootsupport `punkboot::utils` snapshot is stale (same guarded require as the G-125 gate); `tclsh src/make.tcl check` reports the scan's ACTIVE/UNAVAILABLE state and the currently declared smoke-require kits.
- **Kit offset-style pin (G-134), ADVISORY** - at the same seam family, every ASSEMBLED kit image is probed with `punk::zip::archive_info` (via `punkboot::utils::kit_offsetstyle_report`, same guarded-require degradation to a NOTE; `make.tcl check` reports ACTIVE/UNAVAILABLE): a FILE-relative attached zip payload earns a recapped `BUILD-WARNING` naming the kit, because the pipeline emits ARCHIVE-relative by construction and the G-128 stamper refuses file-relative by default - a warning here means an assembly path regressed the output convention. **What the pin does NOT cover:** `plain` and `none` results (a bare zip, or no attached zip at all - the metakit kit shape, and any unreadable input) are silence, never warnings - the pin asserts the offset CONVENTION of an attached zip, not the presence of one; and it asserts only what make.tcl's own assembly paths EMIT - `punk::zip::mkzip -offsettype file` (and modpod's equivalent) remain available for deliberate manual creation of file-relative artifacts, and file-relative INPUT artifacts remain tolerated by the G-124 reader and normalized by any re-bake.
- Kit bakes consume the punk-runtime WORKING COPIES under `bin/runtime/<platform>/`: `make.tcl bake`/`bakehouse` emit a `BUILD-WARNING:` (recapped at end of run) when a wrapped runtime's beside-toml revision is older than an `-r<N>` artifact present in the same folder - the forgot-to-switch-back guard for deliberate `punk-runtime use <old-rN>` testing excursions. Heed it before trusting freshly baked family kits; `bin/punk-runtime.cmd use <artifact-r<N>>` (`.exe` suffix optional) re-materializes the working copy.
- `tclsh src/make.tcl bakelist ?kitname|@group ...?` (G-121/G-024) reports the kit outputs configured in `src/runtime/mapvfs.toml`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/<kit>` vs the `src/_build` build product (`current|stale|absent|nobuild`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r<cur><r<max>` materialization staleness), the cross-target marker `target=<platform>`, groups (`group=<name>`), full-bake exclusion (`default=no`) and scheme roles (`scheme=versioned|dev|release`) in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name/@group arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, config entry, group, paths/sizes/mtimes). `make.tcl bake ?kitname|@group ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Kitname arguments are DECLARED restricted choices (2026-08-02, declaration-authoritative per the G-143 arc): an unknown name is rejected at dispatch with the punk::args choice error (before any build; the choices list the configured names + `@groups`), unambiguous prefixes resolve to canonical names, and the dry-run help mirrors the verdict. The handlers' own validation remains the backstop where the declarations cannot gate: `PUNKBOOT_PLAIN=1` degraded mode, and a definition-time mapping-read failure (the choices clause is then omitted). Bare `bake` processes all configured kits except `bake_default=false` entries; a `versioned`-scheme release output (plain `punk<gen>` name) is created when absent and otherwise skipped - a normal bake never overwrites it (the explicit release step is G-023's). Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format (G-024: toml reader canonical, deprecated `mapvfs.config` line reader retained as fallback; `PUNK_MAPVFS_CONFIG` env points at an alternate config for characterization), and all mapping consumers - the definition-time choices and every handler - share one memoized read per invocation (`mapvfs_model`; success cached, a throwing parse diagnoses fresh per caller). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`.
- **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.
- **Kit offset-style pin (G-134), ADVISORY** - at the same seam family, every ASSEMBLED kit image is probed with `punk::zip::archive_info` (via `punkboot::utils::kit_offsetstyle_report`, same guarded-require degradation to a NOTE; `make.tcl check` reports ACTIVE/UNAVAILABLE): a FILE-relative attached zip payload earns a recapped `BAKE-WARNING` naming the kit, because the pipeline emits ARCHIVE-relative by construction and the G-128 stamper refuses file-relative by default - a warning here means an assembly path regressed the output convention. **What the pin does NOT cover:** `plain` and `none` results (a bare zip, or no attached zip at all - the metakit kit shape, and any unreadable input) are silence, never warnings - the pin asserts the offset CONVENTION of an attached zip, not the presence of one; and it asserts only what make.tcl's own assembly paths EMIT - `punk::zip::mkzip -offsettype file` (and modpod's equivalent) remain available for deliberate manual creation of file-relative artifacts, and file-relative INPUT artifacts remain tolerated by the G-124 reader and normalized by any re-bake.
- Kit bakes consume the punk-runtime WORKING COPIES under `bin/runtime/<platform>/`: `make.tcl bake`/`bakehouse` emit a `BAKE-WARNING:` (recapped at end of run) when a wrapped runtime's beside-toml revision is older than an `-r<N>` artifact present in the same folder - the forgot-to-switch-back guard for deliberate `punk-runtime use <old-rN>` testing excursions. Heed it before trusting freshly baked family kits; `bin/punk-runtime.cmd use <artifact-r<N>>` (`.exe` suffix optional) re-materializes the working copy.
- `tclsh src/make.tcl bakelist ?kitname|@group ...?` (G-121/G-024) reports the kit outputs configured in `src/runtime/mapvfs.toml`: kit name, kit type, runtime (with presence in the runtime store), vfs folder, and the deployed state of `bin/<kit>` vs the `src/_bake` build product (`current|stale|absent|nobake`), with anomalies (`runtime=missing`, `vfs=missing`, `rtrev=r<cur><r<max>` materialization staleness), the cross-target marker `target=<platform>`, groups (`group=<name>`), full-bake exclusion (`default=no`) and scheme roles (`scheme=versioned|dev|release`) in a trailing notes column. A nonexistent store tier is flagged loudly per tier actually referenced (header `(FOLDER MISSING)` for the default tier + stderr derivation; bake's no-runtimes exit self-diagnoses the same way). Name/@group arguments filter the report and add a per-kit detail block (resolved store tier, target and provenance of the target, config entry, group, paths/sizes/mtimes). `make.tcl bake ?kitname|@group ...?` bakes and deploys only the named kits - other kits' `_build`/`bin` artifacts and punkcheck records are untouched and the vfslibs phase narrows to the named kits' vfs folders; flags go before kit names (`make.tcl bake -confirm 0 punk91`). Kitname arguments are DECLARED restricted choices (2026-08-02, declaration-authoritative per the G-143 arc): an unknown name is rejected at dispatch with the punk::args choice error (before any build; the choices list the configured names + `@groups`), unambiguous prefixes resolve to canonical names, and the dry-run help mirrors the verdict. The handlers' own validation remains the backstop where the declarations cannot gate: `PUNKBOOT_PLAIN=1` degraded mode, and a definition-time mapping-read failure (the choices clause is then omitted). Bare `bake` processes all configured kits except `bake_default=false` entries; a `versioned`-scheme release output (plain `punk<gen>` name) is created when absent and otherwise skipped - a normal bake never overwrites it (the explicit release step is G-023's). Both surfaces consume the shared parsed-mapping helpers (`::punkboot::lib::mapvfs_*`) rather than the file format (G-024: toml reader canonical, deprecated `mapvfs.config` line reader retained as fallback; `PUNK_MAPVFS_CONFIG` env points at an alternate config for characterization), and all mapping consumers - the definition-time choices and every handler - share one memoized read per invocation (`mapvfs_model`; success cached, a throwing parse diagnoses fresh per caller). Piped characterization: `src/tests/shell/testsuites/punkexe/maketclbakelist.test`.
- **Host vs target platform (G-122).** Everything a bake EMITS is keyed by the artifact's TARGET platform, never by the driving tclsh's personality: the `bin/runtime/<tier>` store the runtime is read from, `.exe` suffixing of runtime files and kit outputs, the presence checks behind `runtime=missing`, and whether the pre-deploy process sweep uses `tasklist`/`taskkill` or `ps`/`kill`. Host semantics - copy commands, path handling, filesystem case rules, prompts - stay keyed to the host. The default target is the host's own platform canon, EXCEPT a cygwin-family host (an msys2/cygwin-runtime tclsh, which reports `tcl_platform(platform)` `unix` on windows and canonizes as `msys-x86_64`/`cygwin-x86_64`), which targets `win32-x86_64`: such a host now drives the identical kit set, names and store addressing as a native tclsh. `tclsh src/make.tcl check` prints the derivation on one `platform (G-122): host=... target=... store=... exe-suffix=... process-tooling=...` line. A mapvfs entry may declare its own target platform (`target` key - see `src/runtime/AGENTS.md`), which is then read from that platform's tier, named with that platform's executable convention, and skipped by the process sweep (its processes are not visible to this host). Traps this closes, all field-verified 2026-07-26: msys `ps` cannot see a natively-launched kit (only `tasklist` can), and msys2 rewrites arguments that look like absolute posix paths when spawning a native windows program - so `taskkill /PID <n>` arrived as `taskkill C:/<msysroot>/PID <n>`; native-windows command lines therefore go through `::punkboot::exec_nativeargs` (sets `MSYS2_ARG_CONV_EXCL=*`, inert elsewhere). Also note a cygwin-family tclsh is a POSIX Tcl: name the script the way that shell spells paths (`tclsh /c/repo/.../src/make.tcl`, not `C:/repo/...`, which it resolves relative to the cwd) - make.tcl diagnoses the windows-spelling case. Piped characterization: `src/tests/shell/testsuites/punkexe/maketclplatform.test` (the cygwin-host tests self-gate on finding and probing a real msys/cygwin tclsh; `PUNK_MSYS_TCLSH` names one explicitly).
- **Target-keyed kit OUTPUT locations (G-127) - the output half of the G-122 split.** A kit for the host's default target builds to `src/_build/<kit>` and deploys to `bin/<kit>` exactly as always; any other target's kit builds under `src/_build/kits/<platform>/` and deploys under `bin/kits/<platform>/` (per-tier `.punkcheck` ledger; the merged `.vfs` image, resource sidecar, arch scan, offset probe, smoke probe and process-sweep guards all follow the per-kit location). Same-named kits for different targets coexist - the artifact path is a pure function of (name, target) and `.exe` suffixing follows the target - so separate selective bakes can never overwrite each other's cross-target artifacts; only a same-name same-TARGET duplicate keeps the `<name>_<runtime>` disambiguation. A kit NAME matching several targets' entries selects all of them (bakelist and selective bake). Per-.vfs payload declarations gain the same axis: a `[payload.*]` source containing `%platform%` materializes once per CONSUMING kit target into `_targets/<platform>/` staging inside the `.vfs` folder (VCS-ignored), and each kit's merged image receives only its own target's subtree upstream of the arch scan and G-125 gate (`src/vfs/README.md` "Per-platform entries"). Characterization: `src/tests/shell/testsuites/punkexe/maketclkitlocations.test`; output-tier contract for consumers: `bin/AGENTS.md` "Cross-target kit outputs".
- **Target-keyed kit OUTPUT locations (G-127) - the output half of the G-122 split.** A kit for the host's default target builds to `src/_bake/<kit>` and deploys to `bin/<kit>` exactly as always; any other target's kit builds under `src/_bake/kits/<platform>/` and deploys under `bin/kits/<platform>/` (per-tier `.punkcheck` ledger; the merged `.vfs` image, resource sidecar, arch scan, offset probe, smoke probe and process-sweep guards all follow the per-kit location). Same-named kits for different targets coexist - the artifact path is a pure function of (name, target) and `.exe` suffixing follows the target - so separate selective bakes can never overwrite each other's cross-target artifacts; only a same-name same-TARGET duplicate keeps the `<name>_<runtime>` disambiguation. A kit NAME matching several targets' entries selects all of them (bakelist and selective bake). Per-.vfs payload declarations gain the same axis: a `[payload.*]` source containing `%platform%` materializes once per CONSUMING kit target into `_targets/<platform>/` staging inside the `.vfs` folder (VCS-ignored), and each kit's merged image receives only its own target's subtree upstream of the arch scan and G-125 gate (`src/vfs/README.md` "Per-platform entries"). Characterization: `src/tests/shell/testsuites/punkexe/maketclkitlocations.test`; output-tier contract for consumers: `bin/AGENTS.md` "Cross-target kit outputs".
- Zip-type kit assembly does not require zipfs in the driving tcl (G-122): with `tcl::zipfs::mkimg` present it is used as before, otherwise the image is assembled by concatenation (raw-runtime split + `punk::zip::mkzip` + append, the same `::punkboot::assemble_zipcat_image` helper the `zipcat` kit type uses). Both mount identically - tcl zipfs reads the archive with archive-start-relative offsets.
- Neither is EXTRACTING the runtime's own attached zip (needed to carry its `tcl_library` into the kit): `punk::zip` >= 0.2.0 reads a zip with stock Tcl only - no zipfs, no vfs::zip, no tcllib (G-124, the former tcllib `zipfile::decode` dependency is gone). The zipfs-less bake path splits off the executable prefix with `punk::zip::extract_preamble` and then reads the MEMBERS from the original runtime with `punk::zip::unzip`, never from the split-off intermediate: a runtime whose zip offsets are file-relative (the historical `zipfs mkimg` convention - `tclsh90b4_piperepl.exe` in the store is one) splits into a .zip whose offsets still count from the removed prefix, which plain zip readers reject. Reading the whole file at the derived base offset makes the two offset conventions indistinguishable to callers; `punk::zip::archive_info <file>` reports which one a given artifact uses. Verified 2026-07-26 by baking a zip kit from msys2's `/usr/bin/tclsh8.6` (no zipfs, no vfs::zip, no tcllib) and booting the result with its `tcl_library` present.
- What the RUNTIME must provide to be zip-kit-wrappable - which zipfs commands, mount conventions and startup hooks are supported - is documented in `bin/AGENTS.md` "Kit-wrappable runtime requirements (G-129)". Short form: `tcl::zipfs::mount` with pairwise no-arg output (`tcl::zipfs::root` NOT required), any attached-archive mount point (the boot derives it from the runtime's own mount table - the androwish/undroidwish 8.6 backport mounts at the executable's own path), a `main.tcl`/`app/main.tcl` startup hook, and a liftable library payload for the G-125 gate. The bake's runtime capability probe keys `has_zipfs` on `tcl::zipfs::mount` accordingly.
- Use `punk make.tcl bakehouse` or `punk902z make.tcl bakehouse` inside Punk shell when building binaries through Punk. Driving make.tcl from a built punk executable is supported for informational/update subcommands and for kit builds of *other* kits — the kit whose deployed executable is running the build is skipped with a warning (it cannot be replaced while running, and the pre-deploy process sweep must not kill the build itself; the sweep also excludes the build's own pid in all cases). Rebuild that kit from tclsh or a different kit.
- Use `punk make.tcl bakehouse` or `punk902z make.tcl bakehouse` inside Punk shell when baking binaries through Punk. Driving make.tcl from a built punk executable is supported for informational/update subcommands and for bakes of *other* kits — the kit whose deployed executable is running the bake is skipped with a warning (it cannot be replaced while running, and the pre-deploy process sweep must not kill the bake itself; the sweep also excludes the bake's own pid in all cases). Rebake that kit from tclsh or a different kit.
- **Punk-exe-hosted make.tcl runs in a pre-loaded interp, not a virgin one** — the kit's script-mode boot has already loaded much of the punk stack (punk, punk::lib, punk::repl, punk::console, punk::du, flagfilter, struct::set ...) and set process state (app-punkscript forces `::tcl_interactive 0` for script semantics; libunknown/packagepreference are active). Consequences make.tcl must (and now does) handle explicitly: `package require` of an already-provided package is a no-op, so anything make.tcl breaks in the interp (the accelerator-reload block forgets+destroys sha1/md5/struct::* — it re-requires what was loaded), and anything computed at package-load time (punk::repl's `::tcl_interactive` probe — the shell branch recomputes it before `repl::start`), must be restored deliberately rather than relying on later loads to re-fire. Also note the copies that run in this mode are the *kit's* pre-loaded modules, not the bootsupport snapshots make.tcl's paths would otherwise prefer — silent provenance mixing when versions diverge. When adding interp-surgery or load-time-state assumptions to make.tcl, test under both `tclsh src/make.tcl ...` and `<punkexe> src/make.tcl ...`.
- Binary images are platform-specific; build on each target platform rather than expecting a cross-platform flag.
- Remove `_build/` artifacts only when a clean/resync is needed, then rerun the relevant `make.tcl` command. Avoid partial cleans that break boot modules.
- Binary images are platform-specific; bake on each target platform rather than expecting a cross-platform flag.
- Remove `_bake/` / `_mint/` artifacts only when a clean/resync is needed, then rerun the relevant `make.tcl` command. Avoid partial cleans that break boot modules.
- Superseded module intermediates are pruned automatically so payload wrapping inherits clean trees:
- `punk::mix::cli::lib::prune_superseded_target_modules` runs inside `build_modules_from_source_to_base` after each module install/skip (covers root `modules/`, `modules_tcl8/`, `modules_tcl9/`), and from `make.tcl bootsupport` for non-glob `include_modules.config` entries (glob entries may intentionally track multiple versions and are never pruned).
- `punk::mix::cli::lib::prune_sourcevanished_targets` mirror-prunes recorded targets whose recorded source files no longer exist: root `modules*/` vendormodule copies (installer `make.tcl`) and the thin-layout/modpod sync copies recorded in `src/project_layouts/.punkcheck` (installer `make.tcl`, all file types).
- Every deletion is recorded as a punkcheck DELETE event in the owning `.punkcheck`, so change detection stays consistent. Files without a qualifying punkcheck install record (e.g placed manually) are never deleted — they are reported to stderr and must be removed by hand if unwanted.
- Install records carry virtual `module_name`/`module_version` SOURCE entries (punkcheck `targetset_addsource_virtual`, punkcheck >= 0.3.0) identifying which product of a source fileset a target is — needed because successive versions are typically built from the same physical source files. Prune trusts this identity; for legacy records it falls back to requiring the candidate's recorded sources to share the keep-version's source folder(s) and module-name prefix.
- The make.tcl call sites are guarded with `info commands` checks: after changing this tooling itself, the first `make.tcl modules` + `make.tcl bootsupport` pass builds/propagates the new tooling (prune skipped with a stderr note) and the next pass prunes.
- The make.tcl call sites are guarded with `info commands` checks: after changing this tooling itself, the first `make.tcl modules` + `make.tcl bootsupport` pass mints/propagates the new tooling (prune skipped with a stderr note) and the next pass prunes.
- Use `tcl::tm::path add <dir>` to surface project modules when writing focused tooling.
- Review VS Code Tcl lint diagnostics before submitting new agent-produced Tcl code, but do not use lint tooling to reformat existing code.
- When touching VFS payloads, describe regeneration steps in durable docs if the workflow changes.
@ -103,8 +103,8 @@ Recovery after a wrong path guess:
- VS Code Tcl lint diagnostics are reviewed for modified Tcl files when available.
- Relevant tests pass, either `tclsh src/tests/runtests.tcl` or a specific `tclsh src/tests/modules/<modulehierarchypath>/tests/all.tcl`.
- `tclsh src/make.tcl packages` is verified when touching build-critical code.
- `tclsh src/make.tcl bakehouse` (with `-dirty-abort 0` when verifying a deliberately dirty tree) completes without errors when changing build, runtime, or VFS behavior.
- `tclsh src/make.tcl packages` is verified when touching mint/bake-critical code.
- `tclsh src/make.tcl bakehouse` (with `-dirty-abort 0` when verifying a deliberately dirty tree) completes without errors when changing make.tcl, runtime, or VFS behavior.
- Documentation/comments are updated for new behavior, flags, workflow, or ownership rules.
- Diffs are reviewed so no stray whitespace or debugging output remains.

6
src/README.md

@ -17,16 +17,16 @@ Runtimes
```
Build Instructions
Make Instructions (mint / promote / bake)
------------------------------
+ Use `tclsh|<punkruntime> make.tcl <commandname>` to build .tm modules and rebuild the punk executable
+ Use `tclsh|<punkruntime> make.tcl <commandname>` to mint .tm modules and rebake the punk executables
In normal use it should be called from the project root.
e.g
```
tclsh src/make.tcl vendorupdate ;# if needed
tclsh src/make.tcl packages ;# build/install the unversioned libs / modules to <projectroot>
tclsh src/make.tcl packages ;# mint the libs / modules from src to <projectroot> (version-stamped)
# or separately:
# tclsh src/make.tcl modules ;# src/modules -> /modules (etc)
# tclsh src/make.tcl libs ;# src/lib -> /lib (etc)

14
src/bootsupport/AGENTS.md

@ -2,13 +2,13 @@
## Purpose
Modules and libraries required during the build/bootstrap/make process before the full Punk module set is available. These are analogous to npm devDependencies — they must be self-contained with minimal dependencies.
Modules and libraries required during the make/bootstrap process before the full Punk module set is available. These are analogous to npm devDependencies — they must be self-contained with minimal dependencies.
## Ownership
- Agents should not directly modify files in this tree unless the task specifically targets boot behaviour.
- Bootsupport modules are snapshots that may lag behind or diverge from the corresponding `src/modules/` versions intentionally.
- The systematic update workflow is `modules/include_modules.config` (entries are `<base> <module>` pairs; base `src/vendormodules` for vendored modules, base `modules` for the project's *built* root modules - build with `make.tcl modules` first) followed by `tclsh src/make.tcl bootsupport`. Project layouts store no bootsupport snapshots (G-087): `dev project.new` injects bootsupport into generated projects from the generating shell at generation time, and the layouts carry only manifest copies of `include_modules.config` (synced by the thin-layout sync step of `make.tcl modules`/`libs`/`packages`/`project` runs - a manifest edit reaches layouts and the templates modpod on the next such build).
- The systematic update workflow is `modules/include_modules.config` (entries are `<base> <module>` pairs; base `src/vendormodules` for vendored modules, base `modules` for the project's *minted* root modules - mint with `make.tcl modules` first) followed by `tclsh src/make.tcl bootsupport`. Project layouts store no bootsupport snapshots (G-087): `dev project.new` injects bootsupport into generated projects from the generating shell at generation time, and the layouts carry only manifest copies of `include_modules.config` (synced by the thin-layout sync step of `make.tcl modules`/`libs`/`packages`/`project` runs - a manifest edit reaches layouts and the templates modpod on the next such build).
- For non-glob config entries only the latest version is tracked: `make.tcl bootsupport` prunes punkcheck-recorded superseded older `.tm` versions from the bootsupport folders (recorded as DELETE events in `src/bootsupport/.punkcheck`). Use a glob entry to intentionally keep multiple versions. Files without punkcheck install records are never pruned automatically.
- The manifest is expected to be complete: a module physically present in bootsupport but missing from `include_modules.config` (a recordless legacy file) works for this checkout but is invisible to generation-time injection - generated projects then lack it. cmdline and struct::set were re-added to the manifest this way (2026-07-19). `make.tcl bootsupport` itself copies only manifest-listed `.tm` files; injection additionally carries adjacent non-.tm support files (e.g struct's `sets_*.tcl`, textutil's `.tex` data) that some modules load from their own directory.
- `dev lib.copyasmodule` is a manual convenience for copying a module (or a single-file pkgIndex.tcl library, converting it to .tm form) from a running punk shell - see `modules/README.md`; it is not the primary update mechanism.
@ -45,7 +45,7 @@ For modules outside this tracked set (including `punk::mix::util`, `punk::mix::c
If the set of runtime-critical packages in `make.tcl`'s `_runtime_deps` list ever grows, update this list (and the matching one in `src/modules/AGENTS.md`) so the two stay in sync.
After changing any build-critical module (`punkcheck`, `punk::repo`, `punk::mix`, `punk::tdl`, `punk::args`), rebuild bootsupport with `cd src && tclsh make.tcl modules && tclsh make.tcl bootsupport` so the snapshot matches the source version and staleness does not trip (append `-confirm 0` for unattended runs).
After changing any bootstrap-tracked module (`punkcheck`, `punk::repo`, `punk::mix`, `punk::tdl`, `punk::args`), refresh bootsupport with `cd src && tclsh make.tcl modules && tclsh make.tcl bootsupport` so the snapshot matches the source version and staleness does not trip (append `-confirm 0` for unattended runs).
## Work Guidance
@ -60,7 +60,7 @@ overwritten in place while any process has it MOUNTED. A zipfs mount memory-maps
archive (`tclZipfs.c`: `CreateFileMappingW` + `MapViewOfFile`), and windows refuses to
overwrite a file with a user-mapped section open - `ERROR_USER_MAPPED_FILE` (1224). Any
holder blocks it: a running punk shell that loaded the modpod, a `src`-mode session, or the
build itself.
make.tcl run itself.
Tcl reports it as **`invalid argument`**, which names nothing useful. That is not a bad
argument: `Tcl_WinConvertError` maps only Win32 codes 0..267 and sends everything above
@ -73,7 +73,7 @@ attempts the ordinary `file copy -force`, and on failure falls back to **delete-
- unlinking a mapped file IS permitted, the holder keeps reading its own mapping, and new
content lands at the name. The replacement content is staged to a sibling
`<target>.punkboot-new` first so a mid-sequence failure can never leave the target missing;
a `broken` return (unlinked and unrestorable) is reported as a build failure naming the
a `broken` return (unlinked and unrestorable) is reported as a run failure naming the
file to restore by hand. A genuine failure additionally prints
`::punkboot::mapped_file_hint`, which explains the catch-all rather than leaving
`invalid argument` bare.
@ -89,8 +89,8 @@ see it fail, the hint line tells you which process class to look for.
Chicken-and-egg-safe ordering when adding to punkboot::utils (make.tcl must never reference a proc its loaded snapshot lacks):
1. Edit `src/modules/punkboot/utils-999999.0a1.0.tm` and bump its buildversion.
2. `tclsh src/make.tcl modules` (build to root `modules/`).
3. `tclsh src/make.tcl bootsupport` (pull the new build into `src/bootsupport/modules/`).
2. `tclsh src/make.tcl modules` (mint to root `modules/`).
3. `tclsh src/make.tcl bootsupport` (pull the new minted copies into `src/bootsupport/modules/`).
4. Only then repoint make.tcl call sites to the new proc.
make.tcl call sites must use a guarded `package require punkboot::utils` and degrade gracefully (skip the feature with a warning) when the proc is unavailable — a stale or broken bootsupport snapshot must never brick the make.tcl commands used to repair it (`modules`, `bootsupport`).

2
src/buildsuites/AGENTS.md

@ -4,7 +4,7 @@
Suites that build Tcl runtimes and binary companions from external upstream
sources using the pinned zig toolchain (zig-only build policy). Arm's-length
from the project build/bake stages: inputs are upstream checkouts/pins, not
from the project mint/bake stages: inputs are upstream checkouts/pins, not
this project's `src/` tree.
## Ownership

630
src/make.tcl

File diff suppressed because it is too large Load Diff

8
src/modules/AGENTS.md

@ -16,8 +16,8 @@ Source of truth for all editable Punk project modules. This is where agents shou
- Module filenames use the literal suffix `-999999.0a1.0.tm`.
- Corresponding `<modulename>-buildversion.txt` files hold the real version number.
- The exception is `punk::libunknown`, which is manually versioned: the real `major.minor.patch` version lives in the filename (e.g `libunknown-<version>.tm`) and there is no buildversion.txt. The same bump rules apply as for buildversion-tracked modules (see "Versioning And Releases"); the mechanics differ — see the manual-versioning bullet there. (`punk::mix::base` was converted from manual to the magic-version scheme 2026-07-21.)
- `#modpod-*` directories contain internal files packed into `.tm` archives during build; do not flatten or edit them without understanding the modpod format.
- `_build/` directory holds build intermediates and should not be manually edited.
- `#modpod-*` directories contain internal files packed into `.tm` archives during the mint; do not flatten or edit them without understanding the modpod format.
- `_mint/` directory (renamed from `_build/` under G-155) holds mint staging intermediates and should not be manually edited; stale `_build/` copies may remain in old checkouts.
- Always declare dependencies explicitly using `package require <name>` near file tops.
- Prefer fully qualified namespaces when referencing external packages, such as `package require tcl::zlib` or `package require TclOO`.
- Organize custom modules as namespaces mirroring directory structure, such as `namespace eval punk::<modulename>` or deeper paths like `punk::lib::util::<somename>`.
@ -43,7 +43,7 @@ Source of truth for all editable Punk project modules. This is where agents shou
- Prefer Unix-style LF line endings for `.tm` source files in this tree.
- If the same proc exists in both `src/modules/` and `src/modules_tcl<major>/`, prefer `src/modules/` unless version-specific behavior is relevant or only the version-specific file is active.
- Use `deck module.new <name>` or `punk::mix::commandset::module::new` to scaffold new modules.
- Run `tclsh src/make.tcl modules` to build modules, or `tclsh src/make.tcl bakehouse` for a full build from a clean checkout (the deprecated `project` alias still maps).
- Run `tclsh src/make.tcl modules` to mint modules, or `tclsh src/make.tcl bakehouse` for a full consumer run from a clean checkout.
- When adding a new proc to a `.tm` file, add a PUNKARGS `argdoc` block immediately before it — even if the proc parses arguments manually and the PUNKARGS is documentation-only. "User-facing" includes any proc in an exported namespace, not just shell commands. Developer-facing utility procs (e.g `punk::lib::*`) are not exempt.
### Formatting And Layout
@ -357,5 +357,5 @@ Before writing or generating punk::args definitions, know the mechanisms punk::a
- `punk/` — Core punk namespace modules (see punk/AGENTS.md)
- `opunk/` — Alternative punk namespace, voo-based classes (opunk::str, opunk::console) (see opunk/AGENTS.md)
- `punkcheck/`Build/check system (punkcheck + punkcheck::cli)
- `punkcheck/`Install/provenance-check system (punkcheck + punkcheck::cli)
- `test/` — Installed-module test packages (see test/AGENTS.md)

104
src/modules/punk/mix/base-999999.0a1.0.tm

@ -344,7 +344,7 @@ namespace eval punk::mix::base {
}
#we can return module paths even if the project isn't yet under revision control
set src_subs [glob -nocomplain -dir [file join $candidate src] -type d -tail *]
set antipatterns [list *.vfs vendor* lib _build doc embedded runtime bootsupport]
set antipatterns [list *.vfs vendor* lib _build _mint _bake doc embedded runtime bootsupport]
set tm_folders [list]
foreach sub $src_subs {
set is_ok 1
@ -411,17 +411,23 @@ namespace eval punk::mix::base {
return [string map {:: /} $nsq]
}
proc get_build_workdir {path} {
proc get_bake_workdir {path} {
set repo_info [punk::repo::find_repos $path]
set base [lindex [dict get $repo_info project] 0]
if {![string length $base]} {
error "get_build_workdir unable to determine project base for path '$path'"
error "get_bake_workdir unable to determine project base for path '$path'"
}
if {![file exists $base/src] || ![file writable $base/src]} {
error "get_build_workdir unable to access $base/src"
error "get_bake_workdir unable to access $base/src"
}
file mkdir $base/src/_build
return $base/src/_build
file mkdir $base/src/_bake
return $base/src/_bake
}
#legacy name (pre-G-155 stage vocabulary; workdir was src/_build) - old make.tcl copies in
#generated projects may still call this. Delegates to the renamed resolver; retirement is a
#G-156 decision.
proc get_build_workdir {path} {
return [get_bake_workdir $path]
}
@ -963,86 +969,12 @@ namespace eval punk::mix::base {
return [dict create $storedpath $keyvals]
}
#calculate the runtime checksum and vfs checksums
proc get_all_vfs_build_cksums {path {cksum_opts {}}} {
set buildfolder [get_build_workdir $path]
set cksum_base_folder [file dirname $buildfolder] ;#this is the <project>/src folder - a reasonable base for our vfs cksums
set dict_cksums [dict create]
set buildrelpath [punk::repo::path_strip_alreadynormalized_prefixdepth $buildfolder $cksum_base_folder]
set vfs_tail_list [glob -nocomplain -dir $cksum_base_folder -type d -tails *.vfs]
foreach vfstail $vfs_tail_list {
set vname [file rootname $vfstail]
dict set dict_cksums $vfstail [list cksum ""]
dict set dict_cksums [file join $buildrelpath $vname.exe] [list cksum ""]
}
#buildruntime.exe obsolete..
puts stderr "warning obsolete? get_all_vfs_build_cksums 'buildruntime.exe'???"
set fullpath_buildruntime $buildfolder/buildruntime.exe
set ckinfo_buildruntime [cksum_path $fullpath_buildruntime]
set ck [dict get $ckinfo_buildruntime cksum]
set relpath [file join $buildrelpath "buildruntime.exe"]
dict set dict_cksums $relpath [list cksum $ck opts $cksum_opts]
set dict_cksums [fill_relativecksums_from_base_and_relativepathdict $cksum_base_folder $dict_cksums]
return $dict_cksums
}
proc get_vfs_build_cksums_stored {vfsfolder} {
set vfscontainer [file dirname $vfsfolder]
set buildfolder $vfscontainer/_build
set vfs [file tail $vfsfolder]
set vname [file rootname $vfs]
set dict_vfs [list $vname.vfs "" $vname.exe "" buildruntime.exe ""]
set ckfile $buildfolder/$vname.cksums
if {[file exists $ckfile]} {
set data [punk::mix::util::fcat -translation binary $ckfile]
foreach ln [split $data \n] {
if {[string trim $ln] eq ""} {continue}
lassign $ln path cksum
dict set dict_vfs $path $cksum
}
}
return $dict_vfs
}
proc get_all_build_cksums_stored {path} {
set buildfolder [get_build_workdir $path]
set vfscontainer [file dirname $buildfolder]
set vfslist [glob -nocomplain -dir $vfscontainer -type d -tail *.vfs]
set dict_cksums [dict create]
foreach vfs $vfslist {
set vname [file rootname $vfs]
set dict_vfs [get_vfs_build_cksums_stored $vfscontainer/$vfs]
dict set dict_cksums $vname $dict_vfs
}
return $dict_cksums
}
proc store_vfs_build_cksums {vfsfolder} {
if {![file isdirectory $vfsfolder]} {
error "Unable to find supplied vfsfolder: $vfsfolder"
}
set vfscontainer [file dirname $vfsfolder]
set buildfolder $vfscontainer/_build
set dict_vfs [get_vfs_build_cksums $vfsfolder]
set data ""
dict for {path cksum} $dict_vfs {
append data "$path $cksum" \n
}
set fd [open $buildfolder/$vname.cksums w]
chan configure $fd -translation binary
puts $fd $data
close $fd
return $dict_vfs
}
#The legacy vfs-cksums quartet (get_all_vfs_build_cksums, get_vfs_build_cksums_stored,
#get_all_build_cksums_stored, store_vfs_build_cksums) was RETIRED under G-155: repo-wide
#caller search found none, store_vfs_build_cksums called an undefined get_vfs_build_cksums
#(and an unset vname) so it can never have executed, and punkcheck records own the
#change-detection role the cksums files aimed at. Historical copies remain in older
#snapshots (src/vfs/*.vfs, mkzipfix.vfs).

3
src/modules/punk/mix/base-buildversion.txt

@ -1,5 +1,6 @@
0.1.2
0.2.0
#First line must be a semantic version number
#all other lines are ignored.
#0.2.0 - G-155 stage vocabulary: get_build_workdir renamed get_bake_workdir returning src/_bake; old name kept as a delegating alias for stale make.tcl copies (retirement is a G-156 decision); _mint/_bake added to find_source_module_paths antipatterns; RETIRED the never-called vfs-cksums quartet get_all_vfs_build_cksums/get_vfs_build_cksums_stored/get_all_build_cksums_stored/store_vfs_build_cksums (zero callers repo-wide; store_ called an undefined proc + unset var so it could never have executed - dead-code removal treated as non-breaking)
#0.1.2 - fix: cksum_path hung forever on files on non-native (vfs-mounted e.g //zipfs:/) filesystems - the tcllib -file digest modes read via fileevent+vwait and vfs channels never deliver fileevents. Files on non-native filesystems are now slurped and digested in data mode (new cksum_data_command per algorithm incl new cksum_adler32_data/cksum_crc_data helpers; exec-based sha3 returns an unsupported_algorithm_for_vfs_path error for vfs paths). Needed for punkcheck::install from module-carried //zipfs:/ layout payloads (G-087 stage 3).
#0.1.1 - fix: get_template_basefolders no-handler warning used 'put' instead of 'puts'; missing-handler path now warns and returns an empty dict instead of erroring on an unset variable

93
src/modules/punk/mix/cli-999999.0a1.0.tm

@ -169,11 +169,11 @@ namespace eval punk::mix::cli {
set lc_this_exe [string tolower [info nameofexecutable]]
set lc_proj_bin [string tolower $project_base/bin]
set lc_build_bin [string tolower $project_base/src/_build]
set lc_bake_bin [string tolower $project_base/src/_bake]
if {"project" in $args} {
set is_own_exe 0
if {[string match "${lc_proj_bin}*" $lc_this_exe] || [string match "${lc_build_bin}" $lc_this_exe]} {
if {[string match "${lc_proj_bin}*" $lc_this_exe] || [string match "${lc_bake_bin}" $lc_this_exe]} {
set is_own_exe 1
puts stderr "WARNING - running make using executable that may be created by the project being built"
set answer [util::askuser "Do you want to proceed using this executable? (build will probably stop when it is unable to update the executable) Y|N"]
@ -346,7 +346,7 @@ namespace eval punk::mix::cli {
set opt_errorprefix [dict get $opts -errorprefix]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
validate_name_not_empty_or_spaced $projectname -errorprefix $opt_errorprefix
set reserved_words [list etc lib bin modules src doc vendorlib vendormodules embedded runtime _aside _build]
set reserved_words [list etc lib bin modules src doc vendorlib vendormodules embedded runtime _aside _build _mint _bake]
if {$projectname in $reserved_words } {
error "$opt_errorprefix '$projectname' cannot be one of reserved_words: $reserved_words"
}
@ -707,7 +707,7 @@ namespace eval punk::mix::cli {
}
proc build_modules_from_source_to_base {srcdir basedir args} {
set antidir [list "#*" "_build" "_aside" ".git" ".fossil*"] ;#exact or glob patterns for folders (at any level) we don't want to search in or copy.
set antidir [list "#*" "_build" "_mint" "_bake" "_aside" ".git" ".fossil*"] ;#exact or glob patterns for folders (at any level) we don't want to search in or copy.
set defaults [list {*}{
-installer punk::mix::cli::build_modules_from_source_to_base
-call-depth-internal 0
@ -742,14 +742,13 @@ namespace eval punk::mix::cli {
if {[file tail [file dirname $srcdir]] ne "src"} {
puts stderr "ERROR build_modules_from_source_to_base can only be called with a srcdir that is a subfolder of your 'src' directory"
puts stderr "The .tm modules are namespaced based on their directory depth - so we need to start at the root"
puts stderr "To build a subtree of your modules - use an appropriate src/modules folder and pass in the -subdirlist."
puts stderr "e.g if your modules are based at /x/src/modules2 and you wish to build only the .tm files at /x/src/modules2/skunkworks/lib"
puts stderr "To mint a subtree of your modules - use an appropriate src/modules folder and pass in the -subdirlist."
puts stderr "e.g if your modules are based at /x/src/modules2 and you wish to mint only the .tm files at /x/src/modules2/skunkworks/lib"
puts stderr "Use: >build_modules_from_source_to_base /x/src/modules2 /x/modules2 -subdirlist {skunkworks lib}"
exit 2
}
set srcdirname [file tail $srcdir]
set build [file dirname $srcdir]/_build/$srcdirname ;#relative to *original* srcdir - not current_source_dir
if {[llength $subdirlist] == 0} {
set target_module_dir $basedir
set current_source_dir $srcdir
@ -859,8 +858,8 @@ namespace eval punk::mix::cli {
set module_build_version $tmfile_versionsegment
}
set buildfolder $current_source_dir/_build
file mkdir $buildfolder
set mintfolder $current_source_dir/_mint
file mkdir $mintfolder
# -- ---
set config [dict create {*}{
-glob *
@ -870,13 +869,13 @@ namespace eval punk::mix::cli {
# -max-depth -1 for no limit
set build_installername pods_in_$current_source_dir
set build_installer [punkcheck::installtrack new $build_installername $buildfolder/.punkcheck]
#set build_installer [punkcheck::installtrack new $build_installername $buildfolder/.punkcheck stderr] ;#with debugchannel
$build_installer set_source_target $current_source_dir/$modpath $buildfolder
set build_installer [punkcheck::installtrack new $build_installername $mintfolder/.punkcheck]
#set build_installer [punkcheck::installtrack new $build_installername $mintfolder/.punkcheck stderr] ;#with debugchannel
$build_installer set_source_target $current_source_dir/$modpath $mintfolder
set build_event [$build_installer start_event $config]
# -- ---
set podtree_copy $buildfolder/#modpod-$basename-$module_build_version
set modulefile $buildfolder/$basename-$module_build_version.tm
set podtree_copy $mintfolder/#modpod-$basename-$module_build_version
set modulefile $mintfolder/$basename-$module_build_version.tm
#todo - use modpod version as a source for change detection
#package require modpod
@ -894,12 +893,12 @@ namespace eval punk::mix::cli {
if {$did_skip} {set did_skip 0; puts -nonewline stdout \n}
set delete_failed 0
if {[file exists $buildfolder/]} {
puts stderr "deleting existing _build copy at $podtree_copy"
if {[file exists $mintfolder/]} {
puts stderr "deleting existing _mint copy at $podtree_copy"
if {[catch {
file delete -force $podtree_copy
} errMsg]} {
puts stderr "[punk::ansi::a+ red]deletion of _build copy at $podtree_copy failed: $errMsg[punk::ansi::a]"
puts stderr "[punk::ansi::a+ red]deletion of _mint copy at $podtree_copy failed: $errMsg[punk::ansi::a]"
set delete_failed 1
}
}
@ -911,9 +910,9 @@ namespace eval punk::mix::cli {
flush stdout
file copy $current_source_dir/$modpath $podtree_copy
if {$tmfile_versionsegment eq $magicversion} {
set tmfile $buildfolder/#modpod-$basename-$module_build_version/$basename-$magicversion.tm
set tmfile $mintfolder/#modpod-$basename-$module_build_version/$basename-$magicversion.tm
if {[file exists $tmfile]} {
set newname $buildfolder/#modpod-$basename-$module_build_version/$basename-$module_build_version.tm
set newname $mintfolder/#modpod-$basename-$module_build_version/$basename-$module_build_version.tm
file rename $tmfile $newname
set tmfile $newname
}
@ -927,20 +926,20 @@ namespace eval punk::mix::cli {
#delete and regenerate zip and modpod stubbed zip
set notes [list]
if {[catch {
file delete $buildfolder/$basename-$module_build_version.zip
file delete $mintfolder/$basename-$module_build_version.zip
} err] } {
set had_error 1
lappend notes "zip_delete_failed"
}
if {[catch {
file delete $buildfolder/$basename-$module_build_version.tm
file delete $mintfolder/$basename-$module_build_version.tm
} err]} {
set had_error 1
lappend notes "tm_delete_failed"
}
#create ordinary zip file without using external executable
package require punk::zip
set zipfile $buildfolder/$basename-$module_build_version.zip ;#ordinary zip file (deflate)
set zipfile $mintfolder/$basename-$module_build_version.zip ;#ordinary zip file (deflate)
#zipfs mkzip does exactly what we need anyway in this case
#unfortunately it's not available in all Tclsh versions we might be running..
@ -949,7 +948,7 @@ namespace eval punk::mix::cli {
#(Therefore no timestamps)
#zip reading utils generally intuit their existence and display them - but often an editor can't add comments to them
set wd [pwd]
cd $buildfolder
cd $mintfolder
puts "zipfs mkzip $zipfile #modpod-$basename-$module_build_version"
set mkzip_failed [catch {zipfs mkzip $zipfile #modpod-$basename-$module_build_version} errMkzip]
cd $wd
@ -963,14 +962,14 @@ namespace eval punk::mix::cli {
#archive variant; modpod stubs read both shapes).
puts stderr "zipfs mkzip failed under Tcl [info patchlevel] ($errMkzip) - falling back to punk::zip::mkzip (known pre-c971e6c7c4 Tcl 8.7 zipfs dotfile defect - core tkt 7d5f1c13089d463e7796)"
catch {file delete -- $zipfile} ;#a failed zipfs mkzip can leave a partial target zip - punk::zip::mkzip refuses to overwrite
punk::zip::mkzip -base $buildfolder -directory $buildfolder/#modpod-$basename-$module_build_version -- $zipfile *
punk::zip::mkzip -base $mintfolder -directory $mintfolder/#modpod-$basename-$module_build_version -- $zipfile *
}
} else {
#use -base $buildfolder so that -directory is included in the archive - the modpod stub relies on this - and extraction would be potentially messy otherwise
#use -base $mintfolder so that -directory is included in the archive - the modpod stub relies on this - and extraction would be potentially messy otherwise
#put in an archive-level comment to aid in debugging
#punk
punk::zip::mkzip -base $buildfolder -directory $buildfolder/#modpod-$basename-$module_build_version -- $zipfile *
punk::zip::mkzip -base $mintfolder -directory $mintfolder/#modpod-$basename-$module_build_version -- $zipfile *
#punk::zip::mkzip stores permissions - (unix style) - which zipfs mkzip doesn't
#Directory ident in zipfs relies on folders ending with trailing slash - if missing, it misidentifies dirs as files.
#(ie it can't use permissions/attributes alone to determine directory vs file)
@ -1078,8 +1077,8 @@ namespace eval punk::mix::cli {
set module_build_version $tmfile_versionsegment
}
set buildfolder $current_source_dir/_build
file mkdir $buildfolder
set mintfolder $current_source_dir/_mint
file mkdir $mintfolder
# -- ---
set config [dict create {*}{
-glob *
@ -1089,12 +1088,12 @@ namespace eval punk::mix::cli {
# -max-depth -1 for no limit
set build_installername tarjars_in_$current_source_dir
set build_installer [punkcheck::installtrack new $build_installername $buildfolder/.punkcheck]
$build_installer set_source_target $current_source_dir/$modpath $buildfolder
set build_installer [punkcheck::installtrack new $build_installername $mintfolder/.punkcheck]
$build_installer set_source_target $current_source_dir/$modpath $mintfolder
set build_event [$build_installer start_event $config]
# -- ---
set podtree_copy $buildfolder/#tarjar-$basename-$module_build_version
set modulefile $buildfolder/$basename-$module_build_version.tm
set podtree_copy $mintfolder/#tarjar-$basename-$module_build_version
set modulefile $mintfolder/$basename-$module_build_version.tm
$build_event targetset_init INSTALL $podtree_copy
@ -1110,12 +1109,12 @@ namespace eval punk::mix::cli {
if {$did_skip} {set did_skip 0; puts -nonewline stdout \n}
set delete_failed 0
if {[file exists $buildfolder/]} {
puts stderr "deleting existing _build copy at $podtree_copy"
if {[file exists $mintfolder/]} {
puts stderr "deleting existing _mint copy at $podtree_copy"
if {[catch {
file delete -force $podtree_copy
} errMsg]} {
puts stderr "[punk::ansi::a+ red]deletion of _build copy at $podtree_copy failed: $errMsg[punk::ansi::a]"
puts stderr "[punk::ansi::a+ red]deletion of _mint copy at $podtree_copy failed: $errMsg[punk::ansi::a]"
set delete_failed 1
}
}
@ -1126,7 +1125,7 @@ namespace eval punk::mix::cli {
puts stdout "$podtree_copy"
file copy $current_source_dir/$modpath $podtree_copy
if {$tmfile_versionsegment eq $magicversion} {
set tmfile $buildfolder/#tarjar-$basename-$module_build_version/#tarjar-loadscript-$basename.tcl
set tmfile $mintfolder/#tarjar-$basename-$module_build_version/#tarjar-loadscript-$basename.tcl
#we don't need to modify version or name of the loadscript
if {![file exists $tmfile]} {
set had_error 1
@ -1150,16 +1149,16 @@ namespace eval punk::mix::cli {
#delete and regenerate .tm
set notes [list]
if {[catch {
file delete $buildfolder/$basename-$module_build_version.tm
file delete $mintfolder/$basename-$module_build_version.tm
} err]} {
set had_error 1
lappend notes "tm_delete_failed"
}
#create ordinary tar file without using external executable
package require tar ;#tcllib
set tarfile $buildfolder/$basename-$module_build_version.tm ;#ordinary tar file (no compression - store)
set tarfile $mintfolder/$basename-$module_build_version.tm ;#ordinary tar file (no compression - store)
set wd [pwd]
cd $buildfolder
cd $mintfolder
puts "tar::create $tarfile #tarjar-$basename-$module_build_version"
if {[catch {
tar::create $tarfile #tarjar-$basename-$module_build_version
@ -1269,23 +1268,23 @@ namespace eval punk::mix::cli {
#} else {
#}
##REVIEW - should be in same structure/depth as $target_module_dir in _build?
##REVIEW - should be in same structure/depth as $target_module_dir in _mint?
##TODO
#set buildfolder $current_sourcedir/_build
#file mkdir $buildfolder
#set mintfolder $current_sourcedir/_mint
#file mkdir $mintfolder
#set tmfile $buildfolder/$basename-$module_build_version.tm
#file delete -force $buildfolder/#tarjar-$basename-$module_build_version
#set tmfile $mintfolder/$basename-$module_build_version.tm
#file delete -force $mintfolder/#tarjar-$basename-$module_build_version
#file delete -force $tmfile
#file copy -force $current_source_dir/#tarjar-$basename-$magicversion $buildfolder/#tarjar-$basename-$module_build_version
#file copy -force $current_source_dir/#tarjar-$basename-$magicversion $mintfolder/#tarjar-$basename-$module_build_version
##
##bsdtar doesn't seem to work.. or I haven't worked out the right options?
##exec tar -cvf $buildfolder/$basename-$module_build_version.tm $buildfolder/#tarjar-$basename-$module_build_version
##exec tar -cvf $mintfolder/$basename-$module_build_version.tm $mintfolder/#tarjar-$basename-$module_build_version
#package require tar
#tar::create $tmfile $buildfolder/#tarjar-$basename-$module_build_version
#tar::create $tmfile $mintfolder/#tarjar-$basename-$module_build_version
#if {![file exists $tmfile]} {
# puts stdout "ERROR: failed to build tarjar file $tmfile"
# exit 4

3
src/modules/punk/mix/cli-buildversion.txt

@ -1,6 +1,7 @@
0.5.2
0.6.0
#First line must be a semantic version number
#all other lines are ignored.
#0.6.0 - G-155 stage vocabulary: modpod/tarjar mint staging moved <srcdir>/_build -> <srcdir>/_mint (mintfolder var; _mint/_bake added to antidir + reserved_words, old _build stays tolerated); running-from-workdir check follows src/_build -> src/_bake; build_modules_from_source_to_base KEEPS its name (punkcheck -installer identity - G-156 owns any rename); mint-verb error text; removed a dead never-read 'build' path assignment
#0.5.2 - modpod zip build: zipfs mkzip failure now falls back to punk::zip::mkzip (the established zipfs-less path) with a stderr note naming the interp and upstream ticket. Motivation: Tcl 8.7 builds predating core fix c971e6c7c4 (tkt 7d5f1c13089d463e7796 'zipfs mkzip broken on Windows dotfiles') die with 'non-unique path name' on dot-prefixed entries - hit by the templates modpod's layout .fossil-custom payloads (present since G-087 stage 3) when built under 8.7a6
#0.5.1 - comment-only: removed commented-out legacy punkcheck proc-pipeline call sites (installfile_begin/started/finished/skipped, start_installer_event) alongside their live OO equivalents - the legacy procs are retired to error shims in punkcheck 0.5.0 (G-094); no behaviour change
#0.5.0 - added lib::prune_superseded_target_modules and lib::prune_sourcevanished_targets; build_modules_from_source_to_base now prunes punkcheck-recorded superseded module versions from target dirs (recorded as punkcheck DELETE events) and records virtual module_name/module_version sources on installs (punkcheck 0.3.0 targetset_addsource_virtual); requires punk::mix::util explicitly

2
src/modules/punkboot/utils-999999.0a1.0.tm

@ -749,7 +749,7 @@ namespace eval punkboot::utils {
punkshell bake pipeline emits ARCHIVE-relative payloads by
construction; this probe makes that a checked contract - a
'file' result is the pipeline-regression signal make.tcl
surfaces as a recapped BUILD-WARNING (advisory: the kit still
surfaces as a recapped BAKE-WARNING (advisory: the kit still
builds and deploys). 'plain' (the file is a bare zip), 'none'
(no zip attached - e.g the metakit kit shape, or any non-zip
file) and 'unreadable' are silence, not warnings: the pin

3
src/modules/punkboot/utils-buildversion.txt

@ -1,6 +1,7 @@
0.6.0
0.6.1
#First line must be a semantic version number
#all other lines are ignored.
#0.6.1 - doc-only: kit_offsetstyle_report argdoc names the recapped tag BAKE-WARNING (G-155 stage vocabulary; tag renamed from BUILD-WARNING in make.tcl)
#0.6.0 - added kit_offsetstyle_report (advisory zip offset-style probe of an assembled kit image via punk::zip::archive_info; backs make.tcl's G-134 archive-relative output pin)
#0.5.0 - added binary_arch_classify (PE/ELF/Mach-O header classifier, honest unknowns), platform_expected_binary, platform_discriminated_segment and vfs_binary_arch_report (advisory payload/target binary-arch scan of a merged .vfs tree with platform-subdir exemption; backs make.tcl's G-133 payload/target consistency checks)
#0.4.0 - vfs_boot_library_report recognises a third tcl-library convention: tcl<M>.<m>/ at the vfs root, used by runtimes whose archive mounts at the executable's own path rather than //zipfs:/app (the androwish/undroidwish zipfs backport for 8.6). Such a runtime was being refused as unbootable. 'checked' gains the new location.

12
src/runtime/AGENTS.md

@ -1,8 +1,8 @@
# src/runtime — Build Runtimes and VFS Configuration
# src/runtime — Kit Runtimes and VFS Configuration
## Purpose
Houses the `mapvfs.toml` kit mapping (VFS payloads paired with platform runtimes into named kit outputs), the `libpackages.toml` lib-tier artifact declarations, plus support files consumed during binary builds.
Houses the `mapvfs.toml` kit mapping (VFS payloads paired with platform runtimes into named kit outputs), the `libpackages.toml` lib-tier artifact declarations, plus support files consumed during kit bakes.
## Ownership
@ -11,9 +11,9 @@ Houses the `mapvfs.toml` kit mapping (VFS payloads paired with platform runtimes
## Local Contracts
- `mapvfs.toml` (G-024, tomlish-parsed) defines which `src/vfs/*.vfs` folders combine with which runtime binaries (stored under `bin/runtime/<platform>/`) into which named kit outputs. The file's own header comment is the user-facing format spec; keep the two in step. `[kit.<name>]` tables carry `runtime`, `vfs`, `type` (kit|zip|zipcat|cookit|cookfs), optional `target`, `smokerequire`, `group`, `bake_default`; `[group.<name>]` tables name kit groupings (selectable in `bake`/`bakelist` as `@<group>`; `bake_default = false` excludes members from full bakes while leaving them bakeable by name/group); `[scheme.<name>]` tables are generative (G-023 `versioned` scheme: `<prefix>-<version>` + `<prefix>-dev` + release-gated plain `<prefix>` derived from `punkproject.toml` at parse time - the plain name is created when absent and never overwritten by a normal bake). Validation is strict and entry-named: unknown keys/types/schemes, conflicting targets and parse failures are fatal; a full bake refuses a default entry whose vfs folder is missing, and skips (recapped `BUILD-WARNING`) entries whose runtime file is absent from its store tier. Consumers read the parsed model (`::punkboot::lib::mapvfs_*`), never the file format. The pre-G-024 `mapvfs.config` line format remains readable as a DEPRECATED fallback (un-migrated generated projects; toml wins when both exist, legacy parses with a NOTE); the `PUNK_MAPVFS_CONFIG` env var points the reader at an alternate file (characterization-test seam).
- **Smoke-require packages (`smokerequire` key, G-133).** A kit entry may name packages the freshly built artifact must be able to plain-`package require`; the bake executes host-runnable kits via their tclsh subcommand and requires each one inside the real artifact (the only check that sees package resolution order - e.g a higher-versioned wrong-arch copy shadowing a working one). Advisory: failures are recapped `BUILD-WARNING`s; cross-target kits skip with a stated reason; undeclared kits run nothing new.
- **Target platform (`target` key, G-122).** A kit entry may name the canonical punkshell platform its runtime is for (`help platforms` lists the names). It decides which `bin/runtime/<platform>/` tier the runtime is read from (macosx-* collapsing to the universal `macosx` folder), whether the runtime file and the built kit carry `.exe`, and whether the pre-deploy process sweep applies. It is a property of the RUNTIME: entries sharing a runtime may repeat it but must not disagree (a conflict, or a name that is not an `<os>-<cpu>` platform-dir name, is a fatal config error). Omitted means the build host's default target - which for an msys2/cygwin-runtime tclsh is `win32-x86_64`, not that host's own canon. The live example is the `[kit.punkshell902]` entry (target `linux-x86_64`, suffixless artifact).
- `mapvfs.toml` (G-024, tomlish-parsed) defines which `src/vfs/*.vfs` folders combine with which runtime binaries (stored under `bin/runtime/<platform>/`) into which named kit outputs. The file's own header comment is the user-facing format spec; keep the two in step. `[kit.<name>]` tables carry `runtime`, `vfs`, `type` (kit|zip|zipcat|cookit|cookfs), optional `target`, `smokerequire`, `group`, `bake_default`; `[group.<name>]` tables name kit groupings (selectable in `bake`/`bakelist` as `@<group>`; `bake_default = false` excludes members from full bakes while leaving them bakeable by name/group); `[scheme.<name>]` tables are generative (G-023 `versioned` scheme: `<prefix>-<version>` + `<prefix>-dev` + release-gated plain `<prefix>` derived from `punkproject.toml` at parse time - the plain name is created when absent and never overwritten by a normal bake). Validation is strict and entry-named: unknown keys/types/schemes, conflicting targets and parse failures are fatal; a full bake refuses a default entry whose vfs folder is missing, and skips (recapped `BAKE-WARNING`) entries whose runtime file is absent from its store tier. Consumers read the parsed model (`::punkboot::lib::mapvfs_*`), never the file format. The pre-G-024 `mapvfs.config` line format remains readable as a DEPRECATED fallback (un-migrated generated projects; toml wins when both exist, legacy parses with a NOTE); the `PUNK_MAPVFS_CONFIG` env var points the reader at an alternate file (characterization-test seam).
- **Smoke-require packages (`smokerequire` key, G-133).** A kit entry may name packages the freshly built artifact must be able to plain-`package require`; the bake executes host-runnable kits via their tclsh subcommand and requires each one inside the real artifact (the only check that sees package resolution order - e.g a higher-versioned wrong-arch copy shadowing a working one). Advisory: failures are recapped `BAKE-WARNING`s; cross-target kits skip with a stated reason; undeclared kits run nothing new.
- **Target platform (`target` key, G-122).** A kit entry may name the canonical punkshell platform its runtime is for (`help platforms` lists the names). It decides which `bin/runtime/<platform>/` tier the runtime is read from (macosx-* collapsing to the universal `macosx` folder), whether the runtime file and the baked kit carry `.exe`, and whether the pre-deploy process sweep applies. It is a property of the RUNTIME: entries sharing a runtime may repeat it but must not disagree (a conflict, or a name that is not an `<os>-<cpu>` platform-dir name, is a fatal config error). Omitted means the bake host's default target - which for an msys2/cygwin-runtime tclsh is `win32-x86_64`, not that host's own canon. The live example is the `[kit.punkshell902]` entry (target `linux-x86_64`, suffixless artifact).
- Per-.vfs payload declarations (`src/vfs/<name>.vfs.toml`, G-115) are the sibling declaration surface for what goes INSIDE a `.vfs` folder - format and precedence in `src/vfs/README.md`. The former per-package `vendorlib_vfs.toml` here was migrated into those files 2026-07-31 and retired (a leftover copy is ignored with a warning).
- Runtime executables are placed in `bin/runtime/<platform>/` by the `bin/punk-runtime.cmd` helper, manually, or (G-103 family runtimes) copied from the suite_tcl90 `kit-family` build products under `src/buildsuites/_build/suite_tcl90/out/family/`.
- `punkshell.ico` here is the project-DEFAULT kit icon: the bake icon step (G-057) records it in every kit's `<kitname>.resources.toml` sidecar and embeds it into win32-target kits (per-kit override + format + skip semantics: bin/AGENTS.md). It is a derived copy of `src/assets/logo/punk-mark.ico` - its `.assetorigin.toml` sidecar records the derivation; regenerate via the logo pipeline, never edit the `.ico` here.
@ -22,7 +22,7 @@ Houses the `mapvfs.toml` kit mapping (VFS payloads paired with platform runtimes
## Work Guidance
- When adding a new platform: create the runtime directory under `bin/runtime/<platform>/`, add a VFS under `src/vfs/`, and add a `[kit.<name>]` entry to `mapvfs.toml` - declaring the target platform on the entry when it is not the build host's default.
- When adding a new platform: create the runtime directory under `bin/runtime/<platform>/`, add a VFS under `src/vfs/`, and add a `[kit.<name>]` entry to `mapvfs.toml` - declaring the target platform on the entry when it is not the bake host's default.
- Do not commit large binary runtimes to version control unless specifically required.
## Verification

2
src/tests/modules/AGENTS.md

@ -38,7 +38,7 @@ Unit tests for editable source modules under `src/modules/`, `src/modules_tcl8/`
## Child DOX Index
- `opunk/console/` — ::opunk::Console backend subclass tests (`testsuites/console/backends.test`, G-001): virtual dispatch of subclass overrides through base-class calls and punk::console::console_spec_resolve (both unchanged), TestConsole determinism + probe-free at_eof, SshConsole capability/eof + the flagship size-via-ANSI-query-over-socket case (a scripted remote terminal answers CSI 6n), TkConsole widget size/eof (gated behind env PUNK_TEST_TK=1 - Tk in the shared testinterp has side effects; also verifiable standalone under a tk-capable kit e.g `punk91 src <script>`)
- `punkboot/utils/` — punkboot::utils tests (`testsuites/utils/`): the make.tcl build-helper module. `utils.test` (punkproject.toml/CHANGELOG version parsing), `vcsdirty.test` (dirty fossil/git provenance warnings behind the build gate - git-fixture based), and `bootlibrary.test` (G-125 boot-precondition predicate `vfs_boot_library_report`: both tcl-library conventions - `tcl_library/` for zipfs-attached kits and `lib/tcl<major>.<minor>/` for starkit-style kits - the companion-file requirement that stops the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries from answering for a tcl library, near-miss reporting, missing/empty trees, and a sweep asserting every assembled `src/_build/*.vfs` tree still passes so the gate cannot fail kits that boot today). All three are pure fixture tests - no build is run; the make.tcl side of the gate is pinned separately in `shell/testsuites/punkexe/maketclbootgate.test`
- `punkboot/utils/` — punkboot::utils tests (`testsuites/utils/`): the make.tcl helper module. `utils.test` (punkproject.toml/CHANGELOG version parsing), `vcsdirty.test` (dirty fossil/git provenance warnings behind the producing-commands gate - git-fixture based), and `bootlibrary.test` (G-125 boot-precondition predicate `vfs_boot_library_report`: both tcl-library conventions - `tcl_library/` for zipfs-attached kits and `lib/tcl<major>.<minor>/` for starkit-style kits - the companion-file requirement that stops the `lib/BWidget1.10.1/init.tcl` every punkshell kit carries from answering for a tcl library, near-miss reporting, missing/empty trees, and a sweep asserting every assembled `src/_bake/*.vfs` tree still passes so the gate cannot fail kits that boot today). All three are pure fixture tests - no mint or bake is run; the make.tcl side of the gate is pinned separately in `shell/testsuites/punkexe/maketclbootgate.test`
- `punkcheck/` — punkcheck module tests (install, summarize_install_resultdict, installtrack)
- `punk/ansi/` — punk::ansi tests (`testsuites/ansi/`): ansistrip/ansimerge, plus characterization of the ANSI-at-position mechanisms (`ansistring.test`: INDEX/INDEXCODE/INDEXCHAR/RANGE/INSERT grapheme indexing with SGR-prefix merging, INDEXCOLUMNS/COLUMNINDEX double-wide column mapping, trim/VIEW), code splitting invariants (`ta.test`: detect/detectcode distinction, split_codes/split_codes_single/split_at_codes shapes and round-trip) and single-code/effective-state semantics (`codetype.test`: is_sgr_reset/has_sgr_leadingreset, has_any/all_effective, sgr_merge, sequence_type classify), grepstr characterization (`grepstr.test`: return modes incl summarydict (linemap pinned as always-present - the -help says -n-only, reconciliation deferred to the planned hygiene pass), exact highlight SGR wrapping, -n line numbering, invert + empty-highlight strip, -C context/breaks, capture groups, and the tab deficiency: warns once per call on stderr, single-pass tab line survives - the multi-pass mangling is pinned at consumer level in punk/ns corp.test), and untabify characterization (`untabify.test`: -stops int/list/terminal, -with spaces/unicode/custom-pair, multiline, errors, plus the EXPERIMENTAL -plastic elastic-tabstop mode deliberately pinned-as-interim and retained for possible repl editbuf use). Console queries (get_tabstops/get_size + punk::console::tabwidth) are mocked per the overtype renderline.test pattern - they emit live terminal queries that block/error headless. ANSI codes in these tests are literal escape strings so results are colour-state independent
- `punk/args/` — punk::args tests (`testsuites/args/`): parsing, choices/choicegroups, forms, rendering/indentation characterization, synopsis display characterization (`synopsis.test`: basic italic argname/`<type>` styling, longopt `--x=` alias forms, literal/literalprefix/stringstartswith/stringendswith type-alternates rendering unitalicised, option alternate parenthesization, multi-element clause display incl `?type?` members and argname tail-word hints, `-typesynopsis` value-element lists and option passthrough incl documenter ANSI, and the small-restricted-choice-set literal rule: 1-3 restricted choices render as unitalicised `|`-joined literals in leader/option/value positions with choicegroups counted, >3 or `-choicerestricted 0` falling back to italics, `-typesynopsis` taking precedence), usage-marking characterization (`usagemarking.test`: -parsedargs/-badarg/-parsestatus/-scheme marking primitives plus goodchoice highlighting of selected/default-in-effect choice words, asserted by SGR-parameter subset against the live colour arrays; the G-049 nocolour/colour-leak GAP pins flipped 2026-07-10 to scheme-statelessness assertions), the G-049 parse-status structure (`parsestatus.test`: punk::args::parse_status overall/per-argument statuses, badarg for type/allocation failures, -caller attribution, errorcode -argspecs stripping), -parsekey characterization (`parsekey.test`: result/received/solos/multis keying, shared-key required satisfaction and defaults, mash-path and prefix-abbreviation keying, plus GAP pins for last-defined-member default precedence, cross-member -multiple value loss, parsekey/optname collision conflation, and values/leaders parsekey breakage - desired-behaviour pins disabled behind punkargsKnownBug in `testsuites/dev/parsekey-knownbugs.test`), and tclcore doc/interpreter behavioural parity (`tclcoreparity.test`, G-054, gated on have_tclcoredocs: 'string is' class choices equal the live-harvested set, per-class docids exist, error-vs-ok agreement across the probe matrix, version-note labels conditional on class presence - expectations derived from the running interpreter, green on 8.6/8.7/9.0; under 8.6 run the file directly via a plain tclkit + tcltest driver since runtests' harness needs newer infrastructure)

12
src/tests/modules/punkboot/utils/testsuites/utils/binaryarch.test

@ -7,7 +7,7 @@
# - vfs_binary_arch_report: the 2026-07-27 punkluck86 case reproduced as a fixture (an
# x64 thread dll under plain lib_tcl8/ in a win32-ix86 kit), the platform-subdir
# exemptions (iocp-2.0.2's win32-ix86 + win32-x86_64 pair, blend2d-style win-x64),
# and the real assembled trees in src/_build when a build has been run
# and the real assembled trees in src/_bake when a build has been run
# All binary fixtures are GENERATED here with 'binary format' - header bytes only, no
# committed binaries (root AGENTS.md binary policy), nothing executable. The scan reads
# headers only and never executes anything - what makes it usable for cross-target kits.
@ -25,8 +25,8 @@ namespace eval ::testspace {
#<projectroot>/src/tests/modules/punkboot/utils/testsuites/utils -> 7 levels up
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]]
variable buildfolder [file join $projectroot src _build]
testConstraint builtvfsavailable [expr {[llength [glob -nocomplain -type d -directory $buildfolder *.exe.vfs]] > 0}]
variable bakefolder [file join $projectroot src _bake]
testConstraint builtvfsavailable [expr {[llength [glob -nocomplain -type d -directory $bakefolder *.exe.vfs]] > 0}]
proc writebytes {path bytes} {
file mkdir [file dirname $path]
@ -307,9 +307,9 @@ namespace eval ::testspace {
# -- --- --- the trees a bake actually scans --- --- --
test binaryarch_scan_real_built_vfs_trees {assembled kit vfs trees in src/_build produce no findings beyond the KNOWN real ones - the advisory must not cry wolf on payloads that work today}\
test binaryarch_scan_real_built_vfs_trees {assembled kit vfs trees in src/_bake produce no findings beyond the KNOWN real ones - the advisory must not cry wolf on payloads that work today}\
-constraints {builtvfsavailable} -setup $common -body {
variable buildfolder
variable bakefolder
#KNOWN REAL FINDINGS the scan is EXPECTED to report (true positives, not noise):
# zint.dll 2.13.0 in src/vfs/punk8win.vfs/lib_tcl8/ is a 32-bit (i386) PE
# ('file' confirms: PE32 Intel 80386) carried by the x64 tcl8 kits, where its
@ -322,7 +322,7 @@ namespace eval ::testspace {
}
set failures [list]
set checked 0
foreach d [lsort [glob -nocomplain -type d -directory $buildfolder *.exe.vfs]] {
foreach d [lsort [glob -nocomplain -type d -directory $bakefolder *.exe.vfs]] {
switch -glob -- [file tail $d] {
punkluck86.* - punk91ix86.* {set target win32-ix86}
default {set target win32-x86_64}

12
src/tests/modules/punkboot/utils/testsuites/utils/bootlibrary.test

@ -5,7 +5,7 @@
# kits, lib/tcl<major>.<minor>/ for starkit-style kits)
# - the companion-file requirement that keeps a package's own init.tcl (every punkshell
# kit carries lib/BWidget1.10.1/init.tcl) from answering for a tcl library
# - near-miss reporting, missing/empty trees, and the real built vfs trees in src/_build
# - near-miss reporting, missing/empty trees, and the real built vfs trees in src/_bake
# when a build has been run on this checkout
# Directory fixtures only - the check never executes or launches anything, which is what
# makes it usable for cross-target kits.
@ -23,8 +23,8 @@ namespace eval ::testspace {
#<projectroot>/src/tests/modules/punkboot/utils/testsuites/utils -> 7 levels up
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]]
variable buildfolder [file join $projectroot src _build]
testConstraint builtvfsavailable [expr {[llength [glob -nocomplain -type d -directory $buildfolder *.vfs]] > 0}]
variable bakefolder [file join $projectroot src _bake]
testConstraint builtvfsavailable [expr {[llength [glob -nocomplain -type d -directory $bakefolder *.vfs]] > 0}]
proc mkfile {path} {
file mkdir [file dirname $path]
@ -167,12 +167,12 @@ namespace eval ::testspace {
# -- --- --- the trees the gate will actually see --- --- --
test bootlib_real_built_vfs_trees {every assembled kit vfs in src/_build passes the gate - the check must not fail kits that boot today}\
test bootlib_real_built_vfs_trees {every assembled kit vfs in src/_bake passes the gate - the check must not fail kits that boot today}\
-constraints {builtvfsavailable} -setup $common -body {
variable buildfolder
variable bakefolder
set failures [list]
set checked 0
foreach d [lsort [glob -nocomplain -type d -directory $buildfolder *.vfs]] {
foreach d [lsort [glob -nocomplain -type d -directory $bakefolder *.vfs]] {
incr checked
set r [report $d]
if {![dict get $r ok]} {

14
src/tests/modules/punkboot/utils/testsuites/utils/offsetstyle.test

@ -7,10 +7,10 @@
# - runtime-prefixed zip, archive-relative offsets (the pipeline's shape) -> archive
# - runtime-prefixed zip, FILE-relative offsets (mkzip -offsettype file - the
# deliberately-available manual convention; the shape that earns the bake's
# BUILD-WARNING) -> file
# BAKE-WARNING) -> file
# - non-zip files (text, binary junk - the metakit kit shape's class) -> none
# - missing path / unusable input -> unreadable with a detail message
# - baseline: every assembled kit image in src/_build (when a build has run)
# - baseline: every assembled kit image in src/_bake (when a build has run)
# probes archive or none, never file - the G-124-measured property the pin
# asserts
# All fixtures are GENERATED here with punk::zip::mkzip / binary writes - no
@ -30,8 +30,8 @@ namespace eval ::testspace {
#<projectroot>/src/tests/modules/punkboot/utils/testsuites/utils -> 7 levels up
variable projectroot [file normalize [file join [file dirname [info script]] .. .. .. .. .. .. ..]]
variable buildfolder [file join $projectroot src _build]
testConstraint builtkitsavailable [expr {[llength [glob -nocomplain -type f -directory $buildfolder *.exe]] > 0}]
variable bakefolder [file join $projectroot src _bake]
testConstraint builtkitsavailable [expr {[llength [glob -nocomplain -type f -directory $bakefolder *.exe]] > 0}]
proc writefile {path content} {
set f [open $path wb]
@ -96,10 +96,10 @@ namespace eval ::testspace {
list offsetstyle [dict get $r offsetstyle] has_detail [expr {[dict get $r detail] ne ""}]
} -result {offsetstyle unreadable has_detail 1}
test kit_offsetstyle_build_baseline {every assembled kit image in src/_build probes archive or none - never file (the G-124 baseline the pin asserts)} -constraints {builtkitsavailable} -body {
variable buildfolder
test kit_offsetstyle_build_baseline {every assembled kit image in src/_bake probes archive or none - never file (the G-124 baseline the pin asserts)} -constraints {builtkitsavailable} -body {
variable bakefolder
set bad [list]
foreach kitimage [lsort [glob -nocomplain -type f -directory $buildfolder *.exe]] {
foreach kitimage [lsort [glob -nocomplain -type f -directory $bakefolder *.exe]] {
#kit images only: skip runtime prefixes and helper artifacts the
#build folder also holds (raw_/iconed_/build_ prefixes)
set tail [file tail $kitimage]

10
src/tests/shell/testsuites/punkexe/maketclbakelist.test

@ -137,7 +137,7 @@ namespace eval ::testspace {
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count $out]
lappend result header [regexp {(?n)^kit\s+type\s+runtime\s+vfs\s+deployed\s*$} $out]
lappend result punk91row [regexp {(?n)^punk91\s+zip\s+tclsfe-x64\s+punk9wintk903\.vfs\s+(current|stale|absent|nobuild)\y} $out]
lappend result punk91row [regexp {(?n)^punk91\s+zip\s+tclsfe-x64\s+punk9wintk903\.vfs\s+(current|stale|absent|nobake)\y} $out]
set result
} -result {timedout 0 exitcode 0 esc 0 header 1 punk91row 1}
@ -150,7 +150,7 @@ namespace eval ::testspace {
lappend result punk91row [regexp {(?n)^punk91\s+zip\s+} $out]
lappend result otherrows [regexp {(?n)^(punk905|punkbi|punksys|punk86)\s} $out]
lappend result detailblock [regexp {(?n)^detail: punk91\s*$} $out]
lappend result detailpaths [regexp {(?n)^\s+build product: src/_build/punk91} $out]
lappend result detailpaths [regexp {(?n)^\s+bake product:\s+src/_bake/punk91} $out]
set result
} -result {timedout 0 exitcode 0 punk91row 1 otherrows 0 detailblock 1 detailpaths 1}
@ -175,8 +175,8 @@ namespace eval ::testspace {
#added 2026-07-26 (agent, G-121); updated 2026-08-02 (agent) - the no-build
#guarantee now holds at the dispatch gate (choice error; the kit machinery is
#never reached); PUNKBOOT_PLAIN=1 keeps the handler's historic pre-build check
test maketcl_bake_unknown_name_no_build {bake with an unknown kit name exits 1 before any build - at dispatch (choice error), and via the handler check under PUNKBOOT_PLAIN} -constraints {punkexeavailable} -body {
#never reached); PUNKBOOT_PLAIN=1 keeps the handler's historic pre-bake check
test maketcl_bake_unknown_name_no_build {bake with an unknown kit name exits 1 before any bake - at dispatch (choice error), and via the handler check under PUNKBOOT_PLAIN} -constraints {punkexeavailable} -body {
set r [maketcl_run {bake nosuchkitxyz}]
set out [dict get $r output]
set result [list]
@ -264,7 +264,7 @@ namespace eval ::testspace {
} -result {timedout 0 entrynamed 1}
#added 2026-07-31 (agent, G-024)
test maketcl_bake_toml_configerror_aborts {bake against a fixture toml with an unknown scheme kind exits 3 before any build, naming the entry} -constraints {punkexeavailable} -body {
test maketcl_bake_toml_configerror_aborts {bake against a fixture toml with an unknown scheme kind exits 3 before any bake, naming the entry} -constraints {punkexeavailable} -body {
set r [maketcl_run_mapseam fixture_badscheme.toml {
{[scheme.badscheme]}
{scheme = "nosuchscheme"}

12
src/tests/shell/testsuites/punkexe/maketclkitlocations.test

@ -6,7 +6,7 @@ package require tcltest
#src/make.tcl under the built punk executable's 'script' subcommand and pins:
# - one vfs definition paired with two different targets resolves to DISTINCT
# output locations in one report: the default-target kit at the flat
# src/_build/<kit> + bin/<kit> every launcher expects, the non-default-target kit
# src/_bake/<kit> + bin/<kit> every launcher expects, the non-default-target kit
# under the kits/<platform>/ tier in both, with the row carrying target= and
# out= notes (location is a pure function of (name, target), which is what makes
# the pre-G-127 cross-run overwrite impossible by construction)
@ -114,7 +114,7 @@ namespace eval ::testspace {
}
#added 2026-07-31 (agent, G-127)
test maketcl_bakelist_twotarget_onevfs_distinct_locations {one vfs definition under two targets: default-target kit keeps flat locations (no target=/out= notes), non-default-target kit resolves under kits/<platform>/ in both src/_build and bin} -constraints {punkexeavailable} -body {
test maketcl_bakelist_twotarget_onevfs_distinct_locations {one vfs definition under two targets: default-target kit keeps flat locations (no target=/out= notes), non-default-target kit resolves under kits/<platform>/ in both src/_bake and bin} -constraints {punkexeavailable} -body {
set r [maketcl_run_mapseam fixture_twotarget.toml {
{[kit.fixnat]}
{runtime = "tclsfe-x64"}
@ -134,11 +134,11 @@ namespace eval ::testspace {
#legitimately appear on a host without the runtime - only the location notes matter)
lappend result natrow [regexp {(?n)^fixnat\s+zip\s+tclsfe-x64\s+punk9wintk903\.vfs\s} $out]
lappend result natnonotes [expr {![regexp {(?n)^fixnat\s.*(target=|out=)} $out]}]
lappend result natbuild [regexp {(?n)^\s+build product: src/_build/fixnat\.exe } $out]
lappend result natbuild [regexp {(?n)^\s+build product: src/_bake/fixnat\.exe } $out]
lappend result natdeploy [regexp {(?n)^\s+deployed:\s+bin/fixnat\.exe } $out]
#non-default-target row: kits/<platform>/ tier both sides, suffixless for linux
lappend result linrow [regexp {(?n)^fixlin\s+kit\s+tclkit-902-Linux64-intel-dyn\s+punk9wintk903\.vfs\s+\S+\s.*target=linux-x86_64 out=kits/linux-x86_64/} $out]
lappend result linbuild [regexp {(?n)^\s+build product: src/_build/kits/linux-x86_64/fixlin } $out]
lappend result linbuild [regexp {(?n)^\s+build product: src/_bake/kits/linux-x86_64/fixlin } $out]
lappend result lindeploy [regexp {(?n)^\s+deployed:\s+bin/kits/linux-x86_64/fixlin } $out]
set result
} -result {timedout 0 exitcode 0 natrow 1 natnonotes 1 natbuild 1 natdeploy 1 linrow 1 linbuild 1 lindeploy 1}
@ -159,8 +159,8 @@ namespace eval ::testspace {
#the pre-G-127 behaviour disambiguated the second row by RUNTIME name - pin its absence
lappend result norename [expr {![regexp {(?n)^fixsame_} $out]}]
#distinct artifacts by construction: same name, two tiers, suffix follows target
lappend result linbuild [regexp {(?n)^\s+build product: src/_build/kits/linux-x86_64/fixsame } $out]
lappend result ixbuild [regexp {(?n)^\s+build product: src/_build/kits/win32-ix86/fixsame\.exe } $out]
lappend result linbuild [regexp {(?n)^\s+build product: src/_bake/kits/linux-x86_64/fixsame } $out]
lappend result ixbuild [regexp {(?n)^\s+build product: src/_bake/kits/win32-ix86/fixsame\.exe } $out]
set result
} -result {timedout 0 exitcode 0 linrow 1 ixrow 1 norename 1 linbuild 1 ixbuild 1}

4
src/tests/shell/testsuites/punkexe/maketclpayloadcheck.test

@ -7,7 +7,7 @@ package require tcltest
# - 'check' reports the arch scan with one of the two documented statuses (ACTIVE with
# a current bootsupport punkboot::utils carrying vfs_binary_arch_report; UNAVAILABLE
# is the documented guarded-require degradation), states its advisory nature, the
# platform-discriminated-subdir exemption and the recapped BUILD-WARNING reporting
# platform-discriminated-subdir exemption and the recapped BAKE-WARNING reporting
# - 'check' describes the smoke-require probe contract (host-runnable kits only,
# cross-target kits skip with a stated reason, undeclared kits run nothing new) and
# lists the kits declaring smoke-require packages. The declared list is
@ -118,7 +118,7 @@ namespace eval ::testspace {
#structural coverage claim: nothing executed, cross-target kits included
lappend result crosstarget [regexp {nothing executed, cross-target kits included} $out]
#advisory reporting channel (appears for both the scan and the smoke probe)
lappend result recapped [expr {[regexp -all {recapped\s+BUILD-WARNINGs} $out] >= 1}]
lappend result recapped [expr {[regexp -all {recapped\s+BAKE-WARNINGs} $out] >= 1}]
#the exemption that keeps multi-arch payloads legitimate
lappend result exemption [regexp {platform-discriminated subdirs} $out]
#smoke probe contract lines

2
src/tests/shell/testsuites/punkexe/maketclplatform.test

@ -204,7 +204,7 @@ namespace eval ::testspace {
lappend result target [regexp {(?n)^\s+target:\s+linux-x86_64 \(declared in the kit mapping\)} $out]
#linux target -> no .exe on the build product or the deployed artifact
#(G-127: a non-default-target kit's locations sit under the kits/<platform>/ tier)
lappend result nosuffix [regexp {(?n)^\s+build product: src/_build/kits/linux-x86_64/punkshell902 } $out]
lappend result nosuffix [regexp {(?n)^\s+build product: src/_bake/kits/linux-x86_64/punkshell902 } $out]
lappend result nosuffix2 [regexp {(?n)^\s+deployed:\s+bin/kits/linux-x86_64/punkshell902 } $out]
set result
} -result {timedout 0 exitcode 0 row 1 store 1 target 1 nosuffix 1 nosuffix2 1}

4
src/tools/AGENTS.md

@ -25,8 +25,8 @@ needed to build them.
- Toolchain: the pinned zig under `bin/tools/` (currently
`bin/tools/zig-x86_64-windows-0.16.0/`); each tree's `build.zig` carries a comptime
zig-version floor that refuses older toolchains with a clear message.
- zig stays OPTIONAL for punkshell builds: the make.tcl tool step reports the
toolchain's absence rather than failing an ordinary build (G-126).
- zig stays OPTIONAL for the punkshell mint/bake pipeline: the make.tcl tool step
reports the toolchain's absence rather than failing an ordinary make.tcl run (G-126).
## Work Guidance

10
src/vfs/AGENTS.md

@ -1,4 +1,4 @@
# src/vfs — Virtual File System Images for Builds
# src/vfs — Virtual File System Payloads for Kit Bakes
## Purpose
@ -12,10 +12,10 @@ VFS (Virtual File System) folders define the runtime payloads that get wrapped i
## Local Contracts
- `*.vfs` folders are build artifacts consumed by the kit-assembly stage `tclsh src/make.tcl bake` (and its consumer umbrella `bakehouse`).
- `*.vfs` folders are kit-payload sources consumed by the kit-assembly stage `tclsh src/make.tcl bake` (and its consumer umbrella `bakehouse`).
- **Per-.vfs payload declarations (G-115).** A sibling `src/vfs/<name>.vfs.toml` may declare package folders to materialize INTO `<name>.vfs` (sources: vendor trees under `src/`, or the consent-gated `bin/packages` lib tier) - processed by `make.tcl vfslibs` and as a bake phase, punkcheck-tracked (records in `src/vfs/.punkcheck`) with drop-in-wins precedence: undeclared or hand-modified files are preserved, never overwritten (`supersedes`/`replace` are the explicit declared exceptions). A `.vfs` with no declaration file is untouched (pure drop-in mode). Format + precedence spec: `src/vfs/README.md`. This surface supersedes the retired per-package `src/runtime/vendorlib_vfs.toml` (migrated 2026-07-31).
- `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 built 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 `BUILD-WARNING:` token, ANSI-highlighted, recapped at end of run like provenance warnings) but still builds - a kit without a startup script is legal. 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. Overlay-only folders with no runtime mapping (e.g `_vfscommon.vfs`) are not checked.
- VFS content must be compatible with the target platform runtime.
- A root-level `punkshell.ico` in a kit's own custom `.vfs` folder overrides
the embedded kit icon and the sidecar-recorded icon choice for that kit
@ -33,13 +33,13 @@ VFS (Virtual File System) folders define the runtime payloads that get wrapped i
## Work Guidance
- To add a module to a VFS build, update the VFS folder's module set (drop-in, or a per-.vfs declaration entry), then run `tclsh src/make.tcl bake`.
- To add a module to a kit's vfs payload, update the VFS folder's module set (drop-in, or a per-.vfs declaration entry), then run `tclsh src/make.tcl bake`.
- The `src/runtime/mapvfs.toml` file maps VFS folders to platform runtimes and kit outputs.
- Describe regeneration steps in commit messages when touching VFS payloads.
## Verification
- `tclsh src/make.tcl bake` builds without errors.
- `tclsh src/make.tcl bake` bakes without errors.
- Built binary starts and loads the expected module set.
## Child DOX Index

Loading…
Cancel
Save