All 15 make.tcl subcommands are punk::args-declared ((script)::punkboot +
(script)::punkboot::<sub>, built from single-source summary/help/opt dicts with
shared option fragments and explicit @form -synopsis). Bare make.tcl / -help,
'help ?subcommand?' and '<subcommand> -help' render tabled usage; invalid
flags/subcommands raise punk::args usage errors (exit 1) with unambiguous
prefix resolution; 'shell' is a declared passthrough.
Prompt policy: new -confirm 0|1 (default 1) on every confirmation point
(bootsupport minor-staleness gate, vfscommonupdate REPLACE, kit-type mismatch).
Default prompts only on an interactive terminal and aborts fast with guidance
on non-interactive stdin (::punkboot::lib::stdin_is_interactive; Tcl 8.6 lacks
the -inputmode probe and is assumed interactive). -confirm 0 proceeds
(staleness, REPLACE) or skips the mismatched kit. The piped-y agent workflow is
retired; the old vfscommonupdate decline half-abort (printed done, exit 0) now
exits 1.
Degrade rule: punk::args/punk::ansi/textblock boot requires guarded; definition
load caught with fallback to a self-contained scan (-k/-dirty-abort/-confirm
supported) + plain punkboot_gethelp (availability NOTE factored into
punkboot_availability_note, shared by both paths); rendering degrades
independently of parsing (errorstyle minimal / plain help when the ANSI stack
is unhealthy); PUNKBOOT_PLAIN=1 forces degraded mode. punk::args joins the
bootstrap-tracked _runtime_deps staleness set (parsing contract only - the
rendering stack degrades instead; contract docs updated in
src/bootsupport/AGENTS.md + src/modules/AGENTS.md, workflow docs in
src/AGENTS.md + src/tests/shell/AGENTS.md).
Verified: usage/help/error paths, vfscommonupdate fail-fast and -confirm 0 runs
with closed stdin, staleness flag route live (args 0.5.0->0.12.0 tripped the
gate), degraded-mode help/check/info, full 'project -k -confirm 0' kit build.
Dogfooding finding recorded in the goal file: punk::args 0.12.0 reports generic
overflow where 0.5.0 reported choiceviolation for optional-choice mismatches
(worked around with required leaders; candidate error-selection improvement).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Formalize the follow-on work identified during the punk::args documentation
trials and ansi->html prototyping (2026-07-13/14):
- G-078 punk::ansi ANSI->HTML rendering api: promote the prototype ansi2html
converter + headless ansicat flatten entry into a documented punk::ansi
api (palette option, art mode with bold-as-bright and block sealing,
-assets embed|link|none using the vendored punkdoc-mono; docs-screenshot
enabler)
- G-079 default-colour semantics: renderspace replay output bakes explicit
SGR 40 for never-set backgrounds; preserve default fg/bg semantically so
html can theme-match any page background (current workaround: black pre)
- G-080 deterministic cell-grid rendering mode: inline-block cell emulation
to eliminate the residual sub-pixel seams the span-mode recipe accepts
- G-081 argdoc build pipeline: dev doc.* generation of doctools + punk-native
html + markdown from punk::args definitions against dev modules, incl.
per-form table rendering, deeper-namespace pages, @package surfacing
(G-075) and shared doc-set font assets
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Improvements from user review of the punk::ansi trial page:
- command index <nav> at page top (doctools-style jump list); hook to
display @package info above it when specs carry package_info (guarded -
key absent in older punk::args spec dicts)
- multiform commands: punk::args::usage renders all form synopses but only
the default form arg table (terminal UX expects i -form N) - the page now
adds a per-form ARG TABLE for each form that has arguments, trimmed at the
table-header separator so the command/description/synopsis panel is not
repeated per form (renderspace lines are colour-self-contained, so slicing
at a line boundary is safe); argument-less forms are skipped
- pre backdrop is now true black (page body stays dark grey):
overtype::renderspace overlay output emits explicit SGR 40 and example
helpers treat default background as the terminal backdrop - on black they
blend as in a terminal (proper fix = default-colour semantics, goal)
- row pitch line-height:17px (integer, just under the 17.85px inline
background height at font-size 15.36px) - the fractional 1.15em pitch put
row boundaries at fractional positions and showed periodic hairline gaps
through solid-bg runs
- explicit package require punk::lib (tstr placeholder resolution uses
punk::lib::undent; previously loaded only by transitive luck when usage
ran before get_spec)
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Outcome of empirical seam/aspect testing of the ansi2html rendering against
src/testansi cp437 art (chrome + firefox, user-verified):
- punkdoc-mono.woff2 rebuilt with STOCK line metrics. The interim
normalize-to-win-metrics experiment (hhea/typo := win) is REJECTED and now
documented: it grows browsers'"'"' inline content area to 1.3213em, so at row
pitches below that every span'"'"'s background bleeds ~2.5px into neighbouring
rows - wrong-coloured bars under solid blocks. Kept as commented-out
normalize_line_metrics() in make_punkdoc_mono.py with rationale.
- font builds are now byte-reproducible: TTFont(..., recalcTimestamp=False)
keeps the source head.modified (two consecutive builds hash identically).
README records the new output sha256 and the reproducibility note, plus a
cell-geometry section for css authors (0.5859375em advance -> integer 9px
cells at font-size 15.36px; blocks paint the 1.3213em win cell) and the
settled art recipe: stock metrics + tight line-height:1.0 pitch +
transform:scaleY(1.3212890625) for terminal aspect (accepted trade-off:
minor residual sub-pixel seams; deterministic future fix would be
inline-block cell-grid emulation). Larger pitches (17.85/20px) tested and
rejected - stark row seams.
- punkargs_punknative.tcl: font-size 13px -> 15.36px (integer cell width -
fractional advances round differently at styled-span boundaries and seam
block/border glyphs); line-height stays 1.15 for text-oriented doc tables.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Repo policy is LF for text files; the as-downloaded copy had CRLF (also
tripped the fossil crlf warning on checkin).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
src/scriptapps/tools/fontprep/: pinned monospace web font for the punk-native
html doc pipeline, closing the glyph-coverage gap the probe identified -
unicode 16 octants (U+1CC00-1CEBF) and Symbols for Legacy Computing
(U+1FB00-1FBFF) needed by ansicat/textblock rendering are absent from
commonly-installed monospace fonts (even system Cascadia copies are commonly
pre-2404 and lack them).
- punkdoc-mono.woff2: glyph subset of Cascadia Mono v2404.23 VARIABLE ttf
(wght axis 200-700 kept - true bold from one ~80KB file; 1561 cmap
codepoints incl. full CP437 repertoire, box/blocks/geometric, braille,
legacy computing + octants). Coverage verified 157/160 against the measured
glyph inventory - identical to the full source font (misses are deliberate:
em-space + fullwidth forms are converter-normalization/fallback territory).
- renaming is REQUIRED by licence: Cascadia's SIL OFL 1.1 declares Reserved
Font Name "Cascadia Code" and subsetting is modification. Name table is
surgical: family records renamed, Microsoft copyright preserved verbatim
(OFL requirement), trademark record removed (a renamed copy must not assert
Microsoft's mark), OFL statement/url in licence records, provenance credit
in the description record.
- LICENSE-cascadia-OFL.txt (from the v2404.23 tag), punkdoc-unicodes.txt
(whole-block ranges with headroom), make_punkdoc_mono.py (fonttools subset+
rename script), README.md (full provenance: source/output sha256s, exact
command, regeneration steps). Python fonttools is one-time prep tooling
only - the doc build never invokes it; generators just read the .woff2.
Track licence in the G-063 inventory.
punkargs_punknative.tcl: new -assets option consuming the vendored font:
embed (default) = data-uri @font-face, fully self-contained page;
link = copy punkdoc-mono.woff2 beside the output and reference relatively
(shared+cached across a doc set); none = css stack only, with
warn-and-degrade when the asset is missing. font-weight 200 700 descriptor,
punkdoc-mono prepended to the fallback stack.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Two G-candidate prototypes under src/scriptapps/tools/ for punk::args as the
documentation source of truth (run against bootsupport modules with a plain
tclsh, e.g: tclsh punkargs_to_doctools.tcl punk::path out.man):
- punkargs_to_doctools.tcl: package's punk::args definitions -> doctools .man
(Tcl-standard [arg]/[opt] synopsis conventions by design - punk s-style is
out of scope for the doctools target). Covers @cmd summary/help, per-form
[call] synopses, argument detail lists in argspace order (leaders/options/
trailing values), constraint sentences (defaults/choices/ranges), id aliases
as cross-references. Validated via 'dtplite validate' (bin/dtplite.cmd);
punk::path trial output renders clean through the html/text/nroff engines.
- punkargs_punknative.tcl: doctools-free pipeline faithful to punk s-style.
html = punk::args::usage ANSI output converted via a minimal SGR->CSS
ansi2html (16/256/truecolour, bold/dim/italic/underline/strike/reverse) on
punk::ansi::ta::split_codes - candidate for a punk::ansi public api (also
the docs-screenshot enabler for ansicat/textblock output). markdown = GFM
with verbatim s-style synopsis fences + per-form argument tables.
-boxmap none (default) keeps faithful heavy box glyphs; -boxmap light maps
to the light set as a portability fallback.
Key findings baked into the code comments:
- css font stacks must be set on the pre element itself - the UA stylesheet's
'pre {font-family:monospace}' overrides body inheritance (cause of an
initially-misdiagnosed overlong-border rendering bug)
- glyph coverage probe over src/testansi + textblock::periodic/frames +
punk::args::usage (160 codepoints incl. unicode 16 octants and legacy
computing blocks): Cascadia Mono >=2404.23 covers 157/160; embedding or
referencing a pinned punkdoc-mono font remains the plan for doc-set output
since octant/legacy glyphs are absent from commonly installed fonts.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
'dev doc.validate' invokes a bare dtplite command which the repl
unknown-handler resolves via auto_execok on PATH - no dtplite executable
existed (only the legacy misnamed bin/dtplite_run.bat), giving
"invalid command name dtplite".
- src/scriptapps/dtplite.tcl: LF-converted (polyglot payloads are embedded
verbatim into the LF-only multishell output); fall back to the
project-vendored tcllib (src/vendorlib_tcl9/<arch>/tcllib*) when the
invoking tclsh lacks the dtplite package
- src/scriptapps/dtplite_wrap.toml: new scriptset config (tclsh nextshell
on all platforms)
- bin/dtplite.cmd: generated polyglot via
punk::mix::commandset::scriptwrap::multishell (checkfile-clean, LF-only)
- src/tests/shell/testsuites/binscripts/dtplite.test: artifact pins
(LF-only, checkfile, byte-identical round-trip re-wrap) + execution
usecases (usage error, validate good/bad file, validate directory tree,
html generation) - 8/8 pass under runtests
- AGENTS.md (bin, src/scriptapps): record the dtplite scriptset, its test
pin and the LF-only payload requirement
- punkproject.toml: 0.12.22 -> 0.12.23
Verified: 'dev doc.validate' in punk902z src validates all 64
src/doc .man files clean. The doctools pipeline remains interim -
punk::args is the intended doc source of truth.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
make.tcl modules + vfscommonupdate; old versions (args-0.11.2, tclcore-0.3.3)
removed by the update. All four kits (punkbi, punksys, punk91, punk902z)
rebuilt from the updated vfs and verified in kit mode: punk902z (9.0.2) and
punksys (8.6.13) both load args=0.12.0 tclcore=0.3.4, punk::args::formcheck
reports ::after as 6 forms / 15 pairs / 1 sanctioned structural overlap
(cancelid/cancelscript, zero unsanctioned) and ::lseq as the two unsanctioned
type_weakness findings ({range start_count} {range count}) on both runtimes.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New punk::args::formcheck <id> ?-return dict|summary?: reports the form pairs
of a multiform definition that some argument list could cleanly match
simultaneously (the multipleformmatches inputs). Static pairwise pass over the
resolved FORMS enumerates each form's positional word-slot chains (leaders then
values; -optional and ?-wrapped clause-member branching; -multiple capped;
options excluded - order-free), screens aligned equal-length chains per
position (discriminator words via choiceword_match, type witnesses from a
small table), and CONFIRMS every candidate witness arglist with a real
single-form parse against both forms (parse_status - no raise, no
user-supplied words, no define/resolve cost). Findings are therefore witnessed
- discriminated form pairs cannot false-alarm; misses (exotic types,
option-requiring forms, enumeration caps) are the documented conservative
direction. Classes: type_weakness (discriminator aligned with a permissive
non-validating type - any/none/string/ansistring/globstring/expr/script) vs
structural (forms genuinely share an argument shape).
New @form key -overlapallowed <formname-list> sanctions a KNOWN overlap on
either pair member: the finding reports with sanctioned 1 and leaves the
result's unsanctioned list (the actionable/gate subset). Parse behaviour is
never affected; unknown form names are rejected at definition resolve
(end-of-forms cycle). @form directive doc updated.
tclcore moduledoc 0.3.4: the documented after cancelid/cancelscript overlap
(runtime-liveness ambiguity, 0.3.0 record) sanctioned on the cancelid form -
::after now reports zero unsanctioned findings while 'after cancel <id-shaped>'
still raises multipleformmatches. ::lseq deliberately unsanctioned: formcheck
reports range/start_count (witness {1 count 1}) AND range/count (witness
{1 by 1}) - both real, both rooted in the expr-typed end slot, kept visible
pending an expr syntax-validating type (G-069/G-070).
Tests: new args/formcheck.test (7) - no-finding cases (parse withid/withdef
pair, afterish/sharedform fixtures), class+sanction fixture, unknown-form
rejection, -return summary, ::lseq/::after pins with witnesses re-verified,
sanction parse-neutrality. punk::args suite 210/210, punk::ns 57/57.
G-074 achieved and archived (acceptance review in the detail file); G-055
verification gate gains the formcheck step (unsanctioned must be empty for
regenerated multiform commands).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Root AGENTS.md User Preferences gains the binding agent-agnostic rule (an
agent editing .gitignore discovers it at the root without walking into
.fossil-settings/): .gitignore canonical, ignore-glob hand-derived, same work
unit, semantic differences translated per .fossil-settings/AGENTS.md. That
child doc's sync procedure is retitled from "when asked" to mandatory for
any ignore-rule change, with the one-sided-change prohibition stated.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Proposed goal, wording user-approved 2026-07-13: make the perl/python/node -e
reflex work on punk kits (top-level and script subcommand forms) instead of
erroring, without ever reproducing stock-tclsh argv-swallow/stdin misparse
behaviour. Companion to the tclsh -e guardrails commit (AGENTS.md entry +
claude deny hook).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Stock tclsh treats any leading-dash arg other than -encoding as argv for an
interactive/stdin session: on a console it hangs at a prompt, with piped stdin
it exits 0 silently ignoring the supposed one-liner. Agents habitually reach
for -e (perl/python/node reflex), wasting time waiting on hung processes.
- root AGENTS.md User Preferences: document the misparse, the correct patterns
(temp .tcl file or script via stdin) and the defensive stdin-redirect habit
- .claude/settings.json: PreToolUse hook denying Bash/PowerShell invocations
of tclsh with -e, anchored to command position (start/pipe/semicolon/paren)
so prose mentions in commit messages or grep args do not false-positive;
corrective guidance returned to the agent
- .gitignore: narrow the .claude ignore so shared settings.json is tracked
while session-local files stay ignored
- .fossil-settings: ignore-glob hand-derived per the no-negation rule
(.claude/* wholesale + explicit fossil add of settings.json), exception set
in AGENTS.md updated; fossil extras under-ignore check verified empty
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
make.tcl modules + vfscommonupdate + project: all four kits rebuilt
(punkbi, punksys, punk91, punk902z). Kit-mode verification: punk902z
loads args=0.11.2 ns=0.5.0 tclcore=0.3.3 lib=0.4.3 punk=0.2.6, 'i join'
emits no bad-@dynamic warning; punksys (8.6) loads args=0.11.2 and the
after cancel-id discrimination resolves 'cancel someid' to the
cancelscript form (harvested id prefix working on 8.6).
A stale tcl_oauth2_library install leftover in the project's root lib/
tree (its src/vendorlib source was removed 2026-07-12; the libs install
step does not prune targets whose source vanished) was swept into the
vfs lib by vfscommonupdate and removed again before the kit build - it
is deliberately not part of the vendored set yet (G-065..G-068
first-test candidate). The root lib/ leftover itself was removed after
this commit was first authored; its rows remain in lib/.punkcheck.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New goal G-076 (active): adopt the upstream tcl9 dead-console fix (ticket
f10d91c2d3, root-caused in G-039) via a single version gate shared by the
'help tcl' warning and the repl watchdog arming.
- punk::lib 0.4.3: has_tclbug_console_deadspin + pure classifier
tclbug_console_deadspin_applies, gate variable
check::tclbug_console_deadspin_fixed_in (empty = all Tcl 9 windows
runtimes affected). Buginfo dicts gain a mitigated/mitigation axis
orthogonal to level; the deadspin check reports mitigated when
punk::repl >= 0.5.0 is available to the runtime.
- punk::repl 0.5.1: repl::start arms the dead-console watchdog only when
the check reports the runtime affected - recording a verified fixed
release once flips both consumers.
- punk 0.2.6: 'help tcl' renders mitigated warnings subdued (term-grey)
with a "(mitigated)" annotation and the mitigation text; severity
classification unchanged.
- checkbugs.test: classifier truth table, live-check consistency, and
mitigated-axis validation in the generic every-check sweep.
The fixed_in flip is deliberately deferred until a released Tcl passes
the G-039 kill-procedure re-verification (watchdog disabled, clean
script-visible eof exit) - recorded as the goal's remaining acceptance
item.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
User-directed removal of the three tags the 0.3.2 sweep identified.
None has round-2 substitution content and their placeholders are all
display styling/examples, so the definitions now resolve as ordinary
static definitions with display expansion cached per raw definition
(previously @dynamic forced re-expansion on every render). Sweep of
all 462 registered ids now emits zero bad-@dynamic warnings; renders
and parses verified unchanged for all three. punk::args 203/204 (1
pre-existing skip), punk::ns 57/57.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
User-reported: 'i join' emitted the bad-@dynamic warning 4 times ('i
join test' 6). Root cause was a real inefficiency, not just noisy
logging: a @dynamic definition whose round-1 tstr output has no
round-2 parameters bypassed argdefcache_unresolved entirely, so every
resolve in the 'i' flow (doc walk, advisory parse, get_spec for the
render, synopsis) redid display masking plus the full round-1 tstr and
re-warned. Such definitions now cache as a zero-param unresolved entry
- consistent with the round-1 freezing legitimately dynamic
definitions already get - so later resolves take the cheap cached
branch and the warning emits once per definition per interp.
Warning wording corrected: @dynamic is not a complete no-op for a
parse-inert definition - deferred display fields still re-expand per
render instead of caching - so the message says removal is appropriate
only if that display behaviour is unintended. Sweep of all 462
registered ids: ::split, ::array, ::join carry the parse-inert tag
(left in place pending the display-refresh decision).
Incidental finds from the sweep: the malformed ::tcl_startOfNextWord
tclcore definition (double-quoted -help with unescaped quotes in the
embedded man-page example) failed to resolve at all - now braced per
the define quoting rules, 'i tcl_startOfNextWord' works; and resolve's
malformed-record error path printed dev diagnostics (full records dump
+ ::testrecord global) - commented per the stray-debug convention.
Tests: new args/dynamic.test (warn-once + cached parse, legit-dynamic
round-2 freshness; stderr captured via channel transform). punk::args
203/204 (1 pre-existing skip), punk::ns 57/57. Live-verified: 'i join'
and 'i join test' each emit the warning exactly once (punk902z src).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Authoring-style change only, user-directed: the interim %AFTERIDPREFIX%
string map is replaced by plain tstr placeholders, with the harvest
variable set in the argdoc namespace - the defspace registered PUNKARGS
definitions resolve placeholders in when an argdoc child exists (the
rule documented in punk::args 0.11.1). One mechanism now serves both
the -type parse field (expanded at first resolve) and the -help display
fields (expanded at display time); the module showcases the placeholder
style, with string map reserved for genuinely build-time cases.
Behaviour identical to 0.3.0: resolved -type stringstartswith(after#),
form discrimination, parity pins and rendered help re-verified.
punk::args 201/202 (1 pre-existing skip), punk::ns 57/57. G-055
workflow note updated to name the tstr-in-argdoc pattern as preferred.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Documentation-only. The define -help gains two sections: 'Registration
styles' (direct define vs deferred lappend-PUNKARGS registration - the
lazy scan/on-demand definition mechanics, why moduledocs and the module
template use it, PUNKARGS_aliases, punk::args::status timings) and
'Interpolation (tstr placeholders) and the defspace' (display-field
deferral vs parse-field expansion vs @dynamic; the defspace rule with
the argdoc-child subtlety; the silent-literal fallback; safe patterns
for load-time-computed values). Motivated by the ::after id-shape
harvest misattribution - corrected record in goals/G-055.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The 0.3.0 record claimed tstr placeholders in non-display fields are not
expanded for plain PUNKARGS definitions. Controlled probes show all
authoring styles (direct define, registered PUNKARGS, argdoc-subns
variable) DO expand parse-field placeholders - the ::after attempt
failed because update_definitions resolves registered definitions in
the argdoc subnamespace whenever one exists, while the harvest variable
had landed in the parent namespace, and an unresolvable param is left
silently literal. Module comment, buildversion changelog line and the
G-055 workflow note corrected; the silent-literal fallback noted in
G-055 as a diagnostic gap (a literal placeholder reaching a parse field
is always an authoring error and could warn).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The cancelid and info forms' id argument is typed
stringstartswith(<prefix>) with the prefix harvested from the running
interpreter at define time (safe create+cancel probe; after# on 8.6.11
and 9.0.3) - substituted via a build-time %AFTERIDPREFIX% string map
because tstr placeholders in non-display fields are not expanded for
plain PUNKARGS definitions (G-046 defers display fields only; noted in
G-055 for the regeneration workflow). -typesynopsis id keeps the
synopsis as the man page's 'id'; both ids gain man-page-derived help.
Effect under G-041 form candidacy: 'i after cancel someid' resolves
cleanly to the cancelscript form, matching real Tcl's silent
script-match no-op; 'after info <non-id>' is model-rejected where real
errors at runtime (parity-true). An id-SHAPED word after cancel stays
truthfully ambiguous (cancelid+cancelscript) - real Tcl resolves that
junction by id liveness at runtime, which no static type expresses.
Dead-id over-acceptance on 'after info' recorded as the accepted
runtime-liveness boundary.
Tests: three parity pins added in tclcoreparity.test (id-shape harvest
agreement with live ids, cancel discrimination incl the liveness
ambiguity witness, info error-vs-ok parity + accepted dead-id
divergence). punk::args 201/202 (1 pre-existing skip), punk::ns 57/57.
Live-verified on punk902z src ('i after cancel someid' renders info
scheme).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Acceptance reviewed clause-by-clause in the detail file: auto-selection
on a unique clean match (forms.test GAPs flipped), candidate-naming
noformmatch/multipleformmatches errors, -form list restriction across
get_dict/parse/parse_status/arg_error, doc surface presenting the
best-matching form(s) ('i after cancel <id>' presents the cancel form;
's after cancel someid' underlines the matching synopsis lines),
explicit single -form unchanged, full punk::args/punk::ns suites pass
(full tree 822/836 with only the exec-14.3 baseline failure).
Flip edits: index entry to GOALS-archive.md (achieved 2026-07-13),
detail Status flipped with acceptance review and live-verification
record, reference sweep updates G-044 (candidacy API now available -
parse_status formstatus), G-055 backlink, G-072 backlink. Detail-file
move to goals/archive/ follows as a pure rename; goals_lint transiently
unclean between the two commits.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
cmdhelp ('i'): the -form option defaults to * (was 0) with parse's
list semantics, and both render sites present the form the advisory
parse selected - 'i after cancel <id>' presents the cancel form's
argument table with its synopsis line underlined; 'i lseq 0 10 2'
presents the range form info-scheme. When no form matches, the ranked
candidates are passed to arg_error (best candidate's table, all
candidates marked, message naming each form's failure); when several
forms match, all matching forms are marked under the ambiguity message.
punk::ns::synopsis ('s'): trailing argument words after a multiform
command path underline the form(s) they match - matching forms from the
advisory parse's formstatus, or the best candidate when none fully
matches ('s after cancel someid' marks both cancel forms; 's lseq
0 10 2' marks the range form). Ordinal line position maps lines to
declaration-order forms in both full and summary renders; marking is
skipped under alias-currying excess or an explicit -form.
punk::args 0.11.0: the documented @form -synopsis override now renders
in punk::args::synopsis full and summary output (arg_error honoured it
already) - the adjacent forms.test GAP flipped
(forms_form_synopsis_override_rendered). Candidate ranking extended:
form_literal_affinity treats required RESTRICTED-choice arguments as
discriminators via choiceword_match (the tclcore models express
subcommand words as -choices), so 'after cancel' ranks the cancel
forms first instead of falling back to declaration order.
Real-model findings recorded in the goal detail: 'after cancel someid'
is genuinely ambiguous in the doc-faithful model (cancelid vs
cancelscript - real Tcl disambiguates semantically); 'lseq 1 count 5'
ambiguity traces to the expr-typed operands (noted in G-055 as a
model-tightening candidate alongside TIP 746).
Tests: cmdhelp.test gains the multiform doc-surface pins (autoselected
form presented, noformmatch best-candidate table + candidate naming,
synopsis marking present/absent). punk::ns 57/57, punk::args 198/199
(1 pre-existing skip); full source-tree suite in the detail record.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
punk::args::parse/get_dict with the default -form * (or any multi-form
selection) now attempts every permitted form instead of effectively
parsing form 0 only. Exactly one clean match auto-selects (result gains
a 'form' key; 'formstatus' reports every candidate when candidacy ran).
No match raises a 'noformmatch' validation error naming each candidate
form's first-line failure, ranked best-candidate first (leading-literal
affinity with the supplied words, then incomplete before invalid, then
declaration order). Several clean matches raise 'multipleformmatches'
naming the forms - deliberately no silent preference (recorded in the
goal detail); callers pass -form to disambiguate.
-form now accepts the documented list of form names/indices across
get_dict, parse, parse_status and arg_error via a shared resolver
(private::form_selection). arg_error renders the argument table for the
first listed form and marks all listed forms' synopsis entries; parse's
error render passes the ranked candidate list so the closest form's
argument table shows under the no-match message.
parse_status: 'form' is now the matched (or best-candidate) form - the
per-argument statuses and badarg marking are built for it - and the new
documented 'formstatus' key carries per-form status/failureclass/badarg/
message (caller-attributed), the per-form compatibility surface the
G-044 completion/hinting consumer needs on partial argument lists.
Engine restructure: the single-form parse body of get_dict extracted
verbatim to private::get_dict_form (argspecs resolved in the caller's
context before selection; no caller-frame use inside - verified).
Single-form definitions take the direct path unchanged.
Tests: forms.test GAP pins flipped (forms_parse_autoselect,
forms_parse_formlist_restriction) plus new coverage: shared-prologue
arity discrimination, noformmatch ranking + errorcode payload,
multipleformmatches + explicit -form disambiguation, formstatus in
results and parse_status. punk::args 198 pass / 1 pre-existing skip,
punk::ns 53/53, punk::lib 35/35.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
User-approved draft from the 8.6-vs-9 string is dict forward-compat
requirement: a new definition key (candidate -choiceunavailable) for
choices that are recognised but not available in the current runtime -
displayed among the choices with their -choicelabels notes, participating
in prefix disambiguation via the shared choiceword_match resolver (so
abbreviations colliding with forward-version choices are ambiguous
everywhere), and rejected at parse with a tailored recognised-but-
unavailable message. Adoption by the tclcore string is model gives 8.6
users the dict class annotated and reserved (deliberately stricter than
real 8.6 for the di prefix - recorded as a user-sanctioned parity
exemption; full-word behaviour stays parity-true), plus an
i-string-is-dict documentation id generated from the static description.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Space-form docid prefix parity: cmd_traverse's space-delimited child
docid jump, on an exact-word miss, now resolves the word against the
current level's choices-bearing first leader via
punk::args::choiceword_match (the shared G-040 resolver - -choiceprefix,
-nocase, -choicealiases, denylist, reservelist honoured) and retries
with the canonical word. 'i string is tr' resolves to the
"::tcl::string::is true" documentation exactly when 'string is tr 1'
executes; 'string is bool' -> boolean; ambiguous ('d'), unknown ('zz')
and denied words stay at the parent exactly when parse rejects them.
resolvedargs records the canonical, mirroring parse normalization. No
second matching rule exists.
Truthful cmdtype: cmdinfo classifies cmdwhich-notfound + non-empty docid
as 'doconly' (was 'notfound') - documentation-only levels below real
commands (per-class ids like "::tcl::string::is true", documented TclOO
method docids; the method case adopts doconly per the recorded design
decision, refinable by G-052). Consumers audited: cmdhelp/synopsis/eg
are docid-driven, cmdtrace tests only for 'proc', punk::help topic
fallback uses cmdwhich; punk::lib script analysis updated in step
(doconly accepted alongside notfound, bucketing unchanged).
Tests: the four G-051 GAP pins flipped and renamed
(cmdhelp_pseudo_command_cmdtype_doconly,
cmdhelp_spaceform_docid_prefix_honoured + ambiguous/unknown guard,
cmdhelp_string_is_true_pseudo_doconly,
cmdhelp_string_is_prefix_honoured). punk::ns 53/53 (cmdflow + non-GAP
cmdhelp unchanged), punk::args 193 pass / 1 pre-existing skip,
punk::lib 35/35; full source-tree suite result in the detail file.
Live verification (punk902z src): 'i string is tr' renders
byte-identical to 'i string is true' except the failure banner echoing
the typed words.
Flip edits: G-051 index entry to GOALS-archive.md (achieved 2026-07-13),
detail Status flipped with acceptance review and verification record,
reference sweep updates G-052 Notes. Detail-file move to goals/archive/
follows as a pure rename; goals_lint transiently unclean between the two
commits.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Regression report (user, vs older punk executables): "i if" / "i while" /
"i foreach" used to render the error-scheme box with a "Bad number of
trailing values..." message signalling the command cannot be called with
no arguments; recent builds rendered plain info-scheme usage with no
indication, while direct punk::args::parse {} withid ::foreach still
errored.
Root cause: deliberate suppression, not breakage - G-046 item 5 (ns
0.1.4) suppressed the failing advisory parse for the whole
no-supplied-words path because the message then carried internal-looking
attribution ("Bad number of leading values for punk::args::parse
$args_remaining ...", the "i string is" complaint). G-049 (ns 0.2.0,
same day) independently added -caller attribution making bare-query
messages accurate - but the suppression stayed, hiding now-useful
signal.
Fix: reversal rather than rewording (user decision) - both cmdhelp
render sites (alias path and main path) drop the no-supplied-words
special case; a failing advisory parse renders its message and error
scheme uniformly, and dict returns no longer rewrite the scheme to
info. "i if" now leads with "Bad number of trailing values for if. Got
0 values. Expected at least 2"; the original "i string is" case renders
an accurate "Bad number of leading values for string is. Got 0 leaders.
Expected exactly 1" instead of being hidden.
Tests: cmdhelp_leader_required_no_args_plain_usage flipped to
cmdhelp_leader_required_no_args_error_render (message present,
attribution never the internal parse); cmdhelp_return_dict_scheme
expects scheme error for the bare-query failure. punk::ns suite 53/53;
verified live in punk902z src.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Root cause: get_dict_can_assign_value blanket-satisfied the member check
for any argument carrying choices, so an optional restricted-choice value
(the lseq '..'/'to' noise word) consumed any word whenever arity
permitted, starving later elements/clauses and failing validation with a
confusing trailing-choices error ('lseq 0 10 2' shape).
Fix: an allocation choice screen using choiceword_match (the shared G-040
implementation - exact/alias/prefix/nocase semantics; -choicemultiple
words screened per list member within min/max), applied only where
allocation has an alternative: -optional arguments and further
occurrences of -multiple arguments. Required arguments are deliberately
not screened - the word must fill them and validation's choiceviolation
reporting stays informative (the existing parsestatus/cmdhelp/choices
pins forced and now document both refinements). -choicerestricted 0
behaviour unchanged.
Results: the allocation.test GAPs flipped ('1 2 3' and '1 2 by 3' parse
with correct clause shapes {{} 3}/{by 3}; '1 2 3 4' gets the plain
excess-values rejection instead of blaming the unrelated optional);
prefix normalization survives the screen; all noise-word variants and
if-shape guards unchanged. lseq moduledoc matrix parses per lseq.n under
-form range, and 'i lseq 0 10 2' works end-to-end. Remaining lseq
divergences trace to G-041 form selection or the -type expr laxity
recorded in G-055 - not allocation.
Also corrected an increment-1 mis-premise: parse_status ALREADY accepts
-form before the withid/withdef tail per its documented synopsis - the
probe had passed it trailing. GAP replaced by
parsestatus_form_option_order (correct-order acceptance incl. the form
key, trailing rejection). No code change was needed for that acceptance
clause.
Verification: punk::args suite 193 pass / 1 pre-existing skip / 0 fail;
punk::ns 53/53; full source-tree suite 813 pass / 13 skip / 1 fail =
exec-14.3 only (known baseline) - zero regressions (tclsh 9.0.3; lseq
verification in punk902z src, Tcl 9.0.2).
Flip edits: G-071 index entry moved to GOALS-archive.md (achieved
2026-07-12), detail Status flipped with the acceptance review and
before/after record; reference sweep updates G-041 (prerequisite fixed,
parse_status correction) and G-072 Notes. The detail-file move to
goals/archive/ follows as a pure rename; goals_lint transiently unclean
between the two commits.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
G-071 flipped proposed -> active (user direction 2026-07-12).
New allocation.test (7 tests) with reduced fixtures isolating the failing
shape from the tclcore moduledoc: the lseq-range shape (start ?sep? end
?by-step clause?) reproduces the moduledoc failures exactly. Current
mis-allocation pinned as GAP tests ("1 2 3" -> invalid blaming sep,
"1 2 by 3" -> incomplete blaming end, "1 2 3 4" correctly invalid but
misblaming sep), noise-word variants and the if-shape noise-word clauses
pinned as regression guards, parse_status -form absence pinned as GAP.
Also silenced four more unconditional debug puts on get_dict allocation
retreat paths ((111)/(222)/(333)/(444)) - these printed to stderr during
NORMAL successful parses at every optional-skip retreat, polluting
interactive help/parse output for noise-word-modelled commands. Behaviour
unchanged; the retreat logic they marked is exactly this goal fix target.
Full punk::args suite 193 pass / 1 pre-existing skip / 0 fail (tclsh
9.0.3).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Both user-approved drafts from the G-041 prework probe findings:
- G-071: value-allocation correctness for optional standalone values +
trailing optional-member clauses (the lseq "0 10 2" in-form failure),
error-blame quality, and parse_status -form support. A G-041
prerequisite: no lseq form accepts those arglists under the current
allocator, so form auto-selection alone cannot fix them.
- G-072: named compound clause types with per-alternate element typing
and arity for heterogeneous repeating clauses (try on/trap handlers),
building on the design sketches embedded in the ::try moduledoc notes.
Includes a recorded examination of Elixir gradual set-theoretic types
(user direction): punk::args -type | and literal() are already a
fragment of the set algebra; negation would subsume reserved-word
deny-lists; per-alternate arity falls out of union-of-shapes.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Probing (punk902z kit, Tcl 9.0.2) of the tclcore moduledoc models against
the real builtins, driven by user concerns about variable-length clauses
with optional elements before G-041 activation. Findings recorded in the
G-041 detail file (auto-selection evidence on real commands, the
allocator prerequisite, parse_status -form gap) and the G-055 detail file
(modelability over-acceptance list: try compound handler types,
reserved-word clause allocation, "-" fallthrough constraints, switch
two-argument option suppression, unvalidated -type expr, TIP 746
version-conditionality for lseq operands; plus the user doc-direction
guidance that definitions split into @form entries where source docs
simplify).
Key result: the per-form models are doc-faithful (lseq.n synopsis maps
1:1; switch separate/block parse correctly under explicit -form; try
mixed on/trap interleaving and finally positioning all agree with the
real command) - but the value allocator mishandles an optional
single-word choice value followed by a required value plus trailing
optional-member clause, so lseq range arglists without the ../to noise
word ("0 10 2", "0 10 by 2") fail in-form. No lseq form accepts them, so
G-041 form selection alone cannot fix "i lseq 0 10 2" - allocator fix
flagged as a candidate prerequisite goal.
Fix included (punk::args 0.8.2): unconditional debug puts on the clause
type-check path (get_dict_can_assign_value) commented, companion to the
0.2.3 find. Full punk::args suite 186 pass / 1 pre-existing skip / 0 fail.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Both user-approved drafts arising from the G-045 tclparser assessment:
- G-069: dev-time lint cross-checking punk::args record-splitter boundaries
against tclparser parse command where the binary is available -
dialect-aware comparison (-& pre-join, ANSI strip), capability-gated,
zero new runtime dependencies for punk::args.
- G-070: pure-Tcl implementation of the tclparser parse-command API scoped
to what punk::lib consumes, parity-verified against the C library over a
probe corpus; replaces the tclparser_tcl error stub with
prefer-C/fallback-pure dispatch. Reference source to be user-provided
under TEMP_REFERENCE with recorded provenance.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Increment 5 (final): the container quoting rules pinned by defquoting.test
now appear in the punk::args::define -help documentation - braced values
fully literal (backslash sequences survive as typed); double-quoted values
get Tcl backslash semantics at record parse while $ and [] stay literal
with no substitution outside tstr placeholders; the backslash-escaped
placeholder idiom renders a literal placeholder. Rendered section verified
in punk902z src (meta-escapes display correctly). punk::args suite 186
pass / 1 pre-existing skip / 0 fail.
Acceptance review recorded in the detail file: all criteria satisfied
across increments 1-5 (@cmd -unindentedfields 0.6.1, i help alignment
punk 0.2.4, -& record continuation 0.7.0, @normalize + define_docs
consumer proof 0.8.0/punk 0.2.5, quoting documentation 0.8.1; suite grown
128 -> 187 tests, GAPs flipped, none weakened, full source-tree suite zero
regressions).
Flip edits: G-045 index entry moved to GOALS-archive.md (achieved
2026-07-12), detail file Status flipped with evidence in Progress,
reference sweep updates G-053 notes. The detail file move to
goals/archive/ follows in the next commit as a pure rename (per the
goals/AGENTS.md archive rules) - goals_lint is transiently unclean between
the two.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New bare @normalize directive: opts a definition into indent normalization
of BLOCK-FORM multi-line field values (first line whitespace-only, as
authored by opening a braced literal with a newline). The structural first
newline and a whitespace-only trailing line are dropped, the content
lines' common leading whitespace is the block's base indent, the first
content line is unindented fully and subsequent lines are re-based to the
standard 4-space continuation convention (deeper relative indents
preserved; whitespace-only inner lines become empty). Fields in a record's
-unindentedfields are exempt. Implemented as a resolve pre-pass over the
split records (private::normalize_records / rebase_multiline_value);
@normalize with options is an error.
Head-form values are never altered - their base indent is unknowable
(continuations uniformly at 6 may be base-4 with the deliberate +2
relative convention, or base-6 flush; re-basing would flatten the former).
The idempotence test caught exactly that on a file-style definition during
implementation, narrowing the user-confirmed re-base semantics to block
form only - which also makes @normalize a proven no-op on conforming
file-style definitions.
Consumer proof (punk 0.2.5, per the recorded user decision):
::punk::helptopic::define_docs converts from interim left-margin authoring
to indented block-form values under @normalize, dropping its
-unindentedfields declarations; 'i help' and 'i help_chunks' verified
aligned in punk902z src, including the blank-line separator in the
combined basehelp+extra block.
Tests: new normalize.test (5 tests: block re-base, block left-margin,
head-form boundary, exemption byte-exactness, file-style idempotence);
rendering.test P4 characterization stays pinned as the deliberate unopted
default with its text updated to reference @normalize. punk::args suite
186 pass / 1 pre-existing skip / 0 fail; punk::ns 53/53; full source-tree
suite 806 pass / 13 skip / 1 fail = exec-14.3 only (known baseline) - zero
regressions (tclsh 9.0.3). define doc documents the directive.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
An unquoted trailing -& element on a definition record line continues the
record on the next line. Implemented in
private::split_definition_records: the token is dropped and the next line
joins after a single space with its leading whitespace collapsed - exactly
how the Tcl parser joins backslash-newline continuations before a braced
definition ever reaches the splitter - so a -& record assembles
byte-identical to its backslash-continued equivalent. Motivation:
constructed (string-built) definitions cannot author backslash-newline
ergonomically (the building code's own quoting consumes it); -& is plain
text and survives any construction.
Collision rules (analysis and decision recorded in the goal detail file):
bare word preceded by whitespace (or whole line), trailing whitespace
tolerated; braced/quoted -& is data ({-&} escapes a literal trailing
value); mid-line -&, word-suffix abc-&, and -& inside still-open
braced/quoted values are data. Element-count disambiguation rejected
(action-at-a-distance; positional rule is locally decidable). Backslash
authoring unchanged - continuation is additive.
Tests: new recordcontinuation.test (6 tests) incl. backslash-twin
parse+render byte-equality and the constructed-def chaining case. Full
punk::args suite 181 pass / 1 pre-existing skip / 0 fail; full source-tree
suite 801 pass / 13 skip / 1 fail = exec-14.3 only (known baseline) - zero
regressions (tclsh 9.0.3). define doc documents -& alongside backslash
continuation.
Also recorded: tclparser considered and rejected for the splitter (ANSI
unbalanced-bracket data, dialect semantics outside Tcl grammar, binary
dependency vs G-004); dev-time parse cross-check lint flagged as candidate.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
User decision 2026-07-12: when the constructed-definition normalization
mechanism lands, define_docs converts from its interim left-margin
authoring to indented-plus-normalized authoring, remaining the
acceptance criterion's consumer proof as written. Left-margin authoring
via -unindentedfields stays a supported style proven by the rendering
tests, but is not the preferred style for define_docs itself.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
::punk::helptopic::define_docs now authors its help text at the left
margin and declares -unindentedfields {-help} on the generated @cmd line
(honoured as of punk::args 0.6.1, increment 1) and on the topic argument
line (a gate that existed all along but was never applied here).
Previously: the @cmd -help braced literal carried ~16 spaces of source
indent into the constructed definition (constructed defs get no
whole-block normalization, pinned P4 characterization), so the
Description block rendered continuations +12 right of the first line;
the \n-relative topic -help rendered its first line +4 (the injected
display prefix). Both blocks now render flush. Help text content
unchanged (manual ~70-col folding retained).
Verified in punk902z src (arg_error render + i help end-to-end);
punk::ns suite 53/53; make.tcl modules builds clean. Planning note
recorded in the goal detail file: the constructed-def normalization
increment picks its consumer proof (define_docs re-conversion vs
ensemble_subcommands_definition) when it lands.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
arg_error's cmd-help display transform (undent " "+help, max 4 - the
'#unindentedfields ?' todo site) is now gated by "-help" membership in the
@cmd line's -unindentedfields list, mirroring the existing per-argument
gate. The single transform site feeds both the table and string renderers,
so left-margin-authored @cmd help renders its first line flush with
continuations in both. @cmd -summary carries no indent transform in any
renderer, so -unindentedfields for -summary is vacuously honoured. No
in-tree definitions set -unindentedfields on @cmd - existing rendering is
unchanged. define doc for -unindentedfields now states where the option is
valid.
Tests: rendering_unindentedfields_cmd_help_GAP flipped to
rendering_unindentedfields_cmd_help (aligned expectation, both renderers
measured). Full punk::args suite 175 pass / 1 pre-existing skip / 0 fail;
punk::ns suite 53/53 (arg_error consumer); make.tcl modules builds clean
(tclsh 9.0.3).
Also: G-045 flipped proposed -> active (user direction 2026-07-12);
progress and remaining-work recorded in the detail file.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
The args 0.6.0 module work (c309ed47) predated the 0.12.2 bump but its
changelog entry covered only G-039; the vfs payload sync (15c10637) ships
the user-visible synopsis rendering change in built kits, so it gets its
own patch bump and entry per the change-driven policy.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
New user-preference bullet: agent-authored text avoids typographic
Unicode (em/en dashes, curly quotes, arrows, ellipses) in favour of ASCII
equivalents, with hard enforcement for outward-bound artifacts; documented
exceptions for non-ASCII subject matter, verbatim quotes, and explicit
requests. Existing files are not bulk-retrofitted.
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com
Replaces args-0.5.0.tm with the built 0.6.0 (small restricted choice sets
display as literal alternates in synopses).
Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com