Skip to content

feat(cli): filter discovery by ecosystem - #116

Open
justin13888 wants to merge 8 commits into
fix/93-report-declined-updatesfrom
feat/105-ecosystem-filter
Open

feat(cli): filter discovery by ecosystem#116
justin13888 wants to merge 8 commits into
fix/93-report-declined-updatesfrom
feat/105-ecosystem-filter

Conversation

@justin13888

@justin13888 justin13888 commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Closes #105.

--ecosystem narrows the manifests check, list, and fix read to the ecosystems named, so one command can address one slice of a polyglot repository the way --manifest-glob addresses one slice of a monorepo.

dependable check . --ecosystem rust
dependable list . --ecosystem npm --ecosystem rust --format json
dependable fix . --ecosystem rust --dry-run

Stacked on #108 (fix/93-report-declined-updates), the tip of master <- #99 <- #106 <- #108. Merge that first.

What this actually changes

PR #99 removed a --ecosystem flag that parsed, was advertised in --help as restricting the run, and was read by nothing. The requirement it advertised is still open, and making the claim true takes two effects, not one:

  • The manifest set. dependable_fetch::discover's own doc comment says its enabled predicate "gates the notices only — discovery still returns every manifest it recognizes, and narrowing that set stays the caller's job". Composing the request into that predicate alone would suppress unread-manifest warnings and change nothing a command reads — reinstating exactly the flag-that-lies. So collect_manifests filters found.manifests by ManifestKind::detect(p).ecosystem() as well, before the --manifest-glob filter, so the glob's "matched nothing" line counts only manifests still in play.
  • The notices. The request is also composed into the predicate handed to discover, so --ecosystem rust does not print Gradle build-script advice about a build it just excluded.

Both effects are pinned independently. Disabling the manifest-set filter fails 5 of the 8 new tests; dropping the notice composition fails the 6th.

Because the filter lives in collect_manifests, there is one implementation and every caller gets the same behaviour.

Behaviour to disclose

  • The net shipped change is Option<String> -> a typed enum. --ecosystem foo was previously accepted silently and is now rejected. That is a real behaviour change, not a pure restoration.
  • list and fix gaining the flag is new surface, not restoration. The removed flag existed on CheckArgs only.
  • It narrows; it never widens. --ecosystem does not override .dependable.toml's per-ecosystem enabled, and it registers no fetcher. Verified: check --ecosystem jvm against [jvm] enabled = false discovers the catalog and prints the existing skipping <path>: JVM is not enabled or not yet supported, exit 0.
  • conflicts_with = "manifest", mirroring --manifest-glob.
  • An empty selection is exit 0 with a specific line — no manifest for Rust (searched 1 manifest up to --depth 3; found Elixir) — and the generic No supported manifests found. is suppressed in that case, because it is a falsehood in a repository full of manifests the filter removed. report, which has no --ecosystem, prints the generic line exactly as before.
  • report deliberately does not gain the flag. resolve_report_settings' doc comment states that report has no --fail-on, --unstable, --format or --ecosystem, and it has no --manifest-glob for the stated reason that it describes a repository as a whole.
  • No public API change in any library crate. collect_manifests is private. No new exit code, config key, env var, or schema bump (dependable.list/v1 unchanged); no SARIF or report change; no dependency change.

Decisions taken

1. Deliverable boundary — which commands gain --ecosystem in this change

  • Taken: check, list and fix — the three commands the PRD specifies. Adds the ecosystem field to CheckArgs, ListArgs and FixArgs and passes the requested set at the run_check, run_list and run_fix call sites of collect_manifests.
  • Rejected: check + list only — README states why the sibling --manifest-glob flag exists on fix: "without it, dependable fix would rewrite manifests that the matching dependable check deliberately left out." That reasoning transfers verbatim, and fix is the command that writes to the user's files, so the asymmetry is the one with real consequences.
  • Rejected: all four discovery commands including report — the doc comment at runner.rs:968 states that report deliberately has no --fail-on, --unstable, --format or --ecosystem, and report has no --manifest-glob for the same stated reason. Overturning a deliberate documented design statement is a separate argument this issue does not make.
  • Rejected: check only (strict restoration) — check reaches the network on a match, so the narrowing could only be tested by asserting a negative. That is the same evidentiary weakness that let the original unread flag ship.
  • Reverses: delete the ecosystem field from FixArgs and pass &[] at the run_fix call site.
  • Filed: - (report is not a deferred remainder; its omission is a documented design decision in the code, recorded here rather than filed)

2. Flag shape — repeatability and the value strings it accepts

  • Taken: repeatable Vec<EcosystemArg> with union semantics, canonical names only (rust go npm python php dart csharp elixir jvm), with CSharp given an explicit #[value(name = "csharp")] because clap's default kebab-casing would render it c-sharp. This is the exact shape of --manifest-glob, which README already documents as this repository's discovery-filtering idiom.
  • Rejected: canonical names plus aliases (kotlin/java/maven/gradle -> jvm, deno/pnpm/node -> npm, nuget/dotnet -> csharp, and so on) — every accepted string is a permanent compatibility surface, and a later ecosystem split (Deno gaining its own variant) would leave deno pointing at the wrong one forever. Aliases are additive and cheap to add later; they are not removable.
  • Rejected: a comma delimiter (--ecosystem rust,npm) — --manifest-glob deliberately does not take one, and diverging from the sibling flag needs a justification this issue does not supply.
  • Rejected: single-value Option<EcosystemArg> — the issue's own motivating case is a monorepo, where "Rust and npm but not the rest" is the natural request.
  • Reverses: for aliases, add #[value(alias = "...")] attributes to EcosystemArg. For single-value, change the field to Option<EcosystemArg> and drop the union test.

3. What an ecosystem that matches no discovered manifest does

  • Taken: print a specific stderr line naming what was searched and what was found instead, and exit 0 — mirroring the existing no manifest matched {globs} (searched N manifests up to --depth {depth}) line, and suppressing the generic No supported manifests found. in that case.
  • Rejected: exit 2 — .github/actions/dependable-check/action.yml documents exit 2 as "a tool error", which this is not; the direct precedent (an unmatched --manifest-glob) exits 0; and a non-zero exit breaks the per-ecosystem CI matrix job that is this flag's motivating use.
  • Rejected: no new message, letting the existing generic line stand — No supported manifests found. is actively misleading in a repository full of manifests the filter removed, and the issue names this as the less honest of the two answers.
  • Reverses: delete the message branch in collect_manifests and let the generic line stand.

4. The ledger on --ecosystem having been removed by a fix:-typed commit

  • Taken: land this change in the same unreleased cycle as the removal, and record here that no published version ever lacked the flag. Verified: 45a9742 is not on origin/master, crates/dependable/CHANGELOG.md's ## [Unreleased] is empty, and the workspace version is still 0.1.3.
  • Rejected: overriding the version on release pull request chore: release v0.1.4 #80 to 0.2.0 — chore: release v0.1.4 #80 is authored by a bot (release-plz) and this run's write surface forbids touching it at all. It is unavailable, not merely inadvisable. It is also redundant if PR refactor!: harden the workspace-root descriptor before a second ecosystem uses it #102 (refactor!:) merges first, since a breaking-typed commit on master drives the 0.x minor bump on its own.
  • Rejected: recording the disclosure and changing nothing else — that leaves the break publishable if a release goes out between fix: stabilization pass — twelve correctness fixes across core, fetch, cli and report #99 and this change.
  • Rejected: rewriting 45a9742's message to fix!: — this run may not force-push or rewrite published history, and that commit is pushed. Unavailable.
  • Disclosure the reviewer is right to press on: the net shipped change is Option<String> -> a typed enum, so --ecosystem foo was previously accepted silently and is now rejected. That is a real behaviour change landing under a feat: commit, and it is stated here rather than left for a reader to find.
  • Reverses: nothing in code. If the version number itself should carry the signal, override the version on release PR chore: release v0.1.4 #80 before merging it.

5. Where the generic empty-discovery line is decided (taken during implementation)

  • Taken: a private report_no_manifests(&[Ecosystem]) helper at each call site, silent when an ecosystem filter is active. collect_manifests keeps returning Vec<PathBuf>, so the only signature change is the recorded ecosystems: &[Ecosystem] parameter.
  • Rejected: changing collect_manifests to return a struct carrying an "already explained" flag — a second, unrecorded signature change for a one-line decision three callers can make from an argument they already hold.
  • Rejected: printing both lines, as the unmatched---manifest-glob path does today — decision 3 rules that out for --ecosystem; the glob path is left exactly as it was rather than widened into.
  • Reverses: inline eprintln!("No supported manifests found.") at the four call sites and delete the helper.

6. Aliases for the accepted --ecosystem values (kotlin, java, maven, gradle, node, deno, pnpm, nuget, dotnet)

  • Taken: canonical names only, for now. Every accepted string is a permanent compatibility surface, and a later ecosystem split — Deno gaining its own variant — would leave an alias pointing at the wrong one for ever. Aliases are additive and cheap to add later; they are not removable.
  • Rejected: shipping java/kotlin -> jvm and dotnet -> csharp now. The usability gap is real — jvm is not the first word a Kotlin user reaches for — but it is recoverable, and an alias that later points at the wrong variant is not.
  • Reverses: add #[value(alias = "...")] attributes to EcosystemArg.

7. An empty selection under --format json (taken during review repair)

  • Taken: emit the valid empty dependable.list/v1 document — same schema, summary.projects: 0, empty by_ecosystem, empty projects. run_list previously reported the empty selection and returned before any document was built, so stdout was byte-empty and list . --ecosystem csharp --format json | jq '.summary.projects' failed to parse on every matrix shard whose ecosystem the repository does not use — precisely the set that exiting 0 was chosen to keep green. Schema-compatible and additive: a consumer gets a parseable document with zero projects rather than nothing.
  • Rejected: leaving it, as a pre-existing defect out of this change's scope — this change's own README promotes that exact list --ecosystem ... --format json matrix use, so it ships the recommendation that walks into it.
  • Rejected: extending it to table and text — a human reading an empty table wants the stderr line, not an empty table.
  • Reverses: return before constructing the document, as before.

Tests

New crates/dependable/tests/cli_ecosystem.rs (8 tests, hermetic — CARGO_TARGET_TMPDIR scratch dirs, no new fixture directories, no network):

Test What it pins
ecosystem_narrows_the_inventory_to_what_was_asked_for The acceptance boundary: list --format json --ecosystem rust over a Cargo+npm+Go tree returns only the Cargo project and summary.by_ecosystem holds only Rust. The removed flag would have failed this.
an_ecosystem_covers_every_manifest_spelling_it_owns --ecosystem npm returns package.json, deno.json and pnpm-workspace.yaml, not Cargo.toml.
two_ecosystems_are_a_union_not_a_contradiction Repeating the flag unions.
check_narrows_discovery_without_touching_the_network The empty-selection path: exit 0, the specific line, and no generic line.
ecosystem_and_manifest_are_mutually_exclusive clap rejects the pair and names --ecosystem.
the_flag_is_advertised_with_the_values_it_accepts Every accepted spelling appears in clap's own possible-values list, on all three commands. Scoped to that line so go cannot pass on cargo.
an_ecosystem_filter_silences_advice_about_ecosystems_it_excluded The notice effect.
fix_rewrites_only_the_ecosystem_it_was_pointed_at fix --dry-run --ecosystem rust considers no npm or Go manifest and leaves both byte-identical. Hermetic by construction: the only Rust manifest declares no dependencies, so the narrowed run has nothing to look up — a fix that ignored the flag is also the one that would reach a registry.

A ninth test, the_ecosystem_filter_runs_before_the_glob_filter, was added during review repair — see below.

Plus cli.rs::every_ecosystem_can_be_named_on_the_command_line, which asserts the image of EcosystemArg::value_variants() under From is exactly Ecosystem::ALL, and csharp_is_spelled_the_way_it_is_typed, which pins the value strings clap advertises.

Review repairs

Five findings from review, repaired on this branch as six commits.

1. The exhaustiveness guard compared cli.rs to cli.rs (medium)

every_ecosystem_can_be_named_on_the_command_line compared EcosystemArg::value_variants() against a nine-element ALL written a few lines above it, in the same test module. Neither side referenced the real variant set. Adding Ecosystem::Swift changed neither, so the test stayed green while --ecosystem swift became unspellable, --help stopped advertising it, and check . --ecosystem rust silently excluded Swift manifests with no way to opt them back in. Nothing downstream caught it either: Ecosystem is #[non_exhaustive], so no match in crates/dependable is exhaustive — Config::ecosystem_enabled's _ => true included. Two doc comments claimed enforcement that did not exist ("the unit test beside it pins that every ecosystem crosses it"; "A missing variant fails here rather than at a user's prompt").

Fixed by promoting dependable-core's test-only ALL to a public Ecosystem::ALL, placed beside the exhaustive matches in the defining crate — the file a new variant genuinely does not compile past — and asserting the CLI's nameable set equals it. Both doc comments now claim only what is enforced.

Demonstrated, by adding a tenth variant Ecosystem::Zig:

Step Result
Add the variant to the enum alone, cargo check -p dependable-core error[E0004]: non-exhaustive patterns: 'Ecosystem::Zig' not covered6 errors, every one in ecosystem.rs, which is what puts the author in the file where ALL lives
Complete every arm and ALL, then cargo test -p dependable --bin dependable every_ecosystem_can_be_named_on_the_command_line ... FAILEDleft: [.., Jvm] right: [.., Jvm, Zig]
Same tree, cli.rs restored to this branch's previous version every_ecosystem_can_be_named_on_the_command_line ... ok — the old guard is green with a tenth ecosystem present and unnameable

Honest limit, stated on ALL itself: this is a prompt, not a proof. #[non_exhaustive] and the absence of stable variant enumeration mean nothing forces the list to grow — only the six compile errors, which land in the same file.

This is the third instance of this class in the repository. #124 (ALL_KINDS) and #145 (PackageSource -> source_token) are the other two, and are deliberately not touched here.

2. The load-bearing ordering invariant had no test (medium)

collect_manifests documents the ecosystem filter running before the glob filter "so that the glob's 'matched nothing' line counts only the manifests still in play", and nothing tested it. No test passed both flags; the matched() helper passed &[] for ecosystems on every call.

Both filters are set intersections, so the returned set cannot distinguish the two orders — a unit test on the return value provably cannot fail on a swap. The order is observable only in the diagnostics, so that is where it is pinned: the_ecosystem_filter_runs_before_the_glob_filter asserts stderr end to end. The unit test alongside it (an_ecosystem_and_a_glob_narrow_the_same_set) covers the other gap — that the two filters compose at all — and says in its own doc comment that it cannot pin the order.

Demonstrated by swapping the two blocks in collect_manifests:

FAILED: the glob must report against the narrowed set, and be the filter that came
back empty: no manifest for Rust (searched 1 manifest up to --depth 3; found Go)

and, on list . --ecosystem rust --manifest-glob 'nope/*' over a Cargo+npm+Go tree, exactly the two contradicting lines the finding predicted:

swapped:  no manifest matched nope/* (searched 3 manifests up to --depth 3)
          no manifest for Rust (searched 0 manifests up to --depth 3)
restored: no manifest matched nope/* (searched 1 manifest up to --depth 3)

The swap was reverted; the branch carries the correct order.

crates/dependable/tests/fixtures/sample-polyglot/ is a new fixture (a Rust service beside a Go one) rather than an edit to sample-monorepo, which is Rust throughout and so has nothing for an ecosystem filter to remove.

3. Empty selection wrote byte-empty stdout under --format json (medium)

Pre-existing, but promoted into reach by this change's own README. Fixed as decision 7 above. Covered by an_empty_selection_is_an_empty_document_not_empty_stdout, which asserts the document for both ways a selection empties — the ecosystem filter and the glob — and that table and text still print nothing.

4. --ecosystem rust --ecosystem rust said no manifest for Rust, Rust (low)

clap's Vec<T> with ArgAction::Append keeps repeats. Deduplicated by membership in requested_ecosystems, the same Vec::contains pattern no_ecosystem_match already uses (Ecosystem is Eq but not Ord), which also preserves first-named order. Selection was never affected — it was already a contains — so this is the diagnostic alone. Pinned by a_repeated_ecosystem_is_named_once.

5. report_no_manifests' comment described a justification that need not have occurred (low)

The comment said the generic line is suppressed because collect_manifests "has already said which ecosystems were asked for and what was there instead". The suppression is keyed on ecosystems.is_empty() — on a filter having been asked for, not on that filter being what emptied the set. With --ecosystem rust --manifest-glob 'nope/*' over a repository that does contain Rust, the glob empties it and the ecosystem explanation never prints.

Comment only; no behaviour change. The behaviour is right — the glob printed its own specific line, and "No supported manifests found." would still be false in a repository full of manifests a filter removed. The return type stays Vec<PathBuf>, as decision 5 already settled.

Commits

Commit subject Repairs
feat(core): publish the ecosystem list as Ecosystem::ALL finding 1
fix(cli): pin the nameable ecosystems against Ecosystem::ALL finding 1
test(cli): pin that the ecosystem filter runs before the glob filter finding 2
fix(cli): give an empty list --format json selection a document finding 3
fix(cli): say each requested ecosystem once finding 4
docs(cli): say what suppresses the generic no-manifest line finding 5

The only public API change in a library crate is the addition of Ecosystem::ALL — additive, and re-exported through dependable-fetch alongside the type. No exit code, config key, env var or schema version changes; dependable.list/v1 is unchanged and now also emitted where nothing was emitted before.

Validation

All run from the worktree with env -u FORCE_COLOR -u COLORTERM, on 905259a.

Command Outcome
cargo test -p dependable --test cli_ecosystem pass — 9 passed, 0 failed
cargo test -p dependable --test cli_list pass — 14 passed, 0 failed, 1 ignored (#[ignore = "network"])
cargo test -p dependable --bin dependable pass — 107 passed, 0 failed
cargo test -p dependable-core ecosystem pass — 7 passed, 0 failed
mise run test pass — exit 0, no failing test result across the workspace
mise run fmt:check (cargo fmt --all --check) pass — exit 0
mise run lint (cargo clippy --workspace --all-targets -- -D warnings) pass — exit 0
convco check origin/fix/93-report-declined-updates..HEAD no errors in 7 commits

Mutation checks, to prove the tests are not vacuous:

  • Disabling the manifest-set filter: 5 of the original 8 cli_ecosystem tests fail.
  • Dropping && requested(ecosystem) from the notice predicate: an_ecosystem_filter_silences_advice_about_ecosystems_it_excluded fails, alone.
  • Adding a tenth Ecosystem variant: 6 compile errors in ecosystem.rs, then every_ecosystem_can_be_named_on_the_command_line fails. Under the previous guard, the same tree was green.
  • Swapping the ecosystem and glob filters: the_ecosystem_filter_runs_before_the_glob_filter fails.

Not proven, and worth a reviewer's eye:

  • Ecosystem::ALL is checked by convention and by six compile errors in one file, not by the compiler checking the list itself. A macro defining the enum and the list together would be airtight; it was not taken, because it would restructure a heavily documented enum for a guard that already lands the author in the right file.
  • list --format json renders an absolute root with a leading // ("root": "//var/mnt/..."). Pre-existing in output::posix, reproduced on this branch's base with a non-empty selection, and untouched here.

`--ecosystem` narrows the manifests `check`, `list`, and `fix` read to the
ecosystems named, so one command can address one slice of a polyglot
repository the way `--manifest-glob` addresses one slice of a monorepo.

The flag this replaces parsed, was advertised in `--help` as restricting the
run, and was read by nothing. Making the claim true takes two separate
effects, and the load-bearing one is easy to miss:
`dependable_fetch::discover` documents that its `enabled` predicate gates its
unread-manifest notices only — discovery still returns every manifest it
recognizes — so composing the request into that predicate alone would silence
warnings and change nothing a command actually reads. `collect_manifests`
therefore also filters the returned set, before the `--manifest-glob` filter
so the glob's "matched nothing" line counts only what is still in play.

Accepted values are the canonical ecosystem names and nothing else, so
`--ecosystem foo` is now an error where the previous `Option<String>` took it
silently. `CSharp` is spelled `csharp`, not clap's derived `c-sharp`.

The flag only ever narrows: naming an ecosystem that `.dependable.toml`
disables does not switch it back on. Selecting nothing names which ecosystems
were searched and which were found instead, and exits 0 — an unused ecosystem
must not fail a per-ecosystem CI matrix job.
justin13888 added a commit that referenced this pull request Sep 6, 2026
…feat/111-advance-forced-versions

The two branches both rewrote how `fix` accounts for an update it does not
write, from opposite ends: #116 made the accounting exhaustive (five new
`DeclineReason` variants, `Declined::target` an `Option<String>`, a counted
`report_inherited_skips`), and #122 gave the one kind that was still silent —
an `overrides` / `resolutions` entry — a reason of its own and a flag that
advances it.

Resolved on the ordering rather than on the lines. `ForcedVersion` is now the
last variant and the last guard in `plan_fixes`, because every reason above it
would still stand with `--overrides` turned on: an override carrying a wildcard
reports the wildcard, one already at its target reports the range, and one
written as an explicit pin reports the pin, since `--all` and not `--overrides`
is the flag that would move it. Every note names a flag that acts.

The default is unchanged: `edits` still never receives an override unless
`--overrides` was asked for by name, `--all` included.
The list of every variant existed already, but only inside this module's
test module, where nothing outside the crate could reach it. A frontend
that must cover every ecosystem therefore had to write its own copy, and
a copy is not a guard: the hand that forgets a variant is the same hand
that would have updated the copy.

Promote it to a public associated const, beside the exhaustive matches
that a new variant genuinely does not compile past, so the one list a
new ecosystem cannot avoid meeting is also the list consumers pin
against. The doc comment says plainly that this is a prompt rather than
a proof — `#[non_exhaustive]` and the absence of stable variant
enumeration leave no way to make the compiler check the list itself.
`every_ecosystem_can_be_named_on_the_command_line` compared
`EcosystemArg::value_variants()` to a nine-element list written a few
lines above it, in the same test module. Neither side referenced the
real variant set, so adding an ecosystem changed neither and the test
stayed green while `--ecosystem` lost the ability to name it — and
`Ecosystem` being `#[non_exhaustive]` means no match in this crate
breaks either, `Config::ecosystem_enabled`'s `_ => true` included.

Compare against `Ecosystem::ALL` instead. It lives beside the exhaustive
matches in the defining crate, which is where a new variant stops
compiling, so the author of the variant is already in that file when the
list asks to be updated — and once it is, this test goes red.

Both doc comments overstated what was enforced ("the unit test beside it
pins that every ecosystem crosses it", "A missing variant fails here
rather than at a user's prompt"). They now say what the assertion
actually rests on, and why a list local to the test would not do.
`collect_manifests` documents the order as load-bearing — "so the glob's
'matched nothing' line counts only the manifests still in play" — and
nothing tested it. No test passed both `--ecosystem` and
`--manifest-glob`; the `matched` helper passed `&[]` for `ecosystems` on
every call. Swapping the two blocks is an easy move: both are a
`filter`/`collect` over `found`.

Swapped, `list . --ecosystem rust --manifest-glob 'nope/*'` over a
Cargo+npm+Go tree prints two contradicting lines — `no manifest matched
nope/* (searched 3 manifests ...)` counting manifests the ecosystem
filter was about to remove, and then, `found` being empty by that point,
`no manifest for Rust (searched 0 manifests ...)` about a tree that
contains Rust.

The order is invisible in the result, because both filters are set
intersections and either order returns the same manifests. So it is
pinned where it shows: an end-to-end assertion on the diagnostics. The
unit test alongside it covers the other gap — that the two filters
compose at all — and says in its own doc comment that it cannot pin the
order.

`sample-polyglot` is a new fixture rather than an edit to
`sample-monorepo`, which is Rust throughout and so has nothing for an
ecosystem filter to remove.
`run_list` reported the empty selection and returned before any JSON was
constructed, so stdout was byte-empty. `list . --ecosystem csharp
--format json | jq '.summary.projects'` — one shard per ecosystem, which
is what this flag is for — then failed to parse on every shard whose
ecosystem the repository does not use, which is precisely the set that
exiting 0 was chosen to keep green. The stderr line saying what was
searched is not machine-readable.

Emit the empty `dependable.list/v1` document instead: same schema, zero
projects, an empty `by_ecosystem`. Additive for any consumer, and a
parseable answer beats no answer.

`table` and `text` still return early. A human handed an empty table
wants the stderr line, not a blank one.

Both ways a selection can empty are covered — the ecosystem filter and
the glob — because the byte-empty stdout predated `--ecosystem` and the
glob path reached it too.
clap's `Vec<T>` with `ArgAction::Append` keeps repeats, so
`--ecosystem rust --ecosystem rust` reached the empty-selection line as
`no manifest for Rust, Rust (...)`. Dedupe by membership — the same
`Vec::contains` pattern `no_ecosystem_match` already uses to list what
was found instead, `Ecosystem` being `Eq` but not `Ord` — which also
keeps the order the user named them in.

Selection is untouched: it was already a `contains`, so a repeat never
changed which manifests were kept. This is the diagnostic alone.
`report_no_manifests` claimed it is silent because `collect_manifests`
"has already said which ecosystems were asked for and what was there
instead". The suppression is keyed on `ecosystems.is_empty()` — on an
ecosystem filter having been *asked for*, not on that filter being what
emptied the set. `--ecosystem rust --manifest-glob 'nope/*'` over a
repository that does contain Rust is emptied by the glob; the ecosystem
explanation never printed, and the generic line was suppressed on a
justification that had not occurred.

The behaviour is right — the glob printed its own specific line, and
"No supported manifests found." would still be false in a repository
full of manifests a filter removed — so only the comment changes. It now
describes what the predicate tests and says why it is the coarse one: a
caller cannot tell the two cases apart without `collect_manifests`
reporting back which filter emptied the set, and that return type stays
`Vec<PathBuf>` on purpose.
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