Browse Source

make.tcl shell under punk exes: recompute tcl_interactive, restore accelerator-forgotten packages

Fixes the two user-reported symptoms of '<punkexe> src/make.tcl shell':

Missing prompts: app-punkscript correctly forces ::tcl_interactive 0 for
script execution, and punk::repl is already loaded at kit boot - so the shell
branch's 'package require punk::repl' re-loads nothing and the repl's
load-time interactivity probe (repl line ~44) never re-runs, leaving prompts
suppressed on a real console. The shell branch now recomputes
::tcl_interactive via punk::repl::is_interactive against the actual input
channel before repl::init/start. Piped behaviour unchanged (probe still
reports non-interactive).

Raw-mode 'invalid command name struct::set': the accelerator-reload block
(package forget + rename of sha1/md5/struct::* so reloads can pick up
accelerators from the restored full paths) relied on later package-require
chains to restore the forgotten packages. Under a kit, the consumers
(punk::lib, punk::du, punk::nav::fs, flagfilter) were pre-loaded at kit boot,
their requires never re-fire, and the first repl-thread-side call of a
struct::set-backed punk::lib proc (lunique_unordered's self-redefining first
invocation, reached via flagfilter/nav::fs in interactive/raw paths) hit the
destroyed command. The block now records which of the forgotten packages were
actually loaded and re-requires them immediately. Verified by thread-probe:
the repl-thread interp now shows struct::set 2.2.5 + sha1 2.0.5 restored
identically under tclsh90 and punk91 (previously absent under punk91); the
codethread was always unaffected (it re-requires its own package list).

src/AGENTS.md: new note - punk-exe-hosted make.tcl runs in a pre-loaded
interp, not a virgin one (no-op requires, load-time state like the
tcl_interactive probe, kit-vs-bootsupport module provenance mixing); interp
surgery in make.tcl must restore deliberately and be tested under both tclsh
and a punk exe. Layout make.tcl copies re-synced via the punkcheck channel.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 1 day ago
parent
commit
378ba8c084
  1. 4
      CHANGELOG.md
  2. 2
      punkproject.toml
  3. 1
      src/AGENTS.md
  4. 26
      src/make.tcl
  5. 26
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  6. 26
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  7. 26
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

4
CHANGELOG.md

@ -5,6 +5,10 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.12.27] - 2026-07-14
- make.tcl shell under a built punk executable: fixed missing prompts and the raw-mode 'invalid command name struct::set' error. Both stemmed from the kit's script-mode boot pre-loading the punk stack: (1) app-punkscript correctly forces ::tcl_interactive 0 for script runs, and since punk::repl was already loaded its load-time interactivity probe never re-ran - the shell branch now recomputes ::tcl_interactive via punk::repl::is_interactive against the real input channel before repl::start; (2) the accelerator-reload block (package forget + command destroy of sha1/md5/struct::*) relied on later package-require chains to restore the packages, which never re-fire when consumers (punk::lib, punk::du, flagfilter etc) were pre-loaded at kit boot - the block now records what was actually loaded and re-requires it immediately (verified: repl-thread interp state now identical under tclsh and punk91). src/AGENTS.md gains a "pre-loaded interp, not a virgin one" note covering the class: interp surgery and load-time-state assumptions in make.tcl must restore deliberately and be tested under both tclsh and a punk exe (includes the kit-vs-bootsupport module provenance-mixing caveat).
## [0.12.26] - 2026-07-14
- make.tcl under a built punk executable: kit builds gain a self-build guard - the kit whose deployed executable is running the build is skipped with a clear warning (previously the pre-deploy process sweep would taskkill the build's own process mid-run with -k, since it matched processes by executable name only; without -k the polite kill's failure was the only thing saving the run). The sweep now also excludes the build's own pid unconditionally (covers a same-named copy driving the build from a non-deploy path). Informational/update subcommands were already safe (they exit before the kit machinery); building *other* kits from a punk exe remains supported. Also quietened the 'punk::libunknown::init already done - unnecessary call?' stderr diagnostic when a punk exe runs make.tcl: make.tcl now leaves an already-active libunknown in place instead of re-sourcing the bootsupport copy over it and tripping init's rename guard. src/AGENTS.md build guidance updated.

2
punkproject.toml

@ -1,4 +1,4 @@
[project]
name = "punkshell"
version = "0.12.26"
version = "0.12.27"
license = "BSD-2-Clause"

1
src/AGENTS.md

@ -70,6 +70,7 @@ Recovery after a wrong path guess:
- make.tcl subcommands and flags are punk::args-declared (G-030): `tclsh src/make.tcl help ?subcommand?` or `<subcommand> -help` shows tabled usage; invalid arguments produce punk::args usage errors. 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 `::punkboot::argdoc` definitions are constructed (string-built) and use the G-045 authoring mechanisms — block-form help values + `@normalize`, explicit `@form -synopsis` — 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`, and declared per-kit `*.vfs/lib_tcl<N>/<pkg>` subfolders. (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.
- Use `punk make.tcl project` or `punk902z make.tcl project` 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.
- **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.
- Superseded module intermediates are pruned automatically so payload wrapping inherits clean trees:

26
src/make.tcl

@ -2131,12 +2131,31 @@ if {$package_paths_modified} {
#whether they provide functions such as Loaded, Implementations, SwitchTo
set acceleratable [list sha1 md5]
lappend acceleratable {*}[lsearch -all -inline [package names] struct::*]
set acceleratable_reload [list]
foreach p $acceleratable {
if {[package provide $p] ne ""} {
#was actually loaded (not merely indexed) - must be re-required below or consumers
#that already required it are left calling a destroyed/forgotten command
lappend acceleratable_reload $p
}
package forget $p
if {[string match struct::* $p]} {
catch {rename $p ""}
}
}
#Re-require whatever was loaded before the forget. The forget/rename exists so the reload can
#pick up accelerators (e.g tcllibc) from the now-restored full package paths - but the reload
#must actually happen here: when make.tcl runs under a built punk executable the consumers
#(punk::lib, punk::du, flagfilter etc) were already loaded at kit boot, so no later
#'package require' chain re-fires for these and e.g raw-mode repl paths would hit
#'invalid command name struct::set'. (Under plain tclsh the shell-branch loads happened to
#re-require them - relying on that was load-order luck, not design.)
foreach p $acceleratable_reload {
if {[catch {package require $p} _accelerr]} {
puts stderr "make.tcl: failed to reload package $p after accelerator-reload forget ($_accelerr) - its commands are unavailable for the rest of this run"
}
}
unset -nocomplain acceleratable_reload _accelerr
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@ -2393,6 +2412,13 @@ if {$::punkboot::command eq "shell"} {
set ::argv [lrange $::argv 1 end]
incr ::argc -1
#When make.tcl runs under a built punk executable, app-punkscript set
#::tcl_interactive 0 (correct for script execution) and punk::repl was already loaded
#at kit boot - the package require above re-loads nothing, so the repl's load-time
#interactivity probe never re-ran and prompts would stay suppressed on a real console.
#Recompute against the actual input channel now that a shell has been explicitly asked for.
set ::tcl_interactive [punk::repl::is_interactive stdin]
repl::init
set replresult [repl::start stdin -title make.tcl]
#review

26
src/project_layouts/custom/_project/punk.basic/src/make.tcl

@ -2131,12 +2131,31 @@ if {$package_paths_modified} {
#whether they provide functions such as Loaded, Implementations, SwitchTo
set acceleratable [list sha1 md5]
lappend acceleratable {*}[lsearch -all -inline [package names] struct::*]
set acceleratable_reload [list]
foreach p $acceleratable {
if {[package provide $p] ne ""} {
#was actually loaded (not merely indexed) - must be re-required below or consumers
#that already required it are left calling a destroyed/forgotten command
lappend acceleratable_reload $p
}
package forget $p
if {[string match struct::* $p]} {
catch {rename $p ""}
}
}
#Re-require whatever was loaded before the forget. The forget/rename exists so the reload can
#pick up accelerators (e.g tcllibc) from the now-restored full package paths - but the reload
#must actually happen here: when make.tcl runs under a built punk executable the consumers
#(punk::lib, punk::du, flagfilter etc) were already loaded at kit boot, so no later
#'package require' chain re-fires for these and e.g raw-mode repl paths would hit
#'invalid command name struct::set'. (Under plain tclsh the shell-branch loads happened to
#re-require them - relying on that was load-order luck, not design.)
foreach p $acceleratable_reload {
if {[catch {package require $p} _accelerr]} {
puts stderr "make.tcl: failed to reload package $p after accelerator-reload forget ($_accelerr) - its commands are unavailable for the rest of this run"
}
}
unset -nocomplain acceleratable_reload _accelerr
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@ -2393,6 +2412,13 @@ if {$::punkboot::command eq "shell"} {
set ::argv [lrange $::argv 1 end]
incr ::argc -1
#When make.tcl runs under a built punk executable, app-punkscript set
#::tcl_interactive 0 (correct for script execution) and punk::repl was already loaded
#at kit boot - the package require above re-loads nothing, so the repl's load-time
#interactivity probe never re-ran and prompts would stay suppressed on a real console.
#Recompute against the actual input channel now that a shell has been explicitly asked for.
set ::tcl_interactive [punk::repl::is_interactive stdin]
repl::init
set replresult [repl::start stdin -title make.tcl]
#review

26
src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl

@ -2131,12 +2131,31 @@ if {$package_paths_modified} {
#whether they provide functions such as Loaded, Implementations, SwitchTo
set acceleratable [list sha1 md5]
lappend acceleratable {*}[lsearch -all -inline [package names] struct::*]
set acceleratable_reload [list]
foreach p $acceleratable {
if {[package provide $p] ne ""} {
#was actually loaded (not merely indexed) - must be re-required below or consumers
#that already required it are left calling a destroyed/forgotten command
lappend acceleratable_reload $p
}
package forget $p
if {[string match struct::* $p]} {
catch {rename $p ""}
}
}
#Re-require whatever was loaded before the forget. The forget/rename exists so the reload can
#pick up accelerators (e.g tcllibc) from the now-restored full package paths - but the reload
#must actually happen here: when make.tcl runs under a built punk executable the consumers
#(punk::lib, punk::du, flagfilter etc) were already loaded at kit boot, so no later
#'package require' chain re-fires for these and e.g raw-mode repl paths would hit
#'invalid command name struct::set'. (Under plain tclsh the shell-branch loads happened to
#re-require them - relying on that was load-order luck, not design.)
foreach p $acceleratable_reload {
if {[catch {package require $p} _accelerr]} {
puts stderr "make.tcl: failed to reload package $p after accelerator-reload forget ($_accelerr) - its commands are unavailable for the rest of this run"
}
}
unset -nocomplain acceleratable_reload _accelerr
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@ -2393,6 +2412,13 @@ if {$::punkboot::command eq "shell"} {
set ::argv [lrange $::argv 1 end]
incr ::argc -1
#When make.tcl runs under a built punk executable, app-punkscript set
#::tcl_interactive 0 (correct for script execution) and punk::repl was already loaded
#at kit boot - the package require above re-loads nothing, so the repl's load-time
#interactivity probe never re-ran and prompts would stay suppressed on a real console.
#Recompute against the actual input channel now that a shell has been explicitly asked for.
set ::tcl_interactive [punk::repl::is_interactive stdin]
repl::init
set replresult [repl::start stdin -title make.tcl]
#review

26
src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

@ -2131,12 +2131,31 @@ if {$package_paths_modified} {
#whether they provide functions such as Loaded, Implementations, SwitchTo
set acceleratable [list sha1 md5]
lappend acceleratable {*}[lsearch -all -inline [package names] struct::*]
set acceleratable_reload [list]
foreach p $acceleratable {
if {[package provide $p] ne ""} {
#was actually loaded (not merely indexed) - must be re-required below or consumers
#that already required it are left calling a destroyed/forgotten command
lappend acceleratable_reload $p
}
package forget $p
if {[string match struct::* $p]} {
catch {rename $p ""}
}
}
#Re-require whatever was loaded before the forget. The forget/rename exists so the reload can
#pick up accelerators (e.g tcllibc) from the now-restored full package paths - but the reload
#must actually happen here: when make.tcl runs under a built punk executable the consumers
#(punk::lib, punk::du, flagfilter etc) were already loaded at kit boot, so no later
#'package require' chain re-fires for these and e.g raw-mode repl paths would hit
#'invalid command name struct::set'. (Under plain tclsh the shell-branch loads happened to
#re-require them - relying on that was load-order luck, not design.)
foreach p $acceleratable_reload {
if {[catch {package require $p} _accelerr]} {
puts stderr "make.tcl: failed to reload package $p after accelerator-reload forget ($_accelerr) - its commands are unavailable for the rest of this run"
}
}
unset -nocomplain acceleratable_reload _accelerr
# -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
@ -2393,6 +2412,13 @@ if {$::punkboot::command eq "shell"} {
set ::argv [lrange $::argv 1 end]
incr ::argc -1
#When make.tcl runs under a built punk executable, app-punkscript set
#::tcl_interactive 0 (correct for script execution) and punk::repl was already loaded
#at kit boot - the package require above re-loads nothing, so the repl's load-time
#interactivity probe never re-ran and prompts would stay suppressed on a real console.
#Recompute against the actual input channel now that a shell has been explicitly asked for.
set ::tcl_interactive [punk::repl::is_interactive stdin]
repl::init
set replresult [repl::start stdin -title make.tcl]
#review

Loading…
Cancel
Save