@girardmedia/bootspring 1.1.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 (88) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +255 -0
  3. package/agents/README.md +93 -0
  4. package/agents/api-expert/context.md +416 -0
  5. package/agents/architecture-expert/context.md +454 -0
  6. package/agents/backend-expert/context.md +483 -0
  7. package/agents/code-review-expert/context.md +365 -0
  8. package/agents/database-expert/context.md +250 -0
  9. package/agents/devops-expert/context.md +446 -0
  10. package/agents/frontend-expert/context.md +364 -0
  11. package/agents/index.js +140 -0
  12. package/agents/performance-expert/context.md +377 -0
  13. package/agents/security-expert/context.md +343 -0
  14. package/agents/testing-expert/context.md +414 -0
  15. package/agents/ui-ux-expert/context.md +448 -0
  16. package/agents/vercel-expert/context.md +426 -0
  17. package/bin/bootspring.js +310 -0
  18. package/cli/agent.js +337 -0
  19. package/cli/context.js +194 -0
  20. package/cli/dashboard.js +150 -0
  21. package/cli/generate.js +294 -0
  22. package/cli/init.js +410 -0
  23. package/cli/loop.js +421 -0
  24. package/cli/mcp.js +241 -0
  25. package/cli/memory.js +303 -0
  26. package/cli/orchestrator.js +400 -0
  27. package/cli/plugin.js +451 -0
  28. package/cli/quality.js +332 -0
  29. package/cli/skill.js +369 -0
  30. package/cli/task.js +628 -0
  31. package/cli/telemetry.js +114 -0
  32. package/cli/todo.js +614 -0
  33. package/cli/update.js +312 -0
  34. package/core/config.js +245 -0
  35. package/core/context.js +329 -0
  36. package/core/entitlements.js +209 -0
  37. package/core/index.js +43 -0
  38. package/core/policies.js +68 -0
  39. package/core/telemetry.js +247 -0
  40. package/core/utils.js +380 -0
  41. package/dashboard/server.js +818 -0
  42. package/docs/integrations/claude-code.md +42 -0
  43. package/docs/integrations/codex.md +42 -0
  44. package/docs/mcp-api-platform.md +102 -0
  45. package/generators/generate.js +598 -0
  46. package/generators/index.js +18 -0
  47. package/hooks/context-detector.js +177 -0
  48. package/hooks/index.js +35 -0
  49. package/hooks/prompt-enhancer.js +289 -0
  50. package/intelligence/git-memory.js +551 -0
  51. package/intelligence/index.js +59 -0
  52. package/intelligence/orchestrator.js +964 -0
  53. package/intelligence/prd.js +447 -0
  54. package/intelligence/recommendation-weights.json +18 -0
  55. package/intelligence/recommendations.js +234 -0
  56. package/mcp/capabilities.js +71 -0
  57. package/mcp/contracts/mcp-contract.v1.json +497 -0
  58. package/mcp/registry.js +213 -0
  59. package/mcp/response-formatter.js +462 -0
  60. package/mcp/server.js +99 -0
  61. package/mcp/tools/agent-tool.js +137 -0
  62. package/mcp/tools/capabilities-tool.js +54 -0
  63. package/mcp/tools/context-tool.js +49 -0
  64. package/mcp/tools/dashboard-tool.js +58 -0
  65. package/mcp/tools/generate-tool.js +46 -0
  66. package/mcp/tools/loop-tool.js +134 -0
  67. package/mcp/tools/memory-tool.js +180 -0
  68. package/mcp/tools/orchestrator-tool.js +232 -0
  69. package/mcp/tools/plugin-tool.js +76 -0
  70. package/mcp/tools/quality-tool.js +47 -0
  71. package/mcp/tools/skill-tool.js +233 -0
  72. package/mcp/tools/telemetry-tool.js +95 -0
  73. package/mcp/tools/todo-tool.js +133 -0
  74. package/package.json +98 -0
  75. package/plugins/index.js +141 -0
  76. package/quality/index.js +380 -0
  77. package/quality/lint-budgets.json +19 -0
  78. package/skills/index.js +787 -0
  79. package/skills/patterns/README.md +163 -0
  80. package/skills/patterns/api/route-handler.md +217 -0
  81. package/skills/patterns/api/server-action.md +249 -0
  82. package/skills/patterns/auth/clerk.md +132 -0
  83. package/skills/patterns/database/prisma.md +180 -0
  84. package/skills/patterns/payments/stripe.md +272 -0
  85. package/skills/patterns/security/validation.md +268 -0
  86. package/skills/patterns/testing/vitest.md +307 -0
  87. package/templates/bootspring.config.js +83 -0
  88. package/templates/mcp.json +9 -0
@@ -0,0 +1,132 @@
1
+ # Clerk Authentication Patterns
2
+
3
+ Battle-tested patterns for Clerk authentication in Next.js.
4
+
5
+ ## Server-Side Auth Check
6
+
7
+ ```typescript
8
+ // lib/auth.ts
9
+ import { auth } from '@clerk/nextjs/server'
10
+
11
+ export async function requireAuth() {
12
+ const { userId } = await auth()
13
+
14
+ if (!userId) {
15
+ throw new Error('UNAUTHORIZED')
16
+ }
17
+
18
+ return userId
19
+ }
20
+
21
+ // Usage in Server Action
22
+ export async function myAction() {
23
+ const userId = await requireAuth()
24
+ // Continue with authenticated user
25
+ }
26
+ ```
27
+
28
+ ## Protected Layout
29
+
30
+ ```typescript
31
+ // app/(protected)/layout.tsx
32
+ import { auth } from '@clerk/nextjs/server'
33
+ import { redirect } from 'next/navigation'
34
+
35
+ export default async function ProtectedLayout({
36
+ children
37
+ }: {
38
+ children: React.ReactNode
39
+ }) {
40
+ const { userId } = await auth()
41
+
42
+ if (!userId) {
43
+ redirect('/sign-in')
44
+ }
45
+
46
+ return <>{children}</>
47
+ }
48
+ ```
49
+
50
+ ## Get Current User with Metadata
51
+
52
+ ```typescript
53
+ // lib/auth.ts
54
+ import { currentUser } from '@clerk/nextjs/server'
55
+ import { prisma } from '@/lib/db'
56
+
57
+ export async function getCurrentUser() {
58
+ const user = await currentUser()
59
+ if (!user) return null
60
+
61
+ // Get or create database user
62
+ const dbUser = await prisma.user.upsert({
63
+ where: { clerkId: user.id },
64
+ update: {
65
+ email: user.emailAddresses[0]?.emailAddress,
66
+ name: `${user.firstName} ${user.lastName}`.trim()
67
+ },
68
+ create: {
69
+ clerkId: user.id,
70
+ email: user.emailAddresses[0]?.emailAddress ?? '',
71
+ name: `${user.firstName} ${user.lastName}`.trim()
72
+ }
73
+ })
74
+
75
+ return dbUser
76
+ }
77
+ ```
78
+
79
+ ## Client-Side Auth Hook
80
+
81
+ ```typescript
82
+ // hooks/use-user.ts
83
+ 'use client'
84
+ import { useUser } from '@clerk/nextjs'
85
+
86
+ export function useCurrentUser() {
87
+ const { user, isLoaded, isSignedIn } = useUser()
88
+
89
+ return {
90
+ user: isSignedIn ? user : null,
91
+ isLoading: !isLoaded,
92
+ isAuthenticated: isSignedIn
93
+ }
94
+ }
95
+ ```
96
+
97
+ ## Middleware Configuration
98
+
99
+ ```typescript
100
+ // middleware.ts
101
+ import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
102
+
103
+ const isProtectedRoute = createRouteMatcher([
104
+ '/dashboard(.*)',
105
+ '/settings(.*)',
106
+ '/api/user(.*)'
107
+ ])
108
+
109
+ const isPublicRoute = createRouteMatcher([
110
+ '/',
111
+ '/sign-in(.*)',
112
+ '/sign-up(.*)',
113
+ '/api/webhooks(.*)'
114
+ ])
115
+
116
+ export default clerkMiddleware(async (auth, req) => {
117
+ if (isProtectedRoute(req)) {
118
+ await auth.protect()
119
+ }
120
+ })
121
+
122
+ export const config = {
123
+ matcher: ['/((?!.*\\..*|_next).*)', '/', '/(api|trpc)(.*)']
124
+ }
125
+ ```
126
+
127
+ ## When to Use
128
+
129
+ - Server Components needing user context
130
+ - Server Actions modifying user data
131
+ - API routes requiring authentication
132
+ - Protected page layouts
@@ -0,0 +1,180 @@
1
+ # Prisma ORM Patterns
2
+
3
+ Battle-tested patterns for Prisma with Next.js.
4
+
5
+ ## Client Singleton (Prevents Connection Exhaustion)
6
+
7
+ ```typescript
8
+ // lib/db.ts
9
+ import { PrismaClient } from '@prisma/client'
10
+
11
+ const globalForPrisma = globalThis as unknown as {
12
+ prisma: PrismaClient | undefined
13
+ }
14
+
15
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient({
16
+ log: process.env.NODE_ENV === 'development'
17
+ ? ['query', 'error', 'warn']
18
+ : ['error'],
19
+ })
20
+
21
+ if (process.env.NODE_ENV !== 'production') {
22
+ globalForPrisma.prisma = prisma
23
+ }
24
+ ```
25
+
26
+ ## Common Query Patterns
27
+
28
+ ```typescript
29
+ // Create with relations
30
+ const user = await prisma.user.create({
31
+ data: {
32
+ email: 'john@example.com',
33
+ name: 'John Doe',
34
+ profile: {
35
+ create: { bio: 'Hello world' }
36
+ }
37
+ },
38
+ include: { profile: true }
39
+ })
40
+
41
+ // Read with filters and pagination
42
+ const posts = await prisma.post.findMany({
43
+ where: {
44
+ published: true,
45
+ author: { email: { contains: '@example.com' } }
46
+ },
47
+ include: { author: { select: { name: true } } },
48
+ orderBy: { createdAt: 'desc' },
49
+ skip: (page - 1) * pageSize,
50
+ take: pageSize
51
+ })
52
+
53
+ // Update
54
+ const updated = await prisma.user.update({
55
+ where: { id: userId },
56
+ data: { name: 'Updated Name' }
57
+ })
58
+
59
+ // Upsert (create or update)
60
+ const result = await prisma.user.upsert({
61
+ where: { email: 'user@example.com' },
62
+ update: { name: 'Updated' },
63
+ create: { email: 'user@example.com', name: 'New User' }
64
+ })
65
+
66
+ // Soft delete pattern
67
+ const deleted = await prisma.user.update({
68
+ where: { id: userId },
69
+ data: { deletedAt: new Date() }
70
+ })
71
+ ```
72
+
73
+ ## Transactions
74
+
75
+ ```typescript
76
+ // Sequential transaction (atomic)
77
+ const [user, post] = await prisma.$transaction([
78
+ prisma.user.create({ data: userData }),
79
+ prisma.post.create({ data: postData })
80
+ ])
81
+
82
+ // Interactive transaction (with logic)
83
+ await prisma.$transaction(async (tx) => {
84
+ const user = await tx.user.findUnique({ where: { id: userId } })
85
+ if (!user) throw new Error('User not found')
86
+ if (user.credits < 10) throw new Error('Insufficient credits')
87
+
88
+ await tx.user.update({
89
+ where: { id: userId },
90
+ data: { credits: { decrement: 10 } }
91
+ })
92
+
93
+ await tx.transaction.create({
94
+ data: { userId, amount: -10, type: 'PURCHASE' }
95
+ })
96
+ })
97
+ ```
98
+
99
+ ## Server Action Pattern
100
+
101
+ ```typescript
102
+ // actions/user.ts
103
+ 'use server'
104
+ import { prisma } from '@/lib/db'
105
+ import { revalidatePath } from 'next/cache'
106
+ import { z } from 'zod'
107
+
108
+ const UpdateProfileSchema = z.object({
109
+ name: z.string().min(1).max(100),
110
+ bio: z.string().max(500).optional()
111
+ })
112
+
113
+ export async function updateProfile(userId: string, formData: FormData) {
114
+ const validated = UpdateProfileSchema.parse({
115
+ name: formData.get('name'),
116
+ bio: formData.get('bio')
117
+ })
118
+
119
+ await prisma.user.update({
120
+ where: { id: userId },
121
+ data: validated
122
+ })
123
+
124
+ revalidatePath('/profile')
125
+ return { success: true }
126
+ }
127
+ ```
128
+
129
+ ## Migration Commands
130
+
131
+ ```bash
132
+ # Development: create and apply migration
133
+ npx prisma migrate dev --name add_posts_table
134
+
135
+ # Production: apply pending migrations
136
+ npx prisma migrate deploy
137
+
138
+ # Regenerate client after schema changes
139
+ npx prisma generate
140
+
141
+ # Visual database browser
142
+ npx prisma studio
143
+
144
+ # Reset database (WARNING: destroys data)
145
+ npx prisma migrate reset
146
+ ```
147
+
148
+ ## Schema Example
149
+
150
+ ```prisma
151
+ // prisma/schema.prisma
152
+ model User {
153
+ id String @id @default(cuid())
154
+ email String @unique
155
+ name String?
156
+ credits Int @default(0)
157
+ createdAt DateTime @default(now())
158
+ updatedAt DateTime @updatedAt
159
+ deletedAt DateTime?
160
+
161
+ posts Post[]
162
+ profile Profile?
163
+
164
+ @@index([email])
165
+ @@map("users")
166
+ }
167
+
168
+ model Post {
169
+ id String @id @default(cuid())
170
+ title String
171
+ content String?
172
+ published Boolean @default(false)
173
+ authorId String
174
+ author User @relation(fields: [authorId], references: [id])
175
+ createdAt DateTime @default(now())
176
+
177
+ @@index([authorId])
178
+ @@map("posts")
179
+ }
180
+ ```
@@ -0,0 +1,272 @@
1
+ # Stripe Payment Patterns
2
+
3
+ Battle-tested patterns for Stripe integration in Next.js.
4
+
5
+ ## Stripe Client Setup
6
+
7
+ ```typescript
8
+ // lib/stripe.ts
9
+ import Stripe from 'stripe'
10
+
11
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
12
+ apiVersion: '2024-12-18.acacia',
13
+ typescript: true
14
+ })
15
+ ```
16
+
17
+ ## One-Time Payment Checkout
18
+
19
+ ```typescript
20
+ // app/api/checkout/route.ts
21
+ import { stripe } from '@/lib/stripe'
22
+ import { auth } from '@/lib/auth'
23
+ import { NextResponse } from 'next/server'
24
+
25
+ export async function POST(req: Request) {
26
+ const { userId } = await auth()
27
+ if (!userId) {
28
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
29
+ }
30
+
31
+ const { priceId, quantity = 1 } = await req.json()
32
+
33
+ const session = await stripe.checkout.sessions.create({
34
+ mode: 'payment',
35
+ payment_method_types: ['card'],
36
+ line_items: [{ price: priceId, quantity }],
37
+ success_url: `${process.env.NEXT_PUBLIC_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
38
+ cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
39
+ metadata: { userId }
40
+ })
41
+
42
+ return NextResponse.json({ url: session.url })
43
+ }
44
+ ```
45
+
46
+ ## Subscription Checkout
47
+
48
+ ```typescript
49
+ // app/api/subscribe/route.ts
50
+ import { stripe } from '@/lib/stripe'
51
+ import { auth } from '@/lib/auth'
52
+ import { prisma } from '@/lib/db'
53
+
54
+ export async function POST(req: Request) {
55
+ const { userId } = await auth()
56
+ if (!userId) {
57
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
58
+ }
59
+
60
+ const { priceId } = await req.json()
61
+
62
+ // Get or create Stripe customer
63
+ const user = await prisma.user.findUnique({ where: { id: userId } })
64
+ let customerId = user?.stripeCustomerId
65
+
66
+ if (!customerId) {
67
+ const customer = await stripe.customers.create({
68
+ email: user?.email,
69
+ metadata: { userId }
70
+ })
71
+ customerId = customer.id
72
+
73
+ await prisma.user.update({
74
+ where: { id: userId },
75
+ data: { stripeCustomerId: customerId }
76
+ })
77
+ }
78
+
79
+ const session = await stripe.checkout.sessions.create({
80
+ mode: 'subscription',
81
+ customer: customerId,
82
+ line_items: [{ price: priceId, quantity: 1 }],
83
+ success_url: `${process.env.NEXT_PUBLIC_URL}/dashboard?success=true`,
84
+ cancel_url: `${process.env.NEXT_PUBLIC_URL}/pricing`,
85
+ subscription_data: {
86
+ metadata: { userId }
87
+ }
88
+ })
89
+
90
+ return NextResponse.json({ url: session.url })
91
+ }
92
+ ```
93
+
94
+ ## Webhook Handler
95
+
96
+ ```typescript
97
+ // app/api/webhooks/stripe/route.ts
98
+ import { stripe } from '@/lib/stripe'
99
+ import { prisma } from '@/lib/db'
100
+ import { headers } from 'next/headers'
101
+ import Stripe from 'stripe'
102
+
103
+ export async function POST(req: Request) {
104
+ const body = await req.text()
105
+ const headersList = await headers()
106
+ const signature = headersList.get('stripe-signature')!
107
+
108
+ let event: Stripe.Event
109
+
110
+ try {
111
+ event = stripe.webhooks.constructEvent(
112
+ body,
113
+ signature,
114
+ process.env.STRIPE_WEBHOOK_SECRET!
115
+ )
116
+ } catch (err) {
117
+ console.error('Webhook signature verification failed')
118
+ return new Response('Webhook Error', { status: 400 })
119
+ }
120
+
121
+ switch (event.type) {
122
+ case 'checkout.session.completed': {
123
+ const session = event.data.object as Stripe.Checkout.Session
124
+ const userId = session.metadata?.userId
125
+
126
+ if (session.mode === 'subscription') {
127
+ await prisma.user.update({
128
+ where: { id: userId },
129
+ data: {
130
+ subscriptionId: session.subscription as string,
131
+ subscriptionStatus: 'active'
132
+ }
133
+ })
134
+ } else if (session.mode === 'payment') {
135
+ // Handle one-time payment (e.g., add credits)
136
+ await prisma.user.update({
137
+ where: { id: userId },
138
+ data: { credits: { increment: 100 } }
139
+ })
140
+ }
141
+ break
142
+ }
143
+
144
+ case 'customer.subscription.updated':
145
+ case 'customer.subscription.deleted': {
146
+ const subscription = event.data.object as Stripe.Subscription
147
+ const userId = subscription.metadata.userId
148
+
149
+ await prisma.user.update({
150
+ where: { id: userId },
151
+ data: {
152
+ subscriptionStatus: subscription.status,
153
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000)
154
+ }
155
+ })
156
+ break
157
+ }
158
+
159
+ case 'invoice.payment_failed': {
160
+ const invoice = event.data.object as Stripe.Invoice
161
+ // Send email notification, update status, etc.
162
+ break
163
+ }
164
+ }
165
+
166
+ return new Response('OK', { status: 200 })
167
+ }
168
+ ```
169
+
170
+ ## Client-Side Checkout Button
171
+
172
+ ```typescript
173
+ // components/checkout-button.tsx
174
+ 'use client'
175
+
176
+ import { useState } from 'react'
177
+
178
+ export function CheckoutButton({
179
+ priceId,
180
+ mode = 'payment'
181
+ }: {
182
+ priceId: string
183
+ mode?: 'payment' | 'subscription'
184
+ }) {
185
+ const [loading, setLoading] = useState(false)
186
+
187
+ const handleCheckout = async () => {
188
+ setLoading(true)
189
+
190
+ try {
191
+ const endpoint = mode === 'subscription' ? '/api/subscribe' : '/api/checkout'
192
+ const res = await fetch(endpoint, {
193
+ method: 'POST',
194
+ headers: { 'Content-Type': 'application/json' },
195
+ body: JSON.stringify({ priceId })
196
+ })
197
+
198
+ const { url, error } = await res.json()
199
+
200
+ if (error) {
201
+ alert(error)
202
+ return
203
+ }
204
+
205
+ window.location.href = url
206
+ } catch (error) {
207
+ console.error('Checkout error:', error)
208
+ alert('Something went wrong')
209
+ } finally {
210
+ setLoading(false)
211
+ }
212
+ }
213
+
214
+ return (
215
+ <button
216
+ onClick={handleCheckout}
217
+ disabled={loading}
218
+ className="px-6 py-3 bg-blue-600 text-white rounded-lg disabled:opacity-50"
219
+ >
220
+ {loading ? 'Loading...' : mode === 'subscription' ? 'Subscribe' : 'Buy Now'}
221
+ </button>
222
+ )
223
+ }
224
+ ```
225
+
226
+ ## Customer Portal (Manage Subscription)
227
+
228
+ ```typescript
229
+ // app/api/billing/portal/route.ts
230
+ import { stripe } from '@/lib/stripe'
231
+ import { auth } from '@/lib/auth'
232
+ import { prisma } from '@/lib/db'
233
+
234
+ export async function POST() {
235
+ const { userId } = await auth()
236
+ if (!userId) {
237
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
238
+ }
239
+
240
+ const user = await prisma.user.findUnique({ where: { id: userId } })
241
+ if (!user?.stripeCustomerId) {
242
+ return NextResponse.json({ error: 'No billing account' }, { status: 400 })
243
+ }
244
+
245
+ const session = await stripe.billingPortal.sessions.create({
246
+ customer: user.stripeCustomerId,
247
+ return_url: `${process.env.NEXT_PUBLIC_URL}/settings/billing`
248
+ })
249
+
250
+ return NextResponse.json({ url: session.url })
251
+ }
252
+ ```
253
+
254
+ ## Environment Variables
255
+
256
+ ```bash
257
+ # .env.local
258
+ STRIPE_SECRET_KEY=sk_test_...
259
+ STRIPE_WEBHOOK_SECRET=whsec_...
260
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
261
+ ```
262
+
263
+ ## Testing Webhooks Locally
264
+
265
+ ```bash
266
+ # Install Stripe CLI
267
+ brew install stripe/stripe-cli/stripe
268
+
269
+ # Login and forward webhooks
270
+ stripe login
271
+ stripe listen --forward-to localhost:3000/api/webhooks/stripe
272
+ ```