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.
Files changed (159) hide show
  1. package/AGENTS.md +209 -0
  2. package/ARCHITECTURE.md +210 -0
  3. package/COMMANDS.md +241 -0
  4. package/CREATE-EXAMPLE.md +385 -0
  5. package/CREATE.md +315 -0
  6. package/EXTENSION.md +141 -0
  7. package/LICENSE +21 -0
  8. package/MEMORY.md +471 -0
  9. package/PORTABILITY.md +438 -0
  10. package/README.md +789 -0
  11. package/bin/git-hooks/post-commit +16 -0
  12. package/bin/git-hooks/pre-commit +21 -0
  13. package/bin/jdi-build.ps1 +381 -0
  14. package/bin/jdi-build.sh +332 -0
  15. package/bin/jdi-doctor.ps1 +403 -0
  16. package/bin/jdi-doctor.sh +400 -0
  17. package/bin/jdi-install-caveman.ps1 +97 -0
  18. package/bin/jdi-install-caveman.sh +99 -0
  19. package/bin/jdi-install-playwright.ps1 +319 -0
  20. package/bin/jdi-install-playwright.sh +284 -0
  21. package/bin/jdi-install.ps1 +154 -0
  22. package/bin/jdi-install.sh +132 -0
  23. package/bin/jdi-uninstall.ps1 +309 -0
  24. package/bin/jdi-uninstall.sh +264 -0
  25. package/bin/jdi-update.ps1 +215 -0
  26. package/bin/jdi-update.sh +209 -0
  27. package/bin/jdi.js +460 -0
  28. package/bin/lib/jdi-monitor.ps1 +66 -0
  29. package/bin/lib/jdi-monitor.sh +74 -0
  30. package/bin/lib/jdi-truncate.ps1 +96 -0
  31. package/bin/lib/jdi-truncate.sh +99 -0
  32. package/bin/lib/ui.js +197 -0
  33. package/core/agents/jdi-adopter.md +465 -0
  34. package/core/agents/jdi-architect.md +894 -0
  35. package/core/agents/jdi-asker.md +153 -0
  36. package/core/agents/jdi-bootstrap.md +247 -0
  37. package/core/agents/jdi-planner.md +254 -0
  38. package/core/agents/jdi-researcher.md +303 -0
  39. package/core/commands/jdi-adopt.md +155 -0
  40. package/core/commands/jdi-bootstrap.md +81 -0
  41. package/core/commands/jdi-create.md +80 -0
  42. package/core/commands/jdi-discuss.md +80 -0
  43. package/core/commands/jdi-do.md +200 -0
  44. package/core/commands/jdi-loop.md +315 -0
  45. package/core/commands/jdi-new.md +131 -0
  46. package/core/commands/jdi-plan.md +73 -0
  47. package/core/commands/jdi-ship.md +146 -0
  48. package/core/commands/jdi-verify.md +159 -0
  49. package/core/skills/clean-code/SKILL.md +261 -0
  50. package/core/skills/dry/SKILL.md +150 -0
  51. package/core/skills/frontend-rules/SKILL.md +386 -0
  52. package/core/skills/frontend-validator/SKILL.md +567 -0
  53. package/core/skills/kiss/SKILL.md +178 -0
  54. package/core/skills/solid/SKILL.md +281 -0
  55. package/core/skills/yagni/SKILL.md +207 -0
  56. package/core/templates/agent.md +72 -0
  57. package/core/templates/doer-specialist.md +216 -0
  58. package/core/templates/reviewer-specialist.md +405 -0
  59. package/core/templates/skill.md +66 -0
  60. package/package.json +70 -0
  61. package/runtimes/antigravity/agents.md +74 -0
  62. package/runtimes/antigravity/skills/clean-code/SKILL.md +252 -0
  63. package/runtimes/antigravity/skills/dry/SKILL.md +141 -0
  64. package/runtimes/antigravity/skills/frontend-rules/SKILL.md +376 -0
  65. package/runtimes/antigravity/skills/frontend-validator/SKILL.md +559 -0
  66. package/runtimes/antigravity/skills/jdi-adopt/SKILL.md +155 -0
  67. package/runtimes/antigravity/skills/jdi-adopter/SKILL.md +436 -0
  68. package/runtimes/antigravity/skills/jdi-architect/SKILL.md +872 -0
  69. package/runtimes/antigravity/skills/jdi-asker/SKILL.md +125 -0
  70. package/runtimes/antigravity/skills/jdi-asker/references/context-template.md +34 -0
  71. package/runtimes/antigravity/skills/jdi-asker/references/decision-format.md +19 -0
  72. package/runtimes/antigravity/skills/jdi-asker/scripts/find_phase_dir.sh +25 -0
  73. package/runtimes/antigravity/skills/jdi-bootstrap/SKILL.md +81 -0
  74. package/runtimes/antigravity/skills/jdi-create/SKILL.md +80 -0
  75. package/runtimes/antigravity/skills/jdi-discuss/SKILL.md +80 -0
  76. package/runtimes/antigravity/skills/jdi-discuss/scripts/run_command.sh +62 -0
  77. package/runtimes/antigravity/skills/jdi-do/SKILL.md +200 -0
  78. package/runtimes/antigravity/skills/jdi-loop/SKILL.md +315 -0
  79. package/runtimes/antigravity/skills/jdi-new/SKILL.md +131 -0
  80. package/runtimes/antigravity/skills/jdi-plan/SKILL.md +73 -0
  81. package/runtimes/antigravity/skills/jdi-planner/SKILL.md +225 -0
  82. package/runtimes/antigravity/skills/jdi-researcher/SKILL.md +274 -0
  83. package/runtimes/antigravity/skills/jdi-ship/SKILL.md +146 -0
  84. package/runtimes/antigravity/skills/jdi-verify/SKILL.md +159 -0
  85. package/runtimes/antigravity/skills/kiss/SKILL.md +169 -0
  86. package/runtimes/antigravity/skills/solid/SKILL.md +272 -0
  87. package/runtimes/antigravity/skills/yagni/SKILL.md +198 -0
  88. package/runtimes/claude/CLAUDE.md +91 -0
  89. package/runtimes/claude/agents/jdi-adopter.md +430 -0
  90. package/runtimes/claude/agents/jdi-architect.md +864 -0
  91. package/runtimes/claude/agents/jdi-asker.md +119 -0
  92. package/runtimes/claude/agents/jdi-bootstrap.md +213 -0
  93. package/runtimes/claude/agents/jdi-planner.md +221 -0
  94. package/runtimes/claude/agents/jdi-researcher.md +269 -0
  95. package/runtimes/claude/commands/jdi-adopt.md +155 -0
  96. package/runtimes/claude/commands/jdi-bootstrap.md +81 -0
  97. package/runtimes/claude/commands/jdi-create.md +80 -0
  98. package/runtimes/claude/commands/jdi-discuss.md +80 -0
  99. package/runtimes/claude/commands/jdi-do.md +200 -0
  100. package/runtimes/claude/commands/jdi-loop.md +315 -0
  101. package/runtimes/claude/commands/jdi-new.md +131 -0
  102. package/runtimes/claude/commands/jdi-plan.md +73 -0
  103. package/runtimes/claude/commands/jdi-ship.md +146 -0
  104. package/runtimes/claude/commands/jdi-verify.md +159 -0
  105. package/runtimes/claude/settings.example.json +132 -0
  106. package/runtimes/claude/skills/clean-code/SKILL.md +247 -0
  107. package/runtimes/claude/skills/dry/SKILL.md +136 -0
  108. package/runtimes/claude/skills/frontend-rules/SKILL.md +369 -0
  109. package/runtimes/claude/skills/frontend-validator/SKILL.md +553 -0
  110. package/runtimes/claude/skills/kiss/SKILL.md +164 -0
  111. package/runtimes/claude/skills/solid/SKILL.md +267 -0
  112. package/runtimes/claude/skills/yagni/SKILL.md +193 -0
  113. package/runtimes/copilot/agents/jdi-adopter.agent.md +430 -0
  114. package/runtimes/copilot/agents/jdi-architect.agent.md +864 -0
  115. package/runtimes/copilot/agents/jdi-asker.agent.md +119 -0
  116. package/runtimes/copilot/agents/jdi-bootstrap.agent.md +213 -0
  117. package/runtimes/copilot/agents/jdi-planner.agent.md +221 -0
  118. package/runtimes/copilot/agents/jdi-researcher.agent.md +269 -0
  119. package/runtimes/copilot/copilot-instructions.md +80 -0
  120. package/runtimes/copilot/prompts/jdi-adopt.prompt.md +155 -0
  121. package/runtimes/copilot/prompts/jdi-bootstrap.prompt.md +81 -0
  122. package/runtimes/copilot/prompts/jdi-create.prompt.md +80 -0
  123. package/runtimes/copilot/prompts/jdi-discuss.prompt.md +80 -0
  124. package/runtimes/copilot/prompts/jdi-do.prompt.md +200 -0
  125. package/runtimes/copilot/prompts/jdi-loop.prompt.md +315 -0
  126. package/runtimes/copilot/prompts/jdi-new.prompt.md +131 -0
  127. package/runtimes/copilot/prompts/jdi-plan.prompt.md +73 -0
  128. package/runtimes/copilot/prompts/jdi-ship.prompt.md +146 -0
  129. package/runtimes/copilot/prompts/jdi-verify.prompt.md +159 -0
  130. package/runtimes/opencode/AGENTS.md +87 -0
  131. package/runtimes/opencode/agents/jdi-adopter.md +434 -0
  132. package/runtimes/opencode/agents/jdi-architect.md +861 -0
  133. package/runtimes/opencode/agents/jdi-asker.md +123 -0
  134. package/runtimes/opencode/agents/jdi-bootstrap.md +217 -0
  135. package/runtimes/opencode/agents/jdi-planner.md +225 -0
  136. package/runtimes/opencode/agents/jdi-researcher.md +273 -0
  137. package/runtimes/opencode/commands/jdi-adopt.md +155 -0
  138. package/runtimes/opencode/commands/jdi-bootstrap.md +81 -0
  139. package/runtimes/opencode/commands/jdi-create.md +80 -0
  140. package/runtimes/opencode/commands/jdi-discuss.md +80 -0
  141. package/runtimes/opencode/commands/jdi-do.md +200 -0
  142. package/runtimes/opencode/commands/jdi-loop.md +315 -0
  143. package/runtimes/opencode/commands/jdi-new.md +131 -0
  144. package/runtimes/opencode/commands/jdi-plan.md +73 -0
  145. package/runtimes/opencode/commands/jdi-ship.md +146 -0
  146. package/runtimes/opencode/commands/jdi-verify.md +159 -0
  147. package/runtimes/opencode/opencode.example.jsonc +169 -0
  148. package/runtimes/opencode/skills/clean-code/SKILL.md +247 -0
  149. package/runtimes/opencode/skills/dry/SKILL.md +136 -0
  150. package/runtimes/opencode/skills/frontend-rules/SKILL.md +369 -0
  151. package/runtimes/opencode/skills/frontend-validator/SKILL.md +553 -0
  152. package/runtimes/opencode/skills/kiss/SKILL.md +164 -0
  153. package/runtimes/opencode/skills/solid/SKILL.md +267 -0
  154. package/runtimes/opencode/skills/yagni/SKILL.md +193 -0
  155. package/templates-jdi-folder/config.json +18 -0
  156. package/templates-jdi-folder/registry.md +31 -0
  157. package/templates-jdi-folder/reviewers.md +33 -0
  158. package/templates-jdi-folder/skills-registry.md +32 -0
  159. package/templates-jdi-folder/specialists.md +39 -0
@@ -0,0 +1,159 @@
1
+ ---
2
+ name: jdi-verify
3
+ description: Runs phase quality gates via reviewer specialist. Build, tests, coverage, lint, security checks. Verdict APPROVED / APPROVED_WITH_WARNINGS / BLOCKED.
4
+ argument_hint: "<phase_number>"
5
+ runtime_intent:
6
+ invokes_agent: dynamic
7
+ runtime_overrides:
8
+ claude:
9
+ allowed-tools: [Read, Bash, Grep, Glob, Agent]
10
+ copilot:
11
+ tools: [read, grep, glob, terminal]
12
+ opencode:
13
+ subtask: true
14
+ model: anthropic/claude-sonnet-4-20250514
15
+ antigravity:
16
+ triggers:
17
+ - "/jdi-verify"
18
+ - "verify phase {N}"
19
+ ---
20
+
21
+ <objective>
22
+ Verifies the phase was delivered correctly. Runs gates defined in the project's reviewer specialist. Verdict blocks or releases the ship.
23
+ </objective>
24
+
25
+ <arguments>
26
+ - `phase_number` (required)
27
+ </arguments>
28
+
29
+ <process>
30
+
31
+ ### Step 1: Validation
32
+ ```bash
33
+ test -d .jdi/ || { echo "Not a JDI project."; exit 1; }
34
+
35
+ # Verify reviewer exists
36
+ ls .jdi/agents/jdi-reviewer-*.md 2>/dev/null | head -1 || {
37
+ echo "Reviewer missing. /jdi-bootstrap."
38
+ exit 1
39
+ }
40
+
41
+ # Verify phase was executed
42
+ ls .jdi/phases/{NN}*/SUMMARY.md 2>/dev/null || {
43
+ echo "Phase {N} not executed. /jdi-do {N}."
44
+ exit 1
45
+ }
46
+
47
+ # Context budget warm-up (does not block)
48
+ JDI_LIB="$(dirname "$(command -v jdi 2>/dev/null || echo /usr/local/bin/jdi)")/../lib"
49
+ if [ -f "$JDI_LIB/jdi-monitor.sh" ]; then
50
+ bash "$JDI_LIB/jdi-monitor.sh" .jdi/PROJECT.md .jdi/DECISIONS.md .jdi/phases/{NN}*/PLAN.md .jdi/phases/{NN}*/SUMMARY.md || true
51
+ fi
52
+ # Windows: pwsh -File "$JDI_LIB/jdi-monitor.ps1" -Paths @(...)
53
+ ```
54
+
55
+ ### Step 2: Resolve reviewer specialist(s)
56
+
57
+ ```bash
58
+ REVIEWERS=$(grep -oE 'jdi-reviewer-[a-z0-9-]+' .jdi/reviewers.md | sort -u)
59
+ REVIEWER_COUNT=$(echo "$REVIEWERS" | wc -l)
60
+ echo "Reviewers registered: $REVIEWER_COUNT"
61
+ ```
62
+
63
+ **Single-stack** (`REVIEWER_COUNT == 1`): one reviewer, normal flow.
64
+ **Multi-stack** (`REVIEWER_COUNT > 1`): chain reviewers in registry order. Each writes its own REVIEW segment; aggregate verdict = worst-case (1 BLOCK = overall BLOCK).
65
+
66
+ ### Step 3: Spawn reviewer(s)
67
+
68
+ **Single-stack:**
69
+ ```
70
+ Agent(
71
+ subagent_type="${REVIEWERS}",
72
+ description="Verify phase {N}",
73
+ prompt="phase={N}, mode=verify"
74
+ )
75
+ ```
76
+
77
+ **Multi-stack:** spawn each reviewer in sequence (NOT parallel — build/test commands may conflict on ports, locks, output dirs):
78
+
79
+ ```
80
+ for REVIEWER in $REVIEWERS:
81
+ Agent(
82
+ subagent_type="$REVIEWER",
83
+ description="Verify phase {N} ({REVIEWER})",
84
+ prompt="phase={N}, mode=verify, reviewer_segment=${REVIEWER}"
85
+ )
86
+ # Each reviewer appends to .jdi/phases/{NN-slug}/REVIEW.md under section
87
+ # "## Reviewer: {REVIEWER}" with its own gate results and verdict
88
+ ```
89
+
90
+ Each reviewer scopes its gates to its `file_glob` (from frontmatter `scope.file_glob`). Coverage threshold enforced only on files matching the glob.
91
+
92
+ Reviewers are read-only. Wait for completion before next.
93
+
94
+ ### Step 4: Read aggregate verdict
95
+
96
+ ```bash
97
+ test -f .jdi/phases/{NN}*/REVIEW.md || { echo "REVIEW.md not created"; exit 1; }
98
+
99
+ # Collect all per-reviewer verdicts
100
+ VERDICTS=$(grep -oE 'Verdict:\*\* (APPROVED|APPROVED_WITH_WARNINGS|BLOCKED)' .jdi/phases/{NN}*/REVIEW.md | awk '{print $2}')
101
+
102
+ # Worst-case wins: BLOCK > WARNINGS > APPROVED
103
+ if echo "$VERDICTS" | grep -q BLOCKED; then
104
+ VERDICT=BLOCKED
105
+ elif echo "$VERDICTS" | grep -q APPROVED_WITH_WARNINGS; then
106
+ VERDICT=APPROVED_WITH_WARNINGS
107
+ else
108
+ VERDICT=APPROVED
109
+ fi
110
+
111
+ # For single-stack, this collapses to the single reviewer's verdict — backward compatible.
112
+ ```
113
+
114
+ ### Step 5: Update STATE
115
+
116
+ ```markdown
117
+ current_phase: {N}
118
+ phase_status: {verified|blocked}
119
+ phase_verdict: {APPROVED|APPROVED_WITH_WARNINGS|BLOCKED}
120
+ next_step: {if APPROVED or WITH_WARNINGS: /jdi-ship {N}; if BLOCKED: fix and /jdi-do {N} again}
121
+ ```
122
+
123
+ ```bash
124
+ git add .jdi/phases/{NN-slug}/REVIEW.md .jdi/STATE.md
125
+ git commit -m "docs({NN-slug}): verify phase ({VERDICT})"
126
+ ```
127
+
128
+ ### Step 6: Confirm
129
+
130
+ **APPROVED:**
131
+ ```
132
+ Phase {N}: APPROVED. Next: /jdi-ship {N}
133
+ ```
134
+
135
+ **APPROVED_WITH_WARNINGS:**
136
+ ```
137
+ Phase {N}: APPROVED_WITH_WARNINGS ({count} warnings).
138
+ REVIEW.md: .jdi/phases/{NN-slug}/REVIEW.md
139
+ Next: /jdi-ship {N} (or fix first)
140
+ ```
141
+
142
+ **BLOCKED:**
143
+ ```
144
+ Phase {N}: BLOCKED ({count} blockers). REVIEW.md: .jdi/phases/{NN-slug}/REVIEW.md
145
+ Fix → /jdi-do {N} → /jdi-verify {N}
146
+ ```
147
+
148
+ </process>
149
+
150
+ <gates>
151
+ - pre: SUMMARY.md exists + reviewer registered in .jdi/reviewers.md
152
+ - post: REVIEW.md created + STATE updated
153
+ </gates>
154
+
155
+ <errors>
156
+ - Reviewer missing -> /jdi-bootstrap
157
+ - SUMMARY missing -> /jdi-do
158
+ - Reviewer fails -> show error, keep state, suggest retry
159
+ </errors>
@@ -0,0 +1,169 @@
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
+ triggers:
5
+ - "KISS"
6
+ - "keep simple"
7
+ - "over-engineering"
8
+ - "simplicity"
9
+ ---
10
+
11
+ # Skill: KISS
12
+
13
+ > Simplicity is the best design. Every complexity must pay its own cost.
14
+
15
+ 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.
16
+
17
+ ## Rules
18
+
19
+ ### 1. Default is the simplest
20
+
21
+ Ask before adding:
22
+ - **Function** vs class vs framework?
23
+ - **Variable** vs config vs feature flag?
24
+ - **If/else** vs strategy pattern vs plugin system?
25
+ - **Sync** vs async vs queue vs event bus?
26
+ - **Inline** vs helper vs lib?
27
+
28
+ Start with the leftmost. Only step up if there is a real requirement.
29
+
30
+ ### 2. Complexity must justify pain
31
+
32
+ **Allowed:**
33
+ - New pattern if it has 3+ real cases using it
34
+ - Abstraction layer if it has 2+ implementations that exist today
35
+ - Cache if measurement shows hot path
36
+ - Async if there's unacceptable latency synchronous
37
+ - Plugin system if there are confirmed external extenders
38
+
39
+ **Forbidden:**
40
+ - "Will scale later" without current requirement
41
+ - "Other people might need it" without other people
42
+ - "To make it generic" without 2nd use case
43
+ - "Will look cleaner" trading 5 clear lines for 50 elegant ones
44
+ - Enterprise pattern in small codebase (Repository + UoW + Mediator + CQRS for 10-controller app)
45
+
46
+ ### 3. Cognitive load is a real metric
47
+
48
+ Code you read 10x and write 1x. Optimize for reading:
49
+ - **Named variables** > composite expression
50
+ - **Early return** > nested if/else
51
+ - **Linear function** > jumps between callbacks
52
+ - **Explicit types** > magical inference in large codebase
53
+ - **Simple procedural code** > fancy OOP for 50 lines
54
+
55
+ Rule: code that needs a comment explaining "why so complex" is too complex.
56
+
57
+ ### 4. Indicators of over-engineering
58
+
59
+ Signs the code went over the line:
60
+
61
+ - Interface with 1 implementation
62
+ - Factory/Builder for something instantiated 1x
63
+ - Generic <T> only used with 1 type
64
+ - Config with a key that never changed
65
+ - Abstraction layer that only encapsulates a call to another layer (pass-through)
66
+ - Inheritance hierarchy > 2 levels
67
+ - File with more setup than logic
68
+ - Test that needs 30 lines of mock to run 5 lines of logic
69
+
70
+ ### 5. Refactor is the opposite direction
71
+
72
+ Natural tendency: code grows in complexity. Refactoring = REMOVE complexity that no longer pays.
73
+
74
+ Ask:
75
+ - Does this layer still exist to solve a problem, or did it become tradition?
76
+ - Does this abstraction have 2+ implementations today?
77
+ - If I delete this, what breaks?
78
+ - Can I solve it with 5 lines instead of 50?
79
+
80
+ ## Anti-patterns
81
+
82
+ | Anti-pattern | Symptom |
83
+ |---|---|
84
+ | Interface + 1 implementation | `IUserService` + `UserService` (only 1) — delete the interface, use the class |
85
+ | Generic `<T>` used with 1 type | `Repository<User>` but never `Repository<Order>` — concretize |
86
+ | Factory for new() | `UserFactory.create()` that only does `return new User()` |
87
+ | Config string that never changed | `MAX_RETRIES: 3` in config + nobody ever changed it — hardcode |
88
+ | Inheritance > 2 levels | `BaseEntity -> AuditableEntity -> SoftDeletableEntity -> User` — flatten via composition |
89
+ | Pass-through layer | `Controller -> Service -> Repository -> DbContext` where Service only calls Repository without logic — delete Service |
90
+ | Enterprise pattern without demand | Mediator/CQRS in small app — replace with direct call |
91
+ | Comment explaining "why so complex" | Code lost the war — refactor |
92
+ | Mock setup > logic test | Test gets fragile; code under test is over-coupled |
93
+ | Unused future-proof params | `(opts?: { future?: boolean })` without caller passing — remove |
94
+
95
+ ## Procedure
96
+
97
+ ### Doer (before writing)
98
+
99
+ 1. Ask: "What is the **simplest** version that meets the current requirement?"
100
+ 2. Write that version.
101
+ 3. Only step up complexity if you hit real pain.
102
+ 4. After writing, ask: "Can I delete any layer/parameter/abstraction without losing functionality?"
103
+
104
+ ### Reviewer (gate 5)
105
+
106
+ Over-engineering heuristics:
107
+
108
+ ```bash
109
+ # Interfaces with 1 implementation
110
+ grep -RnE '^(public |export )?interface I?[A-Z]\w+' src/ | while read iface; do
111
+ name=$(echo "$iface" | grep -oE '[A-Z]\w+\b' | head -1)
112
+ count=$(grep -RnE "class \w+\s*:\s*$name|implements $name" src/ | wc -l)
113
+ [[ $count -eq 1 ]] && echo "WARN: $iface has only 1 implementation"
114
+ done
115
+
116
+ # Deep inheritance (> 2 levels)
117
+ # (depends on stack — specific heuristic)
118
+
119
+ # Very large or nested functions
120
+ grep -cE '^\s{20,}\S' src/**/* # lines with 20+ spaces = deep nesting
121
+ ```
122
+
123
+ Match -> WARN with suggestion to simplify.
124
+
125
+ ## Inputs
126
+
127
+ - Diff/content of the file
128
+ - Context: codebase size (over-engineering is relative)
129
+
130
+ ## Outputs
131
+
132
+ Does NOT produce a file. Modifies judgement.
133
+
134
+ ## Examples
135
+
136
+ ### Example 1: Interface with 1 impl
137
+
138
+ Wrong:
139
+ ```typescript
140
+ interface ILogger { log(msg: string): void }
141
+ class ConsoleLogger implements ILogger { log(msg) { console.log(msg) } }
142
+ const logger: ILogger = new ConsoleLogger()
143
+ ```
144
+
145
+ Right (KISS):
146
+ ```typescript
147
+ function log(msg: string) { console.log(msg) }
148
+ // or
149
+ class Logger { static log(msg: string) { console.log(msg) } }
150
+ ```
151
+
152
+ Add interface when 2nd impl arrives, not before.
153
+
154
+ ### Example 2: Pass-through service
155
+
156
+ Wrong:
157
+ ```csharp
158
+ public class UserService {
159
+ public User GetById(int id) => _repo.GetById(id); // only calls repo
160
+ }
161
+ ```
162
+
163
+ Right: use `_repo` directly in the controller. Add Service when there is real logic (validation, multi-step, transaction, event).
164
+
165
+ ### Example 3: Hardcodable config
166
+
167
+ Wrong: `appsettings.json -> "MaxItemsPerPage": 50` that nobody ever changed in 2 years.
168
+
169
+ Right: `const MAX_ITEMS_PER_PAGE = 50` in the code. Move back to config if some client actually needs to customize.
@@ -0,0 +1,272 @@
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
+ triggers:
5
+ - "SOLID"
6
+ - "SOLID principles"
7
+ - "SRP OCP LSP ISP DIP"
8
+ - "OO design"
9
+ ---
10
+
11
+ # Skill: SOLID
12
+
13
+ 5 design principles — **S**ingle Responsibility, **O**pen/Closed, **L**iskov Substitution, **I**nterface Segregation, **D**ependency Inversion.
14
+
15
+ 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).
16
+
17
+ ## S - Single Responsibility Principle (SRP)
18
+
19
+ > A class/module must have **1 reason to change**.
20
+
21
+ **1 reason = 1 stakeholder or 1 axis of change**, not "1 action".
22
+
23
+ `UserService` that does auth + persistence + sends email violates — there are 3 reasons to change (security team changes auth, DBA changes persistence, marketing changes email).
24
+
25
+ ### Violation symptoms
26
+ - Class with generic name (`Manager`, `Helper`, `Service`, `Util`)
27
+ - Methods without thematic coherence
28
+ - Multiple imports of unrelated libraries (DB + email + crypto in the same class)
29
+ - Diff of one class affects separate domains in different sprints
30
+
31
+ ### Fix
32
+ Extract responsibilities into separate classes:
33
+ - `UserAuthenticator` (auth)
34
+ - `UserRepository` (persistence)
35
+ - `UserNotifier` (email)
36
+
37
+ Compose in a coordinator if needed, but each piece has 1 reason.
38
+
39
+ ## O - Open/Closed Principle (OCP)
40
+
41
+ > Open for **extension**, closed for **modification**.
42
+
43
+ Adding new behavior shouldn't require editing tested code. Use polymorphism, strategy, plugins, or config — not infinite if/else.
44
+
45
+ ### Violation symptoms
46
+ - `switch (type)` that grows with every new feature
47
+ - `if (provider === "x") ... else if ... else if ...`
48
+ - Each new feature edits N existing classes instead of adding 1 new one
49
+
50
+ ### Fix
51
+ - **Strategy pattern**: each case becomes an impl of an interface
52
+ - **Polymorphism**: subclass override instead of external switch
53
+ - **Registry**: `registry.register("x", handler)` — new feature just adds
54
+ - **Visitor**: for closed type hierarchies
55
+
56
+ ### When to ignore
57
+ OCP is aspirational, not absolute. Apply at points of **known** variation (payment strategies are multiple), don't speculate (KISS + YAGNI).
58
+
59
+ ## L - Liskov Substitution Principle (LSP)
60
+
61
+ > Subtype must be **substitutable** for the supertype without breaking behavior.
62
+
63
+ If `Bird` has method `fly()`, `Penguin extends Bird` violates LSP — penguin doesn't fly. Caller receiving `Bird` breaks when it receives `Penguin`.
64
+
65
+ ### Violation symptoms
66
+ - Subclass that **throws exception** on inherited method ("not supported")
67
+ - Subclass that **strengthens precondition** (`base accepts >= 0`, sub accepts `> 0`)
68
+ - Subclass that **weakens postcondition** (base guarantees "sorted", sub doesn't guarantee)
69
+ - Caller needs `if (instanceof Subtype)` to handle a special case
70
+
71
+ ### Fix
72
+ - Rethink hierarchy: `Penguin` isn't a flying `Bird`, it's a `Bird` that walks. Create `FlyingBird : Bird` and `Penguin : Bird` without fly.
73
+ - Composition over inheritance: prefer composed interfaces over deep hierarchies.
74
+ - Refactor to capabilities: `Flyable`, `Swimmable`, `Walkable` (overlap with ISP).
75
+
76
+ ## I - Interface Segregation Principle (ISP)
77
+
78
+ > Clients should not be forced to depend on interfaces they **do not use**.
79
+
80
+ Big interfaces ("fat interfaces") force impls to stub meaningless methods (throwing NotImplemented), and callers to import broad dependencies.
81
+
82
+ ### Violation symptoms
83
+ - Interface with 15 methods, each caller uses 2-3
84
+ - Impl with several methods throwing `throw new NotImplementedException()`
85
+ - Huge test mock to use 1 method of the interface
86
+
87
+ ### Fix
88
+ Split into smaller, cohesive interfaces:
89
+ ```
90
+ IFileReader { read() }
91
+ IFileWriter { write() }
92
+ // callers pick the subset they need
93
+ ```
94
+
95
+ In FP/Go, same principle: minimal parameters, structural typing doesn't force implementing everything.
96
+
97
+ ## D - Dependency Inversion Principle (DIP)
98
+
99
+ > High-level modules **do not** depend on low-level. Both depend on **abstractions**.
100
+
101
+ Business logic doesn't care about "PostgreSQL", "AWS S3", "SendGrid". It cares about abstractions (`UserRepository`, `BlobStorage`, `EmailSender`). Concrete implementations live in outer layers.
102
+
103
+ ### Violation symptoms
104
+ - Domain class imports `pg`, `aws-sdk`, `sendgrid`, `axios`
105
+ - Business logic testable only with real infra (DB up, S3 mocked, etc)
106
+ - Swapping provider requires rewriting business logic
107
+
108
+ ### Fix
109
+ - **Dependency injection** (constructor / property / parameter)
110
+ - Define interfaces in the domain module; impls live in infra/adapter layer (overlap with Hexagonal/Clean Architecture)
111
+ - Composition root injects the right impl
112
+
113
+ ### DIP != IoC container
114
+ DIP is a principle. IoC container is **one** tool. You can apply DIP with manual constructor, simple factory, or parameter injection — no framework.
115
+
116
+ ## Summary of the 5
117
+
118
+ | Letter | Focus | Key question |
119
+ |---|---|---|
120
+ | **S** | Unit cohesion | How many reasons to change this class/module? (>1 = violates) |
121
+ | **O** | Stability against extension | Does adding a new feature edit tested code or add new code? |
122
+ | **L** | Inheritance contract | Does replacing parent with child break any caller? |
123
+ | **I** | Interface size | Does every impl/caller use all methods? |
124
+ | **D** | Direction of dependency | Does domain import infra or does infra import domain? |
125
+
126
+ ## General anti-patterns
127
+
128
+ | Anti-pattern | Principle violated |
129
+ |---|---|
130
+ | `God class` with 30+ heterogeneous methods | SRP |
131
+ | `switch (kind)` in N callers, grows with each feature | OCP |
132
+ | Subclass with `throw NotSupportedException()` | LSP |
133
+ | `IRepository` with 20 generic methods | ISP |
134
+ | Domain service importing concrete ORM | DIP |
135
+ | Inheritance > 3 levels | LSP + SRP (usually) |
136
+ | Constructor with 8+ parameters | SRP (too many responsibilities) |
137
+ | Util class with 50 unrelated functions | SRP |
138
+
139
+ ## Procedure
140
+
141
+ ### Doer
142
+
143
+ Before creating class/module/interface:
144
+ - **SRP**: describe in 1 sentence. If you need "and", split.
145
+ - **ISP**: list expected callers. Does each one need **all** methods? If not, split.
146
+ - **DIP**: what does this depend on? Concretions (DB, HTTP, FS) -> inject as abstraction.
147
+
148
+ Before subclassing:
149
+ - **LSP**: does substitution by parent work in all callers? If not, wrong hierarchy.
150
+
151
+ Before adding `if/switch` at a growing point:
152
+ - **OCP**: is strategy/registry worth it?
153
+
154
+ ### Reviewer (gate 5)
155
+
156
+ ```bash
157
+ # SRP — very large classes
158
+ find src/ -name '*.cs' -o -name '*.ts' -o -name '*.py' | while read f; do
159
+ loc=$(wc -l < "$f")
160
+ [[ $loc -gt 400 ]] && echo "WARN SRP: $f has $loc lines, possible god class"
161
+ done
162
+
163
+ # OCP — big switches on hot paths
164
+ grep -RnE 'switch\s*\(' src/ | head
165
+ # (manually: evaluate if it grows with each feature)
166
+
167
+ # LSP — NotImplemented in subclass
168
+ grep -RnE 'NotImplemented|UnsupportedOperation|throw new.*not (implemented|supported)' src/
169
+
170
+ # ISP — giant interfaces
171
+ grep -RnA50 '^(public |export )?interface' src/ | grep -cE '^\s+\w+\s*\(' | sort -rn
172
+ # count > 10 methods -> WARN
173
+
174
+ # DIP — domain module importing concretions
175
+ grep -RnE 'from.*pg|from.*aws-sdk|using Npgsql|using AWSSDK' src/domain/ src/core/
176
+ ```
177
+
178
+ Relevant match -> WARN with principle cited.
179
+
180
+ ## Inputs
181
+
182
+ - Diff/content of the file
183
+ - Project structure (to detect domain vs infra)
184
+
185
+ ## Outputs
186
+
187
+ Does NOT produce a file. Modifies judgement.
188
+
189
+ ## Examples
190
+
191
+ ### Example 1: SRP violated
192
+
193
+ Wrong:
194
+ ```csharp
195
+ public class OrderService {
196
+ public Order Create(...) { /* validate + save + email + log + audit */ }
197
+ }
198
+ ```
199
+
200
+ 5 reasons to change.
201
+
202
+ Right:
203
+ ```csharp
204
+ public class OrderValidator { }
205
+ public class OrderRepository { }
206
+ public class OrderNotifier { }
207
+ public class OrderService {
208
+ public Order Create(...) {
209
+ _validator.Validate(...)
210
+ var order = _repo.Save(...)
211
+ _notifier.Notify(order)
212
+ return order;
213
+ }
214
+ }
215
+ ```
216
+
217
+ ### Example 2: OCP violated
218
+
219
+ Wrong:
220
+ ```typescript
221
+ function calcDiscount(type: string, amount: number) {
222
+ if (type === "vip") return amount * 0.2
223
+ else if (type === "newcomer") return amount * 0.1
224
+ else if (type === "blackfriday") return amount * 0.5
225
+ return 0
226
+ }
227
+ ```
228
+
229
+ Each new type edits this function.
230
+
231
+ Right:
232
+ ```typescript
233
+ const strategies: Record<string, (amount: number) => number> = {
234
+ vip: a => a * 0.2,
235
+ newcomer: a => a * 0.1,
236
+ blackfriday: a => a * 0.5,
237
+ }
238
+ function calcDiscount(type: string, amount: number) {
239
+ return strategies[type]?.(amount) ?? 0
240
+ }
241
+ ```
242
+
243
+ New type: register in `strategies`, don't touch `calcDiscount`.
244
+
245
+ ### Example 3: DIP violated
246
+
247
+ Wrong:
248
+ ```python
249
+ # domain/order.py
250
+ import psycopg2
251
+ class OrderService:
252
+ def get(self, id):
253
+ conn = psycopg2.connect(...)
254
+ ...
255
+ ```
256
+
257
+ Domain coupled to Postgres.
258
+
259
+ Right:
260
+ ```python
261
+ # domain/order.py
262
+ class OrderService:
263
+ def __init__(self, repo: OrderRepository): # abstraction
264
+ self._repo = repo
265
+ def get(self, id): return self._repo.get(id)
266
+
267
+ # infra/postgres_order_repo.py
268
+ class PostgresOrderRepository(OrderRepository):
269
+ def get(self, id): ... # concrete impl
270
+ ```
271
+
272
+ Domain doesn't know Postgres exists.