@martinffx/atelier 1.4.0 → 3.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 (93) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +95 -220
  3. package/agents/architect.md +6 -6
  4. package/agents/oracle.md +1 -1
  5. package/agents/{recon.md → sentinel.md} +3 -3
  6. package/dist/atelier.js +167 -26
  7. package/package.json +2 -1
  8. package/skills/atelier-orchestrator/SKILL.md +180 -0
  9. package/skills/atelier-setup/SKILL.md +80 -0
  10. package/skills/code-review/SKILL.md +2 -2
  11. package/skills/code-review/references/reviewers.md +3 -3
  12. package/skills/code-review/references/rq.md +9 -9
  13. package/skills/code-subagents/SKILL.md +44 -75
  14. package/skills/code-subagents/references/batch-reviewer-prompt.md +44 -0
  15. package/skills/code-subagents/references/implementor-prompt.md +10 -11
  16. package/skills/spec-brainstorm/SKILL.md +21 -16
  17. package/skills/spec-finish/SKILL.md +14 -14
  18. package/skills/spec-implement/SKILL.md +42 -42
  19. package/skills/spec-plan/SKILL.md +72 -26
  20. package/skills/spec-plan/references/plan_template.md +77 -0
  21. package/skills/code-subagents/references/code-quality-reviewer-prompt.md +0 -48
  22. package/skills/code-subagents/references/spec-reviewer-prompt.md +0 -62
  23. package/skills/python-architecture/SKILL.md +0 -148
  24. package/skills/python-architecture/references/data-modeling.md +0 -558
  25. package/skills/python-architecture/references/ddd.md +0 -569
  26. package/skills/python-architecture/references/functional-core.md +0 -337
  27. package/skills/python-build-tools/SKILL.md +0 -153
  28. package/skills/python-build-tools/references/basedpyright.md +0 -321
  29. package/skills/python-build-tools/references/ruff.md +0 -225
  30. package/skills/python-build-tools/references/uv.md +0 -257
  31. package/skills/python-fastapi/SKILL.md +0 -184
  32. package/skills/python-fastapi/references/api-design.md +0 -563
  33. package/skills/python-fastapi/references/dependencies.md +0 -479
  34. package/skills/python-fastapi/references/middleware.md +0 -475
  35. package/skills/python-fastapi/references/validation.md +0 -497
  36. package/skills/python-modern-python/SKILL.md +0 -201
  37. package/skills/python-modern-python/references/async.md +0 -76
  38. package/skills/python-modern-python/references/pattern-matching.md +0 -89
  39. package/skills/python-modern-python/references/typing.md +0 -96
  40. package/skills/python-monorepo/SKILL.md +0 -146
  41. package/skills/python-monorepo/references/docker.md +0 -515
  42. package/skills/python-monorepo/references/namespace-packages.md +0 -426
  43. package/skills/python-monorepo/references/workspace-config.md +0 -341
  44. package/skills/python-sqlalchemy/SKILL.md +0 -119
  45. package/skills/python-sqlalchemy/references/async.md +0 -51
  46. package/skills/python-sqlalchemy/references/models.md +0 -67
  47. package/skills/python-sqlalchemy/references/queries.md +0 -57
  48. package/skills/python-temporal/SKILL.md +0 -128
  49. package/skills/python-temporal/references/patterns.md +0 -87
  50. package/skills/python-temporal/references/testing.md +0 -54
  51. package/skills/python-testing/SKILL.md +0 -161
  52. package/skills/python-testing/references/boundaries.md +0 -449
  53. package/skills/python-testing/references/mocking.md +0 -449
  54. package/skills/python-testing/references/pytest.md +0 -524
  55. package/skills/spec-orchestrator/SKILL.md +0 -145
  56. package/skills/typescript-api-design/SKILL.md +0 -396
  57. package/skills/typescript-api-design/references/error-responses.md +0 -469
  58. package/skills/typescript-build-tools/SKILL.md +0 -447
  59. package/skills/typescript-build-tools/references/biome.md +0 -436
  60. package/skills/typescript-build-tools/references/bun.md +0 -432
  61. package/skills/typescript-build-tools/references/turborepo.md +0 -426
  62. package/skills/typescript-build-tools/references/vitest.md +0 -518
  63. package/skills/typescript-drizzle-orm/SKILL.md +0 -398
  64. package/skills/typescript-drizzle-orm/references/cloudflare.md +0 -392
  65. package/skills/typescript-drizzle-orm/references/entity-pattern.md +0 -412
  66. package/skills/typescript-drizzle-orm/references/postgresql.md +0 -191
  67. package/skills/typescript-drizzle-orm/references/repository-pattern.md +0 -479
  68. package/skills/typescript-drizzle-orm/references/sqlite.md +0 -291
  69. package/skills/typescript-dynamodb-toolbox/SKILL.md +0 -370
  70. package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +0 -574
  71. package/skills/typescript-dynamodb-toolbox/references/error-handling.md +0 -495
  72. package/skills/typescript-dynamodb-toolbox/references/modeling.md +0 -332
  73. package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +0 -631
  74. package/skills/typescript-dynamodb-toolbox/references/testing.md +0 -718
  75. package/skills/typescript-dynamodb-toolbox/references/transactions.md +0 -423
  76. package/skills/typescript-effect-ts/SKILL.md +0 -341
  77. package/skills/typescript-effect-ts/references/core.md +0 -412
  78. package/skills/typescript-effect-ts/references/error-handling.md +0 -468
  79. package/skills/typescript-effect-ts/references/resources.md +0 -476
  80. package/skills/typescript-effect-ts/references/schema.md +0 -536
  81. package/skills/typescript-effect-ts/references/services.md +0 -461
  82. package/skills/typescript-fastify/SKILL.md +0 -405
  83. package/skills/typescript-fastify/references/plugins.md +0 -329
  84. package/skills/typescript-fastify/references/typeid.md +0 -315
  85. package/skills/typescript-functional-patterns/SKILL.md +0 -402
  86. package/skills/typescript-functional-patterns/references/adts.md +0 -497
  87. package/skills/typescript-functional-patterns/references/branded-types.md +0 -542
  88. package/skills/typescript-functional-patterns/references/migration-guide.md +0 -695
  89. package/skills/typescript-functional-patterns/references/option-result.md +0 -585
  90. package/skills/typescript-testing/SKILL.md +0 -353
  91. package/skills/typescript-testing/references/mocking.md +0 -675
  92. package/skills/typescript-testing/references/msw.md +0 -852
  93. package/skills/typescript-testing/references/snapshot-testing.md +0 -774
@@ -1,495 +0,0 @@
1
- # Error Handling Patterns
2
-
3
- Comprehensive error handling for DynamoDB operations with domain error mapping.
4
-
5
- ## DynamoDB Error Types
6
-
7
- ### ConditionalCheckFailedException
8
-
9
- Thrown when a conditional expression evaluates to false.
10
-
11
- **Common Causes:**
12
- - Create operation: Item already exists (`exists: false` failed)
13
- - Update operation: Item doesn't exist (`exists: true` failed)
14
- - Custom conditions: Business rule validation failed
15
-
16
- **From AWS SDK:**
17
- ```typescript
18
- import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb'
19
- ```
20
-
21
- ### DynamoDBToolboxError
22
-
23
- Thrown by dynamodb-toolbox for schema validation errors.
24
-
25
- **Common Causes:**
26
- - Required field missing
27
- - Type mismatch (string vs number)
28
- - Validation function returned false
29
- - Invalid attribute name
30
-
31
- **Properties:**
32
- - `path`: Field path that failed validation
33
- - `message`: Error description
34
-
35
- ### TransactionCanceledException
36
-
37
- Thrown when a transaction fails.
38
-
39
- **Key Property:**
40
- - `CancellationReasons`: Array explaining why each operation failed
41
-
42
- ## Domain Error Classes
43
-
44
- Define domain-specific errors that clients can handle meaningfully.
45
-
46
- ```typescript
47
- // src/shared/errors.ts
48
-
49
- export class DuplicateEntityError extends Error {
50
- public readonly entityType: string
51
- public readonly entityKey: string
52
-
53
- constructor(entityType: string, entityKey: string) {
54
- super(`${entityType} '${entityKey}' already exists`)
55
- this.name = "DuplicateEntityError"
56
- this.entityType = entityType
57
- this.entityKey = entityKey
58
- }
59
- }
60
-
61
- export class EntityNotFoundError extends Error {
62
- public readonly entityType: string
63
- public readonly entityKey: string
64
-
65
- constructor(entityType: string, entityKey: string) {
66
- super(`${entityType} '${entityKey}' not found`)
67
- this.name = "EntityNotFoundError"
68
- this.entityType = entityType
69
- this.entityKey = entityKey
70
- }
71
- }
72
-
73
- export class ValidationError extends Error {
74
- public readonly field: string
75
-
76
- constructor(field: string, message: string) {
77
- super(`Validation failed for '${field}': ${message}`)
78
- this.name = "ValidationError"
79
- this.field = field
80
- }
81
- }
82
- ```
83
-
84
- ## Create Operation Error Handling
85
-
86
- **Context:** Creating a new entity with duplicate prevention
87
-
88
- ```typescript
89
- import { PutItemCommand } from 'dynamodb-toolbox'
90
- import { ConditionalCheckFailedException } from '@aws-sdk/client-dynamodb'
91
- import { DynamoDBToolboxError } from 'dynamodb-toolbox'
92
-
93
- async create(user: UserEntity): Promise<UserEntity> {
94
- try {
95
- const result = await this.entity
96
- .build(PutItemCommand)
97
- .item(user.toRecord())
98
- .options({
99
- condition: { attr: "PK", exists: false } // Prevent duplicates
100
- })
101
- .send()
102
-
103
- return UserEntity.fromRecord(result.ToolboxItem)
104
- } catch (error: unknown) {
105
- // Pattern: ConditionalCheckFailed → DuplicateEntityError
106
- if (error instanceof ConditionalCheckFailedException) {
107
- throw new DuplicateEntityError("UserEntity", user.username)
108
- }
109
-
110
- // Pattern: Schema validation → ValidationError
111
- if (error instanceof DynamoDBToolboxError) {
112
- throw new ValidationError(
113
- error.path ?? "entity",
114
- error.message
115
- )
116
- }
117
-
118
- // Unknown errors: re-throw
119
- throw error
120
- }
121
- }
122
- ```
123
-
124
- **Key Points:**
125
- - `ConditionalCheckFailedException` → Item already exists
126
- - Map to `DuplicateEntityError` with entity type and key
127
- - Client can catch and show user-friendly message: "Username 'alice' already taken"
128
-
129
- ## Update Operation Error Handling
130
-
131
- **Context:** Updating an existing entity with existence check
132
-
133
- ```typescript
134
- async update(user: UserEntity): Promise<UserEntity> {
135
- try {
136
- const result = await this.entity
137
- .build(PutItemCommand)
138
- .item(user.toRecord())
139
- .options({
140
- condition: { attr: "PK", exists: true } // Must exist
141
- })
142
- .send()
143
-
144
- return UserEntity.fromRecord(result.ToolboxItem)
145
- } catch (error: unknown) {
146
- // Pattern: ConditionalCheckFailed → EntityNotFoundError
147
- if (error instanceof ConditionalCheckFailedException) {
148
- throw new EntityNotFoundError("UserEntity", user.username)
149
- }
150
-
151
- if (error instanceof DynamoDBToolboxError) {
152
- throw new ValidationError(error.path ?? "entity", error.message)
153
- }
154
-
155
- throw error
156
- }
157
- }
158
- ```
159
-
160
- **Same Exception, Different Meaning:**
161
- - Create: `ConditionalCheckFailed` → "already exists"
162
- - Update: `ConditionalCheckFailed` → "not found"
163
-
164
- Context determines the domain error mapping.
165
-
166
- ## Transaction Error Handling
167
-
168
- **Context:** Multi-entity transaction (create issue, check repo exists)
169
-
170
- ```typescript
171
- import { TransactionCanceledException } from '@aws-sdk/client-dynamodb'
172
-
173
- async createIssue(issue: IssueEntity): Promise<IssueEntity> {
174
- try {
175
- // Transaction: PutTransaction + ConditionCheck
176
- await execute(putIssueTransaction, repoCheckTransaction)
177
-
178
- return await this.get(/* ... */)
179
- } catch (error: unknown) {
180
- handleTransactionError(error, {
181
- entityType: "IssueEntity",
182
- entityKey: issue.getEntityKey(),
183
- parentEntityType: "RepositoryEntity",
184
- parentEntityKey: issue.getParentEntityKey(),
185
- operationName: "issue",
186
- })
187
- }
188
- }
189
- ```
190
-
191
- ### Transaction Error Handler
192
-
193
- ```typescript
194
- function handleTransactionError(
195
- error: unknown,
196
- options: {
197
- entityType: string
198
- entityKey: string
199
- parentEntityType: string
200
- parentEntityKey: string
201
- operationName: string
202
- }
203
- ): never {
204
- if (error instanceof TransactionCanceledException) {
205
- const reasons = error.CancellationReasons || []
206
-
207
- // Validate we have expected number of cancellation reasons
208
- if (reasons.length < 2) {
209
- throw new ValidationError(
210
- "transaction",
211
- `Transaction failed with unexpected cancellation reason count: ${reasons.length}`
212
- )
213
- }
214
-
215
- // Index 0: The entity create/put operation
216
- if (reasons[0]?.Code === "ConditionalCheckFailed") {
217
- throw new DuplicateEntityError(options.entityType, options.entityKey)
218
- }
219
-
220
- // Index 1: The parent existence check
221
- if (reasons[1]?.Code === "ConditionalCheckFailed") {
222
- throw new EntityNotFoundError(
223
- options.parentEntityType,
224
- options.parentEntityKey
225
- )
226
- }
227
-
228
- // Unknown transaction failure
229
- throw new ValidationError(
230
- options.operationName,
231
- `Failed to create ${options.operationName} due to transaction conflict`
232
- )
233
- }
234
-
235
- if (error instanceof DynamoDBToolboxError) {
236
- throw new ValidationError(error.path ?? "entity", error.message)
237
- }
238
-
239
- throw error
240
- }
241
- ```
242
-
243
- **CancellationReasons Structure:**
244
- ```typescript
245
- [
246
- { Code: "ConditionalCheckFailed" }, // Index 0: PutTransaction failed
247
- { Code: "ConditionalCheckFailed" }, // Index 1: ConditionCheck failed
248
- ]
249
- ```
250
-
251
- **Mapping:**
252
- - `reasons[0]` failed → Entity already exists (DuplicateEntityError)
253
- - `reasons[1]` failed → Parent doesn't exist (EntityNotFoundError)
254
-
255
- **Order Matters:** CancellationReasons array matches the order operations were passed to `execute()`.
256
-
257
- ## Three-Way Transaction Error Handling
258
-
259
- **Context:** Create star with user + repo validation
260
-
261
- ```typescript
262
- async createStar(star: StarEntity): Promise<StarEntity> {
263
- try {
264
- // Transaction: PutTransaction + UserCheck + RepoCheck
265
- await execute(putStarTransaction, userCheck, repoCheck)
266
-
267
- return await this.get(/* ... */)
268
- } catch (error: unknown) {
269
- handleThreeWayTransactionError(error, star)
270
- }
271
- }
272
-
273
- function handleThreeWayTransactionError(
274
- error: unknown,
275
- star: StarEntity
276
- ): never {
277
- if (error instanceof TransactionCanceledException) {
278
- const reasons = error.CancellationReasons || []
279
-
280
- if (reasons.length < 3) {
281
- throw new ValidationError(
282
- "transaction",
283
- `Expected 3 cancellation reasons, got ${reasons.length}`
284
- )
285
- }
286
-
287
- // Index 0: Star creation
288
- if (reasons[0]?.Code === "ConditionalCheckFailed") {
289
- throw new DuplicateEntityError(
290
- "StarEntity",
291
- star.getEntityKey()
292
- )
293
- }
294
-
295
- // Index 1: User existence check
296
- if (reasons[1]?.Code === "ConditionalCheckFailed") {
297
- throw new EntityNotFoundError("UserEntity", star.username)
298
- }
299
-
300
- // Index 2: Repo existence check
301
- if (reasons[2]?.Code === "ConditionalCheckFailed") {
302
- throw new EntityNotFoundError(
303
- "RepositoryEntity",
304
- `${star.repoOwner}/${star.repoName}`
305
- )
306
- }
307
-
308
- throw new ValidationError(
309
- "star",
310
- "Failed to create star due to transaction conflict"
311
- )
312
- }
313
-
314
- throw error
315
- }
316
- ```
317
-
318
- ## Reusable Error Handlers
319
-
320
- Create utility functions for common patterns:
321
-
322
- ```typescript
323
- // src/repos/utils.ts
324
-
325
- export function handleCreateError(
326
- error: unknown,
327
- entityType: string,
328
- entityKey: string
329
- ): never {
330
- if (error instanceof ConditionalCheckFailedException) {
331
- throw new DuplicateEntityError(entityType, entityKey)
332
- }
333
- if (error instanceof DynamoDBToolboxError) {
334
- throw new ValidationError(error.path ?? "entity", error.message)
335
- }
336
- throw error
337
- }
338
-
339
- export function handleUpdateError(
340
- error: unknown,
341
- entityType: string,
342
- entityKey: string
343
- ): never {
344
- if (error instanceof ConditionalCheckFailedException) {
345
- throw new EntityNotFoundError(entityType, entityKey)
346
- }
347
- if (error instanceof DynamoDBToolboxError) {
348
- throw new ValidationError(error.path ?? "entity", error.message)
349
- }
350
- throw error
351
- }
352
- ```
353
-
354
- **Usage:**
355
-
356
- ```typescript
357
- async create(user: UserEntity): Promise<UserEntity> {
358
- try {
359
- const result = await this.entity
360
- .build(PutItemCommand)
361
- .item(user.toRecord())
362
- .options({ condition: { attr: "PK", exists: false } })
363
- .send()
364
-
365
- return UserEntity.fromRecord(result.ToolboxItem)
366
- } catch (error: unknown) {
367
- handleCreateError(error, "UserEntity", user.username)
368
- }
369
- }
370
- ```
371
-
372
- ## Testing Error Handling
373
-
374
- ### Test Duplicate Creation
375
-
376
- ```typescript
377
- it("should throw DuplicateEntityError for duplicate username", async () => {
378
- const user1 = createUserEntity({ username: "alice" })
379
- const user2 = createUserEntity({ username: "alice" })
380
-
381
- await userRepo.create(user1)
382
-
383
- await expect(userRepo.create(user2)).rejects.toThrow(DuplicateEntityError)
384
- await expect(userRepo.create(user2)).rejects.toThrow("already exists")
385
- })
386
- ```
387
-
388
- ### Test Update Non-Existent
389
-
390
- ```typescript
391
- it("should throw EntityNotFoundError when updating non-existent user", async () => {
392
- const user = createUserEntity({ username: "nonexistent" })
393
-
394
- await expect(userRepo.update(user)).rejects.toThrow(EntityNotFoundError)
395
- await expect(userRepo.update(user)).rejects.toThrow("not found")
396
- })
397
- ```
398
-
399
- ### Test Transaction Validation
400
-
401
- ```typescript
402
- it("should throw EntityNotFoundError when creating issue for non-existent repo", async () => {
403
- const issue = IssueEntity.fromRequest({
404
- owner: "alice",
405
- repo_name: "nonexistent",
406
- title: "Test Issue",
407
- author: "bob",
408
- })
409
-
410
- await expect(issueRepo.create(issue)).rejects.toThrow(EntityNotFoundError)
411
- await expect(issueRepo.create(issue)).rejects.toThrow("RepositoryEntity")
412
- })
413
- ```
414
-
415
- ## Error Response Formatting (API Layer)
416
-
417
- Convert domain errors to HTTP responses:
418
-
419
- ```typescript
420
- // src/routes/users.ts (Fastify example)
421
-
422
- app.post("/users", async (request, reply) => {
423
- try {
424
- const user = UserEntity.fromRequest(request.body)
425
- const created = await userService.create(user)
426
- return reply.status(201).send(created.toResponse())
427
- } catch (error) {
428
- if (error instanceof DuplicateEntityError) {
429
- return reply.status(409).send({
430
- error: "Conflict",
431
- message: error.message,
432
- entityType: error.entityType,
433
- entityKey: error.entityKey,
434
- })
435
- }
436
-
437
- if (error instanceof ValidationError) {
438
- return reply.status(400).send({
439
- error: "Bad Request",
440
- message: error.message,
441
- field: error.field,
442
- })
443
- }
444
-
445
- if (error instanceof EntityNotFoundError) {
446
- return reply.status(404).send({
447
- error: "Not Found",
448
- message: error.message,
449
- })
450
- }
451
-
452
- // Unknown errors: 500
453
- console.error("Unexpected error:", error)
454
- return reply.status(500).send({
455
- error: "Internal Server Error",
456
- message: "An unexpected error occurred",
457
- })
458
- }
459
- })
460
- ```
461
-
462
- **HTTP Status Code Mapping:**
463
- - `DuplicateEntityError` → 409 Conflict
464
- - `EntityNotFoundError` → 404 Not Found
465
- - `ValidationError` → 400 Bad Request
466
- - Unknown → 500 Internal Server Error
467
-
468
- ## Best Practices
469
-
470
- ### ✓ Do
471
-
472
- - Always catch `ConditionalCheckFailedException` and map to domain errors
473
- - Include entity type and key in error messages
474
- - Inspect `CancellationReasons` array order for transactions
475
- - Log unexpected errors before re-throwing
476
- - Test error handling paths explicitly
477
-
478
- ### ✗ Don't
479
-
480
- - Don't expose DynamoDB error details to clients
481
- - Don't assume transaction error order - always check CancellationReasons
482
- - Don't ignore `DynamoDBToolboxError` - it indicates schema issues
483
- - Don't retry blindly on transaction errors (may have partial state)
484
-
485
- ## Error Handling Checklist
486
-
487
- For each repository operation:
488
-
489
- - [ ] **Create operations** - Catch `ConditionalCheckFailed` → `DuplicateEntityError`
490
- - [ ] **Update operations** - Catch `ConditionalCheckFailed` → `EntityNotFoundError`
491
- - [ ] **Transactions** - Parse `CancellationReasons` by index
492
- - [ ] **Schema validation** - Catch `DynamoDBToolboxError` → `ValidationError`
493
- - [ ] **Unknown errors** - Re-throw with logging
494
- - [ ] **API layer** - Map domain errors to HTTP status codes
495
- - [ ] **Tests** - Verify error handling for each failure scenario