commit 3b29d102e61e6bb0a653facedc660d63d5634476 Author: Zhongwei Li Date: Sat Nov 29 18:46:20 2025 +0800 Initial commit diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json new file mode 100644 index 0000000..9d20f7c --- /dev/null +++ b/.claude-plugin/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "feature-flag-manager", + "description": "Adds feature flag support using LaunchDarkly or JSON-based configuration to toggle features in UI components and Server Actions. This skill should be used when implementing feature flags, feature toggles, progressive rollouts, A/B testing, or gating functionality behind configuration. Use for feature flags, feature toggles, LaunchDarkly integration, progressive rollout, canary releases, or conditional features.", + "version": "1.0.0", + "author": { + "name": "Hope Overture", + "email": "support@worldbuilding-app-skills.dev" + }, + "skills": [ + "./skills" + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..72e1781 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# feature-flag-manager + +Adds feature flag support using LaunchDarkly or JSON-based configuration to toggle features in UI components and Server Actions. This skill should be used when implementing feature flags, feature toggles, progressive rollouts, A/B testing, or gating functionality behind configuration. Use for feature flags, feature toggles, LaunchDarkly integration, progressive rollout, canary releases, or conditional features. diff --git a/plugin.lock.json b/plugin.lock.json new file mode 100644 index 0000000..636d724 --- /dev/null +++ b/plugin.lock.json @@ -0,0 +1,49 @@ +{ + "$schema": "internal://schemas/plugin.lock.v1.json", + "pluginId": "gh:hopeoverture/worldbuilding-app-skills:plugins/feature-flag-manager", + "normalized": { + "repo": null, + "ref": "refs/tags/v20251128.0", + "commit": "b51ac970de117f961c1c6205a1839e934911d900", + "treeHash": "02261fb0837c4b9f1fd4e818871464c78c3ba8a41fc0b0dc814d6c0c6955935e", + "generatedAt": "2025-11-28T10:17:32.682044Z", + "toolVersion": "publish_plugins.py@0.2.0" + }, + "origin": { + "remote": "git@github.com:zhongweili/42plugin-data.git", + "branch": "master", + "commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390", + "repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data" + }, + "manifest": { + "name": "feature-flag-manager", + "description": "Adds feature flag support using LaunchDarkly or JSON-based configuration to toggle features in UI components and Server Actions. This skill should be used when implementing feature flags, feature toggles, progressive rollouts, A/B testing, or gating functionality behind configuration. Use for feature flags, feature toggles, LaunchDarkly integration, progressive rollout, canary releases, or conditional features.", + "version": "1.0.0" + }, + "content": { + "files": [ + { + "path": "README.md", + "sha256": "3e0361790c1987bacac8f7ba98451cb9cfe0e0500b218ab9710060b5f33258a1" + }, + { + "path": ".claude-plugin/plugin.json", + "sha256": "2f7226aecd4a63fa1f084b534c69f4efe554bb03d4567a43c4e319fbe12ffa36" + }, + { + "path": "skills/feature-flag-manager/SKILL.md", + "sha256": "b037a0b67a0f5143c3d7dec0761afd77f14d8264b049e6fe95c1e745adef0f92" + }, + { + "path": "skills/feature-flag-manager/assets/feature-flag-provider.tsx", + "sha256": "756aff37a6d883e6a261b8a23700227712500defb3d471e6c0f589a4c73634d6" + } + ], + "dirSha256": "02261fb0837c4b9f1fd4e818871464c78c3ba8a41fc0b0dc814d6c0c6955935e" + }, + "security": { + "scannedAt": null, + "scannerVersion": null, + "flags": [] + } +} \ No newline at end of file diff --git a/skills/feature-flag-manager/SKILL.md b/skills/feature-flag-manager/SKILL.md new file mode 100644 index 0000000..8b54d65 --- /dev/null +++ b/skills/feature-flag-manager/SKILL.md @@ -0,0 +1,400 @@ +--- +name: feature-flag-manager +description: Adds feature flag support using LaunchDarkly or JSON-based configuration to toggle features in UI components and Server Actions. This skill should be used when implementing feature flags, feature toggles, progressive rollouts, A/B testing, or gating functionality behind configuration. Use for feature flags, feature toggles, LaunchDarkly integration, progressive rollout, canary releases, or conditional features. +--- + +# Feature Flag Manager + +Implement comprehensive feature flag management for controlled feature rollouts and A/B testing in worldbuilding applications. + +## Overview + +To add feature flag capabilities: + +1. Choose between LaunchDarkly (cloud service) or JSON-based (local) implementation +2. Set up feature flag provider with configuration +3. Create feature flag components and hooks +4. Gate UI components and Server Actions behind flags +5. Implement user targeting and progressive rollouts + +## Implementation Options + +### LaunchDarkly Integration + +To integrate LaunchDarkly: + +1. Install LaunchDarkly SDK: `npm install launchdarkly-react-client-sdk` +2. Configure provider in app root with client-side ID +3. Create hooks for flag evaluation +4. Wrap components with feature flag checks +5. Configure flags in LaunchDarkly dashboard + +Use `scripts/setup_launchdarkly.py` to scaffold LaunchDarkly configuration. + +### JSON-Based Feature Flags + +To implement local JSON-based flags: + +1. Create `config/feature-flags.json` with flag definitions +2. Build feature flag provider context +3. Create hooks to read flags from context +4. Implement environment-specific overrides +5. Support runtime flag updates + +Use `scripts/setup_json_flags.py` to generate JSON flag structure. + +Consult `references/feature-flag-patterns.md` for implementation patterns and best practices. + +## Feature Flag Provider Setup + +### LaunchDarkly Provider + +To set up LaunchDarkly in Next.js: + +```typescript +// app/providers.tsx +import { LDProvider } from 'launchdarkly-react-client-sdk'; + +export function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} +``` + +### JSON Provider + +To create custom JSON provider: + +```typescript +// lib/feature-flags/provider.tsx +import { createContext, useContext } from 'react'; +import flags from '@/config/feature-flags.json'; + +const FeatureFlagContext = createContext(flags); + +export function FeatureFlagProvider({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} + +export function useFeatureFlags() { + return useContext(FeatureFlagContext); +} +``` + +Reference `assets/feature-flag-provider.tsx` for complete provider implementation. + +## Using Feature Flags + +### Component Gating + +To gate UI components behind flags: + +```typescript +import { useFeatureFlag } from '@/lib/feature-flags'; + +export function NewFeatureComponent() { + const isEnabled = useFeatureFlag('new-timeline-view'); + + if (!isEnabled) { + return null; + } + + return
New Timeline View
; +} +``` + +### Conditional Rendering + +To show different variants based on flags: + +```typescript +export function EntityList() { + const useNewLayout = useFeatureFlag('entity-list-redesign'); + + return useNewLayout ? : ; +} +``` + +### Server Action Gating + +To gate Server Actions: + +```typescript +// app/actions/entity-actions.ts +'use server'; + +import { getFeatureFlag } from '@/lib/feature-flags/server'; + +export async function createEntity(data: EntityData) { + const allowBulkCreate = await getFeatureFlag('bulk-entity-creation'); + + if (allowBulkCreate && data.items?.length > 1) { + return bulkCreateEntities(data.items); + } + + return createSingleEntity(data); +} +``` + +Reference `assets/server-actions-with-flags.ts` for server-side flag patterns. + +## Flag Configuration + +### Flag Structure + +Define flags with metadata: + +```json +{ + "flags": { + "new-timeline-view": { + "enabled": true, + "description": "Enable new timeline visualization", + "rolloutPercentage": 100, + "allowedUsers": [], + "environments": { + "development": true, + "staging": true, + "production": false + } + } + } +} +``` + +### Flag Types + +Support different flag types: + +- **Boolean**: Simple on/off toggle +- **Percentage**: Gradual rollout (0-100%) +- **User Targeting**: Specific users or groups +- **Multivariate**: Multiple variations (A/B/C testing) + +Consult `references/flag-types.md` for detailed flag type specifications. + +## Progressive Rollout + +To implement gradual feature rollouts: + +1. **Initial Release**: Enable for 5% of users +2. **Monitor Metrics**: Track performance and errors +3. **Increase Rollout**: Gradually increase to 25%, 50%, 100% +4. **Full Release**: Enable for all users +5. **Remove Flag**: Clean up flag code after stable release + +Use `scripts/rollout_manager.py` to automate rollout percentage updates. + +## User Targeting + +To target specific user segments: + +### By User ID + +```typescript +const flags = evaluateFlags(userId, flagConfig); +``` + +### By User Properties + +```typescript +const flags = evaluateFlags({ + userId: 'user-123', + email: 'user@example.com', + role: 'admin', + tier: 'premium', +}); +``` + +### By Custom Rules + +```typescript +const flags = evaluateFlags(user, { + rules: [ + { property: 'role', operator: 'equals', value: 'admin' }, + { property: 'tier', operator: 'in', values: ['premium', 'enterprise'] }, + ], +}); +``` + +## Environment-Specific Flags + +To configure flags per environment: + +```json +{ + "flags": { + "debug-mode": { + "development": true, + "staging": true, + "production": false + }, + "experimental-features": { + "development": true, + "staging": false, + "production": false + } + } +} +``` + +Load environment-specific configuration at build time or runtime. + +## A/B Testing + +To implement A/B tests: + +```typescript +export function EntityDetailPage() { + const variant = useFeatureFlagVariant('entity-detail-layout', { + control: 'grid', + variantA: 'list', + variantB: 'cards', + }); + + return variant === 'list' ? ( + + ) : variant === 'cards' ? ( + + ) : ( + + ); +} +``` + +Track variant exposure for analytics: + +```typescript +useEffect(() => { + trackExposure('entity-detail-layout', variant); +}, [variant]); +``` + +## Flag Lifecycle Management + +### Creating New Flags + +To add a new feature flag: + +1. Define flag in configuration with metadata +2. Set initial state (usually disabled) +3. Implement flag checks in code +4. Deploy with flag disabled +5. Enable flag via dashboard or config update + +### Removing Old Flags + +To clean up completed feature flags: + +1. Ensure flag is at 100% rollout +2. Verify no incidents for 2+ weeks +3. Remove flag checks from code +4. Make flagged code permanent +5. Remove flag from configuration +6. Deploy cleanup changes + +Use `scripts/find_flag_usage.py` to locate all usages of a flag before removal. + +## Testing with Feature Flags + +### Local Development + +To override flags in development: + +```typescript +// .env.local +NEXT_PUBLIC_FEATURE_FLAGS_OVERRIDE='{"new-timeline-view":true}' +``` + +### Testing Specific Variants + +To test flag variations: + +```typescript +// test/setup.ts +import { mockFeatureFlags } from '@/lib/feature-flags/testing'; + +beforeEach(() => { + mockFeatureFlags({ + 'new-timeline-view': true, + 'bulk-entity-creation': false, + }); +}); +``` + +Reference `assets/testing-utils.ts` for testing utilities. + +## Monitoring and Analytics + +To track feature flag impact: + +1. **Exposure Tracking**: Log when flags are evaluated +2. **Performance Metrics**: Monitor performance per variant +3. **Error Rates**: Track errors by flag state +4. **User Engagement**: Measure feature usage +5. **Conversion Metrics**: Track business metrics per variant + +Integrate with analytics platform: + +```typescript +import { analytics } from '@/lib/analytics'; + +export function useFeatureFlagWithTracking(flagName: string) { + const isEnabled = useFeatureFlag(flagName); + + useEffect(() => { + analytics.track('feature_flag_exposure', { + flag: flagName, + enabled: isEnabled, + }); + }, [flagName, isEnabled]); + + return isEnabled; +} +``` + +## LaunchDarkly-Specific Features + +To leverage LaunchDarkly capabilities: + +- **Targeting Rules**: Create complex targeting rules in dashboard +- **Flag Triggers**: Automate flag changes based on metrics +- **Scheduled Rollouts**: Plan feature releases in advance +- **Flag Dependencies**: Define flag prerequisites +- **Audit Logs**: Track all flag changes +- **Team Workflows**: Require approvals for production changes + +Consult LaunchDarkly documentation for advanced features. + +## Best Practices + +1. **Short-Lived Flags**: Remove flags after rollout completes +2. **Clear Naming**: Use descriptive, consistent flag names +3. **Documentation**: Document flag purpose and timeline +4. **Default Values**: Provide safe defaults for missing flags +5. **Testing**: Test both enabled and disabled states +6. **Monitoring**: Track flag performance and errors +7. **Cleanup**: Regularly audit and remove unused flags + +## Troubleshooting + +Common issues: + +- **Flag Not Updating**: Check cache settings and invalidation +- **Inconsistent State**: Verify flag evaluation consistency +- **Performance Impact**: Minimize flag evaluation overhead +- **Testing Challenges**: Use proper mocking in tests +- **Configuration Conflicts**: Ensure environment-specific overrides work correctly diff --git a/skills/feature-flag-manager/assets/feature-flag-provider.tsx b/skills/feature-flag-manager/assets/feature-flag-provider.tsx new file mode 100644 index 0000000..da4be1e --- /dev/null +++ b/skills/feature-flag-manager/assets/feature-flag-provider.tsx @@ -0,0 +1,163 @@ +/** + * Feature Flag Provider - JSON-based implementation + */ + +import { createContext, useContext, ReactNode } from 'react'; + +// Feature flag configuration type +export interface FeatureFlagConfig { + enabled: boolean; + description?: string; + rolloutPercentage?: number; + allowedUsers?: string[]; + environments?: Record; +} + +export interface FeatureFlags { + [key: string]: FeatureFlagConfig; +} + +// Context for feature flags +const FeatureFlagContext = createContext({}); + +interface FeatureFlagProviderProps { + children: ReactNode; + flags: FeatureFlags; + userId?: string; + environment?: string; +} + +/** + * Provider component that makes feature flags available to the app + */ +export function FeatureFlagProvider({ + children, + flags, + userId, + environment = process.env.NODE_ENV, +}: FeatureFlagProviderProps) { + // Evaluate flags based on environment and user + const evaluatedFlags = evaluateFlags(flags, { userId, environment }); + + return ( + + {children} + + ); +} + +/** + * Hook to access feature flags + */ +export function useFeatureFlags() { + const context = useContext(FeatureFlagContext); + if (!context) { + throw new Error('useFeatureFlags must be used within FeatureFlagProvider'); + } + return context; +} + +/** + * Hook to check if a specific feature flag is enabled + */ +export function useFeatureFlag(flagName: string): boolean { + const flags = useFeatureFlags(); + const flag = flags[flagName]; + + if (!flag) { + console.warn(`Feature flag "${flagName}" not found, defaulting to false`); + return false; + } + + return flag.enabled; +} + +/** + * Hook for multivariate flags (A/B testing) + */ +export function useFeatureFlagVariant( + flagName: string, + variants: Record, + defaultVariant: T +): T { + const flags = useFeatureFlags(); + const flag = flags[flagName]; + + if (!flag || !flag.enabled) { + return defaultVariant; + } + + // Simple hash-based variant assignment + const variantKeys = Object.keys(variants); + const hash = hashString(flagName + (flag.allowedUsers?.[0] || '')); + const index = hash % variantKeys.length; + + return variants[variantKeys[index]] || defaultVariant; +} + +/** + * Evaluate flags based on context + */ +function evaluateFlags( + flags: FeatureFlags, + context: { userId?: string; environment?: string } +): FeatureFlags { + const evaluated: FeatureFlags = {}; + + for (const [key, config] of Object.entries(flags)) { + evaluated[key] = { + ...config, + enabled: evaluateFlag(config, context), + }; + } + + return evaluated; +} + +/** + * Evaluate a single flag + */ +function evaluateFlag( + config: FeatureFlagConfig, + context: { userId?: string; environment?: string } +): boolean { + // Check environment-specific override + if (config.environments && context.environment) { + const envEnabled = config.environments[context.environment]; + if (envEnabled !== undefined) { + return envEnabled; + } + } + + // Check user allowlist + if (config.allowedUsers && config.allowedUsers.length > 0 && context.userId) { + if (config.allowedUsers.includes(context.userId)) { + return config.enabled; + } + return false; + } + + // Check rollout percentage + if (config.rolloutPercentage !== undefined && context.userId) { + const hash = hashString(context.userId); + const bucket = hash % 100; + if (bucket >= config.rolloutPercentage) { + return false; + } + } + + return config.enabled; +} + +/** + * Simple string hash function + */ +function hashString(str: string): number { + let hash = 0; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; // Convert to 32-bit integer + } + return Math.abs(hash); +}