@mrciphersmith/keryx 0.3.0 → 0.3.2
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 +4 -1
- package/dist/cli.js +13362 -7078
- package/dist/core.js +11706 -11330
- package/package.json +1 -1
- package/src/gdskills/bundled/agents/go-code-auditor.md +1 -1
- package/src/gdskills/bundled/agents/python-code-auditor.md +1 -1
- package/src/gdskills/bundled/install-manifest.json +271 -4
- package/src/gdskills/bundled/rules/core/model-selection.mdc +51 -0
- package/src/gdskills/bundled/skills/review/review-jev-comments/SKILL.md +184 -0
- package/src/gdskills/bundled/skills/review/review-jev-docs/SKILL.md +189 -0
- package/src/gdskills/bundled/skills/review/review-jev-risk/SKILL.md +190 -0
- package/src/gdskills/bundled/skills/review/review-jev-rules/SKILL.md +267 -0
- package/src/gdskills/bundled/skills/review/review-jev-scenarios/SKILL.md +187 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.detail.md +39 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/stacks/angular/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/angular/governance/eval.json +1751 -0
- package/src/gdskills/bundled/stacks/angular/governance/scout.json +32 -0
- package/src/gdskills/bundled/stacks/angular/pack.json +55 -0
- package/src/gdskills/bundled/stacks/angular/rules/coding-style.mdc +82 -0
- package/src/gdskills/bundled/stacks/angular/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/angular/rules/security.mdc +70 -0
- package/src/gdskills/bundled/stacks/angular/rules/testing.mdc +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/SKILL.md +127 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/SKILL.md +98 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-code-review/evals.json +73 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/SKILL.md +112 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/SKILL.md +102 -0
- package/src/gdskills/bundled/stacks/angular/skills/angular-testing/evals.json +71 -0
- package/src/gdskills/bundled/stacks/mobx/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/mobx/governance/eval.json +904 -0
- package/src/gdskills/bundled/stacks/mobx/governance/scout.json +18 -0
- package/src/gdskills/bundled/stacks/mobx/pack.json +28 -0
- package/src/gdskills/bundled/stacks/mobx/rules/coding-style.mdc +91 -0
- package/src/gdskills/bundled/stacks/mobx/rules/patterns.mdc +122 -0
- package/src/gdskills/bundled/stacks/mobx/rules/security.mdc +56 -0
- package/src/gdskills/bundled/stacks/mobx/rules/testing.mdc +63 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/SKILL.md +124 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-observable-testing/evals.json +73 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/SKILL.md +149 -0
- package/src/gdskills/bundled/stacks/mobx/skills/mobx-store-implementation/evals.json +74 -0
- package/src/gdskills/bundled/stacks/nestjs/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/eval.json +1308 -0
- package/src/gdskills/bundled/stacks/nestjs/governance/scout.json +34 -0
- package/src/gdskills/bundled/stacks/nestjs/pack.json +53 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/coding-style.mdc +70 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/patterns.mdc +83 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/security.mdc +73 -0
- package/src/gdskills/bundled/stacks/nestjs/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/SKILL.md +157 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-build-fix/evals.json +70 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/SKILL.md +129 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-implementation/evals.json +71 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/SKILL.md +143 -0
- package/src/gdskills/bundled/stacks/nestjs/skills/nestjs-testing/evals.json +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/eval.json +2413 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/pack.json +42 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/coding-style.mdc +69 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/patterns.mdc +88 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/security.mdc +72 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/rules/testing.mdc +64 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/SKILL.md +147 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-build-fix/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/SKILL.md +118 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-code-review/evals.json +76 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-implementation/evals.json +78 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/SKILL.md +116 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-testing/evals.json +75 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/SKILL.md +134 -0
- package/src/gdskills/bundled/stacks/nextjs-nuxt/skills/nextjs-nuxt-upgrade-migration/evals.json +76 -0
- package/src/gdskills/bundled/stacks/vue/agent-refs.json +4 -0
- package/src/gdskills/bundled/stacks/vue/governance/eval.json +2215 -0
- package/src/gdskills/bundled/stacks/vue/governance/scout.json +42 -0
- package/src/gdskills/bundled/stacks/vue/pack.json +42 -0
- package/src/gdskills/bundled/stacks/vue/rules/coding-style.mdc +73 -0
- package/src/gdskills/bundled/stacks/vue/rules/patterns.mdc +84 -0
- package/src/gdskills/bundled/stacks/vue/rules/security.mdc +60 -0
- package/src/gdskills/bundled/stacks/vue/rules/testing.mdc +69 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/SKILL.md +137 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-build-fix/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/SKILL.md +120 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-code-review/evals.json +71 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/SKILL.md +122 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-implementation/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/SKILL.md +115 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue-testing/evals.json +72 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/SKILL.md +135 -0
- package/src/gdskills/bundled/stacks/vue/skills/vue2-to-vue3-migration/evals.json +71 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-code-review
|
|
3
|
+
description: "Use when reviewing an Angular component/service/directive diff for framework-specific correctness -- untracked RxJS subscriptions missing takeUntilDestroyed/async-pipe cleanup, OnPush components mutating an @Input reference in place instead of replacing it, an inject() call made outside a valid Angular injection context (NG0203), DomSanitizer bypassSecurityTrust* calls on user-influenced data, and NgModule/standalone mixing. Read-only: reports findings only, never edits the reviewed files, and leaves plain TypeScript correctness and MobX-specific logic to their own reviewers. Not for implementing or fixing the reviewed diff (use angular-implementation or angular-build-fix)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "review this Angular component diff for RxJS subscription leaks"
|
|
6
|
+
- "check this Angular PR for OnPush change detection mistakes"
|
|
7
|
+
- "review this Angular service for DomSanitizer bypass usage"
|
|
8
|
+
- "audit this Angular component for inject() misuse"
|
|
9
|
+
- "review this standalone Angular component for signal anti-patterns"
|
|
10
|
+
- "check this Angular diff before merging"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: review
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Angular code review
|
|
20
|
+
|
|
21
|
+
Review an Angular component/service/directive/pipe diff for
|
|
22
|
+
framework-specific correctness issues. This skill only reports findings —
|
|
23
|
+
it never edits the reviewed code. See `rules/patterns.mdc` and
|
|
24
|
+
`rules/security.mdc` for the full rule set this review draws its checks
|
|
25
|
+
from.
|
|
26
|
+
|
|
27
|
+
## Scope
|
|
28
|
+
|
|
29
|
+
Covers Angular-specific concerns: change detection, signals, dependency
|
|
30
|
+
injection, RxJS lifecycle, and template sanitization. It does not repeat
|
|
31
|
+
generic TypeScript/Node review (unused variables, general async
|
|
32
|
+
correctness) already covered by the base `ts-js-node` reviewers, and it
|
|
33
|
+
does not review NestJS backend structure or MobX store logic — those have
|
|
34
|
+
their own reviewers.
|
|
35
|
+
|
|
36
|
+
## Workflow
|
|
37
|
+
|
|
38
|
+
### Step 1: Read the diff in full before forming an opinion
|
|
39
|
+
|
|
40
|
+
Read every changed file, not just the hunk markers — a subscription's
|
|
41
|
+
teardown, or the component's `changeDetection` setting, is often declared
|
|
42
|
+
outside the changed lines.
|
|
43
|
+
|
|
44
|
+
### Step 2: Check each category against `rules/patterns.mdc` and `rules/security.mdc`
|
|
45
|
+
|
|
46
|
+
- **RxJS lifecycle**: every `.subscribe()` added has a teardown path —
|
|
47
|
+
`takeUntilDestroyed()`, the `async` pipe, or an explicit `ngOnDestroy`
|
|
48
|
+
unsubscribe. Flag any subscription with none.
|
|
49
|
+
- **Change detection**: an `OnPush` component whose template depends on a
|
|
50
|
+
value that's mutated in place (a pushed array element, a mutated object
|
|
51
|
+
property) rather than replaced with a new reference/signal update — that
|
|
52
|
+
mutation won't trigger a re-check under `OnPush`.
|
|
53
|
+
- **Dependency injection**: `inject()` called anywhere outside a
|
|
54
|
+
constructor/field initializer (inside a callback, a `setTimeout`, a
|
|
55
|
+
plain method body without `runInInjectionContext`) — that throws at
|
|
56
|
+
runtime, not compile time, so it's easy to miss without reading the call
|
|
57
|
+
site.
|
|
58
|
+
- **Sanitization**: any `DomSanitizer.bypassSecurityTrust*` call — verify
|
|
59
|
+
the value it wraps is genuinely static/developer-authored, not traceable
|
|
60
|
+
to user input or an external API response; flag any that isn't
|
|
61
|
+
obviously safe.
|
|
62
|
+
- **Standalone/NgModule consistency**: a new `NgModule`-declared
|
|
63
|
+
component/directive/pipe added to a codebase that is otherwise
|
|
64
|
+
standalone (or vice versa) without a stated reason.
|
|
65
|
+
- **Signals**: a `computed()` whose callback has a side effect (an HTTP
|
|
66
|
+
call, a write to another signal) instead of staying pure.
|
|
67
|
+
|
|
68
|
+
### Step 3: Report findings
|
|
69
|
+
|
|
70
|
+
For each finding: file:line, what's wrong, why it matters (cite the
|
|
71
|
+
specific mechanism — e.g. "OnPush won't re-check on this mutation"), and
|
|
72
|
+
the concrete fix (not just "consider using X"). Group by severity: a
|
|
73
|
+
missing subscription teardown or a `bypassSecurityTrust*` on
|
|
74
|
+
user-influenced data is higher severity than a stylistic
|
|
75
|
+
`*ngIf`/`@if` inconsistency.
|
|
76
|
+
|
|
77
|
+
## Red Flags
|
|
78
|
+
|
|
79
|
+
| Rationalization | Why it is wrong |
|
|
80
|
+
|---|---|
|
|
81
|
+
| "The subscription probably gets garbage collected eventually, not worth flagging" | An Observable subscription holds a live reference until explicitly unsubscribed; it does not get cleaned up by GC just because the component view is gone |
|
|
82
|
+
| "This `bypassSecurityTrustHtml` call is fine, the string just looks like static markup" | If the string is built from or includes any user-influenced or API-sourced value, "looks static" isn't a security argument — trace where the value actually comes from before clearing it |
|
|
83
|
+
| "The mutation is inside an OnPush component but it 'usually still updates', so I won't flag it" | An in-place mutation not tracked by `OnPush` is a timing bug waiting to surface under different data/timing, not a hypothetical — flag it even if the current test data happens to trigger a coincidental re-check elsewhere |
|
|
84
|
+
| "I'll just fix the missing takeUntilDestroyed while I'm reviewing, it's a one-line change" | This skill reports findings only; even an obvious one-line fix goes in the report for the author (or angular-implementation) to apply, not applied directly during review |
|
|
85
|
+
|
|
86
|
+
## Verification
|
|
87
|
+
|
|
88
|
+
Before finishing the review, confirm:
|
|
89
|
+
|
|
90
|
+
- Every changed file with a `.subscribe()` call was checked for a
|
|
91
|
+
teardown path.
|
|
92
|
+
- Every `OnPush` component's template dependencies were checked for
|
|
93
|
+
in-place mutation.
|
|
94
|
+
- Every `inject()` call site was checked for being inside a valid
|
|
95
|
+
injection context.
|
|
96
|
+
- Every `bypassSecurityTrust*` call was checked against where its
|
|
97
|
+
argument value actually originates.
|
|
98
|
+
- No code was edited — the output is a findings report only.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"Before I merge this Angular component, can you check whether any of its .subscribe() calls are missing takeUntilDestroyed or another teardown?",
|
|
5
|
+
"Does this Angular pull request handle change detection correctly, or is it mutating an @Input array in place under OnPush?",
|
|
6
|
+
"This Angular service pipes some HTML through DomSanitizer's bypassSecurityTrustHtml — can you check whether that's actually safe here?",
|
|
7
|
+
"Take a look at where this Angular component calls inject() — are any of those calls happening outside a valid injection context?",
|
|
8
|
+
"In this standalone Angular component, does the way its computed() signal is written look like an anti-pattern worth flagging in review?",
|
|
9
|
+
"Check this Angular diff for mixing NgModule and standalone components before merge"
|
|
10
|
+
],
|
|
11
|
+
"negative": [
|
|
12
|
+
"Review this NestJS controller diff for DTO validation and guard usage",
|
|
13
|
+
"Review this MobX store diff for observable/action correctness",
|
|
14
|
+
"Review this React component diff for missing hook dependencies",
|
|
15
|
+
"Add retry and error handling to this Angular service's outgoing HTTP call chain",
|
|
16
|
+
"Write a TestBed spec asserting this Angular route guard blocks an unauthenticated user",
|
|
17
|
+
"Review this Node.js Express route diff for injection vulnerabilities"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scenarios": [
|
|
21
|
+
{
|
|
22
|
+
"id": "flag-subscription-without-teardown",
|
|
23
|
+
"prompt": "Reviewing this Angular component diff:\n\n```ts\nexport class OrderPanel implements OnInit {\n private readonly orders = inject(OrdersService);\n order?: Order;\n\n ngOnInit() {\n this.orders.watchOrder(this.orderId).subscribe(order => {\n this.order = order;\n });\n }\n}\n```\n\nWhat, if anything, should be flagged?",
|
|
24
|
+
"strictness": "high",
|
|
25
|
+
"expected_behavior": [
|
|
26
|
+
{
|
|
27
|
+
"grader": "judge",
|
|
28
|
+
"rubric": "A correct review flags the .subscribe() call in ngOnInit as having no teardown path -- no takeUntilDestroyed, no async pipe, and no ngOnDestroy unsubscribe -- and names the concrete fix (piping through takeUntilDestroyed(), or switching to the async pipe / toSignal()) rather than leaving the subscription open for the component's full lifetime.",
|
|
29
|
+
"pass_criteria": [
|
|
30
|
+
"Identifies that the .subscribe() call has no teardown/unsubscribe mechanism and flags it as a leak risk",
|
|
31
|
+
"Names a concrete fix: takeUntilDestroyed() piped into the subscription, converting to the async pipe, or using toSignal() -- not just 'add cleanup' in the abstract"
|
|
32
|
+
],
|
|
33
|
+
"fail_criteria": [
|
|
34
|
+
"Says the code has no issues worth flagging",
|
|
35
|
+
"Proposes fixing the subscription by editing the code directly instead of reporting it as a finding"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"calibration": {
|
|
40
|
+
"known_right": "Flag this: `this.orders.watchOrder(this.orderId).subscribe(...)` in ngOnInit has no teardown -- no takeUntilDestroyed(), no async pipe, no ngOnDestroy unsubscribe. As written, the subscription stays open for the component's entire lifetime and beyond if the observable is long-lived (a websocket-backed stream, a polling interval), which is a leak. Fix: pipe it through takeUntilDestroyed() (inject() is already called in a valid field-initializer context here, so takeUntilDestroyed() can be called the same way), e.g. `this.orders.watchOrder(this.orderId).pipe(takeUntilDestroyed()).subscribe(order => this.order = order);` -- or, since this is just mirroring an observable into a field for template use, convert it to `order = toSignal(this.orders.watchOrder(this.orderId))` and drop the manual subscription entirely.",
|
|
41
|
+
"known_wrong": "This looks fine -- the component subscribes once in ngOnInit and just updates a local field, nothing unusual there. No changes needed.",
|
|
42
|
+
"vague": "There might be a subscription cleanup concern here worth double-checking before merging.",
|
|
43
|
+
"subtle_wrong": "Worth a note: I'd suggest adding a comment above the subscribe() call documenting that it's expected to live for the component's full lifetime, so future readers know it's intentional rather than an oversight. That way the review concern is addressed without needing to change the subscription handling itself."
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "flag-onpush-inplace-mutation",
|
|
48
|
+
"prompt": "Reviewing this Angular diff. `CartList` is a child OnPush component; `CartPage` is its parent and passes `items` down as an `@Input`:\n\n```ts\n@Component({\n selector: 'app-cart-list',\n changeDetection: ChangeDetectionStrategy.OnPush,\n template: `@for (item of items; track item.id) { <div>{{ item.name }}</div> }`\n})\nexport class CartList {\n @Input() items: CartItem[] = [];\n}\n\n@Component({ selector: 'app-cart-page', template: `<app-cart-list [items]=\"items\" />` })\nexport class CartPage {\n items: CartItem[] = [];\n\n addItem(item: CartItem) {\n this.items.push(item);\n }\n}\n```\n\n`CartPage.addItem` runs (e.g. from a button click in `CartPage`'s own template), but `CartList`'s rendered list doesn't update. What should be flagged?",
|
|
49
|
+
"strictness": "high",
|
|
50
|
+
"expected_behavior": [
|
|
51
|
+
{
|
|
52
|
+
"grader": "judge",
|
|
53
|
+
"rubric": "A correct review flags that CartPage.addItem mutates the items array in place (push) rather than reassigning it, so the SAME array reference is still bound to CartList's @Input -- CartList is OnPush, and OnPush only re-checks a child on a new input reference (or its own event/async-pipe/signal), so a mutation of the same object the parent already passed down produces no reference change for the child to notice. Names the fix: the parent reassigns items to a new array (e.g. this.items = [...this.items, item]) or items is modeled as a signal.",
|
|
54
|
+
"pass_criteria": [
|
|
55
|
+
"States that `this.items.push(item)` in CartPage mutates the array in place, so the SAME object reference is still bound to CartList's `items` @Input -- object identity did not change",
|
|
56
|
+
"States that CartList is OnPush and only re-checks on a new @Input reference (or its own event/async-pipe/signal source), so the child's own re-render is what fails to happen here -- CartPage re-rendering itself (the click handler ran inside CartPage) does not, on its own, make CartList re-check",
|
|
57
|
+
"Names a concrete fix: CartPage reassigns items to a new array before/instead of mutating in place (e.g. `this.items = [...this.items, item];`), or items is modeled as a signal passed down and read via `input<CartItem[]>()`"
|
|
58
|
+
],
|
|
59
|
+
"fail_criteria": [
|
|
60
|
+
"Says the code is correct as-is with no OnPush-related concern",
|
|
61
|
+
"Claims reassigning items to itself (e.g. `this.items = this.items;`) forces CartList to update -- this is a no-op: the reference is identical to what it already was, so CartList's own @Input identity check has nothing new to see. (Spreading into a genuinely new array, e.g. `this.items = [...this.items, item]`, IS a real fix and must not be flagged here -- it produces an actual new reference.)"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
"calibration": {
|
|
66
|
+
"known_right": "Flag `CartPage.addItem`: `this.items.push(item)` mutates the array CartPage already passed to CartList's `items` @Input, so CartList still holds a reference to the SAME object -- its identity never changed. CartList is OnPush, and OnPush only re-checks a component on a new @Input reference, an event that happens inside that component itself, an async pipe emission, or a signal read -- none of which fired for CartList here; CartPage's own click handler running is CartPage's event, not CartList's. Fix by having CartPage reassign the reference: `this.items = [...this.items, item];`, or better, model `items` as a signal input (`items = input<CartItem[]>([]);` on CartList, updated via a signal on CartPage) so the template's `@for` tracks the signal directly.",
|
|
67
|
+
"known_wrong": "This is fine -- `items` is declared as a regular array field and push() is the normal way to add to it. OnPush doesn't need anything special here since CartList's template just iterates over items and Angular tracks array mutations automatically.",
|
|
68
|
+
"vague": "Something about how items get passed down here might not play well with OnPush -- worth a second look at how the child picks up the change.",
|
|
69
|
+
"subtle_wrong": "The real fix is simpler than restructuring the array: right after `this.items.push(item)`, just do `this.items = this.items;` in CartPage. Since Angular's input binding re-evaluates on the parent's own change detection pass, reassigning the field (even to itself) is enough to make CartList see it as a fresh binding and re-render."
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
]
|
|
73
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-implementation
|
|
3
|
+
description: "Use when implementing a new Angular component, service, directive, or pipe -- standalone component structure, signal-based (signal()/computed()/effect()) state, inject()-based dependency injection, and typed Reactive Forms. Wires RxJS-to-signal interop (toSignal/takeUntilDestroyed) correctly and follows the project's own routing conventions. Not for Vue/React component implementation, not for writing unit specs for an Angular component (use angular-testing), and not for repairing an already-failing Angular compiler error (use angular-build-fix)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "create a new standalone Angular component for"
|
|
6
|
+
- "add an Angular service with inject()"
|
|
7
|
+
- "implement this feature using Angular signals"
|
|
8
|
+
- "build a typed reactive form in Angular for"
|
|
9
|
+
- "wire up a new Angular component using inject() and signal state"
|
|
10
|
+
- "add an Angular route guard for"
|
|
11
|
+
- "convert this to a standalone Angular component"
|
|
12
|
+
metadata:
|
|
13
|
+
origin: authored
|
|
14
|
+
category: implement
|
|
15
|
+
version: "1.0.0"
|
|
16
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
17
|
+
license: "MIT"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
# Angular implementation
|
|
21
|
+
|
|
22
|
+
Implement a new Angular component, service, directive, or pipe using
|
|
23
|
+
current Angular idiom: standalone by default, signal-based state,
|
|
24
|
+
`inject()` for dependency injection, and `OnPush` change detection. See
|
|
25
|
+
`rules/coding-style.mdc` for naming/typing conventions and
|
|
26
|
+
`rules/patterns.mdc` for the change-detection and RxJS-interop patterns a
|
|
27
|
+
correct implementation should follow.
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Discover the project's own conventions
|
|
32
|
+
|
|
33
|
+
Before writing a new component/service, check:
|
|
34
|
+
|
|
35
|
+
- `angular.json` for the project's Angular version, builder, and whether
|
|
36
|
+
strict template type checking (`strictTemplates`) is on.
|
|
37
|
+
- A handful of existing components for: standalone vs. NgModule-based,
|
|
38
|
+
`*ngIf`/`*ngFor` vs. `@if`/`@for`, `inject()` vs. constructor injection,
|
|
39
|
+
signal inputs (`input()`) vs. `@Input()` decorators, and the file-suffix
|
|
40
|
+
convention (`.component.ts` vs. plain `.ts`).
|
|
41
|
+
- Whether the project uses a state-management library (NgRx, a signal
|
|
42
|
+
store) beyond plain component/service signals — match it rather than
|
|
43
|
+
introducing a second, competing pattern.
|
|
44
|
+
|
|
45
|
+
Match the project's existing convention even where it differs from the
|
|
46
|
+
newest Angular idiom described below, unless the task explicitly asks for
|
|
47
|
+
a migration.
|
|
48
|
+
|
|
49
|
+
### Step 2: Scaffold with the Angular CLI when available
|
|
50
|
+
|
|
51
|
+
Prefer `ng generate component <name>` / `ng generate service <name>` (or
|
|
52
|
+
the project's own schematics/collection) over hand-writing boilerplate —
|
|
53
|
+
it wires the file into `angular.json`'s conventions and produces a
|
|
54
|
+
standalone component by default on current Angular. Adjust the generated
|
|
55
|
+
file to match the task, don't leave placeholder content.
|
|
56
|
+
|
|
57
|
+
### Step 3: Design the component/service
|
|
58
|
+
|
|
59
|
+
- **State**: model changing state with `signal()`; derive values with
|
|
60
|
+
`computed()`; use `input()`/`model()` for component inputs (typed
|
|
61
|
+
explicitly), `output()` for events. Keep `computed()` pure — no HTTP
|
|
62
|
+
calls or writes to other signals inside it (`rules/patterns.mdc`).
|
|
63
|
+
- **DI**: use `inject()` at the field-initializer/constructor level for
|
|
64
|
+
every dependency, consistently within the class (`rules/coding-style.mdc`).
|
|
65
|
+
- **Change detection**: set `changeDetection: ChangeDetectionStrategy.OnPush`
|
|
66
|
+
on new components; with signal-based state this needs no extra wiring —
|
|
67
|
+
reading a signal in the template registers the dependency automatically.
|
|
68
|
+
- **RxJS boundaries**: when a dependency (HTTP, router events, a form's
|
|
69
|
+
`valueChanges`) is naturally an `Observable`, convert once with
|
|
70
|
+
`toSignal()` for template/computed use, or pipe a manual subscription
|
|
71
|
+
through `takeUntilDestroyed()` — never leave a subscription with no
|
|
72
|
+
teardown path.
|
|
73
|
+
- **Templates**: use `@if`/`@for` (with an explicit `track`)/`@switch`
|
|
74
|
+
unless the surrounding file already uses structural directives
|
|
75
|
+
throughout.
|
|
76
|
+
|
|
77
|
+
### Step 4: Forms (when the task involves user input)
|
|
78
|
+
|
|
79
|
+
Use typed Reactive Forms (`FormGroup<{...}>`/`FormControl<T>`) with
|
|
80
|
+
`ValidatorFn`s on the controls, per `rules/patterns.mdc` — not template-driven
|
|
81
|
+
forms or ad-hoc validation in a submit handler, unless the project's
|
|
82
|
+
existing forms are template-driven and the task is a small addition to one.
|
|
83
|
+
|
|
84
|
+
### Step 5: Verify
|
|
85
|
+
|
|
86
|
+
Run the project's own build/lint/test scripts (see Verification below).
|
|
87
|
+
Fix any new template type error or lint finding by correcting the actual
|
|
88
|
+
mismatch — do not loosen `strictTemplates` or add a suppression to make a
|
|
89
|
+
new component's own code pass.
|
|
90
|
+
|
|
91
|
+
## Red Flags
|
|
92
|
+
|
|
93
|
+
| Rationalization | Why it is wrong |
|
|
94
|
+
|---|---|
|
|
95
|
+
| "I'll just use `@Input()` with a plain field, signals are extra ceremony for one input" | A codebase that has otherwise moved to signal inputs (`input()`) gets fine-grained reactivity and `OnPush` compatibility for free; mixing the two styles in the same component is the actual added ceremony |
|
|
96
|
+
| "I'll call `inject()` inside this `setTimeout` callback, it's simpler than passing the service through" | `inject()` only works inside an injection context; calling it later throws at runtime, not compile time -- inject the dependency in the constructor/field initializer and capture it in a closure instead |
|
|
97
|
+
| "I'll subscribe directly and skip `takeUntilDestroyed`, the component doesn't live long anyway" | An unbounded subscription is a real leak the moment that assumption stops holding (a modal reused, a route revisited); tear it down explicitly every time |
|
|
98
|
+
| "I'll leave `ChangeDetectionStrategy.Default` since OnPush might miss an update" | With signal-reads in the template, `OnPush` tracks the real dependency automatically; `Default` just means the component re-checks on every application-wide change detection pass for no benefit |
|
|
99
|
+
|
|
100
|
+
## Verification
|
|
101
|
+
|
|
102
|
+
Do not report the implementation done until all of the following hold:
|
|
103
|
+
|
|
104
|
+
- `ng build` (or the project's build script) succeeds with no new template
|
|
105
|
+
type errors.
|
|
106
|
+
- The project's lint script (`ng lint` or its `eslint` equivalent) passes
|
|
107
|
+
on the new/changed files.
|
|
108
|
+
- Any new signal-based input/output is explicitly typed, and any
|
|
109
|
+
RxJS subscription created in the new code has an explicit teardown
|
|
110
|
+
(`takeUntilDestroyed`, `async` pipe, or `toSignal()`).
|
|
111
|
+
- `git status` shows changes confined to the component/service the task
|
|
112
|
+
asked for, plus any routing/module registration it genuinely needs.
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"I need a new component in our Angular app that lists a user's past orders",
|
|
5
|
+
"We need a service in our Angular app that wraps the orders HTTP endpoints -- how should it get HttpClient?",
|
|
6
|
+
"How should I hold this component's state in Angular so the template updates when it changes, without plain fields?",
|
|
7
|
+
"Build me a shipping-address form in Angular where every field is typed and validated",
|
|
8
|
+
"Only logged-in users should be able to open /account in our Angular app; anyone else goes back to /login",
|
|
9
|
+
"This component re-renders the whole tree on every parent change -- how do I make it only update when its own signal input actually changes?",
|
|
10
|
+
"This Angular component still lives in an NgModule's declarations array -- I want it to stand on its own"
|
|
11
|
+
],
|
|
12
|
+
"negative": [
|
|
13
|
+
"Create a new Vue component using the Composition API with <script setup>",
|
|
14
|
+
"Create a new React component that fetches and displays a user's order history",
|
|
15
|
+
"Write a TestBed spec that calls fixture.detectChanges and asserts the rendered template",
|
|
16
|
+
"Fix this ng build AOT compile error about a missing NgModule declaration",
|
|
17
|
+
"Review this Angular component's diff for RxJS subscription leaks",
|
|
18
|
+
"Fix this tsc type error in our Node.js backend service",
|
|
19
|
+
"Migrate this MobX store's observable state to use runInAction correctly"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"scenarios": [
|
|
23
|
+
{
|
|
24
|
+
"id": "signal-input-not-input-decorator",
|
|
25
|
+
"prompt": "I'm adding a new standalone Angular component that receives an `order: Order` value as an input. The rest of this codebase uses signals throughout (signal(), computed(), input()). How should I declare and read this input?",
|
|
26
|
+
"strictness": "high",
|
|
27
|
+
"expected_behavior": [
|
|
28
|
+
{
|
|
29
|
+
"grader": "judge",
|
|
30
|
+
"rubric": "A correct answer declares the input with the signal-based `input()` function (e.g. `order = input.required<Order>()` or `order = input<Order | null>(null)`), explicitly typed to Order, and reads it in the class/template by calling it as a function (`order()`), rather than declaring a plain class field decorated with `@Input()`.",
|
|
31
|
+
"pass_criteria": [
|
|
32
|
+
"Shows the actual declaration using `input()` or `input.required()` with an explicit `Order` type parameter, not just a statement that 'signal inputs should be used'",
|
|
33
|
+
"Shows reading the value by calling it as a function (e.g. `order()` in the template or class body), matching the signal-input convention"
|
|
34
|
+
],
|
|
35
|
+
"fail_criteria": [
|
|
36
|
+
"Declares the input as a plain class field with an `@Input()` decorator as the recommended implementation for this signals-based codebase -- naming `@Input()` only to contrast it with the signal-based approach is not a failure",
|
|
37
|
+
"Reads the declared signal input as a plain property access (`this.order`) instead of calling it as a function"
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
"calibration": {
|
|
42
|
+
"known_right": "Since the rest of the codebase already uses signals, declare the input as a signal input rather than a decorated field:\n\nexport class OrderSummary {\n order = input.required<Order>();\n}\n\nRead it by calling it, both in the template (`{{ order().total }}`) and in class code (`this.order().id`), the same way you'd read any other signal. If the input is genuinely optional, use `input<Order | null>(null)` instead of `.required()` so callers that omit it get an explicit typed default rather than `undefined` leaking through untyped. Avoid mixing this with an `@Input()` decorator on a plain field in the same component -- that would give the component two different ways to receive data and break the OnPush/signal-tracking benefit the rest of the codebase relies on.",
|
|
43
|
+
"known_wrong": "Just add a normal `@Input() order!: Order;` field like a typical Angular component -- it's simpler than the new signal syntax and Angular still supports it fine. Read it as `this.order` in the template and class code as usual; there's no real need to switch to `input()` just because other parts of the app use signals elsewhere.",
|
|
44
|
+
"vague": "Use the signal-based way of declaring inputs that matches how the rest of the codebase already handles component state, and read it consistently with the signal convention.",
|
|
45
|
+
"subtle_wrong": "I declared it with the newer syntax: `order = input<Order>();` without `.required()` or a default, and read it in the template as `order()!` with a non-null assertion since I know the parent always passes it. This keeps the signal style but skips `.required()` since the assertion is shorter to write, and I left the class-body access as `this.order` in one helper method since it read more naturally there without the parentheses."
|
|
46
|
+
},
|
|
47
|
+
"anti_patterns": ["@Input()"]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": "inject-inside-injection-context-only",
|
|
51
|
+
"prompt": "In an Angular component's ngOnInit, I need to call a service method inside a setTimeout callback. Should I call inject() right there inside the setTimeout callback to grab the service?",
|
|
52
|
+
"strictness": "high",
|
|
53
|
+
"expected_behavior": [
|
|
54
|
+
{
|
|
55
|
+
"grader": "judge",
|
|
56
|
+
"rubric": "A correct answer explains that inject() only works inside Angular's injection context (constructor or field initializer, or code wrapped in runInInjectionContext) and must not be called inside a setTimeout callback; instead the service should be injected once in the constructor or as a field initializer and the resulting reference captured in a closure for use inside the callback.",
|
|
57
|
+
"pass_criteria": [
|
|
58
|
+
"States that inject() must be called inside an injection context (constructor/field initializer), not inside a setTimeout callback, and that calling it there will fail",
|
|
59
|
+
"Shows or names an actual fix: either injecting the service as a class field/constructor (e.g. `private readonly orders = inject(OrdersService);`) before the setTimeout and referencing that already-injected variable inside the callback, OR wrapping the callback body in `runInInjectionContext(injector, () => { ... })` using an `Injector` that was itself captured in a valid injection context (e.g. `private readonly injector = inject(Injector);` in the constructor/field initializer) before the setTimeout"
|
|
60
|
+
],
|
|
61
|
+
"fail_criteria": [
|
|
62
|
+
"Recommends calling inject() inside the setTimeout callback as a working fix -- whether directly inline, or one level removed through a private helper method that is itself only ever invoked from inside the callback -- without actually injecting the service in the constructor/field initializer beforehand"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
"calibration": {
|
|
67
|
+
"known_right": "No -- inject() only works inside Angular's injection context, which is the constructor, a field initializer, or code explicitly wrapped in runInInjectionContext(). A setTimeout callback runs later, outside that context, so calling inject() there throws at runtime instead of returning the service. Inject the service once where the class is constructed and capture the reference in a field, then use that field inside the callback:\n\nexport class OrderPanel {\n private readonly orders = inject(OrdersService);\n\n ngOnInit() {\n setTimeout(() => {\n this.orders.refresh();\n }, 500);\n }\n}\n\nThe closure over `this.orders` is what makes the deferred call work, not calling inject() again inside the timeout.",
|
|
68
|
+
"known_wrong": "Sure, just call inject(OrdersService) inside the setTimeout callback right where you need it -- that keeps the dependency close to where it's used instead of adding another field to the class. Angular's DI will resolve it fine whenever the callback runs.",
|
|
69
|
+
"vague": "You shouldn't call inject() just anywhere -- it needs to happen at the right time in the component's lifecycle, so grab the service earlier instead of inside the callback.",
|
|
70
|
+
"subtle_wrong": "Calling inject() directly inline inside the setTimeout callback throws, but you can fix that by pulling it into its own private method instead: define `private getOrders() { return inject(OrdersService); }` on the class, then call `this.getOrders().refresh()` from inside the setTimeout callback. Since the inject() call now lives in a named method rather than the callback body itself, it's no longer really \"inside\" the callback, so it resolves normally."
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-testing
|
|
3
|
+
description: "Use when writing or fixing a TestBed spec for an Angular component, service, directive, pipe, or route guard -- configuring TestBed.configureTestingModule, driving a ComponentFixture (componentRef.setInput, detectChanges, whenStable), mocking HttpClient with HttpTestingController, testing a CanActivate/CanMatch guard function, and using fakeAsync/tick for deterministic async specs. Not for Vue/React component tests, not for a full user-flow check against a deployed, running instance, and not for implementing the component/service under test (use angular-implementation)."
|
|
4
|
+
triggers:
|
|
5
|
+
- "write a TestBed spec for this Angular component"
|
|
6
|
+
- "test this Angular service's HTTP calls with HttpTestingController"
|
|
7
|
+
- "add a spec asserting this Angular component's rendered DOM"
|
|
8
|
+
- "write a fakeAsync test for this Angular component"
|
|
9
|
+
- "test this Angular CanActivate route guard's logic"
|
|
10
|
+
- "mock this Angular component's injected service in a spec"
|
|
11
|
+
metadata:
|
|
12
|
+
origin: authored
|
|
13
|
+
category: test
|
|
14
|
+
version: "1.0.0"
|
|
15
|
+
compatible_harnesses: "claude,codex,cursor,zed,opencode"
|
|
16
|
+
license: "MIT"
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
# Angular testing (TestBed)
|
|
20
|
+
|
|
21
|
+
Write or fix a unit test for an Angular component, service, directive, or
|
|
22
|
+
pipe using `TestBed`. See `rules/testing.mdc` for layout, fixture, and
|
|
23
|
+
determinism conventions; this skill is the step-by-step workflow for
|
|
24
|
+
applying them to a specific component or service under test.
|
|
25
|
+
|
|
26
|
+
## Workflow
|
|
27
|
+
|
|
28
|
+
### Step 1: Discover the project's test setup
|
|
29
|
+
|
|
30
|
+
- Check `angular.json`'s `test` builder and `package.json`'s `test` script
|
|
31
|
+
to confirm the actual runner (`ng test`/Karma, or a migrated Jest/Vitest
|
|
32
|
+
setup) before assuming Karma.
|
|
33
|
+
- Look at an existing `.spec.ts` in the same directory/module for the
|
|
34
|
+
project's own conventions: how it configures `TestBed`, whether it uses
|
|
35
|
+
`HttpTestingController` or a hand-rolled HTTP mock, and its
|
|
36
|
+
`fakeAsync`/`waitForAsync` usage.
|
|
37
|
+
|
|
38
|
+
### Step 2: Configure TestBed for the unit under test
|
|
39
|
+
|
|
40
|
+
- For a standalone component: `TestBed.configureTestingModule({ imports:
|
|
41
|
+
[MyComponent, ...anySupportingImports] })` -- standalone components are
|
|
42
|
+
imported, not declared.
|
|
43
|
+
- Provide test doubles for injected dependencies via `providers` (a mock
|
|
44
|
+
object, a `jasmine.createSpyObj`, or `provideHttpClientTesting()` for
|
|
45
|
+
`HttpClient`-backed services) rather than letting the real service reach
|
|
46
|
+
a real network/backend.
|
|
47
|
+
- `TestBed.createComponent(MyComponent)` to get the `ComponentFixture`;
|
|
48
|
+
keep a reference to `fixture.componentInstance` for direct assertions on
|
|
49
|
+
class state.
|
|
50
|
+
|
|
51
|
+
### Step 3: Drive the fixture correctly
|
|
52
|
+
|
|
53
|
+
- Set a signal-based input with `fixture.componentRef.setInput('name',
|
|
54
|
+
value)` -- never by assigning the component instance's field directly,
|
|
55
|
+
since that skips Angular's actual input-binding path.
|
|
56
|
+
- Call `fixture.detectChanges()` after any state change that should affect
|
|
57
|
+
the DOM, and `await fixture.whenStable()` after anything asynchronous
|
|
58
|
+
(a resolved promise, a flushed HTTP request) before asserting on
|
|
59
|
+
rendered markup.
|
|
60
|
+
- Query with `fixture.debugElement.query(By.css(...))` or
|
|
61
|
+
`fixture.nativeElement.querySelector(...)`, preferring a stable selector
|
|
62
|
+
(`data-testid`, semantic role/label) over a styling-only CSS class.
|
|
63
|
+
|
|
64
|
+
### Step 4: HTTP and async determinism
|
|
65
|
+
|
|
66
|
+
- For a service/component that calls `HttpClient`, provide
|
|
67
|
+
`provideHttpClientTesting()` and assert with `HttpTestingController`:
|
|
68
|
+
`httpTestingController.expectOne(url).flush(mockResponse)`. Call
|
|
69
|
+
`httpTestingController.verify()` at the end of the test (or in an
|
|
70
|
+
`afterEach`) to catch an unexpected extra request.
|
|
71
|
+
- Wrap a test that depends on timers/microtasks in `fakeAsync` and advance
|
|
72
|
+
time with `tick(<exact ms>)` (matching the real delay the code under
|
|
73
|
+
test uses) rather than a real `setTimeout` in the spec -- a real timer
|
|
74
|
+
makes the test's runtime and reliability depend on wall-clock timing.
|
|
75
|
+
|
|
76
|
+
### Step 5: Verify
|
|
77
|
+
|
|
78
|
+
Run the project's actual test command and confirm the new/changed spec
|
|
79
|
+
passes without weakening an existing assertion.
|
|
80
|
+
|
|
81
|
+
## Red Flags
|
|
82
|
+
|
|
83
|
+
| Rationalization | Why it is wrong |
|
|
84
|
+
|---|---|
|
|
85
|
+
| "I'll just set `component.order = mockOrder` directly instead of `setInput`" | Direct field assignment on a signal input skips Angular's input-binding path; a real caller's binding wouldn't reach the component that way, so the test can pass while the actual binding is broken |
|
|
86
|
+
| "I'll skip `httpTestingController.verify()`, the test already checked the response I care about" | `verify()` is what catches an unexpected *extra* request (e.g. a duplicated HTTP call from a bug); skipping it lets that regression through silently |
|
|
87
|
+
| "I'll use a real `setTimeout(..., 1000)` in the test instead of `fakeAsync`/`tick`" | A real timer makes the test's speed and reliability depend on wall-clock timing and CI load; `tick()` advances virtual time deterministically |
|
|
88
|
+
| "This assertion fails right after `setInput`, I'll just delete it since the feature clearly still works manually" | The assertion is failing because `detectChanges()`/`whenStable()` wasn't called before it, not because the feature is broken -- fix the missing call, don't delete the check |
|
|
89
|
+
|
|
90
|
+
## Verification
|
|
91
|
+
|
|
92
|
+
Do not report the test done until all of the following hold:
|
|
93
|
+
|
|
94
|
+
- The project's actual test command (`ng test` or its configured
|
|
95
|
+
equivalent) passes, including the new/changed spec.
|
|
96
|
+
- Every signal-based input in the test is set via
|
|
97
|
+
`fixture.componentRef.setInput(...)`, not direct field assignment.
|
|
98
|
+
- Every `HttpClient` call the test exercises goes through
|
|
99
|
+
`HttpTestingController`, and `verify()` runs (no unexpected leftover
|
|
100
|
+
request).
|
|
101
|
+
- No existing assertion was weakened, skipped, or deleted to make the
|
|
102
|
+
suite pass.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"triggers": {
|
|
3
|
+
"positive": [
|
|
4
|
+
"This Angular component reads its data through a signal input — what's the right TestBed setup to cover it with tests?",
|
|
5
|
+
"This Angular service fires off a couple of HTTP requests — how do I mock them out with HttpTestingController for a proper spec?",
|
|
6
|
+
"I need a fakeAsync TestBed spec for this Angular component that proves its debounced search waits the full delay before calling the service — how do I structure the tick() calls?",
|
|
7
|
+
"Test this standalone Angular component's OnPush rendering when the input changes",
|
|
8
|
+
"In a TestBed spec for this component, what's the right way to swap out its injected OrdersService for a mock/fake?",
|
|
9
|
+
"What's the best way to unit test whether this route guard's CanActivate correctly blocks a logged-out user, in Angular?"
|
|
10
|
+
],
|
|
11
|
+
"negative": [
|
|
12
|
+
"Write Vitest component tests for this Vue component using Vue Test Utils",
|
|
13
|
+
"Write React Testing Library tests for this component's rendered list",
|
|
14
|
+
"Implement a new standalone Angular component that shows order history",
|
|
15
|
+
"Fix this ng build AOT compile error about a missing provider",
|
|
16
|
+
"Write a Playwright e2e test for the login flow in our Angular app",
|
|
17
|
+
"Write unit tests for this Go struct's JSON marshaling using the standard testing package"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scenarios": [
|
|
21
|
+
{
|
|
22
|
+
"id": "setinput-not-direct-assignment",
|
|
23
|
+
"prompt": "I'm writing a TestBed spec for a standalone Angular component that has a signal input declared as `order = input.required<Order>();`. In the test, should I just set `component.order = someMockOrder` before calling detectChanges?",
|
|
24
|
+
"strictness": "high",
|
|
25
|
+
"expected_behavior": [
|
|
26
|
+
{
|
|
27
|
+
"grader": "judge",
|
|
28
|
+
"rubric": "A correct answer explains that a signal input must be set through fixture.componentRef.setInput('order', mockOrder), not by directly assigning the component instance's field, because direct assignment bypasses Angular's actual input-binding mechanism and would not reflect what a real caller's template binding does; it should also call fixture.detectChanges() afterward before asserting on the DOM.",
|
|
29
|
+
"pass_criteria": [
|
|
30
|
+
"States directly that assigning `component.order = mockOrder` does not go through Angular's real input-binding path and should not be used",
|
|
31
|
+
"Shows the correct call: `fixture.componentRef.setInput('order', mockOrder)` before `fixture.detectChanges()`"
|
|
32
|
+
],
|
|
33
|
+
"fail_criteria": [
|
|
34
|
+
"Recommends direct field assignment (`component.order = mockOrder`) as a working way to set the input for the test"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"calibration": {
|
|
39
|
+
"known_right": "No -- don't assign `component.order` directly. That field is a signal created by `input.required<Order>()`, and assigning over it doesn't go through Angular's input-binding machinery the way a real caller's template binding would; it also just overwrites the signal reference rather than setting its value through the framework. Use the fixture's binding API instead:\n\nfixture.componentRef.setInput('order', mockOrder);\nfixture.detectChanges();\n\nThat's the same mechanism a real parent template's `[order]=\"someOrder\"` binding goes through, so the test exercises the actual input path, and `detectChanges()` after it makes sure the template re-renders before you assert on the DOM.",
|
|
40
|
+
"known_wrong": "Sure, that's fine -- just do `component.order = someMockOrder;` then `fixture.detectChanges();`. It's the simplest way to get a value into the component for the test and detectChanges() will pick it up either way.",
|
|
41
|
+
"vague": "You shouldn't set inputs directly on the component instance in a test -- use the fixture's proper way of setting inputs instead so it behaves like a real binding.",
|
|
42
|
+
"subtle_wrong": "I'd avoid touching `component.order` directly, so instead I re-declared the field in the test by doing `(component as any).order = signal(mockOrder);`, replacing the whole input signal with a new one I control, then called `fixture.detectChanges()`. This keeps the test off the plain-assignment anti-pattern since it's still a signal, just one I constructed myself instead of going through Angular's binding."
|
|
43
|
+
},
|
|
44
|
+
"anti_patterns": ["component.order ="]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "http-testing-controller-verify",
|
|
48
|
+
"prompt": "I'm testing an Angular service method that calls HttpClient.get() and returns the parsed response. I've set up HttpTestingController and flushed a mock response, and the assertion on the returned value passes. Is that test complete?",
|
|
49
|
+
"strictness": "high",
|
|
50
|
+
"expected_behavior": [
|
|
51
|
+
{
|
|
52
|
+
"grader": "judge",
|
|
53
|
+
"rubric": "A correct answer says the test is not complete until httpTestingController.verify() is called (directly or in an afterEach), because expectOne/flush alone confirm the expected request happened but do not catch an unexpected additional HTTP request the code under test might also be making.",
|
|
54
|
+
"pass_criteria": [
|
|
55
|
+
"States that httpTestingController.verify() must be called (e.g. in afterEach or at the end of the test) to catch any unexpected outstanding/extra request",
|
|
56
|
+
"Explains that verify() checks for requests beyond the one already asserted with expectOne, not just re-checking the one that was flushed"
|
|
57
|
+
],
|
|
58
|
+
"fail_criteria": [
|
|
59
|
+
"Says the test is already complete once the flushed response's assertion passes, with no mention of verify()"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"calibration": {
|
|
64
|
+
"known_right": "Not quite -- asserting on the flushed response only proves the one request you called expectOne on returned what you expected. It doesn't catch a bug where the method under test also fires a second, unexpected HTTP call (a duplicated request, a stray retry). Add httpTestingController.verify() -- typically in an afterEach so every test in the file is covered -- which fails the test if there's any outstanding or unmatched request left over. That's the check that actually proves the method made exactly the request you expected and nothing else.",
|
|
65
|
+
"known_wrong": "Yes, that's complete -- you flushed the mock response and the assertion on the parsed value passed, so the HTTP call and the parsing logic are both verified. No need to add anything else.",
|
|
66
|
+
"vague": "Not fully -- there's usually one more step with HttpTestingController to make sure everything about the request is properly checked before calling it done.",
|
|
67
|
+
"subtle_wrong": "Close, but I'd also add a second `httpMock.expectOne(url)` call right after the first flush just to double-check the same request is still tracked correctly, then flush it again with the same mock response. That extra check makes sure the request handling is solid without needing a separate verify() step in afterEach."
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"agents": [],
|
|
3
|
+
"note": "No generated agent pair by design, matching W2's original decision: mobx has no agentProfile in pack.json, so `agents generate --stack mobx` is refused (agents-catalog-commands.test.ts pins this). code-mobx-store-review already covers MobX-specific review, and ts-js-node/react build-fix already cover the build-failure case, so a generated mobx-code-auditor/mobx-build-fixer pair would duplicate existing coverage rather than add any -- this holds regardless of gate status. Separately (R1 review round 2, PR #719, N-M1): the same realism sweep that demoted angular found mobx-store-implementation's positives were trigger-prefix near-copies too; rewritten with natural phrasing, not iterated against the router. Honest result: mobx-store-implementation's trigger accuracy dropped (TP 4/8), accepted as the true signal. mobx-observable-testing still passes cleanly. mobx's own pack.json stability is demoted to \"experimental\" -- a pack needs every skill to pass."
|
|
4
|
+
}
|