Skip to content

feat: add stage-based feature flagging for modules, groups, and commands#43

Merged
jpage-godaddy merged 19 commits into
mainfrom
feature-flagging
Jul 8, 2026
Merged

feat: add stage-based feature flagging for modules, groups, and commands#43
jpage-godaddy merged 19 commits into
mainfrom
feature-flagging

Conversation

@jpage-godaddy

@jpage-godaddy jpage-godaddy commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a Stage (Experimental < Beta < Ga, default Ga) and FlagPolicy mechanism 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.
  • Cascading resolution (module → group → nested group → command, nearest declaration wins) with tree pruning at mount time via Cli::add_module/add_module_group.
  • CliConfig::with_min_stage/with_feature_override for consumer-level policy, plus environment-level min_stage/feature_overrides in environments.toml and <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).
  • Built-in flags list/flags info <key> introspection commands.
  • Docs (docs/concepts.md, docs/environments.md), AGENTS.md checklist update, and an examples/basic.rs walkthrough.

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 --check
  • cargo clippy --all-targets -- -D warnings
  • RUSTDOCFLAGS='-D warnings' cargo doc --no-deps
  • cargo test --all-targets (260 lib + 3 streaming + 8 feature-flag integration tests, all passing)
  • cargo test --doc
  • Manual check with examples/basic.rs's experimental project preview command under strict vs. permissive min_stage

jpage-godaddy and others added 13 commits July 8, 2026 12:05
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>
@jpage-godaddy jpage-godaddy requested a review from Copilot July 8, 2026 21:38
@jpage-godaddy jpage-godaddy changed the title Add stage-based feature flagging for modules, groups, and commands feat: add stage-based feature flagging for modules, groups, and commands Jul 8, 2026

Copilot AI 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.

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, and FlagRegistry, and wires pruning into Cli::add_module / add_module_group.
  • Extends environment resolution to support min_stage and 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.

Comment thread src/flag_commands.rs
Comment thread src/flag_commands.rs

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/cli.rs
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.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/cli.rs

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Comment thread src/feature_flags.rs Outdated
Comment thread src/middleware.rs Outdated
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>

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread docs/concepts.md Outdated
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.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/environments.rs
… 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.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/flag_commands.rs
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.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Comment thread src/module.rs
Comment thread src/command.rs
Comment thread src/command.rs

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread tests/feature_flags.rs
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.

Copilot AI 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.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@jpage-godaddy jpage-godaddy merged commit 67038d4 into main Jul 8, 2026
4 checks passed
@jpage-godaddy jpage-godaddy deleted the feature-flagging branch July 8, 2026 23:22
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>
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.

3 participants