en un clic
sync-feature-flags
Sync feature flags from Statsig gates to code
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Menu
Sync feature flags from Statsig gates to code
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Basé sur la classification professionnelle SOC
Build the project for testing
Build the project
Take the current plan all the way to a ready-to-merge pull request — review the plan (scaled to risk), implement it test-first, code-review and fix, security-review, verify the acceptance criteria, open the PR, and watch it green. Use after you have an approved plan (e.g. from plan mode) and want the rest of the feature pipeline run end-to-end. Invoking it is itself plan approval — it then runs autonomously to a single hard stop: ready-to-merge.
Run a specific test target or test class
Run all unit tests
Run snapshot tests
| name | sync-feature-flags |
| description | Sync feature flags from Statsig gates to code |
Syncs Statsig feature gates (source of truth) to FeatureFlag.swift in code. Handles adding new flags, updating changed names/descriptions, and removing deleted flags with full cascading cleanup.
This skill syncs Statsig → Code (Statsig is the source of truth). For the reverse direction — creating a new Statsig gate when adding a feature flag in code — see "Feature flag creation" under Project-Specific Rules in CLAUDE.md. New gates are created with mcp__statsig__Create_Gate, enabled for the development environment only.
| File | Role |
|---|---|
Platform/FeatureAccess/Sources/FeatureAccess/Models/FeatureFlag.swift | Static flag definitions + allFlags array |
Platform/FeatureAccess/Tests/FeatureAccessTests/FeatureFlagTests.swift | Count assertion + ID arguments list |
Features/*/Sources/*/*.swift | *Dependencies, view model, and View files that consume flags |
Features/*/Tests/*/*.swift | View model tests covering feature flags (*ViewModelTests.swift) |
Adapters/Contexts/*/Sources/*/*.swift | Adapter FeatureFlagProvider implementations |
Adapters/Contexts/*/Tests/*/*.swift | Adapter provider tests |
App/Features/AppRoot/AppRootDependencies.swift | Root dependencies with navigation-level flags |
App/Features/AppRoot/AppRootViewModel.swift | Root view model mirroring navigation-level flags |
PopcornUITests/FeatureFlag.swift | UI test feature flag enum (mirrors flag IDs as String raw values) |
Statsig gate IDs use snake_case. Swift properties use camelCase.
| Statsig Gate ID | Swift Property | Swift Name |
|---|---|---|
media_search | .mediaSearch | "Media Search" |
explore_discover_movies | .exploreDiscoverMovies | "Explore Discover Movies" |
plot_remix_game | .plotRemixGame | "Plot Remix Game" |
tv_series_intelligence | .tvSeriesIntelligence | "TV Series Intelligence" |
Conversion rules:
_, capitalize each word after the first → camelCase property name_, capitalize each word → Title Case nameexplore → .explore, "Explore")Verify the Statsig MCP server is available by checking for mcp__statsig__Get_List_of_Gates in the tool list. If unavailable, tell the user:
"The Statsig MCP server is not available. Please configure it and try again."
Stop here if unavailable.
Call mcp__statsig__Get_List_of_Gates to retrieve all feature gates. Extract id, name, and description from each gate.
Read Platform/FeatureAccess/Sources/FeatureAccess/Models/FeatureFlag.swift and parse:
static let property: extract id, name, descriptionallFlags array entriesMatch gates to code flags by id. Categorize into three groups:
| Category | Condition |
|---|---|
| New | Gate ID exists in Statsig but not in code |
| Updated | Gate ID exists in both but name or description differ |
| Removed | Flag ID exists in code but not in Statsig |
Present the diff to the user before making any changes:
Feature Flag Sync Report:
New (3):
+ media_search_v2 — "Media Search V2" — "Controls access to Media Search V2"
+ cast_details — "Cast Details" — "Controls access to Cast Details"
+ ...
Updated (1):
~ explore — name: "Explore" → "Explore Tab"
Removed (1):
- backdrop_focal_point — "Backdrop Focal Point"
Proceed with sync?
If all three categories are empty, report "Feature flags are already in sync." and stop.
For new flags, add to FeatureFlag.swift:
static let property at the end of the last extension (before the closing }):/// Controls access to <name>.
static let <camelCaseProperty> = FeatureFlag(
id: "<snake_case_id>",
name: "<Name from Statsig>",
description: "<Description from Statsig>"
)
.<camelCaseProperty> to the allFlags arrayEmpty Statsig descriptions: If a gate has no description, generate one: "Controls access to <name>"
For updated flags, edit the existing static let to match the new name and/or description from Statsig. Also update the doc comment if the description changed.
Update PopcornUITests/FeatureFlag.swift to mirror changes:
case with camelCase name and snake_case raw value, in the matching MARK sectioncaseFeatureFlag.swiftFor each removed flag, perform a full impact search before asking for confirmation.
For a flag named .myFlag with ID my_flag, search for:
# Direct flag references
rg "\.myFlag" -- find .myFlag references in code
rg "isMyFlagEnabled" -- find *Dependencies / view model / View properties
rg "my_flag" -- find ID string references in tests
Show the user every file and usage that will be affected:
Removing flag: .myFlag ("my_flag")
Affected files:
Dependencies: Features/SomeFeature/Sources/.../SomeDependencies.swift
- var isMyFlagEnabled: @Sendable () throws -> Bool
- live(services:) isMyFlagEnabled closure
- preview isMyFlagEnabled closure
ViewModel: Features/SomeFeature/Sources/.../SomeViewModel.swift
- isMyFlagEnabled property
- init parameter
- updateFeatureFlags() assignment
View: Features/SomeFeature/Sources/.../SomeView.swift
- if viewModel.isMyFlagEnabled { ... }
Tests: Features/SomeFeature/Tests/.../SomeViewModelTests.swift
- All test methods referencing isMyFlagEnabled
Adapter: Adapters/.../FeatureFlagProvider.swift (if applicable)
- isMyFlagEnabled() method
Confirm removal of .myFlag? (y/n)
When the user confirms removal of a flag, remove in this order:
static let property and allFlags entry*Dependencies.swift):
var is<Flag>Enabled property from the struct (and its init parameter/assignment)live(services:)#if DEBUG preview valuelet featureFlags = services.featureFlags line*ViewModel.swift):
is<Flag>Enabled property and its init parameterupdateFeatureFlags()guard is<Flag>Enabled checks and their associated logic*View.swift):
if viewModel.is<Flag>Enabled { ... } conditionals*ViewModelTests.swift):
*Dependencies factory (stubDependencies) to drop the flag closureFeatureFlagProviderFeatureFlagProviding protocolAfter all additions, updates, and removals:
#expect(FeatureFlag.allFlags.count == <new_count>)
allFlags array — same order, same IDs.After all changes are applied, present a summary:
Feature Flag Sync Complete:
Added: 3 flags
Updated: 1 flag
Removed: 1 flag
Files modified:
- Platform/FeatureAccess/Sources/FeatureAccess/Models/FeatureFlag.swift
- Platform/FeatureAccess/Tests/FeatureAccessTests/FeatureFlagTests.swift
- ... (list other modified files)
After all changes:
Remember to run the pre-PR checklist:
make lint,/build,/test(formatting is applied automatically by the PostToolUse hook)
Do NOT run these automatically — let the user invoke them.
// In the *Dependencies struct (a plain Sendable struct of closures):
public var is<Flag>Enabled: @Sendable () throws -> Bool
// In live(services:):
is<Flag>Enabled: { featureFlags.isEnabled(.<flagProperty>) }
// In the #if DEBUG preview value:
is<Flag>Enabled: { true }
The view model mirrors each *Dependencies flag closure into an observable property:
// Property (read-only to the view):
public private(set) var is<Flag>Enabled = false
// init parameter (defaults to false so previews/tests can override):
is<Flag>Enabled: Bool = false
// Refresh from dependencies (called from didAppear() / updateFeatureFlags()):
func updateFeatureFlags() {
is<Flag>Enabled = (try? dependencies.is<Flag>Enabled()) ?? false
}
if viewModel.is<Flag>Enabled {
// Conditional UI
}
Instantiate the view model with a stub *Dependencies and assert on the mirrored property after calling updateFeatureFlags(). Four required cases per feature:
truefalsefalsetrue// Domain protocol:
public protocol FeatureFlagProviding: Sendable {
func is<Flag>Enabled() throws(FeatureFlagProviderError) -> Bool
}
// Adapter:
func is<Flag>Enabled() throws(FeatureFlagProviderError) -> Bool {
featureFlags.isEnabled(.<flagProperty>)
}
To find all usage of a flag with property name myFlag and ID my_flag:
# Static property references (*Dependencies live(services:), Adapter providers)
rg "\.myFlag\b"
# *Dependencies / view model / View property references
rg "isMyFlagEnabled"
# String ID references (tests, FeatureFlag.swift)
rg '"my_flag"'
# FeatureFlagProviding protocol methods (adapters)
rg "isMyFlagEnabled.*throws"
$ARGUMENTS