Skip to content

feat(cli): publish clean, grouped help instead of developer docstrings (#389) - #501

Merged
jasonssdev merged 3 commits into
mainfrom
fix/389-help-surface
Aug 9, 2026
Merged

feat(cli): publish clean, grouped help instead of developer docstrings (#389)#501
jasonssdev merged 3 commits into
mainfrom
fix/389-help-surface

Conversation

@jasonssdev

Copy link
Copy Markdown
Owner

Refs #389 — this carries its help-surface bullets (1 and 2). The remaining four (the reconcile/list vocabulary mismatch, the init embedding-warning placement, and the two doctor items) stay open; #389 explicitly says to split any bullet that grows, and this one did.

What a user actually read before

Typer publishes each command's raw __doc__ unless the command sets help=. No command did. So --help showed things like:

Wires the pure bundle.provenance.resolve_backfill_raises sweep core (design D4/D5) into Typer's confirm-gate

along with decision #717, MVP-3 gap #8 S2a, Slice 1 and ADR-0003.

There was never a choice to make between traceability and a clean surface — help= decouples them. All 26 commands now publish a user-facing line while their docstrings keep every reference maintainers rely on. This matters past --help: MCP tool descriptions are usually derived from the same source, and text that does not help a person will not help an agent.

Grouping, and the part grouping alone did not fix

The 26 commands were listed flat in declaration order, which put purge — irreversible and rare — fourth, while query, the value moment, sat near the bottom. They are now grouped into five panels by what the reader is trying to do: get started, explore, curate, maintain, remove. The rule is written down next to the app so the next contributor applies it rather than matching the nearest-looking neighbour.

Grouping by itself made things worse in one respect, which only showed up on rendering the real output: Rich prints a panel when it first meets a command belonging to it, so declaration order still decided panel order and Remove landed second — the irreversible verbs more prominent than the flat list had them. PANEL_ORDER plus a stable sort of the registry makes reading order explicit instead of a side effect of where a function sits in the file.

Three review rounds, each catching what the last missed

This branch went through three full review cycles. Recording what each found, because the pattern is the point:

Round 1 shipped clean help but left the panel ordering above.

Round 2 found two published lines that were clean but wrong — the real hazard of a sweep like this. adjudicate published "Decide which candidate duplicates are really the same concept, with the model proposing and you confirming", which reads as though invoking it performs merges; its own docstring says the plain invocation is read-only and writing needs a separate flag. suggest-volatility omitted the writes-nothing reassurance its three sibling read-only verbs all state. It also caught a published option help string on forget --scope still carrying the internal token S2a, which the jargon test had passed over because it matched only fixed substrings.

Round 3 fixed an anchor in one of the very patterns round 2 added: \bSlice \d+\b matched a bare Slice 1 but missed Slice 2a and Slice 2b-ii, because there is no word boundary between a digit and the letter after it — and the lettered forms are the ones this codebase mostly uses.

Separately, the sweep's first draft dropped a bounded-scope honesty statement from set-sensitivity's published help that a pre-existing test requires to stay published. The suite caught it, which is exactly what that test is for.

Verification

  • uv run pytest -q4025 passed, 1 skipped. uv run mypy . → Success, 179 files. ruff check / ruff format clean and convergent.
  • Both headline behaviors driven RED→GREEN.
  • The strengthened jargon test was mutation-verified: restoring S2a to the published option help fails it with {'forget': ['S2a']}; the substring-only version passed that same mutation, which is why it was replaced.
  • The corrected slice pattern was measured against all three real forms plus two near-misses before commit.
  • The panel failure now names the command, its bad value and the allowed set. Failing at import is deliberate — a misplaced command should not ship quietly — but failing mutely was not.

Native review

Three lineages, all approved with receipts, all zero blockers: review-6e884bb4642ac6fb (high, 4R), review-f3a75ff7d21f93f8 (high, 4R), review-5c4e2c90128c01d4 (medium, one lens, zero findings).

pre-pr denies this PR, same structural reason as #498, #499 and #500: it measures the whole branch against origin/main while each receipt binds its own increment, so no single receipt covers the union. receipt-binding / candidate-or-paths-mismatch, action explicit-maintainer-action. Every changed line was reviewed and approved.

#389)

Typer published each command's raw `__doc__` because no command set `help=`,
so an end user read "Wires the pure
`bundle.provenance.resolve_backfill_raises` sweep core (design D4/D5) into
Typer's confirm-gate", plus `decision #717`, `MVP-3 gap #8 S2a`, `Slice 1`
and `ADR-0003`. There was no need to choose between traceability and a clean
surface: `help=` decouples them. All 26 commands now publish a user-facing
line while their docstrings keep every reference maintainers rely on. This
matters past `--help` too -- MCP tool descriptions are usually derived from
the same source, and text that does not help a person will not help an agent.

The 26 commands were also listed flat in declaration order, which put
`purge` -- irreversible and rare -- fourth, while `query`, the value moment,
sat near the bottom. They are now grouped into five panels by what the reader
is trying to do: get started, explore, curate, maintain, remove.

Grouping alone was not enough. Rich prints a panel when it first meets a
command belonging to it, so declaration order still decided panel order and
"Remove" landed SECOND -- making the irreversible verbs MORE prominent than
the flat list did. `PANEL_ORDER` plus a stable sort of the registry makes the
reading order explicit rather than a side effect of where a function happens
to sit in this file.

Four tests pin the result: no published command help carries traceability
vocabulary, the five panels exist, they print in reading order, and every
command declares a known panel.

Refs #389; its remaining bullets (vocabulary mismatch, the `init` embedding
warning's placement, and two `doctor` items) stay open.

Reviewed under lineage `review-6e884bb4642ac6fb`, high risk, canonical 4R,
approved with receipt, zero blockers.
… legible (#389)

Folds in the five findings from this branch's own four-lens review. Two were
accuracy defects in text this change itself published, which is the sweep's
real hazard: a clean line that is WRONG is worse than the jargon it replaced.

`adjudicate` published "Decide which candidate duplicates are really the same
concept, with the model proposing and you confirming", which reads as though
invoking it performs merges. Its own docstring says the plain invocation is
read-only and never writes, merges or decides -- that needs a separate apply
flag. It now says so.

`suggest-volatility` omitted the advisory, writes-nothing reassurance its
three sibling read-only verbs all state, which is the same inconsistency
#389 complains about elsewhere.

A published option help string on `forget --scope` still carried the internal
token "S2a". The jargon test did not catch it because it matched only fixed
substrings; it now also matches patterns for abbreviated slice tokens, paired
design references and numbered gap references. Mutation-verified: restoring
the token fails the test with `{'forget': ['S2a']}`, and the substring-only
version passed that same mutation.

The panel sort's bare tuple lookup raised at import with a message naming
neither the command nor the bad value -- taking the whole CLI down, help
included, for one typo. `_panel_rank` keeps the hard failure, which is right,
and adds the command name, the offending value and the allowed set. Failing
mutely was the part that was wrong.

Also corrects a test docstring that claimed it prevented that import-time
failure. It cannot: the sort runs when the module loads, long before pytest
collects anything.

Reviewed under lineage `review-f3a75ff7d21f93f8`, high risk, canonical 4R,
approved with receipt, zero blockers.
…y uses (#389)

The pattern added one commit ago required a word boundary immediately after
its digit run, so it matched a bare "Slice 1" and missed "Slice 2a" and
"Slice 2b-ii" -- there is no boundary between a digit and the letter after
it, and the lettered forms are the ones the docstrings here mostly use. A
guard for jargon that skips the dominant shape of that jargon is not a guard.

Measured before and after rather than reasoned about: the three real slice
forms now match, and "Sliced bread" and "sliced" still do not.

Reviewed under lineage `review-5c4e2c90128c01d4`, medium risk, one focus
lens, approved with receipt, zero findings.
@jasonssdev
jasonssdev merged commit 2f33596 into main Aug 9, 2026
6 checks passed
@jasonssdev
jasonssdev deleted the fix/389-help-surface branch August 9, 2026 05:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant