console_fact_get and console_fact_set documented -choices on the key
argument with -choiceprefix defaulting to true (prefix matching promised),
but the manual positional parser feeds key directly into a dict lookup
with no prefix normalization. PUNKARGS now declares -choiceprefix 0 so
documented behaviour matches the implementation (exact match required).
Assisted-by: harness=opencode; primary-model=openrouter/z-ai/glm-5.2; api-location=openrouter.ai
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
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
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
- 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
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