@malamute/ai-rules 1.0.0 → 1.3.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 (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -0,0 +1,335 @@
1
+ ---
2
+ paths:
3
+ - "**/*.pipe.ts"
4
+ - "**/*.directive.ts"
5
+ - "**/pipes/**"
6
+ - "**/directives/**"
7
+ ---
8
+
9
+ # Angular Pipes & Directives
10
+
11
+ ## Custom Pipes
12
+
13
+ ### Pure Pipe (default, memoized)
14
+
15
+ ```typescript
16
+ // pipes/time-ago.pipe.ts
17
+ import { Pipe, PipeTransform } from '@angular/core';
18
+
19
+ @Pipe({
20
+ name: 'timeAgo',
21
+ })
22
+ export class TimeAgoPipe implements PipeTransform {
23
+ public transform(value: Date | string | number): string {
24
+ const date = new Date(value);
25
+ const now = new Date();
26
+ const seconds = Math.floor((now.getTime() - date.getTime()) / 1000);
27
+
28
+ if (seconds < 60) return 'just now';
29
+ if (seconds < 3600) return `${Math.floor(seconds / 60)} minutes ago`;
30
+ if (seconds < 86400) return `${Math.floor(seconds / 3600)} hours ago`;
31
+ if (seconds < 2592000) return `${Math.floor(seconds / 86400)} days ago`;
32
+
33
+ return date.toLocaleDateString();
34
+ }
35
+ }
36
+
37
+ // Usage: {{ createdAt | timeAgo }}
38
+ ```
39
+
40
+ ### Pipe with Parameters
41
+
42
+ ```typescript
43
+ // pipes/truncate.pipe.ts
44
+ @Pipe({
45
+ name: 'truncate',
46
+ })
47
+ export class TruncatePipe implements PipeTransform {
48
+ public transform(
49
+ value: string,
50
+ limit: number = 100,
51
+ trail: string = '...'
52
+ ): string {
53
+ if (!value || value.length <= limit) {
54
+ return value;
55
+ }
56
+ return value.substring(0, limit).trim() + trail;
57
+ }
58
+ }
59
+
60
+ // Usage: {{ description | truncate:50:'…' }}
61
+ ```
62
+
63
+ ### Filter Pipe
64
+
65
+ ```typescript
66
+ // pipes/filter.pipe.ts
67
+ @Pipe({
68
+ name: 'filter',
69
+ })
70
+ export class FilterPipe implements PipeTransform {
71
+ public transform<T>(
72
+ items: T[],
73
+ field: keyof T,
74
+ value: unknown
75
+ ): T[] {
76
+ if (!items || !field || value === undefined) {
77
+ return items;
78
+ }
79
+ return items.filter(item => item[field] === value);
80
+ }
81
+ }
82
+
83
+ // Usage: @for (user of users() | filter:'status':'active'; track user.id)
84
+ ```
85
+
86
+ ### Safe HTML Pipe
87
+
88
+ ```typescript
89
+ // pipes/safe-html.pipe.ts
90
+ import { Pipe, PipeTransform, inject } from '@angular/core';
91
+ import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
92
+
93
+ @Pipe({
94
+ name: 'safeHtml',
95
+ })
96
+ export class SafeHtmlPipe implements PipeTransform {
97
+ private readonly sanitizer = inject(DomSanitizer);
98
+
99
+ public transform(value: string): SafeHtml {
100
+ return this.sanitizer.bypassSecurityTrustHtml(value);
101
+ }
102
+ }
103
+
104
+ // Usage: <div [innerHTML]="content | safeHtml"></div>
105
+ // ⚠️ Only use with trusted content!
106
+ ```
107
+
108
+ ## Custom Directives
109
+
110
+ ### Attribute Directive
111
+
112
+ ```typescript
113
+ // directives/highlight.directive.ts
114
+ import { Directive, ElementRef, HostListener, input, inject } from '@angular/core';
115
+
116
+ @Directive({
117
+ selector: '[appHighlight]',
118
+ })
119
+ export class HighlightDirective {
120
+ private readonly el = inject(ElementRef);
121
+
122
+ public readonly appHighlight = input<string>('#ffff00');
123
+ public readonly defaultColor = input<string>('transparent');
124
+
125
+ @HostListener('mouseenter')
126
+ public onMouseEnter(): void {
127
+ this.highlight(this.appHighlight() || '#ffff00');
128
+ }
129
+
130
+ @HostListener('mouseleave')
131
+ public onMouseLeave(): void {
132
+ this.highlight(this.defaultColor());
133
+ }
134
+
135
+ private highlight(color: string): void {
136
+ this.el.nativeElement.style.backgroundColor = color;
137
+ }
138
+ }
139
+
140
+ // Usage: <p appHighlight="#e0e0e0">Hover me</p>
141
+ ```
142
+
143
+ ### Click Outside Directive
144
+
145
+ ```typescript
146
+ // directives/click-outside.directive.ts
147
+ import { Directive, ElementRef, output, inject } from '@angular/core';
148
+ import { fromEvent } from 'rxjs';
149
+ import { filter } from 'rxjs/operators';
150
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
151
+
152
+ @Directive({
153
+ selector: '[appClickOutside]',
154
+ })
155
+ export class ClickOutsideDirective {
156
+ private readonly el = inject(ElementRef);
157
+
158
+ public readonly appClickOutside = output<void>();
159
+
160
+ constructor() {
161
+ fromEvent<MouseEvent>(document, 'click')
162
+ .pipe(
163
+ filter(event => !this.el.nativeElement.contains(event.target)),
164
+ takeUntilDestroyed(),
165
+ )
166
+ .subscribe(() => this.appClickOutside.emit());
167
+ }
168
+ }
169
+
170
+ // Usage: <div appClickOutside (appClickOutside)="closeDropdown()">
171
+ ```
172
+
173
+ ### Auto Focus Directive
174
+
175
+ ```typescript
176
+ // directives/auto-focus.directive.ts
177
+ import { Directive, ElementRef, AfterViewInit, input, inject } from '@angular/core';
178
+
179
+ @Directive({
180
+ selector: '[appAutoFocus]',
181
+ })
182
+ export class AutoFocusDirective implements AfterViewInit {
183
+ private readonly el = inject(ElementRef);
184
+
185
+ public readonly appAutoFocus = input<boolean>(true);
186
+
187
+ public ngAfterViewInit(): void {
188
+ if (this.appAutoFocus()) {
189
+ setTimeout(() => this.el.nativeElement.focus(), 0);
190
+ }
191
+ }
192
+ }
193
+
194
+ // Usage: <input appAutoFocus />
195
+ ```
196
+
197
+ ### Structural Directive
198
+
199
+ ```typescript
200
+ // directives/permission.directive.ts
201
+ import { Directive, TemplateRef, ViewContainerRef, input, effect, inject } from '@angular/core';
202
+ import { AuthService } from '../services/auth.service';
203
+
204
+ @Directive({
205
+ selector: '[appHasPermission]',
206
+ })
207
+ export class HasPermissionDirective {
208
+ private readonly templateRef = inject(TemplateRef<unknown>);
209
+ private readonly viewContainer = inject(ViewContainerRef);
210
+ private readonly authService = inject(AuthService);
211
+
212
+ public readonly appHasPermission = input.required<string | string[]>();
213
+
214
+ private hasView = false;
215
+
216
+ constructor() {
217
+ effect(() => {
218
+ const permissions = this.appHasPermission();
219
+ const permissionArray = Array.isArray(permissions) ? permissions : [permissions];
220
+ const hasPermission = this.authService.hasAnyPermission(permissionArray);
221
+
222
+ if (hasPermission && !this.hasView) {
223
+ this.viewContainer.createEmbeddedView(this.templateRef);
224
+ this.hasView = true;
225
+ } else if (!hasPermission && this.hasView) {
226
+ this.viewContainer.clear();
227
+ this.hasView = false;
228
+ }
229
+ });
230
+ }
231
+ }
232
+
233
+ // Usage: <button *appHasPermission="'users.create'">Create User</button>
234
+ ```
235
+
236
+ ### Debounce Input Directive
237
+
238
+ ```typescript
239
+ // directives/debounce-input.directive.ts
240
+ import { Directive, ElementRef, output, input, inject, OnInit, DestroyRef } from '@angular/core';
241
+ import { fromEvent, debounceTime, distinctUntilChanged, map } from 'rxjs';
242
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
243
+
244
+ @Directive({
245
+ selector: 'input[appDebounce]',
246
+ })
247
+ export class DebounceInputDirective implements OnInit {
248
+ private readonly el = inject(ElementRef<HTMLInputElement>);
249
+ private readonly destroyRef = inject(DestroyRef);
250
+
251
+ public readonly appDebounce = input<number>(300);
252
+ public readonly debounceValue = output<string>();
253
+
254
+ public ngOnInit(): void {
255
+ fromEvent<Event>(this.el.nativeElement, 'input')
256
+ .pipe(
257
+ map(event => (event.target as HTMLInputElement).value),
258
+ debounceTime(this.appDebounce()),
259
+ distinctUntilChanged(),
260
+ takeUntilDestroyed(this.destroyRef),
261
+ )
262
+ .subscribe(value => this.debounceValue.emit(value));
263
+ }
264
+ }
265
+
266
+ // Usage: <input appDebounce [appDebounce]="500" (debounceValue)="onSearch($event)" />
267
+ ```
268
+
269
+ ## Composition
270
+
271
+ ```typescript
272
+ // components/user-list.component.ts
273
+ @Component({
274
+ selector: 'app-user-list',
275
+ imports: [
276
+ TimeAgoPipe,
277
+ TruncatePipe,
278
+ FilterPipe,
279
+ HighlightDirective,
280
+ HasPermissionDirective,
281
+ ],
282
+ template: `
283
+ @for (user of users() | filter:'status':'active'; track user.id) {
284
+ <div appHighlight="#f0f0f0">
285
+ <h3>{{ user.name }}</h3>
286
+ <p>{{ user.bio | truncate:100 }}</p>
287
+ <span>{{ user.createdAt | timeAgo }}</span>
288
+
289
+ <button *appHasPermission="'users.delete'">Delete</button>
290
+ </div>
291
+ }
292
+ `,
293
+ })
294
+ export class UserListComponent {
295
+ protected readonly users = input.required<User[]>();
296
+ }
297
+ ```
298
+
299
+ ## Anti-patterns
300
+
301
+ ```typescript
302
+ // BAD: Impure pipe for filtering (causes performance issues)
303
+ @Pipe({ name: 'filter', pure: false })
304
+
305
+ // GOOD: Use pure pipe (default) + signal for reactivity
306
+ @Pipe({ name: 'filter' })
307
+ // And update source data via signals
308
+
309
+
310
+ // BAD: Direct DOM manipulation
311
+ this.el.nativeElement.innerHTML = '<b>text</b>';
312
+
313
+ // GOOD: Use Renderer2 or Angular bindings
314
+ @HostBinding('innerHTML') content = '<b>text</b>';
315
+
316
+
317
+ // BAD: Adding standalone: true (it's the default since Angular 19)
318
+ @Pipe({ name: 'myPipe', standalone: true })
319
+
320
+ // GOOD: Omit standalone (defaults to true)
321
+ @Pipe({ name: 'myPipe' })
322
+
323
+
324
+ // BAD: Using @Input() in directives
325
+ @Directive({ selector: '[appHighlight]' })
326
+ export class HighlightDirective {
327
+ @Input() appHighlight: string; // Use input() instead
328
+ }
329
+
330
+ // GOOD: Use signal inputs
331
+ @Directive({ selector: '[appHighlight]' })
332
+ export class HighlightDirective {
333
+ public readonly appHighlight = input<string>();
334
+ }
335
+ ```
@@ -18,6 +18,8 @@
18
18
  "Write"
19
19
  ],
20
20
  "deny": [
21
+ "Bash(git push *)",
22
+ "Bash(git push)",
21
23
  "Bash(rm -rf *)",
22
24
  "Bash(nx reset)",
23
25
  "Read(.env)",
@@ -26,6 +28,7 @@
26
28
  ]
27
29
  },
28
30
  "env": {
31
+ "NODE_ENV": "development",
29
32
  "NX_DAEMON": "true"
30
33
  }
31
34
  }