Browse Source

G-113: make.tcl terminal-aware colour policy (tty probe, force overrides, zero-ESC piped)

Startup policy: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout -winsize tty
probe (Tcl 8.7+/9); Tcl 8.6 fails OFF. Piped/unknown non-forced runs push an
ansistrip transform on stdout+stderr - zero ESC bytes from every emitter incl.
module-side (punk::ansi's colour_disabled deliberately keeps non-colour
effects; design finding recorded in the goal file). shell pops the transform.
sgr_cache cleared for punk-exe-hosted pre-loaded interps per the punk::ansi
contract. define_global_ansi fully gated; raw-SGR literals routed through new
::punkboot::sgr gate (fixes a malformed \x1b\31m). check reports the policy.
make.tcl converted CRLF->LF.

Verified: tclsh90 + punk91 kit piped zero-ESC (check/help/workflow/modules);
force overrides re-colour piped output; NO_COLOR always suppresses colour;
8.6 tcl86-plain; real Windows console mode=tty (hidden-console evidence);
WSL pty mode=tty vs piped-plain (tclkit-902-Linux64). New suite
punkexe/maketclcolour.test 3/3. src/AGENTS.md bullet revised; punkshell 0.22.2.

Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 6 days ago
parent
commit
e03ce1d64a
  1. 11
      CHANGELOG.md
  2. 3
      goals/G-112-maketcl-subcommand-rename.md
  3. 40
      goals/G-113-maketcl-tty-aware-colour.md
  4. 2
      punkproject.toml
  5. 2
      src/AGENTS.md
  6. 168
      src/make.tcl
  7. 168
      src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl
  8. 168
      src/project_layouts/vendor/punk/basic/src/make.tcl
  9. 168
      src/project_layouts/vendor/punk/project-0.1/src/make.tcl
  10. 150
      src/tests/shell/testsuites/punkexe/maketclcolour.test

11
CHANGELOG.md

@ -5,6 +5,17 @@ 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` Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy. "Project Versioning" section for the bump policy.
## [0.22.2] - 2026-07-25
- make.tcl colour output is terminal-aware (G-113): piped/redirected runs produce fully
ESC-free output automatically (ansistrip channel transform on stdout+stderr - covers
module-side emitters too), interactive terminal runs keep colour (stdout `-winsize` tty
probe on Tcl 8.7+/9; verified on a real Windows console and a WSL pty). NO_COLOR always
suppresses colour; PUNK_FORCE_COLOR/FORCE_COLOR re-enable ANSI on piped output; Tcl 8.6
(no probe) fails OFF. `make.tcl check` reports the active policy; `make.tcl shell` pops
the transform for the repl. New characterization suite
`src/tests/shell/testsuites/punkexe/maketclcolour.test`. make.tcl converted to LF endings.
## [0.22.1] - 2026-07-25 ## [0.22.1] - 2026-07-25
- punk::libunknown 0.2.2 (G-120): the package-unknown static-builtin shortcircuit no longer - punk::libunknown 0.2.2 (G-120): the package-unknown static-builtin shortcircuit no longer

3
goals/G-112-maketcl-subcommand-rename.md

@ -62,7 +62,8 @@ act rather than an input folder.
RESOLVED at activation 2026-07-25 (user-directed dispositions): RESOLVED at activation 2026-07-25 (user-directed dispositions):
- `vfslibs` disposition: folds into bake (binary libs are build outputs, per the - `vfslibs` disposition: folds into bake (binary libs are build outputs, per the
G-004/G-103 direction) - the provenance-argument alternative (promote under G-004 direction and archived G-103, achieved 2026-07-22 - see
goals/archive/G-103-runtime-kit-family.md) - the provenance-argument alternative (promote under
`vfscommonupdate`) is not taken. Whether `vfslibs` survives as a standalone `vfscommonupdate`) is not taken. Whether `vfslibs` survives as a standalone
narrowing command is decided in-session during the rename work and recorded narrowing command is decided in-session during the rename work and recorded
in Progress. in Progress.

40
goals/G-113-maketcl-tty-aware-colour.md

@ -23,6 +23,46 @@ Agents drive `tclsh src/make.tcl ...` with captured/piped output on every build-
- Post-hoc SGR stripping in harnesses - rejected: pushes the burden onto every consumer. - Post-hoc SGR stripping in harnesses - rejected: pushes the burden onto every consumer.
- Per-channel colour policy (stderr coloured when it alone is a tty) - more faithful but more state; default is the stdout-keyed single switch, settled in the work. - Per-channel colour policy (stderr coloured when it alone is a tty) - more faithful but more state; default is the stdout-keyed single switch, settled in the work.
## Progress
- 2026-07-25 implementation landed (make.tcl + suite + docs; punkshell 0.22.2):
startup policy block (precedence NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR >
stdout `-winsize` tty probe; modes tty|forced|nocolor|piped-plain|tcl86-plain,
reported by `make.tcl check` and env PUNKBOOT_COLOUR_DEBUG=1), ansistrip
write transform pushed on stdout+stderr for piped/8.6-unknown non-forced runs
(zero-ESC guarantee covering module-side emitters such as punkcheck
summaries; the shell subcommand pops it - repl colour stays the shell's
concern), punk::ansi sgr_cache cleared per that module's contract for
punk-exe-hosted pre-loaded interps, define_global_ansi has_ansi branch fully
gated (A() palette all-empty when colour disabled), raw-SGR literal sites
routed through the new ::punkboot::sgr gate (incl. one malformed \x1b\31m
literal), make.tcl converted CRLF->LF per repo preference.
DESIGN FINDING recorded: punk::ansi's colour_disabled deliberately strips
COLOUR codes only (non-colour effects like bold survive; the state-changer
must clear the sgr cache per the in-module contract). The zero-ESC piped
contract therefore rides the make.tcl boundary transform, not
colour_disabled; NO_COLOR on a real terminal keeps the module's semantics.
- Verification evidence 2026-07-25:
piped (native tclsh90 9.0.3): check/help/workflow/modules all exit 0 with
zero ESC bytes; NO_COLOR=1 zero ESC; FORCE_COLOR=1 / PUNK_FORCE_COLOR=1 emit
colour SGR on piped output (80 colour lines in help); NO_COLOR+FORCE emits
effects but zero colour codes (NO_COLOR wins for colour); FORCE_COLOR=0
treated as unset. punk91 kit (script src/make.tcl): check/help/workflow exit
0, zero ESC. Tcl 8.6 (native bawt tclsh86): mode=tcl86-plain, zero ESC;
FORCE re-enables ANSI. Windows console (hidden-console Start-Process recipe,
real console handles): stdout -winsize probe succeeds ({120 30}), policy
reports mode=tty colour_disabled=0 ansistrip=0. Unix pty (WSL native-fs
staging, tclkit-902-Linux64 under script(1)): mode=tty colour_disabled=0;
piped control run in the same staging: mode=piped-plain ansistrip=1.
Suite: shell/testsuites/punkexe/maketclcolour.test 3/3 pass (punk902z).
src/AGENTS.md interim NO_COLOR bullet replaced with implemented-behaviour
guidance. Remaining manual item: human visual confirmation of colour
appearance on live terminals (the mechanical decision + rendering chain is
fully evidenced above).
- Remains for acceptance: none beyond the recorded manual-visual note; flip is
deferred to the make.tcl-window acceptance pass (with G-112/G-104) so the
layout make.tcl copies sync once for the whole window.
## Notes ## Notes
- Activation freshness 2026-07-25: goals_xref.tcl score G-113 re-run at the - Activation freshness 2026-07-25: goals_xref.tcl score G-113 re-run at the

2
punkproject.toml

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

2
src/AGENTS.md

@ -63,7 +63,7 @@ Recovery after a wrong path guess:
- Use `tclsh src/make.tcl modules` to build just the module packages. - 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 libs` to build just the library packages.
- Use `tclsh src/make.tcl packages` to build both modules and libraries. - Use `tclsh src/make.tcl packages` to build both modules and libraries.
- Agent runs of `tclsh src/make.tcl ...` capture/pipe output: set `NO_COLOR=1` in the environment so captured output carries no ANSI SGR sequences (honoured from startup). Interactive terminal runs keep colour by default. G-113 tracks making this automatic via tty detection - revise this bullet when it lands. - 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 has no boot-phase probe - colour fails OFF there (use the force vars for 8.6 interactive colour). stderr follows the stdout decision; `make.tcl shell` pops the transform (repl colour is the shell's own concern). `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). - 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 (`project`, `packages`, `modules`, `libs`, `vfs`, `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; pass `-dirty-abort` 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). - All build/promotion commands (`project`, `packages`, `modules`, `libs`, `vfs`, `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; pass `-dirty-abort` 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. - 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.

168
src/make.tcl

@ -2,8 +2,140 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} { # ---------------------------------------------------------------------------
# Terminal-aware colour/ANSI policy (G-113) - evaluated before any output.
# Precedence:
# 1. NO_COLOR set (any value) -> colour OFF (always wins; punk::ansi
# NO_COLOR semantics: colour codes stripped, non-colour effects like bold
# may remain when output goes to a terminal)
# 2. PUNK_FORCE_COLOR / FORCE_COLOR set -> ANSI ON even when piped
# (a value of 0/false/no/off is treated as not-set, per ecosystem convention)
# 3. stdout tty probe -> colour ON only when stdout is a
# terminal channel (-winsize option present - Tcl 8.7+/9 terminal channels
# only, mirroring the stdin_is_interactive -inputmode precedent)
# Piped/redirected stdout (probe fails, no force) additionally pushes a strip
# transform on stdout+stderr so the output is fully ESC-free - machine
# consumers get plain text no matter which emitter produced it (punk::ansi's
# colour_disabled contract deliberately retains non-colour SGR effects, and
# module-side emitters like punkcheck summaries follow that contract - the
# transform is the single choke point that guarantees zero ESC bytes).
# Tcl 8.6 has no boot-phase tty probe (no -winsize/-inputmode anywhere): the
# fail direction is plain/OFF (safe for piped/captured agent runs - unlike
# stdin's assume-interactive; use the force env vars for 8.6 interactive colour).
# stderr follows the same single stdout-keyed decision.
# The colour switch is ::punk::console::colour_disabled; when punk::ansi is
# already loaded (punk-exe-hosted runs) its sgr cache is cleared per that
# module's contract ("whatever function disables or re-enables colour should
# have made a call to punk::ansi::sgr_cache -action clear").
# ::punkboot::colour_mode records the decision: one of
# tty | forced | nocolor | piped-plain | tcl86-plain
# ---------------------------------------------------------------------------
namespace eval ::punkboot {
variable colour_mode ""
}
namespace eval ::punkboot::ansistrip {
#Write-side channel transform stripping all ANSI escape sequences (CSI incl.
#SGR, OSC with BEL/ST terminators, and two-char ESC sequences). Handles
#sequences split across write chunks via a per-channel carry buffer; an
#unterminated sequence held at flush/finalize time is dropped (it is an
#incomplete ANSI sequence - dropping it IS stripping it).
variable carry [dict create]
variable re_complete {\x1b(?:\[[0-9;:<=>?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\^_])}
proc strip {chanid data} {
variable carry
variable re_complete
if {[dict exists $carry $chanid]} {
set data "[dict get $carry $chanid]$data"
dict unset carry $chanid
}
#hold back a trailing incomplete escape sequence (conservative: from the
#last ESC onward, if it is not a complete sequence it may be a prefix of
#one still being written)
set idx [string last \x1b $data]
if {$idx >= 0} {
set tail [string range $data $idx end]
if {![regexp -- "^${re_complete}" $tail]} {
dict set carry $chanid $tail
set data [string range $data 0 $idx-1]
}
}
regsub -all -- $re_complete $data {} data
return $data
}
proc transchan {op chanid args} {
variable carry
switch -- $op {
initialize {
return [list initialize finalize write flush clear]
}
write {
return [strip $chanid [lindex $args 0]]
}
flush {
return ""
}
clear -
finalize {
dict unset carry $chanid
return
}
}
}
}
apply {{} {
set force 0
foreach fvar {PUNK_FORCE_COLOR FORCE_COLOR} {
if {[info exists ::env($fvar)] && $::env($fvar) ni {0 false FALSE no NO off OFF}} {
set force 1
break
}
}
set stdout_is_tty [expr {![catch {chan configure stdout -winsize}]}]
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1} namespace eval ::punk::console {variable colour_disabled 1}
set ::punkboot::colour_mode nocolor
} elseif {$force} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode forced
} elseif {$stdout_is_tty} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode tty
} else {
namespace eval ::punk::console {variable colour_disabled 1}
if {[package vsatisfies [package provide Tcl] 8.7-]} {
set ::punkboot::colour_mode piped-plain
} else {
set ::punkboot::colour_mode tcl86-plain
}
}
#zero-ESC guarantee for piped/unknown output unless the caller forced ANSI on.
#(NO_COLOR keeps punk::ansi's documented colour-only-stripping semantics on a
#terminal; a NO_COLOR run with piped stdout still gets the full strip.)
if {!$force && !$stdout_is_tty} {
chan push stdout ::punkboot::ansistrip::transchan
chan push stderr ::punkboot::ansistrip::transchan
set ::punkboot::ansistrip_pushed 1
} else {
set ::punkboot::ansistrip_pushed 0
}
#punk-exe-hosted runs arrive with punk::ansi pre-loaded and a warm sgr cache
#generated under the kit's own colour state - honour the module's contract.
if {[package provide punk::ansi] ne ""} {
catch {punk::ansi::sgr_cache -action clear}
}
if {[info exists ::env(PUNKBOOT_COLOUR_DEBUG)]} {
puts stderr "punkboot colour policy: mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
}
}}
# Colour-policy gate for raw-SGR emission sites that can run before
# define_global_ansi / punk::ansi are available (early package-load
# diagnostics and kit-build warnings). Returns the SGR sequence, or an empty
# string when colour is disabled. Empty/omitted params give the reset.
proc ::punkboot::sgr {{sgrparams ""}} {
if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
return ""
}
return "\x1b\[${sgrparams}m"
} }
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
@ -861,11 +993,11 @@ proc ::punkboot::check_package_availability {args} {
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "[punkboot::sgr 32] $pkgname versions: $versions error: $result[punkboot::sgr]"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " [punkboot::sgr 93]$result[punkboot::sgr]"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
@ -1353,13 +1485,25 @@ proc ::punkboot::define_global_ansi {} {
namespace eval ::punkboot { namespace eval ::punkboot {
namespace import ::punk::ansi::a+ ::punk::ansi::a namespace import ::punk::ansi::a+ ::punk::ansi::a
} }
set A(RST) \x1b\[m if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
#G-113: the A() palette is colour-only highlighting - when colour is
#disabled every entry (including RST) is empty rather than relying on
#a+/a colour stripping (a bare reset would survive as a non-colour code).
set A(RST) ""
set A(HIGHLIGHT) ""
set A(BWHITE) ""
set A(OK) ""
set A(BAD) ""
set A(ERR) ""
} else {
set A(RST) [a]
set A(HIGHLIGHT) [a+ brightyellow] set A(HIGHLIGHT) [a+ brightyellow]
set A(BWHITE) [a+ brightwhite] set A(BWHITE) [a+ brightwhite]
set A(OK) [a+ web-lawngreen] ;#brightgreen set A(OK) [a+ web-lawngreen] ;#brightgreen
set A(BAD) [a+ web-orange] set A(BAD) [a+ web-orange]
set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens
} }
}
} }
proc ::punkboot::punkboot_gethelp {args} { proc ::punkboot::punkboot_gethelp {args} {
#we have currently restricted our package paths to those from 'bootsupport' #we have currently restricted our package paths to those from 'bootsupport'
@ -2277,6 +2421,9 @@ if {$::punkboot::command in {project packages modules libs vfs vfslibs bin boots
if {$::punkboot::command eq "check"} { if {$::punkboot::command eq "check"} {
set sep [string repeat - 75] set sep [string repeat - 75]
puts stdout $sep puts stdout $sep
puts stdout "colour policy (G-113): mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
puts stdout " (precedence: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout tty probe; piped/8.6-unknown output is fully ESC-stripped unless forced)"
puts stdout $sep
puts stdout "module/library checks - paths from bootsupport only" puts stdout "module/library checks - paths from bootsupport only"
puts stdout $sep puts stdout $sep
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
@ -2624,6 +2771,13 @@ if {$::punkboot::command eq "workflow"} {
} }
if {$::punkboot::command eq "shell"} { if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).
if {[info exists ::punkboot::ansistrip_pushed] && $::punkboot::ansistrip_pushed} {
catch {chan pop stdout}
catch {chan pop stderr}
set ::punkboot::ansistrip_pushed 0
}
package require struct::list package require struct::list
package require punk package require punk
package require punk::repl package require punk::repl
@ -4008,7 +4162,7 @@ proc merge_over {sourcedir targetdir {depth 0}} {
set target [file join $targetdir $relpath] set target [file join $targetdir $relpath]
if {[file type $actualsource] eq "file"} { if {[file type $actualsource] eq "file"} {
#fauxlink linktarget (source data) is a file #fauxlink linktarget (source data) is a file
puts -nonewline stdout "\x1b\[32m<fxlnk.targetfor.${name}>\x1b\[m" puts -nonewline stdout "[punkboot::sgr 32]<fxlnk.targetfor.${name}>[punkboot::sgr]"
puts "file copy -force $actualsource $target" puts "file copy -force $actualsource $target"
file copy -force $actualsource $target file copy -force $actualsource $target
} else { } else {
@ -4383,7 +4537,7 @@ foreach vfstail $vfs_tails {
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached? #runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ?? #If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached![punkboot::sgr]"
file copy -force $building_runtime $raw_runtime file copy -force $building_runtime $raw_runtime
} }
} else { } else {
@ -4494,7 +4648,7 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} { if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform? #TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING: No extraction done from runtime $runtime_fullname[punkboot::sgr]"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!" puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
file mkdir $targetvfs file mkdir $targetvfs

168
src/modules/punk/mix/#modpod-templates-999999.0a1.0/templates/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -2,8 +2,140 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} { # ---------------------------------------------------------------------------
# Terminal-aware colour/ANSI policy (G-113) - evaluated before any output.
# Precedence:
# 1. NO_COLOR set (any value) -> colour OFF (always wins; punk::ansi
# NO_COLOR semantics: colour codes stripped, non-colour effects like bold
# may remain when output goes to a terminal)
# 2. PUNK_FORCE_COLOR / FORCE_COLOR set -> ANSI ON even when piped
# (a value of 0/false/no/off is treated as not-set, per ecosystem convention)
# 3. stdout tty probe -> colour ON only when stdout is a
# terminal channel (-winsize option present - Tcl 8.7+/9 terminal channels
# only, mirroring the stdin_is_interactive -inputmode precedent)
# Piped/redirected stdout (probe fails, no force) additionally pushes a strip
# transform on stdout+stderr so the output is fully ESC-free - machine
# consumers get plain text no matter which emitter produced it (punk::ansi's
# colour_disabled contract deliberately retains non-colour SGR effects, and
# module-side emitters like punkcheck summaries follow that contract - the
# transform is the single choke point that guarantees zero ESC bytes).
# Tcl 8.6 has no boot-phase tty probe (no -winsize/-inputmode anywhere): the
# fail direction is plain/OFF (safe for piped/captured agent runs - unlike
# stdin's assume-interactive; use the force env vars for 8.6 interactive colour).
# stderr follows the same single stdout-keyed decision.
# The colour switch is ::punk::console::colour_disabled; when punk::ansi is
# already loaded (punk-exe-hosted runs) its sgr cache is cleared per that
# module's contract ("whatever function disables or re-enables colour should
# have made a call to punk::ansi::sgr_cache -action clear").
# ::punkboot::colour_mode records the decision: one of
# tty | forced | nocolor | piped-plain | tcl86-plain
# ---------------------------------------------------------------------------
namespace eval ::punkboot {
variable colour_mode ""
}
namespace eval ::punkboot::ansistrip {
#Write-side channel transform stripping all ANSI escape sequences (CSI incl.
#SGR, OSC with BEL/ST terminators, and two-char ESC sequences). Handles
#sequences split across write chunks via a per-channel carry buffer; an
#unterminated sequence held at flush/finalize time is dropped (it is an
#incomplete ANSI sequence - dropping it IS stripping it).
variable carry [dict create]
variable re_complete {\x1b(?:\[[0-9;:<=>?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\^_])}
proc strip {chanid data} {
variable carry
variable re_complete
if {[dict exists $carry $chanid]} {
set data "[dict get $carry $chanid]$data"
dict unset carry $chanid
}
#hold back a trailing incomplete escape sequence (conservative: from the
#last ESC onward, if it is not a complete sequence it may be a prefix of
#one still being written)
set idx [string last \x1b $data]
if {$idx >= 0} {
set tail [string range $data $idx end]
if {![regexp -- "^${re_complete}" $tail]} {
dict set carry $chanid $tail
set data [string range $data 0 $idx-1]
}
}
regsub -all -- $re_complete $data {} data
return $data
}
proc transchan {op chanid args} {
variable carry
switch -- $op {
initialize {
return [list initialize finalize write flush clear]
}
write {
return [strip $chanid [lindex $args 0]]
}
flush {
return ""
}
clear -
finalize {
dict unset carry $chanid
return
}
}
}
}
apply {{} {
set force 0
foreach fvar {PUNK_FORCE_COLOR FORCE_COLOR} {
if {[info exists ::env($fvar)] && $::env($fvar) ni {0 false FALSE no NO off OFF}} {
set force 1
break
}
}
set stdout_is_tty [expr {![catch {chan configure stdout -winsize}]}]
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1} namespace eval ::punk::console {variable colour_disabled 1}
set ::punkboot::colour_mode nocolor
} elseif {$force} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode forced
} elseif {$stdout_is_tty} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode tty
} else {
namespace eval ::punk::console {variable colour_disabled 1}
if {[package vsatisfies [package provide Tcl] 8.7-]} {
set ::punkboot::colour_mode piped-plain
} else {
set ::punkboot::colour_mode tcl86-plain
}
}
#zero-ESC guarantee for piped/unknown output unless the caller forced ANSI on.
#(NO_COLOR keeps punk::ansi's documented colour-only-stripping semantics on a
#terminal; a NO_COLOR run with piped stdout still gets the full strip.)
if {!$force && !$stdout_is_tty} {
chan push stdout ::punkboot::ansistrip::transchan
chan push stderr ::punkboot::ansistrip::transchan
set ::punkboot::ansistrip_pushed 1
} else {
set ::punkboot::ansistrip_pushed 0
}
#punk-exe-hosted runs arrive with punk::ansi pre-loaded and a warm sgr cache
#generated under the kit's own colour state - honour the module's contract.
if {[package provide punk::ansi] ne ""} {
catch {punk::ansi::sgr_cache -action clear}
}
if {[info exists ::env(PUNKBOOT_COLOUR_DEBUG)]} {
puts stderr "punkboot colour policy: mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
}
}}
# Colour-policy gate for raw-SGR emission sites that can run before
# define_global_ansi / punk::ansi are available (early package-load
# diagnostics and kit-build warnings). Returns the SGR sequence, or an empty
# string when colour is disabled. Empty/omitted params give the reset.
proc ::punkboot::sgr {{sgrparams ""}} {
if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
return ""
}
return "\x1b\[${sgrparams}m"
} }
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
@ -861,11 +993,11 @@ proc ::punkboot::check_package_availability {args} {
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "[punkboot::sgr 32] $pkgname versions: $versions error: $result[punkboot::sgr]"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " [punkboot::sgr 93]$result[punkboot::sgr]"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
@ -1353,13 +1485,25 @@ proc ::punkboot::define_global_ansi {} {
namespace eval ::punkboot { namespace eval ::punkboot {
namespace import ::punk::ansi::a+ ::punk::ansi::a namespace import ::punk::ansi::a+ ::punk::ansi::a
} }
set A(RST) \x1b\[m if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
#G-113: the A() palette is colour-only highlighting - when colour is
#disabled every entry (including RST) is empty rather than relying on
#a+/a colour stripping (a bare reset would survive as a non-colour code).
set A(RST) ""
set A(HIGHLIGHT) ""
set A(BWHITE) ""
set A(OK) ""
set A(BAD) ""
set A(ERR) ""
} else {
set A(RST) [a]
set A(HIGHLIGHT) [a+ brightyellow] set A(HIGHLIGHT) [a+ brightyellow]
set A(BWHITE) [a+ brightwhite] set A(BWHITE) [a+ brightwhite]
set A(OK) [a+ web-lawngreen] ;#brightgreen set A(OK) [a+ web-lawngreen] ;#brightgreen
set A(BAD) [a+ web-orange] set A(BAD) [a+ web-orange]
set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens
} }
}
} }
proc ::punkboot::punkboot_gethelp {args} { proc ::punkboot::punkboot_gethelp {args} {
#we have currently restricted our package paths to those from 'bootsupport' #we have currently restricted our package paths to those from 'bootsupport'
@ -2277,6 +2421,9 @@ if {$::punkboot::command in {project packages modules libs vfs vfslibs bin boots
if {$::punkboot::command eq "check"} { if {$::punkboot::command eq "check"} {
set sep [string repeat - 75] set sep [string repeat - 75]
puts stdout $sep puts stdout $sep
puts stdout "colour policy (G-113): mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
puts stdout " (precedence: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout tty probe; piped/8.6-unknown output is fully ESC-stripped unless forced)"
puts stdout $sep
puts stdout "module/library checks - paths from bootsupport only" puts stdout "module/library checks - paths from bootsupport only"
puts stdout $sep puts stdout $sep
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
@ -2624,6 +2771,13 @@ if {$::punkboot::command eq "workflow"} {
} }
if {$::punkboot::command eq "shell"} { if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).
if {[info exists ::punkboot::ansistrip_pushed] && $::punkboot::ansistrip_pushed} {
catch {chan pop stdout}
catch {chan pop stderr}
set ::punkboot::ansistrip_pushed 0
}
package require struct::list package require struct::list
package require punk package require punk
package require punk::repl package require punk::repl
@ -4008,7 +4162,7 @@ proc merge_over {sourcedir targetdir {depth 0}} {
set target [file join $targetdir $relpath] set target [file join $targetdir $relpath]
if {[file type $actualsource] eq "file"} { if {[file type $actualsource] eq "file"} {
#fauxlink linktarget (source data) is a file #fauxlink linktarget (source data) is a file
puts -nonewline stdout "\x1b\[32m<fxlnk.targetfor.${name}>\x1b\[m" puts -nonewline stdout "[punkboot::sgr 32]<fxlnk.targetfor.${name}>[punkboot::sgr]"
puts "file copy -force $actualsource $target" puts "file copy -force $actualsource $target"
file copy -force $actualsource $target file copy -force $actualsource $target
} else { } else {
@ -4383,7 +4537,7 @@ foreach vfstail $vfs_tails {
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached? #runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ?? #If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached![punkboot::sgr]"
file copy -force $building_runtime $raw_runtime file copy -force $building_runtime $raw_runtime
} }
} else { } else {
@ -4494,7 +4648,7 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} { if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform? #TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING: No extraction done from runtime $runtime_fullname[punkboot::sgr]"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!" puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
file mkdir $targetvfs file mkdir $targetvfs

168
src/project_layouts/vendor/punk/basic/src/make.tcl vendored

@ -2,8 +2,140 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} { # ---------------------------------------------------------------------------
# Terminal-aware colour/ANSI policy (G-113) - evaluated before any output.
# Precedence:
# 1. NO_COLOR set (any value) -> colour OFF (always wins; punk::ansi
# NO_COLOR semantics: colour codes stripped, non-colour effects like bold
# may remain when output goes to a terminal)
# 2. PUNK_FORCE_COLOR / FORCE_COLOR set -> ANSI ON even when piped
# (a value of 0/false/no/off is treated as not-set, per ecosystem convention)
# 3. stdout tty probe -> colour ON only when stdout is a
# terminal channel (-winsize option present - Tcl 8.7+/9 terminal channels
# only, mirroring the stdin_is_interactive -inputmode precedent)
# Piped/redirected stdout (probe fails, no force) additionally pushes a strip
# transform on stdout+stderr so the output is fully ESC-free - machine
# consumers get plain text no matter which emitter produced it (punk::ansi's
# colour_disabled contract deliberately retains non-colour SGR effects, and
# module-side emitters like punkcheck summaries follow that contract - the
# transform is the single choke point that guarantees zero ESC bytes).
# Tcl 8.6 has no boot-phase tty probe (no -winsize/-inputmode anywhere): the
# fail direction is plain/OFF (safe for piped/captured agent runs - unlike
# stdin's assume-interactive; use the force env vars for 8.6 interactive colour).
# stderr follows the same single stdout-keyed decision.
# The colour switch is ::punk::console::colour_disabled; when punk::ansi is
# already loaded (punk-exe-hosted runs) its sgr cache is cleared per that
# module's contract ("whatever function disables or re-enables colour should
# have made a call to punk::ansi::sgr_cache -action clear").
# ::punkboot::colour_mode records the decision: one of
# tty | forced | nocolor | piped-plain | tcl86-plain
# ---------------------------------------------------------------------------
namespace eval ::punkboot {
variable colour_mode ""
}
namespace eval ::punkboot::ansistrip {
#Write-side channel transform stripping all ANSI escape sequences (CSI incl.
#SGR, OSC with BEL/ST terminators, and two-char ESC sequences). Handles
#sequences split across write chunks via a per-channel carry buffer; an
#unterminated sequence held at flush/finalize time is dropped (it is an
#incomplete ANSI sequence - dropping it IS stripping it).
variable carry [dict create]
variable re_complete {\x1b(?:\[[0-9;:<=>?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\^_])}
proc strip {chanid data} {
variable carry
variable re_complete
if {[dict exists $carry $chanid]} {
set data "[dict get $carry $chanid]$data"
dict unset carry $chanid
}
#hold back a trailing incomplete escape sequence (conservative: from the
#last ESC onward, if it is not a complete sequence it may be a prefix of
#one still being written)
set idx [string last \x1b $data]
if {$idx >= 0} {
set tail [string range $data $idx end]
if {![regexp -- "^${re_complete}" $tail]} {
dict set carry $chanid $tail
set data [string range $data 0 $idx-1]
}
}
regsub -all -- $re_complete $data {} data
return $data
}
proc transchan {op chanid args} {
variable carry
switch -- $op {
initialize {
return [list initialize finalize write flush clear]
}
write {
return [strip $chanid [lindex $args 0]]
}
flush {
return ""
}
clear -
finalize {
dict unset carry $chanid
return
}
}
}
}
apply {{} {
set force 0
foreach fvar {PUNK_FORCE_COLOR FORCE_COLOR} {
if {[info exists ::env($fvar)] && $::env($fvar) ni {0 false FALSE no NO off OFF}} {
set force 1
break
}
}
set stdout_is_tty [expr {![catch {chan configure stdout -winsize}]}]
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1} namespace eval ::punk::console {variable colour_disabled 1}
set ::punkboot::colour_mode nocolor
} elseif {$force} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode forced
} elseif {$stdout_is_tty} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode tty
} else {
namespace eval ::punk::console {variable colour_disabled 1}
if {[package vsatisfies [package provide Tcl] 8.7-]} {
set ::punkboot::colour_mode piped-plain
} else {
set ::punkboot::colour_mode tcl86-plain
}
}
#zero-ESC guarantee for piped/unknown output unless the caller forced ANSI on.
#(NO_COLOR keeps punk::ansi's documented colour-only-stripping semantics on a
#terminal; a NO_COLOR run with piped stdout still gets the full strip.)
if {!$force && !$stdout_is_tty} {
chan push stdout ::punkboot::ansistrip::transchan
chan push stderr ::punkboot::ansistrip::transchan
set ::punkboot::ansistrip_pushed 1
} else {
set ::punkboot::ansistrip_pushed 0
}
#punk-exe-hosted runs arrive with punk::ansi pre-loaded and a warm sgr cache
#generated under the kit's own colour state - honour the module's contract.
if {[package provide punk::ansi] ne ""} {
catch {punk::ansi::sgr_cache -action clear}
}
if {[info exists ::env(PUNKBOOT_COLOUR_DEBUG)]} {
puts stderr "punkboot colour policy: mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
}
}}
# Colour-policy gate for raw-SGR emission sites that can run before
# define_global_ansi / punk::ansi are available (early package-load
# diagnostics and kit-build warnings). Returns the SGR sequence, or an empty
# string when colour is disabled. Empty/omitted params give the reset.
proc ::punkboot::sgr {{sgrparams ""}} {
if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
return ""
}
return "\x1b\[${sgrparams}m"
} }
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
@ -861,11 +993,11 @@ proc ::punkboot::check_package_availability {args} {
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "[punkboot::sgr 32] $pkgname versions: $versions error: $result[punkboot::sgr]"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " [punkboot::sgr 93]$result[punkboot::sgr]"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
@ -1353,13 +1485,25 @@ proc ::punkboot::define_global_ansi {} {
namespace eval ::punkboot { namespace eval ::punkboot {
namespace import ::punk::ansi::a+ ::punk::ansi::a namespace import ::punk::ansi::a+ ::punk::ansi::a
} }
set A(RST) \x1b\[m if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
#G-113: the A() palette is colour-only highlighting - when colour is
#disabled every entry (including RST) is empty rather than relying on
#a+/a colour stripping (a bare reset would survive as a non-colour code).
set A(RST) ""
set A(HIGHLIGHT) ""
set A(BWHITE) ""
set A(OK) ""
set A(BAD) ""
set A(ERR) ""
} else {
set A(RST) [a]
set A(HIGHLIGHT) [a+ brightyellow] set A(HIGHLIGHT) [a+ brightyellow]
set A(BWHITE) [a+ brightwhite] set A(BWHITE) [a+ brightwhite]
set A(OK) [a+ web-lawngreen] ;#brightgreen set A(OK) [a+ web-lawngreen] ;#brightgreen
set A(BAD) [a+ web-orange] set A(BAD) [a+ web-orange]
set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens
} }
}
} }
proc ::punkboot::punkboot_gethelp {args} { proc ::punkboot::punkboot_gethelp {args} {
#we have currently restricted our package paths to those from 'bootsupport' #we have currently restricted our package paths to those from 'bootsupport'
@ -2277,6 +2421,9 @@ if {$::punkboot::command in {project packages modules libs vfs vfslibs bin boots
if {$::punkboot::command eq "check"} { if {$::punkboot::command eq "check"} {
set sep [string repeat - 75] set sep [string repeat - 75]
puts stdout $sep puts stdout $sep
puts stdout "colour policy (G-113): mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
puts stdout " (precedence: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout tty probe; piped/8.6-unknown output is fully ESC-stripped unless forced)"
puts stdout $sep
puts stdout "module/library checks - paths from bootsupport only" puts stdout "module/library checks - paths from bootsupport only"
puts stdout $sep puts stdout $sep
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
@ -2624,6 +2771,13 @@ if {$::punkboot::command eq "workflow"} {
} }
if {$::punkboot::command eq "shell"} { if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).
if {[info exists ::punkboot::ansistrip_pushed] && $::punkboot::ansistrip_pushed} {
catch {chan pop stdout}
catch {chan pop stderr}
set ::punkboot::ansistrip_pushed 0
}
package require struct::list package require struct::list
package require punk package require punk
package require punk::repl package require punk::repl
@ -4008,7 +4162,7 @@ proc merge_over {sourcedir targetdir {depth 0}} {
set target [file join $targetdir $relpath] set target [file join $targetdir $relpath]
if {[file type $actualsource] eq "file"} { if {[file type $actualsource] eq "file"} {
#fauxlink linktarget (source data) is a file #fauxlink linktarget (source data) is a file
puts -nonewline stdout "\x1b\[32m<fxlnk.targetfor.${name}>\x1b\[m" puts -nonewline stdout "[punkboot::sgr 32]<fxlnk.targetfor.${name}>[punkboot::sgr]"
puts "file copy -force $actualsource $target" puts "file copy -force $actualsource $target"
file copy -force $actualsource $target file copy -force $actualsource $target
} else { } else {
@ -4383,7 +4537,7 @@ foreach vfstail $vfs_tails {
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached? #runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ?? #If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached![punkboot::sgr]"
file copy -force $building_runtime $raw_runtime file copy -force $building_runtime $raw_runtime
} }
} else { } else {
@ -4494,7 +4648,7 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} { if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform? #TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING: No extraction done from runtime $runtime_fullname[punkboot::sgr]"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!" puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
file mkdir $targetvfs file mkdir $targetvfs

168
src/project_layouts/vendor/punk/project-0.1/src/make.tcl vendored

@ -2,8 +2,140 @@
# #
# punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src. # punkboot - make any tclkits and modules in <projectdir>/src folders and place them and associated data files/scripts in the parent folder of src.
#e.g in 'bin' and 'modules' folders at same level as 'src' folder. #e.g in 'bin' and 'modules' folders at same level as 'src' folder.
if {[info exists ::env(NO_COLOR)]} { # ---------------------------------------------------------------------------
# Terminal-aware colour/ANSI policy (G-113) - evaluated before any output.
# Precedence:
# 1. NO_COLOR set (any value) -> colour OFF (always wins; punk::ansi
# NO_COLOR semantics: colour codes stripped, non-colour effects like bold
# may remain when output goes to a terminal)
# 2. PUNK_FORCE_COLOR / FORCE_COLOR set -> ANSI ON even when piped
# (a value of 0/false/no/off is treated as not-set, per ecosystem convention)
# 3. stdout tty probe -> colour ON only when stdout is a
# terminal channel (-winsize option present - Tcl 8.7+/9 terminal channels
# only, mirroring the stdin_is_interactive -inputmode precedent)
# Piped/redirected stdout (probe fails, no force) additionally pushes a strip
# transform on stdout+stderr so the output is fully ESC-free - machine
# consumers get plain text no matter which emitter produced it (punk::ansi's
# colour_disabled contract deliberately retains non-colour SGR effects, and
# module-side emitters like punkcheck summaries follow that contract - the
# transform is the single choke point that guarantees zero ESC bytes).
# Tcl 8.6 has no boot-phase tty probe (no -winsize/-inputmode anywhere): the
# fail direction is plain/OFF (safe for piped/captured agent runs - unlike
# stdin's assume-interactive; use the force env vars for 8.6 interactive colour).
# stderr follows the same single stdout-keyed decision.
# The colour switch is ::punk::console::colour_disabled; when punk::ansi is
# already loaded (punk-exe-hosted runs) its sgr cache is cleared per that
# module's contract ("whatever function disables or re-enables colour should
# have made a call to punk::ansi::sgr_cache -action clear").
# ::punkboot::colour_mode records the decision: one of
# tty | forced | nocolor | piped-plain | tcl86-plain
# ---------------------------------------------------------------------------
namespace eval ::punkboot {
variable colour_mode ""
}
namespace eval ::punkboot::ansistrip {
#Write-side channel transform stripping all ANSI escape sequences (CSI incl.
#SGR, OSC with BEL/ST terminators, and two-char ESC sequences). Handles
#sequences split across write chunks via a per-channel carry buffer; an
#unterminated sequence held at flush/finalize time is dropped (it is an
#incomplete ANSI sequence - dropping it IS stripping it).
variable carry [dict create]
variable re_complete {\x1b(?:\[[0-9;:<=>?]*[ -/]*[@-~]|\][^\x07\x1b]*(?:\x07|\x1b\\)|[@-Z\\^_])}
proc strip {chanid data} {
variable carry
variable re_complete
if {[dict exists $carry $chanid]} {
set data "[dict get $carry $chanid]$data"
dict unset carry $chanid
}
#hold back a trailing incomplete escape sequence (conservative: from the
#last ESC onward, if it is not a complete sequence it may be a prefix of
#one still being written)
set idx [string last \x1b $data]
if {$idx >= 0} {
set tail [string range $data $idx end]
if {![regexp -- "^${re_complete}" $tail]} {
dict set carry $chanid $tail
set data [string range $data 0 $idx-1]
}
}
regsub -all -- $re_complete $data {} data
return $data
}
proc transchan {op chanid args} {
variable carry
switch -- $op {
initialize {
return [list initialize finalize write flush clear]
}
write {
return [strip $chanid [lindex $args 0]]
}
flush {
return ""
}
clear -
finalize {
dict unset carry $chanid
return
}
}
}
}
apply {{} {
set force 0
foreach fvar {PUNK_FORCE_COLOR FORCE_COLOR} {
if {[info exists ::env($fvar)] && $::env($fvar) ni {0 false FALSE no NO off OFF}} {
set force 1
break
}
}
set stdout_is_tty [expr {![catch {chan configure stdout -winsize}]}]
if {[info exists ::env(NO_COLOR)]} {
namespace eval ::punk::console {variable colour_disabled 1} namespace eval ::punk::console {variable colour_disabled 1}
set ::punkboot::colour_mode nocolor
} elseif {$force} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode forced
} elseif {$stdout_is_tty} {
namespace eval ::punk::console {variable colour_disabled 0}
set ::punkboot::colour_mode tty
} else {
namespace eval ::punk::console {variable colour_disabled 1}
if {[package vsatisfies [package provide Tcl] 8.7-]} {
set ::punkboot::colour_mode piped-plain
} else {
set ::punkboot::colour_mode tcl86-plain
}
}
#zero-ESC guarantee for piped/unknown output unless the caller forced ANSI on.
#(NO_COLOR keeps punk::ansi's documented colour-only-stripping semantics on a
#terminal; a NO_COLOR run with piped stdout still gets the full strip.)
if {!$force && !$stdout_is_tty} {
chan push stdout ::punkboot::ansistrip::transchan
chan push stderr ::punkboot::ansistrip::transchan
set ::punkboot::ansistrip_pushed 1
} else {
set ::punkboot::ansistrip_pushed 0
}
#punk-exe-hosted runs arrive with punk::ansi pre-loaded and a warm sgr cache
#generated under the kit's own colour state - honour the module's contract.
if {[package provide punk::ansi] ne ""} {
catch {punk::ansi::sgr_cache -action clear}
}
if {[info exists ::env(PUNKBOOT_COLOUR_DEBUG)]} {
puts stderr "punkboot colour policy: mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
}
}}
# Colour-policy gate for raw-SGR emission sites that can run before
# define_global_ansi / punk::ansi are available (early package-load
# diagnostics and kit-build warnings). Returns the SGR sequence, or an empty
# string when colour is disabled. Empty/omitted params give the reset.
proc ::punkboot::sgr {{sgrparams ""}} {
if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
return ""
}
return "\x1b\[${sgrparams}m"
} }
set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###" set hashline "# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ###"
puts $hashline puts $hashline
@ -861,11 +993,11 @@ proc ::punkboot::check_package_availability {args} {
#package missing - or exists - but failing to initialise #package missing - or exists - but failing to initialise
if {!$::opt_quiet} { if {!$::opt_quiet} {
set parent_path [lrange $::test::pkg_stack 0 end-1] set parent_path [lrange $::test::pkg_stack 0 end-1]
puts stderr "\x1b\[32m $pkgname versions: $versions error: $result\x1b\[m" puts stderr "[punkboot::sgr 32] $pkgname versions: $versions error: $result[punkboot::sgr]"
set parent_path [join $parent_path " -> "] set parent_path [join $parent_path " -> "]
puts stderr "pkg requirements: $parent_path" puts stderr "pkg requirements: $parent_path"
puts stderr "error during : '$args'" puts stderr "error during : '$args'"
puts stderr " \x1b\[93m$result\x1b\[m" puts stderr " [punkboot::sgr 93]$result[punkboot::sgr]"
} }
#the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW #the failed package may still exist - so we could check 'package files' and 'package ifneeded' here too - REVIEW
@ -1353,13 +1485,25 @@ proc ::punkboot::define_global_ansi {} {
namespace eval ::punkboot { namespace eval ::punkboot {
namespace import ::punk::ansi::a+ ::punk::ansi::a namespace import ::punk::ansi::a+ ::punk::ansi::a
} }
set A(RST) \x1b\[m if {[info exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
#G-113: the A() palette is colour-only highlighting - when colour is
#disabled every entry (including RST) is empty rather than relying on
#a+/a colour stripping (a bare reset would survive as a non-colour code).
set A(RST) ""
set A(HIGHLIGHT) ""
set A(BWHITE) ""
set A(OK) ""
set A(BAD) ""
set A(ERR) ""
} else {
set A(RST) [a]
set A(HIGHLIGHT) [a+ brightyellow] set A(HIGHLIGHT) [a+ brightyellow]
set A(BWHITE) [a+ brightwhite] set A(BWHITE) [a+ brightwhite]
set A(OK) [a+ web-lawngreen] ;#brightgreen set A(OK) [a+ web-lawngreen] ;#brightgreen
set A(BAD) [a+ web-orange] set A(BAD) [a+ web-orange]
set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens set A(ERR) [a+ web-indianred] ;#easier on the eyes than standard red on some screens
} }
}
} }
proc ::punkboot::punkboot_gethelp {args} { proc ::punkboot::punkboot_gethelp {args} {
#we have currently restricted our package paths to those from 'bootsupport' #we have currently restricted our package paths to those from 'bootsupport'
@ -2277,6 +2421,9 @@ if {$::punkboot::command in {project packages modules libs vfs vfslibs bin boots
if {$::punkboot::command eq "check"} { if {$::punkboot::command eq "check"} {
set sep [string repeat - 75] set sep [string repeat - 75]
puts stdout $sep puts stdout $sep
puts stdout "colour policy (G-113): mode=$::punkboot::colour_mode colour_disabled=$::punk::console::colour_disabled ansistrip=$::punkboot::ansistrip_pushed"
puts stdout " (precedence: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout tty probe; piped/8.6-unknown output is fully ESC-stripped unless forced)"
puts stdout $sep
puts stdout "module/library checks - paths from bootsupport only" puts stdout "module/library checks - paths from bootsupport only"
puts stdout $sep puts stdout $sep
puts stdout "- tcl::tm::list" puts stdout "- tcl::tm::list"
@ -2624,6 +2771,13 @@ if {$::punkboot::command eq "workflow"} {
} }
if {$::punkboot::command eq "shell"} { if {$::punkboot::command eq "shell"} {
#G-113: the ansistrip transform serves make.tcl's own output only - the repl's
#colour behaviour is the shell's own concern (colour on/off + NO_COLOR there).
if {[info exists ::punkboot::ansistrip_pushed] && $::punkboot::ansistrip_pushed} {
catch {chan pop stdout}
catch {chan pop stderr}
set ::punkboot::ansistrip_pushed 0
}
package require struct::list package require struct::list
package require punk package require punk
package require punk::repl package require punk::repl
@ -4008,7 +4162,7 @@ proc merge_over {sourcedir targetdir {depth 0}} {
set target [file join $targetdir $relpath] set target [file join $targetdir $relpath]
if {[file type $actualsource] eq "file"} { if {[file type $actualsource] eq "file"} {
#fauxlink linktarget (source data) is a file #fauxlink linktarget (source data) is a file
puts -nonewline stdout "\x1b\[32m<fxlnk.targetfor.${name}>\x1b\[m" puts -nonewline stdout "[punkboot::sgr 32]<fxlnk.targetfor.${name}>[punkboot::sgr]"
puts "file copy -force $actualsource $target" puts "file copy -force $actualsource $target"
file copy -force $actualsource $target file copy -force $actualsource $target
} else { } else {
@ -4383,7 +4537,7 @@ foreach vfstail $vfs_tails {
#the input building_runtime wasn't mountable as a zip - so presumably a plain executable #the input building_runtime wasn't mountable as a zip - so presumably a plain executable
#runtime executable possibly with kit/cookfs etc attached? #runtime executable possibly with kit/cookfs etc attached?
#If not - init.tcl probably won't be found? should we even proceed ?? #If not - init.tcl probably won't be found? should we even proceed ??
puts stderr "\x1b\[31mWARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached!\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING the runtime was not mountable as a zip - which means tcl_library was not extracted - the executable may not work with zipfs attached![punkboot::sgr]"
file copy -force $building_runtime $raw_runtime file copy -force $building_runtime $raw_runtime
} }
} else { } else {
@ -4494,7 +4648,7 @@ foreach vfstail $vfs_tails {
if {!$extraction_done} { if {!$extraction_done} {
#TODO: if not extracted - use a default tcl_library for patchlevel and platform? #TODO: if not extracted - use a default tcl_library for patchlevel and platform?
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
puts stderr "\x1b\31mWARNING: No extraction done from runtime $runtime_fullname\x1b\[m" puts stderr "[punkboot::sgr 31]WARNING: No extraction done from runtime $runtime_fullname[punkboot::sgr]"
puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!" puts stderr "If no init.tcl provided in the vfs at the proper location (containing init.tcl) - the resulting kit will probably not initialise properly!"
puts stderr "--------------------------------------------" puts stderr "--------------------------------------------"
file mkdir $targetvfs file mkdir $targetvfs

150
src/tests/shell/testsuites/punkexe/maketclcolour.test

@ -0,0 +1,150 @@
package require tcltest
#Piped-output colour characterization for make.tcl's terminal-aware colour policy
#(goal G-113). Runs the WORKING TREE's src/make.tcl under the built punk executable's
#'script' subcommand with output captured through a pipe, and pins:
# - piped output with NO_COLOR unset is fully ESC-free (the ansistrip transform)
# - the check subcommand reports the policy decision (mode=piped-plain, ansistrip=1)
# - FORCE_COLOR re-enables ANSI/colour on piped output (mode=forced)
# - NO_COLOR=1 piped output remains fully ESC-free
#The interactive (tty) side cannot be pinned from a piped harness - evidence for it
#is recorded in the G-113 goal file (hidden-console + WSL pty runs, mode=tty).
#
#Target executable resolved from env(PUNK_SHELL_TEST_EXE), else <projectroot>/bin/punk902z.exe
#then <projectroot>/bin/punkshell902. Skipped (constraint punkexeavailable) if none found.
namespace eval ::testspace {
namespace import ::tcltest::*
variable testdir [file dirname [file normalize [info script]]]
#<projectroot>/src/tests/shell/testsuites/punkexe -> 5 levels up to <projectroot>
variable projectroot [file normalize [file join $testdir .. .. .. .. ..]]
variable maketcl [file join $projectroot src make.tcl]
variable punkexe ""
if {[info exists ::env(PUNK_SHELL_TEST_EXE)] && $::env(PUNK_SHELL_TEST_EXE) ne ""} {
set punkexe [file normalize $::env(PUNK_SHELL_TEST_EXE)]
} else {
foreach candidate [list [file join $projectroot bin punk902z.exe] [file join $projectroot bin punkshell902]] {
if {[file exists $candidate]} {
set punkexe $candidate
break
}
}
}
testConstraint punkexeavailable [expr {$punkexe ne "" && [file exists $punkexe]}]
variable maketcl_run_timeout_ms 60000
variable runstate
array set runstate {}
proc maketcl_run_read {chan} {
variable runstate
append runstate(output) [read $chan]
if {[chan eof $chan]} {
chan event $chan readable {}
set runstate(done) eof
}
}
#Run <punkexe> script src/make.tcl <subcommand...> with a controlled colour env.
#envoverrides is a dict: name value pairs applied to ::env for the child (a value
#of "" means unset). Saved values are restored after the run. Returns dict:
#timedout 0|1, exitcode <int|"">, output <combined stdout+stderr>.
proc maketcl_run {cmdargs envoverrides} {
variable runstate
variable maketcl_run_timeout_ms
variable punkexe
variable maketcl
array unset runstate
set runstate(output) ""
set runstate(done) ""
set saved [dict create]
dict for {evar eval_} $envoverrides {
if {[info exists ::env($evar)]} {
dict set saved $evar [list 1 $::env($evar)]
} else {
dict set saved $evar [list 0 {}]
}
if {$eval_ eq ""} {
unset -nocomplain ::env($evar)
} else {
set ::env($evar) $eval_
}
}
try {
set chan [open |[list $punkexe script $maketcl {*}$cmdargs 2>@1] r+]
chan configure $chan -blocking 0 -translation binary
catch {chan close $chan write} ;#no stdin for the child - immediate EOF
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]]
chan event $chan readable [list [namespace current]::maketcl_run_read $chan]
while {$runstate(done) eq ""} {
vwait [namespace current]::runstate(done)
}
after cancel $timerid
set timedout [expr {$runstate(done) eq "timeout"}]
set exitcode ""
if {$timedout} {
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]}
catch {chan close $chan}
} else {
chan configure $chan -blocking 1
if {[catch {chan close $chan} errdata errdict]} {
set exitcode [lindex [dict get $errdict -errorcode] end]
} else {
set exitcode 0
}
}
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)]
} finally {
dict for {evar restore} $saved {
lassign $restore existed oldval
if {$existed} {
set ::env($evar) $oldval
} else {
unset -nocomplain ::env($evar)
}
}
}
}
proc esc_count {text} {
return [regexp -all {\x1b} $text]
}
#added 2026-07-25 (agent, G-113)
test maketcl_colour_piped_plain_zero_esc {piped make.tcl check with NO_COLOR unset: zero ESC bytes, policy line reports piped-plain with ansistrip} -constraints {punkexeavailable} -body {
set r [maketcl_run {check} {NO_COLOR "" FORCE_COLOR "" PUNK_FORCE_COLOR ""}]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count [dict get $r output]]
lappend result policyline [regexp {colour policy \(G-113\): mode=piped-plain colour_disabled=1 ansistrip=1} [dict get $r output]]
set result
} -result {timedout 0 exitcode 0 esc 0 policyline 1}
#added 2026-07-25 (agent, G-113)
test maketcl_colour_forced_piped_has_colour {FORCE_COLOR=1 re-enables ANSI colour on piped make.tcl help output} -constraints {punkexeavailable} -body {
set r [maketcl_run {help} {NO_COLOR "" FORCE_COLOR 1 PUNK_FORCE_COLOR ""}]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result has_esc [expr {[esc_count [dict get $r output]] > 0}]
#at least one SGR sequence carrying a colour parameter (30-49 / 90-107 fg-bg range or 38;/48; extended)
lappend result has_colour [regexp {\x1b\[[0-9;:]*(?:3[0-9]|4[0-9]|9[0-7]|10[0-7])[;m]} [dict get $r output]]
set result
} -result {timedout 0 exitcode 0 has_esc 1 has_colour 1}
#added 2026-07-25 (agent, G-113)
test maketcl_colour_nocolor_piped_zero_esc {NO_COLOR=1 piped make.tcl check output is fully ESC-free (strip transform applies)} -constraints {punkexeavailable} -body {
set r [maketcl_run {check} {NO_COLOR 1 FORCE_COLOR "" PUNK_FORCE_COLOR ""}]
set result [list]
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode]
lappend result esc [esc_count [dict get $r output]]
lappend result policyline [regexp {colour policy \(G-113\): mode=nocolor colour_disabled=1 ansistrip=1} [dict get $r output]]
set result
} -result {timedout 0 exitcode 0 esc 0 policyline 1}
cleanupTests
}
namespace delete ::testspace
Loading…
Cancel
Save