Browse Source

punk::args 0.25.2 + tclcore moduledoc 0.4.2: qualify ensemble names into ensemble_subcommands_definition - 'i ::tcl::prefix' unqualified-ns warnings gone; G-175 drafted (project 0.62.2)

'i ::tcl::prefix' emitted 12x "warning: update_definitions received
unqualified ns: tcl" on stderr (6x for subcommand renders). Root cause:
the tclcore moduledoc's @dynamic ::tcl::prefix definition passed the
ensemble name to punk::args::ensemble_subcommands_definition UNQUALIFIED
('tcl::prefix'). The generator's space-form id_checks derive namespaces
via 'namespace qualifiers' - for a relative multi-component name that
yields a non-empty UNQUALIFIED namespace ('tcl') which the
empty-qualifier guard ('' -> ::) never catches - and update_definitions
warned once per subcommand per resolve. @dynamic definitions re-expand
on every resolve and the cmdhelp pipeline resolves the id four times
(parse_status spec fetch + its internal parse, cmdhelp's get_spec,
arg_error/synopsis), so 4 resolves x 3 subcommands = the reported 12.

tcl::prefix was the ONLY affected ensemble: all tclcore ensemble docs
pass unqualified names, but the single-component ones (info/dict/file/
namespace/array/encoding/zipfs) derive an empty qualifier which was
already mapped to ::.

Both fixes:
- tclcore call site now passes ::tcl::prefix.
- ensemble_subcommands_definition normalizes its ensemble argument to
  fully-qualified in the CALLER's context (uplevel namespace which, ::
  prepend fallback) before deriving anything - covering any future
  relative multi-component caller.

Verified: zero warnings at both depths; rendered help byte-identical
(6301/5585 chars); generator snippet identical for qualified vs
unqualified input. New pin ensembledef.test
ensembledef_unqualified_ensemble_name_no_warnings (fixture two-component
ensemble called relatively under stderr capture; capture_stderr helper
added to the file). src/tests/modules/AGENTS.md index updated
(ensembledef.test now listed).

G-175 drafted (proposed):
goals/G-175-punkargs-dynamic-resolve-multiplicity.md - the warning was
the only signal of the 4x-per-render @dynamic resolve multiplicity; the
goal records the verdict question (render-scoped single resolution vs
pinned accept), the attributed resolve sites, the measured cost floor
(redundant builder calls ~3ms on 'i ::tcl::prefix', ~22ms on 'i ::dict'
steady-state; the full argdata re-processing share is unmeasured - first
investigation step), and candidate mechanisms smallest-first. GOALS.md
indexed.

punk::args 0.25.1 -> 0.25.2, punk::args::moduledoc::tclcore 0.4.1 ->
0.4.2, project 0.62.1 -> 0.62.2 + CHANGELOG (user-visible repl stderr
noise fix).

Suites: testbody_lint 1688 clean; goals_lint clean (79 active / 96
archived); modules tree 1335 total / 1324 pass / 11 constraint-skipped /
0 fail (zig-built tclsh90s 9.0.5); make.tcl projectversion consistency +
staleness OK.

Claude-Session: https://claude.ai/code/session_01QgaxV27VZkmEec7oNbEVFc
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
master
Julian Noble 5 days ago
parent
commit
b82827d837
  1. 16
      CHANGELOG.md
  2. 4
      GOALS.md
  3. 92
      goals/G-175-punkargs-dynamic-resolve-multiplicity.md
  4. 2
      punkproject.toml
  5. 13
      src/modules/punk/args-999999.0a1.0.tm
  6. 3
      src/modules/punk/args-buildversion.txt
  7. 2
      src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm
  8. 3
      src/modules/punk/args/moduledoc/tclcore-buildversion.txt
  9. 2
      src/tests/modules/AGENTS.md
  10. 47
      src/tests/modules/punk/args/testsuites/args/ensembledef.test

16
CHANGELOG.md

@ -5,6 +5,22 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to
Entries are newest-first; one bullet per notable change. See the root `AGENTS.md`
"Project Versioning" section for the bump policy.
## [0.62.2] - 2026-08-08
- `i ::tcl::prefix` no longer emits 12x "warning: update_definitions
received unqualified ns: tcl" on stderr (nor 6x on subcommand renders):
the tclcore moduledoc passed the ensemble name to
`punk::args::ensemble_subcommands_definition` unqualified, and the
generator's space-form id checks derived the non-empty unqualified
namespace `tcl` from it once per subcommand per `@dynamic` re-resolve.
The tclcore call site now passes `::tcl::prefix`, and the generator
itself normalizes its ensemble argument to fully-qualified in the
caller's context - covering any future relative multi-component caller.
Rendered help verified byte-identical. tcl::prefix was the only ensemble
affected: single-component names (`info`, `dict`, `file`, ...) derive an
empty qualifier that was already mapped to `::`. (punk::args 0.25.2,
punk::args::moduledoc::tclcore 0.4.2)
## [0.62.1] - 2026-08-08
- `grepstr` works with dash-led patterns again: the capture-group probe

4
GOALS.md

@ -400,3 +400,7 @@ Detail: goals/G-171-bake-writes-land-untracked.md
Scope: src/modules/punk/ns-999999.0a1.0.tm (cmd_traverse/cmdinfo doc-lookup walk, cmdhelp advisory parse + synopsis consumers); src/tests/modules/punk/ns/testsuites/ns/{cmdflow,cmdhelp}.test (conservation characterisation + consumer pins); src/modules/punk/ansi-999999.0a1.0.tm (punk::ansi::a? argdoc - real-world witness, edits only if the sample-form tightening decision lands)
Detail: goals/G-174-cmdtraverse-word-accounting.md
### G-175 [proposed] @dynamic resolve multiplicity - one render, one resolution (verdict + optional landing)
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

92
goals/G-175-punkargs-dynamic-resolve-multiplicity.md

@ -0,0 +1,92 @@
# G-175 @dynamic resolve multiplicity - one render, one resolution (verdict + optional landing)
Status: proposed
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)
Goal: a verdict with evidence on whether re-resolving @dynamic definitions multiple times within ONE user-level render (the 'i <cmd>' pipeline) is architecturally required - and if not, either a render-scoped single-resolution mechanism lands (each @dynamic definition's substitution scripts run once per cmdhelp invocation, byte-identical output, cross-render freshness untouched) or the per-pass re-resolution is pinned as a recorded decision with its measured cost, the rationale written in this file.
Acceptance: the resolve-level cost share of the multiplicity is measured (not just the subcommand-builder share - each pass re-runs full argdata processing of the definition text) and recorded here; the by-id fetch sites in the cmdhelp pipeline are enumerated with which could accept a pre-resolved spec; a decision is recorded - EITHER a landed mechanism with pins (a counter fixture proving a dynamic definition's substitution scripts run exactly once per cmdhelp render; byte-identical render output; a mutating fixture proving a SECOND render still observes changed state - the @dynamic freshness contract across renders survives) OR a pinned-as-accepted rationale; punk/args and punk/ns suites pass under the canonical runtests interpreter either way.
## Context
Surfaced 2026-08-08 while root-causing the 'i ::tcl::prefix' unqualified-ns stderr
warnings (12 = 4 resolves x 3 subcommands - the warning fix landed separately,
punk::args 0.25.2 / tclcore 0.4.2). The warning was the only SIGNAL that the
::tcl::prefix @dynamic definition was being re-resolved 4 times in a single 'i'
render; with it fixed, the multiplicity is silent.
Probe evidence (trace on the tclcore tclprefix_subcommands builder, tclsh90s,
2026-08-08 - line anchors point-in-time):
RESOLVE#1 via resolve < get_spec < parse_status < cmdhelp
RESOLVE#2 via resolve < get_dict < parse < parse_status < cmdhelp
RESOLVE#3 via resolve < get_spec < cmdhelp
RESOLVE#4 via resolve < get_spec < synopsis < arg_error < cmdhelp
Four independent by-id fetches: parse_status resolves twice on its own (spec fetch
plus its internal parse), cmdhelp fetches the spec for rendering, and the
arg_error/synopsis path fetches again. Each fetch is correct in isolation; nothing
shares the resolved spec across the pipeline.
Architecture (args-999999.0a1.0.tm as at 2026-08-08): resolve consults the
rawdef_cache_argdata cache ONLY for non-dynamic definitions (`if {!$is_dynamic}`
~args:2175) - a @dynamic definition re-runs its tstr command substitutions AND the
full argdata processing on every resolve. That per-resolve re-expansion is the
documented contract (define -help: "@dynamic definitions re-expand on every
resolve" / "Use @dynamic only when the value can change between resolves") and is
what keeps e.g ensemble subcommand choice-tables live when ensembles are extended
at runtime. ALL tclcore ensemble docs are @dynamic (info, encoding, dict, file,
namespace, array, zipfs, tcl::prefix), so every 'i <core-ensemble>' pays the
multiplicity.
Measured cost (steady-state second render, warmed lazy loads, tclsh90s):
i ::tcl::prefix total ~196ms; subcommand-builder 4 calls = 4.4ms (2%)
i ::dict total ~701ms; subcommand-builder 4 calls = 29.8ms (4%)
The builder share is the FLOOR of the waste (3 redundant calls of 4): the full
argdata re-processing per redundant resolve is not yet isolated - measuring it is
the first investigation step. Note the totals themselves: the multiplicity sits
inside renders already costing 200-700ms.
## Approach
1. Measure the resolve-level share: time resolve for a representative @dynamic
definition (tcl::prefix small, dict large) and multiply out the redundant
passes; record here. If the whole-render saving is negligible even for dict,
the pin-as-accepted arm is available cheaply.
2. Enumerate the by-id entry points in the cmdhelp pipeline (parse_status x2,
get_spec, arg_error/synopsis) and determine which can accept a pre-resolved
spec or share a resolution without API breakage (several already have
spec-shaped internal forms).
3. Candidate mechanisms, smallest-first:
a. cmdhelp resolves once and passes the resolved spec down (plumbing change,
punk::ns-side; punk::args API additions only where a by-id entry lacks a
by-spec form).
b. a render-scoped resolution context in punk::args (explicit begin/end or
token-passed), dynamic argdata memoized within the context only.
c. epoch/generation-keyed short cache - REJECT-by-default: invalidation
semantics are exactly the hard part, and the @dynamic contract is
per-resolve freshness.
4. Whichever arm: pin the outcome (once-per-render counter fixture + byte-identical
render + cross-render freshness for the landing arm; measured-cost rationale
recorded here for the accept arm).
## Notes
- Risk to respect: a @dynamic substitution script may observe state mutated
MID-render (in principle even by an earlier substitution). Render-scoped
memoization changes that observable - almost certainly acceptable (a render is
one logical moment), but it is the semantic delta to state explicitly in the
decision.
- The stale-colour REVIEW comment beside the non-dynamic cache consult
(~args:2176 "don't use cached version if 'colour off' vs 'colour on'
different...") shows even the static cache has known freshness caveats - any
new memoization should not widen that class.
- parse_status resolving twice by itself (spec + its internal parse) may be worth
collapsing independently of the dynamic question - it halves the multiplicity
for ALL definitions, static ones included (static hits the cache, so the win
there is small but the call-shape cleanup may pay anyway).
- Related arcs: the G-046 display-field masking (achieved, see
goals/archive/G-046-punkargs-deferred-help-and-fixes.md - expensive -help
processing deferred to display time) already splits parse-relevant from
display-relevant work - a render-scoped mechanism should compose with it, not
duplicate it.

2
punkproject.toml

@ -1,6 +1,6 @@
[project]
name = "punkshell"
version = "0.62.1"
version = "0.62.2"
license = "BSD-2-Clause"
url = "https://www.gitea1.intx.com.au/jn/punkshell"
#packager: declared identity for published artifacts (declarative, not proof -

13
src/modules/punk/args-999999.0a1.0.tm

@ -14320,6 +14320,19 @@ tcl::namespace::eval punk::args {
set opt_groupdict [dict get $optlist -groupdict]
set opt_columns [dict get $optlist -columns]
if {![string match ::* $ensemble]} {
#normalize to fully-qualified in the caller's context: the id_checks below
#derive namespaces via 'namespace qualifiers' - a relative multi-component
#name (e.g 'tcl::prefix') would send an unqualified ns ('tcl') to
#update_definitions, which warns on stderr for every subcommand.
set fqensemble [uplevel 1 [list ::tcl::namespace::which $ensemble]]
if {$fqensemble ne ""} {
set ensemble $fqensemble
} else {
set ensemble ::$ensemble
}
}
#warning - circular package dependency if we try to use this function on punk::ns!
package require punk::ns
set subdict [uplevel 1 [list punk::ns::ensemble_subcommands -return dict $ensemble]]

3
src/modules/punk/args-buildversion.txt

@ -1,6 +1,7 @@
0.25.1
0.25.2
#First line must be a semantic version number
#all other lines are ignored.
#0.25.2 - bugfix (reported 2026-08-08 as 'i ::tcl::prefix' emitting 12x 'warning: update_definitions received unqualified ns: tcl'): ensemble_subcommands_definition now normalizes its ensemble argument to fully-qualified in the CALLER's context (namespace which via uplevel, :: prepend fallback for a not-yet-existing command) before deriving anything from it. Previously a relative multi-component name (the tclcore moduledoc passed 'tcl::prefix') flowed into the space-form id_checks, whose 'namespace qualifiers' derivation produced a non-empty UNQUALIFIED namespace ('tcl') that the empty-qualifier guard did not catch - update_definitions then warned on stderr once per subcommand per call (and @dynamic definitions re-run their builders on every resolve: 4 resolves x 3 subcommands = the reported 12). Single-component ensemble names (info/dict/file/...) were never affected (empty qualifier -> :: guard). Rendered output verified byte-identical for both name forms. The tclcore moduledoc call site now passes ::tcl::prefix as well (tclcore 0.4.2). Pin: ensembledef.test ensembledef_unqualified_ensemble_name_no_warnings (fixture two-component ensemble called unqualified: zero warnings + snippet parity with the qualified call).
#0.25.1 - bugfix: the -type regex|regexp validator's 'regexp -about' lacked its -- end-of-options guard, so a legitimate dash-led regex VALUE (e.g '-group', a valid ARE matching its literal self) was falsely rejected as 'requires type regexp' with regexp's own bad-option text embedded as the reason. Dash-led regexes now validate; a genuinely invalid dash-led regex still fails as a type mismatch with the real compile reason. Found via the 2026-08-08 grepstr dash-led pattern regression report (punk::ansi 0.2.1 carries the sibling 'regexp -about' fix in grepstr itself; the parse/allocation layer was verified NOT at fault). New validation.test pins: type_regexp_dashled_value (accept + real-reason-reject) and opt_dashword_seats_when_values_require_it (the starved-values dash-led seating rule grepstr relies on - no -- marker needed when word supply equals required-values demand).
#0.25.0 - G-053 allocation participation (directed work, post-G-053): bounded -multiple occurrence ranges now PARTICIPATE in positional allocation for leaders and values instead of being post-loop enforcement only. Previously greedy collection overran a bounded max unless a type screen happened to stop it (untyped {3 3} followed by a -multiple tail collected 4 words and occurrencecount then reported the overrun the allocation itself created), and a later REQUIRED ranged argument was starved to a single reserved clause. Three cooperating sites: (a) get_dict_can_assign_value caps a satisfied -multiple argument at its bounded max - it yields the word (no-consume) so the caller's retreat advances to the next argument, serving both the leaders and values loops; the yield carries a G-082 rejection record of new kind 'occurrence', and both loops' overflow selection sites render it as the pointed occurrence-limit report with the G-053 occurrencecount errorcode (count = the attempted occurrence) instead of the generic toomanyarguments - so genuine over-supply keeps its pointed class; (b) reservations are min-occurrence aware - the allocator's tail_needs and the derived valmin floor (leader/value split + option-scan reservation) reserve min-occurrences x min-clause-length for a required ranged argument (e.g a following required -multiple {3 3} reserves 3 clauses; {2 2} pair clauses reserve 4 words); an explicit '@values -min' still overrides the derived floor, and legacy boolean -multiple (no MULTIPLE_RANGES entry) reserves exactly as before; (c) the greedy leader scan caps a bounded -multiple last leader at max occurrences (tracked via leader_posn_names_assigned) so remaining words flow to the opts/values sections. First occurrences are never capped (resolve guarantees range max >= 1); {0 1} at-most-once scalars are unaffected (no collection). define -help -multiple documents the allocation semantics. New multipleranges.test allocation pins: untyped/typed {3 3} cap incl the cap-beats-type-screen 4-ints case, greedy-up-to-max {1 2}, required-range and pair-clause reservations, leaders-side cap + split floor, pointed over-supply report, legacy-greed-unchanged guard. Full punk/args suite 408/0.
#0.24.0 - G-084 increment 2 (parsekey completeness, part 2 - the settled cross-member design + integrity closes): -multiple on a member of a shared-parsekey OPTION group is now a DEFINE-TIME error unless ALL members belong to one @opts group marked -parsekeymode error (whose G-083 mutual exclusivity makes per-member collection well-defined; a single -multiple member with its own parsekey is unrestricted). This settles the G-084 cross-member accumulation design decision as rejection rather than accumulate-in-received-order: cross-member collection on one storage key is ambiguous (collect-vs-replace undefined; one member's collected list would silently displace another's). The check runs AFTER the G-053 -multiple canonicalisation so range forms get correct verdicts: {0 1} is replace-shape and allowed on a shared group, collect shapes (boolean 1, max>1, unbounded) are rejected. Value -parsekey integrity settled at define time: duplicate value parsekeys are rejected (trailing values consume positionally so distinct values sharing one result slot silently overwrote each other), and a dash-led value parsekey is rejected (it landed in the options namespace of the result/received dicts and ABORTED parse with an internal error in the option-shaped validation paths). parse_status/parse_report storage-key attribution: parse_status_build now resolves a storage key claimed by exactly ONE argument (a value's -parsekey, an option's declared -parsekey or its '='-suffixed trimmed tail) back to that argument, so argstatus/Source report received with the value-in-effect instead of default/0; shared-parsekey group members stay unresolved (per-member attribution does not survive the storage fold - the remaining recorded G-084 display gap, needs engine-level per-member tracking in the parse result; parse_report's display bridge still shows the group value on each member row). undefine/undefine_deflist now return "" (previously returned the final 'dict unset' expression - the WHOLE cache dict, hundreds of KB, to any caller capturing the value). define -help now documents -parsekey (storage-key rename for options and values, leader rejection, shared groups and -parsekeymode interaction, last-defined-member-wins defaults precedence, the -multiple rule, value parsekey uniqueness/non-dash rules). Internal: dead values-loop identifiers removed (val_ident/val_ident_is_parsekey/values-side seen_pks - assigned per word, never read since the api_valname keying landed), the per-parse parsekey reverse map now iterates only VAL_NAMES (leader map removed entirely - a leader -parsekey is rejected at define time), a dead parsekey-from--default derivation removed in resolve, stale pre-G-084 comments corrected. parsekey.test: parsekey_shared_key_multiple_rejected strengthened (message pins, {0 1}-allowed/{0 4}-rejected range coverage, the -parsekeymode error escape hatch incl collection and optionconflict), new parsekey_value_parsekey_integrity; parsereport.test parsereport_storagekey_attribution + parsestatus.test parsestatus_storagekey_attribution pin the attribution (incl the shared-group default/default remaining-gap row). Full punk/args suite 401/0.

2
src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm

@ -4408,7 +4408,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# ---------------------------------------------------------------------------------------------------------------------------
proc tclprefix_subcommands {} {
dict set groups "" {all longest match}
return [punk::args::ensemble_subcommands_definition -groupdict $groups -columns 1 tcl::prefix]
return [punk::args::ensemble_subcommands_definition -groupdict $groups -columns 1 ::tcl::prefix]
}
set DYN_TCLPREFIX_SUBCOMMANDS {${[punk::args::moduledoc::tclcore::argdoc::tclprefix_subcommands]}}
lappend PUNKARGS [list {

3
src/modules/punk/args/moduledoc/tclcore-buildversion.txt

@ -1,6 +1,7 @@
0.4.1
0.4.2
#First line must be a semantic version number
#all other lines are ignored.
#0.4.2 - bugfix (reported 2026-08-08 as 'i ::tcl::prefix' emitting 12x 'warning: update_definitions received unqualified ns: tcl' on stderr): the ::tcl::prefix @dynamic definition's subcommands builder passed the ensemble name to punk::args::ensemble_subcommands_definition UNQUALIFIED ('tcl::prefix'). The generator's space-form id_checks derived 'namespace qualifiers' from it - 'tcl', non-empty and unqualified - and update_definitions warned once per subcommand per dynamic re-resolve (4 resolves x 3 subcommands = 12 for 'i ::tcl::prefix'; 2 x 3 = 6 for a subcommand render). tcl::prefix was the ONLY tclcore ensemble doc with a multi-component name - the single-component names (info/dict/file/...) derive an EMPTY qualifier which the generator already mapped to ::. Now passes ::tcl::prefix. Sibling hardening: punk::args 0.25.2 normalizes the ensemble argument to fully-qualified in the generator itself (caller-context namespace which), covering any future relative multi-component caller; rendered output verified byte-identical. Pin: punk/args ensembledef.test ensembledef_unqualified_ensemble_name_no_warnings.
#0.4.1 - G-166: on a runtime that LACKS a curated forward class, that class's per-class virtual id now LEADS its help with the unavailability statement ('NOT AVAILABLE in this Tcl runtime (<patchlevel>). Recognised forward class: ... but 'string is dict' is rejected by this interpreter.'), followed by a blank line and the unchanged static description. The static description already carried the version note - but at its END ('(class not present in Tcl 8.6)' is its twelfth line), so a reader of 'i string is dict' on 8.6 met the full class documentation before learning the class does not exist there. Runtimes where the class is live generate the id unchanged (the unavailable set is empty on 8.7/9.x). Pinned by tclcoreparity.test tclcoreparity_stringis_unavailable_virtualid_leads_with_note (live-derived: affected arm on 8.6, unchanged arm on 8.7/9.x).
#0.4.0 - G-073 'string is' forward-class adoption: a curated forward-class list (dict; 'unicode' deliberately excluded - unreleased-8.7-only, removed in Tcl 9) is diffed against the harvested live class set at define time, and classes this runtime lacks are declared -choiceunavailable (punk::args 0.16.0 - the require gains that floor). Effect on 8.6: dict displays among the classes under the Unavailable group with its static description ("class not present in Tcl 8.6"), 'string is dict' is rejected with the tailored choiceunavailable error naming that note, 'string is di' is ambiguous (deliberately stricter than real 8.6, which accepts 'di' as digit - preparing users for 9.x where dict makes it genuinely ambiguous; recorded as a user-sanctioned parity exemption in tclcoreparity.test, full words parity-true), and 'i string is dict' documents the class via its per-class virtual id (the choicelabels build and the virtual-id loop now cover unavailable classes from the same static descriptions). Modern runtimes (8.7/9.x - dict live) compute an empty unavailable list and behave identically to 0.3.4.
#0.3.4 - G-074: the documented cancelid/cancelscript overlap on ::after cancel is sanctioned via the new @form -overlapallowed key (punk::args 0.12.0) - punk::args::formcheck now reports it as an acknowledged (sanctioned) structural overlap rather than an actionable finding, leaving ::after with zero unsanctioned findings. Parse behaviour unchanged: an id-shaped 'after cancel' word still raises multipleformmatches (the runtime-liveness ambiguity real Tcl resolves by trying the id first - 0.3.0 record). ::lseq deliberately NOT sanctioned: its range/start_count and range/count overlaps are type-weakness findings (the expr-typed end slot swallows the 'count'/'by' discriminator words) - kept visible pending an expr syntax-validating type (G-069/G-070 territory, G-055 operand-typing record).

2
src/tests/modules/AGENTS.md

File diff suppressed because one or more lines are too long

47
src/tests/modules/punk/args/testsuites/args/ensembledef.test

@ -22,6 +22,31 @@ namespace eval ::testspace {
set result ""
}
#capture stderr written during script (chan push transform; popped in all paths)
proc capture_stderr {script} {
variable caught ""
chan push stderr {apply {{cmd chan args} {
switch -- $cmd {
initialize {return {initialize finalize write}}
finalize {return}
write {append ::testspace::caught [lindex $args 0]; return ""}
}
}}}
set code [catch {uplevel 1 $script} r ropts]
chan pop stderr
if {$code} {
return -options $ropts $r
}
if {[string first \x00 $caught] >= 0} {
#stderr backed by a windows console delivers a utf-16le wide-char stream to
#the transform - decode (see the sibling helpers in punk/args dynamic.test
#and punk/ansi grepstr.test)
set enc [expr {"utf-16le" in [encoding names] ? "utf-16le" : "unicode"}]
set caught [encoding convertfrom $enc $caught]
}
return $caught
}
#fixture: module-style ensemble whose subcommand argdocs are registered for lazy
#loading (as punk::ansi does for ::punk::ansi::ansistring) but never explicitly
#loaded by this file before the generator runs.
@ -77,5 +102,27 @@ namespace eval ::testspace {
-cleanup {
}\
-result [list 1 1 1]
#added 2026-08-08 (agent) - directed work ('i ::tcl::prefix' unqualified-ns warning
#report): a RELATIVE multi-component ensemble name (like the tclcore moduledoc's
#former 'tcl::prefix') made the space-form id_checks derive namespace qualifiers
#'testspace' (non-empty, unqualified - the single-component names' empty-qualifier
#guard never caught it) and update_definitions warned on stderr once per subcommand
#per call. The generator now normalizes the ensemble argument to fully-qualified in
#the caller's context before deriving anything from it.
test ensembledef_unqualified_ensemble_name_no_warnings {an unqualified multi-component ensemble name is normalized: no update_definitions stderr warnings and output identical to the qualified call}\
-setup $common -body {
set qualified [punk::args::ensemble_subcommands_definition -columns 1 ::testspace::ensdeflazy]
set errout [capture_stderr {
set ::testspace::relsnippet [punk::args::ensemble_subcommands_definition -columns 1 testspace::ensdeflazy]
}]
lappend result [regexp -all {warning: update_definitions received unqualified ns} $errout]
lappend result [string equal $::testspace::relsnippet $qualified]
unset ::testspace::relsnippet
set result
}\
-cleanup {
}\
-result [list 0 1]
}
tcltest::cleanupTests ;#needed to produce test summary line.

Loading…
Cancel
Save