cfsa-antigravity 2.0.0 → 2.2.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 (116) hide show
  1. package/README.md +14 -0
  2. package/package.json +1 -1
  3. package/template/.agent/instructions/commands.md +8 -32
  4. package/template/.agent/instructions/example.md +21 -0
  5. package/template/.agent/instructions/patterns.md +3 -3
  6. package/template/.agent/instructions/tech-stack.md +71 -23
  7. package/template/.agent/instructions/workflow.md +12 -1
  8. package/template/.agent/rules/completion-checklist.md +6 -0
  9. package/template/.agent/rules/security-first.md +3 -3
  10. package/template/.agent/rules/vertical-slices.md +1 -1
  11. package/template/.agent/skill-library/MANIFEST.md +6 -0
  12. package/template/.agent/skill-library/stack/devops/git-advanced/SKILL.md +972 -0
  13. package/template/.agent/skill-library/stack/devops/git-workflow/SKILL.md +420 -0
  14. package/template/.agent/skills/api-versioning/SKILL.md +44 -298
  15. package/template/.agent/skills/api-versioning/references/typescript.md +157 -0
  16. package/template/.agent/skills/architecture-mapping/SKILL.md +13 -13
  17. package/template/.agent/skills/bootstrap-agents/SKILL.md +151 -152
  18. package/template/.agent/skills/clean-code/SKILL.md +64 -118
  19. package/template/.agent/skills/clean-code/references/typescript.md +126 -0
  20. package/template/.agent/skills/database-schema-design/SKILL.md +93 -317
  21. package/template/.agent/skills/database-schema-design/references/relational.md +228 -0
  22. package/template/.agent/skills/error-handling-patterns/SKILL.md +62 -557
  23. package/template/.agent/skills/error-handling-patterns/references/go.md +162 -0
  24. package/template/.agent/skills/error-handling-patterns/references/python.md +262 -0
  25. package/template/.agent/skills/error-handling-patterns/references/rust.md +112 -0
  26. package/template/.agent/skills/error-handling-patterns/references/typescript.md +178 -0
  27. package/template/.agent/skills/idea-extraction/SKILL.md +322 -224
  28. package/template/.agent/skills/logging-best-practices/SKILL.md +108 -767
  29. package/template/.agent/skills/logging-best-practices/references/go.md +49 -0
  30. package/template/.agent/skills/logging-best-practices/references/python.md +52 -0
  31. package/template/.agent/skills/logging-best-practices/references/typescript.md +215 -0
  32. package/template/.agent/skills/migration-management/SKILL.md +127 -311
  33. package/template/.agent/skills/migration-management/references/relational.md +214 -0
  34. package/template/.agent/skills/parallel-feature-development/SKILL.md +34 -43
  35. package/template/.agent/skills/pipeline-rubrics/references/be-rubric.md +1 -1
  36. package/template/.agent/skills/pipeline-rubrics/references/ia-rubric.md +2 -2
  37. package/template/.agent/skills/pipeline-rubrics/references/scoring.md +1 -1
  38. package/template/.agent/skills/pipeline-rubrics/references/vision-rubric.md +2 -1
  39. package/template/.agent/skills/prd-templates/SKILL.md +23 -6
  40. package/template/.agent/skills/prd-templates/references/be-spec-template.md +2 -2
  41. package/template/.agent/skills/prd-templates/references/decomposition-templates.md +2 -2
  42. package/template/.agent/skills/prd-templates/references/engineering-standards-template.md +2 -0
  43. package/template/.agent/skills/prd-templates/references/fe-spec-template.md +1 -1
  44. package/template/.agent/skills/prd-templates/references/fractal-cx-template.md +58 -0
  45. package/template/.agent/skills/prd-templates/references/fractal-feature-template.md +93 -0
  46. package/template/.agent/skills/prd-templates/references/fractal-node-index-template.md +55 -0
  47. package/template/.agent/skills/prd-templates/references/ideation-crosscut-template.md +26 -47
  48. package/template/.agent/skills/prd-templates/references/ideation-index-template.md +47 -31
  49. package/template/.agent/skills/prd-templates/references/operational-templates.md +1 -1
  50. package/template/.agent/skills/prd-templates/references/placeholder-workflow-mapping.md +50 -21
  51. package/template/.agent/skills/prd-templates/references/skill-loading-protocol.md +32 -0
  52. package/template/.agent/skills/prd-templates/references/slice-completion-gates.md +29 -0
  53. package/template/.agent/skills/prd-templates/references/spec-coverage-sweep.md +3 -3
  54. package/template/.agent/skills/prd-templates/references/tdd-testing-policy.md +39 -0
  55. package/template/.agent/skills/prd-templates/references/vision-template.md +8 -8
  56. package/template/.agent/skills/regex-patterns/SKILL.md +122 -540
  57. package/template/.agent/skills/regex-patterns/references/go.md +44 -0
  58. package/template/.agent/skills/regex-patterns/references/javascript.md +63 -0
  59. package/template/.agent/skills/regex-patterns/references/python.md +77 -0
  60. package/template/.agent/skills/regex-patterns/references/rust.md +43 -0
  61. package/template/.agent/skills/resolve-ambiguity/SKILL.md +1 -1
  62. package/template/.agent/skills/session-continuity/SKILL.md +11 -9
  63. package/template/.agent/skills/session-continuity/protocols/02-progress-generation.md +2 -2
  64. package/template/.agent/skills/session-continuity/protocols/04-pattern-extraction.md +1 -1
  65. package/template/.agent/skills/session-continuity/protocols/05-session-close.md +1 -1
  66. package/template/.agent/skills/session-continuity/protocols/09-parallel-claim.md +1 -1
  67. package/template/.agent/skills/session-continuity/protocols/10-placeholder-verification-gate.md +57 -78
  68. package/template/.agent/skills/session-continuity/protocols/11-parallel-synthesis.md +1 -1
  69. package/template/.agent/skills/spec-writing/SKILL.md +1 -1
  70. package/template/.agent/skills/tdd-workflow/SKILL.md +94 -317
  71. package/template/.agent/skills/tdd-workflow/references/typescript.md +231 -0
  72. package/template/.agent/skills/testing-strategist/SKILL.md +74 -687
  73. package/template/.agent/skills/testing-strategist/references/typescript.md +328 -0
  74. package/template/.agent/skills/workflow-automation/SKILL.md +62 -154
  75. package/template/.agent/skills/workflow-automation/references/inngest.md +88 -0
  76. package/template/.agent/skills/workflow-automation/references/temporal.md +64 -0
  77. package/template/.agent/workflows/bootstrap-agents-fill.md +85 -143
  78. package/template/.agent/workflows/bootstrap-agents-provision.md +90 -107
  79. package/template/.agent/workflows/create-prd-architecture.md +23 -16
  80. package/template/.agent/workflows/create-prd-compile.md +11 -12
  81. package/template/.agent/workflows/create-prd-design-system.md +1 -1
  82. package/template/.agent/workflows/create-prd-security.md +9 -11
  83. package/template/.agent/workflows/create-prd-stack.md +10 -4
  84. package/template/.agent/workflows/create-prd.md +9 -9
  85. package/template/.agent/workflows/decompose-architecture-structure.md +4 -6
  86. package/template/.agent/workflows/decompose-architecture-validate.md +18 -1
  87. package/template/.agent/workflows/decompose-architecture.md +18 -3
  88. package/template/.agent/workflows/evolve-contract.md +11 -11
  89. package/template/.agent/workflows/evolve-feature-classify.md +14 -6
  90. package/template/.agent/workflows/ideate-discover.md +72 -107
  91. package/template/.agent/workflows/ideate-extract.md +84 -63
  92. package/template/.agent/workflows/ideate-validate.md +26 -22
  93. package/template/.agent/workflows/ideate.md +9 -9
  94. package/template/.agent/workflows/implement-slice-setup.md +25 -23
  95. package/template/.agent/workflows/implement-slice-tdd.md +73 -89
  96. package/template/.agent/workflows/implement-slice.md +4 -4
  97. package/template/.agent/workflows/plan-phase-preflight.md +6 -2
  98. package/template/.agent/workflows/plan-phase-write.md +6 -8
  99. package/template/.agent/workflows/remediate-pipeline-assess.md +2 -1
  100. package/template/.agent/workflows/resolve-ambiguity.md +2 -2
  101. package/template/.agent/workflows/update-architecture-map.md +22 -5
  102. package/template/.agent/workflows/validate-phase-quality.md +155 -0
  103. package/template/.agent/workflows/validate-phase-readiness.md +167 -0
  104. package/template/.agent/workflows/validate-phase.md +19 -157
  105. package/template/.agent/workflows/verify-infrastructure.md +10 -10
  106. package/template/.agent/workflows/write-architecture-spec-design.md +23 -14
  107. package/template/.agent/workflows/write-be-spec-classify.md +25 -21
  108. package/template/.agent/workflows/write-be-spec.md +1 -1
  109. package/template/.agent/workflows/write-fe-spec-classify.md +6 -12
  110. package/template/.agent/workflows/write-fe-spec-write.md +1 -1
  111. package/template/AGENTS.md +6 -2
  112. package/template/GEMINI.md +5 -3
  113. package/template/docs/README.md +10 -10
  114. package/template/docs/kit-architecture.md +126 -33
  115. package/template/docs/plans/ideation/README.md +8 -3
  116. package/template/.agent/skills/prd-templates/references/ideation-domain-template.md +0 -55
@@ -0,0 +1,328 @@
1
+ # TypeScript Testing Patterns
2
+
3
+ Language-specific patterns for the `testing-strategist` skill. Read `SKILL.md` first for universal methodology.
4
+
5
+ ---
6
+
7
+ ## Framework Stack
8
+
9
+ | Level | Tool | Purpose |
10
+ |-------|------|---------|
11
+ | Unit | **Jest** or **Vitest** | Test runner, assertions, mocking |
12
+ | Component | **React Testing Library** | User-centric component testing |
13
+ | Integration | **Supertest** | HTTP API testing |
14
+ | E2E | **Playwright** | Browser automation |
15
+
16
+ ## Commands
17
+
18
+ ```bash
19
+ npm test # Run tests
20
+ npm test -- --watch # Watch mode
21
+ npm run test:coverage # Coverage report
22
+ npx playwright test # E2E tests
23
+ open coverage/lcov-report/index.html # View HTML report
24
+ ```
25
+
26
+ ---
27
+
28
+ ## Unit Test: Business Logic
29
+
30
+ ```typescript
31
+ // src/lib/pricing.ts
32
+ export function calculateTotal(items: { price: number; quantity: number }[]) {
33
+ return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
34
+ }
35
+
36
+ export function applyDiscount(total: number, discountPercent: number) {
37
+ if (discountPercent < 0 || discountPercent > 100) {
38
+ throw new Error('Invalid discount percentage')
39
+ }
40
+ return total * (1 - discountPercent / 100)
41
+ }
42
+
43
+ // src/lib/pricing.test.ts
44
+ import { calculateTotal, applyDiscount } from './pricing'
45
+
46
+ describe('calculateTotal', () => {
47
+ it('calculates total for single item', () => {
48
+ const items = [{ price: 10, quantity: 2 }]
49
+ expect(calculateTotal(items)).toBe(20)
50
+ })
51
+
52
+ it('calculates total for multiple items', () => {
53
+ const items = [
54
+ { price: 10, quantity: 2 },
55
+ { price: 5, quantity: 3 }
56
+ ]
57
+ expect(calculateTotal(items)).toBe(35)
58
+ })
59
+
60
+ it('returns 0 for empty array', () => {
61
+ expect(calculateTotal([])).toBe(0)
62
+ })
63
+ })
64
+
65
+ describe('applyDiscount', () => {
66
+ it('applies discount correctly', () => {
67
+ expect(applyDiscount(100, 20)).toBe(80)
68
+ })
69
+
70
+ it('throws error for invalid discount', () => {
71
+ expect(() => applyDiscount(100, -10)).toThrow('Invalid discount')
72
+ expect(() => applyDiscount(100, 150)).toThrow('Invalid discount')
73
+ })
74
+ })
75
+ ```
76
+
77
+ ## Unit Test: React Components
78
+
79
+ ```typescript
80
+ import { render, screen, fireEvent } from '@testing-library/react'
81
+ import { Button } from './Button'
82
+
83
+ describe('Button', () => {
84
+ it('renders with correct text', () => {
85
+ render(<Button>Click me</Button>)
86
+ expect(screen.getByText('Click me')).toBeInTheDocument()
87
+ })
88
+
89
+ it('applies primary variant by default', () => {
90
+ render(<Button>Click me</Button>)
91
+ expect(screen.getByRole('button')).toHaveClass('btn-primary')
92
+ })
93
+
94
+ it('calls onClick when clicked', () => {
95
+ const handleClick = jest.fn()
96
+ render(<Button onClick={handleClick}>Click me</Button>)
97
+
98
+ fireEvent.click(screen.getByRole('button'))
99
+ expect(handleClick).toHaveBeenCalledTimes(1)
100
+ })
101
+ })
102
+ ```
103
+
104
+ ## Unit Test: Custom Hooks
105
+
106
+ ```typescript
107
+ import { renderHook, act } from '@testing-library/react'
108
+ import { useCounter } from './useCounter'
109
+
110
+ describe('useCounter', () => {
111
+ it('initializes with default value', () => {
112
+ const { result } = renderHook(() => useCounter())
113
+ expect(result.current.count).toBe(0)
114
+ })
115
+
116
+ it('increments count', () => {
117
+ const { result } = renderHook(() => useCounter())
118
+ act(() => { result.current.increment() })
119
+ expect(result.current.count).toBe(1)
120
+ })
121
+
122
+ it('resets to initial value', () => {
123
+ const { result } = renderHook(() => useCounter(10))
124
+ act(() => {
125
+ result.current.increment()
126
+ result.current.reset()
127
+ })
128
+ expect(result.current.count).toBe(10)
129
+ })
130
+ })
131
+ ```
132
+
133
+ ---
134
+
135
+ ## Integration Test: API Routes
136
+
137
+ ```typescript
138
+ import { testClient } from '@/lib/test-utils'
139
+
140
+ describe('POST /api/posts', () => {
141
+ beforeEach(async () => {
142
+ await db.post.deleteMany()
143
+ })
144
+
145
+ it('creates a new post', async () => {
146
+ const response = await testClient
147
+ .post('/api/posts')
148
+ .set('Authorization', `Bearer ${authToken}`)
149
+ .send({ title: 'Test Post', content: 'This is a test post' })
150
+
151
+ expect(response.status).toBe(201)
152
+ expect(response.body).toMatchObject({
153
+ title: 'Test Post',
154
+ content: 'This is a test post'
155
+ })
156
+
157
+ // Verify in database
158
+ const posts = await db.post.findMany()
159
+ expect(posts).toHaveLength(1)
160
+ expect(posts[0].title).toBe('Test Post')
161
+ })
162
+
163
+ it('returns 400 for invalid data', async () => {
164
+ const response = await testClient
165
+ .post('/api/posts')
166
+ .set('Authorization', `Bearer ${authToken}`)
167
+ .send({ title: '' })
168
+
169
+ expect(response.status).toBe(400)
170
+ expect(response.body.errors).toBeDefined()
171
+ })
172
+
173
+ it('returns 401 for unauthenticated request', async () => {
174
+ const response = await testClient.post('/api/posts')
175
+ .send({ title: 'Test', content: 'Test' })
176
+
177
+ expect(response.status).toBe(401)
178
+ })
179
+ })
180
+ ```
181
+
182
+ ## Integration Test: Database Operations
183
+
184
+ ```typescript
185
+ describe('userRepository', () => {
186
+ beforeEach(async () => { await db.user.deleteMany() })
187
+ afterAll(async () => { await db.$disconnect() })
188
+
189
+ describe('createUser', () => {
190
+ it('creates user with hashed password', async () => {
191
+ const user = await createUser({
192
+ email: 'test@example.com',
193
+ password: 'password123'
194
+ })
195
+
196
+ expect(user.email).toBe('test@example.com')
197
+ expect(user.password).not.toBe('password123')
198
+ expect(user.password).toMatch(/^\$2[aby]/) // bcrypt hash
199
+ })
200
+
201
+ it('throws error for duplicate email', async () => {
202
+ await createUser({ email: 'test@example.com', password: 'pass' })
203
+ await expect(createUser({ email: 'test@example.com', password: 'pass' }))
204
+ .rejects.toThrow()
205
+ })
206
+ })
207
+ })
208
+ ```
209
+
210
+ ---
211
+
212
+ ## E2E Test: Playwright
213
+
214
+ ```typescript
215
+ import { test, expect } from '@playwright/test'
216
+
217
+ test.describe('Authentication', () => {
218
+ test('user can sign up and log in', async ({ page }) => {
219
+ await page.goto('/signup')
220
+ await page.fill('input[name="email"]', 'test@example.com')
221
+ await page.fill('input[name="password"]', 'SecurePass123!')
222
+ await page.fill('input[name="confirmPassword"]', 'SecurePass123!')
223
+ await page.click('button[type="submit"]')
224
+
225
+ await expect(page).toHaveURL('/dashboard')
226
+ await expect(page.locator('h1')).toContainText('Welcome')
227
+ })
228
+
229
+ test('shows error for invalid credentials', async ({ page }) => {
230
+ await page.goto('/login')
231
+ await page.fill('input[name="email"]', 'wrong@example.com')
232
+ await page.fill('input[name="password"]', 'wrongpassword')
233
+ await page.click('button[type="submit"]')
234
+
235
+ await expect(page.locator('[role="alert"]')).toContainText('Invalid credentials')
236
+ await expect(page).toHaveURL('/login')
237
+ })
238
+ })
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Mocking
244
+
245
+ ```typescript
246
+ // Mock external API
247
+ import { fetchUserData } from '@/lib/api'
248
+
249
+ jest.mock('@/lib/api')
250
+ const mockFetchUserData = fetchUserData as jest.MockedFunction<typeof fetchUserData>
251
+
252
+ it('displays user data', async () => {
253
+ mockFetchUserData.mockResolvedValue({
254
+ id: '1', name: 'John Doe', email: 'john@example.com'
255
+ })
256
+
257
+ render(<UserProfile userId="1" />)
258
+
259
+ await waitFor(() => {
260
+ expect(screen.getByText('John Doe')).toBeInTheDocument()
261
+ })
262
+ })
263
+
264
+ // Mock Date
265
+ beforeAll(() => {
266
+ jest.useFakeTimers()
267
+ jest.setSystemTime(new Date('2024-01-01'))
268
+ })
269
+ afterAll(() => { jest.useRealTimers() })
270
+
271
+ // Mock Math.random
272
+ const mockRandom = jest.spyOn(Math, 'random')
273
+ mockRandom.mockReturnValue(0.5)
274
+ // ... assertions ...
275
+ mockRandom.mockRestore()
276
+ ```
277
+
278
+ ---
279
+
280
+ ## Coverage Configuration
281
+
282
+ ```javascript
283
+ // jest.config.js
284
+ module.exports = {
285
+ collectCoverageFrom: [
286
+ 'src/**/*.{ts,tsx}',
287
+ '!src/**/*.d.ts',
288
+ '!src/**/*.stories.tsx',
289
+ '!src/types/**'
290
+ ],
291
+ coverageThresholds: {
292
+ global: {
293
+ branches: 70, functions: 70, lines: 70, statements: 70
294
+ },
295
+ './src/lib/auth/**': {
296
+ branches: 90, functions: 90, lines: 90
297
+ }
298
+ }
299
+ }
300
+ ```
301
+
302
+ ## CI/CD
303
+
304
+ ```yaml
305
+ - name: Run Tests
306
+ run: npm test -- --coverage
307
+ - name: Upload Coverage
308
+ uses: codecov/codecov-action@v3
309
+ ```
310
+
311
+ ## Assertion Examples
312
+
313
+ ### ❌ Shallow (BANNED)
314
+ ```typescript
315
+ expect(result).toBeDefined()
316
+ expect(result).toBeTruthy()
317
+ ```
318
+
319
+ ### ✅ Deep (REQUIRED)
320
+ ```typescript
321
+ expect(result).toEqual({
322
+ id: expect.any(String),
323
+ name: 'Test',
324
+ items: expect.arrayContaining([
325
+ expect.objectContaining({ amount: 100 })
326
+ ])
327
+ })
328
+ ```
@@ -1,10 +1,7 @@
1
1
  ---
2
2
  name: workflow-automation
3
- description: "Architect durable, event-driven workflows using platforms like Inngest, Temporal, and BullMQ. Covers step functions, retry strategies, idempotency, fan-out patterns, and the critical differences between orchestration approaches."
3
+ description: "Architect durable, event-driven workflows using step functions, retry strategies, idempotency, fan-out patterns, and the critical differences between orchestration approaches."
4
4
  version: 2.0.0
5
- source: self
6
- date_added: "2026-02-27"
7
- date_rewritten: "2026-03-14"
8
5
  ---
9
6
 
10
7
  # Workflow Automation
@@ -21,31 +18,39 @@ You are a workflow architect who understands the fundamental tradeoff: **simplic
21
18
 
22
19
  ## When NOT to Use
23
20
 
24
- - Simple fire-and-forget tasks (use a queue or `setTimeout`)
21
+ - Simple fire-and-forget tasks (use a queue or a delay)
25
22
  - Synchronous request-response flows
26
23
  - Tasks under 100ms that don't involve external services
27
24
 
25
+ ## Ecosystem-Specific References
26
+
27
+ After reading the methodology below, read the reference matching your orchestration platform:
28
+
29
+ | Platform | Reference | Best For |
30
+ |----------|-----------|----------|
31
+ | Inngest | `references/inngest.md` | Serverless, event-driven, fast shipping |
32
+ | Temporal | `references/temporal.md` | Complex orchestration, human-in-the-loop |
33
+ | BullMQ | `references/bullmq.md` | Simple job queues, rate limiting, Redis stack |
34
+
28
35
  ---
29
36
 
30
37
  ## Core Concept: Durable Execution
31
38
 
32
- The fundamental insight: **wrap each unit of work in a step, so the system can retry, resume, and checkpoint independently.**
39
+ **Wrap each unit of work in a step, so the system can retry, resume, and checkpoint independently.**
33
40
 
34
- Without durable execution:
35
- ```
36
- fetch data → process → save → send email
37
- ↑ server crashes here
38
- ↓ entire pipeline reruns from scratch
39
- ↓ customer gets duplicate email
40
41
  ```
42
+ Without durable execution:
43
+ fetch data → process → save → send email
44
+ ↑ server crashes here
45
+ ↓ entire pipeline reruns from scratch
46
+ ↓ customer gets duplicate email
41
47
 
42
48
  With durable execution:
43
- ```
44
- step("fetch") ✓ saved
45
- step("process") saved
46
- step("save") ← server crashes here
47
- step("save") retries ONLY this step
48
- step("email") ✓ runs once
49
+ step("fetch") ✓ saved
50
+ step("process") ✓ saved
51
+ step("save") server crashes here
52
+ step("save") ← retries ONLY this step
53
+ step("email") runs once
49
54
  ```
50
55
 
51
56
  ---
@@ -57,17 +62,17 @@ step("email") ✓ runs once
57
62
  | **Complexity** | Low (serverless functions) | High (workers + server) | Medium (Redis-backed) |
58
63
  | **Durability** | Steps checkpointed | Full workflow replay | Job-level retry |
59
64
  | **Infrastructure** | Managed or self-hosted | Requires Temporal Server | Requires Redis |
60
- | **Best for** | Serverless, Next.js, event-driven | Complex orchestration, long-running | Simple job queues, rate limiting |
65
+ | **Best for** | Serverless, event-driven | Complex orchestration | Simple job queues |
61
66
  | **Learning curve** | 1-2 days | 1-2 weeks | 1-2 days |
62
67
  | **Language support** | TS, Python, Go | TS, Go, Java, Python, .NET | TS/JS only |
63
68
 
64
69
  ### Decision Rules
65
70
 
66
71
  1. **Serverless + event-driven?** → Inngest
67
- 2. **Complex orchestration, human-in-the-loop, multi-day workflows?** → Temporal
68
- 3. **Simple job queue with rate limiting, Redis already in stack?** → BullMQ
72
+ 2. **Complex orchestration, human-in-the-loop, multi-day?** → Temporal
73
+ 3. **Simple job queue, Redis already in stack?** → BullMQ
69
74
  4. **Team of 1-3, shipping fast?** → Inngest
70
- 5. **Enterprise, compliance-heavy, existing Java/.NET stack?** → Temporal
75
+ 5. **Enterprise, compliance-heavy, Java/.NET stack?** → Temporal
71
76
 
72
77
  ---
73
78
 
@@ -75,85 +80,28 @@ step("email") ✓ runs once
75
80
 
76
81
  Each step depends on the previous result. Steps checkpoint independently.
77
82
 
78
- ### Inngest
79
-
80
- ```typescript
81
- const syncUser = inngest.createFunction(
82
- { id: "sync-user-data" },
83
- { event: "user/signup.completed" },
84
- async ({ event, step }) => {
85
- const user = await step.run("create-db-record", async () => {
86
- return db.users.create({ email: event.data.email });
87
- });
88
-
89
- const stripeCustomer = await step.run("create-stripe-customer", async () => {
90
- return stripe.customers.create({ email: user.email });
91
- });
92
-
93
- await step.run("send-welcome-email", async () => {
94
- return email.send({ to: user.email, template: "welcome" });
95
- });
96
- }
97
- );
98
83
  ```
99
-
100
- ### Temporal
101
-
102
- ```typescript
103
- // activities.ts
104
- export async function createDbRecord(email: string): Promise<User> {
105
- return db.users.create({ email });
106
- }
107
-
108
- export async function createStripeCustomer(email: string): Promise<Customer> {
109
- return stripe.customers.create({ email });
110
- }
111
-
112
- // workflows.ts
113
- const { createDbRecord, createStripeCustomer } = proxyActivities<typeof activities>({
114
- startToCloseTimeout: "30 seconds",
115
- retry: { initialInterval: "1s", maximumAttempts: 3 },
116
- });
117
-
118
- export async function syncUserWorkflow(email: string): Promise<void> {
119
- const user = await createDbRecord(email);
120
- const customer = await createStripeCustomer(user.email);
121
- }
84
+ step("create-db-record") → returns user
85
+ step("create-stripe-customer") → uses user.email
86
+ step("send-welcome-email") → uses user.email
122
87
  ```
123
88
 
89
+ If step 2 fails, only step 2 retries. Steps 1 and 3 are not re-executed.
90
+
124
91
  ---
125
92
 
126
93
  ## Pattern 2: Fan-Out (Parallel Execution)
127
94
 
128
95
  One event triggers multiple independent workflows. Each runs and retries independently.
129
96
 
130
- ### Inngest
131
-
132
- ```typescript
133
- // Each function subscribes to the same event — runs independently
134
- const sendWelcome = inngest.createFunction(
135
- { id: "send-welcome-email" },
136
- { event: "user/signup.completed" },
137
- async ({ event, step }) => {
138
- await step.run("send", async () => {
139
- await email.send({ to: event.data.email, template: "welcome" });
140
- });
141
- }
142
- );
143
-
144
- const startTrial = inngest.createFunction(
145
- { id: "start-stripe-trial" },
146
- { event: "user/signup.completed" },
147
- async ({ event, step }) => {
148
- await step.run("create-trial", async () => {
149
- await stripe.subscriptions.create({
150
- customer: event.data.stripeId,
151
- trial_period_days: 14,
152
- });
153
- });
154
- }
155
- );
156
97
  ```
98
+ Event: user/signup.completed
99
+ → Workflow A: send-welcome-email (independent)
100
+ → Workflow B: create-stripe-trial (independent)
101
+ → Workflow C: provision-workspace (independent)
102
+ ```
103
+
104
+ If Workflow B fails, Workflows A and C are unaffected.
157
105
 
158
106
  ---
159
107
 
@@ -161,72 +109,32 @@ const startTrial = inngest.createFunction(
161
109
 
162
110
  Prevent duplicate execution when the same event fires twice.
163
111
 
164
- ```typescript
165
- const processPayment = inngest.createFunction(
166
- {
167
- id: "process-payment",
168
- // Only runs once per unique orderId within 24h
169
- idempotency: "event.data.orderId",
170
- },
171
- { event: "order/payment.requested" },
172
- async ({ event, step }) => {
173
- await step.run("charge", async () => {
174
- return stripe.charges.create({
175
- amount: event.data.amount,
176
- idempotencyKey: event.data.orderId, // Stripe-level idempotency too
177
- });
178
- });
179
- }
180
- );
181
- ```
112
+ **Rule:** Idempotency at the workflow level AND the external call level. Belt and suspenders.
182
113
 
183
- **Rule**: Idempotency at the workflow level AND the external call level. Belt and suspenders.
114
+ - **Workflow level:** Deduplicate by event ID or business key (e.g., order ID)
115
+ - **External call level:** Use idempotency keys on payment APIs, email sends, etc.
184
116
 
185
117
  ---
186
118
 
187
119
  ## Pattern 4: Scheduled/Recurring
188
120
 
189
- ### Inngest
190
-
191
- ```typescript
192
- const dailyReport = inngest.createFunction(
193
- { id: "daily-metrics-report" },
194
- { cron: "0 9 * * *" }, // 9am daily
195
- async ({ step }) => {
196
- const metrics = await step.run("fetch-metrics", fetchDailyMetrics);
197
- await step.run("send-report", async () => sendSlackReport(metrics));
198
- }
199
- );
200
- ```
121
+ Cron-triggered workflows with observability:
122
+ - Scheduled trigger (e.g., daily at 9am)
123
+ - Steps checkpoint independently
124
+ - Failed runs are visible, retriable, and alertable
201
125
 
202
126
  ---
203
127
 
204
- ## Pattern 5: Human-in-the-Loop (Temporal)
128
+ ## Pattern 5: Human-in-the-Loop
205
129
 
206
130
  Workflows that pause and wait for external input — approval flows, multi-day processes.
207
131
 
208
- ```typescript
209
- export const approvalSignal = defineSignal<[boolean]>("approval");
210
-
211
- export async function purchaseWorkflow(amount: number): Promise<string> {
212
- if (amount > 10000) {
213
- let approved: boolean | undefined;
214
-
215
- setHandler(approvalSignal, (isApproved) => {
216
- approved = isApproved;
217
- });
218
-
219
- // Workflow sleeps until signal received or 72h timeout
220
- const gotApproval = await condition(() => approved !== undefined, "72 hours");
221
-
222
- if (!gotApproval || !approved) {
223
- return "Purchase denied or timed out";
224
- }
225
- }
226
-
227
- // Continue with purchase...
228
- return "Purchase completed";
229
- }
132
+ ```
133
+ workflow: purchaseWorkflow(amount)
134
+ if amount > threshold:
135
+ pause and wait for approval signal (up to 72h)
136
+ if no approval deny
137
+ continue with purchase
230
138
  ```
231
139
 
232
140
  ---
@@ -235,19 +143,19 @@ export async function purchaseWorkflow(amount: number): Promise<string> {
235
143
 
236
144
  | Issue | Severity | Rule |
237
145
  |-------|----------|------|
238
- | No idempotency keys on external calls | Critical | ALWAYS use idempotency keys for payments, emails, API mutations |
239
- | Side effects in workflow code (Temporal) | Critical | Workflows must be deterministic — no I/O, no random, no Date.now() |
240
- | Giant payloads in step results | High | Steps return serialized data — keep under 256KB |
241
- | No timeouts on activities | High | ALWAYS set `startToCloseTimeout` — default infinite hangs forever |
242
- | Linear retry without backoff | Medium | ALWAYS use exponential backoff: `initialInterval * backoffCoefficient^attempt` |
243
- | Missing dead letter handling | High | Failed-after-all-retries jobs need a destination — don't silently drop them |
244
- | Monolithic workflows | Medium | Break workflows >10 steps into child workflows or separate functions |
146
+ | No idempotency keys on external calls | Critical | ALWAYS use idempotency keys for payments, emails, mutations |
147
+ | Side effects in workflow code (Temporal) | Critical | Workflows must be deterministic — no I/O, no random, no clock |
148
+ | Giant payloads in step results | High | Steps serialize data — keep under 256KB |
149
+ | No timeouts on activities | High | ALWAYS set timeouts — default infinite hangs forever |
150
+ | Linear retry without backoff | Medium | ALWAYS use exponential backoff |
151
+ | Missing dead letter handling | High | Failed-after-all-retries jobs need a destination |
152
+ | Monolithic workflows | Medium | Break workflows >10 steps into child workflows |
245
153
 
246
154
  ## Anti-Patterns
247
155
 
248
156
  | Don't | Do |
249
157
  |-------|-----|
250
- | `setTimeout(fn, 3600000)` for delays | `step.sleep("wait-1h", "1h")` — survives restarts |
158
+ | `setTimeout(fn, 3600000)` for delays | Platform sleep — survives restarts |
251
159
  | Global variables for workflow state | Step results and explicit state passing |
252
160
  | Retry loops in application code | Platform-level retry policies |
253
161
  | Polling in a loop for completion | Event-driven triggers or workflow signals |
@@ -255,4 +163,4 @@ export async function purchaseWorkflow(amount: number): Promise<string> {
255
163
 
256
164
  ## Related Skills
257
165
 
258
- Works with: `error-handling-patterns`, `deployment-procedures`, `inngest` (skill-library), `bullmq` (skill-library)
166
+ Works with: `error-handling-patterns`, `deployment-procedures`