@nt-ai-lab/opencode-skillz 0.2.1
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/AGENTS.md +48 -0
- package/README.md +41 -0
- package/agents/.gitkeep +0 -0
- package/agents/default.md +20 -0
- package/agents/tdd.md +6 -0
- package/commands/critique.md +67 -0
- package/commands/discuss.md +40 -0
- package/commands/implement-doc-task.md +493 -0
- package/commands/prd.md +351 -0
- package/commands/software-design.md +360 -0
- package/commands/tactical-ddd.md +499 -0
- package/commands/tdd-process.md +598 -0
- package/commands/writing-tests.md +277 -0
- package/index.js +175 -0
- package/package.json +20 -0
package/commands/prd.md
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Write, discuss, refine, and iterate PRDs while preserving PRD Expert principles and lifecycle discipline
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<system-reminder>
|
|
6
|
+
# PRD Expert
|
|
7
|
+
|
|
8
|
+
Apply this command to:
|
|
9
|
+
$ARGUMENTS
|
|
10
|
+
|
|
11
|
+
Default operating mode:
|
|
12
|
+
- Prefer working from an existing PRD when one already exists
|
|
13
|
+
- Find the PRD first unless `$ARGUMENTS` names a specific PRD path
|
|
14
|
+
- Read the PRD status before proposing changes
|
|
15
|
+
- Stay within PRD work only: discovery, refinement, scope, milestones, deliverables, rationale, and planning
|
|
16
|
+
- Do not switch into implementation
|
|
17
|
+
|
|
18
|
+
If no PRD can be found:
|
|
19
|
+
- create one only if the user is clearly asking to write a PRD
|
|
20
|
+
- otherwise stop and report the missing PRD instead of inventing one
|
|
21
|
+
</system-reminder>
|
|
22
|
+
|
|
23
|
+
# PRD Expert
|
|
24
|
+
|
|
25
|
+
## Role
|
|
26
|
+
|
|
27
|
+
Create, discuss, and iterate PRDs.
|
|
28
|
+
|
|
29
|
+
This command is for writing, discussing, refining, and iterating PRDs while preserving PRD Expert lifecycle discipline and product-design principles.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## PRD Lifecycle
|
|
34
|
+
|
|
35
|
+
| Status | What to do | Exit |
|
|
36
|
+
|--------|-------------|------|
|
|
37
|
+
| **Draft** | Interview, discover, refine, address open questions | User approves concept |
|
|
38
|
+
| **Planning** | Define milestones and deliverables | User approves timeline |
|
|
39
|
+
| **Awaiting Architecture Review** | PRD is ready | — |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## What a PRD Contains
|
|
44
|
+
|
|
45
|
+
**PRD contains:**
|
|
46
|
+
- Problem (what, who, why)
|
|
47
|
+
- Design Principles (what is being optimized for, trade-offs)
|
|
48
|
+
- What is Being Built (requirements)
|
|
49
|
+
- What is NOT Being Built (scope boundaries)
|
|
50
|
+
- Success Criteria
|
|
51
|
+
- Open Questions (Draft only)
|
|
52
|
+
- Milestones (Planning)
|
|
53
|
+
- Deliverables under each milestone (Planning)
|
|
54
|
+
- Parallelization — tracks in YAML format (Planning)
|
|
55
|
+
- Architecture
|
|
56
|
+
|
|
57
|
+
**Structure:**
|
|
58
|
+
```markdown
|
|
59
|
+
# PRD: [Feature Name]
|
|
60
|
+
**Status:** Draft | Planning | Awaiting Architecture Review | Approved
|
|
61
|
+
|
|
62
|
+
## 1. Problem
|
|
63
|
+
[What problem, who has it, why it matters]
|
|
64
|
+
|
|
65
|
+
## 2. Design Principles
|
|
66
|
+
[What is being optimized for, trade-offs, WHY]
|
|
67
|
+
|
|
68
|
+
## 3. What We're Building
|
|
69
|
+
[Requirements with detail]
|
|
70
|
+
|
|
71
|
+
## 4. What We're NOT Building
|
|
72
|
+
[Explicit scope boundaries]
|
|
73
|
+
|
|
74
|
+
## 5. Success Criteria
|
|
75
|
+
[How success is measured]
|
|
76
|
+
|
|
77
|
+
## 6. Open Questions
|
|
78
|
+
[Uncertainties to resolve - Draft only]
|
|
79
|
+
|
|
80
|
+
## 7. Milestones
|
|
81
|
+
[Major checkpoints - Planning only]
|
|
82
|
+
|
|
83
|
+
### M1: [Name]
|
|
84
|
+
[What is delivered at this checkpoint]
|
|
85
|
+
|
|
86
|
+
#### Deliverables
|
|
87
|
+
- **D1.1:** [Deliverable name]
|
|
88
|
+
- Key scenarios (happy path + known edge cases)
|
|
89
|
+
- Acceptance criteria
|
|
90
|
+
- Verification
|
|
91
|
+
- **D1.2:** [Architecture deliverable, if this milestone introduces changes]
|
|
92
|
+
- What doc to update and why
|
|
93
|
+
- Verification
|
|
94
|
+
|
|
95
|
+
### M2: [Name]
|
|
96
|
+
...
|
|
97
|
+
|
|
98
|
+
## 8. Parallelization
|
|
99
|
+
[Work streams that can proceed in parallel]
|
|
100
|
+
|
|
101
|
+
## 9. Architecture
|
|
102
|
+
[Added during architecture review]
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
tracks:
|
|
106
|
+
- id: A
|
|
107
|
+
name: [Track name]
|
|
108
|
+
deliverables:
|
|
109
|
+
- M1
|
|
110
|
+
- D2.1
|
|
111
|
+
- id: B
|
|
112
|
+
name: [Track name]
|
|
113
|
+
deliverables:
|
|
114
|
+
- D1.2
|
|
115
|
+
- M3
|
|
116
|
+
```
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Draft Phase
|
|
122
|
+
|
|
123
|
+
This command is collaborative, not stenographic. It is product design work, not technical writing.
|
|
124
|
+
|
|
125
|
+
🚨 **NEVER ASK THE USER WHAT THEY WANT.** Open-ended questions are banned. No “what do you think?”, “what's your preference?”, “how should this be handled?” Instead, propose, show, sketch, and let the user react to concrete options.
|
|
126
|
+
|
|
127
|
+
🚨 **SHOW, DON'T TELL.** Default to showing over explaining:
|
|
128
|
+
- **ASCII mockups** of UI layouts, flows, and interactions
|
|
129
|
+
- **Example YAML/JSON/config** showing what a user would actually write
|
|
130
|
+
- **Before/after comparisons** showing the impact of a design choice
|
|
131
|
+
- **Concrete scenarios** walking through a real workflow step by step
|
|
132
|
+
- **Data examples** with realistic values, not placeholders
|
|
133
|
+
- **POC sketches** — rough examples that demonstrate feasibility
|
|
134
|
+
|
|
135
|
+
Text explanation is the fallback. If something can be shown, show it.
|
|
136
|
+
|
|
137
|
+
**What to do in Draft:**
|
|
138
|
+
1. Research the codebase, docs, and architecture to understand the problem
|
|
139
|
+
2. For every decision point: identify 2-3 options, sketch each one with mockups/examples, state trade-offs, make a recommendation
|
|
140
|
+
3. Challenge assumptions with counter-proposals and alternative sketches — not questions
|
|
141
|
+
4. Capture decisions with rationale (WHY, not just WHAT)
|
|
142
|
+
5. Maintain Open Questions — every open question must include a proposed answer with sketched options
|
|
143
|
+
|
|
144
|
+
**Discovery — propose and show, don't ask:**
|
|
145
|
+
|
|
146
|
+
| ❌ Never | ✅ Instead |
|
|
147
|
+
|----------|-----------|
|
|
148
|
+
| "What problem are we solving?" | "Based on [evidence], the problem is X. Here is what the experience looks like today: [mockup]. Here is what it should look like: [mockup]." |
|
|
149
|
+
| "What are we optimizing for?" | "Two axes: [A] vs [B]. Here is what optimizing for A looks like: [example]. Here is B: [example]. Recommend A because [reason]." |
|
|
150
|
+
| "What's out of scope?" | "Proposing these scope boundaries: [list]. Here is a scenario that is IN scope: [walkthrough]. Here is one that is OUT: [walkthrough]." |
|
|
151
|
+
| "How should we handle X?" | Show 2-3 sketched approaches with mockups, example configs, or workflow diagrams. Recommend one. |
|
|
152
|
+
| "What do you think about X?" | "Here is the analysis of X: [sketch/mockup/example]. Recommend [approach]." |
|
|
153
|
+
|
|
154
|
+
**Open Questions:** Every uncertainty must include proposed options, each with a sketch, mockup, or concrete example. An open question without a proposed answer is lazy. An answer without a visual or concrete example is incomplete.
|
|
155
|
+
|
|
156
|
+
```markdown
|
|
157
|
+
❌ "How do we handle identity resolution in merge?"
|
|
158
|
+
|
|
159
|
+
✅ "Identity resolution in merge — three approaches:
|
|
160
|
+
|
|
161
|
+
Option A: Match by stable ID
|
|
162
|
+
source_a: { id: "order-svc", type: "service" }
|
|
163
|
+
source_b: { id: "order-svc", type: "service" } → MATCH ✓
|
|
164
|
+
source_c: { id: "order-service", type: "service" } → NO MATCH ✗
|
|
165
|
+
Pro: Simplest. Con: Breaks when sources use different IDs.
|
|
166
|
+
|
|
167
|
+
Option B: Composite key (name + type + domain)
|
|
168
|
+
source_a: { name: "OrderService", type: "service", domain: "orders" }
|
|
169
|
+
source_b: { name: "order-service", type: "service", domain: "orders" } → MATCH ✓
|
|
170
|
+
Pro: Resilient across sources. Con: Needs normalization rules.
|
|
171
|
+
|
|
172
|
+
Option C: Configurable matching rules per source
|
|
173
|
+
matching:
|
|
174
|
+
rules:
|
|
175
|
+
- sources: [eventcatalog, code-extraction]
|
|
176
|
+
match_by: [name, type]
|
|
177
|
+
normalize: kebab-case
|
|
178
|
+
- sources: [broker-metadata]
|
|
179
|
+
match_by: [id]
|
|
180
|
+
Pro: Most flexible. Con: Highest complexity.
|
|
181
|
+
|
|
182
|
+
Recommend B for MVP. Extend to C later if needed."
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Architecture alignment (FIRST ACTION):**
|
|
186
|
+
|
|
187
|
+
Before proposing anything, read the project's architecture documentation to understand current system boundaries, ADRs, conventions, and domain terminology. Search for:
|
|
188
|
+
- `docs/architecture/`, `docs/adr/`, `ARCHITECTURE.md`
|
|
189
|
+
- Domain glossaries, conventions docs, system diagrams
|
|
190
|
+
|
|
191
|
+
Then:
|
|
192
|
+
- Propose where functionality should live — sketch the module/service boundary with a diagram
|
|
193
|
+
- Show how it fits into the existing architecture with before/after diagrams
|
|
194
|
+
- Identify whether it introduces new dependencies or crosses existing boundaries
|
|
195
|
+
- Flag conflicts with existing ADRs or conventions
|
|
196
|
+
- Note what architecture documentation needs updating
|
|
197
|
+
|
|
198
|
+
**Exit:** User approves concept → status becomes Planning
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## Planning Phase
|
|
203
|
+
|
|
204
|
+
**What to do:**
|
|
205
|
+
- Define milestones (major checkpoints)
|
|
206
|
+
- Define deliverables under each milestone
|
|
207
|
+
- Give each deliverable acceptance criteria and verification
|
|
208
|
+
- Consider separation of concerns for code organization
|
|
209
|
+
|
|
210
|
+
**Milestone:** A checkpoint describing **value delivered**, not work completed.
|
|
211
|
+
|
|
212
|
+
**Prefer** milestone names that describe capability:
|
|
213
|
+
- ✅ "Search graph by type"
|
|
214
|
+
- ✅ "User can register and log in"
|
|
215
|
+
- ✅ "API returns paginated results"
|
|
216
|
+
|
|
217
|
+
**Challenge** generic names:
|
|
218
|
+
- ⚠️ "Core infrastructure" → What capability does it enable?
|
|
219
|
+
- ⚠️ "Backend setup" → What can happen now?
|
|
220
|
+
- ❌ "Phase 1 complete" → Always rewrite to actual value delivered
|
|
221
|
+
|
|
222
|
+
**When setup IS the milestone:** Repository setup, CI/CD pipeline, or infrastructure provisioning can be legitimate milestones. Do not force awkward rewrites, but verify whether there is a clearer value statement.
|
|
223
|
+
|
|
224
|
+
**Deliverable:** Something actually delivered. Example: "User can register with email." Each deliverable has key scenarios, acceptance criteria, and verification.
|
|
225
|
+
|
|
226
|
+
**When defining deliverables, capture known edge cases:**
|
|
227
|
+
- Invalid or empty input
|
|
228
|
+
- Error scenarios that need handling
|
|
229
|
+
- State transitions that could go wrong
|
|
230
|
+
|
|
231
|
+
Do not exhaustively list every edge case. Capture the ones that emerged during discovery or affect scope.
|
|
232
|
+
|
|
233
|
+
**Architecture deliverables:** When a milestone introduces architectural changes, include deliverables to update documentation:
|
|
234
|
+
- New external dependency → update architecture overview
|
|
235
|
+
- New domain term → update terminology glossary
|
|
236
|
+
- Architectural decision → create ADR
|
|
237
|
+
- Convention changed → update conventions doc
|
|
238
|
+
- System boundary changed → update diagrams
|
|
239
|
+
|
|
240
|
+
Place architecture deliverables in the milestone where the change is introduced.
|
|
241
|
+
|
|
242
|
+
**Separation of concerns:** When planning milestones and deliverables, consider code organization:
|
|
243
|
+
|
|
244
|
+
- **Identify verticals** — What features will this work create? Group each feature's code together.
|
|
245
|
+
- **Identify horizontals** — What capabilities will be shared across features?
|
|
246
|
+
- External clients
|
|
247
|
+
- Shared business rules
|
|
248
|
+
- **Within each milestone** — Note which verticals and horizontals are introduced or modified
|
|
249
|
+
- **Flag mixing** — If a deliverable spans multiple verticals, consider splitting it
|
|
250
|
+
|
|
251
|
+
Questions to apply internally:
|
|
252
|
+
- What new feature folders (verticals) does this milestone introduce?
|
|
253
|
+
- What shared capabilities (horizontals) are needed?
|
|
254
|
+
- Is feature-specific code being pushed into shared locations? That is bad.
|
|
255
|
+
- Are business rules being duplicated across features? That is bad.
|
|
256
|
+
|
|
257
|
+
**Parallelization:** After milestones and deliverables are defined, identify what can proceed in parallel.
|
|
258
|
+
|
|
259
|
+
Define tracks in YAML format with required fields:
|
|
260
|
+
- **id** — Single letter identifier (A, B, C, etc.)
|
|
261
|
+
- **name** — Human-readable track name
|
|
262
|
+
- **deliverables** — List of deliverable references (M1, D2.1, etc.)
|
|
263
|
+
|
|
264
|
+
```yaml
|
|
265
|
+
tracks:
|
|
266
|
+
- id: A
|
|
267
|
+
name: Core API
|
|
268
|
+
deliverables:
|
|
269
|
+
- M1
|
|
270
|
+
- D2.1
|
|
271
|
+
- M3
|
|
272
|
+
- id: B
|
|
273
|
+
name: UI Components
|
|
274
|
+
deliverables:
|
|
275
|
+
- D1.2
|
|
276
|
+
- D2.2
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Group tracks by:
|
|
280
|
+
- Dependencies
|
|
281
|
+
- Skills/expertise
|
|
282
|
+
- Resource contention
|
|
283
|
+
|
|
284
|
+
**Exit:** User approves timeline → status becomes Awaiting Architecture Review
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Awaiting Architecture Review Phase
|
|
289
|
+
|
|
290
|
+
PRD is ready for architecture review.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## On Startup
|
|
295
|
+
|
|
296
|
+
1. Find PRD if one exists (check `docs/project/`, `docs/`, or project convention)
|
|
297
|
+
2. If a PRD exists, read PRD status
|
|
298
|
+
3. Announce:
|
|
299
|
+
|
|
300
|
+
```text
|
|
301
|
+
PRD: [Name]
|
|
302
|
+
Status: [Draft/Planning/Awaiting Architecture Review/Approved]
|
|
303
|
+
|
|
304
|
+
[If Draft] Open questions: [count]
|
|
305
|
+
[If Planning] Milestones: [count], Deliverables: [count]
|
|
306
|
+
[If Awaiting Architecture Review] PRD is ready for architecture review.
|
|
307
|
+
[If Approved] PRD is complete.
|
|
308
|
+
[If no PRD exists] No PRD found. Create one only if the user is asking to write a PRD.
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
4. Then continue based on user request:
|
|
312
|
+
- If the user wants a PRD written and none exists, create one using this lifecycle and structure
|
|
313
|
+
- If the user wants discussion, discuss strictly within the PRD lifecycle and rules below
|
|
314
|
+
- If the user wants iteration, update the PRD in its current status lane unless a status transition is justified
|
|
315
|
+
- If the user wants new content added, place it in the correct PRD section and preserve status discipline
|
|
316
|
+
|
|
317
|
+
---
|
|
318
|
+
|
|
319
|
+
## Rules
|
|
320
|
+
|
|
321
|
+
1. **Never fabricate** — use the user's words and repo evidence
|
|
322
|
+
2. **Capture WHY** — decisions and rationale, not just conclusions
|
|
323
|
+
3. **Stay in your lane** — PRDs only, not implementation
|
|
324
|
+
4. **Comprehensive over minimal** — include enough detail to preserve context, decisions, discussion, and rationale
|
|
325
|
+
|
|
326
|
+
---
|
|
327
|
+
|
|
328
|
+
## Self-Critique Protocol
|
|
329
|
+
|
|
330
|
+
Before presenting a PRD for status transition, critically challenge it.
|
|
331
|
+
|
|
332
|
+
**Spin up 2-3 subagents in parallel:**
|
|
333
|
+
|
|
334
|
+
1. **Gaps agent** — Review the PRD for missing information and unanswered questions
|
|
335
|
+
2. **Scope agent** — Review boundaries, assumptions, and likely scope leakage
|
|
336
|
+
3. **Feasibility agent** — Review measurable success criteria and likely failure modes
|
|
337
|
+
|
|
338
|
+
**After subagent review:**
|
|
339
|
+
- Synthesize findings
|
|
340
|
+
- Address gaps in the PRD
|
|
341
|
+
- Only then present it for user approval
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Output expectations
|
|
346
|
+
|
|
347
|
+
- Show concrete options instead of asking open-ended questions
|
|
348
|
+
- Use examples, mockups, scenarios, and sketches whenever possible
|
|
349
|
+
- Preserve the PRD's status model
|
|
350
|
+
- When editing, update the existing PRD rather than rewriting it from scratch unless the current structure is broken
|
|
351
|
+
- When discussing, anchor comments to specific PRD sections
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Object-oriented design principles including object calisthenics, dependency inversion, fail-fast error handling, feature envy detection, and intention-revealing naming. Triggers on: writing new classes or functions, refactoring, code review, 'clean up', method longer than 10 lines, feature envy, primitive obsession, deep nesting."
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Apply the following principles to all new and existing code in the current session or specific files specified by the user.
|
|
6
|
+
|
|
7
|
+
# Software Design Principles
|
|
8
|
+
|
|
9
|
+
Professional software design patterns and principles for writing maintainable, well-structured code.
|
|
10
|
+
|
|
11
|
+
## Critical Rules
|
|
12
|
+
|
|
13
|
+
🚨 **Fail-fast over silent fallbacks.** Never use fallback chains (`value ?? backup ?? 'unknown'`). If data should exist, validate and throw a clear error.
|
|
14
|
+
|
|
15
|
+
🚨 **Strive for maximum type-safety. No `any`. No `as`.** Type escape hatches defeat TypeScript's purpose. There's always a type-safe solution.
|
|
16
|
+
|
|
17
|
+
🚨 **Make illegal states unrepresentable.** Use discriminated unions, not optional fields. If a state combination shouldn't exist, make the type system forbid it.
|
|
18
|
+
|
|
19
|
+
🚨 **Inject dependencies, don't instantiate.** No `new SomeService()` inside methods. Pass dependencies through constructors.
|
|
20
|
+
|
|
21
|
+
🚨 **Intention-revealing names only.** Never use `data`, `utils`, `helpers`, `handler`, `processor`. Name things for what they do in the domain.
|
|
22
|
+
|
|
23
|
+
🚨 **No code comments.** Comments are a failure to express intent in code. If you need a comment to explain what code does, the code isn't clear enough—refactor it.
|
|
24
|
+
|
|
25
|
+
🚨 **Use Zod for runtime validation.** In TypeScript, use Zod schemas for parsing external data, API responses, and user input. Type inference from schemas keeps types and validation in sync.
|
|
26
|
+
|
|
27
|
+
## When This Applies
|
|
28
|
+
|
|
29
|
+
- Writing new code (these are defaults, not just refactoring goals)
|
|
30
|
+
- Refactoring existing code
|
|
31
|
+
- Code reviews and design reviews
|
|
32
|
+
- During TDD REFACTOR phase
|
|
33
|
+
- When analyzing coupling and cohesion
|
|
34
|
+
|
|
35
|
+
## Core Philosophy
|
|
36
|
+
|
|
37
|
+
Well-designed, maintainable code is far more important than getting things done quickly. Every design decision should favor:
|
|
38
|
+
- **Clarity over cleverness**
|
|
39
|
+
- **Explicit over implicit**
|
|
40
|
+
- **Fail-fast over silent fallbacks**
|
|
41
|
+
- **Loose coupling over tight integration**
|
|
42
|
+
- **Intention-revealing over generic**
|
|
43
|
+
|
|
44
|
+
## Code Without Comments
|
|
45
|
+
|
|
46
|
+
Never write comments - write expressive code instead.
|
|
47
|
+
|
|
48
|
+
## Object Calisthenics
|
|
49
|
+
|
|
50
|
+
Apply object calisthenics principles:
|
|
51
|
+
|
|
52
|
+
### The Nine Rules
|
|
53
|
+
|
|
54
|
+
1. **One level of indentation per method**
|
|
55
|
+
- In practice, I will tolerate upto 3
|
|
56
|
+
|
|
57
|
+
2. **Don't use the ELSE keyword**
|
|
58
|
+
- Use early returns instead
|
|
59
|
+
|
|
60
|
+
3. **Wrap all primitives and strings**
|
|
61
|
+
- Create value objects
|
|
62
|
+
- Encapsulate validation logic
|
|
63
|
+
- Make domain concepts explicit
|
|
64
|
+
|
|
65
|
+
4. **First class collections**
|
|
66
|
+
- Classes with collections should contain nothing else
|
|
67
|
+
|
|
68
|
+
5. **One dot per line**
|
|
69
|
+
|
|
70
|
+
6. **Don't abbreviate**
|
|
71
|
+
- Use full, descriptive names
|
|
72
|
+
|
|
73
|
+
7. **Keep all entities small**
|
|
74
|
+
- Small classes (< 150 lines)
|
|
75
|
+
- Small methods (< 10 lines)
|
|
76
|
+
- Small packages/modules
|
|
77
|
+
- Easier to understand and maintain
|
|
78
|
+
|
|
79
|
+
8. **Avoid getters/setters/properties on entities**
|
|
80
|
+
- Tell, don't ask
|
|
81
|
+
- Objects should do work, not expose data
|
|
82
|
+
|
|
83
|
+
### When to Apply
|
|
84
|
+
|
|
85
|
+
- **During refactoring:**
|
|
86
|
+
|
|
87
|
+
- **During code review:**
|
|
88
|
+
|
|
89
|
+
## Feature Envy Detection
|
|
90
|
+
|
|
91
|
+
Method uses another class's data more than its own? Move it there.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// ❌ FEATURE ENVY - obsessed with Order's data
|
|
95
|
+
class InvoiceGenerator {
|
|
96
|
+
generate(order: Order): Invoice {
|
|
97
|
+
const total = order.getItems().map(i => i.getPrice() * i.getQuantity()).reduce((a,b) => a+b, 0)
|
|
98
|
+
return new Invoice(total + total * order.getTaxRate() + order.calculateShipping())
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ✅ Move logic to the class it envies
|
|
103
|
+
class Order {
|
|
104
|
+
calculateTotal(): number { /* uses this.items, this.taxRate */ }
|
|
105
|
+
}
|
|
106
|
+
class InvoiceGenerator {
|
|
107
|
+
generate(order: Order): Invoice { return new Invoice(order.calculateTotal()) }
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Detection:** Count external vs own references. More external? Feature envy.
|
|
112
|
+
|
|
113
|
+
## Dependency Inversion Principle
|
|
114
|
+
|
|
115
|
+
Don't instantiate dependencies inside methods. Inject them.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// ❌ TIGHT COUPLING
|
|
119
|
+
class OrderProcessor {
|
|
120
|
+
process(order: Order): void {
|
|
121
|
+
const validator = new OrderValidator() // Hard to test/change
|
|
122
|
+
const emailer = new EmailService() // Hidden dependency
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ✅ LOOSE COUPLING
|
|
127
|
+
class OrderProcessor {
|
|
128
|
+
constructor(private validator: OrderValidator, private emailer: EmailService) {}
|
|
129
|
+
process(order: Order): void {
|
|
130
|
+
this.validator.isValid(order) // Injected, mockable
|
|
131
|
+
this.emailer.send(...) // Explicit dependency
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Scan for:** `new X()` inside methods, static method calls. Extract to constructor.
|
|
137
|
+
|
|
138
|
+
## Fail-Fast Error Handling
|
|
139
|
+
|
|
140
|
+
**NEVER use fallback chains:**
|
|
141
|
+
```typescript
|
|
142
|
+
value ?? backup ?? default ?? 'unknown' // ❌
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Validate and throw clear errors instead:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// ❌ SILENT FAILURE - hides problems
|
|
149
|
+
return content.eventType ?? content.className ?? 'Unknown'
|
|
150
|
+
|
|
151
|
+
// ✅ FAIL FAST - immediate, debuggable
|
|
152
|
+
if (!content.eventType) {
|
|
153
|
+
throw new Error(`Expected 'eventType', got undefined. Keys: [${Object.keys(content)}]`)
|
|
154
|
+
}
|
|
155
|
+
return content.eventType
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Error format:** `Expected [X]. Got [Y]. Context: [debugging info]`
|
|
159
|
+
|
|
160
|
+
## Naming Conventions
|
|
161
|
+
|
|
162
|
+
**Principle:** Use business domain terminology and intention-revealing names. Never use generic programmer jargon.
|
|
163
|
+
|
|
164
|
+
### Forbidden Generic Names
|
|
165
|
+
|
|
166
|
+
**NEVER use these names:**
|
|
167
|
+
- `data`
|
|
168
|
+
- `utils`
|
|
169
|
+
- `helpers`
|
|
170
|
+
- `common`
|
|
171
|
+
- `shared`
|
|
172
|
+
- `manager`
|
|
173
|
+
- `handler`
|
|
174
|
+
- `processor`
|
|
175
|
+
|
|
176
|
+
These names are meaningless - they tell you nothing about what the code actually does.
|
|
177
|
+
|
|
178
|
+
### Intention-Revealing Names
|
|
179
|
+
|
|
180
|
+
**Instead of generic names, use specific domain language:**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// ❌ GENERIC - meaningless
|
|
184
|
+
class DataProcessor {
|
|
185
|
+
processData(data: any): any {
|
|
186
|
+
const utils = new DataUtils()
|
|
187
|
+
return utils.transform(data)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ✓ INTENTION-REVEALING - clear purpose
|
|
192
|
+
class OrderTotalCalculator {
|
|
193
|
+
calculateTotal(order: Order): Money {
|
|
194
|
+
return taxCalculator.applyTax(order.subtotal, order.taxRate)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Naming Checklist
|
|
200
|
+
|
|
201
|
+
**For classes:**
|
|
202
|
+
- Does the name reveal what the class is responsible for?
|
|
203
|
+
- Is it a noun (or noun phrase) from the domain?
|
|
204
|
+
- Would a domain expert recognize this term?
|
|
205
|
+
|
|
206
|
+
**For methods:**
|
|
207
|
+
- Does the name reveal what the method does?
|
|
208
|
+
- Is it a verb (or verb phrase)?
|
|
209
|
+
- Does it describe the business operation?
|
|
210
|
+
|
|
211
|
+
**For variables:**
|
|
212
|
+
- Does the name reveal what the variable contains?
|
|
213
|
+
- Is it specific to this context?
|
|
214
|
+
- Could someone understand it without reading the code?
|
|
215
|
+
|
|
216
|
+
### Refactoring Generic Names
|
|
217
|
+
|
|
218
|
+
When you encounter generic names:
|
|
219
|
+
|
|
220
|
+
1. **Understand the purpose**: What is this really doing?
|
|
221
|
+
2. **Ask domain experts**: What would they call this?
|
|
222
|
+
3. **Extract domain concept**: Is there a domain term for this?
|
|
223
|
+
4. **Rename comprehensively**: Update all references
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
## Type-Driven Design
|
|
227
|
+
|
|
228
|
+
**Principle:** Follow Scott Wlaschin's type-driven approach to domain modeling. Express domain concepts using the type system.
|
|
229
|
+
|
|
230
|
+
### Make Illegal States Unrepresentable
|
|
231
|
+
|
|
232
|
+
Use types to encode business rules:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
// ❌ PRIMITIVE OBSESSION - illegal states possible
|
|
236
|
+
interface Order {
|
|
237
|
+
status: string // Could be any string
|
|
238
|
+
shippedDate: Date | null // Could be set when status != 'shipped'
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ✓ TYPE-SAFE - illegal states impossible
|
|
242
|
+
type UnconfirmedOrder = { type: 'unconfirmed', items: Item[] }
|
|
243
|
+
type ConfirmedOrder = { type: 'confirmed', items: Item[], confirmationNumber: string }
|
|
244
|
+
type ShippedOrder = { type: 'shipped', items: Item[], confirmationNumber: string, shippedDate: Date }
|
|
245
|
+
|
|
246
|
+
type Order = UnconfirmedOrder | ConfirmedOrder | ShippedOrder
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Avoid Type Escape Hatches
|
|
250
|
+
|
|
251
|
+
**STRICTLY FORBIDDEN without explicit user approval:**
|
|
252
|
+
- `any` type
|
|
253
|
+
- `as` type assertions (`as unknown as`, `as any`, `as SomeType`)
|
|
254
|
+
- `@ts-ignore` / `@ts-expect-error`
|
|
255
|
+
|
|
256
|
+
There is always a better type-safe solution. These make code unsafe and defeat TypeScript's purpose.
|
|
257
|
+
|
|
258
|
+
### Use the Type System for Validation
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// ✓ TYPE-SAFE - validates at compile time
|
|
262
|
+
type PositiveNumber = number & { __brand: 'positive' }
|
|
263
|
+
|
|
264
|
+
function createPositive(value: number): PositiveNumber {
|
|
265
|
+
if (value <= 0) {
|
|
266
|
+
throw new Error(`Expected positive number, got ${value}`)
|
|
267
|
+
}
|
|
268
|
+
return value as PositiveNumber
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Can only be called with validated positive numbers
|
|
272
|
+
function calculateDiscount(price: PositiveNumber, rate: number): Money {
|
|
273
|
+
// price is guaranteed positive by type system
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Prefer Immutability
|
|
278
|
+
|
|
279
|
+
**Principle:** Default to immutable data. Mutation is a source of bugs—unexpected changes, race conditions, and difficult debugging.
|
|
280
|
+
|
|
281
|
+
### The Problem: Mutable State
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
// MUTABLE - hard to reason about
|
|
285
|
+
function processOrder(order: Order): void {
|
|
286
|
+
order.status = 'processing' // Mutates input!
|
|
287
|
+
order.items.push(freeGift) // Side effect!
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Caller has no idea their object changed
|
|
291
|
+
const myOrder = getOrder()
|
|
292
|
+
processOrder(myOrder)
|
|
293
|
+
// myOrder is now different - surprise!
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### The Solution: Return New Values
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// IMMUTABLE - predictable
|
|
300
|
+
function processOrder(order: Order): Order {
|
|
301
|
+
return {
|
|
302
|
+
...order,
|
|
303
|
+
status: 'processing',
|
|
304
|
+
items: [...order.items, freeGift]
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Caller controls what happens
|
|
309
|
+
const myOrder = getOrder()
|
|
310
|
+
const processedOrder = processOrder(myOrder)
|
|
311
|
+
// myOrder unchanged, processedOrder is new
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Application Rules
|
|
315
|
+
|
|
316
|
+
- Prefer `const` over `let`
|
|
317
|
+
- Prefer spread (`...`) over mutation
|
|
318
|
+
- Prefer `map`/`filter`/`reduce` over `forEach` with mutation
|
|
319
|
+
- If you must mutate, make it explicit and contained
|
|
320
|
+
|
|
321
|
+
## YAGNI - You Aren't Gonna Need It
|
|
322
|
+
|
|
323
|
+
**Principle:** Don't build features until they're actually needed. Speculative code is waste—it costs time to write, time to maintain, and is often wrong when requirements become clear.
|
|
324
|
+
|
|
325
|
+
### The Problem: Speculative Generalization
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
// YAGNI VIOLATION - over-engineered for "future" needs
|
|
329
|
+
interface PaymentProcessor {
|
|
330
|
+
process(payment: Payment): Result
|
|
331
|
+
refund(payment: Payment): Result
|
|
332
|
+
partialRefund(payment: Payment, amount: Money): Result
|
|
333
|
+
schedulePayment(payment: Payment, date: Date): Result
|
|
334
|
+
recurringPayment(payment: Payment, schedule: Schedule): Result
|
|
335
|
+
// ... 10 more methods "we might need"
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Only ONE method is actually used today
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
### Application Rules
|
|
343
|
+
|
|
344
|
+
- Build the simplest thing that works
|
|
345
|
+
- Add capabilities when requirements demand them, not before
|
|
346
|
+
- "But we might need it" is not a requirement
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
## When Tempted to Cut Corners
|
|
350
|
+
|
|
351
|
+
**STOP if you're about to:**
|
|
352
|
+
- Use `??` chains → fail fast with clear error instead
|
|
353
|
+
- Use `any` or `as` → fix the types, not the symptoms
|
|
354
|
+
- Use `new X()` inside a method → inject through constructor
|
|
355
|
+
- Name something `data`, `utils`, `handler` → use domain language
|
|
356
|
+
- Add a getter → ask if the object should do the work instead
|
|
357
|
+
- Skip refactor because "it works" → refactor IS part of the work
|
|
358
|
+
- Write a comment → make the code self-explanatory
|
|
359
|
+
- Mutate a parameter → return a new value
|
|
360
|
+
- Build "for later" → build what you need now
|