@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,449 +0,0 @@
1
- # Layer Boundary Testing
2
-
3
- Test at component boundaries, not internals. Each layer has specific test focus areas.
4
-
5
- ## What to Test at Each Layer
6
-
7
- ### Entity Layer
8
- - ✅ Domain logic and validation
9
- - ✅ Transformations (from_request, to_response, to_record)
10
- - ✅ Business rules and calculated fields
11
- - ❌ Simple getters/setters
12
- - ❌ Python language features
13
-
14
- ### Repository Layer
15
- - ✅ CRUD operations
16
- - ✅ Query logic and filters
17
- - ✅ Entity ↔ Record transformations
18
- - ✅ Database constraints
19
- - ❌ ORM framework code
20
- - ❌ SQL generation
21
-
22
- ### Service Layer
23
- - ✅ Business workflows and orchestration
24
- - ✅ Error handling and validation
25
- - ✅ Dependency coordination
26
- - ✅ Complex logic
27
- - ❌ Trivial delegation
28
- - ❌ Single-line pass-throughs
29
-
30
- ### Router Layer
31
- - ✅ Request validation and deserialization
32
- - ✅ Response serialization
33
- - ✅ Status codes and HTTP semantics
34
- - ✅ Error response formatting
35
- - ❌ Framework routing mechanics
36
- - ❌ Dependency injection
37
-
38
- ## Entity Layer Examples
39
-
40
- ### Test Transformations
41
-
42
- ```python
43
- from entities import Product
44
- from decimal import Decimal
45
- from uuid import uuid4
46
-
47
- def test_product_from_request():
48
- """Test creation from request object"""
49
- request = CreateProductRequest(
50
- name="Widget",
51
- price=Decimal("9.99"),
52
- sku="SKU123"
53
- )
54
-
55
- product = Product.from_request(request)
56
-
57
- assert product.name == "Widget"
58
- assert product.price == Decimal("9.99")
59
- assert product.sku == "SKU123"
60
- assert isinstance(product.id, UUID)
61
- assert product.created_at is not None
62
-
63
- def test_product_to_response():
64
- """Test transformation to API response"""
65
- product = Product(
66
- id=uuid4(),
67
- name="Widget",
68
- price=Decimal("9.99"),
69
- sku="SKU123",
70
- created_at=datetime.now(),
71
- )
72
-
73
- response = product.to_response()
74
-
75
- assert response.id == str(product.id)
76
- assert response.name == "Widget"
77
- assert response.price == 9.99 # Decimal → float
78
-
79
- def test_product_to_record():
80
- """Test transformation to database record"""
81
- product = Product(
82
- id=uuid4(),
83
- name="Widget",
84
- price=Decimal("9.99"),
85
- )
86
-
87
- record = product.to_record()
88
-
89
- assert record.id == product.id
90
- assert record.name == "Widget"
91
- assert record.price == Decimal("9.99")
92
- ```
93
-
94
- ### Test Validation
95
-
96
- ```python
97
- def test_product_validate_price():
98
- """Test price validation"""
99
- with pytest.raises(ValueError, match="Price must be positive"):
100
- Product(
101
- id=uuid4(),
102
- name="Widget",
103
- price=Decimal("-10"),
104
- )
105
-
106
- def test_product_validate_name():
107
- """Test name is required"""
108
- with pytest.raises(ValueError, match="Name is required"):
109
- Product(
110
- id=uuid4(),
111
- name="",
112
- price=Decimal("9.99"),
113
- )
114
- ```
115
-
116
- ### Test Business Logic
117
-
118
- ```python
119
- def test_product_apply_discount():
120
- """Test discount calculation"""
121
- product = Product(
122
- id=uuid4(),
123
- name="Widget",
124
- price=Decimal("100"),
125
- )
126
-
127
- discounted = product.apply_discount(Decimal("0.1"))
128
-
129
- assert discounted.price == Decimal("90")
130
- assert product.price == Decimal("100") # Original unchanged
131
-
132
- def test_product_tax_calculation():
133
- """Test tax on price"""
134
- product = Product(
135
- id=uuid4(),
136
- name="Widget",
137
- price=Decimal("100"),
138
- )
139
-
140
- tax = product.calculate_tax(Decimal("0.08"))
141
-
142
- assert tax == Decimal("8")
143
- ```
144
-
145
- ## Service Layer Examples
146
-
147
- ### Test Business Workflows
148
-
149
- ```python
150
- from unittest.mock import Mock, call
151
-
152
- def test_create_product_service():
153
- """Test service orchestration"""
154
- # Stub repository
155
- mock_repo = Mock()
156
- mock_repo.save.return_value = Product(
157
- id=uuid4(),
158
- name="Widget",
159
- price=Decimal("9.99"),
160
- )
161
-
162
- # Test service
163
- service = ProductService(repo=mock_repo)
164
- request = CreateProductRequest(name="Widget", price=Decimal("9.99"))
165
-
166
- result = service.create(request)
167
-
168
- # Verify repository was called
169
- mock_repo.save.assert_called_once()
170
- assert result.name == "Widget"
171
-
172
- def test_apply_bulk_discount():
173
- """Test orchestration of complex business logic"""
174
- mock_repo = Mock()
175
- mock_repo.find_all.return_value = [
176
- Product(id=uuid4(), name="A", price=Decimal("100")),
177
- Product(id=uuid4(), name="B", price=Decimal("100")),
178
- ]
179
- mock_repo.save.return_value = None
180
-
181
- service = ProductService(repo=mock_repo)
182
- service.apply_bulk_discount(Decimal("0.1"))
183
-
184
- # Verify save called twice
185
- assert mock_repo.save.call_count == 2
186
- ```
187
-
188
- ### Test Error Handling
189
-
190
- ```python
191
- def test_create_product_validation_error():
192
- """Test service error handling"""
193
- mock_repo = Mock()
194
-
195
- service = ProductService(repo=mock_repo)
196
-
197
- with pytest.raises(ValueError, match="Invalid price"):
198
- service.create(CreateProductRequest(
199
- name="Widget",
200
- price=Decimal("-10"),
201
- ))
202
-
203
- # Repository not called on validation error
204
- mock_repo.save.assert_not_called()
205
- ```
206
-
207
- ## Repository Layer Examples
208
-
209
- ### Test Data Access
210
-
211
- ```python
212
- import pytest
213
- from sqlalchemy import create_engine
214
- from sqlalchemy.orm import sessionmaker
215
-
216
- @pytest.fixture
217
- def test_db():
218
- """In-memory test database"""
219
- engine = create_engine("sqlite:///:memory:")
220
- Base.metadata.create_all(engine)
221
- SessionLocal = sessionmaker(bind=engine)
222
- db = SessionLocal()
223
- try:
224
- yield db
225
- finally:
226
- db.close()
227
-
228
- def test_repository_save(test_db):
229
- """Test saving entity"""
230
- repo = ProductRepository(test_db)
231
-
232
- product = Product(
233
- id=uuid4(),
234
- name="Widget",
235
- price=Decimal("9.99"),
236
- )
237
- saved = repo.save(product)
238
-
239
- assert saved.id == product.id
240
- # Verify in database
241
- record = test_db.query(ProductRecord).filter_by(id=product.id).first()
242
- assert record is not None
243
- assert record.name == "Widget"
244
-
245
- def test_repository_find_by_id(test_db):
246
- """Test retrieving entity by ID"""
247
- repo = ProductRepository(test_db)
248
- product_id = uuid4()
249
-
250
- # Create record
251
- test_db.add(ProductRecord(
252
- id=product_id,
253
- name="Widget",
254
- price=Decimal("9.99"),
255
- ))
256
- test_db.commit()
257
-
258
- # Find entity
259
- found = repo.find_by_id(product_id)
260
-
261
- assert found is not None
262
- assert found.id == product_id
263
- assert found.name == "Widget"
264
-
265
- def test_repository_delete(test_db):
266
- """Test deleting entity"""
267
- repo = ProductRepository(test_db)
268
- product_id = uuid4()
269
-
270
- # Create record
271
- test_db.add(ProductRecord(id=product_id, name="Widget"))
272
- test_db.commit()
273
-
274
- # Delete
275
- repo.delete(product_id)
276
-
277
- # Verify deleted
278
- assert test_db.query(ProductRecord).filter_by(id=product_id).first() is None
279
- ```
280
-
281
- ### Test Query Logic
282
-
283
- ```python
284
- def test_repository_find_by_name(test_db):
285
- """Test complex query"""
286
- repo = ProductRepository(test_db)
287
-
288
- # Insert test data
289
- test_db.add_all([
290
- ProductRecord(id=uuid4(), name="Widget A", price=Decimal("10")),
291
- ProductRecord(id=uuid4(), name="Widget B", price=Decimal("20")),
292
- ProductRecord(id=uuid4(), name="Gadget", price=Decimal("15")),
293
- ])
294
- test_db.commit()
295
-
296
- # Query
297
- results = repo.find_by_name("Widget")
298
-
299
- assert len(results) == 2
300
- assert all("Widget" in p.name for p in results)
301
-
302
- def test_repository_find_expensive_products(test_db):
303
- """Test filtering"""
304
- repo = ProductRepository(test_db)
305
-
306
- test_db.add_all([
307
- ProductRecord(id=uuid4(), name="A", price=Decimal("10")),
308
- ProductRecord(id=uuid4(), name="B", price=Decimal("100")),
309
- ProductRecord(id=uuid4(), name="C", price=Decimal("200")),
310
- ])
311
- test_db.commit()
312
-
313
- results = repo.find_by_price_above(Decimal("50"))
314
-
315
- assert len(results) == 2
316
- ```
317
-
318
- ## Router Layer Examples
319
-
320
- ### Test HTTP Endpoints
321
-
322
- ```python
323
- from fastapi.testclient import TestClient
324
-
325
- def test_create_product_endpoint():
326
- """Test POST endpoint"""
327
- client = TestClient(app)
328
-
329
- response = client.post(
330
- "/products",
331
- json={"name": "Widget", "price": 9.99},
332
- )
333
-
334
- assert response.status_code == 201
335
- data = response.json()
336
- assert data["name"] == "Widget"
337
- assert data["price"] == 9.99
338
- assert "id" in data
339
-
340
- def test_get_product_endpoint(test_db):
341
- """Test GET endpoint"""
342
- client = TestClient(app)
343
-
344
- # Create product
345
- create_response = client.post(
346
- "/products",
347
- json={"name": "Widget", "price": 9.99},
348
- )
349
- product_id = create_response.json()["id"]
350
-
351
- # Get product
352
- response = client.get(f"/products/{product_id}")
353
-
354
- assert response.status_code == 200
355
- assert response.json()["id"] == product_id
356
-
357
- def test_update_product_endpoint():
358
- """Test PUT endpoint"""
359
- client = TestClient(app)
360
-
361
- # Create
362
- create_resp = client.post(
363
- "/products",
364
- json={"name": "Widget", "price": 9.99},
365
- )
366
- product_id = create_resp.json()["id"]
367
-
368
- # Update
369
- response = client.put(
370
- f"/products/{product_id}",
371
- json={"name": "Updated Widget", "price": 19.99},
372
- )
373
-
374
- assert response.status_code == 200
375
- assert response.json()["name"] == "Updated Widget"
376
- ```
377
-
378
- ### Test Error Responses
379
-
380
- ```python
381
- def test_get_nonexistent_product():
382
- """Test 404 response"""
383
- client = TestClient(app)
384
-
385
- response = client.get(f"/products/{uuid4()}")
386
-
387
- assert response.status_code == 404
388
- assert "not found" in response.json()["detail"].lower()
389
-
390
- def test_invalid_request_body():
391
- """Test 422 validation error"""
392
- client = TestClient(app)
393
-
394
- response = client.post(
395
- "/products",
396
- json={"name": "Widget"}, # Missing price
397
- )
398
-
399
- assert response.status_code == 422
400
- ```
401
-
402
- ## Testing Anti-Patterns to Avoid
403
-
404
- ### Don't Test Everything
405
-
406
- ```python
407
- # BAD: Testing simple getters
408
- def test_get_name():
409
- product = Product(id=uuid4(), name="Widget")
410
- assert product.name == "Widget" # Trivial
411
-
412
- # GOOD: Test meaningful business logic
413
- def test_apply_discount_calculates_correctly():
414
- product = Product(id=uuid4(), price=Decimal("100"))
415
- discounted = product.apply_discount(Decimal("0.1"))
416
- assert discounted.price == Decimal("90")
417
- ```
418
-
419
- ### Don't Test Framework Code
420
-
421
- ```python
422
- # BAD: Testing SQLAlchemy internals
423
- def test_sqlalchemy_session():
424
- session = SessionLocal()
425
- assert session is not None # Framework concern
426
-
427
- # GOOD: Test your query logic
428
- def test_find_by_price_returns_products():
429
- repo = ProductRepository(test_db)
430
- results = repo.find_by_price_above(Decimal("50"))
431
- assert all(p.price > 50 for p in results)
432
- ```
433
-
434
- ### Don't Over-Mock
435
-
436
- ```python
437
- # BAD: Mocking too much
438
- def test_service():
439
- mock_repo = Mock()
440
- mock_entity = Mock()
441
- mock_response = Mock()
442
- # Multiple layers of mocks make test fragile
443
-
444
- # GOOD: Real objects, stubbed boundaries
445
- def test_service(test_db):
446
- repo = ProductRepository(test_db) # Real
447
- service = ProductService(repo) # Real
448
- result = service.create(request) # One boundary tested
449
- ```