@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,424 @@
1
+ ---
2
+ paths:
3
+ - "**/*.component.ts"
4
+ - "**/*.component.html"
5
+ - "**/forms/**/*.ts"
6
+ - "**/*-form.component.ts"
7
+ - "**/*-form/**/*.ts"
8
+ ---
9
+
10
+ # Angular Signal-Based Forms
11
+
12
+ Use signals for reactive form state. These patterns leverage `signal()`, `computed()`, and `linkedSignal()` for form management.
13
+
14
+ ## Basic Signal Form
15
+
16
+ ```typescript
17
+ import { Component, signal, computed } from '@angular/core';
18
+ import { FormsModule } from '@angular/forms';
19
+
20
+ @Component({
21
+ selector: 'app-login',
22
+ imports: [FormsModule],
23
+ template: `
24
+ <form (ngSubmit)="onSubmit()">
25
+ <input
26
+ type="email"
27
+ [ngModel]="email()"
28
+ (ngModelChange)="email.set($event)"
29
+ placeholder="Email"
30
+ />
31
+ @if (emailError()) {
32
+ <span class="error">{{ emailError() }}</span>
33
+ }
34
+
35
+ <input
36
+ type="password"
37
+ [ngModel]="password()"
38
+ (ngModelChange)="password.set($event)"
39
+ placeholder="Password"
40
+ />
41
+ @if (passwordError()) {
42
+ <span class="error">{{ passwordError() }}</span>
43
+ }
44
+
45
+ <button type="submit" [disabled]="!isValid()">Login</button>
46
+ </form>
47
+ `,
48
+ })
49
+ export class LoginComponent {
50
+ // Form state as signals
51
+ protected readonly email = signal('');
52
+ protected readonly password = signal('');
53
+
54
+ // Computed validations
55
+ protected readonly emailError = computed(() => {
56
+ const value = this.email();
57
+ if (!value) return 'Email is required';
58
+ if (!value.includes('@')) return 'Invalid email format';
59
+ return null;
60
+ });
61
+
62
+ protected readonly passwordError = computed(() => {
63
+ const value = this.password();
64
+ if (!value) return 'Password is required';
65
+ if (value.length < 8) return 'Password must be at least 8 characters';
66
+ return null;
67
+ });
68
+
69
+ protected readonly isValid = computed(() => !this.emailError() && !this.passwordError());
70
+
71
+ public onSubmit(): void {
72
+ if (this.isValid()) {
73
+ console.log({ email: this.email(), password: this.password() });
74
+ }
75
+ }
76
+ }
77
+ ```
78
+
79
+ ## Form with model() for Two-Way Binding
80
+
81
+ ```typescript
82
+ import { Component, model, computed } from '@angular/core';
83
+
84
+ @Component({
85
+ selector: 'app-user-form',
86
+ template: `
87
+ <input [(ngModel)]="name" placeholder="Name" />
88
+ <input [(ngModel)]="email" placeholder="Email" />
89
+
90
+ <p>Preview: {{ fullInfo() }}</p>
91
+ `,
92
+ })
93
+ export class UserFormComponent {
94
+ // Two-way bindable signals
95
+ public readonly name = model('');
96
+ public readonly email = model('');
97
+
98
+ protected readonly fullInfo = computed(() => `${this.name()} <${this.email()}>`);
99
+ }
100
+
101
+ // Parent usage
102
+ // <app-user-form [(name)]="userName" [(email)]="userEmail" />
103
+ ```
104
+
105
+ ## Complex Form with Nested Objects
106
+
107
+ ```typescript
108
+ interface Address {
109
+ street: string;
110
+ city: string;
111
+ zipCode: string;
112
+ }
113
+
114
+ interface UserForm {
115
+ name: string;
116
+ email: string;
117
+ address: Address;
118
+ }
119
+
120
+ @Component({
121
+ selector: 'app-user-form',
122
+ template: `
123
+ <input [ngModel]="form().name" (ngModelChange)="updateField('name', $event)" />
124
+ <input [ngModel]="form().email" (ngModelChange)="updateField('email', $event)" />
125
+
126
+ <fieldset>
127
+ <legend>Address</legend>
128
+ <input
129
+ [ngModel]="form().address.street"
130
+ (ngModelChange)="updateAddress('street', $event)"
131
+ />
132
+ <input
133
+ [ngModel]="form().address.city"
134
+ (ngModelChange)="updateAddress('city', $event)"
135
+ />
136
+ <input
137
+ [ngModel]="form().address.zipCode"
138
+ (ngModelChange)="updateAddress('zipCode', $event)"
139
+ />
140
+ </fieldset>
141
+ `,
142
+ })
143
+ export class UserFormComponent {
144
+ protected readonly form = signal<UserForm>({
145
+ name: '',
146
+ email: '',
147
+ address: { street: '', city: '', zipCode: '' },
148
+ });
149
+
150
+ protected updateField<K extends keyof UserForm>(field: K, value: UserForm[K]): void {
151
+ this.form.update((f) => ({ ...f, [field]: value }));
152
+ }
153
+
154
+ protected updateAddress<K extends keyof Address>(field: K, value: string): void {
155
+ this.form.update((f) => ({
156
+ ...f,
157
+ address: { ...f.address, [field]: value },
158
+ }));
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## Form Array (Dynamic Fields)
164
+
165
+ ```typescript
166
+ interface TodoItem {
167
+ id: string;
168
+ text: string;
169
+ completed: boolean;
170
+ }
171
+
172
+ @Component({
173
+ selector: 'app-todo-form',
174
+ template: `
175
+ @for (item of items(); track item.id) {
176
+ <div class="todo-item">
177
+ <input
178
+ type="checkbox"
179
+ [checked]="item.completed"
180
+ (change)="toggleComplete(item.id)"
181
+ />
182
+ <input
183
+ [value]="item.text"
184
+ (input)="updateText(item.id, $event)"
185
+ />
186
+ <button (click)="removeItem(item.id)">Remove</button>
187
+ </div>
188
+ }
189
+
190
+ <button (click)="addItem()">Add Item</button>
191
+ `,
192
+ })
193
+ export class TodoFormComponent {
194
+ protected readonly items = signal<TodoItem[]>([]);
195
+
196
+ protected addItem(): void {
197
+ this.items.update((items) => [
198
+ ...items,
199
+ { id: crypto.randomUUID(), text: '', completed: false },
200
+ ]);
201
+ }
202
+
203
+ protected removeItem(id: string): void {
204
+ this.items.update((items) => items.filter((i) => i.id !== id));
205
+ }
206
+
207
+ protected updateText(id: string, event: Event): void {
208
+ const value = (event.target as HTMLInputElement).value;
209
+ this.items.update((items) =>
210
+ items.map((i) => (i.id === id ? { ...i, text: value } : i))
211
+ );
212
+ }
213
+
214
+ protected toggleComplete(id: string): void {
215
+ this.items.update((items) =>
216
+ items.map((i) => (i.id === id ? { ...i, completed: !i.completed } : i))
217
+ );
218
+ }
219
+ }
220
+ ```
221
+
222
+ ## Async Validation
223
+
224
+ ```typescript
225
+ @Component({
226
+ selector: 'app-signup',
227
+ template: `
228
+ <input
229
+ [ngModel]="username()"
230
+ (ngModelChange)="onUsernameChange($event)"
231
+ />
232
+ @if (isCheckingUsername()) {
233
+ <span>Checking...</span>
234
+ }
235
+ @if (usernameError()) {
236
+ <span class="error">{{ usernameError() }}</span>
237
+ }
238
+ @if (usernameAvailable()) {
239
+ <span class="success">Username available!</span>
240
+ }
241
+ `,
242
+ })
243
+ export class SignupComponent {
244
+ private readonly userService = inject(UserService);
245
+ private readonly destroyRef = inject(DestroyRef);
246
+
247
+ protected readonly username = signal('');
248
+ protected readonly isCheckingUsername = signal(false);
249
+ protected readonly usernameAvailable = signal<boolean | null>(null);
250
+
251
+ protected readonly usernameError = computed(() => {
252
+ const value = this.username();
253
+ if (!value) return 'Username is required';
254
+ if (value.length < 3) return 'Username must be at least 3 characters';
255
+ if (this.usernameAvailable() === false) return 'Username is taken';
256
+ return null;
257
+ });
258
+
259
+ protected onUsernameChange(value: string): void {
260
+ this.username.set(value);
261
+ this.usernameAvailable.set(null);
262
+
263
+ if (value.length >= 3) {
264
+ this.checkUsernameAvailability(value);
265
+ }
266
+ }
267
+
268
+ private checkUsernameAvailability(username: string): void {
269
+ this.isCheckingUsername.set(true);
270
+
271
+ this.userService
272
+ .checkUsername(username)
273
+ .pipe(
274
+ debounceTime(300),
275
+ takeUntilDestroyed(this.destroyRef),
276
+ )
277
+ .subscribe({
278
+ next: (available) => {
279
+ this.usernameAvailable.set(available);
280
+ this.isCheckingUsername.set(false);
281
+ },
282
+ error: () => {
283
+ this.isCheckingUsername.set(false);
284
+ },
285
+ });
286
+ }
287
+ }
288
+ ```
289
+
290
+ ## Form Submission with Loading State
291
+
292
+ ```typescript
293
+ @Component({
294
+ selector: 'app-contact-form',
295
+ template: `
296
+ <form (ngSubmit)="onSubmit()">
297
+ <input [ngModel]="name()" (ngModelChange)="name.set($event)" />
298
+ <textarea [ngModel]="message()" (ngModelChange)="message.set($event)"></textarea>
299
+
300
+ @if (submitError()) {
301
+ <div class="error">{{ submitError() }}</div>
302
+ }
303
+
304
+ <button type="submit" [disabled]="isSubmitting() || !isValid()">
305
+ @if (isSubmitting()) {
306
+ Sending...
307
+ } @else {
308
+ Send Message
309
+ }
310
+ </button>
311
+ </form>
312
+ `,
313
+ })
314
+ export class ContactFormComponent {
315
+ private readonly contactService = inject(ContactService);
316
+
317
+ protected readonly name = signal('');
318
+ protected readonly message = signal('');
319
+ protected readonly isSubmitting = signal(false);
320
+ protected readonly submitError = signal<string | null>(null);
321
+
322
+ protected readonly isValid = computed(() => this.name().length > 0 && this.message().length > 0);
323
+
324
+ public async onSubmit(): Promise<void> {
325
+ if (!this.isValid() || this.isSubmitting()) return;
326
+
327
+ this.isSubmitting.set(true);
328
+ this.submitError.set(null);
329
+
330
+ try {
331
+ await this.contactService.send({
332
+ name: this.name(),
333
+ message: this.message(),
334
+ });
335
+
336
+ // Reset form
337
+ this.name.set('');
338
+ this.message.set('');
339
+ } catch (error) {
340
+ this.submitError.set('Failed to send message. Please try again.');
341
+ } finally {
342
+ this.isSubmitting.set(false);
343
+ }
344
+ }
345
+ }
346
+ ```
347
+
348
+ ## Form Reset with linkedSignal
349
+
350
+ Use `linkedSignal` when form values should reset based on external state:
351
+
352
+ ```typescript
353
+ @Component({
354
+ selector: 'app-user-editor',
355
+ template: `
356
+ <input [ngModel]="email()" (ngModelChange)="email.set($event)" />
357
+ <button (click)="save()">Save</button>
358
+ `,
359
+ })
360
+ export class UserEditorComponent {
361
+ // When selectedUser changes, email resets to user's email
362
+ // But user can still edit it manually
363
+ public readonly selectedUser = input.required<User>();
364
+
365
+ protected readonly email = linkedSignal(() => this.selectedUser().email);
366
+
367
+ protected save(): void {
368
+ // email() contains the current (possibly edited) value
369
+ console.log('Saving:', this.email());
370
+ }
371
+ }
372
+ ```
373
+
374
+ ## When to Use Reactive Forms Instead
375
+
376
+ Use traditional `FormGroup`/`FormControl` when you need:
377
+ - Complex cross-field validation
378
+ - Dynamic form generation from schema
379
+ - Integration with third-party form libraries
380
+
381
+ ```typescript
382
+ // For complex forms, ReactiveFormsModule is still valid
383
+ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
384
+
385
+ // But prefer signal-based forms for new, simpler forms
386
+ ```
387
+
388
+ ## Anti-patterns
389
+
390
+ ```typescript
391
+ // BAD: Using BehaviorSubject for form state
392
+ private readonly email$ = new BehaviorSubject('');
393
+
394
+ // GOOD: Use signals
395
+ protected readonly email = signal('');
396
+
397
+
398
+ // BAD: Manual subscription for validation
399
+ ngOnInit() {
400
+ this.email$.subscribe(value => {
401
+ this.emailError = this.validateEmail(value);
402
+ });
403
+ }
404
+
405
+ // GOOD: Use computed
406
+ protected readonly emailError = computed(() => this.validateEmail(this.email()));
407
+
408
+
409
+ // BAD: Forgetting to handle loading state
410
+ public onSubmit(): void {
411
+ this.service.save(this.form()).subscribe(); // No loading indicator!
412
+ }
413
+
414
+ // GOOD: Track submission state
415
+ protected readonly isSubmitting = signal(false);
416
+ public async onSubmit(): Promise<void> {
417
+ this.isSubmitting.set(true);
418
+ try {
419
+ await firstValueFrom(this.service.save(this.form()));
420
+ } finally {
421
+ this.isSubmitting.set(false);
422
+ }
423
+ }
424
+ ```