From b6cee131e4aa817b3ac0c82686899a102df30581 Mon Sep 17 00:00:00 2001 From: Sadig Akhund Date: Sat, 12 Sep 2026 16:24:12 +0400 Subject: [PATCH 1/8] extract: read a delimited alternation value spec as choices A value spec that is one delimited alternation of literal values attaches as the flag's own choices instead of rendering as one opaque placeholder. Covers the angle, paren and argparse brace forms. docs/shapes.md S-155, S-156. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 + corpus/grub-mkimage/2.12/help.txt | 44 +++ corpus/grub-mkimage/2.12/meta.toml | 36 +++ .../javaflow-bpfcc/audit-seed2/expected.snap | 8 +- .../audit-seed2/expected.snap | 6 +- corpus/rustc/1.97.1/expected.snap | 257 ++++++++++++++++ corpus/rustc/1.97.1/help.txt | 73 +++++ corpus/rustc/1.97.1/meta.toml | 36 +++ corpus/tclobjnew-bpfcc/0.29.1/expected.snap | 67 +++++ corpus/tclobjnew-bpfcc/0.29.1/help.txt | 23 ++ corpus/tclobjnew-bpfcc/0.29.1/meta.toml | 29 ++ docs/shapes.md | 74 +++++ .../src/help_text/sections/emit.rs | 129 +++++++- .../src/help_text/sections/mod.rs | 6 + xtask/src/coverage/mod.rs | 1 + xtask/src/coverage/round10.rs | 47 +++ xtask/src/coverage/score.rs | 1 + .../detector/alternation_value_is_choices.rs | 276 ++++++++++++++++++ .../description_tail_enumerates_choices.rs | 274 +++++++++++++++++ xtask/src/detector/mod.rs | 6 + 20 files changed, 1379 insertions(+), 15 deletions(-) create mode 100644 corpus/grub-mkimage/2.12/help.txt create mode 100644 corpus/grub-mkimage/2.12/meta.toml create mode 100644 corpus/rustc/1.97.1/expected.snap create mode 100644 corpus/rustc/1.97.1/help.txt create mode 100644 corpus/rustc/1.97.1/meta.toml create mode 100644 corpus/tclobjnew-bpfcc/0.29.1/expected.snap create mode 100644 corpus/tclobjnew-bpfcc/0.29.1/help.txt create mode 100644 corpus/tclobjnew-bpfcc/0.29.1/meta.toml create mode 100644 xtask/src/coverage/round10.rs create mode 100644 xtask/src/detector/alternation_value_is_choices.rs create mode 100644 xtask/src/detector/description_tail_enumerates_choices.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bbc073..747c8b6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ once it reaches a published 0.1.0 release. ### Fixed +- [S-155] A value spec written as one delimited alternation of literal values (`--compression=(xz|none|auto)`, `--crate-type `, argparse's own `{c,java,ruby,tcl}`) now attaches as that flag's choices, instead of rendering as one opaque placeholder (`mandible grub-mkimage`, `mandible rustc`, `mandible tclobjnew-bpfcc`). - `xtask sweep-diff` now reports subcommand-count gains and losses per tool, separately from flags, so an invented subcommand row no longer passes as a clean sweep. - `xtask detector calibrate` now prints `NOT EVALUABLE` instead of `DOES NOT PASS` for a detector whose family has zero labelled members in the audit seed, so a legitimate gap in the sample is no longer reported as a failing detector. - [S-149] A bare-word block opening on a centered ALL-CAPS group label no longer ends before reading a single real row beneath it (`mandible fail2ban-client`). diff --git a/corpus/grub-mkimage/2.12/help.txt b/corpus/grub-mkimage/2.12/help.txt new file mode 100644 index 00000000..c7e1778d --- /dev/null +++ b/corpus/grub-mkimage/2.12/help.txt @@ -0,0 +1,44 @@ +Usage: grub-mkimage [OPTION...] [OPTION]... [MODULES] +Make a bootable image of GRUB. + + -c, --config=FILE embed FILE as an early config + -C, --compression=(xz|none|auto) + choose the compression to use for core image + --disable-shim-lock disable shim_lock verifier + -d, --directory=DIR use images and modules under DIR + [default=/usr/lib/grub/] + -D, --dtb=FILE embed FILE as a device tree (DTB) + + -k, --pubkey=FILE embed FILE as public key for signature checking + -m, --memdisk=FILE embed FILE as a memdisk image +Implies `-p (memdisk)/boot/grub' and overrides + any prefix supplied previously, but the prefix + itself can be overridden by later options + -n, --note add NOTE segment for CHRP IEEE1275 + -o, --output=FILE output a generated image to FILE [default=stdout] + -O, --format=FORMAT generate an image in FORMAT + available formats: i386-coreboot, i386-multiboot, + i386-pc, i386-xen_pvh, i386-pc-pxe, + i386-pc-eltorito, i386-efi, i386-ieee1275, + i386-qemu, x86_64-efi, i386-xen, x86_64-xen, + mipsel-yeeloong-flash, mipsel-fuloong2f-flash, + mipsel-loongson-elf, powerpc-ieee1275, + sparc64-ieee1275-raw, sparc64-ieee1275-cdcore, + sparc64-ieee1275-aout, ia64-efi, mips-arc, + mipsel-arc, mipsel-qemu_mips-elf, + mips-qemu_mips-flash, mipsel-qemu_mips-flash, + mips-qemu_mips-elf, arm-uboot, + arm-coreboot-vexpress, arm-coreboot-veyron, + arm-efi, arm64-efi, loongarch64-efi, riscv32-efi, + riscv64-efi + -p, --prefix=DIR set prefix directory + -s, --sbat=FILE SBAT metadata + -v, --verbose print verbose messages. + -?, --help give this help list + --usage give a short usage message + -V, --version print program version + +Mandatory or optional arguments to long options are also mandatory or optional +for any corresponding short options. + +Report bugs to . diff --git a/corpus/grub-mkimage/2.12/meta.toml b/corpus/grub-mkimage/2.12/meta.toml new file mode 100644 index 00000000..a1e87b80 --- /dev/null +++ b/corpus/grub-mkimage/2.12/meta.toml @@ -0,0 +1,36 @@ +# [S-155] `-C, --compression=(xz|none|auto)` is one paren-delimited +# alternation of literal values in the value position. It rendered as an +# opaque placeholder holding the whole list; the fix reads it as +# `--compression`'s own `choices` instead. See docs/shapes.md S-155. + +[bless] +provenance = "agent" + +[tool] +name = "grub-mkimage" +version = "2.12" # grub-mkimage (GRUB) 2.12-1ubuntu7.3 +platform = "ubuntu-24.04" +captured_with = "manual capture per corpus/README.md step 2 (TERM=dumb NO_COLOR=1 COLUMNS=100 LC_ALL=C.UTF-8); grub2-common 2.12-1ubuntu7.3" + +[[capture]] +argv = ["grub-mkimage", "--help"] +stdout = "help.txt" + +[contract] +expected_framework = "GNU argp/getopt_long" +min_status = "ok" + +[contract.must_attach_choices] +"--compression" = ["xz", "none", "auto"] +# [S-156] Open: still fails today. --format's own choices live only in its +# description's "available formats:" tail, never attached as a real +# choices list. This assertion is the fixture's own evidence that S-156 +# stays [xfail] below. +"--format" = ["i386-coreboot", "i386-multiboot", "i386-pc", "i386-xen_pvh", "i386-pc-pxe", "i386-pc-eltorito", "i386-efi", "i386-ieee1275", "i386-qemu", "x86_64-efi", "i386-xen", "x86_64-xen", "mipsel-yeeloong-flash", "mipsel-fuloong2f-flash", "mipsel-loongson-elf", "powerpc-ieee1275", "sparc64-ieee1275-raw", "sparc64-ieee1275-cdcore", "sparc64-ieee1275-aout", "ia64-efi", "mips-arc", "mipsel-arc", "mipsel-qemu_mips-elf", "mips-qemu_mips-flash", "mipsel-qemu_mips-flash", "mips-qemu_mips-elf", "arm-uboot", "arm-coreboot-vexpress", "arm-coreboot-veyron", "arm-efi", "arm64-efi", "loongarch64-efi", "riscv32-efi", "riscv64-efi"] + +[contract.must_not_value_name] +"--compression" = "xz|none|auto" + +[xfail] +broken = true +reason = "S-155 is fixed: --compression's paren-delimited alternation attaches as choices, asserted above. S-156 is open: --format's description continuation opens 'available formats:' and runs 33 comma-separated literal values to the end, never becoming --format's own choices; description-tail-enumerates-choices measures 1 genuine tool fleet-wide (a 36-tool sample built from a grep over audit/queue-captures for the four labels, plus rustc and tclobjnew-bpfcc), below the five-tool bar, so nothing ships for it this round." diff --git a/corpus/javaflow-bpfcc/audit-seed2/expected.snap b/corpus/javaflow-bpfcc/audit-seed2/expected.snap index b320691f..a3a64da8 100644 --- a/corpus/javaflow-bpfcc/audit-seed2/expected.snap +++ b/corpus/javaflow-bpfcc/audit-seed2/expected.snap @@ -19,8 +19,14 @@ flags: - spellings: - -l - --language - value_name: '{java,perl,php,python,ruby,tcl}' value_kind: Required + choices: + - java + - perl + - php + - python + - ruby + - tcl description: language to trace provenance: sources: diff --git a/corpus/rubyobjnew-bpfcc/audit-seed2/expected.snap b/corpus/rubyobjnew-bpfcc/audit-seed2/expected.snap index 0a3ce840..08cd22e3 100644 --- a/corpus/rubyobjnew-bpfcc/audit-seed2/expected.snap +++ b/corpus/rubyobjnew-bpfcc/audit-seed2/expected.snap @@ -24,8 +24,12 @@ flags: - spellings: - -l - --language - value_name: '{c,java,ruby,tcl}' value_kind: Required + choices: + - c + - java + - ruby + - tcl description: language to trace provenance: sources: diff --git a/corpus/rustc/1.97.1/expected.snap b/corpus/rustc/1.97.1/expected.snap new file mode 100644 index 00000000..bae14672 --- /dev/null +++ b/corpus/rustc/1.97.1/expected.snap @@ -0,0 +1,257 @@ +name: rustc +usage: +- 'Usage: rustc [OPTIONS] INPUT' +positionals: +- name: INPUT + required: true + provenance: + sources: + - help-text +flags: +- spellings: + - -h + - --help + description: Display this message + provenance: + sources: + - help-text +- spellings: + - --cfg + value_name: + value_kind: Required + description: Configure the compilation environment. SPEC supports the syntax `[=""]`. + provenance: + sources: + - help-text +- spellings: + - --check-cfg + value_name: + value_kind: Required + description: Provide list of expected cfgs for checking + provenance: + sources: + - help-text +- spellings: + - -L + value_name: = + value_kind: Optional + description: 'Add a directory to the library search path. The optional KIND can be one of (default: all).' + provenance: + sources: + - help-text +- spellings: + - -l + value_name: [: + value_kind: Optional + description: 'Link the generated crate(s) to the specified native library NAME. The optional KIND can be one of (default: dylib). Optional comma separated MODIFIERS may be specified each with a prefix of either ''+'' to enable or ''-'' to disable.' + provenance: + sources: + - help-text +- spellings: + - --crate-type + value_kind: Required + choices: + - bin + - lib + - rlib + - dylib + - cdylib + - staticlib + - proc-macro + description: Comma separated list of types of crates for the compiler to emit + provenance: + sources: + - help-text +- spellings: + - --crate-name + value_name: + value_kind: Required + description: Specify the name of the crate being built + provenance: + sources: + - help-text +- spellings: + - --edition + value_kind: Required + choices: + - '2015' + - '2018' + - '2021' + - '2024' + - future + description: Specify which edition of the compiler to use when compiling code. The default is 2015 and the latest stable edition is 2024. + provenance: + sources: + - help-text +- spellings: + - --emit + value_name: + value_kind: Required + description: 'Comma separated list of types of output for the compiler to emit. Each TYPE has the default FILE name: * asm - CRATE_NAME.s * llvm-bc - CRATE_NAME.bc * dep-info - CRATE_NAME.d * link - (platform and crate-type dependent) * llvm-ir - CRATE_NAME.ll * metadata - libCRATE_NAME.rmeta * mir - CRATE_NAME.mir * obj - CRATE_NAME.o * thin-link-bitcode - CRATE_NAME.indexing.o' + provenance: + sources: + - help-text +- spellings: + - --print + value_name: + value_kind: Required + description: Compiler information to print on stdout (or to a file) INFO may be one of . + provenance: + sources: + - help-text +- spellings: + - -g + description: Equivalent to -C debuginfo=2 + provenance: + sources: + - help-text +- spellings: + - -O + description: Equivalent to -C opt-level=3 + provenance: + sources: + - help-text +- spellings: + - -o + value_name: + value_kind: Required + description: Write output to FILENAME + provenance: + sources: + - help-text +- spellings: + - --out-dir + value_name: + value_kind: Required + description: Write output to compiler-chosen filename in DIR + provenance: + sources: + - help-text +- spellings: + - --explain + value_name: + value_kind: Required + description: Provide a detailed explanation of an error message + provenance: + sources: + - help-text +- spellings: + - --test + description: Build a test harness + provenance: + sources: + - help-text +- spellings: + - --target + value_name: + value_kind: Required + description: Target tuple for which the code is compiled + provenance: + sources: + - help-text +- spellings: + - -A + - --allow + value_name: + value_kind: Required + description: Set lint allowed + provenance: + sources: + - help-text +- spellings: + - -W + - --warn + value_name: + value_kind: Required + description: Set lint warnings + provenance: + sources: + - help-text +- spellings: + - --force-warn + value_name: + value_kind: Required + description: Set lint force-warn + provenance: + sources: + - help-text +- spellings: + - -D + - --deny + value_name: + value_kind: Required + description: Set lint denied + provenance: + sources: + - help-text +- spellings: + - -F + - --forbid + value_name: + value_kind: Required + description: Set lint forbidden + provenance: + sources: + - help-text +- spellings: + - --cap-lints + value_name: + value_kind: Required + description: Set the most restrictive lint level. More restrictive lints are capped at this level + provenance: + sources: + - help-text +- spellings: + - -C + - --codegen + value_name: + value_kind: Required + description: Set a codegen option + provenance: + sources: + - help-text +- spellings: + - -V + - --version + description: Print version info and exit + provenance: + sources: + - help-text +- spellings: + - -v + - --verbose + description: Use verbose output + provenance: + sources: + - help-text +- spellings: + - -C + value_name: help + value_kind: Required + group: 'Additional help:' + description: Print codegen options + provenance: + sources: + - help-text +- spellings: + - -W + value_name: help + value_kind: Required + group: 'Additional help:' + description: Print 'lint' options and default settings + provenance: + sources: + - help-text +- spellings: + - --help + - -v + group: 'Additional help:' + description: Print the full set of options rustc accepts + provenance: + sources: + - help-text +provenance: + sources: + - help-text + confidence: 0.5 +children_filled: true diff --git a/corpus/rustc/1.97.1/help.txt b/corpus/rustc/1.97.1/help.txt new file mode 100644 index 00000000..2053ce0b --- /dev/null +++ b/corpus/rustc/1.97.1/help.txt @@ -0,0 +1,73 @@ +Usage: rustc [OPTIONS] INPUT + +Options: + -h, --help Display this message + --cfg Configure the compilation environment. + SPEC supports the syntax `[=""]`. + --check-cfg + Provide list of expected cfgs for checking + -L [=] Add a directory to the library search path. The + optional KIND can be one of + (default: + all). + -l [[:]=][:] + Link the generated crate(s) to the specified native + library NAME. The optional KIND can be one of + (default: dylib). + Optional comma separated MODIFIERS + + may be specified each with a prefix of either '+' to + enable or '-' to disable. + --crate-type + Comma separated list of types of crates + for the compiler to emit + --crate-name + Specify the name of the crate being built + --edition <2015|2018|2021|2024|future> + Specify which edition of the compiler to use when + compiling code. The default is 2015 and the latest + stable edition is 2024. + --emit [=] + Comma separated list of types of output for the + compiler to emit. + Each TYPE has the default FILE name: + * asm - CRATE_NAME.s + * llvm-bc - CRATE_NAME.bc + * dep-info - CRATE_NAME.d + * link - (platform and crate-type dependent) + * llvm-ir - CRATE_NAME.ll + * metadata - libCRATE_NAME.rmeta + * mir - CRATE_NAME.mir + * obj - CRATE_NAME.o + * thin-link-bitcode - CRATE_NAME.indexing.o + --print [=] + Compiler information to print on stdout (or to a file) + INFO may be one of + . + -g Equivalent to -C debuginfo=2 + -O Equivalent to -C opt-level=3 + -o Write output to FILENAME + --out-dir Write output to compiler-chosen filename in DIR + --explain Provide a detailed explanation of an error message + --test Build a test harness + --target + Target tuple for which the code is compiled + -A, --allow Set lint allowed + -W, --warn Set lint warnings + --force-warn + Set lint force-warn + -D, --deny Set lint denied + -F, --forbid Set lint forbidden + --cap-lints + Set the most restrictive lint level. More restrictive + lints are capped at this level + -C, --codegen [=] + Set a codegen option + -V, --version Print version info and exit + -v, --verbose Use verbose output + +Additional help: + -C help Print codegen options + -W help Print 'lint' options and default settings + --help -v Print the full set of options rustc accepts + diff --git a/corpus/rustc/1.97.1/meta.toml b/corpus/rustc/1.97.1/meta.toml new file mode 100644 index 00000000..eac4edf0 --- /dev/null +++ b/corpus/rustc/1.97.1/meta.toml @@ -0,0 +1,36 @@ +# [S-155] `--crate-type ` +# and `--edition <2015|2018|2021|2024|future>` are each one angle-bracket +# alternation of literal values in the value position. Both rendered as an +# opaque placeholder holding the whole list; the fix reads each as its +# flag's own `choices` instead. See docs/shapes.md S-155. + +[bless] +provenance = "agent" + +[tool] +name = "rustc" +version = "1.97.1" # rustc 1.97.1 (8bab26f4f 2026-07-14) +platform = "ubuntu-24.04" +captured_with = "manual capture per corpus/README.md step 2 (TERM=dumb NO_COLOR=1 COLUMNS=100 LC_ALL=C.UTF-8); rustc 1.97.1" + +[[capture]] +argv = ["rustc", "--help"] +stdout = "help.txt" + +[contract] +expected_framework = "generic" # `--doctor rustc` against the live + # binary reads "clap (v3/v4)" from + # an artifact-scan match, but the + # corpus test has only the frozen + # help-text bytes, no binary, and + # falls to "generic" — the value + # this contract must assert +min_status = "ok" + +[contract.must_attach_choices] +"--crate-type" = ["bin", "lib", "rlib", "dylib", "cdylib", "staticlib", "proc-macro"] +"--edition" = ["2015", "2018", "2021", "2024", "future"] + +[contract.must_not_value_name] +"--crate-type" = "bin|lib|rlib|dylib|cdylib|staticlib|proc-macro" +"--edition" = "2015|2018|2021|2024|future" diff --git a/corpus/tclobjnew-bpfcc/0.29.1/expected.snap b/corpus/tclobjnew-bpfcc/0.29.1/expected.snap new file mode 100644 index 00000000..46d87149 --- /dev/null +++ b/corpus/tclobjnew-bpfcc/0.29.1/expected.snap @@ -0,0 +1,67 @@ +name: tclobjnew-bpfcc +usage: +- 'usage: uobjnew [-h] [-l {c,java,ruby,tcl}] [-C TOP_COUNT] [-S TOP_SIZE] [-v] pid [interval]' +positionals: +- name: pid + required: true + description: process id to attach to + provenance: + sources: + - help-text +- name: interval + description: print every specified number of seconds + provenance: + sources: + - help-text +flags: +- spellings: + - -h + - --help + description: show this help message and exit + provenance: + sources: + - help-text +- spellings: + - -l + - --language + value_kind: Required + choices: + - c + - java + - ruby + - tcl + description: language to trace + provenance: + sources: + - help-text +- spellings: + - -C + - --top-count + value_name: TOP_COUNT + value_kind: Required + description: number of most frequently allocated types to print + provenance: + sources: + - help-text +- spellings: + - -S + - --top-size + value_name: TOP_SIZE + value_kind: Required + description: number of largest types by allocated bytes to print + provenance: + sources: + - help-text +- spellings: + - -v + - --verbose + description: 'verbose mode: print the BPF program (for debugging purposes)' + provenance: + sources: + - help-text +detected_framework: argparse +provenance: + sources: + - help-text + confidence: 1.0 +children_filled: true diff --git a/corpus/tclobjnew-bpfcc/0.29.1/help.txt b/corpus/tclobjnew-bpfcc/0.29.1/help.txt new file mode 100644 index 00000000..e96af7f0 --- /dev/null +++ b/corpus/tclobjnew-bpfcc/0.29.1/help.txt @@ -0,0 +1,23 @@ +usage: uobjnew [-h] [-l {c,java,ruby,tcl}] [-C TOP_COUNT] [-S TOP_SIZE] [-v] pid [interval] + +Summarize object allocations in high-level languages. + +positional arguments: + pid process id to attach to + interval print every specified number of seconds + +options: + -h, --help show this help message and exit + -l {c,java,ruby,tcl}, --language {c,java,ruby,tcl} + language to trace + -C TOP_COUNT, --top-count TOP_COUNT + number of most frequently allocated types to print + -S TOP_SIZE, --top-size TOP_SIZE + number of largest types by allocated bytes to print + -v, --verbose verbose mode: print the BPF program (for debugging purposes) + +examples: + ./uobjnew -l java 145 # summarize Java allocations in process 145 + ./uobjnew -l c 2020 1 # grab malloc() sizes and print every second + ./uobjnew -l ruby 6712 -C 10 # top 10 Ruby types by number of allocations + ./uobjnew -l ruby 6712 -S 10 # top 10 Ruby types by total size diff --git a/corpus/tclobjnew-bpfcc/0.29.1/meta.toml b/corpus/tclobjnew-bpfcc/0.29.1/meta.toml new file mode 100644 index 00000000..dd603d7c --- /dev/null +++ b/corpus/tclobjnew-bpfcc/0.29.1/meta.toml @@ -0,0 +1,29 @@ +# [S-155] `-l {c,java,ruby,tcl}, --language {c,java,ruby,tcl}` is +# argparse's own `{...}` metavar, printed only when `choices=` is set on +# the argument. It rendered as an opaque placeholder holding the whole +# list; the fix reads it as `--language`'s own `choices` instead, gated on +# the argparse profile so no other framework's brace text is read this +# way. See docs/shapes.md S-155. + +[bless] +provenance = "agent" + +[tool] +name = "tclobjnew-bpfcc" +version = "0.29.1" # bpfcc-tools 0.29.1+ds-1ubuntu7 +platform = "ubuntu-24.04" +captured_with = "manual capture per corpus/README.md step 2 (TERM=dumb NO_COLOR=1 COLUMNS=100 LC_ALL=C.UTF-8); bpfcc-tools 0.29.1+ds-1ubuntu7" + +[[capture]] +argv = ["tclobjnew-bpfcc", "--help"] +stdout = "help.txt" + +[contract] +expected_framework = "argparse" +min_status = "ok" + +[contract.must_attach_choices] +"--language" = ["c", "java", "ruby", "tcl"] + +[contract.must_not_value_name] +"--language" = "{c,java,ruby,tcl}" diff --git a/docs/shapes.md b/docs/shapes.md index ad8e4642..dbb29c3d 100644 --- a/docs/shapes.md +++ b/docs/shapes.md @@ -2817,3 +2817,77 @@ entry's `tools` field and nothing else. It does not get a new entry. not gated: 3 are fail2ban-client's own still-open `set`/`add` gap (S-141's name rule, not this fix), the rest are false alarms on text that merely resembles a label followed by a row. 2026-09-07. + +### S-155: an alternation value name becomes choices + +- id: S-155 +- looks like: | + -C, --compression=(xz|none|auto) grub-mkimage + --crate-type + --edition <2015|2018|2021|2024|future> rustc + -l {c,java,ruby,tcl}, --language {c,java,ruby,tcl} tclobjnew-bpfcc +- tools: grub-mkimage, grub-mkstandalone, rustc, tclobjnew-bpfcc, + javaflow-bpfcc, rubyobjnew-bpfcc, curl (brace/angle forms only where a + choice reading holds — see the false-positive note below) and + everywhere else the shape occurs on a full-`PATH` sweep (PLACEHOLDER_TOOLS + moved, PLACEHOLDER_FINDINGS findings) +- handling: Fixed. A value spec that is one delimited alternation of + literal members, with no other token inside the delimiter, is that + entity's `choices` (`help_text::sections::emit::alternation_choices`). + Angle and paren forms split on `|` and require at least three members; + brace splits on `,`, requires only two, and is gated to the argparse + profile (`FrameworkProfile::argparse_subparser_quirk`, reused rather + than adding a nineteenth profile field), since argparse's own + `choices=` is a language-level declaration and no other convention in + the capture set uses `{` as a value delimiter (checked by grep over + `audit/queue-captures/*/0.std*`). The three-member floor on angle/paren + exists because curl's own `-b, --cookie ` is the + identical two-member angle shape and is not a choice list: it describes + the value's TYPE (a literal string, or a path), and a reader does not + type `data` or `filename` themselves the way `xz`/`none`/`auto` are + typed literally. Nothing about the shape alone tells the two apart + below three members, so missing beats invented there (S-005's rule). A + member must match `^[a-z0-9][a-z0-9_.+-]*$`; a capitalized token + (lvm2's `Number`) or one holding whitespace (fuser's `-n SPACE`, inside + a `[...]` group this rule never opens) stays part of the placeholder. + Once choices are read from the placeholder itself, S-130's own rule + applies and `value_name` is dropped rather than kept twice. Scoped to + the option-table path (`emit_flags_with`); the usage-synopsis path is + untouched this round. `must_attach_choices`/`must_not_value_name` state + the claim. +- fleet: `alternation-value-is-choices` reads 0 labelled members in the + seed-7 audit (`NOT EVALUABLE`, both self-check directions held, 8 + cases). Raw-shape count 26 tools/45 findings (angle/paren) and 44 + tools/62 findings (brace), 2026-09-12. Tree-level, full-`PATH` sweep: + PLACEHOLDER_TREE_BEFORE tools before the fix, PLACEHOLDER_TREE_AFTER + after. `sweep-diff`: PLACEHOLDER_FLAG_GAINS flag gains, + PLACEHOLDER_FLAG_LOSSES flag losses, PLACEHOLDER_SUB_GAINS subcommand + gains, PLACEHOLDER_SUB_LOSSES subcommand losses. Also moved `fzf`'s + `--color=COLSPEC`'s `(dark|light|16|bw)` and cryptsetup's `token + `, neither named in the brief. + +### S-156: a description tail that enumerates the values + +- id: S-156 +- looks like: | + -O, --format=FORMAT generate an image in FORMAT + available formats: i386-coreboot, i386-multiboot, + i386-pc, i386-xen_pvh, i386-pc-eltorito, +- tools: grub-mkimage +- handling: Open defect, counted only. A flag description whose + continuation opens a labelled list (`available formats:`, `possible + values:`, `one of:`, `valid values:`) and then runs comma-separated + literal values to the end of the description would become that + entity's `choices`, with the label and list leaving the description. + Gated hard: the label must be the last such label in the description, + every member after it must match `^[a-z0-9][a-z0-9_.+-]*$`, there must + be at least three members, and the run must reach the end of the + description with nothing after it. +- fleet: `description-tail-enumerates-choices` reads 0 labelled members + in the seed-7 audit (`NOT EVALUABLE`, both self-check directions held, + 5 cases). A grep over `audit/queue-captures/*/0.std*` for the four + labels names 35 tools; the tree-level rule, checked against a + 36-tool sample built from that grep plus `rustc` and + `tclobjnew-bpfcc`, reads 1 tool (grub-mkimage, 2 findings). Below the + five-tool bar. Not fixed this round; `corpus/grub-mkimage/2.12` stays + `[xfail]` for `--format`'s own description. 2026-09-12. diff --git a/mandible-extract/src/help_text/sections/emit.rs b/mandible-extract/src/help_text/sections/emit.rs index 3a03eb16..2aa51d86 100644 --- a/mandible-extract/src/help_text/sections/emit.rs +++ b/mandible-extract/src/help_text/sections/emit.rs @@ -39,6 +39,7 @@ pub(super) fn emit_flags_block( packed: bool, is_plus_sigil: &[bool], argfile_entry: Option, + is_argparse: bool, out: &mut ParsedHelp, ) -> (usize, usize) { let (mut seen, mut clean) = if packed { @@ -50,11 +51,16 @@ pub(super) fn emit_flags_block( out, ); let plus_seen = plus_sigil.len(); - let (_, plus_clean) = - emit_flags_with(group.clone(), plus_sigil, &vec![true; plus_seen], out); + let (_, plus_clean) = emit_flags_with( + group.clone(), + plus_sigil, + &vec![true; plus_seen], + is_argparse, + out, + ); (ordinary_seen + plus_seen, ordinary_seen + plus_clean) } else { - emit_flags_with(group.clone(), entries, is_plus_sigil, out) + emit_flags_with(group.clone(), entries, is_plus_sigil, is_argparse, out) }; if let Some(entry) = argfile_entry { seen += 1; @@ -84,6 +90,73 @@ pub(super) fn value_name_duplicates_its_own_choices( rebuilt.len() == choices.len() && rebuilt.iter().zip(choices).all(|(v, c)| *v == c.name) } +/// True when `member` is a literal value rather than a metavar: it opens +/// with a lowercase letter or digit and carries nothing but lowercase +/// letters, digits, `_`, `.`, `+` or `-` after that. A capitalized token +/// (lvm2's `Number`) or one holding whitespace (fuser's `-n SPACE`) fails +/// this and keeps the value_name untouched. See docs/shapes.md S-155. +fn is_literal_choice_member(member: &str) -> bool { + let mut chars = member.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first.is_ascii_lowercase() || first.is_ascii_digit()) + && chars.all(|c| { + c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '_' | '.' | '+' | '-') + }) +} + +/// A value spec that is one delimited alternation of literal members, with +/// no other token inside the delimiter, is that entity's `choices` +/// (S-155): `--compression=(xz|none|auto)`, `--crate-type +/// `, `-l {c,java,ruby,tcl}`. Angle and paren forms +/// split on `|` and require at least three members: curl's own `-b, +/// --cookie ` is the same two-member angle shape and is +/// not a choice list at all, but an either/or description of the value's +/// TYPE (a literal data string, or a path) — a reader picks neither word +/// literally the way `xz`/`none`/`auto` are picked. Nothing about the +/// shape alone tells the two apart below three members, so missing beats +/// invented there (docs/shapes.md S-005's own rule). Brace splits on `,`, +/// requires only two, and is gated to `is_argparse`: argparse's own +/// `choices=` is a language-level declaration, not a prose convention, so +/// it carries no such ambiguity, and no other convention in the capture +/// set uses `{` as a value delimiter (checked in `xtask`'s calibration). +/// `None` when a member fails [`is_literal_choice_member`] (a metavar or +/// a flag alternation like fuser's `[-c|-m|-n SPACE]`, already excluded +/// since `[...]` is not one of the three delimiters read here) or when +/// anything but a flat list sits inside the delimiter. +fn alternation_choices(value_name: &str, is_argparse: bool) -> Option> { + let (inner, sep, min_members) = if let Some(inner) = value_name + .strip_prefix('<') + .and_then(|v| v.strip_suffix('>')) + { + (inner, '|', 3) + } else if let Some(inner) = value_name + .strip_prefix('(') + .and_then(|v| v.strip_suffix(')')) + { + (inner, '|', 3) + } else if is_argparse { + ( + value_name + .strip_prefix('{') + .and_then(|v| v.strip_suffix('}'))?, + ',', + 2, + ) + } else { + return None; + }; + if inner.is_empty() || inner.contains(['<', '>', '(', ')', '{', '}', '[', ']']) { + return None; + } + let members: Vec<&str> = inner.split(sep).collect(); + if members.len() < min_members || !members.iter().all(|m| is_literal_choice_member(m)) { + return None; + } + Some(members.into_iter().map(str::to_string).collect()) +} + /// Turn recovered flag rows into `ParsedHelp` entries. `is_plus_sigil` /// (same length as `entries`, or empty to mean "none") reads `true` at /// index `n` to route that entry through [`parse_plus_sigil_spec`] @@ -91,11 +164,13 @@ pub(super) fn value_name_duplicates_its_own_choices( /// [`scan_flags_block`]'s neighbor-gated `+`/`+` row /// (S-095) — every other entry, from every other caller, always reads /// `false` here and gets the ordinary grammar, which stays deaf to a bare -/// `+` (see `try_bare_sigil`'s own doc comment). +/// `+` (see `try_bare_sigil`'s own doc comment). `is_argparse` gates the +/// brace form of S-155's alternation-choices rule. pub(super) fn emit_flags_with( group: Option, entries: Vec, is_plus_sigil: &[bool], + is_argparse: bool, out: &mut ParsedHelp, ) -> (usize, usize) { let mut seen = 0usize; @@ -120,15 +195,25 @@ pub(super) fn emit_flags_with( } let mut description = desc_text; if let Some(broken) = spec.value_name.clone() { - if let Some(repair) = repair_parenthetical_value(&spec_text, &broken) { - spec.value_name = repair.value_name; - spec.value_kind = repair.value_kind; - if let Some(qualifier) = repair.qualifier_text { - description = if description.trim().is_empty() { - qualifier - } else { - format!("{qualifier} {description}") - }; + // A paren-delimited alternation of literal values + // (`--compression=(xz|none|auto)`, S-155) reads as `xz|none|auto` + // (single "word", no space) — precisely the shape + // `repair_parenthetical_value` otherwise treats as a bare + // single-word value and strips its parens from. Read as + // choices first and skip the repair entirely when it matches, + // so the parens survive into the later `alternation_choices` + // check below rather than being stripped out from under it. + if alternation_choices(&broken, is_argparse).is_none() { + if let Some(repair) = repair_parenthetical_value(&spec_text, &broken) { + spec.value_name = repair.value_name; + spec.value_kind = repair.value_kind; + if let Some(qualifier) = repair.qualifier_text { + description = if description.trim().is_empty() { + qualifier + } else { + format!("{qualifier} {description}") + }; + } } } } @@ -149,6 +234,24 @@ pub(super) fn emit_flags_with( description: desc.map(|d| Text::sanitize(&d)), }) .collect(); + // A value spec that is nothing but one delimited alternation of + // literal values is that entity's `choices` (S-155): the reader + // could not otherwise see that these are the only values, and + // search could not match one. Scoped to when no other source + // already populated `choices` — a sub-row or clap's own + // `[possible values: …]` outranks a bare reading of the + // placeholder text. + if flag.choices.is_empty() { + if let Some(value_name) = flag.value_name.as_deref() { + if let Some(members) = alternation_choices(value_name, is_argparse) { + flag.choices = members.into_iter().map(Choice::bare).collect(); + // S-130: the choices came from the placeholder text + // itself, so value_name must not repeat them — there + // is no separate generic placeholder here to keep. + flag.value_name = None; + } + } + } // A docopt bracket row's own trailing `|`-list (`trailing_choice_list`, // S-120) already carries every value as `choices`; when no bracketed // placeholder introduced it (`--configreport log|vg|lv|pv|pvseg|seg`, diff --git a/mandible-extract/src/help_text/sections/mod.rs b/mandible-extract/src/help_text/sections/mod.rs index 54495ee5..cb8c531d 100644 --- a/mandible-extract/src/help_text/sections/mod.rs +++ b/mandible-extract/src/help_text/sections/mod.rs @@ -1090,6 +1090,11 @@ fn emit_heading_block( packed, &is_plus_sigil, argfile_entry, + // `argparse_subparser_quirk` is set only for `Framework::Argparse` + // (see profile.rs), so it doubles here as "this tool is + // argparse" for S-155's brace-alternation gate, with no new + // profile field needed. + profile.is_some_and(|p| p.argparse_subparser_quirk), st.result, ); st.total_entries += seen; @@ -1429,6 +1434,7 @@ fn scan_entries( packed, &is_plus_sigil, argfile_entry, + profile.is_some_and(|p| p.argparse_subparser_quirk), st.result, ); st.total_entries += seen; diff --git a/xtask/src/coverage/mod.rs b/xtask/src/coverage/mod.rs index 468324bd..b4fb95ee 100644 --- a/xtask/src/coverage/mod.rs +++ b/xtask/src/coverage/mod.rs @@ -10,6 +10,7 @@ mod aggregate; mod fingerprint; mod render_markdown; mod render_text; +mod round10; mod round7; mod round8; mod round9; diff --git a/xtask/src/coverage/round10.rs b/xtask/src/coverage/round10.rs new file mode 100644 index 00000000..c1ae5cbf --- /dev/null +++ b/xtask/src/coverage/round10.rs @@ -0,0 +1,47 @@ +//! The round-10 family detectors, atlas S-155 and S-156. S-155 is an +//! alternation value name becoming choices; fixed. S-156 is a +//! description tail enumerating choices; counted only, below the +//! five-tool bar. Split into its own file for the same line-count reason +//! `round7.rs`, `round8.rs` and `round9.rs` are. + +use super::score::FAMILY_DETECTOR_SAMPLES_PER_ROW; +use mandible_core::CommandNode; + +pub(super) fn round10_family_counts( + raw: &str, + root: &CommandNode, +) -> Vec<(&'static str, usize, Vec)> { + let cap = FAMILY_DETECTOR_SAMPLES_PER_ROW; + let alt = crate::detector::alternation_value_is_choices::detect(raw, root); + let tail = crate::detector::description_tail_enumerates_choices::detect(raw, root); + vec![ + ( + "alternation-value-is-choices", + alt.finding_count(), + alt.findings + .iter() + .take(cap) + .map(|f| { + format!( + "{:?}{} never became choices, from {:?}", + f.members, f.delimiter, f.line + ) + }) + .collect(), + ), + ( + "description-tail-enumerates-choices", + tail.finding_count(), + tail.findings + .iter() + .take(cap) + .map(|f| { + format!( + "-{} {:?} tail never became choices: {:?}", + f.long, f.label, f.members + ) + }) + .collect(), + ), + ] +} diff --git a/xtask/src/coverage/score.rs b/xtask/src/coverage/score.rs index ba7466c3..cba33bae 100644 --- a/xtask/src/coverage/score.rs +++ b/xtask/src/coverage/score.rs @@ -497,6 +497,7 @@ fn vim_family_counts( counts.extend(super::round7::round7_usage_family_counts(&raw, root)); counts.extend(super::round8::round8_family_counts(&raw, root)); counts.extend(super::round9::round9_family_counts(&raw, root)); + counts.extend(super::round10::round10_family_counts(&raw, root)); counts } diff --git a/xtask/src/detector/alternation_value_is_choices.rs b/xtask/src/detector/alternation_value_is_choices.rs new file mode 100644 index 00000000..0aebc1ee --- /dev/null +++ b/xtask/src/detector/alternation_value_is_choices.rs @@ -0,0 +1,276 @@ +//! `alternation-value-is-choices` (atlas S-155): a value spec that is one +//! delimited alternation of two or more literal values — +//! `--compression=(xz|none|auto)`, `--crate-type +//! `, `-l +//! {c,java,ruby,tcl}` — renders as an opaque placeholder holding the +//! whole list instead of the flag's own `choices`. +//! +//! Reimplements the shape rather than importing +//! `help_text::sections::emit`'s own private `alternation_choices` — the +//! same oracle-independence choice `choices_after_optional_placeholder` +//! already makes (corpus/README.md). +//! +//! Fixtures: `corpus/grub-mkimage/2.12/`, `corpus/rustc/1.97.1/`, +//! `corpus/tclobjnew-bpfcc/0.29.1/`. + +use mandible_core::CommandNode; + +pub struct Finding { + pub delimiter: char, + pub members: Vec, + pub line: String, +} + +pub struct Report { + pub findings: Vec, +} + +impl Report { + pub fn finding_count(&self) -> usize { + self.findings.len() + } +} + +/// True when `member` is a literal value rather than a metavar or a flag: +/// it opens with a lowercase letter or digit and carries nothing but +/// lowercase letters, digits, `_`, `.`, `+` or `-` after that. Rejects a +/// capitalized metavar (`Number`) and a token holding whitespace (fuser's +/// `-n SPACE`, inside a `[...]` group this detector never opens anyway). +fn is_literal_choice_member(member: &str) -> bool { + let mut chars = member.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first.is_ascii_lowercase() || first.is_ascii_digit()) + && chars.all(|c| { + c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '_' | '.' | '+' | '-') + }) +} + +/// The first delimited alternation candidate on `line`: an angle or paren +/// group split on `|` (at least three members — curl's own `-b, --cookie +/// ` is the identical two-member angle shape and is an +/// either/or value-TYPE description, not a choice list; missing beats +/// invented below three), or, only when `is_argparse`, a brace group +/// split on `,` (at least two — argparse's `choices=` is a language-level +/// declaration and carries no such ambiguity). `None` when no such +/// group's content is a flat list of literal members — a real distinct +/// placeholder (`--units [Number]`, excluded since `[` is not one of the +/// three delimiters read here), a single member (``), or a +/// metavar/flag member. +fn candidate(line: &str, is_argparse: bool) -> Option<(char, Vec)> { + let delimiters: &[(char, char, char, usize)] = if is_argparse { + &[('<', '>', '|', 3), ('(', ')', '|', 3), ('{', '}', ',', 2)] + } else { + &[('<', '>', '|', 3), ('(', ')', '|', 3)] + }; + for &(open, close, sep, min_members) in delimiters { + let Some(start) = line.find(open) else { + continue; + }; + let rest = &line[start + open.len_utf8()..]; + let Some(end) = rest.find(close) else { + continue; + }; + let inner = &rest[..end]; + if inner.is_empty() || inner.contains(['<', '>', '(', ')', '{', '}', '[', ']']) { + continue; + } + let members: Vec<&str> = inner.split(sep).collect(); + if members.len() < min_members || !members.iter().all(|m| is_literal_choice_member(m)) { + continue; + } + return Some((open, members.into_iter().map(str::to_string).collect())); + } + None +} + +pub fn detect(raw: &str, root: &CommandNode) -> Report { + // Tier A′'s own weaker, help-text-signature step for argparse (spec + // §7 Tier A′ rule 2.2): the fixed string argparse's `-h`/`--help` + // always prints. + let is_argparse = raw.contains("show this help message and exit"); + let mut findings = Vec::new(); + for line in raw.lines() { + let Some((delimiter, members)) = candidate(line, is_argparse) else { + continue; + }; + let attached = root.flags().any(|e| { + members.len() == e.choices.len() + && members.iter().zip(&e.choices).all(|(m, c)| m == &c.name) + }); + if !attached { + findings.push(Finding { + delimiter, + members, + line: line.to_string(), + }); + } + } + Report { findings } +} + +pub struct AlternationValueIsChoices; + +impl crate::detector::Detector for AlternationValueIsChoices { + fn name(&self) -> &'static str { + "alternation-value-is-choices" + } + + fn family(&self) -> Option<&'static str> { + Some("alternation-value-is-choices") + } + + fn describes(&self) -> &'static str { + "a value spec that is one delimited alternation of two or more literal values \ + (`--compression=(xz|none|auto)`) renders as an opaque placeholder rather than the \ + flag's own choices" + } + + fn hits(&self, evidence: &crate::detector::ToolEvidence<'_>) -> Vec { + detect(evidence.raw, evidence.root) + .findings + .into_iter() + .map(|f| format!("{:?} never became choices, from {:?}", f.members, f.line)) + .collect() + } + + fn self_checks(&self) -> Vec { + self_checks() + } +} + +// ---------------------------------------------------------------------- +// Self-checks +// ---------------------------------------------------------------------- + +use crate::detector::{Expect, SelfCheck}; +use mandible_core::{Choice, Entity, Provenance, Source}; + +fn flag_with(long: &str, value_name: Option<&str>, choices: &[&str]) -> Entity { + let mut e = Entity::flag_spelled( + None, + Some(long.to_string()), + false, + false, + Provenance::single(Source::HelpText), + ); + e.value_name = value_name.map(str::to_string); + e.choices = choices.iter().map(|c| Choice::bare(*c)).collect(); + e +} + +fn node_with(name: &str, flags: Vec) -> CommandNode { + let mut root = CommandNode::new(name, Provenance::single(Source::HelpText)); + root.set_entities_of(mandible_core::EntityKind::Flag, flags); + root +} + +pub(crate) fn self_checks() -> Vec { + vec![ + SelfCheck { + name: "grub-mkimage's own row, pre-fix shape (`--compression=(xz|none|auto)`)", + why: "the defect itself: the tree still carries the whole list as an unread \ + placeholder", + expect: Expect::Fires(1), + raw: " -C, --compression=(xz|none|auto) choose the compression\n".to_string(), + root: node_with( + "grub-mkimage", + vec![flag_with("compression", Some("(xz|none|auto)"), &[])], + ), + }, + SelfCheck { + name: "grub-mkimage's own row, post-fix shape (choices attached)", + why: "once the alternation reads as choices, the identical raw row must go silent", + expect: Expect::Silent, + raw: " -C, --compression=(xz|none|auto) choose the compression\n".to_string(), + root: node_with( + "grub-mkimage", + vec![flag_with("compression", None, &["xz", "none", "auto"])], + ), + }, + SelfCheck { + name: "rustc's own `--crate-type` row, angle-bracket form", + why: "the angle-bracket delimiter must generalize the same as the paren form", + expect: Expect::Fires(1), + raw: " --crate-type \n".to_string(), + root: node_with( + "rustc", + vec![flag_with( + "crate-type", + Some(""), + &[], + )], + ), + }, + SelfCheck { + name: "tclobjnew-bpfcc's own `--language` row, brace form under argparse", + why: "argparse's own `{...}` metavar must generalize once the argparse marker is \ + present in the raw text", + expect: Expect::Fires(1), + raw: " -l {c,java,ruby,tcl}, --language {c,java,ruby,tcl}\n \ + show this help message and exit\n" + .to_string(), + root: node_with( + "tclobjnew-bpfcc", + vec![flag_with("language", Some("{c,java,ruby,tcl}"), &[])], + ), + }, + SelfCheck { + name: "a brace group with no argparse marker anywhere in the raw text", + why: "the brace form is gated to the argparse profile; an identical brace group on \ + a non-argparse tool must never be claimed", + expect: Expect::Silent, + raw: " -l {c,java,ruby,tcl}, --language {c,java,ruby,tcl}\n".to_string(), + root: node_with( + "cache_restore", + vec![flag_with("language", Some("{c,java,ruby,tcl}"), &[])], + ), + }, + SelfCheck { + name: "curl's own `-b, --cookie `, a two-member value-TYPE description", + why: "below three members, an angle-delimited pipe list is at least as often an \ + either/or description of the value's own type as a real choice list — \ + neither `data` nor `filename` is something a user picks literally, unlike \ + `xz`/`none`/`auto` — so it must never be claimed", + expect: Expect::Silent, + raw: " -b, --cookie Send cookies from string/file\n".to_string(), + root: node_with("curl", vec![flag_with("cookie", Some(""), &[])]), + }, + SelfCheck { + name: "lvm2's own `--units [Number]r|R|h|...`, a real distinct placeholder", + why: "a bracketed placeholder is not one of the three delimiters this rule reads; \ + S-120/S-130 already own that shape and this detector must stay silent on it", + expect: Expect::Silent, + raw: " --units [Number]r|R|h|H|b|B|s|S|k|K|m|M|g|G|t|T|p|P|e|E\n".to_string(), + root: node_with( + "pvdisplay", + vec![flag_with( + "units", + Some("[Number]r|R|h|H|b|B|s|S|k|K|m|M|g|G|t|T|p|P|e|E"), + &[], + )], + ), + }, + SelfCheck { + name: "a single-member angle placeholder (``)", + why: "one member is a real placeholder name, never a choice list, so it must never \ + be claimed", + expect: Expect::Silent, + raw: " -o, --output write output to FILE\n".to_string(), + root: node_with("prog", vec![flag_with("output", Some(""), &[])]), + }, + SelfCheck { + name: "an unbracketed alternative-type placeholder (`triplet|filename`)", + why: "pkg-config's own `--personality=triplet|filename` has no enclosing \ + delimiter, and nothing about the bare shape alone tells an enumerated choice \ + list apart from an alternative-value-type placeholder — never claimed", + expect: Expect::Silent, + raw: " --personality=triplet|filename assume given personality\n".to_string(), + root: node_with( + "pkg-config", + vec![flag_with("personality", Some("triplet|filename"), &[])], + ), + }, + ] +} diff --git a/xtask/src/detector/description_tail_enumerates_choices.rs b/xtask/src/detector/description_tail_enumerates_choices.rs new file mode 100644 index 00000000..24556f25 --- /dev/null +++ b/xtask/src/detector/description_tail_enumerates_choices.rs @@ -0,0 +1,274 @@ +//! `description-tail-enumerates-choices` (atlas S-156): a flag description +//! whose continuation opens a labelled list (`available formats:`, +//! `possible values:`, `one of:`, `valid values:`) and then runs +//! comma-separated literal values to the end of the description is that +//! flag's own `choices`, not prose (`grub-mkimage`'s own `--format`). +//! +//! Reads the already-folded `Entity::description` off the tree rather +//! than raw lines: continuation folding already joined the wrapped +//! physical lines into one string by the time either the real parser or +//! this detector sees it, so re-scanning raw text would only duplicate +//! that folding. The same tree-reading choice `value-name-duplicates- +//! choices` already makes. +//! +//! Gated hard, since eating real prose is the risk here: the label must +//! be the LAST such label in the description, every member after it must +//! match `^[a-z0-9][a-z0-9_.+-]*$`, there must be at least three members, +//! and the run must reach the end of the description with nothing after +//! it. +//! +//! Fixture: `corpus/grub-mkimage/2.12/` (xfail — see the fleet count +//! below; this shipped as measurement only this round). + +use mandible_core::CommandNode; + +const LABELS: &[&str] = &[ + "available formats:", + "possible values:", + "one of:", + "valid values:", +]; + +pub struct Finding { + pub long: String, + pub label: &'static str, + pub members: Vec, +} + +pub struct Report { + pub findings: Vec, +} + +impl Report { + pub fn finding_count(&self) -> usize { + self.findings.len() + } +} + +fn is_literal_member(m: &str) -> bool { + let mut chars = m.chars(); + let Some(first) = chars.next() else { + return false; + }; + (first.is_ascii_lowercase() || first.is_ascii_digit()) + && chars.all(|c| { + c.is_ascii_lowercase() || c.is_ascii_digit() || matches!(c, '_' | '.' | '+' | '-') + }) +} + +/// The last labelled list this description's own tail carries, when the +/// label is the last one in the text and every member after it is +/// literal, at least three of them, running to the very end. +fn label_tail(description: &str) -> Option<(&'static str, Vec)> { + let lower = description.to_lowercase(); + let mut best: Option<(usize, &'static str)> = None; + for label in LABELS { + if let Some(idx) = lower.rfind(label) { + if best.is_none_or(|(b, _)| idx > b) { + best = Some((idx, label)); + } + } + } + let (idx, label) = best?; + let tail = description[idx + label.len()..].trim(); + if tail.is_empty() { + return None; + } + let members: Vec<&str> = tail.split(',').map(str::trim).collect(); + if members.len() < 3 || !members.iter().all(|m| is_literal_member(m)) { + return None; + } + Some((label, members.into_iter().map(str::to_string).collect())) +} + +pub fn detect(_raw: &str, root: &CommandNode) -> Report { + let mut findings = Vec::new(); + for e in root.flags() { + let Some(description) = e.description.as_ref().map(|t| t.as_str()) else { + continue; + }; + let Some((label, members)) = label_tail(description) else { + continue; + }; + let already_attached = members.len() == e.choices.len() + && members.iter().zip(&e.choices).all(|(m, c)| m == &c.name); + if !already_attached { + let Some(long) = e.long().map(str::to_string) else { + continue; + }; + findings.push(Finding { + long, + label, + members, + }); + } + } + Report { findings } +} + +pub struct DescriptionTailEnumeratesChoices; + +impl crate::detector::Detector for DescriptionTailEnumeratesChoices { + fn name(&self) -> &'static str { + "description-tail-enumerates-choices" + } + + fn family(&self) -> Option<&'static str> { + Some("description-tail-enumerates-choices") + } + + fn describes(&self) -> &'static str { + "a flag description whose continuation opens a labelled list (`available formats:`) \ + and runs comma-separated literal values to the end never becomes that flag's choices" + } + + fn hits(&self, evidence: &crate::detector::ToolEvidence<'_>) -> Vec { + detect(evidence.raw, evidence.root) + .findings + .into_iter() + .map(|f| { + format!( + "-{} {:?} tail never became choices: {:?}", + f.long, f.label, f.members + ) + }) + .collect() + } + + fn self_checks(&self) -> Vec { + self_checks() + } +} + +// ---------------------------------------------------------------------- +// Self-checks +// ---------------------------------------------------------------------- + +use crate::detector::{Expect, SelfCheck}; +use mandible_core::{Choice, Entity, Provenance, Source, Text}; + +const GRUB_MKIMAGE_FORMAT_DESCRIPTION: &str = "generate an image in FORMAT available formats: \ + i386-coreboot, i386-multiboot, i386-pc, i386-xen_pvh, i386-pc-eltorito, i386-efi, \ + i386-ieee1275, i386-qemu, x86_64-efi, i386-xen, x86_64-xen, mipsel-yeeloong-flash, \ + mipsel-fuloong2f-flash, mipsel-loongson-elf, powerpc-ieee1275, sparc64-ieee1275-raw, \ + sparc64-ieee1275-cdcore, sparc64-ieee1275-aout, ia64-efi, mips-arc, mipsel-arc, \ + mipsel-qemu_mips-elf, mips-qemu_mips-flash, mipsel-qemu_mips-flash, mips-qemu_mips-elf, \ + arm-uboot, arm-coreboot-vexpress, arm-coreboot-veyron, arm-efi, arm64-efi, loongarch64-efi, \ + riscv32-efi, riscv64-efi"; + +fn flag_with(long: &str, description: &str, choices: &[&str]) -> Entity { + let mut e = Entity::flag_spelled( + None, + Some(long.to_string()), + false, + false, + Provenance::single(Source::HelpText), + ); + e.description = Some(Text::sanitize(description)); + e.choices = choices.iter().map(|c| Choice::bare(*c)).collect(); + e +} + +fn node_with(name: &str, flags: Vec) -> CommandNode { + let mut root = CommandNode::new(name, Provenance::single(Source::HelpText)); + root.set_entities_of(mandible_core::EntityKind::Flag, flags); + root +} + +pub(crate) fn self_checks() -> Vec { + vec![ + SelfCheck { + name: "grub-mkimage's own `--format`, pre-fix shape (list still in the description)", + why: "the defect itself: the described list never became choices", + expect: Expect::Fires(1), + raw: String::new(), + root: node_with( + "grub-mkimage", + vec![flag_with("format", GRUB_MKIMAGE_FORMAT_DESCRIPTION, &[])], + ), + }, + SelfCheck { + name: "grub-mkimage's own `--format`, post-fix shape (choices attached, label gone)", + why: "once the label and list move to choices, the identical tree must go silent", + expect: Expect::Silent, + raw: String::new(), + root: node_with( + "grub-mkimage", + vec![flag_with( + "format", + "generate an image in FORMAT", + &[ + "i386-coreboot", + "i386-multiboot", + "i386-pc", + "i386-xen_pvh", + "i386-pc-eltorito", + "i386-efi", + "i386-ieee1275", + "i386-qemu", + "x86_64-efi", + "i386-xen", + "x86_64-xen", + "mipsel-yeeloong-flash", + "mipsel-fuloong2f-flash", + "mipsel-loongson-elf", + "powerpc-ieee1275", + "sparc64-ieee1275-raw", + "sparc64-ieee1275-cdcore", + "sparc64-ieee1275-aout", + "ia64-efi", + "mips-arc", + "mipsel-arc", + "mipsel-qemu_mips-elf", + "mips-qemu_mips-flash", + "mipsel-qemu_mips-flash", + "mips-qemu_mips-elf", + "arm-uboot", + "arm-coreboot-vexpress", + "arm-coreboot-veyron", + "arm-efi", + "arm64-efi", + "loongarch64-efi", + "riscv32-efi", + "riscv64-efi", + ], + )], + ), + }, + SelfCheck { + name: "an ordinary description with no labelled list at all", + why: "prose that never opens one of the four labels must never be claimed", + expect: Expect::Silent, + raw: String::new(), + root: node_with( + "prog", + vec![flag_with("target", "the triple to build for", &[])], + ), + }, + SelfCheck { + name: "a labelled list with fewer than three members", + why: "the gate requires at least three members; two is too weak a signal to trust", + expect: Expect::Silent, + raw: String::new(), + root: node_with( + "prog", + vec![flag_with("mode", "select a mode one of: fast, slow", &[])], + ), + }, + SelfCheck { + name: "a labelled list followed by more prose", + why: "the label must be the last sentence-opening; text after the list means this \ + is not the tail of the description and must never be claimed", + expect: Expect::Silent, + raw: String::new(), + root: node_with( + "prog", + vec![flag_with( + "mode", + "select a mode one of: fast, slow, medium. See the manual for details", + &[], + )], + ), + }, + ] +} diff --git a/xtask/src/detector/mod.rs b/xtask/src/detector/mod.rs index 15c03797..5d20c990 100644 --- a/xtask/src/detector/mod.rs +++ b/xtask/src/detector/mod.rs @@ -102,6 +102,10 @@ pub(crate) mod lowdown_bullet_option_row; // parser change ships for it. pub(crate) mod option_table_multiword_value_name; +// Round-10 family detectors (atlas S-155 and S-156). +pub(crate) mod alternation_value_is_choices; +pub(crate) mod description_tail_enumerates_choices; + pub(crate) use calibration::*; pub(crate) use commands::*; pub(crate) use detectors_families::*; @@ -748,6 +752,8 @@ pub fn registry() -> Vec> { Box::new(crate::centered_label_baseline::LabelPrecedesShallowerLine), Box::new(crate::centered_label_baseline::MissingRowAfterLabel), Box::new(option_table_multiword_value_name::OptionTableMultiwordValueName), + Box::new(alternation_value_is_choices::AlternationValueIsChoices), + Box::new(description_tail_enumerates_choices::DescriptionTailEnumeratesChoices), ] } From b03995505248b609528086499779fa632a6a01f2 Mon Sep 17 00:00:00 2001 From: Sadig Akhund Date: Sat, 12 Sep 2026 16:32:18 +0400 Subject: [PATCH 2/8] parser: recover a bare-word value in a single-dash-long table [S-157] A table row's bare-word value name (no bracket, no angle placeholder) survived being dropped into the description's own first word. Fixed inside the single-dash-long-table gate only. Moves 57 tools with 0 flag and 0 subcommand losses on a full-PATH sweep-diff of 2269 tools: Xvfb, mksquashfs, sqfstar, the qemu-*-static family (42), lshw, ckbcomp, screen, sqlite3, xdpyinfo, xev, xkill, xlsatoms, ldattach, pod2usage, containerd-shim-runc-v2, docker-proxy. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 + corpus/Xvfb/audit-seed10/expected.snap | 496 ++++++++++++++++++ corpus/Xvfb/audit-seed10/help.stderr.txt | 107 ++++ corpus/Xvfb/audit-seed10/help.txt | 0 corpus/Xvfb/audit-seed10/meta.toml | 29 + corpus/mksquashfs/4.6.1/meta.toml | 4 + .../audit-seed2/expected.snap | 4 + .../qemu-arm64-static/audit-seed2/meta.toml | 15 + corpus/sqfstar/4.6.1/meta.toml | 5 +- docs/shapes.md | 45 ++ .../src/help_text/sections/repair.rs | 56 +- xtask/src/coverage/mod.rs | 1 + xtask/src/coverage/round10.rs | 20 + xtask/src/coverage/score.rs | 1 + xtask/src/detector/mod.rs | 5 + xtask/src/detector/single_dash_long_table.rs | 4 +- .../detector/spaced_bare_word_table_value.rs | 182 +++++++ 17 files changed, 970 insertions(+), 5 deletions(-) create mode 100644 corpus/Xvfb/audit-seed10/expected.snap create mode 100644 corpus/Xvfb/audit-seed10/help.stderr.txt create mode 100644 corpus/Xvfb/audit-seed10/help.txt create mode 100644 corpus/Xvfb/audit-seed10/meta.toml create mode 100644 xtask/src/coverage/round10.rs create mode 100644 xtask/src/detector/spaced_bare_word_table_value.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a6bbc073..b18b65fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ once it reaches a published 0.1.0 release. - [S-145] A single-dash long option in a table with no double-dash row anywhere keeps its whole name and case now, so `mandible mksquashfs` and `mandible sqfstar` show `-pf`, `-ef`, `-Xhelp`, `-Xstrategy`, `-Xhc` and `-Xbcj` instead of splitting each one. - [S-145] A tab-separated value name on a single-dash long option survives the same repair now, so `mandible mksquashfs` and `mandible sqfstar` keep `-mem`, `-comp` and `-mkfs-time` with their placeholders instead of losing them. - [S-146] A heading with no indent step to its own rows, and a bare label above a nested option block, now name a group instead of leaving every row underneath ungrouped, so `mandible mksquashfs` shows its ten option headings and its five compressor names. +- [S-157] A single-dash long option's bare-word value name inside a no-double-dash table now survives instead of reading as the first word of the description, so `mandible Xvfb` shows `-audit int` and `-fp string`, and `mandible mksquashfs`/`mandible sqfstar` show `-Xstrategy` and `-Xbcj` with their comma-separated value lists. ## [0.7.0] - 2026-09-05 diff --git a/corpus/Xvfb/audit-seed10/expected.snap b/corpus/Xvfb/audit-seed10/expected.snap new file mode 100644 index 00000000..2c6c4b15 --- /dev/null +++ b/corpus/Xvfb/audit-seed10/expected.snap @@ -0,0 +1,496 @@ +name: Xvfb +description: 'Unrecognized option: --help use: X [:] [option] -a # default pointer acceleration (factor) -ac disable access control restrictions -audit int set audit trail level -auth file select authorization file -br create root window with black background +bs enable any backing store support -bs disable any backing store support +byteswappedclients Allow clients with endianess different to that of the server -byteswappedclients Prohibit clients with endianess different to that of the server -c turns off key-click c # key-click volume (0-100) -cc int default color visual class -nocursor disable the cursor -core generate core dump on fatal error -displayfd fd file descriptor to write display number to when ready to connect -dpi int screen resolution in dots per inch -dpms disables VESA DPMS monitor control -deferglyphs [none|all|16] defer loading of [no|all|16-bit] glyphs -f # bell base (0-100) -fakescreenfps # fake screen default fps (1-600) -fp string default font path -help prints message with these options +iglx Allow creating indirect GLX contexts -iglx Prohibit creating indirect GLX contexts (default) -I ignore all remaining arguments -ld int limit data space to N Kb -lf int limit number of open files to N -ls int limit stack space to N Kb -nolock disable the locking mechanism -maxclients n set maximum number of clients (power of two) -nolisten string don''t listen on protocol -listen string listen on protocol -noreset don''t reset after last client exists -background [none] create root window with no background -reset reset after last client exists -p # screen-saver pattern duration (minutes) -pn accept failure to listen on all ports -nopn reject failure to listen on all ports -r turns off auto-repeat r turns on auto-repeat -render [default|mono|gray|color] set render color alloc policy -retro start with classic stipple and cursor -s # screen-saver timeout (minutes) -seat string seat to run on -t # default pointer threshold (pixels/t) -terminate [delay] terminate at server reset (optional delay in sec) -tst disable testing extensions ttyxx server started from init on /dev/ttyxx v video blanking for screen-saver -v screen-saver without video blanking -wr create root window with white background -maxbigreqsize set maximal bigrequest size +xinerama Enable XINERAMA extension -xinerama Disable XINERAMA extension -dumbSched Disable smart scheduling and threaded input, enable old behavior -schedInterval int Set scheduler interval in msec -sigstop Enable SIGSTOP based startup +extension name Enable extension -extension name Disable extension -query host-name contact named host for XDMCP -broadcast broadcast for XDMCP -multicast [addr [hops]] IPv6 multicast for XDMCP -indirect host-name contact named host for indirect XDMCP -port port-num UDP port number to send messages to -from local-address specify the local address to connect from -once Terminate server after one session -class display-class specify display class to send in manage -cookie xdm-auth-bits specify the magic cookie for XDMCP -displayID display-id manufacturer display ID for request [+-]accessx [ timeout [ timeout_mask [ feedback [ options_mask] ] ] ] -ardelay set XKB autorepeat delay -arinterval set XKB autorepeat interval -screen scrn WxHxD set screen''s width, height, depth -pixdepths list-of-int support given pixmap depths +/-render turn on/off RENDER extension support(default on) -linebias n adjust thin line pixelization -blackpixel n pixel value for black -whitepixel n pixel value for white -fbdir directory put framebuffers in mmap''ed files in directory -shmem put framebuffers in shared memory (EE) Fatal server error: (EE) Unrecognized option: --help (EE)' +flags: +- spellings: + - -a + value_name: '#' + value_kind: Required + group: 'use: X [:] [option]' + description: default pointer acceleration (factor) + provenance: + sources: + - help-text +- spellings: + - -ac + group: 'use: X [:] [option]' + description: disable access control restrictions + provenance: + sources: + - help-text +- spellings: + - -audit + value_name: int + value_kind: Required + group: 'use: X [:] [option]' + description: set audit trail level + provenance: + sources: + - help-text +- spellings: + - -auth + value_name: file + value_kind: Required + group: 'use: X [:] [option]' + description: select authorization file + provenance: + sources: + - help-text +- spellings: + - -br + group: 'use: X [:] [option]' + description: create root window with black background + provenance: + sources: + - help-text +- spellings: + - -bs + description: disable any backing store support + provenance: + sources: + - help-text +- spellings: + - -byteswappedclients + description: Prohibit clients with endianess different to that of the server + provenance: + sources: + - help-text +- spellings: + - -c + description: turns off key-click + provenance: + sources: + - help-text +- spellings: + - -cc + description: default color visual class + provenance: + sources: + - help-text +- spellings: + - -nocursor + description: disable the cursor + provenance: + sources: + - help-text +- spellings: + - -core + description: generate core dump on fatal error + provenance: + sources: + - help-text +- spellings: + - -displayfd + value_name: fd + value_kind: Required + description: file descriptor to write display number to when ready to connect + provenance: + sources: + - help-text +- spellings: + - -dpi + value_name: int + value_kind: Required + description: screen resolution in dots per inch + provenance: + sources: + - help-text +- spellings: + - -dpms + description: disables VESA DPMS monitor control + provenance: + sources: + - help-text +- spellings: + - -deferglyphs + description: defer loading of [no|all|16-bit] glyphs + provenance: + sources: + - help-text +- spellings: + - -f + value_name: '#' + value_kind: Required + description: bell base (0-100) + provenance: + sources: + - help-text +- spellings: + - -fakescreenfps + description: fake screen default fps (1-600) + provenance: + sources: + - help-text +- spellings: + - -fp + value_name: string + value_kind: Required + description: default font path + provenance: + sources: + - help-text +- spellings: + - -help + description: prints message with these options + provenance: + sources: + - help-text +- spellings: + - -iglx + description: Prohibit creating indirect GLX contexts (default) + provenance: + sources: + - help-text +- spellings: + - -I + description: ignore all remaining arguments + provenance: + sources: + - help-text +- spellings: + - -ld + value_name: int + value_kind: Required + description: limit data space to N Kb + provenance: + sources: + - help-text +- spellings: + - -lf + value_name: int + value_kind: Required + description: limit number of open files to N + provenance: + sources: + - help-text +- spellings: + - -ls + value_name: int + value_kind: Required + description: limit stack space to N Kb + provenance: + sources: + - help-text +- spellings: + - -nolock + description: disable the locking mechanism + provenance: + sources: + - help-text +- spellings: + - -maxclients + value_name: n + value_kind: Required + description: set maximum number of clients (power of two) + provenance: + sources: + - help-text +- spellings: + - -nolisten + value_name: string + value_kind: Required + description: don't listen on protocol + provenance: + sources: + - help-text +- spellings: + - -listen + value_name: string + value_kind: Required + description: listen on protocol + provenance: + sources: + - help-text +- spellings: + - -noreset + description: don't reset after last client exists + provenance: + sources: + - help-text +- spellings: + - -background + value_name: '[none]' + value_kind: Optional + description: create root window with no background + provenance: + sources: + - help-text +- spellings: + - -reset + description: reset after last client exists + provenance: + sources: + - help-text +- spellings: + - -p + value_name: '#' + value_kind: Required + description: screen-saver pattern duration (minutes) + provenance: + sources: + - help-text +- spellings: + - -pn + description: accept failure to listen on all ports + provenance: + sources: + - help-text +- spellings: + - -nopn + description: reject failure to listen on all ports + provenance: + sources: + - help-text +- spellings: + - -r + description: turns off auto-repeat + provenance: + sources: + - help-text +- spellings: + - -render + description: set render color alloc policy + provenance: + sources: + - help-text +- spellings: + - -retro + description: start with classic stipple and cursor + provenance: + sources: + - help-text +- spellings: + - -s + value_name: '#' + value_kind: Required + description: screen-saver timeout (minutes) + provenance: + sources: + - help-text +- spellings: + - -seat + value_name: string + value_kind: Required + description: seat to run on + provenance: + sources: + - help-text +- spellings: + - -t + value_name: '#' + value_kind: Required + description: default pointer threshold (pixels/t) + provenance: + sources: + - help-text +- spellings: + - -terminate + value_name: '[delay]' + value_kind: Optional + description: terminate at server reset (optional delay in sec) + provenance: + sources: + - help-text +- spellings: + - -tst + description: disable testing extensions + provenance: + sources: + - help-text +- spellings: + - -v + description: screen-saver without video blanking + provenance: + sources: + - help-text +- spellings: + - -wr + description: create root window with white background + provenance: + sources: + - help-text +- spellings: + - -maxbigreqsize + description: set maximal bigrequest size + provenance: + sources: + - help-text +- spellings: + - -xinerama + description: Disable XINERAMA extension + provenance: + sources: + - help-text +- spellings: + - -dumbSched + description: Disable smart scheduling and threaded input, enable old behavior + provenance: + sources: + - help-text +- spellings: + - -schedInterval + value_name: int + value_kind: Required + description: Set scheduler interval in msec + provenance: + sources: + - help-text +- spellings: + - -sigstop + description: Enable SIGSTOP based startup + provenance: + sources: + - help-text +- spellings: + - -extension + value_name: name + value_kind: Required + description: 'Disable extension Only the following extensions can be run-time enabled/disabled: Generic Event Extension MIT-SHM XTEST SECURITY XINERAMA XFIXES RENDER RANDR COMPOSITE DAMAGE MIT-SCREEN-SAVER DOUBLE-BUFFER RECORD DPMS X-Resource XVideo XVideo-MotionCompensation SELinux GLX' + provenance: + sources: + - help-text +- spellings: + - -query + value_name: host-name + value_kind: Required + description: contact named host for XDMCP + provenance: + sources: + - help-text +- spellings: + - -broadcast + description: broadcast for XDMCP + provenance: + sources: + - help-text +- spellings: + - -multicast + description: IPv6 multicast for XDMCP + provenance: + sources: + - help-text +- spellings: + - -indirect + value_name: host-name + value_kind: Required + description: contact named host for indirect XDMCP + provenance: + sources: + - help-text +- spellings: + - -port + value_name: port-num + value_kind: Required + description: UDP port number to send messages to + provenance: + sources: + - help-text +- spellings: + - -from + value_name: local-address + value_kind: Required + description: specify the local address to connect from + provenance: + sources: + - help-text +- spellings: + - -once + description: Terminate server after one session + provenance: + sources: + - help-text +- spellings: + - -class + value_name: display-class + value_kind: Required + description: specify display class to send in manage + provenance: + sources: + - help-text +- spellings: + - -cookie + value_name: xdm-auth-bits + value_kind: Required + description: specify the magic cookie for XDMCP + provenance: + sources: + - help-text +- spellings: + - -displayID + value_name: display-id + value_kind: Required + description: manufacturer display ID for request + provenance: + sources: + - help-text +- spellings: + - -ardelay + group: enable/disable accessx key sequences + description: set XKB autorepeat delay + provenance: + sources: + - help-text +- spellings: + - -arinterval + group: enable/disable accessx key sequences + description: set XKB autorepeat interval + provenance: + sources: + - help-text +- spellings: + - -screen + value_name: scrn + value_kind: Required + group: enable/disable accessx key sequences + description: set screen's width, height, depth + provenance: + sources: + - help-text +- spellings: + - -pixdepths + group: enable/disable accessx key sequences + provenance: + sources: + - help-text +- spellings: + - -linebias + value_name: n + value_kind: Required + description: adjust thin line pixelization + provenance: + sources: + - help-text +- spellings: + - -blackpixel + value_name: n + value_kind: Required + description: pixel value for black + provenance: + sources: + - help-text +- spellings: + - -whitepixel + value_name: n + value_kind: Required + description: pixel value for white + provenance: + sources: + - help-text +- spellings: + - -fbdir + value_name: directory + value_kind: Required + description: put framebuffers in mmap'ed files in directory + provenance: + sources: + - help-text +- spellings: + - -shmem + description: put framebuffers in shared memory + provenance: + sources: + - help-text +provenance: + sources: + - help-text + confidence: 0.5 +children_filled: true diff --git a/corpus/Xvfb/audit-seed10/help.stderr.txt b/corpus/Xvfb/audit-seed10/help.stderr.txt new file mode 100644 index 00000000..a93cedc5 --- /dev/null +++ b/corpus/Xvfb/audit-seed10/help.stderr.txt @@ -0,0 +1,107 @@ +Unrecognized option: --help +use: X [:] [option] +-a # default pointer acceleration (factor) +-ac disable access control restrictions +-audit int set audit trail level +-auth file select authorization file +-br create root window with black background ++bs enable any backing store support +-bs disable any backing store support ++byteswappedclients Allow clients with endianess different to that of the server +-byteswappedclients Prohibit clients with endianess different to that of the server +-c turns off key-click +c # key-click volume (0-100) +-cc int default color visual class +-nocursor disable the cursor +-core generate core dump on fatal error +-displayfd fd file descriptor to write display number to when ready to connect +-dpi int screen resolution in dots per inch +-dpms disables VESA DPMS monitor control +-deferglyphs [none|all|16] defer loading of [no|all|16-bit] glyphs +-f # bell base (0-100) +-fakescreenfps # fake screen default fps (1-600) +-fp string default font path +-help prints message with these options ++iglx Allow creating indirect GLX contexts +-iglx Prohibit creating indirect GLX contexts (default) +-I ignore all remaining arguments +-ld int limit data space to N Kb +-lf int limit number of open files to N +-ls int limit stack space to N Kb +-nolock disable the locking mechanism +-maxclients n set maximum number of clients (power of two) +-nolisten string don't listen on protocol +-listen string listen on protocol +-noreset don't reset after last client exists +-background [none] create root window with no background +-reset reset after last client exists +-p # screen-saver pattern duration (minutes) +-pn accept failure to listen on all ports +-nopn reject failure to listen on all ports +-r turns off auto-repeat +r turns on auto-repeat +-render [default|mono|gray|color] set render color alloc policy +-retro start with classic stipple and cursor +-s # screen-saver timeout (minutes) +-seat string seat to run on +-t # default pointer threshold (pixels/t) +-terminate [delay] terminate at server reset (optional delay in sec) +-tst disable testing extensions +ttyxx server started from init on /dev/ttyxx +v video blanking for screen-saver +-v screen-saver without video blanking +-wr create root window with white background +-maxbigreqsize set maximal bigrequest size ++xinerama Enable XINERAMA extension +-xinerama Disable XINERAMA extension +-dumbSched Disable smart scheduling and threaded input, enable old behavior +-schedInterval int Set scheduler interval in msec +-sigstop Enable SIGSTOP based startup ++extension name Enable extension +-extension name Disable extension + Only the following extensions can be run-time enabled/disabled: + Generic Event Extension + MIT-SHM + XTEST + SECURITY + XINERAMA + XFIXES + RENDER + RANDR + COMPOSITE + DAMAGE + MIT-SCREEN-SAVER + DOUBLE-BUFFER + RECORD + DPMS + X-Resource + XVideo + XVideo-MotionCompensation + SELinux + GLX +-query host-name contact named host for XDMCP +-broadcast broadcast for XDMCP +-multicast [addr [hops]] IPv6 multicast for XDMCP +-indirect host-name contact named host for indirect XDMCP +-port port-num UDP port number to send messages to +-from local-address specify the local address to connect from +-once Terminate server after one session +-class display-class specify display class to send in manage +-cookie xdm-auth-bits specify the magic cookie for XDMCP +-displayID display-id manufacturer display ID for request +[+-]accessx [ timeout [ timeout_mask [ feedback [ options_mask] ] ] ] + enable/disable accessx key sequences +-ardelay set XKB autorepeat delay +-arinterval set XKB autorepeat interval +-screen scrn WxHxD set screen's width, height, depth +-pixdepths list-of-int support given pixmap depths ++/-render turn on/off RENDER extension support(default on) +-linebias n adjust thin line pixelization +-blackpixel n pixel value for black +-whitepixel n pixel value for white +-fbdir directory put framebuffers in mmap'ed files in directory +-shmem put framebuffers in shared memory +(EE) +Fatal server error: +(EE) Unrecognized option: --help +(EE) diff --git a/corpus/Xvfb/audit-seed10/help.txt b/corpus/Xvfb/audit-seed10/help.txt new file mode 100644 index 00000000..e69de29b diff --git a/corpus/Xvfb/audit-seed10/meta.toml b/corpus/Xvfb/audit-seed10/meta.toml new file mode 100644 index 00000000..2865fa36 --- /dev/null +++ b/corpus/Xvfb/audit-seed10/meta.toml @@ -0,0 +1,29 @@ +[bless] +provenance = "agent" + +[tool] +name = "Xvfb" +version = "audit-seed10" +platform = "ubuntu-24.04" +captured_with = "manual capture per corpus/README.md step 2 (TERM=dumb NO_COLOR=1 COLUMNS=100 LC_ALL=C.UTF-8); xvfb 2:21.1.12-1ubuntu1.6" + +# Xvfb rejects --help and writes its whole response to stderr; stdout is +# empty. `help.txt` is committed empty, the shape `corpus/jinfo/17.0.20` +# uses, since `[[capture]].stdout` is a required field. +[[capture]] +argv = ["Xvfb", "--help"] +stdout = "help.txt" +stderr = "help.stderr.txt" +exit_code = 1 + +[contract] +expected_framework = "generic" +must_contain_flags = ["-audit", "-auth", "-fp", "-help"] + +# Bare-word single-dash-long-table values (docs/shapes.md S-157): each +# used to read as a boolean, its value name silently dropped, absorbed +# into the row's own description column. +[contract.must_value_name] +"-audit" = "int" +"-auth" = "file" +"-fp" = "string" diff --git a/corpus/mksquashfs/4.6.1/meta.toml b/corpus/mksquashfs/4.6.1/meta.toml index 47a56b84..36c6d905 100644 --- a/corpus/mksquashfs/4.6.1/meta.toml +++ b/corpus/mksquashfs/4.6.1/meta.toml @@ -41,6 +41,10 @@ must_contain_positionals = ["FILESYSTEM"] "-mem" = "" "-comp" = "" "-mkfs-time" = "