360 Secure SkillHub
360 安全技能中心

Phy Flag Janitor

核心功能:扫描本地代码与配置,检测永久开启/关闭、未引用/未定义的功能标志,支持多平台及自定义模式,无需外部API。典型场景:清理代码库堆积的旧标志,减少技术债务。输入为目录路径、配置文件等,输出分类清理报告,含代码位置、删除命令及重构示例,触发词如“dead feature flags”等。

phy041
phy041
热度 47.26
最近更新 2026-06-23 13:16:24
分类:开发依赖:代码执行 / 读写文件应用场景:dead-code / 自动化脚本 / DevOps

安装技能

复制下面的话术发给你的 Agent,它会通过 secure-skills 检索并安装该技能。

执行命令安装此 skill: npx secure-skills-cli add phy-flag-janitor
Metadata
name
Flag Janitor
description
Dead feature flag detector and cleanup planner. Scans source code and config files to find feature flags that are permanently-on, permanently-off, referenced but never defined, or defined but never used. Supports LaunchDarkly, Unleash, Flagsmith, environment-variable flags, and custom boolean flag patterns. Produces a prioritized cleanup report with exact code locations and safe-to-delete confirmation commands. Zero external API — works entirely from local files and git history. Triggers on "dead feature flags", "clean up flags", "feature flag audit", "remove old flags", "flag cleanup", "/flag-janitor".
license
Apache-2.0
homepage
https://canlah.ai
metadata
{"author":"Canlah AI","version":"1.0.3","tags":["feature-flags","cleanup","technical-debt","dead-code","launchdarkly","unleash","developer-tools","refactoring"]}

Flag Janitor

Feature flags accumulate silently. A codebase that started with 5 flags for a safe launch ends up with 50 a year later — half of them permanently on, never cleaned up, making every code review harder and every new developer more confused.

Paste a directory path and get back every dead flag, where it lives, and the exact refactor needed to remove it safely.

Supports any flag system. Zero external API. Works entirely from your local codebase.


Trigger Phrases

  • "dead feature flags", "clean up feature flags", "feature flag audit"
  • "remove old flags", "stale flags", "flag cleanup", "flag debt"
  • "hardcoded feature flags", "always-on flags", "obsolete flags"
  • "LaunchDarkly cleanup", "Unleash cleanup", "feature toggle debt"
  • "/flag-janitor"

How to Provide Input

# Option 1: Audit the current directory
/flag-janitor

# Option 2: Specific directory
/flag-janitor src/

# Option 3: With config file
/flag-janitor --config feature-flags.yaml

# Option 4: Specific flag system
/flag-janitor --system launchdarkly
/flag-janitor --system env-var
/flag-janitor --system custom --pattern "isFeatureEnabled\("

# Option 5: Single flag investigation
/flag-janitor --check ENABLE_NEW_CHECKOUT

Step 1: Detect Flag System

Identify which flag system(s) are in use by scanning for SDK signatures:

# LaunchDarkly
grep -r "variation\|boolVariation\|LDClient\|ldclient" . \
  --include="*.ts" --include="*.js" --include="*.py" --include="*.go" \
  --include="*.rb" --include="*.java" -l 2>/dev/null | head -5

# Unleash
grep -r "isEnabled\|getVariant\|UnleashClient\|unleash" . \
  --include="*.ts" --include="*.js" --include="*.py" -l 2>/dev/null | head -5

# Flagsmith
grep -r "hasFeature\|getValue\|Flagsmith" . \
  --include="*.ts" --include="*.js" --include="*.py" -l 2>/dev/null | head -5

# Environment-variable flags (most common pattern)
grep -rE "process\.env\.[A-Z_]+(_FLAG|_ENABLED|_FEATURE|_TOGGLE)" . \
  --include="*.ts" --include="*.js" --include="*.mjs" -h 2>/dev/null | \
  grep -oE "[A-Z_]+(_FLAG|_ENABLED|_FEATURE|_TOGGLE)" | sort -u

grep -rE "os\.environ\.get\(['\"][A-Z_]+(_FLAG|_ENABLED|_FEATURE)['\"]" . \
  --include="*.py" -h 2>/dev/null | \
  grep -oE "[A-Z_]+(_FLAG|_ENABLED|_FEATURE)" | sort -u

# Custom boolean pattern (e.g., flags.isEnabled('my-flag'))
grep -rE "(flags|featureFlags|features)\.(isEnabled|get|check)\(['\"]([a-zA-Z-_]+)['\"]" . \
  --include="*.ts" --include="*.js" -h 2>/dev/null | \
  grep -oE "['\"]([a-zA-Z-_]+)['\"]" | tr -d "'" | tr -d '"' | sort -u

Step 2: Build the Flag Registry

Collect every flag defined in config files:

LaunchDarkly

# Local flag definitions (if using local config/fallback)
find . -name "*.json" -not -path "*/node_modules/*" | \
  xargs grep -l "variations\|flagKey\|rollout" 2>/dev/null | head -10

# SDK initialization file
grep -rn "variation\|allFlagsState" . --include="*.ts" --include="*.js" | \
  grep -oE "['\"][a-z][a-z0-9-_]+['\"]" | tr -d "'" | tr -d '"' | sort -u

Environment-variable flags

# From .env files
grep -hE "^[A-Z_]+(_FLAG|_ENABLED|_FEATURE|_TOGGLE)=" .env* 2>/dev/null | \
  cut -d= -f1 | sort -u

# From .env.example
grep -hE "^[A-Z_]+(_FLAG|_ENABLED|_FEATURE|_TOGGLE)=" .env.example 2>/dev/null | \
  cut -d= -f1 | sort -u

Custom YAML/JSON config

find . -name "flags.yaml" -o -name "feature-flags.json" -o -name "toggles.yaml" \
  -not -path "*/node_modules/*" 2>/dev/null | \
  xargs grep -hE "^  [a-z][a-z0-9-_]+:" 2>/dev/null | \
  sed 's/://g' | tr -d ' ' | sort -u

Step 3: Classify Every Flag

For each flag found, determine its status:

Class A: PERMANENTLY-ON (hardcoded true)

# Flags forced to always evaluate as true
grep -rn "ENABLE_NEW_UI.*=.*true\|ENABLE_NEW_UI.*===.*true\|ENABLE_NEW_UI.*!==.*false" . \
  --include="*.ts" --include="*.js" --include="*.py" | head -20

# Flags where the fallback value IS the flag value (never varies)
# Pattern: boolVariation('flag-key', true) where false branch is dead code
grep -rn "variation.*true.*true\|boolVariation.*,\s*true" . 2>/dev/null | head -10

Class B: PERMANENTLY-OFF (hardcoded false / always disabled)

grep -rn "ENABLE_.*=.*false\|ENABLE_.*===.*false" . \
  --include="*.ts" --include="*.js" --include="*.py" 2>/dev/null | head -20

# Also: flags that always return the off-state default
grep -rn "variation.*false.*false\|boolVariation.*,\s*false" . 2>/dev/null | head -10

Class C: REFERENCED BUT UNDEFINED (will crash / silently fail)

# Build two sets: flags referenced in code vs flags defined in config
# Then: code_flags - config_flags = referenced but undefined

Cross-reference:

  • All flag names extracted from source code (Step 1)
  • All flag names extracted from config files (Step 2)
  • Flags in set A but not set B → MISSING from config → crashes or silently fails

Class D: DEFINED BUT UNREFERENCED (dead config entries)

Flags in config but never referenced in source code. Pure dead weight — no code path uses them.

Class E: TEST-ONLY (appears in tests, never in production code)

# Flag referenced only in test files
for flag in $ALL_FLAGS; do
  PROD_REFS=$(grep -rl "$flag" src/ lib/ app/ 2>/dev/null | grep -v "test\|spec" | wc -l)
  TEST_REFS=$(grep -rl "$flag" . 2>/dev/null | grep -E "test|spec" | wc -l)
  [ "$PROD_REFS" -eq 0 ] && [ "$TEST_REFS" -gt 0 ] && echo "$flag: test-only"
done

Class F: RECENTLY ADDED (< 30 days) — skip

# Don't recommend deleting flags added in last 30 days — they may still be rolling out
git log --since="30 days ago" --diff-filter=A --name-only --format="" -- "*.ts" "*.js" "*.py" | \
  grep -E "(flag|feature|toggle)" | head -10

Step 4: Staleness Check

For each non-Class-F flag, determine how long it has been unchanged:

# When was this flag's surrounding code last modified?
git log --follow -n1 --format="%ar" -- "$(grep -rl 'ENABLE_NEW_CHECKOUT' src/ | head -1)"

# How long has the flag been in its current state (on/off)?
git log --all --oneline -- .env | grep "ENABLE_NEW_CHECKOUT" | tail -1

Flag age tiers:

  • < 30 days: Active — skip
  • 30-90 days: Aging — watch list
  • 90-180 days: Stale — review recommended
  • 180 days: Dead — safe to remove with test coverage


Step 5: Output Report

## Flag Janitor Report
Project: [name] | $(date) | System: env-var + custom
Flags analyzed: 42 | Dead flags found: 18 | Estimated LOC to remove: ~340

### Summary

| Category | Count | Action |
|----------|-------|--------|
| 🔴 Permanently-ON (hardcoded true) | 7 | Remove flag + keep enabled branch |
| 🔴 Permanently-OFF (hardcoded false) | 3 | Remove flag + delete disabled branch |
| 🟠 Referenced but undefined | 2 | Define in config OR remove from code |
| 🟡 Defined but unreferenced | 4 | Remove from config |
| 🟡 Test-only | 2 | Remove from both tests and config |
| ⚪ Active (< 30 days) | 12 | Skip |
| ✅ Live (varies in production) | 12 | Keep |

---

### 🔴 PERMANENTLY-ON — Remove the flag, keep the behavior

These flags always evaluate to `true`. The feature is always enabled. Remove the flag and
inline the enabled code path.

**`ENABLE_NEW_CHECKOUT`** (185 days old)
- Defined in: `.env.production=true`, `.env.staging=true`, `.env.example=true`
- Referenced in: `src/checkout/CheckoutPage.tsx:14`, `src/cart/CartSummary.tsx:88`
- Status: Always `true` across all environments for 185 days

Safe to remove — inline the `true` branch:
```typescript
// Before:
if (process.env.ENABLE_NEW_CHECKOUT === 'true') {
  return <NewCheckout />;
} else {
  return <LegacyCheckout />;
}

// After (delete flag + legacy branch):
return <NewCheckout />;

Files to update: src/checkout/CheckoutPage.tsx, src/cart/CartSummary.tsx, .env* files


ENABLE_SEARCH_V2 (211 days old)

  • Referenced in: src/search/SearchBar.tsx:22, src/pages/Results.tsx:67
  • Status: true in all environments, never been false since day 45

🔴 PERMANENTLY-OFF — Remove flag + delete the dead branch

ENABLE_BETA_PRICING (290 days old)

  • Always false since being disabled 290 days ago
  • Code inside the true branch has never run in production
  • Referenced in: src/pricing/PricingPage.tsx:103

Safe to remove — delete the flag and the entire true branch (dead code):

// Before:
if (process.env.ENABLE_BETA_PRICING === 'true') {
  return <BetaPricing />;  // ← delete this
}
return <StandardPricing />;

// After:
return <StandardPricing />;

🟠 REFERENCED BUT UNDEFINED — Will silently fail or crash

ENABLE_SMART_RECOMMENDATIONS

  • Referenced in: src/recommendations/Engine.ts:45, src/home/HomePage.tsx:112
  • Defined in: NOWHERE — not in .env, .env.example, or config files
  • Risk: process.env.ENABLE_SMART_RECOMMENDATIONS evaluates to undefined, likely behaving as false unintentionally

Fix: Add to .env.example with explicit value:

# Recommendations engine (set to 'true' when ready to enable)
ENABLE_SMART_RECOMMENDATIONS=false

🟡 DEFINED BUT UNREFERENCED — Dead config entries

These flags exist in config but no code reads them:

Flag Defined In Last Modified Safe to Delete?
ENABLE_OLD_ANALYTICS .env.example 320 days ago ✅ Yes
FEATURE_DARK_MODE_BETA flags.yaml:14 180 days ago ✅ Yes
ENABLE_V1_API_COMPAT .env.production 8 days ago ⚠️ Wait (recent)
EXPERIMENTAL_EDITOR flags.yaml:28 95 days ago 🟡 Review first

Cleanup Commands

# 1. Remove permanently-on flags (keep enabled branch)
# Manual: edit each file listed above

# 2. Remove dead config entries
grep -v "ENABLE_OLD_ANALYTICS\|FEATURE_DARK_MODE_BETA" .env.example > .env.example.tmp
mv .env.example.tmp .env.example

# 3. Remove from flags.yaml
# Edit manually — remove lines 14 and 28

# 4. Confirm after cleanup
grep -rn "ENABLE_OLD_ANALYTICS\|ENABLE_BETA_PRICING\|ENABLE_SEARCH_V2" . \
  --include="*.ts" --include="*.js" --include="*.env*" 2>/dev/null
# Should return zero results

Estimated Impact After Cleanup

Metric Before After
Conditional branches from flags 42 24
Lines of dead code ~340 0
Flags to maintain 42 24
.env.example entries 38 32

---

## Safe Removal Checklist

Before deleting any flag:

□ Flag is permanently-on or permanently-off for > 90 days □ Git log confirms no recent changes to flag value □ Test suite covers the branch that will remain □ No pending PR branches reference this flag □ Flag is not mentioned in any open issue/ticket □ Remove from: source code, .env files, .env.example, config YAML, test files □ Run tests after removal


---

## Quick Mode

Fast count without full analysis:

Flag Janitor Quick Scan: 🔴 7 permanently-on 🔴 3 permanently-off 🟠 2 undefined 🟡 4 dead config entries

Total removable: 16 flags (~280 LOC) Run /flag-janitor for full report with file locations


---

## Why Flags Don't Get Cleaned Up

The standard lifecycle is:
1. Engineer adds flag for safe rollout
2. Flag ships to 100% of users
3. **Engineer meant to remove it "next sprint"**
4. "Next sprint" never comes
5. 6 months later, 8 engineers are reading `if (ENABLE_NEW_UI)` wondering if it's safe to delete

This skill automates step 4: it proves a flag is safe to delete (permanently-on/off + old enough) and generates the exact edit needed, reducing the removal from a 30-minute "is it safe?" investigation to a 2-minute confirmation.

---

## Author

**[Canlah AI](https://canlah.ai)** — Run performance marketing without breaking your brand.

- GitHub: [github.com/PHY041](https://github.com/PHY041)
- All Skills: [clawhub.ai/PHY041](https://clawhub.ai/PHY041)