ワンクリックで
ngp-code-review
// Review changed code in the ng-primitives repo against project conventions. Use when the user asks for a code review, asks "review my changes", or asks if a PR/branch is ready. Do not invoke automatically before commits.
// Review changed code in the ng-primitives repo against project conventions. Use when the user asks for a code review, asks "review my changes", or asks if a PR/branch is ready. Do not invoke automatically before commits.
| name | ngp-code-review |
| description | Review changed code in the ng-primitives repo against project conventions. Use when the user asks for a code review, asks "review my changes", or asks if a PR/branch is ready. Do not invoke automatically before commits. |
Use this skill when reviewing changes on a branch or PR in this repo. The goal is a structured, file-by-file review against project conventions — not a generic code review.
git diff next...HEAD --stat, then git diff next...HEAD for content. (Main is next in this repo.) For a remote PR, use gh pr diff <num>.file:line locations for any finding.CONTRIBUTING.md and the PR template at .github/PULL_REQUEST_TEMPLATE.md (tests added, docs updated, issue linked, breaking-change disclosure).pnpm lint and nx test <project> — don't run them silently; let the user decide.Review only the changed lines, not the whole file. If a file is touched, do not raise issues with surrounding code that the diff did not modify. Pre-existing issues are out of scope — e.g. if an existing getter elsewhere in the file should be a computed() but the diff didn't touch it, leave it alone. Use git diff next...HEAD -- <file> to keep your focus tight.
Verify "unused" / "unreferenced" claims before raising them. Symbols can be referenced where a plain grep won't show: directive attribute selectors used in templates (ngpButton), CSS classes in styles, DI tokens, downstream barrel exports, generator templates under packages/tools/. Before flagging something as unused, grep the selector, check templates (*.html and inline), check index.ts. Time-box this to a couple of greps per claim. If you can't confirm in a quick check, soften the finding to "possibly unused — please verify".
Filter speculative findings. Drop any finding whose justification only kicks in under hypothetical future changes ("if someone later adds X…", "if this describe block grows…", "if this primitive ever supports Y…"). Only raise issues that affect the code as it stands today — i.e. there is a concrete current call site, test, or usage that demonstrates the problem. If a future-proofing concern is genuinely important, reframe it as a concrete present-day risk or drop it. This rule supersedes anything else in the checklist.
Before reviewing details, judge whether the PR is appropriately scoped. A large diff is fine when the work genuinely requires it — a new complex primitive with state, sub-directives, tests, and docs is expected to be big. A large diff is not fine when it's an accident of process. Flag:
The bar: every line in the diff should be defensible against "is this needed for the stated change?" If not, suggest pulling it into a separate PR or dropping it.
Read .claude/rules/angular-patterns.md and flag any violation. Common ones in PRs: decorator inputs/outputs/queries (should be signal forms), non-readonly signals, @internal JSDoc on private members.
Read .claude/rules/naming-conventions.md and flag any violation. Common ones: missing ngp selector prefix, Directive/Component/Service class suffix, .directive.ts file names, inputs without ngp-prefixed aliases, number/boolean inputs without NumberInput/BooleanInput + coercion.
Every primitive uses the same four exports from packages/ng-primitives/state/src/:
NgpXStateToken = createStateToken<NgpX>('X')provideXState = createStateProvider(NgpXStateToken)injectXState = createStateInjector<NgpX>(NgpXStateToken)xState = createState(NgpXStateToken)Flag new primitives that don't follow this quadruple. Specific rule violations (early state, missing generic, emitting state, reading raw input) are owned by §4.
The following live under tools/eslint-rules/rules/ and run automatically via pnpm lint. This table is the single source of truth for rule names — call them out by name in the review so the user knows why something is flagged.
| Rule | What to flag |
|---|---|
prefer-state | Direct this.input() access in stateful directives — use this.state.*. |
avoid-model | Any model() usage — use an explicit input/output pair. |
avoid-state-emit | Outputs that emit this.state.X — emit the local controlled value so parent bindings round-trip. |
avoid-early-state | State registration not at the end of the class property block. |
require-state-generic | createStateInjector() missing <T>. |
take-until-destroyed | takeUntilDestroyed from @angular/core/rxjs-interop — use safeTakeUntilDestroyed from ng-primitives/utils. |
rxjs-compat | Operators imported from rxjs — import from rxjs/operators. |
prefer-entrypoint-imports | Cross-package relative imports — use package aliases (ng-primitives/foo). |
prefer-document-from-common | DOCUMENT imported from @angular/core — import from @angular/common. |
Tests live next to source as *.test.ts and use vitest + @testing-library/angular (render, screen, userEvent, waitFor, fireEvent). Flag:
*.test.ts change — PR template requires tests.ApplicationRef, prefer fireEvent.input over userEvent.type to avoid NG0101 races.afterEach that closes them and waitFors their DOM removal before destroying the fixture. Portals attach to root, not the fixture, so fixture.destroy() alone leaks DOM.Element.prototype.getAnimations.{escape} — Escape fires immediate: true in capture phase, which races; use userEvent.click(document.body) instead.afterEach cleanup for overlays opened directly (e.g. document.querySelectorAll('[ngpTooltip]').forEach(el => el.remove())).Accessibility is first-class per CONTRIBUTING.md. Flag:
aria-* attributes, or tabindex.NgpOverlayRegistry — if a new overlay/portal primitive lets focus move outside a host element, verify focus-trap will treat it as an allowed external target.tabindex values that conflict with FocusMonitor / focus-trap logic.PR template requires docs updates for features and bug fixes. For new public behaviour:
apps/documentation/src/app/examples/<primitive>/.apps/documentation/src/app/pages/primitives/<primitive>/.nx g @ng-primitives/tools:example <name> --primitive <primitive> and nx g @ng-primitives/tools:documentation.feat(scope): …, fix(scope): …, docs(scope): …, chore(scope): …. Scope is usually the primitive name (combobox, focus-trap, tooltip).Closes #...), breaking change disclosed.Formatting is enforced by .prettierrc plus the Prettier plugins (@trivago/prettier-plugin-sort-imports, prettier-plugin-organize-attributes, prettier-plugin-tailwindcss). If something looks off, suggest pnpm format rather than nitpicking line-by-line.
Each finding is its own block. Group blocks under the file they belong to, ordered by severity (High → Medium → Low). Do not pack multiple findings into one bullet — one finding per block so they're easy to read and triage. Always cite file:line. Use real markdown — the structure below shows the literal shape (do not emit it as a code fence).
## Summary
<1-3 sentences: what the diff does and overall verdict>
## <path/to/file.ts>
### 🔴 [HIGH · 95%] <one-line title> — `file.ts:42`
<one or two sentences explaining the issue and why it matters today>
**Fix:**
\`\`\`ts
<short before/after or single-line replacement — only if it fits in ~10 lines>
\`\`\`
### 🟡 [MEDIUM · 80%] <title> — `file.ts:60`
<explanation>
(no fix block if the change isn't short enough to show inline — describe it in prose instead)
Always prefix the severity tag with its emoji so the reader can scan the review visually:
pnpm format. Mention briefly or omit.Express as a percentage (e.g. 85%) reflecting how sure you are the finding is real and the fix is correct, given the code as it stands today:
If a finding's confidence is below 70% and severity is LOW, drop it entirely — it's noise.
When the fix is short (≤10 lines, ideally ≤3), include a **Fix:** code block showing the suggested change. For longer or more invasive changes, describe the fix in prose instead — do not paste large diffs into the review.
Keep the review tight — fewer, sharper findings beat a long list.
Once the findings have been printed, follow this flow. Do not start fixing anything proactively — wait for the user to choose.
Use AskUserQuestion with a single question (header: "Fixes") offering these options. Skip severities that produced no findings — if there are no LOW findings, don't offer "Fix LOW". If there are no findings at all, skip this step entirely and go straight to step 3.
file:line or title).When the user picks a subset, apply only those fixes and report what changed. Do not bundle unrequested cleanups.
After applying any fixes, summarise in one or two sentences what was changed (cite file:line). Do not re-run the full review unless the user asks for it.
After fixes (or immediately, if the user chose "Skip fixes" or there were no findings), prompt the user about a PR.
First check for uncommitted changes with git status --porcelain. If there are any tracked changes that aren't committed:
AskUserQuestion (header: "Commit?"):
Do not auto-stage .env, credentials, or files outside the diff scope. Mention any such files in the prompt rather than including them.
Once the tree is clean (or already was), ask via AskUserQuestion (header: "Open PR?"):
next (this repo's main branch). Use the .github/PULL_REQUEST_TEMPLATE.md sections: PR Checklist, PR Type, linked issue, description, breaking-change disclosure. Confirm the title and body with the user before invoking gh pr create.Never push branches or open PRs without explicit user confirmation at this prompt.