@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,337 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**/handlers/**/*.py"
|
|
4
|
+
- "**/services/**/*.py"
|
|
5
|
+
- "**/consumers/**/*.py"
|
|
6
|
+
- "**/workers/**/*.py"
|
|
7
|
+
- "**/*_async*.py"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Python Async Patterns
|
|
11
|
+
|
|
12
|
+
## Async Function Basics
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
import asyncio
|
|
16
|
+
from typing import AsyncIterator
|
|
17
|
+
|
|
18
|
+
# Always use async def for I/O operations
|
|
19
|
+
async def fetch_user(user_id: int) -> User:
|
|
20
|
+
return await db.users.get(user_id)
|
|
21
|
+
|
|
22
|
+
# Never mix sync I/O in async functions
|
|
23
|
+
# BAD
|
|
24
|
+
async def bad_fetch():
|
|
25
|
+
return requests.get(url) # Blocks event loop!
|
|
26
|
+
|
|
27
|
+
# GOOD
|
|
28
|
+
async def good_fetch():
|
|
29
|
+
async with httpx.AsyncClient() as client:
|
|
30
|
+
return await client.get(url)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Concurrent Execution
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import asyncio
|
|
37
|
+
|
|
38
|
+
# Run tasks concurrently
|
|
39
|
+
async def fetch_all_data():
|
|
40
|
+
# Wrong - sequential execution
|
|
41
|
+
users = await fetch_users()
|
|
42
|
+
orders = await fetch_orders()
|
|
43
|
+
products = await fetch_products()
|
|
44
|
+
|
|
45
|
+
# Right - concurrent execution
|
|
46
|
+
users, orders, products = await asyncio.gather(
|
|
47
|
+
fetch_users(),
|
|
48
|
+
fetch_orders(),
|
|
49
|
+
fetch_products(),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return users, orders, products
|
|
53
|
+
|
|
54
|
+
# With error handling
|
|
55
|
+
async def fetch_with_errors():
|
|
56
|
+
results = await asyncio.gather(
|
|
57
|
+
fetch_users(),
|
|
58
|
+
fetch_orders(),
|
|
59
|
+
fetch_products(),
|
|
60
|
+
return_exceptions=True, # Don't fail on first error
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
for result in results:
|
|
64
|
+
if isinstance(result, Exception):
|
|
65
|
+
logger.error(f"Task failed: {result}")
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## TaskGroup (Python 3.11+)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
async def process_items(items: list[Item]) -> list[Result]:
|
|
72
|
+
results = []
|
|
73
|
+
|
|
74
|
+
async with asyncio.TaskGroup() as tg:
|
|
75
|
+
for item in items:
|
|
76
|
+
tg.create_task(process_item(item))
|
|
77
|
+
|
|
78
|
+
# All tasks complete when exiting context
|
|
79
|
+
return results
|
|
80
|
+
|
|
81
|
+
# With exception handling
|
|
82
|
+
async def process_with_handling(items: list[Item]):
|
|
83
|
+
try:
|
|
84
|
+
async with asyncio.TaskGroup() as tg:
|
|
85
|
+
for item in items:
|
|
86
|
+
tg.create_task(process_item(item))
|
|
87
|
+
except* ValueError as eg:
|
|
88
|
+
for exc in eg.exceptions:
|
|
89
|
+
logger.error(f"Validation error: {exc}")
|
|
90
|
+
except* ConnectionError as eg:
|
|
91
|
+
for exc in eg.exceptions:
|
|
92
|
+
logger.error(f"Connection error: {exc}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Async Context Managers
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from contextlib import asynccontextmanager
|
|
99
|
+
|
|
100
|
+
@asynccontextmanager
|
|
101
|
+
async def get_db_connection():
|
|
102
|
+
conn = await create_connection()
|
|
103
|
+
try:
|
|
104
|
+
yield conn
|
|
105
|
+
finally:
|
|
106
|
+
await conn.close()
|
|
107
|
+
|
|
108
|
+
# Usage
|
|
109
|
+
async def query_users():
|
|
110
|
+
async with get_db_connection() as conn:
|
|
111
|
+
return await conn.fetch("SELECT * FROM users")
|
|
112
|
+
|
|
113
|
+
# Class-based context manager
|
|
114
|
+
class AsyncDatabaseSession:
|
|
115
|
+
async def __aenter__(self):
|
|
116
|
+
self.session = await create_session()
|
|
117
|
+
return self.session
|
|
118
|
+
|
|
119
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
120
|
+
if exc_type:
|
|
121
|
+
await self.session.rollback()
|
|
122
|
+
else:
|
|
123
|
+
await self.session.commit()
|
|
124
|
+
await self.session.close()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Async Iterators
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from typing import AsyncIterator
|
|
131
|
+
|
|
132
|
+
async def fetch_pages(url: str) -> AsyncIterator[Page]:
|
|
133
|
+
next_url = url
|
|
134
|
+
while next_url:
|
|
135
|
+
response = await fetch(next_url)
|
|
136
|
+
yield response.data
|
|
137
|
+
next_url = response.next_url
|
|
138
|
+
|
|
139
|
+
# Usage
|
|
140
|
+
async def process_all_pages():
|
|
141
|
+
async for page in fetch_pages("/api/items"):
|
|
142
|
+
for item in page.items:
|
|
143
|
+
await process_item(item)
|
|
144
|
+
|
|
145
|
+
# Async comprehension
|
|
146
|
+
async def get_all_items():
|
|
147
|
+
return [
|
|
148
|
+
item
|
|
149
|
+
async for page in fetch_pages("/api/items")
|
|
150
|
+
for item in page.items
|
|
151
|
+
]
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Semaphore for Rate Limiting
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import asyncio
|
|
158
|
+
|
|
159
|
+
async def fetch_with_limit(urls: list[str], max_concurrent: int = 10):
|
|
160
|
+
semaphore = asyncio.Semaphore(max_concurrent)
|
|
161
|
+
|
|
162
|
+
async def fetch_one(url: str):
|
|
163
|
+
async with semaphore:
|
|
164
|
+
async with httpx.AsyncClient() as client:
|
|
165
|
+
return await client.get(url)
|
|
166
|
+
|
|
167
|
+
return await asyncio.gather(*[fetch_one(url) for url in urls])
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Timeouts
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import asyncio
|
|
174
|
+
|
|
175
|
+
async def fetch_with_timeout(url: str, timeout: float = 10.0):
|
|
176
|
+
try:
|
|
177
|
+
async with asyncio.timeout(timeout):
|
|
178
|
+
return await fetch(url)
|
|
179
|
+
except asyncio.TimeoutError:
|
|
180
|
+
logger.error(f"Timeout fetching {url}")
|
|
181
|
+
raise
|
|
182
|
+
|
|
183
|
+
# Or with wait_for (older style)
|
|
184
|
+
async def fetch_with_wait_for(url: str):
|
|
185
|
+
try:
|
|
186
|
+
return await asyncio.wait_for(fetch(url), timeout=10.0)
|
|
187
|
+
except asyncio.TimeoutError:
|
|
188
|
+
raise
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Background Tasks
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
import asyncio
|
|
195
|
+
from collections.abc import Callable
|
|
196
|
+
|
|
197
|
+
class BackgroundTasks:
|
|
198
|
+
def __init__(self):
|
|
199
|
+
self._tasks: set[asyncio.Task] = set()
|
|
200
|
+
|
|
201
|
+
def add_task(self, coro):
|
|
202
|
+
task = asyncio.create_task(coro)
|
|
203
|
+
self._tasks.add(task)
|
|
204
|
+
task.add_done_callback(self._tasks.discard)
|
|
205
|
+
|
|
206
|
+
async def shutdown(self):
|
|
207
|
+
for task in self._tasks:
|
|
208
|
+
task.cancel()
|
|
209
|
+
await asyncio.gather(*self._tasks, return_exceptions=True)
|
|
210
|
+
|
|
211
|
+
# Usage in FastAPI
|
|
212
|
+
background = BackgroundTasks()
|
|
213
|
+
|
|
214
|
+
@app.post("/orders")
|
|
215
|
+
async def create_order(order: Order):
|
|
216
|
+
saved = await save_order(order)
|
|
217
|
+
background.add_task(send_notification(order))
|
|
218
|
+
return saved
|
|
219
|
+
|
|
220
|
+
@app.on_event("shutdown")
|
|
221
|
+
async def shutdown():
|
|
222
|
+
await background.shutdown()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## Async Queue
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
import asyncio
|
|
229
|
+
|
|
230
|
+
async def producer(queue: asyncio.Queue[int]):
|
|
231
|
+
for i in range(10):
|
|
232
|
+
await queue.put(i)
|
|
233
|
+
print(f"Produced: {i}")
|
|
234
|
+
await asyncio.sleep(0.1)
|
|
235
|
+
await queue.put(None) # Sentinel to stop
|
|
236
|
+
|
|
237
|
+
async def consumer(queue: asyncio.Queue[int]):
|
|
238
|
+
while True:
|
|
239
|
+
item = await queue.get()
|
|
240
|
+
if item is None:
|
|
241
|
+
break
|
|
242
|
+
print(f"Consumed: {item}")
|
|
243
|
+
queue.task_done()
|
|
244
|
+
|
|
245
|
+
async def main():
|
|
246
|
+
queue: asyncio.Queue[int] = asyncio.Queue(maxsize=5)
|
|
247
|
+
|
|
248
|
+
await asyncio.gather(
|
|
249
|
+
producer(queue),
|
|
250
|
+
consumer(queue),
|
|
251
|
+
)
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Async Lock
|
|
255
|
+
|
|
256
|
+
```python
|
|
257
|
+
import asyncio
|
|
258
|
+
|
|
259
|
+
class Counter:
|
|
260
|
+
def __init__(self):
|
|
261
|
+
self._value = 0
|
|
262
|
+
self._lock = asyncio.Lock()
|
|
263
|
+
|
|
264
|
+
async def increment(self):
|
|
265
|
+
async with self._lock:
|
|
266
|
+
self._value += 1
|
|
267
|
+
return self._value
|
|
268
|
+
|
|
269
|
+
async def get(self) -> int:
|
|
270
|
+
async with self._lock:
|
|
271
|
+
return self._value
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## SQLAlchemy Async
|
|
275
|
+
|
|
276
|
+
```python
|
|
277
|
+
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
278
|
+
from sqlalchemy.orm import sessionmaker
|
|
279
|
+
|
|
280
|
+
# Create async engine
|
|
281
|
+
engine = create_async_engine(
|
|
282
|
+
"postgresql+asyncpg://user:pass@localhost/db",
|
|
283
|
+
echo=True,
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Async session factory
|
|
287
|
+
async_session = sessionmaker(
|
|
288
|
+
engine,
|
|
289
|
+
class_=AsyncSession,
|
|
290
|
+
expire_on_commit=False,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Usage
|
|
294
|
+
async def get_user(user_id: int) -> User | None:
|
|
295
|
+
async with async_session() as session:
|
|
296
|
+
result = await session.execute(
|
|
297
|
+
select(User).where(User.id == user_id)
|
|
298
|
+
)
|
|
299
|
+
return result.scalar_one_or_none()
|
|
300
|
+
|
|
301
|
+
# FastAPI dependency
|
|
302
|
+
async def get_db() -> AsyncIterator[AsyncSession]:
|
|
303
|
+
async with async_session() as session:
|
|
304
|
+
try:
|
|
305
|
+
yield session
|
|
306
|
+
await session.commit()
|
|
307
|
+
except Exception:
|
|
308
|
+
await session.rollback()
|
|
309
|
+
raise
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## httpx Async Client
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
import httpx
|
|
316
|
+
|
|
317
|
+
# Reuse client for connection pooling
|
|
318
|
+
class ApiClient:
|
|
319
|
+
def __init__(self, base_url: str):
|
|
320
|
+
self._client = httpx.AsyncClient(
|
|
321
|
+
base_url=base_url,
|
|
322
|
+
timeout=30.0,
|
|
323
|
+
headers={"User-Agent": "MyApp/1.0"},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
async def get(self, path: str) -> dict:
|
|
327
|
+
response = await self._client.get(path)
|
|
328
|
+
response.raise_for_status()
|
|
329
|
+
return response.json()
|
|
330
|
+
|
|
331
|
+
async def close(self):
|
|
332
|
+
await self._client.aclose()
|
|
333
|
+
|
|
334
|
+
# Usage with context manager
|
|
335
|
+
async with httpx.AsyncClient() as client:
|
|
336
|
+
response = await client.get("https://api.example.com/data")
|
|
337
|
+
```
|