@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,422 @@
1
+ ---
2
+ paths:
3
+ - "**/ui/**/*.component.ts"
4
+ - "**/ui/**/*.component.html"
5
+ - "**/shared/ui/**/*.ts"
6
+ - "**/*-dialog.component.ts"
7
+ - "**/*-modal.component.ts"
8
+ ---
9
+
10
+ # Angular ARIA (Accessibility)
11
+
12
+ The `@angular/cdk-experimental/ui` package provides headless, accessible components.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @angular/cdk-experimental
18
+ ```
19
+
20
+ ## Listbox
21
+
22
+ ```typescript
23
+ import { CdkListbox, CdkOption } from '@angular/cdk-experimental/ui';
24
+
25
+ @Component({
26
+ selector: 'app-color-picker',
27
+ imports: [CdkListbox, CdkOption],
28
+ template: `
29
+ <ul cdkListbox [(value)]="selectedColor" aria-label="Choose a color">
30
+ @for (color of colors; track color.value) {
31
+ <li [cdkOption]="color.value">
32
+ {{ color.label }}
33
+ </li>
34
+ }
35
+ </ul>
36
+ `,
37
+ })
38
+ export class ColorPickerComponent {
39
+ protected readonly colors = [
40
+ { value: 'red', label: 'Red' },
41
+ { value: 'green', label: 'Green' },
42
+ { value: 'blue', label: 'Blue' },
43
+ ];
44
+
45
+ protected readonly selectedColor = model<string>('red');
46
+ }
47
+ ```
48
+
49
+ ## Tabs
50
+
51
+ ```typescript
52
+ import { CdkTabs, CdkTabList, CdkTab, CdkTabPanel } from '@angular/cdk-experimental/ui';
53
+
54
+ @Component({
55
+ selector: 'app-settings-tabs',
56
+ imports: [CdkTabs, CdkTabList, CdkTab, CdkTabPanel],
57
+ template: `
58
+ <div cdkTabs>
59
+ <div cdkTabList aria-label="Settings sections">
60
+ <button cdkTab="profile">Profile</button>
61
+ <button cdkTab="security">Security</button>
62
+ <button cdkTab="notifications">Notifications</button>
63
+ </div>
64
+
65
+ <div cdkTabPanel="profile">
66
+ <app-profile-settings />
67
+ </div>
68
+ <div cdkTabPanel="security">
69
+ <app-security-settings />
70
+ </div>
71
+ <div cdkTabPanel="notifications">
72
+ <app-notification-settings />
73
+ </div>
74
+ </div>
75
+ `,
76
+ })
77
+ export class SettingsTabsComponent { }
78
+ ```
79
+
80
+ ## Disclosure (Accordion)
81
+
82
+ ```typescript
83
+ import { CdkDisclosure, CdkDisclosureTrigger, CdkDisclosureContent } from '@angular/cdk-experimental/ui';
84
+
85
+ @Component({
86
+ selector: 'app-faq',
87
+ imports: [CdkDisclosure, CdkDisclosureTrigger, CdkDisclosureContent],
88
+ template: `
89
+ @for (item of faqItems; track item.id) {
90
+ <div cdkDisclosure>
91
+ <button cdkDisclosureTrigger>
92
+ {{ item.question }}
93
+ </button>
94
+ <div cdkDisclosureContent>
95
+ {{ item.answer }}
96
+ </div>
97
+ </div>
98
+ }
99
+ `,
100
+ })
101
+ export class FaqComponent {
102
+ protected readonly faqItems = [
103
+ { id: 1, question: 'How do I reset my password?', answer: '...' },
104
+ { id: 2, question: 'Where can I find my invoices?', answer: '...' },
105
+ ];
106
+ }
107
+ ```
108
+
109
+ ## Dialog
110
+
111
+ ```typescript
112
+ import { CdkDialog, CdkDialogTrigger, CdkDialogContent } from '@angular/cdk-experimental/ui';
113
+
114
+ @Component({
115
+ selector: 'app-confirm-dialog',
116
+ imports: [CdkDialog, CdkDialogTrigger, CdkDialogContent],
117
+ template: `
118
+ <div cdkDialog #dialog>
119
+ <button cdkDialogTrigger>Delete Item</button>
120
+
121
+ <div cdkDialogContent role="alertdialog" aria-labelledby="dialog-title">
122
+ <h2 id="dialog-title">Confirm Deletion</h2>
123
+ <p>Are you sure you want to delete this item?</p>
124
+ <div class="actions">
125
+ <button (click)="dialog.close()">Cancel</button>
126
+ <button (click)="onConfirm(); dialog.close()">Delete</button>
127
+ </div>
128
+ </div>
129
+ </div>
130
+ `,
131
+ })
132
+ export class ConfirmDialogComponent {
133
+ public readonly confirmed = output<void>();
134
+
135
+ protected onConfirm(): void {
136
+ this.confirmed.emit();
137
+ }
138
+ }
139
+ ```
140
+
141
+ ## Custom Accessible Components
142
+
143
+ ### Focus Management
144
+
145
+ ```typescript
146
+ import { FocusMonitor, FocusTrap, FocusTrapFactory } from '@angular/cdk/a11y';
147
+
148
+ @Component({
149
+ selector: 'app-modal',
150
+ template: `
151
+ <div class="modal-backdrop" (click)="close()"></div>
152
+ <div
153
+ class="modal"
154
+ role="dialog"
155
+ aria-modal="true"
156
+ [attr.aria-labelledby]="titleId"
157
+ #modalElement
158
+ >
159
+ <h2 [id]="titleId">{{ title() }}</h2>
160
+ <ng-content />
161
+ </div>
162
+ `,
163
+ })
164
+ export class ModalComponent implements AfterViewInit, OnDestroy {
165
+ private readonly focusTrapFactory = inject(FocusTrapFactory);
166
+ private readonly elementRef = inject(ElementRef);
167
+
168
+ public readonly title = input.required<string>();
169
+ public readonly closed = output<void>();
170
+
171
+ protected readonly titleId = `modal-title-${crypto.randomUUID()}`;
172
+
173
+ private focusTrap?: FocusTrap;
174
+ private previouslyFocusedElement?: HTMLElement;
175
+
176
+ constructor() {
177
+ afterNextRender(() => {
178
+ this.previouslyFocusedElement = document.activeElement as HTMLElement;
179
+ this.focusTrap = this.focusTrapFactory.create(this.elementRef.nativeElement);
180
+ this.focusTrap.focusInitialElement();
181
+ });
182
+ }
183
+
184
+ ngOnDestroy(): void {
185
+ this.focusTrap?.destroy();
186
+ this.previouslyFocusedElement?.focus();
187
+ }
188
+
189
+ protected close(): void {
190
+ this.closed.emit();
191
+ }
192
+ }
193
+ ```
194
+
195
+ ### Live Announcements
196
+
197
+ ```typescript
198
+ import { LiveAnnouncer } from '@angular/cdk/a11y';
199
+
200
+ @Component({ ... })
201
+ export class NotificationComponent {
202
+ private readonly liveAnnouncer = inject(LiveAnnouncer);
203
+
204
+ public async showSuccess(message: string): Promise<void> {
205
+ // Announces to screen readers
206
+ await this.liveAnnouncer.announce(message, 'polite');
207
+ }
208
+
209
+ public async showError(message: string): Promise<void> {
210
+ // 'assertive' interrupts current speech
211
+ await this.liveAnnouncer.announce(message, 'assertive');
212
+ }
213
+ }
214
+ ```
215
+
216
+ ### Keyboard Navigation
217
+
218
+ ```typescript
219
+ import { ListKeyManager } from '@angular/cdk/a11y';
220
+
221
+ @Component({
222
+ selector: 'app-menu',
223
+ template: `
224
+ <ul
225
+ role="menu"
226
+ (keydown)="onKeydown($event)"
227
+ #menuElement
228
+ >
229
+ @for (item of items(); track item.id) {
230
+ <li
231
+ role="menuitem"
232
+ [tabindex]="keyManager?.activeItem === item ? 0 : -1"
233
+ (click)="selectItem(item)"
234
+ >
235
+ {{ item.label }}
236
+ </li>
237
+ }
238
+ </ul>
239
+ `,
240
+ })
241
+ export class MenuComponent implements AfterViewInit {
242
+ public readonly items = input.required<MenuItem[]>();
243
+
244
+ @ViewChildren('menuItem') menuItems!: QueryList<ElementRef>;
245
+
246
+ protected keyManager?: ListKeyManager<MenuItem>;
247
+
248
+ ngAfterViewInit(): void {
249
+ this.keyManager = new ListKeyManager(this.items())
250
+ .withWrap()
251
+ .withHomeAndEnd()
252
+ .withTypeAhead();
253
+ }
254
+
255
+ protected onKeydown(event: KeyboardEvent): void {
256
+ this.keyManager?.onKeydown(event);
257
+
258
+ if (event.key === 'Enter' || event.key === ' ') {
259
+ event.preventDefault();
260
+ const activeItem = this.keyManager?.activeItem;
261
+ if (activeItem) {
262
+ this.selectItem(activeItem);
263
+ }
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ ## ARIA Attributes
270
+
271
+ ### Common Patterns
272
+
273
+ ```html
274
+ <!-- Buttons -->
275
+ <button aria-label="Close dialog" aria-describedby="close-hint">
276
+ <app-icon name="close" />
277
+ </button>
278
+ <span id="close-hint" class="sr-only">Press Escape to close</span>
279
+
280
+ <!-- Loading states -->
281
+ <button [attr.aria-busy]="isLoading()" [attr.aria-disabled]="isLoading()">
282
+ @if (isLoading()) {
283
+ <app-spinner aria-hidden="true" />
284
+ <span class="sr-only">Loading...</span>
285
+ } @else {
286
+ Submit
287
+ }
288
+ </button>
289
+
290
+ <!-- Expandable -->
291
+ <button
292
+ [attr.aria-expanded]="isExpanded()"
293
+ [attr.aria-controls]="contentId"
294
+ >
295
+ Show Details
296
+ </button>
297
+ <div [id]="contentId" [hidden]="!isExpanded()">
298
+ <!-- Content -->
299
+ </div>
300
+
301
+ <!-- Form errors -->
302
+ <input
303
+ [attr.aria-invalid]="hasError()"
304
+ [attr.aria-describedby]="hasError() ? errorId : null"
305
+ />
306
+ @if (hasError()) {
307
+ <span [id]="errorId" role="alert">
308
+ {{ errorMessage() }}
309
+ </span>
310
+ }
311
+
312
+ <!-- Progress -->
313
+ <div
314
+ role="progressbar"
315
+ [attr.aria-valuenow]="progress()"
316
+ aria-valuemin="0"
317
+ aria-valuemax="100"
318
+ [attr.aria-label]="'Upload progress: ' + progress() + '%'"
319
+ >
320
+ </div>
321
+ ```
322
+
323
+ ### Screen Reader Only Content
324
+
325
+ ```scss
326
+ // styles.scss
327
+ .sr-only {
328
+ position: absolute;
329
+ width: 1px;
330
+ height: 1px;
331
+ padding: 0;
332
+ margin: -1px;
333
+ overflow: hidden;
334
+ clip: rect(0, 0, 0, 0);
335
+ white-space: nowrap;
336
+ border: 0;
337
+ }
338
+ ```
339
+
340
+ ```html
341
+ <button>
342
+ <app-icon name="delete" aria-hidden="true" />
343
+ <span class="sr-only">Delete item</span>
344
+ </button>
345
+ ```
346
+
347
+ ## Anti-patterns
348
+
349
+ ```html
350
+ <!-- BAD: No accessible name -->
351
+ <button><app-icon name="close" /></button>
352
+
353
+ <!-- GOOD: Add aria-label -->
354
+ <button aria-label="Close">
355
+ <app-icon name="close" aria-hidden="true" />
356
+ </button>
357
+
358
+
359
+ <!-- BAD: Using div as button -->
360
+ <div (click)="submit()">Submit</div>
361
+
362
+ <!-- GOOD: Use semantic elements -->
363
+ <button type="submit" (click)="submit()">Submit</button>
364
+
365
+
366
+ <!-- BAD: Missing form labels -->
367
+ <input type="email" placeholder="Email" />
368
+
369
+ <!-- GOOD: Proper labeling -->
370
+ <label for="email">Email</label>
371
+ <input id="email" type="email" />
372
+
373
+ <!-- Or with aria-label -->
374
+ <input type="email" aria-label="Email address" placeholder="email@example.com" />
375
+
376
+
377
+ <!-- BAD: Non-descriptive link text -->
378
+ <a href="/docs">Click here</a>
379
+
380
+ <!-- GOOD: Descriptive link text -->
381
+ <a href="/docs">View documentation</a>
382
+
383
+
384
+ <!-- BAD: Auto-playing content -->
385
+ <video autoplay>...</video>
386
+
387
+ <!-- GOOD: User-controlled -->
388
+ <video controls>...</video>
389
+ ```
390
+
391
+ ## Testing Accessibility
392
+
393
+ ```typescript
394
+ // Using @angular-eslint for static analysis
395
+ // eslint.config.js
396
+ {
397
+ rules: {
398
+ '@angular-eslint/template/accessibility-alt-text': 'error',
399
+ '@angular-eslint/template/accessibility-elements-content': 'error',
400
+ '@angular-eslint/template/accessibility-label-has-associated-control': 'error',
401
+ '@angular-eslint/template/accessibility-valid-aria': 'error',
402
+ '@angular-eslint/template/click-events-have-key-events': 'error',
403
+ '@angular-eslint/template/mouse-events-have-key-events': 'error',
404
+ '@angular-eslint/template/no-autofocus': 'error',
405
+ '@angular-eslint/template/no-positive-tabindex': 'error',
406
+ }
407
+ }
408
+ ```
409
+
410
+ ```typescript
411
+ // E2E with Playwright accessibility testing
412
+ import { test, expect } from '@playwright/test';
413
+ import AxeBuilder from '@axe-core/playwright';
414
+
415
+ test('should have no accessibility violations', async ({ page }) => {
416
+ await page.goto('/');
417
+
418
+ const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
419
+
420
+ expect(accessibilityScanResults.violations).toEqual([]);
421
+ });
422
+ ```