jdi-cli 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/AGENTS.md +209 -0
- package/ARCHITECTURE.md +210 -0
- package/COMMANDS.md +241 -0
- package/CREATE-EXAMPLE.md +385 -0
- package/CREATE.md +315 -0
- package/EXTENSION.md +141 -0
- package/LICENSE +21 -0
- package/MEMORY.md +471 -0
- package/PORTABILITY.md +438 -0
- package/README.md +789 -0
- package/bin/git-hooks/post-commit +16 -0
- package/bin/git-hooks/pre-commit +21 -0
- package/bin/jdi-build.ps1 +381 -0
- package/bin/jdi-build.sh +332 -0
- package/bin/jdi-doctor.ps1 +403 -0
- package/bin/jdi-doctor.sh +400 -0
- package/bin/jdi-install-caveman.ps1 +97 -0
- package/bin/jdi-install-caveman.sh +99 -0
- package/bin/jdi-install-playwright.ps1 +319 -0
- package/bin/jdi-install-playwright.sh +284 -0
- package/bin/jdi-install.ps1 +154 -0
- package/bin/jdi-install.sh +132 -0
- package/bin/jdi-uninstall.ps1 +309 -0
- package/bin/jdi-uninstall.sh +264 -0
- package/bin/jdi-update.ps1 +215 -0
- package/bin/jdi-update.sh +209 -0
- package/bin/jdi.js +460 -0
- package/bin/lib/jdi-monitor.ps1 +66 -0
- package/bin/lib/jdi-monitor.sh +74 -0
- package/bin/lib/jdi-truncate.ps1 +96 -0
- package/bin/lib/jdi-truncate.sh +99 -0
- package/bin/lib/ui.js +197 -0
- package/core/agents/jdi-adopter.md +465 -0
- package/core/agents/jdi-architect.md +894 -0
- package/core/agents/jdi-asker.md +153 -0
- package/core/agents/jdi-bootstrap.md +247 -0
- package/core/agents/jdi-planner.md +254 -0
- package/core/agents/jdi-researcher.md +303 -0
- package/core/commands/jdi-adopt.md +155 -0
- package/core/commands/jdi-bootstrap.md +81 -0
- package/core/commands/jdi-create.md +80 -0
- package/core/commands/jdi-discuss.md +80 -0
- package/core/commands/jdi-do.md +200 -0
- package/core/commands/jdi-loop.md +315 -0
- package/core/commands/jdi-new.md +131 -0
- package/core/commands/jdi-plan.md +73 -0
- package/core/commands/jdi-ship.md +146 -0
- package/core/commands/jdi-verify.md +159 -0
- package/core/skills/clean-code/SKILL.md +261 -0
- package/core/skills/dry/SKILL.md +150 -0
- package/core/skills/frontend-rules/SKILL.md +386 -0
- package/core/skills/frontend-validator/SKILL.md +567 -0
- package/core/skills/kiss/SKILL.md +178 -0
- package/core/skills/solid/SKILL.md +281 -0
- package/core/skills/yagni/SKILL.md +207 -0
- package/core/templates/agent.md +72 -0
- package/core/templates/doer-specialist.md +216 -0
- package/core/templates/reviewer-specialist.md +405 -0
- package/core/templates/skill.md +66 -0
- package/package.json +70 -0
- package/runtimes/antigravity/agents.md +74 -0
- package/runtimes/antigravity/skills/clean-code/SKILL.md +252 -0
- package/runtimes/antigravity/skills/dry/SKILL.md +141 -0
- package/runtimes/antigravity/skills/frontend-rules/SKILL.md +376 -0
- package/runtimes/antigravity/skills/frontend-validator/SKILL.md +559 -0
- package/runtimes/antigravity/skills/jdi-adopt/SKILL.md +155 -0
- package/runtimes/antigravity/skills/jdi-adopter/SKILL.md +436 -0
- package/runtimes/antigravity/skills/jdi-architect/SKILL.md +872 -0
- package/runtimes/antigravity/skills/jdi-asker/SKILL.md +125 -0
- package/runtimes/antigravity/skills/jdi-asker/references/context-template.md +34 -0
- package/runtimes/antigravity/skills/jdi-asker/references/decision-format.md +19 -0
- package/runtimes/antigravity/skills/jdi-asker/scripts/find_phase_dir.sh +25 -0
- package/runtimes/antigravity/skills/jdi-bootstrap/SKILL.md +81 -0
- package/runtimes/antigravity/skills/jdi-create/SKILL.md +80 -0
- package/runtimes/antigravity/skills/jdi-discuss/SKILL.md +80 -0
- package/runtimes/antigravity/skills/jdi-discuss/scripts/run_command.sh +62 -0
- package/runtimes/antigravity/skills/jdi-do/SKILL.md +200 -0
- package/runtimes/antigravity/skills/jdi-loop/SKILL.md +315 -0
- package/runtimes/antigravity/skills/jdi-new/SKILL.md +131 -0
- package/runtimes/antigravity/skills/jdi-plan/SKILL.md +73 -0
- package/runtimes/antigravity/skills/jdi-planner/SKILL.md +225 -0
- package/runtimes/antigravity/skills/jdi-researcher/SKILL.md +274 -0
- package/runtimes/antigravity/skills/jdi-ship/SKILL.md +146 -0
- package/runtimes/antigravity/skills/jdi-verify/SKILL.md +159 -0
- package/runtimes/antigravity/skills/kiss/SKILL.md +169 -0
- package/runtimes/antigravity/skills/solid/SKILL.md +272 -0
- package/runtimes/antigravity/skills/yagni/SKILL.md +198 -0
- package/runtimes/claude/CLAUDE.md +91 -0
- package/runtimes/claude/agents/jdi-adopter.md +430 -0
- package/runtimes/claude/agents/jdi-architect.md +864 -0
- package/runtimes/claude/agents/jdi-asker.md +119 -0
- package/runtimes/claude/agents/jdi-bootstrap.md +213 -0
- package/runtimes/claude/agents/jdi-planner.md +221 -0
- package/runtimes/claude/agents/jdi-researcher.md +269 -0
- package/runtimes/claude/commands/jdi-adopt.md +155 -0
- package/runtimes/claude/commands/jdi-bootstrap.md +81 -0
- package/runtimes/claude/commands/jdi-create.md +80 -0
- package/runtimes/claude/commands/jdi-discuss.md +80 -0
- package/runtimes/claude/commands/jdi-do.md +200 -0
- package/runtimes/claude/commands/jdi-loop.md +315 -0
- package/runtimes/claude/commands/jdi-new.md +131 -0
- package/runtimes/claude/commands/jdi-plan.md +73 -0
- package/runtimes/claude/commands/jdi-ship.md +146 -0
- package/runtimes/claude/commands/jdi-verify.md +159 -0
- package/runtimes/claude/settings.example.json +132 -0
- package/runtimes/claude/skills/clean-code/SKILL.md +247 -0
- package/runtimes/claude/skills/dry/SKILL.md +136 -0
- package/runtimes/claude/skills/frontend-rules/SKILL.md +369 -0
- package/runtimes/claude/skills/frontend-validator/SKILL.md +553 -0
- package/runtimes/claude/skills/kiss/SKILL.md +164 -0
- package/runtimes/claude/skills/solid/SKILL.md +267 -0
- package/runtimes/claude/skills/yagni/SKILL.md +193 -0
- package/runtimes/copilot/agents/jdi-adopter.agent.md +430 -0
- package/runtimes/copilot/agents/jdi-architect.agent.md +864 -0
- package/runtimes/copilot/agents/jdi-asker.agent.md +119 -0
- package/runtimes/copilot/agents/jdi-bootstrap.agent.md +213 -0
- package/runtimes/copilot/agents/jdi-planner.agent.md +221 -0
- package/runtimes/copilot/agents/jdi-researcher.agent.md +269 -0
- package/runtimes/copilot/copilot-instructions.md +80 -0
- package/runtimes/copilot/prompts/jdi-adopt.prompt.md +155 -0
- package/runtimes/copilot/prompts/jdi-bootstrap.prompt.md +81 -0
- package/runtimes/copilot/prompts/jdi-create.prompt.md +80 -0
- package/runtimes/copilot/prompts/jdi-discuss.prompt.md +80 -0
- package/runtimes/copilot/prompts/jdi-do.prompt.md +200 -0
- package/runtimes/copilot/prompts/jdi-loop.prompt.md +315 -0
- package/runtimes/copilot/prompts/jdi-new.prompt.md +131 -0
- package/runtimes/copilot/prompts/jdi-plan.prompt.md +73 -0
- package/runtimes/copilot/prompts/jdi-ship.prompt.md +146 -0
- package/runtimes/copilot/prompts/jdi-verify.prompt.md +159 -0
- package/runtimes/opencode/AGENTS.md +87 -0
- package/runtimes/opencode/agents/jdi-adopter.md +434 -0
- package/runtimes/opencode/agents/jdi-architect.md +861 -0
- package/runtimes/opencode/agents/jdi-asker.md +123 -0
- package/runtimes/opencode/agents/jdi-bootstrap.md +217 -0
- package/runtimes/opencode/agents/jdi-planner.md +225 -0
- package/runtimes/opencode/agents/jdi-researcher.md +273 -0
- package/runtimes/opencode/commands/jdi-adopt.md +155 -0
- package/runtimes/opencode/commands/jdi-bootstrap.md +81 -0
- package/runtimes/opencode/commands/jdi-create.md +80 -0
- package/runtimes/opencode/commands/jdi-discuss.md +80 -0
- package/runtimes/opencode/commands/jdi-do.md +200 -0
- package/runtimes/opencode/commands/jdi-loop.md +315 -0
- package/runtimes/opencode/commands/jdi-new.md +131 -0
- package/runtimes/opencode/commands/jdi-plan.md +73 -0
- package/runtimes/opencode/commands/jdi-ship.md +146 -0
- package/runtimes/opencode/commands/jdi-verify.md +159 -0
- package/runtimes/opencode/opencode.example.jsonc +169 -0
- package/runtimes/opencode/skills/clean-code/SKILL.md +247 -0
- package/runtimes/opencode/skills/dry/SKILL.md +136 -0
- package/runtimes/opencode/skills/frontend-rules/SKILL.md +369 -0
- package/runtimes/opencode/skills/frontend-validator/SKILL.md +553 -0
- package/runtimes/opencode/skills/kiss/SKILL.md +164 -0
- package/runtimes/opencode/skills/solid/SKILL.md +267 -0
- package/runtimes/opencode/skills/yagni/SKILL.md +193 -0
- package/templates-jdi-folder/config.json +18 -0
- package/templates-jdi-folder/registry.md +31 -0
- package/templates-jdi-folder/reviewers.md +33 -0
- package/templates-jdi-folder/skills-registry.md +32 -0
- package/templates-jdi-folder/specialists.md +39 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kiss
|
|
3
|
+
description: KISS (Keep It Simple, Stupid). The simplest solution that solves the problem wins. Complexity only justified by real measured pain. Each layer/abstraction must pay its own cost. Applies in any language.
|
|
4
|
+
type: skill
|
|
5
|
+
applies_to: |
|
|
6
|
+
Loaded by doer when designing a new feature/module.
|
|
7
|
+
Loaded by reviewer at gate 5 to detect over-engineering.
|
|
8
|
+
loaded_by:
|
|
9
|
+
- jdi-doer-{slug}
|
|
10
|
+
- jdi-reviewer-{slug}
|
|
11
|
+
runtime_overrides:
|
|
12
|
+
antigravity:
|
|
13
|
+
triggers:
|
|
14
|
+
- "KISS"
|
|
15
|
+
- "keep simple"
|
|
16
|
+
- "over-engineering"
|
|
17
|
+
- "simplicity"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Skill: KISS
|
|
21
|
+
|
|
22
|
+
> Simplicity is the best design. Every complexity must pay its own cost.
|
|
23
|
+
|
|
24
|
+
KISS is not "dumb code". It's **rejecting unjustified complexity**. Every interface, every layer, every abstraction has maintenance cost — only worth it if it solves real pain.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
### 1. Default is the simplest
|
|
29
|
+
|
|
30
|
+
Ask before adding:
|
|
31
|
+
- **Function** vs class vs framework?
|
|
32
|
+
- **Variable** vs config vs feature flag?
|
|
33
|
+
- **If/else** vs strategy pattern vs plugin system?
|
|
34
|
+
- **Sync** vs async vs queue vs event bus?
|
|
35
|
+
- **Inline** vs helper vs lib?
|
|
36
|
+
|
|
37
|
+
Start with the leftmost. Only step up if there is a real requirement.
|
|
38
|
+
|
|
39
|
+
### 2. Complexity must justify pain
|
|
40
|
+
|
|
41
|
+
**Allowed:**
|
|
42
|
+
- New pattern if it has 3+ real cases using it
|
|
43
|
+
- Abstraction layer if it has 2+ implementations that exist today
|
|
44
|
+
- Cache if measurement shows hot path
|
|
45
|
+
- Async if there's unacceptable latency synchronous
|
|
46
|
+
- Plugin system if there are confirmed external extenders
|
|
47
|
+
|
|
48
|
+
**Forbidden:**
|
|
49
|
+
- "Will scale later" without current requirement
|
|
50
|
+
- "Other people might need it" without other people
|
|
51
|
+
- "To make it generic" without 2nd use case
|
|
52
|
+
- "Will look cleaner" trading 5 clear lines for 50 elegant ones
|
|
53
|
+
- Enterprise pattern in small codebase (Repository + UoW + Mediator + CQRS for 10-controller app)
|
|
54
|
+
|
|
55
|
+
### 3. Cognitive load is a real metric
|
|
56
|
+
|
|
57
|
+
Code you read 10x and write 1x. Optimize for reading:
|
|
58
|
+
- **Named variables** > composite expression
|
|
59
|
+
- **Early return** > nested if/else
|
|
60
|
+
- **Linear function** > jumps between callbacks
|
|
61
|
+
- **Explicit types** > magical inference in large codebase
|
|
62
|
+
- **Simple procedural code** > fancy OOP for 50 lines
|
|
63
|
+
|
|
64
|
+
Rule: code that needs a comment explaining "why so complex" is too complex.
|
|
65
|
+
|
|
66
|
+
### 4. Indicators of over-engineering
|
|
67
|
+
|
|
68
|
+
Signs the code went over the line:
|
|
69
|
+
|
|
70
|
+
- Interface with 1 implementation
|
|
71
|
+
- Factory/Builder for something instantiated 1x
|
|
72
|
+
- Generic <T> only used with 1 type
|
|
73
|
+
- Config with a key that never changed
|
|
74
|
+
- Abstraction layer that only encapsulates a call to another layer (pass-through)
|
|
75
|
+
- Inheritance hierarchy > 2 levels
|
|
76
|
+
- File with more setup than logic
|
|
77
|
+
- Test that needs 30 lines of mock to run 5 lines of logic
|
|
78
|
+
|
|
79
|
+
### 5. Refactor is the opposite direction
|
|
80
|
+
|
|
81
|
+
Natural tendency: code grows in complexity. Refactoring = REMOVE complexity that no longer pays.
|
|
82
|
+
|
|
83
|
+
Ask:
|
|
84
|
+
- Does this layer still exist to solve a problem, or did it become tradition?
|
|
85
|
+
- Does this abstraction have 2+ implementations today?
|
|
86
|
+
- If I delete this, what breaks?
|
|
87
|
+
- Can I solve it with 5 lines instead of 50?
|
|
88
|
+
|
|
89
|
+
## Anti-patterns
|
|
90
|
+
|
|
91
|
+
| Anti-pattern | Symptom |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Interface + 1 implementation | `IUserService` + `UserService` (only 1) — delete the interface, use the class |
|
|
94
|
+
| Generic `<T>` used with 1 type | `Repository<User>` but never `Repository<Order>` — concretize |
|
|
95
|
+
| Factory for new() | `UserFactory.create()` that only does `return new User()` |
|
|
96
|
+
| Config string that never changed | `MAX_RETRIES: 3` in config + nobody ever changed it — hardcode |
|
|
97
|
+
| Inheritance > 2 levels | `BaseEntity -> AuditableEntity -> SoftDeletableEntity -> User` — flatten via composition |
|
|
98
|
+
| Pass-through layer | `Controller -> Service -> Repository -> DbContext` where Service only calls Repository without logic — delete Service |
|
|
99
|
+
| Enterprise pattern without demand | Mediator/CQRS in small app — replace with direct call |
|
|
100
|
+
| Comment explaining "why so complex" | Code lost the war — refactor |
|
|
101
|
+
| Mock setup > logic test | Test gets fragile; code under test is over-coupled |
|
|
102
|
+
| Unused future-proof params | `(opts?: { future?: boolean })` without caller passing — remove |
|
|
103
|
+
|
|
104
|
+
## Procedure
|
|
105
|
+
|
|
106
|
+
### Doer (before writing)
|
|
107
|
+
|
|
108
|
+
1. Ask: "What is the **simplest** version that meets the current requirement?"
|
|
109
|
+
2. Write that version.
|
|
110
|
+
3. Only step up complexity if you hit real pain.
|
|
111
|
+
4. After writing, ask: "Can I delete any layer/parameter/abstraction without losing functionality?"
|
|
112
|
+
|
|
113
|
+
### Reviewer (gate 5)
|
|
114
|
+
|
|
115
|
+
Over-engineering heuristics:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Interfaces with 1 implementation
|
|
119
|
+
grep -RnE '^(public |export )?interface I?[A-Z]\w+' src/ | while read iface; do
|
|
120
|
+
name=$(echo "$iface" | grep -oE '[A-Z]\w+\b' | head -1)
|
|
121
|
+
count=$(grep -RnE "class \w+\s*:\s*$name|implements $name" src/ | wc -l)
|
|
122
|
+
[[ $count -eq 1 ]] && echo "WARN: $iface has only 1 implementation"
|
|
123
|
+
done
|
|
124
|
+
|
|
125
|
+
# Deep inheritance (> 2 levels)
|
|
126
|
+
# (depends on stack — specific heuristic)
|
|
127
|
+
|
|
128
|
+
# Very large or nested functions
|
|
129
|
+
grep -cE '^\s{20,}\S' src/**/* # lines with 20+ spaces = deep nesting
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Match -> WARN with suggestion to simplify.
|
|
133
|
+
|
|
134
|
+
## Inputs
|
|
135
|
+
|
|
136
|
+
- Diff/content of the file
|
|
137
|
+
- Context: codebase size (over-engineering is relative)
|
|
138
|
+
|
|
139
|
+
## Outputs
|
|
140
|
+
|
|
141
|
+
Does NOT produce a file. Modifies judgement.
|
|
142
|
+
|
|
143
|
+
## Examples
|
|
144
|
+
|
|
145
|
+
### Example 1: Interface with 1 impl
|
|
146
|
+
|
|
147
|
+
Wrong:
|
|
148
|
+
```typescript
|
|
149
|
+
interface ILogger { log(msg: string): void }
|
|
150
|
+
class ConsoleLogger implements ILogger { log(msg) { console.log(msg) } }
|
|
151
|
+
const logger: ILogger = new ConsoleLogger()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Right (KISS):
|
|
155
|
+
```typescript
|
|
156
|
+
function log(msg: string) { console.log(msg) }
|
|
157
|
+
// or
|
|
158
|
+
class Logger { static log(msg: string) { console.log(msg) } }
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Add interface when 2nd impl arrives, not before.
|
|
162
|
+
|
|
163
|
+
### Example 2: Pass-through service
|
|
164
|
+
|
|
165
|
+
Wrong:
|
|
166
|
+
```csharp
|
|
167
|
+
public class UserService {
|
|
168
|
+
public User GetById(int id) => _repo.GetById(id); // only calls repo
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Right: use `_repo` directly in the controller. Add Service when there is real logic (validation, multi-step, transaction, event).
|
|
173
|
+
|
|
174
|
+
### Example 3: Hardcodable config
|
|
175
|
+
|
|
176
|
+
Wrong: `appsettings.json -> "MaxItemsPerPage": 50` that nobody ever changed in 2 years.
|
|
177
|
+
|
|
178
|
+
Right: `const MAX_ITEMS_PER_PAGE = 50` in the code. Move back to config if some client actually needs to customize.
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: solid
|
|
3
|
+
description: SOLID. Robert C. Martin's 5 OO design principles - SRP, OCP, LSP, ISP, DIP. Applicable in any language with types/classes/interfaces (C#, Java, TS, Python, Go, Rust, Kotlin, Swift, etc). Direct summary + anti-patterns + detection heuristics.
|
|
4
|
+
type: skill
|
|
5
|
+
applies_to: |
|
|
6
|
+
Loaded by doer when designing classes/modules/interfaces.
|
|
7
|
+
Loaded by reviewer at gate 5 to detect structural violations.
|
|
8
|
+
loaded_by:
|
|
9
|
+
- jdi-doer-{slug}
|
|
10
|
+
- jdi-reviewer-{slug}
|
|
11
|
+
runtime_overrides:
|
|
12
|
+
antigravity:
|
|
13
|
+
triggers:
|
|
14
|
+
- "SOLID"
|
|
15
|
+
- "SOLID principles"
|
|
16
|
+
- "SRP OCP LSP ISP DIP"
|
|
17
|
+
- "OO design"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Skill: SOLID
|
|
21
|
+
|
|
22
|
+
5 design principles — **S**ingle Responsibility, **O**pen/Closed, **L**iskov Substitution, **I**nterface Segregation, **D**ependency Inversion.
|
|
23
|
+
|
|
24
|
+
Applicable in any language with classes or interfaces. In FP/procedural, principles map to modules and functions (SRP: 1 function 1 responsibility; ISP: minimal parameters; DIP: dependency injection via parameter).
|
|
25
|
+
|
|
26
|
+
## S - Single Responsibility Principle (SRP)
|
|
27
|
+
|
|
28
|
+
> A class/module must have **1 reason to change**.
|
|
29
|
+
|
|
30
|
+
**1 reason = 1 stakeholder or 1 axis of change**, not "1 action".
|
|
31
|
+
|
|
32
|
+
`UserService` that does auth + persistence + sends email violates — there are 3 reasons to change (security team changes auth, DBA changes persistence, marketing changes email).
|
|
33
|
+
|
|
34
|
+
### Violation symptoms
|
|
35
|
+
- Class with generic name (`Manager`, `Helper`, `Service`, `Util`)
|
|
36
|
+
- Methods without thematic coherence
|
|
37
|
+
- Multiple imports of unrelated libraries (DB + email + crypto in the same class)
|
|
38
|
+
- Diff of one class affects separate domains in different sprints
|
|
39
|
+
|
|
40
|
+
### Fix
|
|
41
|
+
Extract responsibilities into separate classes:
|
|
42
|
+
- `UserAuthenticator` (auth)
|
|
43
|
+
- `UserRepository` (persistence)
|
|
44
|
+
- `UserNotifier` (email)
|
|
45
|
+
|
|
46
|
+
Compose in a coordinator if needed, but each piece has 1 reason.
|
|
47
|
+
|
|
48
|
+
## O - Open/Closed Principle (OCP)
|
|
49
|
+
|
|
50
|
+
> Open for **extension**, closed for **modification**.
|
|
51
|
+
|
|
52
|
+
Adding new behavior shouldn't require editing tested code. Use polymorphism, strategy, plugins, or config — not infinite if/else.
|
|
53
|
+
|
|
54
|
+
### Violation symptoms
|
|
55
|
+
- `switch (type)` that grows with every new feature
|
|
56
|
+
- `if (provider === "x") ... else if ... else if ...`
|
|
57
|
+
- Each new feature edits N existing classes instead of adding 1 new one
|
|
58
|
+
|
|
59
|
+
### Fix
|
|
60
|
+
- **Strategy pattern**: each case becomes an impl of an interface
|
|
61
|
+
- **Polymorphism**: subclass override instead of external switch
|
|
62
|
+
- **Registry**: `registry.register("x", handler)` — new feature just adds
|
|
63
|
+
- **Visitor**: for closed type hierarchies
|
|
64
|
+
|
|
65
|
+
### When to ignore
|
|
66
|
+
OCP is aspirational, not absolute. Apply at points of **known** variation (payment strategies are multiple), don't speculate (KISS + YAGNI).
|
|
67
|
+
|
|
68
|
+
## L - Liskov Substitution Principle (LSP)
|
|
69
|
+
|
|
70
|
+
> Subtype must be **substitutable** for the supertype without breaking behavior.
|
|
71
|
+
|
|
72
|
+
If `Bird` has method `fly()`, `Penguin extends Bird` violates LSP — penguin doesn't fly. Caller receiving `Bird` breaks when it receives `Penguin`.
|
|
73
|
+
|
|
74
|
+
### Violation symptoms
|
|
75
|
+
- Subclass that **throws exception** on inherited method ("not supported")
|
|
76
|
+
- Subclass that **strengthens precondition** (`base accepts >= 0`, sub accepts `> 0`)
|
|
77
|
+
- Subclass that **weakens postcondition** (base guarantees "sorted", sub doesn't guarantee)
|
|
78
|
+
- Caller needs `if (instanceof Subtype)` to handle a special case
|
|
79
|
+
|
|
80
|
+
### Fix
|
|
81
|
+
- Rethink hierarchy: `Penguin` isn't a flying `Bird`, it's a `Bird` that walks. Create `FlyingBird : Bird` and `Penguin : Bird` without fly.
|
|
82
|
+
- Composition over inheritance: prefer composed interfaces over deep hierarchies.
|
|
83
|
+
- Refactor to capabilities: `Flyable`, `Swimmable`, `Walkable` (overlap with ISP).
|
|
84
|
+
|
|
85
|
+
## I - Interface Segregation Principle (ISP)
|
|
86
|
+
|
|
87
|
+
> Clients should not be forced to depend on interfaces they **do not use**.
|
|
88
|
+
|
|
89
|
+
Big interfaces ("fat interfaces") force impls to stub meaningless methods (throwing NotImplemented), and callers to import broad dependencies.
|
|
90
|
+
|
|
91
|
+
### Violation symptoms
|
|
92
|
+
- Interface with 15 methods, each caller uses 2-3
|
|
93
|
+
- Impl with several methods throwing `throw new NotImplementedException()`
|
|
94
|
+
- Huge test mock to use 1 method of the interface
|
|
95
|
+
|
|
96
|
+
### Fix
|
|
97
|
+
Split into smaller, cohesive interfaces:
|
|
98
|
+
```
|
|
99
|
+
IFileReader { read() }
|
|
100
|
+
IFileWriter { write() }
|
|
101
|
+
// callers pick the subset they need
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
In FP/Go, same principle: minimal parameters, structural typing doesn't force implementing everything.
|
|
105
|
+
|
|
106
|
+
## D - Dependency Inversion Principle (DIP)
|
|
107
|
+
|
|
108
|
+
> High-level modules **do not** depend on low-level. Both depend on **abstractions**.
|
|
109
|
+
|
|
110
|
+
Business logic doesn't care about "PostgreSQL", "AWS S3", "SendGrid". It cares about abstractions (`UserRepository`, `BlobStorage`, `EmailSender`). Concrete implementations live in outer layers.
|
|
111
|
+
|
|
112
|
+
### Violation symptoms
|
|
113
|
+
- Domain class imports `pg`, `aws-sdk`, `sendgrid`, `axios`
|
|
114
|
+
- Business logic testable only with real infra (DB up, S3 mocked, etc)
|
|
115
|
+
- Swapping provider requires rewriting business logic
|
|
116
|
+
|
|
117
|
+
### Fix
|
|
118
|
+
- **Dependency injection** (constructor / property / parameter)
|
|
119
|
+
- Define interfaces in the domain module; impls live in infra/adapter layer (overlap with Hexagonal/Clean Architecture)
|
|
120
|
+
- Composition root injects the right impl
|
|
121
|
+
|
|
122
|
+
### DIP != IoC container
|
|
123
|
+
DIP is a principle. IoC container is **one** tool. You can apply DIP with manual constructor, simple factory, or parameter injection — no framework.
|
|
124
|
+
|
|
125
|
+
## Summary of the 5
|
|
126
|
+
|
|
127
|
+
| Letter | Focus | Key question |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| **S** | Unit cohesion | How many reasons to change this class/module? (>1 = violates) |
|
|
130
|
+
| **O** | Stability against extension | Does adding a new feature edit tested code or add new code? |
|
|
131
|
+
| **L** | Inheritance contract | Does replacing parent with child break any caller? |
|
|
132
|
+
| **I** | Interface size | Does every impl/caller use all methods? |
|
|
133
|
+
| **D** | Direction of dependency | Does domain import infra or does infra import domain? |
|
|
134
|
+
|
|
135
|
+
## General anti-patterns
|
|
136
|
+
|
|
137
|
+
| Anti-pattern | Principle violated |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `God class` with 30+ heterogeneous methods | SRP |
|
|
140
|
+
| `switch (kind)` in N callers, grows with each feature | OCP |
|
|
141
|
+
| Subclass with `throw NotSupportedException()` | LSP |
|
|
142
|
+
| `IRepository` with 20 generic methods | ISP |
|
|
143
|
+
| Domain service importing concrete ORM | DIP |
|
|
144
|
+
| Inheritance > 3 levels | LSP + SRP (usually) |
|
|
145
|
+
| Constructor with 8+ parameters | SRP (too many responsibilities) |
|
|
146
|
+
| Util class with 50 unrelated functions | SRP |
|
|
147
|
+
|
|
148
|
+
## Procedure
|
|
149
|
+
|
|
150
|
+
### Doer
|
|
151
|
+
|
|
152
|
+
Before creating class/module/interface:
|
|
153
|
+
- **SRP**: describe in 1 sentence. If you need "and", split.
|
|
154
|
+
- **ISP**: list expected callers. Does each one need **all** methods? If not, split.
|
|
155
|
+
- **DIP**: what does this depend on? Concretions (DB, HTTP, FS) -> inject as abstraction.
|
|
156
|
+
|
|
157
|
+
Before subclassing:
|
|
158
|
+
- **LSP**: does substitution by parent work in all callers? If not, wrong hierarchy.
|
|
159
|
+
|
|
160
|
+
Before adding `if/switch` at a growing point:
|
|
161
|
+
- **OCP**: is strategy/registry worth it?
|
|
162
|
+
|
|
163
|
+
### Reviewer (gate 5)
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# SRP — very large classes
|
|
167
|
+
find src/ -name '*.cs' -o -name '*.ts' -o -name '*.py' | while read f; do
|
|
168
|
+
loc=$(wc -l < "$f")
|
|
169
|
+
[[ $loc -gt 400 ]] && echo "WARN SRP: $f has $loc lines, possible god class"
|
|
170
|
+
done
|
|
171
|
+
|
|
172
|
+
# OCP — big switches on hot paths
|
|
173
|
+
grep -RnE 'switch\s*\(' src/ | head
|
|
174
|
+
# (manually: evaluate if it grows with each feature)
|
|
175
|
+
|
|
176
|
+
# LSP — NotImplemented in subclass
|
|
177
|
+
grep -RnE 'NotImplemented|UnsupportedOperation|throw new.*not (implemented|supported)' src/
|
|
178
|
+
|
|
179
|
+
# ISP — giant interfaces
|
|
180
|
+
grep -RnA50 '^(public |export )?interface' src/ | grep -cE '^\s+\w+\s*\(' | sort -rn
|
|
181
|
+
# count > 10 methods -> WARN
|
|
182
|
+
|
|
183
|
+
# DIP — domain module importing concretions
|
|
184
|
+
grep -RnE 'from.*pg|from.*aws-sdk|using Npgsql|using AWSSDK' src/domain/ src/core/
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Relevant match -> WARN with principle cited.
|
|
188
|
+
|
|
189
|
+
## Inputs
|
|
190
|
+
|
|
191
|
+
- Diff/content of the file
|
|
192
|
+
- Project structure (to detect domain vs infra)
|
|
193
|
+
|
|
194
|
+
## Outputs
|
|
195
|
+
|
|
196
|
+
Does NOT produce a file. Modifies judgement.
|
|
197
|
+
|
|
198
|
+
## Examples
|
|
199
|
+
|
|
200
|
+
### Example 1: SRP violated
|
|
201
|
+
|
|
202
|
+
Wrong:
|
|
203
|
+
```csharp
|
|
204
|
+
public class OrderService {
|
|
205
|
+
public Order Create(...) { /* validate + save + email + log + audit */ }
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
5 reasons to change.
|
|
210
|
+
|
|
211
|
+
Right:
|
|
212
|
+
```csharp
|
|
213
|
+
public class OrderValidator { }
|
|
214
|
+
public class OrderRepository { }
|
|
215
|
+
public class OrderNotifier { }
|
|
216
|
+
public class OrderService {
|
|
217
|
+
public Order Create(...) {
|
|
218
|
+
_validator.Validate(...)
|
|
219
|
+
var order = _repo.Save(...)
|
|
220
|
+
_notifier.Notify(order)
|
|
221
|
+
return order;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Example 2: OCP violated
|
|
227
|
+
|
|
228
|
+
Wrong:
|
|
229
|
+
```typescript
|
|
230
|
+
function calcDiscount(type: string, amount: number) {
|
|
231
|
+
if (type === "vip") return amount * 0.2
|
|
232
|
+
else if (type === "newcomer") return amount * 0.1
|
|
233
|
+
else if (type === "blackfriday") return amount * 0.5
|
|
234
|
+
return 0
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Each new type edits this function.
|
|
239
|
+
|
|
240
|
+
Right:
|
|
241
|
+
```typescript
|
|
242
|
+
const strategies: Record<string, (amount: number) => number> = {
|
|
243
|
+
vip: a => a * 0.2,
|
|
244
|
+
newcomer: a => a * 0.1,
|
|
245
|
+
blackfriday: a => a * 0.5,
|
|
246
|
+
}
|
|
247
|
+
function calcDiscount(type: string, amount: number) {
|
|
248
|
+
return strategies[type]?.(amount) ?? 0
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
New type: register in `strategies`, don't touch `calcDiscount`.
|
|
253
|
+
|
|
254
|
+
### Example 3: DIP violated
|
|
255
|
+
|
|
256
|
+
Wrong:
|
|
257
|
+
```python
|
|
258
|
+
# domain/order.py
|
|
259
|
+
import psycopg2
|
|
260
|
+
class OrderService:
|
|
261
|
+
def get(self, id):
|
|
262
|
+
conn = psycopg2.connect(...)
|
|
263
|
+
...
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Domain coupled to Postgres.
|
|
267
|
+
|
|
268
|
+
Right:
|
|
269
|
+
```python
|
|
270
|
+
# domain/order.py
|
|
271
|
+
class OrderService:
|
|
272
|
+
def __init__(self, repo: OrderRepository): # abstraction
|
|
273
|
+
self._repo = repo
|
|
274
|
+
def get(self, id): return self._repo.get(id)
|
|
275
|
+
|
|
276
|
+
# infra/postgres_order_repo.py
|
|
277
|
+
class PostgresOrderRepository(OrderRepository):
|
|
278
|
+
def get(self, id): ... # concrete impl
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Domain doesn't know Postgres exists.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yagni
|
|
3
|
+
description: YAGNI (You Aren't Gonna Need It). Build only what the current requirement asks for. Generalize after the 3rd real case, never before. Code not written is code with no bug, no maintenance cost, no pending test. Applies in any language.
|
|
4
|
+
type: skill
|
|
5
|
+
applies_to: |
|
|
6
|
+
Loaded by doer when planning implementation.
|
|
7
|
+
Loaded by reviewer at gate 5 to detect speculative code.
|
|
8
|
+
loaded_by:
|
|
9
|
+
- jdi-doer-{slug}
|
|
10
|
+
- jdi-reviewer-{slug}
|
|
11
|
+
runtime_overrides:
|
|
12
|
+
antigravity:
|
|
13
|
+
triggers:
|
|
14
|
+
- "YAGNI"
|
|
15
|
+
- "speculative code"
|
|
16
|
+
- "future-proof"
|
|
17
|
+
- "premature abstraction"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Skill: YAGNI
|
|
21
|
+
|
|
22
|
+
> You aren't gonna need it.
|
|
23
|
+
|
|
24
|
+
YAGNI is discipline against **speculative code**: features, abstractions, parameters, hooks, layers, configs that exist "in case it's needed". In 90% of cases, never needed — and when needed, the requirement is different from what you imagined.
|
|
25
|
+
|
|
26
|
+
## Rules
|
|
27
|
+
|
|
28
|
+
### 1. Build only what the current requirement asks
|
|
29
|
+
|
|
30
|
+
Ask of every new line of code:
|
|
31
|
+
- **Does this functionality have a requirement today?**
|
|
32
|
+
- **Who is the caller that needs this NOW?**
|
|
33
|
+
|
|
34
|
+
If there's no real caller, don't write it. Dead code is **net negative**: latent bug, maintenance cost, distraction in review, hinders refactor.
|
|
35
|
+
|
|
36
|
+
### 2. Generalize after the 3rd real case
|
|
37
|
+
|
|
38
|
+
Sandi Metz: "Duplication is far cheaper than the wrong abstraction."
|
|
39
|
+
|
|
40
|
+
- 1 case: implement specific
|
|
41
|
+
- 2 cases: copy or minimally parameterize
|
|
42
|
+
- 3 cases: now extract real pattern (one you **saw** happen, not imagined)
|
|
43
|
+
|
|
44
|
+
Generalizing earlier couples callers to the wrong interface. Refactoring later to the right interface is cheap; breaking callers to swap a wrong generic interface is expensive.
|
|
45
|
+
|
|
46
|
+
### 3. Costs of speculative code
|
|
47
|
+
|
|
48
|
+
Every "in case it's needed" line costs:
|
|
49
|
+
|
|
50
|
+
- **Maintenance**: someone will touch it when refactoring the neighborhood
|
|
51
|
+
- **Confusion**: reader thinks "this is being used, must be important"
|
|
52
|
+
- **Tests**: untested code becomes a bomb; tested, wasted time
|
|
53
|
+
- **Coupling**: callers will couple to the speculative interface, making it hard to remove
|
|
54
|
+
- **Scope creep**: simple feature becomes complex feature
|
|
55
|
+
- **Bug surface**: a line that doesn't exist has no bug
|
|
56
|
+
|
|
57
|
+
### 4. What YAGNI is NOT
|
|
58
|
+
|
|
59
|
+
YAGNI is not an excuse to:
|
|
60
|
+
- **Hardcoded everywhere**: some extension points are real requirements (i18n, logging, auth)
|
|
61
|
+
- **Cut real requirement**: if ticket asks for X, deliver X complete, not half
|
|
62
|
+
- **Skip security/error handling**: these are universal requirements, not speculative
|
|
63
|
+
- **Raw illegible code**: clarity is a requirement, not speculation
|
|
64
|
+
- **Skip tests**: coverage is a contract
|
|
65
|
+
|
|
66
|
+
### 5. Symptoms of violation
|
|
67
|
+
|
|
68
|
+
Code smells of broken YAGNI if:
|
|
69
|
+
|
|
70
|
+
- Optional parameters never passed (`fn(a, b, opts?: {...})` with opts always `undefined`)
|
|
71
|
+
- Hooks/events without subscribers
|
|
72
|
+
- Plugin system without plugins
|
|
73
|
+
- Config "in case we want to change" that nobody ever changed
|
|
74
|
+
- Interface with 1 impl (overlap with KISS)
|
|
75
|
+
- Generic `<T>` used with only 1 type
|
|
76
|
+
- "Future-proof" architecture written to scale 100x before validating current requirement
|
|
77
|
+
- Branches in code for scenarios nobody can describe
|
|
78
|
+
|
|
79
|
+
### 6. How to remove
|
|
80
|
+
|
|
81
|
+
After discovering speculative code:
|
|
82
|
+
1. Confirm nobody calls (`grep` callers)
|
|
83
|
+
2. Delete. Yes, delete directly. Git keeps history.
|
|
84
|
+
3. Don't leave "// removed on XX/YY" — more trash.
|
|
85
|
+
4. If you find out later you need it, add it when you need it (safe bet: later you know the real requirement, not imagined).
|
|
86
|
+
|
|
87
|
+
## Anti-patterns
|
|
88
|
+
|
|
89
|
+
| Anti-pattern | Why it violates |
|
|
90
|
+
|---|---|
|
|
91
|
+
| Optional parameter never used | Adds surface area without benefit |
|
|
92
|
+
| "Generic" function used by 1 caller | Generalized too early |
|
|
93
|
+
| Plugin/extension point without extenders | Dead code carries maintenance |
|
|
94
|
+
| "Configurable" config nobody changes | False flexibility — becomes hardcode later |
|
|
95
|
+
| Try/catch for impossible exception | Indicates fear, not requirement |
|
|
96
|
+
| Defensive validation for value coming from a safe type | TypeScript/C#/Python types already guarantee |
|
|
97
|
+
| `for/while` instead of direct return for "future looping" | Invents speculative repetition |
|
|
98
|
+
| Layer "to make it generic" without 2nd impl | Speculation with pass-through cost |
|
|
99
|
+
| Comment "TODO: extend to X later" without ticket | Message to nobody |
|
|
100
|
+
| Abstraction with 1 concrete implementation | Generic abstraction without second case |
|
|
101
|
+
| `enum` with 1 value "will grow" | Add value when it appears |
|
|
102
|
+
|
|
103
|
+
## Procedure
|
|
104
|
+
|
|
105
|
+
### Doer (before/during implementation)
|
|
106
|
+
|
|
107
|
+
Before adding:
|
|
108
|
+
|
|
109
|
+
1. **Is there a current requirement?** (ticket, conversation, explicit business rule) Otherwise, don't add.
|
|
110
|
+
2. **Who calls this today?** If nobody, don't add.
|
|
111
|
+
3. **When will I use that flexibility?** If "don't know", don't add.
|
|
112
|
+
|
|
113
|
+
After writing, ask:
|
|
114
|
+
- Is there a parameter/config/branch that could disappear without losing requirement?
|
|
115
|
+
|
|
116
|
+
### Reviewer (gate 5)
|
|
117
|
+
|
|
118
|
+
Heuristics:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
# Optional parameters never passed
|
|
122
|
+
# (depends on stack — examples)
|
|
123
|
+
grep -RnE 'function \w+\([^)]*opts\?:' src/ # TS
|
|
124
|
+
grep -RnE '\([^)]*=\s*null\)' src/ # optional default null
|
|
125
|
+
|
|
126
|
+
# "TODO: extend" code
|
|
127
|
+
grep -RnE 'TODO.*(extend|future|reserved|placeholder|in case)' src/
|
|
128
|
+
|
|
129
|
+
# Try/catch without clear reason
|
|
130
|
+
grep -RnA3 'try\s*{' src/ | grep -B1 'catch.*:.*ignore'
|
|
131
|
+
|
|
132
|
+
# Declared and unused variables
|
|
133
|
+
# (linter already catches — confirm in review)
|
|
134
|
+
|
|
135
|
+
# Plugin/extension points
|
|
136
|
+
grep -RnE 'register|registerPlugin|EventEmitter|hook(' src/
|
|
137
|
+
# Cross-check: are there actually callers?
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
3+ matches without real caller -> WARN.
|
|
141
|
+
|
|
142
|
+
## Inputs
|
|
143
|
+
|
|
144
|
+
- File diff (focus on additions)
|
|
145
|
+
- List of callers if any
|
|
146
|
+
|
|
147
|
+
## Outputs
|
|
148
|
+
|
|
149
|
+
Does NOT produce a file. Modifies judgement — doer avoids writing, reviewer marks WARN.
|
|
150
|
+
|
|
151
|
+
## Examples
|
|
152
|
+
|
|
153
|
+
### Example 1: Speculative optional param
|
|
154
|
+
|
|
155
|
+
Wrong:
|
|
156
|
+
```python
|
|
157
|
+
def send_email(to: str, subject: str, body: str,
|
|
158
|
+
cc: list[str] = None,
|
|
159
|
+
bcc: list[str] = None,
|
|
160
|
+
attachments: list[Path] = None,
|
|
161
|
+
priority: str = "normal",
|
|
162
|
+
retry_count: int = 3,
|
|
163
|
+
on_failure: Callable = None):
|
|
164
|
+
...
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Current requirement is to send simple email (`to, subject, body`). The other 5 params are speculative.
|
|
168
|
+
|
|
169
|
+
Right:
|
|
170
|
+
```python
|
|
171
|
+
def send_email(to: str, subject: str, body: str):
|
|
172
|
+
...
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Add `cc`, `bcc` etc **when** real requirement arrives, not before.
|
|
176
|
+
|
|
177
|
+
### Example 2: Plugin system without plugins
|
|
178
|
+
|
|
179
|
+
Wrong:
|
|
180
|
+
```typescript
|
|
181
|
+
class PaymentProcessor {
|
|
182
|
+
private plugins: Plugin[] = []
|
|
183
|
+
registerPlugin(p: Plugin) { this.plugins.push(p) }
|
|
184
|
+
process(...) {
|
|
185
|
+
this.plugins.forEach(p => p.beforeProcess())
|
|
186
|
+
// logic
|
|
187
|
+
this.plugins.forEach(p => p.afterProcess())
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Has 0 plugins registered. Whole plugin system is dead code.
|
|
193
|
+
|
|
194
|
+
Right:
|
|
195
|
+
```typescript
|
|
196
|
+
class PaymentProcessor {
|
|
197
|
+
process(...) { /* logic */ }
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
When the 1st real plugin appears, then yes. Not before.
|
|
202
|
+
|
|
203
|
+
### Example 3: Unused config string
|
|
204
|
+
|
|
205
|
+
Wrong: `config.json -> "DEFAULT_LANGUAGE": "pt-BR"` but nobody reads it. Code uses `"pt-BR"` directly.
|
|
206
|
+
|
|
207
|
+
Right: delete the config. Add it when the multi-language feature is actually implemented.
|