18 KiB
ARCHITECTURE.md - Punkshell System Mechanics
About this document
This file is the descriptive, current-state map of how punkshell fits together. It occupies its own lane alongside the project's other documentation:
AGENTS.mdfiles (root + children) are binding work contracts: what you must do when working here.GOALS.md,GOALS-archive.mdandgoals/track intended and landed changes with rationale: what changed and why.- This file says how the system works now.
Rules that keep it useful instead of rotten:
- Pointer-first. Sections state durable mechanics briefly and route to canonical sources (module files, owning AGENTS.md, goal detail files). Never transcribe content that lives elsewhere - point at it. If a section wants to grow past a screenful, the detail belongs in the module or a child doc, with this section pointing at it.
- Current state only. History, rejected alternatives and verification records stay in goal detail files; release-level history stays in
CHANGELOG.md. Do not narrate change history here. - Staleness. When this file conflicts with the code, the code wins and this file is stale. Update or delete the stale section in the same change that breaks it (root
AGENTS.mdCloseout). Delete stale sections rather than annotating them. - In-flux areas. Subsystems under active redesign are documented as one-liners with goal pointers, not detailed prose (see "In-flux areas").
- Verification.
tclsh scriptlib/developer/architecture_lint.tcl- backtick-quoted repo paths must exist,G-<id>references must resolve against the goal indexes, size budget enforced. Run after editing this file.
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).
bin/<kit>.exe = Tcl runtime (zipfs/metakit) + <name>.vfs payload
| boots
v
src/vfs/_config/punk_main.tcl
- package mode: dev / os / src / internal (which module sources to trust)
- subcommand: shell / script / punk / tclsh / shellspy
|
v
app package from src/lib/ (app-punkshell, app-punkscript, app-repl, app-shellspy)
|
v
+------------------- interactive shell --------------------+
| REPL interp (punk::repl) <--> console abstraction |
| + codethread (user code) (punk::console over |
| + shellthread workers opunk::Console) |
+----------------------------------------------------------+
Boot and launch chain
- Kit anatomy. Each
src/vfs/*.vfsfolder is a runtime payload;src/runtime/mapvfs.configmaps payload folders to platform runtimes underbin/runtime/<platform>/;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 fromdev,os,src,internal(internalis always appended when absent). 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 undersrc/lib/(app-punkshell,app-punkscript,app-repl,app-shellspy). - Package modes pick module provenance.
internaluses module snapshots baked into the kit at build time;devloads build outputs from repo-rootmodules/andlib/;srcloads the unbuilt working tree (src/modules,src/lib,src/bootsupport,src/vendormodules) withpackage prefer latestso magic-version dev modules (999999.0a1.0) beat stamped snapshots.srcmode is the standard way to verify working-tree changes without a rebuild. Source:bin/AGENTS.md"Launch package modes". - 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, andpunk::packagepreferenceresolves static-vs-bundled version-aware. Sources:src/vfs/AGENTS.md,goals/archive/G-058-static-runtime-packages.md. scriptsubcommand (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). Supportslib:<name>scriptlib resolution. App package:src/lib/app-punkscript/.tclshsubcommand (G-118, achieved). Plain-tclsh emulation with NO punk modules loaded: stock argument-form parity (-encoding name file; any other leading-argument means no script, everything stays in::argv; no-eone-liner - deliberate stock parity, see G-077 for the punk-level one-liner direction),lib:refused with a pointer atscript, missing script file is a clean exit 1. On piperepl-patched runtimes (the punk family runtimes -src/buildsuites/suite_tcl90/patches/) the C-level machinery publishes::tclshlaunch state (istty,dorepl,evalinput,inputbuffer,reopened) allowing piped input to hand over to a live console repl at eof; runtimes without the machinery evaluate piped stdin whole-buffer with honest exit codes and fail fast on console stdin. The subcommand-family contract (tclsh/script/shell/punk/shellspy) is documented as punk::args definitions insrc/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm(ids(script)::punkexe+ per-subcommand; view viapunk::args::usage), consumable by the future launcher help wiring (G-032). Pipe-testable behaviour pinned bysrc/tests/shell/testsuites/punkexe/tclshcmd.test.bin/launch surface. Built kits plus polyglot.cmdutilities (punk-runtime,punk-getzig,punk-tclargs,dtplite) generated fromsrc/scriptapps/sources by thepunk::mixscriptwrap machinery - outputs are never hand-edited.bin/punk-runtime.cmdfetches/lists/selects plain runtimes underbin/runtime/<platform>/from the punkbin artifact server with sha1 verification. Source:bin/AGENTS.md.
Interp and thread topology
- REPL interp vs codethread.
punk::replruns the reader/prompt loop in the main interp; user commands evaluate in a separate thread's interp (src/modules/punk/repl/codethread-999999.0a1.0.tm) so the reader stays responsive and user-code state is isolated from shell state. - shellfilter stacks. Channel transform/logging layers. Among other roles,
varJUNCTION stacks divert the code interp's stdout/stderr into per-run variables that the repl collects and emits to the console after each run (G-001). The API is known-clumsy; audit is proposed as G-090. Source:src/modules/shellfilter-999999.0a1.0.tm. - shellthread workers. Pooled worker threads (logging, tee handlers). tcludp 1.0.12's Windows per-thread exit handler closed process-global events and wedged worker event loops (G-036, achieved); kits now bundle tcludp >= 1.0.13 and a runtime
has_bug-style check surfaces the vulnerable combination. Worker lifecycle audit is also G-090. Source:src/modules/shellthread-999999.0a1.0.tm. - Shared state. Infrastructure
tsvarrays arepunk_-prefixed by contract (legacy unprefixed arrays predate the rule); console facts, ownership and mode caches live in tsv so every thread sees one truth (G-007). Source:src/modules/AGENTS.md. - Subshells. Named secondary repls exist; the substantial subshell roadmap is in-flux - see "In-flux areas".
Console abstraction
Two layers with a deliberate dependency direction (the class never depends on the integrating layer):
opunk::Console(src/modules/opunk/) - avoovalue-OO base class modelling a console as an in/out pair with settled capability facts. Objects are plain Tcl list values;-virtualmethods dispatch on a class tag so subclasses can override everything channel-related. Cooperative seams are pluggable: the probe-byte store (waiting_chunks_arrayvar), the size-query provider (size_query_provider), and an anchor lifecycle callback. Source:src/modules/opunk/AGENTS.md.punk::console(src/modules/punk/console-999999.0a1.0.tm) - the integrating layer: console-spec resolution, per-console terminal-property facts, raw mode, ANSI emit/query helpers, and registration of the providers the base class consumes.- Spec forms and selection. A console spec is an
{in out}channel pair, an anchored instance name, or an::opunk::Consoleobject value, normalised bypunk::console::console_spec_resolve.repl::init -console <spec>selects the console per repl (G-001, achieved); the default is the process console{stdin stdout}. Source:src/modules/punk/AGENTS.md. - Backends (G-001, achieved). Reference subclasses
opunk::console::test,::ssh,::tk(deterministic test double, socket-carried terminal, Tk text widget) prove non-detectable terminal-like devices can host a repl with no base-class orpunk::consolechanges. Detail:goals/archive/G-001-pluggable-console-backends.md. - Location transparency (G-007, achieved). Facts are tsv-backed and terminal queries are owner-routed: a query from any thread forwards to the console-owning thread through a
thread::sendchoke point, so code-interp code sees the same console the repl sees withoutrepl evalround-trips. Ownership lives in a tsv registry keyed by canonical channel pair. - Raw mode. twapi on Windows when available; otherwise a persistent powershell console-mode named-pipe server serves raw transitions on twapi-less runtimes (G-106, achieved) - quiet, lazy-started, exits with the owning process.
- Dead-console handling. The Tcl 9 Windows console driver never reported a killed host terminal to the script, leaving orphaned shells spinning a core (G-039, achieved; upstream ticket filed and fixed). The repl arms a close-on-probe-failure watchdog only on runtimes a
punk::lib::checkclassifier reports as affected (G-076, achieved); runtimes at or past 9.0.5 deliver the dead console as a readable event and use the normal eof teardown.
Module ecosystem and package loading
- Editable modules live in
src/modules/(+src/modules_tcl8/,src/modules_tcl9/siblings); filenames carry the magic dev version999999.0a1.0with the real semver in a<name>-buildversion.txtsidecar, and namespaces mirror directory layout (punk/ansi/=punk::ansi).punk::libunknownis the manually-versioned exception. Source:src/modules/AGENTS.md. punk::argsis both parser and documentation system.PUNKARGS/argdocblocks 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 thepunkargsentries acrossGOALS.mdandGOALS-archive.md.- Discovery and modpods.
punk::libunknownprovides module discovery/registration beyondtcl::tmdefaults;#modpod-*source directories pack into zip-based multi-file.tmmodules 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.tclclassifies 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/exploresvoo-based value-OO reimplementations (objects as plain Tcl values); thepunk::*modules remain the production implementations. - Vendored code. Third-party packages live in
src/vendormodules/andsrc/vendorlib/(+_tcl8/_tcl9siblings), refreshed bymake.tcl vendorupdatefrom config; never hand-edited (enforcement policy is proposed as G-026).
Build, provenance and kits
src/make.tclis the single build entry. Key subcommands:modules/libs/packages(repo-root build outputs),vfscommonupdate(regenerate_vfscommon.vfs),bake(kit assembly intobin/; optional kit names confine the run to those kits - G-121),bakelist(report the configured kit matrix with runtime/vfs presence and deployed state),bin,bakehouse(packages + bake for a clean checkout;project/vfsremain as deprecated aliases - G-112 rename, achieved),bootsupport(snapshot refresh),vendorupdate,check,workflow,projectversion. Dispatch and help dogfoodpunk::args(G-030, achieved) and degrade to plain scan/help when bootsupportpunk::argsis stale (PUNKBOOT_PLAIN=1forces the degraded mode). Source:src/AGENTS.md.- punkcheck records every install/delete as events in per-folder
.punkcheckdirectories - 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-0PROVENANCE-WARNING:token,-dirty-abortfor strict mode);vendorupdatewarns for dirty source-project checkouts. - 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 thatbin/punk-runtime.cmdfetches from.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-intest-tk; punkshell's own runtests is censused on that runtime at parity with a same-day native-8.6 baseline. Suite child shells scrubTCL<major>_<minor>_TM_PATHas 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.newcomposes thin layouts fromsrc/project_layouts/(overlay chain with.antideletion markers andname@basederivation - G-087, achieved) and injects bootsupport modules from the generating shell at generation time. - Workflow overview.
tclsh src/make.tcl workflowprints the embedded ASCII data-flow overview of the build/release pipeline, with its own update contract insrc/AGENTS.md. This section deliberately summarises rather than copies it.
Test harness
src/tests/runtests.tclis the entry point. Path-glob discovery overtestsuites/with theX/***-style lattice (-include-paths/-exclude-paths, G-093), single-process or multi-process child modes, a-jobs Nworker pool (G-091), report modes (compact/markdown/json) and an optional UDP watch stream. Source:src/tests/AGENTS.md.- Characterization style. Suites pin observed current behaviour, including
_GAP_-named tests that pin known bugs and flip when a goal fixes them - goal acceptance criteria routinely name the pins they flip. - Child-process suites by design. A repl cannot run inside the shared testinterp (codethread exit callbacks target the thread's main interp), so console/repl verification drives child processes (
src/tests/testsupport/repl_console_driver.tcl); kit and launcher behaviour is likewise exec-driven undersrc/tests/shell/. - Mode parity.
scriptlib/developer/runtests_parity.tclcompares-report jsonoutputs across runner modes.
In-flux areas
Mechanics here are documented as pointers only - detailed prose would be obsolete on arrival. Read the goal detail files for intended end states and the code for current state.
- Subshell roadmap: G-002, G-003, G-008, G-009, G-010 (non-nested subshells, resource limits, scoped console state, themed profiles, tree navigation).
- Input mode and completion: G-013 (raw-mode default), G-044 (punk::args-driven completion).
- Stored configuration and help: G-014 (punk::config toml), G-042 (subshell help topics), G-043 (subshell definition plugins).
- Console err channel: G-011.
- Launch surface: G-023 (version-named binaries), G-033 (
proj:package mode), G-077 (-eone-liners), G-089 (scriptlib in kits). - 8.6 buildsuite family: G-101 (kit container) - the core runtime (G-099) and the Tk/tklib/tcllib dependency set (G-100) are achieved 2026-07-26.
Doc map - which lane holds what
- How it works now (durable mechanics): this file.
- What you must do when working here: root
AGENTS.md+ the child AGENTS.md chain (binding). - What changed and why:
GOALS.md,GOALS-archive.md,goals/detail files;CHANGELOG.mdfor release-level history. - Per-command and per-proc usage: PUNKARGS blocks in the modules (
i <command>at a repl); generated docs undersrc/doc/. - User-facing features:
README.md.