用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/skeletorflet/opencode-kit --skill feature-flags命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
基于 SOC 职业分类
正在显示 SKILL.md
| name | feature-flags |
| description | Feature flag patterns. Gradual rollouts, A/B testing, kill switches, LaunchDarkly, environment flags. |
Ship safely. Separate deployment from release.
| Type | Purpose | Lifetime |
|---|---|---|
| Release toggle | Gradual rollout, hide incomplete | Days–weeks |
| Experiment toggle | A/B testing | Duration of test |
| Ops toggle | Kill switch, circuit breaker | Indefinite |
| Permission toggle | Feature per plan/role | Indefinite |
| Config flag | Dynamic configuration | Indefinite |
| Approach | When |
|---|---|
| Environment variables | Simple, per-deployment |
| Database flags | Dynamic, no redeploy |
| Redis flags | Fast reads, dynamic |
| Managed service (LaunchDarkly, Unleash) | Complex targeting |
// Schema
model FeatureFlag {
key String @id
enabled Boolean @default(false)
rolloutPct Int @default(0) // 0-100
allowList String[] @default([]) // User IDs
updatedAt DateTime @updatedAt
}
// Flag evaluation
async function isEnabled(flagKey: string, userId?: string): Promise<boolean> {
const cached = await redis.get(`flag:${flagKey}`);
const flag: FeatureFlag = cached ? JSON.parse(cached) : await loadFromDB(flagKey);
if (!flag || !flag.enabled) return false;
// Allow list
if (userId && flag.allowList.includes(userId)) return true;
// Percentage rollout (consistent per user)
if (flag.rolloutPct > 0 && userId) {
const hash = murmurhash(`${flagKey}:${userId}`) % 100;
return hash < flag.rolloutPct;
}
return flag.rolloutPct === 100;
}
import { init } from "launchdarkly-node-server-sdk";
const client = init(process.env.LAUNCHDARKLY_SDK_KEY!);
await client.waitForInitialization();
// Evaluate flag with user context
const enabled = await client.variation("new-checkout", {
key: user.id,
email: user.email,
country: user.country,
plan: user.plan,
}, false); // default value
if (enabled) {
return <NewCheckout />;
} else {
return <OldCheckout />;
}
function useFeatureFlag(key: string): boolean {
const { data } = useQuery({
queryKey: ["flags", key],
queryFn: () => fetchFlag(key),
staleTime: 60_000, // 1 min
});
return data?.enabled ?? false;
}
// Usage
function Checkout() {
const newCheckout = useFeatureFlag("new-checkout");
return newCheckout ? <NewCheckout /> : <OldCheckout />;
}
Rollout stages:
0% → Deployed but hidden (dark launch)
1% → Canary (catch crashes)
10% → Limited beta
25% → Wider test
50% → Half users
100% → Full release
Flag deleted → Code cleaned up
Monitor per stage:
- Error rate
- Latency
- Business metrics
- User feedback
Lifecycle rules:
├── Every flag has an owner + expiry date
├── Release flags: delete after 2 weeks of 100% rollout
├── Experiment flags: delete after analysis
├── Name: {domain}.{feature} (e.g. checkout.new-flow)
└── Document purpose in flag description
Tech debt prevention:
├── Track flags in JIRA/Linear ticket
├── PR to remove flag after release
└── Alert when flag older than 90 days
// Ops toggle for circuit breaker
const USE_STRIPE = isEnabled("payments.stripe");
const USE_PAYPAL_FALLBACK = isEnabled("payments.paypal-fallback");
async function processPayment(order: Order) {
if (USE_STRIPE) {
return await stripeCharge(order);
}
if (USE_PAYPAL_FALLBACK) {
return await paypalCharge(order);
}
throw new Error("No payment provider available");
}
| ❌ Don't | ✅ Do |
|---|---|
| Nested flag conditions | One flag, one decision |
| Flags forever | Set expiry, clean up |
| No monitoring | Track flag evaluation metrics |
| Flag in wrong layer | Evaluate server-side for security |
| Hardcoded user lists | Use targeting rules |