@tenantegroup/ai-rules-mcp 1.0.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 (67) hide show
  1. package/INSTALLATION.md +52 -0
  2. package/README.md +57 -0
  3. package/USAGE.md +46 -0
  4. package/package.json +57 -0
  5. package/rules/cloudflare/api-services.md +80 -0
  6. package/rules/cloudflare/cicd-deployment.md +56 -0
  7. package/rules/cloudflare/database-orm.md +28 -0
  8. package/rules/cloudflare/edge-parity.md +24 -0
  9. package/rules/cloudflare/kv-usage.md +31 -0
  10. package/rules/cloudflare/logging-observability.md +66 -0
  11. package/rules/cloudflare/performance.md +44 -0
  12. package/rules/cloudflare/realtime-background.md +58 -0
  13. package/rules/cloudflare/security.md +162 -0
  14. package/rules/cloudflare/seeding.md +27 -0
  15. package/rules/cloudflare/workflows.md +593 -0
  16. package/rules/dotnet/api.md +26 -0
  17. package/rules/dotnet/architecture.md +27 -0
  18. package/rules/dotnet/cli.md +26 -0
  19. package/rules/dotnet/configuration.md +26 -0
  20. package/rules/dotnet/logging.md +25 -0
  21. package/rules/dotnet/maui.md +26 -0
  22. package/rules/dotnet/mvvm.md +26 -0
  23. package/rules/dotnet/packaging.md +24 -0
  24. package/rules/dotnet/project-structure.md +26 -0
  25. package/rules/dotnet/sqlite.md +29 -0
  26. package/rules/dotnet/testing.md +24 -0
  27. package/rules/flutter/api.md +29 -0
  28. package/rules/flutter/architecture.md +34 -0
  29. package/rules/flutter/auth.md +27 -0
  30. package/rules/flutter/configuration.md +24 -0
  31. package/rules/flutter/database.md +30 -0
  32. package/rules/flutter/logging.md +27 -0
  33. package/rules/flutter/navigation.md +28 -0
  34. package/rules/flutter/offline-sync.md +26 -0
  35. package/rules/flutter/platform.md +30 -0
  36. package/rules/flutter/project-structure.md +32 -0
  37. package/rules/flutter/riverpod.md +32 -0
  38. package/rules/flutter/testing.md +31 -0
  39. package/rules/nuxt/architecture-principles.md +31 -0
  40. package/rules/nuxt/authentication.md +35 -0
  41. package/rules/nuxt/code-quality.md +71 -0
  42. package/rules/nuxt/configuration.md +31 -0
  43. package/rules/nuxt/core-directives.md +12 -0
  44. package/rules/nuxt/project-initialization.md +53 -0
  45. package/rules/nuxt/project-structure.md +44 -0
  46. package/rules/nuxt/testing.md +48 -0
  47. package/src/index.js +757 -0
  48. package/templates/cloudflare/compile-context.js +43 -0
  49. package/templates/cloudflare/hooks/post-checkout +5 -0
  50. package/templates/cloudflare/hooks/pre-commit +14 -0
  51. package/templates/cloudflare/install-hooks.js +34 -0
  52. package/templates/cloudflare/validate-code.js +57 -0
  53. package/templates/dotnet/compile-context.js +43 -0
  54. package/templates/dotnet/hooks/post-checkout +5 -0
  55. package/templates/dotnet/hooks/pre-commit +14 -0
  56. package/templates/dotnet/install-hooks.js +34 -0
  57. package/templates/dotnet/validate-code.js +84 -0
  58. package/templates/flutter/compile-context.js +43 -0
  59. package/templates/flutter/hooks/post-checkout +5 -0
  60. package/templates/flutter/hooks/pre-commit +14 -0
  61. package/templates/flutter/install-hooks.js +34 -0
  62. package/templates/flutter/validate-code.js +64 -0
  63. package/templates/nuxt/compile-context.js +43 -0
  64. package/templates/nuxt/hooks/post-checkout +5 -0
  65. package/templates/nuxt/hooks/pre-commit +14 -0
  66. package/templates/nuxt/install-hooks.js +34 -0
  67. package/templates/nuxt/validate-code.js +57 -0
@@ -0,0 +1,593 @@
1
+ # Cloudflare Workflows
2
+
3
+ ## Core Principle
4
+ Workflows enable durable, multi-step orchestration with automatic retry, state persistence, and saga-pattern rollback support. Use Workflows for any operation that requires:
5
+ - Coordinating multiple steps across external systems
6
+ - Long-running operations (minutes to weeks)
7
+ - Automatic recovery from failures with state persistence
8
+ - Semantic reversal of operations on failure (saga pattern)
9
+ - Human-in-the-loop approvals or event-driven pauses
10
+
11
+ ## When to Use Workflows
12
+
13
+ ### Use Workflows For
14
+ - **Multi-step transactions** — Fund transfers, payment processing, order fulfillment that span external systems (banks, payment processors, inventory systems)
15
+ - **Long-running orchestration** — Data pipelines, image processing, AI-powered workflows lasting hours or days
16
+ - **Event-driven workflows** — Wait for approvals, user input, webhook responses, or external system callbacks before proceeding
17
+ - **State-machine logic** — Complex workflows with multiple paths, retries, and compensating actions
18
+ - **User lifecycle automation** — Onboarding, trial expirations, automated emails with pauses and external event dependencies
19
+ - **Human-in-the-loop systems** — Pause workflows for approval, resume programmatically after decision
20
+
21
+ ### Do NOT Use Workflows For
22
+ - Simple fire-and-forget async tasks (use **Queues** instead)
23
+ - Stateless, millisecond-scale operations (use **Workers** instead)
24
+ - Real-time collaboration requiring coordinated client state (use **Durable Objects** instead)
25
+ - Pure caching or session storage (use **KV** instead)
26
+
27
+ ## Workflow Architecture
28
+
29
+ ### Durable Steps (`step.do()`)
30
+
31
+ Every step in a Workflow is durable — execution is logged, state is persisted, and retries are automatic:
32
+
33
+ ```typescript
34
+ export class ProcessPaymentWorkflow extends WorkflowEntrypoint {
35
+ async run(event: WorkflowEvent, step: WorkflowStep) {
36
+ // Step 1: Charge the card
37
+ const charge = await step.do("charge-card", async () => {
38
+ return await this.env.PAYMENT.charge({
39
+ amount: event.payload.amount,
40
+ cardToken: event.payload.token,
41
+ idempotencyKey: `${event.payload.orderId}:charge`,
42
+ });
43
+ });
44
+
45
+ // Step 2: Update inventory (fails if charge succeeds but this fails)
46
+ const inventory = await step.do("deduct-inventory", async () => {
47
+ return await this.env.DB.prepare(
48
+ "UPDATE products SET stock = stock - 1 WHERE id = ?"
49
+ ).bind(event.payload.productId).first();
50
+ });
51
+
52
+ // Step 3: Send confirmation
53
+ await step.do("send-email", async () => {
54
+ await this.env.MAILER.send({
55
+ to: event.payload.email,
56
+ subject: "Order Confirmation",
57
+ body: "Your order has been confirmed",
58
+ });
59
+ });
60
+ }
61
+ }
62
+ ```
63
+
64
+ **Key Properties:**
65
+ - Each `step.do()` is uniquely named (name is the identifier)
66
+ - Step body executes exactly once; output is memoized
67
+ - Retries are automatic on transient failures
68
+ - If a step fails after maximum retries, the Workflow fails
69
+
70
+ ### Saga Pattern Rollbacks
71
+
72
+ The **saga pattern** defines a compensating action for each step. If a Workflow fails, rollback handlers execute in **reverse order** (LIFO), undoing completed steps.
73
+
74
+ #### Without Rollbacks (Manual Compensation)
75
+ ```typescript
76
+ let chargeId;
77
+ let inventoryDeducted = false;
78
+
79
+ try {
80
+ chargeId = await step.do("charge-card", async () => {
81
+ return await this.env.PAYMENT.charge(...);
82
+ });
83
+
84
+ inventoryDeducted = await step.do("deduct-inventory", async () => {
85
+ return await this.env.DB.prepare(...).first();
86
+ });
87
+
88
+ // If email fails, manual cleanup outside steps
89
+ await step.do("send-email", async () => {
90
+ // Fails here — how do we undo inventory?
91
+ throw new Error("Email service down");
92
+ });
93
+ } catch (error) {
94
+ // Manual unwinding — brittle, error-prone, easy to forget steps
95
+ if (inventoryDeducted) {
96
+ await step.do("undo-inventory", async () => {
97
+ return await this.env.DB.prepare(
98
+ "UPDATE products SET stock = stock + 1 WHERE id = ?"
99
+ ).bind(event.payload.productId).first();
100
+ });
101
+ }
102
+ if (chargeId) {
103
+ await step.do("refund-charge", async () => {
104
+ return await this.env.PAYMENT.refund({
105
+ chargeId,
106
+ idempotencyKey: `${event.payload.orderId}:refund`,
107
+ });
108
+ });
109
+ }
110
+ throw error;
111
+ }
112
+ ```
113
+
114
+ #### With Rollbacks (Built-In Compensation)
115
+ ```typescript
116
+ export class ProcessPaymentWorkflow extends WorkflowEntrypoint {
117
+ async run(event: WorkflowEvent, step: WorkflowStep) {
118
+ // Charge card — if needed, refund
119
+ const charge = await step.do(
120
+ "charge-card",
121
+ async () => {
122
+ return await this.env.PAYMENT.charge({
123
+ amount: event.payload.amount,
124
+ cardToken: event.payload.token,
125
+ idempotencyKey: `${event.payload.orderId}:charge`,
126
+ });
127
+ },
128
+ {
129
+ rollback: async ({ output }) => {
130
+ // output is the charge object returned from step.do()
131
+ if (output && output.id) {
132
+ await this.env.PAYMENT.refund({
133
+ chargeId: output.id,
134
+ idempotencyKey: `${event.payload.orderId}:rollback-charge`,
135
+ });
136
+ }
137
+ },
138
+ }
139
+ );
140
+
141
+ // Deduct inventory — if needed, restock
142
+ const inventory = await step.do(
143
+ "deduct-inventory",
144
+ async () => {
145
+ return await this.env.DB.prepare(
146
+ "UPDATE products SET stock = stock - 1 WHERE id = ? RETURNING *"
147
+ ).bind(event.payload.productId).first();
148
+ },
149
+ {
150
+ rollback: async ({ output }) => {
151
+ // Restock inventory (must be idempotent)
152
+ await this.env.DB.prepare(
153
+ "UPDATE products SET stock = stock + 1 WHERE id = ?"
154
+ ).bind(event.payload.productId).run();
155
+ },
156
+ }
157
+ );
158
+
159
+ // Send confirmation (no rollback needed — email is idempotent)
160
+ await step.do("send-email", async () => {
161
+ await this.env.MAILER.send({
162
+ to: event.payload.email,
163
+ subject: "Order Confirmation",
164
+ body: "Your order has been confirmed",
165
+ });
166
+ });
167
+ }
168
+ }
169
+ ```
170
+
171
+ **Rollback Rules:**
172
+ 1. **Rollback handlers execute in reverse LIFO order** — If steps A → B → C run, and C fails, rollbacks run C → B → A
173
+ 2. **Only failed Workflows trigger rollback** — If user code catches an error and Workflow continues, no rollback. Rollback only starts when Workflow is about to fail terminally
174
+ 3. **Failed steps CAN still rollback** — A step that fails may have partially succeeded (e.g., charge captured but transient error before returning). Rollback handlers receive `output`, but must handle `output === undefined`
175
+ 4. **Rollback handlers MUST be idempotent** — Design as if they will be called multiple times. Use idempotency keys for all external operations:
176
+ ```typescript
177
+ // Refund with idempotency key — safe to retry
178
+ await this.env.PAYMENT.refund({
179
+ chargeId: output.id,
180
+ idempotencyKey: `${orderId}:rollback-charge`, // ← Same key on retry
181
+ });
182
+ ```
183
+
184
+ ## State Persistence & Long-Running Workflows
185
+
186
+ Workflows persist state across the entire execution, even if the Worker isolate is recycled:
187
+
188
+ ```typescript
189
+ export class DataProcessingWorkflow extends WorkflowEntrypoint {
190
+ async run(event: WorkflowEvent, step: WorkflowStep) {
191
+ // Pause workflow for 7 days (state is persisted)
192
+ await step.sleep("wait-7-days", "7 days");
193
+
194
+ // Process data
195
+ const result = await step.do("process-data", async () => {
196
+ return await expensiveProcessing(event.payload);
197
+ });
198
+
199
+ // Pause for user approval (wait for external event)
200
+ const approval = await step.waitForEvent("await-approval", {
201
+ event: "user-approved",
202
+ timeout: "24 hours",
203
+ });
204
+
205
+ if (approval.data.approved) {
206
+ await step.do("publish", async () => {
207
+ await this.env.STORAGE.put(`published/${event.payload.id}`, result);
208
+ });
209
+ }
210
+ }
211
+ }
212
+ ```
213
+
214
+ **Key Features:**
215
+ - **`step.sleep(name, duration)`** — Pause for seconds, hours, days, weeks
216
+ - **`step.sleepUntil(name, timestamp)`** — Resume at specific ISO timestamp
217
+ - **`step.waitForEvent(name, options)`** — Pause until external event (webhook, API call)
218
+ - **State is persisted across pauses** — Isolate recycling, server shutdown, etc. do not affect execution
219
+ - **Timeouts prevent forever-waiting** — `waitForEvent()` supports `timeout` to terminate if event never arrives
220
+
221
+ ## Workflow Lifecycle Management
222
+
223
+ ### Triggering Workflows
224
+
225
+ ```typescript
226
+ // From a Worker/API endpoint, trigger a new Workflow instance
227
+ export default {
228
+ async fetch(request, env) {
229
+ const workflowId = crypto.randomUUID();
230
+
231
+ await env.WORKFLOWS.create(ProcessPaymentWorkflow, {
232
+ id: workflowId,
233
+ params: {
234
+ orderId: "order-123",
235
+ amount: 9999,
236
+ email: "user@example.com",
237
+ },
238
+ });
239
+
240
+ return new Response(
241
+ JSON.stringify({ workflowId, status: "created" }),
242
+ { status: 202 }
243
+ );
244
+ },
245
+ };
246
+ ```
247
+
248
+ ### Pausing & Resuming Workflows
249
+
250
+ ```typescript
251
+ // Pause a running Workflow
252
+ await env.WORKFLOWS.pause(workflowId);
253
+
254
+ // Resume a paused Workflow
255
+ await env.WORKFLOWS.resume(workflowId);
256
+
257
+ // Terminate a Workflow
258
+ await env.WORKFLOWS.terminate(workflowId);
259
+ ```
260
+
261
+ ### Querying Workflow Status
262
+
263
+ ```typescript
264
+ // Get current Workflow status (used for polling or dashboard)
265
+ const status = await env.WORKFLOWS.getStatus(workflowId);
266
+ // Returns: { id, status, steps, history, nextScheduledTime, output, error }
267
+ ```
268
+
269
+ ## Configuration & Binding
270
+
271
+ ### wrangler.toml
272
+
273
+ Workflows do NOT require explicit binding; they are built-in to the Workers runtime:
274
+
275
+ ```toml
276
+ # wrangler.toml
277
+ name = "my-app"
278
+ main = "src/index.ts"
279
+
280
+ [[workflows]]
281
+ binding = "WORKFLOWS"
282
+ name = "my_workflows"
283
+
284
+ # Bind the workflow class
285
+ [[env.production.workflows]]
286
+ binding = "WORKFLOWS"
287
+ class = "ProcessPaymentWorkflow"
288
+ ```
289
+
290
+ ### Database Access from Workflows
291
+
292
+ Workflows can access D1, KV, and other Cloudflare bindings the same way Workers do:
293
+
294
+ ```typescript
295
+ export class UserOnboardingWorkflow extends WorkflowEntrypoint {
296
+ async run(event: WorkflowEvent, step: WorkflowStep) {
297
+ const user = await step.do("fetch-user", async () => {
298
+ return await this.env.DB.prepare(
299
+ "SELECT * FROM users WHERE id = ?"
300
+ ).bind(event.payload.userId).first();
301
+ });
302
+
303
+ // Send welcome email
304
+ await step.do("send-welcome-email", async () => {
305
+ await this.env.MAILER.send({
306
+ to: user.email,
307
+ subject: "Welcome!",
308
+ body: `Hi ${user.name}`,
309
+ });
310
+ });
311
+
312
+ // Set trial expiration (14 days from now)
313
+ await step.sleep("wait-trial", "14 days");
314
+
315
+ // Check if user has converted
316
+ const hasConverted = await step.do("check-conversion", async () => {
317
+ const subscription = await this.env.DB.prepare(
318
+ "SELECT * FROM subscriptions WHERE user_id = ? AND status = 'active'"
319
+ ).bind(event.payload.userId).first();
320
+ return !!subscription;
321
+ });
322
+
323
+ if (!hasConverted) {
324
+ // Send expiration notice
325
+ await step.do("send-expiration-email", async () => {
326
+ await this.env.MAILER.send({
327
+ to: user.email,
328
+ subject: "Your trial has ended",
329
+ body: "Subscribe now to continue",
330
+ });
331
+ });
332
+ }
333
+ }
334
+ }
335
+ ```
336
+
337
+ ## Error Handling & Observability
338
+
339
+ ### Retry Behavior
340
+
341
+ Workflows automatically retry failed steps with exponential backoff:
342
+ - Default max retries: 5 (configurable)
343
+ - Backoff: exponential with jitter
344
+ - If a step exceeds max retries, the Workflow fails and rollback begins
345
+
346
+ ### Logging from Workflows
347
+
348
+ ```typescript
349
+ export class OrderWorkflow extends WorkflowEntrypoint {
350
+ async run(event: WorkflowEvent, step: WorkflowStep) {
351
+ // Use console for structured logging (same as Workers)
352
+ console.log(JSON.stringify({
353
+ event: "workflow_start",
354
+ workflowId: event.id,
355
+ orderId: event.payload.orderId,
356
+ timestamp: new Date().toISOString(),
357
+ }));
358
+
359
+ const charge = await step.do("charge", async () => {
360
+ const result = await this.env.PAYMENT.charge({...});
361
+ console.log(JSON.stringify({
362
+ event: "charge_success",
363
+ chargeId: result.id,
364
+ amount: result.amount,
365
+ }));
366
+ return result;
367
+ });
368
+ }
369
+ }
370
+ ```
371
+
372
+ ### Debugging in Dashboard
373
+
374
+ - **Cloudflare Dashboard** → **Workflows** → View step-by-step execution history
375
+ - See input/output for each step
376
+ - View retry attempts and timing
377
+ - Inspect rollback execution order
378
+
379
+ ## Idempotency & Distributed Systems
380
+
381
+ ### Critical: External Idempotency Keys
382
+
383
+ Since steps can be retried, **all external operations MUST use idempotency keys** to prevent duplicate side effects:
384
+
385
+ ```typescript
386
+ // ❌ NOT IDEMPOTENT — repeated calls charge multiple times
387
+ await this.env.PAYMENT.charge({
388
+ amount: 9999,
389
+ card: token,
390
+ // No idempotency key — if Workflow retries, user charged twice
391
+ });
392
+
393
+ // ✅ IDEMPOTENT — repeated calls use same key, provider deduplicates
394
+ await this.env.PAYMENT.charge({
395
+ amount: 9999,
396
+ card: token,
397
+ idempotencyKey: `${orderId}:charge:attempt1`,
398
+ });
399
+ ```
400
+
401
+ ### D1 Transactions
402
+
403
+ For D1 operations, use Drizzle transactions to ensure atomic steps:
404
+
405
+ ```typescript
406
+ const result = await step.do("create-order", async () => {
407
+ return await this.env.DB.transaction(async (tx) => {
408
+ const order = await tx.insert(ordersTable).values({
409
+ id: event.payload.orderId,
410
+ amount: event.payload.amount,
411
+ status: "pending",
412
+ }).returning();
413
+
414
+ await tx.insert(orderItemsTable).values(
415
+ event.payload.items.map((item) => ({
416
+ orderId: order[0].id,
417
+ productId: item.productId,
418
+ quantity: item.quantity,
419
+ }))
420
+ );
421
+
422
+ return order[0];
423
+ });
424
+ });
425
+ ```
426
+
427
+ ## Deployment & Versioning
428
+
429
+ ### Deploy Workflows with Wrangler
430
+
431
+ ```bash
432
+ # Deploy to production
433
+ wrangler deploy
434
+
435
+ # Deploy to staging
436
+ wrangler deploy --env staging
437
+ ```
438
+
439
+ Workflows are versioned with your application code. If you deploy a new Workflow definition:
440
+ - New instances use the new definition
441
+ - Running instances continue with the old definition (no breaking mid-flight)
442
+ - After deploying, new workflow IDs reference the new version
443
+
444
+ ### Updating Running Workflows
445
+
446
+ Avoid changing Workflow definitions while instances are running:
447
+
448
+ ```typescript
449
+ // ❌ RISKY — Modifying a running Workflow can cause inconsistency
450
+ export class OrderWorkflow extends WorkflowEntrypoint {
451
+ async run(event: WorkflowEvent, step: WorkflowStep) {
452
+ // Previously: step 1 was "charge"
453
+ // Now: renamed to "charge-card"
454
+ // Running instances fail because state history expects "charge"
455
+ await step.do("charge-card", ...); // ← Causes desynchronization
456
+ }
457
+ }
458
+
459
+ // ✅ SAFE — Create new version, let old instances finish
460
+ export class OrderWorkflowV2 extends WorkflowEntrypoint {
461
+ async run(event: WorkflowEvent, step: WorkflowStep) {
462
+ // New Workflow class, old instances unaffected
463
+ await step.do("charge-card", ...);
464
+ }
465
+ }
466
+ ```
467
+
468
+ Best practice: Trigger new instances to use `OrderWorkflowV2`, and let old `OrderWorkflow` instances complete.
469
+
470
+ ## Best Practices
471
+
472
+ ### 1. Keep Steps Focused
473
+ - One step = one logical operation
474
+ - Avoid grouping multiple external calls in a single step
475
+ ```typescript
476
+ // ❌ TOO BROAD
477
+ await step.do("process-order", async () => {
478
+ const charge = await this.env.PAYMENT.charge(...);
479
+ const inventory = await this.env.DB.updateInventory(...);
480
+ const email = await this.env.MAILER.send(...);
481
+ return { charge, inventory, email };
482
+ });
483
+
484
+ // ✅ FOCUSED
485
+ await step.do("charge", async () => {
486
+ return await this.env.PAYMENT.charge(...);
487
+ });
488
+ await step.do("deduct-inventory", async () => {
489
+ return await this.env.DB.updateInventory(...);
490
+ });
491
+ await step.do("send-email", async () => {
492
+ return await this.env.MAILER.send(...);
493
+ });
494
+ ```
495
+
496
+ ### 2. Always Provide Rollback for External State Changes
497
+ ```typescript
498
+ // ✅ Fund transfer with rollback
499
+ await step.do(
500
+ "debit-source",
501
+ () => bankA.debit(from, amount),
502
+ {
503
+ rollback: async ({ output }) => {
504
+ bankA.credit(from, amount, output.id);
505
+ },
506
+ }
507
+ );
508
+ ```
509
+
510
+ ### 3. Use Timeouts on External Waits
511
+ ```typescript
512
+ // ✅ Wait for approval with timeout
513
+ const approval = await step.waitForEvent("approval", {
514
+ event: "user-approved",
515
+ timeout: "24 hours", // Fail if no approval within 24 hours
516
+ });
517
+ ```
518
+
519
+ ### 4. Implement Application-Level Idempotency Checks
520
+ ```typescript
521
+ // ✅ Check if order already exists before creating
522
+ const existing = await step.do("check-existing", async () => {
523
+ return await this.env.DB.prepare(
524
+ "SELECT id FROM orders WHERE id = ?"
525
+ ).bind(event.payload.orderId).first();
526
+ });
527
+
528
+ if (existing) {
529
+ console.log("Order already processed");
530
+ return { status: "already_completed", orderId: existing.id };
531
+ }
532
+
533
+ // Proceed with creation
534
+ await step.do("create-order", async () => {
535
+ return await this.env.DB.prepare(
536
+ "INSERT INTO orders (id, ...) VALUES (?, ...)"
537
+ ).bind(event.payload.orderId, ...).run();
538
+ });
539
+ ```
540
+
541
+ ### 5. Monitor Workflow Metrics
542
+ - Track execution time per step
543
+ - Monitor rollback frequency
544
+ - Alert on max retries exceeded
545
+ - Log all external API calls with timing and status
546
+
547
+ ### 6. Design for Long Pauses
548
+ ```typescript
549
+ // ✅ Designed for week-long pause
550
+ export class TrialExpirationWorkflow extends WorkflowEntrypoint {
551
+ async run(event: WorkflowEvent, step: WorkflowStep) {
552
+ // Pause for entire trial period
553
+ await step.sleep("trial-period", "14 days");
554
+
555
+ // Check conversion status
556
+ const converted = await step.do("check-conversion", async () => {
557
+ const sub = await this.env.DB.prepare(
558
+ "SELECT * FROM subscriptions WHERE user_id = ?"
559
+ ).bind(event.payload.userId).first();
560
+ return !!sub?.activeUntil;
561
+ });
562
+
563
+ // Send appropriate email based on conversion
564
+ await step.do("notify", async () => {
565
+ if (converted) {
566
+ // Already subscribed — send nothing
567
+ return;
568
+ }
569
+ // Trial ended — send upgrade email
570
+ await this.env.MAILER.send({...});
571
+ });
572
+ }
573
+ }
574
+ ```
575
+
576
+ ## When to Choose Between Workflows, Queues, and Durable Objects
577
+
578
+ | Feature | Workflows | Queues | Durable Objects |
579
+ |---------|-----------|--------|-----------------|
580
+ | **Multi-step orchestration** | ✅ Native | ⚠️ Manual | ❌ Not designed for |
581
+ | **Long-running (hours/days)** | ✅ Yes | ❌ ~15 min limit | ✅ Yes, but stateless |
582
+ | **State persistence** | ✅ Yes | ❌ No | ✅ Yes, but in-memory |
583
+ | **Automatic retries** | ✅ Yes | ✅ Yes | ❌ No |
584
+ | **Saga pattern rollback** | ✅ Yes (native) | ❌ Manual | ❌ Not applicable |
585
+ | **Wait for external events** | ✅ Yes | ❌ No | ❌ No |
586
+ | **Cost per operation** | Low (pay per step) | Low (pay per message) | High (compute-intensive) |
587
+ | **Use case** | Orchestration, transactions, approvals | Simple async tasks, email | Realtime coordination, chat |
588
+
589
+ **Decision Tree:**
590
+ - Need to orchestrate multiple external systems? → **Workflows**
591
+ - Need simple fire-and-forget async tasks? → **Queues**
592
+ - Need coordinated state across multiple clients? → **Durable Objects**
593
+ - Otherwise → Use **Workers** directly
@@ -0,0 +1,26 @@
1
+ Rule: Enforce API Integration with Refit
2
+ Requirements:
3
+ - AI MUST follow all `.ai/rules/*.md` files and prioritize architecture constraints.
4
+ - AI MUST use Refit interfaces for HTTP API contracts.
5
+ - AI MUST use strongly typed DTOs for requests and responses.
6
+ - AI MUST wrap Refit interfaces in service classes before ViewModel consumption.
7
+ - AI MUST configure authenticated requests via DelegatingHandler.
8
+ - AI MUST set request timeout to 30 seconds unless feature-specific override is required.
9
+ - AI MUST implement retry policy: 3 retries with exponential backoff (250ms, 500ms, 1000ms) for transient network failures and HTTP 5xx only.
10
+ - AI MUST map API DTOs into domain/data models before persistence or UI use.
11
+ - AI MUST persist API results to SQLite before UI reads where offline-first applies.
12
+ - AI MUST handle `ApiException` and surface safe errors.
13
+ Prohibited:
14
+ - AI MUST NOT call Refit interfaces directly from Views or ViewModels.
15
+ - AI MUST NOT use raw `HttpClient` for standard API integration.
16
+ - AI MUST NOT log auth headers, tokens, passwords, or secret payload fields.
17
+ - AI MUST NOT use dynamic/weakly typed API payload handling.
18
+ - AI MUST NOT bypass retry/error handling constraints.
19
+ Patterns:
20
+ - `public interface IUserApi { [Get("/users/me")] Task<UserDto> GetCurrentUserAsync(); }`
21
+ - `UserApiService` depends on `IUserApi`.
22
+ - Authentication handler injects bearer token from secure secret provider.
23
+ - Service flow: Refit call -> DTO mapping -> repository save -> ViewModel read.
24
+ Examples:
25
+ - `builder.Services.AddRefitClient<IUserApi>().ConfigureHttpClient(c => c.BaseAddress = new Uri(baseUrl));`
26
+ - `catch (ApiException ex) { ... }`
@@ -0,0 +1,27 @@
1
+ Rule: Enforce Application Architecture
2
+ Requirements:
3
+ - AI MUST follow this file and all `.ai/rules/*.md` files before generating any code.
4
+ - AI MUST prioritize these rules over default generation behavior.
5
+ - AI MUST use .NET MAUI with MVVM via CommunityToolkit.Mvvm.
6
+ - AI MUST keep strict separation: Views = UI only, ViewModels = state/commands only, Services = business logic.
7
+ - AI MUST use XAML-first UI and MAUI Shell URI routing.
8
+ - AI MUST use dependency injection for all dependencies.
9
+ - AI MUST register dependencies only in composition root files (`MauiProgram.cs` or `Program.cs`).
10
+ - AI MUST register services through interfaces.
11
+ - AI MUST register ViewModels as transient by default; singleton usage requires explicit justification.
12
+ - AI MUST place business logic in services only.
13
+ - AI MUST route data flow as API -> SQLite -> Service -> ViewModel -> View.
14
+ Prohibited:
15
+ - AI MUST NOT bypass these architecture constraints.
16
+ - AI MUST NOT invent alternative architectures, patterns, or folder models.
17
+ - AI MUST NOT place business logic in Views, code-behind, or ViewModels.
18
+ - AI MUST NOT call APIs or SQLite directly from Views or ViewModels.
19
+ - AI MUST NOT instantiate services with `new` outside composition root.
20
+ Patterns:
21
+ - Register dependencies in `MauiProgram.cs` or `Program.cs` only.
22
+ - Use constructor injection only.
23
+ - Use `InitializeAsync()` for runtime initialization.
24
+ - Keep feature modules self-contained under `Features/<FeatureName>/`.
25
+ Examples:
26
+ - `builder.Services.AddSingleton<IAuthService, AuthService>();`
27
+ - `await Shell.Current.GoToAsync("//dashboard");`
@@ -0,0 +1,26 @@
1
+ Rule: Enforce CLI and Console Standards
2
+ Requirements:
3
+ - AI MUST follow all `.ai/rules/*.md` files when generating CLI code.
4
+ - AI MUST use Spectre.Console and Spectre.Console.Cli for CLI applications.
5
+ - AI MUST implement command handlers as command classes.
6
+ - AI MUST define arguments/options via `CommandSettings` classes.
7
+ - AI MUST register commands through `CommandApp` configuration.
8
+ - AI MUST use dependency injection for services and command dependencies.
9
+ - AI MUST return deterministic exit codes (`0` success, `1` runtime error, `2` invalid arguments, `3` configuration error).
10
+ - AI MUST support `--help` and `--version`.
11
+ - AI MUST use async command execution for I/O operations.
12
+ - AI MUST apply centralized logging with Serilog.
13
+ Prohibited:
14
+ - AI MUST NOT parse command-line args manually.
15
+ - AI MUST NOT instantiate services directly inside commands.
16
+ - AI MUST NOT use blocking synchronous I/O in command execution paths.
17
+ - AI MUST NOT use inconsistent command naming conventions.
18
+ - AI MUST NOT rely on interactive prompts for automation-only command paths.
19
+ Patterns:
20
+ - `public sealed class SyncCommand : AsyncCommand<SyncSettings>`
21
+ - `app.Configure(c => c.AddCommand<SyncCommand>("sync"));`
22
+ - `SyncCommand` depends on `ISyncService` via constructor injection.
23
+ - Output uses `AnsiConsole` rendering primitives.
24
+ Examples:
25
+ - `return 2; // invalid args`
26
+ - `await _syncService.RunAsync(cancellationToken);`
@@ -0,0 +1,26 @@
1
+ Rule: Enforce Configuration and Secrets
2
+ Requirements:
3
+ - AI MUST follow all `.ai/rules/*.md` files for configuration and secret handling.
4
+ - AI MUST store non-sensitive settings in `appsettings.json`.
5
+ - AI MUST support environment-specific overrides (`appsettings.Development.json`, `appsettings.Production.json`).
6
+ - AI MUST centralize config access through a typed configuration service.
7
+ - AI MUST load and validate required configuration at startup (fail fast on missing required values).
8
+ - AI MUST store secrets in platform secure storage only.
9
+ - AI MUST use a dedicated secrets service abstraction.
10
+ - AI MUST inject configuration and secrets services via DI.
11
+ - AI MUST use strongly typed option models where applicable.
12
+ - AI MUST keep runtime configuration immutable after startup load unless explicitly designed for reload.
13
+ Prohibited:
14
+ - AI MUST NOT store secrets in `appsettings*.json`.
15
+ - AI MUST NOT commit secrets, credentials, or tokens to source control.
16
+ - AI MUST NOT access configuration via scattered magic-string lookups.
17
+ - AI MUST NOT log secret configuration values.
18
+ - AI MUST NOT bypass startup validation of required settings.
19
+ Patterns:
20
+ - `Configuration/AppConfigurationService.cs`
21
+ - `Configuration/SecretsService.cs`
22
+ - `builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: false);`
23
+ - Bind and validate options at startup.
24
+ Examples:
25
+ - `public string ApiBaseUrl => _config["Api:BaseUrl"]!;`
26
+ - `await SecureStorage.SetAsync("AuthToken", token);`