@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,574 +0,0 @@
1
- # Entity Layer Transformation Patterns
2
-
3
- Domain entities manage all data transformations between layers, ensuring clean separation of concerns.
4
-
5
- ## The Four Transformation Methods
6
-
7
- Every domain entity implements four methods for layer boundary crossing:
8
-
9
- ```typescript
10
- class EntityName {
11
- // 1. API Request → Domain Entity
12
- static fromRequest(request: EntityCreateRequest): EntityName
13
-
14
- // 2. DynamoDB Record → Domain Entity
15
- static fromRecord(record: EntityFormatted): EntityName
16
-
17
- // 3. Domain Entity → DynamoDB Record
18
- toRecord(): EntityInput
19
-
20
- // 4. Domain Entity → API Response
21
- toResponse(): EntityResponse
22
- }
23
- ```
24
-
25
- ## Data Flow Through Layers
26
-
27
- ### Create/Update Flow
28
-
29
- ```
30
- HTTP Request
31
- ↓ fromRequest()
32
- Domain Entity
33
- ↓ toRecord()
34
- DynamoDB Record
35
- ↓ DynamoDB save
36
- DynamoDB Record (with timestamps)
37
- ↓ fromRecord()
38
- Domain Entity
39
- ↓ toResponse()
40
- HTTP Response
41
- ```
42
-
43
- ### Read Flow
44
-
45
- ```
46
- HTTP Request
47
- ↓ repository.get(id)
48
- DynamoDB Record
49
- ↓ fromRecord()
50
- Domain Entity
51
- ↓ toResponse()
52
- HTTP Response
53
- ```
54
-
55
- ## Complete Entity Example
56
-
57
- ```typescript
58
- import { type InputItem, type FormattedItem } from 'dynamodb-toolbox/entity'
59
- import { DateTime } from 'luxon'
60
-
61
- // Type exports from schema
62
- type IssueRecord = typeof IssueRecord
63
- type IssueInput = InputItem<typeof IssueRecord>
64
- type IssueFormatted = FormattedItem<typeof IssueRecord>
65
-
66
- // API types
67
- interface IssueCreateRequest {
68
- owner: string
69
- repo_name: string
70
- title: string
71
- body?: string
72
- author: string
73
- assignees?: string[]
74
- labels?: string[]
75
- }
76
-
77
- interface IssueResponse {
78
- owner: string
79
- repo_name: string
80
- issue_number: number
81
- title: string
82
- body?: string
83
- status: "open" | "closed"
84
- author: string
85
- assignees: string[]
86
- labels: string[]
87
- created_at: string
88
- updated_at: string
89
- }
90
-
91
- // Domain entity
92
- class IssueEntity {
93
- // Immutable properties
94
- public readonly owner: string
95
- public readonly repoName: string
96
- public readonly issueNumber: number
97
- public readonly title: string
98
- public readonly body?: string
99
- public readonly status: "open" | "closed"
100
- public readonly author: string
101
- public readonly assignees: string[]
102
- public readonly labels: string[]
103
- public readonly created: DateTime
104
- public readonly modified: DateTime
105
-
106
- constructor(props: IssueEntityOpts) {
107
- this.owner = props.owner
108
- this.repoName = props.repoName
109
- this.issueNumber = props.issueNumber
110
- this.title = props.title
111
- this.body = props.body
112
- this.status = props.status || "open"
113
- this.author = props.author
114
- this.assignees = props.assignees || []
115
- this.labels = props.labels || []
116
- this.created = props.created || DateTime.utc()
117
- this.modified = props.modified || DateTime.utc()
118
- }
119
-
120
- // 1. API Request → Entity
121
- static fromRequest(data: IssueCreateRequest): IssueEntity {
122
- // Validation
123
- IssueEntity.validate(data)
124
-
125
- return new IssueEntity({
126
- owner: data.owner,
127
- repoName: data.repo_name,
128
- issueNumber: 0, // Set by repository after getting from counter
129
- title: data.title,
130
- body: data.body,
131
- status: "open",
132
- author: data.author,
133
- assignees: data.assignees || [],
134
- labels: data.labels || [],
135
- })
136
- }
137
-
138
- // 2. DynamoDB Record → Entity
139
- static fromRecord(record: IssueFormatted): IssueEntity {
140
- return new IssueEntity({
141
- owner: record.owner,
142
- repoName: record.repo_name,
143
- issueNumber: record.issue_number,
144
- title: record.title,
145
- body: record.body,
146
- status: record.status,
147
- author: record.author,
148
- // DynamoDB Sets → Arrays
149
- assignees: record.assignees ? Array.from(record.assignees) : [],
150
- labels: record.labels ? Array.from(record.labels) : [],
151
- // ISO strings → DateTime
152
- created: DateTime.fromISO(record.created),
153
- modified: DateTime.fromISO(record.modified),
154
- })
155
- }
156
-
157
- // 3. Entity → DynamoDB Record
158
- toRecord(): IssueInput {
159
- return {
160
- owner: this.owner,
161
- repo_name: this.repoName,
162
- issue_number: this.issueNumber,
163
- title: this.title,
164
- body: this.body,
165
- status: this.status,
166
- author: this.author,
167
- // Arrays → Sets (DynamoDB doesn't allow empty sets)
168
- assignees: this.assignees.length > 0
169
- ? new Set(this.assignees)
170
- : undefined,
171
- labels: this.labels.length > 0
172
- ? new Set(this.labels)
173
- : undefined,
174
- // DateTime → ISO strings handled by DynamoDB Toolbox
175
- }
176
- }
177
-
178
- // 4. Entity → API Response
179
- toResponse(): IssueResponse {
180
- return {
181
- owner: this.owner,
182
- repo_name: this.repoName,
183
- issue_number: this.issueNumber,
184
- title: this.title,
185
- body: this.body,
186
- status: this.status,
187
- author: this.author,
188
- assignees: this.assignees,
189
- labels: this.labels,
190
- created_at: this.created.toISO() ?? "",
191
- updated_at: this.modified.toISO() ?? "",
192
- }
193
- }
194
-
195
- // Validation (called by fromRequest)
196
- private static validate(data: IssueCreateRequest): void {
197
- if (!data.title || data.title.trim().length === 0) {
198
- throw new ValidationError("title", "Title is required")
199
- }
200
- if (data.title.length > 256) {
201
- throw new ValidationError("title", "Title must be 256 characters or less")
202
- }
203
- }
204
-
205
- // Immutable update (returns new instance)
206
- updateIssue(opts: UpdateIssueEntityOpts): IssueEntity {
207
- return new IssueEntity({
208
- // Preserve immutable fields
209
- owner: this.owner,
210
- repoName: this.repoName,
211
- issueNumber: this.issueNumber,
212
- author: this.author,
213
- created: this.created,
214
- // Apply updates
215
- title: opts.title ?? this.title,
216
- body: opts.body ?? this.body,
217
- status: opts.status ?? this.status,
218
- assignees: opts.assignees ?? this.assignees,
219
- labels: opts.labels ?? this.labels,
220
- // Update timestamp
221
- modified: DateTime.utc(),
222
- })
223
- }
224
-
225
- // Helper: Entity key for error messages
226
- getEntityKey(): string {
227
- return `ISSUE#${this.owner}#${this.repoName}#${this.issueNumber}`
228
- }
229
-
230
- // Helper: Parent entity key for transactions
231
- getParentEntityKey(): string {
232
- return `REPO#${this.owner}#${this.repoName}`
233
- }
234
- }
235
- ```
236
-
237
- ## Field Naming Conventions
238
-
239
- ### Entity Layer (TypeScript/camelCase)
240
-
241
- ```typescript
242
- class RepositoryEntity {
243
- public readonly repoName: string
244
- public readonly isPrivate: boolean
245
- public readonly paymentPlanId?: string
246
- }
247
- ```
248
-
249
- ### Database Layer (DynamoDB/snake_case)
250
-
251
- ```typescript
252
- schema: item({
253
- repo_name: string().required(),
254
- is_private: boolean().default(false),
255
- payment_plan_id: string().optional(),
256
- })
257
- ```
258
-
259
- ### Mapping in Transformations
260
-
261
- ```typescript
262
- // fromRecord: snake_case → camelCase
263
- static fromRecord(record: RepoFormatted): RepositoryEntity {
264
- return new RepositoryEntity({
265
- repoName: record.repo_name,
266
- isPrivate: record.is_private,
267
- paymentPlanId: record.payment_plan_id,
268
- })
269
- }
270
-
271
- // toRecord: camelCase → snake_case
272
- toRecord(): RepoInput {
273
- return {
274
- repo_name: this.repoName,
275
- is_private: this.isPrivate,
276
- payment_plan_id: this.paymentPlanId,
277
- }
278
- }
279
-
280
- // toResponse: camelCase → snake_case (API convention)
281
- toResponse(): RepoResponse {
282
- return {
283
- repo_name: this.repoName,
284
- is_private: this.isPrivate,
285
- payment_plan_id: this.paymentPlanId,
286
- }
287
- }
288
- ```
289
-
290
- **Why snake_case for API responses?**
291
- Matches common REST API conventions and DynamoDB attribute names.
292
-
293
- ## DynamoDB Set Conversion
294
-
295
- DynamoDB doesn't support empty Sets - convert to/from Arrays.
296
-
297
- ### Reading: Set → Array
298
-
299
- ```typescript
300
- static fromRecord(record: IssueFormatted): IssueEntity {
301
- return new IssueEntity({
302
- // DynamoDB Set (or undefined) → Array
303
- assignees: record.assignees ? Array.from(record.assignees) : [],
304
- labels: record.labels ? Array.from(record.labels) : [],
305
- })
306
- }
307
- ```
308
-
309
- ### Writing: Array → Set (or undefined)
310
-
311
- ```typescript
312
- toRecord(): IssueInput {
313
- return {
314
- // Array → Set (only if non-empty)
315
- assignees: this.assignees.length > 0
316
- ? new Set(this.assignees)
317
- : undefined,
318
- labels: this.labels.length > 0
319
- ? new Set(this.labels)
320
- : undefined,
321
- }
322
- }
323
- ```
324
-
325
- **Critical:** DynamoDB rejects empty Sets. Always check length before creating Set.
326
-
327
- ## Timestamp Handling
328
-
329
- DynamoDB Toolbox auto-manages `created` and `modified` timestamps.
330
-
331
- ### Schema Configuration
332
-
333
- ```typescript
334
- // Timestamps are added automatically as _ct and _md
335
- // When querying through Entity, they're formatted as 'created' and 'modified'
336
- const entity = new Entity({
337
- name: "USER",
338
- table: AppTable,
339
- schema: item({
340
- username: string().required().key(),
341
- // created/modified added automatically
342
- }),
343
- })
344
- ```
345
-
346
- ### Reading Timestamps
347
-
348
- ```typescript
349
- static fromRecord(record: UserFormatted): UserEntity {
350
- return new UserEntity({
351
- username: record.username,
352
- // DynamoDB Toolbox returns ISO strings
353
- created: DateTime.fromISO(record.created),
354
- modified: DateTime.fromISO(record.modified),
355
- })
356
- }
357
- ```
358
-
359
- ### Writing Timestamps
360
-
361
- ```typescript
362
- // Don't include created/modified in toRecord()
363
- // DynamoDB Toolbox handles them automatically
364
- toRecord(): UserInput {
365
- return {
366
- username: this.username,
367
- email: this.email,
368
- // created/modified omitted - auto-managed
369
- }
370
- }
371
- ```
372
-
373
- ## Immutable Update Pattern
374
-
375
- Never mutate entity properties. Return new instance with updated values.
376
-
377
- ```typescript
378
- class UserEntity {
379
- public readonly username: string // Never changes
380
- public readonly email: string
381
- public readonly bio?: string
382
- public readonly created: DateTime // Never changes
383
- public readonly modified: DateTime
384
-
385
- // Immutable update
386
- updateUser(opts: UpdateUserEntityOpts): UserEntity {
387
- return new UserEntity({
388
- // Preserve identity
389
- username: this.username,
390
- created: this.created,
391
- // Apply updates
392
- email: opts.email ?? this.email,
393
- bio: opts.bio ?? this.bio,
394
- // Update timestamp
395
- modified: DateTime.utc(),
396
- })
397
- }
398
- }
399
- ```
400
-
401
- **Usage:**
402
-
403
- ```typescript
404
- // Repository layer
405
- async update(user: UserEntity): Promise<UserEntity> {
406
- // Create updated entity with new timestamp
407
- const updated = user.updateUser({
408
- email: "newemail@example.com",
409
- })
410
-
411
- // Save to DynamoDB
412
- const result = await this.entity
413
- .build(PutItemCommand)
414
- .item(updated.toRecord())
415
- .send()
416
-
417
- return UserEntity.fromRecord(result.ToolboxItem)
418
- }
419
- ```
420
-
421
- **Why immutable?**
422
- - Predictable: No hidden state changes
423
- - Testable: Pure functions
424
- - Thread-safe: No race conditions
425
- - Traceable: Clear audit trail
426
-
427
- ## Validation in fromRequest
428
-
429
- Validate business rules when converting API request to entity.
430
-
431
- ```typescript
432
- static fromRequest(data: RepositoryCreateRequest): RepositoryEntity {
433
- // Validate required fields
434
- if (!data.owner || data.owner.trim().length === 0) {
435
- throw new ValidationError("owner", "Owner is required")
436
- }
437
-
438
- // Validate format
439
- if (!/^[a-zA-Z0-9_-]+$/.test(data.owner)) {
440
- throw new ValidationError(
441
- "owner",
442
- "Owner must contain only alphanumeric characters, hyphens, and underscores"
443
- )
444
- }
445
-
446
- // Validate length
447
- if (data.repo_name.length > 100) {
448
- throw new ValidationError(
449
- "repo_name",
450
- "Repository name must be 100 characters or less"
451
- )
452
- }
453
-
454
- return new RepositoryEntity({
455
- owner: data.owner,
456
- repoName: data.repo_name,
457
- description: data.description,
458
- isPrivate: data.is_private ?? false,
459
- })
460
- }
461
- ```
462
-
463
- **Validation Layers:**
464
- 1. **Schema validation** (DynamoDB Toolbox) - Type and required fields
465
- 2. **Business validation** (Entity `fromRequest`) - Format, length, rules
466
- 3. **Existence validation** (Repository transactions) - Foreign key checks
467
-
468
- ## Helper Methods
469
-
470
- ### getEntityKey()
471
-
472
- Returns entity identifier for error messages.
473
-
474
- ```typescript
475
- getEntityKey(): string {
476
- return `ISSUE#${this.owner}#${this.repoName}#${this.issueNumber}`
477
- }
478
-
479
- // Usage in error handling
480
- throw new EntityNotFoundError("IssueEntity", issue.getEntityKey())
481
- // → "IssueEntity 'ISSUE#alice#my-repo#00000001' not found"
482
- ```
483
-
484
- ### getParentEntityKey()
485
-
486
- Returns parent entity identifier for transaction error handling.
487
-
488
- ```typescript
489
- getParentEntityKey(): string {
490
- return `REPO#${this.owner}#${this.repoName}`
491
- }
492
-
493
- // Usage in transaction error handling
494
- handleTransactionError(error, {
495
- entityType: "IssueEntity",
496
- entityKey: issue.getEntityKey(),
497
- parentEntityType: "RepositoryEntity",
498
- parentEntityKey: issue.getParentEntityKey(),
499
- })
500
- ```
501
-
502
- ## Testing Entity Transformations
503
-
504
- Entity transformations are tested implicitly through repository tests, not in isolation.
505
-
506
- **Don't:**
507
- ```typescript
508
- // ❌ Don't test transformations in isolation
509
- describe("IssueEntity", () => {
510
- it("should convert to record", () => {
511
- const issue = new IssueEntity(/* ... */)
512
- const record = issue.toRecord()
513
- expect(record.owner).toBe(issue.owner)
514
- })
515
- })
516
- ```
517
-
518
- **Do:**
519
- ```typescript
520
- // ✓ Test through repository layer
521
- describe("IssueRepository", () => {
522
- it("should create issue and retrieve with correct data", async () => {
523
- const issue = IssueEntity.fromRequest({
524
- owner: "alice",
525
- repo_name: "my-repo",
526
- title: "Test Issue",
527
- author: "bob",
528
- })
529
-
530
- const created = await issueRepo.create(issue)
531
-
532
- // Transformations tested implicitly
533
- expect(created.owner).toBe("alice")
534
- expect(created.repoName).toBe("my-repo")
535
- expect(created.title).toBe("Test Issue")
536
- })
537
- })
538
- ```
539
-
540
- **Why?** Entity transformations are an implementation detail. Test the full flow (API → DynamoDB → API) through repository tests.
541
-
542
- ## Best Practices
543
-
544
- ### ✓ Do
545
-
546
- - Implement all four transformation methods
547
- - Use immutable entity properties
548
- - Return new instances from update methods
549
- - Convert Sets ↔ Arrays (handle empty sets)
550
- - Parse timestamps to DateTime objects
551
- - Validate in `fromRequest()`
552
- - Use helper methods for entity keys
553
-
554
- ### ✗ Don't
555
-
556
- - Don't mutate entity properties
557
- - Don't include timestamps in `toRecord()` (auto-managed)
558
- - Don't create empty Sets (DynamoDB rejects them)
559
- - Don't test entity transformations in isolation
560
- - Don't include PK/SK in entity (computed by schema)
561
-
562
- ## Entity Transformation Checklist
563
-
564
- For each domain entity:
565
-
566
- - [ ] **Four methods implemented** - fromRequest, fromRecord, toRecord, toResponse
567
- - [ ] **Immutable properties** - All fields marked `readonly`
568
- - [ ] **Immutable updates** - `updateEntity()` returns new instance
569
- - [ ] **Field naming** - camelCase in entity, snake_case in DB
570
- - [ ] **Set conversion** - Arrays ↔ Sets with empty check
571
- - [ ] **Timestamp handling** - DateTime objects, not included in toRecord
572
- - [ ] **Validation** - Business rules in fromRequest
573
- - [ ] **Helper methods** - getEntityKey() and getParentEntityKey()
574
- - [ ] **Type exports** - EntityInput and EntityFormatted from schema