feat: add stage-based feature flagging for modules, groups, and commands#43
Merged
Conversation
Introduce foundational stage-based readiness gating types (Stage, FeatureFlag, FlagPolicy) in a new src/feature_flags.rs module, distinct from the existing GlobalFlags concept in src/flags.rs.
Add #[must_use] to FeatureFlag::new to match the crate's constructor convention, and switch Stage's serde rename_all to "lowercase" so its attribute matches Tier's sibling style exactly (wire strings are identical either way since Stage variants are single words).
Adds an Option<FeatureFlag> field and .with_feature_flag(key, stage) builder to both CommandSpec and GroupSpec, mirroring the existing with_tier pattern. This only records each node's own flag declaration; ancestor-chain resolution and tree pruning land in a later task.
The feature_flag doc comment on CommandSpec and GroupSpec described ancestor-chain cascading resolution in present tense, overclaiming behavior that lands in a later task. Reword to future tense and clarify that nearest ancestor wins.
Mirrors the feature_flag field and with_feature_flag builder already added to CommandSpec/GroupSpec, letting a module declare its own readiness stage. Ancestor-chain resolution to an effective stage still lands in a later change.
Resolve each command/group/module's effective feature-flag stage by cascading from its nearest ancestor (self-declaration wins, else the nearest ancestor's resolved flag, else the implicit Ga default), and prune invisible nodes and emptied-out groups before they reach schema, view, clap, or dispatch registration. Fixes add_module silently discarding a module's own feature_flag by routing both add_module and add_module_group through a shared add_module_group_inner that threads the inherited flag down. Adds FlagEntry/FlagRegistry to record every flagged path discovered while pruning, and CliConfig::min_stage / with_feature_override to configure the policy driving it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…olicy EnvironmentDef gains min_stage and feature_overrides (TOML `[env.features]`) fields with builders, merged across compiled/file layers and overridable via <ENV>_MIN_STAGE and <ENV>_FEATURE_<KEY> env vars (apply_env_vars is now fallible to surface malformed stage values). Environment threads the resolved fields through. Cli::new merges the active environment's contribution into middleware.flag_policy on top of the CliConfig-level policy before tree pruning runs.
Adds a built-in `flags` command group (mirroring `env`/`config`) that lets users inspect which command/group/module nodes were flagged, what stage they declared, and whether the active policy judged them visible. `flags list` dumps the whole flag_registry; `flags info <key>` shows the merged policy plus every node that resolved to a given flag key, including whether an override or the plain min_stage comparison decided its visibility. Mounted unconditionally in Cli::new (unlike env/config, which are opt-in) since introspection doesn't depend on any other system being configured.
Add a "Feature Flags & Stages" reference section to docs/concepts.md, document EnvironmentDef's min_stage/feature_overrides layering (TOML shape, env-var overrides, full precedence order) in docs/environments.md, add a checklist bullet to AGENTS.md, and wire an experimental `project preview` command into examples/basic.rs (hidden by default, with a commented-out .with_min_stage(...) line showing how to reveal it). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The new "Feature Flags & Stages" section in docs/concepts.md was
hard-wrapped at ~96 columns, breaking paragraphs mid-sentence. Re-wrap
each paragraph as a single logical line to match docs/environments.md
and the file's own documented convention ("Write each paragraph as a
single physical line").
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add tests/feature_flags.rs exercising the feature-flagging pipeline end to end via Cli::run: cascading resolution with pruning (absence from --help and --schema, unknown-command error on direct dispatch), a permissive min_stage policy revealing a pruned subtree, environment-layered min_stage (compiled and via an <ENV>_MIN_STAGE env var), and the built-in flags list/info introspection commands distinguishing override- vs min_stage-decided flags. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Rename Test 3's environment from "prod" to the test-scoped "flagtest-envmin". The env-var override layer derives <ENV>_MIN_STAGE from the environment name (uppercased, - -> _), so a "prod" environment reads PROD_MIN_STAGE; an unrelated PROD_MIN_STAGE set in a developer/CI shell would silently override the compiled .with_min_stage(Stage::Experimental) the test asserts on. The distinctive name makes the derived FLAGTEST_ENVMIN_MIN_STAGE unable to collide, matching Test 4's existing flagtest-envvar approach; no ENV_LOCK needed since renaming removes the real-world collision entirely. Also add environment_feature_override_reveals_pruned_subtree_end_to_end, closing the one uncovered layer in this file: an environment's compiled per-key feature_overrides (distinct from consumer-level feature_override and environment min_stage) reaching tree-pruning through the full Cli::new + Cli::run pipeline. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- docs/environments.md: correct the staging feature-override example to "ga" (an override to "beta" under the Ga min_stage floor stays hidden), and document that env-resolution errors fail open when a consumer relies on an environment to tighten a permissive compiled default. - src/environments.rs: give env_var_min_stage_overrides_compiled_and_file_layers its own fixture with a compiled min_stage that differs from the env-var value, so it proves override-wins rather than mere field population. - tests/feature_flags.rs: add an end-to-end test proving the tightening direction (environment raising min_stage above a permissive consumer policy re-hides a node). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Introduces a stage-based feature-flagging system (Experimental/Beta/GA) that allows modules, groups, and commands to declare visibility gates, with pruning at mount time and policy layering from consumer config + environments, plus built-in introspection commands.
Changes:
- Adds
Stage,FeatureFlag,FlagPolicy, andFlagRegistry, and wires pruning intoCli::add_module/add_module_group. - Extends environment resolution to support
min_stageand per-key feature overrides (file + env var layering). - Adds built-in
flags list/flags info <key>plus end-to-end and unit tests and documentation updates.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/feature_flags.rs | End-to-end integration tests covering pruning, overrides, env layering, and flags introspection. |
| src/module.rs | Adds module-level feature-flag declaration and builder + unit tests. |
| src/middleware.rs | Stores merged flag_policy and flag_registry in middleware for pruning + introspection. |
| src/lib.rs | Exposes new feature-flag primitives publicly and adds the internal flag_commands module. |
| src/flag_commands.rs | Implements the built-in flags command group (list/info). |
| src/feature_flags.rs | Adds core types: Stage, FeatureFlag, FlagPolicy, FlagEntry, FlagRegistry + unit tests. |
| src/environments.rs | Adds environment-level min_stage + features overrides, plus env-var overrides and tests. |
| src/command.rs | Adds feature-flag declarations to CommandSpec/GroupSpec with builders + unit tests. |
| src/cli.rs | Implements pruning + registry recording during mount; layers env policy into middleware.flag_policy; mounts flags group. |
| examples/basic.rs | Demonstrates an Experimental-gated command and how to reveal it via policy. |
| docs/environments.md | Documents environment-level feature-flag plumbing, TOML shape, env vars, and precedence. |
| docs/concepts.md | Documents feature flags/stages and the built-in flags commands. |
| AGENTS.md | Updates contributor checklist with feature-flag guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Addresses Copilot review feedback: apply_env_flag updates middleware.env per invocation but never recomputes flag_policy or re-prunes the already-built command tree, so --env cannot change feature-flag visibility (help/schema/dispatch, flags list/info). Documents this as the current, intentional behavior rather than changing it.
FlagRegistry's doc comment on both feature_flags.rs and middleware.rs referred to `flags list`/`flags info` as future work, but this PR already ships those commands. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "Registered when" column said flags list/info are always registered, but ensure_flags_command intentionally skips registration if a consumer module already defines a top-level flags group.
… to `feature_overrides` `features` is a plausible name for an app-specific `extra` bag key that a consumer could already have in their environments.toml under an older crate version. Renaming to `feature_overrides` matches the Rust field name (same convention as `min_stage`) and avoids the collision.
An override only "decides" a node's visibility if removing it would flip the outcome; two nodes sharing a key can have different declared stages, so the override may be irrelevant to one while deciding the other.
std::env::var(key).ok() silently drops VarError::NotUnicode, causing EnvGuard to treat a non-Unicode prior value as unset and remove it on drop instead of restoring it. var_os/OsString round-trips byte-for-byte.
jbrooks2-godaddy
approved these changes
Jul 8, 2026
jpage-godaddy
pushed a commit
that referenced
this pull request
Jul 8, 2026
🤖 I have created a release *beep* *boop* --- ## [0.4.2](cli-engine-v0.4.1...cli-engine-v0.4.2) (2026-07-08) ### Features * add stage-based feature flagging for modules, groups, and commands ([#43](#43)) ([67038d4](67038d4)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stage(Experimental<Beta<Ga, defaultGa) andFlagPolicymechanism so modules/groups/commands can declare.with_feature_flag(key, stage)and be hidden from--help/--schema/dispatch/search until promoted — opt-in gating, zero behavior change when nothing is flagged.Cli::add_module/add_module_group.CliConfig::with_min_stage/with_feature_overridefor consumer-level policy, plus environment-levelmin_stage/feature_overridesinenvironments.tomland<ENV>_MIN_STAGE/<ENV>_FEATURE_<KEY>env-var overrides, with a defined six-layer precedence order (env var for a key > env var min-stage > env file per-key override > env file min-stage > consumer per-key override > consumer min-stage).flags list/flags info <key>introspection commands.docs/concepts.md,docs/environments.md),AGENTS.mdchecklist update, and anexamples/basic.rswalkthrough.Primary motivation: the GoDaddy public CLI is a separate consumer binary built on the published crate; this gives contributors a way to ship new commands continuously through normal CI while gating anything that needs extra scrutiny before it reaches that public audience, without slowing down internal iteration.
Test plan
cargo fmt --all --checkcargo clippy --all-targets -- -D warningsRUSTDOCFLAGS='-D warnings' cargo doc --no-depscargo test --all-targets(260 lib + 3 streaming + 8 feature-flag integration tests, all passing)cargo test --docexamples/basic.rs's experimentalproject previewcommand under strict vs. permissivemin_stage