Initial commit
This commit is contained in:
12
.claude-plugin/plugin.json
Normal file
12
.claude-plugin/plugin.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
3
README.md
Normal file
3
README.md
Normal file
@@ -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.
|
||||
49
plugin.lock.json
Normal file
49
plugin.lock.json
Normal file
@@ -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": []
|
||||
}
|
||||
}
|
||||
400
skills/feature-flag-manager/SKILL.md
Normal file
400
skills/feature-flag-manager/SKILL.md
Normal file
@@ -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 (
|
||||
<LDProvider
|
||||
clientSideID={process.env.NEXT_PUBLIC_LD_CLIENT_ID}
|
||||
user={{
|
||||
key: 'user-id',
|
||||
email: 'user@example.com',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LDProvider>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### 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 (
|
||||
<FeatureFlagContext.Provider value={flags}>
|
||||
{children}
|
||||
</FeatureFlagContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
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 <div>New Timeline View</div>;
|
||||
}
|
||||
```
|
||||
|
||||
### Conditional Rendering
|
||||
|
||||
To show different variants based on flags:
|
||||
|
||||
```typescript
|
||||
export function EntityList() {
|
||||
const useNewLayout = useFeatureFlag('entity-list-redesign');
|
||||
|
||||
return useNewLayout ? <NewEntityList /> : <LegacyEntityList />;
|
||||
}
|
||||
```
|
||||
|
||||
### 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' ? (
|
||||
<EntityListView />
|
||||
) : variant === 'cards' ? (
|
||||
<EntityCardView />
|
||||
) : (
|
||||
<EntityGridView />
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
163
skills/feature-flag-manager/assets/feature-flag-provider.tsx
Normal file
163
skills/feature-flag-manager/assets/feature-flag-provider.tsx
Normal file
@@ -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<string, boolean>;
|
||||
}
|
||||
|
||||
export interface FeatureFlags {
|
||||
[key: string]: FeatureFlagConfig;
|
||||
}
|
||||
|
||||
// Context for feature flags
|
||||
const FeatureFlagContext = createContext<FeatureFlags>({});
|
||||
|
||||
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 (
|
||||
<FeatureFlagContext.Provider value={evaluatedFlags}>
|
||||
{children}
|
||||
</FeatureFlagContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<T extends string>(
|
||||
flagName: string,
|
||||
variants: Record<string, T>,
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user