@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,469 +0,0 @@
1
- # Error Response Patterns
2
-
3
- Comprehensive error handling patterns using RFC 7807 Problem Details standard.
4
-
5
- ## Error Class Hierarchy
6
-
7
- ```typescript
8
- type ErrorContext = {
9
- [key: string]: unknown
10
- }
11
-
12
- // Base error class
13
- abstract class AppError extends Error {
14
- abstract readonly status: number
15
- abstract readonly type: string
16
-
17
- constructor(message: string, public readonly context?: ErrorContext) {
18
- super(message)
19
- this.name = this.constructor.name
20
- }
21
-
22
- toResponse(instance: string, traceId: string): ProblemDetail {
23
- return {
24
- type: this.type,
25
- status: this.status,
26
- title: this.name.replace(/Error$/, '').replace(/([A-Z])/g, ' $1').trim(),
27
- detail: this.message,
28
- instance,
29
- traceId,
30
- ...this.context,
31
- }
32
- }
33
- }
34
-
35
- // 4xx Client Errors
36
- class BadRequestError extends AppError {
37
- readonly status = 400
38
- readonly type = 'BAD_REQUEST'
39
- }
40
-
41
- class UnauthorizedError extends AppError {
42
- readonly status = 401
43
- readonly type = 'UNAUTHORIZED'
44
- }
45
-
46
- class ForbiddenError extends AppError {
47
- readonly status = 403
48
- readonly type = 'FORBIDDEN'
49
- }
50
-
51
- class NotFoundError extends AppError {
52
- readonly status = 404
53
- readonly type = 'NOT_FOUND'
54
- }
55
-
56
- class ConflictError extends AppError {
57
- readonly status = 409
58
- readonly type = 'CONFLICT'
59
-
60
- constructor(
61
- message: string,
62
- public readonly retryable: boolean = false,
63
- context?: ErrorContext
64
- ) {
65
- super(message, context)
66
- }
67
- }
68
-
69
- class ValidationError extends AppError {
70
- readonly status = 422
71
- readonly type = 'VALIDATION_ERROR'
72
-
73
- constructor(
74
- message: string,
75
- public readonly errors: Array<{ field: string; message: string }>,
76
- context?: ErrorContext
77
- ) {
78
- super(message, { ...context, errors })
79
- }
80
- }
81
-
82
- class TooManyRequestsError extends AppError {
83
- readonly status = 429
84
- readonly type = 'TOO_MANY_REQUESTS'
85
-
86
- constructor(
87
- message: string,
88
- public readonly retryAfter: number, // seconds
89
- context?: ErrorContext
90
- ) {
91
- super(message, { ...context, retryAfter })
92
- }
93
- }
94
-
95
- // 5xx Server Errors
96
- class InternalServerError extends AppError {
97
- readonly status = 500
98
- readonly type = 'INTERNAL_SERVER_ERROR'
99
- }
100
-
101
- class ServiceUnavailableError extends AppError {
102
- readonly status = 503
103
- readonly type = 'SERVICE_UNAVAILABLE'
104
-
105
- constructor(
106
- message: string,
107
- public readonly retryable: boolean = true,
108
- context?: ErrorContext
109
- ) {
110
- super(message, { ...context, retryable })
111
- }
112
- }
113
- ```
114
-
115
- ## TypeBox Schemas
116
-
117
- Define error response schemas for OpenAPI:
118
-
119
- ```typescript
120
- import { Type } from '@sinclair/typebox'
121
-
122
- // Base ProblemDetail
123
- const ProblemDetail = Type.Object({
124
- type: Type.String({ description: 'Error type identifier' }),
125
- status: Type.Number({ description: 'HTTP status code' }),
126
- title: Type.String({ description: 'Short, human-readable summary' }),
127
- detail: Type.String({ description: 'Specific explanation' }),
128
- instance: Type.String({ description: 'URI to this error occurrence' }),
129
- traceId: Type.String({ description: 'Request trace ID' }),
130
- })
131
-
132
- // Specific error responses
133
- export const BadRequestErrorResponse = Type.Composite([
134
- ProblemDetail,
135
- Type.Object({
136
- type: Type.Literal('BAD_REQUEST'),
137
- status: Type.Literal(400),
138
- }),
139
- ], { $id: 'BadRequestErrorResponse' })
140
-
141
- export const UnauthorizedErrorResponse = Type.Composite([
142
- ProblemDetail,
143
- Type.Object({
144
- type: Type.Literal('UNAUTHORIZED'),
145
- status: Type.Literal(401),
146
- }),
147
- ], { $id: 'UnauthorizedErrorResponse' })
148
-
149
- export const ForbiddenErrorResponse = Type.Composite([
150
- ProblemDetail,
151
- Type.Object({
152
- type: Type.Literal('FORBIDDEN'),
153
- status: Type.Literal(403),
154
- }),
155
- ], { $id: 'ForbiddenErrorResponse' })
156
-
157
- export const NotFoundErrorResponse = Type.Composite([
158
- ProblemDetail,
159
- Type.Object({
160
- type: Type.Literal('NOT_FOUND'),
161
- status: Type.Literal(404),
162
- }),
163
- ], { $id: 'NotFoundErrorResponse' })
164
-
165
- export const ConflictErrorResponse = Type.Composite([
166
- ProblemDetail,
167
- Type.Object({
168
- type: Type.Literal('CONFLICT'),
169
- status: Type.Literal(409),
170
- retryable: Type.Optional(Type.Boolean()),
171
- }),
172
- ], { $id: 'ConflictErrorResponse' })
173
-
174
- export const ValidationErrorResponse = Type.Composite([
175
- ProblemDetail,
176
- Type.Object({
177
- type: Type.Literal('VALIDATION_ERROR'),
178
- status: Type.Literal(422),
179
- errors: Type.Array(Type.Object({
180
- field: Type.String(),
181
- message: Type.String(),
182
- })),
183
- }),
184
- ], { $id: 'ValidationErrorResponse' })
185
-
186
- export const TooManyRequestsErrorResponse = Type.Composite([
187
- ProblemDetail,
188
- Type.Object({
189
- type: Type.Literal('TOO_MANY_REQUESTS'),
190
- status: Type.Literal(429),
191
- retryAfter: Type.Number({ description: 'Seconds until retry allowed' }),
192
- }),
193
- ], { $id: 'TooManyRequestsErrorResponse' })
194
-
195
- export const InternalServerErrorResponse = Type.Composite([
196
- ProblemDetail,
197
- Type.Object({
198
- type: Type.Literal('INTERNAL_SERVER_ERROR'),
199
- status: Type.Literal(500),
200
- }),
201
- ], { $id: 'InternalServerErrorResponse' })
202
-
203
- export const ServiceUnavailableErrorResponse = Type.Composite([
204
- ProblemDetail,
205
- Type.Object({
206
- type: Type.Literal('SERVICE_UNAVAILABLE'),
207
- status: Type.Literal(503),
208
- retryable: Type.Optional(Type.Boolean()),
209
- }),
210
- ], { $id: 'ServiceUnavailableErrorResponse' })
211
- ```
212
-
213
- ## Global Error Handler (Fastify)
214
-
215
- ```typescript
216
- import type { FastifyError, FastifyRequest, FastifyReply } from 'fastify'
217
-
218
- const globalErrorHandler = (
219
- error: FastifyError,
220
- request: FastifyRequest,
221
- reply: FastifyReply
222
- ) => {
223
- // Handle Fastify validation errors
224
- if (error.code === 'FST_ERR_VALIDATION') {
225
- return reply.status(400).send({
226
- type: 'BAD_REQUEST',
227
- status: 400,
228
- title: 'Bad Request',
229
- detail: error.message,
230
- instance: request.url,
231
- traceId: request.id,
232
- })
233
- }
234
-
235
- // Handle domain errors
236
- if (error instanceof AppError) {
237
- const response = error.toResponse(request.url, request.id)
238
-
239
- // Add Retry-After header for 429 and 503
240
- if (error instanceof TooManyRequestsError) {
241
- reply.header('Retry-After', error.retryAfter.toString())
242
- }
243
- if (error instanceof ServiceUnavailableError && error.retryable) {
244
- reply.header('Retry-After', '60') // Default 60 seconds
245
- }
246
-
247
- return reply.status(error.status).send(response)
248
- }
249
-
250
- // Unknown error - log and return 500
251
- request.log.error({ err: error }, 'Unhandled error')
252
- return reply.status(500).send({
253
- type: 'INTERNAL_SERVER_ERROR',
254
- status: 500,
255
- title: 'Internal Server Error',
256
- detail: 'An unexpected error occurred',
257
- instance: request.url,
258
- traceId: request.id,
259
- })
260
- }
261
-
262
- app.setErrorHandler(globalErrorHandler)
263
- ```
264
-
265
- ## Usage Examples
266
-
267
- ### Not Found
268
-
269
- ```typescript
270
- async function getUser(userId: string): Promise<UserEntity> {
271
- const record = await db.query.users.findFirst({
272
- where: eq(users.id, userId),
273
- })
274
-
275
- if (!record) {
276
- throw new NotFoundError(
277
- `User with ID ${userId} not found`,
278
- { userId }
279
- )
280
- }
281
-
282
- return UserEntity.fromRecord(record)
283
- }
284
-
285
- // Response
286
- {
287
- "type": "NOT_FOUND",
288
- "status": 404,
289
- "title": "Not Found",
290
- "detail": "User with ID usr_abc123 not found",
291
- "instance": "/api/v1/users/usr_abc123",
292
- "traceId": "req_xyz789",
293
- "userId": "usr_abc123"
294
- }
295
- ```
296
-
297
- ### Conflict with Retryable Flag
298
-
299
- ```typescript
300
- async function updateAccount(entity: AccountEntity): Promise<AccountEntity> {
301
- const result = await db.update(accounts)
302
- .set(entity.toRecord())
303
- .where(and(
304
- eq(accounts.id, entity.id),
305
- eq(accounts.lockVersion, entity.lockVersion)
306
- ))
307
- .returning()
308
-
309
- if (result.length === 0) {
310
- throw new ConflictError(
311
- 'Account was modified by another transaction',
312
- true, // retryable
313
- { accountId: entity.id, lockVersion: entity.lockVersion }
314
- )
315
- }
316
-
317
- return AccountEntity.fromRecord(result[0])
318
- }
319
-
320
- // Response
321
- {
322
- "type": "CONFLICT",
323
- "status": 409,
324
- "title": "Conflict",
325
- "detail": "Account was modified by another transaction",
326
- "instance": "/api/v1/accounts/acc_abc123",
327
- "traceId": "req_xyz789",
328
- "retryable": true,
329
- "accountId": "acc_abc123",
330
- "lockVersion": 5
331
- }
332
- ```
333
-
334
- ### Validation Error
335
-
336
- ```typescript
337
- async function createUser(rq: CreateUserRequest): Promise<UserEntity> {
338
- const errors: Array<{ field: string; message: string }> = []
339
-
340
- if (!rq.name || rq.name.length < 2) {
341
- errors.push({ field: 'name', message: 'Name must be at least 2 characters' })
342
- }
343
-
344
- if (!rq.email || !rq.email.includes('@')) {
345
- errors.push({ field: 'email', message: 'Invalid email format' })
346
- }
347
-
348
- if (errors.length > 0) {
349
- throw new ValidationError('Invalid user data', errors)
350
- }
351
-
352
- // Continue with creation...
353
- }
354
-
355
- // Response
356
- {
357
- "type": "VALIDATION_ERROR",
358
- "status": 422,
359
- "title": "Validation Error",
360
- "detail": "Invalid user data",
361
- "instance": "/api/v1/users",
362
- "traceId": "req_xyz789",
363
- "errors": [
364
- { "field": "name", "message": "Name must be at least 2 characters" },
365
- { "field": "email", "message": "Invalid email format" }
366
- ]
367
- }
368
- ```
369
-
370
- ### Forbidden (Missing Permission)
371
-
372
- ```typescript
373
- async function deleteUser(userId: string, token: Token): Promise<void> {
374
- if (!token.permissions.includes('user:delete')) {
375
- throw new ForbiddenError(
376
- 'Missing permission: user:delete',
377
- { userId, requiredPermission: 'user:delete' }
378
- )
379
- }
380
-
381
- await userRepo.delete(userId)
382
- }
383
-
384
- // Response
385
- {
386
- "type": "FORBIDDEN",
387
- "status": 403,
388
- "title": "Forbidden",
389
- "detail": "Missing permission: user:delete",
390
- "instance": "/api/v1/users/usr_abc123",
391
- "traceId": "req_xyz789",
392
- "userId": "usr_abc123",
393
- "requiredPermission": "user:delete"
394
- }
395
- ```
396
-
397
- ### Rate Limit
398
-
399
- ```typescript
400
- async function checkRateLimit(userId: string): Promise<void> {
401
- const limit = await rateLimiter.check(userId)
402
-
403
- if (limit.exceeded) {
404
- throw new TooManyRequestsError(
405
- 'Rate limit exceeded',
406
- limit.resetIn, // seconds
407
- { userId, limit: limit.max, window: '1 minute' }
408
- )
409
- }
410
- }
411
-
412
- // Response with Retry-After header
413
- {
414
- "type": "TOO_MANY_REQUESTS",
415
- "status": 429,
416
- "title": "Too Many Requests",
417
- "detail": "Rate limit exceeded",
418
- "instance": "/api/v1/posts",
419
- "traceId": "req_xyz789",
420
- "retryAfter": 45,
421
- "userId": "usr_abc123",
422
- "limit": 100,
423
- "window": "1 minute"
424
- }
425
- ```
426
-
427
- ### Service Unavailable (Database Error)
428
-
429
- ```typescript
430
- function handleDBError(error: unknown, context: ErrorContext): never {
431
- const code = (error as { code?: string }).code
432
-
433
- switch (code) {
434
- case '40001': // serialization_failure
435
- case 'OC000': // occ_conflict (AWS DSQL)
436
- throw new ServiceUnavailableError(
437
- 'Transaction conflict - please retry',
438
- true, // retryable
439
- context
440
- )
441
- default:
442
- throw new InternalServerError('Database error', context)
443
- }
444
- }
445
-
446
- // Response with Retry-After header
447
- {
448
- "type": "SERVICE_UNAVAILABLE",
449
- "status": 503,
450
- "title": "Service Unavailable",
451
- "detail": "Transaction conflict - please retry",
452
- "instance": "/api/v1/transactions",
453
- "traceId": "req_xyz789",
454
- "retryable": true
455
- }
456
- ```
457
-
458
- ## Guidelines
459
-
460
- 1. **Use RFC 7807** - Standard ProblemDetail format for all errors
461
- 2. **Include context** - Add relevant IDs to error context for debugging
462
- 3. **Retryable flag** - Mark 409/503 errors as retryable when appropriate
463
- 4. **Validation arrays** - List all validation errors, not just the first one
464
- 5. **Retry-After header** - Include for 429 and 503 responses
465
- 6. **Log server errors** - Always log 500 errors with full stack trace
466
- 7. **Don't expose internals** - Keep error messages user-friendly, not technical
467
- 8. **Consistent types** - Use same error type strings across all services
468
- 9. **TraceId always** - Include request ID for correlating logs
469
- 10. **TypeBox schemas** - Define error response schemas for OpenAPI documentation