@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,353 @@
1
+ ---
2
+ paths:
3
+ - "apps/**/app/**/*.tsx"
4
+ - "apps/**/app/**/*.ts"
5
+ - "app/**/*.tsx"
6
+ - "app/**/*.ts"
7
+ - "**/actions.ts"
8
+ - "**/actions/*.ts"
9
+ ---
10
+
11
+ # Next.js Server Actions
12
+
13
+ ## Basic Server Action
14
+
15
+ ```typescript
16
+ // app/actions.ts
17
+ 'use server';
18
+
19
+ import { revalidatePath } from 'next/cache';
20
+
21
+ export async function createPost(formData: FormData) {
22
+ const title = formData.get('title') as string;
23
+ const content = formData.get('content') as string;
24
+
25
+ await db.post.create({ data: { title, content } });
26
+
27
+ revalidatePath('/posts');
28
+ }
29
+ ```
30
+
31
+ ## With Validation (Zod)
32
+
33
+ ```typescript
34
+ // app/actions.ts
35
+ 'use server';
36
+
37
+ import { z } from 'zod';
38
+ import { revalidatePath } from 'next/cache';
39
+
40
+ const CreatePostSchema = z.object({
41
+ title: z.string().min(1).max(100),
42
+ content: z.string().min(1),
43
+ });
44
+
45
+ export async function createPost(formData: FormData) {
46
+ const parsed = CreatePostSchema.safeParse({
47
+ title: formData.get('title'),
48
+ content: formData.get('content'),
49
+ });
50
+
51
+ if (!parsed.success) {
52
+ return { error: parsed.error.flatten().fieldErrors };
53
+ }
54
+
55
+ await db.post.create({ data: parsed.data });
56
+ revalidatePath('/posts');
57
+
58
+ return { success: true };
59
+ }
60
+ ```
61
+
62
+ ## Return Type Pattern
63
+
64
+ ```typescript
65
+ // Type-safe action results
66
+ type ActionResult<T> =
67
+ | { success: true; data: T }
68
+ | { success: false; error: string; fieldErrors?: Record<string, string[]> };
69
+
70
+ export async function createUser(formData: FormData): Promise<ActionResult<User>> {
71
+ const parsed = UserSchema.safeParse(Object.fromEntries(formData));
72
+
73
+ if (!parsed.success) {
74
+ return {
75
+ success: false,
76
+ error: 'Validation failed',
77
+ fieldErrors: parsed.error.flatten().fieldErrors,
78
+ };
79
+ }
80
+
81
+ try {
82
+ const user = await db.user.create({ data: parsed.data });
83
+ revalidatePath('/users');
84
+ return { success: true, data: user };
85
+ } catch (error) {
86
+ return { success: false, error: 'Failed to create user' };
87
+ }
88
+ }
89
+ ```
90
+
91
+ ## useActionState (React 19)
92
+
93
+ ```tsx
94
+ 'use client';
95
+
96
+ import { useActionState } from 'react';
97
+ import { createPost } from './actions';
98
+
99
+ export function PostForm() {
100
+ const [state, formAction, isPending] = useActionState(createPost, null);
101
+
102
+ return (
103
+ <form action={formAction}>
104
+ <input name="title" disabled={isPending} />
105
+ {state?.fieldErrors?.title && (
106
+ <span className="error">{state.fieldErrors.title}</span>
107
+ )}
108
+
109
+ <textarea name="content" disabled={isPending} />
110
+ {state?.fieldErrors?.content && (
111
+ <span className="error">{state.fieldErrors.content}</span>
112
+ )}
113
+
114
+ <button type="submit" disabled={isPending}>
115
+ {isPending ? 'Creating...' : 'Create Post'}
116
+ </button>
117
+
118
+ {state?.error && <div className="error">{state.error}</div>}
119
+ </form>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## useOptimistic
125
+
126
+ ```tsx
127
+ 'use client';
128
+
129
+ import { useOptimistic } from 'react';
130
+ import { toggleLike } from './actions';
131
+
132
+ interface Post {
133
+ id: string;
134
+ likes: number;
135
+ isLiked: boolean;
136
+ }
137
+
138
+ export function LikeButton({ post }: { post: Post }) {
139
+ const [optimisticPost, addOptimistic] = useOptimistic(
140
+ post,
141
+ (state, newLiked: boolean) => ({
142
+ ...state,
143
+ isLiked: newLiked,
144
+ likes: newLiked ? state.likes + 1 : state.likes - 1,
145
+ })
146
+ );
147
+
148
+ async function handleClick() {
149
+ addOptimistic(!optimisticPost.isLiked);
150
+ await toggleLike(post.id);
151
+ }
152
+
153
+ return (
154
+ <button onClick={handleClick}>
155
+ {optimisticPost.isLiked ? '❤️' : '🤍'} {optimisticPost.likes}
156
+ </button>
157
+ );
158
+ }
159
+ ```
160
+
161
+ ## Revalidation Strategies
162
+
163
+ ```typescript
164
+ 'use server';
165
+
166
+ import { revalidatePath, revalidateTag } from 'next/cache';
167
+
168
+ export async function createPost(data: PostData) {
169
+ await db.post.create({ data });
170
+
171
+ // Revalidate specific path
172
+ revalidatePath('/posts');
173
+
174
+ // Revalidate dynamic path
175
+ revalidatePath(`/posts/${data.slug}`);
176
+
177
+ // Revalidate layout (all child pages)
178
+ revalidatePath('/posts', 'layout');
179
+
180
+ // Revalidate by cache tag
181
+ revalidateTag('posts');
182
+ }
183
+
184
+ // In data fetching, use tags
185
+ async function getPosts() {
186
+ return fetch('/api/posts', {
187
+ next: { tags: ['posts'] },
188
+ });
189
+ }
190
+ ```
191
+
192
+ ## Redirect After Action
193
+
194
+ ```typescript
195
+ 'use server';
196
+
197
+ import { redirect } from 'next/navigation';
198
+
199
+ export async function createPost(formData: FormData) {
200
+ const post = await db.post.create({
201
+ data: {
202
+ title: formData.get('title') as string,
203
+ content: formData.get('content') as string,
204
+ },
205
+ });
206
+
207
+ redirect(`/posts/${post.slug}`);
208
+ }
209
+ ```
210
+
211
+ ## With Authentication
212
+
213
+ ```typescript
214
+ 'use server';
215
+
216
+ import { auth } from '@/lib/auth';
217
+ import { redirect } from 'next/navigation';
218
+
219
+ export async function createPost(formData: FormData) {
220
+ const session = await auth();
221
+
222
+ if (!session?.user) {
223
+ redirect('/login');
224
+ }
225
+
226
+ await db.post.create({
227
+ data: {
228
+ title: formData.get('title') as string,
229
+ content: formData.get('content') as string,
230
+ authorId: session.user.id,
231
+ },
232
+ });
233
+
234
+ revalidatePath('/posts');
235
+ }
236
+ ```
237
+
238
+ ## File Upload
239
+
240
+ ```typescript
241
+ 'use server';
242
+
243
+ export async function uploadFile(formData: FormData) {
244
+ const file = formData.get('file') as File;
245
+
246
+ if (!file || file.size === 0) {
247
+ return { error: 'No file provided' };
248
+ }
249
+
250
+ // Validate file type
251
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
252
+ if (!allowedTypes.includes(file.type)) {
253
+ return { error: 'Invalid file type' };
254
+ }
255
+
256
+ // Validate file size (5MB)
257
+ if (file.size > 5 * 1024 * 1024) {
258
+ return { error: 'File too large' };
259
+ }
260
+
261
+ const bytes = await file.arrayBuffer();
262
+ const buffer = Buffer.from(bytes);
263
+
264
+ // Save to storage (S3, local, etc.)
265
+ const url = await uploadToStorage(buffer, file.name);
266
+
267
+ return { success: true, url };
268
+ }
269
+ ```
270
+
271
+ ## Progressive Enhancement
272
+
273
+ ```tsx
274
+ // Works without JavaScript (form submits normally)
275
+ // Enhanced with JavaScript (no page reload)
276
+
277
+ export function ContactForm() {
278
+ return (
279
+ <form action={sendMessage}>
280
+ <input name="email" type="email" required />
281
+ <textarea name="message" required />
282
+ <button type="submit">Send</button>
283
+ </form>
284
+ );
285
+ }
286
+ ```
287
+
288
+ ## Error Handling
289
+
290
+ ```typescript
291
+ 'use server';
292
+
293
+ export async function riskyAction(formData: FormData) {
294
+ try {
295
+ await someRiskyOperation();
296
+ return { success: true };
297
+ } catch (error) {
298
+ // Log server-side
299
+ console.error('Action failed:', error);
300
+
301
+ // Return safe error to client
302
+ if (error instanceof KnownError) {
303
+ return { success: false, error: error.message };
304
+ }
305
+
306
+ return { success: false, error: 'An unexpected error occurred' };
307
+ }
308
+ }
309
+ ```
310
+
311
+ ## Binding Arguments
312
+
313
+ ```tsx
314
+ // Pass additional data to action
315
+ import { updatePost } from './actions';
316
+
317
+ export function EditButton({ postId }: { postId: string }) {
318
+ const updateWithId = updatePost.bind(null, postId);
319
+
320
+ return (
321
+ <form action={updateWithId}>
322
+ <input name="title" />
323
+ <button type="submit">Update</button>
324
+ </form>
325
+ );
326
+ }
327
+
328
+ // actions.ts
329
+ 'use server';
330
+
331
+ export async function updatePost(postId: string, formData: FormData) {
332
+ const title = formData.get('title') as string;
333
+ await db.post.update({ where: { id: postId }, data: { title } });
334
+ }
335
+ ```
336
+
337
+ ## Non-Form Usage
338
+
339
+ ```tsx
340
+ 'use client';
341
+
342
+ import { deletePost } from './actions';
343
+
344
+ export function DeleteButton({ postId }: { postId: string }) {
345
+ async function handleDelete() {
346
+ if (confirm('Are you sure?')) {
347
+ await deletePost(postId);
348
+ }
349
+ }
350
+
351
+ return <button onClick={handleDelete}>Delete</button>;
352
+ }
353
+ ```
@@ -82,7 +82,7 @@ export const useUserStore = create<UserState>((set) => ({
82
82
 
83
83
  ```typescript
84
84
  // stores/app-store.ts
85
- import { create } from 'zustand';
85
+ import { create, StateCreator } from 'zustand';
86
86
 
87
87
  // User slice
88
88
  interface UserSlice {
@@ -90,7 +90,7 @@ interface UserSlice {
90
90
  setUser: (user: User | null) => void;
91
91
  }
92
92
 
93
- const createUserSlice = (set: any): UserSlice => ({
93
+ const createUserSlice: StateCreator<AppStore, [], [], UserSlice> = (set) => ({
94
94
  user: null,
95
95
  setUser: (user) => set({ user }),
96
96
  });
@@ -103,13 +103,13 @@ interface CartSlice {
103
103
  clearCart: () => void;
104
104
  }
105
105
 
106
- const createCartSlice = (set: any): CartSlice => ({
106
+ const createCartSlice: StateCreator<AppStore, [], [], CartSlice> = (set) => ({
107
107
  items: [],
108
- addItem: (item) => set((state: any) => ({
108
+ addItem: (item) => set((state) => ({
109
109
  items: [...state.items, item]
110
110
  })),
111
- removeItem: (id) => set((state: any) => ({
112
- items: state.items.filter((item: CartItem) => item.id !== id)
111
+ removeItem: (id) => set((state) => ({
112
+ items: state.items.filter((item) => item.id !== id)
113
113
  })),
114
114
  clearCart: () => set({ items: [] }),
115
115
  });
@@ -14,6 +14,7 @@
14
14
  "Bash(npm install *)",
15
15
  "Bash(npm ci)",
16
16
  "Bash(npx nx *)",
17
+ "Bash(npx prisma *)",
17
18
  "Read",
18
19
  "Edit",
19
20
  "Write"
@@ -25,5 +26,9 @@
25
26
  "Read(.env.*)",
26
27
  "Read(**/secrets/**)"
27
28
  ]
29
+ },
30
+ "env": {
31
+ "NODE_ENV": "development",
32
+ "NX_DAEMON": "true"
28
33
  }
29
34
  }