@rune-kit/rune 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +8 -6
  3. package/commands/rune.md +168 -168
  4. package/contexts/dev.md +34 -34
  5. package/contexts/research.md +43 -43
  6. package/contexts/review.md +55 -55
  7. package/extensions/ai-ml/PACK.md +88 -88
  8. package/extensions/ai-ml/skills/ai-agents.md +172 -172
  9. package/extensions/ai-ml/skills/code-sandbox.md +187 -187
  10. package/extensions/ai-ml/skills/deep-research.md +146 -146
  11. package/extensions/ai-ml/skills/embedding-search.md +66 -66
  12. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
  13. package/extensions/ai-ml/skills/llm-architect.md +125 -125
  14. package/extensions/ai-ml/skills/llm-integration.md +64 -64
  15. package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
  16. package/extensions/ai-ml/skills/rag-patterns.md +66 -66
  17. package/extensions/ai-ml/skills/web-extraction.md +114 -114
  18. package/extensions/analytics/PACK.md +92 -92
  19. package/extensions/analytics/skills/ab-testing.md +72 -72
  20. package/extensions/analytics/skills/dashboard-patterns.md +83 -83
  21. package/extensions/analytics/skills/data-validation.md +68 -68
  22. package/extensions/analytics/skills/funnel-analysis.md +81 -81
  23. package/extensions/analytics/skills/sql-patterns.md +57 -57
  24. package/extensions/analytics/skills/statistical-analysis.md +79 -79
  25. package/extensions/analytics/skills/tracking-setup.md +71 -71
  26. package/extensions/backend/PACK.md +104 -104
  27. package/extensions/backend/skills/api-patterns.md +84 -84
  28. package/extensions/backend/skills/async-pipeline.md +193 -193
  29. package/extensions/backend/skills/auth-patterns.md +97 -97
  30. package/extensions/backend/skills/background-jobs.md +133 -133
  31. package/extensions/backend/skills/caching-patterns.md +108 -108
  32. package/extensions/backend/skills/cli-generation.md +133 -133
  33. package/extensions/backend/skills/database-patterns.md +87 -87
  34. package/extensions/backend/skills/middleware-patterns.md +104 -104
  35. package/extensions/chrome-ext/PACK.md +93 -93
  36. package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
  37. package/extensions/chrome-ext/skills/cws-publish.md +104 -104
  38. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
  39. package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
  40. package/extensions/chrome-ext/skills/ext-storage.md +133 -133
  41. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
  42. package/extensions/content/PACK.md +96 -96
  43. package/extensions/content/skills/blog-patterns.md +88 -88
  44. package/extensions/content/skills/cms-integration.md +131 -131
  45. package/extensions/content/skills/content-scoring.md +107 -107
  46. package/extensions/content/skills/i18n.md +83 -83
  47. package/extensions/content/skills/mdx-authoring.md +137 -137
  48. package/extensions/content/skills/reference.md +1014 -1014
  49. package/extensions/content/skills/seo-patterns.md +67 -67
  50. package/extensions/content/skills/video-repurpose.md +153 -153
  51. package/extensions/devops/PACK.md +101 -101
  52. package/extensions/devops/skills/chaos-testing.md +67 -67
  53. package/extensions/devops/skills/ci-cd.md +75 -75
  54. package/extensions/devops/skills/docker.md +58 -58
  55. package/extensions/devops/skills/edge-serverless.md +163 -163
  56. package/extensions/devops/skills/infra-as-code.md +158 -158
  57. package/extensions/devops/skills/kubernetes.md +110 -110
  58. package/extensions/devops/skills/monitoring.md +57 -57
  59. package/extensions/devops/skills/server-setup.md +64 -64
  60. package/extensions/devops/skills/ssl-domain.md +42 -42
  61. package/extensions/ecommerce/PACK.md +116 -116
  62. package/extensions/ecommerce/skills/cart-system.md +79 -79
  63. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
  64. package/extensions/ecommerce/skills/order-management.md +126 -126
  65. package/extensions/ecommerce/skills/payment-integration.md +472 -472
  66. package/extensions/ecommerce/skills/shopify-dev.md +69 -69
  67. package/extensions/ecommerce/skills/subscription-billing.md +93 -93
  68. package/extensions/ecommerce/skills/tax-compliance.md +117 -117
  69. package/extensions/gamedev/PACK.md +142 -142
  70. package/extensions/gamedev/skills/asset-pipeline.md +74 -74
  71. package/extensions/gamedev/skills/audio-system.md +129 -129
  72. package/extensions/gamedev/skills/camera-system.md +87 -87
  73. package/extensions/gamedev/skills/ecs.md +98 -98
  74. package/extensions/gamedev/skills/game-loops.md +72 -72
  75. package/extensions/gamedev/skills/input-system.md +199 -199
  76. package/extensions/gamedev/skills/multiplayer.md +180 -180
  77. package/extensions/gamedev/skills/particles.md +105 -105
  78. package/extensions/gamedev/skills/physics-engine.md +89 -89
  79. package/extensions/gamedev/skills/scene-management.md +146 -146
  80. package/extensions/gamedev/skills/threejs-patterns.md +90 -90
  81. package/extensions/gamedev/skills/webgl.md +71 -71
  82. package/extensions/mobile/PACK.md +106 -106
  83. package/extensions/mobile/skills/app-store-connect.md +152 -152
  84. package/extensions/mobile/skills/app-store-prep.md +66 -66
  85. package/extensions/mobile/skills/deep-linking.md +109 -109
  86. package/extensions/mobile/skills/flutter.md +60 -60
  87. package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
  88. package/extensions/mobile/skills/native-bridge.md +66 -66
  89. package/extensions/mobile/skills/ota-updates.md +97 -97
  90. package/extensions/mobile/skills/push-notifications.md +111 -111
  91. package/extensions/mobile/skills/react-native.md +82 -82
  92. package/extensions/saas/PACK.md +116 -116
  93. package/extensions/saas/skills/billing-integration.md +200 -200
  94. package/extensions/saas/skills/feature-flags.md +130 -130
  95. package/extensions/saas/skills/multi-tenant.md +103 -103
  96. package/extensions/saas/skills/onboarding-flow.md +139 -139
  97. package/extensions/saas/skills/subscription-flow.md +95 -95
  98. package/extensions/saas/skills/team-management.md +144 -144
  99. package/extensions/security/PACK.md +99 -99
  100. package/extensions/security/skills/api-security.md +140 -140
  101. package/extensions/security/skills/compliance.md +68 -68
  102. package/extensions/security/skills/owasp-audit.md +64 -64
  103. package/extensions/security/skills/pentest-patterns.md +77 -77
  104. package/extensions/security/skills/secret-mgmt.md +65 -65
  105. package/extensions/security/skills/supply-chain.md +65 -65
  106. package/extensions/trading/PACK.md +80 -80
  107. package/extensions/trading/skills/chart-components.md +55 -55
  108. package/extensions/trading/skills/experiment-loop.md +125 -125
  109. package/extensions/trading/skills/fintech-patterns.md +47 -47
  110. package/extensions/trading/skills/indicator-library.md +58 -58
  111. package/extensions/trading/skills/quant-analysis.md +111 -111
  112. package/extensions/trading/skills/realtime-data.md +58 -58
  113. package/extensions/trading/skills/trade-logic.md +104 -104
  114. package/extensions/ui/PACK.md +130 -130
  115. package/extensions/ui/skills/a11y-audit.md +91 -91
  116. package/extensions/ui/skills/animation-patterns.md +127 -127
  117. package/extensions/ui/skills/component-patterns.md +100 -100
  118. package/extensions/ui/skills/design-decision.md +108 -108
  119. package/extensions/ui/skills/design-system.md +68 -68
  120. package/extensions/ui/skills/landing-patterns.md +155 -155
  121. package/extensions/ui/skills/palette-picker.md +173 -173
  122. package/extensions/ui/skills/react-health.md +90 -90
  123. package/extensions/ui/skills/type-system.md +125 -125
  124. package/extensions/ui/skills/web-vitals.md +153 -153
  125. package/extensions/zalo/PACK.md +145 -145
  126. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
  127. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
  128. package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
  129. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
  130. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
  131. package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
  132. package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
  133. package/hooks/auto-format/index.cjs +48 -48
  134. package/hooks/hooks.json +111 -111
  135. package/hooks/post-session-reflect/index.cjs +189 -189
  136. package/hooks/pre-compact/index.cjs +95 -95
  137. package/hooks/run-hook.cmd +1 -1
  138. package/hooks/secrets-scan/index.cjs +100 -100
  139. package/hooks/session-start/index.cjs +71 -71
  140. package/hooks/typecheck/index.cjs +65 -65
  141. package/package.json +63 -63
  142. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
  143. package/references/ui-pro-max-data/charts.csv +26 -26
  144. package/references/ui-pro-max-data/colors.csv +161 -161
  145. package/references/ui-pro-max-data/styles.csv +68 -68
  146. package/references/ui-pro-max-data/typography.csv +74 -74
  147. package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
  148. package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
  149. package/skills/adversary/SKILL.md +283 -283
  150. package/skills/asset-creator/SKILL.md +157 -157
  151. package/skills/audit/SKILL.md +147 -2
  152. package/skills/autopsy/SKILL.md +335 -335
  153. package/skills/brainstorm/SKILL.md +342 -342
  154. package/skills/browser-pilot/SKILL.md +168 -168
  155. package/skills/constraint-check/SKILL.md +165 -165
  156. package/skills/context-engine/SKILL.md +404 -404
  157. package/skills/cook/SKILL.md +917 -863
  158. package/skills/db/SKILL.md +273 -273
  159. package/skills/debug/SKILL.md +465 -465
  160. package/skills/dependency-doctor/SKILL.md +265 -235
  161. package/skills/deploy/SKILL.md +274 -231
  162. package/skills/design/DESIGN-REFERENCE.md +365 -365
  163. package/skills/design/SKILL.md +589 -589
  164. package/skills/doc-processor/SKILL.md +254 -254
  165. package/skills/docs/SKILL.md +374 -374
  166. package/skills/docs-seeker/SKILL.md +177 -177
  167. package/skills/fix/SKILL.md +330 -330
  168. package/skills/git/SKILL.md +339 -339
  169. package/skills/hallucination-guard/SKILL.md +219 -219
  170. package/skills/incident/SKILL.md +254 -253
  171. package/skills/integrity-check/SKILL.md +169 -169
  172. package/skills/journal/SKILL.md +240 -240
  173. package/skills/launch/SKILL.md +344 -344
  174. package/skills/logic-guardian/SKILL.md +251 -251
  175. package/skills/marketing/SKILL.md +290 -289
  176. package/skills/mcp-builder/SKILL.md +425 -425
  177. package/skills/neural-memory/SKILL.md +362 -362
  178. package/skills/onboard/SKILL.md +404 -403
  179. package/skills/perf/SKILL.md +346 -346
  180. package/skills/plan/SKILL.md +433 -428
  181. package/skills/preflight/SKILL.md +415 -415
  182. package/skills/problem-solver/SKILL.md +380 -284
  183. package/skills/rescue/SKILL.md +474 -474
  184. package/skills/retro/SKILL.md +3 -1
  185. package/skills/review/SKILL.md +612 -588
  186. package/skills/review-intake/SKILL.md +249 -249
  187. package/skills/safeguard/SKILL.md +200 -200
  188. package/skills/sast/SKILL.md +190 -190
  189. package/skills/scaffold/SKILL.md +328 -287
  190. package/skills/scope-guard/SKILL.md +180 -180
  191. package/skills/scout/SKILL.md +263 -263
  192. package/skills/sentinel/SKILL.md +382 -381
  193. package/skills/sentinel-env/SKILL.md +254 -254
  194. package/skills/sequential-thinking/SKILL.md +234 -234
  195. package/skills/session-bridge/SKILL.md +543 -543
  196. package/skills/skill-forge/SKILL.md +581 -581
  197. package/skills/skill-router/SKILL.md +3 -0
  198. package/skills/surgeon/SKILL.md +215 -215
  199. package/skills/team/SKILL.md +556 -537
  200. package/skills/test/SKILL.md +614 -614
  201. package/skills/trend-scout/SKILL.md +145 -145
  202. package/skills/verification/SKILL.md +326 -326
  203. package/skills/video-creator/SKILL.md +201 -201
  204. package/skills/watchdog/SKILL.md +168 -168
  205. package/skills/worktree/SKILL.md +140 -140
@@ -1,130 +1,130 @@
1
- ---
2
- name: "feature-flags"
3
- pack: "@rune/saas"
4
- description: "Feature flag management — gradual rollouts, kill switches, A/B testing, user-segment targeting, and stale flag cleanup. Supports self-hosted (Unleash, custom Redis) and managed (LaunchDarkly, Statsig, Flagsmith)."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # feature-flags
10
-
11
- Feature flag management — gradual rollouts, kill switches, A/B testing, user-segment targeting, and stale flag cleanup. Supports self-hosted (Unleash, custom Redis) and managed (LaunchDarkly, Statsig, Flagsmith).
12
-
13
- #### Flag Types
14
-
15
- | Type | Use Case | Example |
16
- |---|---|---|
17
- | Boolean | Simple on/off for a feature | `new_dashboard_ui` |
18
- | Percentage rollout | Gradual release 1% → 100% | `redesigned_editor: 25%` |
19
- | User segment | Specific users/orgs first | `beta_users`, `enterprise_plan` |
20
- | A/B test | Compare variants with metrics | `checkout_flow: variant_a / variant_b` |
21
- | Kill switch | Instant disable on failure | `payment_processor_v2` |
22
- | Environment | Dev/staging/prod separation | Auto by `NODE_ENV` |
23
-
24
- #### Rollout Pattern: Canary → Gradual → GA
25
-
26
- ```
27
- 1% (internal + beta users) → 10% → 25% → 50% → 100% → cleanup flag after 30 days at 100%
28
- ```
29
-
30
- #### Workflow
31
-
32
- **Step 1 — Identify feature boundary**
33
- Before writing code, define the flag: name (kebab-case, descriptive), default value (false = safe default), targeting rules (who sees it first), and planned cleanup date. Document in your flag provider dashboard.
34
-
35
- **Step 2 — Create flag with targeting rules**
36
- In Unleash/LaunchDarkly/Flagsmith: create flag with gradual rollout strategy. Start at 0%. Add a "beta users" segment for internal testing before any percentage rollout. Set environment-specific defaults: always-on in dev, gradual in staging, starts at 0% in prod.
37
-
38
- **Step 3 — Implement client/server evaluation**
39
- Client: evaluate flag in a React hook, never inline. Server: evaluate in middleware or at request start, attach result to request context. Never evaluate flags inside hot loops — cache the result for the request lifetime.
40
-
41
- **Step 4 — Add analytics event tracking**
42
- Every flag evaluation on a user-facing feature should fire an analytics event: `feature_flag_evaluated` with `{ flag, variant, userId, tenantId }`. This enables funnel analysis by variant and measures the rollout's impact on key metrics.
43
-
44
- **Step 5 — Schedule flag cleanup**
45
- Flags that have been at 100% for >30 days are stale. Run a weekly lint job: grep all flag keys used in code, compare against provider's flag list, flag mismatches (code uses a flag that was deleted → runtime error, or flag exists but never referenced → cleanup candidate). Remove stale flags from both code and provider in the same PR.
46
-
47
- #### Example
48
-
49
- ```typescript
50
- // Custom Redis-based flag evaluation (self-hosted, zero SaaS dependency)
51
- import { Redis } from 'ioredis';
52
- const redis = new Redis(process.env.REDIS_URL!);
53
-
54
- interface FlagConfig {
55
- enabled: boolean;
56
- percentage?: number; // 0-100 for gradual rollout
57
- allowedUsers?: string[]; // canary user IDs
58
- allowedPlans?: string[]; // plan-based targeting
59
- }
60
-
61
- const evaluateFlag = async (
62
- flagKey: string,
63
- ctx: { userId: string; tenantId: string; plan: string }
64
- ): Promise<boolean> => {
65
- const raw = await redis.get(`flag:${flagKey}`);
66
- if (!raw) return false; // default off = safe
67
- const config: FlagConfig = JSON.parse(raw);
68
- if (!config.enabled) return false;
69
- if (config.allowedUsers?.includes(ctx.userId)) return true;
70
- if (config.allowedPlans?.includes(ctx.plan)) return true;
71
- if (config.percentage !== undefined) {
72
- // Deterministic: same user always gets same bucket
73
- const hash = parseInt(ctx.userId.slice(-8), 16) % 100;
74
- return hash < config.percentage;
75
- }
76
- return config.enabled;
77
- };
78
-
79
- // React hook — evaluate once per render cycle, never in loops
80
- function useFlag(flagKey: string): boolean {
81
- const { user } = useAuth();
82
- const { data: enabled = false } = useQuery({
83
- queryKey: ['flag', flagKey, user?.id],
84
- queryFn: () => fetchFlag(flagKey),
85
- staleTime: 30_000, // cache 30s — flags don't change every millisecond
86
- });
87
- return enabled;
88
- }
89
-
90
- // Server middleware — evaluate at request boundary, attach to context
91
- const flagMiddleware = (flagKey: string) => async (req: Request, res: Response, next: NextFunction) => {
92
- req.flags = req.flags ?? {};
93
- req.flags[flagKey] = await evaluateFlag(flagKey, {
94
- userId: req.user!.id,
95
- tenantId: req.tenantId!,
96
- plan: req.user!.plan,
97
- });
98
- next();
99
- };
100
-
101
- // Usage in route — flag already evaluated, no async needed
102
- app.get('/api/checkout', flagMiddleware('new_checkout_v2'), (req, res) => {
103
- if (req.flags['new_checkout_v2']) {
104
- return checkoutV2Handler(req, res);
105
- }
106
- return checkoutV1Handler(req, res);
107
- });
108
-
109
- // Stale flag detection — run weekly in CI
110
- import { execSync } from 'child_process';
111
-
112
- const findStaleFlags = async () => {
113
- const flagsInCode = execSync('grep -r "useFlag\\|evaluateFlag" src/ --include="*.ts" -h')
114
- .toString()
115
- .match(/(?:useFlag|evaluateFlag)\(['"]([^'"]+)['"]/g)
116
- ?.map(m => m.match(/['"]([^'"]+)['"]/)?.[1])
117
- .filter(Boolean) ?? [];
118
-
119
- const flagsInProvider = await redis.keys('flag:*').then(keys => keys.map(k => k.replace('flag:', '')));
120
- const stale = flagsInProvider.filter(f => !flagsInCode.includes(f));
121
- const missing = flagsInCode.filter(f => !flagsInProvider.includes(f));
122
- return { stale, missing };
123
- };
124
- ```
125
-
126
- **Sharp edges for flags:**
127
- - Never evaluate flags on hot paths (e.g., inside `Array.map` over 1000 items) — cache the flag state at the top of the function.
128
- - In tests: mock flag evaluation at the provider level, not by conditionally skipping flag checks. Every code path should be testable with flags on and off.
129
- - Flag dependency chains (flag A enables flag B) — avoid. If you need compound logic, evaluate both flags independently and combine in application code. Provider-level dependencies are invisible in code review.
130
- - Percentage rollout is not the same as A/B test — percentage rollout has no control group. For A/B tests, always keep a 50/50 split or a defined control group.
1
+ ---
2
+ name: "feature-flags"
3
+ pack: "@rune/saas"
4
+ description: "Feature flag management — gradual rollouts, kill switches, A/B testing, user-segment targeting, and stale flag cleanup. Supports self-hosted (Unleash, custom Redis) and managed (LaunchDarkly, Statsig, Flagsmith)."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # feature-flags
10
+
11
+ Feature flag management — gradual rollouts, kill switches, A/B testing, user-segment targeting, and stale flag cleanup. Supports self-hosted (Unleash, custom Redis) and managed (LaunchDarkly, Statsig, Flagsmith).
12
+
13
+ #### Flag Types
14
+
15
+ | Type | Use Case | Example |
16
+ |---|---|---|
17
+ | Boolean | Simple on/off for a feature | `new_dashboard_ui` |
18
+ | Percentage rollout | Gradual release 1% → 100% | `redesigned_editor: 25%` |
19
+ | User segment | Specific users/orgs first | `beta_users`, `enterprise_plan` |
20
+ | A/B test | Compare variants with metrics | `checkout_flow: variant_a / variant_b` |
21
+ | Kill switch | Instant disable on failure | `payment_processor_v2` |
22
+ | Environment | Dev/staging/prod separation | Auto by `NODE_ENV` |
23
+
24
+ #### Rollout Pattern: Canary → Gradual → GA
25
+
26
+ ```
27
+ 1% (internal + beta users) → 10% → 25% → 50% → 100% → cleanup flag after 30 days at 100%
28
+ ```
29
+
30
+ #### Workflow
31
+
32
+ **Step 1 — Identify feature boundary**
33
+ Before writing code, define the flag: name (kebab-case, descriptive), default value (false = safe default), targeting rules (who sees it first), and planned cleanup date. Document in your flag provider dashboard.
34
+
35
+ **Step 2 — Create flag with targeting rules**
36
+ In Unleash/LaunchDarkly/Flagsmith: create flag with gradual rollout strategy. Start at 0%. Add a "beta users" segment for internal testing before any percentage rollout. Set environment-specific defaults: always-on in dev, gradual in staging, starts at 0% in prod.
37
+
38
+ **Step 3 — Implement client/server evaluation**
39
+ Client: evaluate flag in a React hook, never inline. Server: evaluate in middleware or at request start, attach result to request context. Never evaluate flags inside hot loops — cache the result for the request lifetime.
40
+
41
+ **Step 4 — Add analytics event tracking**
42
+ Every flag evaluation on a user-facing feature should fire an analytics event: `feature_flag_evaluated` with `{ flag, variant, userId, tenantId }`. This enables funnel analysis by variant and measures the rollout's impact on key metrics.
43
+
44
+ **Step 5 — Schedule flag cleanup**
45
+ Flags that have been at 100% for >30 days are stale. Run a weekly lint job: grep all flag keys used in code, compare against provider's flag list, flag mismatches (code uses a flag that was deleted → runtime error, or flag exists but never referenced → cleanup candidate). Remove stale flags from both code and provider in the same PR.
46
+
47
+ #### Example
48
+
49
+ ```typescript
50
+ // Custom Redis-based flag evaluation (self-hosted, zero SaaS dependency)
51
+ import { Redis } from 'ioredis';
52
+ const redis = new Redis(process.env.REDIS_URL!);
53
+
54
+ interface FlagConfig {
55
+ enabled: boolean;
56
+ percentage?: number; // 0-100 for gradual rollout
57
+ allowedUsers?: string[]; // canary user IDs
58
+ allowedPlans?: string[]; // plan-based targeting
59
+ }
60
+
61
+ const evaluateFlag = async (
62
+ flagKey: string,
63
+ ctx: { userId: string; tenantId: string; plan: string }
64
+ ): Promise<boolean> => {
65
+ const raw = await redis.get(`flag:${flagKey}`);
66
+ if (!raw) return false; // default off = safe
67
+ const config: FlagConfig = JSON.parse(raw);
68
+ if (!config.enabled) return false;
69
+ if (config.allowedUsers?.includes(ctx.userId)) return true;
70
+ if (config.allowedPlans?.includes(ctx.plan)) return true;
71
+ if (config.percentage !== undefined) {
72
+ // Deterministic: same user always gets same bucket
73
+ const hash = parseInt(ctx.userId.slice(-8), 16) % 100;
74
+ return hash < config.percentage;
75
+ }
76
+ return config.enabled;
77
+ };
78
+
79
+ // React hook — evaluate once per render cycle, never in loops
80
+ function useFlag(flagKey: string): boolean {
81
+ const { user } = useAuth();
82
+ const { data: enabled = false } = useQuery({
83
+ queryKey: ['flag', flagKey, user?.id],
84
+ queryFn: () => fetchFlag(flagKey),
85
+ staleTime: 30_000, // cache 30s — flags don't change every millisecond
86
+ });
87
+ return enabled;
88
+ }
89
+
90
+ // Server middleware — evaluate at request boundary, attach to context
91
+ const flagMiddleware = (flagKey: string) => async (req: Request, res: Response, next: NextFunction) => {
92
+ req.flags = req.flags ?? {};
93
+ req.flags[flagKey] = await evaluateFlag(flagKey, {
94
+ userId: req.user!.id,
95
+ tenantId: req.tenantId!,
96
+ plan: req.user!.plan,
97
+ });
98
+ next();
99
+ };
100
+
101
+ // Usage in route — flag already evaluated, no async needed
102
+ app.get('/api/checkout', flagMiddleware('new_checkout_v2'), (req, res) => {
103
+ if (req.flags['new_checkout_v2']) {
104
+ return checkoutV2Handler(req, res);
105
+ }
106
+ return checkoutV1Handler(req, res);
107
+ });
108
+
109
+ // Stale flag detection — run weekly in CI
110
+ import { execSync } from 'child_process';
111
+
112
+ const findStaleFlags = async () => {
113
+ const flagsInCode = execSync('grep -r "useFlag\\|evaluateFlag" src/ --include="*.ts" -h')
114
+ .toString()
115
+ .match(/(?:useFlag|evaluateFlag)\(['"]([^'"]+)['"]/g)
116
+ ?.map(m => m.match(/['"]([^'"]+)['"]/)?.[1])
117
+ .filter(Boolean) ?? [];
118
+
119
+ const flagsInProvider = await redis.keys('flag:*').then(keys => keys.map(k => k.replace('flag:', '')));
120
+ const stale = flagsInProvider.filter(f => !flagsInCode.includes(f));
121
+ const missing = flagsInCode.filter(f => !flagsInProvider.includes(f));
122
+ return { stale, missing };
123
+ };
124
+ ```
125
+
126
+ **Sharp edges for flags:**
127
+ - Never evaluate flags on hot paths (e.g., inside `Array.map` over 1000 items) — cache the flag state at the top of the function.
128
+ - In tests: mock flag evaluation at the provider level, not by conditionally skipping flag checks. Every code path should be testable with flags on and off.
129
+ - Flag dependency chains (flag A enables flag B) — avoid. If you need compound logic, evaluate both flags independently and combine in application code. Provider-level dependencies are invisible in code review.
130
+ - Percentage rollout is not the same as A/B test — percentage rollout has no control group. For A/B tests, always keep a 50/50 split or a defined control group.
@@ -1,103 +1,103 @@
1
- ---
2
- name: "multi-tenant"
3
- pack: "@rune/saas"
4
- description: "Multi-tenancy patterns — database isolation strategies, tenant context middleware, data partitioning, cross-tenant query prevention, tenant-aware background jobs, and GDPR data export."
5
- model: sonnet
6
- tools: [Read, Edit, Write, Grep, Glob, Bash]
7
- ---
8
-
9
- # multi-tenant
10
-
11
- Multi-tenancy patterns — database isolation strategies, tenant context middleware, data partitioning, cross-tenant query prevention, tenant-aware background jobs, and GDPR data export.
12
-
13
- #### Isolation Strategy Comparison
14
-
15
- | Strategy | Cost | Isolation | Migration Difficulty | When to Use |
16
- |---|---|---|---|---|
17
- | Shared DB, tenant column | Low | Weak (app-enforced) | Easy | Early-stage, <1000 tenants |
18
- | Shared DB + PostgreSQL RLS | Low | Strong (DB-enforced) | Easy | Best default for most SaaS |
19
- | Schema-per-tenant | Medium | Strong | Medium | When tenants need schema customization |
20
- | DB-per-tenant | High | Perfect | Hard | Enterprise, compliance (HIPAA, SOC2) |
21
-
22
- #### Workflow
23
-
24
- **Step 1 — Detect current isolation strategy**
25
- Use Grep to find tenant-related code: `tenantId`, `organizationId`, `workspaceId`, `x-tenant-id` header, RLS policies, schema-per-tenant patterns, database switching logic. Read the database schema and middleware to classify the isolation strategy in use.
26
-
27
- **Step 2 — Audit isolation boundaries**
28
- Check for: queries without tenant filter (data leak risk), missing tenant context in middleware, no RLS policies on shared tables, admin endpoints that bypass tenant isolation, background jobs processing cross-tenant data without scoping. Flag each with severity.
29
-
30
- **Step 3 — Emit tenant-safe patterns**
31
- Based on detected strategy, emit: tenant middleware (extract from JWT/header, set on request context), RLS policies for shared-schema approach, scoped repository pattern that injects tenant filter on every query, and tenant-aware test fixtures.
32
-
33
- **Step 4 — Tenant-aware background jobs**
34
- Every background job MUST carry `tenantId`. Use BullMQ job data to pass tenant context, then initialize a scoped repository inside the job processor. Never process tenant data in a job without an explicit `tenantId` guard.
35
-
36
- **Step 5 — Tenant data export (GDPR portability)**
37
- Implement `/api/tenants/:id/export` that collects all data rows belonging to a tenant across all tables, serializes to JSON or CSV, and streams the result as a download. Log the export event in the audit trail with timestamp and requesting user.
38
-
39
- #### Example
40
-
41
- ```typescript
42
- // Tenant middleware — extract from JWT, inject into request context
43
- const tenantMiddleware = async (req: Request, res: Response, next: NextFunction) => {
44
- const tenantId = req.user?.tenantId ?? req.headers['x-tenant-id'] as string;
45
- if (!tenantId) return res.status(403).json({ error: { code: 'TENANT_REQUIRED', message: 'Tenant context missing' } });
46
- req.tenantId = tenantId;
47
- next();
48
- };
49
-
50
- // Scoped repository — every query automatically filtered by tenant
51
- class ScopedRepository<T extends { tenantId: string }> {
52
- constructor(private model: PrismaModel<T>, private tenantId: string) {}
53
-
54
- async findMany(where: Partial<Omit<T, 'tenantId'>> = {}) {
55
- return this.model.findMany({ where: { ...where, tenantId: this.tenantId } });
56
- }
57
-
58
- async create(data: Omit<T, 'tenantId' | 'id' | 'createdAt' | 'updatedAt'>) {
59
- return this.model.create({ data: { ...data, tenantId: this.tenantId } as any });
60
- }
61
- }
62
-
63
- // PostgreSQL RLS — DB-enforced isolation, safest approach
64
- -- Enable RLS on every shared table
65
- ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
66
-
67
- -- Set tenant context before query (from app middleware)
68
- SET LOCAL app.tenant_id = '550e8400-e29b-41d4-a716-446655440000';
69
-
70
- -- Policy reads from session variable — automatic for all queries
71
- CREATE POLICY tenant_isolation ON projects
72
- USING (tenant_id = current_setting('app.tenant_id')::uuid);
73
-
74
- -- Set in Prisma $executeRaw before each query block:
75
- -- await prisma.$executeRaw`SELECT set_config('app.tenant_id', ${tenantId}, true)`;
76
-
77
- // BullMQ — tenant-aware background job
78
- const emailQueue = new Queue('emails');
79
-
80
- // Producer: always pass tenantId in job data
81
- await emailQueue.add('send-invoice', { tenantId, invoiceId, recipientEmail });
82
-
83
- // Consumer: initialize scoped context from job data
84
- const worker = new Worker('emails', async (job) => {
85
- const { tenantId, invoiceId } = job.data;
86
- const invoices = new ScopedRepository(prisma.invoice, tenantId);
87
- const invoice = await invoices.findMany({ id: invoiceId });
88
- // process...
89
- });
90
-
91
- // GDPR export — stream all tenant data
92
- app.get('/api/tenants/:id/export', requireOwner, async (req, res) => {
93
- const { id: tenantId } = req.params;
94
- const [projects, members, invoices] = await Promise.all([
95
- prisma.project.findMany({ where: { tenantId } }),
96
- prisma.member.findMany({ where: { tenantId } }),
97
- prisma.invoice.findMany({ where: { tenantId } }),
98
- ]);
99
- await prisma.auditLog.create({ data: { tenantId, action: 'DATA_EXPORT', actorId: req.user.id } });
100
- res.setHeader('Content-Disposition', `attachment; filename="export-${tenantId}.json"`);
101
- res.json({ exportedAt: new Date(), projects, members, invoices });
102
- });
103
- ```
1
+ ---
2
+ name: "multi-tenant"
3
+ pack: "@rune/saas"
4
+ description: "Multi-tenancy patterns — database isolation strategies, tenant context middleware, data partitioning, cross-tenant query prevention, tenant-aware background jobs, and GDPR data export."
5
+ model: sonnet
6
+ tools: [Read, Edit, Write, Grep, Glob, Bash]
7
+ ---
8
+
9
+ # multi-tenant
10
+
11
+ Multi-tenancy patterns — database isolation strategies, tenant context middleware, data partitioning, cross-tenant query prevention, tenant-aware background jobs, and GDPR data export.
12
+
13
+ #### Isolation Strategy Comparison
14
+
15
+ | Strategy | Cost | Isolation | Migration Difficulty | When to Use |
16
+ |---|---|---|---|---|
17
+ | Shared DB, tenant column | Low | Weak (app-enforced) | Easy | Early-stage, <1000 tenants |
18
+ | Shared DB + PostgreSQL RLS | Low | Strong (DB-enforced) | Easy | Best default for most SaaS |
19
+ | Schema-per-tenant | Medium | Strong | Medium | When tenants need schema customization |
20
+ | DB-per-tenant | High | Perfect | Hard | Enterprise, compliance (HIPAA, SOC2) |
21
+
22
+ #### Workflow
23
+
24
+ **Step 1 — Detect current isolation strategy**
25
+ Use Grep to find tenant-related code: `tenantId`, `organizationId`, `workspaceId`, `x-tenant-id` header, RLS policies, schema-per-tenant patterns, database switching logic. Read the database schema and middleware to classify the isolation strategy in use.
26
+
27
+ **Step 2 — Audit isolation boundaries**
28
+ Check for: queries without tenant filter (data leak risk), missing tenant context in middleware, no RLS policies on shared tables, admin endpoints that bypass tenant isolation, background jobs processing cross-tenant data without scoping. Flag each with severity.
29
+
30
+ **Step 3 — Emit tenant-safe patterns**
31
+ Based on detected strategy, emit: tenant middleware (extract from JWT/header, set on request context), RLS policies for shared-schema approach, scoped repository pattern that injects tenant filter on every query, and tenant-aware test fixtures.
32
+
33
+ **Step 4 — Tenant-aware background jobs**
34
+ Every background job MUST carry `tenantId`. Use BullMQ job data to pass tenant context, then initialize a scoped repository inside the job processor. Never process tenant data in a job without an explicit `tenantId` guard.
35
+
36
+ **Step 5 — Tenant data export (GDPR portability)**
37
+ Implement `/api/tenants/:id/export` that collects all data rows belonging to a tenant across all tables, serializes to JSON or CSV, and streams the result as a download. Log the export event in the audit trail with timestamp and requesting user.
38
+
39
+ #### Example
40
+
41
+ ```typescript
42
+ // Tenant middleware — extract from JWT, inject into request context
43
+ const tenantMiddleware = async (req: Request, res: Response, next: NextFunction) => {
44
+ const tenantId = req.user?.tenantId ?? req.headers['x-tenant-id'] as string;
45
+ if (!tenantId) return res.status(403).json({ error: { code: 'TENANT_REQUIRED', message: 'Tenant context missing' } });
46
+ req.tenantId = tenantId;
47
+ next();
48
+ };
49
+
50
+ // Scoped repository — every query automatically filtered by tenant
51
+ class ScopedRepository<T extends { tenantId: string }> {
52
+ constructor(private model: PrismaModel<T>, private tenantId: string) {}
53
+
54
+ async findMany(where: Partial<Omit<T, 'tenantId'>> = {}) {
55
+ return this.model.findMany({ where: { ...where, tenantId: this.tenantId } });
56
+ }
57
+
58
+ async create(data: Omit<T, 'tenantId' | 'id' | 'createdAt' | 'updatedAt'>) {
59
+ return this.model.create({ data: { ...data, tenantId: this.tenantId } as any });
60
+ }
61
+ }
62
+
63
+ // PostgreSQL RLS — DB-enforced isolation, safest approach
64
+ -- Enable RLS on every shared table
65
+ ALTER TABLE projects ENABLE ROW LEVEL SECURITY;
66
+
67
+ -- Set tenant context before query (from app middleware)
68
+ SET LOCAL app.tenant_id = '550e8400-e29b-41d4-a716-446655440000';
69
+
70
+ -- Policy reads from session variable — automatic for all queries
71
+ CREATE POLICY tenant_isolation ON projects
72
+ USING (tenant_id = current_setting('app.tenant_id')::uuid);
73
+
74
+ -- Set in Prisma $executeRaw before each query block:
75
+ -- await prisma.$executeRaw`SELECT set_config('app.tenant_id', ${tenantId}, true)`;
76
+
77
+ // BullMQ — tenant-aware background job
78
+ const emailQueue = new Queue('emails');
79
+
80
+ // Producer: always pass tenantId in job data
81
+ await emailQueue.add('send-invoice', { tenantId, invoiceId, recipientEmail });
82
+
83
+ // Consumer: initialize scoped context from job data
84
+ const worker = new Worker('emails', async (job) => {
85
+ const { tenantId, invoiceId } = job.data;
86
+ const invoices = new ScopedRepository(prisma.invoice, tenantId);
87
+ const invoice = await invoices.findMany({ id: invoiceId });
88
+ // process...
89
+ });
90
+
91
+ // GDPR export — stream all tenant data
92
+ app.get('/api/tenants/:id/export', requireOwner, async (req, res) => {
93
+ const { id: tenantId } = req.params;
94
+ const [projects, members, invoices] = await Promise.all([
95
+ prisma.project.findMany({ where: { tenantId } }),
96
+ prisma.member.findMany({ where: { tenantId } }),
97
+ prisma.invoice.findMany({ where: { tenantId } }),
98
+ ]);
99
+ await prisma.auditLog.create({ data: { tenantId, action: 'DATA_EXPORT', actorId: req.user.id } });
100
+ res.setHeader('Content-Disposition', `attachment; filename="export-${tenantId}.json"`);
101
+ res.json({ exportedAt: new Date(), projects, members, invoices });
102
+ });
103
+ ```