make.tcl modules + vfscommonupdate; old versions (args-0.11.2, tclcore-0.3.3)
removed by the update. All four kits (punkbi, punksys, punk91, punk902z)
rebuilt from the updated vfs and verified in kit mode: punk902z (9.0.2) and
punksys (8.6.13) both load args=0.12.0 tclcore=0.3.4, punk::args::formcheck
reports ::after as 6 forms / 15 pairs / 1 sanctioned structural overlap
(cancelid/cancelscript, zero unsanctioned) and ::lseq as the two unsanctioned
type_weakness findings ({range start_count} {range count}) on both runtimes.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
make.tcl modules + vfscommonupdate + project: all four kits rebuilt
(punkbi, punksys, punk91, punk902z). Kit-mode verification: punk902z
loads args=0.11.2 ns=0.5.0 tclcore=0.3.3 lib=0.4.3 punk=0.2.6, 'i join'
emits no bad-@dynamic warning; punksys (8.6) loads args=0.11.2 and the
after cancel-id discrimination resolves 'cancel someid' to the
cancelscript form (harvested id prefix working on 8.6).
A stale tcl_oauth2_library install leftover in the project's root lib/
tree (its src/vendorlib source was removed 2026-07-12; the libs install
step does not prune targets whose source vanished) was swept into the
vfs lib by vfscommonupdate and removed again before the kit build - it
is deliberately not part of the vendored set yet (G-065..G-068
first-test candidate). The root lib/ leftover itself was removed after
this commit was first authored; its rows remain in lib/.punkcheck.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Replaces args-0.5.0.tm with the built 0.6.0 (small restricted choice sets
display as literal alternates in synopses).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
An interactive shell orphaned by its hosting console dying (killed
conhost/terminal) previously spun ~2 cores forever: the Tcl 9 windows
console driver never delivers the dead-console state to the script as a
fileevent (tclWinConsole.c ConsoleEventProc only notifies on buffered
data) while its reader thread busy-loops on the persistent channel error,
so the script level is completely blind and nothing ever closes the
channel.
Fix: repl::console_watchdog - a self-rescheduling liveness poll (default
5s, repl::console_watchdog_ms; read-only GetConsoleMode probe via
chan configure -inputmode) armed by repl::start only for a tcl9 console
input channel serving the process-default console on windows. On probe
failure it closes the input channel (stopping the driver's reader
thread) and finishes the repl via the normal eof done-path; app-punkshell
then finds no console reopenable and exits cleanly. Piped, foreign-console
and tcl 8.6 inputs never arm it. repl::start's post-vwait reader
deregistration now tolerates a watchdog-closed channel.
Verification (2026-07-12): kill procedure post-fix exits the orphan in
1.5-2.0s with no traceback (src mode and rebuilt kits punkbi/punk91/
punk902z); 25s live-console soak shows no spurious trigger, CPU delta 0;
piped stdin unaffected; repl consolebackends 3/3 and punk::console suites
88 pass / 1 skip; root cause verified against plain tclsh 9.0.3.
Upstream ticket filed: https://core.tcl-lang.org/tcl/tktview/f10d91c2d3
Included:
- goals: G-039 flipped achieved 2026-07-12 and archived (index entry to
GOALS-archive.md, detail file to goals/archive/ with full root-cause,
repro and verification record); reference sweep adds the eof done-value
producer note to G-038
- src/modules/punk/AGENTS.md: watchdog contract documented
- project version 0.12.1 -> 0.12.2 + CHANGELOG entry
- vfs payload sync: _vfscommon.vfs punk/repl 0.4.0 -> 0.5.0
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Re-vendor the G-001 modules into bootsupport, the project-layout
bootsupport copies, and the runtime vfs: punk::repl 0.4.0 replaces 0.3.0
and opunk::console::tk 0.2.0 replaces 0.1.0.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Verified nothing depends on the old version or filename before renaming:
all 'package require punk::libunknown' sites are unversioned; punk_main.tcl
and punk::repl locate the module by globbing libunknown-*.tm and picking the
highest by vcompare (so the renamed dev copy now also outversions the stale
bootsupport and project.vfs 0.1 copies deterministically, instead of relying
on tm path order for a same-version tie); bootsupport's
include_modules.config lists it by name only.
The 0.2.0 number retroactively versions the two changes shipped against the
0.1 filename earlier today (register_all_tm deep discovery - a minor API
addition - and the source_pkgindex sourcing-scope fix). A version-history
comment block in the module header now substitutes for the buildversion.txt
changelog other modules carry.
src/modules/AGENTS.md: the punk::libunknown exception bullets now spell out
the manual-versioning mechanics for agents - same Patch/Minor/Major bump
rules as buildversion-tracked modules, executed as a file rename plus Meta
line, manpage_begin, provide-block and version-history updates, with a
check for exact-version/filename dependents before any first bump.
discovery.test/libsearch.test: the source-tree libunknown locator now picks
the highest libunknown-*.tm by vcompare instead of plain lsort (robust to
future version bumps). Verified live in punk902z src: package provide
reports 0.2.0, register_all_tm present, lib.search deep discovery intact.
Suites green under tclsh90 and tclsh87. Build artifacts synced (root
modules/ and _vfscommon.vfs carry only libunknown-0.2.0.tm).
punkshell 0.10.2. Stale 0.1 copies remain in src/bootsupport/modules
(pending 'make.tcl bootsupport') and src/vfs/project.vfs (pending layout
maintenance) - both now cleanly outversioned at runtime.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
zipfs_tclPkgUnknown previously declared 'global dir' and executed pkgIndex.tcl
scripts via 'namespace eval ::' - the pkgIndex $dir contract was met by
writing the global namespace's dir variable, silently overwriting any user
global named 'dir' whenever a package require fell through to the pkg unknown
handler (observed misdirecting a file write into the user's Tcl installation
during test development), and each index's stray unqualified variables (ver,
pkg, script, _CawtSubDirs, ...) leaked into ::.
Index scripts now execute in a source_pkgindex proc frame: $dir is a formal
parameter and auto_path/env are global links - exactly the environment stock
tclPkgUnknown documents (its indexes see dir as the handler's proc-local plus
'global auto_path env'), without stock's incidental exposure of all its other
proc locals. tcl::Pkg::source uplevels into the caller's frame, so the scheme
works unchanged for the 8.6 pre-tip459 fallback. The legacy non-epoch branch
already sourced in the handler frame and simply gains stock parity from the
'global dir' removal.
Reviewed against tcllib 2.0's vendored index behaviours (all compatible):
qualified '$dir ni $::auto_path'/'lappend ::auto_path' extension in the
top-level index, its own apply-scoped subindex sweep (the same isolation
idiom), vsatisfies guards with early return, 'package provide' during index
sourcing (try/pkgIndex.tcl on 9+), and critcl-generated ifneeded strings
(md5c, tcllibc) whose proc definitions run at require time, not source time.
One deliberate divergence from stock: each index file gets its own frame,
so unqualified variables no longer persist between index files within one
sweep (stock shares its handler frame across the sweep) - nothing in tcllib
relies on that.
discovery.test: the GAP pin flips to libunknown_pkgunknown_preserves_global_dir,
plus new pins for the index contract - $dir-based ifneeded/source works,
unqualified 'lappend auto_path' extension reaches the real ::auto_path, and
stray index-script sets don't leak to ::. Verified live in punk902z src:
::dir no longer set at startup, user dir global survives package activity,
tcllib md5/struct::set/json load through the new frame, lib.search deep
discovery unaffected. Remaining startup globals def/pkg/ver come from
punkshell's own boot code (punk_main.tcl foreach loops), not the handler.
punkshell 0.10.1. punk::libunknown remains manually versioned at 0.1;
vfs artifact synced, bootsupport copy untouched as before.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk::libunknown gains register_all_tm: walks every tcl:™️:list path
recursively and registers ifneeded scripts for .tm modules at every
namespace depth (the tm unknown handler only registers siblings at the
requested depth, leaving never-requested subfolders like test::* invisible
to 'package names'). First registration wins - parity with the handler and
the shadowing pins. Reuses/populates the per-epoch directory index cache
(zipfs whole-tree listing; per-dir globs for filesystem paths; #modpod-*,
#tarjar-* and _build skipped) and runs at most once per tm epoch per
interp - the guard is the interp-local tm_fullscan variable, deliberately
not in the shareable epoch dict since ifneeded registrations are
interp-local.
punk::mix::commandset::loadedlib 0.2.0: search runs register_all_tm plus
the auto_path pkgIndex sweep by default when punk::libunknown is active,
so 'dev lib.search test' now shows deep modules without -refresh.
-refresh is repurposed to mean a genuine filesystem re-scan: package epoch
incr (invalidating the scan caches) then rediscovery - picking up .tm
files added/removed on disk. Without punk::libunknown (or with an older
copy lacking register_all_tm) -refresh falls back to the previous
dummy-require deep walk. Dependency fixes: highlight ansi computed only
when -highlight 1 via qualified punk::ansi with inline require (previously
relied on the shell-global a+ alias unconditionally and errored in bare
interps), inline requires for punk::path (fallback walk) and textblock
(table output).
New test suites (green under tclsh90 and tclsh87):
- modules/punk/libunknown/testsuites/discovery/discovery.test: lazy-depth
sibling registration, register_all_tm all-depths/cached-per-epoch/
head-wins-parity, package epoch shape and trace-driven increments,
epoch-cache short-circuit + incr recipe, and a GAP pin of
zipfs_tclPkgUnknown clobbering a user global 'dir' variable (stock
tclPkgUnknown keeps dir proc-local; fix candidate).
- modules/punk/mix/testsuites/loadedlib/libsearch.test: search match
semantics, default deep discovery, -refresh re-scan contract, highlight
without shell-global ansi aliases.
Both suites source the source-tree punk::libunknown directly by path:
'package require' would tie-break the same-version bootsupport copy by tm
path order (testinterp order favours bootsupport; child-interp
'tm::add {*}list' reverses precedence).
Verified live in 'punk902z src'. Build artifacts synced via make.tcl
packages + vfscommonupdate (loadedlib-0.2.0.tm, libunknown-0.1.tm).
Note: src/bootsupport copy of punk::libunknown intentionally untouched -
becomes stale until the next 'make.tcl bootsupport' sync; loadedlib
degrades gracefully against it. punk::libunknown remains manually
versioned at 0.1.
punkshell 0.10.0 (see CHANGELOG.md; also records the 0.9.1 doc-only
-refresh help rewrite).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punkcheck-managed regeneration propagating the G-049 module releases (parse-status data model, machine-parsable cmdhelp returns) into src/bootsupport/modules, the punk.project-0.1 and punk.shell-0.1 layout bootsupport copies, and _vfscommon.vfs/modules.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Output of src/make.tcl refreshing bootsupport, vfs, and project_layouts
bootsupport trees to match the recently-bumped module versions.
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
Root cause: Tk registers a main loop (Tcl_SetMainLoop); tclsh''s Tcl_Main
services it after the script, so `tclsh gui.tcl` behaves like wish. The
script subcommand sourced-then-exited, so a Tk script''s after-callbacks
never ran (scriptlib/tktimer.tcl flashed and died; shell was the
workaround). Confirmed: native tclsh tktimer 1 blocks ~1.35s; script did
0.38s.
Fix: after the script body (success paths only), if a Tk main loop is
registered (info exists ::tk_version && winfo exists .), tkwait window .
services the event loop until the main window closes, then exit - the
script-level equivalent of Tcl_Main. A script that exits from a callback
(tktimer countdown) terminates directly. Console scripts have no main
loop and exit at once; a script error exits at once (no hanging window).
Verified both generations: tktimer via script now blocks the countdown
(punk902z 1.49s, punksys 1.57s - Tk present in both) exit 0; tkhello_exit
immediate; tk-script-that-errors exit 1 in 0.37s (no hang); tkhello (no
self-close) blocks like wish; console script unaffected. Enables G-020
GUI automation via `script` instead of `shell` (noted in G-020 detail).
Project version 0.4.1.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
app-punkscript accepts lib:<name> (with or without .tcl - extensionless
appends it) as the shell subcommand does: `punkexe script lib:hello` or
bare `punkexe lib:hello` via reclassification. The prefix always wins; a
literal path beginning lib: (pathological - colon is illegal in Windows
filenames) is reachable via ./lib:... Not-found errors list every
searched location; only .tcl runs via the script subcommand.
Resolution policy is factored into punk::path::scriptlib_resolve
(PUNKARGS-documented) rather than copied inline: kit-internal
app/scriptlib first and deliberately not externally overridable, then
scriptlib dirs relative to the executable - the same policy
app-punkshell encodes inline (todo noted: refactor the shell path onto
the shared proc). Share definitions via modules, not launcher control
flow.
Verified on both generations (punk902z, punksys): lib:hello
extensionless and explicit, bare-arg reclassification, not-found
candidate listing (bin/scriptlib then <root>/scriptlib for a bin/ exe).
Project version 0.4.0 (backward-compatible behaviour addition).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
app-punkscript loads punk::args::moduledoc::tclcore (catch-guarded,
mirroring the punk::repl code-interp setup) so core Tcl commands are
documented in script runs as at the repl: `''i list'' | punkexe script`
now renders the doc table instead of "Undocumented command". Cost ~40ms;
kit script startup ~0.33s. REVIEW note added: the default-punk-shell
package set is now expressed in app-punkscript AND the repl codethread
setup - consolidation into one shared definition is the G-015-noted
follow-on.
punk::packagepreference 0.1.1: moduledoc auto-load success notice moved
stdout -> stderr (stdout must stay machine-clean for script/exec
contexts; the failure branch already used stderr).
Verified on both generations (punk902z, punksys): i list doc table on
stdout, notice on stderr, timing unchanged. Build-order footnote: lib
changes need make.tcl libs before vfscommonupdate - the modules-only
first rebuild shipped a stale app-punkscript payload.
Project version 0.3.1.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New lean app package src/lib/app-punkscript behind the (previously stub)
script subcommand in punk_main.tcl:
- `<commands> | <punkexe> script` runs piped stdin to EOF - no trailing
exit needed; `<punkexe> script <file> ?args?` runs a file with
conventional ::argv0/::argv (bare non-subcommand first arg still
reclassifies to the file form). Script may itself read stdin in the
file form.
- Default punk shell environment via shared definitions (package punk
registers deck aliases e.g dev; punk::aliascore::init adds the utility
aliases) - no local alias lists, no boilerplate in piped one-liners.
- Honest exit codes: 0 success, 1 on error with errorInfo on stderr,
script''s own exit honoured. No shellfilter stacks/transforms, no
logging side effects, no interactive fallback (terminal stdin with no
scriptname is a usage error, never a blocking read or shell).
- Stdin form echoes the script''s final result when non-empty (one-shot
eval ergonomics: return-value commands like `dev projects.work <glob>`
emit their table without a puts wrapper); file form keeps pure script
semantics.
Verified on both generations (punk902z/tcl9 from _build - bin deploy was
file-locked; punksys/tcl8.6 from bin): piped success/error/exit-code,
file form with args and stdin passthrough, missing-file error, motivating
one-liner `dev projects.work *tomlish*` emitting the project table with
exit 0, `src` mode combination (dev modules load), and `shell` subcommand
regression smoke. G-015 flipped to achieved 2026-07-07 per GOALS.md
rules; goal detail records the verification and the manual console
spot-check item. Project version 0.3.0 (new user-visible shell
capability - minor bump).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
vendorupdate pulls tomlish 1.1.10 from a clean tomlish checkout: the
library API punk::config will consume now carries punk::args (PUNKARGS)
documentation - from_toml, to_dict, from_dict, to_toml,
update_tomlish_from_dict - completing the G-014 precondition
(documentation added upstream in the tomlish project, landed here by
re-vendoring, never by editing src/vendormodules copies). 1.1.10 also
carries the explicit tomlish::cmdline_main entry point (wrapped-exe
startup fix; the load-time argv side effect is gone).
vfscommonupdate refreshes the punkshell kit payload to the current built
modules: shellfilter 0.2.3 (channel-restoration try/finally), shellrun
0.1.4, punkboot::utils 0.1.1, punk::console 0.7.1 content catch-up, and
tomlish 1.1.10.
Project version 0.2.7.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Copies produced by the developer's src/make.tcl runs alongside the
punk::console work:
- _vfscommon.vfs: punk/console 0.1.7 -> 0.3.0 (final content),
punk/lib 0.1.6 -> 0.2.0, punk/repl 0.1.6 -> 0.2.0,
punk/tcltestrun 0.1.0 -> 0.3.0 (matching their committed source)
- bootsupport (root + punk.project/punk.shell layouts): punk/console
0.1.7 -> 0.2.0. NOTE: this snapshot was taken mid-development - it is
stamped 0.2.0 but already contains the 0.3.0 per-console facts code
(without the final PUNKARGS @leaders restructure). The next
'make.tcl bootsupport' run will supersede it with a true 0.3.0 copy.
Resolves the build-maintenance TODO: built module trees no longer accumulate
superseded intermediate versions, and .punkcheck state stays consistent because
every deletion is recorded through punkcheck's event mechanism.
punkcheck (0.3.0):
- installsource_add_virtual + installevent method targetset_addsource_virtual:
'virtual' named-value SOURCE records (-type virtual -path virtual:<id> -value <v>)
compared by stored value, participating in targetset_source_changes. Needed
because successive module versions are built from the same physical source
files - the recorded virtual module_name/module_version identify which
product of the source fileset a target is, at install and delete time.
punk::mix::cli (0.5.0):
- lib::prune_superseded_target_modules: deletes lower-version .tm siblings only
when records prove the same module line (virtual module_name match, or legacy
fallback: recorded sources share the keep-version's source folder(s) and name
prefix). Unrecorded files are never deleted - reported to stderr instead.
- lib::prune_sourcevanished_targets: mirror-prune of recorded targets whose
recorded source files no longer exist (layout bootsupport copies, vendormodule
copies) - intentional multi-version vendoring is preserved.
- build_modules_from_source_to_base prunes after each module install/skip and
records virtual module identity sources on installs.
make.tcl:
- bootsupport: non-glob include_modules.config entries track only the latest
version - superseded recorded snapshot versions pruned; project-layout copies
mirror-pruned; virtual identity recorded on snapshot installs. Calls guarded
(info commands) so a stale bootsupport snapshot degrades to a warning and the
two-pass modules+bootsupport bootstrap self-heals.
- vendormodule copies in root modules*/ mirror-pruned after install.
docs/config:
- include_modules.config headers now document the entry format (non-glob =
latest-only + prune; glob chars = keep all matches; unrecorded never pruned)
- src/AGENTS.md TODO replaced with the durable prune contract;
src/bootsupport/AGENTS.md + src/tests/modules/AGENTS.md updated
tests: src/tests/modules/punk/mix/testsuites/cli/prune.test (19 tests) covering
virtual source recording/comparison and prune identity/safety rules.
Suites: punkcheck 64/64, full suite under Tcl 9.0.3 green except pre-existing
exec-14.3.
payload sync: bootsupport snapshots, project layouts and _vfscommon.vfs pruned
of superseded intermediates (~570k lines of stale .tm copies removed).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
opunk::console (0.3.0):
- base size method resolution order: -winsize fast path -> capability gate (settled or heuristic
can_respond; unresponsive consoles get default_size with no query emission) -> pluggable
::opunk::console::size_query_provider -> default_size. The provider hook follows the
waiting-store pattern: the class carries no punk::console dependency, and non-channel
subclasses (tk widget / channel-environment terminals) simply override size.
punk::console (0.1.7):
- get_size dispatches object-governed calls through the virtual ::opunk::Console::size method
after first-use settling - subclass size overrides are honoured from every punk::console
call site
- ANSI/tput mechanisms + per-pair timing cache factored into size_via_query_mechanisms, shared
by the legacy channel-pair path and the registered provider (console_size_provider)
- integration wiring consolidated into once-latched ensure_object_integration (probe-byte store
redirect + provider registration, respecting customised providers), invoked from
default_console, console_spec_resolve lazy-require sites and the get_size object path
- legacy path returns the documented 80x24 fallback instead of erroring when undetermined
tests: WidgetishConsole subclass override returned through punk::console::get_size for both
anchored-name and object-value specs; class-level provider consultation/capability-gating with
a counting fake provider; provider registration assert. Console suites 27/27 + 10/10; punkexe
6/6, goals 8/8; full suite green except pre-existing exec-14.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
opunk::console (0.2.0):
- probe-byte waiting store changed from dict to array (::opunk::console::waiting_chunks) and made
pluggable via waiting_chunks_arrayvar: integrating layers redirect it to their established
cooperative store so bytes consumed by opunk::Console probe reads stay visible to active readers.
The class remains free of punk::console dependencies - redirection is the integrator's act.
punk::console (0.1.6):
- default_console redirects the opunk::console store to punk::console::input_chunks_waiting
(closing the split-store hazard that previously made opunk-level probes invisible to the
punk repl reader)
- get_ansi_response_payload performs first-use active settling for persistable console specs,
guarded by a recursion latch around settle_can_respond's own probe: every query path
(get_cursor_pos, get_device_status, ...) now pays at most one settling probe instead of a
fresh timeout per call on unsettled ambiguous consoles
punk::repl (0.1.6):
- repl::init routes default console construction through punk::console::default_console
(deduplicates creation logic and gains the store unification)
tests: store-unification and get_cursor_pos first-use settling cases in consolespec.test
(zero-emission and persistence asserts); consoleclass.test store assertions converted to array
form. Console suites 25/25 + 9/9; punkexe 6/6, goals 8/8; full suite green except pre-existing
exec-14.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk::console (0.1.5):
- settle_can_respond: layered active settling of a console's response capability, persisted on
the anchored opunk::console instance. Layer 1 certainty (input eof -> 0, no emission),
layer 2 strong-negative heuristic (plain pipe, no terminal hints -> 0, no emission - piped/CI
output streams stay clean), layer 3 active CSI 6n probe with timeout, only in the ambiguous
zone (real consoles / pipe-presenting terminals such as mintty without winpty). -force
unsettles and re-probes; -timeout_ms overrides can_respond_probe_timeout_ms (default 500).
Unmatched input consumed while waiting is preserved via input_chunks_waiting.
- get_size triggers first-use settling for persistable specs only (anchored instance name or
the auto-attached default) so unanchored object values cannot cause per-call probes
- get_ansi_response_payload only cycles console raw mode when the input channel is a
console/tty: pipe-targeted probes no longer flip the real console's mode as a side effect,
and a repeat-cycle deadlock under captured-channel environments (runtests runx) is fixed.
(Latent enableRaw_twapi/disableRaw_twapi restore asymmetry noted for future attention.)
punk::repl (0.1.5) / opunk::console (0.1.1): docs reference the active settling mechanism
(no behaviour change; probe machinery stays in punk::console so the class remains free of
that dependency)
tests: 5 new settle_can_respond cases in consolespec.test - certainty and clean-pipe settling
with zero-emission asserts, ambiguous probe timeout with emission and input-preservation
asserts, response arrival settling 1 with realistic event-loop delivery then -force
transition to 0, and get_size first-use settling. Console suites 23/23; full suite green
except pre-existing exec-14.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com