@malamute/ai-rules 1.0.0 → 1.2.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 +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/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/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- 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/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/exceptions/**/*.py"
|
|
4
|
+
- "**/errors/**/*.py"
|
|
5
|
+
- "**/handlers/**/*.py"
|
|
6
|
+
- "**/api/**/*.py"
|
|
7
|
+
- "**/routers/**/*.py"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Python Error Handling
|
|
11
|
+
|
|
12
|
+
## Custom Exception Hierarchy
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
# exceptions/base.py
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class AppError(Exception):
|
|
20
|
+
"""Base exception for application errors."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
message: str,
|
|
25
|
+
code: str = "APP_ERROR",
|
|
26
|
+
details: dict[str, Any] | None = None,
|
|
27
|
+
):
|
|
28
|
+
self.message = message
|
|
29
|
+
self.code = code
|
|
30
|
+
self.details = details or {}
|
|
31
|
+
super().__init__(message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ValidationError(AppError):
|
|
35
|
+
"""Input validation failed."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, message: str, field: str | None = None):
|
|
38
|
+
super().__init__(
|
|
39
|
+
message=message,
|
|
40
|
+
code="VALIDATION_ERROR",
|
|
41
|
+
details={"field": field} if field else {},
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NotFoundError(AppError):
|
|
46
|
+
"""Resource not found."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, resource: str, id: str | int):
|
|
49
|
+
super().__init__(
|
|
50
|
+
message=f"{resource} with id {id} not found",
|
|
51
|
+
code="NOT_FOUND",
|
|
52
|
+
details={"resource": resource, "id": str(id)},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ConflictError(AppError):
|
|
57
|
+
"""Resource already exists."""
|
|
58
|
+
|
|
59
|
+
def __init__(self, resource: str, field: str, value: str):
|
|
60
|
+
super().__init__(
|
|
61
|
+
message=f"{resource} with {field}={value} already exists",
|
|
62
|
+
code="CONFLICT",
|
|
63
|
+
details={"resource": resource, "field": field, "value": value},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class UnauthorizedError(AppError):
|
|
68
|
+
"""Authentication required."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, message: str = "Authentication required"):
|
|
71
|
+
super().__init__(message=message, code="UNAUTHORIZED")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class ForbiddenError(AppError):
|
|
75
|
+
"""Permission denied."""
|
|
76
|
+
|
|
77
|
+
def __init__(self, message: str = "Permission denied"):
|
|
78
|
+
super().__init__(message=message, code="FORBIDDEN")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class ExternalServiceError(AppError):
|
|
82
|
+
"""External service call failed."""
|
|
83
|
+
|
|
84
|
+
def __init__(self, service: str, message: str):
|
|
85
|
+
super().__init__(
|
|
86
|
+
message=f"{service}: {message}",
|
|
87
|
+
code="EXTERNAL_SERVICE_ERROR",
|
|
88
|
+
details={"service": service},
|
|
89
|
+
)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## FastAPI Exception Handlers
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# exceptions/handlers.py
|
|
96
|
+
from fastapi import FastAPI, Request
|
|
97
|
+
from fastapi.responses import JSONResponse
|
|
98
|
+
from pydantic import ValidationError as PydanticValidationError
|
|
99
|
+
import logging
|
|
100
|
+
|
|
101
|
+
from .base import (
|
|
102
|
+
AppError,
|
|
103
|
+
NotFoundError,
|
|
104
|
+
ValidationError,
|
|
105
|
+
UnauthorizedError,
|
|
106
|
+
ForbiddenError,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
logger = logging.getLogger(__name__)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def register_exception_handlers(app: FastAPI) -> None:
|
|
113
|
+
"""Register all exception handlers."""
|
|
114
|
+
|
|
115
|
+
@app.exception_handler(NotFoundError)
|
|
116
|
+
async def not_found_handler(request: Request, exc: NotFoundError):
|
|
117
|
+
return JSONResponse(
|
|
118
|
+
status_code=404,
|
|
119
|
+
content={
|
|
120
|
+
"error": exc.code,
|
|
121
|
+
"message": exc.message,
|
|
122
|
+
"details": exc.details,
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
@app.exception_handler(ValidationError)
|
|
127
|
+
async def validation_handler(request: Request, exc: ValidationError):
|
|
128
|
+
return JSONResponse(
|
|
129
|
+
status_code=400,
|
|
130
|
+
content={
|
|
131
|
+
"error": exc.code,
|
|
132
|
+
"message": exc.message,
|
|
133
|
+
"details": exc.details,
|
|
134
|
+
},
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@app.exception_handler(UnauthorizedError)
|
|
138
|
+
async def unauthorized_handler(request: Request, exc: UnauthorizedError):
|
|
139
|
+
return JSONResponse(
|
|
140
|
+
status_code=401,
|
|
141
|
+
content={"error": exc.code, "message": exc.message},
|
|
142
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@app.exception_handler(ForbiddenError)
|
|
146
|
+
async def forbidden_handler(request: Request, exc: ForbiddenError):
|
|
147
|
+
return JSONResponse(
|
|
148
|
+
status_code=403,
|
|
149
|
+
content={"error": exc.code, "message": exc.message},
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@app.exception_handler(AppError)
|
|
153
|
+
async def app_error_handler(request: Request, exc: AppError):
|
|
154
|
+
logger.error(f"Application error: {exc.code} - {exc.message}")
|
|
155
|
+
return JSONResponse(
|
|
156
|
+
status_code=422,
|
|
157
|
+
content={
|
|
158
|
+
"error": exc.code,
|
|
159
|
+
"message": exc.message,
|
|
160
|
+
"details": exc.details,
|
|
161
|
+
},
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
@app.exception_handler(PydanticValidationError)
|
|
165
|
+
async def pydantic_validation_handler(
|
|
166
|
+
request: Request, exc: PydanticValidationError
|
|
167
|
+
):
|
|
168
|
+
return JSONResponse(
|
|
169
|
+
status_code=422,
|
|
170
|
+
content={
|
|
171
|
+
"error": "VALIDATION_ERROR",
|
|
172
|
+
"message": "Request validation failed",
|
|
173
|
+
"details": exc.errors(),
|
|
174
|
+
},
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
@app.exception_handler(Exception)
|
|
178
|
+
async def unhandled_exception_handler(request: Request, exc: Exception):
|
|
179
|
+
logger.exception("Unhandled exception")
|
|
180
|
+
return JSONResponse(
|
|
181
|
+
status_code=500,
|
|
182
|
+
content={
|
|
183
|
+
"error": "INTERNAL_ERROR",
|
|
184
|
+
"message": "An unexpected error occurred",
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Error Response Schema
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# schemas/error.py
|
|
193
|
+
from pydantic import BaseModel
|
|
194
|
+
from typing import Any
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ErrorResponse(BaseModel):
|
|
198
|
+
"""Standard error response format."""
|
|
199
|
+
|
|
200
|
+
error: str
|
|
201
|
+
message: str
|
|
202
|
+
details: dict[str, Any] = {}
|
|
203
|
+
|
|
204
|
+
model_config = {
|
|
205
|
+
"json_schema_extra": {
|
|
206
|
+
"examples": [
|
|
207
|
+
{
|
|
208
|
+
"error": "NOT_FOUND",
|
|
209
|
+
"message": "User with id 123 not found",
|
|
210
|
+
"details": {"resource": "User", "id": "123"},
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Usage in Services
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# services/user.py
|
|
221
|
+
from exceptions import NotFoundError, ConflictError
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class UserService:
|
|
225
|
+
async def get_by_id(self, user_id: int) -> User:
|
|
226
|
+
user = await self.repository.get(user_id)
|
|
227
|
+
if not user:
|
|
228
|
+
raise NotFoundError("User", user_id)
|
|
229
|
+
return user
|
|
230
|
+
|
|
231
|
+
async def create(self, data: UserCreate) -> User:
|
|
232
|
+
existing = await self.repository.get_by_email(data.email)
|
|
233
|
+
if existing:
|
|
234
|
+
raise ConflictError("User", "email", data.email)
|
|
235
|
+
return await self.repository.create(data)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Result Pattern (Alternative)
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
# core/result.py
|
|
242
|
+
from dataclasses import dataclass
|
|
243
|
+
from typing import Generic, TypeVar
|
|
244
|
+
|
|
245
|
+
T = TypeVar("T")
|
|
246
|
+
E = TypeVar("E", bound=Exception)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@dataclass
|
|
250
|
+
class Ok(Generic[T]):
|
|
251
|
+
value: T
|
|
252
|
+
|
|
253
|
+
def is_ok(self) -> bool:
|
|
254
|
+
return True
|
|
255
|
+
|
|
256
|
+
def is_err(self) -> bool:
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dataclass
|
|
261
|
+
class Err(Generic[E]):
|
|
262
|
+
error: E
|
|
263
|
+
|
|
264
|
+
def is_ok(self) -> bool:
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
def is_err(self) -> bool:
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
Result = Ok[T] | Err[E]
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
# Usage
|
|
275
|
+
async def get_user(user_id: int) -> Result[User, NotFoundError]:
|
|
276
|
+
user = await repository.get(user_id)
|
|
277
|
+
if not user:
|
|
278
|
+
return Err(NotFoundError("User", user_id))
|
|
279
|
+
return Ok(user)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
# Handling
|
|
283
|
+
result = await get_user(123)
|
|
284
|
+
match result:
|
|
285
|
+
case Ok(user):
|
|
286
|
+
return user
|
|
287
|
+
case Err(error):
|
|
288
|
+
raise error
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Anti-Patterns
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
# BAD: Catching all exceptions
|
|
295
|
+
try:
|
|
296
|
+
user = await get_user(user_id)
|
|
297
|
+
except Exception:
|
|
298
|
+
return None # Swallows all errors!
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
# GOOD: Catch specific exceptions
|
|
302
|
+
try:
|
|
303
|
+
user = await get_user(user_id)
|
|
304
|
+
except NotFoundError:
|
|
305
|
+
return None
|
|
306
|
+
except Exception:
|
|
307
|
+
logger.exception("Unexpected error getting user")
|
|
308
|
+
raise
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
# BAD: Raising generic exceptions
|
|
312
|
+
if not user:
|
|
313
|
+
raise Exception("User not found")
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
# GOOD: Raise typed exceptions
|
|
317
|
+
if not user:
|
|
318
|
+
raise NotFoundError("User", user_id)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
# BAD: Exposing internal errors to clients
|
|
322
|
+
except Exception as e:
|
|
323
|
+
return {"error": str(e)} # Leaks implementation details!
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
# GOOD: Map to safe error responses
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.exception("Internal error")
|
|
329
|
+
return {"error": "INTERNAL_ERROR", "message": "An unexpected error occurred"}
|
|
330
|
+
```
|