@malamute/ai-rules 1.0.0 → 1.3.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/README.md +272 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/_shared/rules/conventions/documentation.md +324 -0
- package/configs/_shared/rules/conventions/git.md +265 -0
- package/configs/_shared/rules/conventions/npm.md +80 -0
- package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
- package/configs/_shared/rules/conventions/principles.md +334 -0
- package/configs/_shared/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/rules/devops/docker.md +275 -0
- package/configs/_shared/rules/devops/nx.md +194 -0
- package/configs/_shared/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/rules/lang/python/async.md +337 -0
- package/configs/_shared/rules/lang/python/celery.md +476 -0
- package/configs/_shared/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/rules/lang/python/python.md +172 -0
- package/configs/_shared/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/rules/quality/error-handling.md +48 -0
- package/configs/_shared/rules/quality/logging.md +45 -0
- package/configs/_shared/rules/quality/observability.md +240 -0
- package/configs/_shared/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/rules/security/secrets-management.md +222 -0
- package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
- package/configs/angular/rules/core/resource.md +285 -0
- package/configs/angular/rules/core/signals.md +323 -0
- package/configs/angular/rules/http.md +338 -0
- package/configs/angular/rules/routing.md +291 -0
- package/configs/angular/rules/ssr.md +312 -0
- package/configs/angular/rules/state/signal-store.md +408 -0
- package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
- package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
- package/configs/angular/rules/ui/aria.md +422 -0
- package/configs/angular/rules/ui/forms.md +424 -0
- package/configs/angular/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/{.claude/settings.json → settings.json} +3 -0
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/dotnet/rules/background-services.md +552 -0
- package/configs/dotnet/rules/configuration.md +426 -0
- package/configs/dotnet/rules/ddd.md +447 -0
- package/configs/dotnet/rules/dependency-injection.md +343 -0
- package/configs/dotnet/rules/mediatr.md +320 -0
- package/configs/dotnet/rules/middleware.md +489 -0
- package/configs/dotnet/rules/result-pattern.md +363 -0
- package/configs/dotnet/rules/validation.md +388 -0
- package/configs/dotnet/settings.json +29 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/fastapi/rules/background-tasks.md +254 -0
- package/configs/fastapi/rules/dependencies.md +170 -0
- package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
- package/configs/fastapi/rules/lifespan.md +274 -0
- package/configs/fastapi/rules/middleware.md +229 -0
- package/configs/fastapi/rules/pydantic.md +433 -0
- package/configs/fastapi/rules/responses.md +251 -0
- package/configs/fastapi/rules/routers.md +202 -0
- package/configs/fastapi/rules/security.md +222 -0
- package/configs/fastapi/rules/testing.md +251 -0
- package/configs/fastapi/rules/websockets.md +298 -0
- package/configs/fastapi/settings.json +35 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/flask/rules/blueprints.md +208 -0
- package/configs/flask/rules/cli.md +285 -0
- package/configs/flask/rules/configuration.md +281 -0
- package/configs/flask/rules/context.md +238 -0
- package/configs/flask/rules/error-handlers.md +278 -0
- package/configs/flask/rules/extensions.md +278 -0
- package/configs/flask/rules/flask.md +171 -0
- package/configs/flask/rules/marshmallow.md +206 -0
- package/configs/flask/rules/security.md +267 -0
- package/configs/flask/rules/testing.md +284 -0
- package/configs/flask/settings.json +35 -0
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nestjs/rules/common-patterns.md +300 -0
- package/configs/nestjs/rules/filters.md +376 -0
- package/configs/nestjs/rules/interceptors.md +317 -0
- package/configs/nestjs/rules/middleware.md +321 -0
- package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
- package/configs/nestjs/rules/pipes.md +351 -0
- package/configs/nestjs/rules/websockets.md +451 -0
- package/configs/nestjs/settings.json +31 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/configs/nextjs/rules/api-routes.md +358 -0
- package/configs/nextjs/rules/authentication.md +355 -0
- package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
- package/configs/nextjs/rules/data-fetching.md +249 -0
- package/configs/nextjs/rules/database.md +400 -0
- package/configs/nextjs/rules/middleware.md +303 -0
- package/configs/nextjs/rules/routing.md +324 -0
- package/configs/nextjs/rules/seo.md +350 -0
- package/configs/nextjs/rules/server-actions.md +353 -0
- package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
- package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
- package/package.json +24 -9
- package/src/cli.js +218 -0
- package/src/config.js +63 -0
- package/src/index.js +4 -0
- package/src/installer.js +414 -0
- package/src/merge.js +109 -0
- package/src/tech-config.json +45 -0
- package/src/utils.js +88 -0
- package/configs/dotnet/.claude/settings.json +0 -9
- package/configs/nestjs/.claude/settings.json +0 -15
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
- /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
- /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
- /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
- /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
- /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Git Rules
|
|
7
|
+
|
|
8
|
+
## Commit Messages (Conventional Commits)
|
|
9
|
+
|
|
10
|
+
Format: `type(scope): description`
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Types
|
|
14
|
+
feat # New feature
|
|
15
|
+
fix # Bug fix
|
|
16
|
+
docs # Documentation only
|
|
17
|
+
style # Formatting, no code change
|
|
18
|
+
refactor # Code change, no feature/fix
|
|
19
|
+
perf # Performance improvement
|
|
20
|
+
test # Adding/updating tests
|
|
21
|
+
chore # Build, CI, dependencies
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Good examples
|
|
26
|
+
feat(auth): add OAuth2 login with Google
|
|
27
|
+
fix(cart): resolve race condition in checkout
|
|
28
|
+
refactor(api): simplify error handling middleware
|
|
29
|
+
perf(db): add index on users.email column
|
|
30
|
+
test(orders): add integration tests for payment flow
|
|
31
|
+
chore(deps): upgrade typescript to 5.3
|
|
32
|
+
|
|
33
|
+
# Bad examples
|
|
34
|
+
fix: bug fix # Too vague
|
|
35
|
+
updated stuff # No type, unclear
|
|
36
|
+
feat: Add new feature for users # Capitalized, vague
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Branch Naming
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Pattern: type/description or type/TICKET-description
|
|
43
|
+
feat/user-authentication
|
|
44
|
+
feat/JIRA-123-oauth-login
|
|
45
|
+
fix/cart-total-calculation
|
|
46
|
+
fix/BUG-456-null-pointer
|
|
47
|
+
refactor/api-error-handling
|
|
48
|
+
chore/upgrade-dependencies
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Workflow
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# Start new feature
|
|
55
|
+
git checkout main
|
|
56
|
+
git pull --rebase
|
|
57
|
+
git checkout -b feat/my-feature
|
|
58
|
+
|
|
59
|
+
# Regular commits during work
|
|
60
|
+
git add -p # Stage interactively
|
|
61
|
+
git commit -m "feat(scope): description"
|
|
62
|
+
|
|
63
|
+
# Before pushing - rebase on main
|
|
64
|
+
git fetch origin
|
|
65
|
+
git rebase origin/main
|
|
66
|
+
|
|
67
|
+
# Push (first time)
|
|
68
|
+
git push -u origin feat/my-feature
|
|
69
|
+
|
|
70
|
+
# Push (subsequent)
|
|
71
|
+
git push
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Rebase vs Merge
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# ALWAYS rebase local changes on remote
|
|
78
|
+
git pull --rebase origin main
|
|
79
|
+
|
|
80
|
+
# NEVER merge main into feature branch
|
|
81
|
+
git merge main # Creates ugly merge commits
|
|
82
|
+
|
|
83
|
+
# Interactive rebase to clean up commits before PR
|
|
84
|
+
git rebase -i HEAD~3
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Interactive Rebase
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# Clean up last 3 commits
|
|
91
|
+
git rebase -i HEAD~3
|
|
92
|
+
|
|
93
|
+
# In editor:
|
|
94
|
+
pick abc1234 feat(auth): add login endpoint
|
|
95
|
+
squash def5678 fix typo # Squash into previous
|
|
96
|
+
fixup ghi9012 more fixes # Squash, discard message
|
|
97
|
+
reword jkl3456 wip # Edit commit message
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Stashing
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Save work in progress
|
|
104
|
+
git stash push -m "WIP: feature description"
|
|
105
|
+
|
|
106
|
+
# List stashes
|
|
107
|
+
git stash list
|
|
108
|
+
|
|
109
|
+
# Apply and drop
|
|
110
|
+
git stash pop
|
|
111
|
+
|
|
112
|
+
# Apply specific stash
|
|
113
|
+
git stash apply stash@{2}
|
|
114
|
+
|
|
115
|
+
# Drop stash
|
|
116
|
+
git stash drop stash@{0}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Undoing Changes
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Unstage file (keep changes)
|
|
123
|
+
git restore --staged file.ts
|
|
124
|
+
|
|
125
|
+
# Discard local changes (DESTRUCTIVE)
|
|
126
|
+
git restore file.ts
|
|
127
|
+
|
|
128
|
+
# Undo last commit (keep changes staged)
|
|
129
|
+
git reset --soft HEAD~1
|
|
130
|
+
|
|
131
|
+
# Undo last commit (keep changes unstaged)
|
|
132
|
+
git reset HEAD~1
|
|
133
|
+
|
|
134
|
+
# Completely undo last commit (DESTRUCTIVE)
|
|
135
|
+
git reset --hard HEAD~1
|
|
136
|
+
|
|
137
|
+
# Create new commit that undoes previous
|
|
138
|
+
git revert abc1234
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Viewing History
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
# Compact log
|
|
145
|
+
git log --oneline -20
|
|
146
|
+
|
|
147
|
+
# With graph
|
|
148
|
+
git log --oneline --graph --all
|
|
149
|
+
|
|
150
|
+
# Changes in commit
|
|
151
|
+
git show abc1234
|
|
152
|
+
|
|
153
|
+
# Who changed this line
|
|
154
|
+
git blame file.ts
|
|
155
|
+
|
|
156
|
+
# Search commits by message
|
|
157
|
+
git log --grep="fix auth"
|
|
158
|
+
|
|
159
|
+
# Search commits by code change
|
|
160
|
+
git log -S "functionName" --oneline
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Cherry-Pick
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Apply specific commit to current branch
|
|
167
|
+
git cherry-pick abc1234
|
|
168
|
+
|
|
169
|
+
# Cherry-pick without committing
|
|
170
|
+
git cherry-pick --no-commit abc1234
|
|
171
|
+
|
|
172
|
+
# Cherry-pick range
|
|
173
|
+
git cherry-pick abc1234..def5678
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Tags
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Create annotated tag
|
|
180
|
+
git tag -a v1.2.0 -m "Release 1.2.0"
|
|
181
|
+
|
|
182
|
+
# Push tags
|
|
183
|
+
git push origin v1.2.0
|
|
184
|
+
git push origin --tags
|
|
185
|
+
|
|
186
|
+
# List tags
|
|
187
|
+
git tag -l "v1.*"
|
|
188
|
+
|
|
189
|
+
# Delete tag
|
|
190
|
+
git tag -d v1.2.0
|
|
191
|
+
git push origin :refs/tags/v1.2.0
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Hooks (Husky)
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# .husky/pre-commit
|
|
198
|
+
npm run lint-staged
|
|
199
|
+
|
|
200
|
+
# .husky/commit-msg
|
|
201
|
+
npx commitlint --edit $1
|
|
202
|
+
|
|
203
|
+
# .husky/pre-push
|
|
204
|
+
npm run test
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## .gitignore Essentials
|
|
208
|
+
|
|
209
|
+
```gitignore
|
|
210
|
+
# Dependencies
|
|
211
|
+
node_modules/
|
|
212
|
+
.venv/
|
|
213
|
+
vendor/
|
|
214
|
+
|
|
215
|
+
# Build
|
|
216
|
+
dist/
|
|
217
|
+
build/
|
|
218
|
+
*.dll
|
|
219
|
+
*.exe
|
|
220
|
+
|
|
221
|
+
# IDE
|
|
222
|
+
.idea/
|
|
223
|
+
.vscode/
|
|
224
|
+
*.swp
|
|
225
|
+
|
|
226
|
+
# Environment
|
|
227
|
+
.env
|
|
228
|
+
.env.local
|
|
229
|
+
*.local
|
|
230
|
+
|
|
231
|
+
# Secrets (NEVER commit)
|
|
232
|
+
*.pem
|
|
233
|
+
*.key
|
|
234
|
+
credentials.json
|
|
235
|
+
secrets.yaml
|
|
236
|
+
|
|
237
|
+
# OS
|
|
238
|
+
.DS_Store
|
|
239
|
+
Thumbs.db
|
|
240
|
+
|
|
241
|
+
# Logs
|
|
242
|
+
*.log
|
|
243
|
+
logs/
|
|
244
|
+
|
|
245
|
+
# Test
|
|
246
|
+
coverage/
|
|
247
|
+
.nyc_output/
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## PR Best Practices
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
# Keep PRs small and focused
|
|
254
|
+
# - One feature/fix per PR
|
|
255
|
+
# - <400 lines changed ideal
|
|
256
|
+
# - Split large changes into stacked PRs
|
|
257
|
+
|
|
258
|
+
# Before creating PR
|
|
259
|
+
git rebase -i origin/main # Clean history
|
|
260
|
+
npm run lint # Pass lint
|
|
261
|
+
npm run test # Pass tests
|
|
262
|
+
|
|
263
|
+
# PR title follows commit convention
|
|
264
|
+
feat(auth): add OAuth2 login with Google
|
|
265
|
+
```
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/package.json"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# npm Conventions
|
|
7
|
+
|
|
8
|
+
## Version Pinning
|
|
9
|
+
|
|
10
|
+
**Always use exact versions** - no `^` or `~` prefixes.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
// GOOD
|
|
14
|
+
{
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"express": "4.18.2",
|
|
17
|
+
"lodash": "4.17.21"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// BAD
|
|
22
|
+
{
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"express": "^4.18.2",
|
|
25
|
+
"lodash": "~4.17.21"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Why?
|
|
31
|
+
|
|
32
|
+
- **Reproducible builds** across environments
|
|
33
|
+
- **No surprise breaking changes** from minor/patch updates
|
|
34
|
+
- **Lock file is source of truth** but pinning adds defense in depth
|
|
35
|
+
- **Explicit upgrades** via `npm update` or renovate/dependabot
|
|
36
|
+
|
|
37
|
+
### Commands
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Install with exact version
|
|
41
|
+
npm install express --save-exact
|
|
42
|
+
|
|
43
|
+
# Configure npm to always save exact
|
|
44
|
+
npm config set save-exact true
|
|
45
|
+
|
|
46
|
+
# Or in .npmrc
|
|
47
|
+
save-exact=true
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Scripts
|
|
51
|
+
|
|
52
|
+
Use consistent script names:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"scripts": {
|
|
57
|
+
"dev": "...",
|
|
58
|
+
"build": "...",
|
|
59
|
+
"start": "...",
|
|
60
|
+
"test": "...",
|
|
61
|
+
"test:watch": "...",
|
|
62
|
+
"test:cov": "...",
|
|
63
|
+
"lint": "...",
|
|
64
|
+
"lint:fix": "...",
|
|
65
|
+
"format": "..."
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Engine Requirements
|
|
71
|
+
|
|
72
|
+
Specify Node.js version:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"engines": {
|
|
77
|
+
"node": ">=20.0.0"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
@@ -79,7 +79,7 @@ await cache.delete(`user:${userId}`);
|
|
|
79
79
|
### Memoization
|
|
80
80
|
```typescript
|
|
81
81
|
// Memoize pure functions
|
|
82
|
-
const memoize = <T>(fn: (...args:
|
|
82
|
+
const memoize = <T, Args extends unknown[]>(fn: (...args: Args) => T): ((...args: Args) => T) => {
|
|
83
83
|
const cache = new Map();
|
|
84
84
|
return (...args) => {
|
|
85
85
|
const key = JSON.stringify(args);
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/*.ts"
|
|
4
|
+
- "**/*.tsx"
|
|
5
|
+
- "**/*.js"
|
|
6
|
+
- "**/*.py"
|
|
7
|
+
- "**/*.cs"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Software Engineering Principles
|
|
11
|
+
|
|
12
|
+
## YAGNI - You Aren't Gonna Need It
|
|
13
|
+
|
|
14
|
+
Don't implement features until they're actually needed.
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// BAD - Building for hypothetical future
|
|
18
|
+
interface UserService {
|
|
19
|
+
getUser(id: string): User;
|
|
20
|
+
getUserWithCache(id: string, ttl?: number): User;
|
|
21
|
+
getUserAsync(id: string): Promise<User>;
|
|
22
|
+
getUserBatch(ids: string[]): User[];
|
|
23
|
+
getUserByEmail(email: string): User;
|
|
24
|
+
getUserByPhone(phone: string): User; // No one asked for this
|
|
25
|
+
getUserBySSN(ssn: string): User; // No one asked for this
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// GOOD - Only what's needed now
|
|
29
|
+
interface UserService {
|
|
30
|
+
getUser(id: string): Promise<User>;
|
|
31
|
+
getUserByEmail(email: string): Promise<User>;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// BAD - Premature abstraction
|
|
37
|
+
class AbstractDataProcessor<T, R, C extends Config> {
|
|
38
|
+
// 200 lines of "flexible" code no one uses
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// GOOD - Concrete implementation
|
|
42
|
+
function processUserData(users: User[]): ProcessedUser[] {
|
|
43
|
+
return users.map(u => ({ ...u, fullName: `${u.first} ${u.last}` }));
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## KISS - Keep It Simple, Stupid
|
|
48
|
+
|
|
49
|
+
Choose the simplest solution that works.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// BAD - Over-engineered
|
|
53
|
+
class StringUtils {
|
|
54
|
+
private static instance: StringUtils;
|
|
55
|
+
private constructor() {}
|
|
56
|
+
|
|
57
|
+
static getInstance(): StringUtils {
|
|
58
|
+
if (!this.instance) this.instance = new StringUtils();
|
|
59
|
+
return this.instance;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
capitalize(str: string): string {
|
|
63
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Usage: StringUtils.getInstance().capitalize('hello')
|
|
67
|
+
|
|
68
|
+
// GOOD - Simple function
|
|
69
|
+
function capitalize(str: string): string {
|
|
70
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
71
|
+
}
|
|
72
|
+
// Usage: capitalize('hello')
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// BAD - Unnecessary complexity
|
|
77
|
+
const isAdult = (age: number) =>
|
|
78
|
+
new AgeValidator(new AgePolicy(18)).validate(age).isValid();
|
|
79
|
+
|
|
80
|
+
// GOOD - Direct and clear
|
|
81
|
+
const isAdult = (age: number) => age >= 18;
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## SOLID Principles
|
|
85
|
+
|
|
86
|
+
### S - Single Responsibility
|
|
87
|
+
|
|
88
|
+
A class/function should have only one reason to change.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// BAD - Multiple responsibilities
|
|
92
|
+
class UserService {
|
|
93
|
+
createUser(data: UserData) { /* ... */ }
|
|
94
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
95
|
+
generatePDF(user: User) { /* ... */ }
|
|
96
|
+
validateCreditCard(card: Card) { /* ... */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// GOOD - Single responsibility each
|
|
100
|
+
class UserService {
|
|
101
|
+
createUser(data: UserData) { /* ... */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
class EmailService {
|
|
105
|
+
sendWelcomeEmail(user: User) { /* ... */ }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
class PDFService {
|
|
109
|
+
generateUserReport(user: User) { /* ... */ }
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### O - Open/Closed
|
|
114
|
+
|
|
115
|
+
Open for extension, closed for modification.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// BAD - Must modify to add new types
|
|
119
|
+
function calculateArea(shape: Shape) {
|
|
120
|
+
if (shape.type === 'circle') {
|
|
121
|
+
return Math.PI * shape.radius ** 2;
|
|
122
|
+
} else if (shape.type === 'rectangle') {
|
|
123
|
+
return shape.width * shape.height;
|
|
124
|
+
}
|
|
125
|
+
// Must add new else-if for each shape
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// GOOD - Extend without modifying
|
|
129
|
+
interface Shape {
|
|
130
|
+
area(): number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Circle implements Shape {
|
|
134
|
+
constructor(private radius: number) {}
|
|
135
|
+
area() { return Math.PI * this.radius ** 2; }
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
class Rectangle implements Shape {
|
|
139
|
+
constructor(private width: number, private height: number) {}
|
|
140
|
+
area() { return this.width * this.height; }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Add new shapes without changing existing code
|
|
144
|
+
class Triangle implements Shape {
|
|
145
|
+
constructor(private base: number, private height: number) {}
|
|
146
|
+
area() { return 0.5 * this.base * this.height; }
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### L - Liskov Substitution
|
|
151
|
+
|
|
152
|
+
Subtypes must be substitutable for their base types.
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// BAD - Violates LSP
|
|
156
|
+
class Bird {
|
|
157
|
+
fly() { /* ... */ }
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
class Penguin extends Bird {
|
|
161
|
+
fly() { throw new Error("Can't fly!"); } // Breaks substitutability
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// GOOD - Proper hierarchy
|
|
165
|
+
interface Bird {
|
|
166
|
+
move(): void;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
class FlyingBird implements Bird {
|
|
170
|
+
move() { this.fly(); }
|
|
171
|
+
private fly() { /* ... */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
class Penguin implements Bird {
|
|
175
|
+
move() { this.swim(); }
|
|
176
|
+
private swim() { /* ... */ }
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### I - Interface Segregation
|
|
181
|
+
|
|
182
|
+
Clients shouldn't depend on interfaces they don't use.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
// BAD - Fat interface
|
|
186
|
+
interface Worker {
|
|
187
|
+
work(): void;
|
|
188
|
+
eat(): void;
|
|
189
|
+
sleep(): void;
|
|
190
|
+
attendMeeting(): void;
|
|
191
|
+
writeReport(): void;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// GOOD - Segregated interfaces
|
|
195
|
+
interface Workable {
|
|
196
|
+
work(): void;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface Meetable {
|
|
200
|
+
attendMeeting(): void;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
interface Reportable {
|
|
204
|
+
writeReport(): void;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
class Developer implements Workable, Meetable {
|
|
208
|
+
work() { /* ... */ }
|
|
209
|
+
attendMeeting() { /* ... */ }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
class Robot implements Workable {
|
|
213
|
+
work() { /* ... */ }
|
|
214
|
+
// Doesn't need eat, sleep, or meetings
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### D - Dependency Inversion
|
|
219
|
+
|
|
220
|
+
Depend on abstractions, not concretions.
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
// BAD - Depends on concrete implementation
|
|
224
|
+
class OrderService {
|
|
225
|
+
private db = new MySQLDatabase();
|
|
226
|
+
private mailer = new SendGridMailer();
|
|
227
|
+
|
|
228
|
+
createOrder(order: Order) {
|
|
229
|
+
this.db.save(order);
|
|
230
|
+
this.mailer.send(order.userEmail, 'Order confirmed');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// GOOD - Depends on abstractions
|
|
235
|
+
class OrderService {
|
|
236
|
+
constructor(
|
|
237
|
+
private repository: OrderRepository,
|
|
238
|
+
private notifier: Notifier,
|
|
239
|
+
) {}
|
|
240
|
+
|
|
241
|
+
createOrder(order: Order) {
|
|
242
|
+
this.repository.save(order);
|
|
243
|
+
this.notifier.notify(order.userEmail, 'Order confirmed');
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Can inject any implementation
|
|
248
|
+
new OrderService(new PostgresOrderRepo(), new EmailNotifier());
|
|
249
|
+
new OrderService(new MongoOrderRepo(), new SMSNotifier());
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## SoC - Separation of Concerns
|
|
253
|
+
|
|
254
|
+
Separate code into distinct sections, each handling a specific concern.
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
// BAD - Mixed concerns
|
|
258
|
+
async function handleUserRegistration(req: Request, res: Response) {
|
|
259
|
+
// Validation
|
|
260
|
+
if (!req.body.email.includes('@')) {
|
|
261
|
+
return res.status(400).json({ error: 'Invalid email' });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Business logic
|
|
265
|
+
const hashedPassword = await bcrypt.hash(req.body.password, 10);
|
|
266
|
+
|
|
267
|
+
// Data access
|
|
268
|
+
const user = await db.query(
|
|
269
|
+
'INSERT INTO users (email, password) VALUES ($1, $2)',
|
|
270
|
+
[req.body.email, hashedPassword]
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// Presentation
|
|
274
|
+
return res.json({ id: user.id, email: user.email });
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// GOOD - Separated concerns
|
|
278
|
+
// validation.ts
|
|
279
|
+
const userSchema = z.object({
|
|
280
|
+
email: z.string().email(),
|
|
281
|
+
password: z.string().min(8),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// user.service.ts
|
|
285
|
+
class UserService {
|
|
286
|
+
async register(data: CreateUserDto): Promise<User> {
|
|
287
|
+
const hashedPassword = await this.hashPassword(data.password);
|
|
288
|
+
return this.userRepository.create({ ...data, password: hashedPassword });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// user.controller.ts
|
|
293
|
+
async function register(req: Request, res: Response) {
|
|
294
|
+
const data = userSchema.parse(req.body);
|
|
295
|
+
const user = await userService.register(data);
|
|
296
|
+
return res.json(toUserResponse(user));
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## DRY - Don't Repeat Yourself
|
|
301
|
+
|
|
302
|
+
But don't over-apply it. Duplication is better than the wrong abstraction.
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// BAD - Premature DRY (wrong abstraction)
|
|
306
|
+
function processEntity(entity: User | Product | Order, action: string) {
|
|
307
|
+
// 100 lines of if/else handling all cases
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// GOOD - Some duplication is OK when contexts differ
|
|
311
|
+
function processUser(user: User) { /* user-specific logic */ }
|
|
312
|
+
function processProduct(product: Product) { /* product-specific logic */ }
|
|
313
|
+
function processOrder(order: Order) { /* order-specific logic */ }
|
|
314
|
+
|
|
315
|
+
// GOOD - DRY when truly duplicated
|
|
316
|
+
function formatCurrency(amount: number): string {
|
|
317
|
+
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(amount);
|
|
318
|
+
}
|
|
319
|
+
// Use everywhere instead of repeating the formatting logic
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Summary
|
|
323
|
+
|
|
324
|
+
| Principle | Remember |
|
|
325
|
+
|-----------|----------|
|
|
326
|
+
| YAGNI | Build what you need now, not what you might need |
|
|
327
|
+
| KISS | Simple > Clever. If it's hard to explain, simplify it |
|
|
328
|
+
| SRP | One reason to change per class/function |
|
|
329
|
+
| OCP | Add new code, don't modify existing code |
|
|
330
|
+
| LSP | Subtypes must honor parent contracts |
|
|
331
|
+
| ISP | Small, focused interfaces > large, general ones |
|
|
332
|
+
| DIP | Inject dependencies, don't instantiate them |
|
|
333
|
+
| SoC | Validation, business logic, data access = separate |
|
|
334
|
+
| DRY | Avoid duplication, but not at the cost of clarity |
|