TEMP_REFERENCE checkouts confirmed current (Tcl core 2026-07-07, tcludp
2026-07-05). core-9-0-branch carries no tclWinConsole.c fix since 9.0.2, so
the wedge - if in the console driver - is likely still present upstream.
Current driver architecture recorded (process-global gConsoleLock, console
handle list shared across all Tcl threads, per-handle SRWLOCKs, reader/writer
threads with CONDITION_VARIABLE wakeups + Tcl_ThreadAlert fan-out) as the
credible wedge surface. Worker-side hypothesis sharpened: std-channel init on
a worker's first channel creation (udp_open / log-file open) attaches it to
the shared console machinery when the process stdin handle is the console -
fits which workers wedge vs survive, though probes have not yet reproduced.
Probe 'adopt' variant added (main thread closes stdin, CONIN$ adopted into
the stdin slot, SetStdHandle, active reader) - clean, ruling out the last
easy stdin-arrangement candidate. Approach reordered: native thread stacks
of a wedged in-context specimen promoted to step 1 (no debugger installed
yet - tool options noted).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Index entry recorded active per user. The detail file carries the full
investigation record: diagnosis chain of the piped-stdin exit/quit hang,
evidence and scoring caveats, ruled-out repro ingredients, remaining
candidates, and inline tooling listings (standalone probe script and the
hidden-console batch scorer with its 2026-07-08 baseline: punk902z run-2
syslog workers wedged 4/4, punksys 8.6 alive 4/4). Upstream reporting is the
user's manual step, gated on re-verifying against a Tcl 9 built from current
core sources.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Piping a script into shell mode then exiting the restarted interactive repl
intermittently froze the shell: shellfilter::run's teardown made a bare
synchronous thread::send (settings-reset in shellthread::manager::unsubscribe)
to the shellfilter-run syslog log worker, whose event loop had silently
stopped servicing events (G-036: Tcl 9-only console+udp worker wedge -
root-cause tracked separately).
- shellthread 1.6.3: unsubscribe settings-reset send is now -async (sends to
a thread are FIFO, so a later reuse still sees the reset applied first);
shutdown_free_threads keeps its timeout timer armed across all vwait
iterations (was cancelled on the first response, leaving later waits
unbounded); get_tag_config's bare sync send flagged as same hazard class.
- shellfilter 0.2.4: shellfilter::run honors its -syslog option (default
empty -> noop runtag log worker, no thread created) instead of the
hardcoded 127.0.0.1:514 debug leftover. Syslog remains supported when
explicitly configured.
Verified: automated repro (hidden console, piped script scheduling a delayed
::punk::repl::exit in the restarted repl) hung before, exits cleanly after -
including with syslog force-enabled against a genuinely wedged worker.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New suite src/tests/modules/punk/libunknown/testsuites/shadowing/
(7 tests, all pass) pins the .tm same-version shadowing rules verified
experimentally 2026-07-06/07: tcl:™️:add PREPENDS each argument (last
arg ends at the head of tcl:™️:list); the head of tcl:™️:list wins
exact-version ties (single and separate add calls); a higher version
wins from any position (order only arbitrates exact ties); and
punk::libunknown preserves all of it (scanner parity). Shipped behaviour
depends on these rules - the runtests testinterp tm ordering (bootsupport
over vendormodules) and punk_main''s package-mode block precedence that
G-033''s proj:/kit-first design builds on - so a Tcl or libunknown change
that shifts the tie-break now goes red here instead of silently
mis-resolving.
Goal G-035 (proposed): characterise mixed .tm / pkgIndex.tcl provision of
the same package - same or differing versions, under standard package
unknown, punk::libunknown and punk::packagepreference, across
scan-trigger orderings, forget/re-require crossing forms, and 8.6/9.
Converts the standing informal rule ("avoid mixing provision forms for
one package - unexpected behaviour even with libunknown''s improvements")
from folklore into either substantiated AGENTS.md guidance with named
failure modes, or retirement if mixing proves predictable. Discovery
work, so goal-specified rather than written now; extends the shadowing
suite when implemented.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Design review outcomes (2026-07-07):
- Syntax: proj is not a fifth ordered token (category error - the dash
list is an ordered sequence of path blocks whose order IS the
same-version tie-break dial, per the verified tm rules and punk_main''s
own comment). Decided form: [proj:]<block>[-<block>...] - the prefix
scopes WHICH project root dev/src resolve against (cwd walk-up to the
nearest VCS repo root vs exe-relative), staying outside the ordered
list whose semantics are untouched. Explicit prefix = no silent rebind
on cd. Rejected: auto-detection, an explicit flag (less convenient),
token-attached qualifiers.
- Shadowing: visitor mode makes cross-lineage same-version-different-code
ties structurally expected (binary and visited project snapshots are
different vintages by definition). Order only arbitrates exact ties -
dev-versioned, higher-versioned and kit-absent modules come from the
project in either order. Canonical documented invocation is
proj:internal-src (kit-first: the visiting shell''s infrastructure
survives ties; for a standalone downloaded binary the kit is its only
trustworthy lineage and internal is location-independent); proj:src
documented as the faithful-vintage variant. Trimmed kits (G-019)
compose gracefully - kit-first only arbitrates ties.
- Required boot notice: detected root + effective precedence.
- packagemode help text drafted and persisted in the detail file as the
designated G-032 source (live only when the prefix ships); G-032 detail
cross-references it.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-033: an explicit `proj` package-mode token lets an installed (visitor)
punkshell binary detect the punk project containing the cwd - walking up
to the nearest VCS repo root, the marker punk::repo::is_project_root
already uses - and set up source/dev library resolution against THAT
project, so `punkshell proj shell` explores a project interactively even
when the project builds no shell-capable binary (or no binary at all).
History check recorded: cwd project-structure detection was never active
in the boot modes (src/dev are exe-relative; os is shallow cwd-only), but
the runtime detector find_project already exists and dev commands use it -
so the marker question (VCS repo root, not punkproject.toml which tomlish
lacks) is already answered; this aligns boot modes with it. Explicit token
gates it per user decision (no silent rebind on cd); boot chicken-and-egg
(find_project is in punk::repo, not loadable at the package-mode phase)
handled by a lean inline VCS-root walk-up or post-boot deferral. Reuses
the existing src-mode path machinery, parameterised on the detected root.
Not gated on the module.templates/punk::cap plugin machinery.
G-034: zip-based #modpod modules (e.g. punk::mix::templates) fail to mount
in the shell subcommand's code interp on Tcl 8.6 - `dev module.templates`
errors with `invalid command name vfs::RegisterMount` / no template
providers - while the same call works via the script subcommand /
main interp (reproduced 2026-07-07). The code interp lacks the vfs::zip
library that supplies zip mounting for pre-zipfs Tcl. Tcl 9 (zipfs)
unaffected.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Adds src/tests/shell/testsuites/punkexe/scriptexec.test (12 tests, all
pass) covering app-punkscript / the script subcommand (G-015): piped
execution and honest exit codes, error->exit 1, exitcode propagation,
stdin result echo, default-env dev alias present without boilerplate,
file-form argv passing, the lib:<name> scriptlib mechanism (subpath,
extensionless, bare-arg reclassification, not-found location listing),
and the tclsh-matching Tk main-loop behaviour (0.4.1): a serviced
after-callback runs and exits, a no-exit GUI script stays alive, an
errored GUI script exits promptly without hanging.
Reuses the shellexit.test built-exe harness pattern (pipe stdin,
half-close for EOF, timeout+force-kill) so runs are hang-proof; Tk cases
gated by an auto-detected punktk constraint (probes whether the kit loads
Tk). Full shell suite: 19 pass / 2 skip (console-only punkgoals) / 0 fail.
lib:/Tk cases resolve committed fixtures under scriptlib/_punktest/ - a
lib:-resolvable fixture cannot live in a tcltest temp dir since resolution
only searches scriptlib locations relative to the exe. The subfolder is
test-owned (README + do-not-edit fixture headers); root AGENTS.md carves
it out as the one agent-manageable exception to scriptlib being
user-only. No project version bump - tests/docs/fixtures only, no shipped
shell behaviour change.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
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
Not a mode of app-punkshell. The acceptance is defined by absence (no
shellfilter stacks, no logging side effects, no interactive fallback),
which a package that never loads that machinery guarantees by
construction; app-punkshell''s load-time behaviour (shellfilter stacks
with syslog/file targets writing punkshell_*.log into the caller''s cwd,
logging threads) is wrong for one-shot calls; isolation caps the blast
radius of shell-path machinery. Duplication mitigation: the default punk
shell module/alias environment definition is factored into a shared
module-level definition (punk::aliascore as seed) consumed by both app
packages - share definitions via modules, not launcher control flow.
Package shape previews the G-031 registration model, as app-tomlish
demonstrated.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-031: split the monolithic vfs main (punk_main.tcl: 1190-line apply
block - ~1060 lines boot machinery, ~100 lines dispatch tail) into a
layout-owned boot core (shipped via _vfscommon.vfs, sourced by
vfs-relative path since no package paths exist at boot; pull-updatable
per the G-027 ownership model) and a thin project-owned main declaring
app subcommands at commented customization points. Fork cost measured:
tomlish_main.tcl carries ~20 custom lines on a stale 500-line 2025
vintage copy; project_main.tcl (901 lines) same at template vintage -
boot improvements never reach derived projects. Acceptance: behaviour
parity across dispatch cases and vfs types, project subcommand added by
editing only the thin main (tomlish the proving ground), boot core
versioned/reportable (G-025).
G-032: the default dispatch defines subcommands via punk::args - tabled
-help enumerating built-in and project-registered subcommands, parsed
options with tabled usage errors (script/G-015 or version-report/G-025
as first candidates), G-030 degradation rules (guarded require; parsing
falls back to switch semantics, help to a plain list; rendering degrades
independently). Parked decision: the silent unknown-arg->script
reclassification should stop swallowing typos (candidate rule:
reclassify only when the argument names an existing file).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-030 (proposed): make.tcl - the first surface a developer sees - parses
subcommands/options via punk::args and showcases the tabled usage for
help and argument errors; every interactive y/n prompt gains a declared
flag equivalent (interactive stdin prompts as today; flag proceeds;
non-interactive stdin without the flag fails fast with usage instead of
hanging or half-aborting) so agents drive make.tcl with arguments rather
than piped y/n; punk::args joins the bootstrap-tracked staleness set.
Degradation is a hard requirement: boot stays self-contained and the
repair commands (check/bootsupport/modules) keep working with plain
fallback help when the bootsupport punk::args or the ANSI rendering
stack is stale/unavailable - this week''s stale-fileutil and
stale-textblock incidents are the cautionary tales.
G-015 detail: recorded the runx -tcl result-shape observation (dict keyed
{result stdout stderr}, no exitcode key unlike exec mode).
G-020 detail: stamped the 2026-07-07 decision - acceptance gates on a
full testable Windows system without requiring other platforms first.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Root AGENTS.md "Git Commit Conventions" renamed "Commit Conventions (any
VCS)": the Co-Authored-By/AI-attribution ban and the Assisted-by trailer
apply to git commits and fossil checkins alike (git parses trailers
structurally, fossil comments are free text - either way the trailer is
the last line of the message). Generated projects start fossil-only but
may adopt git; punkshell itself is git-primary today and could flip.
CLAUDE.md mirror section updated to match.
G-027 detail: documentation/goals ownership design input - layout-owned
child AGENTS.md docs ride the pull channel under the
unmodified-since-install classification; project-owned skeletons (thin
root AGENTS.md, GOALS.md, goals/AGENTS.md, empty GOALS-archive.md) are
seeded once and never pulled; commit conventions are part of the seed
(developer-changeable, VCS-neutral); layout-owned marker convention;
transitive chains inherit the parent''s modified docs as their baseline.
G-012 detail: AGENTS.md template payloads recorded as the same hazard
class as live .gitignore payloads - a template AGENTS.md under
src/project_layouts/ is binding DOX for agents walking the punkshell tree
though written for generated projects; needs the same inert storage +
materialize-at-generation treatment.
First field test: the tomlish project was seeded today with the thin root
AGENTS.md + CLAUDE.md + GOALS framework this design describes.
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
Read-side DOX rule: re-read the canonical source for a topic (GOALS.md,
nearest owning AGENTS.md, punkproject.toml, etc.) on recheck/re-verify
queries, rather than relying on session memory. Directory re-scans are
complementary, not a substitute.
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
src/tests becomes the single source of truth for module test suites: a
punkcheck-tracked make.tcl step generates the packaged test::<modulename>
#modpod modules from src/tests/modules/<namespacepath>/testsuites,
ending hand-maintained parallel copies under src/modules/test/.
The packaged form is a distributable in its own right: a user who
downloads a built module can optionally download the matching
test::<modulename> and verify the module''s behaviour on their own
system - package require + RUN interface, or a wrapped executable''s
-app test - with no source tree or harness. Acceptance proves the
standalone consumer scenario (runs without the project source tree),
suite-result parity with src/tests/runtests.tcl, and byte-for-byte
data fidelity (crlf roundtrip fixtures).
Motivated by the tomlish dual-copy interim (its modpod testsuites were
ported to src/tests while the modpod remains the live test::tomlish
build source - tomlish checkins 20bd00dc, feedb6f0).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Motivated by a make.tcl project deploy failure ("could not delete target
binary") where the locker turned out to be 7-Zip inspecting the exe -
identifying it took several dead ends because Windows has no cheap
file-to-process reverse lookup (name matching and Process.Modules scans
both miss plain file handles; openfiles needs a pre-enabled global flag;
handle.exe is external and elevation-bound).
G-028: a punkboot::utils helper returns the locking processes via the
Windows Restart Manager API (the installers-grade answer to "who is
blocking this file"), with twapi or cffi required lazily at call time -
punkboot::utils stays pure Tcl per the bootsupport no-compiled-extensions
contract, and unavailability degrades to the existing message. make.tcl
kit deploy failures append the locker report when determinable. Recurring
customer: G-023 versioned/dev binaries rewritten every build.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Projects generated by dev project.new receive a layout snapshot that
drifts; the current remedy is a push from punkshell (project.new -force 1
-update 1). G-027: one command run inside the derived project pulls
make.tcl/build.tcl, bootsupport modules/libs and template payloads from
the originating punkshell project - .punkcheck records remain the
provenance basis, origin identity becomes durable to workdir moves (not
relative paths alone; projects.work discovery as re-locator), VCS
awareness on both ends (G-026 clean-checkout policy on the source,
uncommitted-local-modification detection on the target), and the
remote-pull question gets a recorded decision.
Detail file records the motivating incident (tomlish 2026-07-06: a stale
make.tcl whose bootsupport updater was disabled by the very drift it
needed to fix - broken bundled fileutil::traverse under Tcl 9) and the
constraint that the pull entrypoint must not depend on the freshness of
what it updates.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
shellfilter::run now wraps everything between tee-stack add and removal in
try/finally so the callers stdout/stderr are always restored, and the
decorative stack-status table rendering in log::critical blocks is
catch-guarded. Previously an error there aborted the run after diverting
the channels and before restoring them, leaving process stdout/stderr
broken with all subsequent output silently lost - observed when a stale
same-version textblock snapshot (old option-value syntax) called the new
punk::ansi sgr_merge_singles inside the stack-status render during tomlish
test runs.
runtests.tcl:
- testinterp tm path order: bootsupport moved to last test_tmlist element
so it wins same-version module ties over vendormodules. Rules verified
experimentally on Tcl 9.0.3 (identical under standard package unknown
and punk::libunknown): tcl:™️:add prepends each argument, head of
tcl:™️:list wins same-version ties, so the last element of a
single-call add list wins.
- testinterp auto_path gains the parent of [info library] so binary
packages such as Thread (needed by shellrun) resolve in projects
without a root lib_tcl<N>/<arch> payload.
Project version 0.2.6. Verified: full shellspy suite at baseline (517
pass / exec-14.3 known); tomlish ported suite 184/184; reconstructed
stale-module scenario now completes with captured results and live
stdout instead of silent loss.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Move vendor_source_dirty_warnings out of make.tcl into punkboot::utils as
vcs_dirty_warnings (PUNKARGS-documented, caller-label param so the
bootsupport update path can share it per G-026). make.tcl now uses a
guarded lazy require and degrades to skipped-with-warning if the
bootsupport snapshot lacks the proc - a stale/broken snapshot must never
brick the make.tcl commands used to repair it.
Chicken-and-egg-safe ordering used and now documented in
src/bootsupport/AGENTS.md ("Moving make.tcl functionality into
punkboot::utils"): edit src module + bump buildversion, make.tcl modules,
make.tcl bootsupport, only then repoint make.tcl call sites. Boot-phase
code (path setup, punkboot::lib prompts, package-availability checks)
stays self-contained in make.tcl.
Bootsupport snapshot refresh from the sanctioned pipeline: punkboot::utils
0.1.0->0.1.1, shellrun 0.1.2->0.1.4 (punk::args require fix catch-up),
punk::console 0.7.1 content catch-up; layout mirrors via the
punkcheck-tracked sync steps.
New tests: src/tests/modules/punkboot/utils/testsuites/utils/vcsdirty.test
(git fixture: dirty/clean/dedupe/label/unversioned/missing) - 25/25 across
punkboot suites; vendorupdate verified end-to-end through the module path.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
make.tcl vendorupdate now walks up from each vendor source path to the
nearest fossil and/or git root and warns (non-fatal, once per VCS root)
when the checkout is dirty - vendored artifacts built from uncommitted
source have no committed provenance. Silent for unversioned or missing
locations; dual git+fossil roots report each VCS separately. First real
run flagged 8 dirty source projects (the tomlish 1.1.7/1.1.8 incident -
vendored content whose source history ended at 1.1.6 - motivated this,
remedied upstream by tomlish checkin 37b71e82).
Goal G-026 (proposed) carries the enforcement follow-on: abort with
explicit override, warn-only as configured mode, shared coverage of
bootsupport_localupdate, and a recorded decision on the residual
staleness gap. Abort-by-default now would block routine work given the
dirty-project count.
Project version 0.2.5; vendorupdate doc note in src/AGENTS.md.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-023: builds produce punk8|9-<major>-<minor>-<patch>.exe from the
punkproject.toml version plus punk8|9-dev.exe tracking the latest build;
plain punk8|9.exe created initially and replaced only by an explicit
release step. Disk growth tolerated for now; archival/deletion trigger
recorded as a later question.
G-024: src/runtime/mapvfs.config converted to toml parsed via tomlish -
explicit runtime/vfs/name/kittype mappings preserved, plus generative
scheme declarations so the G-023 versioned outputs are one succinct entry
rather than per-version lines. Entry-level validation errors; full
migration preferred over a deprecation window.
G-025: build stamps project version, runtime binary and vfs folder into
the kit; a version-report subcommand prints them machine-parseably, with
the same-named punk-module command as the single implementation (the
subcommand is a thin wrapper) so scripts get the report in-process. Report
keeps build stamp, live runtime facts and source-tree version distinct:
src-mode sessions report stamp and live versions separately when they
differ; unstamped contexts (tclsh src/make.tcl shell, plain tclsh with
punk modules) report stamp fields absent rather than fabricated.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Goal G-022: scriptable safe fossil move/rename in dev repo (all checkouts
repointed, no phantom central config-db entries, no dangling old repo db,
project-name renamable with project-code preserved), then rename this
project fossil repo shellspy -> punkshell through that mechanism via a
G-015 piped script call.
New tests src/tests/modules/punk/mix/testsuites/repo/fossilmove.test pin
current behaviour in a FOSSIL_HOME-isolated sandbox with an isolation
guard: the file-copy + fossil test-move-repository sequence repoints the
checkout but leaves the old db file on disk, a phantom repo: entry in the
central config-db, and a stale ckout: back-reference; file moves never
touch project-name. GAP-marked tests flip when G-022 lands.
shellrun 0.1.4: add missing package require punk::args - the 0.1.3
migration left run/runout/runerr/runx calling punk::args::parse without
requiring it, which broke shellrun in bare interps and made every
src/tests/runtests.tcl run fail at test-file invocation. Full suite now
back to baseline (523 tests: 508 pass, 14 skip, exec-14.3 known failure).
Project version 0.2.4 (changelog entry for the shellrun fix).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-018: zig-built plain zip-based tclsh kits with no punk infrastructure
(Tcl 9 kit satisfies acceptance; 8.6 interim = plain non-kit builds, kits
when practical, non-gating). Notes record that wish executables are never
a build target - Tk ships as a loadable package.
G-019: dependency-scan-driven module trimming for punk executables via the
tclparser parse API (c-only library today; punk::lib pure-Tcl fallback is a
stub), with a declared-extras manifest for scan-invisible dynamic requires;
batteries-included builds preserved.
G-020: screen capture + input injection module with per-platform backends -
Windows first (treectrl loupe + twapi, all already shipped in punk9win kit);
X11 (Linux/FreeBSD) and macOS follow behind the same contract; Wayland-native
out of scope. Platform show-stopper assessment recorded in the detail file.
G-021: agent-drivable visual/UI verification - window match, snapshot to
PNG/base64, scripted interactions - through G-015 piped script calls, with
guidance to land alongside G-017. aloupe.tcl stays a standalone app.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-015: complete the punk executable `script` subcommand - run a script file
or piped stdin (scriptname optional) in an interp preloaded with the default
punk shell modules/aliases, honest exit codes, no shellfilter transforms, no
interactive-shell fallthrough. Replaces the fragile `...;exit | punkexe`
repl-pipe pattern (currently a stub in punk_main.tcl that only emits stderr
diagnostics). Lean bare-script execution deferred to a later `tclsh`
subcommand expansion.
G-016: extend `dev projects.work` to discover git-based projects alongside
fossil (git has no central registry - enumeration-source candidates recorded
in the detail file).
G-017 (index-only): once G-015 lands, direct agents via repo guidance to
locate sibling projects with a piped projects.work call instead of scanning
the filesystem.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Scope gains the vendored tomlish module (canonical source in the external
tomlish project space). Goal line rules out ad-hoc toml parsing; acceptance
now requires the tomlish API procs punk::config consumes to carry punk::args
(PUNKARGS) documentation, added upstream and re-vendored before punk::config
implementation proceeds. Detail file records the 1.1.8 coverage gap: argdocs
exist only on the cmdline app layer, not the library API (from_toml, to_dict,
from_dict, update_tomlish_from_dict).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-013: raw input mode as the repl default - per-keystroke debug
emissions (add_chunk frame, rhs editbuf view) individually gated and
repl-toggleable, raw editor parity for history/cursor movement,
debugrepl marked-line form retained for simple terminals with its
first-word activation mechanism under review; retires the tcl 8.6
idle-prompt query residue guarded in punk::console 0.7.1.
G-014: complete ::punk::config - toml files in the XDG config dir with
per-named-subshell override/inherit resolution; first consumer is the
G-013 editbuf-view startup default. Write-back persistence deferred.
Both proposed; detail files under goals/.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
On a tcl 8.6 windows console in cooked (line) mode with a readable handler
armed on stdin - the idle-at-a-line-mode-prompt condition, e.g. a query
fired from an after-script or worker thread while the shell waits for
input - the 8.6 channel driver has a blocking cooked-mode ReadConsole
parked (arming posts it; driver reads sample the console mode at issue
time), so a terminal query's response is swallowed until Enter and then
leaks to the line reader as phantom input. This is the one remaining
hostage window after the 0.7.1 detection fixes and repl 0.2.2 read
discipline: it is the reader legitimately doing its job, so no repl-side
change can remove it (and line mode remains supported for user
scripts/mini-apps even once raw becomes the default).
get_ansi_response_payload now detects the condition before emitting
(cooked + no -inputmode key + twapi console handle + armed readable
handler) and refuses fast with errorcode
{PUNK CONSOLE QUERY HOSTAGE_COOKED_READ} - no emission, no ~500ms timeout,
no input corruption. Mid-command queries (repl reader disarmed) and raw
mode are unaffected. Best-effort by design: a parked read can outlive a
removed handler, so the guard catches the systematic case only. The G-007
routing wrapper now preserves the owner-side errorcode so brokered callers
can discriminate the refusal.
Tests: constraint-gated guard test in probes.test (engages on a real 8.6
console, self-skips on 8.7/9 via the -inputmode key and in piped runs);
ownerrouting.test passes with the errorcode-preserving wrapper. Full suite
at baseline (exec-14.3 only) under Tcl 9.0.3; probes suite also verified
under Tcl 8.6. Project version bumped to 0.2.2 with CHANGELOG entry per
the versioning policy (user-visible failure-mode change).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Move project-version helper procs (parse_punkproject_version,
read_punkproject_version, read_changelog_latest_version) out of make.tcl
inline definitions into a loadable punkboot::utils module so the parsing
logic is unit-testable.
- src/modules/punkboot/utils-999999.0a1.0.tm: new module (0.1.0) with
the 3 procs in punkboot::utils namespace, PUNKARGS argdoc blocks.
- src/modules/punkboot/utils-buildversion.txt: initial 0.1.0.
- src/tests/modules/punkboot/utils/testsuites/utils/utils.test: 19 tests
covering section tracking, missing fields, wrong sections, whitespace,
quote styles, changelog header parsing, pre-release suffixes. All pass.
- src/make.tcl: inline procs removed; projectversion block now does
package require punkboot::utils and calls punkboot::utils:: procs.
- Layout make.tcl files (punk.shell-0.1, punk.project-0.1): same edit;
punk.basic keeps inline procs (no bootsupport tree).
- include_modules.config: added punkboot::utils entry (main + 2 layouts).
- Bootsupport snapshots propagated via make.tcl bootsupport.
- AGENTS.md: project version bump 0.2.0 -> 0.2.1 (patch: new make.tcl
projectversion subcommand is part of the product surface); added
clarifying line that make.tcl command interface warrants at least a
patch bump; src/project_layouts/ added to directories agents should
not directly modify.
- CHANGELOG.md: 0.2.1 entry.
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
The projectversion commit accidentally removed the
'if {$::punkboot::command eq "shell"} {' guard line, orphaning the
shell command body (package require punk/repl, repl::start) at top-level
scope. This broke every command whose if-block lives after line 1942
(vfscommonupdate, vendorupdate, bin, project, modules, libs, packages,
vfs) — they would hit the orphaned shell body and attempt to start a
REPL instead of running their intended function.
No behaviour change beyond restoring the guard; diff vs pre-projectversion
make.tcl is now purely additive.
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
Add a project-level version bumping mechanism for punkshell, tracked in
punkproject.toml, independent of individual module versions.
- root AGENTS.md: new 'Project Versioning' section with change-driven
SEMVER bump policy (patch/minor/major triggers), CHANGELOG.md requirement,
advisory enforcement contract, and independence from module versions.
- src/AGENTS.md: cross-reference to root Project Versioning section so
agents working in src/ discover the rule during their DOX chain walk.
- CHANGELOG.md: new repo-root file with initial 0.2.0 entry; latest
'## [X.Y.Z]' header must match punkproject.toml version.
- src/make.tcl: new 'projectversion' subcommand — read-only, advisory check
that warns on CHANGELOG/punkproject.toml version mismatch and on src/
commits since the last punkproject.toml change. Exempt from bootsupport
staleness abort (same as 'check'). Self-contained Tcl, no punk deps.
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
Project layouts carry .gitignore files as payload for generated projects,
but git treats any nested .gitignore as live configuration scoped to its
directory - the templates self-censor, silently untracking their own
content in the outer repo (discovered via the git/fossil tracked-set
verification; stopgap force-adds in fc1c474c). G-012 [proposed] requires
payloads stored inert (neutral rename or fauxlink-encoded - mechanism an
implementation decision recorded in the detail file) with materialization
at project generation, plus a punkcheck-tracked make.tcl template-refresh
step generalising the existing sync_layouts (which covers only
build.tcl/make.tcl into custom/_project) to the vendor/punk layout base
and derived payloads. Acceptance covers a provably-inert nested-rule scan,
generated-project content equivalence, a working refresh after a
canonical-source edit, and retiring the per-file force-add exceptions.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Four project-layout template README.md files were untracked because the
layout templates' own nested .gitignore files are honoured by git as
nested ignores of the outer repo (fossil, which reads no nested ignore
files, already manages them). Two vendored critcl TODO.txt files were
swallowed by the unanchored 'todo.txt' ignore pattern matching
case-insensitively on Windows (core.ignorecase). All six force-added so
git's tracked set matches fossil's view; note force-adds protect only
these existing files - future files under the layout templates or new
vendored TODO.txt files will drop silently again unless the underlying
patterns are revisited.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Two git/fossil divergence classes discovered during the tracked-set
verification: nested .gitignore files (e.g. in the project-layout templates)
are honoured by git as nested ignores of the outer repo but invisible to
fossil, leaving template content untracked in git yet managed by fossil;
and git ignore matching is case-insensitive on Windows (core.ignorecase)
while fossil globs are case-sensitive (a todo.txt pattern hits vendored
TODO.txt in git only). Both recorded in the coexistence contract with the
prescribed treatment.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
git tracked fossil's generated checkout artifacts (manifest, manifest.tags,
manifest.uuid - regenerated on every fossil checkin/checkout per the
versioned 'manifest' setting value 'rut'), which would churn as soon as
fossil commits begin. They are now untracked and git-ignored with
root-anchored patterns (nested manifest-named files elsewhere are real
content and stay tracked). Fossil control files (.fslckout/_FOSSIL_/
*.fossil) were already correctly git-ignored, and .fossil-settings/
.fossil-custom correctly git-tracked.
New .fossil-settings/AGENTS.md is the canonical home of the git+fossil
contract: which files each system must not track, the ignore-glob
derivation rules (no negation -> wholesale-ignore + explicit fossil add of
tracked exceptions; directory pruning; * crossing /; */ variants for
match-anywhere patterns; default dotfile skipping), and the verification
procedure any future "sync the ignores" request must follow (fossil extras
against git check-ignore, and a tracked-set comparison bounded to the
documented exceptions). The ignore-glob header now points there instead of
duplicating the rules; root AGENTS.md Child DOX Index gains the entry.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The fossil ignore list had drifted well behind .gitignore (missing *.log,
lib_tcl8/9, modules_tcl8/9, vendorbuilds, scratch*, src/testdata,
src/scriptapps/test_*, src/buildsuites, nested _build/_aside, and using
.punkcheck where git has *.punkcheck; carrying a stale 'builds' entry with
no git counterpart). Rewritten to mirror .gitignore as the canonical
statement of intent, translated for fossil glob semantics (verified
empirically against fossil 2.28 with a scratch repo): no negation exists,
so bin/* is ignored wholesale with the source-controlled bin exceptions
(AGENTS.md, *.cmd, *.kit, *.tcl, *.sh, *.bash) handled by explicit
'fossil add' (ignore-glob does not affect already-managed files); a bare
directory name prunes its whole tree; * crosses directory separators; git
patterns matching at any depth get an additional */ variant; # comment
lines are honoured. The header documents the derivation and the semantic
translation rules.
Verification against the live checkout: extras drop 10108 -> 2855 with no
bin/log/scratch leakage; zero extras are git-ignored (no under-ignoring);
git-tracked files invisible to fossil reduce to the designed bin exception
set plus fossil's own generated manifest files and a vendored nmakehlp.exe
(*.exe - needs explicit fossil add, as in git).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The dash-delimited launch package modes of built punk shells (dev/os/src/
internal, implemented in src/vfs/_config/punk_main.tcl) had no agent-facing
record. bin/AGENTS.md now documents the mode grammar and, in operational
terms, the src mode that matters for verifying working-tree changes:
exe-location-based project-root discovery, the source module paths it
prepends, package prefer latest (dev 999999.0a1.0 modules beating
kit-stamped snapshots), and how 'package present' distinguishes a src-mode
session from a plain kit launch. Also records the both-Tcl-generations
interactive verification convention (a Tcl 8.6-based shell plus the current
Tcl 9-based incarnation, deliberately not version-pinned) and the role of
the plain runtime kits under bin/runtime/ for punk-free probes.
Root AGENTS.md Child DOX Index gains the bin/ entry, and .gitignore gains
a !/bin/AGENTS.md negation alongside the other source-controlled bin items
(the /bin/* ignore is why no doc lived there before).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
get_size on tcl 8.6 returned 'columns 1' (real size e.g. 260x49): with the
0.7.1 console-detection fixes in place, 8.6 takes the ANSI size mechanism
(no -winsize shortcut) for the first time under G-007 routing - and that
mechanism is compound: the far-corner cursor move was written unflushed
into the calling thread's stdout channel instance while the routed position
query executed and flushed in the console-owning thread's separate instance
of the same OS handle. The terminal answered before the move reached it.
Pre-routing this was masked because emit and query shared one channel
instance, so the query's own flush pushed the emissions too.
All compound emit-then-query operations now flush their emissions before
querying: get_size_using_cursormove and get_size_using_cursorrestore (the
far-corner move), test_char_width (both the positioning emission and the
measured test emission - the latter would silently corrupt character-width
results), and test_string_cursor (alt-screen/move/erase). Error paths flush
their cursor-restore emissions likewise.
Interactively verified on punksys (tcl 8.6.13, src launch): get_size
reports the true terminal dimensions and test_char_width \t returns 8.
Full suite passes at baseline (exec-14.3 only) under Tcl 9.0.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Two wiring-order holes surfaced during the G-007 interactive verification:
1. Calling ensure_object_integration before opunk::console is loaded errored
into the nonexistent namespace (can't set "::opunk::console::
waiting_chunks_arrayvar": parent namespace doesn't exist). It now returns
0 as a graceful no-op without latching object_integration_done, so a
later call after opunk::console loads still performs the wiring; the
wired/latched path returns 1.
2. 'package require opunk::console; opunk::console::create ...' with no
intervening punk::console object operation left the created console
without a registered owner: the lifecycle callback that records
ownership is only installed by ensure_object_integration, which is wired
lazily from punk::console's object-spec paths. Behaviour was unaffected
for the owning thread (share qualifiers fall back to the calling thread)
but other threads could not address that console's facts. On wiring the
callback, ensure_object_integration now retro-registers ownership for
anchors already present in the interp - anchors are per-interp/per-thread
so the anchoring context is the calling thread. Only empty registry
entries are filled; existing live registrations are preserved (and the
default console keeps first-registration-wins semantics).
Tests: new objectintegration.test covers the no-op return (unlatched),
retro-registration of a pre-wiring anchor with lifecycle forget clearing
the entry, idempotency of the latched path, and normal post-wiring
registration - passing under Tcl 9.0.3 and 8.6. Full suite at baseline
(exec-14.3 only).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Companion to punk::console 0.7.1 (three-site 8.6 console misdetection fix).
The line-mode waiting-chunks path in repl_handler performed an unsized read
that, entered via 'after idle' with the channel drained, made the tcl 8.6
windows console driver park a blocking cooked-mode ReadConsole (driver reads
sample the console mode at issue time - a later raw flip cannot cancel one).
After typed-ahead was stashed during a terminal-query raw window, that
parked read swallowed every subsequent query response in the session until
Enter, with the responses later leaking to the reader.
On hostage-prone consoles (windows, no -inputmode configure key, real twapi
console handle) the path now consumes only data already in the Tcl channel
buffer (chan pending input + sized read - no driver probe). With nothing
buffered it processes the stashed complete lines directly - preserving the
recovered-typeahead execution order - and arms the readable handler for any
remaining partial line, replacing an after-idle reinvoke that, without the
read, could never have progressed; the parked read that arming creates is
the reader legitimately waiting for the user to finish typing. Behaviour on
tcl 9/8.7 (-inputmode consoles) and in raw mode is unchanged.
Interactively verified on punksys (tcl 8.6.13, src launch): the previously
failing case - typing a full command during a line-mode brokered query loop
- now runs with all queries answering, the typed line executing after the
loop, and no timeouts, response leakage or chan-blocked diagnostics; the
session remains healthy afterwards. Full suite passes at baseline
(exec-14.3 only) under Tcl 9.0.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Tcl 8.6 windows console channels expose no -inputmode configure key, so
punk::console guards testing -inputmode/-mode classified a real 8.6 console
as a pipe. A twapi console handle for stdin is now treated as definitive
console detection at all three affected sites (stdin only - pipe probes
still never touch the process console; mintty-as-pipes has no console
handle and falls through to the env heuristics as before):
1. get_ansi_response_payload's raw-cycling gate (introduced 0.1.5) skipped
the raw cycle for 8.6 line-mode queries.
2. is_input_console_or_tty returned a false negative, making
settle_can_respond's layer-2 heuristic settle a real 8.6 console as
unable to respond.
3. input_at_eof (introduced 0.1.2) took the pipe branch and performed its
probe read on the drained console channel. This was the line-mode query
killer: verified empirically against clean tclkits 8.6.13 and 8.6.17,
a read on a drained 8.6 console channel makes the channel driver park a
blocking cooked-mode ReadConsole; driver reads sample the console mode
when issued, not when data arrives, so the query's subsequent raw flip
cannot rescue it and the terminal response is swallowed until Enter.
Since get_ansi_response_payload calls input_at_eof immediately before
its raw cycle, every 8.6 line-mode query timed out (~500ms) with the
response later leaking to the line reader as phantom input - also the
source of increased ANSI artifacts (e.g. 'help env') once 0.7.0
brokering made code-interp queries actually reach the terminal.
Interactively verified on punksys (tcl 8.6.13, src launch): line-mode
get_cursor_pos and dec_get_mode succeed mid-command, brokered query loops
run clean, and typed-ahead lines are recovered without phantom input.
Known residue (documented in the G-007 detail notes): queries fired while
the repl reader is armed and idle at a line-mode prompt (including some
repl-init detection queries) still hit the parked-read limitation on 8.6 -
repl init ordering is a follow-up; raw mode is unaffected.
Tests: probes.test gains constraint-gated wiring tests for the twapi
console branch of is_input_console_or_tty and input_at_eof (skip in piped
runs, engage on a real console). Full suite passes at baseline (exec-14.3
only) under Tcl 9.0.3; probes suite also verified under Tcl 8.6.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Flip G-007 (location-transparent punk::console) to achieved 2026-07-05 in the
GOALS.md index and detail file. Interactive verification on built punksys
(tcl 8.6) and punk902z (tcl 9) kits (punk::console 0.7.0, opunk::console
0.4.0; punk902z also rerun via src launch against dev modules): fact parity
and immediate cross-context visibility in both directions; brokered terminal
queries from the code interp succeed and cooperate with the repl reader in
raw mode with typed-ahead preserved; a chan-pipe console anchored from the
code interp registers its codethread as owner, operates locally, and clears
its registry entry on forget.
Detail-file Notes record the verification and characterize line-mode reader
cooperation (out of scope, no routing regression - brokered and owner-side
queries behave identically): on tcl 9, complete typed-ahead lines survive
with cosmetic garbling while incomplete typed-ahead starves the query raw
window and leaks responses as phantom input; on tcl 8.6 every line-mode
query fails that way, owner-side included (repro:
repl eval {punk::console::get_cursor_pos}) - candidate owner-side regression
to investigate separately. Also noted: the leaked stderr diagnostics are the
traffic G-011's per-console err channel would house.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com