specweave 0.24.0 → 0.24.6

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 (42) hide show
  1. package/.claude-plugin/marketplace.json +55 -0
  2. package/CLAUDE.md +42 -0
  3. package/dist/src/cli/commands/init.d.ts.map +1 -1
  4. package/dist/src/cli/commands/init.js +80 -41
  5. package/dist/src/cli/commands/init.js.map +1 -1
  6. package/dist/src/cli/helpers/issue-tracker/types.d.ts +1 -1
  7. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  8. package/dist/src/config/types.d.ts +24 -24
  9. package/dist/src/core/config/types.d.ts +25 -0
  10. package/dist/src/core/config/types.d.ts.map +1 -1
  11. package/dist/src/core/config/types.js +6 -0
  12. package/dist/src/core/config/types.js.map +1 -1
  13. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +33 -0
  14. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -0
  15. package/dist/src/core/repo-structure/repo-bulk-discovery.js +275 -0
  16. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -0
  17. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +9 -0
  18. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  19. package/dist/src/core/repo-structure/repo-structure-manager.js +255 -87
  20. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  21. package/dist/src/init/architecture/types.d.ts +6 -6
  22. package/dist/src/utils/plugin-validator.d.ts.map +1 -1
  23. package/dist/src/utils/plugin-validator.js +15 -14
  24. package/dist/src/utils/plugin-validator.js.map +1 -1
  25. package/package.json +4 -4
  26. package/plugins/specweave/.claude-plugin/plugin.json +4 -4
  27. package/plugins/specweave/agents/pm/AGENT.md +2 -0
  28. package/plugins/specweave/commands/specweave-do.md +0 -47
  29. package/plugins/specweave/commands/specweave-increment.md +0 -82
  30. package/plugins/specweave/commands/specweave-next.md +0 -47
  31. package/plugins/specweave/hooks/post-task-completion.sh +67 -6
  32. package/plugins/specweave/hooks/pre-edit-spec.sh +11 -0
  33. package/plugins/specweave/hooks/pre-task-completion.sh +69 -2
  34. package/plugins/specweave/hooks/pre-write-spec.sh +11 -0
  35. package/plugins/specweave/skills/increment-planner/SKILL.md +124 -4
  36. package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +21 -0
  37. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +150 -0
  38. package/plugins/specweave-payments/commands/stripe-setup.md +931 -0
  39. package/plugins/specweave-payments/commands/subscription-flow.md +1193 -0
  40. package/plugins/specweave-payments/commands/subscription-manage.md +386 -0
  41. package/plugins/specweave-payments/commands/webhook-setup.md +295 -0
  42. package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +21 -0
@@ -0,0 +1,386 @@
1
+ # Subscription Management
2
+
3
+ Generate complete subscription management system.
4
+
5
+ ## Task
6
+
7
+ You are a subscription billing expert. Generate production-ready subscription management with billing, upgrades, and cancellations.
8
+
9
+ ### Steps:
10
+
11
+ 1. **Ask for Requirements**:
12
+ - Pricing tiers (Basic, Pro, Enterprise)
13
+ - Billing interval (monthly, annual)
14
+ - Features per tier
15
+ - Trial period
16
+
17
+ 2. **Generate Pricing Configuration**:
18
+
19
+ ```typescript
20
+ // config/pricing.ts
21
+ export const PRICING_PLANS = {
22
+ basic: {
23
+ id: 'basic',
24
+ name: 'Basic',
25
+ description: 'For individuals and small teams',
26
+ prices: {
27
+ monthly: {
28
+ amount: 9,
29
+ stripePriceId: 'price_basic_monthly',
30
+ },
31
+ annual: {
32
+ amount: 90,
33
+ stripePriceId: 'price_basic_annual',
34
+ savings: 18, // 2 months free
35
+ },
36
+ },
37
+ features: [
38
+ '10 projects',
39
+ '5 GB storage',
40
+ 'Basic support',
41
+ ],
42
+ limits: {
43
+ projects: 10,
44
+ storage: 5 * 1024 * 1024 * 1024, // 5 GB in bytes
45
+ apiCallsPerMonth: 10000,
46
+ },
47
+ },
48
+ pro: {
49
+ id: 'pro',
50
+ name: 'Pro',
51
+ description: 'For growing teams',
52
+ prices: {
53
+ monthly: {
54
+ amount: 29,
55
+ stripePriceId: 'price_pro_monthly',
56
+ },
57
+ annual: {
58
+ amount: 290,
59
+ stripePriceId: 'price_pro_annual',
60
+ savings: 58,
61
+ },
62
+ },
63
+ features: [
64
+ 'Unlimited projects',
65
+ '50 GB storage',
66
+ 'Priority support',
67
+ 'Advanced analytics',
68
+ ],
69
+ limits: {
70
+ projects: Infinity,
71
+ storage: 50 * 1024 * 1024 * 1024,
72
+ apiCallsPerMonth: 100000,
73
+ },
74
+ },
75
+ enterprise: {
76
+ id: 'enterprise',
77
+ name: 'Enterprise',
78
+ description: 'For large organizations',
79
+ prices: {
80
+ monthly: {
81
+ amount: 99,
82
+ stripePriceId: 'price_enterprise_monthly',
83
+ },
84
+ annual: {
85
+ amount: 990,
86
+ stripePriceId: 'price_enterprise_annual',
87
+ savings: 198,
88
+ },
89
+ },
90
+ features: [
91
+ 'Unlimited everything',
92
+ '1 TB storage',
93
+ '24/7 dedicated support',
94
+ 'Custom integrations',
95
+ 'SLA guarantee',
96
+ ],
97
+ limits: {
98
+ projects: Infinity,
99
+ storage: 1024 * 1024 * 1024 * 1024,
100
+ apiCallsPerMonth: Infinity,
101
+ },
102
+ },
103
+ };
104
+ ```
105
+
106
+ 3. **Generate Subscription Service**:
107
+
108
+ ```typescript
109
+ // services/subscription.service.ts
110
+ import Stripe from 'stripe';
111
+ import { PRICING_PLANS } from '../config/pricing';
112
+
113
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
114
+
115
+ export class SubscriptionService {
116
+ // Create subscription
117
+ async create(userId: string, planId: string, interval: 'monthly' | 'annual') {
118
+ const user = await db.users.findUnique({ where: { id: userId } });
119
+ const plan = PRICING_PLANS[planId];
120
+
121
+ if (!plan) throw new Error('Invalid plan');
122
+
123
+ // Create Stripe customer if doesn't exist
124
+ let customerId = user.stripeCustomerId;
125
+ if (!customerId) {
126
+ const customer = await stripe.customers.create({
127
+ email: user.email,
128
+ metadata: { userId },
129
+ });
130
+ customerId = customer.id;
131
+ await db.users.update({
132
+ where: { id: userId },
133
+ data: { stripeCustomerId: customerId },
134
+ });
135
+ }
136
+
137
+ // Create subscription
138
+ const subscription = await stripe.subscriptions.create({
139
+ customer: customerId,
140
+ items: [{ price: plan.prices[interval].stripePriceId }],
141
+ trial_period_days: 14, // 14-day trial
142
+ payment_behavior: 'default_incomplete',
143
+ payment_settings: {
144
+ save_default_payment_method: 'on_subscription',
145
+ },
146
+ expand: ['latest_invoice.payment_intent'],
147
+ });
148
+
149
+ // Save to database
150
+ await db.subscriptions.create({
151
+ data: {
152
+ userId,
153
+ stripeSubscriptionId: subscription.id,
154
+ stripePriceId: plan.prices[interval].stripePriceId,
155
+ status: subscription.status,
156
+ planId,
157
+ interval,
158
+ currentPeriodStart: new Date(subscription.current_period_start * 1000),
159
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
160
+ trialEnd: subscription.trial_end
161
+ ? new Date(subscription.trial_end * 1000)
162
+ : null,
163
+ },
164
+ });
165
+
166
+ return {
167
+ subscriptionId: subscription.id,
168
+ clientSecret: subscription.latest_invoice.payment_intent.client_secret,
169
+ };
170
+ }
171
+
172
+ // Upgrade/downgrade subscription
173
+ async changePlan(userId: string, newPlanId: string, newInterval: 'monthly' | 'annual') {
174
+ const subscription = await db.subscriptions.findFirst({
175
+ where: { userId, status: 'active' },
176
+ });
177
+
178
+ if (!subscription) throw new Error('No active subscription');
179
+
180
+ const newPlan = PRICING_PLANS[newPlanId];
181
+ const newPriceId = newPlan.prices[newInterval].stripePriceId;
182
+
183
+ // Update Stripe subscription
184
+ const stripeSubscription = await stripe.subscriptions.retrieve(
185
+ subscription.stripeSubscriptionId
186
+ );
187
+
188
+ const updatedSubscription = await stripe.subscriptions.update(
189
+ subscription.stripeSubscriptionId,
190
+ {
191
+ items: [
192
+ {
193
+ id: stripeSubscription.items.data[0].id,
194
+ price: newPriceId,
195
+ },
196
+ ],
197
+ proration_behavior: 'always_invoice', // Prorate charges
198
+ }
199
+ );
200
+
201
+ // Update database
202
+ await db.subscriptions.update({
203
+ where: { id: subscription.id },
204
+ data: {
205
+ stripePriceId: newPriceId,
206
+ planId: newPlanId,
207
+ interval: newInterval,
208
+ },
209
+ });
210
+
211
+ return updatedSubscription;
212
+ }
213
+
214
+ // Cancel subscription
215
+ async cancel(userId: string, cancelAtPeriodEnd = true) {
216
+ const subscription = await db.subscriptions.findFirst({
217
+ where: { userId, status: 'active' },
218
+ });
219
+
220
+ if (!subscription) throw new Error('No active subscription');
221
+
222
+ if (cancelAtPeriodEnd) {
223
+ // Cancel at end of billing period
224
+ await stripe.subscriptions.update(subscription.stripeSubscriptionId, {
225
+ cancel_at_period_end: true,
226
+ });
227
+
228
+ await db.subscriptions.update({
229
+ where: { id: subscription.id },
230
+ data: { cancelAtPeriodEnd: true },
231
+ });
232
+ } else {
233
+ // Cancel immediately
234
+ await stripe.subscriptions.cancel(subscription.stripeSubscriptionId);
235
+
236
+ await db.subscriptions.update({
237
+ where: { id: subscription.id },
238
+ data: { status: 'canceled', canceledAt: new Date() },
239
+ });
240
+ }
241
+ }
242
+
243
+ // Resume canceled subscription
244
+ async resume(userId: string) {
245
+ const subscription = await db.subscriptions.findFirst({
246
+ where: { userId, cancelAtPeriodEnd: true },
247
+ });
248
+
249
+ if (!subscription) throw new Error('No subscription to resume');
250
+
251
+ await stripe.subscriptions.update(subscription.stripeSubscriptionId, {
252
+ cancel_at_period_end: false,
253
+ });
254
+
255
+ await db.subscriptions.update({
256
+ where: { id: subscription.id },
257
+ data: { cancelAtPeriodEnd: false },
258
+ });
259
+ }
260
+
261
+ // Get subscription status
262
+ async getStatus(userId: string) {
263
+ const subscription = await db.subscriptions.findFirst({
264
+ where: { userId },
265
+ orderBy: { createdAt: 'desc' },
266
+ });
267
+
268
+ if (!subscription) return null;
269
+
270
+ const plan = PRICING_PLANS[subscription.planId];
271
+ return {
272
+ ...subscription,
273
+ plan,
274
+ isActive: subscription.status === 'active',
275
+ isTrialing: subscription.status === 'trialing',
276
+ isCanceling: subscription.cancelAtPeriodEnd,
277
+ };
278
+ }
279
+
280
+ // Check feature access
281
+ async canAccess(userId: string, feature: string, value?: number) {
282
+ const status = await this.getStatus(userId);
283
+
284
+ if (!status || !status.isActive) return false;
285
+
286
+ const limits = status.plan.limits;
287
+
288
+ // Check specific limits
289
+ switch (feature) {
290
+ case 'projects':
291
+ const projectCount = await db.projects.count({ where: { userId } });
292
+ return projectCount < limits.projects;
293
+
294
+ case 'storage':
295
+ const storageUsed = await this.getStorageUsage(userId);
296
+ return storageUsed < limits.storage;
297
+
298
+ case 'api_calls':
299
+ const apiCalls = await this.getApiCallsThisMonth(userId);
300
+ return apiCalls < limits.apiCallsPerMonth;
301
+
302
+ default:
303
+ return false;
304
+ }
305
+ }
306
+ }
307
+ ```
308
+
309
+ 4. **Generate Usage Tracking**:
310
+
311
+ ```typescript
312
+ // Track API usage for metered billing
313
+ export class UsageTracker {
314
+ async recordApiCall(userId: string) {
315
+ const subscription = await db.subscriptions.findFirst({
316
+ where: { userId, status: 'active' },
317
+ });
318
+
319
+ if (!subscription) return;
320
+
321
+ // Increment usage
322
+ await db.usageRecords.create({
323
+ data: {
324
+ subscriptionId: subscription.id,
325
+ type: 'api_call',
326
+ quantity: 1,
327
+ timestamp: new Date(),
328
+ },
329
+ });
330
+
331
+ // Optional: Report to Stripe for metered billing
332
+ if (subscription.meteringEnabled) {
333
+ await stripe.subscriptionItems.createUsageRecord(
334
+ subscription.stripeSubscriptionItemId,
335
+ {
336
+ quantity: 1,
337
+ timestamp: Math.floor(Date.now() / 1000),
338
+ }
339
+ );
340
+ }
341
+ }
342
+
343
+ async getUsage(userId: string, period: 'month' | 'all' = 'month') {
344
+ const subscription = await db.subscriptions.findFirst({
345
+ where: { userId },
346
+ });
347
+
348
+ if (!subscription) return null;
349
+
350
+ const startDate =
351
+ period === 'month'
352
+ ? new Date(new Date().setDate(1)) // Start of month
353
+ : undefined;
354
+
355
+ const usage = await db.usageRecords.groupBy({
356
+ by: ['type'],
357
+ where: {
358
+ subscriptionId: subscription.id,
359
+ timestamp: startDate ? { gte: startDate } : undefined,
360
+ },
361
+ _sum: {
362
+ quantity: true,
363
+ },
364
+ });
365
+
366
+ return usage;
367
+ }
368
+ }
369
+ ```
370
+
371
+ ### Best Practices Included:
372
+
373
+ - Trial periods
374
+ - Proration on plan changes
375
+ - Cancel at period end vs immediate
376
+ - Usage tracking for metered billing
377
+ - Feature gating based on plan
378
+ - Subscription resumption
379
+ - Clear pricing configuration
380
+
381
+ ### Example Usage:
382
+
383
+ ```
384
+ User: "Set up subscription with Basic, Pro, Enterprise tiers"
385
+ Result: Complete subscription system with billing, upgrades, trials
386
+ ```
@@ -0,0 +1,295 @@
1
+ # Payment Webhook Configuration
2
+
3
+ Generate secure webhook handlers for payment providers.
4
+
5
+ ## Task
6
+
7
+ You are a payment webhook security expert. Generate secure, production-ready webhook handlers.
8
+
9
+ ### Steps:
10
+
11
+ 1. **Ask for Provider**:
12
+ - Stripe
13
+ - PayPal
14
+ - Square
15
+ - Custom payment gateway
16
+
17
+ 2. **Generate Webhook Endpoint** (Stripe):
18
+
19
+ ```typescript
20
+ import crypto from 'crypto';
21
+ import express from 'express';
22
+ import Stripe from 'stripe';
23
+
24
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
25
+ const app = express();
26
+
27
+ // CRITICAL: Use raw body for webhook signature verification
28
+ app.post(
29
+ '/api/webhooks/stripe',
30
+ express.raw({ type: 'application/json' }),
31
+ async (req, res) => {
32
+ const sig = req.headers['stripe-signature'];
33
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
34
+
35
+ let event;
36
+
37
+ try {
38
+ // Verify webhook signature
39
+ event = stripe.webhooks.constructEvent(
40
+ req.body,
41
+ sig!,
42
+ webhookSecret
43
+ );
44
+ } catch (err) {
45
+ console.error(`Webhook signature verification failed: ${err.message}`);
46
+ return res.status(400).send(`Webhook Error: ${err.message}`);
47
+ }
48
+
49
+ // Handle event idempotently
50
+ const eventId = event.id;
51
+ const existingEvent = await db.webhookEvents.findUnique({
52
+ where: { stripeEventId: eventId },
53
+ });
54
+
55
+ if (existingEvent) {
56
+ console.log(`Duplicate webhook event: ${eventId}`);
57
+ return res.status(200).json({ received: true });
58
+ }
59
+
60
+ // Store event (prevents duplicate processing)
61
+ await db.webhookEvents.create({
62
+ data: {
63
+ stripeEventId: eventId,
64
+ type: event.type,
65
+ processedAt: new Date(),
66
+ },
67
+ });
68
+
69
+ // Process event in background to return 200 quickly
70
+ processWebhookEvent(event).catch((error) => {
71
+ console.error(`Failed to process webhook: ${error.message}`);
72
+ // Alert ops team
73
+ });
74
+
75
+ res.status(200).json({ received: true });
76
+ }
77
+ );
78
+
79
+ async function processWebhookEvent(event: Stripe.Event) {
80
+ switch (event.type) {
81
+ case 'checkout.session.completed':
82
+ await handleCheckoutComplete(event.data.object);
83
+ break;
84
+
85
+ case 'customer.subscription.created':
86
+ case 'customer.subscription.updated':
87
+ await handleSubscriptionChange(event.data.object);
88
+ break;
89
+
90
+ case 'customer.subscription.deleted':
91
+ await handleSubscriptionCanceled(event.data.object);
92
+ break;
93
+
94
+ case 'invoice.paid':
95
+ await handleInvoicePaid(event.data.object);
96
+ break;
97
+
98
+ case 'invoice.payment_failed':
99
+ await handleInvoicePaymentFailed(event.data.object);
100
+ break;
101
+
102
+ case 'payment_intent.succeeded':
103
+ await handlePaymentSuccess(event.data.object);
104
+ break;
105
+
106
+ case 'payment_intent.payment_failed':
107
+ await handlePaymentFailed(event.data.object);
108
+ break;
109
+
110
+ case 'charge.dispute.created':
111
+ await handleDisputeCreated(event.data.object);
112
+ break;
113
+
114
+ case 'customer.created':
115
+ await handleCustomerCreated(event.data.object);
116
+ break;
117
+
118
+ default:
119
+ console.log(`Unhandled event type: ${event.type}`);
120
+ }
121
+ }
122
+ ```
123
+
124
+ 3. **Generate PayPal Webhook**:
125
+
126
+ ```typescript
127
+ import crypto from 'crypto';
128
+
129
+ app.post('/api/webhooks/paypal', express.json(), async (req, res) => {
130
+ const webhookId = process.env.PAYPAL_WEBHOOK_ID!;
131
+ const webhookEvent = req.body;
132
+
133
+ // Verify PayPal webhook signature
134
+ const isValid = await verifyPayPalWebhook(req, webhookId);
135
+
136
+ if (!isValid) {
137
+ return res.status(400).send('Invalid webhook signature');
138
+ }
139
+
140
+ const eventType = webhookEvent.event_type;
141
+
142
+ switch (eventType) {
143
+ case 'PAYMENT.CAPTURE.COMPLETED':
144
+ await handlePayPalPaymentCompleted(webhookEvent.resource);
145
+ break;
146
+
147
+ case 'BILLING.SUBSCRIPTION.CREATED':
148
+ await handlePayPalSubscriptionCreated(webhookEvent.resource);
149
+ break;
150
+
151
+ case 'BILLING.SUBSCRIPTION.CANCELLED':
152
+ await handlePayPalSubscriptionCancelled(webhookEvent.resource);
153
+ break;
154
+
155
+ default:
156
+ console.log(`Unhandled PayPal event: ${eventType}`);
157
+ }
158
+
159
+ res.status(200).json({ received: true });
160
+ });
161
+
162
+ async function verifyPayPalWebhook(req, webhookId) {
163
+ const transmissionId = req.headers['paypal-transmission-id'];
164
+ const timestamp = req.headers['paypal-transmission-time'];
165
+ const signature = req.headers['paypal-transmission-sig'];
166
+ const certUrl = req.headers['paypal-cert-url'];
167
+
168
+ const response = await fetch(
169
+ `https://api.paypal.com/v1/notifications/verify-webhook-signature`,
170
+ {
171
+ method: 'POST',
172
+ headers: {
173
+ 'Content-Type': 'application/json',
174
+ Authorization: `Bearer ${await getPayPalAccessToken()}`,
175
+ },
176
+ body: JSON.stringify({
177
+ transmission_id: transmissionId,
178
+ transmission_time: timestamp,
179
+ cert_url: certUrl,
180
+ auth_algo: req.headers['paypal-auth-algo'],
181
+ transmission_sig: signature,
182
+ webhook_id: webhookId,
183
+ webhook_event: req.body,
184
+ }),
185
+ }
186
+ );
187
+
188
+ const data = await response.json();
189
+ return data.verification_status === 'SUCCESS';
190
+ }
191
+ ```
192
+
193
+ 4. **Generate Webhook Testing Script**:
194
+
195
+ ```typescript
196
+ // test-webhook.ts
197
+ import { exec } from 'child_process';
198
+ import util from 'util';
199
+
200
+ const execPromise = util.promisify(exec);
201
+
202
+ async function testWebhook() {
203
+ // Install Stripe CLI: brew install stripe/stripe-cli/stripe
204
+
205
+ // Listen to webhooks
206
+ const { stdout } = await execPromise('stripe listen --forward-to localhost:3000/api/webhooks/stripe');
207
+ console.log(stdout);
208
+
209
+ // Trigger test events
210
+ await execPromise('stripe trigger payment_intent.succeeded');
211
+ await execPromise('stripe trigger customer.subscription.created');
212
+ await execPromise('stripe trigger invoice.payment_failed');
213
+ }
214
+
215
+ testWebhook();
216
+ ```
217
+
218
+ 5. **Generate Monitoring & Alerting**:
219
+
220
+ ```typescript
221
+ // Monitor webhook failures
222
+ async function monitorWebhooks() {
223
+ const failedEvents = await db.webhookEvents.findMany({
224
+ where: {
225
+ processed: false,
226
+ createdAt: {
227
+ lt: new Date(Date.now() - 5 * 60 * 1000), // 5 minutes ago
228
+ },
229
+ },
230
+ });
231
+
232
+ if (failedEvents.length > 0) {
233
+ await sendAlert({
234
+ type: 'webhook_failure',
235
+ count: failedEvents.length,
236
+ events: failedEvents.map((e) => e.type),
237
+ });
238
+ }
239
+ }
240
+
241
+ // Retry failed webhooks
242
+ async function retryFailedWebhooks() {
243
+ const failedEvents = await db.webhookEvents.findMany({
244
+ where: { processed: false },
245
+ take: 10,
246
+ });
247
+
248
+ for (const event of failedEvents) {
249
+ try {
250
+ await processWebhookEvent(event.data);
251
+ await db.webhookEvents.update({
252
+ where: { id: event.id },
253
+ data: { processed: true, processedAt: new Date() },
254
+ });
255
+ } catch (error) {
256
+ console.error(`Retry failed for event ${event.id}: ${error.message}`);
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ 6. **Generate Webhook Schema** (Database):
263
+
264
+ ```sql
265
+ CREATE TABLE webhook_events (
266
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
267
+ stripe_event_id VARCHAR(255) UNIQUE NOT NULL,
268
+ type VARCHAR(100) NOT NULL,
269
+ data JSONB NOT NULL,
270
+ processed BOOLEAN DEFAULT FALSE,
271
+ created_at TIMESTAMP DEFAULT NOW(),
272
+ processed_at TIMESTAMP
273
+ );
274
+
275
+ CREATE INDEX idx_webhook_events_type ON webhook_events(type);
276
+ CREATE INDEX idx_webhook_events_processed ON webhook_events(processed);
277
+ ```
278
+
279
+ ### Security Best Practices:
280
+
281
+ - ✅ Verify webhook signatures (prevent spoofing)
282
+ - ✅ Use raw request body for signature validation
283
+ - ✅ Idempotency (track event IDs, prevent duplicate processing)
284
+ - ✅ Return 200 immediately (process in background)
285
+ - ✅ Retry logic for failures
286
+ - ✅ Monitoring and alerting
287
+ - ✅ HTTPS only (secure transmission)
288
+ - ✅ IP whitelisting (optional but recommended)
289
+
290
+ ### Example Usage:
291
+
292
+ ```
293
+ User: "Set up secure Stripe webhook handler"
294
+ Result: Complete webhook endpoint with signature verification, idempotency, and monitoring
295
+ ```
@@ -795,3 +795,24 @@ it('should return 404 when user not found', () => { ... });
795
795
  - [ ] Rollback plan tested
796
796
 
797
797
  You are ready to ensure world-class quality through comprehensive testing strategies!
798
+
799
+ ## How to Invoke This Agent
800
+
801
+ Use the Task tool with the following subagent type:
802
+
803
+ ```typescript
804
+ Task({
805
+ subagent_type: "specweave-testing:qa-engineer:qa-engineer",
806
+ prompt: "Your QA/testing task here",
807
+ description: "Brief task description"
808
+ })
809
+ ```
810
+
811
+ **Example**:
812
+ ```typescript
813
+ Task({
814
+ subagent_type: "specweave-testing:qa-engineer:qa-engineer",
815
+ prompt: "Create a comprehensive test strategy for an e-commerce checkout flow using Playwright E2E and Vitest unit tests",
816
+ description: "Design test strategy for checkout"
817
+ })
818
+ ```