forgecommandline 0.8.0__py3-none-any.whl
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.
- forgecli/__init__.py +2 -0
- forgecli/ads.py +322 -0
- forgecli/agent.py +1093 -0
- forgecli/art.py +574 -0
- forgecli/attach.py +246 -0
- forgecli/brief.py +215 -0
- forgecli/browsercheck.py +99 -0
- forgecli/checkpoints.py +81 -0
- forgecli/city.py +833 -0
- forgecli/cli.py +1668 -0
- forgecli/config.py +194 -0
- forgecli/courier.py +177 -0
- forgecli/courier_data.py +164 -0
- forgecli/courier_hd.py +431 -0
- forgecli/courier_hd_data.bin +0 -0
- forgecli/courier_hd_data.py +19 -0
- forgecli/ghlogin.py +76 -0
- forgecli/github.py +54 -0
- forgecli/guards.py +403 -0
- forgecli/hooks.py +50 -0
- forgecli/httpapi.py +434 -0
- forgecli/images.py +110 -0
- forgecli/keys.py +152 -0
- forgecli/keyvault.py +320 -0
- forgecli/mcp.py +674 -0
- forgecli/memory.py +42 -0
- forgecli/openapi.py +331 -0
- forgecli/parallel.py +308 -0
- forgecli/prices.py +72 -0
- forgecli/procs.py +144 -0
- forgecli/providers.py +758 -0
- forgecli/quality.py +192 -0
- forgecli/repomap.py +78 -0
- forgecli/roster.py +227 -0
- forgecli/safety.py +93 -0
- forgecli/sandbox.py +83 -0
- forgecli/sessions.py +97 -0
- forgecli/skills.py +446 -0
- forgecli/tools.py +1184 -0
- forgecli/ui.py +860 -0
- forgecli/worktrees.py +79 -0
- forgecommandline-0.8.0.data/data/skills/commit/SKILL.md +14 -0
- forgecommandline-0.8.0.data/data/skills/debug/SKILL.md +17 -0
- forgecommandline-0.8.0.data/data/skills/explain/SKILL.md +14 -0
- forgecommandline-0.8.0.data/data/skills/init/SKILL.md +19 -0
- forgecommandline-0.8.0.data/data/skills/library/LICENSES/agent-skills-MIT.txt +21 -0
- forgecommandline-0.8.0.data/data/skills/library/LICENSES/wondelai-MIT.txt +23 -0
- forgecommandline-0.8.0.data/data/skills/library/README.md +9 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/accessibility-checklist.md +160 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/definition-of-done.md +67 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/observability-checklist.md +92 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/orchestration-patterns.md +370 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/performance-checklist.md +238 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/security-checklist.md +248 -0
- forgecommandline-0.8.0.data/data/skills/library/_references/testing-patterns.md +235 -0
- forgecommandline-0.8.0.data/data/skills/library/api-and-interface-design/SKILL.md +367 -0
- forgecommandline-0.8.0.data/data/skills/library/architecture-optimization/SKILL.md +279 -0
- forgecommandline-0.8.0.data/data/skills/library/architecture-optimization/references/artifact-templates.md +157 -0
- forgecommandline-0.8.0.data/data/skills/library/architecture-optimization/references/methods.md +87 -0
- forgecommandline-0.8.0.data/data/skills/library/awwwards-tier-web-application/SKILL.md +877 -0
- forgecommandline-0.8.0.data/data/skills/library/browser-testing-with-devtools/SKILL.md +317 -0
- forgecommandline-0.8.0.data/data/skills/library/ci-cd-and-automation/SKILL.md +390 -0
- forgecommandline-0.8.0.data/data/skills/library/code-review-and-quality/SKILL.md +398 -0
- forgecommandline-0.8.0.data/data/skills/library/code-simplification/SKILL.md +331 -0
- forgecommandline-0.8.0.data/data/skills/library/constraint-driven-development/SKILL.md +311 -0
- forgecommandline-0.8.0.data/data/skills/library/constraint-driven-development/references/floor-guard.md +182 -0
- forgecommandline-0.8.0.data/data/skills/library/context-engineering/SKILL.md +353 -0
- forgecommandline-0.8.0.data/data/skills/library/debugging-and-error-recovery/SKILL.md +300 -0
- forgecommandline-0.8.0.data/data/skills/library/deprecation-and-migration/SKILL.md +247 -0
- forgecommandline-0.8.0.data/data/skills/library/documentation-and-adrs/SKILL.md +297 -0
- forgecommandline-0.8.0.data/data/skills/library/doubt-driven-development/SKILL.md +243 -0
- forgecommandline-0.8.0.data/data/skills/library/frontend-ui-engineering/SKILL.md +340 -0
- forgecommandline-0.8.0.data/data/skills/library/git-workflow-and-versioning/SKILL.md +355 -0
- forgecommandline-0.8.0.data/data/skills/library/idea-refine/SKILL.md +193 -0
- forgecommandline-0.8.0.data/data/skills/library/idea-refine/examples.md +238 -0
- forgecommandline-0.8.0.data/data/skills/library/idea-refine/frameworks.md +99 -0
- forgecommandline-0.8.0.data/data/skills/library/idea-refine/refinement-criteria.md +113 -0
- forgecommandline-0.8.0.data/data/skills/library/idea-refine/scripts/idea-refine.sh +15 -0
- forgecommandline-0.8.0.data/data/skills/library/improve-code-quality/SKILL.md +275 -0
- forgecommandline-0.8.0.data/data/skills/library/improve-code-quality/references/artifact-templates.md +101 -0
- forgecommandline-0.8.0.data/data/skills/library/incremental-implementation/SKILL.md +249 -0
- forgecommandline-0.8.0.data/data/skills/library/interview-me/SKILL.md +235 -0
- forgecommandline-0.8.0.data/data/skills/library/observability-and-instrumentation/SKILL.md +238 -0
- forgecommandline-0.8.0.data/data/skills/library/performance-optimization/SKILL.md +267 -0
- forgecommandline-0.8.0.data/data/skills/library/performance-optimization/references/optimization-patterns.md +250 -0
- forgecommandline-0.8.0.data/data/skills/library/planning-and-task-breakdown/SKILL.md +257 -0
- forgecommandline-0.8.0.data/data/skills/library/remove-technical-debt/SKILL.md +203 -0
- forgecommandline-0.8.0.data/data/skills/library/remove-technical-debt/references/artifact-templates.md +79 -0
- forgecommandline-0.8.0.data/data/skills/library/security-and-hardening/SKILL.md +216 -0
- forgecommandline-0.8.0.data/data/skills/library/security-and-hardening/references/hardening-patterns.md +324 -0
- forgecommandline-0.8.0.data/data/skills/library/shipping-and-launch/SKILL.md +336 -0
- forgecommandline-0.8.0.data/data/skills/library/source-driven-development/SKILL.md +216 -0
- forgecommandline-0.8.0.data/data/skills/library/spec-driven-development/SKILL.md +259 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/SKILL.md +207 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/building-blocks.md +272 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/common-designs.md +257 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/database-scaling.md +295 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/estimation-numbers.md +250 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/four-step-process.md +286 -0
- forgecommandline-0.8.0.data/data/skills/library/system-design/references/reliability-operations.md +373 -0
- forgecommandline-0.8.0.data/data/skills/library/test-driven-development/SKILL.md +398 -0
- forgecommandline-0.8.0.data/data/skills/library/using-agent-skills/SKILL.md +192 -0
- forgecommandline-0.8.0.data/data/skills/review/SKILL.md +16 -0
- forgecommandline-0.8.0.data/data/skills/run/SKILL.md +17 -0
- forgecommandline-0.8.0.data/data/skills/security-review/SKILL.md +20 -0
- forgecommandline-0.8.0.data/data/skills/simplify/SKILL.md +18 -0
- forgecommandline-0.8.0.data/data/skills/test/SKILL.md +16 -0
- forgecommandline-0.8.0.data/data/skills/web-build/SKILL.md +25 -0
- forgecommandline-0.8.0.dist-info/METADATA +357 -0
- forgecommandline-0.8.0.dist-info/RECORD +113 -0
- forgecommandline-0.8.0.dist-info/WHEEL +5 -0
- forgecommandline-0.8.0.dist-info/entry_points.txt +2 -0
- forgecommandline-0.8.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: api-and-interface-design
|
|
3
|
+
description: Guides stable API and interface design. Use when designing APIs, module boundaries, or any public interface. Use when creating REST or GraphQL endpoints, defining type contracts between modules, or establishing boundaries between frontend and backend.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# API and Interface Design
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Design stable, well-documented interfaces that are hard to misuse. Good interfaces make the right thing easy and the wrong thing hard. This applies to REST APIs, GraphQL schemas, module boundaries, component props, and any surface where one piece of code talks to another.
|
|
11
|
+
|
|
12
|
+
## When to Use
|
|
13
|
+
|
|
14
|
+
- Designing new API endpoints
|
|
15
|
+
- Defining module boundaries or contracts between teams
|
|
16
|
+
- Creating component prop interfaces
|
|
17
|
+
- Establishing database schema that informs API shape
|
|
18
|
+
- Changing existing public interfaces
|
|
19
|
+
|
|
20
|
+
## Core Principles
|
|
21
|
+
|
|
22
|
+
### Hyrum's Law
|
|
23
|
+
|
|
24
|
+
> With a sufficient number of users of an API, all observable behaviors of your system will be depended on by somebody, regardless of what you promise in the contract.
|
|
25
|
+
|
|
26
|
+
This means: every public behavior — including undocumented quirks, error message text, timing, and ordering — becomes a de facto contract once users depend on it. Design implications:
|
|
27
|
+
|
|
28
|
+
- **Be intentional about what you expose.** Every observable behavior is a potential commitment.
|
|
29
|
+
- **Don't leak implementation details.** If users can observe it, they will depend on it.
|
|
30
|
+
- **Plan for deprecation at design time.** See `deprecation-and-migration` for how to safely remove things users depend on.
|
|
31
|
+
- **Tests are not enough.** Even with perfect contract tests, Hyrum's Law means "safe" changes can break real users who depend on undocumented behavior.
|
|
32
|
+
|
|
33
|
+
### The One-Version Rule
|
|
34
|
+
|
|
35
|
+
Avoid forcing consumers to choose between multiple versions of the same dependency or API. Diamond dependency problems arise when different consumers need different versions of the same thing. Design for a world where only one version exists at a time — extend rather than fork.
|
|
36
|
+
|
|
37
|
+
### 1. Contract First
|
|
38
|
+
|
|
39
|
+
Define the interface before implementing it. The contract is the spec — implementation follows.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Define the contract first
|
|
43
|
+
interface TaskAPI {
|
|
44
|
+
// Creates a task and returns the created task with server-generated fields
|
|
45
|
+
createTask(input: CreateTaskInput): Promise<Task>;
|
|
46
|
+
|
|
47
|
+
// Returns paginated tasks matching filters
|
|
48
|
+
listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
|
|
49
|
+
|
|
50
|
+
// Returns a single task or throws NotFoundError
|
|
51
|
+
getTask(id: string): Promise<Task>;
|
|
52
|
+
|
|
53
|
+
// Partial update — only provided fields change
|
|
54
|
+
updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
|
|
55
|
+
|
|
56
|
+
// Idempotent delete — succeeds even if already deleted
|
|
57
|
+
deleteTask(id: string): Promise<void>;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 2. Consistent Error Semantics
|
|
62
|
+
|
|
63
|
+
Pick one error strategy and use it everywhere:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// REST: HTTP status codes + structured error body
|
|
67
|
+
// Every error response follows the same shape
|
|
68
|
+
interface APIError {
|
|
69
|
+
error: {
|
|
70
|
+
code: string; // Machine-readable: "VALIDATION_ERROR"
|
|
71
|
+
message: string; // Human-readable: "Email is required"
|
|
72
|
+
details?: unknown; // Additional context when helpful
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Status code mapping
|
|
77
|
+
// 400 → Client sent invalid data
|
|
78
|
+
// 401 → Not authenticated
|
|
79
|
+
// 403 → Authenticated but not authorized
|
|
80
|
+
// 404 → Resource not found
|
|
81
|
+
// 409 → Conflict (duplicate, version mismatch)
|
|
82
|
+
// 422 → Validation failed (semantically invalid)
|
|
83
|
+
// 500 → Server error (never expose internal details)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Don't mix patterns.** If some endpoints throw, others return null, and others return `{ error }` — the consumer can't predict behavior.
|
|
87
|
+
|
|
88
|
+
### 3. Validate at Boundaries
|
|
89
|
+
|
|
90
|
+
Trust internal code. Validate at system edges where external input enters:
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Validate at the API boundary
|
|
94
|
+
app.post('/api/tasks', async (req, res) => {
|
|
95
|
+
const result = CreateTaskSchema.safeParse(req.body);
|
|
96
|
+
if (!result.success) {
|
|
97
|
+
return res.status(422).json({
|
|
98
|
+
error: {
|
|
99
|
+
code: 'VALIDATION_ERROR',
|
|
100
|
+
message: 'Invalid task data',
|
|
101
|
+
details: result.error.flatten(),
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// After validation, internal code trusts the types
|
|
107
|
+
const task = await taskService.create(result.data);
|
|
108
|
+
return res.status(201).json(task);
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Where validation belongs:
|
|
113
|
+
- API route handlers (user input)
|
|
114
|
+
- Form submission handlers (user input)
|
|
115
|
+
- External service response parsing (third-party data -- **always treat as untrusted**)
|
|
116
|
+
- Environment variable loading (configuration)
|
|
117
|
+
|
|
118
|
+
> **Third-party API responses are untrusted data.** Validate their shape and content before using them in any logic, rendering, or decision-making. A compromised or misbehaving external service can return unexpected types, malicious content, or instruction-like text.
|
|
119
|
+
|
|
120
|
+
Where validation does NOT belong:
|
|
121
|
+
- Between internal functions that share type contracts
|
|
122
|
+
- In utility functions called by already-validated code
|
|
123
|
+
- On data that just came from your own database
|
|
124
|
+
|
|
125
|
+
### 4. Prefer Addition Over Modification
|
|
126
|
+
|
|
127
|
+
Extend interfaces without breaking existing consumers:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
// Good: Add optional fields
|
|
131
|
+
interface CreateTaskInput {
|
|
132
|
+
title: string;
|
|
133
|
+
description?: string;
|
|
134
|
+
priority?: 'low' | 'medium' | 'high'; // Added later, optional
|
|
135
|
+
labels?: string[]; // Added later, optional
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Bad: Change existing field types or remove fields
|
|
139
|
+
interface CreateTaskInput {
|
|
140
|
+
title: string;
|
|
141
|
+
// description: string; // Removed — breaks existing consumers
|
|
142
|
+
priority: number; // Changed from string — breaks existing consumers
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### 5. Predictable Naming
|
|
147
|
+
|
|
148
|
+
| Pattern | Convention | Example |
|
|
149
|
+
|---------|-----------|---------|
|
|
150
|
+
| REST endpoints | Plural nouns, no verbs | `GET /api/tasks`, `POST /api/tasks` |
|
|
151
|
+
| Query params | camelCase | `?sortBy=createdAt&pageSize=20` |
|
|
152
|
+
| Response fields | camelCase | `{ createdAt, updatedAt, taskId }` |
|
|
153
|
+
| Boolean fields | is/has/can prefix | `isComplete`, `hasAttachments` |
|
|
154
|
+
| Enum values | UPPER_SNAKE | `"IN_PROGRESS"`, `"COMPLETED"` |
|
|
155
|
+
|
|
156
|
+
### 6. Honouring an Idempotency Key
|
|
157
|
+
|
|
158
|
+
Accepting an `Idempotency-Key` is the contract. Honouring it is the implementation, and it is where the money is lost — a key the server accepts but handles carelessly is worse than no key at all, because the client now believes retrying is safe.
|
|
159
|
+
|
|
160
|
+
**Derive the key from the intent, not the attempt.** The key must be stable across retries of one intent and different across distinct intents:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
crypto.randomUUID() // ✗ new key per attempt — every retry is a new charge
|
|
164
|
+
`${userId}:${amount}` // ✗ two legitimate $50 charges collapse into one
|
|
165
|
+
`${orderId}:${Date.now()}` // ✗ a timestamp is randomUUID() wearing a hat
|
|
166
|
+
|
|
167
|
+
req.headers['idempotency-key'] // ✓ client generates once, reuses on retry
|
|
168
|
+
`charge:v1:${orderId}` // ✓ derived from an immutable identifier
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The key comes from the client or the initiating event — never from the layer doing the retrying.
|
|
172
|
+
|
|
173
|
+
**Claim atomically. A check followed by an act is a race:**
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// ✗ TOCTOU: two concurrent retries both read "not seen", both charge
|
|
177
|
+
if (!(await db.exists(key))) {
|
|
178
|
+
await chargeCard(amount);
|
|
179
|
+
await db.insert(key);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ✓ let the unique constraint pick the winner
|
|
183
|
+
try {
|
|
184
|
+
await db.insert({ key, state: 'in_progress', requestHash });
|
|
185
|
+
} catch (e) {
|
|
186
|
+
if (isUniqueViolation(e)) return replayOrReject(key);
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
189
|
+
const result = await chargeCard(amount);
|
|
190
|
+
await db.update({ key, state: 'succeeded', response: result });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The unique constraint *is* the mechanism. A store that cannot enforce uniqueness in one operation cannot back this.
|
|
194
|
+
|
|
195
|
+
**Guard the payload.** Same key with a different body is a client bug, and must fail loudly rather than serving the first response to a second request:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
if (existing.requestHash !== hash(req.body)) {
|
|
199
|
+
return res.status(422).json({ error: 'idempotency key reused with a different payload' });
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
**Decide what an in-flight duplicate gets.** The first request is still running when the second arrives — the common case under retry storms:
|
|
204
|
+
|
|
205
|
+
| Strategy | Response | Use when |
|
|
206
|
+
|---|---|---|
|
|
207
|
+
| Reject | `409 Conflict` | Client can retry later; simplest and safest |
|
|
208
|
+
| Wait | Block for the result, bounded | Caller needs it synchronously |
|
|
209
|
+
| Return pending | `202` + status URL | Long-running effects |
|
|
210
|
+
|
|
211
|
+
Never let the second caller through because the first "seems stuck". A stalled attempt whose fate is unknown is exactly when duplicating costs most.
|
|
212
|
+
|
|
213
|
+
**Every call has three outcomes, not two: success, failure, and _unknown_.** A timeout tells you nothing about whether the effect applied. Record the intent *before* calling out, so a crash between the call and the response leaves evidence something must resolve later — rather than a silently retried charge.
|
|
214
|
+
|
|
215
|
+
**Set retention from the longest retry chain**, not from disk cost. Keys must outlive every path that can re-deliver the same intent, including a dead-letter queue replayed a week later and any provider dispute window. A 24-hour key TTL behind a 7-day DLQ is a duplicate waiting to happen.
|
|
216
|
+
|
|
217
|
+
## REST API Patterns
|
|
218
|
+
|
|
219
|
+
### Resource Design
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
GET /api/tasks → List tasks (with query params for filtering)
|
|
223
|
+
POST /api/tasks → Create a task
|
|
224
|
+
GET /api/tasks/:id → Get a single task
|
|
225
|
+
PATCH /api/tasks/:id → Update a task (partial)
|
|
226
|
+
DELETE /api/tasks/:id → Delete a task
|
|
227
|
+
|
|
228
|
+
GET /api/tasks/:id/comments → List comments for a task (sub-resource)
|
|
229
|
+
POST /api/tasks/:id/comments → Add a comment to a task
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Pagination
|
|
233
|
+
|
|
234
|
+
Paginate list endpoints:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
// Request
|
|
238
|
+
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
|
|
239
|
+
|
|
240
|
+
// Response
|
|
241
|
+
{
|
|
242
|
+
"data": [...],
|
|
243
|
+
"pagination": {
|
|
244
|
+
"page": 1,
|
|
245
|
+
"pageSize": 20,
|
|
246
|
+
"totalItems": 142,
|
|
247
|
+
"totalPages": 8
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Filtering
|
|
253
|
+
|
|
254
|
+
Use query parameters for filters:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Partial Updates (PATCH)
|
|
261
|
+
|
|
262
|
+
Accept partial objects — only update what's provided:
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Only title changes, everything else preserved
|
|
266
|
+
PATCH /api/tasks/123
|
|
267
|
+
{ "title": "Updated title" }
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## TypeScript Interface Patterns
|
|
271
|
+
|
|
272
|
+
### Use Discriminated Unions for Variants
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Good: Each variant is explicit
|
|
276
|
+
type TaskStatus =
|
|
277
|
+
| { type: 'pending' }
|
|
278
|
+
| { type: 'in_progress'; assignee: string; startedAt: Date }
|
|
279
|
+
| { type: 'completed'; completedAt: Date; completedBy: string }
|
|
280
|
+
| { type: 'cancelled'; reason: string; cancelledAt: Date };
|
|
281
|
+
|
|
282
|
+
// Consumer gets type narrowing
|
|
283
|
+
function getStatusLabel(status: TaskStatus): string {
|
|
284
|
+
switch (status.type) {
|
|
285
|
+
case 'pending': return 'Pending';
|
|
286
|
+
case 'in_progress': return `In progress (${status.assignee})`;
|
|
287
|
+
case 'completed': return `Done on ${status.completedAt}`;
|
|
288
|
+
case 'cancelled': return `Cancelled: ${status.reason}`;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Input/Output Separation
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// Input: what the caller provides
|
|
297
|
+
interface CreateTaskInput {
|
|
298
|
+
title: string;
|
|
299
|
+
description?: string;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Output: what the system returns (includes server-generated fields)
|
|
303
|
+
interface Task {
|
|
304
|
+
id: string;
|
|
305
|
+
title: string;
|
|
306
|
+
description: string | null;
|
|
307
|
+
createdAt: Date;
|
|
308
|
+
updatedAt: Date;
|
|
309
|
+
createdBy: string;
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Use Branded Types for IDs
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
type TaskId = string & { readonly __brand: 'TaskId' };
|
|
317
|
+
type UserId = string & { readonly __brand: 'UserId' };
|
|
318
|
+
|
|
319
|
+
// Prevents accidentally passing a UserId where a TaskId is expected
|
|
320
|
+
function getTask(id: TaskId): Promise<Task> { ... }
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Common Rationalizations
|
|
324
|
+
|
|
325
|
+
| Rationalization | Reality |
|
|
326
|
+
|---|---|
|
|
327
|
+
| "We'll document the API later" | The types ARE the documentation. Define them first. |
|
|
328
|
+
| "We don't need pagination for now" | You will the moment someone has 100+ items. Add it from the start. |
|
|
329
|
+
| "PATCH is complicated, let's just use PUT" | PUT requires the full object every time. PATCH is what clients actually want. |
|
|
330
|
+
| "We'll version the API when we need to" | Breaking changes without versioning break consumers. Design for extension from the start. |
|
|
331
|
+
| "Nobody uses that undocumented behavior" | Hyrum's Law: if it's observable, somebody depends on it. Treat every public behavior as a commitment. |
|
|
332
|
+
| "We can just maintain two versions" | Multiple versions multiply maintenance cost and create diamond dependency problems. Prefer the One-Version Rule. |
|
|
333
|
+
| "Internal APIs don't need contracts" | Internal consumers are still consumers. Contracts prevent coupling and enable parallel work. |
|
|
334
|
+
| "Accepting the Idempotency-Key header is enough" | The header is the contract; storing the key against the result is the implementation. A key you accept but don't honour tells the client retrying is safe when it isn't. |
|
|
335
|
+
| "Our queue guarantees exactly-once delivery" | No queue does across a consumer crash — the broker's ack and your side effect are not in one transaction. Design for at-least-once with idempotent processing. |
|
|
336
|
+
| "Duplicate requests are rare" | They're *correlated*. Retries spike exactly when a dependency is degraded — the moment duplicates are most likely and most expensive. |
|
|
337
|
+
|
|
338
|
+
## Red Flags
|
|
339
|
+
|
|
340
|
+
- Endpoints that return different shapes depending on conditions
|
|
341
|
+
- Inconsistent error formats across endpoints
|
|
342
|
+
- Validation scattered throughout internal code instead of at boundaries
|
|
343
|
+
- Breaking changes to existing fields (type changes, removals)
|
|
344
|
+
- List endpoints without pagination
|
|
345
|
+
- Verbs in REST URLs (`/api/createTask`, `/api/getUsers`)
|
|
346
|
+
- Third-party API responses used without validation or sanitization
|
|
347
|
+
- A `SELECT` for an idempotency key followed by an `INSERT` — that's a race, not a guard
|
|
348
|
+
- An idempotency key derived from a UUID, timestamp, or anything else regenerated per attempt
|
|
349
|
+
- The same key accepted with a different request body, silently returning the first response
|
|
350
|
+
- A key retention window shorter than the longest path that can re-deliver the request
|
|
351
|
+
|
|
352
|
+
## Verification
|
|
353
|
+
|
|
354
|
+
After designing an API:
|
|
355
|
+
|
|
356
|
+
- [ ] Every endpoint has typed input and output schemas
|
|
357
|
+
- [ ] Error responses follow a single consistent format
|
|
358
|
+
- [ ] Validation happens at system boundaries only
|
|
359
|
+
- [ ] List endpoints support pagination
|
|
360
|
+
- [ ] New fields are additive and optional (backward compatible)
|
|
361
|
+
- [ ] Naming follows consistent conventions across all endpoints
|
|
362
|
+
- [ ] API documentation or types are committed alongside the implementation
|
|
363
|
+
- [ ] State-changing endpoints either honour an idempotency key or are documented as unsafe to retry
|
|
364
|
+
- [ ] The key is claimed in one atomic operation, guarded by a unique constraint
|
|
365
|
+
- [ ] A reused key with a different payload fails loudly rather than replaying the wrong response
|
|
366
|
+
- [ ] The in-flight-duplicate response is a deliberate choice (409, wait, or 202) rather than whatever falls out
|
|
367
|
+
- [ ] Key retention outlives the longest retry path, including dead-letter replay
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: architecture-optimization
|
|
3
|
+
description: 'Guided journey from a working codebase grown slow and tangled to one measurably fast, cleanly bounded, and readable. Orchestrates eight skills phase by phase - working-with-legacy-code, clean-architecture, software-design-philosophy, refactoring-patterns, system-design, ddia-systems, release-it, pragmatic-programmer - every phase carries its method inline so it runs standalone, asking the user questions at every decision point and recording results in the project docs/ folder (PERFORMANCE.md, ARCHITECTURE.md, ARCHITECTURE-OPTIMIZATION-PLAN.md) so the journey resumes across sessions. Use when the user wants to make an app faster, untangle drifted boundaries, fix slow endpoints and queries, or says ''it works but it is slow and getting worse''. For an untested prototype, improve-code-quality; for an aged codebase you fear to touch, remove-technical-debt; for greenfield structure, design-code-architecture; for marketing-site page speed, improve-website. For one framework in isolation, invoke that skill directly.'
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: wondelai
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Architecture Optimization
|
|
11
|
+
|
|
12
|
+
Optimize a working codebase on three axes at once — architecture, code quality, and performance —
|
|
13
|
+
without breaking what works. This is an interactive, resumable journey of eight phases: the agent asks
|
|
14
|
+
before every decision and records the outcome in your project's `docs/` folder, so you can stop after
|
|
15
|
+
any phase and pick up later. It is for a system that ships and earns but has grown slow and tangled;
|
|
16
|
+
the structure phases make the code safe and cheap to change, the performance phases make it measurably
|
|
17
|
+
fast, and the closing phases keep it that way.
|
|
18
|
+
|
|
19
|
+
## Core Principle
|
|
20
|
+
|
|
21
|
+
**Measure before optimizing, pin before restructuring — the profiler and the safety net decide, not
|
|
22
|
+
intuition.** Premature optimization is the root of much evil not because optimization is bad, but
|
|
23
|
+
because unmeasured optimization targets the wrong 97% of the code; and a restructure without pinned
|
|
24
|
+
behavior is a gamble, not an improvement. This skill sequences the phases, asks the decision
|
|
25
|
+
questions, and records every choice in `docs/`. The constituent skills carry the method — invoke them
|
|
26
|
+
rather than improvising their frameworks.
|
|
27
|
+
|
|
28
|
+
## Journey Map
|
|
29
|
+
|
|
30
|
+
| Phase | Skill | Question it answers | Artifact |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| 1 | working-with-legacy-code | Is behavior pinned and performance measured, so every change is provable? | Creates docs/PERFORMANCE.md + docs/TECH-DEBT.md; extends docs/TESTING.md — GATE |
|
|
33
|
+
| 2 | clean-architecture | Do dependencies still point inward, or has the boundary drifted as the code grew? | Extends docs/ARCHITECTURE.md |
|
|
34
|
+
| 3 | software-design-philosophy | Are modules deep, or has the structure itself become the complexity? | Extends docs/TECH-DEBT.md |
|
|
35
|
+
| 4 | refactoring-patterns | Can we reshape the hot paths in named, behavior-preserving steps? | Extends docs/TECH-DEBT.md + docs/TESTING.md |
|
|
36
|
+
| 5 | system-design | What does the measured load say the bottleneck is, and what is the cheapest fix? | Extends docs/PERFORMANCE.md + docs/ARCHITECTURE.md |
|
|
37
|
+
| 6 | ddia-systems | Is the data layer the bottleneck — queries, indexes, isolation, derived data? | Extends docs/ARCHITECTURE.md + docs/PERFORMANCE.md |
|
|
38
|
+
| 7 | release-it | Does it stay fast and stable when a dependency is slow or down? | Creates-or-extends docs/RELIABILITY.md |
|
|
39
|
+
| 8 | pragmatic-programmer | What budgets and habits keep it fast and clean after we stop? | Extends docs/PERFORMANCE.md + docs/TECH-DEBT.md + docs/TESTING.md |
|
|
40
|
+
|
|
41
|
+
## Operating Rules
|
|
42
|
+
|
|
43
|
+
1. **Resume first.** Before anything else, read `docs/ARCHITECTURE-OPTIMIZATION-PLAN.md` and every artifact in the Journey Map. If the tracker exists, summarize the journey state in 3-5 lines and ask which phase to enter. Done when the user has confirmed an entry point. A journey with a tracker is resumed, never restarted.
|
|
44
|
+
2. **Intake on first run only.** No tracker: run the Intake below, then create `docs/ARCHITECTURE-OPTIMIZATION-PLAN.md` with every phase statused `pending | in-progress | awaiting-evidence | done | deferred: reason | skipped: reason`. Done when the tracker exists and the user has confirmed the phase plan.
|
|
45
|
+
3. **Phase entry.** Announce: what the phase does, the decision it forces, the artifact it produces, rough effort. Offer proceed / skip / defer — phases marked GATE may be deferred, never skipped. Mark the phase `in-progress` on proceed. Done when the user chose.
|
|
46
|
+
4. **Skill invocation and fallback.** Load the phase's skill and use it: each phase's Invoke line names the skill by slug — use that skill to run the phase. If it is not available, offer: `npx skills add wondelai/skills/<slug> --global`. If the user declines, run the phase from its Brief — the minimum viable method. State which mode you are in.
|
|
47
|
+
5. **In-phase decisions.** Ask every question under "Decide with the user" — with concrete options and your recommendation. Record the choice in the tracker's Key Decisions. A decision made silently is a defect.
|
|
48
|
+
6. **Phase exit.** Present the draft artifact content for sign-off before writing. On approval: write or extend the docs/ files, update the tracker (status, Key Decisions, Next Actions). Done when the files are written and the phase row shows `done`.
|
|
49
|
+
7. **Artifact discipline.** Read before writing; create a file only if missing, otherwise extend — add or update your sections, preserve everyone else's. Files are UPPERCASE in `docs/`. Every recommendation lands as a checkbox or a table row with owner and priority. See [references/artifact-templates.md](references/artifact-templates.md) when creating a docs/ file for the first time — create it from the full skeleton (all section headings), then fill the sections your phase names.
|
|
50
|
+
8. **Never optimize unmeasured, never restructure unpinned.** Every change made to reduce a measured baseline cites that baseline and lands in the PERFORMANCE.md Optimization Ledger with before/after — one that doesn't move its number gets reverted, not kept. (Resilience and gating work — timeouts, breakers, bulkheads, pagination, CI gates — is judged by the Done-when of its own phase, not by a latency delta.) Structural changes touch only code pinned in the Safety Net Map, preserve behavior, and land in structure-only commits separate from behavior and optimization commits.
|
|
51
|
+
|
|
52
|
+
## Intake
|
|
53
|
+
|
|
54
|
+
Ask these before creating the tracker:
|
|
55
|
+
|
|
56
|
+
1. **What does the system do, and what does "too slow" cost** — lost users, SLA breaches, infra bills? (Frames which metric matters and how much effort the journey is worth.)
|
|
57
|
+
2. **What is the evidence so far** — APM traces, slow-query logs, p95 latencies, a cloud bill, or just complaints? (Feeds the Phase 1 baseline; complaints alone mean instrumentation comes first.)
|
|
58
|
+
3. **Which flows or endpoints hurt most, and which modules implement them?** (Picks the hot paths every phase works on.)
|
|
59
|
+
4. **Do automated tests exist and run green?** (Scopes the Phase 1 safety net — unpinned hot paths get pinned before anything touches them.)
|
|
60
|
+
5. **What is the stack** — language, framework, ORM, database, cache — and where does it run? (Gates Phases 5-6.)
|
|
61
|
+
6. **What are the real load numbers** — QPS average and peak, data volumes, growth rate? (Gates Phase 5 — sizing by numbers, not fear.)
|
|
62
|
+
7. **How much of the journey do you want now?** (Phases 1-4 make it safe and clean to change; 5-6 make it fast; 7-8 keep it that way.)
|
|
63
|
+
|
|
64
|
+
Skip heuristics: compress Phases 2-3 to an audit-only pass when the structure is sound and the pain is purely performance — record what the audit found either way and status the phase `done` with an "audit only, no changes" note; skip Phase 7 only when a prior journey's RELIABILITY.md Integration-Point Audit is verifiably current (check the file, don't assume). Never skip Phase 1 — an optimization without a baseline is a guess, and a restructure without a net is a gamble.
|
|
65
|
+
|
|
66
|
+
Then create `docs/ARCHITECTURE-OPTIMIZATION-PLAN.md` from the template and confirm the plan. Done when the tracker exists with every phase statused and the user has confirmed the plan.
|
|
67
|
+
|
|
68
|
+
## Phases
|
|
69
|
+
|
|
70
|
+
Phases run in the listed order — each assumes the previous phase's artifact exists. Structure before speed is deliberate: Phases 2-4 make the hot paths safe and cheap to change, which is what makes the Phase 5-6 optimizations small diffs instead of surgery. Any phase can be entered, skipped, or deferred per the Operating Rules, but Phase 1 gates them all — as two independent nets: pinned behavior unlocks Phases 2-4, and a recorded baseline unlocks Phases 5-6, so structure work need not wait on a profile that takes weeks to gather. Phases 5 and 6 may be swapped when the Phase 1 profile shows the database dominating: fixing an N+1 or a missing index before adding a cache is the skill's own cheapest-first law. When running any phase from its Brief (constituent skill not installed), read [references/methods.md](references/methods.md) first — it carries each phase's full method, checklists, formulas, and heuristics; the Brief is only the summary.
|
|
71
|
+
|
|
72
|
+
### Phase 1 — Pin it and measure it (working-with-legacy-code) — GATE
|
|
73
|
+
|
|
74
|
+
**Purpose:** Make every later change provable twice over — behavior pinned by tests, performance pinned by numbers. No phase touches unpinned code or optimizes an unmeasured path.
|
|
75
|
+
|
|
76
|
+
**Brief (fallback):** Two nets. Behavior: code without tests is legacy code — cover and modify, never
|
|
77
|
+
edit and pray. Find the change points on the hot paths, break inline dependencies at the
|
|
78
|
+
least-invasive seam (Parameterize Constructor with a production default; Extract and Override for one
|
|
79
|
+
buried call), and write characterization tests that photograph actual behavior — assert something
|
|
80
|
+
wrong, read the failure, pin the real value. Performance: profile before touching anything — the
|
|
81
|
+
bottleneck is rarely where intuition points. Record p50/p95/p99 latency, throughput, and resource use
|
|
82
|
+
per hot flow under realistic data volumes (dev-database timings lie), and work the USE method (Gregg)
|
|
83
|
+
per resource: Utilization, Saturation, Errors for CPU, memory, disk, network, and connection pools.
|
|
84
|
+
Set the budget each metric must meet, so "done" is a number, not a feeling.
|
|
85
|
+
|
|
86
|
+
**Invoke:** Use the `working-with-legacy-code` skill with the hot-path modules from intake. Ask for the seams and the smallest characterization-test set that pins current behavior of each flow to be optimized; then capture profiler or APM baselines for those flows.
|
|
87
|
+
|
|
88
|
+
**Decide with the user:** (1) Confirm the hot paths in scope — measured pain, not suspicion. (2) The budget per metric (e.g. checkout p95 < 500ms) and the tool of record (profiler, APM, load test) so before/after numbers stay comparable. (3) Bugs found while characterizing: pin the current behavior and ledger them, never silently fix — callers may depend on the quirk.
|
|
89
|
+
|
|
90
|
+
**Artifact:** Extend docs/TESTING.md `## Safety Net Map` and `## Characterization Backlog`; create docs/PERFORMANCE.md with `## Baselines & Budgets`, `## Load Reality`, `## Profile Findings`, and `## Optimization Ledger`; create-or-extend docs/TECH-DEBT.md `## Debt Ledger` and `## Sprout / Wrap Register` for bugs pinned as-is and untested hosts. Update the tracker.
|
|
91
|
+
|
|
92
|
+
**Done when:** every in-scope flow has pinned behavior (suite green) — which unlocks Phases 2-4 — and a recorded baseline with a budget, which unlocks Phases 5-6. Record the two separately; a profile still being gathered parks at `awaiting-evidence` with a Next Actions row rather than blocking the structure phases.
|
|
93
|
+
|
|
94
|
+
### Phase 2 — Re-draw the drifted boundaries (clean-architecture)
|
|
95
|
+
|
|
96
|
+
**Purpose:** Restore the Dependency Rule the codebase grew away from — mixed concerns are why changes feel risky and why the slow parts can't be optimized in isolation.
|
|
97
|
+
|
|
98
|
+
**Brief (fallback):** Source dependencies point inward: Frameworks → Interface Adapters → Use Cases →
|
|
99
|
+
Entities; nothing inner names anything outer. In a grown codebase the drift is concrete: business
|
|
100
|
+
logic importing the ORM, controllers computing domain rules, a vendor SDK called from everywhere. Map
|
|
101
|
+
the actual dependency graph and list the violations; extract the hot-path business rules into
|
|
102
|
+
framework-free use cases behind owned interfaces (Dependency Inversion) — this also enables Phases
|
|
103
|
+
5-6, because a boundary is where a cache or a queue can later be inserted without surgery. Draw full
|
|
104
|
+
boundaries only at real volatility (DB, external services, delivery); collapse ceremony layers
|
|
105
|
+
elsewhere — direction matters, not folder count.
|
|
106
|
+
|
|
107
|
+
**Invoke:** Use the `clean-architecture` skill with the module map and stack from intake. Ask for the dependency graph, every violation where business logic names the framework, ORM, or a vendor, and the extraction plan for the hot-path use cases — flagging which boundaries earn their cost.
|
|
108
|
+
|
|
109
|
+
**Decide with the user:** How far to push the boundary this pass — hot paths first, never a big-bang re-layering; which vendor gets wrapped behind an owned interface first; which violations get fixed now versus ledgered.
|
|
110
|
+
|
|
111
|
+
**Artifact:** Extend docs/ARCHITECTURE.md `## Layer Map & Dependency Rule` (violation | location | fix | status) and `## Decision Log`. Update the tracker.
|
|
112
|
+
|
|
113
|
+
**Done when:** the dependency graph is mapped, every violation is a tracked row, the hot-path business rules run in tests with no framework, and the suite is green.
|
|
114
|
+
|
|
115
|
+
### Phase 3 — Deepen the modules (software-design-philosophy)
|
|
116
|
+
|
|
117
|
+
**Purpose:** Cut the complexity tax — a grown codebase accretes shallow classes and leaked decisions, and every one of them slows the team down before it slows the code down.
|
|
118
|
+
|
|
119
|
+
**Brief (fallback):** Module depth = functionality ÷ interface complexity. Merge shallow pass-through
|
|
120
|
+
classes that always travel together and share state; hide each design decision in exactly one place —
|
|
121
|
+
information leakage (one decision reflected in many modules) is the top red flag; replace temporal
|
|
122
|
+
decomposition (modules organized by order-of-execution) with modules organized by knowledge.
|
|
123
|
+
Same-abstraction pass-throughs across layers signal a boundary that isn't earning its cost. This is
|
|
124
|
+
the tactical→strategic flip: invest 10-20% now so every later phase touches fewer files. Consolidation
|
|
125
|
+
also collapses call-chain ceremony on hot paths — but readability, not nanoseconds, is the reason.
|
|
126
|
+
|
|
127
|
+
**Invoke:** Use the `software-design-philosophy` skill with the modules mapped in Phase 2. Ask which classes are shallow, where one decision leaks across modules, and for a consolidation plan into deeper modules with smaller interfaces.
|
|
128
|
+
|
|
129
|
+
**Decide with the user:** Which consolidations happen now versus ledgered — guarding against over-merging genuinely unrelated concerns; the design conventions the team adopts going forward.
|
|
130
|
+
|
|
131
|
+
**Artifact:** Extend docs/TECH-DEBT.md `## Smell Inventory` (shallow-module and information-leakage entries with the consolidation applied) and `## Adopted Conventions`. Update the tracker.
|
|
132
|
+
|
|
133
|
+
**Done when:** each shallow-module cluster is consolidated or ledgered with a fix, every identified leaked decision is consolidated or a Smell Inventory row, and the suite is green.
|
|
134
|
+
|
|
135
|
+
### Phase 4 — Refactor the hot paths (refactoring-patterns)
|
|
136
|
+
|
|
137
|
+
**Purpose:** Reshape the code you're about to optimize with named, behavior-preserving transformations — clean first, then fast, because you can't safely optimize what you can't safely change.
|
|
138
|
+
|
|
139
|
+
**Brief (fallback):** Each smell maps to a named refactoring: Extract Method for comment-sized blocks;
|
|
140
|
+
Replace Nested Conditional with Guard Clauses; Replace Conditional with Polymorphism; Introduce
|
|
141
|
+
Parameter Object; Replace Temp with Query. Workflow: tests green → one transformation → tests green →
|
|
142
|
+
commit; a red test means revert, not debug. Fold in the clean-code disciplines as you pass: names that
|
|
143
|
+
reveal intent, functions doing one thing at one level of abstraction, no null returns, errors carrying
|
|
144
|
+
operation and state context. Preparatory Refactoring is the bridge to Phases 5-6: before each
|
|
145
|
+
optimization, first make the change easy (restructure), then make the easy change (optimize) — in
|
|
146
|
+
separate commits.
|
|
147
|
+
|
|
148
|
+
**Invoke:** Use the `refactoring-patterns` skill with the hot-path modules and the Phase 1 tests. Ask it to name each smell, cite the transformation, and apply one at a time with tests run between each.
|
|
149
|
+
|
|
150
|
+
**Decide with the user:** Scope — which smells this pass versus ledgered; which upcoming optimization warrants a Preparatory Refactoring at its insertion point first; whether the refactored modules join the CI gate list in TESTING.md.
|
|
151
|
+
|
|
152
|
+
**Artifact:** Extend docs/TECH-DEBT.md `## Smell Inventory` (smell | location | refactoring | status); extend docs/TESTING.md `## CI Gates` with any module promoted to the gate list. Update the tracker.
|
|
153
|
+
|
|
154
|
+
**Done when:** targeted smells show a named refactoring and `done` / `ticketed` status, tests are green, and structural commits contain no behavior changes.
|
|
155
|
+
|
|
156
|
+
### Phase 5 — Attack the measured bottleneck (system-design)
|
|
157
|
+
|
|
158
|
+
**Purpose:** Spend optimization effort where the profile says the time goes, in cheapest-first order, sized by real numbers.
|
|
159
|
+
|
|
160
|
+
**Brief (fallback):** Amdahl's law caps every win: total speedup is bounded by the fraction of time
|
|
161
|
+
the optimized part actually consumes — a 10× win on 5% of the request saves 4.5%. The profile, not
|
|
162
|
+
the code review, picks the target. Back-of-envelope the load (QPS = daily-active-users × actions/day ÷
|
|
163
|
+
86,400, peak 2-5× average) and confirm the gap against the budget. Then fix in order: the algorithm
|
|
164
|
+
first (an O(n²) loop or chatty per-item I/O beats any infrastructure), vertical headroom, cache-aside
|
|
165
|
+
with a TTL and explicit invalidation on read-heavy paths (measure the hit rate — a cold cache is pure
|
|
166
|
+
overhead), a message queue to move slow work off the request path (Little's law: in-flight requests =
|
|
167
|
+
arrival rate × latency, so cutting latency is also a capacity fix), then read replicas — and shard
|
|
168
|
+
only with evidence. Re-measure after every change; keep what moves the number, revert what doesn't.
|
|
169
|
+
|
|
170
|
+
**Invoke:** Use the `system-design` skill with the Phase 1 profile and the load numbers from intake. Ask which component bottlenecks first, the cheapest ordered list of moves for the measured gap, and the machinery you explicitly do NOT need yet.
|
|
171
|
+
|
|
172
|
+
**Decide with the user:** Which moves ship now versus defer with the trigger number written down; the first workload, if any, to move behind a queue; the invalidation rule for each cached path — what event invalidates which key.
|
|
173
|
+
|
|
174
|
+
**Artifact:** Extend docs/PERFORMANCE.md `## Profile Findings` and `## Optimization Ledger` (change | before | after | verdict | date); extend docs/ARCHITECTURE.md `## Decision Log` (each adopt/defer with its trigger) and `## System Context`, which cites PERFORMANCE.md `## Load Reality` rather than repeating the numbers. Update the tracker.
|
|
175
|
+
|
|
176
|
+
**Done when:** the bottleneck is named from the profile, each move is applied with before/after in the ledger or deferred with a trigger, and no adopted move failed to beat its baseline.
|
|
177
|
+
|
|
178
|
+
### Phase 6 — Fix the data layer (ddia-systems)
|
|
179
|
+
|
|
180
|
+
**Purpose:** The database is the usual suspect — most measured slowness is queries, and most correctness debt is isolation assumptions. Fix both by evidence.
|
|
181
|
+
|
|
182
|
+
**Brief (fallback):** Read the query plans, not the ORM code. The classics: N+1 queries (one per row —
|
|
183
|
+
batch or join; ORMs generate these silently), missing indexes on real access paths (EXPLAIN the slow
|
|
184
|
+
queries; index predicate and sort columns, but every index taxes writes), unbounded result sets
|
|
185
|
+
(paginate every list), SELECT * over wide rows, and deep offset pagination (use keyset). Storage
|
|
186
|
+
engines trade reads against writes (LSM write-throughput versus B-tree read-latency) — match the model
|
|
187
|
+
to the access pattern before buying hardware. Correctness under concurrency: most databases default to
|
|
188
|
+
read-committed or snapshot, not serializable — read-then-write paths get write skew; lock explicitly
|
|
189
|
+
(`SELECT ... FOR UPDATE`) or use a serializable transaction where invariants demand it. A second read
|
|
190
|
+
pattern (search, analytics, feeds) justifies derived data kept in sync by CDC — never dual writes; and
|
|
191
|
+
replicas from Phase 5 force deliberate read-your-writes.
|
|
192
|
+
|
|
193
|
+
**Invoke:** Use the `ddia-systems` skill with the Phase 1 profile, the Phase 5 findings, and the database from intake. If no query-level source exists yet, enable one first (`pg_stat_statements`, `auto_explain`, slow-query log) — that is Phase 1 instrumentation deferred, not a reason to guess. Ask for a query-plan audit (N+1s, missing indexes, unbounded reads), the actual default isolation level and its anomalies on your paths, and a per-workload model and engine fit.
|
|
194
|
+
|
|
195
|
+
**Decide with the user:** Which indexes to add, weighing write cost; which paths get locks versus serializable transactions versus tolerated anomalies; whether any workload justifies a second datastore synced by CDC.
|
|
196
|
+
|
|
197
|
+
**Artifact:** Extend docs/ARCHITECTURE.md `## Data & Storage Decisions` and `## Decision Log`; extend docs/PERFORMANCE.md `## Profile Findings` and `## Optimization Ledger` with query before/afters. Update the tracker.
|
|
198
|
+
|
|
199
|
+
**Done when:** the slow queries are fixed with measured before/after, every list endpoint on the in-scope flows is paginated (the rest become Debt Ledger rows), the isolation level is documented with risky paths locked, and any derived data has a defined sync mechanism.
|
|
200
|
+
|
|
201
|
+
### Phase 7 — Keep it fast when things fail (release-it)
|
|
202
|
+
|
|
203
|
+
**Purpose:** A fast system that collapses under a slow dependency isn't fast — latency under failure is a performance property.
|
|
204
|
+
|
|
205
|
+
**Brief (fallback):** Integration points are the number-one killer, and a slow response is worse than
|
|
206
|
+
none: one hanging dependency exhausts threads and pools with nothing in the logs. Non-negotiables:
|
|
207
|
+
connect + read timeouts on every outbound call (a timeout is a latency budget); circuit breakers on
|
|
208
|
+
critical dependencies (fail fast beats waiting); bulkheads so one slow dependency can't drain the
|
|
209
|
+
shared pool; retry with exponential backoff and jitter (naive retries triple load exactly when the
|
|
210
|
+
dependency is dying); steady-state cleanup for logs, temp data, and caches that grow forever. Wire RED
|
|
211
|
+
metrics (rate, errors, duration) per endpoint and alert on symptoms (p95 over budget) — the Phase 1
|
|
212
|
+
budgets become production guardrails instead of a one-time snapshot.
|
|
213
|
+
|
|
214
|
+
**Invoke:** Use the `release-it` skill with the outbound dependencies from intake and the budgets from Phase 1. Ask for timeout values derived from the flow latency budgets, breaker and bulkhead placement, and the RED-metrics plus symptom-alert design.
|
|
215
|
+
|
|
216
|
+
**Decide with the user:** Timeout and breaker thresholds per dependency, tied to the flow budget; which dependencies get dedicated pools; how each core flow degrades when a non-critical dependency is down.
|
|
217
|
+
|
|
218
|
+
**Artifact:** Create-or-extend docs/RELIABILITY.md `## Integration-Point Audit` (dependency | timeout | circuit breaker | bulkhead | retry policy | status), `## Query & Resource Findings`, and `## Health Checks & Metrics`. Update the tracker.
|
|
219
|
+
|
|
220
|
+
**Done when:** every outbound call on the in-scope flows has a timeout inside its flow's budget (calls outside them become Debt Ledger rows), critical dependencies have breakers and bulkheads, unbounded result sets and blocked threads are recorded in `## Query & Resource Findings`, and RED metrics with symptom alerts guard the Phase 1 budgets in production.
|
|
221
|
+
|
|
222
|
+
### Phase 8 — Lock in budgets and habits (pragmatic-programmer)
|
|
223
|
+
|
|
224
|
+
**Purpose:** Make the gains permanent — regressions arrive one innocent commit at a time unless a gate catches them.
|
|
225
|
+
|
|
226
|
+
**Brief (fallback):** Turn each Phase 1 budget into a CI gate: perf tests or query-count assertions on
|
|
227
|
+
the hot paths, where a p95 budget breach fails the build like a failing test. DRY is about knowledge:
|
|
228
|
+
the same rule computed in two places will drift — and the same query issued from two layers is both a
|
|
229
|
+
bug farm and a performance tax. Broken Window Theory: the first unreviewed slow query or skipped index
|
|
230
|
+
gets fixed or ticketed immediately, never left as ambient decay. Reversibility: vendors and
|
|
231
|
+
infrastructure behind owned interfaces, so the next optimization — swapping the cache, changing the
|
|
232
|
+
queue — stays a week's work instead of a rewrite. Set the debt budget per iteration and write the
|
|
233
|
+
conventions down; the ledger, not memory, carries what was deferred.
|
|
234
|
+
|
|
235
|
+
**Invoke:** Use the `pragmatic-programmer` skill across the touched modules. Ask for duplicated-knowledge hits (including duplicated queries and rules), untracked TODOs and broken windows, and a CI-gate design for the performance budgets.
|
|
236
|
+
|
|
237
|
+
**Decide with the user:** Which budgets become blocking CI gates versus dashboard alerts; the debt budget per iteration; the broken-windows policy — what gets fixed now versus ticketed.
|
|
238
|
+
|
|
239
|
+
**Artifact:** Extend docs/PERFORMANCE.md `## Baselines & Budgets` (mark each budget's gate); extend docs/TECH-DEBT.md `## Debt Budget & Broken-Windows Policy` and `## Adopted Conventions`; extend docs/TESTING.md `## CI Gates`. Update the tracker.
|
|
240
|
+
|
|
241
|
+
**Done when:** each hot-path budget is a CI gate or an owned alert, duplicated knowledge is fixed or ledgered, and the conventions are written down.
|
|
242
|
+
|
|
243
|
+
## Optional Phases
|
|
244
|
+
|
|
245
|
+
| Skill | Add when | Artifact |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| clean-code | readability is poor beyond the hot paths — the whole codebase needs the naming, function, and error-handling pass | Extends docs/TECH-DEBT.md `## Smell Inventory`, `## Adopted Conventions` |
|
|
248
|
+
| domain-driven-design | boundaries keep fighting the business language — modules split where the domain doesn't | Extends docs/ARCHITECTURE.md `## Bounded Contexts & Context Map`, `## Domain Glossary (Ubiquitous Language)` |
|
|
249
|
+
| high-perf-browser | the measured slowness is in the browser — page load, LCP, blocking resources — not the backend | Extends docs/METRICS.md `## Baselines & Targets`, docs/WEBSITE.md `## Audit Findings` |
|
|
250
|
+
| team-topologies | more than one team owns the system, so module boundaries must align with team boundaries (Conway) | Extends docs/OPERATIONS.md `## Team Structure` |
|
|
251
|
+
|
|
252
|
+
Optional phases follow the same operating rules — load and use each listed skill exactly as a core phase would; insert where the Add-when condition first becomes true. They carry no inline Brief: standalone, run clean-code as a naming, function-size, and error-handling pass, domain-driven-design as a ubiquitous-language and bounded-context map, high-perf-browser as a Core Web Vitals audit (LCP, INP, CLS), and team-topologies as a cognitive-load and team-boundary review — or install the named skill for its full framework.
|
|
253
|
+
|
|
254
|
+
## Common Mistakes
|
|
255
|
+
|
|
256
|
+
| Mistake | Fix |
|
|
257
|
+
|---|---|
|
|
258
|
+
| Optimizing where intuition points instead of where the profiler does | Profile first (Phase 1); Amdahl's law caps any win by the fraction of time that code actually consumes. |
|
|
259
|
+
| Rewriting for speed without a safety net | Pin behavior with characterization tests first (working-with-legacy-code); a fast wrong answer is still wrong. |
|
|
260
|
+
| Keeping an optimization that didn't move the number | Every change gets before/after in the Optimization Ledger; revert what doesn't beat its baseline — complexity without payoff is pure debt. |
|
|
261
|
+
| Reaching for infrastructure before fixing the algorithm | An O(n²) loop or an N+1 query beats any cache; fix the code, then size the machinery (system-design). |
|
|
262
|
+
| Caching without an invalidation rule | Stale-data bugs cost more than the latency saved; every cached path names what event invalidates which key. |
|
|
263
|
+
| Trusting the ORM to write good SQL | EXPLAIN the slow queries (ddia-systems); N+1s and missing indexes hide behind innocent-looking code. |
|
|
264
|
+
| Calling it fast with no timeout on outbound calls | Latency under failure is a performance property (release-it); one hanging dependency erases every optimization. |
|
|
265
|
+
|
|
266
|
+
## Completing the Journey
|
|
267
|
+
|
|
268
|
+
Match the dose to the pain: a slow-endpoint complaint may need only Phases 1, 5, and 6 — baseline, bottleneck, queries — a few days that pay immediately; a codebase where every change is slow and risky wants the structure phases first, because clean boundaries are what make the optimizations small. Either way the ledger keeps score: kept changes beat their baselines, everything else was reverted.
|
|
269
|
+
|
|
270
|
+
Exit checklist — every box tied to an artifact:
|
|
271
|
+
|
|
272
|
+
- [ ] Every phase in `docs/ARCHITECTURE-OPTIMIZATION-PLAN.md` is `done`, `deferred: reason`, or `skipped: reason`.
|
|
273
|
+
- [ ] Every hot flow has a baseline, a budget, and a current measurement inside it (PERFORMANCE.md Baselines & Budgets).
|
|
274
|
+
- [ ] Every kept optimization shows measured before/after; nothing kept failed its baseline (PERFORMANCE.md Optimization Ledger).
|
|
275
|
+
- [ ] Dependency Rule violations on the hot paths are closed or tracked rows (ARCHITECTURE.md Layer Map).
|
|
276
|
+
- [ ] Slow queries are fixed by plan, lists are paginated, the isolation level is documented, and risky paths are locked (ARCHITECTURE.md Data & Storage Decisions).
|
|
277
|
+
- [ ] Every outbound call has a timeout and the budgets are guarded by RED metrics and alerts — or CI gates (RELIABILITY.md, TESTING.md CI Gates).
|
|
278
|
+
|
|
279
|
+
Close the tracker: remaining Next Actions carried into the PERFORMANCE.md ledger and TECH-DEBT.md so nothing is lost. Then route forward: when the pain is fear of change rather than speed, continue with the `remove-technical-debt` skill; when a fresh untested prototype needs the full production pass, `improve-code-quality`; when the next system deserves this structure from day one, `design-code-architecture`.
|