feat(cli): apm experimental - feature-flag registry with list/enable/disable/reset#849
feat(cli): apm experimental - feature-flag registry with list/enable/disable/reset#849sergio-sisternes-epam wants to merge 4 commits intomainfrom
Conversation
…disable/reset
Introduces the `apm experimental` command group and a lightweight feature-flag
registry so APM can ship opt-in behaviour behind named flags, gather signal
from early adopters, and graduate to default when proven.
Ships with one worked example: `verbose-version`, which extends
`apm --version` to also print Python version, platform, and install path.
Highlights:
- O(1) flag lookup on top of the cached config (no I/O after first load)
- `is_enabled("typo")` raises ValueError (fail loud in code paths)
- `apm experimental enable <typo>` surfaces difflib "Did you mean?" suggestion
- Loader rejects non-bool overrides; falls back to registry default
- Security invariant: experimental flags MUST NOT gate security-critical paths
- 71+ targeted tests; full unit suite green (4,842 passed)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Fix CHANGELOG placeholder (#TBD -> #845) - Use typing.Any in test_helpers_version.py - Align CONTRIBUTING.md recipe code with its function-scope-import prose - Correct singular/plural "default(s)" in reset doc example - Pass symbol="check" to logger.success on enable/disable/reset so they render [+] - Switch already-enabled/disabled no-op messages to logger.warning ([!]) per traffic-light rule - Route reset confirmation preamble through CommandLogger (was raw click.echo) - Verified: removing ignore_unknown_options breaks `reset --yes`; keeping both flags Addresses Copilot review + PR Review Panel CLI Logging UX findings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces a first-class experimental feature-flag subsystem to APM, including a new apm experimental command group for managing user-scoped opt-in flags stored in ~/.apm/config.json, plus one initial flag (verbose-version) that extends apm --version output when enabled.
Changes:
- Added a static experimental flag registry (
FLAGS) with config-backed enable/disable/reset helpers and strict lookup semantics (is_enabledfails loud on unknown flags). - Added
apm experimentalCLI group withlist/enable/disable/resetand Rich UI + plain-text fallbacks. - Added documentation + contributor guidance and comprehensive unit tests; added a gated
verbose-versionblock toapm --version.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/apm_cli/core/experimental.py | New feature-flag registry, config integration, and mutation/query APIs. |
| src/apm_cli/commands/experimental.py | New apm experimental command group (list/enable/disable/reset) with Rich/table output and confirmations. |
| src/apm_cli/commands/_helpers.py | Adds a verbose-version experimental branch to print_version. |
| src/apm_cli/cli.py | Registers the new experimental command group with the main CLI. |
| tests/unit/core/test_experimental.py | Unit tests for registry invariants, config parsing/validation, and mutator behavior. |
| tests/unit/commands/test_experimental_command.py | CLI tests for list/filters, enable/disable flows, reset prompts, and -v behavior. |
| tests/unit/commands/test_helpers_version.py | Tests the verbose-version behavior and the fail-open --version guard. |
| docs/src/content/docs/reference/experimental.md | New user-facing reference page for experimental flags and workflows. |
| docs/src/content/docs/reference/cli-commands.md | Adds apm experimental to the CLI reference. |
| packages/apm-guide/.apm/skills/apm-usage/commands.md | Syncs the command list for the apm-guide skill resources. |
| CONTRIBUTING.md | Adds contributor recipe and rules for adding experimental flags. |
| CHANGELOG.md | Adds an Unreleased entry documenting apm experimental and the initial flag. |
APM Review Panel VerdictDisposition: APPROVE (with minor follow-up items) Per-persona findingsPython Architect Overall structure is clean and idiomatic. Highlights and concerns: (+) (-) [Minor] Private API leakage across module boundaries. (-) [Minor] Double normalisation in (-) [Minor] CLI Logging Expert (+) All four subcommands instantiate (-) [Low] Preamble noise on DevX UX Expert (+) (-) [Medium -- should fix before or shortly after merge] (-) [Low] No (-) [Low] Supply Chain Security Expert (+) Config path is No blocking security findings. No new path traversal, no token surfaces, no integrity weakening. Auth Expert No authentication surfaces are introduced or modified. OSS Growth Hacker (+) The experimental flag system is a mature OSS signal -- projects like Rust/Cargo and bun use feature flags to de-risk rollouts. Surfaces this narrative angle in release notes: "APM ships features opt-in before graduation to default." Side-channel note to CEO: The experimental flag system, if communicated at the next release, gives APM a compelling "we ship responsibly" story that enterprise evaluators will value. Recommend a brief callout in the CHANGELOG / release post. CEO arbitrationThis PR delivers a well-scoped infrastructure capability with good test coverage, complete documentation, and explicit security invariant enforcement. The specialists reached consensus: no blocking issues, two medium items, and several low/minor follow-ups. Strategic rationale for approving as-is: The The security invariant is correctly documented and enforced; no strategic risk. The feature flag system aligns with APM's "ship fast, communicate clearly" principle and gives the maintainer team a controlled rollout lever for future changes. Required actions before merge: None blocking. Required actions before mergeNone. The implementation is correct, tested, and documented. Optional follow-ups
|
- Reset now cleans malformed overrides of registered flags (#849, Copilot) - --verbose works after subcommand name (#849, Panel P-UX-1) - Promote name-handling helpers to public API (#849, Panel P-ARCH-1) - Deduplicate flag-name normalisation (#849, Panel P-ARCH-2) - reset() returns removal count instead of list (#849, Panel P-ARCH-3) - Demote list preamble to --verbose only (#849, Panel P-LOG-1) - Add --json output to list (#849, Panel P-UX-2) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Starting doc sync for the final |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Doc sync is complete and pushed to Updated:
Coverage includes |
|
Thanks for the thorough review. Rather than defer any of this to follow-ups, I've applied every actionable finding in this PR to keep the first release of Commits on this branch:
Full unit suite: 5275 passed. Findings addressed
P-UX-3 — keeping
|
|
@sergio-sisternes-epam , did you think of using a config key set to activate experimental functionality and just go with it? I see a lot of commands related to "experimental" which may be overcomplicating things? In Copilot CLI for example, one simply activated experimental mode and all experimental features were enabled. I think per-feature management is overkill at our stage. |
danielmeppiel
left a comment
There was a problem hiding this comment.
simplify to single "experimental mode" activation with a config setting
|
@danielmeppiel the rationale behind the design and implementation is the following:
Why not a "single experimental mode"? This comes from my personal frustration when using Copilot CLI, which follows this approach. Taking that as an example: In Copilot CLI experimental, Why not enable all? We don't expect to have as many experimental features. Hence, it made sense to drop the "--all" and let users enable the features that are relevant and work for them, and disable what is not working for them or are not willing to test/explore. Let me know if the design rationale makes sense and we should keep it, or you prefer to fall back to the "single experimental mode" NOTE: Internally, experimental relies on the "config" capabilities to store the flags. It only creates this UX I was mentioning earlier. |
Summary
Introduces
apm experimental— a first-class feature-flag subsystem that lets APM ship opt-in changes behind named flags so early adopters can try new behaviour without branching releases.Ships with one worked example:
verbose-version, which extendsapm --versionto also print Python version, platform, and install path.Why
Today we have no safe way to iterate on potentially disruptive changes in
main. Contributors either ship full switches (risk breaking users) or carry long-lived branches (rot risk, no real-user signal). A lightweight registry + O(1) lookup gives us a third path: merge the code, gate it behind a flag, collect signal from opt-in users, and graduate to default when proven.What's in this PR
New command group
kebab-caseandsnake_caseon input; displayskebab-case.difflib-powered "Did you mean?" on unknown flag names.resetwith no argument clears all overrides (interactive confirmation;--yesskips it for CI).listand cleaned up byreset.Core registry
src/apm_cli/core/experimental.py— frozenExperimentalFlagdataclass, staticFLAGSregistry, module-levelis_enabled(name).apm_cli.config._config_cache. No I/O after first load, no extra caches, no singleton. Safe for use in hot paths.is_enabled("typo")raisesValueError— fail loud on developer typos in shipped code.~/.apm/config.json.Storage
Overrides persist under a new
experimentalkey in~/.apm/config.json:{ "default_client": "vscode", "experimental": { "verbose_version": true } }Only overrides are written. Unset flags fall through to the registry default (which is always
false— asserted by an invariant test).Worked example:
verbose-versionWhen enabled,
apm --versionappends Python version, platform, and install path. The gated block is wrapped intry/except Exception: passso a flag-subsystem bug can never break--version.Security invariant
Experimental flags MUST NOT gate security-critical behaviour (content scanning, path validation, lockfile integrity, token handling, MCP trust checks). Documented in
docs/src/content/docs/reference/experimental.mdandCONTRIBUTING.md. The silent-ignore policy for stale keys is safe only because of this invariant.How to add a flag (for contributors)
See the new "How to add an experimental feature flag" section in
CONTRIBUTING.md. TL;DR:ExperimentalFlag(...)entry toFLAGSinsrc/apm_cli/core/experimental.py.is_enabledat function scope (not module top-level — avoids config I/O at import time) and branch on it.docs/src/content/docs/reference/experimental.md.Testing
tests/unit/core/test_experimental.py,tests/unit/commands/test_experimental_command.py, and extendedtests/unit/commands/test_helpers_version.py.uv run pytest tests/unit tests/test_console.py-> 4,842 passed.name==key,default is False, printable-ASCII descriptions), round-trip enable/disable, stale-key diagnostic, unknown-flag error + difflib suggestions,resetconfirmation (accept/decline/--yes), kebab/snake input parity, plain-text fallback when Rich is unavailable, and thatapm --versiondefault output is unchanged when the flag is off.Documentation
docs/src/content/docs/reference/experimental.md— user-facing reference (commands, storage schema, graduation lifecycle, security boundary).CONTRIBUTING.md— "How to add an experimental feature flag" recipe.CHANGELOG.md—Unreleased>Addedentry.docs/src/content/docs/reference/cli-commands.md— new command entry.packages/apm-guide/.apm/skills/apm-usage/commands.md— new command entry.Review panel
Pre-open review via the
apm-review-panelskill: python-architect, cli-logging-expert, devx-ux-expert, supply-chain-security-expert, oss-growth-hacker, and apm-ceo. All 10 ratified pre-PR fixes have been applied. CEO verdict: SHIP-WITH-FIXES — all fixes in; deferred follow-ups tracked separately.Out of scope (deliberate)
apm.yml.APM_EXPERIMENTAL_*).Backward compatibility
Zero impact for existing users. Users with no
experimentalkey inconfig.jsonsee identical behaviour to today.apm --versionoutput is unchanged unless a user opts in.