Updated Understanding (2026-08-17)
Investigation revealed that lazy child resolution already caches correctly within a single container lifetime -- resolveAllLazyChildren() resolves all children once, stores them in childParsers, then clears lazyChildClasses. Subsequent calls are no-ops. The maps and child parsers persist across clear() calls between parses.
The actual optimization opportunity is: resolve children eagerly during container construction rather than deferring to the first parse. Currently, the first executeCommand() triggers child resolution via findChildParserException() -> getAllChildParsers() -> resolveAllLazyChildren(). This adds latency to the user-visible first-command response time.
Problem
When AeshCommandContainerBuilder.buildFromProvider() creates a container for a group command like JBangCommand, it registers child classes as lazy references via addLazyChildWithAliases(). The actual child ProcessedCommand objects (with all their options, lookup maps, etc.) are not created until the first time getAllChildParsers() is called.
For the jbang-like CLI with 11 subcommands, this means the first executeCommand() call pays the cost of:
Profile Evidence
async-profiler CPU profiling (generated path, 10k full-startup iterations):
resolveAllLazyChildren fan-out: ~5% of full CPU (199 samples out of 3,897)
- Triggered from
findChildParserException -> getAllChildParsers on the parse path
Proposed Fix
Resolve lazy children eagerly in AeshCommandContainerBuilder.buildFromProvider() after the container is fully constructed:
// In buildFromProvider(), after addLazyChildWithAliases() calls:
parser.resolveAllLazyChildren(); // resolve eagerly instead of waiting for first parse
Or alternatively, call it in AeshCommandContainer construction or in MutableCommandRegistryImpl.addCommand().
This moves the child resolution cost from the first executeCommand() to container construction, where it is part of the expected initialization overhead.
Impact
- Eliminates ~5% of CPU from the first-command parse path
- No behavior change -- children are resolved identically, just earlier
- Zero risk -- the guard clause in
resolveAllLazyChildren() already handles double-calls safely
- Benefits single-shot CLI tools (
AeshRuntimeRunner) where the first command IS the only command
Updated Understanding (2026-08-17)
Investigation revealed that lazy child resolution already caches correctly within a single container lifetime --
resolveAllLazyChildren()resolves all children once, stores them inchildParsers, then clearslazyChildClasses. Subsequent calls are no-ops. The maps and child parsers persist acrossclear()calls between parses.The actual optimization opportunity is: resolve children eagerly during container construction rather than deferring to the first parse. Currently, the first
executeCommand()triggers child resolution viafindChildParserException() -> getAllChildParsers() -> resolveAllLazyChildren(). This adds latency to the user-visible first-command response time.Problem
When
AeshCommandContainerBuilder.buildFromProvider()creates a container for a group command like JBangCommand, it registers child classes as lazy references viaaddLazyChildWithAliases(). The actual childProcessedCommandobjects (with all their options, lookup maps, etc.) are not created until the first timegetAllChildParsers()is called.For the jbang-like CLI with 11 subcommands, this means the first
executeCommand()call pays the cost of:AeshCommandContainerobjectsAeshCommandLineParserobjectsProcessedCommandobjects (with all their options)ProcessedCommandobjectsisNoColorSet()11 times (see Cache NO_COLOR environment variable check instead of calling System.getenv per parser #574)Profile Evidence
async-profiler CPU profiling (generated path, 10k full-startup iterations):
resolveAllLazyChildrenfan-out: ~5% of full CPU (199 samples out of 3,897)findChildParserException -> getAllChildParserson the parse pathProposed Fix
Resolve lazy children eagerly in
AeshCommandContainerBuilder.buildFromProvider()after the container is fully constructed:Or alternatively, call it in
AeshCommandContainerconstruction or inMutableCommandRegistryImpl.addCommand().This moves the child resolution cost from the first
executeCommand()to container construction, where it is part of the expected initialization overhead.Impact
resolveAllLazyChildren()already handles double-calls safelyAeshRuntimeRunner) where the first command IS the only command