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
Acceptance now names the discovery fact (console_fact_get fact key err,
returning the effective err channel name) so the clause is testable like
G-007's named facts. Detail file additions: "every -console site" is
satisfied at the module's documented resolution choke points
(console_spec_resolve, hybrid_console_spec, opt_console_* helpers,
spec_to_channelpair, shared PUNKARGS fragments) rather than by per-site
audit; the two consumer-shaped acceptance clauses' division of labour is
stated (internal diagnostics migration = first consumer, external consumer
= public emit form); G-001 backends wanting a distinct diagnostic stream
must set err explicitly (default merges into out); and
previous_stty_state_stderr stays as-is (stty-state restoration, orthogonal
to diagnostic routing).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk::console models two channels per console ({in out}); its own
diagnostics fall back to raw puts stderr and subshell/backend/theme goals
(G-001/G-008/G-009) inherit the dual-channel assumption. G-011 defines err
as an attribute of the canonical {in out} console identity: {in out err}
specs accepted everywhere -console is, a nullable err field on
opunk::Console, err defaulting to process stderr for the default console
and to the console's out channel elsewhere, console-directed diagnostics
and an emit-to-err path, and cross-thread discovery via console_fact_get.
Ownership/fact/mode-cache keys stay canonical {in out}, so the G-007 work
is untouched.
Detail file records the gap analysis, the rejected shapes (err-in-identity
triple key, scope-boundary-only, uniform err==out, unset-means-none) and
the G-001 sequencing interaction.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
A terminal query (internal::get_ansi_response_payload and every query proc
layered above it - get_cursor_pos, dec_get_mode, ...) on the process-default
console {stdin stdout} issued by a thread that is not the registered owner is
now forwarded whole to the owning thread via synchronous thread::send, so the
query queueing, raw-mode cycling, cooperative reader handling
(input_chunks_waiting) and settled can_respond gating all execute in the
owner's context. Owner-side errors propagate to the caller with routing
context. Routing happens before the local can_respond gate deliberately: a
non-owner context's anchored view of the default console may be unsettled
while the owner's is settled.
New internal::console_route_owner makes the decision: routing applies to the
default console pair only - non-std channel names are thread-local, so an
{in out} pair spec always names the calling thread's own console and operates
locally (a console constructed and owned by code-interp/worker code
round-trips nowhere). Unregistered, owner==caller and dead-owner
(liveness-validated) cases also operate locally, preserving single-interp
behaviour exactly.
The synchronous send relies on the owner servicing events while the caller
blocks - the property the repl-installed vt52/colour/mode aliases already
depend on. Those aliases are unaffected: a call arriving in the owner
resolves to owner==self and takes the local path, so no double-hop and no
ping-pong.
Tests: new ownerrouting.test covers the routing decision (unregistered/self/
other-live-thread/non-default-pair/dead-owner) and the transport (recorder in
a worker-thread owner: args marshalled intact, execution in the owner thread,
query procs above the choke point inherit the routing, owner-side error
propagation). Full suite passes (baseline exec-14.3 only).
G-007 remains active: the remaining acceptance verification is interactive -
a terminal query from a live punk session's code interp against the default
console (cooperating with the repl reader), which needs a real responding
terminal.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Per-console facts are now visible to every thread of a punk session: the
default console {stdin stdout} keeps its legacy namespace variables as
authoritative local storage, with write traces mirroring every write
(including direct variable writes) into tsv punk_console_facts; non-default
consoles store facts only in tsv with owner-qualified keys (non-std channel
names are thread-local and would otherwise alias). console_fact_set from a
non-owner thread forwards to the owning thread (vt52-alias transport) and
always writes the tsv mirror itself (covers unreachable or older-version
owners). New console_fact_clear for tests/maintenance.
New console ownership registry (console_owner_register/get/forget, tsv
punk_console_owners keyed by canonical {in out} pair): ownership is captured
when an opunk::console instance is anchored - via the new pluggable
::opunk::console::lifecycle_callback (opunk::console 0.4.0, base class
unchanged) wired by ensure_object_integration - and by default_console.
Consult-time liveness validation clears entries for exited threads. For
{stdin stdout} first registration wins and only the owner's forget releases
the entry, so a thread anchoring a local view cannot steal ownership.
dec_has_mode/ansi_has_mode caches moved to tsv punk_console_modecache
(single-key atomic entries shared process-wide).
Infrastructure tsv arrays are now punk_-prefixed to avoid collisions with
application tsv usage in subshells: console -> punk_console (is_raw), with
call-site patches in punk::repl 0.2.1, punk::lib 0.2.1, punk::basictelnet
0.1.1 (rule recorded in src/modules/AGENTS.md; legacy repl/codethread_*/
zzzload_pkg* arrays left for a coordinated follow-up).
punk::console is now loadable in secondary threads without ::argv0
(powershell consolemode fallback guarded).
Tests: consolefacts gains ownership-registry and cross-thread visibility
coverage (worker-thread punk::console load, trace-mirror, forwarded set with
deadline-polled sync - see comments re runtests child-interp topology);
cleanups use console_fact_clear / tsv instead of poking removed internal
stores. Full suite passes (baseline exec-14.3 only).
G-007 flipped proposed -> active (user-confirmed); remaining acceptance work
is the choke-point brokering slice.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Migration plan phase 4 (final phase):
- get_cursor_pos, get_cursor_pos_list, get_checksum_rect,
get_device_status, get_device_attributes,
get_device_attributes_tertiary, get_terminal_id, get_tabstops,
get_tabstop_apparent_width, get_dimensions, get_xterm_size,
get_xterm_pixels, dec_get_mode_line_wrap and ansi_get_mode_LNM
accept -console <consolespec> (any spec form) or the legacy
trailing positional, parsed by new internal::hybrid_console_spec;
each carries a PUNKARGS definition (@leaders per the Argument
Order rule)
- get_size refactored onto internal::hybrid_console_spec
(behaviour unchanged)
- documentation-only PUNKARGS for input_at_eof,
is_input_console_or_tty, size_via_query_mechanisms,
console_size_provider and the get_size_using_* mechanism helpers
(signatures unchanged - internal size mechanisms remain
canonical-pair only, per the updated AGENTS.md contract)
AGENTS.md migration bullet rewritten from 'incrementally migrating'
to the completed-state contract. New queryprocs.test (6 tests,
fed-response coverage for every converted proc across spec forms);
full console suite 58/58 on Tcl 9.0.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Index entries in GOALS.md plus detail files, all proposed:
- G-007 location-transparent punk::console across repl and code
interps: punk-side tsv-backed ownership registry, fact/cache
visibility, choke-point brokering generalising the vt52 alias;
detail records rejected alternatives (audience namespace split,
owner field on ::opunk::Console, opunk-package-namespace registry)
- G-008 scoped console state for same-console subshells: activatable
per-subshell state sets (journal-on-write, re-emit on activation)
rather than a LIFO undo stack, so later tree switching works;
restore of tabstops/modes/palette on quit; opt-in, shared behaviour
stays default
- G-009 themed subshell profiles: poshinfo schemes bound to
restriction aspects (G-003 limits/hidden commands, punk::island) so
restricted subshells are visually distinct; terminal effects ride
the G-008 state set
- G-010 subshell suspend/resume and tree navigation: switch to any
live subshell (grandparent, cross-branch) via attachment handoff +
G-008 state activation; navigation commands governed by G-003
hide/expose; depends on G-002's non-nested model
Sequencing: G-007 -> G-008 -> G-009/G-010, with G-002 prerequisite
for G-010.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Migration plan phase 1:
- dec_set_mode/dec_unset_mode/ansi_set_mode/ansi_unset_mode and
cursor_style resolve -console via console_spec_resolve instead of
'[lindex $terminal 1]' - anchored opunk::console instance names and
::opunk::Console object values now work (previously silently wrote
to the wrong target)
- the fourteen '-console -type list -minsize 2' declarations replaced
with new shared fragment ::punk::console::argdoc::console_opts (the
-minsize 2 constraint rejected 1-element instance-name specs at
parse time); show_input_response and cell_size -console docs unified
on the fragment
- dec_has_mode/ansi_has_mode cache on the canonical {in out} pair -
all spec forms addressing the same console share one cache entry
- test_is_vt52 option -inoutchannels renamed to -console per the
documented convention (experimental/broken proc, no callers)
- set/unset mode argdocs corrected: <codes> are semicolon delimited
AGENTS.md records the fragment usage and the -minsize 2 prohibition.
3 new tests in consolespec.test (setter spec forms, cache
canonicalisation via mixed specs, test_is_vt52 -console); full console
suite 52/52 on Tcl 9.0.3.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
From 'tclsh src/make.tcl bootsupport' run after commit 0ee66dce -
correctly stamped and containing the final 0.3.0 content (per-console
facts + PUNKARGS @leaders restructure).
Note: the punk.shell-0.1 layout retains console-0.1.0.tm and
console-0.2.0.tm alongside the new 0.3.0 - the prune kept them (no
qualifying punkcheck install record); pending manual removal if
unwanted, per the established prune-leftovers handling.
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.
Root AGENTS.md closeout now includes a step to re-check modified .tm files for new procs lacking PUNKARGS argdoc blocks. src/modules/AGENTS.md Work Guidance explicitly requires argdoc blocks for all new procs in .tm files (documentation-only is acceptable). The Documentation section clarifies that 'user-facing' includes developer-facing utility procs. A new 'Argument Order In PUNKARGS' section explains the distinction between @leaders, @opts, and @values and why putting a leading argument in @values produces an incorrect synopsis. Verification section adds a check for PUNKARGS argdoc blocks on new procs. The interp_sync.test file contains 6 tcltest tests for the new punk::lib helper procs.
The new 'src' package_mode loads unbuilt modules and libraries from the project's src/ tree (src/modules, src/lib, src/bootsupport, src/vendormodules) instead of built output. It differs from 'dev' mode which points at built <projectroot>/modules. The src mode also runs an inline #modpod package-ifneeded scanner using Tcl builtins only (since no punk modules are loaded at boot time) and sets package prefer latest so 999999.0a1.0 dev modules are preferred over stable bootsupport/vendored copies. The exhaustive permutation switch for package_mode parsing was replaced with token-by-token validation that scales to any number of modes. Verified: punk902z src launches an interactive shell with package provide punk returning 999999.0a1.0.
The codethread init_script now propagates package prefer via a new %packageprefer% scriptmap entry. The code interp (punk/0 repltype) creation replaces the manual epoch-only copy with punk::lib::interp_sync_package_paths code -libunknown 1, which propagates tm list, auto_path, package prefer, and libunknown epoch+init from the codethread to the code interp. This fixes the issue where package prefer latest set in punk_main.tcl was not inherited by the REPL's code interp, causing 999999.0a1.0 dev modules to be passed over in favour of stable VFS-bundled versions.
snapshot_package_paths returns a script string reproducing the caller's tcl:™️:list, auto_path, and package prefer for use in thread::create init scripts. With -libunknown 1 it also copies the punk::libunknown epoch and sources+inits libunknown in the target. interp_sync_package_paths now propagates package prefer (was missing) and accepts optional -libunknown 1 flag for epoch copy + libunknown init in the child interp. Both procs have PUNKARGS argdoc documentation. Includes 6 tcltest tests covering basic propagation, version selection with package prefer latest, ordering preservation, snapshot script validity, -libunknown flag, and regression.
Both tm_path_additional_ifneeded call sites now pass \ as required by the 0.3.0 API. The second call site also passed the wrong path (../modules instead of ../modules_tcl\) due to a copy-paste bug, so Tcl-major-specific #modpod modules were never scanned. Added a zipper #modpod verification test that confirms package require zipper resolves to the 999999.0a1.0 #modpod version via the ifneeded mechanism and that initialize/addentry/finalize work correctly. All 4 tests pass under tclsh90.
tm_path_additional_ifneeded referenced an out-of-scope \ variable that was never a parameter or local, causing 'can't read project_root: no such variable' whenever a #modpod module was found. The proc now requires project_root as a 2nd parameter. Additionally, the #modpod directory pattern was hardcoded to 999999.0a1.0 which the build system replaces with the real version (e.g 0.3.0) during make.tcl modules — breaking pattern matching in built bootsupport copies that looked for #modpod-*-0.3.0 directories which don't exist on disk. The proc now extracts both modname and version dynamically from the #modpod-<modname>-<version> directory name by splitting on the last dash, making it version-agnostic. Punk::lib::tm_version_magic convention was considered but dynamic extraction is preferred here as it will also work in the future src package_mode boot context where punk modules aren't loaded yet.
runtests_failure_summaries now includes errorinfo, result_was, and result_expected when present in the parsed testdict, so the json report (which iterates keys) and downstream consumers get the full failure context. runtests_print_failure_details prints an errorInfo fenced block for ERROR-status failures and result_was/result_expected fenced blocks for FAILED-status mismatches, with full multi-line text. The compact report appends one-line truncated message=, actual=, and expected= fields (120/80/80 chars) so compact mode stays compact while still being actionable for agents. src/tests/AGENTS.md Local Contracts and Work Guidance updated to document the new failure-detail surfacing.
parse_testrun now keeps the full Tcl errorInfo in testcase_fails entries for ERROR-status failures (previously parsed then discarded), and captures the actual vs expected result blocks for FAILED-status result mismatches via a new result_stage sub-state. Test name extraction in the took/PASSED/SKIPPED handlers is fixed to use the full substring between the structural delimiters instead of lindex \ 1, which corrupted timing associations and testcase_passes/testcase_constraintskips keys for any test name containing spaces; the took handler uses string last to allow test names that themselves contain the word 'took'. The closing-FAILED microseconds lookup now keys on \ (authoritative) instead of the stale \. New dict keys are additive; existing consumers using dict exists/dict get are unaffected.
- disableAnsi was defined as a duplicate enableAnsi in the windows
no-twapi branch, leaving disableAnsi undefined there
- get_size_using_cursormove flushed stdout instead of the supplied
output channel
- get_size_using_chanconfigure: removed unreachable code after its
unconditional error
- cell_size: query path now passes its -console spec to get_size
instead of always querying the default console; bad-format error
message named punk::sixel instead of punk::console
- test_string_cursor: undefined $test_string reference in the no-ansi
path; rowoffset/columnoffset return labels were swapped
- ansi::move_emit vt52 branch called nonexistent punk::ansi::v52move_emit
(now vt52move_emit)
- move_emit_return re-expanded {*}$args inside its per-triple loop,
duplicating emissions when extra row/col/data triples were supplied
- dec_has_mode unknown-mode error message named dec_get_mode
Console module test suites pass (27/27, Tcl 9.0.3).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Agents must not add new executable binaries (or zip-based .tm modules
embedding executables). Existing committed binaries are intentional
interim state pending goals G-004/G-005/G-006 and must not be flagged
or 'fixed'; the rule does not block the developer's own interim commits.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Index entries in GOALS.md plus detail files. G-004 targets a binary-free
committed tree, achievable once G-005 (zig-based build of binary deps from
source) or G-006 (consent-gated download of pre-built artifacts) provides
the replacement supply of the previously committed binaries.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com