14 KiB
G-032 Kit launcher dogfoods punk::args: tabled help and parsed subcommands
Status: achieved 2026-08-03
Scope: src/vfs/_config/ (default dispatch); src/lib/app-punkshell and sibling app packages as touched
Goal: the default launch dispatch defines its subcommands via punk::args - <punkexe> -help and argument errors render the tabled usage enumerating built-in and project-registered subcommands with summaries, and subcommand options parse through punk::args so projects can declare complex arguments - with the G-030 degradation rules (boot never fails and help degrades to plain text when punk::args or the ANSI rendering stack is unavailable).
Acceptance: <punkexe> -help renders tabled usage listing all subcommands including project-registered ones, each with a summary; at least one built-in subcommand's options are declared and parsed via punk::args with tabled usage errors on invalid input (the G-015 script subcommand or the G-025 version-report subcommand are the natural candidates); a project-registered subcommand's help appears by registration alone - no edits to the shared dispatch; with punk::args or the rendering stack unloadable, boot proceeds and help degrades to a plain subcommand list; verified on both a zipfs-based and a non-zipfs kit where both remain supported.
Context
The kit launcher is, alongside make.tcl (G-030), the first surface a user or
agent meets - and it should showcase the ecosystem's own argument tooling the
way the tomlish cmdline app already does (tabled usage on -help/no-args).
Today the dispatch in punk_main.tcl is a bare switch on the first argument
with no help output at all: an unknown first argument silently becomes a
script attempt, and there is no way to discover the subcommands
(tclsh/shellspy/punk/shell/script) or the package-mode tokens without reading
the source.
Builds on G-031's registration model (landed - G-031 achieved 2026-08-02, see
goals/archive/G-031-componentized-kit-boot.md): the thin project main declares
subcommands with optional punk::args definition ids; the shared dispatch
renders and parses from those declarations - so a project's subcommands appear
in -help by registration alone.
Approach
- punk::args definitions for the top-level dispatch (subcommand enumeration
with @cmd summaries, package-mode leader documented) and per-subcommand
options.
<punkexe> -help,<punkexe> help <subcommand>, and<subcommand> -helpall render tabled usage. - The packagemode leader's help text is LIVE as of G-033's achievement
(2026-07-28, goals/archive/G-033-proj-mode-cwd-project.md): the drafted text
(block semantics, ordering-as-tie-break rule, the
proj:scope prefix with the kit-firstproj:internal-srccanonical example) now sits as the packagemode leader in punk::args::moduledoc::punkexe 0.1.1's(script)::punkexedefinition. This goal's remaining lane is the rendering/parse wiring that consumes it. - Parse errors on declared subcommands produce punk::args usage errors (tabled), not silent fallthrough. The unknown-first-arg -> script reclassification survives only where it does not swallow obvious mistakes - the exact rule is an implementation decision to record here (candidates: reclassify only when the argument names an existing file, else usage error).
- Degradation per the G-030 rules: the kit normally bundles punk::args and the rendering stack, but src/dev modes and damaged kits must not brick the launcher - guarded lazy require; parsing degrades to the current switch semantics and help to a plain subcommand list when punk::args (or the ANSI stack, independently) is unavailable.
- Exit-code and output-cleanliness expectations follow G-015: help to stdout exit 0; usage errors to stderr nonzero; no launcher chatter on the app's channels.
Alternatives considered
- Help text maintained by hand in the dispatch - rejected: the same hand-maintained-help drift make.tcl has (G-030), and project-registered subcommands would need dispatch edits to become discoverable.
- Requiring punk::args at boot - rejected: same repair-path reasoning as G-030 and the punkboot::utils guarded-require rule.
Progress
Landed 2026-08-03 (activation day; boot core 0.2.1 -> 0.3.0, moduledoc::punkexe 0.4.0, moduledoc::punkboot 0.2.0, punkshell 0.48.0):
- Help surface in the shared boot core (G-031 registration model consumer): new built-in subcommand
help(help ?subcommand?, subject prefix-resolving),-helpfirst-argument interception,<subcommand> -helpper-subcommand usage. Rendered from the punk::args::moduledoc::punkexe ids; the per-kit top-level table is(script)::punkexe.launcher- apunk::args::resolved_def -overrideclone of the static(script)::punkexewith the subcommand choices/choicelabels replaced by the kit's LIVE surface (built-ins + help + project registrations);(script)::punkexe.launcherhelpis the same clone of the new(script)::punkexe::helpfor subject validation. Help to stdout exit 0; usage errors to stderr exit 1 (G-015 doctrine). - Registration model extension (additive, back-compatible):
::punkboot::project_subcommand_info- per-name optional keyssummary(top-level table line),argsid(definition id rendered byhelp <name>/<name> -help),package(guarded require before resolving argsid),parse(validate arguments through argsid before the handler; tabled usage error exit 1 on failure).project_subcommandskeeps its proven name->handler shape untouched (tomlish thin main unaffected). Documented runtime-queryably in moduledoc::punkboot's registration definition. - Unknown-first-argument rule (the goal's recorded open decision, adopted with a scriptlib extension): when the effective
unknownfirsttarget isscript, a first argument is reclassified toscriptonly when it names an existing file OR is alib:*scriptlib reference; anything else is refused with the top-level usage on stderr, exit 1 (message + table + a force-script hint). Degrades to the historic always-reclassify semantics when punk::args is unavailable. - Parsed built-in:
buildinfoenforces its@values -min 0 -max 0contract throughpunk::args::parse- argument rejection prints the historicbuildinfo: takes no argumentsline (buildinfo.test pin) PLUS the tabled usage on stderr, exit 2 unchanged; the empty-argument fast path never loads punk::args. Thehelpsubcommand's subject is also genuinely punk::args-parsed (tabled choice errors). - Deliberate carve-outs (all recorded in the moduledoc definitions): tool-style kits (
unknownfirstnaming a project subcommand, e.g the tomlish kits) keep help FLAGS routed to their processor - thehelpword is their launch-surface entry (tomlish-helpparity preserved);tclshintercepts a help flag only as its SOLE argument (every other leading-dash form keeps stock all-args-to-::argv parity - tclshcmd.test pins untouched); a registration withoutargsidkeeps<name> -helpfull passthrough to its handler (never shadows an app's own -help);script/shell/punk/shellspystay declared passthrough surfaces (their arguments belong to the launched script/app). - Degradation (G-030 doctrine): all wiring lazy + guarded - normal boot/dispatch paths never load punk::args; one capability probe per process (punk::args + moduledoc ids resolving),
PUNKBOOT_PLAIN=1forces degraded mode (same env hook as make.tcl), rendering degrades independently of parsing (errorstyle standard/minimal on punk::ansi+textblock availability). Degraded help = plain subcommand list (names + summaries incl registration summaries) on stdout exit 0; boot never fails on any help/parse path. - Characterization:
src/tests/shell/testsuites/punkexe/launcherhelp.test(22 tests: help forms, prefix resolution, refusal rule, tclsh carve-out, buildinfo tabled rejection, PUNKBOOT_PLAIN degradation, fake-thin-main registration harness proving help-by-registration-alone + declared parse, and a metakit-kit leg).
Verification at the achieved flip (2026-08-03):
- Kits (deployed, internal package mode, freshly baked with core 0.3.0 + moduledoc::punkexe 0.4.0): zipfs punk902z + punk91 AND metakit punk86 all render the tabled top-level usage on
help/-help(choices table incl summaries), per-subcommand usage onhelp <sub>/<sub> -help, refuse an unknown non-file first argument (stderr usage, exit 1, stdout empty), rejectbuildinfo extrawith the pinned one-liner + usage table (exit 2, stdout empty), and keep normalbuildinfoclean (stamp reports boot_core_version=0.3.0, project_version=0.48.0). Registration-alone help + declared parse proven twice: interactive fake-thin-main harness runs and the committed launcherhelp.test registration tests (both via<kit> tclsh <fakemain>with a copied core - the G-031 bake-free harness shape). - Degradation proven three ways: raw tclsh90 plain-script run (no punk::args anywhere) boots and renders the plain subcommand list exit 0; PUNKBOOT_PLAIN=1 on a healthy kit forces the plain list (pinned by test); punk::args-present-but-render-broken falls through per-path catches (errorstyle split).
- Suites: launcherhelp.test 22/22; full punkexe subtree 140 total / 136 passed / 4 skipped (pre-existing env-gated console skips) / 0 failed - includes tclshcmd.test (leading-dash parity pins untouched), scriptexec.test, buildinfo.test (exit-2 + one-liner pins intact, test unchanged), kitmountpoint.test (plain-script main shape under raw runtimes incl the 8.6 backport family).
- Traps found and recorded: (a) punk::args' choices-grid render path hits bare
lpopon Tcl 8.6 unless punk::lib's forward-compat shims are loaded - the known punk::args/punk::lib module-web gap surfacing in the new lean launcher context; fixed with a guardedpackage require punk::libafter punk::args in the capability probe (memory + core comment). (b) punk::args choice tables ANSI-highlight the unambiguous PREFIX of each choice name mid-word ('b'+'uildinfo'), so literal substring assertions on choice names must ansistrip first (launcherhelp.test stripansi helper). (c) The tcl87 PATH tclsh mkimg file-relative bake trap re-confirmed (G-134 warnings on every zip kit); re-driven with native Tcl903 per the recorded recipe - final bake clean. - Remaining manual item (cross-repo, user-paced): the tomlish checkout's hand-seeded core copy is still 0.2.1 - a re-seed with the 0.3.0 master gives its kits the
helpword surface (its-helprouting to the tomlish processor is preserved by design via the tool-style-kit carve-out); optionally its thin main can then declare project_subcommand_info (summary/argsid) for a richer table.
Notes
- Activation-freshness overlap survey (goals_xref score G-032, run at activation 2026-08-03), relationships recorded per the GOALS.md activation rule:
- Related: G-038 (piped-to-interactive session continuity) - shares the src/lib/app-punkshell surface this goal's Scope names "as touched"; G-038 already points here (back-pointer added at activation). Different lanes: G-038 owns eof-restart/session state, this goal owns launch help/parse.
- Related: G-077 (punkexe -e one-liner) - same top-level dispatch surface (src/vfs/_config/punk_main.tcl and the boot core it sources since G-031). The unknown-first-arg reclassification rule this goal settles is the surface G-077's -e affordance will extend; G-077 should follow this goal's shipped rule rather than re-deciding it.
- Related: G-131 (boot payload autodetection) - sibling work in the same layout-owned boot core file (punkboot_core.tcl); its container-detection arm lands beside this goal's dispatch wiring. No contract overlap.
- Related: G-089 (scriptlib in kits + packagemode-aware lib: resolution) - sibling consumer of the boot core/thin-main surface (boot_state, punk_main.tcl); the script subcommand's lib: resolution documented in moduledoc::punkexe is its lane.
- Related: G-031 (achieved 2026-08-02 - see goals/archive/G-031-componentized-kit-boot.md; the registration model this renders from is live: ::punkboot::project_subcommands / launch_defaults in boot core 0.2.x, documented runtime-queryably by punk::args::moduledoc::punkboot - render from those), G-030 (make.tcl counterpart and the shared degradation doctrine), G-015 (script subcommand options are a natural first parsed subcommand; exit-code semantics), G-025 (achieved 2026-08-02 - see goals/archive/G-025-exe-selfreport.md; the buildinfo subcommand landed with a moduledoc::punkexe 0.3.0 definition - a natural first parsed subcommand), G-013 (the debugrepl activation review mentions proper-command conversions that would slot into the same pattern).
- G-118 item 10 relationship (recorded 2026-07-24 after overlap review; G-118 achieved
2026-07-25 - see goals/archive/G-118-tclsh-subcommand-review.md): G-118 DELIVERED the
launch-subcommand-family documentation as G-032-COMPATIBLE punk::args definitions -
this goal's source material now exists in punk::args::moduledoc::punkexe
(src/modules/punk/args/moduledoc/punkexe-999999.0a1.0.tm): (script)::punkexe
top-level (subcommand enumeration with choicelabels, package-mode leader documented
in help text, default reclassification described) + per-subcommand
(script)::punkexe::tclsh (full contract, multi-form) and summary-level
script/shell/punk/shellspy. The -help rendering, subcommand option parsing,
registration integration and degradation wiring stay here - this goal wires the
dispatch to render/parse from those ids (punk::args 0.12.7+ resolves the
(script)::* id family through update_definitions/usage). Also: G-118 item 4
shipped a file-exists pre-check in the tclsh subcommand (clean not-found + exit 1)
- precedent for this goal's open unknown-first-arg reclassification rule (reclassify-only-when-file-exists candidate).
- Archived-goal references in this file: G-015 achieved 2026-07-07 (goals/archive/G-015-script-subcommand-piped-stdin.md); G-030 achieved 2026-07-14 (goals/archive/G-030-maketcl-punkargs.md) - the make.tcl counterpart shipped: follow its shipped decisions (
-confirm 0|1prompt policy, explicit@form -synopsis, track-parsing-degrade-rendering split, required-choice leaders for pointed choiceviolation errors).