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
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
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
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
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
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.
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.
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.
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