@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.
Files changed (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,447 @@
1
+ ---
2
+ paths:
3
+ - "**/*.ts"
4
+ - "**/*.tsx"
5
+ ---
6
+
7
+ # TypeScript Async Patterns
8
+
9
+ ## Promise Basics
10
+
11
+ ```typescript
12
+ // GOOD - explicit return type
13
+ async function fetchUser(id: string): Promise<User> {
14
+ const response = await fetch(`/api/users/${id}`);
15
+ if (!response.ok) {
16
+ throw new Error(`Failed to fetch user: ${response.status}`);
17
+ }
18
+ return response.json();
19
+ }
20
+
21
+ // GOOD - type the promise result
22
+ const userPromise: Promise<User> = fetchUser('123');
23
+
24
+ // BAD - untyped promise
25
+ const data = await fetch(url).then(r => r.json()); // any!
26
+
27
+ // GOOD - typed fetch
28
+ const data: User = await fetch(url).then(r => r.json() as Promise<User>);
29
+ ```
30
+
31
+ ## Error Handling
32
+
33
+ ```typescript
34
+ // GOOD - try/catch with typed error handling
35
+ async function fetchData<T>(url: string): Promise<T> {
36
+ try {
37
+ const response = await fetch(url);
38
+ if (!response.ok) {
39
+ throw new HttpError(response.status, response.statusText);
40
+ }
41
+ return await response.json();
42
+ } catch (error) {
43
+ if (error instanceof HttpError) {
44
+ // Handle HTTP errors specifically
45
+ throw error;
46
+ }
47
+ if (error instanceof TypeError) {
48
+ // Network error
49
+ throw new NetworkError('Network request failed');
50
+ }
51
+ throw error;
52
+ }
53
+ }
54
+
55
+ // Result pattern - avoid throwing for expected failures
56
+ type Result<T, E = Error> =
57
+ | { ok: true; value: T }
58
+ | { ok: false; error: E };
59
+
60
+ async function safeFetch<T>(url: string): Promise<Result<T>> {
61
+ try {
62
+ const response = await fetch(url);
63
+ if (!response.ok) {
64
+ return { ok: false, error: new Error(`HTTP ${response.status}`) };
65
+ }
66
+ const data = await response.json();
67
+ return { ok: true, value: data };
68
+ } catch (error) {
69
+ return { ok: false, error: error as Error };
70
+ }
71
+ }
72
+
73
+ // Usage
74
+ const result = await safeFetch<User>('/api/users/1');
75
+ if (result.ok) {
76
+ console.log(result.value.name);
77
+ } else {
78
+ console.error(result.error.message);
79
+ }
80
+ ```
81
+
82
+ ## Parallel Execution
83
+
84
+ ```typescript
85
+ // GOOD - parallel independent requests
86
+ async function fetchDashboard(userId: string): Promise<Dashboard> {
87
+ const [user, posts, notifications] = await Promise.all([
88
+ fetchUser(userId),
89
+ fetchPosts(userId),
90
+ fetchNotifications(userId),
91
+ ]);
92
+
93
+ return { user, posts, notifications };
94
+ }
95
+
96
+ // GOOD - parallel with error handling
97
+ async function fetchAllUsers(ids: string[]): Promise<(User | null)[]> {
98
+ const results = await Promise.allSettled(
99
+ ids.map(id => fetchUser(id))
100
+ );
101
+
102
+ return results.map(result =>
103
+ result.status === 'fulfilled' ? result.value : null
104
+ );
105
+ }
106
+
107
+ // BAD - sequential when parallel is possible
108
+ async function fetchDashboardSlow(userId: string) {
109
+ const user = await fetchUser(userId);
110
+ const posts = await fetchPosts(userId); // Waits for user
111
+ const notifications = await fetchNotifications(userId); // Waits for posts
112
+ return { user, posts, notifications };
113
+ }
114
+
115
+ // GOOD - controlled concurrency
116
+ async function fetchWithLimit<T>(
117
+ items: string[],
118
+ fetcher: (id: string) => Promise<T>,
119
+ limit = 5
120
+ ): Promise<T[]> {
121
+ const results: T[] = [];
122
+
123
+ for (let i = 0; i < items.length; i += limit) {
124
+ const batch = items.slice(i, i + limit);
125
+ const batchResults = await Promise.all(batch.map(fetcher));
126
+ results.push(...batchResults);
127
+ }
128
+
129
+ return results;
130
+ }
131
+ ```
132
+
133
+ ## AbortController & Cancellation
134
+
135
+ ```typescript
136
+ // GOOD - cancellable fetch
137
+ async function fetchWithTimeout<T>(
138
+ url: string,
139
+ timeoutMs: number
140
+ ): Promise<T> {
141
+ const controller = new AbortController();
142
+ const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
143
+
144
+ try {
145
+ const response = await fetch(url, { signal: controller.signal });
146
+ return await response.json();
147
+ } finally {
148
+ clearTimeout(timeoutId);
149
+ }
150
+ }
151
+
152
+ // GOOD - cancellable operation
153
+ async function searchUsers(
154
+ query: string,
155
+ signal?: AbortSignal
156
+ ): Promise<User[]> {
157
+ const response = await fetch(`/api/users?q=${query}`, { signal });
158
+
159
+ if (signal?.aborted) {
160
+ throw new DOMException('Aborted', 'AbortError');
161
+ }
162
+
163
+ return response.json();
164
+ }
165
+
166
+ // Usage in React
167
+ function useSearch(query: string) {
168
+ const [results, setResults] = useState<User[]>([]);
169
+
170
+ useEffect(() => {
171
+ const controller = new AbortController();
172
+
173
+ searchUsers(query, controller.signal)
174
+ .then(setResults)
175
+ .catch(error => {
176
+ if (error.name !== 'AbortError') {
177
+ console.error(error);
178
+ }
179
+ });
180
+
181
+ return () => controller.abort();
182
+ }, [query]);
183
+
184
+ return results;
185
+ }
186
+
187
+ // Cancellation token pattern
188
+ class CancellationToken {
189
+ private controller = new AbortController();
190
+
191
+ get signal(): AbortSignal {
192
+ return this.controller.signal;
193
+ }
194
+
195
+ get isCancelled(): boolean {
196
+ return this.controller.signal.aborted;
197
+ }
198
+
199
+ cancel(): void {
200
+ this.controller.abort();
201
+ }
202
+
203
+ throwIfCancelled(): void {
204
+ if (this.isCancelled) {
205
+ throw new DOMException('Cancelled', 'AbortError');
206
+ }
207
+ }
208
+ }
209
+ ```
210
+
211
+ ## Retry Logic
212
+
213
+ ```typescript
214
+ interface RetryOptions {
215
+ maxAttempts: number;
216
+ delayMs: number;
217
+ backoff?: 'linear' | 'exponential';
218
+ shouldRetry?: (error: Error) => boolean;
219
+ }
220
+
221
+ async function withRetry<T>(
222
+ fn: () => Promise<T>,
223
+ options: RetryOptions
224
+ ): Promise<T> {
225
+ const { maxAttempts, delayMs, backoff = 'exponential', shouldRetry } = options;
226
+
227
+ let lastError: Error;
228
+
229
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
230
+ try {
231
+ return await fn();
232
+ } catch (error) {
233
+ lastError = error as Error;
234
+
235
+ if (shouldRetry && !shouldRetry(lastError)) {
236
+ throw lastError;
237
+ }
238
+
239
+ if (attempt === maxAttempts) {
240
+ throw lastError;
241
+ }
242
+
243
+ const delay = backoff === 'exponential'
244
+ ? delayMs * Math.pow(2, attempt - 1)
245
+ : delayMs * attempt;
246
+
247
+ await sleep(delay);
248
+ }
249
+ }
250
+
251
+ throw lastError!;
252
+ }
253
+
254
+ // Usage
255
+ const user = await withRetry(
256
+ () => fetchUser('123'),
257
+ {
258
+ maxAttempts: 3,
259
+ delayMs: 1000,
260
+ backoff: 'exponential',
261
+ shouldRetry: (error) => error instanceof NetworkError,
262
+ }
263
+ );
264
+ ```
265
+
266
+ ## Debounce & Throttle
267
+
268
+ ```typescript
269
+ // Debounce - wait for pause in calls
270
+ function debounce<T extends (...args: any[]) => any>(
271
+ fn: T,
272
+ delayMs: number
273
+ ): (...args: Parameters<T>) => void {
274
+ let timeoutId: ReturnType<typeof setTimeout>;
275
+
276
+ return (...args: Parameters<T>) => {
277
+ clearTimeout(timeoutId);
278
+ timeoutId = setTimeout(() => fn(...args), delayMs);
279
+ };
280
+ }
281
+
282
+ // Debounce with promise
283
+ function debounceAsync<T extends (...args: any[]) => Promise<any>>(
284
+ fn: T,
285
+ delayMs: number
286
+ ): (...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>> {
287
+ let timeoutId: ReturnType<typeof setTimeout>;
288
+ let pending: Promise<any> | null = null;
289
+
290
+ return (...args: Parameters<T>) => {
291
+ clearTimeout(timeoutId);
292
+
293
+ pending = new Promise((resolve, reject) => {
294
+ timeoutId = setTimeout(async () => {
295
+ try {
296
+ resolve(await fn(...args));
297
+ } catch (error) {
298
+ reject(error);
299
+ }
300
+ }, delayMs);
301
+ });
302
+
303
+ return pending;
304
+ };
305
+ }
306
+
307
+ // Throttle - limit call frequency
308
+ function throttle<T extends (...args: any[]) => any>(
309
+ fn: T,
310
+ limitMs: number
311
+ ): (...args: Parameters<T>) => void {
312
+ let lastRun = 0;
313
+
314
+ return (...args: Parameters<T>) => {
315
+ const now = Date.now();
316
+ if (now - lastRun >= limitMs) {
317
+ lastRun = now;
318
+ fn(...args);
319
+ }
320
+ };
321
+ }
322
+ ```
323
+
324
+ ## Async Iteration
325
+
326
+ ```typescript
327
+ // Async generator
328
+ async function* fetchPages<T>(
329
+ baseUrl: string,
330
+ pageSize: number
331
+ ): AsyncGenerator<T[]> {
332
+ let page = 0;
333
+ let hasMore = true;
334
+
335
+ while (hasMore) {
336
+ const response = await fetch(`${baseUrl}?page=${page}&size=${pageSize}`);
337
+ const data = await response.json();
338
+
339
+ yield data.items;
340
+
341
+ hasMore = data.items.length === pageSize;
342
+ page++;
343
+ }
344
+ }
345
+
346
+ // Usage
347
+ for await (const users of fetchPages<User>('/api/users', 100)) {
348
+ console.log(`Fetched ${users.length} users`);
349
+ await processUsers(users);
350
+ }
351
+
352
+ // Async iterable with cancellation
353
+ async function* fetchWithCancellation<T>(
354
+ urls: string[],
355
+ signal?: AbortSignal
356
+ ): AsyncGenerator<T> {
357
+ for (const url of urls) {
358
+ signal?.throwIfAborted();
359
+
360
+ const response = await fetch(url, { signal });
361
+ yield await response.json();
362
+ }
363
+ }
364
+ ```
365
+
366
+ ## Queue Pattern
367
+
368
+ ```typescript
369
+ class AsyncQueue<T> {
370
+ private queue: (() => Promise<T>)[] = [];
371
+ private processing = false;
372
+ private concurrency: number;
373
+
374
+ constructor(concurrency = 1) {
375
+ this.concurrency = concurrency;
376
+ }
377
+
378
+ async add(task: () => Promise<T>): Promise<T> {
379
+ return new Promise((resolve, reject) => {
380
+ this.queue.push(async () => {
381
+ try {
382
+ resolve(await task());
383
+ } catch (error) {
384
+ reject(error);
385
+ }
386
+ });
387
+ this.process();
388
+ });
389
+ }
390
+
391
+ private async process(): Promise<void> {
392
+ if (this.processing) return;
393
+ this.processing = true;
394
+
395
+ while (this.queue.length > 0) {
396
+ const batch = this.queue.splice(0, this.concurrency);
397
+ await Promise.all(batch.map(task => task()));
398
+ }
399
+
400
+ this.processing = false;
401
+ }
402
+ }
403
+
404
+ // Usage
405
+ const queue = new AsyncQueue(3);
406
+
407
+ urls.forEach(url => {
408
+ queue.add(() => fetch(url).then(r => r.json()));
409
+ });
410
+ ```
411
+
412
+ ## Utilities
413
+
414
+ ```typescript
415
+ // Sleep
416
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
417
+
418
+ // Timeout wrapper
419
+ function withTimeout<T>(
420
+ promise: Promise<T>,
421
+ timeoutMs: number,
422
+ message = 'Operation timed out'
423
+ ): Promise<T> {
424
+ const timeout = new Promise<never>((_, reject) =>
425
+ setTimeout(() => reject(new Error(message)), timeoutMs)
426
+ );
427
+ return Promise.race([promise, timeout]);
428
+ }
429
+
430
+ // First successful
431
+ async function firstSuccess<T>(
432
+ promises: Promise<T>[]
433
+ ): Promise<T> {
434
+ const errors: Error[] = [];
435
+
436
+ return new Promise((resolve, reject) => {
437
+ promises.forEach(promise => {
438
+ promise.then(resolve).catch(error => {
439
+ errors.push(error);
440
+ if (errors.length === promises.length) {
441
+ reject(new AggregateError(errors, 'All promises failed'));
442
+ }
443
+ });
444
+ });
445
+ });
446
+ }
447
+ ```