@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
@@ -7,6 +7,59 @@ paths:
7
7
 
8
8
  # Component Rules (Angular 21)
9
9
 
10
+ ## Visibility Modifiers (MANDATORY)
11
+
12
+ **Every class member MUST have an explicit visibility modifier.**
13
+
14
+ ```typescript
15
+ // BAD - No visibility modifiers
16
+ export class UserComponent {
17
+ store = inject(Store);
18
+ users = this.store.selectSignal(selectUsers);
19
+
20
+ loadUsers(): void {}
21
+ }
22
+
23
+ // GOOD - Explicit visibility on ALL members
24
+ export class UserComponent {
25
+ private readonly store = inject(Store);
26
+
27
+ protected readonly users = this.store.selectSignal(selectUsers);
28
+
29
+ // Inputs/outputs are public (they are the component API)
30
+ public readonly name = input.required<string>();
31
+ public readonly userSelected = output<User>();
32
+
33
+ public loadUsers(): void {}
34
+
35
+ private formatUser(user: User): string {}
36
+ }
37
+ ```
38
+
39
+ ### Visibility Rules
40
+
41
+ | Visibility | When to Use |
42
+ |------------|-------------|
43
+ | `private` | Injected services, internal state, helper methods |
44
+ | `protected` | State/methods needed by child classes |
45
+ | `public` | Inputs, outputs, template-bound methods |
46
+ | `readonly` | Injected services, signals (add with visibility) |
47
+
48
+ ```typescript
49
+ // Standard pattern
50
+ private readonly store = inject(Store); // Service injection
51
+ private readonly destroyRef = inject(DestroyRef);
52
+
53
+ protected readonly loading = signal(false); // Internal state
54
+ protected readonly error = signal<string | null>(null);
55
+
56
+ public readonly items = input.required<Item[]>(); // Component API
57
+ public readonly itemSelected = output<Item>();
58
+
59
+ public onItemClick(item: Item): void {} // Template-bound
60
+ private processItem(item: Item): void {} // Internal helper
61
+ ```
62
+
10
63
  ## Code Quality
11
64
 
12
65
  ### No Useless Comments
@@ -108,15 +161,15 @@ Smart components are located in `feature/` libraries.
108
161
  export class ProductPageComponent {
109
162
  private readonly store = inject(Store);
110
163
 
111
- categories = this.store.selectSignal(selectCategories);
112
- filteredProducts = this.store.selectSignal(selectFilteredProducts);
113
- loading = this.store.selectSignal(selectProductsLoading);
164
+ protected readonly categories = this.store.selectSignal(selectCategories);
165
+ protected readonly filteredProducts = this.store.selectSignal(selectFilteredProducts);
166
+ protected readonly loading = this.store.selectSignal(selectProductsLoading);
114
167
 
115
- onFilterChange(filters: Filters): void {
168
+ public onFilterChange(filters: Filters): void {
116
169
  this.store.dispatch(ProductActions.setFilters({ filters }));
117
170
  }
118
171
 
119
- onAddToCart(product: Product): void {
172
+ public onAddToCart(product: Product): void {
120
173
  this.store.dispatch(CartActions.addItem({ product }));
121
174
  }
122
175
  }
@@ -168,8 +221,8 @@ UI components are located in `ui/` libraries. They are purely presentational.
168
221
  changeDetection: ChangeDetectionStrategy.OnPush,
169
222
  })
170
223
  export class ProductCardComponent {
171
- product = input.required<Product>();
172
- addToCart = output<Product>();
224
+ public readonly product = input.required<Product>();
225
+ public readonly addToCart = output<Product>();
173
226
  }
174
227
  ```
175
228
 
@@ -191,17 +244,17 @@ Always use signal-based functions, NOT decorators:
191
244
 
192
245
  ```typescript
193
246
  // Inputs - use input(), NOT @Input()
194
- name = input<string>(); // Optional
195
- name = input('default'); // With default
196
- name = input.required<string>(); // Required
247
+ public readonly name = input<string>(); // Optional
248
+ public readonly name = input('default'); // With default
249
+ public readonly name = input.required<string>(); // Required
197
250
 
198
251
  // Outputs - use output(), NOT @Output()
199
- clicked = output<void>();
200
- selected = output<Item>();
252
+ public readonly clicked = output<void>();
253
+ public readonly selected = output<Item>();
201
254
 
202
255
  // Two-way binding - use model()
203
- value = model<string>(''); // Optional with default
204
- value = model.required<string>(); // Required
256
+ public readonly value = model<string>(''); // Optional with default
257
+ public readonly value = model.required<string>(); // Required
205
258
 
206
259
  // Emit
207
260
  this.clicked.emit();
@@ -216,9 +269,10 @@ this.value.set('new value'); // model is writable
216
269
  @Component({
217
270
  selector: 'app-search-input',
218
271
  templateUrl: './search-input.component.html',
272
+ changeDetection: ChangeDetectionStrategy.OnPush,
219
273
  })
220
274
  export class SearchInputComponent {
221
- query = model(''); // Creates [(query)] binding
275
+ public readonly query = model(''); // Creates [(query)] binding
222
276
  }
223
277
  ```
224
278
 
@@ -0,0 +1,285 @@
1
+ ---
2
+ paths:
3
+ - "**/*.component.ts"
4
+ - "**/*.store.ts"
5
+ - "**/services/**/*.ts"
6
+ - "**/data-access/**/*.ts"
7
+ ---
8
+
9
+ # Angular Resource API
10
+
11
+ The `resource()` and `rxResource()` APIs provide declarative data fetching with signals.
12
+
13
+ ## Basic Resource
14
+
15
+ ```typescript
16
+ import { resource, Signal } from '@angular/core';
17
+
18
+ @Component({
19
+ selector: 'app-user-profile',
20
+ template: `
21
+ @if (userResource.isLoading()) {
22
+ <app-spinner />
23
+ } @else if (userResource.error()) {
24
+ <app-error [message]="userResource.error()?.message" />
25
+ } @else if (userResource.hasValue()) {
26
+ <app-user-card [user]="userResource.value()!" />
27
+ }
28
+ `,
29
+ })
30
+ export class UserProfileComponent {
31
+ private readonly userService = inject(UserService);
32
+
33
+ public readonly userId = input.required<string>();
34
+
35
+ // Resource automatically refetches when userId changes
36
+ protected readonly userResource = resource({
37
+ request: () => this.userId(),
38
+ loader: ({ request: userId }) => this.userService.getById(userId),
39
+ });
40
+ }
41
+ ```
42
+
43
+ ## Resource Properties
44
+
45
+ ```typescript
46
+ const userResource = resource({
47
+ request: () => this.userId(),
48
+ loader: ({ request }) => this.userService.getById(request),
49
+ });
50
+
51
+ // Signals exposed by resource
52
+ userResource.value(); // T | undefined - the loaded data
53
+ userResource.isLoading(); // boolean - true while loading
54
+ userResource.error(); // unknown | undefined - error if failed
55
+ userResource.status(); // ResourceStatus - 'idle' | 'loading' | 'resolved' | 'error'
56
+
57
+ // Type guard
58
+ userResource.hasValue(); // boolean - true if value is available
59
+
60
+ // Manual control
61
+ userResource.reload(); // Force refetch with current request
62
+ ```
63
+
64
+ ## Resource with Multiple Dependencies
65
+
66
+ ```typescript
67
+ @Component({ ... })
68
+ export class ProductListComponent {
69
+ protected readonly category = signal<string>('all');
70
+ protected readonly sortBy = signal<'name' | 'price'>('name');
71
+ protected readonly page = signal(1);
72
+
73
+ // Refetches when ANY dependency changes
74
+ protected readonly productsResource = resource({
75
+ request: () => ({
76
+ category: this.category(),
77
+ sortBy: this.sortBy(),
78
+ page: this.page(),
79
+ }),
80
+ loader: ({ request }) => this.productService.search(request),
81
+ });
82
+ }
83
+ ```
84
+
85
+ ## rxResource - RxJS Integration
86
+
87
+ Use `rxResource` when your service returns Observables:
88
+
89
+ ```typescript
90
+ import { rxResource } from '@angular/core/rxjs-interop';
91
+
92
+ @Component({ ... })
93
+ export class UserListComponent {
94
+ private readonly userService = inject(UserService);
95
+
96
+ protected readonly searchQuery = signal('');
97
+
98
+ // Works with Observable-returning services
99
+ protected readonly usersResource = rxResource({
100
+ request: () => this.searchQuery(),
101
+ loader: ({ request: query }) => this.userService.search(query),
102
+ });
103
+ }
104
+ ```
105
+
106
+ ## Conditional Loading
107
+
108
+ ```typescript
109
+ @Component({ ... })
110
+ export class UserDetailComponent {
111
+ public readonly userId = input<string | null>(null);
112
+
113
+ protected readonly userResource = resource({
114
+ // Only fetch when userId is provided
115
+ request: () => {
116
+ const id = this.userId();
117
+ return id ? { id } : undefined; // undefined = don't load
118
+ },
119
+ loader: ({ request }) => this.userService.getById(request.id),
120
+ });
121
+ }
122
+ ```
123
+
124
+ ## Resource with AbortSignal
125
+
126
+ Handle cancellation for long-running requests:
127
+
128
+ ```typescript
129
+ protected readonly searchResource = resource({
130
+ request: () => this.query(),
131
+ loader: async ({ request, abortSignal }) => {
132
+ const response = await fetch(`/api/search?q=${request}`, {
133
+ signal: abortSignal, // Automatically cancelled on new request
134
+ });
135
+ return response.json();
136
+ },
137
+ });
138
+ ```
139
+
140
+ ## Local State with Resource
141
+
142
+ Combine resource with local state for optimistic updates:
143
+
144
+ ```typescript
145
+ @Component({ ... })
146
+ export class TodoListComponent {
147
+ protected readonly todosResource = resource({
148
+ request: () => ({}),
149
+ loader: () => this.todoService.getAll(),
150
+ });
151
+
152
+ // Local state for optimistic updates
153
+ protected readonly localTodos = linkedSignal(() => this.todosResource.value() ?? []);
154
+
155
+ public async addTodo(text: string): Promise<void> {
156
+ const tempTodo = { id: crypto.randomUUID(), text, completed: false };
157
+
158
+ // Optimistic update
159
+ this.localTodos.update(todos => [...todos, tempTodo]);
160
+
161
+ try {
162
+ const saved = await this.todoService.create({ text });
163
+ // Replace temp with saved
164
+ this.localTodos.update(todos =>
165
+ todos.map(t => t.id === tempTodo.id ? saved : t)
166
+ );
167
+ } catch {
168
+ // Rollback on error
169
+ this.localTodos.update(todos =>
170
+ todos.filter(t => t.id !== tempTodo.id)
171
+ );
172
+ }
173
+ }
174
+ }
175
+ ```
176
+
177
+ ## Error Handling
178
+
179
+ ```typescript
180
+ @Component({
181
+ template: `
182
+ @switch (usersResource.status()) {
183
+ @case ('idle') {
184
+ <p>Enter a search term</p>
185
+ }
186
+ @case ('loading') {
187
+ <app-spinner />
188
+ }
189
+ @case ('error') {
190
+ <div class="error">
191
+ <p>Failed to load users</p>
192
+ <button (click)="usersResource.reload()">Retry</button>
193
+ </div>
194
+ }
195
+ @case ('resolved') {
196
+ @for (user of usersResource.value(); track user.id) {
197
+ <app-user-card [user]="user" />
198
+ } @empty {
199
+ <p>No users found</p>
200
+ }
201
+ }
202
+ }
203
+ `,
204
+ })
205
+ export class UserSearchComponent {
206
+ protected readonly query = signal('');
207
+
208
+ protected readonly usersResource = resource({
209
+ request: () => {
210
+ const q = this.query();
211
+ return q.length >= 3 ? q : undefined;
212
+ },
213
+ loader: ({ request }) => this.userService.search(request),
214
+ });
215
+ }
216
+ ```
217
+
218
+ ## Prefetching Data
219
+
220
+ ```typescript
221
+ @Component({ ... })
222
+ export class ProductPageComponent {
223
+ public readonly productId = input.required<string>();
224
+
225
+ // Main product data
226
+ protected readonly productResource = resource({
227
+ request: () => this.productId(),
228
+ loader: ({ request }) => this.productService.getById(request),
229
+ });
230
+
231
+ // Prefetch related products (starts loading immediately)
232
+ protected readonly relatedResource = resource({
233
+ request: () => this.productId(),
234
+ loader: ({ request }) => this.productService.getRelated(request),
235
+ });
236
+ }
237
+ ```
238
+
239
+ ## Anti-patterns
240
+
241
+ ```typescript
242
+ // BAD: Using resource for mutations
243
+ const saveResource = resource({
244
+ request: () => this.formData(),
245
+ loader: ({ request }) => this.service.save(request), // Mutating!
246
+ });
247
+
248
+ // GOOD: Use resource for reads, methods for mutations
249
+ protected readonly userResource = resource({ ... });
250
+
251
+ public async save(): Promise<void> {
252
+ await this.userService.save(this.formData());
253
+ this.userResource.reload(); // Refresh after mutation
254
+ }
255
+
256
+
257
+ // BAD: Not handling loading state
258
+ @if (userResource.value()) {
259
+ <app-user [user]="userResource.value()!" />
260
+ }
261
+ // Missing loading and error states!
262
+
263
+ // GOOD: Handle all states
264
+ @if (userResource.isLoading()) {
265
+ <app-spinner />
266
+ } @else if (userResource.error()) {
267
+ <app-error />
268
+ } @else if (userResource.hasValue()) {
269
+ <app-user [user]="userResource.value()!" />
270
+ }
271
+
272
+
273
+ // BAD: Subscribing to service in component
274
+ ngOnInit() {
275
+ this.userService.getById(this.userId()).subscribe(user => {
276
+ this.user = user;
277
+ });
278
+ }
279
+
280
+ // GOOD: Use resource for declarative data fetching
281
+ protected readonly userResource = resource({
282
+ request: () => this.userId(),
283
+ loader: ({ request }) => this.userService.getById(request),
284
+ });
285
+ ```