4. Parsing Help and Replaying Cached Evidence
A help parser converts text into a command model. A scraper decides which help queries to execute and how to recurse. Keeping those responsibilities separate allows the same parser to operate on a live binary or a cache of previously collected help.
The useful debugging question is whether the evidence was collected incorrectly or interpreted incorrectly. Replay isolates the second possibility.
A command tree is an intermediate model
The Design model contains a binary name, optional version and description, and a root command. Each command contains options, positional arguments and nested commands.
The following JSON is a minimal illustrative model, not a captured full Vagrant snapshot. Its field names match the command-tree serializer.
{
"binaryName": "example",
"version": "1.0.0",
"root": {
"name": "example",
"options": [],
"arguments": [],
"subCommands": [
{
"name": "status",
"options": [],
"arguments": [],
"subCommands": []
}
]
}
}{
"binaryName": "example",
"version": "1.0.0",
"root": {
"name": "example",
"options": [],
"arguments": [],
"subCommands": [
{
"name": "status",
"options": [],
"arguments": [],
"subCommands": []
}
]
}
}This model records invocation syntax. It contains no assertion about messages printed by status. A parser for status output is a separate runtime component.
JSON also decouples acquisition from compilation. The source generator has mirrored read models compatible with netstandard2.0; it does not reference the modern Design assembly or invoke its parser.
Interpreting a help format
The core parser contract is small. This is the existing interface, shown as a declaration excerpt.
public interface IHelpParser
{
CommandNode? Parse(string helpText, string commandName);
}public interface IHelpParser
{
CommandNode? Parse(string helpText, string commandName);
}The parser receives the text for one command and its name. It extracts descriptions, available subcommands, flag aliases, value kinds, CLR types and positional arguments where the format permits.
GNU-like, Cobra, argparse, Packer and Vagrant formats do not have identical section headings or syntax. A flag may accept a required value, an optional value, multiple values or no value. An angle-bracket token in usage can have a different meaning from a displayed default in the options section.
Consumer-specific parsers handle these conventions. LoggingHelpParser wraps interpretation with contextual logging, making it possible to identify which version and command produced an unexpected node.
Recursive acquisition
HelpScraper walks the command hierarchy through a help callback. The acquisition configuration includes the binary name, parser, help flag, depth and parallelism.
Root help provides the first set of subcommands. Each recognized child can cause another query. The result must preserve hierarchy even when a parent can also be invoked directly. Generation normally selects leaves; IncludeParentCommands opts into generating root and parent invocations.
Recursion also needs limits. A malformed help parser must not create an endless tree or unbounded help process creation. Depth limits and configured concurrency bound exploration; tests should cover duplicate or unexpected command names.
For Git, Packer and Vagrant, -h is deliberately selected. Replacing it indiscriminately with --help can change whether the tool prints terminal help, opens a viewer or accepts the query at all.
The two pieces of cached evidence
The pipeline can preserve both raw help and the interpreted tree.
| Artifact | Purpose |
|---|---|
help/<version>/<command_path>.help.txt |
Text supplied to a help parser for a particular invocation path |
<binary>-<version>.json |
Parsed command hierarchy supplied to Roslyn |
| WinGet option-probe cache | Additional evidence needed to distinguish option semantics not fully exposed by help |
The generic cache callback builds filenames from help-argument path segments joined by underscores, excluding the final help flag. The scraper and replay callback must agree on this convention.
A complete JSON tree cannot recreate every raw help formatting choice. Preserving the help cache is therefore important when changing recognition rules or investigating lost positional arguments.
Replaying without an executable
The replay middleware changes RunHelp into a file-reading callback. The rest of the scraping stage can remain the same.
var replay = new DesignPipeline()
.UseCachedHelp()
.UseScraper("vagrant", parserFactory: parser, helpFlag: "-h")
.Build();var replay = new DesignPipeline()
.UseCachedHelp()
.UseScraper("vagrant", parserFactory: parser, helpFlag: "-h")
.Build();This is a pipeline-construction excerpt; parser comes from the consumer's Design entry point. In the runner's reparse path, versions are discovered from cached help, rather than reacquired through a version collector. Image resolution and container creation are bypassed.
WindowsBinary replay is similarly offline. It must not resolve or inspect the installed executable, run probes again or call installation lifecycle callbacks. It consumes the previously collected help and probe evidence.
A missing cached file is missing evidence. It should lead to an explicit failure or coverage gap, rather than quietly triggering a live acquisition within an operation presented as replay.
A practical parser maintenance loop
First identify the original help file and expected command path. Add or adjust a parser fixture using that exact text. Reparse the same version and inspect changes to options, arguments and subcommands in the JSON.
Then compile the consumer and inspect the generated type. Finally verify ToArguments() for a concrete invocation. This last step catches cases where the parsed shape is syntactically valid but produces incorrect tokens.
For example, Git's optional porcelain value must become --porcelain=2. Packer's template positional arguments currently need PackerInvocation where the scraped schema is incomplete. These issues are visible only when the intermediate model is followed all the way into invocation.
What replay cannot prove
Replay proves how a parser interprets fixed evidence. It does not prove that the binary is still downloadable, that the installation recipe succeeds or that the resulting command works against a real daemon or VM.
Those are acquisition and integration checks. Keeping them separate makes failures attributable and avoids launching infrastructure merely to repair a help parser. See the validation chapter for the full sequence.
Source snapshot
Source inspection: 2 October 2026. These implementation paths are relative to Net/FrenchExDev in the local ecosystem checkout. The v2 changes are largely uncommitted; these paths identify inspected code, without claiming that public branch links contain it.
BinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.Design/Code.csBinaryWrapper/src/FrenchExDev.Net.BinaryWrapper.Design.Lib/DesignPipelineExtensions.csVagrant/src/FrenchExDev.Net.Vagrant.Design/VagrantHelpParser.csWinGet/src/FrenchExDev.Net.WinGet.Design/Program.cs
Previous · Series index · Next