add-plan-based-features
Use when adding plan-gated features, flags, limits, or validating plan access via IPlanService
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Use when adding plan-gated features, flags, limits, or validating plan access via IPlanService
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Consolidated view of the add-pro ecosystem - commands, skills, relationships and dependencies. Loaded by /add as source of truth.
Source of truth for ADD doc rules, depth floors, IDs, refs, validation gate. Load before any doc write.
Use when running agent-judged QA validation (read-PNG by default; the playwright plugin adds live driving) — the Level C judge rubric, severity taxonomy, dual-judge (@ux-agent review ∥ @qa-agent) method, report schema/template, and the config.json/screens.json formats. Consumed by /add.qa and both judges.
Use when a state-materializing command starts or is asked to upgrade — reads the setup receipt, compares the recorded contract against the shipped one, executes the declared upgrade deltas sequentially, and rewrites the receipt even on a verified-current no-op. Consumed by /add.qa-setup STEP 1.5 and STEP 11.
Internal skill for developing ADD framework artefacts (commands, skills, agents, scripts). Use when add-framework--plan analyzes viability of new framework features, when add-framework--build implements framework artefacts, or when creating/modifying commands, skills, or agents. Always use this skill before proposing or implementing changes to the framework itself.
Use when building, styling, or theming UI components, pages, layouts, dashboards, charts, tables, or forms for SaaS products.
| name | add-plan-based-features |
| description | Use when adding plan-gated features, flags, limits, or validating plan access via IPlanService |
This template uses a 3-layer feature system stored in JSONB. Features are managed via Manager app and validated via IPlanService. DO NOT create new guards, schemas, or feature systems - use the existing pattern.
| Layer | Purpose | Example |
|---|---|---|
limits | Numeric caps | workspaces:3, usersPerWorkspace:5 |
flags | Boolean toggles | reportsExport:true, apiAccess:false |
display | UI display | badge, ctaText, displayFeatures[] |
// libs/domain/src/types/PlanFeatures.ts
interface PlanFeatures {
limits: PlanLimits; // Quantitative constraints
flags: PlanFlags; // Boolean toggles
display: PlanDisplay; // UI/marketing info
}
| File | Purpose |
|---|---|
libs/domain/src/types/PlanFeatures.ts | Type definitions |
libs/backend/src/billing/IPlanService.ts | Validation interface |
apps/backend/src/api/modules/billing/plan.service.ts | Implementation |
libs/app-database/migrations/20250101002_seed_default_plans.js | Default plans |
Plans are managed via Manager app (/plans). Use the UI to:
features.flags// In your service
@Inject('IPlanService')
private readonly planService: IPlanService;
async exportReport(workspaceId: string) {
const canExport = await this.planService.canUseFeature(workspaceId, 'reportsExport');
if (!canExport) {
throw new ForbiddenException('Upgrade your plan to export reports');
}
// ... export logic
}
// libs/domain/src/types/PlanFeatures.ts
export interface PlanLimits {
workspaces: number;
usersPerWorkspace: number;
projects?: number; // NEW LIMIT
}
Update via Manager or migration:
// Update all plans with new limit
const plans = await knex('plans').select('*');
for (const plan of plans) {
const features = JSON.parse(plan.features);
features.limits.projects = plan.code === 'FREE' ? 3 :
plan.code === 'STARTER' ? 10 : 50;
await knex('plans').where('id', plan.id).update({
features: JSON.stringify(features)
});
}
// Create validation method in PlanService
async validateProjectCreation(workspaceId: string, currentCount: number): Promise<ValidationResult> {
const plan = await this.getWorkspacePlan(workspaceId);
const limit = plan.features.limits.projects ?? Infinity;
if (currentCount >= limit) {
return {
allowed: false,
reason: `Project limit of ${limit} reached. Please upgrade.`
};
}
return { allowed: true };
}
interface IPlanService {
canUseFeature(workspaceId: string, featureName: string): Promise<boolean>;
checkLimit(workspaceId: string, limitName: string): Promise<FeatureCheckResult>;
getWorkspacePlan(workspaceId: string): Promise<Plan>;
validateWorkspaceCreation(accountId: string): Promise<ValidationResult>;
validateUserAddition(workspaceId: string): Promise<ValidationResult>;
}
Use these exact codes (defined in libs/domain/src/enums/PlanCode.ts):
| Code | Tier |
|---|---|
FREE | Free tier |
STARTER | Basic paid |
PROFESSIONAL | Full features |
| Error | Fix |
|---|---|
| Creating new FeatureGuard | Use IPlanService.canUseFeature() |
| Adding JSONB column for features | Already exists - just update data |
| Creating new PlanFeatures interface | Use existing in libs/domain/src/types/ |
| Hardcoding feature checks | Use dynamic flag names with canUseFeature() |
| Using wrong plan codes | Use FREE, STARTER, PROFESSIONAL |
Manager defines features → Subscription links workspace via plan_price_id → PlanService.getWorkspacePlan() resolves active plan → canUseFeature() / checkLimit() validates access → Controller/Service allows or blocks operation.