@rune-kit/rune 2.10.0 → 2.12.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 (240) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +65 -6
  3. package/commands/rune.md +168 -168
  4. package/compiler/__tests__/detect-invariants.test.js +136 -0
  5. package/compiler/__tests__/doctor-mesh.test.js +229 -0
  6. package/compiler/__tests__/hook-dispatch.test.js +91 -0
  7. package/compiler/__tests__/hooks-antigravity.test.js +118 -0
  8. package/compiler/__tests__/hooks-cursor.test.js +139 -0
  9. package/compiler/__tests__/hooks-install.test.js +305 -0
  10. package/compiler/__tests__/hooks-merge.test.js +204 -0
  11. package/compiler/__tests__/hooks-tiers.test.js +519 -0
  12. package/compiler/__tests__/hooks-windsurf.test.js +115 -0
  13. package/compiler/__tests__/inject-claude-md.test.js +152 -0
  14. package/compiler/__tests__/load-invariants.test.js +408 -0
  15. package/compiler/__tests__/onboard-invariants.test.js +240 -0
  16. package/compiler/adapters/hooks/antigravity.js +140 -0
  17. package/compiler/adapters/hooks/claude.js +166 -0
  18. package/compiler/adapters/hooks/cursor.js +191 -0
  19. package/compiler/adapters/hooks/index.js +82 -0
  20. package/compiler/adapters/hooks/tier-emitter.js +182 -0
  21. package/compiler/adapters/hooks/windsurf.js +202 -0
  22. package/compiler/bin/rune.js +196 -6
  23. package/compiler/commands/hook-dispatch.js +87 -0
  24. package/compiler/commands/hooks/install.js +120 -0
  25. package/compiler/commands/hooks/merge.js +211 -0
  26. package/compiler/commands/hooks/presets.js +116 -0
  27. package/compiler/commands/hooks/status.js +112 -0
  28. package/compiler/commands/hooks/tiers.js +221 -0
  29. package/compiler/commands/hooks/uninstall.js +94 -0
  30. package/compiler/doctor.js +236 -0
  31. package/contexts/dev.md +34 -34
  32. package/contexts/research.md +43 -43
  33. package/contexts/review.md +55 -55
  34. package/extensions/ai-ml/PACK.md +88 -88
  35. package/extensions/ai-ml/skills/ai-agents.md +172 -172
  36. package/extensions/ai-ml/skills/code-sandbox.md +187 -187
  37. package/extensions/ai-ml/skills/deep-research.md +146 -146
  38. package/extensions/ai-ml/skills/embedding-search.md +66 -66
  39. package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
  40. package/extensions/ai-ml/skills/llm-architect.md +125 -125
  41. package/extensions/ai-ml/skills/llm-integration.md +64 -64
  42. package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
  43. package/extensions/ai-ml/skills/rag-patterns.md +66 -66
  44. package/extensions/ai-ml/skills/web-extraction.md +114 -114
  45. package/extensions/analytics/PACK.md +92 -92
  46. package/extensions/analytics/skills/ab-testing.md +72 -72
  47. package/extensions/analytics/skills/dashboard-patterns.md +83 -83
  48. package/extensions/analytics/skills/data-validation.md +68 -68
  49. package/extensions/analytics/skills/funnel-analysis.md +81 -81
  50. package/extensions/analytics/skills/sql-patterns.md +57 -57
  51. package/extensions/analytics/skills/statistical-analysis.md +79 -79
  52. package/extensions/analytics/skills/tracking-setup.md +71 -71
  53. package/extensions/backend/PACK.md +104 -104
  54. package/extensions/backend/skills/api-patterns.md +84 -84
  55. package/extensions/backend/skills/async-pipeline.md +193 -193
  56. package/extensions/backend/skills/auth-patterns.md +97 -97
  57. package/extensions/backend/skills/background-jobs.md +133 -133
  58. package/extensions/backend/skills/caching-patterns.md +108 -108
  59. package/extensions/backend/skills/cli-generation.md +133 -133
  60. package/extensions/backend/skills/database-patterns.md +87 -87
  61. package/extensions/backend/skills/middleware-patterns.md +104 -104
  62. package/extensions/chrome-ext/PACK.md +93 -93
  63. package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
  64. package/extensions/chrome-ext/skills/cws-publish.md +104 -104
  65. package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
  66. package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
  67. package/extensions/chrome-ext/skills/ext-storage.md +133 -133
  68. package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
  69. package/extensions/content/PACK.md +96 -96
  70. package/extensions/content/skills/blog-patterns.md +88 -88
  71. package/extensions/content/skills/cms-integration.md +131 -131
  72. package/extensions/content/skills/content-scoring.md +107 -107
  73. package/extensions/content/skills/i18n.md +83 -83
  74. package/extensions/content/skills/mdx-authoring.md +137 -137
  75. package/extensions/content/skills/reference.md +1014 -1014
  76. package/extensions/content/skills/seo-patterns.md +67 -67
  77. package/extensions/content/skills/video-repurpose.md +153 -153
  78. package/extensions/devops/PACK.md +101 -101
  79. package/extensions/devops/skills/chaos-testing.md +67 -67
  80. package/extensions/devops/skills/ci-cd.md +75 -75
  81. package/extensions/devops/skills/docker.md +58 -58
  82. package/extensions/devops/skills/edge-serverless.md +163 -163
  83. package/extensions/devops/skills/infra-as-code.md +158 -158
  84. package/extensions/devops/skills/kubernetes.md +110 -110
  85. package/extensions/devops/skills/monitoring.md +57 -57
  86. package/extensions/devops/skills/server-setup.md +64 -64
  87. package/extensions/devops/skills/ssl-domain.md +42 -42
  88. package/extensions/ecommerce/PACK.md +116 -116
  89. package/extensions/ecommerce/skills/cart-system.md +79 -79
  90. package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
  91. package/extensions/ecommerce/skills/order-management.md +126 -126
  92. package/extensions/ecommerce/skills/payment-integration.md +472 -472
  93. package/extensions/ecommerce/skills/shopify-dev.md +69 -69
  94. package/extensions/ecommerce/skills/subscription-billing.md +93 -93
  95. package/extensions/ecommerce/skills/tax-compliance.md +117 -117
  96. package/extensions/gamedev/PACK.md +142 -142
  97. package/extensions/gamedev/skills/asset-pipeline.md +74 -74
  98. package/extensions/gamedev/skills/audio-system.md +129 -129
  99. package/extensions/gamedev/skills/camera-system.md +87 -87
  100. package/extensions/gamedev/skills/ecs.md +98 -98
  101. package/extensions/gamedev/skills/game-loops.md +72 -72
  102. package/extensions/gamedev/skills/input-system.md +199 -199
  103. package/extensions/gamedev/skills/multiplayer.md +180 -180
  104. package/extensions/gamedev/skills/particles.md +105 -105
  105. package/extensions/gamedev/skills/physics-engine.md +89 -89
  106. package/extensions/gamedev/skills/scene-management.md +146 -146
  107. package/extensions/gamedev/skills/threejs-patterns.md +90 -90
  108. package/extensions/gamedev/skills/webgl.md +71 -71
  109. package/extensions/mobile/PACK.md +106 -106
  110. package/extensions/mobile/skills/app-store-connect.md +152 -152
  111. package/extensions/mobile/skills/app-store-prep.md +66 -66
  112. package/extensions/mobile/skills/deep-linking.md +109 -109
  113. package/extensions/mobile/skills/flutter.md +60 -60
  114. package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
  115. package/extensions/mobile/skills/native-bridge.md +66 -66
  116. package/extensions/mobile/skills/ota-updates.md +97 -97
  117. package/extensions/mobile/skills/push-notifications.md +111 -111
  118. package/extensions/mobile/skills/react-native.md +82 -82
  119. package/extensions/saas/PACK.md +116 -116
  120. package/extensions/saas/skills/billing-integration.md +200 -200
  121. package/extensions/saas/skills/feature-flags.md +130 -130
  122. package/extensions/saas/skills/multi-tenant.md +103 -103
  123. package/extensions/saas/skills/onboarding-flow.md +139 -139
  124. package/extensions/saas/skills/subscription-flow.md +95 -95
  125. package/extensions/saas/skills/team-management.md +144 -144
  126. package/extensions/security/PACK.md +99 -99
  127. package/extensions/security/skills/api-security.md +140 -140
  128. package/extensions/security/skills/compliance.md +68 -68
  129. package/extensions/security/skills/owasp-audit.md +64 -64
  130. package/extensions/security/skills/pentest-patterns.md +77 -77
  131. package/extensions/security/skills/secret-mgmt.md +65 -65
  132. package/extensions/security/skills/supply-chain.md +65 -65
  133. package/extensions/trading/PACK.md +80 -80
  134. package/extensions/trading/skills/chart-components.md +55 -55
  135. package/extensions/trading/skills/experiment-loop.md +125 -125
  136. package/extensions/trading/skills/fintech-patterns.md +47 -47
  137. package/extensions/trading/skills/indicator-library.md +58 -58
  138. package/extensions/trading/skills/quant-analysis.md +111 -111
  139. package/extensions/trading/skills/realtime-data.md +58 -58
  140. package/extensions/trading/skills/trade-logic.md +104 -104
  141. package/extensions/ui/PACK.md +130 -130
  142. package/extensions/ui/skills/a11y-audit.md +91 -91
  143. package/extensions/ui/skills/animation-patterns.md +127 -127
  144. package/extensions/ui/skills/component-patterns.md +100 -100
  145. package/extensions/ui/skills/design-decision.md +108 -108
  146. package/extensions/ui/skills/design-system.md +68 -68
  147. package/extensions/ui/skills/landing-patterns.md +155 -155
  148. package/extensions/ui/skills/palette-picker.md +173 -173
  149. package/extensions/ui/skills/react-health.md +90 -90
  150. package/extensions/ui/skills/type-system.md +125 -125
  151. package/extensions/ui/skills/web-vitals.md +153 -153
  152. package/extensions/zalo/PACK.md +145 -145
  153. package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
  154. package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
  155. package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
  156. package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
  157. package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
  158. package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
  159. package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
  160. package/hooks/auto-format/index.cjs +48 -48
  161. package/hooks/hooks.json +111 -111
  162. package/hooks/post-session-reflect/index.cjs +189 -189
  163. package/hooks/pre-compact/index.cjs +95 -95
  164. package/hooks/run-hook.cmd +1 -1
  165. package/hooks/secrets-scan/index.cjs +100 -100
  166. package/hooks/session-start/index.cjs +71 -71
  167. package/hooks/typecheck/index.cjs +65 -65
  168. package/package.json +63 -63
  169. package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
  170. package/references/ui-pro-max-data/charts.csv +26 -26
  171. package/references/ui-pro-max-data/colors.csv +161 -161
  172. package/references/ui-pro-max-data/styles.csv +68 -68
  173. package/references/ui-pro-max-data/typography.csv +74 -74
  174. package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
  175. package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
  176. package/skills/adversary/SKILL.md +283 -283
  177. package/skills/asset-creator/SKILL.md +157 -157
  178. package/skills/audit/SKILL.md +147 -2
  179. package/skills/autopsy/SKILL.md +335 -335
  180. package/skills/ba/SKILL.md +85 -1
  181. package/skills/brainstorm/SKILL.md +380 -342
  182. package/skills/browser-pilot/SKILL.md +169 -168
  183. package/skills/constraint-check/SKILL.md +165 -165
  184. package/skills/context-engine/SKILL.md +408 -404
  185. package/skills/cook/SKILL.md +917 -863
  186. package/skills/db/SKILL.md +273 -273
  187. package/skills/debug/SKILL.md +465 -465
  188. package/skills/dependency-doctor/SKILL.md +265 -235
  189. package/skills/deploy/SKILL.md +274 -231
  190. package/skills/design/DESIGN-REFERENCE.md +365 -365
  191. package/skills/design/SKILL.md +590 -589
  192. package/skills/doc-processor/SKILL.md +254 -254
  193. package/skills/docs/SKILL.md +374 -374
  194. package/skills/docs-seeker/SKILL.md +178 -177
  195. package/skills/fix/SKILL.md +332 -330
  196. package/skills/git/SKILL.md +339 -339
  197. package/skills/hallucination-guard/SKILL.md +220 -219
  198. package/skills/incident/SKILL.md +254 -253
  199. package/skills/integrity-check/SKILL.md +169 -169
  200. package/skills/journal/SKILL.md +241 -240
  201. package/skills/launch/SKILL.md +344 -344
  202. package/skills/logic-guardian/SKILL.md +269 -251
  203. package/skills/marketing/SKILL.md +351 -289
  204. package/skills/mcp-builder/SKILL.md +425 -425
  205. package/skills/neural-memory/SKILL.md +359 -362
  206. package/skills/onboard/SKILL.md +432 -403
  207. package/skills/onboard/references/invariants-template.md +76 -0
  208. package/skills/onboard/scripts/detect-invariants.js +439 -0
  209. package/skills/onboard/scripts/inject-claude-md.js +150 -0
  210. package/skills/onboard/scripts/onboard-invariants.js +194 -0
  211. package/skills/perf/SKILL.md +347 -346
  212. package/skills/plan/SKILL.md +435 -428
  213. package/skills/preflight/SKILL.md +415 -415
  214. package/skills/problem-solver/SKILL.md +380 -284
  215. package/skills/rescue/SKILL.md +474 -474
  216. package/skills/research/SKILL.md +4 -0
  217. package/skills/retro/SKILL.md +3 -1
  218. package/skills/review/SKILL.md +614 -588
  219. package/skills/review-intake/SKILL.md +249 -249
  220. package/skills/safeguard/SKILL.md +200 -200
  221. package/skills/sast/SKILL.md +190 -190
  222. package/skills/scaffold/SKILL.md +328 -287
  223. package/skills/scope-guard/SKILL.md +183 -180
  224. package/skills/scout/SKILL.md +269 -263
  225. package/skills/sentinel/SKILL.md +384 -381
  226. package/skills/sentinel-env/SKILL.md +254 -254
  227. package/skills/sequential-thinking/SKILL.md +234 -234
  228. package/skills/session-bridge/SKILL.md +595 -543
  229. package/skills/session-bridge/scripts/load-invariants.js +397 -0
  230. package/skills/skill-forge/SKILL.md +581 -581
  231. package/skills/skill-router/SKILL.md +3 -0
  232. package/skills/slides/SKILL.md +19 -0
  233. package/skills/surgeon/SKILL.md +215 -215
  234. package/skills/team/SKILL.md +557 -537
  235. package/skills/test/SKILL.md +620 -614
  236. package/skills/trend-scout/SKILL.md +145 -145
  237. package/skills/verification/SKILL.md +334 -326
  238. package/skills/video-creator/SKILL.md +201 -201
  239. package/skills/watchdog/SKILL.md +168 -168
  240. 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
+ ```