agent-directives 0.3.0 → 0.4.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.
package/README.md CHANGED
@@ -13,7 +13,7 @@ dependencies between files.
13
13
  | **Navigation** | 1 directive | SAFE pattern for exploring codebases before implementation |
14
14
  | **Memory** | 2 directives | Error memory and session decisions for persistent learning |
15
15
  | **Skills** | 13 skills | Code reviewer, test reviewer, spec reviewer, product requirements writer, implementation task planner, subagent-driven development, self-audit, systematic debugging, architecture boundary reviewer, codebase health reviewer, production readiness reviewer, harness hooks reviewer, and MCP integration reviewer |
16
- | **Rules** | 3 Angular rules | Lazy-loaded Angular project, component/template, and testing standards selected by stack or explicit category |
16
+ | **Rules** | 6 Angular rules | Lazy-loaded Angular project structure, components/templates, coding style, application patterns, security, and testing standards selected by stack or explicit category |
17
17
  | **Templates** | 4 templates | Drop-in instruction files for AGENTS.md, CLAUDE.md, Copilot, and decision logs |
18
18
  | **Tooling** | TypeScript scripts | Validate directive wiring, assemble eval scenarios, record loaded-file manifests, and generate eval health reports |
19
19
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: adaptive-routing
3
3
  description: Selects the lightest safe workflow path, relevant directives/skills, and handoff requirements based on task intent, risk, and touched surfaces.
4
- version: 1.4.0
4
+ version: 1.6.0
5
5
  required: true
6
6
  category: workflow
7
7
  tools:
@@ -138,13 +138,27 @@ work but do not replace directives or skills.
138
138
  - Do not load unrelated framework rule packs. Project-local instructions override
139
139
  rule-pack guidance when they conflict.
140
140
 
141
- Initial rule pack:
142
-
143
- | Situation / evidence | Selected rules |
144
- | --- | --- |
145
- | Angular project structure, routing, source layout, or workspace config (`angular.json`, `@angular/core`, or Angular app files) | `rules/angular/project-structure.md` |
146
- | Angular component, template, component style, inputs/outputs, lifecycle, accessibility, or UI behavior | `rules/angular/components-and-templates.md` |
147
- | Angular tests, specs, services under test, or Angular behavior changes needing test evidence | `rules/angular/testing.md` |
141
+ ### Selecting rules by discovery
142
+
143
+ Do not maintain a per-rule routing table in this directive. Rule packs grow, and
144
+ this directive loads on every task; an inline catalog would put every rule pack's
145
+ metadata in always-loaded context. Discover matching rules on demand instead:
146
+
147
+ 1. **Detect the stack** from project evidence. For example, `angular.json` or an
148
+ `@angular/core` dependency selects the `angular` pack.
149
+ 2. **Open the rule index.** Each rule entry in `manifest.json` carries `category`
150
+ (the pack), `description` (what it governs), and `applies_to` (the file globs
151
+ it scopes to). The `rules/<pack>/` directory holds the same entries on disk;
152
+ read a rule's frontmatter for the authoritative scope.
153
+ 3. **Match before loading.** Load a rule only when its `applies_to` globs match a
154
+ file you will touch *and* its `description` is relevant to what the task
155
+ actually changes. A glob match alone is not enough — editing a
156
+ `*.component.ts` file does not pull in the security rule unless the task
157
+ involves untrusted input, HTTP, secrets, or SSR.
158
+ 4. **Skip absent packs.** Do not load a pack whose evidence is missing, and do
159
+ not load a whole pack by default.
160
+
161
+ The only pack today is `rules/angular/`.
148
162
 
149
163
  ---
150
164
 
@@ -8,6 +8,7 @@ export interface ManifestEntry {
8
8
  required: boolean;
9
9
  category: string;
10
10
  tools: string[];
11
+ applies_to?: string[];
11
12
  }
12
13
  export interface Manifest {
13
14
  version: string;
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAGD,eAAO,MAAM,WAAW,QAAwB,CAAC;AAEjD,wBAAgB,YAAY,IAAI,QAAQ,CAMvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,aAAa,GAAG,aAAa,EAAE,CAQ5F;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEzF"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,OAAO,GAAG,MAAM,CAAC;AAE/D,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,iBAAiB,CAAC;CAC1B;AAGD,eAAO,MAAM,WAAW,QAAwB,CAAC;AAEjD,wBAAgB,YAAY,IAAI,QAAQ,CAMvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,IAAI,EAAE,aAAa,GAAG,aAAa,EAAE,CAQ5F;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAEzF"}
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA2BzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAEjD,MAAM,UAAU,YAAY;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,8BAA8B,YAAY,0CAA0C,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAa,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAwB,EAAE,IAAmB;IACzE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QACpE,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClF,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAwB,EAAE,EAAU;IAC5D,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC"}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AA4BzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAEjD,MAAM,UAAU,YAAY;IAC1B,MAAM,YAAY,GAAG,IAAI,CAAC,WAAW,EAAE,eAAe,CAAC,CAAC;IACxD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,8BAA8B,YAAY,0CAA0C,CAAC,CAAC;IACxG,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAa,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAwB,EAAE,IAAmB;IACzE,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE;QAC9B,IAAI,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QACpE,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ;YAAE,OAAO,KAAK,CAAC;QAClF,IAAI,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QAChE,IAAI,IAAI,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAAE,OAAO,KAAK,CAAC;QACxD,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAwB,EAAE,EAAU;IAC5D,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC"}
package/manifest.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "type": "directive",
7
7
  "path": "directives/adaptive-routing.md",
8
8
  "description": "Selects the lightest safe workflow path, relevant directives/skills, and handoff requirements based on task intent, risk, and touched surfaces.",
9
- "version": "1.4.0",
9
+ "version": "1.6.0",
10
10
  "required": true,
11
11
  "category": "workflow",
12
12
  "tools": [
@@ -383,11 +383,51 @@
383
383
  "cursor"
384
384
  ]
385
385
  },
386
+ {
387
+ "id": "angular-coding-style",
388
+ "type": "rule",
389
+ "path": "rules/angular/coding-style.md",
390
+ "description": "Concrete Angular coding-style patterns — signals, dependency injection with inject(), change detection, RxJS usage, and TypeScript strictness for Angular code.",
391
+ "version": "1.0.0",
392
+ "required": false,
393
+ "category": "angular",
394
+ "tools": [
395
+ "claude",
396
+ "copilot",
397
+ "codex",
398
+ "cursor"
399
+ ],
400
+ "applies_to": [
401
+ "src/app/**/*.ts",
402
+ "src/**/*.ts"
403
+ ]
404
+ },
386
405
  {
387
406
  "id": "angular-components-and-templates",
388
407
  "type": "rule",
389
408
  "path": "rules/angular/components-and-templates.md",
390
- "description": "Angular component and template rules for typed, maintainable Angular UI changes.",
409
+ "description": "Concrete patterns for building modern Angular components, signal-based inputs/outputs, OnPush change detection, and v17+ control-flow templates.",
410
+ "version": "1.1.0",
411
+ "required": false,
412
+ "category": "angular",
413
+ "tools": [
414
+ "claude",
415
+ "copilot",
416
+ "codex",
417
+ "cursor"
418
+ ],
419
+ "applies_to": [
420
+ "src/app/**/*.component.ts",
421
+ "src/app/**/*.component.html",
422
+ "src/app/**/*.component.css",
423
+ "src/app/**/*.component.scss"
424
+ ]
425
+ },
426
+ {
427
+ "id": "angular-patterns",
428
+ "type": "rule",
429
+ "path": "rules/angular/patterns.md",
430
+ "description": "Concrete Angular application patterns — smart/dumb component split, service-layer ownership, routing/guards/resolvers, HTTP interceptors, and reactive state with signals or RxJS.",
391
431
  "version": "1.0.0",
392
432
  "required": false,
393
433
  "category": "angular",
@@ -396,13 +436,45 @@
396
436
  "copilot",
397
437
  "codex",
398
438
  "cursor"
439
+ ],
440
+ "applies_to": [
441
+ "src/app/**/*.component.ts",
442
+ "src/app/**/*.component.html",
443
+ "src/app/**/*.service.ts",
444
+ "src/app/**/*.store.ts",
445
+ "src/app/**/*.routes.ts",
446
+ "src/app/**/*.guard.ts",
447
+ "src/app/**/*.resolver.ts",
448
+ "src/app/**/*.interceptor.ts"
399
449
  ]
400
450
  },
401
451
  {
402
452
  "id": "angular-project-structure",
403
453
  "type": "rule",
404
454
  "path": "rules/angular/project-structure.md",
405
- "description": "Angular workspace and project-structure standards for agents working in Angular applications.",
455
+ "description": "Concrete Angular workspace, file-naming, feature-folder, and provider-bootstrapping standards for agents working in Angular applications.",
456
+ "version": "1.1.0",
457
+ "required": false,
458
+ "category": "angular",
459
+ "tools": [
460
+ "claude",
461
+ "copilot",
462
+ "codex",
463
+ "cursor"
464
+ ],
465
+ "applies_to": [
466
+ "angular.json",
467
+ "package.json",
468
+ "src/main.ts",
469
+ "src/app/**/*.ts",
470
+ "src/app/**/*.html"
471
+ ]
472
+ },
473
+ {
474
+ "id": "angular-security",
475
+ "type": "rule",
476
+ "path": "rules/angular/security.md",
477
+ "description": "Concrete Angular security rules — XSS prevention, HttpClient discipline, secret handling, route guards, and SSR safety.",
406
478
  "version": "1.0.0",
407
479
  "required": false,
408
480
  "category": "angular",
@@ -411,14 +483,25 @@
411
483
  "copilot",
412
484
  "codex",
413
485
  "cursor"
486
+ ],
487
+ "applies_to": [
488
+ "src/app/**/*.component.ts",
489
+ "src/app/**/*.component.html",
490
+ "src/app/**/*.service.ts",
491
+ "src/app/**/*.interceptor.ts",
492
+ "src/app/**/*.guard.ts",
493
+ "src/app/**/*.routes.ts",
494
+ "src/environments/**/*.ts",
495
+ "src/**/*.server.ts",
496
+ "server.ts"
414
497
  ]
415
498
  },
416
499
  {
417
500
  "id": "angular-testing",
418
501
  "type": "rule",
419
502
  "path": "rules/angular/testing.md",
420
- "description": "Angular testing rules for component, service, and integration-style Angular changes.",
421
- "version": "1.0.0",
503
+ "description": "Concrete Angular testing patterns TestBed configuration, signal-input harnesses, router and HTTP testing utilities, and behavior-first test design.",
504
+ "version": "1.1.0",
422
505
  "required": false,
423
506
  "category": "angular",
424
507
  "tools": [
@@ -426,6 +509,11 @@
426
509
  "copilot",
427
510
  "codex",
428
511
  "cursor"
512
+ ],
513
+ "applies_to": [
514
+ "src/app/**/*.spec.ts",
515
+ "src/app/**/*.test.ts",
516
+ "src/**/*.spec.ts"
429
517
  ]
430
518
  }
431
519
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-directives",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Reusable agent directives, skills, and CLI tooling for AI coding workflows",
5
5
  "license": "MIT",
6
6
  "author": "Rob Simpson <rsimpson2@me.com>",
@@ -0,0 +1,164 @@
1
+ ---
2
+ name: angular-coding-style
3
+ description: Concrete Angular coding-style patterns — signals, dependency injection with inject(), change detection, RxJS usage, and TypeScript strictness for Angular code.
4
+ version: 1.0.0
5
+ required: false
6
+ category: angular
7
+ tools:
8
+ - claude
9
+ - copilot
10
+ - codex
11
+ - cursor
12
+ source_urls:
13
+ - https://angular.dev/style-guide
14
+ - https://angular.dev/guide/signals
15
+ - https://angular.dev/guide/di
16
+ - https://angular.dev/guide/components/dependency-injection
17
+ - https://rxjs.dev/guide/operators
18
+ applies_to:
19
+ - src/app/**/*.ts
20
+ - src/**/*.ts
21
+ ---
22
+
23
+ # Angular Coding Style Rules
24
+
25
+ **Load when:** Writing or reviewing Angular TypeScript — components, services, directives, pipes, guards, resolvers, interceptors, or any code that uses Angular's DI / signals / RxJS surface.
26
+
27
+ ## Version Awareness
28
+
29
+ Many of the patterns below (`inject()`, signal primitives, `linkedSignal`, `resource`, functional guards/interceptors, `takeUntilDestroyed`) are version-gated. Check `package.json` / `ng version` and use the patterns the installed version supports — do not introduce a v18+ API into a v15 project.
30
+
31
+ ## Sources
32
+
33
+ - Angular Style Guide — https://angular.dev/style-guide
34
+ - Angular Signals Guide — https://angular.dev/guide/signals
35
+ - Angular Dependency Injection Guide — https://angular.dev/guide/di
36
+ - RxJS Operators — https://rxjs.dev/guide/operators
37
+
38
+ ## Rules
39
+
40
+ ### Dependency injection
41
+
42
+ Use `inject()` over constructor injection in components, services, guards, resolvers, interceptors, and pipes. Keep constructors empty (or remove them) so DI calls live at field declarations next to their use.
43
+
44
+ ```typescript
45
+ @Injectable({ providedIn: 'root' })
46
+ export class UserService {
47
+ private http = inject(HttpClient);
48
+ private router = inject(Router);
49
+ }
50
+ ```
51
+
52
+ ```typescript
53
+ // Avoid on new code — verbose and harder to tree-shake/refactor
54
+ constructor(private http: HttpClient, private router: Router) {}
55
+ ```
56
+
57
+ Use `InjectionToken` for non-class dependencies (config, env, capability flags):
58
+
59
+ ```typescript
60
+ export const API_URL = new InjectionToken<string>('API_URL');
61
+
62
+ // In providers:
63
+ { provide: API_URL, useValue: 'https://api.example.com' }
64
+
65
+ // At call site:
66
+ private apiUrl = inject(API_URL);
67
+ ```
68
+
69
+ - Prefer `providedIn: 'root'` for app-wide singletons. Scope a service to a component/route only when its lifecycle must follow that subtree.
70
+ - `inject()` runs in an injection context. Calls outside a constructor/field initializer/factory need `runInInjectionContext` — do not stash `inject` calls inside arbitrary functions.
71
+
72
+ ### Signals
73
+
74
+ Use signals for reactive state. Three primitives:
75
+
76
+ ```typescript
77
+ count = signal(0); // writable
78
+ doubled = computed(() => this.count() * 2); // derived, read-only
79
+ selectedItem = linkedSignal(() => this.items()[0]); // writable derived: resets with source, also user-settable
80
+ ```
81
+
82
+ - Never duplicate derived data in a separate writable signal updated through `effect()` — use `computed` (or `linkedSignal` if it must also be writable).
83
+ - Bridge Observables to signals with `toSignal(stream$, { initialValue: ... })`. Do not assign Observable values into signals from manual `.subscribe()` callbacks.
84
+ - For async fetching backed by signals, prefer `resource({ request, loader })` on v19+ projects:
85
+
86
+ ```typescript
87
+ userResource = resource({
88
+ request: () => ({ id: this.userId() }),
89
+ loader: ({ request }) => fetch(`/api/users/${request.id}`).then(r => r.json()),
90
+ });
91
+ // Access: userResource.value(), userResource.isLoading(), userResource.error(), userResource.reload()
92
+ ```
93
+
94
+ #### `effect()` usage
95
+
96
+ Reserve `effect()` for genuine side effects that must react to signal changes — logging, third-party DOM libraries, telemetry. Never use `effect()` to keep two signals in sync; that is what `computed` / `linkedSignal` are for.
97
+
98
+ ```typescript
99
+ // Correct — side effect that must react
100
+ effect(() => analytics.track('user_changed', { id: this.user().id }));
101
+
102
+ // Wrong — derived state, use computed
103
+ effect(() => this.fullName.set(`${this.first()} ${this.last()}`));
104
+ ```
105
+
106
+ For DOM work that must run after the view renders, use `afterRenderEffect` (or `afterNextRender` for one-shot work).
107
+
108
+ ### Change detection
109
+
110
+ - Default to `ChangeDetectionStrategy.OnPush` on every new component.
111
+ - Drive view updates via signals or the `async` pipe. Avoid `ChangeDetectorRef.markForCheck()` / `detectChanges()` unless you have a narrow, documented reason (e.g., third-party callback outside the zone).
112
+ - Consider zoneless change detection (`provideExperimentalZonelessChangeDetection()` / `provideZonelessChangeDetection()` on supporting versions) only as a project-level decision, never as a side effect of a feature change.
113
+
114
+ ### RxJS
115
+
116
+ When the project still uses RxJS for streams alongside (or instead of) signals:
117
+
118
+ | Operator | Use for |
119
+ | --- | --- |
120
+ | `switchMap` | Search, navigation, latest-wins — cancels prior inner stream |
121
+ | `mergeMap` | Independent parallel inner streams |
122
+ | `exhaustMap` | Form submissions, idempotency — ignores new emissions until current completes |
123
+ | `concatMap` | Ordered queueing |
124
+
125
+ - Always handle errors with `catchError`. Never let a long-lived stream die silently and stop emitting.
126
+ - Use `takeUntilDestroyed(this.destroyRef)` for manual subscriptions in components/directives. Do not roll a manual `ngOnDestroy` + `Subject<void>` + `takeUntil` on new code.
127
+ - Prefer the `async` pipe in templates over manual subscribe + assign. If the data is needed in component logic too, expose it as a signal via `toSignal`.
128
+
129
+ ```typescript
130
+ search$ = this.query$.pipe(
131
+ debounceTime(300),
132
+ distinctUntilChanged(),
133
+ switchMap(q => this.service.search(q).pipe(catchError(() => of([])))),
134
+ );
135
+ ```
136
+
137
+ ### TypeScript strictness
138
+
139
+ - Run with `strict: true` and `strictTemplates: true` (Angular compiler option). Do not weaken either to make a change compile.
140
+ - Type every public surface: component inputs/outputs, service method signatures, route data, interceptor return types.
141
+ - Treat external data as `unknown` at the boundary. Narrow it with a validator (Zod, io-ts, hand-rolled type guard) before binding it to typed signals or components.
142
+ - Do not use `any`, `as any`, or non-null `!` to silence template type errors — fix the type or narrow the value.
143
+
144
+ ### File-level style
145
+
146
+ - One artifact per file. Co-locate template (`.html`) and styles (`.scss`/`.css`) with the component class.
147
+ - Imports ordered: Angular core/common, third-party, project absolute (`@/...`), project relative (`./...`). Match the project's existing convention if it differs.
148
+ - Member order inside a class: signal fields → injected dependencies → inputs/outputs → other state → constructor (if any) → lifecycle hooks → public methods → private methods. Adjust to project style if it has a stronger local convention.
149
+
150
+ ## Anti-patterns to refuse
151
+
152
+ - Constructor-injected dependencies on a project that has already adopted `inject()`.
153
+ - `effect()` used to synchronize signals.
154
+ - Manual `.subscribe()` in components without `takeUntilDestroyed`, or with a hand-rolled `destroy$` subject.
155
+ - `any` introduced to make typed templates compile, or `as any` casts on `HttpClient` responses.
156
+ - Calling `inject()` outside an injection context (e.g., inside an arbitrary helper function not invoked from a factory).
157
+
158
+ ## Evidence
159
+
160
+ For style-aligned changes, run the project's lint and typecheck:
161
+
162
+ - `ng lint` (or the project's lint script) on touched files
163
+ - `tsc --noEmit` or `ng build` to verify typed templates and the public TypeScript surface
164
+ - Targeted tests covering the changed behavior (see `rules/angular/testing.md`)
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: angular-components-and-templates
3
- description: Angular component and template rules for typed, maintainable Angular UI changes.
4
- version: 1.0.0
3
+ description: Concrete patterns for building modern Angular components, signal-based inputs/outputs, OnPush change detection, and v17+ control-flow templates.
4
+ version: 1.1.0
5
5
  required: false
6
6
  category: angular
7
7
  tools:
@@ -13,6 +13,9 @@ source_urls:
13
13
  - https://angular.dev/style-guide
14
14
  - https://angular.dev/guide/components
15
15
  - https://angular.dev/guide/templates
16
+ - https://angular.dev/guide/signals
17
+ - https://angular.dev/guide/components/inputs
18
+ - https://angular.dev/guide/components/outputs
16
19
  applies_to:
17
20
  - src/app/**/*.component.ts
18
21
  - src/app/**/*.component.html
@@ -22,31 +25,114 @@ applies_to:
22
25
 
23
26
  # Angular Components and Templates Rules
24
27
 
25
- **Load when:** Creating, changing, or reviewing Angular components, templates, component styles, inputs, outputs, lifecycle behavior, or view logic.
28
+ **Load when:** Creating, changing, or reviewing Angular components, templates, component styles, inputs/outputs, lifecycle behavior, or view logic.
26
29
 
27
- ## Sources
30
+ ## Version Awareness
31
+
32
+ Check the project's Angular version before writing code — APIs differ significantly between versions. Use `ng version` or inspect `package.json`. Match the project's existing component style first; do not migrate component styles as part of an unrelated change.
28
33
 
29
- Track source material explicitly so future updates can verify whether the rule is stale:
34
+ ## Sources
30
35
 
31
36
  - Angular Style Guide — https://angular.dev/style-guide
32
37
  - Angular Components Guide — https://angular.dev/guide/components
33
38
  - Angular Templates Guide — https://angular.dev/guide/templates
39
+ - Angular Signals Guide — https://angular.dev/guide/signals
34
40
 
35
41
  ## Rules
36
42
 
37
- - Match the project's existing Angular style first, then Angular's current guidance.
38
- - Prefer standalone components for new components unless the project is clearly NgModule-centered.
39
- - Keep components focused on presentation and UI coordination. Move reusable business logic to services, utilities, or state primitives already used by the project.
40
- - Keep templates declarative. Avoid hiding complex branching, transformations, or side effects inside template expressions.
41
- - Type component inputs, outputs, and public component methods explicitly when they form a contract with templates, parents, or tests.
42
- - Do not introduce `any` for template or component data. Narrow external data at the boundary before binding it.
43
- - Preserve existing accessibility patterns. New interactive elements need keyboard, label, and semantic HTML coverage appropriate to the component.
44
- - Avoid drive-by component rewrites, style-system changes, or broad file moves while making localized UI changes.
43
+ ### Component shape
44
+
45
+ - Prefer standalone components (v17+ default). Add `standalone: true` and declare template dependencies in `imports`. Do not mix standalone and NgModule registration for the same component.
46
+ - Default to `ChangeDetectionStrategy.OnPush` on all new components.
47
+ - Keep components focused on presentation and UI coordination. Move data access, business rules, and cross-component state to services or state primitives the project already uses.
48
+ - One artifact per file `user-card.component.ts`, `user-card.component.html`, `user-card.component.scss`, `user-card.component.spec.ts`.
49
+
50
+ ```typescript
51
+ @Component({
52
+ selector: 'app-user-card',
53
+ standalone: true,
54
+ imports: [RouterLink, DatePipe],
55
+ templateUrl: './user-card.component.html',
56
+ styleUrl: './user-card.component.scss',
57
+ changeDetection: ChangeDetectionStrategy.OnPush,
58
+ })
59
+ export class UserCardComponent {
60
+ user = input.required<User>();
61
+ highlight = input(false, { transform: booleanAttribute });
62
+ select = output<string>();
63
+
64
+ onClick() {
65
+ this.select.emit(this.user().id);
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Inputs and outputs
71
+
72
+ - On v17.1+ projects, prefer signal-based `input()` / `input.required()` / `output()` over decorators. Stay on `@Input()`/`@Output()` only if the project is uniformly decorator-based and the file you are editing already uses them.
73
+ - Type every input and output. Do not introduce `any` to make a binding compile — narrow the source data at the boundary instead.
74
+ - Use `input.required<T>()` when a parent must supply the value. Provide defaults (`input(false)`) only when the absence has a meaningful, documented behavior.
75
+ - Two-way binding uses `model()` (e.g., `value = model<string>('')`). Do not invent custom `xChange` outputs when `model()` covers the use case.
76
+
77
+ ### Templates (v17+ control flow)
78
+
79
+ Use the built-in block syntax. Always provide `track` in `@for`.
80
+
81
+ ```html
82
+ @if (isLoading()) {
83
+ <app-spinner />
84
+ } @else if (error()) {
85
+ <app-error [message]="error()" />
86
+ } @else {
87
+ @for (item of items(); track item.id) {
88
+ <app-item [item]="item" (select)="onSelect($event)" />
89
+ } @empty {
90
+ <p>No items found.</p>
91
+ }
92
+ }
93
+
94
+ @switch (status()) {
95
+ @case ('active') { <app-active /> }
96
+ @case ('pending') { <app-pending /> }
97
+ @default { <app-unknown /> }
98
+ }
99
+ ```
100
+
101
+ - Keep templates declarative. Move complex branching, transformations, or side effects into the component class, a `computed`, or a pure pipe.
102
+ - Call signals as functions in the template (`items()`, not `items`). Do not access `.value` on a signal — that is not the public API.
103
+ - Use the `async` pipe for Observables in templates rather than subscribing manually. Prefer converting to signals via `toSignal()` when possible.
104
+ - Use `@defer` for non-critical UI blocks (below the fold, behind interaction). Pair with `@placeholder`, `@loading`, and `@error` blocks.
105
+
106
+ ### Lifecycle and DOM access
107
+
108
+ - Use `inject()` over constructor injection. Keep constructors empty.
109
+ - For DOM work that must run after the view renders, prefer `afterRenderEffect` / `afterNextRender`. Do not reach into the DOM from `ngOnInit`.
110
+ - Use `viewChild()` / `viewChildren()` / `contentChild()` (signal queries) over the decorator forms on v17.2+ projects.
111
+ - Avoid `ngOnChanges` when an `input()` signal plus a `computed` or `effect` expresses the dependency directly.
112
+
113
+ ### Styles
114
+
115
+ - Keep `ViewEncapsulation.Emulated` (default). Use `ViewEncapsulation.None` only for design-system roots that intentionally bleed styles.
116
+ - Scope styles to the component file. Use `:host`, `:host-context`, and CSS custom properties for themeable values rather than global selectors.
117
+ - Preserve existing accessibility patterns. New interactive elements need keyboard support, programmatic labels, focus management, and semantic HTML appropriate to the component.
118
+
119
+ ### Change detection hygiene
120
+
121
+ - Signals and the `async` pipe drive change detection on `OnPush` components automatically. Do not reach for `ChangeDetectorRef.markForCheck()` or `detectChanges()` unless you have a documented, narrow reason.
122
+ - Do not mutate input objects in place when `OnPush` is in effect — produce new references.
123
+
124
+ ## Anti-patterns to refuse
125
+
126
+ - `@Component({ standalone: false })` on new components without an NgModule that already owns it.
127
+ - `ChangeDetectionStrategy.Default` on new components.
128
+ - Using `*ngIf` / `*ngFor` / `*ngSwitch` on projects that have adopted block syntax.
129
+ - Calling `.subscribe()` from a template, or storing computed/derived values in plain signals updated via `effect()`.
130
+ - Typing inputs/outputs as `any`, or bypassing the type system with `!` to silence template errors.
45
131
 
46
132
  ## Evidence
47
133
 
48
134
  For component behavior changes, include one of:
49
135
 
50
136
  - updated component/unit tests,
51
- - existing tests that cover the changed behavior,
52
- - or a stated reason tests are unavailable plus a lower-confidence fallback such as build/typecheck evidence.
137
+ - existing tests that already cover the changed behavior,
138
+ - or a stated reason tests are unavailable plus a lower-confidence fallback such as build/typecheck output (`ng build`, `tsc --noEmit`).