agentleFS
PackmindHubPackmindHub/packmind/.claude/skills/feature-flags-authoring/SKILL.md

feature-flags-authoring

Skill321 starsChanged 54 days agoApache-2.0View on GitHub

Add, edit, or remove a Packmind feature flag across the shared registry, the frontend gate, and the backend helper. Use when the user wants to create a new feature flag, gate a feature behind a flag, open or restrict a flag to a domain/email, change a flag audience, retire or delete a flag, or asks how the feature-flag system is wired. This is the developer how-to counterpart to the feature-flags-audit skill.

  • Reads credentials

The file

We split this skill into the rules it gives an agent

4 rules. About 0 are common, 2 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Add the key constant, the audience entry, and extend the union.

    line 27
  2. Distinctive:

    Delete every call site:

    line 91
  3. Very specific:

    Run the feature-flags-audit skill to confirm no orphaned keys, map entries, or call sites remain.

    line 95
  4. Very specific:

    Never log an email in clear text: If you log a flag decision, mask the email (first 6 chars + *) or, better, log only the flag key and the …

    line 100

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Feature Flags Authoring

A developer how-to for the full lifecycle of a Packmind feature flag: add, edit, remove. Flags gate a feature by email domain (or exact email). The same decision function runs on frontend and backend, so behavior never diverges. Follow the steps below exactly — every flag touches three places.

For inventorying existing flags, orphan detection, and audience audits, use the feature-flags-audit skill instead. This skill changes flags; that one scans them.

Where things live

  • Shared registry — @packmind/feature-flags (pure, env:shared, browser-safe). The single source of truth:
    • registry.ts — the *_FEATURE_KEY constants, DEFAULT_FEATURE_DOMAIN_MAP (Record<string, readonly string[]> mapping each flag's string value to allowed entries), and the FeatureFlagKey union.
    • isFeatureFlagEnabled.ts — the pure evaluator: isFeatureFlagEnabled({ featureKeys, featureDomainMap, userEmail }).
  • Frontend gate — <PMFeatureFlag> from @packmind/ui (or a direct isFeatureFlagEnabled(...) call). The keys, DEFAULT_FEATURE_DOMAIN_MAP, and isFeatureFlagEnabled are re-exported from @packmind/ui for back-compat, so existing consumers can import from either place.
  • Backend gate — isFeatureEnabled(flag, { userEmail }), a plain async helper from @packmind/node-utils. It resolves the env kill-switch first, then defers to the shared email-domain rule. It is NOT a port and has no DI — import it and call it.
  • Env kill-switch — FF_<SCREAMING_SNAKE> (e.g. FF_CHANGE_PROPOSALS_IN_WEBAPP). on/all/true force the flag on; off/none/false force it off; unset or empty falls through to the email-domain map.

Two flags exist today: 'change-proposals-in-webapp' and 'orga-space-management'.

Add a flag

1. Register it in @packmind/feature-flags

Add the key constant, the audience entry, and extend the union. Do all three — a constant with no map entry is never enabled for anyone; a map entry with no union member can't be typed at call sites.

// registry.ts
export const MY_FEATURE_KEY = 'my-feature' as const;

export type FeatureFlagKey =
  | 'change-proposals-in-webapp'
  | 'orga-space-management'
  | 'my-feature';

export const DEFAULT_FEATURE_DOMAIN_MAP: Record<string, readonly string[]> = {
  // ...
  'my-feature': ['@packmind.com'], // domains keep the `@`; exact emails have no leading `@`
};

2. Gate the frontend

Wrap the UI that should appear only when the flag is on:

import { PMFeatureFlag } from '@packmind/ui';
import { MY_FEATURE_KEY, DEFAULT_FEATURE_DOMAIN_MAP } from '@packmind/feature-flags';

<PMFeatureFlag
  featureKeys={[MY_FEATURE_KEY]}
  featureDomainMap={DEFAULT_FEATURE_DOMAIN_MAP}
  userEmail={currentUser.email}
>
  {/* gated UI */}
</PMFeatureFlag>

For a branch (route, tab, boolean), call isFeatureFlagEnabled({ featureKeys: [MY_FEATURE_KEY], featureDomainMap: DEFAULT_FEATURE_DOMAIN_MAP, userEmail }) directly.

3. Gate the backend

Inside a member use case, command.user.email is available. Branch on the helper:

import { isFeatureEnabled } from '@packmind/node-utils';
import { MY_FEATURE_KEY } from '@packmind/feature-flags';

if (await isFeatureEnabled(MY_FEATURE_KEY, { userEmail: command.user.email })) {
  // new path
} else {
  // old path
}

4. (Optional) Env override

Set FF_MY_FEATURE=on to force the flag on everywhere, or FF_MY_FEATURE=off to force it off, regardless of the domain map. Leave unset to defer to the map. Document the var in the deploy config if you rely on it.

Edit a flag

Change the allowed audience in DEFAULT_FEATURE_DOMAIN_MAP — add or remove domains (@company.com) or exact emails (user@company.com). This is the only place audience lives; the frontend and backend both read it.

Remember: the FF_* env kill-switch overrides the map at runtime. If a flag looks stuck on or off in an environment, check the env var before editing the map.

Remove a flag

  1. In @packmind/feature-flags: delete the *_FEATURE_KEY constant, its DEFAULT_FEATURE_DOMAIN_MAP entry, and its FeatureFlagKey union member.
  2. Delete every call site:
    • Frontend: remove the <PMFeatureFlag> wrappers (keep the children — the UI ships unconditionally now) and any isFeatureFlagEnabled branches.
    • Backend: remove the isFeatureEnabled branches, keeping the winning branch inline.
  3. Drop any FF_<SCREAMING_SNAKE> env var from deploy config.
  4. Run the feature-flags-audit skill to confirm no orphaned keys, map entries, or call sites remain.

Conventions

  • Audience is email-domain only (Proposal A). No org, plan, team, or percentage targeting exists yet — do not describe or build it.
  • Never log an email in clear text. If you log a flag decision, mask the email (first 6 chars + *) or, better, log only the flag key and the boolean outcome.
  • Keep @packmind/feature-flags pure and browser-safe. No env:node imports (no @packmind/node-utils, no Configuration.getConfig, no process.env). It ships to the browser bundle.
  • The backend gate is a plain helper call, not an injected port. Import isFeatureEnabled and call it directly; do not add a port/adapter/registry.

See also

  • feature-flags-audit — the review/scan counterpart: inventories every flag, its audience, its usages, and flags orphans. Run it after removing a flag and whenever you need the current state of the system.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.