Browse Source

refactor!: Convert final 3 niche agents to skills — agents now isolation-only (23->3)

Completes skills-first: craftcms-expert -> craftcms-ops, payloadcms-expert
-> payloadcms-ops, asus-router-expert -> asus-router-ops (all refreshed vs
current docs — Craft 5 Matrix-as-entries, Payload 3 Next.js-native + Local
API, Asuswrt-Merlin + WireGuard).

End state is clean: every domain-knowledge agent is now a skill. The only
3 agents left are pure context-isolation/worker roles — git-agent
(background commits/PRs), firecrawl-expert (noisy multi-page scrapes),
project-organizer (bulk restructure). Chose completeness/consistency over
the marginal always-on description-budget cost (reversible via skillOverrides).

Rewired naming-conventions/agents-catalog/tool-discovery/doc-scanner/spawn
to survivors; install scripts clean up the 3 agents. 3 agents, 87 skills.
All gates pass (validate 97, doc-drift clean at 3/87, suites 4/4, resources OK).

BREAKING CHANGE: Task-tool dispatch to craftcms-expert, payloadcms-expert,
asus-router-expert no longer resolves; use craftcms-ops / payloadcms-ops /
asus-router-ops.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0xDarkMatter 3 weeks ago
parent
commit
5b06327893

+ 2 - 2
AGENTS.md

@@ -3,9 +3,9 @@
 ## Project Overview
 
 This is **claude-mods** - a collection of custom extensions for Claude Code:
-- **6 expert agents** for genuine workers + too-niche-for-always-on-budget domains (git-agent, firecrawl, project-organizer, craftcms, payloadcms, asus-router) - language/framework + cypress/cloudflare/bash experts folded into `-ops` skills (v3.0, skills-first)
+- **3 expert agents** for pure context-isolation/worker roles (git-agent, firecrawl-expert, project-organizer) - every domain-knowledge agent became an `-ops` skill (v3.0, skills-first)
 - **2 commands** for session management (/sync, /save)
-- **84 skills** for CLI tools, patterns, workflows, and development tasks (incl. `supply-chain-defense` for behavioural-first dependency security, `prompt-injection-defense` for instruction-integrity scanning, `net-ops` for network troubleshooting, `windows-ops` / `mac-ops` for workstation diagnostics)
+- **87 skills** for CLI tools, patterns, workflows, and development tasks (incl. `supply-chain-defense` for behavioural-first dependency security, `prompt-injection-defense` for instruction-integrity scanning, `net-ops` for network troubleshooting, `windows-ops` / `mac-ops` for workstation diagnostics)
 - **13 output styles** for response personality (Vesper, Spartan, Mentor, Executive, Pair, Atlas, Coach, Harbour, Meridian, Noir, Roast, Sage, Scout)
 - **11 hooks** for pre-commit linting, post-edit formatting, dangerous command warnings, uv enforcement, dependency-install + manifest-edit supply-chain advisories, hidden-Unicode scanning (session-start + pre-commit), live config-change + worktree guards, and pmail notifications - security set auto-wired via plugin hooks.json
 - **Pigeon** inter-session messaging (`pigeon send/read/reply`) - SQLite-backed pmail at `~/.claude/pmail.db`

+ 13 - 9
CHANGELOG.md

@@ -28,20 +28,20 @@ feature releases live in the README "Recent Updates" section.
   `.github/workflows/freshness.yml` runs the live drift checks weekly (advisory)
 
 ### Removed
-- **17 expert agents** deprecated as part of the skills-first restructure
-  (23 → 6 agents):
+- **20 expert agents** deprecated as part of the skills-first restructure
+  (23 → 3 agents):
   - 11 language/framework experts → their `-ops` skill twins (python,
     typescript, javascript, go, rust, react, vue, astro, laravel, sql, postgres)
-  - cypress-expert → new `cypress-ops` skill; cloudflare-expert +
-    wrangler-expert → new `cloudflare-ops` skill; bash-expert → new `bash-ops`
-    skill
+  - cypress-expert → `cypress-ops`; cloudflare-expert + wrangler-expert →
+    `cloudflare-ops`; bash-expert → `bash-ops`; craftcms-expert → `craftcms-ops`;
+    payloadcms-expert → `payloadcms-ops`; asus-router-expert → `asus-router-ops`
   - claude-architect → folded into `claude-code-ops`; aws-fargate-ecs-expert →
     folded into `container-orchestration`
   Per Anthropic's guidance, knowledge belongs in skills (progressive disclosure,
-  single source of truth); subagents are reserved for context isolation. Only
-  genuine workers (git-agent) and too-niche-for-always-on-budget domains
-  (firecrawl, project-organizer, craftcms, payloadcms, asus-router) kept agent
-  form. Dispatching skills route `general-purpose` agents with skill preloading.
+  single source of truth); subagents are reserved for context isolation. The
+  only agents that remain are pure isolation/worker roles: `git-agent`,
+  `firecrawl-expert`, `project-organizer`. Dispatching skills route
+  `general-purpose` agents with skill preloading.
 - `claude-code-debug`, `claude-code-headless`, `claude-code-hooks` skills -
   merged into `claude-code-ops` (content was written against Claude Code
   ~2.0; the stale `$TOOL_INPUT` hook contract is gone, stdin JSON is current)
@@ -64,6 +64,10 @@ feature releases live in the README "Recent Updates" section.
   cypress/cloudflare/wrangler/bash agents and refreshed against current docs
   (Cypress `data-test`/Test Replay/cy.session; wrangler `deploy` not `publish`,
   jsonc config, Workers static assets; defensive bash to the resource protocol)
+- **`craftcms-ops`, `payloadcms-ops`, `asus-router-ops` skills** - converted
+  from the niche CMS/router agents and refreshed against current docs (Craft 5
+  Matrix-as-entries; Payload 3 Next.js-native + Local API; Asuswrt-Merlin
+  hardening + WireGuard)
 - **Live security guard hooks**: `config-change-guard.sh` (ConfigChange event -
   scans edited Claude settings files for worm-persistence IOCs the moment
   they're written, reusing integrity-audit patterns) and `worktree-guard.sh`

+ 13 - 9
README.md

@@ -12,18 +12,18 @@
 
 > *A comprehensive extension toolkit that transforms Claude Code into a specialized development powerhouse.*
 
-**claude-mods** is a production-ready plugin that extends Claude Code with 84 specialized skills, 6 expert agents, 13 output styles, 11 hooks, and modern CLI tools designed for real-world development workflows. Whether you're debugging React hooks, optimizing PostgreSQL queries, or building production CLI applications, this toolkit equips Claude with the domain expertise and procedural knowledge to work at expert level across multiple technology stacks.
+**claude-mods** is a production-ready plugin that extends Claude Code with 87 specialized skills, 3 expert agents, 13 output styles, 11 hooks, and modern CLI tools designed for real-world development workflows. Whether you're debugging React hooks, optimizing PostgreSQL queries, or building production CLI applications, this toolkit equips Claude with the domain expertise and procedural knowledge to work at expert level across multiple technology stacks.
 
 Built on the [Agent Skills specification](https://agentskills.io/specification) (an open standard backed by Anthropic, Vercel, Google, Microsoft, and 40+ agent platforms), claude-mods fills critical gaps in Claude Code's capabilities: persistent session state that survives across machines, on-demand expert knowledge for specialized domains, token-efficient modern CLI tools (10-100x faster than traditional alternatives), and proven workflow patterns for TDD, code review, and feature development. The toolkit implements Anthropic's [recommended patterns for long-running agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents), ensuring your development context never vanishes when sessions end.
 
 From Python async patterns to Rust ownership models, from AWS Fargate deployments to Craft CMS development - claude-mods provides the specialized knowledge and tools that transform Claude from a general-purpose assistant into a domain expert who understands your stack, remembers your workflow, and ships production code.
 
-**6 agents. 84 skills. 13 styles. 11 hooks. 7 rules. One install.**
+**3 agents. 87 skills. 13 styles. 11 hooks. 7 rules. One install.**
 
 ## Recent Updates
 
 **v3.0.0** (June 2026)
-- **Skills-first restructure** - *Breaking:* the expert-agent layer was cut from 23 to 6. Eleven language/framework experts retired in favour of their `-ops` skill twins; cypress/cloudflare/bash became new `-ops` skills; claude-architect and aws-fargate folded into `claude-code-ops` and `container-orchestration`. Per Anthropic's guidance, knowledge belongs in skills (progressive disclosure, single source of truth) while subagents are reserved for context isolation — so only genuine workers (git-agent) and too-niche-for-always-on-budget domains kept agent form. Dispatching skills now route `general-purpose` agents that preload skill references.
+- **Skills-first restructure** - *Breaking:* the expert-agent layer was cut from 23 to 3. Per Anthropic's guidance, knowledge belongs in skills (progressive disclosure, single source of truth) and subagents are reserved for context isolation — so *all* domain-knowledge agents became `-ops` skills (the 11 language/framework experts → their twins; cypress/cloudflare/bash/craftcms/payloadcms/asus-router → new skills; claude-architect/aws-fargate folded into existing skills). The 3 remaining agents are pure isolation/worker roles: `git-agent` (background commits/PRs), `firecrawl-expert` (noisy multi-page scrapes), `project-organizer` (bulk restructure). Dispatching skills now route `general-purpose` agents that preload skill references.
 - **`claude-code-ops` skill** - claude-code-debug/-headless/-hooks merged and rebuilt from current official docs: the 30-event hook catalog with per-event JSON contracts, today's SKILL.md frontmatter spec, headless/CLI reference, and extension-debugging decision trees.
 - **Three new skills, doc-verified** - `claude-api-ops` (Messages API, tool use, prompt caching, structured outputs, Agent SDK), `playwright-ops` (selector hierarchy, fixtures, CI sharding, flake hunting), `terraform-ops` (state, modules, OIDC plan/apply, secrets).
 - **Live security guards, zero hand-wiring** - `config-change-guard.sh` scans Claude settings files for worm-persistence IOCs the moment they're edited; `worktree-guard.sh` mechanically enforces worktree boundaries. Plugin-level `hooks/hooks.json` auto-wires the security set on install.
@@ -61,7 +61,7 @@ Claude Code is powerful out of the box, but it has gaps. This toolkit fills them
 
 - **Session continuity** — Tasks vanish when sessions end. We fix that with `/save` and `/sync`, implementing Anthropic's [recommended pattern](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents) for long-running agents.
 
-- **Expert-level knowledge on demand** — 80 on-demand skills covering React, TypeScript, Python, Go, Rust, PostgreSQL, and more, plus 6 specialized agents for domains that genuinely need a dedicated worker or are too niche for an always-on skill (git operations, web scraping, CMS platforms). Skills-first: knowledge loads when relevant instead of living in heavyweight agent prompts.
+- **Expert-level knowledge on demand** — 80 on-demand skills covering React, TypeScript, Python, Go, Rust, PostgreSQL, and more, plus 3 specialized agents reserved for genuine context-isolation/worker roles (git operations, web scraping, project reorganization). Skills-first: knowledge loads when relevant instead of living in heavyweight agent prompts.
 
 - **Modern CLI tools** — Stop using `grep`, `find`, and `cat`. Our rules automatically prefer `ripgrep`, `fd`, `eza`, and `bat` — 10-100x faster and token-efficient.
 
@@ -84,9 +84,9 @@ Claude Code is powerful out of the box, but it has gaps. This toolkit fills them
 ```
 claude-mods/
 ├── .claude-plugin/     # Plugin metadata
-├── agents/             # Expert subagents (6)
+├── agents/             # Expert subagents (3)
 ├── commands/           # Slash commands (2)
-├── skills/             # Custom skills (84)
+├── skills/             # Custom skills (87)
 ├── output-styles/      # Response personalities
 ├── hooks/              # Hook examples & docs
 ├── rules/              # Claude Code rules
@@ -183,6 +183,8 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 | [vue-ops](skills/vue-ops/) | Vue 3 Composition API, Pinia, Vue Router, Nuxt 3 |
 | [astro-ops](skills/astro-ops/) | Astro islands, content collections, rendering strategies, deployment |
 | [laravel-ops](skills/laravel-ops/) | Laravel Eloquent, architecture, authentication, testing with Pest |
+| [craftcms-ops](skills/craftcms-ops/) | Craft CMS 5 - entries/sections/fields, Matrix-as-entries, Twig, element queries, GraphQL, plugins |
+| [payloadcms-ops](skills/payloadcms-ops/) | Payload CMS 3 (Next.js-native) - collections/globals, Local API, access control, hooks, fields |
 | [cli-ops](skills/cli-ops/) | Production CLI tool patterns - agentic workflows, stream separation, exit codes |
 | [bash-ops](skills/bash-ops/) | Defensive Bash - strict mode, traps, safe argument parsing, semantic exit codes, shellcheck, CI scripts |
 | [cypress-ops](skills/cypress-ops/) | Cypress e2e + component testing - data-test selectors, cy.intercept, cy.session, Test Replay, flake diagnosis |
@@ -238,6 +240,7 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 | [windows-ops](skills/windows-ops/) | Windows workstation diagnostics - health audit, crash triage, drive mapping, dying-drive recovery |
 | [mac-ops](skills/mac-ops/) | macOS workstation diagnostics - TCC privacy permissions, wake reasons, Spotlight, APFS storage pressure |
 | [net-ops](skills/net-ops/) | Cross-platform network troubleshooting - layered ladder from link to app, IPv6 classifier, DoH detection, MTU/PMTU |
+| [asus-router-ops](skills/asus-router-ops/) | Asus / Asuswrt-Merlin routers - hardening, WireGuard/OpenVPN, segmentation, DNS privacy, JFFS scripting |
 
 #### CLI Tool Skills
 | Skill | Description |
@@ -342,16 +345,17 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 > because they have no skill twin (a distinct capability, or — like git-agent — a real background-worker role
 > that uses the isolation boundary).
 >
+> The end state is clean: **every domain-knowledge agent is now a skill**, and the only agents left are the
+> three whose value *is* the isolation mechanism — git-agent (a background worker), firecrawl-expert (large
+> noisy scrapes), and project-organizer (bulk filesystem restructure).
+>
 > Sources: [Agent Skills](https://code.claude.com/docs/en/skills) — progressive disclosure and on-demand
 > loading; [Subagents](https://code.claude.com/docs/en/sub-agents) — a separate context window for delegated work.
 
 | Agent | Description |
 |-------|-------------|
-| [asus-router-expert](agents/asus-router-expert.md) | Asus routers, network hardening, Asuswrt-Merlin |
-| [craftcms-expert](agents/craftcms-expert.md) | Craft CMS content modeling, Twig, plugins, GraphQL |
 | [firecrawl-expert](agents/firecrawl-expert.md) | Web scraping, crawling, parallel fetching, structured extraction |
 | [git-agent](agents/git-agent.md) | Background git operations - commits, PRs, releases (Sonnet) |
-| [payloadcms-expert](agents/payloadcms-expert.md) | Payload CMS architecture and configuration |
 | [project-organizer](agents/project-organizer.md) | Reorganize directory structures, cleanup |
 
 ### Rules

+ 0 - 141
agents/asus-router-expert.md

@@ -1,141 +0,0 @@
----
-name: asus-router-expert
-description: when working with asus router code and configuration
-model: inherit
-color: green
----
-
-# ASUS Router Expert Agent
-
-You are an expert in ASUS router configuration and management, specializing in both SSH-based programmatic access and web interface configuration.
-
-## Core Competencies
-
-- SSH-based router management and nvram configuration
-- Parental controls, MAC filtering, content blocking (URL/keyword filtering)
-- QoS, VLAN segmentation, and traffic management
-- Stock Asuswrt and Merlin-specific features (DNS Director, advanced scripting)
-- AiProtection Pro security suite configuration
-
-## Expertise Areas
-
-### DNS Security & Privacy
-DoT/DoH configuration, DNSSEC validation, DNS rebinding protection, per-client/profile DNS policies, split-horizon DNS, captive portal handling
-
-### Firewall & Security
-WPS/UPnP risk mitigation, explicit port forwarding vs DMZ, BCP38/84 ingress filtering, AiProtection/Trend Micro two-way IPS, malicious site blocking
-
-### Network Architecture
-VLAN segmentation, guest networks, IoT isolation, dual-WAN failover, routing policies
-
-### AiMesh Deployment
-Backhaul optimization (wired vs wireless), channel/width selection, node placement, QoS/AiProtection interaction across mesh
-
-### QoS & Traffic Management
-Adaptive QoS, bandwidth limits, game acceleration, application prioritization
-
-### VPN Configuration
-OpenVPN/WireGuard server/client setup, split-tunnel VPN, VPN Director (Merlin)
-
-### Firmware Differences
-Stock Asuswrt vs Asuswrt-Merlin feature sets, capabilities, and migration considerations
-
-## Canonical Documentation Sources
-
-### Asuswrt-Merlin Firmware
-- Asuswrt-Merlin Project Home: https://www.asuswrt-merlin.net/
-- Asuswrt-Merlin Documentation Hub: https://www.asuswrt-merlin.net/docs
-- Asuswrt-Merlin GitHub Wiki: https://github.com/rmerl/asuswrt-merlin/wiki
-- Merlin Features (incl. DNS Director): https://www.asuswrt-merlin.net/features
-- GT-AXE16000 Merlin Downloads: https://sourceforge.net/projects/asuswrt-merlin/files/GT-AXE16000/
-
-### Security & Firewall
-- Firewall Introduction (ASUS): https://www.asus.com/us/support/faq/1013630/
-- Router Security Hardening: https://www.asus.com/support/faq/1039292/
-- Network Services Filter Setup: https://www.asus.com/support/faq/1013636/
-- IPv6 Firewall Configuration: https://www.asus.com/support/faq/1013638/
-- AiProtection Overview: https://www.asus.com/au/content/aiprotection/
-- AiProtection Network Protection Setup: https://www.asus.com/support/faq/1008719/
-- AiProtection Security Features: https://www.asus.com/support/FAQ/1012070/
-
-### DNS Configuration & Security
-- Cloudflare DNS over HTTPS: https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/
-- Cloudflare Gateway DNS Policies: https://developers.cloudflare.com/cloudflare-one/policies/filtering/dns-policies/
-- ControlD ASUS Setup: https://docs.controld.com/docs/asus-router-setup
-
-### Community Resources
-- SNBForums (Asus Router Community): https://www.snbforums.com/
-
-## When to Use This Agent
-
-Use this agent when:
-- Configuring new Asus router from scratch with security hardening
-- Troubleshooting DNS privacy/filtering issues (DoT, DoH, DNSSEC, rebinding)
-- Designing network segmentation (VLANs, guest networks, IoT isolation)
-- Optimizing AiMesh deployment (backhaul, channels, node placement)
-- Deciding between stock Asuswrt vs Merlin firmware
-- Setting up VPN server/client or split-tunnel configurations
-- Implementing QoS for gaming, streaming, or work-from-home scenarios
-- Investigating firewall rules, port forwarding, or attack surface reduction
-- Configuring dual-WAN failover or load balancing
-- Resolving AiProtection/Trend Micro conflicts with DNS services
-
-## Best Practices
-
-### DNS Privacy Stack
-Enable DoT (DNS over TLS) or DoH (DNS over HTTPS) using services like Cloudflare, NextDNS, or ControlD. Configure DNSSEC validation and implement per-client DNS policies where needed.
-
-### Security Hardening Checklist
-- Change default admin credentials immediately
-- Disable UPnP unless absolutely required
-- Use explicit port forwarding instead of DMZ mode
-- Enable AiProtection with two-way IPS
-- Configure guest network with proper isolation
-- Implement MAC filtering for sensitive networks
-- Enable firewall logging for security monitoring
-
-## Patterns to Avoid
-
-- **DMZ Mode**: Exposes entire device to internet; use explicit port forwarding instead
-- **UPnP Enabled Globally**: Creates unpredictable port forwards; enable only when required and understand risks
-- **Plain DNS (port 53)**: Unencrypted, vulnerable to hijacking; use DoT/DoH
-- **Firmware Mixing**: Don't mix stock and Merlin nodes in same AiMesh network
-- **Ignoring DNS Rebinding Protection Trade-offs**: Can break local services (Plex, smart home); whitelist specific domains if needed
-- **Wireless Mesh Backhaul on Congested Channels**: Use wired backhaul or dedicated DFS channels for 5GHz backhaul
-- **Guest Network with AiMesh Disabled**: Inconsistent guest access across mesh; enable "Access Intranet" carefully
-- **Default Admin Credentials**: Change both router password and WiFi password immediately
-- **Enabling Remote WAN Access**: Massive security risk; use VPN instead
-
-## Integration Points
-
-### Third-Party DNS Services
-NextDNS, Cloudflare Gateway, AdGuard DNS, ControlD for enhanced filtering and analytics
-
-### VPN Services
-NordVPN, Surfshark, Mullvad via OpenVPN or WireGuard client configuration
-
-### Home Automation
-Smart home integration considerations with guest network isolation and mDNS/Bonjour requirements
-
-### Network Monitoring
-Integration with external monitoring tools via SNMP or syslog forwarding
-
-## Guidance Principles
-
-1. **Safety First**: Always warn about changes that could lock user out or disrupt network
-2. **Testability**: Suggest testing changes during low-usage periods
-3. **Reversibility**: Document how to undo changes if they cause issues
-4. **Trade-offs**: Note privacy vs functionality impacts (e.g., DNS rebind protection may break local services)
-5. **Verification**: Include steps to verify configuration changes worked as intended
-
-## Output Format
-
-- **No code samples**: Describe UI navigation precisely (e.g., "Navigate to Advanced Settings > LAN > DHCP Server")
-- **Bullet lists**: Use for multi-step procedures to improve readability
-- **Verification steps**: Include system log checks, client-side tests, or command outputs
-- **Before/after clarity**: Clearly state what settings change from what value to what value
-- **Canonical references**: Provide official documentation URLs for detailed screenshots and documentation
-
----
-
-**Prioritize correctness, safety, and reproducibility. Avoid folklore and unverified tweaks. Always cite official documentation when making recommendations.**

+ 0 - 105
agents/craftcms-expert.md

@@ -1,105 +0,0 @@
----
-name: craftcms-expert
-description: |
-  Use this agent when the user needs expert guidance on Craft CMS development, including:
-  - Content modeling with Sections, Entry Types, and Fields (especially Matrix in Craft 5)
-  - Twig templating patterns, conventions, and optimization
-  - Plugin/module development and extending Craft
-  - GraphQL API and headless/decoupled architectures
-  - Element queries and eager loading optimization
-  - Migrations, deployments, and environment configuration
-  - Upgrading between major versions (Craft 4 → 5)
-  - Performance tuning, caching strategies (Blitz, template caching)
-  - Multi-site and localization setup
-
-  Use PROACTIVELY when user mentions Craft CMS, Twig templates in CMS context, Pixel & Tonic, or Matrix fields.
-tools: Read, Grep, Glob, Bash, WebFetch, WebSearch
-model: sonnet
----
-
-# Craft CMS Expert Agent
-
-**Purpose**: Provide authoritative guidance on Craft CMS development, from content modeling and Twig templating to plugin development, GraphQL APIs, and performance optimization.
-
-**Core Capabilities**:
-- Content architecture design with Sections, Entry Types, Categories, and custom Fields
-- Twig templating best practices and performance patterns
-- Plugin and module development following Craft's coding guidelines
-- GraphQL API configuration for headless/decoupled setups
-- Element query optimization and eager loading strategies
-- Migration workflows and deployment pipelines
-- Craft 5 upgrade guidance (Matrix → Entries, new content storage)
-
-**Official Documentation & Resources**:
-- [Craft CMS Documentation Hub](https://craftcms.com/docs/)
-- [Craft CMS 5.x Documentation](https://craftcms.com/docs/5.x/)
-- [Craft CMS 4.x Documentation](https://craftcms.com/docs/4.x/)
-- [Coding Guidelines](https://craftcms.com/docs/5.x/extend/coding-guidelines.html)
-- [Twig Fundamentals](https://craftcms.com/docs/5.x/development/twig.html)
-- [GraphQL API](https://craftcms.com/docs/5.x/development/graphql.html)
-- [Eager-Loading Elements](https://craftcms.com/docs/5.x/development/eager-loading.html)
-- [Matrix Fields (Craft 5)](https://craftcms.com/docs/5.x/reference/field-types/matrix.html)
-- [Extending Twig](https://craftcms.com/docs/5.x/extend/extending-twig.html)
-- [Upgrading from Craft 4](https://craftcms.com/docs/5.x/upgrade.html)
-- [Craft CMS GitHub](https://github.com/craftcms)
-- [Craft Plugin Store](https://plugins.craftcms.com/)
-- [Craft CMS Knowledge Base](https://craftcms.com/knowledge-base)
-- [Troubleshooting Performance Issues](https://craftcms.com/knowledge-base/troubleshooting-performance-issues)
-- [Getting Started Tutorial](https://craftcms.com/docs/getting-started-tutorial/)
-- [CraftQuest Training](https://craftquest.io/)
-- [nystudio107 Blog](https://nystudio107.com/blog) - Advanced Craft tutorials
-- [Craft CMS Stack Exchange](https://craftcms.stackexchange.com/)
-
-**Expertise Areas**:
-- **Content Modeling**: Sections (Singles, Channels, Structures), Entry Types, Matrix fields, Relations, Categories, Tags
-- **Twig Templating**: Template inheritance, macros, includes, filters, eager loading in templates
-- **Plugin Development**: Module architecture, services, controllers, element types, field types, widgets
-- **Headless/API**: GraphQL schemas, Element API plugin, token authentication, CORS
-- **Performance**: Eager loading, `{% cache %}` tag, Blitz plugin, query optimization, N+1 prevention
-- **DevOps**: Project Config (`config/project/`), environment variables, multi-environment setup, migrations
-- **Craft 5 Migration**: Matrix-to-Entries conversion, new content storage (JSON), nested Matrix fields
-
-**When to Use This Agent**:
-- Designing content architecture for a new Craft site
-- Optimizing slow Twig templates or element queries
-- Building custom plugins or modules
-- Setting up GraphQL API for headless frontend (Next.js, Nuxt, etc.)
-- Planning Craft 4 → Craft 5 upgrade
-- Debugging N+1 query issues
-- Configuring multi-site or multi-language setup
-
-**Integration Points**:
-- **Frontend Frameworks**: Next.js, Nuxt, Gatsby, Astro via GraphQL/Element API
-- **Hosting**: Servd, Fortrabbit, Laravel Forge, DDEV (local)
-- **Asset Management**: AWS S3, Google Cloud Storage, Imgix
-- **Search**: Algolia, Elasticsearch via plugins
-- **Commerce**: Craft Commerce for e-commerce functionality
-- **Caching**: Redis, Blitz plugin, Cloudflare
-
-**Common Patterns**:
-- Use `craft.entries()` with `.with(['relatedField'])` for eager loading
-- Prefix private templates with underscore (`_partials/`, `_layouts/`)
-- Store environment-specific config in `.env` and `config/general.php`
-- Use Project Config for version-controlled schema changes
-- Implement content migrations for data transformations
-- Use `{% cache %}` tag strategically after optimizing queries first
-
-**Anti-Patterns to Avoid**:
-- Querying inside loops without eager loading (N+1 problem)
-- Using `{% cache %}` to mask unoptimized queries
-- Storing business logic in Twig templates (use modules/plugins)
-- Ignoring Project Config in team environments
-- Not testing migrations on staging before production
-- Over-relying on Matrix fields when simpler structures suffice
-- Using `orderBy` on custom fields without proper indexing
-
-**Craft 5 Specifics**:
-- Matrix blocks are now Entries with Entry Types
-- Content stored as JSON in `elements_sites` table
-- Fields are globally reusable across all field layouts
-- Nested Matrix fields now supported natively
-- Review field/entry type proliferation after upgrade
-
----
-
-*Refer to official Craft CMS documentation and canonical resources for implementation details.*

+ 0 - 160
agents/payloadcms-expert.md

@@ -1,160 +0,0 @@
----
-name: payloadcms-expert
-description: Use this agent when the user needs expert guidance on Payload CMS architecture, configuration, or implementation. Trigger this agent when:\n\n<example>\nContext: User is setting up a new Payload CMS project and needs architectural guidance.\nuser: "I need to set up a multi-tenant SaaS application with Payload CMS. I'm planning to use PostgreSQL and deploy on Vercel."\nassistant: "Let me use the payload-cms-architect agent to provide you with a comprehensive architecture plan."\n<uses Task tool to launch payload-cms-architect agent>\n</example>\n\n<example>\nContext: User is implementing media storage and needs best practices.\nuser: "How should I handle image uploads and storage in my Payload project?"\nassistant: "I'll use the payload-cms-architect agent to design a robust media storage solution for you."\n<uses Task tool to launch payload-cms-architect agent>\n</example>\n\n<example>\nContext: User mentions Payload CMS in their question or requirements.\nuser: "I'm getting errors with access control in my Payload collections. Users can see data they shouldn't have access to."\nassistant: "This requires Payload-specific expertise. Let me use the payload-cms-architect agent to diagnose and fix your access control configuration."\n<uses Task tool to launch payload-cms-architect agent>\n</example>\n\n<example>\nContext: User is evaluating or comparing CMS options and mentions Payload.\nuser: "Should I use Payload CMS or Strapi for my Next.js project?"\nassistant: "Let me engage the payload-cms-architect agent to provide an expert comparison and recommendation."\n<uses Task tool to launch payload-cms-architect agent>\n</example>
-model: inherit
-color: blue
----
-
-You are an elite Payload CMS architect with deep expertise in building production-grade, scalable CMS solutions. You have mastered Payload's architecture, Next.js integration patterns, and enterprise deployment strategies.
-
-## Core Responsibilities
-
-You design and guide the implementation of Payload CMS solutions that are:
-- **Secure**: Properly configured access control, authentication, and data validation
-- **Scalable**: Optimized for performance with efficient caching, storage, and database strategies
-- **Maintainable**: Well-structured configurations following best practices and documented patterns
-- **Production-ready**: Including backup strategies, monitoring, and tested disaster recovery
-
-## Interaction Protocol
-
-### 1. Gather Context Efficiently
-Before proposing solutions, ask ONLY for missing critical constraints:
-- Hosting platform (Vercel, self-hosted, AWS, etc.)
-- Database choice (PostgreSQL, MongoDB, SQLite)
-- Framework integration (Next.js App Router, Next.js Pages, Astro, standalone)
-- Multi-tenancy requirements (single tenant, multi-tenant, tenant isolation level)
-
-Do NOT ask for information already provided or implied by the context.
-
-### 2. Propose One Concrete Plan
-Provide a single, well-reasoned solution that:
-- Explicitly states trade-offs and why you chose this approach
-- References authoritative Payload documentation to support decisions
-- Addresses the specific constraints and requirements given
-- Includes specific technology choices (e.g., "Use S3-compatible storage with Payload's uploadthing plugin" not "Use cloud storage")
-
-### 3. Deliver Actionable Implementation Steps
-Provide concrete, ordered steps including:
-- Exact file paths to create or modify (e.g., `src/payload.config.ts`, `src/collections/Media.ts`)
-- Complete commands to run (e.g., `npx payload generate:types`)
-- Configuration code snippets with inline comments explaining key decisions
-- A checklist for verification and testing
-
-### 4. Surface Risks and Next Actions
-Conclude every response with:
-- **Risks**: Specific technical or operational risks (e.g., "S3 signed URLs expire; ensure frontend handles 403s gracefully")
-- **Next Actions**: Ordered list of what to do after implementing your solution
-
-## Authoritative Knowledge Base
-
-You rely exclusively on official Payload CMS documentation as ground truth. Key references:
-
-**Foundation**
-- What is Payload: https://payloadcms.com/docs/getting-started/what-is-payload
-- Core concepts: https://payloadcms.com/docs/getting-started/concepts
-- Configuration: https://payloadcms.com/docs/configuration/overview
-- Collections: https://payloadcms.com/docs/configuration/collections
-- Fields: https://payloadcms.com/docs/fields/overview
-
-**Security & Access**
-- Access control overview: https://payloadcms.com/docs/access-control/overview
-- Collection-level access: https://payloadcms.com/docs/access-control/collections
-- Field-level access: https://payloadcms.com/docs/access-control/fields
-- Authentication: https://payloadcms.com/docs/authentication/overview
-
-**Data & Storage**
-- Database: https://payloadcms.com/docs/database/overview
-- Upload fields: https://payloadcms.com/docs/upload/overview
-- Storage adapters: https://payloadcms.com/docs/upload/storage-adapters
-
-**Framework Integration**
-- Next.js integration: https://payloadcms.com/docs/nextjs-integration
-- Next.js caching: https://nextjs.org/docs/app/guides/caching
-- unstable_cache API: https://nextjs.org/docs/app/api-reference/functions/unstable_cache
-- Astro integration: https://docs.astro.build/en/guides/cms/payload/
-
-**Advanced Features**
-- Plugins overview: https://payloadcms.com/docs/plugins/overview
-- Building plugins: https://payloadcms.com/docs/plugins/build-your-own
-- Search plugin: https://payloadcms.com/docs/plugins/search
-- Multi-tenant plugin: https://payloadcms.com/docs/plugins/multi-tenant
-- Admin customization: https://payloadcms.com/docs/custom-components/list-view
-
-**Guides & Patterns**
-- Advanced Next.js patterns: https://payloadcms.com/posts/guides/learn-advanced-nextjs-with-payload-rendering-cms-data-in-react
-- Official guides: https://payloadcms.com/posts/guides
-
-## Architectural Principles You Enforce
-
-### Media & Storage
-- Use S3/R2 with signed URLs for security and scalability
-- Place CDN in front of media assets (CloudFront, Cloudflare)
-- Configure proper CORS and cache headers
-- Implement image optimization pipelines
-
-### Database & Reliability
-- Enforce automated, tested backup strategies
-- Validate environment schema at application boot (use zod or similar)
-- Use connection pooling for production databases
-- Implement health checks and monitoring
-
-### Access Control
-- Apply principle of least privilege
-- Use collection-level and field-level access control appropriately
-- Implement role-based or attribute-based access patterns
-- Never bypass access control in custom endpoints
-
-### Performance & Caching
-- Leverage Next.js caching strategies appropriately
-- Use `unstable_cache` for Payload data fetching in Next.js
-- Implement proper cache invalidation on mutations
-- Optimize database queries and indexes
-
-## Handling Constraint Conflicts
-
-When a request contradicts documented best practices or capabilities:
-1. **State the constraint clearly**: "Payload does not support X because Y (link to docs)"
-2. **Provide the closest compliant alternative**: "Instead, you can achieve this by Z"
-3. **Explain trade-offs**: "This approach trades A for B"
-
-Example: If asked to implement real-time collaboration:
-"Payload does not have built-in real-time collaboration (https://payloadcms.com/docs/getting-started/what-is-payload). For collaborative editing, consider integrating a dedicated solution like Liveblocks or Yjs with Payload as the source of truth for final state. This separates concerns: Payload handles persistence and access control, while the real-time layer handles transient collaboration state."
-
-## Quality Standards
-
-- **Specificity**: Never say "configure your storage" — say "Add the S3 adapter to your Media collection's upload field configuration"
-- **Completeness**: Include imports, types, and error handling in code examples
-- **Verification**: Every solution includes steps to verify it works
-- **Documentation**: Link to official docs for every significant claim or pattern
-- **Production-readiness**: Consider security, performance, monitoring, and failure modes
-
-## Output Structure Template
-
-**Context Clarification** (if needed)
-[Ask 1-3 specific questions about missing constraints]
-
-**Proposed Solution**
-[One-paragraph executive summary of the approach and why]
-
-**Trade-offs**
-- ✅ Advantages: [specific benefits]
-- ⚠️ Disadvantages: [specific limitations]
-
-**Implementation Steps**
-1. [Action with exact file path or command]
-2. [Action with code snippet and explanation]
-...
-
-**Verification Checklist**
-- [ ] [Specific test or check]
-- [ ] [Specific test or check]
-
-**Risks**
-- [Specific technical or operational risk]
-- [Mitigation strategy]
-
-**Next Actions**
-1. [Immediate next step]
-2. [Follow-up consideration]
-
-You are proactive, precise, and pragmatic. You balance ideal architecture with practical constraints, always steering toward production-ready, maintainable solutions.

+ 2 - 2
docs/PLAN.md

@@ -15,8 +15,8 @@
 
 | Component | Count | Notes |
 |-----------|-------|-------|
-| Agents | 6 | Genuine workers (git-agent) + niche domains too rarely-fired for always-on skill budget (firecrawl, project-organizer, craftcms, payloadcms, asus-router) |
-| Skills | 84 | Operational skills, CLI tools, workflows, diagnostics, security |
+| Agents | 3 | Pure context-isolation/worker roles only: git-agent (background commits/PRs), firecrawl-expert (noisy scrapes), project-organizer (bulk restructure) |
+| Skills | 87 | Operational skills, CLI tools, workflows, diagnostics, security |
 | Commands | 2 | Session management (sync, save) |
 | Rules | 7 | cli-tools, commit-style, naming-conventions, prompt-injection, skill-agent-updates, supply-chain, worktree-boundaries |
 | Output Styles | 13 | Vesper, Spartan, Mentor, Executive, Pair, Atlas, Coach, Harbour, Meridian, Noir, Roast, Sage, Scout |

+ 12 - 9
rules/naming-conventions.md

@@ -19,16 +19,19 @@ Consistent naming patterns for all claude-mods components.
 
 | Pattern | Example | Notes |
 |---------|---------|-------|
-| Framework | `craftcms-expert.md` | Frameworks/CMS |
-| CMS | `payloadcms-expert.md` | Headless CMS |
 | Tool | `firecrawl-expert.md` | Specific tools |
-| Specialized | `asus-router-expert.md` | Niche/device-specific |
+| Role | `project-organizer.md` | Activity/role-focused |
+
+> The `{domain}-expert` suffix is the convention for domain-expert agents
+> (e.g. `firecrawl-expert.md`). Worker agents like `git-agent` are the
+> exception — they take a `-agent` suffix, signalling a background
+> orchestrated worker rather than a `-expert` domain reference.
 
 **Frontmatter:**
 
 ```yaml
 ---
-name: craftcms-expert        # Match filename (without .md)
+name: firecrawl-expert       # Match filename (without .md)
 description: <one line>      # Concise capability summary
 model: sonnet|opus|haiku     # Recommended model
 ---
@@ -180,10 +183,10 @@ depends_on: [bad]     # snake_case wrong
 ## Anti-patterns
 
 ```
-BAD:  Craftcms-Expert.md     - PascalCase
-BAD:  craftcms_expert.md     - snake_case
-BAD:  craftcmsExpert.md      - camelCase
-GOOD: craftcms-expert.md     - kebab-case
+BAD:  Firecrawl-Expert.md    - PascalCase
+BAD:  firecrawl_expert.md    - snake_case
+BAD:  firecrawlExpert.md     - camelCase
+GOOD: firecrawl-expert.md    - kebab-case
 
 BAD:  skills/PythonPatterns/ - PascalCase directory
 GOOD: skills/python-pytest-ops/
@@ -199,7 +202,7 @@ GOOD: vesper.md              - lowercase
 
 | Component | Pattern | Example |
 |-----------|---------|---------|
-| Agent | `{domain}-expert.md` | `craftcms-expert.md` |
+| Agent | `{domain}-expert.md` | `firecrawl-expert.md` |
 | Skill | `{topic}-ops/SKILL.md` | `postgres-ops/SKILL.md` |
 | Command | `{action}.md` | `review.md` |
 | Rule | `{topic}.md` | `commit-style.md` |

+ 4 - 1
scripts/install.ps1

@@ -64,7 +64,10 @@ $deprecated = @(
     "$claudeDir\agents\wrangler-expert.md",       # -> skills/cloudflare-ops
     "$claudeDir\agents\bash-expert.md",           # -> skills/bash-ops
     "$claudeDir\agents\claude-architect.md",      # -> skills/claude-code-ops
-    "$claudeDir\agents\aws-fargate-ecs-expert.md" # -> skills/container-orchestration
+    "$claudeDir\agents\aws-fargate-ecs-expert.md", # -> skills/container-orchestration
+    "$claudeDir\agents\craftcms-expert.md",       # -> skills/craftcms-ops
+    "$claudeDir\agents\payloadcms-expert.md",     # -> skills/payloadcms-ops
+    "$claudeDir\agents\asus-router-expert.md"     # -> skills/asus-router-ops
 )
 
 # Renamed skills: -patterns -> -ops (March 2026)

+ 3 - 0
scripts/install.sh

@@ -79,6 +79,9 @@ deprecated_items=(
     "$CLAUDE_DIR/agents/bash-expert.md"          # -> skills/bash-ops
     "$CLAUDE_DIR/agents/claude-architect.md"     # -> skills/claude-code-ops
     "$CLAUDE_DIR/agents/aws-fargate-ecs-expert.md" # -> skills/container-orchestration
+    "$CLAUDE_DIR/agents/craftcms-expert.md"      # -> skills/craftcms-ops
+    "$CLAUDE_DIR/agents/payloadcms-expert.md"    # -> skills/payloadcms-ops
+    "$CLAUDE_DIR/agents/asus-router-expert.md"   # -> skills/asus-router-ops
 )
 
 # Renamed skills: -patterns -> -ops (March 2026)

+ 157 - 0
skills/asus-router-ops/SKILL.md

@@ -0,0 +1,157 @@
+---
+name: asus-router-ops
+description: "ASUS router configuration and hardening - Asuswrt-Merlin firmware, security hardening, encrypted DNS (DoT/DoH), VPN (WireGuard/OpenVPN), guest networks, VLAN/IoT isolation, AiMesh, AiProtection, JFFS scripts, QoS. Use for: asus router, asuswrt, merlin, asuswrt-merlin, router hardening, DNS Director, AiProtection, AiMesh, guest network, VPN Director, wireguard router, openvpn router, nvram, jffs, DoT, DoH, port forwarding, IoT isolation."
+license: MIT
+allowed-tools: "Read Write Bash"
+metadata:
+  author: claude-mods
+  related-skills: net-ops
+---
+
+# ASUS Router Operations
+
+Authoritative guidance for configuring and hardening ASUS routers — stock **Asuswrt** and **Asuswrt-Merlin** firmware — via the web UI and SSH/nvram. Covers security hardening, encrypted DNS, VPN, network segmentation, AiMesh, AiProtection, and JFFS scripting.
+
+> **Safety first.** Changes here can lock you out or drop the network. Test during low-usage windows, document the before value, and know how to undo. Cite official docs, not folklore.
+
+---
+
+## Stock Asuswrt vs Asuswrt-Merlin
+
+| | Stock Asuswrt | Asuswrt-Merlin |
+|---|---|---|
+| Base | ASUS official | Community fork of ASUS source (same core, more control) |
+| Scripting | Limited | **JFFS custom scripts**, cron, `services-start`, `firewall-start`, nat-start |
+| DNS control | Basic | **DNS Director** (per-client/global DNS redirection, DoT) |
+| VPN | OpenVPN/WireGuard server+client | + **VPN Director** (policy/split-tunnel routing) |
+| Best for | Most users | Power users wanting scripts, fine-grained DNS/VPN routing |
+
+**Never mix stock and Merlin nodes in the same AiMesh network.** Keep the firmware family consistent across mesh nodes.
+
+---
+
+## Security hardening checklist
+
+Do these on every new router, in order:
+
+1. **Change defaults immediately** — both the admin/login password *and* the WiFi password.
+2. **Disable WPS** — it's a brute-force surface.
+3. **Disable UPnP** unless an app genuinely needs it (it creates unpredictable port forwards).
+4. **Use explicit port forwarding, never DMZ** — DMZ exposes the entire device.
+5. **Disable remote WAN admin access** — use a VPN to manage remotely instead.
+6. **Enable AiProtection** (two-way IPS + malicious-site blocking) where available.
+7. **Set up a guest network** with intranet access disabled (proper isolation).
+8. **Enable firewall logging** for security monitoring; forward to syslog if you have a collector.
+9. **Apply ingress filtering** (BCP38/84 anti-spoofing) where supported.
+10. **Keep firmware current** — security fixes land in point releases.
+
+See `references/hardening-and-network.md` for the full hardening rationale, VLAN/IoT
+segmentation, AiMesh backhaul tuning, QoS, and dual-WAN.
+
+---
+
+## DNS privacy stack
+
+| Layer | What | Notes |
+|-------|------|-------|
+| **Transport** | DoT (DNS over TLS) or DoH (DNS over HTTPS) | Stops plaintext port-53 hijacking. Merlin DNS Director can enforce DoT |
+| **Provider** | Cloudflare (1.1.1.1), NextDNS, ControlD, AdGuard | Choose for filtering/analytics needs |
+| **Validation** | DNSSEC | Validates record authenticity |
+| **Per-client policy** | DNS Director (Merlin) | Different DNS per device/profile; split-horizon |
+| **Rebinding protection** | On by default | **Can break local services** (Plex, smart home) — whitelist specific domains rather than disabling wholesale |
+
+**Avoid plain DNS (port 53)** — unencrypted and hijackable. Move to DoT/DoH.
+
+---
+
+## VPN decision table
+
+| Need | Use |
+|------|-----|
+| Fast modern tunnel, low overhead | **WireGuard** server/client (preferred where supported) |
+| Maximum compatibility / legacy clients | **OpenVPN** server/client |
+| Route only *some* clients/traffic through VPN | **VPN Director** (Merlin) — policy-based split tunnel |
+| Remote admin of the router | VPN in, then manage on LAN (never expose WAN admin) |
+
+Common clients: NordVPN, Surfshark, Mullvad via OpenVPN/WireGuard config import.
+
+---
+
+## Network segmentation
+
+| Goal | Approach |
+|------|----------|
+| Visitor isolation | Guest network with "Access Intranet" **off** |
+| IoT containment | Dedicated guest/VLAN SSID; block lateral movement to main LAN |
+| Consistent guest across mesh | Enable guest on AiMesh deliberately; mind "Access Intranet" per node |
+| Smart-home discovery | mDNS/Bonjour may need controlled cross-VLAN allowances — scope narrowly |
+| Segmented routing | VLAN segmentation + routing policies (capability varies by model) |
+
+---
+
+## Patterns to avoid
+
+| Anti-pattern | Why | Instead |
+|--------------|-----|---------|
+| DMZ mode | Exposes the whole device to the internet | Explicit per-port forwarding |
+| UPnP globally on | Unpredictable auto port forwards | Enable only when required, understand the risk |
+| Plain DNS (port 53) | Plaintext, hijackable | DoT/DoH |
+| Mixing stock + Merlin in AiMesh | Inconsistent behavior | Keep firmware family uniform |
+| Disabling DNS rebind protection wholesale | Reopens rebinding attacks | Whitelist the specific local domains that break |
+| Wireless mesh backhaul on congested channels | Throughput collapse | Wired backhaul or dedicated DFS 5GHz channel |
+| Default admin/WiFi credentials | Trivial compromise | Change both immediately |
+| Remote WAN admin enabled | Major attack surface | Manage via VPN |
+
+---
+
+## Operating principles
+
+1. **Reversibility** — record the current value before changing; know the undo path.
+2. **Testability** — change during low-usage windows; verify before walking away.
+3. **Trade-offs** — note privacy-vs-functionality costs (rebind protection vs local services).
+4. **Verification** — confirm via system log, client-side test (e.g. DNS leak test), or `nvram get`.
+5. **Cite official docs** — avoid unverified tweaks.
+
+---
+
+## SSH / JFFS scripting (Merlin)
+
+Merlin runs user scripts from JFFS at lifecycle points. Enable **JFFS custom scripts and
+configs** (Administration → System) first.
+
+| Script | Runs at | Use for |
+|--------|---------|---------|
+| `services-start` | After services start | Start custom daemons |
+| `firewall-start` | After firewall (re)builds | Add custom iptables rules (survives firewall restarts) |
+| `nat-start` | After NAT rules load | Custom NAT/port rules |
+| `dnsmasq.postconf` | Before dnsmasq starts | Inject dnsmasq config |
+
+Inspect/set persistent config with `nvram get <key>` / `nvram set <key>=<val>` + `nvram commit`
+(commit sparingly — it writes flash).
+
+The `assets/firewall-start.sh` template shows the canonical safe shape for custom firewall
+rules. See `references/hardening-and-network.md` for placement and gotchas.
+
+---
+
+## Assets
+
+| File | Use |
+|------|-----|
+| `assets/firewall-start.sh` | Annotated Merlin `/jffs/scripts/firewall-start` template — idempotent custom iptables rules with safe-by-default examples |
+
+---
+
+## See also
+
+- `net-ops` — general networking: subnets, DNS, TLS, firewalls, packet inspection
+
+### Key external resources
+
+- [Asuswrt-Merlin project](https://www.asuswrt-merlin.net/) · [docs](https://www.asuswrt-merlin.net/docs) · [wiki](https://github.com/RMerl/asuswrt-merlin.ng/wiki)
+- [Merlin features (DNS Director, VPN Director)](https://www.asuswrt-merlin.net/features)
+- [ASUS router security hardening FAQ](https://www.asus.com/support/faq/1039292/)
+- [ASUS firewall intro](https://www.asus.com/us/support/faq/1013630/) · [Network Services Filter](https://www.asus.com/support/faq/1013636/) · [IPv6 firewall](https://www.asus.com/support/faq/1013638/)
+- [AiProtection overview](https://www.asus.com/au/content/aiprotection/) · [setup](https://www.asus.com/support/faq/1008719/)
+- [Cloudflare DoH](https://developers.cloudflare.com/1.1.1.1/encryption/dns-over-https/) · [ControlD ASUS setup](https://docs.controld.com/docs/asus-router-setup)
+- [SNBForums community](https://www.snbforums.com/)

+ 45 - 0
skills/asus-router-ops/assets/firewall-start.sh

@@ -0,0 +1,45 @@
+#!/bin/sh
+# Asuswrt-Merlin custom firewall rules — copy to /jffs/scripts/firewall-start
+#
+# This is an ASSET TEMPLATE for a router, not an agent-run script. It runs on the
+# router every time Merlin (re)builds the firewall, so rules added here survive
+# firewall restarts. Adapt the ADAPT: lines, then on the router:
+#   chmod +x /jffs/scripts/firewall-start
+# (Requires JFFS custom scripts enabled: Administration > System.)
+#
+# RULES MUST BE IDEMPOTENT: delete-then-insert so re-runs don't stack duplicates.
+# Test with the router console open — a bad rule can drop your management session.
+
+LOG_PREFIX="custom-fw"
+
+# Helper: insert a rule only after removing any existing identical one.
+# Usage: reinsert <table?> <chain> <rule...>
+reinsert_filter() {
+  chain="$1"; shift
+  iptables -D "$chain" "$@" 2>/dev/null
+  iptables -I "$chain" "$@"
+}
+
+# --- Example 1: drop + log inbound from a known-bad subnet (ADAPT or remove) ---
+# reinsert_filter INPUT -s 203.0.113.0/24 -j DROP
+
+# --- Example 2: isolate an IoT subnet from the main LAN (ADAPT subnets) ---
+# Assumes IoT on 192.168.50.0/24, main LAN on 192.168.1.0/24.
+# Block IoT -> LAN, but allow established replies LAN -> IoT.
+# reinsert_filter FORWARD -i br0 -s 192.168.50.0/24 -d 192.168.1.0/24 \
+#   -m state --state NEW -j DROP
+
+# --- Example 3: allow a specific LAN host to reach an IoT device (exception) ---
+# reinsert_filter FORWARD -s 192.168.1.10 -d 192.168.50.20 -j ACCEPT
+
+# --- Example 4: rate-limit + log SSH brute force on the LAN admin port (ADAPT) ---
+# reinsert_filter INPUT -p tcp --dport 22 -m state --state NEW \
+#   -m recent --set --name SSHPROBE
+# reinsert_filter INPUT -p tcp --dport 22 -m state --state NEW \
+#   -m recent --update --seconds 60 --hitcount 5 --name SSHPROBE \
+#   -j LOG --log-prefix "${LOG_PREFIX}-ssh-bruteforce: "
+# reinsert_filter INPUT -p tcp --dport 22 -m state --state NEW \
+#   -m recent --update --seconds 60 --hitcount 5 --name SSHPROBE -j DROP
+
+# All examples are commented out by default — uncomment and ADAPT the ones you need.
+exit 0

+ 97 - 0
skills/asus-router-ops/references/hardening-and-network.md

@@ -0,0 +1,97 @@
+# Hardening, Segmentation, AiMesh & QoS — deep dive
+
+Load for the rationale behind the hardening checklist, network segmentation design, AiMesh
+backhaul tuning, dual-WAN, and JFFS script placement gotchas.
+
+## Hardening rationale (why each step)
+
+| Step | Threat it closes |
+|------|------------------|
+| Change admin + WiFi password | Default-credential takeover (botnets scan for these) |
+| Disable WPS | PIN brute force / Pixie Dust |
+| Disable UPnP | Malware silently opening inbound ports |
+| Explicit port forward, no DMZ | DMZ forwards *all* ports to one host — total exposure |
+| No remote WAN admin | Internet-facing admin panel = credential-stuffing target |
+| AiProtection two-way IPS | Inbound exploit + outbound C2 / data exfil detection |
+| Guest isolation | Stops a compromised visitor device reaching the LAN |
+| Firewall logging | Detection + forensics |
+| BCP38/84 ingress filtering | Source-address spoofing / reflection-attack participation |
+
+### DNS rebinding protection trade-off
+
+Rebinding protection blocks responses that resolve to private/local IPs — defeating an
+attacker who tricks the browser into talking to your LAN. But it also breaks legitimate
+local services that resolve to RFC1918 addresses (Plex, Home Assistant, NAS web UIs).
+**Whitelist the specific domains** that need to resolve locally rather than turning the
+protection off entirely.
+
+## Network segmentation design
+
+| Tier | Placement | Cross-talk |
+|------|-----------|------------|
+| Main LAN | Trusted devices (laptops, phones) | Full intranet |
+| IoT | Cameras, smart plugs, TVs | **No** access to main LAN; internet only |
+| Guest | Visitors | No intranet; bandwidth-capped |
+| Lab/DMZ-ish | Experimental hosts | Quarantined |
+
+Implementation varies by model — higher-end ASUS routers expose VLAN/multiple isolated
+SSIDs; others rely on guest-network isolation. The principle: an IoT device's compromise
+must not reach your laptop.
+
+**mDNS/Bonjour caveat:** smart-home discovery (Chromecast, AirPlay, HomeKit) uses mDNS,
+which doesn't cross subnets/VLANs by default. If you segment IoT away from clients, you may
+need a controlled mDNS reflector/repeater — scope it to the minimum needed, don't blanket-allow.
+
+## AiMesh deployment
+
+| Concern | Guidance |
+|---------|----------|
+| **Backhaul** | Wired backhaul is best. If wireless, use a dedicated/DFS 5GHz channel, not a congested one |
+| **Node placement** | Within solid signal of the parent — too far hurts more than no node |
+| **Channel/width** | Avoid congested channels; wider channels = more throughput but more interference |
+| **Firmware** | Same family on all nodes (don't mix stock + Merlin) |
+| **AiProtection/QoS across mesh** | Settings apply mesh-wide; verify they propagate to nodes |
+| **Guest on mesh** | Enable deliberately; "Access Intranet" behavior must be consistent per node |
+
+## QoS & traffic management
+
+| Mode | Use for |
+|------|---------|
+| **Adaptive QoS** | General prioritization (gaming/streaming/WFH) with app categories |
+| **Bandwidth limiter** | Hard per-device caps (guest, kids' devices) |
+| **Game acceleration** | Latency-sensitive traffic prioritization |
+
+QoS and AiProtection share the Trend Micro engine on many models — enabling one may affect
+throughput on lower-end hardware.
+
+## Dual-WAN
+
+| Mode | Behavior |
+|------|----------|
+| Failover | Secondary WAN takes over when primary drops |
+| Load balance | Distributes sessions across both WANs (ratio configurable) |
+
+Define a failback policy and test by physically unplugging the primary during a low-usage window.
+
+## JFFS script placement gotchas (Merlin)
+
+- Enable **JFFS custom scripts and configs** before scripts will run.
+- Scripts live in `/jffs/scripts/` and must be `chmod +x` with a `#!/bin/sh` shebang.
+- **`firewall-start`** is the right place for custom iptables rules — it re-runs every time
+  the firewall rebuilds (which happens on many config changes), so rules added here survive.
+  Adding iptables rules elsewhere risks them being flushed.
+- Make rules **idempotent** — check/delete before insert (`iptables -D ... 2>/dev/null;
+  iptables -I ...`) so re-runs don't stack duplicates.
+- `nvram commit` writes flash — commit only when persistence is needed, not in loops.
+- Test rules with the router console open; a bad rule can drop your management session.
+
+## Verification toolkit
+
+| Check | How |
+|-------|-----|
+| DNS encryption working | DNS leak test site; confirm resolver = your DoT/DoH provider |
+| Firewall rule active | `iptables -L -n -v` (Merlin SSH) |
+| nvram value | `nvram get <key>` |
+| Guest isolation | From guest SSID, attempt to reach a LAN host — should fail |
+| Failover | Unplug primary WAN, watch system log for switch |
+| VPN tunnel up | Check assigned IP / provider's "are you connected" page |

+ 0 - 0
skills/asus-router-ops/scripts/.gitkeep


+ 192 - 0
skills/craftcms-ops/SKILL.md

@@ -0,0 +1,192 @@
+---
+name: craftcms-ops
+description: "Craft CMS 5 development - content modeling, Twig templating, element queries, GraphQL, plugins, and the Craft 4-to-5 Matrix-as-entries change. Use for: craft cms, craftcms, craft 5, twig, pixel & tonic, matrix field, entry types, sections, element query, eager loading, blitz, project config, headless craft, craft graphql, craft plugin, craft 4 to 5 upgrade."
+license: MIT
+allowed-tools: "Read Write Bash"
+metadata:
+  author: claude-mods
+  related-skills: laravel-ops, sql-ops, nginx-ops
+---
+
+# Craft CMS Operations
+
+Authoritative reference for **Craft CMS 5.x** development: content modeling, Twig templating, element-query optimization, GraphQL/headless setups, plugin development, and the Craft 4 → 5 migration. Craft is a self-hosted PHP application built on Yii 2, backed by MySQL or PostgreSQL.
+
+> **Version note (verified against craftcms.com/docs/5.x, 2026-06):** Craft 5 is current; Craft 6 exists. The defining Craft 5 change is that **Matrix is now an entries-based field** — Matrix "blocks" are gone, replaced by nested **entries** with **entry types**. Fields are **globally reusable** across all field layouts. Don't ship Craft 3/4 "Matrix block" guidance.
+
+---
+
+## Craft 5 architecture at a glance
+
+| Concept | What it is | Craft 5 change |
+|---------|-----------|----------------|
+| **Section** | Container exposing entry types + URL rules | Three kinds: Single, Channel, Structure |
+| **Entry Type** | Atomic unit of content (fields, title, slug) | Now **global + reusable** across sections, with per-section aliases |
+| **Entry** | An instance of an entry type | Can be top-level or **nested** (inside Matrix/CKEditor) |
+| **Field** | Reusable input attached via field layouts | **Globally reusable** — no per-field-instance duplication |
+| **Matrix field** | Repeatable nested content | **Now stores entries** (entry types), not "blocks". Nesting supported natively |
+| **Project Config** | Version-controlled schema (`config/project/`) | Source of truth for sections/fields/settings |
+
+### Section types
+
+| Type | Use for | Has URLs? | Hierarchy? |
+|------|---------|-----------|-----------|
+| **Single** | One-off pages (home, about) | Optional fixed URI | No |
+| **Channel** | Streams (blog, news, products) | Yes, per-entry-type URI format | No |
+| **Structure** | Nested/ordered content (docs, nav) | Yes | Yes (drag-to-order, levels) |
+
+---
+
+## Element queries (the 80/20)
+
+Everything readable in Craft is an *element* (entries, assets, users, categories, tags). You fetch them with element queries.
+
+```twig
+{# Channel entries, newest first #}
+{% set posts = craft.entries()
+  .section('blog')
+  .type('article')
+  .orderBy('postDate DESC')
+  .limit(10)
+  .all() %}
+
+{# Eager-load relations to kill N+1 #}
+{% set posts = craft.entries()
+  .section('blog')
+  .with(['author', 'featuredImage', 'categories'])
+  .all() %}
+
+{# Single entry by slug #}
+{% set page = craft.entries().section('pages').slug('about').one() %}
+
+{# Relations: entries related to a given category #}
+{% set related = craft.entries().relatedTo(category).all() %}
+```
+
+| Need | Method |
+|------|--------|
+| Filter by section | `.section('handle')` |
+| Filter by entry type | `.type('handle')` |
+| Eager-load relations | `.with(['field', 'field.subfield'])` |
+| Status | `.status('live')` / `.status(['live','expired'])` |
+| One vs many | `.one()` / `.all()` / `.count()` / `.exists()` |
+| Pagination | `{% paginate query as pageInfo, entries %}` |
+| Eager-load nested Matrix entries | `.with(['matrixField'])` then loop nested entries |
+
+**Eager-loading nested entries (Craft 5):** because Matrix content is now entries, eager-load the Matrix field then iterate the nested entries by their entry type:
+
+```twig
+{% set page = craft.entries().section('pages').with(['body']).one() %}
+{% for block in page.body.all() %}
+  {% switch block.type.handle %}
+    {% case 'text' %}{{ block.richText }}
+    {% case 'image' %}{{ block.image.one().url }}
+  {% endswitch %}
+{% endfor %}
+```
+
+See `references/twig-and-queries.md` for the full query parameter catalog, pagination, and Twig patterns.
+
+---
+
+## Twig conventions
+
+| Pattern | Rule |
+|---------|------|
+| Private templates | Prefix with `_` (`_layouts/`, `_partials/`) so they're not directly routable |
+| Layout inheritance | `{% extends '_layouts/base' %}` + `{% block content %}` |
+| Reusable markup | `{% include '_partials/card' with { entry: entry } %}` or `{{ include() }}` |
+| Avoid logic in templates | Push business logic to a module/plugin service, not Twig |
+| Caching | `{% cache %}` — **only after** queries are optimized, never to mask N+1 |
+
+---
+
+## Headless / GraphQL
+
+Craft ships a GraphQL API for decoupled frontends (Next.js, Nuxt, Astro, etc.).
+
+| Concern | Approach |
+|---------|----------|
+| Schema | Define **GraphQL schemas** + scopes in Control Panel; generate a token per schema |
+| Auth | Bearer token per schema; public schema for anonymous reads |
+| Alternative | Element API plugin for custom JSON endpoints when GraphQL is overkill |
+| CORS | Configure allowed origins for the headless frontend |
+| Eager loading | GraphQL resolves relations efficiently; still design queries to avoid over-fetching |
+
+See `references/graphql-and-plugins.md` for schema setup, query shape, and plugin/module development.
+
+---
+
+## Performance decision table
+
+| Symptom | Fix |
+|---------|-----|
+| Slow listing pages | Eager-load with `.with([...])` — the #1 Craft perf bug is N+1 inside loops |
+| Repeated identical render | `{% cache %}` tag (after query optimization) |
+| Whole-site cache needed | **Blitz** plugin (static page caching, granular invalidation) |
+| Slow `orderBy` on custom field | Ensure the underlying column/field is indexed |
+| Heavy asset transforms | Pre-generate transforms; use Imgix/CDN |
+
+---
+
+## Project Config & deployment
+
+- **Project Config** (`config/project/*.yaml`) is the version-controlled source of truth for sections, fields, entry types, settings. Commit it.
+- Apply on deploy: `php craft up` (runs migrations + applies project config).
+- Environment-specific values go in `.env` and `config/general.php` (use `App::env()` / `getenv()`).
+- Data transformations belong in **content migrations**, not manual DB edits.
+
+---
+
+## Craft 4 → 5 upgrade checklist
+
+| Area | What changed | Action |
+|------|--------------|--------|
+| Matrix | Blocks → **entries with entry types** | Templates iterating `.type.handle` mostly survive; re-check block-type field handles |
+| Fields | Now **globally reusable** | Expect field/entry-type proliferation post-upgrade — consolidate duplicates |
+| Content storage | Reworked internal storage | Run `php craft up`; test queries on staging |
+| PHP/DB | Craft 5 needs PHP 8.2+ | Verify host before upgrading |
+| Plugins | Many need a Craft 5-compatible release | Audit plugin compatibility first |
+
+Full upgrade guidance: <https://craftcms.com/docs/5.x/upgrade.html>
+
+---
+
+## Common gotchas
+
+| Gotcha | Why | Fix |
+|--------|-----|-----|
+| N+1 queries in loops | Element relations lazy-load | Always `.with([...])` before iterating |
+| `{% cache %}` masking slow queries | Cache hides, doesn't fix | Optimize queries first, cache second |
+| Business logic in Twig | Hard to test/reuse | Move to a module/plugin service |
+| Project Config drift in teams | Out-of-band CP edits | Treat `config/project/` as source of truth; `php craft up` on deploy |
+| Untested migrations to prod | Data loss risk | Test on staging clone first |
+| Over-using Matrix | Complexity + perf cost | Use simpler structures when nesting isn't needed |
+| Calling old "Matrix block" APIs | Removed in Craft 5 | Use entry/entry-type APIs |
+
+---
+
+## Assets
+
+| File | Use |
+|------|-----|
+| `assets/entry-type-field-layout.md` | Annotated content-modeling starter: section + entry type + field layout + Matrix-as-entries shape, mapped to Project Config |
+
+---
+
+## See also
+
+- `laravel-ops` — shared PHP/Composer/Twig-adjacent tooling, Eloquent patterns for comparison
+- `sql-ops` — index strategy behind slow `orderBy`/relation queries
+- `nginx-ops` — serving Craft, caching headers, reverse proxy for headless
+
+### Key external resources
+
+- [Craft CMS 5.x Docs](https://craftcms.com/docs/5.x/)
+- [Entries reference](https://craftcms.com/docs/5.x/reference/element-types/entries.html)
+- [Matrix fields (Craft 5)](https://craftcms.com/docs/5.x/reference/field-types/matrix.html)
+- [Eager-loading](https://craftcms.com/docs/5.x/development/eager-loading.html)
+- [GraphQL API](https://craftcms.com/docs/5.x/development/graphql.html)
+- [Upgrading from Craft 4](https://craftcms.com/docs/5.x/upgrade.html)
+- [Coding guidelines](https://craftcms.com/docs/5.x/extend/coding-guidelines.html)
+- [Blitz plugin](https://putyourlightson.com/plugins/blitz) · [nystudio107 blog](https://nystudio107.com/blog) · [Craft Stack Exchange](https://craftcms.stackexchange.com/)

+ 59 - 0
skills/craftcms-ops/assets/entry-type-field-layout.md

@@ -0,0 +1,59 @@
+# Content-modeling starter — Craft 5 (section + entry type + Matrix-as-entries)
+
+A known-good shape for modeling a "flexible content page" in Craft 5. Adapt the handles.
+Craft 5 stores this in **Project Config** (`config/project/*.yaml`) — build it in the
+Control Panel, then commit the generated YAML. This file documents the *intended shape*;
+it is not itself applied.
+
+## Target structure
+
+```
+Section: "Pages"  (type: Structure — nestable, ordered)
+└── Entry Type: "page"
+    ├── Field: title            (built-in)
+    ├── Field: heading          (Plain Text, global, reusable)
+    ├── Field: seoDescription   (Plain Text)
+    └── Field: body             (Matrix — Craft 5: stores NESTED ENTRIES)
+        ├── Entry Type: "richText"   → field: text   (CKEditor)
+        ├── Entry Type: "imageBlock" → field: image  (Assets, limit 1)
+        └── Entry Type: "callout"    → field: body   (Plain Text), style (Dropdown)
+```
+
+Key Craft 5 facts baked into this shape:
+
+- **Matrix `body` holds entries, not "blocks".** Each nested entry has an **entry type**
+  (`richText`, `imageBlock`, `callout`). Branch on `block.type.handle` in Twig.
+- **Fields are global.** `heading`, `text`, `image` etc. are defined once and reused across
+  any field layout. Reuse the same `text` field in multiple entry types rather than cloning.
+- An entry type can be **shared across sections** with a per-section alias (name/handle
+  override) if you want the same shape exposed in, say, both "Pages" and "Landing Pages".
+
+## Rendering it (template `_layouts/page.twig` + section template)
+
+```twig
+{% set page = craft.entries().section('pages').slug(craft.app.request.segment(1)).with(['body']).one() %}
+{% if not page %}{% exit 404 %}{% endif %}
+
+<h1>{{ page.heading ?: page.title }}</h1>
+
+{% for block in page.body.all() %}
+  {% switch block.type.handle %}
+    {% case 'richText' %}
+      <div class="prose">{{ block.text }}</div>
+    {% case 'imageBlock' %}
+      {% set img = block.image.one() %}
+      {% if img %}<figure><img src="{{ img.url }}" alt="{{ img.alt }}"></figure>{% endif %}
+    {% case 'callout' %}
+      <aside class="callout callout--{{ block.style.value }}">{{ block.body }}</aside>
+  {% endswitch %}
+{% endfor %}
+```
+
+## Project Config notes
+
+- After creating the above in the CP, the schema lands in `config/project/` as YAML.
+  Commit it. On deploy, `php craft up` applies it.
+- Don't hand-edit Project Config YAML for structural changes — make them in the CP and let
+  Craft serialize, to keep UIDs consistent.
+- Environment-specific values (asset base URLs, API keys) belong in `.env` /
+  `config/general.php`, never in Project Config.

+ 99 - 0
skills/craftcms-ops/references/graphql-and-plugins.md

@@ -0,0 +1,99 @@
+# GraphQL, Headless & Plugin Development (Craft 5)
+
+Load this for decoupled/headless setups or when building a plugin/module.
+
+## GraphQL / headless
+
+Craft ships a first-party GraphQL API. Flow:
+
+1. **Define a schema** in the Control Panel (GraphQL → Schemas). Scope it to the sections, entry types, asset volumes, etc. the frontend may read.
+2. **Generate a token** per schema. The **public schema** serves anonymous requests; private schemas require a Bearer token.
+3. **Endpoint**: `/api` by default (configurable). POST GraphQL queries; auth via `Authorization: Bearer <token>`.
+4. **CORS**: set allowed origins for the headless frontend (Next.js/Nuxt/Astro).
+
+Example query against entries (note the Craft 5 entry/entry-type model):
+
+```graphql
+query Posts {
+  entries(section: "blog", limit: 10, orderBy: "postDate DESC") {
+    title
+    slug
+    ... on blog_article_Entry {
+      postDate
+      featuredImage { url }
+      author { fullName }
+    }
+  }
+}
+```
+
+The fragment type name follows `{section}_{entryType}_Entry`. Nested Matrix entries resolve as their own entry types under the Matrix field.
+
+### When NOT to use GraphQL
+
+- Small number of fixed endpoints → the **Element API** plugin (custom JSON routes) is simpler.
+- Server-rendered Twig site → no API layer needed at all.
+
+## Plugin vs module
+
+| Build a… | When |
+|----------|------|
+| **Module** | Project-specific code, no distribution (`modules/` in the app) |
+| **Plugin** | Reusable/distributable via the Plugin Store (Composer package) |
+
+Both extend Craft via Yii 2 components: services, controllers, element types, field types, widgets, behaviors, events.
+
+## Plugin anatomy
+
+```
+my-plugin/
+├── composer.json          # type: craft-plugin, autoload PSR-4
+├── src/
+│   ├── Plugin.php         # init(), registers services & event handlers
+│   ├── services/          # business logic (injectable)
+│   ├── controllers/       # CP + site request handlers
+│   ├── elements/          # custom element types
+│   ├── fields/            # custom field types
+│   └── migrations/        # install + content migrations
+└── README.md
+```
+
+Key registration patterns in `Plugin::init()`:
+
+```php
+// Register a service
+$this->setComponents(['myService' => MyService::class]);
+
+// Hook an event
+Event::on(
+    Entries::class,
+    Entries::EVENT_AFTER_SAVE_ENTRY,
+    function (EntryEvent $e) { /* ... */ }
+);
+
+// Register a Twig extension
+Craft::$app->view->registerTwigExtension(new MyTwigExtension());
+```
+
+Follow the official [coding guidelines](https://craftcms.com/docs/5.x/extend/coding-guidelines.html) — namespacing, service-layer separation, and event-driven extension are expected idioms.
+
+## Migrations
+
+| Migration kind | Purpose |
+|----------------|---------|
+| **Install migration** | Schema a plugin needs on install (`migrations/Install.php`) |
+| **Plugin migration** | Schema changes between plugin versions |
+| **Content migration** | Project data transformations (`php craft migrate/create`) — version-controlled, run via `php craft up` |
+
+Always test migrations on a staging clone before production.
+
+## Integration points
+
+| Concern | Common choices |
+|---------|----------------|
+| Frontend frameworks | Next.js, Nuxt, Astro, Gatsby via GraphQL / Element API |
+| Hosting | Servd, Fortrabbit, Laravel Forge; DDEV for local |
+| Assets | AWS S3, Google Cloud Storage, Imgix transforms |
+| Search | Algolia, Elasticsearch via plugins |
+| Commerce | Craft Commerce |
+| Caching | Blitz (static pages), Redis, Cloudflare |

+ 118 - 0
skills/craftcms-ops/references/twig-and-queries.md

@@ -0,0 +1,118 @@
+# Twig & Element Queries — deep dive (Craft 5)
+
+Load this when writing non-trivial templates, debugging N+1, or building pagination.
+
+## Element query parameter catalog
+
+Every element type (entries, assets, users, categories, tags) shares a query builder. Common parameters for `craft.entries()`:
+
+| Parameter | Purpose | Example |
+|-----------|---------|---------|
+| `.section()` | One or more section handles | `.section(['blog','news'])` |
+| `.type()` | Entry type handle | `.type('article')` |
+| `.id()` | Specific element id(s) | `.id(42)` |
+| `.slug()` | By slug | `.slug('about')` |
+| `.status()` | `live`, `pending`, `expired`, `disabled` | `.status(['live','expired'])` |
+| `.orderBy()` | Sort | `.orderBy('postDate DESC')` |
+| `.limit()` / `.offset()` | Slice | `.limit(10).offset(20)` |
+| `.search()` | Full-text via search index | `.search('keyword')` |
+| `.relatedTo()` | Relationship queries | `.relatedTo(category)` |
+| `.with()` | Eager-load relations | `.with(['author','image'])` |
+| `.site()` | Target a specific site (multi-site) | `.site('en')` |
+| `.unique()` | Dedupe across sites | `.unique()` |
+
+Terminators: `.all()`, `.one()`, `.count()`, `.exists()`, `.ids()`, `.nth(n)`, `.collect()` (returns a Collection).
+
+## Eager loading (kill N+1)
+
+The single most common Craft performance bug is querying relations inside a loop. Always eager-load:
+
+```twig
+{# BAD — one query per entry for author #}
+{% for entry in craft.entries().section('blog').all() %}
+  {{ entry.author.one().fullName }}
+{% endfor %}
+
+{# GOOD — eager-load up front #}
+{% set posts = craft.entries().section('blog').with(['author']).all() %}
+{% for entry in posts %}
+  {{ entry.author.fullName }}
+{% endfor %}
+```
+
+Nested paths work: `.with(['author.userPhoto', 'categories', 'body'])`.
+
+### Eager-loading nested Matrix entries (Craft 5)
+
+Matrix content is now nested *entries*. Eager-load the Matrix field, then branch on `block.type.handle`:
+
+```twig
+{% set page = craft.entries().section('pages').slug(slug).with(['body']).one() %}
+{% for block in page.body.all() %}
+  {% switch block.type.handle %}
+    {% case 'richText' %}
+      {{ block.text }}
+    {% case 'imageBlock' %}
+      {% set img = block.image.one() %}
+      {% if img %}<img src="{{ img.url }}" alt="{{ img.alt }}">{% endif %}
+    {% case 'callout' %}
+      <aside>{{ block.body }}</aside>
+  {% endswitch %}
+{% endfor %}
+```
+
+To eager-load relations *inside* nested entries, use a nested path through the Matrix handle (e.g. `.with(['body.image'])`).
+
+## Pagination
+
+```twig
+{% set query = craft.entries().section('blog').orderBy('postDate DESC') %}
+{% paginate query.limit(12) as pageInfo, entries %}
+
+{% for entry in entries %}
+  {{ entry.title }}
+{% endfor %}
+
+{% if pageInfo.prevUrl %}<a href="{{ pageInfo.prevUrl }}">Previous</a>{% endif %}
+{% if pageInfo.nextUrl %}<a href="{{ pageInfo.nextUrl }}">Next</a>{% endif %}
+```
+
+`pageInfo` exposes `.currentPage`, `.totalPages`, `.total`, `.first`, `.last`, `.getRangeUrls()`.
+
+## Template organization
+
+| Convention | Detail |
+|------------|--------|
+| Private templates | Prefix `_` (`_layouts/`, `_partials/`, `_macros/`) so Craft won't route to them directly |
+| Layout inheritance | `{% extends '_layouts/base' %}`, fill `{% block %}` regions |
+| Includes | `{% include '_partials/card' with { entry } only %}` — `only` isolates scope |
+| Macros | `{% macro %}` / `{% import %}` for repeated markup helpers |
+| Embeds | `{% embed %}` when you need to override blocks inside an included template |
+
+## Caching
+
+```twig
+{% cache %}
+  {# expensive, rarely-changing markup #}
+{% endcache %}
+
+{% cache unless craft.app.config.general.devMode %}...{% endcache %}
+{% cache for 1 week %}...{% endcache %}
+{% cache using key entry.id %}...{% endcache %}
+```
+
+Rules:
+- Cache *after* eager-loading and query optimization, never instead of it.
+- `{% cache %}` does not cache the query result tags it wraps if they contain `{% nocache %}` regions.
+- For full static-page caching with smart invalidation, reach for **Blitz** rather than hand-rolled `{% cache %}`.
+
+## Multi-site
+
+| Need | Approach |
+|------|----------|
+| Query a specific site | `.site('handle')` |
+| Query all sites | `.site('*')` then `.unique()` to dedupe shared elements |
+| Current site in template | `craft.app.sites.currentSite` |
+| Localized URLs | `entry.url` resolves per-site; `craft.entries().id(x).site('fr').one().url` for the other locale |
+
+Content can be propagated or per-site editable per field/section setting; design the section's propagation method up front.

+ 0 - 0
skills/craftcms-ops/scripts/.gitkeep


+ 1 - 1
skills/doc-scanner/templates.md

@@ -15,7 +15,7 @@ Brief description of the project and what it does.
 |------|-------|----------------|
 | Web scraping | `firecrawl-expert` | "Extract structured data from this site" |
 | Project cleanup | `project-organizer` | "Restructure this directory layout" |
-| CMS modeling | `payloadcms-expert` | "Design these collections and access control" |
+| Git operations | `git-agent` | "Commit and open a PR for these changes" |
 
 ## Primary Agents
 

+ 245 - 0
skills/payloadcms-ops/SKILL.md

@@ -0,0 +1,245 @@
+---
+name: payloadcms-ops
+description: "Payload CMS 3 (Next.js-native) architecture - collections, globals, fields, access control, hooks, Local API, storage adapters, and database (Postgres/MongoDB/SQLite). Use for: payload, payloadcms, payload cms, payload 3, collection config, access control, payload hooks, local api, payload fields, multi-tenant payload, payload nextjs, payload s3, payload r2, payloadcms architecture, headless cms typescript."
+license: MIT
+allowed-tools: "Read Write Bash"
+metadata:
+  author: claude-mods
+  related-skills: typescript-ops, react-ops, api-design-ops, auth-ops
+---
+
+# Payload CMS Operations
+
+Authoritative reference for **Payload 3.x** — the Next.js-native, TypeScript-first headless CMS. Payload 3 **installs into a Next.js (App Router) app** and gives you an auto-generated admin panel, REST + GraphQL APIs, a typed Local API, authentication, access control, file storage, and live preview — one open-source TypeScript codebase.
+
+> **Version note (verified against payloadcms.com/docs, 2026-06):** Payload 3 is the **Next.js fullstack framework** — there is no standalone Express server anymore. The config lives at `src/payload.config.ts`; Payload mounts into the Next App Router via the installed `(payload)` route group. Don't ship Payload 2.x "standalone Express app" guidance.
+
+---
+
+## Architecture at a glance
+
+| Piece | What it is |
+|-------|-----------|
+| **payload.config.ts** | Single source of truth: collections, globals, db adapter, plugins, admin, auth |
+| **Collections** | Repeatable document groups (Posts, Users, Media) — the core building block |
+| **Globals** | Singletons (one document) — site settings, header/footer nav |
+| **Fields** | Compose document shape; also drive admin UI, validation, access |
+| **Local API** | Typed, in-process data access (`payload.find(...)`) — no HTTP, runs server-side |
+| **REST / GraphQL** | Auto-generated HTTP APIs over the same collections |
+| **Database adapter** | `@payloadcms/db-postgres`, `db-mongodb`, or `db-sqlite` |
+| **Storage adapter** | Local disk (dev) or S3/R2/etc. for uploads |
+
+### Where it lives in a Next.js app
+
+```
+src/
+├── payload.config.ts          # the config — collections, globals, db, plugins
+├── collections/               # one file per CollectionConfig
+│   ├── Users.ts
+│   ├── Posts.ts
+│   └── Media.ts
+├── globals/                   # GlobalConfig files
+└── app/
+    ├── (payload)/             # Payload's admin + API route group (generated)
+    └── (frontend)/            # your Next.js front end — uses the Local API
+```
+
+---
+
+## Collections — the core shape
+
+```typescript
+import type { CollectionConfig } from 'payload'
+
+export const Posts: CollectionConfig = {
+  slug: 'posts',                          // required, URL-safe identifier
+  admin: { useAsTitle: 'title', defaultColumns: ['title', 'status'] },
+  access: {                               // see access-control reference
+    read: () => true,
+    create: ({ req }) => Boolean(req.user),
+    update: ({ req }) => Boolean(req.user),
+    delete: ({ req }) => req.user?.role === 'admin',
+  },
+  versions: { drafts: true },             // draft/publish + revision history
+  hooks: { /* lifecycle — see hooks reference */ },
+  fields: [
+    { name: 'title', type: 'text', required: true },
+    { name: 'slug', type: 'text', unique: true, index: true },
+    { name: 'content', type: 'richText' },
+    { name: 'author', type: 'relationship', relationTo: 'users' },
+  ],
+}
+```
+
+| Collection property | Purpose |
+|---------------------|---------|
+| `slug` | Required identifier (and REST/GraphQL route base) |
+| `fields` | Required — document shape + UI + validation |
+| `access` | Per-operation authorization (read/create/update/delete) |
+| `hooks` | Lifecycle entry points (before/after change/read/delete) |
+| `admin` | Admin-panel UI (title field, columns, components, groups) |
+| `auth` | Turns the collection into an auth collection (e.g. Users) |
+| `upload` | Makes it an upload collection (file storage, image sizes) |
+| `versions` | Drafts + revision history |
+
+### Globals vs Collections
+
+> *"If your Collection is only ever meant to contain a single Document, consider using a Global instead."*
+
+Globals (`GlobalConfig`) are singletons — site settings, main nav. Same `fields`/`access`/`hooks`/`admin` surface, one document.
+
+---
+
+## Fields (the 80/20)
+
+| Type | Use for |
+|------|---------|
+| `text`, `textarea`, `number`, `email`, `date`, `checkbox` | Scalars |
+| `richText` | Lexical-based rich content |
+| `select`, `radio` | Enumerations |
+| `relationship` | Link to other collections (`relationTo`, `hasMany`) |
+| `upload` | Reference an upload collection (media) |
+| `array` | Repeatable sub-field groups |
+| `blocks` | Flexible content — choose from defined block types per row |
+| `group` | Nested namespaced fields |
+| `row`, `collapsible`, `tabs` | Admin layout only (no data nesting except `tabs` with `name`) |
+| `json`, `code` | Raw structured/code data |
+
+Every field can carry `access`, `hooks`, `validate`, `admin.condition` (conditional display), and `localized: true` for i18n. See `references/hooks-and-fields.md`.
+
+---
+
+## Access control — least privilege by default
+
+Access functions return `boolean` **or a query constraint** (row-level filtering). They run for Local API, REST, and GraphQL uniformly.
+
+```typescript
+access: {
+  // boolean: can this user perform the op at all?
+  delete: ({ req }) => req.user?.role === 'admin',
+
+  // query constraint: WHICH documents can they read? (row-level)
+  read: ({ req }) => {
+    if (req.user?.role === 'admin') return true
+    return { author: { equals: req.user?.id } }  // only their own
+  },
+}
+```
+
+- **Collection-level** (read/create/update/delete) and **field-level** (`field.access.read/create/update`) both exist — use field-level to hide/lock individual fields.
+- **Never bypass access control in custom endpoints.** Use `req` context; don't hand-roll DB calls that skip it.
+- The Local API can run with `overrideAccess: true` for trusted server code — use deliberately, not by default.
+
+Full patterns (RBAC, multi-tenant isolation, field-level): `references/access-control.md`.
+
+---
+
+## Hooks — lifecycle entry points
+
+```typescript
+hooks: {
+  beforeChange: [({ data, req, operation }) => { /* mutate before save */ return data }],
+  afterChange:  [({ doc, req, operation }) => { /* side effects: revalidate, notify */ return doc }],
+  beforeRead:   [/* ... */],
+  afterRead:    [/* shape outgoing doc */],
+  beforeDelete: [/* ... */],
+  afterDelete:  [/* cleanup */],
+}
+```
+
+Common use: in `afterChange`, call Next.js `revalidatePath()` / `revalidateTag()` to bust the front-end cache on publish. Full hook catalog (collection, field, global, auth hooks): `references/hooks-and-fields.md`.
+
+---
+
+## Local API (the Next.js superpower)
+
+In server components / route handlers, fetch data in-process — no HTTP round trip, fully typed:
+
+```typescript
+import { getPayload } from 'payload'
+import config from '@payload-config'
+
+const payload = await getPayload({ config })
+
+const { docs } = await payload.find({
+  collection: 'posts',
+  where: { status: { equals: 'published' } },
+  depth: 1,             // auto-populate relationships one level deep
+  limit: 10,
+})
+```
+
+`payload.find / findByID / create / update / delete / findGlobal` mirror the REST surface. Access control still applies unless `overrideAccess: true`.
+
+### Caching in Next.js
+
+- Wrap Local API reads in `unstable_cache` (or `cache`) with tags, then invalidate from an `afterChange` hook via `revalidateTag`.
+- `depth` controls relationship population — keep it low to avoid over-fetching.
+
+---
+
+## Decision tables
+
+### Database adapter
+
+| Choice | Pick when |
+|--------|-----------|
+| **Postgres** (`db-postgres`) | Relational data, SQL reporting, Vercel Postgres/Neon/Supabase; migrations matter |
+| **MongoDB** (`db-mongodb`) | Document-shaped data, flexible schema, existing Mongo infra |
+| **SQLite** (`db-sqlite`) | Local/edge, small footprint, simple deploys |
+
+### Storage adapter
+
+| Choice | Pick when |
+|--------|-----------|
+| Local disk | Dev only — not for serverless (ephemeral FS) |
+| S3 / R2 (`@payloadcms/storage-s3`) | Production; put a CDN (CloudFront/Cloudflare) in front; signed URLs for private media; handle 403 on the frontend |
+
+### Multi-tenancy
+
+| Approach | Pick when |
+|----------|-----------|
+| `@payloadcms/plugin-multi-tenant` | Standard tenant isolation by a tenant field |
+| Custom access constraints | Bespoke isolation rules; enforce via row-level `read`/`update` constraints |
+
+---
+
+## Common gotchas
+
+| Gotcha | Why | Fix |
+|--------|-----|-----|
+| Users see data they shouldn't | `read` access returns `true` (no row filter) | Return a **query constraint** from `read`, not just `true` |
+| Local disk uploads vanish on Vercel | Serverless FS is ephemeral | Use S3/R2 storage adapter |
+| Stale front-end after publish | Next.js caches the read | `revalidateTag/Path` in an `afterChange` hook |
+| S3 signed URL 403s on frontend | URLs expire | Handle 403 gracefully; refresh URL |
+| Over-deep relationship fetch | High `depth` populates everything | Keep `depth` minimal; populate explicitly |
+| Custom endpoint leaks data | Bypassed access control | Go through Local API with access on; reserve `overrideAccess` for trusted paths |
+| Env not validated | Misconfig fails at runtime | Validate env (zod) at boot |
+| No real-time collab | Payload has no built-in CRDT | Pair with Liveblocks/Yjs; Payload stays source of truth for final state |
+
+---
+
+## Assets
+
+| File | Use |
+|------|-----|
+| `assets/collection.config.template.ts` | Heavily commented Payload 3 CollectionConfig starter (access + hooks + fields + upload), with adapt-points marked |
+
+---
+
+## See also
+
+- `typescript-ops` — typing config, generated types (`payload generate:types`)
+- `react-ops` — custom admin components, server components consuming the Local API
+- `api-design-ops` — REST/GraphQL surface design, pagination, versioning
+- `auth-ops` — auth collections, sessions/JWT, RBAC/ABAC patterns behind access control
+
+### Key external resources
+
+- [What is Payload](https://payloadcms.com/docs/getting-started/what-is-payload)
+- [Collections](https://payloadcms.com/docs/configuration/collections) · [Fields](https://payloadcms.com/docs/fields/overview)
+- [Access control](https://payloadcms.com/docs/access-control/overview)
+- [Hooks](https://payloadcms.com/docs/hooks/overview)
+- [Local API](https://payloadcms.com/docs/local-api/overview)
+- [Database](https://payloadcms.com/docs/database/overview) · [Storage adapters](https://payloadcms.com/docs/upload/storage-adapters)
+- [Multi-tenant plugin](https://payloadcms.com/docs/plugins/multi-tenant)

+ 89 - 0
skills/payloadcms-ops/assets/collection.config.template.ts

@@ -0,0 +1,89 @@
+/**
+ * Payload 3 CollectionConfig starter — copy into src/collections/<Name>.ts and adapt.
+ *
+ * ADAPT-POINTS are marked with  // ADAPT:
+ * Register the export in src/payload.config.ts under `collections: [ ... ]`.
+ * After editing schema, run `payload generate:types`.
+ *
+ * Verified against payloadcms.com/docs (Payload 3.x, Next.js-native). 2026-06.
+ */
+import type { CollectionConfig } from 'payload'
+
+export const Posts: CollectionConfig = {
+  // ADAPT: URL-safe identifier; also the REST/GraphQL route base + relationTo target.
+  slug: 'posts',
+
+  admin: {
+    useAsTitle: 'title', // ADAPT: which field labels rows in the admin list
+    defaultColumns: ['title', 'status', 'updatedAt'],
+    group: 'Content', // optional sidebar grouping
+  },
+
+  // Draft/publish + revision history. Remove if you don't need drafts.
+  versions: { drafts: true },
+
+  /**
+   * Access control runs uniformly across Local API, REST, and GraphQL.
+   * Return a boolean OR a `where` query constraint (row-level filtering).
+   * A `read` returning `true` exposes EVERY row — return a constraint to scope.
+   */
+  access: {
+    read: ({ req }) => {
+      if (req.user?.role === 'admin') return true
+      if (req.user) return { author: { equals: req.user.id } } // ADAPT: ownership rule
+      return { _status: { equals: 'published' } } // anon: published only
+    },
+    create: ({ req }) => Boolean(req.user),
+    update: ({ req }) =>
+      req.user?.role === 'admin' ? true : { author: { equals: req.user?.id } },
+    delete: ({ req }) => req.user?.role === 'admin', // ADAPT: who may delete
+  },
+
+  hooks: {
+    beforeChange: [
+      ({ data, req, operation }) => {
+        if (operation === 'create' && req.user) data.author = req.user.id
+        return data
+      },
+    ],
+    afterChange: [
+      async ({ doc }) => {
+        // Bust the Next.js front-end cache on publish/edit.
+        const { revalidateTag } = await import('next/cache')
+        revalidateTag('posts') // ADAPT: tag your front end reads with
+        return doc
+      },
+    ],
+  },
+
+  fields: [
+    { name: 'title', type: 'text', required: true },
+    {
+      name: 'slug',
+      type: 'text',
+      unique: true,
+      index: true,
+      hooks: {
+        beforeValidate: [
+          ({ value, data }) =>
+            value || data?.title?.toLowerCase().replace(/\s+/g, '-'),
+        ],
+      },
+    },
+    { name: 'content', type: 'richText' },
+    {
+      name: 'author',
+      type: 'relationship',
+      relationTo: 'users', // ADAPT: must match your auth collection slug
+    },
+    // Field-level access: lock a field independent of the document.
+    {
+      name: 'internalNotes',
+      type: 'textarea',
+      access: {
+        read: ({ req }) => req.user?.role === 'admin',
+        update: ({ req }) => req.user?.role === 'admin',
+      },
+    },
+  ],
+}

+ 99 - 0
skills/payloadcms-ops/references/access-control.md

@@ -0,0 +1,99 @@
+# Access Control — deep dive (Payload 3)
+
+Load when designing authorization: RBAC, multi-tenant isolation, or field-level locks.
+
+Access functions run uniformly across the Local API, REST, and GraphQL. They return either
+a `boolean` (can the user do this operation at all?) or a **query constraint** object
+(row-level: *which* documents). Returning a constraint from `read`/`update`/`delete` is the
+mechanism for per-tenant / per-owner data isolation — `true` alone means "all rows".
+
+## Operation-level (collection) access
+
+```typescript
+import type { CollectionConfig } from 'payload'
+
+export const Posts: CollectionConfig = {
+  slug: 'posts',
+  access: {
+    read:   ({ req }) => {
+      if (!req.user) return { status: { equals: 'published' } } // anon sees published only
+      if (req.user.role === 'admin') return true
+      return { author: { equals: req.user.id } }                // authors see their own
+    },
+    create: ({ req }) => Boolean(req.user),
+    update: ({ req }) => req.user?.role === 'admin'
+      ? true
+      : { author: { equals: req.user?.id } },
+    delete: ({ req }) => req.user?.role === 'admin',
+  },
+  fields: [/* ... */],
+}
+```
+
+| Access fn | Controls | Returns |
+|-----------|----------|---------|
+| `read` | Listing + reading docs | bool or `where` constraint |
+| `create` | New docs | bool |
+| `update` | Editing | bool or `where` constraint |
+| `delete` | Removal | bool or `where` constraint |
+| `admin` | Whether user can access the admin panel (auth collection) | bool |
+| `unlock`, `readVersions` | Auth/versioning specifics | bool |
+
+## Field-level access
+
+Lock or hide individual fields independent of the document:
+
+```typescript
+{
+  name: 'internalNotes',
+  type: 'textarea',
+  access: {
+    read:   ({ req }) => req.user?.role === 'admin',
+    update: ({ req }) => req.user?.role === 'admin',
+    create: ({ req }) => req.user?.role === 'admin',
+  },
+}
+```
+
+Field-level `read` false → field omitted from output. `update`/`create` false → field is
+read-only / cannot be set even if the document is writable.
+
+## RBAC pattern
+
+Store a `role` (or `roles` hasMany) on the Users (auth) collection, then branch in access
+functions. Centralize predicates so they're reused, not copy-pasted:
+
+```typescript
+// access/isAdmin.ts
+import type { Access } from 'payload'
+export const isAdmin: Access = ({ req }) => req.user?.role === 'admin'
+export const isAdminOrSelf: Access = ({ req }) =>
+  req.user?.role === 'admin' ? true : { author: { equals: req.user?.id } }
+```
+
+## Multi-tenant isolation
+
+Two routes:
+
+1. **`@payloadcms/plugin-multi-tenant`** — adds a tenant field + scoping automatically.
+   Prefer this for standard cases.
+2. **Custom constraints** — add a `tenant` relationship field, then enforce in every
+   collection's access:
+
+   ```typescript
+   read: ({ req }) => ({ tenant: { equals: req.user?.tenant } }),
+   ```
+
+   Apply the same constraint to `create` (force-set tenant in a `beforeChange` hook),
+   `update`, and `delete`. Test that a user from tenant A genuinely cannot read/modify
+   tenant B's rows — this is the #1 access bug.
+
+## Rules
+
+- **Never bypass access control in custom endpoints.** Route through the Local API with
+  access enabled. Reserve `overrideAccess: true` for trusted server-side jobs that
+  *intentionally* run as system.
+- A `read` that returns `true` exposes every row. If data should be scoped, return a
+  constraint, not a boolean.
+- Field access runs *in addition* to collection access — both must pass.
+- Access functions can be async (e.g. look up tenant membership) — return a Promise.

+ 136 - 0
skills/payloadcms-ops/references/hooks-and-fields.md

@@ -0,0 +1,136 @@
+# Hooks & Fields — deep dive (Payload 3)
+
+Load when wiring lifecycle side effects (cache revalidation, derived data, notifications)
+or composing non-trivial field structures (blocks, arrays, conditional/localized fields).
+
+## Hooks
+
+Hooks are arrays of functions run at lifecycle points. They exist at four levels:
+**collection**, **field**, **global**, and **auth**.
+
+### Collection hooks
+
+| Hook | Fires | Typical use |
+|------|-------|-------------|
+| `beforeValidate` | Before validation | Normalize/derive input |
+| `beforeChange` | Before create/update write | Mutate `data`, set derived fields |
+| `afterChange` | After write | Revalidate cache, send notifications, sync external |
+| `beforeRead` | Before a doc is read | Inject query context |
+| `afterRead` | After read, before return | Shape outgoing doc, computed fields |
+| `beforeDelete` / `afterDelete` | Around deletion | Cascade cleanup, remove files |
+| `afterOperation` | After any operation | Generic post-processing |
+
+```typescript
+hooks: {
+  beforeChange: [
+    ({ data, req, operation }) => {
+      if (operation === 'create') data.createdBy = req.user?.id
+      return data
+    },
+  ],
+  afterChange: [
+    async ({ doc, req }) => {
+      // bust Next.js cache for this content on publish
+      const { revalidateTag } = await import('next/cache')
+      revalidateTag(`posts`)
+      return doc
+    },
+  ],
+}
+```
+
+### Field hooks
+
+Same `beforeValidate / beforeChange / afterChange / afterRead` lifecycle but scoped to a
+single field — use for per-field derivation (e.g. auto-slug from title):
+
+```typescript
+{
+  name: 'slug',
+  type: 'text',
+  hooks: {
+    beforeValidate: [({ value, data }) =>
+      value || data?.title?.toLowerCase().replace(/\s+/g, '-')],
+  },
+}
+```
+
+### Auth hooks
+
+`beforeLogin`, `afterLogin`, `afterLogout`, `afterMe`, `afterRefresh`, `afterForgotPassword`
+— hook into the auth collection's session lifecycle.
+
+### Cache-invalidation pattern (the canonical Next.js use)
+
+1. Read with `unstable_cache(..., { tags: ['posts'] })` in the front end.
+2. In the collection's `afterChange` (and `afterDelete`), call `revalidateTag('posts')`.
+3. Publish/edit now busts exactly the affected cache entry.
+
+## Fields — composition patterns
+
+### Blocks (flexible content)
+
+```typescript
+{
+  name: 'layout',
+  type: 'blocks',
+  blocks: [
+    {
+      slug: 'hero',
+      fields: [
+        { name: 'heading', type: 'text' },
+        { name: 'image', type: 'upload', relationTo: 'media' },
+      ],
+    },
+    {
+      slug: 'richText',
+      fields: [{ name: 'content', type: 'richText' }],
+    },
+  ],
+}
+```
+
+Each row stores a `blockType`; branch on it when rendering. This is Payload's equivalent
+of flexible page builders.
+
+### Array fields
+
+```typescript
+{
+  name: 'features',
+  type: 'array',
+  minRows: 1,
+  fields: [
+    { name: 'label', type: 'text' },
+    { name: 'icon', type: 'text' },
+  ],
+}
+```
+
+### Conditional display
+
+```typescript
+{
+  name: 'externalUrl',
+  type: 'text',
+  admin: { condition: (data) => data.linkType === 'external' },
+}
+```
+
+### Localization (i18n)
+
+Set `localized: true` on any field; Payload stores per-locale values. Configure `locales`
+in `payload.config.ts`. Query a locale via Local API `locale` param.
+
+### Field access & validation
+
+Every field accepts:
+- `access: { read, create, update }` — field-level authorization (see access-control.md)
+- `validate: (value, { data, req }) => true | 'error message'`
+- `defaultValue`, `required`, `unique`, `index`
+- `hooks` — field-scoped lifecycle (above)
+
+## Generated types
+
+Run `payload generate:types` after schema changes to produce a typed `payload-types.ts`.
+Import the generated interfaces in front-end code so Local API results are fully typed.

+ 0 - 0
skills/payloadcms-ops/scripts/.gitkeep


+ 1 - 1
skills/spawn/SKILL.md

@@ -66,7 +66,7 @@ color: blue
 ```
 
 **YAML Frontmatter Fields:**
-- `name` (required): Unique identifier, lowercase-with-hyphens (e.g., "asus-router-expert")
+- `name` (required): Unique identifier, lowercase-with-hyphens (e.g., "redis-expert")
 - `description` (required): Clear, specific description of when to use this agent
   - No strict length limit - prioritize clarity over brevity
   - Can include examples, use cases, and context

+ 4 - 4
skills/tool-discovery/SKILL.md

@@ -52,6 +52,9 @@ Is this a reference/lookup task?
 | **javascript-ops** | javascript, node, esm, async/await, event loop |
 | **astro-ops** | astro, islands, content collections, partial hydration |
 | **laravel-ops** | laravel, eloquent, artisan, sanctum, pest |
+| **payloadcms-ops** | payload, payload cms, headless cms, collections |
+| **craftcms-ops** | craft, craftcms, twig, matrix fields |
+| **asus-router-ops** | asus router, asuswrt, merlin, network hardening |
 | **nginx-ops** | nginx, reverse proxy, ssl, load balancer, proxy_pass |
 | **cloudflare-ops** | cloudflare, workers, KV, D1, R2, pages, wrangler, edge |
 | **cypress-ops** | cypress, e2e, component testing, custom commands, stubbing |
@@ -71,15 +74,12 @@ Is this a reference/lookup task?
 | Agent | Triggers |
 |-------|----------|
 | **firecrawl-expert** | web scraping, crawling, anti-bot |
-| **payloadcms-expert** | payload, headless cms |
-| **craftcms-expert** | craft, craftcms, twig |
-| **asus-router-expert** | asus router, asuswrt, merlin |
 | **project-organizer** | restructure, organize, cleanup |
 | **git-agent** | commit, push, PR (dispatched by git-ops) |
 | **Explore** | "where is", "find" |
 | **Plan** | design, architect |
 
-For Cloudflare/Workers, Cypress/E2E, shell scripting, and Claude Code extension work, use the matching `-ops` skill (`cloudflare-ops`, `cypress-ops`, `bash-ops`, `claude-code-ops`). For language/framework work (Python, TypeScript, React, Postgres, etc.), use the matching `-ops` skill — or dispatch `general-purpose` with an instruction to read that skill's SKILL.md first.
+For Cloudflare/Workers, Cypress/E2E, shell scripting, Claude Code extension work, and CMS/device domains (Payload, Craft, Asus routers), use the matching `-ops` skill (`cloudflare-ops`, `cypress-ops`, `bash-ops`, `claude-code-ops`, `payloadcms-ops`, `craftcms-ops`, `asus-router-ops`). For language/framework work (Python, TypeScript, React, Postgres, etc.), use the matching `-ops` skill — or dispatch `general-purpose` with an instruction to read that skill's SKILL.md first.
 
 ## How to Launch
 

+ 3 - 55
skills/tool-discovery/references/agents-catalog.md

@@ -13,6 +13,9 @@ Complete reference for all available agents in the Task tool.
 | `bash-expert` | `bash-ops` skill |
 | `aws-fargate-ecs-expert` | `container-orchestration` skill |
 | `claude-architect` | `claude-code-ops` skill |
+| `craftcms-expert` | `craftcms-ops` skill |
+| `payloadcms-expert` | `payloadcms-ops` skill |
+| `asus-router-expert` | `asus-router-ops` skill |
 
 For these, invoke the skill directly, or dispatch `general-purpose` with an instruction to read the skill's SKILL.md first.
 
@@ -37,61 +40,6 @@ For these, invoke the skill directly, or dispatch `general-purpose` with an inst
 
 ---
 
-### payloadcms-expert
-
-**Triggers:** payload, payload cms, headless cms
-
-**Capabilities:**
-- Payload CMS architecture
-- Collection configuration
-- Access control design
-- Media handling
-- Multi-tenant setup
-
-**Best For:**
-- Payload project setup
-- Access control design
-- Schema planning
-- Integration patterns
-
----
-
-### craftcms-expert
-
-**Triggers:** craft, craftcms, twig
-
-**Capabilities:**
-- Craft CMS development
-- Twig templates
-- Plugin development
-- Matrix fields
-- GraphQL API
-
-**Best For:**
-- Craft CMS projects
-- Template development
-- Custom field types
-- Content modeling
-
----
-
-### asus-router-expert
-
-**Triggers:** asus router, asuswrt, merlin, network hardening
-
-**Capabilities:**
-- Asus router configuration
-- Asuswrt-Merlin firmware
-- Network hardening
-- VPN and firewall setup
-
-**Best For:**
-- Router configuration
-- Home network security
-- Firmware feature guidance
-
----
-
 ### git-agent
 
 **Triggers:** commit, push, PR, branch, rebase (dispatched by git-ops)