@martinffx/atelier 0.1.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.
- package/CHANGELOG.md +38 -0
- package/LICENSE +21 -0
- package/README.md +285 -0
- package/agents/architect.md +38 -0
- package/agents/oracle.md +40 -0
- package/agents/recon.md +39 -0
- package/dist/atelier.js +33082 -0
- package/package.json +44 -0
- package/skills/code-conventional-commit/SKILL.md +134 -0
- package/skills/code-conventional-commit/evals/evals.json +23 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/commit-message.txt +4 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-0/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-1/with_skill/outputs/validation-result.txt +16 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/commit-message.txt +8 -0
- package/skills/code-conventional-commit/workspace/iteration-1/eval-2/with_skill/outputs/eval_metadata.json +6 -0
- package/skills/code-conventional-commit/workspace/iteration-1/report.html +1325 -0
- package/skills/code-debug/SKILL.md +271 -0
- package/skills/code-debug/references/language-tools.md +139 -0
- package/skills/code-debug/references/techniques.md +117 -0
- package/skills/code-docs/SKILL.md +171 -0
- package/skills/code-docs/references/api-docs.md +116 -0
- package/skills/code-docs/references/changelog.md +64 -0
- package/skills/code-docs/references/readme-structure.md +94 -0
- package/skills/code-git-worktrees/SKILL.md +218 -0
- package/skills/code-perf/SKILL.md +163 -0
- package/skills/code-perf/references/common-bottlenecks.md +124 -0
- package/skills/code-perf/references/optimization-patterns.md +167 -0
- package/skills/code-perf/references/profiling-tools.md +126 -0
- package/skills/code-review/SKILL.md +78 -0
- package/skills/code-review/references/output.md +200 -0
- package/skills/code-review/references/reviewers.md +719 -0
- package/skills/code-review/references/rq.md +357 -0
- package/skills/code-review/references/rs.md +30 -0
- package/skills/code-security/SKILL.md +183 -0
- package/skills/code-security/references/owasp-top-10.md +129 -0
- package/skills/code-security/references/security-tools.md +102 -0
- package/skills/code-security/references/vulnerability-patterns.md +129 -0
- package/skills/code-stacked-commit/SKILL.md +155 -0
- package/skills/code-subagents/SKILL.md +184 -0
- package/skills/code-subagents/references/code-quality-reviewer-prompt.md +48 -0
- package/skills/code-subagents/references/implementor-prompt.md +79 -0
- package/skills/code-subagents/references/spec-reviewer-prompt.md +62 -0
- package/skills/oracle-architect/SKILL.md +246 -0
- package/skills/oracle-architect/references/api-design.md +345 -0
- package/skills/oracle-architect/references/data-modeling.md +318 -0
- package/skills/oracle-architect/references/ddd-patterns.md +247 -0
- package/skills/oracle-challenge/SKILL.md +126 -0
- package/skills/oracle-testing/SKILL.md +237 -0
- package/skills/oracle-testing/references/anti-patterns.md +618 -0
- package/skills/oracle-testing/references/boundaries.md +579 -0
- package/skills/oracle-testing/references/core-testing.md +691 -0
- package/skills/oracle-testing/references/edge-testing.md +794 -0
- package/skills/oracle-testing/references/stub-driven-tdd.md +322 -0
- package/skills/oracle-thinkdeep/SKILL.md +164 -0
- package/skills/python-architecture/SKILL.md +148 -0
- package/skills/python-architecture/references/data-modeling.md +558 -0
- package/skills/python-architecture/references/ddd.md +569 -0
- package/skills/python-architecture/references/functional-core.md +337 -0
- package/skills/python-build-tools/SKILL.md +153 -0
- package/skills/python-build-tools/references/basedpyright.md +321 -0
- package/skills/python-build-tools/references/ruff.md +225 -0
- package/skills/python-build-tools/references/uv.md +257 -0
- package/skills/python-fastapi/SKILL.md +184 -0
- package/skills/python-fastapi/references/api-design.md +563 -0
- package/skills/python-fastapi/references/dependencies.md +479 -0
- package/skills/python-fastapi/references/middleware.md +475 -0
- package/skills/python-fastapi/references/validation.md +497 -0
- package/skills/python-modern-python/SKILL.md +201 -0
- package/skills/python-modern-python/references/async.md +76 -0
- package/skills/python-modern-python/references/pattern-matching.md +89 -0
- package/skills/python-modern-python/references/typing.md +96 -0
- package/skills/python-monorepo/SKILL.md +146 -0
- package/skills/python-monorepo/references/docker.md +515 -0
- package/skills/python-monorepo/references/namespace-packages.md +426 -0
- package/skills/python-monorepo/references/workspace-config.md +341 -0
- package/skills/python-sqlalchemy/SKILL.md +119 -0
- package/skills/python-sqlalchemy/references/async.md +51 -0
- package/skills/python-sqlalchemy/references/models.md +67 -0
- package/skills/python-sqlalchemy/references/queries.md +57 -0
- package/skills/python-temporal/SKILL.md +128 -0
- package/skills/python-temporal/references/patterns.md +87 -0
- package/skills/python-temporal/references/testing.md +54 -0
- package/skills/python-testing/SKILL.md +161 -0
- package/skills/python-testing/references/boundaries.md +449 -0
- package/skills/python-testing/references/mocking.md +449 -0
- package/skills/python-testing/references/pytest.md +524 -0
- package/skills/spec-brainstorm/SKILL.md +325 -0
- package/skills/spec-finish/SKILL.md +159 -0
- package/skills/spec-implement/SKILL.md +296 -0
- package/skills/spec-orchestrator/SKILL.md +156 -0
- package/skills/spec-plan/SKILL.md +248 -0
- package/skills/typescript-api-design/SKILL.md +396 -0
- package/skills/typescript-api-design/references/error-responses.md +469 -0
- package/skills/typescript-build-tools/SKILL.md +447 -0
- package/skills/typescript-build-tools/references/biome.md +436 -0
- package/skills/typescript-build-tools/references/bun.md +432 -0
- package/skills/typescript-build-tools/references/turborepo.md +426 -0
- package/skills/typescript-build-tools/references/vitest.md +518 -0
- package/skills/typescript-drizzle-orm/SKILL.md +398 -0
- package/skills/typescript-drizzle-orm/references/cloudflare.md +392 -0
- package/skills/typescript-drizzle-orm/references/entity-pattern.md +412 -0
- package/skills/typescript-drizzle-orm/references/postgresql.md +191 -0
- package/skills/typescript-drizzle-orm/references/repository-pattern.md +479 -0
- package/skills/typescript-drizzle-orm/references/sqlite.md +291 -0
- package/skills/typescript-dynamodb-toolbox/SKILL.md +370 -0
- package/skills/typescript-dynamodb-toolbox/references/entity-layer.md +574 -0
- package/skills/typescript-dynamodb-toolbox/references/error-handling.md +495 -0
- package/skills/typescript-dynamodb-toolbox/references/modeling.md +332 -0
- package/skills/typescript-dynamodb-toolbox/references/repository-layer.md +631 -0
- package/skills/typescript-dynamodb-toolbox/references/testing.md +718 -0
- package/skills/typescript-dynamodb-toolbox/references/transactions.md +423 -0
- package/skills/typescript-effect-ts/SKILL.md +341 -0
- package/skills/typescript-effect-ts/references/core.md +412 -0
- package/skills/typescript-effect-ts/references/error-handling.md +468 -0
- package/skills/typescript-effect-ts/references/resources.md +476 -0
- package/skills/typescript-effect-ts/references/schema.md +536 -0
- package/skills/typescript-effect-ts/references/services.md +461 -0
- package/skills/typescript-fastify/SKILL.md +405 -0
- package/skills/typescript-fastify/references/plugins.md +329 -0
- package/skills/typescript-fastify/references/typeid.md +315 -0
- package/skills/typescript-functional-patterns/SKILL.md +402 -0
- package/skills/typescript-functional-patterns/references/adts.md +497 -0
- package/skills/typescript-functional-patterns/references/branded-types.md +542 -0
- package/skills/typescript-functional-patterns/references/migration-guide.md +695 -0
- package/skills/typescript-functional-patterns/references/option-result.md +585 -0
- package/skills/typescript-testing/SKILL.md +353 -0
- package/skills/typescript-testing/references/mocking.md +675 -0
- package/skills/typescript-testing/references/msw.md +852 -0
- package/skills/typescript-testing/references/snapshot-testing.md +774 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
# Data Modeling Reference
|
|
2
|
+
|
|
3
|
+
Entity design principles, schema patterns, and access pattern optimization.
|
|
4
|
+
|
|
5
|
+
## Entity Design
|
|
6
|
+
|
|
7
|
+
### Design Process
|
|
8
|
+
1. Start with domain concepts from product requirements
|
|
9
|
+
2. Identify aggregates and their boundaries
|
|
10
|
+
3. Define attributes and relationships
|
|
11
|
+
4. Add validation rules and invariants
|
|
12
|
+
5. Include transformation methods (fromRequest, toRecord, toResponse)
|
|
13
|
+
|
|
14
|
+
### Entity Structure
|
|
15
|
+
|
|
16
|
+
**Core transformation methods:**
|
|
17
|
+
```typescript
|
|
18
|
+
class Order {
|
|
19
|
+
// Create from external request
|
|
20
|
+
static fromRequest(req: CreateOrderRequest): Order
|
|
21
|
+
|
|
22
|
+
// Create from database record
|
|
23
|
+
static fromRecord(record: OrderRecord): Order
|
|
24
|
+
|
|
25
|
+
// Convert to database record
|
|
26
|
+
toRecord(): OrderRecord
|
|
27
|
+
|
|
28
|
+
// Convert to API response
|
|
29
|
+
toResponse(): OrderResponse
|
|
30
|
+
|
|
31
|
+
// Validate invariants
|
|
32
|
+
validate(): Result<Order>
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Example Entity Design
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
class Order {
|
|
40
|
+
constructor(
|
|
41
|
+
public readonly id: string,
|
|
42
|
+
public readonly customerId: string,
|
|
43
|
+
public readonly items: OrderItem[],
|
|
44
|
+
public readonly status: OrderStatus,
|
|
45
|
+
public readonly total: number,
|
|
46
|
+
public readonly createdAt: Date,
|
|
47
|
+
public readonly metadata: OrderMetadata
|
|
48
|
+
) {}
|
|
49
|
+
|
|
50
|
+
static fromRequest(req: CreateOrderRequest): Order {
|
|
51
|
+
return new Order(
|
|
52
|
+
generateId(),
|
|
53
|
+
req.customerId,
|
|
54
|
+
req.items.map(i => new OrderItem(i)),
|
|
55
|
+
'pending',
|
|
56
|
+
req.items.reduce((sum, i) => sum + i.price * i.quantity, 0),
|
|
57
|
+
new Date(),
|
|
58
|
+
{ source: req.source || 'web' }
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
toRecord(): OrderRecord {
|
|
63
|
+
return {
|
|
64
|
+
id: this.id,
|
|
65
|
+
customer_id: this.customerId,
|
|
66
|
+
items: JSON.stringify(this.items),
|
|
67
|
+
status: this.status,
|
|
68
|
+
total: this.total,
|
|
69
|
+
created_at: this.createdAt.toISOString(),
|
|
70
|
+
metadata: this.metadata
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
toResponse(): OrderResponse {
|
|
75
|
+
return {
|
|
76
|
+
id: this.id,
|
|
77
|
+
customerId: this.customerId,
|
|
78
|
+
items: this.items,
|
|
79
|
+
status: this.status,
|
|
80
|
+
total: this.total,
|
|
81
|
+
createdAt: this.createdAt.toISOString()
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
validate(): Result<Order> {
|
|
86
|
+
if (this.items.length === 0) {
|
|
87
|
+
return Err('Order must have at least one item');
|
|
88
|
+
}
|
|
89
|
+
if (this.total < 0) {
|
|
90
|
+
return Err('Order total cannot be negative');
|
|
91
|
+
}
|
|
92
|
+
return Ok(this);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Schema Patterns
|
|
98
|
+
|
|
99
|
+
### Relational Design
|
|
100
|
+
|
|
101
|
+
**Orders table:**
|
|
102
|
+
```sql
|
|
103
|
+
CREATE TABLE orders (
|
|
104
|
+
id UUID PRIMARY KEY,
|
|
105
|
+
customer_id UUID NOT NULL REFERENCES customers(id),
|
|
106
|
+
status VARCHAR(20) NOT NULL CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered')),
|
|
107
|
+
total DECIMAL(10,2) NOT NULL,
|
|
108
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
109
|
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
CREATE INDEX idx_orders_customer ON orders(customer_id);
|
|
113
|
+
CREATE INDEX idx_orders_status ON orders(status);
|
|
114
|
+
CREATE INDEX idx_orders_created ON orders(created_at DESC);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Order items (normalized):**
|
|
118
|
+
```sql
|
|
119
|
+
CREATE TABLE order_items (
|
|
120
|
+
id UUID PRIMARY KEY,
|
|
121
|
+
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
|
|
122
|
+
product_id UUID NOT NULL,
|
|
123
|
+
quantity INT NOT NULL CHECK (quantity > 0),
|
|
124
|
+
price DECIMAL(10,2) NOT NULL,
|
|
125
|
+
UNIQUE(order_id, product_id)
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
CREATE INDEX idx_order_items_order ON order_items(order_id);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### DynamoDB Single Table Design
|
|
132
|
+
|
|
133
|
+
**Items structure:**
|
|
134
|
+
```typescript
|
|
135
|
+
{
|
|
136
|
+
PK: 'ORDER#123',
|
|
137
|
+
SK: 'ORDER#123',
|
|
138
|
+
type: 'Order',
|
|
139
|
+
customerId: 'CUST#456',
|
|
140
|
+
status: 'pending',
|
|
141
|
+
total: 99.99,
|
|
142
|
+
items: [...],
|
|
143
|
+
createdAt: '2024-01-21T10:00:00Z',
|
|
144
|
+
|
|
145
|
+
// GSI for customer lookup
|
|
146
|
+
GSI1PK: 'CUST#456',
|
|
147
|
+
GSI1SK: 'ORDER#2024-01-21T10:00:00Z',
|
|
148
|
+
|
|
149
|
+
// GSI for status lookup
|
|
150
|
+
GSI2PK: 'pending',
|
|
151
|
+
GSI2SK: 'ORDER#2024-01-21T10:00:00Z'
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
{
|
|
155
|
+
PK: 'ORDER#123',
|
|
156
|
+
SK: 'ITEM#item-001',
|
|
157
|
+
type: 'OrderItem',
|
|
158
|
+
productId: 'PROD#999',
|
|
159
|
+
quantity: 2,
|
|
160
|
+
price: 49.99
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Key design:**
|
|
165
|
+
- PK = partition key (primary lookup)
|
|
166
|
+
- SK = sort key (range queries)
|
|
167
|
+
- GSI = global secondary index (alternative queries)
|
|
168
|
+
|
|
169
|
+
## Access Patterns
|
|
170
|
+
|
|
171
|
+
### Define Patterns First
|
|
172
|
+
|
|
173
|
+
List all data access patterns **before** designing schema:
|
|
174
|
+
|
|
175
|
+
1. **Find order by ID**
|
|
176
|
+
- Query: `PK = ORDER#123, SK = ORDER#123`
|
|
177
|
+
- Index: Primary key
|
|
178
|
+
|
|
179
|
+
2. **List orders by customer**
|
|
180
|
+
- Query: `GSI1PK = CUST#456`
|
|
181
|
+
- Index: Global secondary index
|
|
182
|
+
|
|
183
|
+
3. **List pending orders by date**
|
|
184
|
+
- Query: `GSI2PK = pending, GSI2SK > date`
|
|
185
|
+
- Index: Global secondary index sorted by date
|
|
186
|
+
|
|
187
|
+
4. **Count orders by status**
|
|
188
|
+
- Query: Scan with filter (or GSI)
|
|
189
|
+
- Performance: Consider status table
|
|
190
|
+
|
|
191
|
+
5. **Get recent orders for customer**
|
|
192
|
+
- Query: `GSI1PK = CUST#456` sorted by date DESC
|
|
193
|
+
- Index: Global secondary index
|
|
194
|
+
|
|
195
|
+
### Query Optimization
|
|
196
|
+
|
|
197
|
+
**N+1 Problem:**
|
|
198
|
+
```typescript
|
|
199
|
+
// BAD: N+1 queries
|
|
200
|
+
const orders = await orderRepo.findByCustomer(customerId);
|
|
201
|
+
const itemsPerOrder = await Promise.all(
|
|
202
|
+
orders.map(o => itemRepo.findByOrder(o.id)) // N queries
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// GOOD: Single query with denormalization
|
|
206
|
+
const orderData = await db.orders
|
|
207
|
+
.where({ customerId })
|
|
208
|
+
.select(['*', 'items']) // Includes denormalized items
|
|
209
|
+
.sort({ createdAt: 'DESC' });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Index Coverage:**
|
|
213
|
+
```sql
|
|
214
|
+
-- Bad: Full table scan
|
|
215
|
+
SELECT * FROM orders WHERE customer_id = $1;
|
|
216
|
+
|
|
217
|
+
-- Good: Index-covered query
|
|
218
|
+
CREATE INDEX idx_orders_customer_status
|
|
219
|
+
ON orders(customer_id, status);
|
|
220
|
+
|
|
221
|
+
SELECT * FROM orders
|
|
222
|
+
WHERE customer_id = $1 AND status = 'pending';
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Data Transformation Patterns
|
|
226
|
+
|
|
227
|
+
### Request → Entity → Record → Response Flow
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// 1. HTTP Request arrives
|
|
231
|
+
POST /orders
|
|
232
|
+
{
|
|
233
|
+
"customerId": "cust-123",
|
|
234
|
+
"items": [{ "productId": "prod-1", "quantity": 2 }]
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 2. Router parses to request type
|
|
238
|
+
const request: CreateOrderRequest = req.body;
|
|
239
|
+
|
|
240
|
+
// 3. Entity transforms from request (with ID generation)
|
|
241
|
+
const order = Order.fromRequest(request);
|
|
242
|
+
|
|
243
|
+
// 4. Entity validates business rules
|
|
244
|
+
const validation = order.validate();
|
|
245
|
+
if (!validation.ok) return error;
|
|
246
|
+
|
|
247
|
+
// 5. Service performs logic, uses repository
|
|
248
|
+
await repo.save(order.toRecord());
|
|
249
|
+
|
|
250
|
+
// 6. Repository converts to database record
|
|
251
|
+
{
|
|
252
|
+
id: "order-abc",
|
|
253
|
+
customer_id: "cust-123",
|
|
254
|
+
items: "[...]",
|
|
255
|
+
status: "pending",
|
|
256
|
+
created_at: "2024-01-21T10:00:00Z"
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// 7. Response converts from record
|
|
260
|
+
const response = order.toResponse();
|
|
261
|
+
|
|
262
|
+
// 8. HTTP Response sends
|
|
263
|
+
{
|
|
264
|
+
"id": "order-abc",
|
|
265
|
+
"customerId": "cust-123",
|
|
266
|
+
"items": [...],
|
|
267
|
+
"status": "pending"
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Transformation Considerations
|
|
272
|
+
|
|
273
|
+
**Immutability:**
|
|
274
|
+
```typescript
|
|
275
|
+
// Bad: Mutation during transform
|
|
276
|
+
toRecord(): OrderRecord {
|
|
277
|
+
this.items.forEach(i => i.status = 'recorded');
|
|
278
|
+
return { items: this.items };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Good: No side effects
|
|
282
|
+
toRecord(): OrderRecord {
|
|
283
|
+
return {
|
|
284
|
+
items: this.items.map(i => ({
|
|
285
|
+
...i,
|
|
286
|
+
// Transform properties as needed
|
|
287
|
+
}))
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
**Versioning:**
|
|
293
|
+
```typescript
|
|
294
|
+
// Handle multiple API versions
|
|
295
|
+
toResponse(version: 'v1' | 'v2'): OrderResponse {
|
|
296
|
+
const base = {
|
|
297
|
+
id: this.id,
|
|
298
|
+
customerId: this.customerId,
|
|
299
|
+
status: this.status
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (version === 'v2') {
|
|
303
|
+
return { ...base, total: this.total };
|
|
304
|
+
}
|
|
305
|
+
return base;
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Partial Loading:**
|
|
310
|
+
```typescript
|
|
311
|
+
// Only load fields needed for the operation
|
|
312
|
+
async findOrderSummary(id: string): Promise<OrderSummary> {
|
|
313
|
+
const record = await db.orders
|
|
314
|
+
.select(['id', 'status', 'total'])
|
|
315
|
+
.findOne({ id });
|
|
316
|
+
return { id: record.id, status: record.status, total: record.total };
|
|
317
|
+
}
|
|
318
|
+
```
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# DDD Patterns Reference
|
|
2
|
+
|
|
3
|
+
Domain-Driven Design patterns for modeling business domains and enforcing consistency.
|
|
4
|
+
|
|
5
|
+
## Aggregates
|
|
6
|
+
|
|
7
|
+
**Definition:** Cluster of domain objects treated as a single unit for data changes
|
|
8
|
+
|
|
9
|
+
**Rules:**
|
|
10
|
+
- One entity is the aggregate root (e.g., Order)
|
|
11
|
+
- External references only to the root
|
|
12
|
+
- Changes go through the root
|
|
13
|
+
- Root enforces invariants
|
|
14
|
+
|
|
15
|
+
**Example:**
|
|
16
|
+
```typescript
|
|
17
|
+
class Order { // Aggregate root
|
|
18
|
+
private items: OrderItem[]; // Part of aggregate
|
|
19
|
+
|
|
20
|
+
addItem(item: OrderItem): Result<Order> {
|
|
21
|
+
// Root enforces invariants
|
|
22
|
+
if (this.items.length >= 100) {
|
|
23
|
+
return Err('Order cannot have more than 100 items');
|
|
24
|
+
}
|
|
25
|
+
this.items.push(item);
|
|
26
|
+
return Ok(this);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Business Rules Enforcement:**
|
|
32
|
+
```typescript
|
|
33
|
+
class Order {
|
|
34
|
+
private status: OrderStatus;
|
|
35
|
+
private appliedDiscounts: Discount[] = [];
|
|
36
|
+
|
|
37
|
+
applyDiscount(discount: Discount): Result<void> {
|
|
38
|
+
// Business rule: can't apply discount to shipped orders
|
|
39
|
+
if (this.status === 'shipped') {
|
|
40
|
+
return Err('Cannot discount shipped orders');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Business rule: no duplicate discounts
|
|
44
|
+
if (this.appliedDiscounts.some(d => d.id === discount.id)) {
|
|
45
|
+
return Err('Discount already applied');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
this.appliedDiscounts.push(discount);
|
|
49
|
+
return Ok(undefined);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Value Objects
|
|
55
|
+
|
|
56
|
+
**Definition:** Immutable objects defined by their attributes, not identity
|
|
57
|
+
|
|
58
|
+
**Characteristics:**
|
|
59
|
+
- No identity (equality by value)
|
|
60
|
+
- Immutable
|
|
61
|
+
- Self-validating
|
|
62
|
+
- Encapsulate validation logic
|
|
63
|
+
|
|
64
|
+
**Example:**
|
|
65
|
+
```typescript
|
|
66
|
+
class Money {
|
|
67
|
+
constructor(
|
|
68
|
+
public readonly amount: number,
|
|
69
|
+
public readonly currency: string
|
|
70
|
+
) {
|
|
71
|
+
if (amount < 0) throw new Error('Amount cannot be negative');
|
|
72
|
+
if (!['USD', 'EUR', 'GBP'].includes(currency)) {
|
|
73
|
+
throw new Error('Unsupported currency');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
add(other: Money): Money {
|
|
78
|
+
if (this.currency !== other.currency) {
|
|
79
|
+
throw new Error('Cannot add different currencies');
|
|
80
|
+
}
|
|
81
|
+
return new Money(this.amount + other.amount, this.currency);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
equals(other: Money): boolean {
|
|
85
|
+
return this.amount === other.amount && this.currency === other.currency;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static fromCents(cents: number, currency: string): Money {
|
|
89
|
+
return new Money(cents / 100, currency);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**More Examples:**
|
|
95
|
+
```typescript
|
|
96
|
+
// Email VO with validation
|
|
97
|
+
class Email {
|
|
98
|
+
readonly value: string;
|
|
99
|
+
|
|
100
|
+
constructor(value: string) {
|
|
101
|
+
if (!this.isValid(value)) throw new Error('Invalid email');
|
|
102
|
+
this.value = value.toLowerCase();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
private isValid(email: string): boolean {
|
|
106
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
equals(other: Email): boolean {
|
|
110
|
+
return this.value === other.value;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Address VO with composition
|
|
115
|
+
class Address {
|
|
116
|
+
constructor(
|
|
117
|
+
public readonly street: string,
|
|
118
|
+
public readonly city: string,
|
|
119
|
+
public readonly zipCode: string,
|
|
120
|
+
public readonly country: string
|
|
121
|
+
) {}
|
|
122
|
+
|
|
123
|
+
equals(other: Address): boolean {
|
|
124
|
+
return this.street === other.street &&
|
|
125
|
+
this.city === other.city &&
|
|
126
|
+
this.zipCode === other.zipCode &&
|
|
127
|
+
this.country === other.country;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Domain Events
|
|
133
|
+
|
|
134
|
+
**Definition:** Something that happened in the domain that domain experts care about
|
|
135
|
+
|
|
136
|
+
**Characteristics:**
|
|
137
|
+
- Immutable facts about what happened
|
|
138
|
+
- Named in past tense
|
|
139
|
+
- Include context about what changed
|
|
140
|
+
- Timestamped
|
|
141
|
+
|
|
142
|
+
**Example:**
|
|
143
|
+
```typescript
|
|
144
|
+
class OrderPlaced {
|
|
145
|
+
constructor(
|
|
146
|
+
public readonly orderId: string,
|
|
147
|
+
public readonly customerId: string,
|
|
148
|
+
public readonly total: number,
|
|
149
|
+
public readonly occurredAt: Date
|
|
150
|
+
) {}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Service publishes events
|
|
154
|
+
const order = await orderService.createOrder(request);
|
|
155
|
+
await eventBus.publish(
|
|
156
|
+
new OrderPlaced(order.id, order.customerId, order.total, new Date())
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Events cross bounded contexts
|
|
160
|
+
class OrderShipped {
|
|
161
|
+
constructor(
|
|
162
|
+
public readonly orderId: string,
|
|
163
|
+
public readonly shippingAddress: Address,
|
|
164
|
+
public readonly trackingNumber: string,
|
|
165
|
+
public readonly occurredAt: Date
|
|
166
|
+
) {}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Event-Driven Workflows:**
|
|
171
|
+
```typescript
|
|
172
|
+
// Order context publishes OrderPlaced
|
|
173
|
+
// Inventory context listens and consumes
|
|
174
|
+
class InventoryService {
|
|
175
|
+
async handleOrderPlaced(event: OrderPlaced): Promise<Result<void>> {
|
|
176
|
+
// Reserve inventory in response to order
|
|
177
|
+
const result = await this.inventoryRepo.reserve(event.orderId);
|
|
178
|
+
if (!result.ok) {
|
|
179
|
+
// Handle reservation failure
|
|
180
|
+
await this.eventBus.publish(new InventoryReservationFailed(event.orderId));
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Bounded Contexts
|
|
188
|
+
|
|
189
|
+
**Definition:** Explicit boundaries where a domain model applies
|
|
190
|
+
|
|
191
|
+
**Guidelines:**
|
|
192
|
+
- Each context has its own ubiquitous language
|
|
193
|
+
- Contexts communicate via well-defined contracts
|
|
194
|
+
- Same concept may have different models in different contexts
|
|
195
|
+
- Example: "Customer" in Sales context vs "User" in Auth context
|
|
196
|
+
|
|
197
|
+
**Context Composition:**
|
|
198
|
+
```
|
|
199
|
+
Bounded Context: "Orders"
|
|
200
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
201
|
+
│ │
|
|
202
|
+
│ Effectful Edge Functional Core │
|
|
203
|
+
│ ┌──────────────────┐ ┌──────────────────┐ │
|
|
204
|
+
│ │ Router │────────────▶│ Service │ │
|
|
205
|
+
│ │ POST /orders │ │ OrderService │ │
|
|
206
|
+
│ └──────────────────┘ └────────┬─────────┘ │
|
|
207
|
+
│ │ │
|
|
208
|
+
│ ┌──────────────────┐ ┌────────▼─────────┐ │
|
|
209
|
+
│ │ Repository │◀────────────│ Aggregate │ │
|
|
210
|
+
│ │ OrderRepository │ │ Order (root) │ │
|
|
211
|
+
│ └──────────────────┘ │ └─ OrderItem[] │ │
|
|
212
|
+
│ │ └─ Money (VO) │ │
|
|
213
|
+
│ ┌──────────────────┐ └──────────────────┘ │
|
|
214
|
+
│ │ Producer │◀── Domain Event: OrderPlaced │
|
|
215
|
+
│ └────────┬─────────┘ │
|
|
216
|
+
└────────────│───────────────────────────────────────────────────┘
|
|
217
|
+
│
|
|
218
|
+
▼ Events cross context boundaries
|
|
219
|
+
┌────────────────────────────────────────────────────────────────┐
|
|
220
|
+
│ Bounded Context: "Inventory" │
|
|
221
|
+
│ Consumer ──▶ InventoryService ──▶ StockLevel (Aggregate) │
|
|
222
|
+
└────────────────────────────────────────────────────────────────┘
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
**Context Anti-Patterns:**
|
|
226
|
+
- Shared database (breaks boundary enforcement)
|
|
227
|
+
- Generic "core" or "common" domain (pollutes context boundaries)
|
|
228
|
+
- Cross-context validation (move validation to appropriate context)
|
|
229
|
+
- Tight coupling via shared types (use context-specific DTOs)
|
|
230
|
+
|
|
231
|
+
## Composition Rules
|
|
232
|
+
|
|
233
|
+
| DDD Pattern | Maps To | Location |
|
|
234
|
+
|-------------|---------|----------|
|
|
235
|
+
| Bounded Context | Module/Package boundary | Contains all layers |
|
|
236
|
+
| Aggregate | Entity cluster | Functional Core |
|
|
237
|
+
| Aggregate Root | Primary Entity | Entity layer |
|
|
238
|
+
| Value Object | Immutable type | Entity layer |
|
|
239
|
+
| Domain Event | Event class | Published from Service, consumed by edge |
|
|
240
|
+
| Repository | Data access | Effectful Edge (interface in Core) |
|
|
241
|
+
|
|
242
|
+
**Key Interactions:**
|
|
243
|
+
1. **Router → Service**: HTTP request parsed, passed to service
|
|
244
|
+
2. **Service → Aggregate**: Service orchestrates aggregate operations
|
|
245
|
+
3. **Aggregate → Repository**: Service uses repository to persist aggregate
|
|
246
|
+
4. **Service → Producer**: Service publishes domain events after state change
|
|
247
|
+
5. **Consumer → Service**: Events from other contexts trigger service operations
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: oracle-challenge
|
|
3
|
+
description: Challenge an approach with critical thinking. Use when questioning assumptions, validating decisions, testing approach validity, or preventing automatic agreement.
|
|
4
|
+
user-invocable: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Challenge: Critical Thinking Prompt
|
|
8
|
+
|
|
9
|
+
## Step 1: Parse Challenge Request
|
|
10
|
+
|
|
11
|
+
<strategist>
|
|
12
|
+
@agent-oracle
|
|
13
|
+
|
|
14
|
+
Analyze the challenge request: $ARGUMENTS
|
|
15
|
+
|
|
16
|
+
**Challenge Extraction:**
|
|
17
|
+
- **Core concern**: Extract the main doubt or question
|
|
18
|
+
- **Target approach**: Identify what is being challenged
|
|
19
|
+
- **Context**: Relevant background from current session
|
|
20
|
+
- **Specific aspects**: Particular elements to question
|
|
21
|
+
|
|
22
|
+
**Challenge Summary:**
|
|
23
|
+
You're challenging: [identified approach]
|
|
24
|
+
Because: [extracted concern]
|
|
25
|
+
In context of: [session context]
|
|
26
|
+
</strategist>
|
|
27
|
+
|
|
28
|
+
## Step 2: Set Up Critical Thinking Framework
|
|
29
|
+
|
|
30
|
+
<framework>
|
|
31
|
+
**What to Question:**
|
|
32
|
+
- **Underlying assumptions**: What beliefs support this approach?
|
|
33
|
+
- **Evidence base**: What data or experience validates it?
|
|
34
|
+
- **Context fit**: How well does it work in your specific situation?
|
|
35
|
+
- **Alternatives considered**: What other options were explored?
|
|
36
|
+
- **Risk factors**: What could go wrong with this approach?
|
|
37
|
+
|
|
38
|
+
**Critical Thinking Prompts:**
|
|
39
|
+
- Is this approach solving the right problem?
|
|
40
|
+
- Are the underlying assumptions still valid?
|
|
41
|
+
- What evidence contradicts this direction?
|
|
42
|
+
- How does this fit with your constraints and goals?
|
|
43
|
+
- What are the opportunity costs?
|
|
44
|
+
</framework>
|
|
45
|
+
|
|
46
|
+
## Step 3: Sequential Thinking Analysis
|
|
47
|
+
|
|
48
|
+
<strategist>
|
|
49
|
+
@agent-oracle
|
|
50
|
+
|
|
51
|
+
Use sequential thinking (mcp__sequential-thinking__sequentialthinking) to analyze this challenge:
|
|
52
|
+
|
|
53
|
+
**Thought 1**: Question the fundamental assumptions
|
|
54
|
+
**Thought 2**: Examine contradictory evidence
|
|
55
|
+
**Thought 3**: Explore alternative approaches
|
|
56
|
+
**Thought 4**: Assess context-specific fit
|
|
57
|
+
**Thought 5**: Evaluate risks and trade-offs
|
|
58
|
+
**Thought 6**: Synthesize findings into recommendation
|
|
59
|
+
|
|
60
|
+
Build systematically through evidence, alternatives, and risks.
|
|
61
|
+
Continue until you reach a clear conclusion.
|
|
62
|
+
</strategist>
|
|
63
|
+
|
|
64
|
+
## Step 4: Critical Evaluation Output
|
|
65
|
+
|
|
66
|
+
**Self-Critique Questions:**
|
|
67
|
+
- Does the analysis address the user's specific context?
|
|
68
|
+
- Are the recommendations practical and implementable?
|
|
69
|
+
- Have we considered the most important constraints?
|
|
70
|
+
- Are there any blind spots or missing perspectives?
|
|
71
|
+
|
|
72
|
+
**Final Synthesis:**
|
|
73
|
+
- **Assumption validity**: Are the underlying assumptions sound?
|
|
74
|
+
- **Evidence assessment**: Does evidence support or contradict?
|
|
75
|
+
- **Alternative recommendation**: If current approach is problematic, what instead?
|
|
76
|
+
- **Risk mitigation**: How to address identified concerns?
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Usage Examples
|
|
81
|
+
|
|
82
|
+
**Challenge Technical Decisions:**
|
|
83
|
+
```
|
|
84
|
+
/atelier-challenge "Do we really need a microservices architecture for this simple app?"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Challenge Implementation Approach:**
|
|
88
|
+
```
|
|
89
|
+
/atelier-challenge "I think this caching strategy will actually slow things down"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Challenge Requirements:**
|
|
93
|
+
```
|
|
94
|
+
/atelier-challenge "Are we solving the right problem with this feature?"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Challenge Architectural Patterns:**
|
|
98
|
+
```
|
|
99
|
+
/atelier-challenge "Should we really use event sourcing for this use case?"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## When to Use Challenge
|
|
103
|
+
|
|
104
|
+
**Before Major Decisions:**
|
|
105
|
+
- Architecture choices
|
|
106
|
+
- Technology stack decisions
|
|
107
|
+
- Design pattern selection
|
|
108
|
+
- Implementation approach
|
|
109
|
+
|
|
110
|
+
**When Something Feels Off:**
|
|
111
|
+
- "This seems overly complex"
|
|
112
|
+
- "I'm not sure this solves the real problem"
|
|
113
|
+
- "This approach feels wrong"
|
|
114
|
+
- "Are we over-engineering this?"
|
|
115
|
+
|
|
116
|
+
**To Prevent Automatic Agreement:**
|
|
117
|
+
- When you want genuine critical evaluation
|
|
118
|
+
- When you need to challenge conventional wisdom
|
|
119
|
+
- When you want to test your own assumptions
|
|
120
|
+
|
|
121
|
+
## Challenge vs ThinkDeep
|
|
122
|
+
|
|
123
|
+
**Use /atelier-challenge**: Question assumptions, test validity, assess risks, prevent automatic agreement
|
|
124
|
+
**Use /atelier-thinkdeep**: Deep exploration, comprehensive analysis, alternative discovery, complex decisions
|
|
125
|
+
|
|
126
|
+
**Key distinction**: Challenge = critical evaluation, ThinkDeep = deep exploration
|