Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ once it reaches a published 0.1.0 release.
- [S-137] Every invocation form lvm2 documents now reaches usage as its own alternative, keeping the prose sentence above it as its option group instead of inventing a heading from the form's own usage line (`mandible lvcreate`).
- [S-139] A single-dash long option carrying an interior uppercase letter keeps its whole name when a sibling row in the same table shares its lowercase prefix, so `mandible mksquashfs` shows `-noI` and its nine siblings instead of ten rows reading `-n`.
- [S-143, S-144] lowdown's man-page-like `--help` rendering now parses its bullet-marked commands and options instead of rendering verbatim, recovering all 30 subcommands and 9 flags of `mandible nix`, including a `/`-joined alias and a multi-word value name (#138).
- [S-147] A flag documented once per invocation form now keeps every form's own value name instead of one form's name beside another's choices, so `mandible lvcreate`'s `--type` shows `linear, striped, raid10, snapshot, thin` beside its `raid1, mirror` choices.
- [S-147] A flag documented once per invocation form now renders one placeholder and one unioned choice list instead of splitting across a value name and a separate choices line, so `mandible lvcreate`'s `--type` shows no placeholder beside `values: linear, striped, raid1, mirror, raid, raid10, snapshot, thin-pool, cache-pool, thin, vdo, cache, writecache`.
- [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.
Expand All @@ -52,6 +52,7 @@ once it reaches a published 0.1.0 release.
- [S-163] A `+/-name` alternation row's own expansion no longer collides with an ordinary row documenting the same spelling, and a `+word` row's value column is now borrowed onto its `-word` sibling when the ordinary repair can't recover a bare one, so `mandible Xvfb` keeps one `-render` with its four choices, one `+render`, and the same `name` value on both `+extension` and `-extension`; its `+word` spellings also render in the long column now, beside `-render`, instead of the short column at column 0.
- [S-168] A colon-introduced list of bare-name choices sitting directly under a placeholder row — or a `+word`/`-word` pair sharing one — now becomes that placeholder's own choices instead of folding into the row's own description, so `mandible Xvfb`'s `+extension`/`-extension` pair shows its 19 run-time-toggleable extension names as choices on both halves.
- [S-145] A single-dash-long table with no column padding at all now recovers a row's genuine bracketed value one space after its name, so `mandible Xvfb`'s `-render` and `-deferglyphs` keep their own choices instead of losing them outright.
- [S-167] A `Usage:` line whose leading word carries a bracketed optional-abbreviation suffix (`v[ersion]`, `g[dbserver]`) now reaches the tree as its own subcommand, named and shown by its full word with the short prefix kept as an alias, and probed for its own flags, instead of folding into the usage block (`mandible lldb-server`).

## [0.7.0] - 2026-09-05

Expand Down
57 changes: 45 additions & 12 deletions corpus/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -571,21 +571,21 @@ produces no root, the same reasoning `must_not_describe` uses.
### A value name after the real app's own root refill: `must_value_names_after_root_refill`

`must_value_name` is satisfied by *any* entity carrying the spelling, on
the raw, unrefilled tree — which is exactly why it cannot see `lvcreate`'s
own defect (docs/shapes.md S-147). `--type` reaches that tree once per
invocation form (`linear`, `striped`, the choices `raid1`/`mirror`, ...),
each form still its own entity, each already naming its own value: the
positive claim passes trivially before any fold has even happened. The
loss only exists once the same node is folded, and every running session
folds it: `mandible::background::Warmer::submit_root_fill` always merges
the already-extracted root against a fresh reprobe as soon as the TUI
opens, and `Runner::fill_node`'s own contract ("`existing` is always
included as a candidate") is what pools every same-spelling entity into
one bucket for `mandible_core::merge::merge_entity_bucket` to fold.
the raw, unrefilled tree — which is exactly why it cannot see a
same-spelling merge bucket's own defect (docs/shapes.md S-147). A flag
whose several invocation forms each name their own value reaches the raw
tree once per form, each form still its own entity, each already naming
its own value: the positive claim passes trivially before any fold has
even happened. The loss only exists once the same node is folded, and
every running session folds it: `mandible::background::Warmer::submit_root_fill`
always merges the already-extracted root against a fresh reprobe as soon
as the TUI opens, and `Runner::fill_node`'s own contract ("`existing` is
always included as a candidate") is what pools every same-spelling entity
into one bucket for `mandible_core::merge::merge_entity_bucket` to fold.

```toml
[contract.must_value_names_after_root_refill]
"--type" = ["linear", "striped", "raid10", "snapshot", "thin"]
"-help" = ["topic"]
```

`cargo xtask corpus` simulates that exact refill — `merge_nodes` over two
Expand All @@ -599,6 +599,39 @@ listed substring, naming what was expected and what survived instead. A
fixture that produces no root fails this exactly as it fails
`must_value_name`.

S-147's own follow-up ruling (2026-09-07 "queue", docs/design.md §16)
narrowed what this field can still claim about `lvcreate`'s own `--type`:
a merge bucket whose forms disagree about a *literal* value now unions
every one of them into `choices` instead, rendering one placeholder
(`<type>`, or none, never a comma list of literals) beside the union —
see `must_choices_after_root_refill` below for that half. This field
still states its own claim correctly for a bucket disagreeing about a
genuine, capitalised placeholder name.

### Choices after the real app's own root refill: `must_choices_after_root_refill`

The `choices` twin of `must_value_names_after_root_refill`, for the same
S-147 follow-up: a same-spelling bucket whose forms each name a different
*literal* value (`linear`, `striped`, `raid10`, ...) unions every one,
plus any `choices` a bracket-row form already carried, into one list on
the refilled entity. `must_attach_choices` alone cannot see this: it
walks the raw, unrefilled tree, where `.find()` sees only the first
invocation form's own entity and its usually-empty `choices`.

```toml
[contract.must_choices_after_root_refill]
"--type" = ["linear", "striped", "raid1", "mirror", "raid", "raid10", "snapshot", "thin-pool", "cache-pool", "thin", "vdo", "cache", "writecache"]
```

`cargo xtask corpus` simulates the same refill
`must_value_names_after_root_refill` does and checks the named flag's
*merged* `choices` for every listed value (matched the way
`must_attach_choices` matches: exact name, no substring). `cargo xtask
corpus` fails when the flag is absent from the refilled tree, or when
the merged choices are missing any listed value, naming what was
expected and what the merged choices actually held. A fixture that
produces no root fails this exactly as it fails `must_attach_choices`.

### What `--bless` does and does not assert: `verdict_scope`

`--bless` freezes the *entire* tree into `expected.snap` — node summaries,
Expand Down
2 changes: 1 addition & 1 deletion corpus/fail2ban-client/1.0.2/meta.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ min_subcommands = 14

[xfail]
broken = true
reason = "1. Fixed. A description continuation beginning with a dash used to end the Command: bare block early and read as a flag row; bare_block_end now recognizes it as a wrapped-prose continuation (S-027's own test) instead, so no --with-time flag reaches the tree. 2. The Command: section holds 95 rows of multi-word command patterns (set loglevel <LEVEL>, get <JAIL> banip [<SEP>|--with-time], reload [--restart] [--unban] <JAIL>) under ten centered ALL-CAPS group labels. bare_block_end's baseline is fixed (docs/shapes.md S-149), so the block now reads every row cleanly, but emit_subcommands still drops any row whose name field is not a single command-name-shaped word: a bare-word first line recovers (start, restart, stop, banned, status, ping, echo, help, version, flushlogs, 10 nodes), while every set, get, add, unban and reload row is dropped rather than fabricated, since none of them ever has a bare-word-only form. The command-pattern-table detector still reads 18 tools and 224 findings fleet-wide, 85 of them here, above the five-tool bar. The name rule (one command node per distinct leading word, each row's own pattern appended to its usage list) is not yet shipped."
reason = "1. Fixed. A description continuation beginning with a dash used to end the Command: bare block early and read as a flag row; bare_block_end now recognizes it as a wrapped-prose continuation (S-027's own test) instead, so no --with-time flag reaches the tree. 2. The Command: section holds 95 rows of multi-word command patterns (set loglevel <LEVEL>, get <JAIL> banip [<SEP>|--with-time], reload [--restart] [--unban] <JAIL>) under ten centered ALL-CAPS group labels. bare_block_end's baseline is fixed (docs/shapes.md S-149), so the block now reads every row cleanly, but emit_subcommands still drops any row whose name field is not a single command-name-shaped word: a bare-word first line recovers (start, restart, stop, banned, status, ping, echo, help, version, flushlogs, 10 nodes), while every set, get, add, unban and reload row is dropped rather than fabricated, since none of them ever has a bare-word-only form. The command-pattern-table detector still reads 18 tools and 224 findings fleet-wide, 85 of them here, above the five-tool bar. The name rule (one command node per distinct leading word, each row's own pattern appended to its usage list) is not shipped: round 10 built it behind a column-identity guard, which held here and gained the four named targets, and a full-PATH sweep-diff then caught six fabricated nodes on three other tools (options on xauth, gprofng on gprofng, attach/logs/respawn/rm on claude). That is S-141's third refusal; docs/shapes.md S-141 carries all three with the names each one invented."
32 changes: 32 additions & 0 deletions corpus/lldb-server/18.1.3/expected.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: lldb-server
description: Invoke subcommand for additional help
provenance:
sources:
- help-text
confidence: 0.15
children_filled: true
subcommands:
- name: version
aliases:
- v
provenance:
sources:
- help-text
invocation_attested: true
abbrev_probe_attested: true
- name: gdbserver
aliases:
- g
provenance:
sources:
- help-text
invocation_attested: true
abbrev_probe_attested: true
- name: platform
aliases:
- p
provenance:
sources:
- help-text
invocation_attested: true
abbrev_probe_attested: true
5 changes: 5 additions & 0 deletions corpus/lldb-server/18.1.3/help.stderr.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Usage:
lldb-server v[ersion]
lldb-server g[dbserver] [options]
lldb-server p[latform] [options]
Invoke subcommand for additional help
Empty file.
34 changes: 34 additions & 0 deletions corpus/lldb-server/18.1.3/meta.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# S-167: a bare `Usage:` heading's rows repeat the tool's own name plus
# one subcommand word abbreviated with a bracket suffix. `version`,
# `gdbserver` and `platform` recover as subcommand nodes, never usage
# forms, each shown by its full word (never the bracketed source
# spelling) with the row's short prefix kept as an alias. Each node is
# invocation_attested and abbrev_probe_attested (docs/design.md §16,
# 2026-09-13), so a live `mandible` run probes `<word> --help` for its own
# flags; this frozen fixture captures only the root parse, with no
# subprocess, so it cannot show that fill. The help text sits entirely on
# stderr; stdout is empty.

[bless]
provenance = "agent"

[tool]
name = "lldb-server"
version = "18.1.3"
platform = "ubuntu-24.04 (aarch64)"
captured_with = "mandible 0.7.0"

[[capture]]
argv = ["lldb-server", "--help"]
stdout = "help.txt"
stderr = "help.stderr.txt"
exit_code = 0

[contract]
expected_framework = "generic"
min_subcommands = 3

[contract.must_display_name]
version = "version"
gdbserver = "gdbserver"
platform = "platform"
80 changes: 80 additions & 0 deletions corpus/lvcreate/2.03.16/expected.snap
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: raid
value_kind: Required
group: Create a raid LV (a specific raid level must be used, e.g. raid1).
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -315,6 +323,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: thin-pool
value_kind: Required
group: Create a thin pool.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -405,6 +421,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: cache-pool
value_kind: Required
group: Create a cache pool.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -536,13 +560,29 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: thin
value_kind: Required
group: Create a thin LV that is a snapshot of an external origin LV.
provenance:
sources:
- help-text-synopsis
- spellings:
- -T
- --thin
group: Create a thin LV that is a snapshot of an external origin LV.
provenance:
sources:
- help-text
- spellings:
- --type
value_name: vdo
value_kind: Required
group: Create a LV that returns VDO when used.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -621,6 +661,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: cache
value_kind: Required
group: Create a new LV, then attach the specified cachepool which converts the new LV to type cache.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -720,6 +768,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: cache
value_kind: Required
group: Create a new LV, then attach the specified cachevol which converts the new LV to type cache.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -794,6 +850,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: cache
value_kind: Required
group: Create a new LV, then attach a cachevol created from the specified cache device, which converts the new LV to type cache.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -876,6 +940,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: writecache
value_kind: Required
group: Create a new LV, then attach the specified cachevol which converts the new LV to type writecache.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down Expand Up @@ -911,6 +983,14 @@ flags:
provenance:
sources:
- help-text
- spellings:
- --type
value_name: writecache
value_kind: Required
group: Create a new LV, then attach a cachevol created from the specified cache device, which converts the new LV to type writecache.
provenance:
sources:
- help-text-synopsis
- spellings:
- -l
- --extents
Expand Down
24 changes: 14 additions & 10 deletions corpus/lvcreate/2.03.16/meta.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,17 @@ must_not_contain_positionals = ["COMMON_OPTIONS"]
[contract.must_flag_group]
"--mirrorlog" = "Create a raid1 or mirror LV."

# docs/shapes.md S-147: seven invocation forms all reach the tree spelled
# `--type`, each naming (or, for the raid1/mirror form's own choices, not
# naming) a different literal value. `must_value_name` alone cannot state
# this claim — it passes vacuously off the raw, unrefilled tree, where the
# forms are still separate entities and each already names its own value.
# The claim only means something once the same background root-refill the
# running app always performs (`Warmer::submit_root_fill`) folds them into
# one bucket, which is what this field simulates.
[contract.must_value_names_after_root_refill]
"--type" = ["linear", "striped", "raid10", "snapshot", "thin"]
# docs/shapes.md S-147 follow-up (ruled 2026-09-07 "queue"): thirteen
# invocation forms all reach the tree spelled `--type`, each naming a
# different literal value, either directly (`--type raid`) or via a
# bracket row's own trailing choice list (`raid1|mirror`).
# `must_attach_choices` alone cannot state this claim — it walks the raw,
# unrefilled tree, where `.find()` sees only the first form's own,
# usually-empty `choices`. The claim only means something once the same
# background root-refill the running app always performs
# (`Warmer::submit_root_fill`) folds every form into one bucket, which is
# what `must_choices_after_root_refill` simulates. No form ever names
# `--type`'s value with a capitalised token, so the merged flag carries no
# placeholder at all rather than a fabricated `<type>`.
[contract.must_choices_after_root_refill]
"--type" = ["linear", "striped", "raid1", "mirror", "raid", "raid10", "snapshot", "thin-pool", "cache-pool", "thin", "vdo", "cache", "writecache"]
Loading
Loading