feat(cli): apm experimental - feature-flag registry with list/enable/disable/reset#845
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>
There was a problem hiding this comment.
Pull request overview
Adds a first-class experimental feature-flag subsystem to the APM CLI, including a new apm experimental command group and a worked example flag (verbose-version) that augments apm --version output when enabled.
Changes:
- Introduces a static experimental flag registry (
FLAGS) with config-backed overrides and helpers (is_enabled, enable/disable/reset, stale-key detection). - Adds
apm experimentalCLI group withlist/enable/disable/resetand associated unit tests. - Updates version output behind an experimental gate and adds docs/changelog entries for the new command.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/core/experimental.py |
Implements the experimental flag registry, config integration, and mutation/query helpers. |
src/apm_cli/commands/experimental.py |
New CLI command group to manage experimental flags. |
src/apm_cli/commands/_helpers.py |
Gates additional apm --version details behind verbose_version. |
src/apm_cli/cli.py |
Registers the new experimental command group with the top-level CLI. |
tests/unit/core/test_experimental.py |
Unit coverage for registry behavior, config parsing, overrides, and invariants. |
tests/unit/commands/test_experimental_command.py |
End-to-end CLI tests for list/enable/disable/reset flows and UX. |
tests/unit/commands/test_helpers_version.py |
Tests the verbose_version behavior in apm --version. |
docs/src/content/docs/reference/experimental.md |
New user-facing reference page for apm experimental. |
docs/src/content/docs/reference/cli-commands.md |
Adds apm experimental to the CLI reference index. |
packages/apm-guide/.apm/skills/apm-usage/commands.md |
Syncs the command into the shipped skill docs. |
CONTRIBUTING.md |
Adds contributor guidance for adding new experimental flags. |
CHANGELOG.md |
Adds an Unreleased changelog entry for the new command group. |
CLI Logging & Output UX Review —
|
- Fix CHANGELOG placeholder (#TBD -> microsoft#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>
Response to Panel Review — All actionable items addressed in
|
| Outcome | Symbol | Colour | Method |
|---|---|---|---|
| Success | [+] |
green | logger.success(symbol="check") |
| No-op warning | [!] |
yellow | logger.warning(...) |
| Neutral info | [i] |
blue | logger.progress(...) |
| Error | [x] |
red | logger.error(...) |
Validation
- Full unit suite:
5258 passed in 12.69s - Targeted:
tests/unit/commands/test_experimental_command.py(71 passed) — assertions updated to expect[!]on no-op paths and[+]on success paths
Appreciate the panel verdict. Ready for another pass if anything in the follow-up commit regressed the UX.
|
Re-opened as #849 with the branch moved from my fork ( All prior review history (Copilot 8 threads resolved + Panel CLI UX review approved) stays here on #845 for audit. Please continue review on #849. |
Pull request was closed
…disable/reset (#849) * feat(cli): apm experimental - feature-flag registry with list/enable/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(experimental): address PR #845 review feedback - 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> * fix(experimental): apply PR #849 review feedback - 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> * docs(experimental): document --json, per-subcommand --verbose, reset fix Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Sergio Sisternes <sergio.sisternes@epam.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com>
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.