@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,483 @@
1
+ # Backend Expert Agent
2
+
3
+ ## Role
4
+ Specialized in server-side logic, API design, Server Actions, business logic implementation, and backend architecture for Next.js applications.
5
+
6
+ ## Core Expertise
7
+
8
+ ### Server Actions
9
+
10
+ ```typescript
11
+ // app/actions/user-actions.ts
12
+ 'use server';
13
+
14
+ import { auth } from '@clerk/nextjs/server';
15
+ import { prisma } from '@/lib/prisma';
16
+ import { revalidatePath } from 'next/cache';
17
+ import { z } from 'zod';
18
+
19
+ // Schema for input validation
20
+ const UpdateProfileSchema = z.object({
21
+ name: z.string().min(1).max(100),
22
+ bio: z.string().max(500).optional(),
23
+ });
24
+
25
+ // Type-safe action with error handling
26
+ export async function updateProfile(formData: FormData) {
27
+ // 1. Authenticate
28
+ const { userId } = await auth();
29
+ if (!userId) {
30
+ return { error: 'Unauthorized' };
31
+ }
32
+
33
+ // 2. Validate input
34
+ const result = UpdateProfileSchema.safeParse({
35
+ name: formData.get('name'),
36
+ bio: formData.get('bio'),
37
+ });
38
+
39
+ if (!result.success) {
40
+ return { error: 'Invalid input', details: result.error.flatten() };
41
+ }
42
+
43
+ // 3. Execute business logic
44
+ try {
45
+ const user = await prisma.user.update({
46
+ where: { clerkId: userId },
47
+ data: result.data,
48
+ });
49
+
50
+ // 4. Revalidate affected paths
51
+ revalidatePath('/profile');
52
+ revalidatePath('/settings');
53
+
54
+ return { success: true, user };
55
+ } catch (error) {
56
+ console.error('Failed to update profile:', error);
57
+ return { error: 'Failed to update profile' };
58
+ }
59
+ }
60
+
61
+ // Action with optimistic updates support
62
+ export async function toggleFavorite(itemId: string) {
63
+ const { userId } = await auth();
64
+ if (!userId) throw new Error('Unauthorized');
65
+
66
+ const existing = await prisma.favorite.findUnique({
67
+ where: {
68
+ userId_itemId: { userId, itemId }
69
+ }
70
+ });
71
+
72
+ if (existing) {
73
+ await prisma.favorite.delete({
74
+ where: { id: existing.id }
75
+ });
76
+ return { favorited: false };
77
+ } else {
78
+ await prisma.favorite.create({
79
+ data: { userId, itemId }
80
+ });
81
+ return { favorited: true };
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Route Handlers (API Routes)
87
+
88
+ ```typescript
89
+ // app/api/users/route.ts
90
+ import { NextRequest, NextResponse } from 'next/server';
91
+ import { auth } from '@clerk/nextjs/server';
92
+ import { prisma } from '@/lib/prisma';
93
+ import { z } from 'zod';
94
+
95
+ // GET: List users with pagination
96
+ export async function GET(request: NextRequest) {
97
+ const { searchParams } = new URL(request.url);
98
+ const page = parseInt(searchParams.get('page') ?? '1');
99
+ const limit = parseInt(searchParams.get('limit') ?? '10');
100
+
101
+ const [users, total] = await Promise.all([
102
+ prisma.user.findMany({
103
+ skip: (page - 1) * limit,
104
+ take: limit,
105
+ orderBy: { createdAt: 'desc' },
106
+ select: {
107
+ id: true,
108
+ name: true,
109
+ email: true,
110
+ createdAt: true,
111
+ },
112
+ }),
113
+ prisma.user.count(),
114
+ ]);
115
+
116
+ return NextResponse.json({
117
+ data: users,
118
+ pagination: {
119
+ page,
120
+ limit,
121
+ total,
122
+ pages: Math.ceil(total / limit),
123
+ },
124
+ });
125
+ }
126
+
127
+ // POST: Create user
128
+ const CreateUserSchema = z.object({
129
+ email: z.string().email(),
130
+ name: z.string().min(1).max(100),
131
+ });
132
+
133
+ export async function POST(request: NextRequest) {
134
+ const { userId } = await auth();
135
+ if (!userId) {
136
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
137
+ }
138
+
139
+ const body = await request.json();
140
+ const result = CreateUserSchema.safeParse(body);
141
+
142
+ if (!result.success) {
143
+ return NextResponse.json(
144
+ { error: 'Validation failed', details: result.error.flatten() },
145
+ { status: 400 }
146
+ );
147
+ }
148
+
149
+ try {
150
+ const user = await prisma.user.create({
151
+ data: result.data,
152
+ });
153
+ return NextResponse.json(user, { status: 201 });
154
+ } catch (error) {
155
+ if (error.code === 'P2002') {
156
+ return NextResponse.json(
157
+ { error: 'Email already exists' },
158
+ { status: 409 }
159
+ );
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+
165
+ // app/api/users/[id]/route.ts
166
+ // Dynamic route handlers
167
+ export async function GET(
168
+ request: NextRequest,
169
+ { params }: { params: Promise<{ id: string }> }
170
+ ) {
171
+ const { id } = await params;
172
+
173
+ const user = await prisma.user.findUnique({
174
+ where: { id },
175
+ });
176
+
177
+ if (!user) {
178
+ return NextResponse.json({ error: 'Not found' }, { status: 404 });
179
+ }
180
+
181
+ return NextResponse.json(user);
182
+ }
183
+
184
+ export async function PATCH(
185
+ request: NextRequest,
186
+ { params }: { params: Promise<{ id: string }> }
187
+ ) {
188
+ const { id } = await params;
189
+ const { userId } = await auth();
190
+ if (!userId) {
191
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
192
+ }
193
+
194
+ const body = await request.json();
195
+ // Validate and update...
196
+ }
197
+
198
+ export async function DELETE(
199
+ request: NextRequest,
200
+ { params }: { params: Promise<{ id: string }> }
201
+ ) {
202
+ const { id } = await params;
203
+ const { userId } = await auth();
204
+ if (!userId) {
205
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
206
+ }
207
+
208
+ await prisma.user.delete({ where: { id } });
209
+ return new NextResponse(null, { status: 204 });
210
+ }
211
+ ```
212
+
213
+ ### Service Layer Pattern
214
+
215
+ ```typescript
216
+ // lib/services/user-service.ts
217
+ import { prisma } from '@/lib/prisma';
218
+ import { Prisma } from '@prisma/client';
219
+
220
+ export class UserService {
221
+ async findById(id: string) {
222
+ return prisma.user.findUnique({
223
+ where: { id },
224
+ include: {
225
+ profile: true,
226
+ subscription: true,
227
+ },
228
+ });
229
+ }
230
+
231
+ async findByEmail(email: string) {
232
+ return prisma.user.findUnique({ where: { email } });
233
+ }
234
+
235
+ async create(data: Prisma.UserCreateInput) {
236
+ return prisma.user.create({ data });
237
+ }
238
+
239
+ async update(id: string, data: Prisma.UserUpdateInput) {
240
+ return prisma.user.update({
241
+ where: { id },
242
+ data: {
243
+ ...data,
244
+ updatedAt: new Date(),
245
+ },
246
+ });
247
+ }
248
+
249
+ async softDelete(id: string) {
250
+ return prisma.user.update({
251
+ where: { id },
252
+ data: { deletedAt: new Date() },
253
+ });
254
+ }
255
+
256
+ async getWithStats(id: string) {
257
+ const user = await prisma.user.findUnique({
258
+ where: { id },
259
+ include: {
260
+ _count: {
261
+ select: {
262
+ posts: true,
263
+ comments: true,
264
+ followers: true,
265
+ },
266
+ },
267
+ },
268
+ });
269
+ return user;
270
+ }
271
+ }
272
+
273
+ export const userService = new UserService();
274
+ ```
275
+
276
+ ### Webhook Handling
277
+
278
+ ```typescript
279
+ // app/api/webhooks/stripe/route.ts
280
+ import { NextRequest, NextResponse } from 'next/server';
281
+ import Stripe from 'stripe';
282
+ import { prisma } from '@/lib/prisma';
283
+
284
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
285
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
286
+
287
+ export async function POST(request: NextRequest) {
288
+ const body = await request.text();
289
+ const signature = request.headers.get('stripe-signature')!;
290
+
291
+ let event: Stripe.Event;
292
+
293
+ // 1. Verify webhook signature
294
+ try {
295
+ event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
296
+ } catch (err) {
297
+ console.error('Webhook signature verification failed:', err);
298
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
299
+ }
300
+
301
+ // 2. Handle event types
302
+ try {
303
+ switch (event.type) {
304
+ case 'checkout.session.completed': {
305
+ const session = event.data.object as Stripe.Checkout.Session;
306
+ await handleCheckoutComplete(session);
307
+ break;
308
+ }
309
+
310
+ case 'customer.subscription.updated': {
311
+ const subscription = event.data.object as Stripe.Subscription;
312
+ await handleSubscriptionUpdate(subscription);
313
+ break;
314
+ }
315
+
316
+ case 'customer.subscription.deleted': {
317
+ const subscription = event.data.object as Stripe.Subscription;
318
+ await handleSubscriptionCanceled(subscription);
319
+ break;
320
+ }
321
+
322
+ case 'invoice.payment_failed': {
323
+ const invoice = event.data.object as Stripe.Invoice;
324
+ await handlePaymentFailed(invoice);
325
+ break;
326
+ }
327
+
328
+ default:
329
+ console.log(`Unhandled event type: ${event.type}`);
330
+ }
331
+
332
+ return NextResponse.json({ received: true });
333
+ } catch (error) {
334
+ console.error('Webhook handler error:', error);
335
+ return NextResponse.json(
336
+ { error: 'Webhook handler failed' },
337
+ { status: 500 }
338
+ );
339
+ }
340
+ }
341
+
342
+ async function handleCheckoutComplete(session: Stripe.Checkout.Session) {
343
+ const userId = session.metadata?.userId;
344
+ if (!userId) throw new Error('Missing userId in metadata');
345
+
346
+ await prisma.user.update({
347
+ where: { id: userId },
348
+ data: {
349
+ stripeCustomerId: session.customer as string,
350
+ subscriptionStatus: 'active',
351
+ },
352
+ });
353
+ }
354
+
355
+ async function handleSubscriptionUpdate(subscription: Stripe.Subscription) {
356
+ await prisma.subscription.upsert({
357
+ where: { stripeSubscriptionId: subscription.id },
358
+ update: {
359
+ status: subscription.status,
360
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
361
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
362
+ },
363
+ create: {
364
+ stripeSubscriptionId: subscription.id,
365
+ stripeCustomerId: subscription.customer as string,
366
+ status: subscription.status,
367
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
368
+ },
369
+ });
370
+ }
371
+ ```
372
+
373
+ ### Background Jobs Pattern
374
+
375
+ ```typescript
376
+ // lib/jobs/email-job.ts
377
+ import { Resend } from 'resend';
378
+
379
+ const resend = new Resend(process.env.RESEND_API_KEY);
380
+
381
+ export async function sendWelcomeEmail(userId: string, email: string) {
382
+ try {
383
+ await resend.emails.send({
384
+ from: 'welcome@yourapp.com',
385
+ to: email,
386
+ subject: 'Welcome to Our App!',
387
+ react: WelcomeEmailTemplate({ userId }),
388
+ });
389
+
390
+ // Log successful send
391
+ await prisma.emailLog.create({
392
+ data: {
393
+ userId,
394
+ type: 'welcome',
395
+ status: 'sent',
396
+ sentAt: new Date(),
397
+ },
398
+ });
399
+ } catch (error) {
400
+ console.error('Failed to send welcome email:', error);
401
+ // Queue for retry
402
+ await prisma.emailLog.create({
403
+ data: {
404
+ userId,
405
+ type: 'welcome',
406
+ status: 'failed',
407
+ error: error.message,
408
+ },
409
+ });
410
+ }
411
+ }
412
+ ```
413
+
414
+ ### Error Handling
415
+
416
+ ```typescript
417
+ // lib/errors.ts
418
+ export class AppError extends Error {
419
+ constructor(
420
+ message: string,
421
+ public code: string,
422
+ public statusCode: number = 400
423
+ ) {
424
+ super(message);
425
+ this.name = 'AppError';
426
+ }
427
+ }
428
+
429
+ export class NotFoundError extends AppError {
430
+ constructor(resource: string) {
431
+ super(`${resource} not found`, 'NOT_FOUND', 404);
432
+ }
433
+ }
434
+
435
+ export class UnauthorizedError extends AppError {
436
+ constructor(message = 'Unauthorized') {
437
+ super(message, 'UNAUTHORIZED', 401);
438
+ }
439
+ }
440
+
441
+ export class ValidationError extends AppError {
442
+ constructor(public details: Record<string, string[]>) {
443
+ super('Validation failed', 'VALIDATION_ERROR', 400);
444
+ }
445
+ }
446
+
447
+ // Usage in route handler
448
+ export async function GET(request: NextRequest) {
449
+ try {
450
+ const user = await userService.findById(id);
451
+ if (!user) throw new NotFoundError('User');
452
+ return NextResponse.json(user);
453
+ } catch (error) {
454
+ if (error instanceof AppError) {
455
+ return NextResponse.json(
456
+ { error: error.message, code: error.code },
457
+ { status: error.statusCode }
458
+ );
459
+ }
460
+ console.error('Unexpected error:', error);
461
+ return NextResponse.json(
462
+ { error: 'Internal server error' },
463
+ { status: 500 }
464
+ );
465
+ }
466
+ }
467
+ ```
468
+
469
+ ## Backend Checklist
470
+
471
+ - [ ] Input validated with Zod schemas
472
+ - [ ] Authentication checked before mutations
473
+ - [ ] Authorization verified for resource access
474
+ - [ ] Errors handled gracefully
475
+ - [ ] Database operations optimized
476
+ - [ ] Webhook signatures verified
477
+ - [ ] Sensitive data not logged
478
+ - [ ] Rate limiting on public endpoints
479
+ - [ ] Proper HTTP status codes used
480
+ - [ ] Transactions used for related operations
481
+
482
+ ## Trigger Keywords
483
+ api, server action, route handler, backend, business logic, service, webhook, endpoint, post, get, patch, delete, mutation, query, authentication, authorization, middleware