diff --git a/GOALS.md b/GOALS.md index 3407ff26..6b90aadb 100644 --- a/GOALS.md +++ b/GOALS.md @@ -270,8 +270,8 @@ Detail: goals/G-037-vendorlib-vfs-propagation.md Goal: platform-specific vendored binary packages under src/vendorlib_tcl/ reach the kit vfs lib_tcl trees through a make.tcl step instead of hand-copying - updating a vendored package becomes a vendorlib drop plus standard build invocations (motivating case 2026-07-08: tcludp 1.0.12 -> 1.0.13 for the G-036 wedge fix - `libs`, `vfscommonupdate` and `project` all completed while every kit vfs still bundled udp 1.0.12, requiring a manual copy into each vfs lib_tcl9 folder). Acceptance: with a newer package version placed under src/vendorlib_tcl9/, one documented make.tcl invocation updates the participating src/vfs/*/lib_tcl9 trees - installing the new package and removing or explicitly retiring the superseded version (no silent mixed-version provision, per the G-035 concerns) - with punkcheck-tracked provenance; which vfs folders participate is explicitly declared per kit rather than blanket-copied (kit vfs package sets may intentionally differ), with the declaration mechanism recorded (candidate home: the G-024 mapvfs toml); a subsequent `make.tcl project` yields kits loading the new version (provable via the tcludp case: built punk902z reports `package require udp` == 1.0.13 with no udp1.0.12 folder remaining in its vfs); the lib_tcl8 tree gets the same treatment or an explicit exclusion rationale in the goal record. -### G-038 [proposed] Piped-phase errors inspectable in the restarted interactive shell -Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (error capture at the reporting site and/or teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as needed for code-interp access) -Detail: goals/G-038-piped-error-retention.md -Goal: when piped stdin ends and app-punkshell restarts an interactive shell on the console, the last error from the piped phase is retained across the codethread/interp boundary and inspectable at the interactive prompt via a documented punk-owned record - not bare ::errorInfo, which boot-time internal caught errors (e.g. tcllib virtchannel's `catch {unset timer}`) clobber - with a one-line notice at the first prompt when such an error exists. -Acceptance: `'puts test;error xxxx' | shell` then, at the first interactive prompt, a documented command or variable yields the xxxx message, its errorInfo traceback and errorCode from the piped phase, and the first prompt is preceded by a one-line notice that a piped-phase error was retained; the retained record survives boot-time caught-error noise (proven against the motivating transcript, where plain `set errorInfo` showed virtchannel's `can't unset "timer"` instead); an error-free piped phase produces no notice and an explicitly-empty record; plain ::errorInfo semantics are otherwise unchanged; verified on both Tcl generations. +### G-038 [proposed] Piped-to-interactive restart continues the same session (context preserved) +Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (eof-restart done-mode that skips codethread teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as touched) +Detail: goals/G-038-piped-session-continuity.md +Goal: when piped stdin ends and app-punkshell opens the interactive console shell, the session continues rather than restarts - the piped phase's codethread/code interp survives (variables, procs, namespaces, loaded packages, cwd, ::errorInfo/::errorCode) and only the input channel and repl reader are renewed - so `'set ::jjj blah' | shell` leaves ::jjj inspectable at the prompt, replacing today's silent fresh-session swap (least-surprise violation; motivating transcripts in the detail file) and obsoleting the standalone piped-error-record mechanism this goal described before its 2026-07-08 rework. +Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. diff --git a/goals/G-038-piped-error-retention.md b/goals/G-038-piped-error-retention.md deleted file mode 100644 index 840b0c9e..00000000 --- a/goals/G-038-piped-error-retention.md +++ /dev/null @@ -1,56 +0,0 @@ -# G-038 Piped-phase errors inspectable in the restarted interactive shell - -Status: proposed -Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (error capture at the reporting site and/or teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as needed for code-interp access) -Acceptance: `'puts test;error xxxx' | shell` then, at the first interactive prompt, a documented command or variable yields the xxxx message, its errorInfo traceback and errorCode from the piped phase, and the first prompt is preceded by a one-line notice that a piped-phase error was retained; the retained record survives boot-time caught-error noise (proven against the motivating transcript, where plain `set errorInfo` showed virtchannel's `can't unset "timer"` instead); an error-free piped phase produces no notice and an explicitly-empty record; plain ::errorInfo semantics are otherwise unchanged; verified on both Tcl generations. - -## Context - -Motivating transcript (user, 2026-07-08): `'puts test;error xxxx' | punk902z shell` - the -piped phase prints `test` then the xxxx error, EOF triggers app-punkshell's CONIN$ restart, -and at the interactive prompt `set errorInfo` shows an unrelated -`can't unset "timer": no such variable ... "unset timer"` instead of the xxxx traceback. -The user had hoped the piped error would be retained for interactive inspection. - -Two mechanisms defeat the natural expectation: - -1. **Restart boundary**: the restarted shell is a fresh `repl::init`/`repl::start` with a new - codethread and code interp (app-punkshell do_shell). The piped phase's ::errorInfo lives in - run-1's code interp, which is torn down with its codethread. Nothing carries it across. -2. **::errorInfo is boot-noise-prone anyway**: a *caught* error still stamps ::errorInfo. - tcllib's `virtchannel_core/events.tcl` (`tcl::chan::events`) runs `catch { unset timer }` - as ordinary control flow, and the repl-exec log tee construction (fifo2 stacks during - repl::init) triggers it in the fresh code interp - so a brand-new shell's resident - ::errorInfo is whatever internal caught error happened last during boot. Restoring the - retained error into plain ::errorInfo would be clobbered by the same noise, hence the - punk-owned record in the goal statement (or injection strictly after boot completes - - but a dedicated record is robust and self-describing). - -## Approach (design points) - -- **Capture at the reporting site, not by scraping ::errorInfo at teardown**: the repl already - distinguishes command errors when it renders the tcl-err (lowercase `p%`) prompt path - that - is the natural place to record {message errorInfo errorCode command-string timestamp} for - the most recent errored command. Teardown-time capture of the code interp's ::errorInfo is - the fallback but inherits the noise problem above. -- **Handover**: the record must cross thread/interp teardown - candidates: tsv (already used - by the repl for runchunks), or app-punkshell capturing it from the run-1 exitinfo/donevalue - plumbing and seeding the run-2 code interp after init. -- **Surface**: a documented variable in the code interp (e.g. ::punk::last_pipeline_error) or - a small command; the first-prompt notice should be one line and only appear when a retained - error exists (piped runs that end cleanly stay quiet - agents/CI pipe scripts constantly). -- **Scope question to settle**: retain only across the piped->interactive restart, or also - expose "last error of the previous run" generally (subshell exits, `repl::start` restarts)? - The goal commits only to the piped->interactive case; wider retention can be recorded here - if it falls out naturally. -- Note the errorInfo-noise observation stands alone too: even without retention, - `set errorInfo` in a fresh punk shell reports internals' caught errors. Out of scope to - "fix" tcllib, but the dedicated-record design sidesteps it and the doc/help for the new - record can mention the caveat. - -## Notes - -- Related: G-036 detail (same eof-restart machinery, where the codethread/interp lifecycle is - mapped); G-015 (script subcommand is the agent-facing piped path and already reports errors - on stderr with honest exit codes - this goal is about the *shell* subcommand's interactive - restart, a human-facing convenience). diff --git a/goals/G-038-piped-session-continuity.md b/goals/G-038-piped-session-continuity.md new file mode 100644 index 00000000..c3d03bdf --- /dev/null +++ b/goals/G-038-piped-session-continuity.md @@ -0,0 +1,82 @@ +# G-038 Piped-to-interactive restart continues the same session (context preserved) + +Status: proposed +Scope: src/lib/app-punkshell/punkshell.tcl (eof-restart handover), src/modules/punk/repl-999999.0a1.0.tm (eof-restart done-mode that skips codethread teardown), src/modules/punk/repl/codethread-999999.0a1.0.tm (as touched) +Acceptance: after `'set ::jjj blah;error xxxx' | shell` reaches the interactive prompt: `set ::jjj` returns blah; the xxxx message, errorInfo traceback and errorCode are inspectable (via preserved ::errorInfo if nothing in the handover overwrites it, else a documented record - the chosen mechanism noted in the detail file); a proc defined and a package required during the piped phase remain available; a cwd change from the piped phase persists; a one-line notice at the restart says the session continued from piped input (mentioning the error when the last piped command errored, quiet otherwise); interactive exit/quit teardown afterwards is clean (the G-036 regression harness still passes); PUNK_PIPE_EOF policy semantics and the script subcommand are unchanged; verified on both Tcl generations. + +## Context + +Reworked 2026-07-08 from a narrower draft ("piped-phase errors inspectable in the restarted +interactive shell") after the user identified the general least-surprise violation: + +- `'set ::jjj blah' | punk902z` -> interactive prompt -> `set ::jjj` fails: no such variable. +- `'puts test;error xxxx' | punk902z shell` -> `set errorInfo` at the prompt shows unrelated + boot noise (`can't unset "timer"` from tcllib virtchannel_core's `catch { unset timer }`, + stamped during the fresh repl::init's fifo2 log-tee construction) instead of xxxx. + +Cause: app-punkshell's piped-EOF handling (the path that actually runs for pipes, since a +pipe is not tcl_interactive) rebuilds the shell from scratch - `do_shell` runs a fresh +`repl::init` (new codethread + code interp) and `repl::start`. Everything the piped phase +did in the code interp - variables, procs, namespaces, package requires, cwd, errorInfo - +is silently discarded. The user's mental model is "my session continues interactively", +i.e. the same expectation a tclsh user has typing those commands then continuing. + +Precedent in the codebase: the repl-internal restart path (`repl::reopen_stdin`) is +context-preserving by design - it reuses the live codethread and restarts the repl on the +reopened console channel. Its problem is structural (it nests a second repl::start inside +the dying repl's event stack, sharing ::repl::done/codethread state - see the G-036 detail +file's early analysis of that path's races), not conceptual. + +## Approach + +The caller-driven restart already identified during G-036 as the clean fix direction: + +1. `repl::start` gains an eof-restart outcome that returns WITHOUT tearing down the + codethread (today's teardown at the end of repl::start unconditionally cancels it - + the exit/quit/eof done-values must branch). +2. app-punkshell's eof branch, on deciding to go interactive (existing PUNK_PIPE_EOF + policy unchanged), opens CONIN$/SetStdHandle as today but then calls `repl::start` + again on the new channel WITHOUT `repl::init` - same codethread, same code interp, + session continues. (do_shell currently bakes `repl::init` into the script it runs - + the restart invocation needs a variant.) +3. What renews vs persists: + - renewed: input channel + repl reader, tcl_interactive, prompt state; the anchored + default console object must be rebound to the new channels (known stale-after-reopen + comment at repl-999999 ~line 3168; adjacent to G-001/G-011 console-object work). + - persists: everything in the codethread/code interp, including `after` timers and + fileevents registered by the piped script (a feature, but note it as a behaviour + change - a piped script's scheduled work now keeps running into the interactive + session; the notice line mitigates surprise). + - decide: repl-side editbuf/history fresh or carried (fresh is acceptable; record the + choice). +4. Error inspectability likely falls out free: with no re-init there is no boot-noise + re-stamping, so the piped phase's ::errorInfo is simply still present. If some handover + step does overwrite it, fall back to the superseded draft's mechanism: capture at the + repl's error-reporting site (the lowercase p% prompt path) into a punk-owned record + (e.g. ::punk::last_pipeline_error) surfaced with the notice line. Note regardless: + bare ::errorInfo in a long-lived shell is always subject to later caught-error + stamping by internals - the notice line should show the error message itself so the + user need not race to inspect. + +Risks / interactions: +- The teardown-mode branching touches exactly the machinery mapped during G-036 + (shellthread worker lifecycle, shellfilter::run tail, thread teardown ordering) - the + G-036 regression harness is the safety net and is in this goal's acceptance. +- shellfilter::run/do_shell wrap the repl in tee stacks per invocation; the restart + variant must not double-stack or leak them across the handover. + +## Alternatives considered + +- Standalone piped-error record without session continuity (this goal's original draft) - + superseded by the rework: it fixed one symptom (error inspection) while leaving the + general context loss (::jjj case) in place, and continuity likely obviates the record. +- Keeping fresh-session semantics but documenting them + printing a "new session" notice - + rejected as the primary outcome (documentation doesn't restore the lost work), though + the notice-line idea is retained within the continuity design. + +## Notes + +- Related: G-036 detail (restart machinery map, teardown races, regression harness); + G-002 (non-nested subshell architecture - same family of repl lifecycle decoupling); + G-031 (componentized boot); G-001/G-011 (console object rebinding on reopen); + G-015 (script subcommand unaffected - it never drops to interactive).