@girardmedia/bootspring 3.3.2 → 3.4.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 (171) hide show
  1. package/assets/agents/accessibility-auditor.md +39 -0
  2. package/assets/agents/api-designer.md +40 -0
  3. package/assets/agents/auth-implementer.md +64 -0
  4. package/assets/agents/bug-hunter.md +42 -0
  5. package/assets/agents/bundle-analyzer.md +40 -0
  6. package/assets/agents/cache-optimizer.md +55 -0
  7. package/assets/agents/changelog-writer.md +55 -0
  8. package/assets/agents/ci-cd-builder.md +40 -0
  9. package/assets/agents/code-explainer.md +39 -0
  10. package/assets/agents/code-reviewer.md +39 -0
  11. package/assets/agents/cost-optimizer.md +57 -0
  12. package/assets/agents/cron-scheduler.md +51 -0
  13. package/assets/agents/data-seeder.md +56 -0
  14. package/assets/agents/database-architect.md +40 -0
  15. package/assets/agents/dependency-updater.md +40 -0
  16. package/assets/agents/deploy-checker.md +40 -0
  17. package/assets/agents/docker-optimizer.md +40 -0
  18. package/assets/agents/documentation-writer.md +40 -0
  19. package/assets/agents/email-builder.md +55 -0
  20. package/assets/agents/env-setup.md +40 -0
  21. package/assets/agents/error-handler.md +40 -0
  22. package/assets/agents/eslint-fixer.md +46 -0
  23. package/assets/agents/feature-flagger.md +69 -0
  24. package/assets/agents/git-detective.md +39 -0
  25. package/assets/agents/graphql-builder.md +60 -0
  26. package/assets/agents/incident-responder.md +59 -0
  27. package/assets/agents/log-analyzer.md +39 -0
  28. package/assets/agents/migration-planner.md +41 -0
  29. package/assets/agents/monorepo-navigator.md +39 -0
  30. package/assets/agents/nextjs-expert.md +57 -0
  31. package/assets/agents/notification-builder.md +56 -0
  32. package/assets/agents/onboarding-guide.md +39 -0
  33. package/assets/agents/performance-profiler.md +40 -0
  34. package/assets/agents/prisma-expert.md +57 -0
  35. package/assets/agents/rate-limiter.md +58 -0
  36. package/assets/agents/react-expert.md +58 -0
  37. package/assets/agents/refactorer.md +42 -0
  38. package/assets/agents/regex-builder.md +46 -0
  39. package/assets/agents/release-manager.md +40 -0
  40. package/assets/agents/s3-manager.md +58 -0
  41. package/assets/agents/schema-validator.md +40 -0
  42. package/assets/agents/search-builder.md +62 -0
  43. package/assets/agents/security-auditor.md +39 -0
  44. package/assets/agents/sitemap-generator.md +53 -0
  45. package/assets/agents/stripe-integrator.md +59 -0
  46. package/assets/agents/tailwind-expert.md +55 -0
  47. package/assets/agents/tech-debt-tracker.md +39 -0
  48. package/assets/agents/test-writer.md +42 -0
  49. package/assets/agents/type-fixer.md +45 -0
  50. package/assets/agents/webhook-builder.md +54 -0
  51. package/assets/rules/cpp.md +53 -0
  52. package/assets/rules/css.md +52 -0
  53. package/assets/rules/go.md +50 -0
  54. package/assets/rules/html.md +52 -0
  55. package/assets/rules/java.md +51 -0
  56. package/assets/rules/kotlin.md +50 -0
  57. package/assets/rules/php.md +51 -0
  58. package/assets/rules/python.md +51 -0
  59. package/assets/rules/ruby.md +51 -0
  60. package/assets/rules/rust.md +49 -0
  61. package/assets/rules/shell.md +52 -0
  62. package/assets/rules/sql.md +49 -0
  63. package/assets/rules/swift.md +50 -0
  64. package/assets/rules/typescript.md +52 -0
  65. package/assets/rules/yaml-json.md +51 -0
  66. package/assets/skills/accessibility.md +210 -0
  67. package/assets/skills/agent-patterns.md +387 -0
  68. package/assets/skills/ai-integration.md +263 -0
  69. package/assets/skills/animation-patterns.md +224 -0
  70. package/assets/skills/api-design.md +218 -0
  71. package/assets/skills/api-gateway.md +341 -0
  72. package/assets/skills/api-versioning.md +226 -0
  73. package/assets/skills/astro-patterns.md +233 -0
  74. package/assets/skills/auth-patterns.md +248 -0
  75. package/assets/skills/aws-patterns.md +171 -0
  76. package/assets/skills/background-jobs.md +162 -0
  77. package/assets/skills/browser-extensions.md +309 -0
  78. package/assets/skills/caching-patterns.md +253 -0
  79. package/assets/skills/ci-cd.md +251 -0
  80. package/assets/skills/cli-development.md +296 -0
  81. package/assets/skills/code-review.md +185 -0
  82. package/assets/skills/cron-patterns.md +327 -0
  83. package/assets/skills/data-fetching.md +231 -0
  84. package/assets/skills/database-migrations.md +346 -0
  85. package/assets/skills/database-patterns.md +219 -0
  86. package/assets/skills/debugging.md +281 -0
  87. package/assets/skills/design-system.md +289 -0
  88. package/assets/skills/django-patterns.md +182 -0
  89. package/assets/skills/docker-patterns.md +235 -0
  90. package/assets/skills/e2e-testing.md +287 -0
  91. package/assets/skills/edge-computing.md +268 -0
  92. package/assets/skills/electron-patterns.md +266 -0
  93. package/assets/skills/email-templates.md +206 -0
  94. package/assets/skills/error-handling.md +265 -0
  95. package/assets/skills/event-driven.md +232 -0
  96. package/assets/skills/express-patterns.md +239 -0
  97. package/assets/skills/fastapi-patterns.md +198 -0
  98. package/assets/skills/feature-flags.md +212 -0
  99. package/assets/skills/figma-to-code.md +298 -0
  100. package/assets/skills/file-upload.md +228 -0
  101. package/assets/skills/forms-patterns.md +264 -0
  102. package/assets/skills/gcp-patterns.md +189 -0
  103. package/assets/skills/git-workflow.md +187 -0
  104. package/assets/skills/golang-patterns.md +185 -0
  105. package/assets/skills/graphql-patterns.md +244 -0
  106. package/assets/skills/i18n-patterns.md +172 -0
  107. package/assets/skills/image-processing.md +350 -0
  108. package/assets/skills/java-springboot.md +226 -0
  109. package/assets/skills/kotlin-patterns.md +207 -0
  110. package/assets/skills/kubernetes-patterns.md +326 -0
  111. package/assets/skills/laravel-patterns.md +261 -0
  112. package/assets/skills/llm-fine-tuning.md +335 -0
  113. package/assets/skills/load-testing.md +303 -0
  114. package/assets/skills/logging-observability.md +228 -0
  115. package/assets/skills/markdown-processing.md +318 -0
  116. package/assets/skills/mcp-server-patterns.md +292 -0
  117. package/assets/skills/microservices.md +272 -0
  118. package/assets/skills/migration-patterns.md +239 -0
  119. package/assets/skills/mongodb-patterns.md +189 -0
  120. package/assets/skills/monorepo-patterns.md +287 -0
  121. package/assets/skills/nextjs-app-router.md +237 -0
  122. package/assets/skills/notification-patterns.md +348 -0
  123. package/assets/skills/oauth-patterns.md +246 -0
  124. package/assets/skills/payment-integration.md +222 -0
  125. package/assets/skills/pdf-generation.md +307 -0
  126. package/assets/skills/performance-optimization.md +277 -0
  127. package/assets/skills/php-patterns.md +210 -0
  128. package/assets/skills/prisma-patterns.md +241 -0
  129. package/assets/skills/prompt-engineering.md +193 -0
  130. package/assets/skills/pwa-patterns.md +247 -0
  131. package/assets/skills/python-patterns.md +158 -0
  132. package/assets/skills/python-testing.md +172 -0
  133. package/assets/skills/queue-patterns.md +295 -0
  134. package/assets/skills/rag-patterns.md +159 -0
  135. package/assets/skills/rate-limiting.md +319 -0
  136. package/assets/skills/react-components.md +201 -0
  137. package/assets/skills/react-native-patterns.md +299 -0
  138. package/assets/skills/real-time-patterns.md +181 -0
  139. package/assets/skills/redis-patterns.md +188 -0
  140. package/assets/skills/refactoring.md +218 -0
  141. package/assets/skills/regex-patterns.md +191 -0
  142. package/assets/skills/remix-patterns.md +262 -0
  143. package/assets/skills/responsive-design.md +199 -0
  144. package/assets/skills/ruby-rails-patterns.md +178 -0
  145. package/assets/skills/rust-patterns.md +211 -0
  146. package/assets/skills/search-patterns.md +227 -0
  147. package/assets/skills/security-hardening.md +237 -0
  148. package/assets/skills/seo-patterns.md +179 -0
  149. package/assets/skills/serverless-patterns.md +223 -0
  150. package/assets/skills/sql-optimization.md +154 -0
  151. package/assets/skills/state-management.md +254 -0
  152. package/assets/skills/storybook-patterns.md +330 -0
  153. package/assets/skills/svelte-patterns.md +258 -0
  154. package/assets/skills/swift-patterns.md +227 -0
  155. package/assets/skills/tailwind-patterns.md +272 -0
  156. package/assets/skills/tdd-workflow.md +199 -0
  157. package/assets/skills/terraform-patterns.md +270 -0
  158. package/assets/skills/testing-react.md +240 -0
  159. package/assets/skills/testing-vitest.md +232 -0
  160. package/assets/skills/typescript-strict.md +159 -0
  161. package/assets/skills/video-processing.md +340 -0
  162. package/assets/skills/vue-patterns.md +247 -0
  163. package/assets/skills/web-workers.md +327 -0
  164. package/assets/skills/webhooks-patterns.md +283 -0
  165. package/assets/skills/websocket-patterns.md +306 -0
  166. package/dist/cli/index.js +941 -958
  167. package/dist/core/index.d.ts +341 -11
  168. package/dist/core.js +58 -95
  169. package/dist/mcp/index.d.ts +33 -1
  170. package/dist/mcp-server.js +177 -255
  171. package/package.json +4 -1
@@ -0,0 +1,227 @@
1
+ ---
2
+ name: swift-patterns
3
+ description: Swift patterns for protocols, async/await, Combine, SwiftUI state, property wrappers, and Codable.
4
+ ---
5
+
6
+ # Swift Patterns
7
+
8
+ ## When to Use
9
+
10
+ Apply these patterns when writing Swift 5.9+ code for iOS, macOS, or server-side
11
+ Swift. Use this skill for protocol-oriented design, structured concurrency with
12
+ async/await, managing SwiftUI state, creating property wrappers, and encoding/
13
+ decoding data with Codable.
14
+
15
+ ## How It Works
16
+
17
+ ### Protocol-Oriented Design
18
+
19
+ Define protocols for behavior contracts. Use protocol extensions for default
20
+ implementations. Prefer protocols over class inheritance.
21
+
22
+ ```swift
23
+ protocol Repository {
24
+ associatedtype Entity: Identifiable
25
+ func fetch(id: Entity.ID) async throws -> Entity
26
+ func save(_ entity: Entity) async throws
27
+ }
28
+
29
+ extension Repository {
30
+ func fetchOrCreate(id: Entity.ID, default: Entity) async throws -> Entity {
31
+ do {
32
+ return try await fetch(id: id)
33
+ } catch {
34
+ try await save(default)
35
+ return default
36
+ }
37
+ }
38
+ }
39
+
40
+ struct UserRepository: Repository {
41
+ func fetch(id: UUID) async throws -> User { ... }
42
+ func save(_ entity: User) async throws { ... }
43
+ }
44
+ ```
45
+
46
+ ### Async/Await and Structured Concurrency
47
+
48
+ Use `async let` for parallel work. Use `TaskGroup` for dynamic fan-out. Use
49
+ `Task` for fire-and-forget from synchronous contexts.
50
+
51
+ ```swift
52
+ func loadDashboard() async throws -> Dashboard {
53
+ async let profile = fetchProfile()
54
+ async let notifications = fetchNotifications()
55
+ async let analytics = fetchAnalytics()
56
+
57
+ return try await Dashboard(
58
+ profile: profile,
59
+ notifications: notifications,
60
+ analytics: analytics
61
+ )
62
+ }
63
+
64
+ func fetchAll(urls: [URL]) async throws -> [Data] {
65
+ try await withThrowingTaskGroup(of: (Int, Data).self) { group in
66
+ for (index, url) in urls.enumerated() {
67
+ group.addTask {
68
+ let (data, _) = try await URLSession.shared.data(from: url)
69
+ return (index, data)
70
+ }
71
+ }
72
+ var results = [Data](repeating: Data(), count: urls.count)
73
+ for try await (index, data) in group {
74
+ results[index] = data
75
+ }
76
+ return results
77
+ }
78
+ }
79
+ ```
80
+
81
+ ### Combine for Reactive Pipelines
82
+
83
+ Use Combine for event streams, timers, and publisher chains. Prefer async/await
84
+ for one-shot async work; use Combine when you need ongoing observation.
85
+
86
+ ```swift
87
+ class SearchModel: ObservableObject {
88
+ @Published var query = ""
89
+ @Published private(set) var results: [SearchResult] = []
90
+
91
+ private var cancellables = Set<AnyCancellable>()
92
+
93
+ init(service: SearchService) {
94
+ $query
95
+ .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
96
+ .removeDuplicates()
97
+ .filter { $0.count >= 2 }
98
+ .flatMap { query in
99
+ service.search(query: query)
100
+ .catch { _ in Just([]) }
101
+ }
102
+ .assign(to: &$results)
103
+ }
104
+ }
105
+ ```
106
+
107
+ ### SwiftUI State Management
108
+
109
+ Use `@State` for local view state, `@Binding` for child views, `@StateObject`
110
+ for owned observable objects, `@EnvironmentObject` for shared dependencies.
111
+
112
+ ```swift
113
+ struct ItemListView: View {
114
+ @StateObject private var viewModel = ItemListViewModel()
115
+ @State private var showingAddSheet = false
116
+
117
+ var body: some View {
118
+ List(viewModel.items) { item in
119
+ ItemRow(item: item, onToggle: { viewModel.toggle(item) })
120
+ }
121
+ .searchable(text: $viewModel.searchText)
122
+ .sheet(isPresented: $showingAddSheet) {
123
+ AddItemView(onSave: { item in
124
+ viewModel.add(item)
125
+ showingAddSheet = false
126
+ })
127
+ }
128
+ }
129
+ }
130
+
131
+ @Observable
132
+ class ItemListViewModel {
133
+ var items: [Item] = []
134
+ var searchText = ""
135
+
136
+ var filteredItems: [Item] {
137
+ guard !searchText.isEmpty else { return items }
138
+ return items.filter { $0.title.localizedCaseInsensitiveContains(searchText) }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Property Wrappers
144
+
145
+ Create property wrappers for cross-cutting concerns: clamping values, UserDefaults
146
+ persistence, or validation.
147
+
148
+ ```swift
149
+ @propertyWrapper
150
+ struct Clamped<Value: Comparable> {
151
+ private var value: Value
152
+ let range: ClosedRange<Value>
153
+
154
+ var wrappedValue: Value {
155
+ get { value }
156
+ set { value = min(max(newValue, range.lowerBound), range.upperBound) }
157
+ }
158
+
159
+ init(wrappedValue: Value, _ range: ClosedRange<Value>) {
160
+ self.range = range
161
+ self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
162
+ }
163
+ }
164
+
165
+ struct AudioSettings {
166
+ @Clamped(0...100) var volume: Int = 50
167
+ @Clamped(0.5...2.0) var playbackSpeed: Double = 1.0
168
+ }
169
+ ```
170
+
171
+ ### Codable
172
+
173
+ Use `CodingKeys` for key mapping. Implement custom `init(from:)` for complex
174
+ decoding. Use `@propertyWrapper` for date formats.
175
+
176
+ ```swift
177
+ struct APIResponse: Codable {
178
+ let id: Int
179
+ let displayName: String
180
+ let createdAt: Date
181
+ let tags: [String]
182
+
183
+ enum CodingKeys: String, CodingKey {
184
+ case id
185
+ case displayName = "display_name"
186
+ case createdAt = "created_at"
187
+ case tags
188
+ }
189
+ }
190
+
191
+ // Decoder configuration
192
+ let decoder = JSONDecoder()
193
+ decoder.dateDecodingStrategy = .iso8601
194
+ decoder.keyDecodingStrategy = .convertFromSnakeCase
195
+ ```
196
+
197
+ ## Examples
198
+
199
+ **Pattern: Result type with typed errors**
200
+ ```swift
201
+ enum NetworkError: Error, LocalizedError {
202
+ case notFound
203
+ case unauthorized
204
+ case serverError(statusCode: Int)
205
+
206
+ var errorDescription: String? {
207
+ switch self {
208
+ case .notFound: "Resource not found"
209
+ case .unauthorized: "Authentication required"
210
+ case .serverError(let code): "Server error (\(code))"
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ ## Checklist
217
+
218
+ - [ ] Protocols over class inheritance; protocol extensions for defaults
219
+ - [ ] `async let` for parallel work, `TaskGroup` for dynamic concurrency
220
+ - [ ] `@State` for local, `@StateObject` for owned objects, `@EnvironmentObject` for shared
221
+ - [ ] `@Observable` macro (iOS 17+) over `ObservableObject` where possible
222
+ - [ ] Property wrappers for cross-cutting validation and persistence
223
+ - [ ] `CodingKeys` for JSON key mapping; snake_case strategy on decoder
224
+ - [ ] Errors conform to `LocalizedError` with `errorDescription`
225
+ - [ ] `guard let` / `guard else` for early returns, not nested `if let`
226
+ - [ ] `Task` cancellation handled (check `Task.isCancelled` in loops)
227
+ - [ ] SwiftUI previews for every view with representative data
@@ -0,0 +1,272 @@
1
+ ---
2
+ name: tailwind-patterns
3
+ description: Write effective Tailwind CSS — responsive design, dark mode, animations, component extraction, and custom utilities.
4
+ ---
5
+
6
+ # Tailwind CSS Patterns
7
+
8
+ ## When to Use
9
+
10
+ Use these patterns when building UIs with Tailwind CSS. Tailwind's utility-first
11
+ approach is fast but can produce unreadable class strings and duplicated styles
12
+ without discipline. These patterns keep your Tailwind code maintainable as the
13
+ project grows.
14
+
15
+ ## How It Works
16
+
17
+ ### 1. Responsive Design
18
+
19
+ Mobile-first breakpoints. Unprefixed utilities apply to all screens, prefixed
20
+ utilities apply at that breakpoint and above.
21
+
22
+ ```tsx
23
+ <div className="
24
+ grid grid-cols-1 gap-4 p-4
25
+ md:grid-cols-2 md:gap-6 md:p-6
26
+ lg:grid-cols-3 lg:gap-8
27
+ xl:grid-cols-4
28
+ ">
29
+ {items.map(item => <Card key={item.id} item={item} />)}
30
+ </div>
31
+ ```
32
+
33
+ Common breakpoints: `sm` (640px), `md` (768px), `lg` (1024px), `xl` (1280px), `2xl` (1536px).
34
+
35
+ Container pattern for consistent page width:
36
+
37
+ ```tsx
38
+ <main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
39
+ {children}
40
+ </main>
41
+ ```
42
+
43
+ ### 2. Dark Mode
44
+
45
+ Use the `dark:` variant. Configure with `class` strategy for user-controlled
46
+ toggling.
47
+
48
+ ```javascript
49
+ // tailwind.config.js
50
+ module.exports = {
51
+ darkMode: 'class', // toggled via class on <html>
52
+ };
53
+ ```
54
+
55
+ ```tsx
56
+ <div className="bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100">
57
+ <h1 className="text-2xl font-bold text-gray-800 dark:text-gray-200">
58
+ Dashboard
59
+ </h1>
60
+ <p className="text-gray-600 dark:text-gray-400">
61
+ Welcome back
62
+ </p>
63
+ </div>
64
+ ```
65
+
66
+ Toggle implementation:
67
+
68
+ ```typescript
69
+ function toggleDarkMode() {
70
+ document.documentElement.classList.toggle('dark');
71
+ const isDark = document.documentElement.classList.contains('dark');
72
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
73
+ }
74
+
75
+ // Initialize on page load
76
+ const theme = localStorage.getItem('theme') ??
77
+ (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
78
+ document.documentElement.classList.toggle('dark', theme === 'dark');
79
+ ```
80
+
81
+ ### 3. Animations and Transitions
82
+
83
+ ```tsx
84
+ // Hover transition
85
+ <button className="
86
+ rounded-lg bg-blue-600 px-4 py-2 text-white
87
+ transition-colors duration-200
88
+ hover:bg-blue-700
89
+ active:bg-blue-800
90
+ ">
91
+ Save
92
+ </button>
93
+
94
+ // Smooth expand/collapse
95
+ <div className={`
96
+ overflow-hidden transition-all duration-300 ease-in-out
97
+ ${isOpen ? 'max-h-96 opacity-100' : 'max-h-0 opacity-0'}
98
+ `}>
99
+ {content}
100
+ </div>
101
+
102
+ // Keyframe animation (built-in)
103
+ <div className="animate-pulse rounded-lg bg-gray-200 h-48" /> {/* skeleton */}
104
+ <div className="animate-spin h-5 w-5 border-2 border-white border-t-transparent rounded-full" /> {/* spinner */}
105
+ ```
106
+
107
+ Custom animations in config:
108
+
109
+ ```javascript
110
+ // tailwind.config.js
111
+ module.exports = {
112
+ theme: {
113
+ extend: {
114
+ keyframes: {
115
+ 'slide-in': {
116
+ '0%': { transform: 'translateX(100%)' },
117
+ '100%': { transform: 'translateX(0)' },
118
+ },
119
+ 'fade-in': {
120
+ '0%': { opacity: '0' },
121
+ '100%': { opacity: '1' },
122
+ },
123
+ },
124
+ animation: {
125
+ 'slide-in': 'slide-in 0.3s ease-out',
126
+ 'fade-in': 'fade-in 0.2s ease-in',
127
+ },
128
+ },
129
+ },
130
+ };
131
+ ```
132
+
133
+ ### 4. Component Extraction with CVA
134
+
135
+ Use `class-variance-authority` to build variant-driven components.
136
+
137
+ ```typescript
138
+ import { cva, type VariantProps } from 'class-variance-authority';
139
+ import { cn } from '@/lib/utils';
140
+
141
+ const buttonVariants = cva(
142
+ 'inline-flex items-center justify-center rounded-md font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50',
143
+ {
144
+ variants: {
145
+ variant: {
146
+ primary: 'bg-blue-600 text-white hover:bg-blue-700',
147
+ secondary: 'bg-gray-100 text-gray-900 hover:bg-gray-200 dark:bg-gray-800 dark:text-gray-100',
148
+ danger: 'bg-red-600 text-white hover:bg-red-700',
149
+ ghost: 'hover:bg-gray-100 dark:hover:bg-gray-800',
150
+ },
151
+ size: {
152
+ sm: 'h-8 px-3 text-sm',
153
+ md: 'h-10 px-4 text-sm',
154
+ lg: 'h-12 px-6 text-base',
155
+ },
156
+ },
157
+ defaultVariants: {
158
+ variant: 'primary',
159
+ size: 'md',
160
+ },
161
+ },
162
+ );
163
+
164
+ interface ButtonProps
165
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
166
+ VariantProps<typeof buttonVariants> {}
167
+
168
+ export function Button({ className, variant, size, ...props }: ButtonProps) {
169
+ return (
170
+ <button className={cn(buttonVariants({ variant, size }), className)} {...props} />
171
+ );
172
+ }
173
+ ```
174
+
175
+ The `cn` utility merges Tailwind classes without conflicts:
176
+
177
+ ```typescript
178
+ import { clsx, type ClassValue } from 'clsx';
179
+ import { twMerge } from 'tailwind-merge';
180
+
181
+ export function cn(...inputs: ClassValue[]) {
182
+ return twMerge(clsx(inputs));
183
+ }
184
+ ```
185
+
186
+ ### 5. Custom Utilities
187
+
188
+ Extend Tailwind for project-specific needs.
189
+
190
+ ```javascript
191
+ // tailwind.config.js
192
+ module.exports = {
193
+ theme: {
194
+ extend: {
195
+ colors: {
196
+ brand: {
197
+ 50: '#f0f4ff',
198
+ 500: '#3b6cf5',
199
+ 600: '#2a5ce0',
200
+ 700: '#1a4cc0',
201
+ },
202
+ },
203
+ spacing: {
204
+ '18': '4.5rem',
205
+ '88': '22rem',
206
+ },
207
+ fontSize: {
208
+ 'display': ['3.5rem', { lineHeight: '1.1', letterSpacing: '-0.02em' }],
209
+ },
210
+ },
211
+ },
212
+ };
213
+ ```
214
+
215
+ ### 6. Prose for Long-Form Content
216
+
217
+ ```tsx
218
+ <article className="prose prose-lg dark:prose-invert max-w-none">
219
+ <h1>Getting Started</h1>
220
+ <p>This content is styled automatically by the typography plugin.</p>
221
+ <pre><code>npm install @bootspring/cli</code></pre>
222
+ </article>
223
+ ```
224
+
225
+ Install: `@tailwindcss/typography` plugin.
226
+
227
+ ### 7. Layout Patterns
228
+
229
+ ```tsx
230
+ // Sticky header
231
+ <header className="sticky top-0 z-50 border-b bg-white/80 backdrop-blur dark:bg-gray-900/80">
232
+ <nav className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4">
233
+ ...
234
+ </nav>
235
+ </header>
236
+
237
+ // Sidebar layout
238
+ <div className="flex h-screen">
239
+ <aside className="hidden w-64 shrink-0 border-r md:block">...</aside>
240
+ <main className="flex-1 overflow-y-auto p-6">...</main>
241
+ </div>
242
+
243
+ // Card grid with consistent height
244
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
245
+ <div className="flex flex-col rounded-lg border p-6">
246
+ <h3 className="text-lg font-semibold">Title</h3>
247
+ <p className="mt-2 flex-1 text-gray-600">Description fills space</p>
248
+ <button className="mt-4">Action</button>
249
+ </div>
250
+ </div>
251
+ ```
252
+
253
+ ## Examples
254
+
255
+ | Pattern | Classes | Purpose |
256
+ |---------|---------|---------|
257
+ | Responsive grid | `grid-cols-1 md:grid-cols-2 lg:grid-cols-3` | Adapts to screen size |
258
+ | Dark mode text | `text-gray-900 dark:text-gray-100` | Theme-aware content |
259
+ | Smooth hover | `transition-colors duration-200 hover:bg-blue-700` | Polished interactions |
260
+ | Loading skeleton | `animate-pulse bg-gray-200 rounded h-4` | Content placeholder |
261
+ | Truncated text | `truncate` or `line-clamp-2` | Overflow prevention |
262
+
263
+ ## Checklist
264
+
265
+ - [ ] All responsive styles are mobile-first (unprefixed = mobile, `md:` = tablet, `lg:` = desktop)
266
+ - [ ] Dark mode uses `dark:` variants with `darkMode: 'class'` strategy
267
+ - [ ] Repeated class patterns are extracted into components with CVA
268
+ - [ ] `cn()` utility (clsx + tailwind-merge) is used for conditional and merged classes
269
+ - [ ] Custom colors use the brand scale defined in `tailwind.config.js`
270
+ - [ ] Transitions use `duration-200` or `duration-300` for polished feel
271
+ - [ ] Long class strings are broken across multiple lines for readability
272
+ - [ ] The `@tailwindcss/typography` plugin is used for prose content
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: tdd-workflow
3
+ description: Test-driven development workflow with red-green-refactor, test doubles, outside-in TDD, and property-based testing.
4
+ ---
5
+
6
+ # Test-Driven Development
7
+
8
+ ## When to Use
9
+ Apply when building new features, fixing bugs, or refactoring existing code. TDD catches design problems early, produces naturally testable code, and creates a safety net for future changes. Skip TDD only for throwaway spikes or exploratory prototypes that will be rewritten.
10
+
11
+ ## How It Works
12
+
13
+ ### Red-Green-Refactor Cycle
14
+
15
+ The core loop. Never skip a step.
16
+
17
+ ```
18
+ RED -> Write a failing test for the next behavior you need
19
+ GREEN -> Write the minimum code to make it pass
20
+ REFACTOR -> Clean up without changing behavior (tests stay green)
21
+ ```
22
+
23
+ ```typescript
24
+ // RED -- test for a function that doesn't exist yet
25
+ import { describe, it, expect } from "vitest";
26
+ import { calculateDiscount } from "./pricing";
27
+
28
+ describe("calculateDiscount", () => {
29
+ it("applies 10% discount for orders over $100", () => {
30
+ expect(calculateDiscount(150)).toBe(15);
31
+ });
32
+
33
+ it("returns 0 for orders at or below $100", () => {
34
+ expect(calculateDiscount(100)).toBe(0);
35
+ expect(calculateDiscount(50)).toBe(0);
36
+ });
37
+ });
38
+
39
+ // GREEN -- simplest implementation
40
+ export function calculateDiscount(orderTotal: number): number {
41
+ if (orderTotal > 100) return orderTotal * 0.1;
42
+ return 0;
43
+ }
44
+
45
+ // REFACTOR -- extract magic numbers
46
+ const DISCOUNT_THRESHOLD = 100;
47
+ const DISCOUNT_RATE = 0.1;
48
+
49
+ export function calculateDiscount(orderTotal: number): number {
50
+ return orderTotal > DISCOUNT_THRESHOLD ? orderTotal * DISCOUNT_RATE : 0;
51
+ }
52
+ ```
53
+
54
+ Each cycle should take 1-5 minutes. If you spend longer, the step is too big.
55
+
56
+ ### Test Doubles
57
+
58
+ Use the right kind of double for the job:
59
+
60
+ ```typescript
61
+ import { vi, describe, it, expect } from "vitest";
62
+
63
+ // STUB -- returns canned data, no assertions on calls
64
+ const userRepo = {
65
+ findById: vi.fn().mockResolvedValue({ id: "1", name: "Alice" }),
66
+ };
67
+
68
+ // MOCK -- asserts specific interactions happened
69
+ const emailService = { send: vi.fn() };
70
+ await resetPassword(userRepo, emailService, "alice@test.com");
71
+ expect(emailService.send).toHaveBeenCalledWith(
72
+ expect.objectContaining({
73
+ to: "alice@test.com",
74
+ subject: expect.stringContaining("Reset"),
75
+ })
76
+ );
77
+
78
+ // SPY -- wraps real implementation, records calls
79
+ const logSpy = vi.spyOn(console, "warn");
80
+ processInput(invalidData);
81
+ expect(logSpy).toHaveBeenCalledOnce();
82
+ logSpy.mockRestore();
83
+
84
+ // FAKE -- working in-memory implementation
85
+ class InMemoryUserRepo implements UserRepository {
86
+ private users = new Map<string, User>();
87
+ async save(user: User) { this.users.set(user.id, user); }
88
+ async findById(id: string) { return this.users.get(id) ?? null; }
89
+ }
90
+ ```
91
+
92
+ Prefer fakes for repositories and stubs for external services. Only use mocks when verifying that a side effect actually happened.
93
+
94
+ ### Outside-In TDD (London School)
95
+
96
+ Start from the outermost boundary and work inward:
97
+
98
+ ```typescript
99
+ // Step 1: API test (outermost layer)
100
+ describe("POST /api/orders", () => {
101
+ it("creates an order and returns 201", async () => {
102
+ const res = await request(app)
103
+ .post("/api/orders")
104
+ .send({ items: [{ sku: "WIDGET-1", qty: 2 }] });
105
+ expect(res.status).toBe(201);
106
+ expect(res.body.order.total).toBe(49.98);
107
+ });
108
+ });
109
+
110
+ // Step 2: Discover the service interface from the route handler
111
+ // -> orderService.create(items) => Order
112
+
113
+ // Step 3: Discover the repository interface from the service
114
+ // -> productRepo.findBySku(sku) => Product
115
+
116
+ // Step 4: Implement the repository (innermost layer)
117
+ // -> Use a fake for unit tests, real DB for integration tests
118
+ ```
119
+
120
+ Each layer defines the interface it needs from the next layer down.
121
+
122
+ ### Property-Based Testing
123
+
124
+ Instead of testing individual examples, declare properties that must always hold:
125
+
126
+ ```typescript
127
+ import { fc } from "@fast-check/vitest";
128
+ import { describe, it, expect } from "vitest";
129
+
130
+ describe("sort", () => {
131
+ it.prop([fc.array(fc.integer())])("output length equals input length", (arr) => {
132
+ expect(sort(arr)).toHaveLength(arr.length);
133
+ });
134
+
135
+ it.prop([fc.array(fc.integer())])("output is ordered", (arr) => {
136
+ const sorted = sort(arr);
137
+ for (let i = 1; i < sorted.length; i++) {
138
+ expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i - 1]);
139
+ }
140
+ });
141
+
142
+ it.prop([fc.array(fc.integer())])("output contains same elements", (arr) => {
143
+ expect(sort(arr).sort()).toEqual([...arr].sort());
144
+ });
145
+ });
146
+ ```
147
+
148
+ Property-based testing excels at finding edge cases: empty arrays, negative numbers, duplicates, and boundary values that hand-written examples miss.
149
+
150
+ ### Mutation Testing
151
+
152
+ Tests pass, but are they actually checking the right things?
153
+
154
+ ```bash
155
+ npx stryker run
156
+ ```
157
+
158
+ ```javascript
159
+ // stryker.config.mjs
160
+ export default {
161
+ mutator: "typescript",
162
+ testRunner: "vitest",
163
+ reporters: ["html", "clear-text"],
164
+ coverageAnalysis: "perTest",
165
+ thresholds: { high: 80, low: 60, break: 50 },
166
+ };
167
+ ```
168
+
169
+ Common mutations Stryker introduces: replace `>` with `>=`, remove function calls, swap `true`/`false`, replace `+` with `-`. A mutation score below 70% means tests are passing but not actually verifying behavior.
170
+
171
+ ### When to Write Tests After
172
+
173
+ TDD is the default, but some situations call for test-after:
174
+
175
+ - **Exploratory spikes** -- learning how an API works, throw away the code
176
+ - **UI layout** -- visual correctness verified by eye or screenshot tests
177
+ - **Generated code** -- test the generator, not every generated file
178
+
179
+ Even in test-after mode, write tests before considering the work done.
180
+
181
+ ## Examples
182
+
183
+ | Situation | TDD Approach | Outcome |
184
+ |-----------|-------------|---------|
185
+ | New pricing engine | Red-green-refactor, one rule at a time | Each rule tested in isolation |
186
+ | Integrating Stripe webhooks | Outside-in from route handler | Clean service boundary |
187
+ | Bug: discount applied twice | Write failing test reproducing the bug | Regression test prevents reoccurrence |
188
+ | Refactoring a 500-line function | Characterization tests, then extract | Safe refactoring with full coverage |
189
+ | Serializer edge cases | Property-based testing | Finds empty/null/unicode edge cases |
190
+
191
+ ## Checklist
192
+ - [ ] Every new feature starts with a failing test
193
+ - [ ] Each red-green-refactor cycle takes under 5 minutes
194
+ - [ ] Test doubles chosen intentionally (fake > stub > mock)
195
+ - [ ] Tests verify behavior, not implementation details
196
+ - [ ] Outside-in tests define interfaces from the caller's perspective
197
+ - [ ] Property-based tests cover invariants for pure functions
198
+ - [ ] Mutation testing runs on critical business logic (score > 70%)
199
+ - [ ] Bug fixes include a regression test written before the fix