diff --git a/GOALS.md b/GOALS.md index 554d02b0..a80c7b6c 100644 --- a/GOALS.md +++ b/GOALS.md @@ -404,3 +404,7 @@ Detail: goals/G-174-cmdtraverse-word-accounting.md Scope: src/modules/punk/args-999999.0a1.0.tm (resolve @dynamic cache-skip, by-id entry points get_spec/parse_status/arg_error/synopsis); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp render pipeline - the four independent by-id fetches); src/tests/modules/punk/args/testsuites/args/dynamic.test + src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (once-per-render counter pins + cross-render freshness pins, if the landing arm is taken) Detail: goals/G-175-punkargs-dynamic-resolve-multiplicity.md +### G-176 [proposed] commandstack doc integration - stack-tied overrider argdocs, override hints, and the libunknown package-override migration +Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.2.4.tm (package-override migration to commandstack - fixed-version file, renamed on bump); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins) +Detail: goals/G-176-commandstack-doc-integration.md + diff --git a/goals/G-176-commandstack-doc-integration.md b/goals/G-176-commandstack-doc-integration.md new file mode 100644 index 00000000..a719166a --- /dev/null +++ b/goals/G-176-commandstack-doc-integration.md @@ -0,0 +1,135 @@ +# G-176 commandstack doc integration - stack-tied overrider argdocs, override hints, and the libunknown package-override migration + +Status: proposed +Scope: src/modules/commandstack-999999.0a1.0.tm (rename_command -punkargs option, stackdocs pending-docs namespace, remove_rename detach hook, help/argdoc how-to); src/modules/punk/libunknown-0.2.4.tm (package-override migration to commandstack - fixed-version file, renamed on bump); src/modules/punk/ns-999999.0a1.0.tm (cmdhelp/cmdinfo override-in-place hint); src/tests/modules/punk/libunknown/testsuites/discovery/ (override characterisation arms - discovery.test extensions or a sibling packageoverride.test); src/tests/modules/commandstack/testsuites/commandstack/commandstack.test (-punkargs lifecycle pins); src/tests/modules/punk/ns/testsuites/ns/cmdhelp.test (override-hint pins) +Goal: a command overrider using commandstack can attach punk::args doc blocks that live and die with its stack entry (supplied at rename_command time, active whether or not punk::args is loaded yet, removed by every removal path), cmdhelp renders an override-in-place hint on any commandstack-managed command's existing documentation (naming the renamers and any doc ids their records added), and the punk::libunknown ::package override - the flagship consumer, whose 'package epoch' subcommand gains real documentation this way - is migrated onto commandstack so both live ::package overrides share one managed stack. +Acceptance: characterisation tests of the CURRENT libunknown override land before any behaviour change and stay green byte-identically through the migration (the epoch tm/pkg single-arg forms, indexed ?keys...? queries and their error arms, the forget arm incl the tcl/Tcl/tcl::oo/tk denial and epoch-scripts pruning, abbreviation acceptance (ep/epo/epoc, fo/for/forge), a delegation pass-through pin, the re-init guard, and a packagepreference interop child covering both install orders) with only explicitly marked install-shape pins (::package import link, ::package:: parked name) flipping when the migration lands; libunknown installs its ::package override via commandstack::rename_command with a stack-query install guard mirroring punk::packagepreference and no ::package:: residue; commandstack::rename_command accepts a -punkargs list of definitionlists whose docs are queryable while the record lives - including renames performed BEFORE punk::args loads (pinned in a bare child that loads punk::args afterwards) - and are removed by remove_rename, pop_rename, remove_renamer and restore_original alike (record key appended per the key-order contract, reload-contract guard on new state); punk::ns::cmdhelp shows an override-in-place notice for a commandstack-managed command (cmdinfo carrying it as a second-axis key in the G-166 (achieved, see goals/archive/G-166-cmdinfo-unavailable-surfacing.md) style) without a hard commandstack dependency, enumerating record-attached doc ids; 'i package epoch' resolves the libunknown-attached doc in a punk shell and the mechanism how-to is described in commandstack::help and the rename_command argdoc; punk/libunknown, commandstack, punk/ns and punk/packagepreference suites pass under the canonical runtests interpreter. + +## Context + +Feasibility investigated 2026-08-08 (user-directed; probe evidence below from tclsh90s +with src modules + tclcore moduledoc). Motivating observation: punk::libunknown extends +::package with an 'epoch' subcommand that has no punk::args documentation, and an +overrider currently has no way to attach docs whose lifetime matches the override. + +Probe-verified doc mechanics (the punk::args side needs NO changes): + +- A space-form id `{::package epoch}` defines cleanly and punk::args::define RETURNS + the id. `i package epoch` resolves the new doc as a focused render EVEN THOUGH + 'epoch' is absent from the parent ::package definition's restricted subcommand + choices - the G-051 space-form docid jump (achieved, see + goals/archive/G-051-cmdinfo-pseudo-and-prefix.md) bypasses the choice screen. + `i package` is unaffected. undefine (quiet flag available) removes cleanly and + `i package epoch` falls back to the parent render; re-define cycles are clean. +- punk::args::undefine_deflist is deflist-keyed - commandstack never parses ids. +- Recorded limitation: the parent def's choice TABLE never lists 'epoch' and parsing + 'epoch' against ::package fails its choice restriction - mitigated by the hint + mechanism enumerating record-attached doc ids on the parent render. + +commandstack architecture facts (as at 2026-08-08, commandstack 0.7.1): + +- Record keys are APPEND-only extensible by documented contract (token/renamer at + lsearch -index 1/-index 3; 'additive keys must be APPENDED'). +- EVERY removal path funnels through remove_rename (pop_rename :884, + remove_renamer :936, restore_original :996 all delegate) - one detach choke point + at the doomed_record site; one attach point in rename_command's success path. +- Early boot: commandstack renames happen BEFORE punk::args loads. The inert + registration pattern (namespace eval ::punk::args::register {lappend NAMESPACES ...} + - used by libunknown itself at :2175) covers this: a ::commandstack::stackdocs + namespace PUNKARGS variable mirrors live records' deflists; punk::args consumes it + lazily on first doc query. When punk::args is ALREADY loaded the attach must also + define immediately (a namespace already consumed into loaded_packages never + lazy-loads later appends; redefinition is idempotent). +- The reload contract (0.7.1) preserves module state across re-source via info-exists + guards - new stackdocs state needs the same guard. + +libunknown override facts (libunknown-0.2.4.tm, line anchors point-in-time): + +- Current install (:1692-1822): `proc package` in ::punk::libunknown + + `rename ::package ::package::` + NAMESPACE IMPORT into :: - the import is + load-bearing (an imported proc executes in its origin namespace, resolving the + body's bare epoch_incr_pkg/epoch_incr_tm calls and `variable` statement). A + commandstack::rename_command migration was started and commented out (:1689). +- Migration deltas are enumerable: qualify 4 bare epoch_incr_pkg/tm calls (a + commandstack-installed proc runs in ::), swap the 2 `::package::` delegation sites + (:1734 forget, :1816 default) for COMMANDSTACKNEXT, drop the DEAD + `variable has_package_files` declaration (:1695 - its only use :1709 is commented + out; the :143-147 module-load probe stays), replace rename+import with + rename_command + a stack-query install guard (punk::packagepreference::install + :110-137 is the exact in-tree precedent incl the G-160 (achieved, see + goals/archive/G-160-commandstack-hygiene.md) silent re-install return), keep the + :1574 info-commands re-init guard semantics. +- NO external ::package:: callers exist anywhere in the tree (grepped src/modules + + tests) - the parked-name change is libunknown-internal. +- Latent hazard the migration cures: in a punkshell boot packagepreference installs + its ::package wrapper via commandstack FIRST (app entrypoints, e.g punkshell.tcl:9) + and libunknown's plain rename lands ON TOP (repl-triggered init) - commandstack's + ::package stack then believes the wrapper is live at ::package while libunknown's + import actually is; restore_original or a third-party rename_command would operate + on the wrong command and strand ::package::. make.tcl (:1748) runs libunknown with + NO packagepreference - both orders occur, and one managed stack handles either + (multi-renamer any-order removal is commandstack.test-pinned). +- Hot-path note: rename_command's injected header resolves COMMANDSTACKNEXT via + get_next_command on every invocation - packagepreference already pays exactly this + on the same hot command (every package require); precedent accepted. + +Coverage audit (2026-08-08): discovery.test pins six epoch arms (0-arg dict shape, +tm-paths/auto_path trace increments, `epoch incr`, cache-blocks-until-incr, +register_all_tm per-epoch no-op) plus libsearch.test indirect use. UNTESTED: the +`epoch tm`/`epoch pkg` single-arg forms, indexed queries + error arms, the entire +forget arm, abbreviation acceptance (the override accepts prefixes like 'ep' that +the builtin would reject - an uncharacterised behaviour delta), delegation as a +contract, the install shape, the re-init guard, and any packagepreference interop. + +## Approach + +Increments land as separate commits, each leaving all suites green: + +1. Characterisation: pin every untested arm of the CURRENT override (list in + Acceptance) in punk/libunknown testsuites (discovery.test extensions or a sibling + packageoverride.test - child-interp pattern, children source libunknown AND + commandstack by path). Install-shape pins (::package import link, ::package:: + parked name) carry explicit marked-to-flip comments. +2. Migration: libunknown installs via commandstack::rename_command (deltas per + Context). Only the marked install-shape pins flip; every behaviour pin stays + green unchanged. Decision recorded in this file: hard `package require + commandstack` (lean, dependency-free, present in bootsupport + kits) - no + plain-rename fallback path (two install behaviours would double the test + surface). Fixed-version file rename on bump: git mv + fossil mv. +3. commandstack -punkargs mechanism: leading option on rename_command (beside + -renamer) taking a list of definitionlists; record key `punkargs` appended; dual + activation (stackdocs PUNKARGS variable + immediate define when punk::args + present); detach in remove_rename via undefine_deflist (quiet) + variable + removal; reload guard; help/argdoc how-to; commandstack.test pins incl the + pre-punk::args pending path (bare child renames with -punkargs, then loads + punk::args and queries). +4. Override hint + flagship docs: cmdinfo gains a second-axis override key sourced + from a guarded live commandstack::get_stack query (no hard dependency); + cmdhelp renders the notice naming renamers + record-attached doc ids; libunknown + attaches the `{::package epoch}` doc block via -punkargs (retiring the doc gap + that motivated the arc); cmdhelp.test pins; `i package epoch` verified in a punk + shell. + +## Notes + +- Same-id shadowing: one live record per doc id is the documented constraint + (natural usage - each overrider documents its OWN added subcommands); no + re-instatement machinery unless a real consumer needs it. +- Rename_stack parking: default decision is detach-on-park (docs describe a command + that no longer answers); revisit if an unpark consumer appears. +- The abbreviation-acceptance delta (override accepts ep/fo prefixes the builtin + rejects) is characterised as CURRENT behaviour in increment 1 - whether to keep it + is a separate question, deliberately out of scope here (the migration must not + silently change it). +- Mechanism B's optional heuristic for UNMANAGED overrides (doc @cmd -name says + 'Built-in:' while the live command is a proc/import) is out of scope - after the + increment-2 migration both live ::package overrides are managed. +- The tclcore parent ::package definition is untouched: 'epoch' stays outside its + restricted subcommand choices; the hint line plus the space-form doc are the + contract. Extending parent choice tables from override records is a possible + follow-on, not part of this goal. +- G-058 static-package interplay (achieved, see + goals/archive/G-058-static-runtime-packages.md): the packagepreference wrapper + body consulted in Context is the same one the interop child exercises - its + COMMANDSTACKNEXT_ORIGINAL fast-path is behaviour the interop pins must not + disturb.