Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac20cc63b6 | ||
|
|
e1c84cd8f4 | ||
|
|
73e51321ca | ||
|
|
eca46228ed | ||
|
|
aa164fac16 | ||
|
|
6247fcefab | ||
|
|
b46e45fb4d | ||
|
|
8839ed1b2d | ||
|
|
5ba1fe9a97 | ||
|
|
85f26eb186 | ||
|
|
0fc520c7fe | ||
|
|
7f5ca000bd | ||
|
|
679eb72d23 | ||
|
|
2b3277c066 | ||
|
|
850c940dfd | ||
|
|
1bc750e4a1 | ||
|
|
84a41851e0 | ||
|
|
6d94cf984c | ||
|
|
129949ddf0 | ||
|
|
f893807051 | ||
|
|
9040899e65 | ||
|
|
b29fa15bf3 | ||
|
|
b05245e68b | ||
|
|
49e01dd216 | ||
|
|
460a8432a5 | ||
|
|
878b876475 | ||
|
|
189c0824d2 | ||
|
|
67e7e998f8 | ||
|
|
4432e60445 | ||
|
|
b71eff117b | ||
|
|
991de2de2f | ||
|
|
73ceec4e7d | ||
|
|
263c507684 | ||
|
|
afc06d1af6 | ||
|
|
988f528708 | ||
|
|
3f7dce00b8 | ||
|
|
3d6c75d37f | ||
|
|
2070a91ef7 | ||
|
|
17505fe683 | ||
|
|
f9f4375e4e | ||
|
|
637c20f3c3 | ||
|
|
72f5b9500d | ||
|
|
bedfbb5c1c | ||
|
|
318199e9b3 | ||
|
|
cafa9d5c52 | ||
|
|
963e4660c8 | ||
|
|
49f1bf1335 | ||
|
|
d6eefe200d | ||
|
|
41717e78db | ||
|
|
2a016df011 | ||
|
|
386b3c757e | ||
|
|
3d79501eba | ||
|
|
7e24ed2568 | ||
|
|
418982eb85 | ||
|
|
335359f138 | ||
|
|
4ec91e9fbe |
15
.github/ISSUE_49_COMMENT.md
vendored
Normal file
15
.github/ISSUE_49_COMMENT.md
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
Suggested comment for [Issue #49](https://github.com/sickn33/antigravity-awesome-skills/issues/49). Paste this on the issue:
|
||||
|
||||
---
|
||||
|
||||
The 404 happens because the package wasn’t published to npm yet. We’ve addressed it in two ways:
|
||||
|
||||
1. **Publish to npm** – We’re set up to publish so `npx antigravity-awesome-skills` will work after the first release. You can also trigger a manual publish via the “Publish to npm” workflow (Actions tab) if you have `NPM_TOKEN` configured.
|
||||
|
||||
2. **Fallback** – Until then (or if you hit a 404 for any reason), use:
|
||||
```bash
|
||||
npx github:sickn33/antigravity-awesome-skills
|
||||
```
|
||||
The README, GETTING_STARTED, and FAQ now mention this fallback.
|
||||
|
||||
Thanks for reporting.
|
||||
12
.github/MAINTENANCE.md
vendored
12
.github/MAINTENANCE.md
vendored
@@ -173,12 +173,22 @@ When cutting a new version (e.g., V4):
|
||||
|
||||
1. **Run Full Validation**: `python3 scripts/validate_skills.py --strict`
|
||||
2. **Update Changelog**: Add the new release section to `CHANGELOG.md`.
|
||||
3. **Bump Version**: Update header in `README.md`.
|
||||
3. **Bump Version**:
|
||||
- Update `package.json` → `"version": "X.Y.Z"` (source of truth for npm).
|
||||
- Update version header in `README.md` if it displays the number.
|
||||
- One-liner: `npm version patch` (or `minor`/`major`) — bumps `package.json` and creates a git tag; then amend if you need to tag after release.
|
||||
4. **Tag Release**:
|
||||
```bash
|
||||
git tag -a v4.0.0 -m "V4 Enterprise Edition"
|
||||
git push origin v4.0.0
|
||||
```
|
||||
5. **Publish to npm** (so `npx antigravity-awesome-skills` works):
|
||||
- **Option A (manual):** From repo root, with npm logged in and 2FA/token set up:
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
You cannot republish the same version; always bump `package.json` before publishing.
|
||||
- **Option B (CI):** On GitHub, create a **Release** (tag e.g. `v4.6.1`). The workflow [Publish to npm](.github/workflows/publish-npm.yml) runs on **Release published** and runs `npm publish` if the repo secret `NPM_TOKEN` is set (npm → Access Tokens → Granular token with Publish, then add as repo secret `NPM_TOKEN`).
|
||||
|
||||
### 📋 Changelog Entry Template
|
||||
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@@ -45,6 +45,13 @@ jobs:
|
||||
- name: Install npm dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Audit npm dependencies
|
||||
run: npm audit --audit-level=high
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
||||
|
||||
- name: 📦 Build catalog
|
||||
run: npm run catalog
|
||||
|
||||
|
||||
28
.github/workflows/publish-npm.yml
vendored
Normal file
28
.github/workflows/publish-npm.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Publish antigravity-awesome-skills to npm on release.
|
||||
# Requires NPM_TOKEN secret (npm → Access Tokens → Granular token with Publish).
|
||||
# Before creating a Release: bump package.json "version" (npm forbids republishing the same version).
|
||||
# Release tag (e.g. v4.6.1) should match package.json version.
|
||||
|
||||
name: Publish to npm
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: "20"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
|
||||
- name: Publish
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node_modules/
|
||||
__pycache__/
|
||||
.worktrees/
|
||||
|
||||
walkthrough.md
|
||||
.agent/rules/
|
||||
@@ -24,3 +26,6 @@ scripts/*voltagent*.py
|
||||
scripts/*html*.py
|
||||
scripts/*similar*.py
|
||||
scripts/*count*.py
|
||||
|
||||
# Optional baseline for legacy JS validator (scripts/validate-skills.js)
|
||||
validation-baseline.json
|
||||
|
||||
34
CATALOG.md
34
CATALOG.md
@@ -1,13 +1,15 @@
|
||||
# Skill Catalog
|
||||
|
||||
Generated at: 2026-02-01T07:22:00.871Z
|
||||
Generated at: 2026-02-04T07:52:07.897Z
|
||||
|
||||
Total skills: 619
|
||||
Total skills: 631
|
||||
|
||||
## architecture (59)
|
||||
## architecture (62)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
| `angular` | Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIV... | angular | angular, v20, deep, knowledge, signals, standalone, components, zoneless, applications, ssr, hydration, reactive |
|
||||
| `angular-state-management` | Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solu... | angular, state | angular, state, signals, ngrx, rxjs, setting, up, global, managing, component, stores, choosing |
|
||||
| `architect-review` | Master software architect specializing in modern architecture patterns, clean architecture, microservices, event-driven systems, and DDD. Reviews system desi... | | architect, review, software, specializing, architecture, clean, microservices, event, driven, ddd, reviews, designs |
|
||||
| `architecture` | Architectural decision-making framework. Requirements analysis, trade-off evaluation, ADR documentation. Use when making architecture decisions or analyzing ... | architecture | architecture, architectural, decision, making, framework, requirements, analysis, trade, off, evaluation, adr, documentation |
|
||||
| `architecture-decision-records` | Write and maintain Architecture Decision Records (ADRs) following best practices for technical decision documentation. Use when documenting significant techn... | architecture, decision, records | architecture, decision, records, write, maintain, adrs, following, technical, documentation, documenting, significant, decisions |
|
||||
@@ -36,6 +38,7 @@ Total skills: 619
|
||||
| `error-handling-patterns` | Master error handling patterns across languages including exceptions, Result types, error propagation, and graceful degradation to build resilient applicatio... | error, handling | error, handling, languages, including, exceptions, result, types, propagation, graceful, degradation, resilient, applications |
|
||||
| `event-sourcing-architect` | Expert in event sourcing, CQRS, and event-driven architecture patterns. Masters event store design, projection building, saga orchestration, and eventual con... | event, sourcing | event, sourcing, architect, cqrs, driven, architecture, masters, store, projection, building, saga, orchestration |
|
||||
| `event-store-design` | Design and implement event stores for event-sourced systems. Use when building event sourcing infrastructure, choosing event store technologies, or implement... | event, store | event, store, stores, sourced, building, sourcing, infrastructure, choosing, technologies, implementing, persistence |
|
||||
| `game-development/multiplayer` | Multiplayer game development principles. Architecture, networking, synchronization. | game, development/multiplayer | game, development/multiplayer, multiplayer, development, principles, architecture, networking, synchronization |
|
||||
| `godot-gdscript-patterns` | Master Godot 4 GDScript patterns including signals, scenes, state machines, and optimization. Use when building Godot games, implementing game systems, or le... | godot, gdscript | godot, gdscript, including, signals, scenes, state, machines, optimization, building, games, implementing, game |
|
||||
| `haskell-pro` | Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programm... | haskell | haskell, pro, engineer, specializing, type, pure, functional, high, reliability, software, proactively, level |
|
||||
| `i18n-localization` | Internationalization and localization patterns. Detecting hardcoded strings, managing translations, locale files, RTL support. | i18n, localization | i18n, localization, internationalization, detecting, hardcoded, strings, managing, translations, locale, files, rtl |
|
||||
@@ -120,6 +123,7 @@ Total skills: 619
|
||||
| `ai-engineer` | Build production-ready LLM applications, advanced RAG systems, and intelligent agents. Implements vector search, multimodal AI, agent orchestration, and ente... | ai | ai, engineer, llm, applications, rag, intelligent, agents, implements, vector, search, multimodal, agent |
|
||||
| `ai-wrapper-product` | Expert in building products that wrap AI APIs (OpenAI, Anthropic, etc.) into focused tools people will pay for. Not just 'ChatGPT but different' - products t... | ai, wrapper, product | ai, wrapper, product, building, products, wrap, apis, openai, anthropic, etc, people, pay |
|
||||
| `analytics-tracking` | Design, audit, and improve analytics tracking systems that produce reliable, decision-ready data. Use when the user wants to set up, fix, or evaluate analyti... | analytics, tracking | analytics, tracking, audit, improve, produce, reliable, decision, data, user, wants, set, up |
|
||||
| `angular-ui-patterns` | Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component ... | angular, ui | angular, ui, loading, states, error, handling, data, display, building, components, async, managing |
|
||||
| `api-documenter` | Master API documentation with OpenAPI 3.1, AI-powered tools, and modern developer experience practices. Create interactive docs, generate SDKs, and build com... | api, documenter | api, documenter, documentation, openapi, ai, powered, developer, experience, interactive, docs, generate, sdks |
|
||||
| `autonomous-agent-patterns` | Design patterns for building autonomous coding agents. Covers tool integration, permission systems, browser automation, and human-in-the-loop workflows. Use ... | autonomous, agent | autonomous, agent, building, coding, agents, covers, integration, permission, browser, automation, human, loop |
|
||||
| `autonomous-agents` | Autonomous agents are AI systems that can independently decompose goals, plan actions, execute tools, and self-correct without constant human guidance. The c... | autonomous, agents | autonomous, agents, ai, independently, decompose, goals, plan, actions, execute, self, correct, without |
|
||||
@@ -205,10 +209,9 @@ Total skills: 619
|
||||
| `voice-ai-development` | Expert in building voice AI applications - from real-time voice agents to voice-enabled apps. Covers OpenAI Realtime API, Vapi for voice agents, Deepgram for... | voice, ai | voice, ai, development, building, applications, real, time, agents, enabled, apps, covers, openai |
|
||||
| `voice-ai-engine-development` | Build real-time conversational AI voice engines using async worker pipelines, streaming transcription, LLM agents, and TTS synthesis with interrupt handling ... | voice, ai, engine | voice, ai, engine, development, real, time, conversational, engines, async, worker, pipelines, streaming |
|
||||
| `web-artifacts-builder` | Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn/ui). Use ... | web, artifacts, builder | web, artifacts, builder, suite, creating, elaborate, multi, component, claude, ai, html, frontend |
|
||||
| `xlsx` | Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work ... | xlsx | xlsx, spreadsheet, creation, editing, analysis, formulas, formatting, data, visualization, claude, work, spreadsheets |
|
||||
| `xlsx-official` | Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work ... | xlsx, official | xlsx, official, spreadsheet, creation, editing, analysis, formulas, formatting, data, visualization, claude, work |
|
||||
|
||||
## development (80)
|
||||
## development (81)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -241,6 +244,7 @@ Total skills: 619
|
||||
| `frontend-developer` | Build React components, implement responsive layouts, and handle client-side state management. Masters React 19, Next.js 15, and modern frontend architecture... | frontend | frontend, developer, react, components, responsive, layouts, handle, client, side, state, masters, 19 |
|
||||
| `frontend-mobile-development-component-scaffold` | You are a React component architecture expert specializing in scaffolding production-ready, accessible, and performant components. Generate complete componen... | frontend, mobile, component | frontend, mobile, component, development, scaffold, react, architecture, specializing, scaffolding, accessible, performant, components |
|
||||
| `frontend-slides` | Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a... | frontend, slides | frontend, slides, stunning, animation, rich, html, presentations, scratch, converting, powerpoint, files, user |
|
||||
| `game-development/mobile-games` | Mobile game development principles. Touch input, battery, performance, app stores. | game, development/mobile, games | game, development/mobile, games, mobile, development, principles, touch, input, battery, performance, app, stores |
|
||||
| `go-concurrency-patterns` | Master Go concurrency with goroutines, channels, sync primitives, and context. Use when building concurrent Go applications, implementing worker pools, or de... | go, concurrency | go, concurrency, goroutines, channels, sync, primitives, context, building, concurrent, applications, implementing, worker |
|
||||
| `golang-pro` | Master Go 1.21+ with modern patterns, advanced concurrency, performance optimization, and production-ready microservices. Expert in the latest Go ecosystem i... | golang | golang, pro, go, 21, concurrency, performance, optimization, microservices, latest, ecosystem, including, generics |
|
||||
| `hubspot-integration` | Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects. Covers... | hubspot, integration | hubspot, integration, crm, including, oauth, authentication, objects, associations, batch, operations, webhooks, custom |
|
||||
@@ -294,16 +298,18 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `viral-generator-builder` | Expert in building shareable generator tools that go viral - name generators, quiz makers, avatar creators, personality tests, and calculator tools. Covers t... | viral, generator, builder | viral, generator, builder, building, shareable, go, name, generators, quiz, makers, avatar, creators |
|
||||
| `webapp-testing` | Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing... | webapp | webapp, testing, toolkit, interacting, local, web, applications, playwright, supports, verifying, frontend, functionality |
|
||||
|
||||
## general (122)
|
||||
## general (130)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
| `address-github-comments` | Use when you need to address review or issue comments on an open GitHub Pull Request using the gh CLI. | address, github, comments | address, github, comments, review, issue, open, pull, request, gh, cli |
|
||||
| `agent-manager-skill` | Manage multiple local CLI agents via tmux sessions (start/stop/monitor/assign) with cron-friendly scheduling. | agent, manager, skill | agent, manager, skill, multiple, local, cli, agents, via, tmux, sessions, start, stop |
|
||||
| `algorithmic-art` | Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, gener... | algorithmic, art | algorithmic, art, creating, p5, js, seeded, randomness, interactive, parameter, exploration, users, request |
|
||||
| `angular-best-practices` | Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and... | angular, best, practices | angular, best, practices, performance, optimization, writing, reviewing, refactoring, code, optimal, bundle, size |
|
||||
| `angular-migration` | Migrate from AngularJS to Angular using hybrid mode, incremental component rewriting, and dependency injection updates. Use when upgrading AngularJS applicat... | angular, migration | angular, migration, migrate, angularjs, hybrid, mode, incremental, component, rewriting, dependency, injection, updates |
|
||||
| `anti-reversing-techniques` | Understand anti-reversing, obfuscation, and protection techniques encountered during software analysis. Use when analyzing protected binaries, bypassing anti... | anti, reversing, techniques | anti, reversing, techniques, understand, obfuscation, protection, encountered, during, software, analysis, analyzing, protected |
|
||||
| `app-builder` | Main application building orchestrator. Creates full-stack applications from natural language requests. Determines project type, selects tech stack, coordina... | app, builder | app, builder, main, application, building, orchestrator, creates, full, stack, applications, natural, language |
|
||||
| `app-builder/templates` | Project scaffolding templates for new applications. Use when creating new projects from scratch. Contains 12 templates for various tech stacks. | app, builder/templates | app, builder/templates, scaffolding, new, applications, creating, scratch, contains, 12, various, tech, stacks |
|
||||
| `arm-cortex-expert` | Senior embedded software engineer specializing in firmware and driver development for ARM Cortex-M microcontrollers (Teensy, STM32, nRF52, SAMD). Decades of ... | arm, cortex | arm, cortex, senior, embedded, software, engineer, specializing, firmware, driver, development, microcontrollers, teensy |
|
||||
| `avalonia-layout-zafiro` | Guidelines for modern Avalonia UI layout using Zafiro.Avalonia, emphasizing shared styles, generic components, and avoiding XAML redundancy. | avalonia, layout, zafiro | avalonia, layout, zafiro, guidelines, ui, emphasizing, shared, styles, generic, components, avoiding, xaml |
|
||||
| `avalonia-zafiro-development` | Mandatory skills, conventions, and behavioral rules for Avalonia UI development using the Zafiro toolkit. | avalonia, zafiro | avalonia, zafiro, development, mandatory, skills, conventions, behavioral, rules, ui, toolkit |
|
||||
@@ -332,6 +338,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `commit` | Create commit messages following Sentry conventions. Use when committing code changes, writing commit messages, or formatting git history. Follows convention... | commit | commit, messages, following, sentry, conventions, committing, code, changes, writing, formatting, git, history |
|
||||
| `comprehensive-review-full-review` | Use when working with comprehensive review full review | comprehensive, full | comprehensive, full, review, working |
|
||||
| `comprehensive-review-pr-enhance` | You are a PR optimization expert specializing in creating high-quality pull requests that facilitate efficient code reviews. Generate comprehensive PR descri... | comprehensive, pr, enhance | comprehensive, pr, enhance, review, optimization, specializing, creating, high, quality, pull, requests, facilitate |
|
||||
| `computer-vision-expert` | SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis. | computer, vision | computer, vision, sota, 2026, specialized, yolo26, segment, anything, sam, language, models, real |
|
||||
| `concise-planning` | Use when a user asks for a plan for a coding task, to generate a clear, actionable, and atomic checklist. | concise, planning | concise, planning, user, asks, plan, coding, task, generate, clear, actionable, atomic, checklist |
|
||||
| `context-compression` | Design and evaluate compression strategies for long-running sessions | compression | compression, context, evaluate, long, running, sessions |
|
||||
| `context-fundamentals` | Understand what context is, why it matters, and the anatomy of context in agent systems | fundamentals | fundamentals, context, understand, what, why, matters, anatomy, agent |
|
||||
@@ -345,7 +352,6 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `debugging-toolkit-smart-debug` | Use when working with debugging toolkit smart debug | debugging, debug | debugging, debug, toolkit, smart, working |
|
||||
| `design-md` | Analyze Stitch projects and synthesize a semantic design system into DESIGN.md files | md | md, analyze, stitch, synthesize, semantic, files |
|
||||
| `dispatching-parallel-agents` | Use when facing 2+ independent tasks that can be worked on without shared state or sequential dependencies | dispatching, parallel, agents | dispatching, parallel, agents, facing, independent, tasks, worked, without, shared, state, sequential, dependencies |
|
||||
| `docx` | Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude ... | docx | docx, document, creation, editing, analysis, tracked, changes, comments, formatting, preservation, text, extraction |
|
||||
| `docx-official` | Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude ... | docx, official | docx, official, document, creation, editing, analysis, tracked, changes, comments, formatting, preservation, text |
|
||||
| `dx-optimizer` | Developer Experience specialist. Improves tooling, setup, and workflows. Use PROACTIVELY when setting up new projects, after team feedback, or when developme... | dx, optimizer | dx, optimizer, developer, experience, improves, tooling, setup, proactively, setting, up, new, after |
|
||||
| `environment-setup-guide` | Guide developers through setting up development environments with proper tools, dependencies, and configurations | environment, setup | environment, setup, developers, through, setting, up, development, environments, proper, dependencies, configurations |
|
||||
@@ -360,6 +366,13 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `fix-review` | Verify fix commits address audit findings without new bugs | fix | fix, review, verify, commits, address, audit, findings, without, new, bugs |
|
||||
| `framework-migration-code-migrate` | You are a code migration expert specializing in transitioning codebases between frameworks, languages, versions, and platforms. Generate comprehensive migrat... | framework, migration, code, migrate | framework, migration, code, migrate, specializing, transitioning, codebases, between, frameworks, languages, versions, platforms |
|
||||
| `game-development` | Game development orchestrator. Routes to platform-specific skills based on project needs. | game | game, development, orchestrator, routes, platform, specific, skills |
|
||||
| `game-development/2d-games` | 2D game development principles. Sprites, tilemaps, physics, camera. | game, development/2d, games | game, development/2d, games, 2d, development, principles, sprites, tilemaps, physics, camera |
|
||||
| `game-development/3d-games` | 3D game development principles. Rendering, shaders, physics, cameras. | game, development/3d, games | game, development/3d, games, 3d, development, principles, rendering, shaders, physics, cameras |
|
||||
| `game-development/game-audio` | Game audio principles. Sound design, music integration, adaptive audio systems. | game, development/game, audio | game, development/game, audio, principles, sound, music, integration, adaptive |
|
||||
| `game-development/game-design` | Game design principles. GDD structure, balancing, player psychology, progression. | game, development/game | game, development/game, principles, gdd, structure, balancing, player, psychology, progression |
|
||||
| `game-development/pc-games` | PC and console game development principles. Engine selection, platform features, optimization strategies. | game, development/pc, games | game, development/pc, games, pc, console, development, principles, engine, selection, platform, features, optimization |
|
||||
| `game-development/vr-ar` | VR/AR development principles. Comfort, interaction, performance requirements. | game, development/vr, ar | game, development/vr, ar, vr, development, principles, comfort, interaction, performance, requirements |
|
||||
| `game-development/web-games` | Web browser game development principles. Framework selection, WebGPU, optimization, PWA. | game, development/web, games | game, development/web, games, web, browser, development, principles, framework, selection, webgpu, optimization, pwa |
|
||||
| `git-advanced-workflows` | Master advanced Git workflows including rebasing, cherry-picking, bisect, worktrees, and reflog to maintain clean history and recover from any situation. Use... | git, advanced | git, advanced, including, rebasing, cherry, picking, bisect, worktrees, reflog, maintain, clean, history |
|
||||
| `git-pr-workflows-onboard` | You are an **expert onboarding specialist and knowledge transfer architect** with deep experience in remote-first organizations, technical team integration, ... | git, pr, onboard | git, pr, onboard, onboarding, knowledge, transfer, architect, deep, experience, remote, first, organizations |
|
||||
| `git-pr-workflows-pr-enhance` | You are a PR optimization expert specializing in creating high-quality pull requests that facilitate efficient code reviews. Generate comprehensive PR descri... | git, pr, enhance | git, pr, enhance, optimization, specializing, creating, high, quality, pull, requests, facilitate, efficient |
|
||||
@@ -386,7 +399,6 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `plan-writing` | Structured task planning with clear breakdowns, dependencies, and verification criteria. Use when implementing features, refactoring, or any multi-step work. | plan, writing | plan, writing, structured, task, planning, clear, breakdowns, dependencies, verification, criteria, implementing, features |
|
||||
| `planning-with-files` | Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks,... | planning, with, files | planning, with, files, implements, manus, style, file, complex, tasks, creates, task, plan |
|
||||
| `posix-shell-pro` | Expert in strict POSIX sh scripting for maximum portability across Unix-like systems. Specializes in shell scripts that run on any POSIX-compliant shell (das... | posix, shell | posix, shell, pro, strict, sh, scripting, maximum, portability, unix, like, specializes, scripts |
|
||||
| `pptx` | Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying o... | pptx | pptx, presentation, creation, editing, analysis, claude, work, presentations, files, creating, new, modifying |
|
||||
| `pptx-official` | Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying o... | pptx, official | pptx, official, presentation, creation, editing, analysis, claude, work, presentations, files, creating, new |
|
||||
| `privilege-escalation-methods` | This skill should be used when the user asks to "escalate privileges", "get root access", "become administrator", "privesc techniques", "abuse sudo", "exploi... | privilege, escalation, methods | privilege, escalation, methods, skill, should, used, user, asks, escalate, privileges, get, root |
|
||||
| `prompt-library` | Curated collection of high-quality prompts for various use cases. Includes role-based prompts, task-specific templates, and prompt refinement techniques. Use... | prompt, library | prompt, library, curated, collection, high, quality, prompts, various, cases, includes, role, task |
|
||||
@@ -421,7 +433,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `writing-skills` | Use when creating, updating, or improving agent skills. | writing, skills | writing, skills, creating, updating, improving, agent |
|
||||
| `x-article-publisher-skill` | Publish articles to X/Twitter | x, article, publisher, skill | x, article, publisher, skill, publish, articles, twitter |
|
||||
|
||||
## infrastructure (77)
|
||||
## infrastructure (78)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -458,6 +470,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `expo-deployment` | Deploy Expo apps to production | expo, deployment | expo, deployment, deploy, apps |
|
||||
| `file-uploads` | Expert at handling file uploads and cloud storage. Covers S3, Cloudflare R2, presigned URLs, multipart uploads, and image optimization. Knows how to handle l... | file, uploads | file, uploads, handling, cloud, storage, covers, s3, cloudflare, r2, presigned, urls, multipart |
|
||||
| `flutter-expert` | Master Flutter development with Dart 3, advanced widgets, and multi-platform deployment. Handles state management, animations, testing, and performance optim... | flutter | flutter, development, dart, widgets, multi, platform, deployment, state, animations, testing, performance, optimization |
|
||||
| `game-development/game-art` | Game art principles. Visual style selection, asset pipeline, animation workflow. | game, development/game, art | game, development/game, art, principles, visual, style, selection, asset, pipeline, animation |
|
||||
| `gcp-cloud-run` | Specialized skill for building production-ready serverless applications on GCP. Covers Cloud Run services (containerized), Cloud Run Functions (event-driven)... | gcp, cloud, run | gcp, cloud, run, specialized, skill, building, serverless, applications, covers, containerized, functions, event |
|
||||
| `git-pr-workflows-git-workflow` | Orchestrate a comprehensive git workflow from code review through PR creation, leveraging specialized agents for quality assurance, testing, and deployment r... | git, pr | git, pr, orchestrate, code, review, through, creation, leveraging, specialized, agents, quality, assurance |
|
||||
| `github-actions-templates` | Create production-ready GitHub Actions workflows for automated testing, building, and deploying applications. Use when setting up CI/CD with GitHub Actions, ... | github, actions | github, actions, automated, testing, building, deploying, applications, setting, up, ci, cd, automating |
|
||||
@@ -647,7 +660,7 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `unit-testing-test-generate` | Generate comprehensive, maintainable unit tests across languages with strong coverage and edge case focus. | unit, generate | unit, generate, testing, test, maintainable, tests, languages, strong, coverage, edge, case |
|
||||
| `web3-testing` | Test smart contracts comprehensively using Hardhat and Foundry with unit tests, integration tests, and mainnet forking. Use when testing Solidity contracts, ... | web3 | web3, testing, test, smart, contracts, comprehensively, hardhat, foundry, unit, tests, integration, mainnet |
|
||||
|
||||
## workflow (17)
|
||||
## workflow (16)
|
||||
|
||||
| Skill | Description | Tags | Triggers |
|
||||
| --- | --- | --- | --- |
|
||||
@@ -664,7 +677,6 @@ TRIGGER: "shopify", "shopify app", "checkout extension",... | shopify | shopify,
|
||||
| `git-pushing` | Stage, commit, and push git changes with conventional commit messages. Use when user wants to commit and push changes, mentions pushing to remote, or asks to... | git, pushing | git, pushing, stage, commit, push, changes, conventional, messages, user, wants, mentions, remote |
|
||||
| `kaizen` | Guide for continuous improvement, error proofing, and standardization. Use this skill when the user wants to improve code quality, refactor, or discuss proce... | kaizen | kaizen, continuous, improvement, error, proofing, standardization, skill, user, wants, improve, code, quality |
|
||||
| `mermaid-expert` | Create Mermaid diagrams for flowcharts, sequences, ERDs, and architectures. Masters syntax for all diagram types and styling. Use PROACTIVELY for visual docu... | mermaid | mermaid, diagrams, flowcharts, sequences, erds, architectures, masters, syntax, all, diagram, types, styling |
|
||||
| `pdf` | Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs ... | pdf | pdf, manipulation, toolkit, extracting, text, tables, creating, new, pdfs, merging, splitting, documents |
|
||||
| `pdf-official` | Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs ... | pdf, official | pdf, official, manipulation, toolkit, extracting, text, tables, creating, new, pdfs, merging, splitting |
|
||||
| `team-collaboration-issue` | You are a GitHub issue resolution expert specializing in systematic bug investigation, feature implementation, and collaborative development workflows. Your ... | team, collaboration, issue | team, collaboration, issue, github, resolution, specializing, systematic, bug, investigation, feature, collaborative, development |
|
||||
| `track-management` | Use this skill when creating, managing, or working with Conductor tracks - the logical work units for features, bugs, and refactors. Applies to spec.md, plan... | track | track, skill, creating, managing, working, conductor, tracks, logical, work, units, features, bugs |
|
||||
|
||||
38
CHANGELOG.md
38
CHANGELOG.md
@@ -7,6 +7,44 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
---
|
||||
|
||||
## [4.8.0] - 2026-02-04 - "Computer Vision & Angular"
|
||||
|
||||
> Major SOTA Computer Vision update and complete Angular 20+ skill suite.
|
||||
|
||||
### Added
|
||||
|
||||
- **New Skill**: `computer-vision-expert` – Updated foundational skill for 2026 SOTA Computer Vision (YOLO26, SAM 3, VLMs).
|
||||
- **New Suite**: `angular` – 4 comprehensive skills for Angular v20+ development:
|
||||
- `angular` – Core patterns (Signals, Standalone, Zoneless).
|
||||
- `angular-best-practices` – Performance and optimization.
|
||||
- `angular-state-management` – Signal-based state management (NgRx, ComponentStore).
|
||||
- `angular-ui-patterns` – UI/UX engineering patterns.
|
||||
|
||||
### Registry
|
||||
|
||||
- **Total Skills**: 631 (from 626). Catalog regenerated.
|
||||
|
||||
### Credits
|
||||
|
||||
- [@PabloSMD](https://github.com/PabloSMD) – computer-vision-expert (PR #58)
|
||||
- [@chauey](https://github.com/chauey) – angular suite (PR #60)
|
||||
|
||||
---
|
||||
|
||||
## [4.7.0] - 2026-02-03 - "Installer Fix & OpenCode Docs"
|
||||
|
||||
> Critical installer fix for Windows and OpenCode documentation completion.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Installer**: Resolved `ReferenceError` for `tagArg` variable in `bin/install.js` ensuring correct execution on Windows/PowerShell (PR #53).
|
||||
|
||||
### Documentation
|
||||
|
||||
- **OpenCode**: Completed documentation for OpenCode integration in `README.md`.
|
||||
|
||||
---
|
||||
|
||||
## [4.6.0] - 2026-02-01 - "SPDD & Radix UI Design System"
|
||||
|
||||
> Agent workflow docs (SPDD) and Radix UI design system skill.
|
||||
|
||||
@@ -48,6 +48,23 @@ You don't need to be an expert! Here are ways anyone can help:
|
||||
|
||||
---
|
||||
|
||||
## Local development setup
|
||||
|
||||
To run validation, index generation, and README updates locally:
|
||||
|
||||
1. **Node.js** (for catalog and installer): `npm ci`
|
||||
2. **Python 3** (for validate, index, readme scripts): install dependencies with
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
Then you can run `npm run chain` (validate → index → readme) and `npm run catalog`.
|
||||
|
||||
**Validation:** The canonical validator is **Python** (`scripts/validate_skills.py`). Use `npm run validate` (or `npm run validate:strict` for CI-style checks). The JavaScript validator (`scripts/validate-skills.js`) is legacy/optional and uses a different schema; CI and PR checks rely on the Python validator only.
|
||||
|
||||
**npm audit:** CI runs `npm audit --audit-level=high`. To fix issues locally: run `npm audit`, then `npm update` or `npm audit fix` as appropriate; for breaking changes, update dependencies manually and run tests.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a New Skill
|
||||
|
||||
### Step-by-Step Guide
|
||||
|
||||
93
README.md
93
README.md
@@ -1,6 +1,6 @@
|
||||
# 🌌 Antigravity Awesome Skills: 626+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
|
||||
# 🌌 Antigravity Awesome Skills: 631+ Agentic Skills for Claude Code, Gemini CLI, Cursor, Copilot & More
|
||||
|
||||
> **The Ultimate Collection of 626+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode**
|
||||
> **The Ultimate Collection of 631+ Universal Agentic Skills for AI Coding Assistants — Claude Code, Gemini CLI, Codex CLI, Antigravity IDE, GitHub Copilot, Cursor, OpenCode, AdaL**
|
||||
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://claude.ai)
|
||||
@@ -10,8 +10,10 @@
|
||||
[](https://github.com/features/copilot)
|
||||
[](https://github.com/opencode-ai/opencode)
|
||||
[](https://github.com/sickn33/antigravity-awesome-skills)
|
||||
[](https://sylph.ai/)
|
||||
[](https://github.com/yeasy/ask)
|
||||
|
||||
**Antigravity Awesome Skills** is a curated, battle-tested library of **626 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
|
||||
**Antigravity Awesome Skills** is a curated, battle-tested library of **631 high-performance agentic skills** designed to work seamlessly across all major AI coding assistants:
|
||||
|
||||
- 🟣 **Claude Code** (Anthropic CLI)
|
||||
- 🔵 **Gemini CLI** (Google DeepMind)
|
||||
@@ -20,6 +22,7 @@
|
||||
- 🩵 **GitHub Copilot** (VSCode Extension)
|
||||
- 🟠 **Cursor** (AI-native IDE)
|
||||
- ⚪ **OpenCode** (Open-source CLI)
|
||||
- 🌸 **AdaL CLI** (Self-evolving Coding Agent)
|
||||
|
||||
This repository provides essential skills to transform your AI assistant into a **full-stack digital agency**, including official capabilities from **Anthropic**, **OpenAI**, **Google**, **Supabase**, and **Vercel Labs**.
|
||||
|
||||
@@ -29,7 +32,7 @@ This repository provides essential skills to transform your AI assistant into a
|
||||
- [🔌 Compatibility & Invocation](#compatibility--invocation)
|
||||
- [📦 Features & Categories](#features--categories)
|
||||
- [🎁 Curated Collections (Bundles)](#curated-collections)
|
||||
- [📚 Browse 626+ Skills](#browse-626-skills)
|
||||
- [📚 Browse 631+ Skills](#browse-631-skills)
|
||||
- [🛠️ Installation](#installation)
|
||||
- [🤝 How to Contribute](#how-to-contribute)
|
||||
- [👥 Contributors & Credits](#credits--sources)
|
||||
@@ -55,6 +58,7 @@ AI Agents (like Claude Code, Cursor, or Gemini) are smart, but they lack **speci
|
||||
Install once (clone or npx); then use our **Starter Packs** in [docs/BUNDLES.md](docs/BUNDLES.md) to see which skills fit your role. You get the full repo; Starter Packs are curated lists, not a separate install.
|
||||
|
||||
1. **Install** (pick one):
|
||||
|
||||
```bash
|
||||
# Easiest: npx installer (clones to ~/.agent/skills by default)
|
||||
npx antigravity-awesome-skills
|
||||
@@ -62,6 +66,7 @@ Install once (clone or npx); then use our **Starter Packs** in [docs/BUNDLES.md]
|
||||
# Or clone manually
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .agent/skills
|
||||
```
|
||||
|
||||
2. **Pick your persona** (See [docs/BUNDLES.md](docs/BUNDLES.md)):
|
||||
- **Web Dev?** use the `Web Wizard` pack.
|
||||
- **Hacker?** use the `Security Engineer` pack.
|
||||
@@ -86,9 +91,12 @@ These skills follow the universal **SKILL.md** format and work with any AI codin
|
||||
| :-------------- | :--- | :-------------------------------- | :---------------- |
|
||||
| **Claude Code** | CLI | `>> /skill-name help me...` | `.claude/skills/` |
|
||||
| **Gemini CLI** | CLI | `(User Prompt) Use skill-name...` | `.gemini/skills/` |
|
||||
| **Codex CLI** | CLI | `(User Prompt) Use skill-name...` | `.codex/skills/` |
|
||||
| **Antigravity** | IDE | `(Agent Mode) Use skill...` | `.agent/skills/` |
|
||||
| **Cursor** | IDE | `@skill-name (in Chat)` | `.cursor/skills/` |
|
||||
| **Copilot** | Ext | `(Paste content manually)` | N/A |
|
||||
| **OpenCode** | CLI | `opencode run @skill-name` | `.agent/skills/` |
|
||||
| **AdaL CLI** | CLI | `(Auto) Skills load on-demand` | `.adal/skills/` |
|
||||
|
||||
> [!TIP]
|
||||
> **Universal Path**: We recommend cloning to `.agent/skills/`. Most modern tools (Antigravity, recent CLIs) look here by default.
|
||||
@@ -100,7 +108,7 @@ These skills follow the universal **SKILL.md** format and work with any AI codin
|
||||
|
||||
---
|
||||
|
||||
Whether you are using **Gemini CLI**, **Claude Code**, **Codex CLI**, **Cursor**, **GitHub Copilot**, **Antigravity**, or **OpenCode**, these skills are designed to drop right in and supercharge your AI agent.
|
||||
Whether you are using **Gemini CLI**, **Claude Code**, **Codex CLI**, **Cursor**, **GitHub Copilot**, **Antigravity**, **OpenCode**, or **AdaL**, these skills are designed to drop right in and supercharge your AI agent.
|
||||
|
||||
This repository aggregates the best capabilities from across the open-source community, transforming your AI assistant into a full-stack digital agency capable of Engineering, Design, Security, Marketing, and Autonomous Operations.
|
||||
|
||||
@@ -108,23 +116,23 @@ This repository aggregates the best capabilities from across the open-source com
|
||||
|
||||
The repository is organized into specialized domains to transform your AI into an expert across the entire software development lifecycle:
|
||||
|
||||
| Category | Focus | Example skills |
|
||||
| :--- | :--- | :--- |
|
||||
| Architecture (52) | System design, ADRs, C4, and scalable patterns | `architecture`, `c4-context`, `senior-architect` |
|
||||
| Business (35) | Growth, pricing, CRO, SEO, and go-to-market | `copywriting`, `pricing-strategy`, `seo-audit` |
|
||||
| Data & AI (81) | LLM apps, RAG, agents, observability, analytics | `rag-engineer`, `prompt-engineer`, `langgraph` |
|
||||
| Development (72) | Language mastery, framework patterns, code quality | `typescript-expert`, `python-patterns`, `react-patterns` |
|
||||
| General (95) | Planning, docs, product ops, writing, guidelines | `brainstorming`, `doc-coauthoring`, `writing-plans` |
|
||||
| Infrastructure (72) | DevOps, cloud, serverless, deployment, CI/CD | `docker-expert`, `aws-serverless`, `vercel-deployment` |
|
||||
| Security (107) | AppSec, pentesting, vuln analysis, compliance | `api-security-best-practices`, `sql-injection-testing`, `vulnerability-scanner` |
|
||||
| Testing (21) | TDD, test design, fixes, QA workflows | `test-driven-development`, `testing-patterns`, `test-fixing` |
|
||||
| Workflow (17) | Automation, orchestration, jobs, agents | `workflow-automation`, `inngest`, `trigger-dev` |
|
||||
| Category | Focus | Example skills |
|
||||
| :------------------ | :------------------------------------------------- | :------------------------------------------------------------------------------ |
|
||||
| Architecture (52) | System design, ADRs, C4, and scalable patterns | `architecture`, `c4-context`, `senior-architect` |
|
||||
| Business (35) | Growth, pricing, CRO, SEO, and go-to-market | `copywriting`, `pricing-strategy`, `seo-audit` |
|
||||
| Data & AI (81) | LLM apps, RAG, agents, observability, analytics | `rag-engineer`, `prompt-engineer`, `langgraph` |
|
||||
| Development (72) | Language mastery, framework patterns, code quality | `typescript-expert`, `python-patterns`, `react-patterns` |
|
||||
| General (95) | Planning, docs, product ops, writing, guidelines | `brainstorming`, `doc-coauthoring`, `writing-plans` |
|
||||
| Infrastructure (72) | DevOps, cloud, serverless, deployment, CI/CD | `docker-expert`, `aws-serverless`, `vercel-deployment` |
|
||||
| Security (107) | AppSec, pentesting, vuln analysis, compliance | `api-security-best-practices`, `sql-injection-testing`, `vulnerability-scanner` |
|
||||
| Testing (21) | TDD, test design, fixes, QA workflows | `test-driven-development`, `testing-patterns`, `test-fixing` |
|
||||
| Workflow (17) | Automation, orchestration, jobs, agents | `workflow-automation`, `inngest`, `trigger-dev` |
|
||||
|
||||
## Curated Collections
|
||||
|
||||
[Check out our Starter Packs in docs/BUNDLES.md](docs/BUNDLES.md) to find the perfect toolkit for your role.
|
||||
|
||||
## Browse 626+ Skills
|
||||
## Browse 631+ Skills
|
||||
|
||||
We have moved the full skill registry to a dedicated catalog to keep this README clean.
|
||||
|
||||
@@ -132,7 +140,7 @@ We have moved the full skill registry to a dedicated catalog to keep this README
|
||||
|
||||
## Installation
|
||||
|
||||
To use these skills with **Claude Code**, **Gemini CLI**, **Codex CLI**, **Cursor**, **Antigravity**, or **OpenCode**:
|
||||
To use these skills with **Claude Code**, **Gemini CLI**, **Codex CLI**, **Cursor**, **Antigravity**, **OpenCode**, or **AdaL**:
|
||||
|
||||
### Option A: npx (recommended)
|
||||
|
||||
@@ -149,12 +157,20 @@ npx antigravity-awesome-skills --claude
|
||||
# Gemini CLI
|
||||
npx antigravity-awesome-skills --gemini
|
||||
|
||||
# Codex CLI
|
||||
npx antigravity-awesome-skills --codex
|
||||
|
||||
# OpenCode (Universal)
|
||||
npx antigravity-awesome-skills
|
||||
|
||||
# Custom path
|
||||
npx antigravity-awesome-skills --path ./my-skills
|
||||
```
|
||||
|
||||
Run `npx antigravity-awesome-skills --help` for all options. If the directory already exists, the installer runs `git pull` to update.
|
||||
|
||||
> **If you see a 404 error:** the package may not be published to npm yet. Use: `npx github:sickn33/antigravity-awesome-skills`
|
||||
|
||||
### Option B: git clone
|
||||
|
||||
```bash
|
||||
@@ -167,8 +183,14 @@ git clone https://github.com/sickn33/antigravity-awesome-skills.git .claude/skil
|
||||
# Gemini CLI specific
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .gemini/skills
|
||||
|
||||
# Codex CLI specific
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .codex/skills
|
||||
|
||||
# Cursor specific
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .cursor/skills
|
||||
|
||||
# OpenCode specific (Universal path)
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .agent/skills
|
||||
```
|
||||
|
||||
---
|
||||
@@ -267,34 +289,47 @@ ai-developer-tools, ai-pair-programming, vibe-coding, skill, skills, SKILL.md, r
|
||||
|
||||
## Repo Contributors
|
||||
|
||||
<a href="https://github.com/sickn33/antigravity-awesome-skills/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=sickn33/antigravity-awesome-skills" />
|
||||
</a>
|
||||
|
||||
Made with [contrib.rocks](https://contrib.rocks).
|
||||
|
||||
We officially thank the following contributors for their help in making this repository awesome!
|
||||
|
||||
- [mvanhorn](https://github.com/mvanhorn)
|
||||
- [rookie-ricardo](https://github.com/rookie-ricardo)
|
||||
- [sck_0](https://github.com/sck_0)
|
||||
- [Munir Abbasi](https://github.com/munirabbasi)
|
||||
- [Mohammad Faiz](https://github.com/mohdfaiz2k9)
|
||||
- [Ianj332](https://github.com/Ianj332)
|
||||
- [sck_0](https://github.com/sck000)
|
||||
- [Munir Abbasi](https://github.com/munir-abbasi)
|
||||
- [sickn33](https://github.com/sickn33)
|
||||
- [Mohammad Faiz](https://github.com/Mohammad-Faiz-Cloud-Engineer)
|
||||
- [Đỗ Khắc Gia Khoa](https://github.com/Dokhacgiakhoa)
|
||||
- [Ianj332](https://github.com/IanJ332)
|
||||
- [GuppyTheCat](https://github.com/GuppyTheCat)
|
||||
- [Tiger-Foxx](https://github.com/Tiger-Foxx)
|
||||
- [arathiesh](https://github.com/arathiesh)
|
||||
- [1bcMax](https://github.com/1bcMax)
|
||||
- [ALEKGG1](https://github.com/ALEKGG1)
|
||||
- [Ahmed Rehan](https://github.com/ar27111994)
|
||||
- [BenedictKing](https://github.com/BenedictKing)
|
||||
- [whatiskadudoing](https://github.com/whatiskadudoing)
|
||||
- [Nguyen Huu Loc](https://github.com/LocNguyenSGU)
|
||||
- [Owen Wu](https://github.com/yubing744)
|
||||
- [SuperJMN](https://github.com/SuperJMN)
|
||||
- [Truong Nguyen](https://github.com/truongnmt)
|
||||
- [Viktor Ferenczi](https://github.com/viktor-ferenczi)
|
||||
- [Đỗ Khắc Gia Khoa](https://github.com/Dokhacgiakhoa)
|
||||
- [evandro-miguel](https://github.com/evandro-miguel)
|
||||
- [c1c3ru](https://github.com/c1c3ru)
|
||||
- [ckdwns9121](https://github.com/ckdwns9121)
|
||||
- [junited31](https://github.com/junited31)
|
||||
- [krisnasantosa15](https://github.com/krisnasantosa15)
|
||||
- [raeef1001](https://github.com/raeef1001)
|
||||
- [liyin2015](https://github.com/liyin2015)
|
||||
- [krisnasantosa15](https://github.com/KrisnaSantosa15)
|
||||
- [sstklen](https://github.com/sstklen)
|
||||
- [taksrules](https://github.com/taksrules)
|
||||
- [zebbern](https://github.com/zebbern)
|
||||
- [vuth-dogo](https://github.com/vuth-dogo)
|
||||
- [whatiskadudoing](https://github.com/whatiskadudoing)
|
||||
- [mvanhorn](https://github.com/mvanhorn)
|
||||
- [rookie-ricardo](https://github.com/rookie-ricardo)
|
||||
- [evandro-miguel](https://github.com/evandro-miguel)
|
||||
- [raeef1001](https://github.com/raeef1001)
|
||||
- [devchangjun](https://github.com/devchangjun)
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
@@ -16,18 +16,23 @@ function resolveDir(p) {
|
||||
function parseArgs() {
|
||||
const a = process.argv.slice(2);
|
||||
let pathArg = null;
|
||||
let cursor = false, claude = false, gemini = false;
|
||||
let versionArg = null;
|
||||
let tagArg = null;
|
||||
let cursor = false, claude = false, gemini = false, codex = false;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] === '--help' || a[i] === '-h') return { help: true };
|
||||
if (a[i] === '--path' && a[i + 1]) { pathArg = a[++i]; continue; }
|
||||
if (a[i] === '--version' && a[i + 1]) { versionArg = a[++i]; continue; }
|
||||
if (a[i] === '--tag' && a[i + 1]) { tagArg = a[++i]; continue; }
|
||||
if (a[i] === '--cursor') { cursor = true; continue; }
|
||||
if (a[i] === '--claude') { claude = true; continue; }
|
||||
if (a[i] === '--gemini') { gemini = true; continue; }
|
||||
if (a[i] === '--codex') { codex = true; continue; }
|
||||
if (a[i] === 'install') continue;
|
||||
}
|
||||
|
||||
return { pathArg, cursor, claude, gemini };
|
||||
return { pathArg, versionArg, tagArg, cursor, claude, gemini, codex };
|
||||
}
|
||||
|
||||
function defaultDir(opts) {
|
||||
@@ -35,6 +40,11 @@ function defaultDir(opts) {
|
||||
if (opts.cursor) return path.join(HOME, '.cursor', 'skills');
|
||||
if (opts.claude) return path.join(HOME, '.claude', 'skills');
|
||||
if (opts.gemini) return path.join(HOME, '.gemini', 'skills');
|
||||
if (opts.codex) {
|
||||
const codexHome = process.env.CODEX_HOME;
|
||||
if (codexHome) return path.join(codexHome, 'skills');
|
||||
return path.join(HOME, '.codex', 'skills');
|
||||
}
|
||||
return path.join(HOME, '.agent', 'skills');
|
||||
}
|
||||
|
||||
@@ -50,11 +60,15 @@ Options:
|
||||
--cursor Install to ~/.cursor/skills (Cursor)
|
||||
--claude Install to ~/.claude/skills (Claude Code)
|
||||
--gemini Install to ~/.gemini/skills (Gemini CLI)
|
||||
--codex Install to ~/.codex/skills (Codex CLI)
|
||||
--path <dir> Install to <dir> (default: ~/.agent/skills)
|
||||
--version <ver> After clone, checkout tag v<ver> (e.g. 4.6.0 -> v4.6.0)
|
||||
--tag <tag> After clone, checkout this tag (e.g. v4.6.0)
|
||||
|
||||
Examples:
|
||||
npx antigravity-awesome-skills
|
||||
npx antigravity-awesome-skills --cursor
|
||||
npx antigravity-awesome-skills --version 4.6.0
|
||||
npx antigravity-awesome-skills --path ./my-skills
|
||||
`);
|
||||
}
|
||||
@@ -66,6 +80,8 @@ function run(cmd, args, opts = {}) {
|
||||
|
||||
function main() {
|
||||
const opts = parseArgs();
|
||||
const { tagArg, versionArg } = opts;
|
||||
|
||||
if (opts.help) {
|
||||
printHelp();
|
||||
return;
|
||||
@@ -106,6 +122,13 @@ function main() {
|
||||
run('git', ['clone', REPO, target]);
|
||||
}
|
||||
|
||||
const ref = tagArg || (versionArg ? (versionArg.startsWith('v') ? versionArg : `v${versionArg}`) : null);
|
||||
if (ref) {
|
||||
console.log(`Checking out ${ref}…`);
|
||||
process.chdir(target);
|
||||
run('git', ['checkout', ref]);
|
||||
}
|
||||
|
||||
console.log(`\nInstalled to ${target}`);
|
||||
console.log('Pick a bundle in docs/BUNDLES.md and use @skill-name in your AI assistant.');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-02-01T07:22:00.871Z",
|
||||
"generatedAt": "2026-02-04T07:52:07.897Z",
|
||||
"aliases": {
|
||||
"accessibility-compliance-audit": "accessibility-compliance-accessibility-audit",
|
||||
"active directory attacks": "active-directory-attacks",
|
||||
@@ -7,6 +7,7 @@
|
||||
"agent-orchestration-optimize": "agent-orchestration-multi-agent-optimize",
|
||||
"api fuzzing for bug bounty": "api-fuzzing-bug-bounty",
|
||||
"api-testing-mock": "api-testing-observability-api-mock",
|
||||
"templates": "app-builder/templates",
|
||||
"application-performance-optimization": "application-performance-performance-optimization",
|
||||
"aws penetration testing": "aws-penetration-testing",
|
||||
"backend-development-feature": "backend-development-feature-development",
|
||||
@@ -45,6 +46,7 @@
|
||||
"deployment-validation-validate": "deployment-validation-config-validate",
|
||||
"distributed-debugging-trace": "distributed-debugging-debug-trace",
|
||||
"documentation-generation-generate": "documentation-generation-doc-generate",
|
||||
"docx": "docx-official",
|
||||
"error-debugging-analysis": "error-debugging-error-analysis",
|
||||
"error-debugging-review": "error-debugging-multi-agent-review",
|
||||
"error-diagnostics-analysis": "error-diagnostics-error-analysis",
|
||||
@@ -59,6 +61,16 @@
|
||||
"frontend-mobile-scaffold": "frontend-mobile-development-component-scaffold",
|
||||
"frontend-mobile-scan": "frontend-mobile-security-xss-scan",
|
||||
"full-stack-feature": "full-stack-orchestration-full-stack-feature",
|
||||
"2d-games": "game-development/2d-games",
|
||||
"3d-games": "game-development/3d-games",
|
||||
"game-art": "game-development/game-art",
|
||||
"game-audio": "game-development/game-audio",
|
||||
"game-design": "game-development/game-design",
|
||||
"mobile-games": "game-development/mobile-games",
|
||||
"multiplayer": "game-development/multiplayer",
|
||||
"pc-games": "game-development/pc-games",
|
||||
"vr-ar": "game-development/vr-ar",
|
||||
"web-games": "game-development/web-games",
|
||||
"git-pr-workflow": "git-pr-workflows-git-workflow",
|
||||
"html injection testing": "html-injection-testing",
|
||||
"idor vulnerability testing": "idor-testing",
|
||||
@@ -79,11 +91,13 @@
|
||||
"observability-monitoring-setup": "observability-monitoring-monitor-setup",
|
||||
"observability-monitoring-implement": "observability-monitoring-slo-implement",
|
||||
"obsidian-clipper-creator": "obsidian-clipper-template-creator",
|
||||
"pdf": "pdf-official",
|
||||
"pentest checklist": "pentest-checklist",
|
||||
"pentest commands": "pentest-commands",
|
||||
"performance-testing-ai": "performance-testing-review-ai-review",
|
||||
"performance-testing-agent": "performance-testing-review-multi-agent-review",
|
||||
"supabase-postgres-best-practices": "postgres-best-practices",
|
||||
"pptx": "pptx-official",
|
||||
"privilege escalation methods": "privilege-escalation-methods",
|
||||
"python-development-scaffold": "python-development-python-scaffold",
|
||||
"vercel-react-best-practices": "react-best-practices",
|
||||
@@ -107,6 +121,7 @@
|
||||
"windows privilege escalation": "windows-privilege-escalation",
|
||||
"wireshark network traffic analysis": "wireshark-analysis",
|
||||
"wordpress penetration testing": "wordpress-penetration-testing",
|
||||
"xlsx": "xlsx-official",
|
||||
"cross-site scripting and html injection testing": "xss-html-injection"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generatedAt": "2026-02-01T07:22:00.871Z",
|
||||
"generatedAt": "2026-02-04T07:52:07.897Z",
|
||||
"bundles": {
|
||||
"core-dev": {
|
||||
"description": "Core development skills across languages, frameworks, and backend/frontend fundamentals.",
|
||||
@@ -55,6 +55,7 @@
|
||||
"frontend-mobile-security-xss-scan",
|
||||
"frontend-security-coder",
|
||||
"frontend-slides",
|
||||
"game-development/mobile-games",
|
||||
"go-concurrency-patterns",
|
||||
"golang-pro",
|
||||
"graphql",
|
||||
@@ -258,6 +259,7 @@
|
||||
"skills": [
|
||||
"airflow-dag-patterns",
|
||||
"analytics-tracking",
|
||||
"angular-ui-patterns",
|
||||
"blockrun",
|
||||
"business-analyst",
|
||||
"cc-skill-backend-patterns",
|
||||
@@ -319,7 +321,6 @@
|
||||
"unity-ecs-patterns",
|
||||
"using-neon",
|
||||
"vector-database-engineer",
|
||||
"xlsx",
|
||||
"xlsx-official"
|
||||
]
|
||||
},
|
||||
@@ -357,6 +358,7 @@
|
||||
"error-diagnostics-error-trace",
|
||||
"expo-deployment",
|
||||
"flutter-expert",
|
||||
"game-development/game-art",
|
||||
"git-pr-workflows-git-workflow",
|
||||
"gitlab-ci-patterns",
|
||||
"gitops-workflow",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"generatedAt": "2026-02-01T07:22:00.871Z",
|
||||
"total": 619,
|
||||
"generatedAt": "2026-02-04T07:52:07.897Z",
|
||||
"total": 631,
|
||||
"skills": [
|
||||
{
|
||||
"id": "3d-web-experience",
|
||||
@@ -505,6 +505,56 @@
|
||||
],
|
||||
"path": "skills/analytics-tracking/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "angular",
|
||||
"name": "angular",
|
||||
"description": "Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIVELY for Angular development, component architecture, state management, performance optimization, and migration to modern patterns.",
|
||||
"category": "architecture",
|
||||
"tags": [
|
||||
"angular"
|
||||
],
|
||||
"triggers": [
|
||||
"angular",
|
||||
"v20",
|
||||
"deep",
|
||||
"knowledge",
|
||||
"signals",
|
||||
"standalone",
|
||||
"components",
|
||||
"zoneless",
|
||||
"applications",
|
||||
"ssr",
|
||||
"hydration",
|
||||
"reactive"
|
||||
],
|
||||
"path": "skills/angular/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "angular-best-practices",
|
||||
"name": "angular-best-practices",
|
||||
"description": "Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"angular",
|
||||
"best",
|
||||
"practices"
|
||||
],
|
||||
"triggers": [
|
||||
"angular",
|
||||
"best",
|
||||
"practices",
|
||||
"performance",
|
||||
"optimization",
|
||||
"writing",
|
||||
"reviewing",
|
||||
"refactoring",
|
||||
"code",
|
||||
"optimal",
|
||||
"bundle",
|
||||
"size"
|
||||
],
|
||||
"path": "skills/angular-best-practices/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "angular-migration",
|
||||
"name": "angular-migration",
|
||||
@@ -530,6 +580,56 @@
|
||||
],
|
||||
"path": "skills/angular-migration/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "angular-state-management",
|
||||
"name": "angular-state-management",
|
||||
"description": "Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.",
|
||||
"category": "architecture",
|
||||
"tags": [
|
||||
"angular",
|
||||
"state"
|
||||
],
|
||||
"triggers": [
|
||||
"angular",
|
||||
"state",
|
||||
"signals",
|
||||
"ngrx",
|
||||
"rxjs",
|
||||
"setting",
|
||||
"up",
|
||||
"global",
|
||||
"managing",
|
||||
"component",
|
||||
"stores",
|
||||
"choosing"
|
||||
],
|
||||
"path": "skills/angular-state-management/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "angular-ui-patterns",
|
||||
"name": "angular-ui-patterns",
|
||||
"description": "Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.",
|
||||
"category": "data-ai",
|
||||
"tags": [
|
||||
"angular",
|
||||
"ui"
|
||||
],
|
||||
"triggers": [
|
||||
"angular",
|
||||
"ui",
|
||||
"loading",
|
||||
"states",
|
||||
"error",
|
||||
"handling",
|
||||
"data",
|
||||
"display",
|
||||
"building",
|
||||
"components",
|
||||
"async",
|
||||
"managing"
|
||||
],
|
||||
"path": "skills/angular-ui-patterns/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "anti-reversing-techniques",
|
||||
"name": "anti-reversing-techniques",
|
||||
@@ -760,6 +860,31 @@
|
||||
],
|
||||
"path": "skills/app-builder/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "app-builder/templates",
|
||||
"name": "templates",
|
||||
"description": "Project scaffolding templates for new applications. Use when creating new projects from scratch. Contains 12 templates for various tech stacks.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"app",
|
||||
"builder/templates"
|
||||
],
|
||||
"triggers": [
|
||||
"app",
|
||||
"builder/templates",
|
||||
"scaffolding",
|
||||
"new",
|
||||
"applications",
|
||||
"creating",
|
||||
"scratch",
|
||||
"contains",
|
||||
"12",
|
||||
"various",
|
||||
"tech",
|
||||
"stacks"
|
||||
],
|
||||
"path": "skills/app-builder/templates/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "app-store-optimization",
|
||||
"name": "app-store-optimization",
|
||||
@@ -3066,6 +3191,31 @@
|
||||
],
|
||||
"path": "skills/computer-use-agents/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "computer-vision-expert",
|
||||
"name": "computer-vision-expert",
|
||||
"description": "SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"computer",
|
||||
"vision"
|
||||
],
|
||||
"triggers": [
|
||||
"computer",
|
||||
"vision",
|
||||
"sota",
|
||||
"2026",
|
||||
"specialized",
|
||||
"yolo26",
|
||||
"segment",
|
||||
"anything",
|
||||
"sam",
|
||||
"language",
|
||||
"models",
|
||||
"real"
|
||||
],
|
||||
"path": "skills/computer-vision-expert/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "concise-planning",
|
||||
"name": "concise-planning",
|
||||
@@ -4778,30 +4928,6 @@
|
||||
],
|
||||
"path": "skills/documentation-templates/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "docx",
|
||||
"name": "docx",
|
||||
"description": "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"docx"
|
||||
],
|
||||
"triggers": [
|
||||
"docx",
|
||||
"document",
|
||||
"creation",
|
||||
"editing",
|
||||
"analysis",
|
||||
"tracked",
|
||||
"changes",
|
||||
"comments",
|
||||
"formatting",
|
||||
"preservation",
|
||||
"text",
|
||||
"extraction"
|
||||
],
|
||||
"path": "skills/docx/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "docx-official",
|
||||
"name": "docx",
|
||||
@@ -6272,6 +6398,245 @@
|
||||
],
|
||||
"path": "skills/game-development/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/2d-games",
|
||||
"name": "2d-games",
|
||||
"description": "2D game development principles. Sprites, tilemaps, physics, camera.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/2d",
|
||||
"games"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/2d",
|
||||
"games",
|
||||
"2d",
|
||||
"development",
|
||||
"principles",
|
||||
"sprites",
|
||||
"tilemaps",
|
||||
"physics",
|
||||
"camera"
|
||||
],
|
||||
"path": "skills/game-development/2d-games/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/3d-games",
|
||||
"name": "3d-games",
|
||||
"description": "3D game development principles. Rendering, shaders, physics, cameras.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/3d",
|
||||
"games"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/3d",
|
||||
"games",
|
||||
"3d",
|
||||
"development",
|
||||
"principles",
|
||||
"rendering",
|
||||
"shaders",
|
||||
"physics",
|
||||
"cameras"
|
||||
],
|
||||
"path": "skills/game-development/3d-games/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/game-art",
|
||||
"name": "game-art",
|
||||
"description": "Game art principles. Visual style selection, asset pipeline, animation workflow.",
|
||||
"category": "infrastructure",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/game",
|
||||
"art"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/game",
|
||||
"art",
|
||||
"principles",
|
||||
"visual",
|
||||
"style",
|
||||
"selection",
|
||||
"asset",
|
||||
"pipeline",
|
||||
"animation"
|
||||
],
|
||||
"path": "skills/game-development/game-art/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/game-audio",
|
||||
"name": "game-audio",
|
||||
"description": "Game audio principles. Sound design, music integration, adaptive audio systems.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/game",
|
||||
"audio"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/game",
|
||||
"audio",
|
||||
"principles",
|
||||
"sound",
|
||||
"music",
|
||||
"integration",
|
||||
"adaptive"
|
||||
],
|
||||
"path": "skills/game-development/game-audio/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/game-design",
|
||||
"name": "game-design",
|
||||
"description": "Game design principles. GDD structure, balancing, player psychology, progression.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/game"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/game",
|
||||
"principles",
|
||||
"gdd",
|
||||
"structure",
|
||||
"balancing",
|
||||
"player",
|
||||
"psychology",
|
||||
"progression"
|
||||
],
|
||||
"path": "skills/game-development/game-design/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/mobile-games",
|
||||
"name": "mobile-games",
|
||||
"description": "Mobile game development principles. Touch input, battery, performance, app stores.",
|
||||
"category": "development",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/mobile",
|
||||
"games"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/mobile",
|
||||
"games",
|
||||
"mobile",
|
||||
"development",
|
||||
"principles",
|
||||
"touch",
|
||||
"input",
|
||||
"battery",
|
||||
"performance",
|
||||
"app",
|
||||
"stores"
|
||||
],
|
||||
"path": "skills/game-development/mobile-games/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/multiplayer",
|
||||
"name": "multiplayer",
|
||||
"description": "Multiplayer game development principles. Architecture, networking, synchronization.",
|
||||
"category": "architecture",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/multiplayer"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/multiplayer",
|
||||
"multiplayer",
|
||||
"development",
|
||||
"principles",
|
||||
"architecture",
|
||||
"networking",
|
||||
"synchronization"
|
||||
],
|
||||
"path": "skills/game-development/multiplayer/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/pc-games",
|
||||
"name": "pc-games",
|
||||
"description": "PC and console game development principles. Engine selection, platform features, optimization strategies.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/pc",
|
||||
"games"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/pc",
|
||||
"games",
|
||||
"pc",
|
||||
"console",
|
||||
"development",
|
||||
"principles",
|
||||
"engine",
|
||||
"selection",
|
||||
"platform",
|
||||
"features",
|
||||
"optimization"
|
||||
],
|
||||
"path": "skills/game-development/pc-games/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/vr-ar",
|
||||
"name": "vr-ar",
|
||||
"description": "VR/AR development principles. Comfort, interaction, performance requirements.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/vr",
|
||||
"ar"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/vr",
|
||||
"ar",
|
||||
"vr",
|
||||
"development",
|
||||
"principles",
|
||||
"comfort",
|
||||
"interaction",
|
||||
"performance",
|
||||
"requirements"
|
||||
],
|
||||
"path": "skills/game-development/vr-ar/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "game-development/web-games",
|
||||
"name": "web-games",
|
||||
"description": "Web browser game development principles. Framework selection, WebGPU, optimization, PWA.",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"game",
|
||||
"development/web",
|
||||
"games"
|
||||
],
|
||||
"triggers": [
|
||||
"game",
|
||||
"development/web",
|
||||
"games",
|
||||
"web",
|
||||
"browser",
|
||||
"development",
|
||||
"principles",
|
||||
"framework",
|
||||
"selection",
|
||||
"webgpu",
|
||||
"optimization",
|
||||
"pwa"
|
||||
],
|
||||
"path": "skills/game-development/web-games/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "gcp-cloud-run",
|
||||
"name": "gcp-cloud-run",
|
||||
@@ -9604,30 +9969,6 @@
|
||||
],
|
||||
"path": "skills/pci-compliance/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "pdf",
|
||||
"name": "pdf",
|
||||
"description": "Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.",
|
||||
"category": "workflow",
|
||||
"tags": [
|
||||
"pdf"
|
||||
],
|
||||
"triggers": [
|
||||
"pdf",
|
||||
"manipulation",
|
||||
"toolkit",
|
||||
"extracting",
|
||||
"text",
|
||||
"tables",
|
||||
"creating",
|
||||
"new",
|
||||
"pdfs",
|
||||
"merging",
|
||||
"splitting",
|
||||
"documents"
|
||||
],
|
||||
"path": "skills/pdf/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "pdf-official",
|
||||
"name": "pdf",
|
||||
@@ -10086,30 +10427,6 @@
|
||||
],
|
||||
"path": "skills/powershell-windows/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "pptx",
|
||||
"name": "pptx",
|
||||
"description": "Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks",
|
||||
"category": "general",
|
||||
"tags": [
|
||||
"pptx"
|
||||
],
|
||||
"triggers": [
|
||||
"pptx",
|
||||
"presentation",
|
||||
"creation",
|
||||
"editing",
|
||||
"analysis",
|
||||
"claude",
|
||||
"work",
|
||||
"presentations",
|
||||
"files",
|
||||
"creating",
|
||||
"new",
|
||||
"modifying"
|
||||
],
|
||||
"path": "skills/pptx/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "pptx-official",
|
||||
"name": "pptx",
|
||||
@@ -14971,30 +15288,6 @@
|
||||
],
|
||||
"path": "skills/x-article-publisher-skill/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "xlsx",
|
||||
"name": "xlsx",
|
||||
"description": "Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas",
|
||||
"category": "data-ai",
|
||||
"tags": [
|
||||
"xlsx"
|
||||
],
|
||||
"triggers": [
|
||||
"xlsx",
|
||||
"spreadsheet",
|
||||
"creation",
|
||||
"editing",
|
||||
"analysis",
|
||||
"formulas",
|
||||
"formatting",
|
||||
"data",
|
||||
"visualization",
|
||||
"claude",
|
||||
"work",
|
||||
"spreadsheets"
|
||||
],
|
||||
"path": "skills/xlsx/SKILL.md"
|
||||
},
|
||||
{
|
||||
"id": "xlsx-official",
|
||||
"name": "xlsx",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "4.2.0",
|
||||
"version": "4.6.0",
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
Skills are specialized instruction files that teach AI assistants how to handle specific tasks. Think of them as expert knowledge modules that your AI can load on-demand.
|
||||
**Simple analogy:** Just like you might consult different experts (a lawyer, a doctor, a mechanic), these skills let your AI become an expert in different areas when you need them.
|
||||
|
||||
### Do I need to install all 624+ skills?
|
||||
### Do I need to install all 626+ skills?
|
||||
|
||||
**No!** When you clone the repository, all skills are available, but your AI only loads them when you explicitly invoke them with `@skill-name`.
|
||||
It's like having a library - all books are there, but you only read the ones you need.
|
||||
@@ -62,7 +62,11 @@ _Always check the Risk label and review the code._
|
||||
|
||||
### Where should I install the skills?
|
||||
|
||||
The universal path that works with most tools is `.agent/skills/`:
|
||||
The universal path that works with most tools is `.agent/skills/`.
|
||||
|
||||
**Using npx:** `npx antigravity-awesome-skills` (or `npx github:sickn33/antigravity-awesome-skills` if you get a 404).
|
||||
|
||||
**Using git clone:**
|
||||
|
||||
```bash
|
||||
git clone https://github.com/sickn33/antigravity-awesome-skills.git .agent/skills
|
||||
@@ -72,6 +76,7 @@ git clone https://github.com/sickn33/antigravity-awesome-skills.git .agent/skill
|
||||
|
||||
- Claude Code: `.claude/skills/`
|
||||
- Gemini CLI: `.gemini/skills/`
|
||||
- Codex CLI: `.codex/skills/`
|
||||
- Cursor: `.cursor/skills/` or project root
|
||||
|
||||
### Does this work with Windows?
|
||||
|
||||
@@ -28,7 +28,9 @@ You **install the full repo once** (npx or clone); Starter Packs are curated lis
|
||||
npx antigravity-awesome-skills
|
||||
```
|
||||
|
||||
This clones to `~/.agent/skills` by default. Use `--cursor`, `--claude`, or `--gemini` to install for a specific tool, or `--path <dir>` for a custom location. Run `npx antigravity-awesome-skills --help` for details.
|
||||
This clones to `~/.agent/skills` by default. Use `--cursor`, `--claude`, `--gemini`, or `--codex` to install for a specific tool, or `--path <dir>` for a custom location. Run `npx antigravity-awesome-skills --help` for details.
|
||||
|
||||
If you see a 404 error, use: `npx github:sickn33/antigravity-awesome-skills`
|
||||
|
||||
**Option B — git clone:**
|
||||
|
||||
@@ -80,6 +82,7 @@ Once installed, just talk to your AI naturally.
|
||||
| :-------------- | :-------------- | :---------------- |
|
||||
| **Claude Code** | ✅ Full Support | `.claude/skills/` |
|
||||
| **Gemini CLI** | ✅ Full Support | `.gemini/skills/` |
|
||||
| **Codex CLI** | ✅ Full Support | `.codex/skills/` |
|
||||
| **Antigravity** | ✅ Native | `.agent/skills/` |
|
||||
| **Cursor** | ✅ Native | `.cursor/skills/` |
|
||||
| **Copilot** | ⚠️ Text Only | Manual copy-paste |
|
||||
@@ -100,7 +103,7 @@ _Check the [Skill Catalog](../CATALOG.md) for the full list._
|
||||
|
||||
## ❓ FAQ
|
||||
|
||||
**Q: Do I need to install all 624 skills?**
|
||||
**Q: Do I need to install all 626 skills?**
|
||||
A: You clone the whole repo once; your AI only _reads_ the skills you invoke (or that are relevant), so it stays lightweight. **Starter Packs** in [BUNDLES.md](BUNDLES.md) are curated lists to help you discover the right skills for your role—they don't change how you install.
|
||||
|
||||
**Q: Can I make my own skills?**
|
||||
|
||||
@@ -21,6 +21,7 @@ The skill MUST have a section explicitly stating when to trigger it.
|
||||
|
||||
- **Good**: "Use when the user asks to debug a React component."
|
||||
- **Bad**: "This skill helps you with code."
|
||||
Accepted headings: `## When to Use`, `## Use this skill when`, `## When to Use This Skill`.
|
||||
|
||||
### 3. Safety & Risk Classification
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ Some skills include additional metadata:
|
||||
---
|
||||
name: my-skill-name
|
||||
description: "Brief description"
|
||||
risk: "safe" # safe | risk | official
|
||||
risk: "safe" # none | safe | critical | offensive (see QUALITY_BAR.md)
|
||||
source: "community"
|
||||
tags: ["react", "typescript"]
|
||||
---
|
||||
|
||||
@@ -73,7 +73,7 @@ Một số skill bao gồm thêm siêu dữ liệu bổ sung:
|
||||
---
|
||||
name: my-skill-name
|
||||
description: "Mô tả ngắn"
|
||||
risk: "safe" # safe | risk | official
|
||||
risk: "safe" # none | safe | critical | offensive (xem QUALITY_BAR.md)
|
||||
source: "community"
|
||||
tags: ["react", "typescript"]
|
||||
---
|
||||
|
||||
@@ -159,8 +159,29 @@ function listSkillIds(skillsDir) {
|
||||
.sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively list all skill directory paths under skillsDir (relative paths).
|
||||
* Matches generate_index.py behavior so catalog includes nested skills (e.g. game-development/2d-games).
|
||||
*/
|
||||
function listSkillIdsRecursive(skillsDir, baseDir = skillsDir, acc = []) {
|
||||
const entries = fs.readdirSync(baseDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.name.startsWith('.')) continue;
|
||||
if (!entry.isDirectory()) continue;
|
||||
const dirPath = path.join(baseDir, entry.name);
|
||||
const skillPath = path.join(dirPath, 'SKILL.md');
|
||||
const relPath = path.relative(skillsDir, dirPath);
|
||||
if (fs.existsSync(skillPath)) {
|
||||
acc.push(relPath);
|
||||
}
|
||||
listSkillIdsRecursive(skillsDir, dirPath, acc);
|
||||
}
|
||||
return acc.sort();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
listSkillIds,
|
||||
listSkillIdsRecursive,
|
||||
parseFrontmatter,
|
||||
parseInlineList,
|
||||
readSkill,
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "4.2.0",
|
||||
"version": "4.8.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "4.2.0",
|
||||
"version": "4.8.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
|
||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "antigravity-awesome-skills",
|
||||
"version": "4.6.0",
|
||||
"version": "4.8.0",
|
||||
"description": "626+ agentic skills for Claude Code, Gemini CLI, Cursor, Antigravity & more. Installer CLI.",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
@@ -10,17 +10,18 @@
|
||||
"readme": "python3 scripts/update_readme.py",
|
||||
"chain": "npm run validate && npm run index && npm run readme",
|
||||
"catalog": "node scripts/build-catalog.js",
|
||||
"build": "npm run chain && npm run catalog"
|
||||
"build": "npm run chain && npm run catalog",
|
||||
"test": "node scripts/tests/validate_skills_headings.test.js && python3 scripts/tests/test_validate_skills_headings.py"
|
||||
},
|
||||
"dependencies": {
|
||||
"devDependencies": {
|
||||
"yaml": "^2.8.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sickn33/antigravity-awesome-skills.git"
|
||||
"url": "git+https://github.com/sickn33/antigravity-awesome-skills.git"
|
||||
},
|
||||
"bin": {
|
||||
"antigravity-awesome-skills": "./bin/install.js"
|
||||
"antigravity-awesome-skills": "bin/install.js"
|
||||
},
|
||||
"files": [
|
||||
"bin"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const {
|
||||
listSkillIds,
|
||||
listSkillIdsRecursive,
|
||||
readSkill,
|
||||
tokenize,
|
||||
unique,
|
||||
@@ -297,8 +297,8 @@ function renderCatalogMarkdown(catalog) {
|
||||
}
|
||||
|
||||
function buildCatalog() {
|
||||
const skillIds = listSkillIds(SKILLS_DIR);
|
||||
const skills = skillIds.map(skillId => readSkill(SKILLS_DIR, skillId));
|
||||
const skillRelPaths = listSkillIdsRecursive(SKILLS_DIR);
|
||||
const skills = skillRelPaths.map(relPath => readSkill(SKILLS_DIR, relPath));
|
||||
const catalogSkills = [];
|
||||
|
||||
for (const skill of skills) {
|
||||
|
||||
@@ -8,6 +8,8 @@ const SKILLS_DIR = path.join(ROOT, 'skills');
|
||||
const ALLOWED_FIELDS = new Set([
|
||||
'name',
|
||||
'description',
|
||||
'risk',
|
||||
'source',
|
||||
'license',
|
||||
'compatibility',
|
||||
'metadata',
|
||||
|
||||
@@ -21,8 +21,12 @@ python3 scripts/generate_index.py
|
||||
echo "Running update_readme.py..."
|
||||
python3 scripts/update_readme.py
|
||||
|
||||
# 2. Stats Consistency Check
|
||||
echo -e "\n${YELLOW}Step 2: verifying Stats Consistency...${NC}"
|
||||
# 2. Catalog (required for CI)
|
||||
echo -e "\n${YELLOW}Step 2: Build catalog...${NC}"
|
||||
npm run catalog
|
||||
|
||||
# 3. Stats Consistency Check
|
||||
echo -e "\n${YELLOW}Step 3: Verifying Stats Consistency...${NC}"
|
||||
JSON_COUNT=$(python3 -c "import json; print(len(json.load(open('skills_index.json'))))")
|
||||
echo "Skills in Registry (JSON): $JSON_COUNT"
|
||||
|
||||
@@ -36,8 +40,14 @@ if [[ "$README_CONTENT" != *"$JSON_COUNT high-performance"* ]]; then
|
||||
fi
|
||||
echo -e "${GREEN}✅ Stats Consistent.${NC}"
|
||||
|
||||
# 3. Contributor Check
|
||||
echo -e "\n${YELLOW}Step 3: Contributor Check${NC}"
|
||||
# 4. Version check (package.json is source of truth for npm)
|
||||
echo -e "\n${YELLOW}Step 4: Version check${NC}"
|
||||
PKG_VERSION=$(node -p "require('./package.json').version")
|
||||
echo "package.json version: $PKG_VERSION"
|
||||
echo "Ensure this version is bumped before 'npm publish' (npm forbids republishing the same version)."
|
||||
|
||||
# 5. Contributor Check
|
||||
echo -e "\n${YELLOW}Step 5: Contributor Check${NC}"
|
||||
echo "Recent commits by author (check against README 'Repo Contributors'):"
|
||||
git shortlog -sn --since="1 month ago" --all --no-merges | head -n 10
|
||||
|
||||
@@ -52,4 +62,5 @@ if [ "$CONFIRM_CONTRIB" != "yes" ]; then
|
||||
fi
|
||||
|
||||
echo -e "\n${GREEN}✅ Release Cycle Checks Passed. You may now commit and push.${NC}"
|
||||
echo -e "${YELLOW}After tagging a release: run \`npm publish\` from repo root (or use GitHub Release + NPM_TOKEN for CI).${NC}"
|
||||
exit 0
|
||||
|
||||
18
scripts/tests/test_validate_skills_headings.py
Normal file
18
scripts/tests/test_validate_skills_headings.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
|
||||
from validate_skills import has_when_to_use_section
|
||||
|
||||
SAMPLES = [
|
||||
("## When to Use", True),
|
||||
("## Use this skill when", True),
|
||||
("## When to Use This Skill", True),
|
||||
("## Overview", False),
|
||||
]
|
||||
|
||||
for heading, expected in SAMPLES:
|
||||
content = f"\n{heading}\n- item\n"
|
||||
assert has_when_to_use_section(content) is expected, heading
|
||||
|
||||
print("ok")
|
||||
16
scripts/tests/validate_skills_headings.test.js
Normal file
16
scripts/tests/validate_skills_headings.test.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const assert = require('assert');
|
||||
const { hasUseSection } = require('../validate-skills');
|
||||
|
||||
const samples = [
|
||||
['## When to Use', true],
|
||||
['## Use this skill when', true],
|
||||
['## When to Use This Skill', true],
|
||||
['## Overview', false],
|
||||
];
|
||||
|
||||
for (const [heading, expected] of samples) {
|
||||
const content = `\n${heading}\n- item\n`;
|
||||
assert.strictEqual(hasUseSection(content), expected, heading);
|
||||
}
|
||||
|
||||
console.log('ok');
|
||||
@@ -36,7 +36,7 @@ def update_readme():
|
||||
|
||||
# 3. Update Intro Text Count
|
||||
content = re.sub(
|
||||
r"(library of \*\*)\d+( high-performance skills\*\*)",
|
||||
r"(library of \*\*)\d+( high-performance agentic skills\*\*)",
|
||||
rf"\g<1>{total_skills}\g<2>",
|
||||
content,
|
||||
)
|
||||
|
||||
@@ -32,12 +32,24 @@ const MAX_SKILL_LINES = 500;
|
||||
const ALLOWED_FIELDS = new Set([
|
||||
'name',
|
||||
'description',
|
||||
'risk',
|
||||
'source',
|
||||
'license',
|
||||
'compatibility',
|
||||
'metadata',
|
||||
'allowed-tools',
|
||||
]);
|
||||
|
||||
const USE_SECTION_PATTERNS = [
|
||||
/^##\s+When\s+to\s+Use/im,
|
||||
/^##\s+Use\s+this\s+skill\s+when/im,
|
||||
/^##\s+When\s+to\s+Use\s+This\s+Skill/im,
|
||||
];
|
||||
|
||||
function hasUseSection(content) {
|
||||
return USE_SECTION_PATTERNS.some(pattern => pattern.test(content));
|
||||
}
|
||||
|
||||
function isPlainObject(value) {
|
||||
return value && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
@@ -99,172 +111,183 @@ function addStrictSectionErrors(label, missing, baselineSet) {
|
||||
}
|
||||
}
|
||||
|
||||
const skillIds = listSkillIds(SKILLS_DIR);
|
||||
const baseline = loadBaseline();
|
||||
const baselineUse = new Set(baseline.useSection || []);
|
||||
const baselineDoNotUse = new Set(baseline.doNotUseSection || []);
|
||||
const baselineInstructions = new Set(baseline.instructionsSection || []);
|
||||
const baselineLongFile = new Set(baseline.longFile || []);
|
||||
function run() {
|
||||
const skillIds = listSkillIds(SKILLS_DIR);
|
||||
const baseline = loadBaseline();
|
||||
const baselineUse = new Set(baseline.useSection || []);
|
||||
const baselineDoNotUse = new Set(baseline.doNotUseSection || []);
|
||||
const baselineInstructions = new Set(baseline.instructionsSection || []);
|
||||
const baselineLongFile = new Set(baseline.longFile || []);
|
||||
|
||||
for (const skillId of skillIds) {
|
||||
const skillPath = path.join(SKILLS_DIR, skillId, 'SKILL.md');
|
||||
for (const skillId of skillIds) {
|
||||
const skillPath = path.join(SKILLS_DIR, skillId, 'SKILL.md');
|
||||
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
addError(`Missing SKILL.md: ${skillId}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(skillPath, 'utf8');
|
||||
const { data, errors: fmErrors, hasFrontmatter } = parseFrontmatter(content);
|
||||
const lineCount = content.split(/\r?\n/).length;
|
||||
|
||||
if (!hasFrontmatter) {
|
||||
addError(`Missing frontmatter: ${skillId}`);
|
||||
}
|
||||
|
||||
if (fmErrors && fmErrors.length) {
|
||||
fmErrors.forEach(error => addError(`Frontmatter parse error (${skillId}): ${error}`));
|
||||
}
|
||||
|
||||
if (!NAME_PATTERN.test(skillId)) {
|
||||
addError(`Folder name must match ${NAME_PATTERN}: ${skillId}`);
|
||||
}
|
||||
|
||||
if (data.name !== undefined) {
|
||||
const nameError = validateStringField('name', data.name, { min: 1, max: MAX_NAME_LENGTH });
|
||||
if (nameError) {
|
||||
addError(`${nameError} (${skillId})`);
|
||||
} else {
|
||||
const nameValue = String(data.name).trim();
|
||||
if (!NAME_PATTERN.test(nameValue)) {
|
||||
addError(`name must match ${NAME_PATTERN}: ${skillId}`);
|
||||
}
|
||||
if (nameValue !== skillId) {
|
||||
addError(`name must match folder name: ${skillId} -> ${nameValue}`);
|
||||
}
|
||||
if (!fs.existsSync(skillPath)) {
|
||||
addError(`Missing SKILL.md: ${skillId}`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const descError = data.description === undefined
|
||||
? 'description is required.'
|
||||
: validateStringField('description', data.description, { min: 1, max: MAX_DESCRIPTION_LENGTH });
|
||||
if (descError) {
|
||||
addError(`${descError} (${skillId})`);
|
||||
}
|
||||
const content = fs.readFileSync(skillPath, 'utf8');
|
||||
const { data, errors: fmErrors, hasFrontmatter } = parseFrontmatter(content);
|
||||
const lineCount = content.split(/\r?\n/).length;
|
||||
|
||||
if (data.license !== undefined) {
|
||||
const licenseError = validateStringField('license', data.license, { min: 1, max: 128 });
|
||||
if (licenseError) {
|
||||
addError(`${licenseError} (${skillId})`);
|
||||
if (!hasFrontmatter) {
|
||||
addError(`Missing frontmatter: ${skillId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.compatibility !== undefined) {
|
||||
const compatibilityError = validateStringField(
|
||||
'compatibility',
|
||||
data.compatibility,
|
||||
{ min: 1, max: MAX_COMPATIBILITY_LENGTH },
|
||||
);
|
||||
if (compatibilityError) {
|
||||
addError(`${compatibilityError} (${skillId})`);
|
||||
if (fmErrors && fmErrors.length) {
|
||||
fmErrors.forEach(error => addError(`Frontmatter parse error (${skillId}): ${error}`));
|
||||
}
|
||||
}
|
||||
|
||||
if (data['allowed-tools'] !== undefined) {
|
||||
if (typeof data['allowed-tools'] !== 'string') {
|
||||
addError(`allowed-tools must be a space-delimited string. (${skillId})`);
|
||||
} else if (!data['allowed-tools'].trim()) {
|
||||
addError(`allowed-tools cannot be empty. (${skillId})`);
|
||||
if (!NAME_PATTERN.test(skillId)) {
|
||||
addError(`Folder name must match ${NAME_PATTERN}: ${skillId}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.metadata !== undefined) {
|
||||
if (!isPlainObject(data.metadata)) {
|
||||
addError(`metadata must be a string map/object. (${skillId})`);
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(data.metadata)) {
|
||||
if (typeof value !== 'string') {
|
||||
addError(`metadata.${key} must be a string. (${skillId})`);
|
||||
if (data.name !== undefined) {
|
||||
const nameError = validateStringField('name', data.name, { min: 1, max: MAX_NAME_LENGTH });
|
||||
if (nameError) {
|
||||
addError(`${nameError} (${skillId})`);
|
||||
} else {
|
||||
const nameValue = String(data.name).trim();
|
||||
if (!NAME_PATTERN.test(nameValue)) {
|
||||
addError(`name must match ${NAME_PATTERN}: ${skillId}`);
|
||||
}
|
||||
if (nameValue !== skillId) {
|
||||
addError(`name must match folder name: ${skillId} -> ${nameValue}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data && Object.keys(data).length) {
|
||||
const unknownFields = Object.keys(data).filter(key => !ALLOWED_FIELDS.has(key));
|
||||
if (unknownFields.length) {
|
||||
unknownFieldSkills.push(skillId);
|
||||
addError(`Unknown frontmatter fields (${skillId}): ${unknownFields.join(', ')}`);
|
||||
const descError = data.description === undefined
|
||||
? 'description is required.'
|
||||
: validateStringField('description', data.description, { min: 1, max: MAX_DESCRIPTION_LENGTH });
|
||||
if (descError) {
|
||||
addError(`${descError} (${skillId})`);
|
||||
}
|
||||
|
||||
if (data.license !== undefined) {
|
||||
const licenseError = validateStringField('license', data.license, { min: 1, max: 128 });
|
||||
if (licenseError) {
|
||||
addError(`${licenseError} (${skillId})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.compatibility !== undefined) {
|
||||
const compatibilityError = validateStringField(
|
||||
'compatibility',
|
||||
data.compatibility,
|
||||
{ min: 1, max: MAX_COMPATIBILITY_LENGTH },
|
||||
);
|
||||
if (compatibilityError) {
|
||||
addError(`${compatibilityError} (${skillId})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data['allowed-tools'] !== undefined) {
|
||||
if (typeof data['allowed-tools'] !== 'string') {
|
||||
addError(`allowed-tools must be a space-delimited string. (${skillId})`);
|
||||
} else if (!data['allowed-tools'].trim()) {
|
||||
addError(`allowed-tools cannot be empty. (${skillId})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.metadata !== undefined) {
|
||||
if (!isPlainObject(data.metadata)) {
|
||||
addError(`metadata must be a string map/object. (${skillId})`);
|
||||
} else {
|
||||
for (const [key, value] of Object.entries(data.metadata)) {
|
||||
if (typeof value !== 'string') {
|
||||
addError(`metadata.${key} must be a string. (${skillId})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data && Object.keys(data).length) {
|
||||
const unknownFields = Object.keys(data).filter(key => !ALLOWED_FIELDS.has(key));
|
||||
if (unknownFields.length) {
|
||||
unknownFieldSkills.push(skillId);
|
||||
addError(`Unknown frontmatter fields (${skillId}): ${unknownFields.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (lineCount > MAX_SKILL_LINES) {
|
||||
longFiles.push(skillId);
|
||||
}
|
||||
|
||||
if (!hasUseSection(content)) {
|
||||
missingUseSection.push(skillId);
|
||||
}
|
||||
|
||||
if (!content.includes('## Do not use')) {
|
||||
missingDoNotUseSection.push(skillId);
|
||||
}
|
||||
|
||||
if (!content.includes('## Instructions')) {
|
||||
missingInstructionsSection.push(skillId);
|
||||
}
|
||||
}
|
||||
|
||||
if (lineCount > MAX_SKILL_LINES) {
|
||||
longFiles.push(skillId);
|
||||
if (missingUseSection.length) {
|
||||
addWarning(`Missing "Use this skill when" section: ${missingUseSection.length} skills (examples: ${missingUseSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (!content.includes('## Use this skill when')) {
|
||||
missingUseSection.push(skillId);
|
||||
if (missingDoNotUseSection.length) {
|
||||
addWarning(`Missing "Do not use" section: ${missingDoNotUseSection.length} skills (examples: ${missingDoNotUseSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (!content.includes('## Do not use')) {
|
||||
missingDoNotUseSection.push(skillId);
|
||||
if (missingInstructionsSection.length) {
|
||||
addWarning(`Missing "Instructions" section: ${missingInstructionsSection.length} skills (examples: ${missingInstructionsSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (!content.includes('## Instructions')) {
|
||||
missingInstructionsSection.push(skillId);
|
||||
if (longFiles.length) {
|
||||
addWarning(`SKILL.md over ${MAX_SKILL_LINES} lines: ${longFiles.length} skills (examples: ${longFiles.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingUseSection.length) {
|
||||
addWarning(`Missing "Use this skill when" section: ${missingUseSection.length} skills (examples: ${missingUseSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (missingDoNotUseSection.length) {
|
||||
addWarning(`Missing "Do not use" section: ${missingDoNotUseSection.length} skills (examples: ${missingDoNotUseSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (missingInstructionsSection.length) {
|
||||
addWarning(`Missing "Instructions" section: ${missingInstructionsSection.length} skills (examples: ${missingInstructionsSection.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (longFiles.length) {
|
||||
addWarning(`SKILL.md over ${MAX_SKILL_LINES} lines: ${longFiles.length} skills (examples: ${longFiles.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
if (unknownFieldSkills.length) {
|
||||
addWarning(`Unknown frontmatter fields detected: ${unknownFieldSkills.length} skills (examples: ${unknownFieldSkills.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
|
||||
addStrictSectionErrors('Use this skill when', missingUseSection, baselineUse);
|
||||
addStrictSectionErrors('Do not use', missingDoNotUseSection, baselineDoNotUse);
|
||||
addStrictSectionErrors('Instructions', missingInstructionsSection, baselineInstructions);
|
||||
addStrictSectionErrors(`SKILL.md line count <= ${MAX_SKILL_LINES}`, longFiles, baselineLongFile);
|
||||
|
||||
if (writeBaseline) {
|
||||
const baselineData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
useSection: [...missingUseSection].sort(),
|
||||
doNotUseSection: [...missingDoNotUseSection].sort(),
|
||||
instructionsSection: [...missingInstructionsSection].sort(),
|
||||
longFile: [...longFiles].sort(),
|
||||
};
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineData, null, 2));
|
||||
console.log(`Baseline written to ${BASELINE_PATH}`);
|
||||
}
|
||||
|
||||
if (warnings.length) {
|
||||
console.warn('Warnings:');
|
||||
for (const warning of warnings) {
|
||||
console.warn(`- ${warning}`);
|
||||
if (unknownFieldSkills.length) {
|
||||
addWarning(`Unknown frontmatter fields detected: ${unknownFieldSkills.length} skills (examples: ${unknownFieldSkills.slice(0, 5).join(', ')})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error('\nErrors:');
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
addStrictSectionErrors('Use this skill when', missingUseSection, baselineUse);
|
||||
addStrictSectionErrors('Do not use', missingDoNotUseSection, baselineDoNotUse);
|
||||
addStrictSectionErrors('Instructions', missingInstructionsSection, baselineInstructions);
|
||||
addStrictSectionErrors(`SKILL.md line count <= ${MAX_SKILL_LINES}`, longFiles, baselineLongFile);
|
||||
|
||||
if (writeBaseline) {
|
||||
const baselineData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
useSection: [...missingUseSection].sort(),
|
||||
doNotUseSection: [...missingDoNotUseSection].sort(),
|
||||
instructionsSection: [...missingInstructionsSection].sort(),
|
||||
longFile: [...longFiles].sort(),
|
||||
};
|
||||
fs.writeFileSync(BASELINE_PATH, JSON.stringify(baselineData, null, 2));
|
||||
console.log(`Baseline written to ${BASELINE_PATH}`);
|
||||
}
|
||||
process.exit(1);
|
||||
|
||||
if (warnings.length) {
|
||||
console.warn('Warnings:');
|
||||
for (const warning of warnings) {
|
||||
console.warn(`- ${warning}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (errors.length) {
|
||||
console.error('\nErrors:');
|
||||
for (const error of errors) {
|
||||
console.error(`- ${error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Validation passed for ${skillIds.length} skills.`);
|
||||
}
|
||||
|
||||
console.log(`Validation passed for ${skillIds.length} skills.`);
|
||||
if (require.main === module) {
|
||||
run();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
hasUseSection,
|
||||
run,
|
||||
};
|
||||
|
||||
@@ -3,6 +3,15 @@ import re
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
WHEN_TO_USE_PATTERNS = [
|
||||
re.compile(r"^##\s+When\s+to\s+Use", re.MULTILINE | re.IGNORECASE),
|
||||
re.compile(r"^##\s+Use\s+this\s+skill\s+when", re.MULTILINE | re.IGNORECASE),
|
||||
re.compile(r"^##\s+When\s+to\s+Use\s+This\s+Skill", re.MULTILINE | re.IGNORECASE),
|
||||
]
|
||||
|
||||
def has_when_to_use_section(content):
|
||||
return any(pattern.search(content) for pattern in WHEN_TO_USE_PATTERNS)
|
||||
|
||||
def parse_frontmatter(content):
|
||||
"""
|
||||
Simple frontmatter parser using regex to avoid external dependencies.
|
||||
@@ -30,7 +39,6 @@ def validate_skills(skills_dir, strict_mode=False):
|
||||
|
||||
# Pre-compiled regex
|
||||
security_disclaimer_pattern = re.compile(r"AUTHORIZED USE ONLY", re.IGNORECASE)
|
||||
trigger_section_pattern = re.compile(r"^##\s+When to Use", re.MULTILINE | re.IGNORECASE)
|
||||
|
||||
valid_risk_levels = ["none", "safe", "critical", "offensive"]
|
||||
|
||||
@@ -80,7 +88,7 @@ def validate_skills(skills_dir, strict_mode=False):
|
||||
else: warnings.append(msg)
|
||||
|
||||
# 3. Content Checks (Triggers)
|
||||
if not trigger_section_pattern.search(content):
|
||||
if not has_when_to_use_section(content):
|
||||
msg = f"⚠️ {rel_path}: Missing '## When to Use' section"
|
||||
if strict_mode: errors.append(msg.replace("⚠️", "❌"))
|
||||
else: warnings.append(msg)
|
||||
|
||||
559
skills/angular-best-practices/SKILL.md
Normal file
559
skills/angular-best-practices/SKILL.md
Normal file
@@ -0,0 +1,559 @@
|
||||
---
|
||||
name: angular-best-practices
|
||||
description: Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.
|
||||
risk: safe
|
||||
source: self
|
||||
---
|
||||
|
||||
# Angular Best Practices
|
||||
|
||||
Comprehensive performance optimization guide for Angular applications. Contains prioritized rules for eliminating performance bottlenecks, optimizing bundles, and improving rendering.
|
||||
|
||||
## When to Apply
|
||||
|
||||
Reference these guidelines when:
|
||||
|
||||
- Writing new Angular components or pages
|
||||
- Implementing data fetching patterns
|
||||
- Reviewing code for performance issues
|
||||
- Refactoring existing Angular code
|
||||
- Optimizing bundle size or load times
|
||||
- Configuring SSR/hydration
|
||||
|
||||
---
|
||||
|
||||
## Rule Categories by Priority
|
||||
|
||||
| Priority | Category | Impact | Focus |
|
||||
| -------- | --------------------- | ---------- | ------------------------------- |
|
||||
| 1 | Change Detection | CRITICAL | Signals, OnPush, Zoneless |
|
||||
| 2 | Async Waterfalls | CRITICAL | RxJS patterns, SSR preloading |
|
||||
| 3 | Bundle Optimization | CRITICAL | Lazy loading, tree shaking |
|
||||
| 4 | Rendering Performance | HIGH | @defer, trackBy, virtualization |
|
||||
| 5 | Server-Side Rendering | HIGH | Hydration, prerendering |
|
||||
| 6 | Template Optimization | MEDIUM | Control flow, pipes |
|
||||
| 7 | State Management | MEDIUM | Signal patterns, selectors |
|
||||
| 8 | Memory Management | LOW-MEDIUM | Cleanup, subscriptions |
|
||||
|
||||
---
|
||||
|
||||
## 1. Change Detection (CRITICAL)
|
||||
|
||||
### Use OnPush Change Detection
|
||||
|
||||
```typescript
|
||||
// CORRECT - OnPush with Signals
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `<div>{{ count() }}</div>`,
|
||||
})
|
||||
export class CounterComponent {
|
||||
count = signal(0);
|
||||
}
|
||||
|
||||
// WRONG - Default change detection
|
||||
@Component({
|
||||
template: `<div>{{ count }}</div>`, // Checked every cycle
|
||||
})
|
||||
export class CounterComponent {
|
||||
count = 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Prefer Signals Over Mutable Properties
|
||||
|
||||
```typescript
|
||||
// CORRECT - Signals trigger precise updates
|
||||
@Component({
|
||||
template: `
|
||||
<h1>{{ title() }}</h1>
|
||||
<p>Count: {{ count() }}</p>
|
||||
`,
|
||||
})
|
||||
export class DashboardComponent {
|
||||
title = signal("Dashboard");
|
||||
count = signal(0);
|
||||
}
|
||||
|
||||
// WRONG - Mutable properties require zone.js checks
|
||||
@Component({
|
||||
template: `
|
||||
<h1>{{ title }}</h1>
|
||||
<p>Count: {{ count }}</p>
|
||||
`,
|
||||
})
|
||||
export class DashboardComponent {
|
||||
title = "Dashboard";
|
||||
count = 0;
|
||||
}
|
||||
```
|
||||
|
||||
### Enable Zoneless for New Projects
|
||||
|
||||
```typescript
|
||||
// main.ts - Zoneless Angular (v20+)
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
|
||||
- No zone.js patches on async APIs
|
||||
- Smaller bundle (~15KB savings)
|
||||
- Clean stack traces for debugging
|
||||
- Better micro-frontend compatibility
|
||||
|
||||
---
|
||||
|
||||
## 2. Async Operations & Waterfalls (CRITICAL)
|
||||
|
||||
### Eliminate Sequential Data Fetching
|
||||
|
||||
```typescript
|
||||
// WRONG - Nested subscriptions create waterfalls
|
||||
this.route.params.subscribe((params) => {
|
||||
// 1. Wait for params
|
||||
this.userService.getUser(params.id).subscribe((user) => {
|
||||
// 2. Wait for user
|
||||
this.postsService.getPosts(user.id).subscribe((posts) => {
|
||||
// 3. Wait for posts
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// CORRECT - Parallel execution with forkJoin
|
||||
forkJoin({
|
||||
user: this.userService.getUser(id),
|
||||
posts: this.postsService.getPosts(id),
|
||||
}).subscribe((data) => {
|
||||
// Fetched in parallel
|
||||
});
|
||||
|
||||
// CORRECT - Flatten dependent calls with switchMap
|
||||
this.route.params
|
||||
.pipe(
|
||||
map((p) => p.id),
|
||||
switchMap((id) => this.userService.getUser(id)),
|
||||
)
|
||||
.subscribe();
|
||||
```
|
||||
|
||||
### Avoid Client-Side Waterfalls in SSR
|
||||
|
||||
```typescript
|
||||
// CORRECT - Use resolvers or blocking hydration for critical data
|
||||
export const route: Route = {
|
||||
path: "profile/:id",
|
||||
resolve: { data: profileResolver }, // Fetched on server before navigation
|
||||
component: ProfileComponent,
|
||||
};
|
||||
|
||||
// WRONG - Component fetches data on init
|
||||
class ProfileComponent implements OnInit {
|
||||
ngOnInit() {
|
||||
// Starts ONLY after JS loads and component renders
|
||||
this.http.get("/api/profile").subscribe();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Bundle Optimization (CRITICAL)
|
||||
|
||||
### Lazy Load Routes
|
||||
|
||||
```typescript
|
||||
// CORRECT - Lazy load feature routes
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: "admin",
|
||||
loadChildren: () =>
|
||||
import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
|
||||
},
|
||||
{
|
||||
path: "dashboard",
|
||||
loadComponent: () =>
|
||||
import("./dashboard/dashboard.component").then(
|
||||
(m) => m.DashboardComponent,
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// WRONG - Eager loading everything
|
||||
import { AdminModule } from "./admin/admin.module";
|
||||
export const routes: Routes = [
|
||||
{ path: "admin", component: AdminComponent }, // In main bundle
|
||||
];
|
||||
```
|
||||
|
||||
### Use @defer for Heavy Components
|
||||
|
||||
```html
|
||||
<!-- CORRECT - Heavy component loads on demand -->
|
||||
@defer (on viewport) {
|
||||
<app-analytics-chart [data]="data()" />
|
||||
} @placeholder {
|
||||
<div class="chart-skeleton"></div>
|
||||
}
|
||||
|
||||
<!-- WRONG - Heavy component in initial bundle -->
|
||||
<app-analytics-chart [data]="data()" />
|
||||
```
|
||||
|
||||
### Avoid Barrel File Re-exports
|
||||
|
||||
```typescript
|
||||
// WRONG - Imports entire barrel, breaks tree-shaking
|
||||
import { Button, Modal, Table } from "@shared/components";
|
||||
|
||||
// CORRECT - Direct imports
|
||||
import { Button } from "@shared/components/button/button.component";
|
||||
import { Modal } from "@shared/components/modal/modal.component";
|
||||
```
|
||||
|
||||
### Dynamic Import Third-Party Libraries
|
||||
|
||||
```typescript
|
||||
// CORRECT - Load heavy library on demand
|
||||
async loadChart() {
|
||||
const { Chart } = await import('chart.js');
|
||||
this.chart = new Chart(this.canvas, config);
|
||||
}
|
||||
|
||||
// WRONG - Bundle Chart.js in main chunk
|
||||
import { Chart } from 'chart.js';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Rendering Performance (HIGH)
|
||||
|
||||
### Always Use trackBy with @for
|
||||
|
||||
```html
|
||||
<!-- CORRECT - Efficient DOM updates -->
|
||||
@for (item of items(); track item.id) {
|
||||
<app-item-card [item]="item" />
|
||||
}
|
||||
|
||||
<!-- WRONG - Entire list re-renders on any change -->
|
||||
@for (item of items(); track $index) {
|
||||
<app-item-card [item]="item" />
|
||||
}
|
||||
```
|
||||
|
||||
### Use Virtual Scrolling for Large Lists
|
||||
|
||||
```typescript
|
||||
import { CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll } from '@angular/cdk/scrolling';
|
||||
|
||||
@Component({
|
||||
imports: [CdkVirtualScrollViewport, CdkFixedSizeVirtualScroll],
|
||||
template: `
|
||||
<cdk-virtual-scroll-viewport itemSize="50" class="viewport">
|
||||
<div *cdkVirtualFor="let item of items" class="item">
|
||||
{{ item.name }}
|
||||
</div>
|
||||
</cdk-virtual-scroll-viewport>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### Prefer Pure Pipes Over Methods
|
||||
|
||||
```typescript
|
||||
// CORRECT - Pure pipe, memoized
|
||||
@Pipe({ name: 'filterActive', standalone: true, pure: true })
|
||||
export class FilterActivePipe implements PipeTransform {
|
||||
transform(items: Item[]): Item[] {
|
||||
return items.filter(i => i.active);
|
||||
}
|
||||
}
|
||||
|
||||
// Template
|
||||
@for (item of items() | filterActive; track item.id) { ... }
|
||||
|
||||
// WRONG - Method called every change detection
|
||||
@for (item of getActiveItems(); track item.id) { ... }
|
||||
```
|
||||
|
||||
### Use computed() for Derived Data
|
||||
|
||||
```typescript
|
||||
// CORRECT - Computed, cached until dependencies change
|
||||
export class ProductStore {
|
||||
products = signal<Product[]>([]);
|
||||
filter = signal('');
|
||||
|
||||
filteredProducts = computed(() => {
|
||||
const f = this.filter().toLowerCase();
|
||||
return this.products().filter(p =>
|
||||
p.name.toLowerCase().includes(f)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// WRONG - Recalculates every access
|
||||
get filteredProducts() {
|
||||
return this.products.filter(p =>
|
||||
p.name.toLowerCase().includes(this.filter)
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Server-Side Rendering (HIGH)
|
||||
|
||||
### Configure Incremental Hydration
|
||||
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import {
|
||||
provideClientHydration,
|
||||
withIncrementalHydration,
|
||||
} from "@angular/platform-browser";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideClientHydration(withIncrementalHydration(), withEventReplay()),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Defer Non-Critical Content
|
||||
|
||||
```html
|
||||
<!-- Critical above-the-fold content -->
|
||||
<app-header />
|
||||
<app-hero />
|
||||
|
||||
<!-- Below-fold deferred with hydration triggers -->
|
||||
@defer (hydrate on viewport) {
|
||||
<app-product-grid />
|
||||
} @defer (hydrate on interaction) {
|
||||
<app-chat-widget />
|
||||
}
|
||||
```
|
||||
|
||||
### Use TransferState for SSR Data
|
||||
|
||||
```typescript
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class DataService {
|
||||
private http = inject(HttpClient);
|
||||
private transferState = inject(TransferState);
|
||||
private platformId = inject(PLATFORM_ID);
|
||||
|
||||
getData(key: string): Observable<Data> {
|
||||
const stateKey = makeStateKey<Data>(key);
|
||||
|
||||
if (isPlatformBrowser(this.platformId)) {
|
||||
const cached = this.transferState.get(stateKey, null);
|
||||
if (cached) {
|
||||
this.transferState.remove(stateKey);
|
||||
return of(cached);
|
||||
}
|
||||
}
|
||||
|
||||
return this.http.get<Data>(`/api/${key}`).pipe(
|
||||
tap((data) => {
|
||||
if (isPlatformServer(this.platformId)) {
|
||||
this.transferState.set(stateKey, data);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Template Optimization (MEDIUM)
|
||||
|
||||
### Use New Control Flow Syntax
|
||||
|
||||
```html
|
||||
<!-- CORRECT - New control flow (faster, smaller bundle) -->
|
||||
@if (user()) {
|
||||
<span>{{ user()!.name }}</span>
|
||||
} @else {
|
||||
<span>Guest</span>
|
||||
} @for (item of items(); track item.id) {
|
||||
<app-item [item]="item" />
|
||||
} @empty {
|
||||
<p>No items</p>
|
||||
}
|
||||
|
||||
<!-- WRONG - Legacy structural directives -->
|
||||
<span *ngIf="user; else guest">{{ user.name }}</span>
|
||||
<ng-template #guest><span>Guest</span></ng-template>
|
||||
```
|
||||
|
||||
### Avoid Complex Template Expressions
|
||||
|
||||
```typescript
|
||||
// CORRECT - Precompute in component
|
||||
class Component {
|
||||
items = signal<Item[]>([]);
|
||||
sortedItems = computed(() =>
|
||||
[...this.items()].sort((a, b) => a.name.localeCompare(b.name))
|
||||
);
|
||||
}
|
||||
|
||||
// Template
|
||||
@for (item of sortedItems(); track item.id) { ... }
|
||||
|
||||
// WRONG - Sorting in template every render
|
||||
@for (item of items() | sort:'name'; track item.id) { ... }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. State Management (MEDIUM)
|
||||
|
||||
### Use Selectors to Prevent Re-renders
|
||||
|
||||
```typescript
|
||||
// CORRECT - Selective subscription
|
||||
@Component({
|
||||
template: `<span>{{ userName() }}</span>`,
|
||||
})
|
||||
class HeaderComponent {
|
||||
private store = inject(Store);
|
||||
// Only re-renders when userName changes
|
||||
userName = this.store.selectSignal(selectUserName);
|
||||
}
|
||||
|
||||
// WRONG - Subscribing to entire state
|
||||
@Component({
|
||||
template: `<span>{{ state().user.name }}</span>`,
|
||||
})
|
||||
class HeaderComponent {
|
||||
private store = inject(Store);
|
||||
// Re-renders on ANY state change
|
||||
state = toSignal(this.store);
|
||||
}
|
||||
```
|
||||
|
||||
### Colocate State with Features
|
||||
|
||||
```typescript
|
||||
// CORRECT - Feature-scoped store
|
||||
@Injectable() // NOT providedIn: 'root'
|
||||
export class ProductStore { ... }
|
||||
|
||||
@Component({
|
||||
providers: [ProductStore], // Scoped to component tree
|
||||
})
|
||||
export class ProductPageComponent {
|
||||
store = inject(ProductStore);
|
||||
}
|
||||
|
||||
// WRONG - Everything in global store
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class GlobalStore {
|
||||
// Contains ALL app state - hard to tree-shake
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Memory Management (LOW-MEDIUM)
|
||||
|
||||
### Use takeUntilDestroyed for Subscriptions
|
||||
|
||||
```typescript
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({...})
|
||||
export class DataComponent {
|
||||
private destroyRef = inject(DestroyRef);
|
||||
|
||||
constructor() {
|
||||
this.data$.pipe(
|
||||
takeUntilDestroyed(this.destroyRef)
|
||||
).subscribe(data => this.process(data));
|
||||
}
|
||||
}
|
||||
|
||||
// WRONG - Manual subscription management
|
||||
export class DataComponent implements OnDestroy {
|
||||
private subscription!: Subscription;
|
||||
|
||||
ngOnInit() {
|
||||
this.subscription = this.data$.subscribe(...);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe(); // Easy to forget
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Prefer Signals Over Subscriptions
|
||||
|
||||
```typescript
|
||||
// CORRECT - No subscription needed
|
||||
@Component({
|
||||
template: `<div>{{ data().name }}</div>`,
|
||||
})
|
||||
export class Component {
|
||||
data = toSignal(this.service.data$, { initialValue: null });
|
||||
}
|
||||
|
||||
// WRONG - Manual subscription
|
||||
@Component({
|
||||
template: `<div>{{ data?.name }}</div>`,
|
||||
})
|
||||
export class Component implements OnInit, OnDestroy {
|
||||
data: Data | null = null;
|
||||
private sub!: Subscription;
|
||||
|
||||
ngOnInit() {
|
||||
this.sub = this.service.data$.subscribe((d) => (this.data = d));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.sub.unsubscribe();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference Checklist
|
||||
|
||||
### New Component
|
||||
|
||||
- [ ] `changeDetection: ChangeDetectionStrategy.OnPush`
|
||||
- [ ] `standalone: true`
|
||||
- [ ] Signals for state (`signal()`, `input()`, `output()`)
|
||||
- [ ] `inject()` for dependencies
|
||||
- [ ] `@for` with `track` expression
|
||||
|
||||
### Performance Review
|
||||
|
||||
- [ ] No methods in templates (use pipes or computed)
|
||||
- [ ] Large lists virtualized
|
||||
- [ ] Heavy components deferred
|
||||
- [ ] Routes lazy-loaded
|
||||
- [ ] Third-party libs dynamically imported
|
||||
|
||||
### SSR Check
|
||||
|
||||
- [ ] Hydration configured
|
||||
- [ ] Critical content renders first
|
||||
- [ ] Non-critical content uses `@defer (hydrate on ...)`
|
||||
- [ ] TransferState for server-fetched data
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Angular Performance Guide](https://angular.dev/best-practices/performance)
|
||||
- [Zoneless Angular](https://angular.dev/guide/experimental/zoneless)
|
||||
- [Angular SSR Guide](https://angular.dev/guide/ssr)
|
||||
- [Change Detection Deep Dive](https://angular.dev/guide/change-detection)
|
||||
634
skills/angular-state-management/SKILL.md
Normal file
634
skills/angular-state-management/SKILL.md
Normal file
@@ -0,0 +1,634 @@
|
||||
---
|
||||
name: angular-state-management
|
||||
description: Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.
|
||||
risk: safe
|
||||
source: self
|
||||
---
|
||||
|
||||
# Angular State Management
|
||||
|
||||
Comprehensive guide to modern Angular state management patterns, from Signal-based local state to global stores and server state synchronization.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Setting up global state management in Angular
|
||||
- Choosing between Signals, NgRx, or Akita
|
||||
- Managing component-level stores
|
||||
- Implementing optimistic updates
|
||||
- Debugging state-related issues
|
||||
- Migrating from legacy state patterns
|
||||
|
||||
## Do Not Use This Skill When
|
||||
|
||||
- The task is unrelated to Angular state management
|
||||
- You need React state management → use `react-state-management`
|
||||
|
||||
---
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### State Categories
|
||||
|
||||
| Type | Description | Solutions |
|
||||
| ---------------- | ---------------------------- | --------------------- |
|
||||
| **Local State** | Component-specific, UI state | Signals, `signal()` |
|
||||
| **Shared State** | Between related components | Signal services |
|
||||
| **Global State** | App-wide, complex | NgRx, Akita, Elf |
|
||||
| **Server State** | Remote data, caching | NgRx Query, RxAngular |
|
||||
| **URL State** | Route parameters | ActivatedRoute |
|
||||
| **Form State** | Input values, validation | Reactive Forms |
|
||||
|
||||
### Selection Criteria
|
||||
|
||||
```
|
||||
Small app, simple state → Signal Services
|
||||
Medium app, moderate state → Component Stores
|
||||
Large app, complex state → NgRx Store
|
||||
Heavy server interaction → NgRx Query + Signal Services
|
||||
Real-time updates → RxAngular + Signals
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start: Signal-Based State
|
||||
|
||||
### Pattern 1: Simple Signal Service
|
||||
|
||||
```typescript
|
||||
// services/counter.service.ts
|
||||
import { Injectable, signal, computed } from "@angular/core";
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class CounterService {
|
||||
// Private writable signals
|
||||
private _count = signal(0);
|
||||
|
||||
// Public read-only
|
||||
readonly count = this._count.asReadonly();
|
||||
readonly doubled = computed(() => this._count() * 2);
|
||||
readonly isPositive = computed(() => this._count() > 0);
|
||||
|
||||
increment() {
|
||||
this._count.update((v) => v + 1);
|
||||
}
|
||||
|
||||
decrement() {
|
||||
this._count.update((v) => v - 1);
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._count.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage in component
|
||||
@Component({
|
||||
template: `
|
||||
<p>Count: {{ counter.count() }}</p>
|
||||
<p>Doubled: {{ counter.doubled() }}</p>
|
||||
<button (click)="counter.increment()">+</button>
|
||||
`,
|
||||
})
|
||||
export class CounterComponent {
|
||||
counter = inject(CounterService);
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 2: Feature Signal Store
|
||||
|
||||
```typescript
|
||||
// stores/user.store.ts
|
||||
import { Injectable, signal, computed, inject } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
|
||||
interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface UserState {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class UserStore {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
// State signals
|
||||
private _user = signal<User | null>(null);
|
||||
private _loading = signal(false);
|
||||
private _error = signal<string | null>(null);
|
||||
|
||||
// Selectors (read-only computed)
|
||||
readonly user = computed(() => this._user());
|
||||
readonly loading = computed(() => this._loading());
|
||||
readonly error = computed(() => this._error());
|
||||
readonly isAuthenticated = computed(() => this._user() !== null);
|
||||
readonly displayName = computed(() => this._user()?.name ?? "Guest");
|
||||
|
||||
// Actions
|
||||
async loadUser(id: string) {
|
||||
this._loading.set(true);
|
||||
this._error.set(null);
|
||||
|
||||
try {
|
||||
const user = await fetch(`/api/users/${id}`).then((r) => r.json());
|
||||
this._user.set(user);
|
||||
} catch (e) {
|
||||
this._error.set("Failed to load user");
|
||||
} finally {
|
||||
this._loading.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
updateUser(updates: Partial<User>) {
|
||||
this._user.update((user) => (user ? { ...user, ...updates } : null));
|
||||
}
|
||||
|
||||
logout() {
|
||||
this._user.set(null);
|
||||
this._error.set(null);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: SignalStore (NgRx Signals)
|
||||
|
||||
```typescript
|
||||
// stores/products.store.ts
|
||||
import {
|
||||
signalStore,
|
||||
withState,
|
||||
withMethods,
|
||||
withComputed,
|
||||
patchState,
|
||||
} from "@ngrx/signals";
|
||||
import { inject } from "@angular/core";
|
||||
import { ProductService } from "./product.service";
|
||||
|
||||
interface ProductState {
|
||||
products: Product[];
|
||||
loading: boolean;
|
||||
filter: string;
|
||||
}
|
||||
|
||||
const initialState: ProductState = {
|
||||
products: [],
|
||||
loading: false,
|
||||
filter: "",
|
||||
};
|
||||
|
||||
export const ProductStore = signalStore(
|
||||
{ providedIn: "root" },
|
||||
|
||||
withState(initialState),
|
||||
|
||||
withComputed((store) => ({
|
||||
filteredProducts: computed(() => {
|
||||
const filter = store.filter().toLowerCase();
|
||||
return store
|
||||
.products()
|
||||
.filter((p) => p.name.toLowerCase().includes(filter));
|
||||
}),
|
||||
totalCount: computed(() => store.products().length),
|
||||
})),
|
||||
|
||||
withMethods((store, productService = inject(ProductService)) => ({
|
||||
async loadProducts() {
|
||||
patchState(store, { loading: true });
|
||||
|
||||
try {
|
||||
const products = await productService.getAll();
|
||||
patchState(store, { products, loading: false });
|
||||
} catch {
|
||||
patchState(store, { loading: false });
|
||||
}
|
||||
},
|
||||
|
||||
setFilter(filter: string) {
|
||||
patchState(store, { filter });
|
||||
},
|
||||
|
||||
addProduct(product: Product) {
|
||||
patchState(store, ({ products }) => ({
|
||||
products: [...products, product],
|
||||
}));
|
||||
},
|
||||
})),
|
||||
);
|
||||
|
||||
// Usage
|
||||
@Component({
|
||||
template: `
|
||||
<input (input)="store.setFilter($event.target.value)" />
|
||||
@if (store.loading()) {
|
||||
<app-spinner />
|
||||
} @else {
|
||||
@for (product of store.filteredProducts(); track product.id) {
|
||||
<app-product-card [product]="product" />
|
||||
}
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class ProductListComponent {
|
||||
store = inject(ProductStore);
|
||||
|
||||
ngOnInit() {
|
||||
this.store.loadProducts();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## NgRx Store (Global State)
|
||||
|
||||
### Setup
|
||||
|
||||
```typescript
|
||||
// store/app.state.ts
|
||||
import { ActionReducerMap } from "@ngrx/store";
|
||||
|
||||
export interface AppState {
|
||||
user: UserState;
|
||||
cart: CartState;
|
||||
}
|
||||
|
||||
export const reducers: ActionReducerMap<AppState> = {
|
||||
user: userReducer,
|
||||
cart: cartReducer,
|
||||
};
|
||||
|
||||
// main.ts
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
provideStore(reducers),
|
||||
provideEffects([UserEffects, CartEffects]),
|
||||
provideStoreDevtools({ maxAge: 25 }),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### Feature Slice Pattern
|
||||
|
||||
```typescript
|
||||
// store/user/user.actions.ts
|
||||
import { createActionGroup, props, emptyProps } from "@ngrx/store";
|
||||
|
||||
export const UserActions = createActionGroup({
|
||||
source: "User",
|
||||
events: {
|
||||
"Load User": props<{ userId: string }>(),
|
||||
"Load User Success": props<{ user: User }>(),
|
||||
"Load User Failure": props<{ error: string }>(),
|
||||
"Update User": props<{ updates: Partial<User> }>(),
|
||||
Logout: emptyProps(),
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
```typescript
|
||||
// store/user/user.reducer.ts
|
||||
import { createReducer, on } from "@ngrx/store";
|
||||
import { UserActions } from "./user.actions";
|
||||
|
||||
export interface UserState {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const initialState: UserState = {
|
||||
user: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export const userReducer = createReducer(
|
||||
initialState,
|
||||
|
||||
on(UserActions.loadUser, (state) => ({
|
||||
...state,
|
||||
loading: true,
|
||||
error: null,
|
||||
})),
|
||||
|
||||
on(UserActions.loadUserSuccess, (state, { user }) => ({
|
||||
...state,
|
||||
user,
|
||||
loading: false,
|
||||
})),
|
||||
|
||||
on(UserActions.loadUserFailure, (state, { error }) => ({
|
||||
...state,
|
||||
loading: false,
|
||||
error,
|
||||
})),
|
||||
|
||||
on(UserActions.logout, () => initialState),
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// store/user/user.selectors.ts
|
||||
import { createFeatureSelector, createSelector } from "@ngrx/store";
|
||||
import { UserState } from "./user.reducer";
|
||||
|
||||
export const selectUserState = createFeatureSelector<UserState>("user");
|
||||
|
||||
export const selectUser = createSelector(
|
||||
selectUserState,
|
||||
(state) => state.user,
|
||||
);
|
||||
|
||||
export const selectUserLoading = createSelector(
|
||||
selectUserState,
|
||||
(state) => state.loading,
|
||||
);
|
||||
|
||||
export const selectIsAuthenticated = createSelector(
|
||||
selectUser,
|
||||
(user) => user !== null,
|
||||
);
|
||||
```
|
||||
|
||||
```typescript
|
||||
// store/user/user.effects.ts
|
||||
import { Injectable, inject } from "@angular/core";
|
||||
import { Actions, createEffect, ofType } from "@ngrx/effects";
|
||||
import { switchMap, map, catchError, of } from "rxjs";
|
||||
|
||||
@Injectable()
|
||||
export class UserEffects {
|
||||
private actions$ = inject(Actions);
|
||||
private userService = inject(UserService);
|
||||
|
||||
loadUser$ = createEffect(() =>
|
||||
this.actions$.pipe(
|
||||
ofType(UserActions.loadUser),
|
||||
switchMap(({ userId }) =>
|
||||
this.userService.getUser(userId).pipe(
|
||||
map((user) => UserActions.loadUserSuccess({ user })),
|
||||
catchError((error) =>
|
||||
of(UserActions.loadUserFailure({ error: error.message })),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Component Usage
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
@if (loading()) {
|
||||
<app-spinner />
|
||||
} @else if (user(); as user) {
|
||||
<h1>Welcome, {{ user.name }}</h1>
|
||||
<button (click)="logout()">Logout</button>
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class HeaderComponent {
|
||||
private store = inject(Store);
|
||||
|
||||
user = this.store.selectSignal(selectUser);
|
||||
loading = this.store.selectSignal(selectUserLoading);
|
||||
|
||||
logout() {
|
||||
this.store.dispatch(UserActions.logout());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RxJS-Based Patterns
|
||||
|
||||
### Component Store (Local Feature State)
|
||||
|
||||
```typescript
|
||||
// stores/todo.store.ts
|
||||
import { Injectable } from "@angular/core";
|
||||
import { ComponentStore } from "@ngrx/component-store";
|
||||
import { switchMap, tap, catchError, EMPTY } from "rxjs";
|
||||
|
||||
interface TodoState {
|
||||
todos: Todo[];
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class TodoStore extends ComponentStore<TodoState> {
|
||||
constructor(private todoService: TodoService) {
|
||||
super({ todos: [], loading: false });
|
||||
}
|
||||
|
||||
// Selectors
|
||||
readonly todos$ = this.select((state) => state.todos);
|
||||
readonly loading$ = this.select((state) => state.loading);
|
||||
readonly completedCount$ = this.select(
|
||||
this.todos$,
|
||||
(todos) => todos.filter((t) => t.completed).length,
|
||||
);
|
||||
|
||||
// Updaters
|
||||
readonly addTodo = this.updater((state, todo: Todo) => ({
|
||||
...state,
|
||||
todos: [...state.todos, todo],
|
||||
}));
|
||||
|
||||
readonly toggleTodo = this.updater((state, id: string) => ({
|
||||
...state,
|
||||
todos: state.todos.map((t) =>
|
||||
t.id === id ? { ...t, completed: !t.completed } : t,
|
||||
),
|
||||
}));
|
||||
|
||||
// Effects
|
||||
readonly loadTodos = this.effect<void>((trigger$) =>
|
||||
trigger$.pipe(
|
||||
tap(() => this.patchState({ loading: true })),
|
||||
switchMap(() =>
|
||||
this.todoService.getAll().pipe(
|
||||
tap({
|
||||
next: (todos) => this.patchState({ todos, loading: false }),
|
||||
error: () => this.patchState({ loading: false }),
|
||||
}),
|
||||
catchError(() => EMPTY),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server State with Signals
|
||||
|
||||
### HTTP + Signals Pattern
|
||||
|
||||
```typescript
|
||||
// services/api.service.ts
|
||||
import { Injectable, signal, inject } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
|
||||
interface ApiState<T> {
|
||||
data: T | null;
|
||||
loading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class ProductApiService {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
private _state = signal<ApiState<Product[]>>({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
readonly products = computed(() => this._state().data ?? []);
|
||||
readonly loading = computed(() => this._state().loading);
|
||||
readonly error = computed(() => this._state().error);
|
||||
|
||||
async fetchProducts(): Promise<void> {
|
||||
this._state.update((s) => ({ ...s, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const data = await firstValueFrom(
|
||||
this.http.get<Product[]>("/api/products"),
|
||||
);
|
||||
this._state.update((s) => ({ ...s, data, loading: false }));
|
||||
} catch (e) {
|
||||
this._state.update((s) => ({
|
||||
...s,
|
||||
loading: false,
|
||||
error: "Failed to fetch products",
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// Optimistic update
|
||||
async deleteProduct(id: string): Promise<void> {
|
||||
const previousData = this._state().data;
|
||||
|
||||
// Optimistically remove
|
||||
this._state.update((s) => ({
|
||||
...s,
|
||||
data: s.data?.filter((p) => p.id !== id) ?? null,
|
||||
}));
|
||||
|
||||
try {
|
||||
await firstValueFrom(this.http.delete(`/api/products/${id}`));
|
||||
} catch {
|
||||
// Rollback on error
|
||||
this._state.update((s) => ({ ...s, data: previousData }));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Do's
|
||||
|
||||
| Practice | Why |
|
||||
| ---------------------------------- | ---------------------------------- |
|
||||
| Use Signals for local state | Simple, reactive, no subscriptions |
|
||||
| Use `computed()` for derived data | Auto-updates, memoized |
|
||||
| Colocate state with feature | Easier to maintain |
|
||||
| Use NgRx for complex flows | Actions, effects, devtools |
|
||||
| Prefer `inject()` over constructor | Cleaner, works in factories |
|
||||
|
||||
### Don'ts
|
||||
|
||||
| Anti-Pattern | Instead |
|
||||
| --------------------------------- | ----------------------------------------------------- |
|
||||
| Store derived data | Use `computed()` |
|
||||
| Mutate signals directly | Use `set()` or `update()` |
|
||||
| Over-globalize state | Keep local when possible |
|
||||
| Mix RxJS and Signals chaotically | Choose primary, bridge with `toSignal`/`toObservable` |
|
||||
| Subscribe in components for state | Use template with signals |
|
||||
|
||||
---
|
||||
|
||||
## Migration Path
|
||||
|
||||
### From BehaviorSubject to Signals
|
||||
|
||||
```typescript
|
||||
// Before: RxJS-based
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class OldUserService {
|
||||
private userSubject = new BehaviorSubject<User | null>(null);
|
||||
user$ = this.userSubject.asObservable();
|
||||
|
||||
setUser(user: User) {
|
||||
this.userSubject.next(user);
|
||||
}
|
||||
}
|
||||
|
||||
// After: Signal-based
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class UserService {
|
||||
private _user = signal<User | null>(null);
|
||||
readonly user = this._user.asReadonly();
|
||||
|
||||
setUser(user: User) {
|
||||
this._user.set(user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Bridging Signals and RxJS
|
||||
|
||||
```typescript
|
||||
import { toSignal, toObservable } from '@angular/core/rxjs-interop';
|
||||
|
||||
// Observable → Signal
|
||||
@Component({...})
|
||||
export class ExampleComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
// Convert Observable to Signal
|
||||
userId = toSignal(
|
||||
this.route.params.pipe(map(p => p['id'])),
|
||||
{ initialValue: '' }
|
||||
);
|
||||
}
|
||||
|
||||
// Signal → Observable
|
||||
export class DataService {
|
||||
private filter = signal('');
|
||||
|
||||
// Convert Signal to Observable
|
||||
filter$ = toObservable(this.filter);
|
||||
|
||||
filteredData$ = this.filter$.pipe(
|
||||
debounceTime(300),
|
||||
switchMap(filter => this.http.get(`/api/data?q=${filter}`))
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Angular Signals Guide](https://angular.dev/guide/signals)
|
||||
- [NgRx Documentation](https://ngrx.io/)
|
||||
- [NgRx SignalStore](https://ngrx.io/guide/signals)
|
||||
- [RxAngular](https://www.rx-angular.io/)
|
||||
508
skills/angular-ui-patterns/SKILL.md
Normal file
508
skills/angular-ui-patterns/SKILL.md
Normal file
@@ -0,0 +1,508 @@
|
||||
---
|
||||
name: angular-ui-patterns
|
||||
description: Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.
|
||||
risk: safe
|
||||
source: self
|
||||
---
|
||||
|
||||
# Angular UI Patterns
|
||||
|
||||
## Core Principles
|
||||
|
||||
1. **Never show stale UI** - Loading states only when actually loading
|
||||
2. **Always surface errors** - Users must know when something fails
|
||||
3. **Optimistic updates** - Make the UI feel instant
|
||||
4. **Progressive disclosure** - Use `@defer` to show content as available
|
||||
5. **Graceful degradation** - Partial data is better than no data
|
||||
|
||||
---
|
||||
|
||||
## Loading State Patterns
|
||||
|
||||
### The Golden Rule
|
||||
|
||||
**Show loading indicator ONLY when there's no data to display.**
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
@if (error()) {
|
||||
<app-error-state [error]="error()" (retry)="load()" />
|
||||
} @else if (loading() && !items().length) {
|
||||
<app-skeleton-list />
|
||||
} @else if (!items().length) {
|
||||
<app-empty-state message="No items found" />
|
||||
} @else {
|
||||
<app-item-list [items]="items()" />
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class ItemListComponent {
|
||||
private store = inject(ItemStore);
|
||||
|
||||
items = this.store.items;
|
||||
loading = this.store.loading;
|
||||
error = this.store.error;
|
||||
}
|
||||
```
|
||||
|
||||
### Loading State Decision Tree
|
||||
|
||||
```
|
||||
Is there an error?
|
||||
→ Yes: Show error state with retry option
|
||||
→ No: Continue
|
||||
|
||||
Is it loading AND we have no data?
|
||||
→ Yes: Show loading indicator (spinner/skeleton)
|
||||
→ No: Continue
|
||||
|
||||
Do we have data?
|
||||
→ Yes, with items: Show the data
|
||||
→ Yes, but empty: Show empty state
|
||||
→ No: Show loading (fallback)
|
||||
```
|
||||
|
||||
### Skeleton vs Spinner
|
||||
|
||||
| Use Skeleton When | Use Spinner When |
|
||||
| -------------------- | --------------------- |
|
||||
| Known content shape | Unknown content shape |
|
||||
| List/card layouts | Modal actions |
|
||||
| Initial page load | Button submissions |
|
||||
| Content placeholders | Inline operations |
|
||||
|
||||
---
|
||||
|
||||
## Control Flow Patterns
|
||||
|
||||
### @if/@else for Conditional Rendering
|
||||
|
||||
```html
|
||||
@if (user(); as user) {
|
||||
<span>Welcome, {{ user.name }}</span>
|
||||
} @else if (loading()) {
|
||||
<app-spinner size="small" />
|
||||
} @else {
|
||||
<a routerLink="/login">Sign In</a>
|
||||
}
|
||||
```
|
||||
|
||||
### @for with Track
|
||||
|
||||
```html
|
||||
@for (item of items(); track item.id) {
|
||||
<app-item-card [item]="item" (delete)="remove(item.id)" />
|
||||
} @empty {
|
||||
<app-empty-state
|
||||
icon="inbox"
|
||||
message="No items yet"
|
||||
actionLabel="Create Item"
|
||||
(action)="create()"
|
||||
/>
|
||||
}
|
||||
```
|
||||
|
||||
### @defer for Progressive Loading
|
||||
|
||||
```html
|
||||
<!-- Critical content loads immediately -->
|
||||
<app-header />
|
||||
<app-hero-section />
|
||||
|
||||
<!-- Non-critical content deferred -->
|
||||
@defer (on viewport) {
|
||||
<app-comments [postId]="postId()" />
|
||||
} @placeholder {
|
||||
<div class="h-32 bg-gray-100 animate-pulse"></div>
|
||||
} @loading (minimum 200ms) {
|
||||
<app-spinner />
|
||||
} @error {
|
||||
<app-error-state message="Failed to load comments" />
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Handling Patterns
|
||||
|
||||
### Error Handling Hierarchy
|
||||
|
||||
```
|
||||
1. Inline error (field-level) → Form validation errors
|
||||
2. Toast notification → Recoverable errors, user can retry
|
||||
3. Error banner → Page-level errors, data still partially usable
|
||||
4. Full error screen → Unrecoverable, needs user action
|
||||
```
|
||||
|
||||
### Always Show Errors
|
||||
|
||||
**CRITICAL: Never swallow errors silently.**
|
||||
|
||||
```typescript
|
||||
// CORRECT - Error always surfaced to user
|
||||
@Component({...})
|
||||
export class CreateItemComponent {
|
||||
private store = inject(ItemStore);
|
||||
private toast = inject(ToastService);
|
||||
|
||||
async create(data: CreateItemDto) {
|
||||
try {
|
||||
await this.store.create(data);
|
||||
this.toast.success('Item created successfully');
|
||||
this.router.navigate(['/items']);
|
||||
} catch (error) {
|
||||
console.error('createItem failed:', error);
|
||||
this.toast.error('Failed to create item. Please try again.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WRONG - Error silently caught
|
||||
async create(data: CreateItemDto) {
|
||||
try {
|
||||
await this.store.create(data);
|
||||
} catch (error) {
|
||||
console.error(error); // User sees nothing!
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Error State Component Pattern
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: "app-error-state",
|
||||
standalone: true,
|
||||
imports: [NgOptimizedImage],
|
||||
template: `
|
||||
<div class="error-state">
|
||||
<img ngSrc="/assets/error-icon.svg" width="64" height="64" alt="" />
|
||||
<h3>{{ title() }}</h3>
|
||||
<p>{{ message() }}</p>
|
||||
@if (retry.observed) {
|
||||
<button (click)="retry.emit()" class="btn-primary">Try Again</button>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class ErrorStateComponent {
|
||||
title = input("Something went wrong");
|
||||
message = input("An unexpected error occurred");
|
||||
retry = output<void>();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Button State Patterns
|
||||
|
||||
### Button Loading State
|
||||
|
||||
```html
|
||||
<button
|
||||
(click)="handleSubmit()"
|
||||
[disabled]="isSubmitting() || !form.valid"
|
||||
class="btn-primary"
|
||||
>
|
||||
@if (isSubmitting()) {
|
||||
<app-spinner size="small" class="mr-2" />
|
||||
Saving... } @else { Save Changes }
|
||||
</button>
|
||||
```
|
||||
|
||||
### Disable During Operations
|
||||
|
||||
**CRITICAL: Always disable triggers during async operations.**
|
||||
|
||||
```typescript
|
||||
// CORRECT - Button disabled while loading
|
||||
@Component({
|
||||
template: `
|
||||
<button
|
||||
[disabled]="saving()"
|
||||
(click)="save()"
|
||||
>
|
||||
@if (saving()) {
|
||||
<app-spinner size="sm" /> Saving...
|
||||
} @else {
|
||||
Save
|
||||
}
|
||||
</button>
|
||||
`
|
||||
})
|
||||
export class SaveButtonComponent {
|
||||
saving = signal(false);
|
||||
|
||||
async save() {
|
||||
this.saving.set(true);
|
||||
try {
|
||||
await this.service.save();
|
||||
} finally {
|
||||
this.saving.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WRONG - User can click multiple times
|
||||
<button (click)="save()">
|
||||
{{ saving() ? 'Saving...' : 'Save' }}
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Empty States
|
||||
|
||||
### Empty State Requirements
|
||||
|
||||
Every list/collection MUST have an empty state:
|
||||
|
||||
```html
|
||||
@for (item of items(); track item.id) {
|
||||
<app-item-card [item]="item" />
|
||||
} @empty {
|
||||
<app-empty-state
|
||||
icon="folder-open"
|
||||
title="No items yet"
|
||||
description="Create your first item to get started"
|
||||
actionLabel="Create Item"
|
||||
(action)="openCreateDialog()"
|
||||
/>
|
||||
}
|
||||
```
|
||||
|
||||
### Contextual Empty States
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: "app-empty-state",
|
||||
template: `
|
||||
<div class="empty-state">
|
||||
<span class="icon" [class]="icon()"></span>
|
||||
<h3>{{ title() }}</h3>
|
||||
<p>{{ description() }}</p>
|
||||
@if (actionLabel()) {
|
||||
<button (click)="action.emit()" class="btn-primary">
|
||||
{{ actionLabel() }}
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class EmptyStateComponent {
|
||||
icon = input("inbox");
|
||||
title = input.required<string>();
|
||||
description = input("");
|
||||
actionLabel = input<string | null>(null);
|
||||
action = output<void>();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Form Patterns
|
||||
|
||||
### Form with Loading and Validation
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||
<div class="form-field">
|
||||
<label for="name">Name</label>
|
||||
<input
|
||||
id="name"
|
||||
formControlName="name"
|
||||
[class.error]="isFieldInvalid('name')"
|
||||
/>
|
||||
@if (isFieldInvalid("name")) {
|
||||
<span class="error-text">
|
||||
{{ getFieldError("name") }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" formControlName="email" />
|
||||
@if (isFieldInvalid("email")) {
|
||||
<span class="error-text">
|
||||
{{ getFieldError("email") }}
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<button type="submit" [disabled]="form.invalid || submitting()">
|
||||
@if (submitting()) {
|
||||
<app-spinner size="sm" /> Submitting...
|
||||
} @else {
|
||||
Submit
|
||||
}
|
||||
</button>
|
||||
</form>
|
||||
`,
|
||||
})
|
||||
export class UserFormComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
submitting = signal(false);
|
||||
|
||||
form = this.fb.group({
|
||||
name: ["", [Validators.required, Validators.minLength(2)]],
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
isFieldInvalid(field: string): boolean {
|
||||
const control = this.form.get(field);
|
||||
return control ? control.invalid && control.touched : false;
|
||||
}
|
||||
|
||||
getFieldError(field: string): string {
|
||||
const control = this.form.get(field);
|
||||
if (control?.hasError("required")) return "This field is required";
|
||||
if (control?.hasError("email")) return "Invalid email format";
|
||||
if (control?.hasError("minlength")) return "Too short";
|
||||
return "";
|
||||
}
|
||||
|
||||
async onSubmit() {
|
||||
if (this.form.invalid) return;
|
||||
|
||||
this.submitting.set(true);
|
||||
try {
|
||||
await this.service.submit(this.form.value);
|
||||
this.toast.success("Submitted successfully");
|
||||
} catch {
|
||||
this.toast.error("Submission failed");
|
||||
} finally {
|
||||
this.submitting.set(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Dialog/Modal Patterns
|
||||
|
||||
### Confirmation Dialog
|
||||
|
||||
```typescript
|
||||
// dialog.service.ts
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DialogService {
|
||||
private dialog = inject(Dialog); // CDK Dialog or custom
|
||||
|
||||
async confirm(options: {
|
||||
title: string;
|
||||
message: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
}): Promise<boolean> {
|
||||
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
|
||||
data: options,
|
||||
});
|
||||
|
||||
return await firstValueFrom(dialogRef.closed) ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
async deleteItem(item: Item) {
|
||||
const confirmed = await this.dialog.confirm({
|
||||
title: 'Delete Item',
|
||||
message: `Are you sure you want to delete "${item.name}"?`,
|
||||
confirmText: 'Delete',
|
||||
});
|
||||
|
||||
if (confirmed) {
|
||||
await this.store.delete(item.id);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### Loading States
|
||||
|
||||
```typescript
|
||||
// WRONG - Spinner when data exists (causes flash on refetch)
|
||||
@if (loading()) {
|
||||
<app-spinner />
|
||||
}
|
||||
|
||||
// CORRECT - Only show loading without data
|
||||
@if (loading() && !items().length) {
|
||||
<app-spinner />
|
||||
}
|
||||
```
|
||||
|
||||
### Error Handling
|
||||
|
||||
```typescript
|
||||
// WRONG - Error swallowed
|
||||
try {
|
||||
await this.service.save();
|
||||
} catch (e) {
|
||||
console.log(e); // User has no idea!
|
||||
}
|
||||
|
||||
// CORRECT - Error surfaced
|
||||
try {
|
||||
await this.service.save();
|
||||
} catch (e) {
|
||||
console.error("Save failed:", e);
|
||||
this.toast.error("Failed to save. Please try again.");
|
||||
}
|
||||
```
|
||||
|
||||
### Button States
|
||||
|
||||
```html
|
||||
<!-- WRONG - Button not disabled during submission -->
|
||||
<button (click)="submit()">Submit</button>
|
||||
|
||||
<!-- CORRECT - Disabled and shows loading -->
|
||||
<button (click)="submit()" [disabled]="loading()">
|
||||
@if (loading()) {
|
||||
<app-spinner size="sm" />
|
||||
} Submit
|
||||
</button>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI State Checklist
|
||||
|
||||
Before completing any UI component:
|
||||
|
||||
### UI States
|
||||
|
||||
- [ ] Error state handled and shown to user
|
||||
- [ ] Loading state shown only when no data exists
|
||||
- [ ] Empty state provided for collections (`@empty` block)
|
||||
- [ ] Buttons disabled during async operations
|
||||
- [ ] Buttons show loading indicator when appropriate
|
||||
|
||||
### Data & Mutations
|
||||
|
||||
- [ ] All async operations have error handling
|
||||
- [ ] All user actions have feedback (toast/visual)
|
||||
- [ ] Optimistic updates rollback on failure
|
||||
|
||||
### Accessibility
|
||||
|
||||
- [ ] Loading states announced to screen readers
|
||||
- [ ] Error messages linked to form fields
|
||||
- [ ] Focus management after state changes
|
||||
|
||||
---
|
||||
|
||||
## Integration with Other Skills
|
||||
|
||||
- **angular-state-management**: Use Signal stores for state
|
||||
- **angular**: Apply modern patterns (Signals, @defer)
|
||||
- **testing-patterns**: Test all UI states
|
||||
821
skills/angular/SKILL.md
Normal file
821
skills/angular/SKILL.md
Normal file
@@ -0,0 +1,821 @@
|
||||
---
|
||||
name: angular
|
||||
description: >-
|
||||
Modern Angular (v20+) expert with deep knowledge of Signals, Standalone
|
||||
Components, Zoneless applications, SSR/Hydration, and reactive patterns.
|
||||
Use PROACTIVELY for Angular development, component architecture, state
|
||||
management, performance optimization, and migration to modern patterns.
|
||||
risk: safe
|
||||
source: self
|
||||
---
|
||||
|
||||
# Angular Expert
|
||||
|
||||
Master modern Angular development with Signals, Standalone Components, Zoneless applications, SSR/Hydration, and the latest reactive patterns.
|
||||
|
||||
## When to Use This Skill
|
||||
|
||||
- Building new Angular applications (v20+)
|
||||
- Implementing Signals-based reactive patterns
|
||||
- Creating Standalone Components and migrating from NgModules
|
||||
- Configuring Zoneless Angular applications
|
||||
- Implementing SSR, prerendering, and hydration
|
||||
- Optimizing Angular performance
|
||||
- Adopting modern Angular patterns and best practices
|
||||
|
||||
## Do Not Use This Skill When
|
||||
|
||||
- Migrating from AngularJS (1.x) → use `angular-migration` skill
|
||||
- Working with legacy Angular apps that cannot upgrade
|
||||
- General TypeScript issues → use `typescript-expert` skill
|
||||
|
||||
## Instructions
|
||||
|
||||
1. Assess the Angular version and project structure
|
||||
2. Apply modern patterns (Signals, Standalone, Zoneless)
|
||||
3. Implement with proper typing and reactivity
|
||||
4. Validate with build and tests
|
||||
|
||||
## Safety
|
||||
|
||||
- Always test changes in development before production
|
||||
- Gradual migration for existing apps (don't big-bang refactor)
|
||||
- Keep backward compatibility during transitions
|
||||
|
||||
---
|
||||
|
||||
## Angular Version Timeline
|
||||
|
||||
| Version | Release | Key Features |
|
||||
| -------------- | ------- | ------------------------------------------------------ |
|
||||
| **Angular 20** | Q2 2025 | Signals stable, Zoneless stable, Incremental hydration |
|
||||
| **Angular 21** | Q4 2025 | Signals-first default, Enhanced SSR |
|
||||
| **Angular 22** | Q2 2026 | Signal Forms, Selectorless components |
|
||||
|
||||
---
|
||||
|
||||
## 1. Signals: The New Reactive Primitive
|
||||
|
||||
Signals are Angular's fine-grained reactivity system, replacing zone.js-based change detection.
|
||||
|
||||
### Core Concepts
|
||||
|
||||
```typescript
|
||||
import { signal, computed, effect } from "@angular/core";
|
||||
|
||||
// Writable signal
|
||||
const count = signal(0);
|
||||
|
||||
// Read value
|
||||
console.log(count()); // 0
|
||||
|
||||
// Update value
|
||||
count.set(5); // Direct set
|
||||
count.update((v) => v + 1); // Functional update
|
||||
|
||||
// Computed (derived) signal
|
||||
const doubled = computed(() => count() * 2);
|
||||
|
||||
// Effect (side effects)
|
||||
effect(() => {
|
||||
console.log(`Count changed to: ${count()}`);
|
||||
});
|
||||
```
|
||||
|
||||
### Signal-Based Inputs and Outputs
|
||||
|
||||
```typescript
|
||||
import { Component, input, output, model } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-card",
|
||||
standalone: true,
|
||||
template: `
|
||||
<div class="card">
|
||||
<h3>{{ name() }}</h3>
|
||||
<span>{{ role() }}</span>
|
||||
<button (click)="select.emit(id())">Select</button>
|
||||
</div>
|
||||
`,
|
||||
})
|
||||
export class UserCardComponent {
|
||||
// Signal inputs (read-only)
|
||||
id = input.required<string>();
|
||||
name = input.required<string>();
|
||||
role = input<string>("User"); // With default
|
||||
|
||||
// Output
|
||||
select = output<string>();
|
||||
|
||||
// Two-way binding (model)
|
||||
isSelected = model(false);
|
||||
}
|
||||
|
||||
// Usage:
|
||||
// <app-user-card [id]="'123'" [name]="'John'" [(isSelected)]="selected" />
|
||||
```
|
||||
|
||||
### Signal Queries (ViewChild/ContentChild)
|
||||
|
||||
```typescript
|
||||
import {
|
||||
Component,
|
||||
viewChild,
|
||||
viewChildren,
|
||||
contentChild,
|
||||
} from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-container",
|
||||
standalone: true,
|
||||
template: `
|
||||
<input #searchInput />
|
||||
<app-item *ngFor="let item of items()" />
|
||||
`,
|
||||
})
|
||||
export class ContainerComponent {
|
||||
// Signal-based queries
|
||||
searchInput = viewChild<ElementRef>("searchInput");
|
||||
items = viewChildren(ItemComponent);
|
||||
projectedContent = contentChild(HeaderDirective);
|
||||
|
||||
focusSearch() {
|
||||
this.searchInput()?.nativeElement.focus();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### When to Use Signals vs RxJS
|
||||
|
||||
| Use Case | Signals | RxJS |
|
||||
| ----------------------- | --------------- | -------------------------------- |
|
||||
| Local component state | ✅ Preferred | Overkill |
|
||||
| Derived/computed values | ✅ `computed()` | `combineLatest` works |
|
||||
| Side effects | ✅ `effect()` | `tap` operator |
|
||||
| HTTP requests | ❌ | ✅ HttpClient returns Observable |
|
||||
| Event streams | ❌ | ✅ `fromEvent`, operators |
|
||||
| Complex async flows | ❌ | ✅ `switchMap`, `mergeMap` |
|
||||
|
||||
---
|
||||
|
||||
## 2. Standalone Components
|
||||
|
||||
Standalone components are self-contained and don't require NgModule declarations.
|
||||
|
||||
### Creating Standalone Components
|
||||
|
||||
```typescript
|
||||
import { Component } from "@angular/core";
|
||||
import { CommonModule } from "@angular/common";
|
||||
import { RouterLink } from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: "app-header",
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterLink], // Direct imports
|
||||
template: `
|
||||
<header>
|
||||
<a routerLink="/">Home</a>
|
||||
<a routerLink="/about">About</a>
|
||||
</header>
|
||||
`,
|
||||
})
|
||||
export class HeaderComponent {}
|
||||
```
|
||||
|
||||
### Bootstrapping Without NgModule
|
||||
|
||||
```typescript
|
||||
// main.ts
|
||||
import { bootstrapApplication } from "@angular/platform-browser";
|
||||
import { provideRouter } from "@angular/router";
|
||||
import { provideHttpClient } from "@angular/common/http";
|
||||
import { AppComponent } from "./app/app.component";
|
||||
import { routes } from "./app/app.routes";
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideRouter(routes), provideHttpClient()],
|
||||
});
|
||||
```
|
||||
|
||||
### Lazy Loading Standalone Components
|
||||
|
||||
```typescript
|
||||
// app.routes.ts
|
||||
import { Routes } from "@angular/router";
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: "dashboard",
|
||||
loadComponent: () =>
|
||||
import("./dashboard/dashboard.component").then(
|
||||
(m) => m.DashboardComponent,
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "admin",
|
||||
loadChildren: () =>
|
||||
import("./admin/admin.routes").then((m) => m.ADMIN_ROUTES),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Zoneless Angular
|
||||
|
||||
Zoneless applications don't use zone.js, improving performance and debugging.
|
||||
|
||||
### Enabling Zoneless Mode
|
||||
|
||||
```typescript
|
||||
// main.ts
|
||||
import { bootstrapApplication } from "@angular/platform-browser";
|
||||
import { provideZonelessChangeDetection } from "@angular/core";
|
||||
import { AppComponent } from "./app/app.component";
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [provideZonelessChangeDetection()],
|
||||
});
|
||||
```
|
||||
|
||||
### Zoneless Component Patterns
|
||||
|
||||
```typescript
|
||||
import { Component, signal, ChangeDetectionStrategy } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-counter",
|
||||
standalone: true,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
template: `
|
||||
<div>Count: {{ count() }}</div>
|
||||
<button (click)="increment()">+</button>
|
||||
`,
|
||||
})
|
||||
export class CounterComponent {
|
||||
count = signal(0);
|
||||
|
||||
increment() {
|
||||
this.count.update((v) => v + 1);
|
||||
// No zone.js needed - Signal triggers change detection
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Key Zoneless Benefits
|
||||
|
||||
- **Performance**: No zone.js patches on async APIs
|
||||
- **Debugging**: Clean stack traces without zone wrappers
|
||||
- **Bundle size**: Smaller without zone.js (~15KB savings)
|
||||
- **Interoperability**: Better with Web Components and micro-frontends
|
||||
|
||||
---
|
||||
|
||||
## 4. Server-Side Rendering & Hydration
|
||||
|
||||
### SSR Setup with Angular CLI
|
||||
|
||||
```bash
|
||||
ng add @angular/ssr
|
||||
```
|
||||
|
||||
### Hydration Configuration
|
||||
|
||||
```typescript
|
||||
// app.config.ts
|
||||
import { ApplicationConfig } from "@angular/core";
|
||||
import {
|
||||
provideClientHydration,
|
||||
withEventReplay,
|
||||
} from "@angular/platform-browser";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideClientHydration(withEventReplay())],
|
||||
};
|
||||
```
|
||||
|
||||
### Incremental Hydration (v20+)
|
||||
|
||||
```typescript
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-page",
|
||||
standalone: true,
|
||||
template: `
|
||||
<app-hero />
|
||||
|
||||
@defer (hydrate on viewport) {
|
||||
<app-comments />
|
||||
}
|
||||
|
||||
@defer (hydrate on interaction) {
|
||||
<app-chat-widget />
|
||||
}
|
||||
`,
|
||||
})
|
||||
export class PageComponent {}
|
||||
```
|
||||
|
||||
### Hydration Triggers
|
||||
|
||||
| Trigger | When to Use |
|
||||
| ---------------- | --------------------------------------- |
|
||||
| `on idle` | Low-priority, hydrate when browser idle |
|
||||
| `on viewport` | Hydrate when element enters viewport |
|
||||
| `on interaction` | Hydrate on first user interaction |
|
||||
| `on hover` | Hydrate when user hovers |
|
||||
| `on timer(ms)` | Hydrate after specified delay |
|
||||
|
||||
---
|
||||
|
||||
## 5. Modern Routing Patterns
|
||||
|
||||
### Functional Route Guards
|
||||
|
||||
```typescript
|
||||
// auth.guard.ts
|
||||
import { inject } from "@angular/core";
|
||||
import { Router, CanActivateFn } from "@angular/router";
|
||||
import { AuthService } from "./auth.service";
|
||||
|
||||
export const authGuard: CanActivateFn = (route, state) => {
|
||||
const auth = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
if (auth.isAuthenticated()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return router.createUrlTree(["/login"], {
|
||||
queryParams: { returnUrl: state.url },
|
||||
});
|
||||
};
|
||||
|
||||
// Usage in routes
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: "dashboard",
|
||||
loadComponent: () => import("./dashboard.component"),
|
||||
canActivate: [authGuard],
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Route-Level Data Resolvers
|
||||
|
||||
```typescript
|
||||
import { inject } from '@angular/core';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
import { UserService } from './user.service';
|
||||
import { User } from './user.model';
|
||||
|
||||
export const userResolver: ResolveFn<User> = (route) => {
|
||||
const userService = inject(UserService);
|
||||
return userService.getUser(route.paramMap.get('id')!);
|
||||
};
|
||||
|
||||
// In routes
|
||||
{
|
||||
path: 'user/:id',
|
||||
loadComponent: () => import('./user.component'),
|
||||
resolve: { user: userResolver }
|
||||
}
|
||||
|
||||
// In component
|
||||
export class UserComponent {
|
||||
private route = inject(ActivatedRoute);
|
||||
user = toSignal(this.route.data.pipe(map(d => d['user'])));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Dependency Injection Patterns
|
||||
|
||||
### Modern inject() Function
|
||||
|
||||
```typescript
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { UserService } from './user.service';
|
||||
|
||||
@Component({...})
|
||||
export class UserComponent {
|
||||
// Modern inject() - no constructor needed
|
||||
private http = inject(HttpClient);
|
||||
private userService = inject(UserService);
|
||||
|
||||
// Works in any injection context
|
||||
users = toSignal(this.userService.getUsers());
|
||||
}
|
||||
```
|
||||
|
||||
### Injection Tokens for Configuration
|
||||
|
||||
```typescript
|
||||
import { InjectionToken, inject } from "@angular/core";
|
||||
|
||||
// Define token
|
||||
export const API_BASE_URL = new InjectionToken<string>("API_BASE_URL");
|
||||
|
||||
// Provide in config
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [{ provide: API_BASE_URL, useValue: "https://api.example.com" }],
|
||||
});
|
||||
|
||||
// Inject in service
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class ApiService {
|
||||
private baseUrl = inject(API_BASE_URL);
|
||||
|
||||
get(endpoint: string) {
|
||||
return this.http.get(`${this.baseUrl}/${endpoint}`);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Component Composition & Reusability
|
||||
|
||||
### Content Projection (Slots)
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
selector: 'app-card',
|
||||
template: `
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<!-- Select by attribute -->
|
||||
<ng-content select="[card-header]"></ng-content>
|
||||
</div>
|
||||
<div class="body">
|
||||
<!-- Default slot -->
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
export class CardComponent {}
|
||||
|
||||
// Usage
|
||||
<app-card>
|
||||
<h3 card-header>Title</h3>
|
||||
<p>Body content</p>
|
||||
</app-card>
|
||||
```
|
||||
|
||||
### Host Directives (Composition)
|
||||
|
||||
```typescript
|
||||
// Reusable behaviors without inheritance
|
||||
@Directive({
|
||||
standalone: true,
|
||||
selector: '[appTooltip]',
|
||||
inputs: ['tooltip'] // Signal input alias
|
||||
})
|
||||
export class TooltipDirective { ... }
|
||||
|
||||
@Component({
|
||||
selector: 'app-button',
|
||||
standalone: true,
|
||||
hostDirectives: [
|
||||
{
|
||||
directive: TooltipDirective,
|
||||
inputs: ['tooltip: title'] // Map input
|
||||
}
|
||||
],
|
||||
template: `<ng-content />`
|
||||
})
|
||||
export class ButtonComponent {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. State Management Patterns
|
||||
|
||||
### Signal-Based State Service
|
||||
|
||||
```typescript
|
||||
import { Injectable, signal, computed } from "@angular/core";
|
||||
|
||||
interface AppState {
|
||||
user: User | null;
|
||||
theme: "light" | "dark";
|
||||
notifications: Notification[];
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: "root" })
|
||||
export class StateService {
|
||||
// Private writable signals
|
||||
private _user = signal<User | null>(null);
|
||||
private _theme = signal<"light" | "dark">("light");
|
||||
private _notifications = signal<Notification[]>([]);
|
||||
|
||||
// Public read-only computed
|
||||
readonly user = computed(() => this._user());
|
||||
readonly theme = computed(() => this._theme());
|
||||
readonly notifications = computed(() => this._notifications());
|
||||
readonly unreadCount = computed(
|
||||
() => this._notifications().filter((n) => !n.read).length,
|
||||
);
|
||||
|
||||
// Actions
|
||||
setUser(user: User | null) {
|
||||
this._user.set(user);
|
||||
}
|
||||
|
||||
toggleTheme() {
|
||||
this._theme.update((t) => (t === "light" ? "dark" : "light"));
|
||||
}
|
||||
|
||||
addNotification(notification: Notification) {
|
||||
this._notifications.update((n) => [...n, notification]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Component Store Pattern with Signals
|
||||
|
||||
```typescript
|
||||
import { Injectable, signal, computed, inject } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { toSignal } from "@angular/core/rxjs-interop";
|
||||
|
||||
@Injectable()
|
||||
export class ProductStore {
|
||||
private http = inject(HttpClient);
|
||||
|
||||
// State
|
||||
private _products = signal<Product[]>([]);
|
||||
private _loading = signal(false);
|
||||
private _filter = signal("");
|
||||
|
||||
// Selectors
|
||||
readonly products = computed(() => this._products());
|
||||
readonly loading = computed(() => this._loading());
|
||||
readonly filteredProducts = computed(() => {
|
||||
const filter = this._filter().toLowerCase();
|
||||
return this._products().filter((p) =>
|
||||
p.name.toLowerCase().includes(filter),
|
||||
);
|
||||
});
|
||||
|
||||
// Actions
|
||||
loadProducts() {
|
||||
this._loading.set(true);
|
||||
this.http.get<Product[]>("/api/products").subscribe({
|
||||
next: (products) => {
|
||||
this._products.set(products);
|
||||
this._loading.set(false);
|
||||
},
|
||||
error: () => this._loading.set(false),
|
||||
});
|
||||
}
|
||||
|
||||
setFilter(filter: string) {
|
||||
this._filter.set(filter);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Forms with Signals (Coming in v22+)
|
||||
|
||||
### Current Reactive Forms
|
||||
|
||||
```typescript
|
||||
import { Component, inject } from "@angular/core";
|
||||
import { FormBuilder, Validators, ReactiveFormsModule } from "@angular/forms";
|
||||
|
||||
@Component({
|
||||
selector: "app-user-form",
|
||||
standalone: true,
|
||||
imports: [ReactiveFormsModule],
|
||||
template: `
|
||||
<form [formGroup]="form" (ngSubmit)="onSubmit()">
|
||||
<input formControlName="name" placeholder="Name" />
|
||||
<input formControlName="email" type="email" placeholder="Email" />
|
||||
<button [disabled]="form.invalid">Submit</button>
|
||||
</form>
|
||||
`,
|
||||
})
|
||||
export class UserFormComponent {
|
||||
private fb = inject(FormBuilder);
|
||||
|
||||
form = this.fb.group({
|
||||
name: ["", Validators.required],
|
||||
email: ["", [Validators.required, Validators.email]],
|
||||
});
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.valid) {
|
||||
console.log(this.form.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Signal-Aware Form Patterns (Preview)
|
||||
|
||||
```typescript
|
||||
// Future Signal Forms API (experimental)
|
||||
import { Component, signal } from '@angular/core';
|
||||
|
||||
@Component({...})
|
||||
export class SignalFormComponent {
|
||||
name = signal('');
|
||||
email = signal('');
|
||||
|
||||
// Computed validation
|
||||
isValid = computed(() =>
|
||||
this.name().length > 0 &&
|
||||
this.email().includes('@')
|
||||
);
|
||||
|
||||
submit() {
|
||||
if (this.isValid()) {
|
||||
console.log({ name: this.name(), email: this.email() });
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Performance Optimization
|
||||
|
||||
### Change Detection Strategies
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
// Only checks when:
|
||||
// 1. Input signal/reference changes
|
||||
// 2. Event handler runs
|
||||
// 3. Async pipe emits
|
||||
// 4. Signal value changes
|
||||
})
|
||||
```
|
||||
|
||||
### Defer Blocks for Lazy Loading
|
||||
|
||||
```typescript
|
||||
@Component({
|
||||
template: `
|
||||
<!-- Immediate loading -->
|
||||
<app-header />
|
||||
|
||||
<!-- Lazy load when visible -->
|
||||
@defer (on viewport) {
|
||||
<app-heavy-chart />
|
||||
} @placeholder {
|
||||
<div class="skeleton" />
|
||||
} @loading (minimum 200ms) {
|
||||
<app-spinner />
|
||||
} @error {
|
||||
<p>Failed to load chart</p>
|
||||
}
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
### NgOptimizedImage
|
||||
|
||||
```typescript
|
||||
import { NgOptimizedImage } from '@angular/common';
|
||||
|
||||
@Component({
|
||||
imports: [NgOptimizedImage],
|
||||
template: `
|
||||
<img
|
||||
ngSrc="hero.jpg"
|
||||
width="800"
|
||||
height="600"
|
||||
priority
|
||||
/>
|
||||
|
||||
<img
|
||||
ngSrc="thumbnail.jpg"
|
||||
width="200"
|
||||
height="150"
|
||||
loading="lazy"
|
||||
placeholder="blur"
|
||||
/>
|
||||
`
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Testing Modern Angular
|
||||
|
||||
### Testing Signal Components
|
||||
|
||||
```typescript
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { CounterComponent } from "./counter.component";
|
||||
|
||||
describe("CounterComponent", () => {
|
||||
let component: CounterComponent;
|
||||
let fixture: ComponentFixture<CounterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CounterComponent], // Standalone import
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CounterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should increment count", () => {
|
||||
expect(component.count()).toBe(0);
|
||||
|
||||
component.increment();
|
||||
|
||||
expect(component.count()).toBe(1);
|
||||
});
|
||||
|
||||
it("should update DOM on signal change", () => {
|
||||
component.count.set(5);
|
||||
fixture.detectChanges();
|
||||
|
||||
const el = fixture.nativeElement.querySelector(".count");
|
||||
expect(el.textContent).toContain("5");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### Testing with Signal Inputs
|
||||
|
||||
```typescript
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
import { ComponentRef } from "@angular/core";
|
||||
import { UserCardComponent } from "./user-card.component";
|
||||
|
||||
describe("UserCardComponent", () => {
|
||||
let fixture: ComponentFixture<UserCardComponent>;
|
||||
let componentRef: ComponentRef<UserCardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [UserCardComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(UserCardComponent);
|
||||
componentRef = fixture.componentRef;
|
||||
|
||||
// Set signal inputs via setInput
|
||||
componentRef.setInput("id", "123");
|
||||
componentRef.setInput("name", "John Doe");
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it("should display user name", () => {
|
||||
const el = fixture.nativeElement.querySelector("h3");
|
||||
expect(el.textContent).toContain("John Doe");
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices Summary
|
||||
|
||||
| Pattern | ✅ Do | ❌ Don't |
|
||||
| -------------------- | ------------------------------ | ------------------------------- |
|
||||
| **State** | Use Signals for local state | Overuse RxJS for simple state |
|
||||
| **Components** | Standalone with direct imports | Bloated SharedModules |
|
||||
| **Change Detection** | OnPush + Signals | Default CD everywhere |
|
||||
| **Lazy Loading** | `@defer` and `loadComponent` | Eager load everything |
|
||||
| **DI** | `inject()` function | Constructor injection (verbose) |
|
||||
| **Inputs** | `input()` signal function | `@Input()` decorator (legacy) |
|
||||
| **Zoneless** | Enable for new projects | Force on legacy without testing |
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
|
||||
- [Angular.dev Documentation](https://angular.dev)
|
||||
- [Angular Signals Guide](https://angular.dev/guide/signals)
|
||||
- [Angular SSR Guide](https://angular.dev/guide/ssr)
|
||||
- [Angular Update Guide](https://angular.dev/update-guide)
|
||||
- [Angular Blog](https://blog.angular.dev)
|
||||
|
||||
---
|
||||
|
||||
## Common Troubleshooting
|
||||
|
||||
| Issue | Solution |
|
||||
| ------------------------------ | --------------------------------------------------- |
|
||||
| Signal not updating UI | Ensure `OnPush` + call signal as function `count()` |
|
||||
| Hydration mismatch | Check server/client content consistency |
|
||||
| Circular dependency | Use `inject()` with `forwardRef` |
|
||||
| Zoneless not detecting changes | Trigger via signal updates, not mutations |
|
||||
| SSR fetch fails | Use `TransferState` or `withFetch()` |
|
||||
@@ -186,7 +186,7 @@ class CompetitorAnalyzer:
|
||||
|
||||
def _analyze_title(self, title: str) -> Dict[str, Any]:
|
||||
"""Analyze title structure and keyword usage."""
|
||||
parts = re.split(r'[-:|]', title)
|
||||
parts = re.split(r'[-' + r':|]', title)
|
||||
|
||||
return {
|
||||
'title': title,
|
||||
|
||||
70
skills/computer-vision-expert/SKILL.md
Normal file
70
skills/computer-vision-expert/SKILL.md
Normal file
@@ -0,0 +1,70 @@
|
||||
---
|
||||
name: computer-vision-expert
|
||||
description: SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.
|
||||
---
|
||||
|
||||
# Computer Vision Expert (SOTA 2026)
|
||||
|
||||
**Role**: Advanced Vision Systems Architect & Spatial Intelligence Expert
|
||||
|
||||
## Purpose
|
||||
To provide expert guidance on designing, implementing, and optimizing state-of-the-art computer vision pipelines. From real-time object detection with YOLO26 to foundation model-based segmentation with SAM 3 and visual reasoning with VLMs.
|
||||
|
||||
## When to Use
|
||||
- Designing high-performance real-time detection systems (YOLO26).
|
||||
- Implementing zero-shot or text-guided segmentation tasks (SAM 3).
|
||||
- Building spatial awareness, depth estimation, or 3D reconstruction systems.
|
||||
- Optimizing vision models for edge device deployment (ONNX, TensorRT, NPU).
|
||||
- Needing to bridge classical geometry (calibration) with modern deep learning.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### 1. Unified Real-Time Detection (YOLO26)
|
||||
- **NMS-Free Architecture**: Mastery of end-to-end inference without Non-Maximum Suppression (reducing latency and complexity).
|
||||
- **Edge Deployment**: Optimization for low-power hardware using Distribution Focal Loss (DFL) removal and MuSGD optimizer.
|
||||
- **Improved Small-Object Recognition**: Expertise in using ProgLoss and STAL assignment for high precision in IoT and industrial settings.
|
||||
|
||||
### 2. Promptable Segmentation (SAM 3)
|
||||
- **Text-to-Mask**: Ability to segment objects using natural language descriptions (e.g., "the blue container on the right").
|
||||
- **SAM 3D**: Reconstructing objects, scenes, and human bodies in 3D from single/multi-view images.
|
||||
- **Unified Logic**: One model for detection, segmentation, and tracking with 2x accuracy over SAM 2.
|
||||
|
||||
### 3. Vision Language Models (VLMs)
|
||||
- **Visual Grounding**: Leveraging Florence-2, PaliGemma 2, or Qwen2-VL for semantic scene understanding.
|
||||
- **Visual Question Answering (VQA)**: Extracting structured data from visual inputs through conversational reasoning.
|
||||
|
||||
### 4. Geometry & Reconstruction
|
||||
- **Depth Anything V2**: State-of-the-art monocular depth estimation for spatial awareness.
|
||||
- **Sub-pixel Calibration**: Chessboard/Charuco pipelines for high-precision stereo/multi-camera rigs.
|
||||
- **Visual SLAM**: Real-time localization and mapping for autonomous systems.
|
||||
|
||||
## Patterns
|
||||
|
||||
### 1. Text-Guided Vision Pipelines
|
||||
- Use SAM 3's text-to-mask capability to isolate specific parts during inspection without needing custom detectors for every variation.
|
||||
- Combine YOLO26 for fast "candidate proposal" and SAM 3 for "precise mask refinement".
|
||||
|
||||
### 2. Deployment-First Design
|
||||
- Leverage YOLO26's simplified ONNX/TensorRT exports (NMS-free).
|
||||
- Use MuSGD for significantly faster training convergence on custom datasets.
|
||||
|
||||
### 3. Progressive 3D Scene Reconstruction
|
||||
- Integrate monocular depth maps with geometric homographies to build accurate 2.5D/3D representations of scenes.
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- **Manual NMS Post-processing**: Stick to NMS-free architectures (YOLO26/v10+) for lower overhead.
|
||||
- **Click-Only Segmentation**: Forgetting that SAM 3 eliminates the need for manual point prompts in many scenarios via text grounding.
|
||||
- **Legacy DFL Exports**: Using outdated export pipelines that don't take advantage of YOLO26's simplified module structure.
|
||||
|
||||
## Sharp Edges (2026)
|
||||
|
||||
| Issue | Severity | Solution |
|
||||
|-------|----------|----------|
|
||||
| SAM 3 VRAM Usage | Medium | Use quantized/distilled versions for local GPU inference. |
|
||||
| Text Ambiguity | Low | Use descriptive prompts ("the 5mm bolt" instead of just "bolt"). |
|
||||
| Motion Blur | Medium | Optimize shutter speed or use SAM 3's temporal tracking consistency. |
|
||||
| Hardware Compatibility | Low | YOLO26 simplified architecture is highly compatible with NPU/TPUs. |
|
||||
|
||||
## Related Skills
|
||||
`ai-engineer`, `robotics-expert`, `research-engineer`, `embedded-systems`
|
||||
@@ -197,6 +197,24 @@
|
||||
"risk": "unknown",
|
||||
"source": "unknown"
|
||||
},
|
||||
{
|
||||
"id": "angular",
|
||||
"path": "skills/angular",
|
||||
"category": "uncategorized",
|
||||
"name": "angular",
|
||||
"description": "Modern Angular (v20+) expert with deep knowledge of Signals, Standalone Components, Zoneless applications, SSR/Hydration, and reactive patterns. Use PROACTIVELY for Angular development, component architecture, state management, performance optimization, and migration to modern patterns.",
|
||||
"risk": "safe",
|
||||
"source": "self"
|
||||
},
|
||||
{
|
||||
"id": "angular-best-practices",
|
||||
"path": "skills/angular-best-practices",
|
||||
"category": "uncategorized",
|
||||
"name": "angular-best-practices",
|
||||
"description": "Angular performance optimization and best practices guide. Use when writing, reviewing, or refactoring Angular code for optimal performance, bundle size, and rendering efficiency.",
|
||||
"risk": "safe",
|
||||
"source": "self"
|
||||
},
|
||||
{
|
||||
"id": "angular-migration",
|
||||
"path": "skills/angular-migration",
|
||||
@@ -206,6 +224,24 @@
|
||||
"risk": "unknown",
|
||||
"source": "unknown"
|
||||
},
|
||||
{
|
||||
"id": "angular-state-management",
|
||||
"path": "skills/angular-state-management",
|
||||
"category": "uncategorized",
|
||||
"name": "angular-state-management",
|
||||
"description": "Master modern Angular state management with Signals, NgRx, and RxJS. Use when setting up global state, managing component stores, choosing between state solutions, or migrating from legacy patterns.",
|
||||
"risk": "safe",
|
||||
"source": "self"
|
||||
},
|
||||
{
|
||||
"id": "angular-ui-patterns",
|
||||
"path": "skills/angular-ui-patterns",
|
||||
"category": "uncategorized",
|
||||
"name": "angular-ui-patterns",
|
||||
"description": "Modern Angular UI patterns for loading states, error handling, and data display. Use when building UI components, handling async data, or managing component states.",
|
||||
"risk": "safe",
|
||||
"source": "self"
|
||||
},
|
||||
{
|
||||
"id": "anti-reversing-techniques",
|
||||
"path": "skills/anti-reversing-techniques",
|
||||
@@ -1106,6 +1142,15 @@
|
||||
"risk": "unknown",
|
||||
"source": "vibeship-spawner-skills (Apache 2.0)"
|
||||
},
|
||||
{
|
||||
"id": "computer-vision-expert",
|
||||
"path": "skills/computer-vision-expert",
|
||||
"category": "uncategorized",
|
||||
"name": "computer-vision-expert",
|
||||
"description": "SOTA Computer Vision Expert (2026). Specialized in YOLO26, Segment Anything 3 (SAM 3), Vision Language Models, and real-time spatial analysis.",
|
||||
"risk": "unknown",
|
||||
"source": "unknown"
|
||||
},
|
||||
{
|
||||
"id": "concise-planning",
|
||||
"path": "skills/concise-planning",
|
||||
|
||||
Reference in New Issue
Block a user