Skip to content

Guard dl.bash's tables against the grammar, and complete the five missing flags - #351

Merged
blooop merged 5 commits into
mainfrom
wayfinder/devlaunch-311
Aug 23, 2026
Merged

blooop merged 5 commits into
mainfrom
wayfinder/devlaunch-311

Conversation

@blooop

@blooop blooop commented Aug 22, 2026 •

Copy link
Copy Markdown
Owner

Closes #311

devlaunch-core/completions/dl.bash carries three tables copied in by hand — the
flags dl takes as a first argument, the workspace verbs, and one --flag per
coding agent for the aid it also serves. The originals live in two other crates
with nothing linking them, and the drift had already happened: the script offered
six flags where the grammar declares thirteen user-facing ones, so --prune,
--purge, --reconcile, --refresh, --rm, --stop and --autorm had been
uncompletable for releases with nothing failing.

rust/dl/tests/completion_tables.rs is the diff that fails instead, and the seven
flags are added so it passes.

What is derived and what is not

Everything the guard can derive, it derives — the flag list from Cli's fields,
the verbs from the reserved-verb table, the retired words from the retired table,
the agents from aid's agent table. Two small tables are hand-written with a
reason per entry
, and that is the mechanism rather than a shortcut: a flag added
to the grammar breaks the test until somebody either completes it or writes down
why it is not completed.

  • Four flags are not offered for a first argument — --json, --size, --yes,
    --force — because none of them is ever the first word; they modify a line that
    already named a command.
  • Flags the grammar marks hide = true drop out by rule, not by hand, so a new
    hidden flag needs no edit here.

The one fact written down three times — which flag a value follows — is compared
three ways rather than two-against-a-literal: the script's value_opts, aid's
DL_VALUE_OPTIONS, and the grammar's non-bool flag field.

Why the tables are read as source text

Every original is private to its crate (dl's Cli and verb table, aid's agent
table). Widening a type's visibility so a test can see it is a worse trade than
reading the file it is written in, so the extractors parse text — and each one
asserts the count it found against a count declared in the source, so a parser
that silently matched nothing fails instead of passing vacuously.

Red before green

Slice 1 was genuinely red: the first-argument diff came back missing exactly the
seven flags the ticket names. The other three tables were already consistent, so
each of their guards was checked by mutation — dropping reset, adding prune,
adding a --bogus, adding a non-value flag to value_opts, dropping --gemini
from aid's list — confirming each fails its own test and no other, then restored.

Deliberately not in this PR

  • Generated completions. That is the sibling map's question (Drift-proofing rapid development #300). This is the
    tactical stop-loss that makes the hand-written copy safe to keep until then, and
    the test's own docs say so.
  • Completing anything after a leading flag. dl --autorm <TAB> still offers
    nothing, because the script ends completion once the first word starts with --.
    A real gap, and a behaviour change rather than a table diff.

Checks

cargo test --workspace, cargo clippy --locked --all-targets -- -D warnings and
cargo fmt --check are clean. test/test_bash_completion.py, which sources the
script in a real bash, passes unchanged (25 tests). No source file outside the
completion script and the new test was touched.

🤖 Generated with Claude Code

Summary by Sourcery

Keep Bash completions aligned with the command grammars by adding the missing flags and enforcing table consistency in tests.

New Features:

  • Complete the Bash completion list with the missing user-facing dl flags.

Bug Fixes:

  • Prevent completion tables from drifting from the dl grammar and aid agent definitions, including retired verbs and unsupported flags.

Enhancements:

  • Add source-based completion table consistency tests covering dl flags, workspace verbs, value-taking options, and aid agent flags.

CI:

  • Run the completion table integration test explicitly in CI and include it in the test build matrix.

Tests:

  • Add regression coverage that detects missing, extra, retired, or incorrectly classified completion entries.

blooop and others added 3 commits August 22, 2026 16:32
The completion script offered six flags where the argument grammar declares
thirteen user-facing ones, so --prune, --purge, --reconcile, --refresh, --rm,
--stop and --autorm had been uncompletable for releases with nothing failing.

The new test derives the expected list from cli.rs's Cli struct instead of
restating it: hidden flags drop out by rule, and the four that only modify a
line already naming a command are a hand-written table with a reason each, so
a flag added to the grammar breaks the test until somebody decides which side
it belongs on.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three assertions over the list offered after a workspace: it is exactly the
grammar's reserved verbs, it never offers a word the grammar retired (which the
CLI recognises only to refuse), and every flag in it is one the grammar accepts.

Each was checked red by mutation: dropping `reset`, adding `prune`, and adding
a `--bogus` each fail the matching test and no other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The flag a value follows is written down three times — the completion script
switches to completing a value after it, aid has to tell such a value from the
workspace spec, and the grammar declares it — so all three are compared against
each other rather than two of them against a literal.

aid's own flag list is derived from aid's agent table, so adding an agent cannot
leave its --flag uncompletable. The two flags beside them (help and version) are
hand-written with a reason: aid's grammar is not a clap grammar and has no table
to derive them from.

Both checked red by mutation: adding a non-value flag to the script's value list
and dropping --gemini from aid's each fail their own test and no other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @blooop, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 22, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds missing dl first-argument flags to the bash completion script and introduces a Rust test suite that parses source files to diff the script’s hand-written tables against the dl and aid grammars, ensuring future drift is caught automatically.

Flow diagram for completion table consistency checks

flowchart LR
    Grammar[dl Cli and verb grammar] --> Extract[Source-text extractors]
    AidGrammar[aid agent table] --> Extract
    Script[dl.bash hand-written tables] --> Compare[completion_tables.rs comparisons]
    Extract --> Compare
    Compare --> Result{Tables consistent?}
    Result -->|yes| Pass[Tests pass]
    Result -->|no| Fail[Test failure identifies drift]
Loading

File-Level Changes

Change Details Files
Align dl.bash global first-argument flags with the dl CLI grammar and document intentional omissions.
  • Expanded the global_opts list for dl to include --refresh, --prune, --reconcile, --purge, --stop, --rm, and --autorm alongside existing flags.
  • Reordered and consolidated the flag list to match the grammar-derived expectations, including adding --help and -h explicitly.
  • Added inline comments explaining that four flags (--json, --size, --yes, --force) are intentionally not offered as the first word, referencing the new Rust test for enforcement.
rust/devlaunch-core/completions/dl.bash
Introduce a Rust test module that parses dl and aid source files to validate bash completion tables against the argument grammars.
  • Added helpers to read the completion script, dl CLI source, and aid rewrite source from paths relative to CARGO_MANIFEST_DIR.
  • Implemented parsers that extract dl flags, reserved verbs, retired verbs, and aid agents/flag lists directly from Rust source text with assertion-based sanity checks on counts.
  • Defined small hand-written exception tables (NOT_OFFERED_FIRST and AID_FLAGS_BESIDE_THE_AGENTS) documenting flags deliberately not offered or derived.
  • Added tests to ensure: all user-facing dl flags (minus documented exceptions) are offered as first-argument completions; workspace verbs match the VERBS table and exclude retired verbs; workspace-adjacent flags exist in the grammar; value-taking flags are consistent across the completion script, aid’s DL_VALUE_OPTIONS, and grammar; aid’s completion exposes one flag per agent plus documented extra flags.
  • Structured tests to fail if tables drift or if parsers silently match nothing, turning table changes into explicit test failures rather than invisible behavioral changes.
rust/dl/tests/completion_tables.rs

Assessment against linked issues

Issue Objective Addressed Explanation
#311 Add the seven missing first-argument flags (--prune, --purge, --reconcile, --refresh, --rm, --stop, and --autorm) to dl.bash. ✅
#311 Add tests that extract the completion script's flag, workspace-verb, agent, retired-word, and value-option tables and compare them against the corresponding grammar and aid source-of-truth tables, while documenting intentional omissions. ✅

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@blooop blooop left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during review.

Two-axis review in fresh context. Both axes ran as independent subagents in their own worktrees at 117f469; neither saw the other's output, and each finding below is one the axis reported after actually running the probe. Preflight: merge base a1c6fe8 resolves, diff is 2 files / +458, all 14 checks green including gate.

Standards

Tooling is clean: cargo clippy --locked --all-targets -- -D warnings and cargo fmt --check both exit 0; the 6 new tests pass.

Extractor robustness — probed by reformatting, mostly clean. Seven meaning-preserving reformats of cli.rs/rewrite.rs, each one run: a multi-line #[arg(...)] goes red at rust/dl/tests/completion_tables.rs:147; an attribute or doc comment inserted between attribute and field goes red at :156; pub struct Cli goes red at :134; an AGENTS entry collapsed to ("claude", Agent { goes red at :253 naming what it found; two fields reordered stays correctly green. No reformat produced a silent pass. That is the important result and it holds.

  • nit — wrapping const VERBS … ] =\n [ fails at :197 with a message that dumps ~20 lines of unrelated source: split_once("] = ") scans forward into RETIRED instead of anchoring to the named table.

aid_agents can pass vacuously — should-fix. With AGENTS emptied, names.len() == 0 and body.matches("Agent {").count() == 0, so the self-check at :253 passes on 0 == 0; only the outer diff at :445 went red. grammar_words has the same 0 == 0 hole — RETIRED is saved by the explicit !retired.is_empty() at :354, VERBS is not. The module doc at :24-26 claims "every extractor asserts it found something", and for these two that is not true. One assert!(!names.is_empty()) each closes it, and it matters more here than usual because self-validating extractors are this file's whole anti-vacuity argument.

assert!(flags.len() > 5) (:171) — nit. 17 flags declared, 13 user-facing. Any silent subset loss already fails the outer diffs, so the specific number is arbitrary.

The DL_VALUE_OPTIONS derivation is not honest — should-fix. Verified by mutation: adding profile: Option<String> to Cli, to DL_VALUE_OPTIONS, and to dl's own bash tables still leaves :445 red, demanding --profile in aid's flag list under the message "the completion script's aid flags have drifted from aid's agent table". "dl flags whose value is a separate word" and "the dl pass-throughs aid's completion offers" coincide at --devcontainer and nowhere else; a non-value pass-through has no derivation at all. Reuse at :438 reads as derived when it is a coincidence.

NOT_OFFERED_FIRST is justified; the comment shipped in dl.bash is not — should-fix. Running the built binary: dl --json --ls prints [], dl --force --ls prints "--force means nothing for --ls.", dl --yes --prune runs. All four withheld flags can be the first word, so rust/devlaunch-core/completions/dl.bash:60-63's "none of them is ever the first word" is false. The test's own doc at :290 is right for the other reason — sourcing the script confirms dl --yes <TAB> and dl --ls <TAB> return nothing. requires = "ls" verified at rust/dl/src/cli.rs:431,435.

  • nits — neither exception table's reason string is ever asserted or printed on failure, so they are documentation in tuple clothing; "these two" for three entries is fine (--help/-h are one flag); aid's help/version really are literals (rust/aid/src/main.rs:75,80), so "no table to derive" holds.

Placement and duplication — clean. Cross-crate ../ has precedent (rust/aid/tests/rewrite.rs:50, rust/devlaunch-core/src/clients/devpod.rs:1977); no crate sets publish and no workflow runs cargo publish, and the packaging job builds a wheel without tests, so vendored-tree breakage is unreachable. devlaunch-core has no tests/ dir, so dl/tests is the only workspace-level home. test/test_bash_completion.py:137-168 asserts subsets in real bash and never could catch drift; the new test asserts contents but never that the table is actually offered — complementary, not contradictory.

Spec

Measured against #311: "completions/dl.bash:58 is missing seven current flags (--prune --purge --reconcile --refresh --rm --stop --autorm) … Add a test diffing the script's extracted tables against the source-of-truth tables."

The named defect is fixed and the guard was genuinely red. Restoring only the old global_opts line onto the new test fails every_user_facing_dl_flag_is_offered_for_a_first_argument (:317) with a diff of exactly those seven flags — no more, no fewer. All three tables the ticket names are diffed: VERBS (rust/dl/src/cli.rs:38), aid's AGENTS (rust/aid/src/rewrite.rs:51), and the flag list. Scope held: only dl.bash and the new test changed, no visibility widened.

Real bash checked, not assumed. Sourcing dl.bash and driving _dl_completion via COMP_LINE/COMPREPLY: dl --pr→--prune, dl --a→--autorm, dl --r→--refresh --reconcile --rm, dl --s→--stop, no duplicates; dl --l→--ls, dl <ws> <TAB>→verbs, aid --c→--claude --codex, dl blooop/, dl dev all unregressed. test/test_bash_completion.py passes (25), and it does not itself cover the new flags.

Mutation results — each applied, run, restored:

mutation went red
old global_opts line first-argument (exactly the 7)
visible #[arg(long)] bool added to Cli first-argument
hidden #[arg(long, hide = true)] added none (correct)
Option<String> flag added first-argument + value-taking
verb added to / removed from VERBS workspace-verbs (both)
second word in RETIRED, still offered retired-verb
--bogus in ws_cmds phantom-flag
4th agent added / agent removed aid-agents (both)
2nd DL_VALUE_OPTIONS entry value-taking + aid-agents
short-only #[arg(short = 'w')] none — hole
stale NOT_OFFERED_FIRST entry (--nonexistent) none — hole
hidden --update-cache offered in ws_cmds none — hole
  1. should-fix — short-only flags are invisible to the guard. Adding #[arg(short = 'w')] widget: bool builds, dl --help lists -w as user-facing, and all six tests pass. grammar_flags keys on long only (:158-167), so against the file's own promise at :9-10 — "a flag added to the grammar completes nothing, and nothing fails" — a gap remains. (A field with no #[arg] at all is not a hole: clap panics, "Argument 'widget' is positional and it must take a value".)
  2. nit — the escape hatch is unvalidated. A NOT_OFFERED_FIRST entry naming a flag the grammar no longer has passes silently. Asserting withheld ⊆ grammar flags is one line, and without it the exception table can outlive its exceptions.
  3. nit — asymmetry between the two halves of the diff. every_flag_offered_beside_a_workspace_is_a_flag_the_grammar_accepts compares against all grammar flags including hidden ones (:372), so ws_cmds may offer --update-cache undetected — contra the PR body's "Flags the grammar marks hide = true drop out by rule".
  4. should-fix, scope-defensible — the declined gap is wider than the PR says. dl --autorm <TAB> offers nothing (verified in real bash). But parse(&["--autorm", "ws"]), parse(&["--stop", "ws"]) and parse(&["--rm", "ws", "--force"]) are all tested-valid forms (rust/dl/src/cli.rs:1648,1396,1398), so newly offering these flags in first position creates three dead-end completions, not the one the PR body names. Deferring the fix is consistent with the ticket's "this guard is the tactical stop-loss either way"; understating the blast radius is not.

Verdict

Request changes — the guard is real and well-built (10 of 13 mutations caught, no reformat produced a silent pass, red-before-green independently reproduced), but three cheap fixes should land before merge, and two of them are places where the PR's own claims are not true of the code:

  1. aid_agents and grammar_words can pass on 0 == 0 (:253, :210), while the module doc at :24-26 says every extractor asserts it found something. Add assert!(!names.is_empty()) / the same for VERBS. This is the file's central anti-vacuity mechanism, so the exception is worth closing rather than documenting.
  2. dl.bash:60-63 states something false — all four withheld flags can be the first word; what is true is that the script offers nothing after a leading --. Reword to the reason the test's own doc gives at :290.
  3. DL_VALUE_OPTIONS reused as aid's pass-through table (:438) will mis-fire on the next value-taking flag and blame "aid's agent table" for it. Either hand-write aid's pass-throughs in AID_FLAGS_BESIDE_THE_AGENTS with a reason, like the other exceptions, or say in a comment that the two sets are asserted equal on purpose.

Non-blocking, worth a line each: short-only flags (Spec 1) and hidden flags beside a workspace (Spec 3) are documented gaps in the guard's coverage rather than defects in what it does check; the NOT_OFFERED_FIRST subset assertion (Spec 2), the flags.len() > 5 floor and the VERBS error message are nits. The PR body should say three dead-end completions, not one (Spec 4).

blooop added 2 commits August 23, 2026 23:01
Two things this branch got wrong once main caught up with it, and the first is
the guard reporting on itself.

`--stop` and `--autorm` were user-facing flags when this list was written. Main
retired both (`--rm` is docker's `--rm` now) and marked them `hide = true`, so
offering them completes a line the CLI answers with "that spelling moved". The
test said so on the first run after the merge, which is the whole claim it makes
— the list is five flags longer than it was, not seven.

They come out with no entry in `NOT_OFFERED_FIRST`, deliberately: hidden flags
drop by rule, so a retirement needs no edit to the test, only to the list. The
comment in the script now says that, because "why is a flag the grammar declares
missing from here" is the question the table has to answer on its own.

The second is worse, and it is not about flags. `ci.yml`'s `rust` job names one
test binary per step -- the split that gives each a timeout naming the wedged
suite -- and `completion_tables` was in no step. `cargo build --all-targets`
compiled it, clippy checked it, and nothing ran it: a guard against drift that
would itself have drifted silently, which is the exact failure mode the file's
own `gate` comment describes as "a check nobody ran reads exactly like a check
that passed". Named now, in the `rust` job and in `rust-coverage`'s suite list.

Checked by mutation both ways round, twelve edits: dropping and inventing a
first-argument flag, dropping a verb, offering a retired one, inventing a
workspace flag, adding a non-value flag to `value_opts`, dropping an agent from
aid's list -- and, on the grammar's side, a new flag, a new value-taking flag, a
newly hidden `--rm`, a ninth verb, a fourth agent. Each reddens its own test and
no other; all six pass restored.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
@blooop blooop changed the title Guard dl.bash's tables against the grammar, and complete the seven missing flags Guard dl.bash's tables against the grammar, and complete the five missing flags Aug 23, 2026
@blooop

blooop commented Aug 23, 2026 •

Copy link
Copy Markdown
Owner Author

Updated from main (the branch was ~100 commits behind, so its green tick was
about a grammar that has since changed). Two adjustments, and the first is the
guard reporting on its own subject matter.

It is five flags now, not seven. --stop and --autorm were user-facing
flags when this list was written; main's b6b637f retired both — --rm is
docker's --rm now — and marked them hide = true. The first run after the
merge failed with exactly that, which is the claim this PR makes:

left:  {..., "--autorm", ..., "--stop", ...}
right: {"--devcontainer", "--help", "--install", "--ls", "--prune",
        "--purge", "--reconcile", "--refresh", "--rm", "--version", "-h"}

Both come out of the list with no entry in NOT_OFFERED_FIRST: hidden flags
drop by rule, so a retirement needs no edit to the test. Nothing main added since
was missing — --rm is the rename of --autorm and this list already had it.
Retitled accordingly.

The guard was in no CI step. ci.yml's rust job names one test binary per
step, and completion_tables was in none of them: cargo build --all-targets
compiled it, clippy checked it, and nothing ran it. A drift guard that itself
drifts silently — the failure mode that file's own gate comment describes as "a
check nobody ran reads exactly like a check that passed". Added to the rust job
and to rust-coverage's suite list.

Mutation-checked both directions, 12 edits. Script side: dropped --prune,
invented --bogus, dropped the reset verb, offered the retired prune,
invented a workspace flag, put a non-value flag in value_opts, dropped
--gemini from aid's list. Grammar side: a new flag, a new value-taking flag, a
newly hidden --rm, a ninth verb, a fourth agent. Each reddens its own test and
no other; all six pass restored.

bash -n clean, and all three tables are consumed by compgen rather than being
dead variables. Local: cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, every test binary in CI's list plus picker (~1650 tests), and
the python suite (268 passed, 6 skipped).

Pre-existing and left alone: rust/dl/tests/picker.rs is also in no CI step.

@blooop
blooop merged commit 2a6ad49 into main Aug 23, 2026
14 checks passed
@blooop
blooop deleted the wayfinder/devlaunch-311 branch August 23, 2026 22:10
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.

Guard dl.bash's tables against cli.rs

1 participant