16 KiB
bin/
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.
Generated polyglot .cmd scripts - never edit in place
The .cmd scripts here (e.g punk-runtime.cmd) are punk MULTISHELL polyglots GENERATED by
punk::mix::commandset::scriptwrap::multishell from scriptset sources under
src/scriptapps/bin/ (the home for bin-deployed scriptsets, e.g punk-runtime.* alongside
punk-getzig.*) or src/scriptapps/ itself (e.g punk-tclargs.*, dtplite.*): payload scripts
(<name>.ps1, <name>.bash, <name>.tcl, ...) plus a <name>_wrap.toml
config, spliced into the punk.multishell.cmd template. The polyglot structure is
deliberately fragile (mutual shell-hiding tricks, LF-only endings, cmd.exe's 512-byte
label-scanner constraints) - a hand-edit can silently break one of the participating
shells.
When asked to "fix bin/.cmd":
- Edit the payload/config sources under
src/scriptapps/bin/(never the output file). - Re-wrap from the scriptset's folder: cd
src/scriptapps/binthenpunk::mix::commandset::scriptwrap::multishell <name> -askme 0(output defaults to<projectroot>/bin; requires Thread + the punk::mix modules, e.g a punk shell or a tclsh with project module paths). - The wrapper runs
checkfile(the 512-byte label/boundary validator) automatically - heed ERROR output; "possibly bogus target" warnings are normal for polyglots. Payload growth can push a TEMPLATE label beyond the payloads (e.g:exit_multishell) across a 512-byte boundary: fix by resizing the byte-alignment spacer comment at the end of the affected payload (see the(512B spacer)comment inpunk-runtime.bash) and re-wrapping until checkfile is ERROR-free. - Commit source AND regenerated output together: the scriptwrap test suite pins that a
re-wrap of the punk-runtime scriptset reproduces
bin/punk-runtime.cmdbyte for byte (src/tests/modules/punk/mix/testsuites/scriptwrap/runtimecmd_roundtrip.testscriptwrap_runtime_cmd_roundtrip_no_drift), so drift in either direction fails tests.bin/dtplite.cmdis pinned the same way (src/tests/shell/testsuites/binscripts/dtplite.testdtplite_cmd_roundtrip_no_drift).
dtplite (dtplite.cmd)
bin/dtplite.cmd wraps tcllib's dtplite doctools processor (payload
src/scriptapps/dtplite.tcl, tclsh nextshell on all platforms). It exists so the punk
repl's unknown-handler can resolve the bare dtplite command via PATH - dev doc.validate (punk::mix::commandset::doc) depends on it. The payload falls back to the
project-vendored tcllib (src/vendorlib_tcl9/<arch>/tcllib*) when the invoking tclsh
has no dtplite package installed.
Local Contracts
Utility naming policy: punk- prefix (G-096)
Punkshell-own utilities deployed to bin/ take a punk- name prefix (e.g
punk-getzig) so a user who adds <project>/bin to their PATH gets
collision-resistant names. Exceptions: wrappers whose purpose is to present an
external tool under its own name (dtplite, sdx, kettle), and getpunk
(already punkshell-specific; intended future single-download cross-platform entry
point). Policy decided by the user 2026-07-20 (G-096); the remaining pre-policy names
were swept under G-097 (2026-07-21): punk-bits, punk-runtime, punk-tclargs and the
punk- prefixed selfsign experiment scripts.
.ps1 twin (corrected 2026-07-22 - the twin is SELF-MATERIALIZED, not manually
refreshed): the generated .cmd's batch layer creates <name>.ps1 beside itself
when missing AND fc-compares/re-copies it whenever content differs (powershell
needs a .ps1 extension for -File), so the twin regenerates on any .cmd launch
and self-heals after a re-wrap - no manual copy step. The twin is a byte-copy of the
polyglot and is VCS-ignored (a runtime artifact, unlike the committed .cmd).
Caveats: a user who only ever launches the .ps1 directly can ride a stale twin
until the next .cmd-path launch heals it; and the two launch paths run DIFFERENT
powershells - ./bin/<name> in a powershell session resolves the .ps1 and runs it
under the INVOKING shell (pwsh 7 or powershell 5), while the .cmd route is pinned
by the wrap toml (cmd.exe /c powershell = Windows PowerShell) - payload code must
be edition-portable and avoid implementation-defined behaviour (e.g. Hashtable
enumeration order differs between the editions; sort explicitly).
INVOCATION GUIDELINE for user-facing documentation (user policy 2026-07-22; no
user-facing guidelines existed yet when recorded - apply to all future README/help/
doc examples involving the polyglot bin/*.cmd utilities): show invocations WITH
the .cmd extension, e.g. bin/punk-runtime.cmd list, even though extensionless
invocation happens to work on windows in some cases. Rationale: extensionless
resolution in powershell prefers the .ps1 twin, whose direct execution is
ExecutionPolicy-gated (Restricted/AllSigned hosts fail) and runs under the invoking
edition, whereas the .cmd always executes and follows the wrap-pinned, policy-
bypassing tested path. The SAME .cmd file also runs on unix shells (the polyglot
needs no extension stripping - ./bin/punk-runtime.cmd from bash/zsh, or
bash bin/punk-runtime.cmd), so one form serves all platforms and the only
platform-specific difference left in examples is path separator style
(bin/punk-runtime.cmd vs bin\punk-runtime.cmd).
Launch package modes (built punk shells)
The first argument to a built punk shell may be a dash-delimited package mode composed of tokens from dev, os, src, internal (e.g. punksys src, punk902z dev-os). internal is the default and is always appended when absent. A first argument that is not a valid mode list is treated as a subcommand instead. Implementation: src/vfs/_config/punk_main.tcl (search all_package_modes).
src mode is the one that matters for verifying working-tree changes:
- Discovers the project root from the executable's location (exe in
bin/-> parent directory), not from the cwd - it works from any working directory as long as the executable resides in the project'sbin/. - Prepends
src/modules,src/modules_tcl<N>,src/bootsupport/modules{,_tcl<N>}andsrc/vendormodules{,_tcl<N>}to the module path, setspackage prefer latestso dev-numbered999999.0a1.0source modules beat kit-stamped snapshots on unversionedpackage require, and registers#modpodmodules fromsrc/modules(startup notice:src mode: registered N #modpod modules from .../src/modules). - Consequence:
punksys src/punk902z srcexercises current working-tree source with no kit rebuild; a plain launch (punksys) exercises the module snapshots baked into the kit at build time.package present <pkg>reporting999999.0a1.0(vs a release version like0.7.1) tells you which set a session is running - runbooks with version expectations must state the intended launch mode.
Runtime fetch/selection (punk-runtime.cmd)
bin/punk-runtime.cmd {fetch|list|use|run} manages the plain runtimes under
bin/runtime/<platform>/ (retrieved from the punkbin artifact repo with sha1
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).
Hashing is deliberately dependency-free in both payloads: the powershell payload computes
sha1 via .NET (Get-PunkFileSha1) rather than Get-FileHash, because Get-FileHash is a
script-defined function in Windows PowerShell 5.1 resolved through PSModulePath and
vanishes on machines with a damaged PSModulePath while compiled cmdlets still work
(field-observed; a one-time note flags the condition); the bash payload probes
sha1sum/shasum/sha1/openssl. Keep new payload code free of PS 5.1
script-module-defined cmdlets (Get-FileHash, New-TemporaryFile, New-Guid,
Import-PowerShellDataFile, ...) for the same reason.
list -remote compares local runtimes against the server's sha1sums (Same version /
UPDATE AVAILABLE / not-listed, plus remote-only entries; cached sha1sums.txt fallback
with a warning when the server is unreachable). It also surfaces the selection state:
summary lines show the platform's server default (from defaults.txt, best-effort)
and the locally active runtime (annotated (= server default) when they match), the
active runtime's row carries the same * marker the local listing uses, and the
default's row - local or remote-only - is annotated (server default). The artifact
server base url is overridable via PUNKBIN_URL (mirrors/testing) in both payloads.
Which runtime run launches is the per-machine "active" selection in
bin/runtime/<platform>/active.toml (constrained single-key toml active = "<name>",
written by punk-runtime.cmd use <name>, marked in list, VCS-ignored via the existing
bin/* globs). Resolution order for run: PUNK_ACTIVE_RUNTIME env override, then
active.toml, then a sole installed candidate - with multiple runtimes and no selection
it errors listing candidates rather than guessing. The first fetch sets the active
runtime only when none is recorded.
G-103 runtime-family artifact metadata (both payloads): a runtime may carry a
<rootname>.toml metadata record beside it (emitted by the suite_tcl90
kit-family-artifacts step; fetched from punkbin alongside -r<N>-named artifacts -
absence tolerated for pre-family runtimes). list shows a per-runtime summary from it
(variant, tcl patchlevel, revision, piperepl policy, and - on a materialized working
copy - which immutable artifact it came from). use <artifact-r<N>-name> MATERIALIZES
the immutable artifact into its WORKING name (the name minus -r<N> - what mapvfs and
projects reference), copies the metadata toml alongside, and selects the working name;
use <workingname> selects as before. Root-name handling strips only a .exe suffix
(dotted tcl patchlevels make generic last-dot stripping wrong for extensionless unix
names). Candidate listing excludes directories and .txt/.toml/.tm/.tmp/.log files.
G-119 freshness verdict (both payloads): whenever the ACTIVE runtime and the SERVER
DEFAULT are both known, list -remote states their relationship explicitly instead
of only displaying the two facts. freshness: lines report AHEAD/BEHIND naming both
revisions (BEHIND adds a fetch+use update guidance line), an equivalent
current-verdict line where the (= server default) identity annotation did not
already fire, a different-series incomparable note, or a stated no-revision-basis
note. Revision basis: -r<N> parsed from an artifact-named active, else the
beside-toml revision field (materialized working copies); the default's revision
parses from its defaults.txt name. Actives with no revision basis (pre-family: no
toml, no -r<N> name) fall back to sha1 VALUE-matching against the server sha1sums -
a byte-copy of a server artifact is identified by content and gains that artifact's
revision verdict (support-file rows .txt/.toml/... are excluded from the match; the
first ordinal/C-collation match wins in both payloads). A NO-NAME fetch (the
"give me the default" form) prints the same verdict as a one-line note when the
active diverges from the resolved default - silent when current, and BEHIND carries
an inline use <default> hint since the artifact was just fetched. Verdict lines
are byte-identical across the payloads; only the guidance lines carry each payload's
program-name convention. Identity middle column (2026-07-25 follow-on, user-decided
shape): list -remote rows the server listing does not name carry an UNTITLED
middle column - from=<artifact> for a materialized working copy (its beside-toml
source artifact; content is then verified against that artifact's server sha1, so
the Remote cell shows a real status: Same version, CONTENT DIFFERS for the
immutable-artifact anomaly, or (artifact not listed on server)), and
sha1=<name> for a tomlless byte-copy of a server artifact (default-first-then-
ordinal pick - the same rule as the freshness fallback, so row identity and verdict
always name the same artifact). (not listed on server) remains only for genuinely
unidentified files, and the (server default) row annotation follows artifact
identity (a working copy of the default carries it). Rationale: the starred active
row is the salient element - it must change with every use switch and teach the
working-copy mechanics, and column position carries meaning (identity phrasing
inside the Remote column would read as a server fact). Pinned by
src/tests/shell/testsuites/binscripts/runtimecmd_freshness.test against a fixture
punkbin server (PUNKBIN_URL override + src/tests/testsupport/httpfixture.tcl).
Cross-platform surface (G-105 groundwork; both payloads): fetch/list/use accept
-platform <p> where <p> is a punkbin platform-DIR name (win32-x86_64,
linux-x86_64, macosx, ... - never zig triples; the buildsuite maps triples to
platform dirs at build time). Resolution: -platform arg > PUNK_RUNTIME_PLATFORM
env > the detected local platform; validation is shape-only, lowercase (the artifact
server's URL paths are case-sensitive - the server is the truth for what exists).
Foreign-platform folders serve cross-build staging/provisioning: a foreign fetch
requires an explicit runtime name (no foreign defaults), use -platform manages that
folder's active.toml/materialization (the marker travels with the folder when
deployed; unix exec bits are restored at deploy time), and list flags a runtime
whose metadata target disagrees with the folder it sits in (!TARGET-MISMATCH).
run is LOCAL ONLY - it rejects a leading -platform and ignores the env override
(later run args pass through to the runtime untouched). A no-args invocation prints
a short usage block; the help action gives the full operator reference (actions,
options, env vars incl PUNKBIN_URL, examples). platforms ?-remote? enumerates
platform folders: local bin/runtime/* dirs (local platform marked), or - with
-remote - the platforms the artifact server serves, read from the server's
root-level platforms.txt discovery manifest (part of the punkbin layout contract,
generated by punkbin's src/build_sha1sums.tcl; third-party mirrors using the layout
carry the same file - raw-file servers have no directory listing, so the manifest IS
the discovery mechanism), with local presence marked and cached-copy fallback;
servers without the manifest get an actionable message.
The default runtime a no-name fetch retrieves is NOT baked into the payloads: it
comes from the server's root-level curated defaults.txt (<platform> <runtimename>
per line - a punkbin RELEASE DECISION, hand-edited there as part of each publication
change-set and validated by punkbin's src/build_sha1sums.tcl; mirrors may curate
their own). The lookup keys on the resolved platform, so a no-name
fetch -platform <p> works too; platforms without a recorded default, and servers
without the file, produce an actionable name-it-explicitly message (cached-copy
fallback on network failure). Platform names follow the CANONICAL
punkshell platform-dir names defined by the punk::platform module and surfaced as
help platforms in the punk shell (cpu tokens normalized: amd64->x86_64,
aarch64->arm64; the bash payload's local-platform prongs emit canonical names - the
FreeBSD dir was realigned from freebsd-amd64 to freebsd-x86_64 accordingly).
Interactive verification shells
- Interactive console/repl verification should cover both Tcl generations - behaviour can differ materially (e.g. the Tcl 8.6 windows console channel driver vs the Tcl 9 rewrite). Use a Tcl 8.6-based punk shell (
punksys.exe) and a current Tcl 9-based punk shell (named for the Tcl release it embeds, e.g.punk902z.exeat the time of writing - ask the user which is current rather than assuming).info patchlevelin-session confirms the runtime. runtime/win32-x86_64/holds plain tclkits/tclsh runtimes without the punk boot layer - use these for clean-environment probes isolating Tcl-level behaviour from punk (they may lack extensions such as twapi; add an external lib dir toauto_pathwhen a probe needs one).
Work Guidance
Verification
None - build outputs; behaviour is verified via src/tests/ and interactive runbooks.
Child DOX Index
runtime/win32-x86_64/- plain runtime kits (no child AGENTS.md needed; covered by this file's contracts)