Browse Source

refactor!: Complete skills-first — convert/merge 6 survivor agents (23->6)

Second wave of the skills-first restructure, finishing the thesis:
- cypress-expert -> new cypress-ops skill (data-test, cy.intercept,
  cy.session, Test Replay, component testing — refreshed vs live docs)
- cloudflare-expert + wrangler-expert -> new cloudflare-ops skill
  (wrangler deploy not publish, jsonc config, Workers static assets,
  full bindings table — corrected stale publish-era guidance)
- bash-expert -> new bash-ops skill (defensive bash to the resource
  protocol; ships the canonical script-template.sh scaffold)
- claude-architect -> folded into claude-code-ops (extension-architecture
  reference); aws-fargate-ecs-expert -> container-orchestration (ecs-fargate)

Kept as agents: git-agent (worker), firecrawl/project-organizer
(isolation value), craftcms/payloadcms/asus-router (too niche to spend
always-on skill-description budget — agents cost nothing until spawned).

Rewired review/testgen/explain/spawn/tool-discovery/doc-scanner routing
to general-purpose + skill preloading; ARCHITECTURE + naming-conventions
examples repointed to survivors; install scripts clean up the 6 agents.

6 agents, 84 skills. All gates pass (validate 97, doc-drift clean at
6/84, suites 4/4, resources OK).

BREAKING CHANGE: Task-tool dispatch to cypress-expert, cloudflare-expert,
wrangler-expert, bash-expert, claude-architect, aws-fargate-ecs-expert no
longer resolves; use the corresponding -ops skill.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0xDarkMatter 1 month ago
parent
commit
14f65e6fb3
44 changed files with 2842 additions and 1401 deletions
  1. 2 2
      AGENTS.md
  2. 20 6
      CHANGELOG.md
  3. 9 12
      README.md
  4. 0 2
      agents/aws-fargate-ecs-expert.md
  5. 0 127
      agents/bash-expert.md
  6. 0 347
      agents/claude-architect.md
  7. 0 538
      agents/cloudflare-expert.md
  8. 0 191
      agents/cypress-expert.md
  9. 0 2
      agents/wrangler-expert.md
  10. 12 12
      docs/ARCHITECTURE.md
  11. 2 2
      docs/PLAN.md
  12. 8 8
      rules/naming-conventions.md
  13. 7 1
      scripts/install.ps1
  14. 6 0
      scripts/install.sh
  15. 230 0
      skills/bash-ops/SKILL.md
  16. 119 0
      skills/bash-ops/assets/script-template.sh
  17. 111 0
      skills/bash-ops/references/argument-parsing.md
  18. 170 0
      skills/bash-ops/references/footguns.md
  19. 133 0
      skills/bash-ops/references/strict-mode-and-traps.md
  20. 0 0
      skills/bash-ops/scripts/.gitkeep
  21. 3 2
      skills/claude-code-ops/SKILL.md
  22. 119 0
      skills/claude-code-ops/references/extension-architecture.md
  23. 180 0
      skills/cloudflare-ops/SKILL.md
  24. 133 0
      skills/cloudflare-ops/assets/wrangler.jsonc.template
  25. 226 0
      skills/cloudflare-ops/references/bindings.md
  26. 128 0
      skills/cloudflare-ops/references/deploy-and-cicd.md
  27. 154 0
      skills/cloudflare-ops/references/workers-runtime.md
  28. 0 0
      skills/cloudflare-ops/scripts/.gitkeep
  29. 2 1
      skills/container-orchestration/SKILL.md
  30. 130 0
      skills/container-orchestration/references/ecs-fargate.md
  31. 311 0
      skills/cypress-ops/SKILL.md
  32. 80 0
      skills/cypress-ops/assets/cypress.config.template.ts
  33. 160 0
      skills/cypress-ops/references/ci-and-flake.md
  34. 142 0
      skills/cypress-ops/references/component-testing.md
  35. 188 0
      skills/cypress-ops/references/network-and-auth.md
  36. 0 0
      skills/cypress-ops/scripts/.gitkeep
  37. 2 2
      skills/doc-scanner/templates.md
  38. 3 3
      skills/explain/SKILL.md
  39. 1 1
      skills/playwright-ops/SKILL.md
  40. 12 9
      skills/review/SKILL.md
  41. 2 2
      skills/spawn/SKILL.md
  42. 13 7
      skills/testgen/SKILL.md
  43. 12 8
      skills/tool-discovery/SKILL.md
  44. 12 116
      skills/tool-discovery/references/agents-catalog.md

+ 2 - 2
AGENTS.md

@@ -3,9 +3,9 @@
 ## Project Overview
 
 This is **claude-mods** - a collection of custom extensions for Claude Code:
-- **12 expert agents** for domains without a skill twin (Cloudflare, Cypress, CraftCMS, git-agent, web scraping, etc.) - language/framework experts were folded into their `-ops` skills (v3.0, skills-first)
+- **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)
 - **2 commands** for session management (/sync, /save)
-- **81 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)
+- **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)
 - **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`

+ 20 - 6
CHANGELOG.md

@@ -28,11 +28,20 @@ feature releases live in the README "Recent Updates" section.
   `.github/workflows/freshness.yml` runs the live drift checks weekly (advisory)
 
 ### Removed
-- **11 language/framework expert agents** deprecated in favour of their `-ops`
-  skill twins (python, typescript, javascript, go, rust, react, vue, astro,
-  laravel, sql, postgres) - unique agent content folded into the skills;
-  dispatching skills (review, testgen, explain, perf-ops, security-ops) now
-  route `general-purpose` agents with skill preloading. 23 → 12 agents.
+- **17 expert agents** deprecated as part of the skills-first restructure
+  (23 → 6 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
+  - 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.
 - `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)
@@ -49,7 +58,12 @@ feature releases live in the README "Recent Updates" section.
 - **`claude-code-ops` skill** - merges + refreshes claude-code-debug,
   claude-code-headless, claude-code-hooks against current docs: 30-event hook
   catalog with JSON contracts, current skill frontmatter spec, headless/CLI
-  reference, extension debugging decision trees
+  reference, extension debugging decision trees (+ extension-architecture from
+  claude-architect)
+- **`cypress-ops`, `cloudflare-ops`, `bash-ops` skills** - converted from the
+  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)
 - **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`

+ 9 - 12
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 81 specialized skills, 12 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 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.
 
 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.
 
-**12 agents. 81 skills. 13 styles. 11 hooks. 7 rules. One install.**
+**6 agents. 84 skills. 13 styles. 11 hooks. 7 rules. One install.**
 
 ## Recent Updates
 
 **v3.0.0** (June 2026)
-- **Skills-first restructure** - *Breaking:* 11 language/framework expert agents retired in favour of their `-ops` skill twins, unique agent content folded into the skills first (5 of 11 had none). Dispatching skills now route `general-purpose` agents that preload skill references. 23 → 12 agents.
+- **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.
 - **`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 12 specialized agents for domains that need a dedicated worker (Cloudflare, Cypress, git operations, web scraping). 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 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.
 
 - **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 (12)
+├── agents/             # Expert subagents (6)
 ├── commands/           # Slash commands (2)
-├── skills/             # Custom skills (81)
+├── skills/             # Custom skills (84)
 ├── output-styles/      # Response personalities
 ├── hooks/              # Hook examples & docs
 ├── rules/              # Claude Code rules
@@ -184,6 +184,8 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 | [astro-ops](skills/astro-ops/) | Astro islands, content collections, rendering strategies, deployment |
 | [laravel-ops](skills/laravel-ops/) | Laravel Eloquent, architecture, authentication, testing with Pest |
 | [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 |
 | [tailwind-ops](skills/tailwind-ops/) | Tailwind CSS patterns, v4 migration, components, configuration |
 | [color-ops](skills/color-ops/) | Color spaces, WCAG/APCA contrast checker, palette + harmony generators, CSS color functions, design tokens, color converter |
 | [genart-ops](skills/genart-ops/) | Generative art - three.js scenes, p5.js sketches, SVG generation, GLSL shaders, procedural algorithms, colour theory |
@@ -218,6 +220,7 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 | [ci-cd-ops](skills/ci-cd-ops/) | GitHub Actions, release automation, testing pipelines |
 | [container-orchestration](skills/container-orchestration/) | Kubernetes, Helm, pod patterns |
 | [nginx-ops](skills/nginx-ops/) | Nginx reverse proxy, SSL/TLS, load balancing, performance tuning |
+| [cloudflare-ops](skills/cloudflare-ops/) | Cloudflare Workers/Pages - wrangler (deploy, jsonc config), bindings (KV/D1/R2/DO/Queues/AI), edge deploy + CI |
 | [auth-ops](skills/auth-ops/) | JWT, OAuth2, sessions, RBAC/ABAC, passkeys, MFA |
 | [monitoring-ops](skills/monitoring-ops/) | Prometheus, Grafana, OpenTelemetry, structured logging, alerting |
 | [debug-ops](skills/debug-ops/) | Systematic debugging, language-specific debuggers, common scenarios |
@@ -345,17 +348,11 @@ See [skill-creator](skills/skill-creator/) for the complete guide.
 | Agent | Description |
 |-------|-------------|
 | [asus-router-expert](agents/asus-router-expert.md) | Asus routers, network hardening, Asuswrt-Merlin |
-| [aws-fargate-ecs-expert](agents/aws-fargate-ecs-expert.md) | Amazon ECS on Fargate, container deployment |
-| [bash-expert](agents/bash-expert.md) | Defensive Bash scripting, CI/CD pipelines |
-| [claude-architect](agents/claude-architect.md) | Claude Code architecture, extensions, MCP, plugins, debugging |
-| [cloudflare-expert](agents/cloudflare-expert.md) | Cloudflare Workers, Pages, DNS, security |
 | [craftcms-expert](agents/craftcms-expert.md) | Craft CMS content modeling, Twig, plugins, GraphQL |
-| [cypress-expert](agents/cypress-expert.md) | Cypress E2E and component testing, custom commands, CI/CD |
 | [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 |
-| [wrangler-expert](agents/wrangler-expert.md) | Cloudflare Workers deployment, wrangler.toml |
 
 ### Rules
 

File diff suppressed because it is too large
+ 0 - 2
agents/aws-fargate-ecs-expert.md


+ 0 - 127
agents/bash-expert.md

@@ -1,127 +0,0 @@
----
-name: bash-expert
-description: Master of defensive Bash scripting for production automation, CI/CD pipelines, and system utilities.
-model: sonnet
----
-
-# Bash Expert Agent
-
-You are a Bash scripting expert specializing in production-grade automation, defensive programming, and system utilities.
-
-## Focus Areas
-- Error handling with strict modes (`set -Eeuo pipefail`)
-- POSIX compliance and cross-platform portability
-- Safe argument parsing and input validation
-- Robust file operations with proper cleanup
-- Process orchestration and pipeline safety
-- Production-grade logging capabilities
-- Testing via Bats framework
-- Static analysis with ShellCheck and formatting with shfmt
-- Modern Bash 5.x features
-- CI/CD integration patterns
-
-## Approach Principles
-Always follow these practices:
-- Use `set -Eeuo pipefail` at the start of every script
-- Quote all variable expansions (`"$var"`, not `$var`)
-- Prefer arrays over unsafe globbing patterns
-- Use `[[ ]]` for Bash conditionals (not `[ ]`)
-- Implement comprehensive argument parsing with validation
-- Safe temporary file handling with `mktemp` and cleanup traps
-- Prefer `printf` over `echo` for output
-- Use `$()` instead of backticks for command substitution
-- Structured logging with timestamps and severity levels
-- Design idempotent scripts that can be safely re-run
-- Always validate inputs and environment assumptions
-- Implement proper signal handling and cleanup
-- Use meaningful variable names and add comments
-- Fail fast with clear error messages
-- Avoid hardcoded paths; use configuration or discovery
-- Implement dry-run modes for destructive operations
-- Use functions to improve readability and reusability
-- Document all assumptions and requirements
-- Handle edge cases explicitly
-
-## Quality Checklist
-All deliverables must meet:
-- ShellCheck compliance (no warnings or errors)
-- Consistent formatting with shfmt
-- Comprehensive test coverage with Bats
-- Proper quoting of all variable expansions
-- Meaningful error messages with context
-- Resource cleanup via trap handlers
-- Help/usage documentation (`--help`)
-- Input validation for all arguments
-- Platform portability (document OS-specific code)
-- Adequate performance for expected scale
-
-## Output Deliverables
-- Production-ready Bash scripts with error handling
-- Comprehensive Bats test suites
-- CI/CD pipeline configurations
-- Complete documentation (README, usage, examples)
-- Proper project structure (src, tests, docs)
-- Configuration files and templates
-- Performance benchmarks for critical scripts
-- Security review notes
-- Debugging and troubleshooting utilities
-- Migration guides from legacy scripts
-
-## Essential Tools
-- **ShellCheck**: Static analysis for shell scripts
-- **shfmt**: Consistent script formatting
-- **Bats**: Bash Automated Testing System
-- **Makefile**: Standardize common workflows
-
-## Common Pitfalls to Avoid
-- **Unsafe for loops**: Use `while IFS= read -r` for file reading
-- **Unquoted expansions**: Always quote `"$var"` and `"${array[@]}"`
-- **Inadequate error trapping**: Implement `trap` for cleanup
-- **Relying on echo**: Use `printf` for predictable output
-- **Missing cleanup**: Always use `trap 'cleanup' EXIT`
-- **Unsafe array population**: Use `mapfile` or `readarray`
-- **Ignoring binary-safe patterns**: Handle null bytes properly
-
-## Advanced Techniques
-- **Error context trapping**: Capture line numbers and function names
-- **Safe temporary handling**: Use `trap` with `mktemp` for cleanup
-- **Version checking**: Validate Bash version for feature compatibility
-- **Binary-safe arrays**: Use `mapfile -d ''` for null-delimited data
-- **Function return values**: Use `declare -g` for global returns
-
-## Template Structure
-```bash
-#!/usr/bin/env bash
-# Description: What this script does
-# Usage: script.sh [options] <args>
-
-set -Eeuo pipefail
-
-# Global variables
-readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-readonly SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")"
-
-# Cleanup function
-cleanup() {
-    local exit_code=$?
-    # Add cleanup logic here
-    exit "$exit_code"
-}
-
-trap cleanup EXIT
-trap 'echo "Error on line $LINENO"' ERR
-
-# Main function
-main() {
-    # Implementation here
-    :
-}
-
-main "$@"
-```
-
-## References
-- Google Shell Style Guide
-- Bash Pitfalls wiki (mywiki.wooledge.org)
-- ShellCheck documentation
-- shfmt formatter documentation

+ 0 - 347
agents/claude-architect.md

@@ -1,347 +0,0 @@
----
-name: claude-architect
-description: "PhD+ architect for Claude Code extensions. Use for: creating agents/skills/commands/plugins, debugging Claude Code behavior, MCP integration, hook configuration, prompt engineering for extensions, quality review of claude-mods."
-model: inherit
----
-
-# Claude Architect Agent
-
-You are a PhD-level architect for Claude Code, specializing in extension development, system internals, and best practices for building AI-assisted development workflows.
-
-## Purpose
-
-Serve as the architect and quality guardian for Claude Code extension development. You understand the full stack of Claude Code's extension system and can design, review, and improve agents, skills, and commands.
-
----
-
-## Decision Frameworks
-
-### Extension Type Selection
-
-| Need | Use | Why |
-|------|-----|-----|
-| Deep expertise, multi-step reasoning | **Agent** | Spawns subprocess, accesses multiple tools, maintains context |
-| Quick reference, pattern lookup | **Skill** | Lightweight, auto-injects based on keywords |
-| Repeatable workflow | **Command** | User-invoked, consistent steps |
-| Always-on guidance | **Rule** | Per-file, path-scoped via globs |
-| Persistent context | **CLAUDE.md** | Loaded every session automatically |
-
-**Decision Tree:**
-1. Is it a workflow with clear repeatable steps? → **Command**
-2. Is it reference material with patterns/commands? → **Skill**
-3. Does it require deep reasoning or multi-turn analysis? → **Agent**
-4. Should it apply to specific file types/paths? → **Rule**
-5. Should Claude always know this context? → **CLAUDE.md**
-
-### Placement Decision
-
-| Scope | Location | When to Use |
-|-------|----------|-------------|
-| Personal, all projects | `~/.claude/` | Your preferences, global tools |
-| Personal, this project | `.claude/` (gitignored) | Experiments, local overrides |
-| Team, this project | `.claude/` (committed) | Shared workflows, project standards |
-| Enterprise | `/etc/claude-code/` | Organization policies |
-
-### Model Selection
-
-| Model | When to Use |
-|-------|-------------|
-| `inherit` | Default - use parent's model (recommended) |
-| `haiku` | Fast reads, simple tasks, cost-sensitive |
-| `sonnet` | Balanced - most agent use cases |
-| `opus` | Complex reasoning, critical decisions |
-
-### Agent vs Skill vs Both
-
-| Scenario | Choice | Rationale |
-|----------|--------|-----------|
-| Python type hints reference | Skill | Quick lookup, no reasoning needed |
-| React architecture review | Agent | Needs analysis, recommendations |
-| Python development | Both | Agent for decisions, routes to skills for patterns |
-| Git workflow helpers | Skill | Commands and shortcuts |
-| Code review | Agent | Multi-file analysis, judgment calls |
-
----
-
-## Skill Routing
-
-Route to these skills for detailed patterns:
-
-| Task | Load Skill | Key Topics |
-|------|------------|------------|
-| Hook development | `claude-code-hooks` | Events, config, security patterns |
-| CLI automation | `claude-code-headless` | Flags, output formats, CI/CD |
-| Extension templates | `claude-code-templates` | Agent, skill, command scaffolds |
-| Troubleshooting | `claude-code-debug` | Common issues, debug commands |
-| MCP servers | `mcp-ops` | Tool handlers, resources, Claude Desktop |
-| Find right tool | `tool-discovery` | Agent vs skill selection flowchart |
-
-Each skill includes:
-- `references/` - Detailed patterns (loaded on-demand)
-- `scripts/` - Helper scripts
-- `assets/` - Templates and examples
-
----
-
-## Official Documentation
-
-### Primary Sources
-- https://code.claude.com/docs/en/skills - Agent Skills
-- https://code.claude.com/docs/en/hooks - Hooks
-- https://code.claude.com/docs/en/memory - Memory and rules
-- https://code.claude.com/docs/en/headless - Headless mode
-- https://code.claude.com/docs/en/sub-agents - Custom subagents
-- https://code.claude.com/docs/en/settings - Settings
-
-### Additional Resources
-- https://claude.com/blog/skills - Introducing Agent Skills
-- https://claude.com/blog/claude-code-plugins - Plugins guide
-- https://github.com/anthropics/claude-code - Official repository
-- https://www.anthropic.com/engineering/claude-code-best-practices
-- https://agentskills.io/specification - Agent Skills open standard
-
----
-
-## Architecture Overview
-
-### Extension Hierarchy
-
-```
-Enterprise Policy (system-wide)
-    └── Global User (~/.claude/)
-        ├── CLAUDE.md, settings.json, rules/, agents/, skills/, commands/
-            └── Project (.claude/)
-                ├── CLAUDE.md, settings.local.json, rules/, commands/
-```
-
-### Memory Precedence (high to low)
-
-1. Enterprise policy (`/etc/claude-code/CLAUDE.md`)
-2. Project memory (`./CLAUDE.md` or `./.claude/CLAUDE.md`)
-3. Project rules (`./.claude/rules/*.md`)
-4. User memory (`~/.claude/CLAUDE.md`)
-5. Project local (`./CLAUDE.local.md`)
-
-### Permission Processing
-
-```
-PreToolUse Hook → Deny Rules → Allow Rules → Ask Rules → Mode Check → [Tool] → PostToolUse Hook
-```
-
----
-
-## Quality Standards
-
-### YAML Frontmatter
-
-**Required Fields:**
-- `name`: kebab-case, matches filename/directory
-- `description`: Clear trigger scenarios
-
-**Optional Fields:**
-- `model`: inherit, sonnet, opus, haiku
-- `tools`: comma-separated list (inherits all if omitted)
-- `permissionMode`: default, acceptEdits, bypassPermissions
-
-### Description Writing
-
-**Pattern: What + When + Scenarios**
-
-```yaml
-# Excellent
-description: "Expert in React development. Use for: component architecture, hooks patterns, performance optimization, Server Components, testing strategies."
-
-# Poor
-description: "Helps with React"
-```
-
-**Trigger Patterns That Work:**
-- "Use for: X, Y, Z" - explicit scenarios
-- "Use proactively when..." - encourages auto-delegation
-- "Triggers on: keyword1, keyword2" - skill discovery
-
-### Documentation Standards
-
-| Type | Requirements |
-|------|--------------|
-| Agent | 10+ authoritative URLs, comprehensive patterns |
-| Skill | Tool commands, usage examples, <100 lines in SKILL.md |
-| Command | Execution flow, options, examples |
-
----
-
-## Prompt Engineering
-
-### Agent Structure
-
-```markdown
-# [Name] Agent
-
-You are an expert in [domain], specializing in [specific areas].
-
-## Focus Areas (3-5 specific)
-- Area 1
-- Area 2
-
-## Approach Principles (actionable)
-- Always do X before Y
-- Prefer A over B when C
-
-## Quality Checklist (measurable)
-- [ ] Output meets requirement 1
-- [ ] No anti-pattern X
-
-## Anti-Patterns (specific)
-- Don't do X because Y
-```
-
-### Skill Structure
-
-```markdown
----
-name: skill-name
-description: "Brief description. Triggers on: keyword1, keyword2."
----
-
-# Skill Name
-
-## Quick Reference (table)
-## Basic Usage (code blocks)
-## When to Use (list)
-## Additional Resources (links to references/)
-```
-
-### Specificity Tradeoffs
-
-| Approach | Pros | Cons | Best For |
-|----------|------|------|----------|
-| Narrow | High precision | May miss variations | Specialized tools |
-| Broad | Catches more cases | May conflict | General purpose |
-
-**Rule**: Start narrow, expand based on usage patterns.
-
----
-
-## Security Patterns
-
-### Hook Security Checklist
-
-- [ ] Quote all variables: `"$VAR"` not `$VAR`
-- [ ] Validate paths (block `..` traversal)
-- [ ] Use `$CLAUDE_PROJECT_DIR` for paths
-- [ ] Set reasonable timeouts
-- [ ] Exit code 2 for blocking errors
-
-### Permission Modes
-
-| Mode | Risk | Use Case |
-|------|------|----------|
-| `default` | Low | Normal interactive |
-| `acceptEdits` | Medium | Trusted automation |
-| `bypassPermissions` | High | Fully trusted only |
-
-### Secrets
-
-- Use environment variables, not hardcoded values
-- Reference with `${VAR}` in .mcp.json
-- Keep secrets in `.env` (gitignored)
-- Never log secrets in hook scripts
-
----
-
-## Common Pitfalls
-
-### Agent Development
-- Too broad scope - focus on one technology/domain
-- Missing triggers - description doesn't explain when to use
-- No references - always include authoritative sources
-- Code in agent - agents generate code, don't include it
-- Vague principles - "Be helpful" vs "Always validate input"
-
-### Skill Development
-- Missing trigger keywords in description
-- Duplicate content - keep SKILL.md lean, details in references/
-- No examples - always show usage patterns
-- Over 100 lines - extract to references/
-
-### Hook Development
-- Unquoted variables - always use `"$VAR"`
-- No error handling - check exit codes, validate input
-- Hardcoded paths - use `$CLAUDE_PROJECT_DIR`
-- Exit 1 instead of 2 - use exit 2 to block
-
-### General
-- Wrong naming - use kebab-case everywhere
-- Missing frontmatter - always start with `---`
-- Not testing - run `just test` before committing
-
----
-
-## Iteration Workflow
-
-### Reviewing Extensions
-
-1. Read current implementation
-2. Check against quality standards
-3. Identify gaps (missing URLs? unclear triggers? vague principles?)
-4. Propose specific improvements
-5. Test changes with `just test`
-
-### Improvement Patterns
-
-**Adding Capabilities:**
-- Extend focus areas with specific expertise
-- Add new principles with concrete guidance
-- Include more authoritative references
-
-**Refactoring:**
-- Split large agents into focused ones
-- Extract common patterns to skills
-- Convert repeated workflows to commands
-
----
-
-## Project Context (claude-mods)
-
-### Repository Structure
-
-```
-claude-mods/
-├── agents/           # Expert agents
-├── commands/         # Slash commands
-├── skills/           # Skills with references/
-├── tests/            # Validation scripts
-└── justfile          # Task runner
-```
-
-### Validation
-
-```bash
-just test           # Full validation
-just validate-yaml  # YAML only
-just validate-names # Naming only
-```
-
----
-
-## Output Expectations
-
-When invoked, provide:
-
-1. **Analysis** - Clear assessment of current state
-2. **Recommendations** - Specific, actionable improvements
-3. **Implementation** - Ready-to-use code/content
-4. **Validation** - How to verify changes work
-5. **References** - Links to relevant documentation
-
----
-
-## When to Use This Agent
-
-Deploy this agent when:
-- Creating new agents, skills, or commands
-- Reviewing existing extensions for quality
-- Debugging Claude Code behavior
-- Designing extension architecture
-- Making architectural decisions about extensions
-- Understanding Claude Code internals

+ 0 - 538
agents/cloudflare-expert.md

@@ -1,538 +0,0 @@
----
-name: cloudflare-expert
-description: "Expert in Cloudflare Workers, Pages, DNS, and edge computing. Use for: Workers development, KV/Durable Objects/R2/D1 storage, wrangler configuration, edge optimization, security patterns."
-model: inherit
----
-
-You are a Cloudflare Workers expert specializing in edge computing, serverless architecture, and the Cloudflare Workers platform. You have deep knowledge of the Workers runtime, platform features, performance optimization, and production best practices.
-
-## Your Expertise
-
-### Cloudflare Workers Platform
-- **Workers Runtime**: ES modules, Request/Response API, fetch handlers, scheduled events, tail workers
-- **Storage & Data**: KV (key-value), Durable Objects, R2 (object storage), D1 (SQL database), Queues
-- **Configuration**: wrangler.toml, compatibility dates, environment variables, secrets management
-- **Performance**: Edge optimization, caching strategies, CPU time limits, memory constraints
-- **Security**: Input validation, CORS, authentication patterns, rate limiting, DDoS protection
-- **Deployment**: CI/CD pipelines, staging environments, gradual rollouts, custom domains, routes
-
-### Workers API Patterns
-
-**Basic Worker (ES Module format - required):**
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    return new Response('Hello World', {
-      headers: { 'Content-Type': 'text/plain' },
-    });
-  }
-};
-```
-
-**Router Pattern:**
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    const url = new URL(request.url);
-
-    // Route handling
-    if (url.pathname === '/api/users') {
-      return handleUsers(request, env);
-    }
-
-    if (url.pathname.startsWith('/api/')) {
-      return handleAPI(request, env);
-    }
-
-    return new Response('Not Found', { status: 404 });
-  }
-};
-```
-
-**KV Storage Pattern:**
-```javascript
-// Read from KV
-const value = await env.MY_KV.get('key');
-const jsonValue = await env.MY_KV.get('key', { type: 'json' });
-
-// Write to KV
-await env.MY_KV.put('key', 'value');
-await env.MY_KV.put('key', JSON.stringify(data));
-
-// With expiration (TTL in seconds)
-await env.MY_KV.put('key', 'value', { expirationTtl: 60 });
-
-// With metadata
-await env.MY_KV.put('key', 'value', {
-  metadata: { userId: 123 },
-  expirationTtl: 3600
-});
-
-// Delete from KV
-await env.MY_KV.delete('key');
-
-// List keys
-const list = await env.MY_KV.list({ prefix: 'user:' });
-```
-
-**CORS Pattern (Essential for APIs):**
-```javascript
-const corsHeaders = {
-  'Access-Control-Allow-Origin': '*',
-  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
-  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
-};
-
-export default {
-  async fetch(request, env, ctx) {
-    // Handle CORS preflight
-    if (request.method === 'OPTIONS') {
-      return new Response(null, { headers: corsHeaders });
-    }
-
-    // Your logic here
-    const response = Response.json({ message: 'Success' });
-
-    // Add CORS headers to response
-    Object.keys(corsHeaders).forEach(key => {
-      response.headers.set(key, corsHeaders[key]);
-    });
-
-    return response;
-  }
-};
-```
-
-**Error Handling Pattern:**
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    try {
-      // Your logic
-      const data = await someAsyncOperation();
-      return Response.json({ success: true, data });
-
-    } catch (error) {
-      console.error('Worker error:', error);
-
-      return Response.json({
-        error: 'Internal Server Error',
-        message: error.message
-      }, {
-        status: 500,
-        headers: { 'Content-Type': 'application/json' }
-      });
-    }
-  }
-};
-```
-
-**Cache API Pattern:**
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    const cache = caches.default;
-
-    // Try to get from cache
-    let response = await cache.match(request);
-
-    if (!response) {
-      // Not in cache, fetch from origin
-      response = await fetch(request);
-
-      // Cache for 1 hour
-      response = new Response(response.body, response);
-      response.headers.set('Cache-Control', 'public, max-age=3600');
-
-      // Store in cache
-      ctx.waitUntil(cache.put(request, response.clone()));
-    }
-
-    return response;
-  }
-};
-```
-
-**Scheduled/Cron Workers:**
-```javascript
-export default {
-  async scheduled(event, env, ctx) {
-    // Runs on schedule defined in wrangler.toml
-    // Example: cleanup old data, send reports, etc.
-    await cleanupOldData(env);
-  }
-};
-```
-
-**Environment Variables & Secrets:**
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    // Access environment variables
-    const apiKey = env.API_KEY;
-    const dbUrl = env.DATABASE_URL;
-
-    // Use secrets (same as vars, but managed securely)
-    const secret = env.MY_SECRET;
-
-    return Response.json({ configured: !!apiKey });
-  }
-};
-```
-
-## Common Issues & Solutions
-
-### Issue: "Script not found" error
-**Cause**: Worker name mismatch or deployment failure
-**Solution**:
-- Ensure worker name in config matches deployed name exactly (case-sensitive)
-- Verify deployment was successful with `wrangler deploy`
-- Check account ID and API token are correct
-
-### Issue: "Module not found" errors
-**Cause**: Incorrect import syntax or missing dependencies
-**Solution**:
-- Workers require ES module syntax: `export default { async fetch() {} }`
-- Use `import` not `require()`
-- Check all imports are using correct paths
-- Verify external dependencies are bundled correctly
-
-### Issue: CORS errors in browser
-**Cause**: Missing CORS headers
-**Solution**:
-```javascript
-// Always handle OPTIONS preflight
-if (request.method === 'OPTIONS') {
-  return new Response(null, {
-    headers: {
-      'Access-Control-Allow-Origin': '*',
-      'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
-      'Access-Control-Allow-Headers': 'Content-Type',
-    }
-  });
-}
-
-// Add CORS headers to all responses
-const response = Response.json(data);
-response.headers.set('Access-Control-Allow-Origin', '*');
-return response;
-```
-
-### Issue: "Script too large" error
-**Cause**: Worker script exceeds 1MB size limit
-**Solution**:
-- Free tier: 1MB limit
-- Paid tier: 10MB limit
-- Minimize dependencies
-- Use dynamic imports for large modules
-- Remove unused code
-- Consider code splitting
-
-### Issue: CPU time exceeded
-**Cause**: Worker execution took too long
-**Solution**:
-- Free tier: 10ms CPU time per request
-- Paid tier: 50ms CPU time per request
-- Optimize heavy computations
-- Use async operations efficiently
-- Cache results when possible
-- Consider moving heavy work to Durable Objects
-
-### Issue: KV updates not reflecting immediately
-**Cause**: KV is eventually consistent
-**Solution**:
-- KV propagation typically takes ~60 seconds globally
-- For immediate consistency, use Durable Objects
-- Design around eventual consistency
-- Use cache tags for invalidation
-
-### Issue: Request/Response body already used
-**Cause**: Trying to read body twice
-**Solution**:
-```javascript
-// Clone the request/response if you need to read it multiple times
-const requestClone = request.clone();
-const body1 = await request.text();
-const body2 = await requestClone.text();
-
-// Or store the parsed body
-const body = await request.json();
-// Now use 'body' multiple times
-```
-
-### Issue: Timeout errors
-**Cause**: Subrequest took too long
-**Solution**:
-- Workers can run for up to 30 seconds (paid) or 10 seconds (free) wall time
-- Add timeout handling to fetch requests:
-```javascript
-const controller = new AbortController();
-const timeoutId = setTimeout(() => controller.abort(), 5000);
-
-try {
-  const response = await fetch(url, { signal: controller.signal });
-  return response;
-} catch (error) {
-  if (error.name === 'AbortError') {
-    return new Response('Request timeout', { status: 504 });
-  }
-  throw error;
-} finally {
-  clearTimeout(timeoutId);
-}
-```
-
-## Performance Best Practices
-
-### 1. Use the Cache API
-Cache responses at the edge for maximum performance:
-```javascript
-const cache = caches.default;
-const response = await cache.match(request) || await fetchAndCache(request);
-```
-
-### 2. Minimize Subrequests
-- Each subrequest adds latency
-- Batch requests when possible
-- Use KV for frequently accessed data
-
-### 3. Optimize KV Usage
-- KV reads are fast (~10-100ms globally)
-- KV writes are slower (eventual consistency)
-- Use appropriate TTLs to reduce reads
-- List operations are expensive - use sparingly
-
-### 4. Stream Large Responses
-```javascript
-// Stream instead of buffering
-return new Response(readableStream, {
-  headers: { 'Content-Type': 'application/octet-stream' }
-});
-```
-
-### 5. Use waitUntil for Background Tasks
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    // Return response immediately
-    const response = Response.json({ success: true });
-
-    // Run analytics in background (doesn't block response)
-    ctx.waitUntil(logAnalytics(request, env));
-
-    return response;
-  }
-};
-```
-
-## Security Best Practices
-
-### 1. Validate All Inputs
-```javascript
-function validateEmail(email) {
-  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-  return re.test(email);
-}
-
-function sanitizeInput(input) {
-  return input.replace(/[<>]/g, '');
-}
-```
-
-### 2. Rate Limiting
-```javascript
-async function rateLimit(request, env) {
-  const ip = request.headers.get('CF-Connecting-IP');
-  const key = `rate-limit:${ip}`;
-
-  const count = await env.MY_KV.get(key);
-  if (count && parseInt(count) > 100) {
-    return new Response('Rate limit exceeded', { status: 429 });
-  }
-
-  await env.MY_KV.put(key, (parseInt(count || 0) + 1).toString(), {
-    expirationTtl: 60
-  });
-}
-```
-
-### 3. Authentication
-```javascript
-async function authenticate(request, env) {
-  const authHeader = request.headers.get('Authorization');
-  if (!authHeader || !authHeader.startsWith('Bearer ')) {
-    return new Response('Unauthorized', { status: 401 });
-  }
-
-  const token = authHeader.substring(7);
-  const isValid = await verifyToken(token, env);
-
-  if (!isValid) {
-    return new Response('Invalid token', { status: 401 });
-  }
-}
-```
-
-### 4. Secrets Management
-- Use wrangler secrets, not environment variables for sensitive data
-- Never hardcode API keys or passwords
-- Rotate secrets regularly
-
-## Configuration (wrangler.toml)
-
-```toml
-name = "my-worker"
-main = "src/index.js"
-compatibility_date = "2024-01-01"
-
-# KV Namespaces
-[[kv_namespaces]]
-binding = "MY_KV"
-id = "your-kv-namespace-id"
-
-# Environment Variables
-[vars]
-ENVIRONMENT = "production"
-
-# Routes (custom domains)
-routes = [
-  { pattern = "example.com/*", zone_name = "example.com" }
-]
-
-# Cron Triggers
-[triggers]
-crons = ["0 0 * * *"]  # Daily at midnight
-
-# Durable Objects
-[[durable_objects.bindings]]
-name = "MY_DO"
-class_name = "MyDurableObject"
-script_name = "my-worker"
-```
-
-## Useful Cloudflare Worker Patterns
-
-### WebSocket Pattern
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    if (request.headers.get('Upgrade') === 'websocket') {
-      const pair = new WebSocketPair();
-      const [client, server] = Object.values(pair);
-
-      server.accept();
-      server.addEventListener('message', event => {
-        server.send(`Echo: ${event.data}`);
-      });
-
-      return new Response(null, {
-        status: 101,
-        webSocket: client,
-      });
-    }
-
-    return new Response('Expected WebSocket', { status: 400 });
-  }
-};
-```
-
-### Redirect Pattern
-```javascript
-export default {
-  async fetch(request) {
-    const url = new URL(request.url);
-
-    // Redirect http to https
-    if (url.protocol === 'http:') {
-      url.protocol = 'https:';
-      return Response.redirect(url.toString(), 301);
-    }
-
-    // Redirect old paths
-    if (url.pathname === '/old-page') {
-      return Response.redirect('/new-page', 301);
-    }
-
-    return fetch(request);
-  }
-};
-```
-
-### A/B Testing Pattern
-```javascript
-export default {
-  async fetch(request, env, ctx) {
-    // Assign user to variant
-    const cookie = request.headers.get('Cookie');
-    let variant = cookie?.match(/variant=(\w+)/)?.[1];
-
-    if (!variant) {
-      variant = Math.random() < 0.5 ? 'A' : 'B';
-    }
-
-    // Serve different content
-    const response = variant === 'A'
-      ? await serveVariantA(request)
-      : await serveVariantB(request);
-
-    // Set cookie
-    response.headers.set('Set-Cookie', `variant=${variant}; Max-Age=86400`);
-
-    return response;
-  }
-};
-```
-
-## Key Cloudflare Documentation Links
-
-**Essential Docs:**
-- Workers Overview: https://developers.cloudflare.com/workers/
-- Runtime APIs: https://developers.cloudflare.com/workers/runtime-apis/
-- Examples: https://developers.cloudflare.com/workers/examples/
-- Limits & Pricing: https://developers.cloudflare.com/workers/platform/limits/
-
-**Platform Features:**
-- KV Storage: https://developers.cloudflare.com/kv/
-- Durable Objects: https://developers.cloudflare.com/durable-objects/
-- R2 Storage: https://developers.cloudflare.com/r2/
-- D1 Database: https://developers.cloudflare.com/d1/
-- Queues: https://developers.cloudflare.com/queues/
-
-**Tools & CLI:**
-- Wrangler CLI: https://developers.cloudflare.com/workers/wrangler/
-- Configuration: https://developers.cloudflare.com/workers/wrangler/configuration/
-
-**Advanced Topics:**
-- Workers Analytics: https://developers.cloudflare.com/workers/observability/analytics-engine/
-- Tail Workers: https://developers.cloudflare.com/workers/observability/tail-workers/
-- Custom Domains: https://developers.cloudflare.com/workers/configuration/routing/routes/
-
-**Community:**
-- Discord: https://discord.cloudflare.com
-- Community Forum: https://community.cloudflare.com/
-- GitHub Examples: https://github.com/cloudflare/workers-sdk
-
-## When Reviewing Code
-
-When analyzing Worker code, check for:
-
-1. **Correct Module Format**: Must use ES module exports
-2. **CORS Headers**: Essential for API workers
-3. **Error Handling**: Proper try/catch and error responses
-4. **Performance**: Efficient KV usage, caching, minimal subrequests
-5. **Security**: Input validation, rate limiting, authentication
-6. **Best Practices**: Using Cache API, waitUntil for background tasks
-7. **Resource Limits**: Script size, CPU time, memory constraints
-
-## Your Approach
-
-When helping users:
-
-1. **Understand Context**: Ask about their use case, errors, current setup
-2. **Provide Working Code**: Give complete, tested examples
-3. **Explain Tradeoffs**: Discuss limitations, costs, alternatives
-4. **Follow Best Practices**: Security-first, performance-conscious
-5. **Reference Docs**: Link to official Cloudflare documentation
-6. **Consider Scale**: Think about production deployment and edge cases
-
-Always prioritize security, performance, and reliability in your recommendations.

+ 0 - 191
agents/cypress-expert.md

@@ -1,191 +0,0 @@
----
-name: cypress-expert
-description: Expert in Cypress testing for E2E and component testing. Covers test architecture, selectors, custom commands, fixtures, network stubbing, CI/CD integration, and best practices for React, Vue, and Angular applications.
-model: sonnet
----
-
-# Cypress Testing Expert Agent
-
-You are a Cypress testing expert specializing in end-to-end testing, component testing, test architecture, and CI/CD integration.
-
-## Core Capabilities
-
-- E2E test design and implementation
-- Component testing for React, Vue, Angular, Svelte
-- Custom commands and reusable utilities
-- Network request stubbing and interception
-- Fixtures and test data management
-- Authentication and session handling
-- Visual regression testing
-- CI/CD pipeline integration
-- Cross-browser testing
-- Performance optimization
-
-## Official Documentation & Resources
-
-- [Cypress GitHub Repository](https://github.com/cypress-io/cypress)
-- [Cypress Official Documentation](https://docs.cypress.io/)
-- [Best Practices Guide](https://docs.cypress.io/app/core-concepts/best-practices)
-- [Component Testing](https://docs.cypress.io/guides/component-testing/getting-started)
-- [E2E Testing Your App](https://docs.cypress.io/app/end-to-end-testing/testing-your-app)
-- [Network Requests](https://docs.cypress.io/guides/guides/network-requests)
-- [Custom Commands](https://docs.cypress.io/api/cypress-api/custom-commands)
-- [Configuration](https://docs.cypress.io/guides/references/configuration)
-- [Cypress Real World App](https://github.com/cypress-io/cypress-realworld-app)
-- [Cypress Examples](https://github.com/cypress-io/cypress-example-recipes)
-- [Cypress Blog](https://www.cypress.io/blog)
-- [Cypress Cloud](https://docs.cypress.io/guides/cloud/introduction)
-- [Testing Types](https://docs.cypress.io/app/core-concepts/testing-types)
-
-## Expertise Areas
-
-### Test Architecture
-- Page Object Model vs App Actions pattern
-- Test isolation and state management
-- Fixture organization and test data factories
-- Support file structure (commands, types, hooks)
-- Environment-specific configuration
-
-### Selectors & Queries
-- `data-cy` attribute strategy (recommended)
-- `cy.get()`, `cy.find()`, `cy.contains()`
-- Chaining and assertions
-- Retry-ability and flake prevention
-- Custom selector strategies
-
-### Network Layer
-- `cy.intercept()` for request stubbing
-- Waiting for requests with aliases
-- Response fixtures
-- GraphQL mocking
-- WebSocket testing
-
-### Authentication
-- `cy.session()` for session caching
-- Login command patterns
-- Token management
-- OAuth/SSO testing strategies
-
-### Component Testing
-- Framework-specific mounting (React, Vue, Angular)
-- Props and event testing
-- Slot/children testing
-- Store/context mocking
-- Styling verification
-
-## When to Use This Agent
-
-- Setting up Cypress in a new project
-- Designing test architecture and patterns
-- Debugging flaky tests
-- Implementing custom commands
-- Configuring CI/CD pipelines
-- Optimizing test execution time
-- Migrating from other testing frameworks
-- Component testing setup
-- Network stubbing strategies
-
-## Best Practices
-
-### Selectors
-```javascript
-// Good - use data-cy attributes
-cy.get('[data-cy=submit-button]')
-
-// Avoid - fragile selectors
-cy.get('.btn-primary')
-cy.get('#submit')
-```
-
-### Test Structure
-```javascript
-// Good - query -> query -> command/assertion
-cy.get('[data-cy=email]')
-  .type('user@example.com')
-
-cy.get('[data-cy=form]')
-  .find('[data-cy=submit]')
-  .click()
-
-// Avoid - chaining after action
-cy.get('[data-cy=submit]')
-  .click()
-  .should('be.disabled') // Don't do this
-```
-
-### Network Stubbing
-```javascript
-// Stub before visiting
-cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers')
-cy.visit('/users')
-cy.wait('@getUsers')
-```
-
-### Authentication
-```javascript
-// Use cy.session() for caching
-Cypress.Commands.add('login', (email, password) => {
-  cy.session([email, password], () => {
-    cy.visit('/login')
-    cy.get('[data-cy=email]').type(email)
-    cy.get('[data-cy=password]').type(password)
-    cy.get('[data-cy=submit]').click()
-    cy.url().should('include', '/dashboard')
-  })
-})
-```
-
-## Anti-Patterns to Avoid
-
-- Using `cy.wait(5000)` for arbitrary delays
-- Chaining commands after actions
-- Using fragile CSS selectors
-- Not using aliases for intercepts
-- Testing third-party sites
-- Sharing state between tests
-- Using `const result = cy.get(...)` (commands are async)
-- Not leveraging retry-ability
-- Skipping `baseUrl` configuration
-- Not using `data-cy` attributes
-
-## Project Structure
-
-```
-cypress/
-├── e2e/                    # E2E test specs
-│   ├── auth/
-│   │   └── login.cy.ts
-│   └── dashboard/
-│       └── overview.cy.ts
-├── component/              # Component test specs
-│   └── Button.cy.tsx
-├── fixtures/               # Test data
-│   └── users.json
-├── support/
-│   ├── commands.ts         # Custom commands
-│   ├── e2e.ts             # E2E support
-│   └── component.ts       # Component support
-└── tsconfig.json
-```
-
-## CI/CD Integration
-
-- Use `cypress run` for headless execution
-- Parallelize with Cypress Cloud
-- Record results with `--record`
-- Set up retries for flaky tests
-- Configure viewport and browser
-- Use environment variables for secrets
-
-## Performance Tips
-
-- Use `cy.session()` to cache auth state
-- Stub network requests (faster than real API)
-- Run tests in parallel
-- Use `testIsolation: false` carefully
-- Optimize fixtures (smaller payloads)
-- Disable video recording in CI if not needed
-
----
-
-*Refer to official Cypress documentation for detailed implementation guidance.*

File diff suppressed because it is too large
+ 0 - 2
agents/wrangler-expert.md


+ 12 - 12
docs/ARCHITECTURE.md

@@ -317,20 +317,20 @@ Agents are markdown files in `agents/` or `.claude/agents/`:
 
 ```yaml
 ---
-name: cypress-expert
-description: Expert in Cypress E2E and component testing, custom commands, and CI integration
+name: firecrawl-expert
+description: Expert in web scraping, crawling, anti-bot bypass, and structured data extraction
 model: sonnet
 ---
 
-# Cypress Expert
+# Firecrawl Expert
 
-You are a Cypress expert specializing in reliable end-to-end testing...
+You are a web-scraping expert specializing in reliable data extraction...
 
 ## Core Expertise
-- E2E and component test architecture
-- Custom commands and page objects
-- Network stubbing and fixtures
-- CI integration and flake reduction
+- Crawl and scrape architecture
+- Anti-bot and Cloudflare bypass
+- Dynamic/JS-rendered content handling
+- Structured data extraction pipelines
 
 ## Patterns
 [Detailed patterns and examples...]
@@ -338,13 +338,13 @@ You are a Cypress expert specializing in reliable end-to-end testing...
 
 ### Example Usage
 
-When Claude encounters a Cypress-specific question, it can spawn the cypress-expert:
+When Claude encounters a scraping-specific question, it can spawn the firecrawl-expert:
 
 ```
-User: "Why do these E2E tests pass locally but flake in CI?"
+User: "How do I extract structured data from this Cloudflare-protected site?"
 
-Claude: I'll consult the cypress-expert agent for specialized guidance.
-[Uses Task tool with subagent_type="cypress-expert"]
+Claude: I'll consult the firecrawl-expert agent for specialized guidance.
+[Uses Task tool with subagent_type="firecrawl-expert"]
 ```
 
 ### References

+ 2 - 2
docs/PLAN.md

@@ -15,8 +15,8 @@
 
 | Component | Count | Notes |
 |-----------|-------|-------|
-| Agents | 12 | Domain experts without skill twins + git-agent background worker |
-| Skills | 81 | Operational skills, CLI tools, workflows, diagnostics, security |
+| 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 |
 | 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 |

+ 8 - 8
rules/naming-conventions.md

@@ -20,15 +20,15 @@ Consistent naming patterns for all claude-mods components.
 | Pattern | Example | Notes |
 |---------|---------|-------|
 | Framework | `craftcms-expert.md` | Frameworks/CMS |
-| Tool | `cypress-expert.md` | Specific tools |
-| Domain | `aws-fargate-ecs-expert.md` | Compound domains |
+| CMS | `payloadcms-expert.md` | Headless CMS |
+| Tool | `firecrawl-expert.md` | Specific tools |
 | Specialized | `asus-router-expert.md` | Niche/device-specific |
 
 **Frontmatter:**
 
 ```yaml
 ---
-name: cypress-expert         # Match filename (without .md)
+name: craftcms-expert        # Match filename (without .md)
 description: <one line>      # Concise capability summary
 model: sonnet|opus|haiku     # Recommended model
 ---
@@ -180,10 +180,10 @@ depends_on: [bad]     # snake_case wrong
 ## Anti-patterns
 
 ```
-BAD:  Cypress-Expert.md      - PascalCase
-BAD:  cypress_expert.md      - snake_case
-BAD:  cypressExpert.md       - camelCase
-GOOD: cypress-expert.md      - kebab-case
+BAD:  Craftcms-Expert.md     - PascalCase
+BAD:  craftcms_expert.md     - snake_case
+BAD:  craftcmsExpert.md      - camelCase
+GOOD: craftcms-expert.md     - kebab-case
 
 BAD:  skills/PythonPatterns/ - PascalCase directory
 GOOD: skills/python-pytest-ops/
@@ -199,7 +199,7 @@ GOOD: vesper.md              - lowercase
 
 | Component | Pattern | Example |
 |-----------|---------|---------|
-| Agent | `{domain}-expert.md` | `cypress-expert.md` |
+| Agent | `{domain}-expert.md` | `craftcms-expert.md` |
 | Skill | `{topic}-ops/SKILL.md` | `postgres-ops/SKILL.md` |
 | Command | `{action}.md` | `review.md` |
 | Rule | `{topic}.md` | `commit-style.md` |

+ 7 - 1
scripts/install.ps1

@@ -58,7 +58,13 @@ $deprecated = @(
     "$claudeDir\agents\astro-expert.md",
     "$claudeDir\agents\laravel-expert.md",
     "$claudeDir\agents\sql-expert.md",
-    "$claudeDir\agents\postgres-expert.md"
+    "$claudeDir\agents\postgres-expert.md",
+    "$claudeDir\agents\cypress-expert.md",        # -> skills/cypress-ops
+    "$claudeDir\agents\cloudflare-expert.md",     # -> skills/cloudflare-ops
+    "$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
 )
 
 # Renamed skills: -patterns -> -ops (March 2026)

+ 6 - 0
scripts/install.sh

@@ -73,6 +73,12 @@ deprecated_items=(
     "$CLAUDE_DIR/agents/laravel-expert.md"
     "$CLAUDE_DIR/agents/sql-expert.md"
     "$CLAUDE_DIR/agents/postgres-expert.md"
+    "$CLAUDE_DIR/agents/cypress-expert.md"       # -> skills/cypress-ops
+    "$CLAUDE_DIR/agents/cloudflare-expert.md"    # -> skills/cloudflare-ops
+    "$CLAUDE_DIR/agents/wrangler-expert.md"      # -> skills/cloudflare-ops
+    "$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
 )
 
 # Renamed skills: -patterns -> -ops (March 2026)

+ 230 - 0
skills/bash-ops/SKILL.md

@@ -0,0 +1,230 @@
+---
+name: bash-ops
+description: "Defensive Bash scripting for production automation, CI scripts, and agent-facing tools. Triggers on: bash, shell script, defensive bash, bash strict mode, set -euo pipefail, set -Eeuo pipefail, shellcheck, trap, IFS, cleanup trap, mktemp, getopts, argument parsing, exit codes, stream separation, stdout stderr, quoting, word splitting, pipefail, subshell, CI script, shell footgun, bats, shfmt, POSIX, portable shell."
+when_to_use: "Use when writing or reviewing any Bash/shell script — especially skill scripts, CI steps, and automation that must fail safely. Covers strict mode, quoting, argument parsing, traps/cleanup, safe tempfiles, the stream-separation + exit-code contract, and shellcheck."
+license: MIT
+allowed-tools: "Read Write Edit Bash"
+metadata:
+  author: claude-mods
+  related-skills: cli-ops, ci-cd-ops
+---
+
+# Bash Operations
+
+Defensive Bash for scripts that run unattended — CI steps, automation, and the
+`scripts/` a skill ships. The goal: a script that **fails loudly on the first
+problem, never corrupts state, and emits parseable output**.
+
+This is the house standard for any shell script in this repo. The script contract
+below is the same one enforced by
+[`docs/SKILL-RESOURCE-PROTOCOL.md`](../../docs/SKILL-RESOURCE-PROTOCOL.md) §2–§7 —
+that protocol governs every skill resource, and its rules *are* bash rules. Treat
+the two as one standard: the resource protocol decides **what** a skill script must
+guarantee (streams, exit codes, help block); this skill teaches **how** to write
+the Bash that delivers it. The canonical reference implementation is
+[`skills/supply-chain-defense/scripts/preinstall-check.sh`](../supply-chain-defense/scripts/preinstall-check.sh) —
+read it whenever you need a worked example of every rule here applied at once.
+
+## Bash vs Python — choose before you write
+
+Reach for Python (and the `python-cli-ops` skill) when a script grows past
+**~100 lines**, needs **data structures** (nested maps, JSON manipulation beyond a
+`jq` filter), arithmetic beyond integers, or string processing with real parsing.
+Bash excels at **gluing processes together**: launching tools, moving files,
+checking conditions, wiring pipelines. The moment you find yourself simulating a
+hash-of-hashes or doing float math, stop — that's Python's job. This mirrors
+SKILL-RESOURCE-PROTOCOL §3, which expects `.sh` for shell glue and `.py` for logic.
+
+## Strict mode — the first three lines
+
+```bash
+#!/usr/bin/env bash
+set -Eeuo pipefail
+IFS=$'\n\t'
+```
+
+Each flag earns its place — and each has a sharp edge:
+
+| Flag | Buys you | The edge to know |
+|---|---|---|
+| `set -e` | Abort on any unchecked non-zero command | Does **not** fire inside `if`/`&&`/`||` conditions, in a function called in such a context, or for the *non-last* command of a pipe. `local x=$(cmd)` masks `cmd`'s failure — `local` returns 0. Split: `local x; x=$(cmd)`. |
+| `set -u` | Error on unset variable expansion | `"$@"` and `"${arr[@]}"` on an empty array trip `-u` in old Bash; use `"${arr[@]:-}"` or guard with Bash 4.4+. |
+| `set -o pipefail` | A pipe fails if **any** stage fails, not just the last | Without it, `grep x file | head` hides a `grep` error. With it, a `head` that closes the pipe early can surface `141` (SIGPIPE) — expected, not a bug. |
+| `set -E` | `ERR` trap inherits into functions/subshells/command-subs | Pair with a `trap … ERR` that reports `$LINENO`. |
+| `IFS=$'\n\t'` | Word-splitting only on newline/tab, never spaces | Filenames with spaces stop splitting into pieces. Unset/space-IFS is the #1 cause of "it worked until a path had a space". |
+
+`set -e` is the contested one. Use the full `set -Eeuo pipefail` when **every**
+unchecked failure should abort (most scripts). Drop to `set -uo pipefail` when the
+script deliberately inspects exit codes itself (the resource-protocol exemplars do
+this — they branch on registry exit codes, so a non-zero `curl` must not kill the
+run). Decide consciously; don't cargo-cult either way.
+
+→ Full treatment, ERR-trap recipes, and the `set -e` exemption rules:
+[`references/strict-mode-and-traps.md`](references/strict-mode-and-traps.md).
+
+## Quoting discipline
+
+**Quote every expansion** unless you have a specific, commented reason not to.
+
+```bash
+cp "$src" "$dst"                 # not cp $src $dst  — breaks on spaces/globs
+for f in "${files[@]}"; do …     # not ${files[@]}   — array stays element-safe
+rm -- "$path"                    # -- ends options; $path starting with - is data
+[[ -n "$x" ]]                    # [[ ]] doesn't word-split, but quote for habit
+grep -- "$pattern" "$file"
+```
+
+- Unquoted `$var` undergoes **word splitting** (on `IFS`) then **glob expansion**.
+  A variable holding `*.txt` or `a b` becomes multiple args. This is the canonical
+  footgun.
+- `"$@"` (quoted) preserves arguments exactly; `$@` and `$*` mangle them. Always
+  `"$@"` to forward args.
+- Use `--` before user/agent-supplied operands so a value like `-rf` is treated as
+  data, not flags.
+
+## Argument parsing — case-based long flags
+
+The resource protocol mandates `--help` with an EXAMPLES section and rejects
+unknown flags with a USAGE error (exit 2). Use a `while`/`case` loop — it handles
+GNU-style long flags (`--json`, `--dry-run`), which `getopts` cannot:
+
+```bash
+JSON=0; DRY_RUN=0; ARGS=()
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --json)      JSON=1 ;;
+    --dry-run)   DRY_RUN=1 ;;
+    -h|--help)   usage; exit 0 ;;
+    --)          shift; ARGS+=("$@"); break ;;   # everything after -- is positional
+    -*)          printf 'ERROR: unknown flag: %s (try --help)\n' "$1" >&2; exit 2 ;;
+    *)           ARGS+=("$1") ;;
+  esac
+  shift
+done
+```
+
+`getopts` is fine for **short flags only** (`-v -o file`) and is more compact there,
+but it has no long-flag support and clusters awkwardly. Prefer the `case` loop for
+anything agent-facing — it matches `preinstall-check.sh` exactly.
+
+→ Both styles in full, value-taking flags, `--flag=value`, and validation:
+[`references/argument-parsing.md`](references/argument-parsing.md).
+
+## Traps, cleanup, and safe tempfiles
+
+Never leave a tempfile or half-written output behind. Create temp paths with
+`mktemp`, register a cleanup `trap` **immediately after**, and write atomically.
+
+```bash
+tmp="$(mktemp)" || exit 1
+cleanup() { rm -f "$tmp"; }
+trap cleanup EXIT          # fires on normal exit, error, and signals via EXIT
+
+build_output >"$tmp"       # write to temp
+mv -- "$tmp" "$dst"        # atomic rename — reader never sees a partial file
+trap - EXIT; rm -f "$tmp"  # (optional) disarm after successful move
+```
+
+- `trap cleanup EXIT` is the workhorse — `EXIT` fires for normal exit, `set -e`
+  abort, and (in practice) `INT`/`TERM` if you let them propagate. Add explicit
+  `trap cleanup INT TERM` if you do signal handling yourself.
+- `mktemp -d` for a temp **directory**; clean it with `rm -rf -- "$tmpdir"`.
+- **Atomic write = `tmp` + `mv`** (same filesystem). A reader sees either the old
+  file or the complete new one, never a truncated mid-write — exactly the
+  idempotency the resource protocol §6 requires.
+
+→ Signal handling, ERR-trap with line numbers, nested traps:
+[`references/strict-mode-and-traps.md`](references/strict-mode-and-traps.md).
+
+## The stream-separation + exit-code contract
+
+This is the load-bearing rule for any agent-facing script, lifted directly from
+SKILL-RESOURCE-PROTOCOL §4–§5. Claude parses stdout; pollution breaks `| jq`.
+
+- **stdout = the data product only.** JSON under `--json`, else plain/TSV.
+- **stderr = everything else.** Headers, progress, warnings, errors, prompts.
+- **Semantic exit codes**, not just 0/1:
+
+| Code | Meaning |
+|---|---|
+| `0` | success |
+| `2` | usage (bad/missing args, unknown flag) |
+| `3` | not found (input absent) |
+| `4` | validation (input present but malformed) |
+| `5` | precondition (missing dependency, wrong cwd) |
+| `7` | unavailable (external resource down — *advisory*, not a real failure) |
+| `10`+ | domain signal — a non-error "finding" the caller branches on |
+
+Code `10` is the workhorse for verifiers/scanners: "ran fine, found something."
+Reserve `7` so a network blip never looks like a content failure. Print human
+framing to stderr, the record to stdout:
+
+```bash
+printf '%s\t%s\n' "$name" "$status"          # data → stdout
+printf '  [ok] %s checked\n' "$name" >&2      # framing → stderr
+```
+
+→ The shipped [`assets/script-template.sh`](assets/script-template.sh) bakes this
+contract in — copy it as the starting point for any new skill script.
+
+## ShellCheck — non-negotiable
+
+Run [`shellcheck`](https://www.shellcheck.net/) on every script; it catches the
+quoting/word-splitting/`set -e` bugs above mechanically.
+
+```bash
+shellcheck script.sh                  # lint
+shellcheck -x script.sh               # follow sourced files
+shfmt -i 2 -ci -w script.sh           # format (2-space indent, indent switch-cases)
+```
+
+- Fix warnings; don't blanket-suppress. When a suppression is genuinely correct,
+  scope it to one line with a reason: `# shellcheck disable=SC2086 # word split intended`.
+- CI gate: `shellcheck **/*.sh` should pass clean before merge.
+- `bash -n script.sh` is a free syntax-only check (no execution) — run it in tests.
+
+## Common footguns (quick table)
+
+| Footgun | Why it bites | Fix |
+|---|---|---|
+| Unquoted `$var` | Word-split + glob expansion | `"$var"` always |
+| `[ "$a" == "$b" ]` | `[` is POSIX `test`; `==` non-portable, no `&&` grouping | `[[ "$a" == "$b" ]]` in Bash |
+| `var=$(cmd) ; echo $?` | `$?` is the assignment's status (always 0), not `cmd`'s | `cmd; rc=$?` or check inline |
+| `cmd | while read x; do total=$x; done` | `while` runs in a **subshell**; `$total` is lost after the pipe | `while …; do … done < <(cmd)` (process substitution) |
+| `local x=$(cmd)` under `set -e` | `local` returns 0, masking `cmd` failure | `local x; x=$(cmd)` on two lines |
+| `echo "$x"` for arbitrary data | `echo` mangles `-n`, `-e`, backslashes | `printf '%s\n' "$x"` |
+| `for f in $(ls)` | Splits on whitespace, breaks on spaces/newlines | `for f in *` or `while IFS= read -r f` |
+| `pipefail` + `head` shows `141` | Downstream closes pipe early (SIGPIPE) | Expected; tolerate `141` from truncating consumers |
+
+→ Each footgun with a reproducer and the underlying mechanism:
+[`references/footguns.md`](references/footguns.md).
+
+## Bash version notes (attribute features correctly)
+
+Bash is stable, but several common idioms are **version-gated**. macOS still ships
+Bash **3.2** (2007, GPLv2); Linux/CI is usually Bash 5.x. If a script must run on
+stock macOS, avoid the 4.x+ features below or guard with `((BASH_VERSINFO[0]>=4))`.
+
+| Feature | Introduced | Notes |
+|---|---|---|
+| `mapfile` / `readarray` | Bash **4.0** | Read lines into an array. `mapfile -t arr < file`. `-d ''` (null-delimited) needs **4.4**. |
+| Associative arrays (`declare -A`) | Bash **4.0** | Hash maps. Unavailable on macOS stock 3.2. |
+| `${var,,}` / `${var^^}` (case conversion) | Bash **4.0** | Lowercase/uppercase expansion. |
+| `&>>` append-both-streams, `|&` | Bash **4.0** | `cmd |& filter` = `cmd 2>&1 | filter`. |
+| `${var@Q}` (quote operator) | Bash **4.4** | Produces a re-input-safe quoted form. Also `@U @L @E`. |
+| `wait -n` (any child) | Bash **4.3** | Useful for bounded parallelism. |
+| `local -n` (nameref) | Bash **4.3** | Pass array/var by reference into a function. |
+
+When in doubt, state the requirement in the first-comment-block (`# Requires: bash 4+`)
+and check at startup: `((BASH_VERSINFO[0] >= 4)) || { echo "needs bash 4+" >&2; exit 5; }`.
+
+## Checklist before shipping a skill script
+
+- [ ] `#!/usr/bin/env bash` + first-comment-block contract (desc, Usage, Exit, Examples)
+- [ ] `set -Eeuo pipefail` (or a *deliberate* `set -uo pipefail`) + `IFS=$'\n\t'`
+- [ ] All expansions quoted; `"$@"` to forward args; `--` before operands
+- [ ] `case` arg loop; `--help` exits 0 with EXAMPLES; unknown flag → exit 2
+- [ ] `trap cleanup EXIT` + `mktemp`; atomic `tmp`+`mv` writes
+- [ ] stdout data-only, stderr for framing; semantic exit codes (§5)
+- [ ] `shellcheck` clean; `bash -n` passes; `chmod +x`
+- [ ] Version-gated features guarded or documented

+ 119 - 0
skills/bash-ops/assets/script-template.sh

@@ -0,0 +1,119 @@
+#!/usr/bin/env bash
+# Starter scaffold for an agent-facing skill script. <one-line, ends with a period.>
+#
+# Usage:   script-template.sh [OPTIONS] <input>...
+# Input:   one or more inputs as positionals; flags select behaviour
+# Output:  stdout = data product only (TSV, or JSON under --json)
+# Stderr:  headers, progress, warnings, errors
+# Exit:    0 ok, 2 usage, 3 not-found, 4 validation, 5 missing-dep,
+#          7 unavailable, 10 <domain signal — document it here>
+#
+# Examples:
+#   script-template.sh input.txt
+#   script-template.sh --json a.txt b.txt | jq '.data[]'
+#   script-template.sh --out result.tsv --quiet input.txt
+#
+# Requires: bash 4+ (uses mapfile-style idioms); shellcheck-clean.
+
+set -Eeuo pipefail
+IFS=$'\n\t'
+
+# --- semantic exit codes (SKILL-RESOURCE-PROTOCOL §5) ---
+readonly EXIT_OK=0 EXIT_USAGE=2 EXIT_NOTFOUND=3 EXIT_VALIDATION=4
+readonly EXIT_MISSING_DEP=5 EXIT_UNAVAILABLE=7 EXIT_FINDING=10
+
+readonly SCRIPT_NAME="$(basename -- "${BASH_SOURCE[0]}")"
+
+# --- help (stdout, exit 0, EXAMPLES mandatory) ---
+usage() {
+  cat <<EOF
+Usage: ${SCRIPT_NAME} [OPTIONS] <input>...
+
+Options:
+  --json            emit a JSON envelope to stdout (needs jq)
+  --out FILE        write output to FILE atomically (default: stdout)
+  -q, --quiet       suppress progress framing on stderr
+  -h, --help        show this help and exit
+
+EXAMPLES:
+  ${SCRIPT_NAME} input.txt
+  ${SCRIPT_NAME} --json a.txt b.txt | jq '.data[]'
+  ${SCRIPT_NAME} --out result.tsv -q input.txt
+EOF
+}
+
+# --- framing helpers: human text ALWAYS to stderr, never stdout ---
+log()  { [[ "$QUIET" -eq 1 ]] && return 0; printf '%s\n' "$*" >&2; }
+die()  { printf 'ERROR: %s\n' "$*" >&2; exit "${2:-1}"; }
+
+# --- cleanup trap + atomic write scaffolding ---
+TMPFILE=""
+cleanup() {
+  local rc=$?
+  [[ -n "$TMPFILE" && -e "$TMPFILE" ]] && rm -f -- "$TMPFILE"
+  exit "$rc"
+}
+trap cleanup EXIT
+trap 'die "interrupted" 130' INT TERM
+
+# --- argument parsing: case loop, long flags, hard usage errors ---
+JSON=0; QUIET=0; OUT=""; ARGS=()
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --json)      JSON=1 ;;
+    -q|--quiet)  QUIET=1 ;;
+    --out)       OUT="${2:?--out needs a value}"; shift ;;
+    --out=*)     OUT="${1#*=}" ;;
+    -h|--help)   usage; exit "$EXIT_OK" ;;
+    --)          shift; ARGS+=("$@"); break ;;
+    -*)          die "unknown flag: $1 (try --help)" "$EXIT_USAGE" ;;
+    *)           ARGS+=("$1") ;;
+  esac
+  shift
+done
+
+# --- validation (per §5/§6) ---
+[[ ${#ARGS[@]} -ge 1 ]] || die "need at least one input (try --help)" "$EXIT_USAGE"
+if [[ "$JSON" -eq 1 ]]; then
+  command -v jq >/dev/null 2>&1 || die "jq required for --json" "$EXIT_MISSING_DEP"
+fi
+for in_f in "${ARGS[@]}"; do
+  [[ -e "$in_f" ]] || die "input not found: $in_f" "$EXIT_NOTFOUND"
+done
+
+# --- work: write the DATA PRODUCT to a buffer, framing to stderr ---
+log "=== ${SCRIPT_NAME}: processing ${#ARGS[@]} input(s) ==="
+
+emit_records() {
+  # Replace this with real logic. Data → stdout, one TSV record per line.
+  local f
+  for f in "${ARGS[@]}"; do
+    local lines
+    lines=$(wc -l < "$f" 2>/dev/null || echo 0)
+    printf '%s\t%s\n' "$f" "$lines"   # DATA → stdout
+    log "  [ok] ${f} (${lines} lines)"  # framing → stderr
+  done
+}
+
+if [[ "$JSON" -eq 1 ]]; then
+  # Build the §4 success envelope. Booleans true/false, empty lists [], ISO-8601 Z.
+  records="$(emit_records | jq -R -s -c 'split("\n")
+    | map(select(length>0) | split("\t") | {file: .[0], lines: (.[1]|tonumber)})')"
+  payload="$(jq -cn --argjson d "$records" \
+    '{data: $d, meta: {count: ($d|length), schema: "claude-mods.bash-ops.script-template/v1"}}')"
+else
+  payload="$(emit_records)"
+fi
+
+# --- output: atomic write when --out, else stdout ---
+if [[ -n "$OUT" ]]; then
+  TMPFILE="$(mktemp -- "${OUT}.XXXXXX")" || die "mktemp failed" "$EXIT_UNAVAILABLE"
+  printf '%s\n' "$payload" > "$TMPFILE"
+  mv -- "$TMPFILE" "$OUT"     # atomic rename — reader never sees a partial file
+  TMPFILE=""                  # disarm cleanup; the file is now $OUT
+  log "wrote ${OUT}"
+else
+  printf '%s\n' "$payload"    # DATA → stdout
+fi
+
+exit "$EXIT_OK"

+ 111 - 0
skills/bash-ops/references/argument-parsing.md

@@ -0,0 +1,111 @@
+# Argument Parsing — deep dive
+
+Robust, agent-facing argument handling. The resource protocol requires `--help`
+with EXAMPLES (exit 0 to stdout) and a hard USAGE error (exit 2) on unknown flags
+or extra positionals. Two idioms cover everything: the **`case` loop** (preferred,
+long-flag capable) and **`getopts`** (short-flags only, more compact).
+
+## The `case` loop (preferred)
+
+Handles long flags (`--json`), short flags, value-taking flags, `--flag=value`,
+and the `--` end-of-options sentinel. This is what `preinstall-check.sh` uses.
+
+```bash
+usage() {
+  cat <<'EOF'
+Usage: tool [OPTIONS] <input>...
+  --json            emit JSON to stdout
+  --out FILE        write result to FILE (default: stdout)
+  --retries N       retry N times (default: 3)
+  -q, --quiet       suppress progress on stderr
+  -h, --help        show this help
+
+EXAMPLES:
+  tool input.txt
+  tool --json --out result.json a.txt b.txt
+  tool --retries 5 -q input.txt
+EOF
+}
+
+JSON=0; QUIET=0; OUT=""; RETRIES=3; ARGS=()
+while [[ $# -gt 0 ]]; do
+  case "$1" in
+    --json)        JSON=1 ;;
+    -q|--quiet)    QUIET=1 ;;
+    --out)         OUT="${2:?--out needs a value}"; shift ;;
+    --out=*)       OUT="${1#*=}" ;;
+    --retries)     RETRIES="${2:?--retries needs a value}"; shift ;;
+    --retries=*)   RETRIES="${1#*=}" ;;
+    -h|--help)     usage; exit 0 ;;
+    --)            shift; ARGS+=("$@"); break ;;
+    -*)            printf 'ERROR: unknown flag: %s (try --help)\n' "$1" >&2; exit 2 ;;
+    *)             ARGS+=("$1") ;;
+  esac
+  shift
+done
+
+# validation
+[[ ${#ARGS[@]} -ge 1 ]] || { printf 'ERROR: need at least one input (try --help)\n' >&2; exit 2; }
+[[ "$RETRIES" =~ ^[0-9]+$ ]] || { printf 'ERROR: --retries must be an integer\n' >&2; exit 2; }
+```
+
+Key points:
+
+- **Value-taking flags** consume `$2` then `shift` an extra time. `${2:?msg}` aborts
+  with `msg` if the value is missing — clean for `set -u` scripts.
+- **`--flag=value` form** handled by a parallel `--flag=*` arm using `${1#*=}`
+  (strip up to the first `=`).
+- **`--` sentinel** stops option parsing: everything after is positional, even if it
+  starts with `-`. Essential when an input filename might be `-weird`.
+- **Unknown flag** (`-*`) is a hard exit 2 — never silently ignore (protocol §6).
+- **Positionals** accumulate into an array so spaces survive; consume them with
+  `"${ARGS[@]}"`.
+- Validate *after* parsing: required count, integer ranges, file existence (exit 3
+  for a missing input file per §5).
+
+## `getopts` (short flags only)
+
+POSIX-portable, compact, but **no long-flag support** and no `--flag=value`. Good
+for a small script with only single-letter options.
+
+```bash
+JSON=0; OUT=""; verbose=0
+while getopts ':jo:vh' opt; do
+  case "$opt" in
+    j) JSON=1 ;;
+    o) OUT="$OPTARG" ;;          # ':' after o means it takes a value
+    v) verbose=1 ;;
+    h) usage; exit 0 ;;
+    :) printf 'ERROR: -%s needs a value\n' "$OPTARG" >&2; exit 2 ;;
+    \?) printf 'ERROR: unknown flag: -%s\n' "$OPTARG" >&2; exit 2 ;;
+  esac
+done
+shift $((OPTIND - 1))            # drop parsed options; "$@" is now positionals
+```
+
+- The **leading `:`** in `':jo:vh'` enables *silent* error mode, letting you handle
+  `:` (missing value) and `\?` (unknown) yourself with proper exit-2 messages.
+- A letter followed by `:` (here `o:`) takes a value in `$OPTARG`.
+- `shift $((OPTIND - 1))` discards the consumed options so `"$@"` holds positionals.
+- Limitations that push you to the `case` loop: `getopts` cannot do `--json`,
+  cannot do `--out=x`, and clustering long flags is impossible. For anything an
+  agent invokes by long name, use the `case` loop.
+
+## Choosing between them
+
+| Need | Use |
+|---|---|
+| Any long flag (`--json`, `--dry-run`) | `case` loop |
+| `--flag=value` form | `case` loop |
+| Agent-facing skill script | `case` loop (matches the exemplar) |
+| Tiny script, only `-x -y -z` short flags, max portability | `getopts` |
+
+## The `--help` contract (both styles)
+
+- Writes to **stdout** and exits **0** (it is requested output, not an error).
+- Includes a usage line, every option, and an **EXAMPLES** block — the protocol
+  makes EXAMPLES mandatory because it is what makes the tool discoverable when the
+  agent runs `--help`.
+- A common compact trick (used by `preinstall-check.sh`) is to render help from the
+  first-comment-block itself: `sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'` — single
+  source of truth for the contract and the help text.

+ 170 - 0
skills/bash-ops/references/footguns.md

@@ -0,0 +1,170 @@
+# Bash Footguns — reproducers and mechanisms
+
+Each entry: what breaks, *why* at the shell-mechanics level, and the fix. These are
+the bugs `shellcheck` and `set -Eeuo pipefail` exist to catch.
+
+## 1. Unquoted expansion → word splitting + globbing
+
+```bash
+file="my report.txt"
+rm $file            # runs: rm my report.txt   → tries to remove TWO files
+rm "$file"          # correct
+```
+
+**Mechanism.** After parameter expansion, an *unquoted* result undergoes (a) word
+splitting on `IFS` (default: space/tab/newline) then (b) pathname expansion
+(globbing). `*.bak` in a variable expands to matching files; `a b` splits to two
+args. Quoting suppresses both. This is the single most common shell bug.
+
+Fix: quote everything — `"$file"`, `"${arr[@]}"`. Set `IFS=$'\n\t'` to drop space
+from the split set as defence-in-depth.
+
+## 2. `[ ]` vs `[[ ]]`
+
+```bash
+[ -n $x ]                  # if $x is empty/unset → [ -n ] → true (wrong!)
+[ "$a" == "$b" ]           # == is non-POSIX in [; && doesn't work; word-splits
+[[ -n $x ]]                # safe: [[ ]] does not word-split its operands
+[[ "$a" == "$b" && -f c ]] # &&, ==, < all work inside [[ ]]
+```
+
+**Mechanism.** `[` is the external/builtin `test` command — its operands are
+subject to normal word splitting, so an unquoted empty variable vanishes and
+changes the expression's arity. `[[ ]]` is a Bash *keyword*: it parses operands
+without word splitting or globbing, supports `&&`/`||`/`<`/`==` with pattern
+matching and `=~` for regex. Use `[[ ]]` in Bash always; reserve `[ ]` for strict
+POSIX `sh` scripts.
+
+## 3. `$?` after assignment is always 0
+
+```bash
+output=$(might_fail)
+echo $?              # prints 0 — the ASSIGNMENT succeeded, not might_fail
+```
+
+**Mechanism.** A simple assignment's exit status is that of the *last command
+substitution*, but a plain `var=$(cmd)` reports the assignment builtin's status,
+which is 0 unless the substitution itself errors fatally. Capture inline:
+
+```bash
+if ! output=$(might_fail); then echo "failed" >&2; fi
+# or
+output=$(might_fail); rc=$?       # rc only reliable when not 'local'/'declare'
+```
+
+## 4. Pipe into `while read` loses variables (subshell)
+
+```bash
+count=0
+printf 'a\nb\nc\n' | while read -r line; do count=$((count+1)); done
+echo "$count"        # prints 0 — the while ran in a subshell
+```
+
+**Mechanism.** Each stage of a pipeline runs in its **own subshell**. The `while`'s
+variable mutations happen in a child process and evaporate when it exits. Fixes:
+
+```bash
+# process substitution — while runs in the current shell
+count=0
+while read -r line; do count=$((count+1)); done < <(printf 'a\nb\nc\n')
+echo "$count"        # 3
+
+# or a here-string / file redirect
+while read -r line; do …; done <<< "$data"
+```
+
+(Bash's `shopt -s lastpipe` runs the last pipe stage in the current shell, but only
+non-interactively and with job control off — process substitution is more portable.)
+
+## 5. `local x=$(cmd)` masks failure under `set -e`
+
+```bash
+set -e
+get() { local v=$(false); echo "reached"; }   # 'reached' prints — failure hidden
+get() { local v; v=$(false); echo "nope"; }    # aborts at v=$(false)
+```
+
+**Mechanism.** `local`/`declare`/`export` are commands; their exit status is the
+builtin's (0 on a valid declaration), which overrides the substitution's failing
+status. `set -e` sees 0 and continues. Always split declaration and assignment when
+the command's success matters.
+
+## 6. `echo` is not portable for data
+
+```bash
+echo "-n"            # may print nothing (treated as a flag) or "-n"
+echo "a\tb"          # prints literal \t or a tab depending on shell/xpg_echo
+printf '%s\n' "$x"   # correct, deterministic
+```
+
+**Mechanism.** `echo`'s handling of `-n`, `-e`, and backslash escapes is
+unspecified across shells and `shopt xpg_echo`. For any variable data, use
+`printf '%s\n'`. Reserve `echo` for fixed, escape-free literals.
+
+## 7. `for f in $(ls)` / parsing `ls`
+
+```bash
+for f in $(ls *.txt); do …   # breaks on spaces, newlines, globs in names
+for f in *.txt; do            # correct: glob directly, no ls
+  [[ -e "$f" ]] || continue   # handle "no matches" (glob stays literal)
+  …
+done
+```
+
+**Mechanism.** `$(ls)` produces a single string that word-splits on whitespace — a
+filename with a space becomes two loop iterations. Globbing (`*.txt`) yields each
+match as one word safely. Guard the no-match case (a non-matching glob expands to
+itself unless `shopt -s nullglob`).
+
+## 8. Reading a file line-by-line
+
+```bash
+while IFS= read -r line; do
+  printf 'got: %s\n' "$line"
+done < "$file"
+# trailing line with no newline:
+while IFS= read -r line || [[ -n "$line" ]]; do …; done < "$file"
+```
+
+**Mechanism.** `IFS=` (empty) stops leading/trailing whitespace trimming; `-r`
+stops backslash interpretation. Without both, indentation and backslashes are
+mangled. The `|| [[ -n "$line" ]]` clause catches a final line lacking a newline
+(`read` returns non-zero but still sets `line`).
+
+## 9. `pipefail` + early-closing consumer = exit 141
+
+```bash
+set -o pipefail
+generate_huge_stream | head -5    # head exits after 5 lines → SIGPIPE to generator
+echo $?                            # 141 (128 + 13) even though nothing is wrong
+```
+
+**Mechanism.** When `head` closes the read end, the producer gets SIGPIPE (signal
+13) and dies with `128+13=141`; `pipefail` propagates that as the pipeline status.
+Tolerate it: `{ generate_huge_stream || [[ $? -eq 141 ]]; } | head -5`.
+
+## 10. Empty array under `set -u` (older Bash)
+
+```bash
+set -u
+arr=()
+printf '%s\n' "${arr[@]}"     # Bash <4.4: "unbound variable" error
+printf '%s\n' "${arr[@]:-}"   # safe everywhere
+```
+
+**Mechanism.** Pre-4.4 Bash treated `"${arr[@]}"` of an empty array as referencing
+an unset variable under `-u`. Use `"${arr[@]:-}"`, or test `((${#arr[@]}))` first.
+Fixed in Bash 4.4 for `@`/`*`, but the guard keeps scripts portable to macOS 3.2
+and old CI images.
+
+## 11. `cd` without checking, then destructive op
+
+```bash
+cd "$dir" && rm -rf -- ./*     # if cd fails, rm never runs (good)
+cd "$dir"; rm -rf -- ./*       # if cd fails (under no -e), rm runs in WRONG dir
+```
+
+**Mechanism.** A failed `cd` (typo, missing dir) leaves you in the *current*
+directory. A following unconditional `rm -rf ./*` then wipes the wrong tree. Always
+`cd … || exit 1`, or chain with `&&`, or run under `set -e`. Add `--` and `./`
+prefixes so a path beginning with `-` is data, not flags.

+ 133 - 0
skills/bash-ops/references/strict-mode-and-traps.md

@@ -0,0 +1,133 @@
+# Strict Mode and Traps — deep dive
+
+The error-handling backbone of a defensive Bash script. The SKILL.md body covers
+the three-line preamble; this file covers the edge cases that bite in production.
+
+## The four flags, precisely
+
+### `set -e` (errexit) — and where it silently does nothing
+
+`set -e` aborts the script when a command returns non-zero. It is the most useful
+and the most misunderstood flag because of where it **does not** fire:
+
+1. **Conditions.** A command in an `if`, `while`, `until`, `&&`, `||`, or negated
+   with `!` is a *tested* command — its failure is expected, so `-e` ignores it.
+   ```bash
+   if ! grep -q foo file; then …    # grep failing here does NOT abort
+   ```
+2. **Inside functions called in a condition.** If `f` is invoked as `if f; then`,
+   `-e` is disabled *for the entire body of `f`* (POSIX behaviour). A failing
+   command deep inside `f` won't abort. This surprises everyone once.
+3. **Non-final pipe stages.** Without `pipefail`, only the last command's status
+   counts: `false | true` succeeds. Add `set -o pipefail`.
+4. **`local`/`declare` masking.** `local x=$(cmd)` — the *assignment builtin*
+   returns 0 even if `cmd` failed. Split the declaration from the assignment:
+   ```bash
+   local x; x=$(cmd)    # now $? reflects cmd, and -e can fire
+   ```
+5. **Command substitution in an unchecked statement.** `echo "$(false)"` does not
+   abort under older Bash because the outer `echo` succeeds. Assign first if the
+   substitution's success matters.
+
+**When to drop `-e`.** Scripts that *inspect* exit codes themselves (a checker that
+branches on whether `curl` got a 404 vs a network error) must NOT let `-e` kill the
+run on the first non-zero. Those use `set -uo pipefail` and check `$?` explicitly —
+this is exactly what `preinstall-check.sh` does, and why the resource protocol §2
+says "use `-e` only when every failure is fatal."
+
+### `set -u` (nounset)
+
+Expanding an unset variable becomes a fatal error instead of an empty string —
+catches typos (`$fil` vs `$file`) and missing arguments.
+
+- `"$1"` when no `$1` was passed → aborts. Guard: `"${1:-default}"` or
+  `[[ $# -ge 1 ]] || { usage; exit 2; }`.
+- **Empty-array gotcha:** in Bash before 4.4, `"${arr[@]}"` on an empty array trips
+  `-u`. Use `"${arr[@]:-}"` or test `[[ ${#arr[@]} -gt 0 ]]` first. (Bash 4.4+ fixed
+  this for `@`/`*`.)
+
+### `set -o pipefail`
+
+A pipeline's exit status becomes the rightmost **non-zero** status (or 0 if all
+succeed). Without it, a failing producer is invisible:
+
+```bash
+set -o pipefail
+data="$(curl -fsS "$url" | jq '.x')"   # now a curl failure fails the assignment
+```
+
+**SIGPIPE / 141 caveat:** when a consumer closes the pipe early (`producer | head -1`),
+the producer is killed by SIGPIPE and reports `141`. With `pipefail` this surfaces as
+a pipeline failure even though nothing is wrong. Tolerate it for truncating
+consumers: `{ big_producer || [[ $? -eq 141 ]]; } | head`.
+
+### `set -E` (errtrace)
+
+Makes an `ERR` trap inherit into shell functions, command substitutions, and
+subshells. Without `-E`, your nice line-number-reporting `ERR` trap silently fails
+to fire inside functions. Always pair `-E` with an `ERR` trap.
+
+## ERR trap with context
+
+```bash
+set -Eeuo pipefail
+
+err() {
+  local rc=$?
+  printf 'ERROR: rc=%d at %s:%d in %s()\n' \
+    "$rc" "${BASH_SOURCE[1]:-?}" "${BASH_LINENO[0]:-?}" "${FUNCNAME[1]:-main}" >&2
+  exit "$rc"
+}
+trap err ERR
+```
+
+`BASH_LINENO`/`BASH_SOURCE`/`FUNCNAME` are parallel stack arrays — index `[0]`/`[1]`
+walk up the call stack. This turns "it failed somewhere" into "it failed at
+deploy.sh:42 in push_image()".
+
+## EXIT trap — cleanup that always runs
+
+```bash
+tmpdir="$(mktemp -d)"
+cleanup() {
+  local rc=$?            # capture BEFORE any command in cleanup changes $?
+  rm -rf -- "$tmpdir"
+  exit "$rc"            # preserve the original exit code
+}
+trap cleanup EXIT
+```
+
+- `EXIT` fires on normal exit, `set -e` abort, `exit N`, and — if not separately
+  trapped — after the default signal handlers. It is the single most reliable place
+  to release resources.
+- **Capture `$?` as the first line** of the handler. Any command inside `cleanup`
+  overwrites `$?`, so grab it before `rm` et al.
+- A `trap … EXIT` set in a subshell only covers that subshell.
+
+## Signal handling
+
+```bash
+interrupted=0
+on_int() { interrupted=1; printf '\ninterrupted, cleaning up…\n' >&2; }
+trap on_int INT TERM
+```
+
+- `INT` (Ctrl-C), `TERM` (`kill`), `HUP` (terminal closed) are the common ones.
+- After handling a signal, `EXIT` still runs — so put resource release in the EXIT
+  handler and use signal handlers only for *additional* behaviour (a message, a
+  flag).
+- To re-raise a signal with the correct exit code (`128 + signum`), reset and
+  resend: `trap - INT; kill -INT $$`.
+- `trap '' INT` ignores a signal; `trap - INT` restores the default.
+
+## Idempotency and atomic writes
+
+The resource protocol §6 requires re-running with the same inputs to be safe.
+
+- Never write the destination directly. Write `"$dst.tmp"` (or a `mktemp` file on
+  the same filesystem) and `mv -- "$tmp" "$dst"`. `mv` within one filesystem is
+  atomic — a concurrent reader sees old-or-new, never a partial file. (`mv` across
+  filesystems falls back to copy+unlink and is *not* atomic — keep the temp beside
+  the destination.)
+- Guard creation with `mkdir -p` (idempotent) rather than `mkdir` (fails if exists).
+- For "create only if absent" use `set -C` (noclobber) + `>` or `mkdir` as a lock.

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


+ 3 - 2
skills/claude-code-ops/SKILL.md

@@ -1,7 +1,7 @@
 ---
 name: claude-code-ops
-description: "Claude Code internals - hooks, skills, subagents, headless mode, and debugging, current as of June 2026. Use for: hooks, hook events, hook not firing, PreToolUse, PostToolUse, SessionStart, Stop hook, hook script, stdin JSON contract, tool validation, audit logging, skill frontmatter, SKILL.md, skill not loading, skill not triggering, disable-model-invocation, context fork, dynamic context injection, skill description budget, headless, claude -p, CLI automation, --print, output-format, stream-json, json-schema structured output, CI/CD scripting, bare mode, background agents, debug, troubleshoot, not working, agent not found, plugin not loading, claude plugin validate, /doctor, --safe-mode, MCP server not connecting, settings precedence."
-when_to_use: "Use for questions about Claude Code itself — e.g. 'my hook isn't firing', 'why won't this skill trigger', 'run claude headless in CI', 'plugin fails to validate', 'which settings file wins'."
+description: "Claude Code internals - hooks, skills, subagents, headless mode, and debugging, current as of June 2026. Use for: hooks, hook events, hook not firing, PreToolUse, PostToolUse, SessionStart, Stop hook, hook script, stdin JSON contract, tool validation, audit logging, skill frontmatter, SKILL.md, skill not loading, skill not triggering, disable-model-invocation, context fork, dynamic context injection, skill description budget, headless, claude -p, CLI automation, --print, output-format, stream-json, json-schema structured output, CI/CD scripting, bare mode, background agents, debug, troubleshoot, not working, agent not found, plugin not loading, claude plugin validate, /doctor, --safe-mode, MCP server not connecting, settings precedence, extension architecture, which extension type, agent vs skill vs command vs hook, where to put a skill, extension scope, plugin design, authoring an agent, writing a skill description."
+when_to_use: "Use for questions about Claude Code itself — e.g. 'my hook isn't firing', 'why won't this skill trigger', 'run claude headless in CI', 'plugin fails to validate', 'which settings file wins', 'should this be an agent or a skill', 'how do I design this extension'."
 license: MIT
 compatibility: "Claude Code CLI v2.1.x (June 2026 docs)"
 allowed-tools: "Bash Read Grep"
@@ -24,6 +24,7 @@ One skill for the machinery of Claude Code itself: the **hook system**, the **sk
 | Authoring a skill; frontmatter fields; `$ARGUMENTS`; `` !`cmd` `` injection; triggering problems | [references/skills-reference.md](references/skills-reference.md) |
 | `claude -p`; CI scripts; output parsing; stream-json; structured output; background agents | [references/headless-reference.md](references/headless-reference.md) |
 | Anything configured isn't taking effect; plugin validation; /doctor | [references/debugging-reference.md](references/debugging-reference.md) |
+| Designing a new extension — which surface to build, where to scope it, how to author the description/prompt | [references/extension-architecture.md](references/extension-architecture.md) |
 
 ## Resources
 

+ 119 - 0
skills/claude-code-ops/references/extension-architecture.md

@@ -0,0 +1,119 @@
+# Extension Architecture — designing what to build
+
+The other references cover the *mechanics* of each surface (hook contracts, skill
+frontmatter, headless flags, debugging). This one covers the layer above: **which
+surface to reach for, where to put it, and how to author it well.** Reach here when
+you're designing a new agent / skill / command / hook / MCP server rather than fixing
+an existing one.
+
+## Pick the surface
+
+Each extension surface has a job it does best. Match the need, don't default.
+
+| Need | Surface | Why |
+|------|---------|-----|
+| Repeatable workflow the user kicks off | **Skill** (`user-invocable`, slash-style) | Consistent steps, on-demand body |
+| Reference material / patterns loaded by keyword | **Skill** | Description always in context, body forks in on match |
+| Deep multi-step reasoning in isolated context | **Subagent** | Own context window, system prompt, tool/model/permission set |
+| Must-never-happen guarantee | **Hook** (or a permission deny rule) | Deterministic; runs regardless of model judgment |
+| Always-on, file/path-scoped guidance | **Rule** (path-scoped) | Injected per matching file |
+| Context every session needs | **CLAUDE.md** | Loaded automatically at session start |
+| External capability (DB, API, service) | **MCP server** | Tools/resources the model calls |
+
+**Decision tree:**
+
+1. Does it *have to* happen (security, safety, an invariant)? → **Hook** or permission rule — not a skill. Skills are guidance, not guarantees.
+2. Clear repeatable steps a user invokes? → **Skill** (user-invocable).
+3. Reference patterns surfaced by keyword? → **Skill** (model-invocable).
+4. Needs its own context window / deep reasoning / a different model? → **Subagent**.
+5. Applies to specific file types or paths? → **Rule**.
+6. Should the model always know it? → **CLAUDE.md**.
+7. New external capability? → **MCP server** (see mcp-ops).
+
+**Skill vs subagent (the common fork):** a skill injects prompt content into the
+*current* context; a subagent runs in a *fresh, isolated* context and reports back.
+Reach for a subagent when the work would pollute or blow the main context (large
+multi-file analysis, long research), needs a cheaper/stronger model, or wants a
+narrowed tool set. Reach for a skill when the guidance belongs inline. They compose:
+a subagent can `skills:` preload full skill bodies, so "agent for the decision, skill
+for the patterns" is a valid pairing rather than a duplication.
+
+## Where to put it (scope)
+
+| Scope | Location | When |
+|-------|----------|------|
+| Personal, all projects | `~/.claude/` | Your own preferences, global tooling |
+| Personal, this project | `.claude/` (gitignored) | Experiments, local overrides |
+| Team, this project | `.claude/` (committed) | Shared workflow, project standard |
+| Enterprise | managed policy dir | Org-wide policy |
+
+Precedence when the same thing is defined twice: managed always wins, then
+local > project > user; flags/env override files. (Full resolution table in
+[debugging-reference.md](debugging-reference.md).) Design corollary: put a thing at
+the *widest* scope where it's still correct, and never rely on a narrow-scope file to
+override a managed one — it can't.
+
+## Author it well
+
+### Descriptions are the trigger surface
+
+A model-invocable skill or subagent only fires if its description matches. Write
+**what it does + when to use + concrete trigger scenarios**, key phrases first (the
+listing is budget-capped — see skills-reference.md).
+
+```yaml
+# Good — explicit scenarios, trigger phrases up front
+description: "ECS Fargate deployment guidance. Use for: task definitions, services,
+  ALB integration, awsvpc networking, FARGATE_SPOT, Service Auto Scaling."
+
+# Poor — no trigger signal
+description: "Helps with AWS"
+```
+
+Patterns that pull their weight: `Use for: X, Y, Z` (explicit scenarios),
+`Use proactively when…` (encourages auto-delegation), `Triggers on: kw1, kw2`
+(discovery). Start **narrow** and widen as real usage reveals missed cases — a broad
+description that overlaps three other skills helps no one.
+
+### Structure
+
+**Subagent prompt** — focus areas (3–5), actionable approach principles
+("always X before Y", "prefer A over B when C"), a *measurable* quality checklist,
+named anti-patterns. Agents *generate* code; don't hard-code solutions into the prompt.
+
+**Skill body** — quick-reference table, minimal usage, "when to use", links to
+`references/` for depth. Keep `SKILL.md` lean (well under 500 lines); push detail into
+supporting files that fork in on demand.
+
+### Authoring quality bar
+
+Before shipping any extension:
+
+- [ ] `name` is kebab-case and matches the file/dir; frontmatter opens with `---`.
+- [ ] Description names concrete trigger scenarios, not a vague capability.
+- [ ] Scope is the widest one that's still correct.
+- [ ] Body/prompt is lean; depth lives in `references/`, not the always-loaded surface.
+- [ ] Subagent has principles + measurable checklist + anti-patterns, no embedded code.
+- [ ] A guarantee is enforced by a hook/permission, not merely *described* in a skill.
+- [ ] Validated (`claude plugin validate --strict` for plugins; project test runner).
+
+## Authoring pitfalls
+
+| Pitfall | Fix |
+|---------|-----|
+| Skill scope too broad / overlaps others | One technology or workflow per skill; start narrow |
+| Description has no trigger keywords | Add `Use for:` / `Triggers on:` with real phrases |
+| Detail crammed into the always-loaded body | Move to `references/`, link from the body |
+| Code baked into a subagent prompt | Describe the pattern; let the agent generate code |
+| "Be helpful" / vague principles | Concrete, testable ("always validate input") |
+| Using a skill to *enforce* an invariant | Invariants belong in hooks/permissions — skills are guidance |
+| Hook script with unquoted vars / hardcoded paths | Quote `"$VAR"`; root paths with `${CLAUDE_PROJECT_DIR}` (see hooks-reference.md) |
+
+## Official references
+
+- https://code.claude.com/docs/en/skills — Agent Skills
+- https://code.claude.com/docs/en/sub-agents — subagents
+- https://code.claude.com/docs/en/memory — memory + rules
+- https://code.claude.com/docs/en/plugins-reference — plugin schemas
+- https://www.anthropic.com/engineering/claude-code-best-practices
+- https://agentskills.io/specification — Agent Skills open standard

+ 180 - 0
skills/cloudflare-ops/SKILL.md

@@ -0,0 +1,180 @@
+---
+name: cloudflare-ops
+description: "Cloudflare Workers + Wrangler edge-platform operations - Workers runtime, bindings, local dev, secrets, deploy/CI, Pages-vs-Workers, observability. Use for: cloudflare, workers, wrangler, cloudflare pages, KV, D1, R2, durable objects, queues, hyperdrive, workers ai, vectorize, wrangler.toml, wrangler.jsonc, wrangler deploy, wrangler dev, wrangler secret, bindings, compatibility_date, static assets, edge functions, cron triggers, tail workers, gradual deployments."
+license: MIT
+allowed-tools: "Read Write Bash"
+metadata:
+  author: claude-mods
+  related-skills: "terraform-ops, nginx-ops"
+when_to_use: "Building, configuring, or deploying Cloudflare Workers; writing or fixing wrangler config; choosing or wiring a binding (KV/D1/R2/DO/Queues/Hyperdrive/AI/Vectorize); deciding Workers vs Pages; setting up local dev, secrets, or CI/CD for the edge; debugging deploy errors, CPU limits, or bundling."
+---
+
+# Cloudflare Operations
+
+Cloudflare Workers + Wrangler: runtime patterns, bindings, local dev, secrets, deploy, CI/CD, observability.
+
+**Version context (verified 2026-06):** Wrangler **v4.x** · config is **`wrangler.jsonc`** (Cloudflare's recommended format for new projects — some newer features are JSON-config-only; `wrangler.toml` still works and is widespread in older repos) · deploy command is **`wrangler deploy`** (the old **`wrangler publish` is deprecated** — see [gotchas](#common-gotchas)). Workers can now **serve static assets**, which is the current direction for full-stack and static sites over Pages (see [Workers vs Pages](#workers-vs-pages-decision)).
+
+## Reference Files
+
+| File | Covers |
+|------|--------|
+| [references/bindings.md](references/bindings.md) | Every binding (KV/D1/R2/DO/Queues/Hyperdrive/AI/Vectorize/Service/Analytics Engine) — config block, runtime API, when to reach for each, consistency model |
+| [references/workers-runtime.md](references/workers-runtime.md) | Runtime APIs, handlers (fetch/scheduled/queue/email/tail), CORS, caching, streaming, WebSockets, Durable Objects deep-dive, limits |
+| [references/deploy-and-cicd.md](references/deploy-and-cicd.md) | `wrangler deploy`, environments, secrets, Workers Builds, GitHub Actions + OIDC/API-token, gradual deployments, rollbacks, observability |
+| [assets/wrangler.jsonc.template](assets/wrangler.jsonc.template) | Commented, current `wrangler.jsonc` covering all common bindings + assets |
+
+## Workers vs Pages Decision
+
+Cloudflare added static-asset hosting to Workers; a single Worker now serves a static site, a full-stack app, or an API + SPA. **For new projects, default to Workers with static assets.** Pages still works and isn't deprecated, but Workers has the broader, faster-moving feature set (Durable Objects, Cron Triggers, Queues, richer observability) and is where Cloudflare's investment goes.
+
+```
+New project?
+│
+├─ Pure static site (no server logic)
+│  └─ Workers + assets binding (asset-only — requests matching files never invoke Worker code, $0 for those).
+│     Pages is also fine here; Workers keeps one platform if you later add logic.
+│
+├─ Full-stack / SPA + API / SSR framework (Next, Astro, Remix, SvelteKit, Hono)
+│  └─ Workers + assets + a Worker script. Use the framework's Cloudflare adapter (C3: `npm create cloudflare@latest`).
+│     This is the current recommended path — Pages' framework story is converging into Workers.
+│
+├─ Already on Pages and happy
+│  └─ Stay. "What works in Pages works in Workers" — migrate only when you need a Workers-only
+│     feature (DO, Cron, Queues, advanced observability). See the migrate-from-pages guide.
+│
+└─ Need Durable Objects / Cron Triggers / Queues / Tail Workers
+   └─ Workers (these are Workers-only).
+```
+
+**Asset serving modes** (in the `assets` block): asset-only (no `main`) serves files directly and never bills Worker invocations for matches; **assets + Worker** serves matching files first, falls through to your `fetch` handler for everything else (or set `run_worker_first` to invoke the Worker before asset matching). Reach assets from code via `env.ASSETS.fetch(request)`.
+
+## Wrangler Config Skeleton (jsonc)
+
+Full annotated version: [assets/wrangler.jsonc.template](assets/wrangler.jsonc.template).
+
+```jsonc
+{
+  "$schema": "node_modules/wrangler/config-schema.json",
+  "name": "my-worker",
+  "main": "src/index.ts",
+  "compatibility_date": "2026-06-01",   // pins the runtime version — REQUIRED, bump deliberately
+  "compatibility_flags": ["nodejs_compat"],  // opt-in runtime features (Node built-ins, etc.)
+
+  "observability": { "enabled": true },  // turn on Workers Logs (off by default)
+
+  "assets": { "directory": "./public", "binding": "ASSETS" },
+
+  "kv_namespaces": [{ "binding": "CACHE", "id": "<kv-id>" }],
+  "d1_databases": [{ "binding": "DB", "database_name": "app", "database_id": "<d1-id>" }],
+  "r2_buckets":   [{ "binding": "BUCKET", "bucket_name": "uploads" }],
+
+  "vars": { "ENVIRONMENT": "production" },  // NON-secret config only — never put secrets here
+
+  "env": {
+    "staging": { "vars": { "ENVIRONMENT": "staging" } }  // named env: deploy with --env staging
+  }
+}
+```
+
+- **`compatibility_date`** = `yyyy-mm-dd`, selects the runtime version. It's required and load-bearing: bumping it can change behaviour, so do it deliberately and test. **`compatibility_flags`** opt into upcoming/Node-compat features (e.g. `nodejs_compat`).
+- Keep secrets OUT of `vars` — they land in plaintext in the deployed config. Use `wrangler secret put` / `.dev.vars` ([secrets](#local-dev--secrets)).
+- TOML equivalent still parses; the binding shapes map 1:1 (`[[kv_namespaces]]`, `[[d1_databases]]`, …). New repos: prefer jsonc.
+
+## Bindings Table — When Each
+
+Full config + runtime API for every binding: [references/bindings.md](references/bindings.md).
+
+| Binding | Reach for it when… | Consistency / note |
+|---------|--------------------|--------------------|
+| **KV** | Read-heavy config/cache, infrequent writes, global reads | **Eventually consistent** (~60s propagation). Fast reads, slow-ish writes. Not for "read your own write". |
+| **D1** | Relational/SQL data, moderate scale, per-app database | SQLite at the edge. Strong within a DB; read replication is async. Use for app data with joins. |
+| **R2** | Object/blob storage, large files, **zero egress fees** | S3-compatible. Replaces S3 for media/backups/assets you serve. |
+| **Durable Objects** | **Strong consistency**, coordination, stateful realtime (chat, presence, game rooms, rate limit counters) | Single-threaded per object instance = serialized = consistent. The answer when KV's eventual consistency bites. SQLite-backed storage available. |
+| **Queues** | Async/background work, decoupling, batching, retries | Producer binding + consumer Worker. Smooths spikes; guaranteed delivery with retries + DLQ. |
+| **Hyperdrive** | Connecting to an **existing external Postgres/MySQL** with pooling + edge caching | Makes a regional DB feel fast from Workers. Needs `nodejs_compat`. |
+| **Workers AI** | Run inference (LLM, embeddings, image) on Cloudflare's GPUs | `ai` binding → `env.AI.run(model, ...)`. Pairs with Vectorize for RAG. |
+| **Vectorize** | Vector DB for embeddings / semantic search / RAG | `vectorize` binding. Store + query embeddings, often fed by Workers AI. |
+| **Service bindings** | Worker-to-Worker RPC without a network hop | Zero-latency internal calls; compose Workers as services. |
+
+Decision shortcut: **need strong consistency or coordination → Durable Objects. Relational queries → D1. Big files → R2. Cheap global cache → KV. Background work → Queues. External SQL DB → Hyperdrive.**
+
+## Minimal Worker
+
+```javascript
+export default {
+  async fetch(request, env, ctx) {
+    const url = new URL(request.url);
+    if (url.pathname === "/health") return Response.json({ ok: true });
+    return new Response("Hello from the edge");
+  },
+};
+```
+
+`env` carries every binding (`env.DB`, `env.CACHE`, `env.ASSETS`, secrets, vars). `ctx.waitUntil(promise)` runs background work after the response is sent. Workers require **ES module** format (`export default { fetch }`) — the old service-worker `addEventListener("fetch")` format is legacy. Full handler patterns (scheduled/queue/email/tail, CORS, caching, WebSockets, DO): [references/workers-runtime.md](references/workers-runtime.md).
+
+## Local Dev & Secrets
+
+```bash
+npm create cloudflare@latest my-app   # C3 scaffolder — picks framework + adapter + wrangler.jsonc
+wrangler dev                          # local dev server (Miniflare/workerd) on localhost:8787
+wrangler dev --remote                 # run on Cloudflare's edge (real bindings) instead of local sim
+wrangler types                        # generate TS types for env from your bindings → worker-configuration.d.ts
+```
+
+**Secrets** (never in `vars`):
+
+| Where | Mechanism |
+|-------|-----------|
+| Local dev | **`.dev.vars`** file (dotenv format, gitignored) — `wrangler dev` loads it as `env.*`. Per-env: `.dev.vars.staging`. |
+| Deployed | **`wrangler secret put NAME`** (prompts for value, encrypts it) · `wrangler secret list` · `wrangler secret delete NAME` |
+| CI bulk | `wrangler secret bulk secrets.json` |
+| Newer | Cloudflare **Secrets Store** bindings (account-level shared secrets) — see deploy reference |
+
+Add `.dev.vars*` to `.gitignore`. `vars` in config = plaintext public config; secrets are encrypted and write-only.
+
+## Deploy & CI/CD
+
+Full detail: [references/deploy-and-cicd.md](references/deploy-and-cicd.md).
+
+```bash
+wrangler deploy                  # build + upload + activate (NOT `wrangler publish` — deprecated)
+wrangler deploy --env staging    # deploy a named environment
+wrangler versions upload         # upload a new version WITHOUT making it live (gradual deploys)
+wrangler versions deploy         # split traffic across versions (e.g. 10% new / 90% old)
+wrangler rollback                # revert to the previous deployed version
+wrangler tail                    # stream live logs from the deployed Worker
+```
+
+- **Workers Builds** — Cloudflare's native git-connected CI: push to GitHub/GitLab, Cloudflare builds + deploys. Zero-config for simple Workers; the default for most teams.
+- **GitHub Actions** — `cloudflare/wrangler-action`. Authenticate with a scoped **API token** (`CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` as secrets), least-privilege (Workers Scripts:Edit). Template + workflow in the deploy reference.
+- **Gradual deployments** — `versions upload` then `versions deploy` to shift a percentage of traffic; instant `rollback` if metrics regress.
+
+## Observability
+
+- `"observability": { "enabled": true }` in config turns on **Workers Logs** (structured `console.log` capture in the dashboard) — **off by default**, opt in.
+- `wrangler tail` for live request log streaming during an incident.
+- **Tail Workers** — a Worker that receives execution traces of another Worker (centralised logging/alerting).
+- **Analytics Engine** — write custom time-series metrics from a Worker (`env.AE.writeDataPoint(...)`), query via GraphQL/SQL API.
+
+## Common Gotchas
+
+| Gotcha | Detail | Fix |
+|--------|--------|-----|
+| **`wrangler publish` is gone** | Renamed to `wrangler deploy` (Wrangler v3+). Old tutorials/CI still say `publish`. | Use `wrangler deploy`. Update any `publish` in scripts/CI. |
+| **`wrangler.toml` vs `.jsonc`** | Both parse, but newer features are JSON-config-only and Cloudflare recommends jsonc for new projects. | New projects: `wrangler.jsonc`. Migrating: `wrangler.toml` → jsonc is a mechanical 1:1. |
+| **Missing `compatibility_date`** | Required; absent or stale date silently pins old runtime behaviour. | Set it; bump deliberately and test — it can change semantics. |
+| **CPU time limit** | Default **30s** CPU per invocation (was 10ms/50ms historically; raised). Wall-clock can be longer while awaiting I/O. CPU-bound loops still get killed. | Offload heavy compute; use Queues for long async work; check the limits page for your plan. |
+| **Script size limit** | 3 MB (free) / 10 MB (paid) gzipped. | Trim deps, dynamic-import large modules, avoid bundling node-only libs. |
+| **KV eventual consistency** | A write isn't globally visible for up to ~60s; not "read your own write". | Use **Durable Objects** when you need strong consistency. |
+| **Node built-ins fail** | `fs`, `crypto`, etc. aren't there by default. | `"compatibility_flags": ["nodejs_compat"]` enables a polyfill subset; check what's actually supported. |
+| **Secrets in `vars`** | `vars` ships plaintext in the deployed config. | `wrangler secret put` (deployed) / `.dev.vars` (local). |
+| **`request`/`response` body read twice** | Streams are single-use. | `request.clone()` before the first read. |
+| **Bundling surprises** | Wrangler uses esbuild; some packages assume Node/CommonJS. | Prefer Workers-compatible libs; set `nodejs_compat`; check the build output. |
+
+## Setup
+
+1. Install: `npm install -g wrangler` (or use `npx wrangler` / `npm create cloudflare@latest` to scaffold).
+2. Auth: `wrangler login` (OAuth) for local; **API token** for CI.
+3. Copy [assets/wrangler.jsonc.template](assets/wrangler.jsonc.template), strip the bindings you don't need, fill in IDs.
+4. `wrangler dev` → `wrangler deploy`.

+ 133 - 0
skills/cloudflare-ops/assets/wrangler.jsonc.template

@@ -0,0 +1,133 @@
+{
+  // Cloudflare Workers config — current format (Wrangler v4, verified 2026-06).
+  // Cloudflare recommends wrangler.jsonc for new projects; some newer features are
+  // JSON-config-only. The legacy wrangler.toml still parses (binding shapes map 1:1).
+  //
+  // Strip the bindings you don't use. Fill <placeholders> with real IDs
+  // (get them from `wrangler kv namespace create`, `wrangler d1 create`, etc.).
+  // Deploy with `wrangler deploy` (NOT the deprecated `wrangler publish`).
+
+  // Enables editor autocompletion + validation of this file.
+  "$schema": "node_modules/wrangler/config-schema.json",
+
+  "name": "my-worker",            // Worker name (becomes part of the *.workers.dev subdomain)
+  "main": "src/index.ts",         // entry module — omit entirely for an asset-only (static) Worker
+
+  // REQUIRED. Pins the Workers runtime version. Bump deliberately and test —
+  // a newer date can change runtime behaviour. Format: yyyy-mm-dd.
+  "compatibility_date": "2026-06-01",
+
+  // Opt into upcoming/Node-compat runtime features. "nodejs_compat" enables a
+  // subset of Node built-ins (node:crypto, node:buffer, ...). Hyperdrive needs it.
+  "compatibility_flags": ["nodejs_compat"],
+
+  // Workers Logs — captures console.log/errors in the dashboard. OFF by default; opt in.
+  "observability": { "enabled": true, "head_sampling_rate": 1 },
+
+  // Explicit CPU budget (ms). Paid plans allow up to 30000. Omit for the default.
+  // "limits": { "cpu_ms": 50 },
+
+  // --- Static assets (the current way to serve a static site / SPA from a Worker) ---
+  // Asset-only: keep this block, REMOVE "main" above — files are served directly,
+  //   matching requests never invoke (or bill) Worker code.
+  // Assets + Worker: keep both — assets served first, your fetch() handles the rest;
+  //   reach files from code via env.ASSETS.fetch(request).
+  "assets": {
+    "directory": "./public",
+    "binding": "ASSETS",
+    "html_handling": "auto-trailing-slash",
+    // "single-page-application" → serve index.html for unmatched routes (SPA routing)
+    // "404-page" → serve a custom /404.html ; "none" → plain 404
+    "not_found_handling": "single-page-application"
+    // "run_worker_first": true   // run the Worker BEFORE asset matching (auth gates, rewrites)
+  },
+
+  // --- Non-secret config. NEVER put secrets here (ships plaintext). ---
+  // Secrets: `wrangler secret put NAME` (deployed) / .dev.vars file (local).
+  "vars": {
+    "ENVIRONMENT": "production"
+  },
+
+  // --- Bindings (delete the ones you don't need) ---
+
+  // KV — eventually-consistent (~60s) global key-value. Cache/config, read-heavy.
+  "kv_namespaces": [
+    { "binding": "CACHE", "id": "<kv-namespace-id>", "preview_id": "<kv-preview-id>" }
+  ],
+
+  // D1 — SQLite at the edge. Relational app data. Always use .bind() params.
+  "d1_databases": [
+    { "binding": "DB", "database_name": "app", "database_id": "<d1-database-id>" }
+  ],
+
+  // R2 — S3-compatible object storage, zero egress fees. Files/blobs/media.
+  "r2_buckets": [
+    { "binding": "BUCKET", "bucket_name": "uploads" }
+  ],
+
+  // Durable Objects — strong consistency + coordination (realtime, counters, locks).
+  // Requires a migration to register the class (see "migrations" below).
+  "durable_objects": {
+    "bindings": [
+      { "name": "ROOM", "class_name": "ChatRoom" }
+    ]
+  },
+  "migrations": [
+    { "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }  // use new_classes for non-SQLite DOs
+  ],
+
+  // Queues — async background work. Producer binding + consumer handler.
+  "queues": {
+    "producers": [
+      { "binding": "JOBS", "queue": "jobs" }
+    ],
+    "consumers": [
+      { "queue": "jobs", "max_batch_size": 10, "max_batch_timeout": 30, "dead_letter_queue": "jobs-dlq" }
+    ]
+  },
+
+  // Hyperdrive — pooled + cached access to an EXISTING external Postgres/MySQL.
+  // Needs "nodejs_compat" (above) and a Workers-compatible driver.
+  "hyperdrive": [
+    { "binding": "HYPERDRIVE", "id": "<hyperdrive-config-id>" }
+  ],
+
+  // Workers AI — inference on Cloudflare GPUs. Single object binding (no array).
+  "ai": { "binding": "AI" },
+
+  // Vectorize — vector DB for embeddings / RAG.
+  "vectorize": [
+    { "binding": "INDEX", "index_name": "docs" }
+  ],
+
+  // Service bindings — zero-latency Worker-to-Worker RPC.
+  "services": [
+    { "binding": "AUTH", "service": "auth-worker" }
+  ],
+
+  // Analytics Engine — custom time-series metrics from the Worker.
+  "analytics_engine_datasets": [
+    { "binding": "AE", "dataset": "my_metrics" }
+  ],
+
+  // Cron triggers — scheduled() handler.
+  "triggers": {
+    "crons": ["0 0 * * *"]
+  },
+
+  // Custom routes (serve on your own domain instead of *.workers.dev).
+  // "routes": [
+  //   { "pattern": "example.com/api/*", "zone_name": "example.com" }
+  // ],
+
+  // --- Named environments: override vars/bindings per target. Deploy: `wrangler deploy --env staging`.
+  // Bindings are NOT inherited — redeclare what each env needs.
+  "env": {
+    "staging": {
+      "vars": { "ENVIRONMENT": "staging" },
+      "kv_namespaces": [
+        { "binding": "CACHE", "id": "<staging-kv-namespace-id>" }
+      ]
+    }
+  }
+}

+ 226 - 0
skills/cloudflare-ops/references/bindings.md

@@ -0,0 +1,226 @@
+# Cloudflare Bindings — config + runtime API
+
+A *binding* is a capability injected into `env` at runtime. You declare it in `wrangler.jsonc`; the runtime hands you a live client. All config snippets are jsonc; the TOML form is a mechanical translation (`kv_namespaces` → `[[kv_namespaces]]`, nested objects → `[table]`).
+
+Verified 2026-06 against developers.cloudflare.com/workers/wrangler/configuration.
+
+## Quick chooser
+
+| Need | Binding |
+|------|---------|
+| Global cache / config, read-heavy, writes rare | **KV** |
+| Relational data, SQL, joins | **D1** |
+| Files / blobs / media, no egress fee | **R2** |
+| Strong consistency, coordination, realtime state | **Durable Objects** |
+| Background jobs, batching, retries | **Queues** |
+| Existing external Postgres/MySQL | **Hyperdrive** |
+| Model inference (LLM/embeddings/image) | **Workers AI** |
+| Vector search / RAG | **Vectorize** |
+| Worker-to-Worker RPC | **Service binding** |
+| Custom metrics | **Analytics Engine** |
+| Static files | **Assets** |
+
+---
+
+## KV — eventually-consistent key-value
+
+```jsonc
+{ "kv_namespaces": [{ "binding": "CACHE", "id": "<namespace-id>", "preview_id": "<preview-id>" }] }
+```
+
+```javascript
+await env.CACHE.put("key", "value", { expirationTtl: 3600, metadata: { v: 1 } });
+const v   = await env.CACHE.get("key");                  // string | null
+const j   = await env.CACHE.get("key", { type: "json" });
+const { value, metadata } = await env.CACHE.getWithMetadata("key");
+const list = await env.CACHE.list({ prefix: "user:" });
+await env.CACHE.delete("key");
+```
+
+- **Eventually consistent**: a write propagates globally in up to ~60s. Reading your own write from another colo may return stale. Not a database — a cache/config store.
+- Reads fast (cached at edge); writes + `list` are comparatively expensive. Use TTLs; avoid hot `list` in the request path.
+- CLI: `wrangler kv namespace create CACHE`, `wrangler kv key put --binding=CACHE k v`.
+
+## D1 — SQLite at the edge
+
+```jsonc
+{ "d1_databases": [{ "binding": "DB", "database_name": "app", "database_id": "<d1-id>" }] }
+```
+
+```javascript
+const { results } = await env.DB.prepare("SELECT * FROM users WHERE id = ?").bind(id).all();
+const row = await env.DB.prepare("SELECT * FROM users WHERE id = ?").bind(id).first();
+await env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind(name).run();
+await env.DB.batch([stmt1, stmt2]);   // batched, atomic
+```
+
+- Always use **`.bind()`** parameters — never string-interpolate SQL.
+- Strong consistency within the primary; read replication (Sessions API / read replicas) is async — opt in when you need it.
+- Migrations: `wrangler d1 migrations create` / `apply`. Local: `wrangler d1 execute DB --local --file=schema.sql`.
+
+## R2 — S3-compatible object storage, zero egress
+
+```jsonc
+{ "r2_buckets": [{ "binding": "BUCKET", "bucket_name": "uploads" }] }
+```
+
+```javascript
+await env.BUCKET.put("path/file.png", request.body, { httpMetadata: { contentType: "image/png" } });
+const obj = await env.BUCKET.get("path/file.png");
+if (obj) return new Response(obj.body, { headers: { "etag": obj.httpEtag } });
+await env.BUCKET.delete("path/file.png");
+const listed = await env.BUCKET.list({ prefix: "path/" });
+```
+
+- **No egress fees** — the reason to move media/backups/static delivery off S3.
+- S3 API compatible (use existing S3 SDKs against the R2 endpoint for external access).
+- Pair with the Cache API or a custom domain for public serving.
+
+## Durable Objects — strong consistency + coordination
+
+```jsonc
+{
+  "durable_objects": { "bindings": [{ "name": "ROOM", "class_name": "ChatRoom" }] },
+  "migrations": [{ "tag": "v1", "new_sqlite_classes": ["ChatRoom"] }]
+}
+```
+
+```javascript
+export class ChatRoom {
+  constructor(state, env) { this.state = state; this.env = env; }
+  async fetch(request) {
+    let count = (await this.state.storage.get("count")) || 0;
+    await this.state.storage.put("count", ++count);   // serialized — no races
+    return Response.json({ count });
+  }
+}
+
+// From another Worker:
+const id   = env.ROOM.idFromName("room-42");
+const stub = env.ROOM.get(id);
+const res  = await stub.fetch(request);
+```
+
+- **Single-threaded per object instance** → operations on one object serialize → strong consistency. This is the answer when KV's eventual consistency hurts (counters, locks, presence, realtime rooms, rate limiters).
+- Storage: transactional KV-style API, or **SQLite-backed** DO storage (`new_sqlite_classes` in migrations) for relational state per object.
+- Migrations are required to register/rename/delete DO classes — `tag` each migration; never edit an applied one.
+- WebSocket hibernation API lets idle connections sleep without billing.
+
+## Queues — async background work
+
+```jsonc
+{
+  "queues": {
+    "producers": [{ "binding": "JOBS", "queue": "jobs" }],
+    "consumers": [{ "queue": "jobs", "max_batch_size": 10, "max_batch_timeout": 30, "dead_letter_queue": "jobs-dlq" }]
+  }
+}
+```
+
+```javascript
+export default {
+  async fetch(req, env)  { await env.JOBS.send({ task: "resize", id: 7 }); return new Response("queued"); },
+  async queue(batch, env) {
+    for (const msg of batch.messages) {
+      try { await handle(msg.body); msg.ack(); }
+      catch { msg.retry(); }            // retried; exhausted → dead-letter queue
+    }
+  },
+};
+```
+
+- Decouples spikes from processing; guaranteed delivery with retries + DLQ.
+- Batch settings tune throughput vs latency. `ack()`/`retry()` per message or per batch.
+
+## Hyperdrive — pooled, cached access to external SQL
+
+```jsonc
+{
+  "compatibility_flags": ["nodejs_compat"],
+  "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<hyperdrive-config-id>" }]
+}
+```
+
+```javascript
+import postgres from "postgres";
+const sql = postgres(env.HYPERDRIVE.connectionString);
+const rows = await sql`SELECT * FROM orders WHERE id = ${id}`;
+```
+
+- Fronts an **existing** regional Postgres/MySQL with connection pooling + edge query caching, so a far-away DB feels fast from Workers.
+- Requires `nodejs_compat`. Use a Workers-compatible driver (`postgres`, `pg` with compat, `mysql2`).
+- Not a database itself — it's an accelerator for one you already run (RDS, Neon, Supabase, etc.).
+
+## Workers AI — inference on Cloudflare GPUs
+
+```jsonc
+{ "ai": { "binding": "AI" } }
+```
+
+```javascript
+const out = await env.AI.run("@cf/meta/llama-3.1-8b-instruct", { prompt: "Hello" });
+const emb = await env.AI.run("@cf/baai/bge-base-en-v1.5", { text: ["doc one", "doc two"] });
+```
+
+- Single `ai` object binding (no array). Run text-gen, embeddings, image, speech models by ID.
+- Feed embeddings into **Vectorize** for RAG.
+
+## Vectorize — vector database
+
+```jsonc
+{ "vectorize": [{ "binding": "INDEX", "index_name": "docs" }] }
+```
+
+```javascript
+await env.INDEX.upsert([{ id: "1", values: embedding, metadata: { url } }]);
+const matches = await env.INDEX.query(queryEmbedding, { topK: 5, returnMetadata: true });
+```
+
+- Store + similarity-search embeddings for semantic search / RAG. Commonly fed by Workers AI embeddings.
+- CLI: `wrangler vectorize create docs --dimensions=768 --metric=cosine`.
+
+## Service bindings — Worker-to-Worker RPC
+
+```jsonc
+{ "services": [{ "binding": "AUTH", "service": "auth-worker", "entrypoint": "AuthEntrypoint" }] }
+```
+
+```javascript
+const ok = await env.AUTH.verify(token);          // RPC method (WorkerEntrypoint) — no network hop
+const res = await env.AUTH.fetch(internalRequest); // or HTTP-style
+```
+
+- Zero-latency internal calls; compose a system as multiple Workers. Supports RPC method calls (via `WorkerEntrypoint`) or `fetch`-style.
+
+## Analytics Engine — custom metrics
+
+```jsonc
+{ "analytics_engine_datasets": [{ "binding": "AE", "dataset": "my_metrics" }] }
+```
+
+```javascript
+env.AE.writeDataPoint({ blobs: [country], doubles: [latencyMs], indexes: [route] });
+```
+
+- Write high-cardinality time-series from a Worker; query via the GraphQL/SQL Analytics API. Cheap, sampled, fast.
+
+## Assets — static files
+
+```jsonc
+{ "assets": { "directory": "./public", "binding": "ASSETS",
+              "html_handling": "auto-trailing-slash", "not_found_handling": "single-page-application" } }
+```
+
+```javascript
+// In a Worker with both `main` and `assets`:
+export default {
+  async fetch(request, env) {
+    if (new URL(request.url).pathname.startsWith("/api/")) return handleApi(request, env);
+    return env.ASSETS.fetch(request);   // serve static files
+  },
+};
+```
+
+- Asset-only (omit `main`) = pure static host; matching requests never invoke Worker code (and aren't billed as invocations).
+- `not_found_handling: "single-page-application"` serves `index.html` for unmatched routes (SPA routing); `"404-page"` serves a custom 404.
+- `run_worker_first: true` runs your Worker before asset matching (for auth gates, rewrites).

+ 128 - 0
skills/cloudflare-ops/references/deploy-and-cicd.md

@@ -0,0 +1,128 @@
+# Deploy, Environments, Secrets, CI/CD, Observability
+
+Verified 2026-06. **The deploy command is `wrangler deploy`. `wrangler publish` is deprecated** (renamed in Wrangler v3; v4 is current) — update any CI/script still calling `publish`.
+
+## Core commands
+
+```bash
+wrangler deploy                 # build + upload + activate the Worker
+wrangler deploy --env staging   # deploy the "staging" named environment
+wrangler deploy --dry-run --outdir=dist   # build only, inspect bundle, no upload
+wrangler dev                    # local dev (workerd/Miniflare) at localhost:8787
+wrangler dev --remote           # run on Cloudflare's edge with real bindings
+wrangler tail                   # stream live logs from the deployed Worker
+wrangler types                  # generate worker-configuration.d.ts from bindings
+wrangler delete                 # remove the deployed Worker
+```
+
+## Environments
+
+Named environments share one config file; each can override `vars`, bindings, routes, name.
+
+```jsonc
+{
+  "name": "my-worker",
+  "vars": { "ENVIRONMENT": "production" },
+  "env": {
+    "staging": {
+      "vars": { "ENVIRONMENT": "staging" },
+      "kv_namespaces": [{ "binding": "CACHE", "id": "<staging-kv-id>" }]
+    }
+  }
+}
+```
+
+- Deploy: `wrangler deploy` (top-level / production) vs `wrangler deploy --env staging`.
+- The deployed Worker is named `my-worker` for top-level and `my-worker-staging` for the named env (unless you override `name` inside the env).
+- Bindings are NOT inherited into named envs by default — redeclare what each env needs.
+
+## Secrets
+
+| Scope | How |
+|-------|-----|
+| Local dev | **`.dev.vars`** (dotenv, gitignored). `wrangler dev` injects as `env.*`. Per-env: `.dev.vars.staging`. |
+| Deployed | `wrangler secret put NAME` (prompts, encrypts) · `wrangler secret list` · `wrangler secret delete NAME` |
+| Named env | `wrangler secret put NAME --env staging` |
+| Bulk (CI) | `wrangler secret bulk secrets.json` |
+| Account-shared | **Secrets Store** bindings — define a secret once at the account level, bind it into multiple Workers |
+
+```jsonc
+// Secrets Store binding
+{ "secrets_store_secrets": [{ "binding": "API_KEY", "store_id": "<id>", "secret_name": "api-key" }] }
+```
+
+Rules: never put secrets in `vars` (plaintext in deployed config). Gitignore `.dev.vars*`. Rotate via `secret put` (overwrites).
+
+## Workers Builds (native CI)
+
+Cloudflare's git-connected build+deploy: connect a GitHub/GitLab repo in the dashboard, Cloudflare runs your build command and `wrangler deploy` on push. Zero extra CI for most projects; supports per-branch preview deployments, build env vars, and monorepo build paths. Default choice unless you need custom CI steps.
+
+## GitHub Actions
+
+Use `cloudflare/wrangler-action`. Authenticate with a **scoped API token**, not your global key.
+
+```yaml
+name: deploy
+on:
+  push:
+    branches: [main]
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@<pinned-sha>
+      - uses: actions/setup-node@<pinned-sha>
+        with: { node-version: 20 }
+      - run: npm ci
+      - uses: cloudflare/wrangler-action@<pinned-sha>
+        with:
+          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+          command: deploy
+          # secrets: |          # optional: push secrets at deploy time
+          #   API_KEY
+        # env:
+        #   API_KEY: ${{ secrets.API_KEY }}
+```
+
+**API token scope (least privilege):** create a token with `Account > Workers Scripts > Edit` (plus `Workers KV/R2/D1` edit if the deploy provisions them). Store `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` as repo secrets. Pin action SHAs (supply-chain hygiene), not floating tags.
+
+Cloudflare does not offer OIDC trusted-publishing for Workers deploys today — the auth path is a scoped API token. Keep it least-privilege and rotate it; treat it like a publish credential.
+
+## Gradual deployments + rollback
+
+```bash
+wrangler versions upload          # upload a new version, NOT live (0% traffic)
+wrangler versions deploy          # interactively split traffic across versions (e.g. 10% new / 90% old)
+wrangler versions list            # see versions + their traffic split
+wrangler rollback [VERSION_ID]    # revert to a previous version instantly
+```
+
+Pattern: `versions upload` → `versions deploy` at 10% → watch metrics/logs → ramp to 100%, or `rollback` if it regresses. This is the safe path for risky changes vs a straight `deploy` (100% instantly).
+
+## Observability
+
+| Tool | Use |
+|------|-----|
+| **Workers Logs** | `"observability": { "enabled": true }` in config — captures `console.log`/errors, queryable in dashboard. **Off by default.** Sampling configurable (`head_sampling_rate`). |
+| `wrangler tail` | Live log stream during an incident / local debugging of prod. |
+| **Tail Workers** | A Worker bound to receive execution traces of another Worker — centralised logging, alerting, forwarding to a SIEM. |
+| **Analytics Engine** | `env.AE.writeDataPoint(...)` for custom high-cardinality metrics; query via GraphQL/SQL Analytics API. |
+| Dashboard metrics | Per-Worker requests, errors, CPU time, subrequests — built in, no setup. |
+
+```jsonc
+{ "observability": { "enabled": true, "head_sampling_rate": 1 } }
+```
+
+## Deploy safety checklist
+
+```
+□ `wrangler deploy` (not publish) — CI updated
+□ compatibility_date present and intentional (bumping changes runtime behaviour)
+□ secrets via `secret put` / Secrets Store — none in `vars`
+□ bindings declared for the target env (named envs don't inherit)
+□ risky change → versions upload + gradual deploy, not 100% deploy
+□ observability enabled so you can see errors after rollout
+□ API token scoped least-privilege; action SHAs pinned in the workflow
+□ rollback path known: `wrangler rollback`
+```

+ 154 - 0
skills/cloudflare-ops/references/workers-runtime.md

@@ -0,0 +1,154 @@
+# Workers Runtime — handlers, APIs, patterns, limits
+
+The Workers runtime is **workerd** (open source), running V8 isolates at the edge. It implements web-platform APIs (`Request`/`Response`/`fetch`/`URL`/`crypto.subtle`/streams) — not Node, unless `nodejs_compat` is set. Workers use **ES module** format; the legacy service-worker (`addEventListener("fetch")`) format is deprecated.
+
+## Handlers
+
+```javascript
+export default {
+  async fetch(request, env, ctx)    { /* HTTP requests */ },
+  async scheduled(event, env, ctx)  { /* cron triggers */ },
+  async queue(batch, env, ctx)      { /* queue consumer */ },
+  async email(message, env, ctx)    { /* Email Workers */ },
+  async tail(events, env, ctx)      { /* Tail Worker — traces of another Worker */ },
+};
+```
+
+- **`env`** — all bindings, vars, secrets.
+- **`ctx.waitUntil(p)`** — keep the isolate alive for background work after the response is returned (analytics, cache writes).
+- **`ctx.passThroughOnException()`** — on an unhandled error, fall through to origin instead of erroring.
+
+### Scheduled (cron)
+
+```jsonc
+{ "triggers": { "crons": ["0 0 * * *", "*/15 * * * *"] } }
+```
+
+```javascript
+async scheduled(event, env, ctx) {
+  ctx.waitUntil(cleanup(env));   // event.cron tells you which schedule fired
+}
+```
+
+Test locally: `wrangler dev --test-scheduled` then `curl "localhost:8787/__scheduled?cron=0+0+*+*+*"`.
+
+## CORS
+
+```javascript
+const CORS = {
+  "Access-Control-Allow-Origin": "https://app.example.com",   // prefer an explicit origin over "*"
+  "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
+  "Access-Control-Allow-Headers": "Content-Type, Authorization",
+};
+export default {
+  async fetch(request) {
+    if (request.method === "OPTIONS") return new Response(null, { headers: CORS });
+    const res = Response.json({ ok: true });
+    for (const [k, v] of Object.entries(CORS)) res.headers.set(k, v);
+    return res;
+  },
+};
+```
+
+If you send credentials, you cannot use `*` — echo a validated origin and add `Access-Control-Allow-Credentials: true`.
+
+## Cache API
+
+```javascript
+async fetch(request, env, ctx) {
+  const cache = caches.default;
+  let res = await cache.match(request);
+  if (!res) {
+    res = await fetch(request);
+    res = new Response(res.body, res);
+    res.headers.set("Cache-Control", "public, max-age=3600");
+    ctx.waitUntil(cache.put(request, res.clone()));   // clone — body is single-use
+  }
+  return res;
+}
+```
+
+The Cache API is per-colo (not global). For global caching use Cloudflare's CDN/Cache Rules at the zone level, or KV for app-controlled cache.
+
+## Streaming
+
+```javascript
+// Stream rather than buffer large bodies
+const { readable, writable } = new TransformStream();
+streamInto(writable);                       // write chunks asynchronously
+return new Response(readable, { headers: { "Content-Type": "application/octet-stream" } });
+```
+
+`Response` accepts a `ReadableStream`; stream from R2 (`obj.body`) or `fetch` directly to keep memory flat.
+
+## WebSockets
+
+```javascript
+async fetch(request) {
+  if (request.headers.get("Upgrade") !== "websocket")
+    return new Response("expected websocket", { status: 426 });
+  const [client, server] = Object.values(new WebSocketPair());
+  server.accept();
+  server.addEventListener("message", (e) => server.send(`echo: ${e.data}`));
+  return new Response(null, { status: 101, webSocket: client });
+}
+```
+
+For stateful/multi-client sockets (chat, presence) terminate them in a **Durable Object** and use the **WebSocket Hibernation API** so idle connections don't bill compute.
+
+## Body reuse
+
+Streams are single-use. To read a body twice, `clone()` before the first read:
+
+```javascript
+const copy = request.clone();
+const text = await request.text();
+const json = await copy.json();
+```
+
+## Error handling
+
+```javascript
+async fetch(request, env, ctx) {
+  try {
+    return Response.json({ data: await work(env) });
+  } catch (err) {
+    console.error("worker error", err);   // captured by Workers Logs when observability is on
+    return Response.json({ error: err.message }, { status: 500 });
+  }
+}
+```
+
+## Subrequests, timeouts, abort
+
+```javascript
+const ctrl = new AbortController();
+const t = setTimeout(() => ctrl.abort(), 5000);
+try {
+  return await fetch(url, { signal: ctrl.signal });
+} catch (e) {
+  if (e.name === "AbortError") return new Response("upstream timeout", { status: 504 });
+  throw e;
+} finally { clearTimeout(t); }
+```
+
+## Limits (check the live limits page — these move)
+
+| Limit | Free | Paid |
+|-------|------|------|
+| CPU time / invocation | 10 ms default, configurable | up to **30 s** (raised from the old 50 ms; set via limits config) |
+| Script size (gzipped) | 3 MB | 10 MB |
+| Subrequests / invocation | 50 | 1000+ |
+| Memory | 128 MB | 128 MB |
+| Env vars + secrets | bounded | bounded |
+
+- CPU time ≠ wall-clock: awaiting I/O doesn't burn CPU budget; a tight compute loop does and gets killed.
+- Configure CPU limit explicitly: `"limits": { "cpu_ms": 50 }` (or higher on paid).
+
+## Node compatibility
+
+`"compatibility_flags": ["nodejs_compat"]` enables a subset of Node built-ins (`node:crypto`, `node:buffer`, `node:async_hooks`, streams, etc.). Not everything is polyfilled — verify the specific module is supported rather than assuming. Many "Node" npm packages work once this flag is on; some assume `fs`/native addons and won't.
+
+## Bundling
+
+Wrangler bundles with esbuild. Pitfalls: CommonJS-only packages, packages that reach for Node natives, and large transitive deps blowing the size limit. Prefer Workers-/edge-labelled libraries; dynamic-import heavy modules so they're only pulled when used; inspect the build output if a deploy is unexpectedly large.

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


+ 2 - 1
skills/container-orchestration/SKILL.md

@@ -1,6 +1,6 @@
 ---
 name: container-orchestration
-description: "Docker and Kubernetes patterns. Triggers on: Dockerfile, docker-compose, kubernetes, k8s, helm, pod, deployment, service, ingress, container, image."
+description: "Docker, Kubernetes, and AWS ECS/Fargate patterns. Triggers on: Dockerfile, docker-compose, kubernetes, k8s, helm, pod, deployment, service, ingress, container, image, ecs, fargate, task definition, ecs service, awsvpc, FARGATE_SPOT, ALB, ecs vs kubernetes."
 license: MIT
 compatibility: "Docker 20+, Kubernetes 1.25+, Helm 3+"
 allowed-tools: "Read Write Bash"
@@ -222,6 +222,7 @@ spec:
 - `./references/dockerfile-patterns.md` - Advanced Dockerfile techniques
 - `./references/k8s-manifests.md` - Full Kubernetes manifest examples
 - `./references/helm-patterns.md` - Helm chart structure and values
+- `./references/ecs-fargate.md` - Amazon ECS on AWS Fargate (task definitions, services, awsvpc networking, IAM roles, secrets, scaling, ALB/NLB, ECS vs Kubernetes)
 
 ## Scripts
 

+ 130 - 0
skills/container-orchestration/references/ecs-fargate.md

@@ -0,0 +1,130 @@
+# Amazon ECS on AWS Fargate
+
+The rest of this skill covers Docker and Kubernetes. ECS on Fargate is a distinct
+orchestrator: AWS-native, serverless containers — no nodes to patch, no kubelet, no
+control plane to run. Reach here for production-grade Fargate workloads.
+
+Verify specifics against the canonical sources (links per section); ECS features move
+across platform versions.
+
+## ECS vs Kubernetes — when to pick Fargate
+
+| | ECS on Fargate | Kubernetes (EKS / self-managed) |
+|---|---|---|
+| Ops burden | None — no nodes, no control plane | You run/patch nodes (EKS manages the control plane) |
+| Portability | AWS-only | Portable across clouds |
+| Ecosystem | AWS-native (ALB, IAM, CloudWatch, Secrets Manager) | Huge CNCF ecosystem, Helm, operators |
+| Learning curve | Low | High |
+| Best for | AWS-committed teams wanting minimal infra ops | Multi-cloud, complex scheduling, existing k8s investment |
+
+Pick Fargate when you're AWS-committed and want to stop managing container hosts.
+Pick Kubernetes when you need portability, advanced scheduling, or already run k8s.
+Note **EKS can also run on Fargate** — that's k8s pods on serverless capacity, a
+different product from the ECS-on-Fargate covered here.
+
+## Core building blocks
+
+- **Task definition** — the blueprint: container image(s), CPU/memory at the task
+  level (Fargate requires valid CPU/memory pairs, e.g. 0.25 vCPU / 0.5 GB up to
+  16 vCPU / 120 GB), `awsvpc` network mode (mandatory on Fargate), log config, the
+  two IAM roles (below), and `secrets` mappings.
+- **Service** — keeps N task copies running, registers them with a load balancer,
+  handles rolling or blue/green deploys, and integrates Service Auto Scaling and
+  Service Connect / service discovery.
+- **Cluster** — logical grouping; with Fargate it's effectively just a namespace
+  plus a capacity-provider strategy.
+- **Capacity providers** — `FARGATE` (on-demand) and `FARGATE_SPOT` (interruptible,
+  up to ~70% cheaper). Mix them with a strategy (e.g. a base of FARGATE plus a
+  weighted FARGATE_SPOT layer) to cut cost while protecting a reliable baseline.
+
+Architecting for AWS Fargate:
+https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html ·
+ECS Developer Guide:
+https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html
+
+## Networking (awsvpc)
+
+Every Fargate task gets its own ENI with a private IP — security groups attach to the
+*task*, not a shared host.
+
+- Run tasks in **private subnets**; reach the internet via a NAT Gateway, or skip NAT
+  with **VPC interface endpoints** (ECR, Secrets Manager, CloudWatch Logs, etc.) to
+  cut NAT cost and keep traffic on the AWS backbone.
+- **Security groups**: the task SG is the source. Common gotcha — a task can't reach
+  RDS even though SGs "look right": the RDS SG must allow inbound *from the task's SG*
+  (reference the SG id, not a CIDR), and the task must be in a subnet with a route to
+  RDS.
+- **Load balancing**: **ALB** for HTTP/HTTPS (path/host routing, TLS termination) —
+  the default for web services; **NLB** for TCP/UDP, ultra-low latency, or a static
+  IP. The service registers task ENIs into the target group as `ip` targets.
+- **Service Connect** (preferred) or ECS Service Discovery for service-to-service
+  communication with built-in naming and health.
+
+## IAM — two distinct roles
+
+| Role | Used by | Grants |
+|------|---------|--------|
+| **Task execution role** | the ECS agent, at launch | Pull the image from ECR, write logs, fetch `secrets` from Secrets Manager / SSM |
+| **Task role** | your application, at runtime | The app's own AWS permissions (S3, DynamoDB, SQS…) |
+
+Keep them separate and least-privilege. Don't grant the app's S3 access on the
+execution role, and don't put image-pull permission on the task role.
+
+## Secrets
+
+Inject via the task definition `secrets` block from **Secrets Manager** or **SSM
+Parameter Store** — values land as env vars at start, never baked into the image or
+committed. The *execution role* needs read access to the secret (and to the KMS key
+if the secret is CMK-encrypted).
+
+## Scaling & cost
+
+- **Service Auto Scaling** via Application Auto Scaling: target tracking (e.g. hold
+  CPU at 60%, or scale on ALB requests-per-target), step scaling, or scheduled
+  scaling for known peaks.
+- **Cost levers**: FARGATE_SPOT for fault-tolerant/stateless work, right-size task
+  CPU/memory (Container Insights shows actual utilization), Compute Savings Plans for
+  steady baseline load, VPC endpoints to drop NAT data charges.
+
+## Observability & deploys
+
+- **Logging** — `awslogs` driver to CloudWatch is the simple default; **FireLens**
+  (Fluent Bit sidecar) when you need to route/transform logs to a third party or
+  multiple sinks.
+- **Metrics** — CloudWatch Container Insights for per-task CPU/memory/network;
+  alarm on those.
+- **Debugging** — **ECS Exec** opens an interactive shell into a running task (no SSH,
+  no public IP). Needs ECS Exec enabled on the service and the SSM permissions on the
+  task role.
+- **Deploys** — rolling (built-in, `minimumHealthyPercent` / `maximumPercent`) or
+  **blue/green** via CodeDeploy (shift traffic, auto-rollback on alarm). Always wire
+  container **health checks** so a bad revision fails the deployment instead of
+  serving errors.
+
+## Deployment tooling
+
+| Tool | Best for |
+|------|----------|
+| **AWS Copilot** | Fastest path — opinionated, generates the VPC/ALB/service for you. Great for getting a service live and managing environments. |
+| **AWS CDK** | Real IaC with `ecs-patterns` L3 constructs (e.g. `ApplicationLoadBalancedFargateService`); programmable, testable. |
+| **CloudFormation** | Declarative, no extra runtime; verbose. |
+| **Terraform** | When the org standardizes on Terraform across clouds. |
+
+Copilot to start fast; CDK/Terraform when you need full control and review.
+
+## Common failure modes
+
+| Symptom | Likely cause |
+|---------|--------------|
+| Task can't reach RDS (timeout) | RDS SG doesn't allow the task SG; or task subnet has no route |
+| `CannotPullContainerError` | Execution role lacks ECR pull, or no route to ECR (no NAT / no VPC endpoint) |
+| Secret injection fails at start | Execution role missing Secrets Manager / SSM (and KMS) read permission |
+| Tasks killed under load | Task CPU/memory under-provisioned — check Container Insights, right-size |
+| Spot tasks vanish | FARGATE_SPOT interruption — add a FARGATE base in the capacity strategy |
+
+## Canonical references
+
+- AWS Fargate architecture: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html
+- ECS Developer Guide: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/Welcome.html
+- ECS Best Practices: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs-best-practices.html
+- Getting started with Fargate: https://aws.amazon.com/fargate/getting-started/

+ 311 - 0
skills/cypress-ops/SKILL.md

@@ -0,0 +1,311 @@
+---
+name: cypress-ops
+description: "Cypress end-to-end and component testing operations - selector/retry-ability strategy, cy.intercept network stubbing, cy.session auth, component vs e2e, flake diagnosis, CI, Test Replay. Use for: cypress, e2e test, component test, cy.get, cy.intercept, cy.session, data-cy, data-test, retry-ability, flake, flaky test, cypress.config, cy.mount, Test Replay, custom commands, fixtures."
+license: MIT
+allowed-tools: "Read Write Bash"
+metadata:
+  author: claude-mods
+  related-skills: "playwright-ops, testing-ops, ci-cd-ops"
+---
+
+# Cypress Operations
+
+End-to-end and component testing with Cypress (`cypress`, TS/JS). The runner executes
+tests *inside* a real browser via the **Cypress App** (`cypress open`) or headlessly
+(`cypress run`). The defining mental model: **`cy.*` commands are not promises** — they
+enqueue onto an async command chain that Cypress drains for you. Internalise that and the
+agentic gotchas below disappear.
+
+## Quick Start
+
+```bash
+npm install -D cypress
+npx cypress open                  # launch the Cypress App: pick E2E or Component, real browser
+npx cypress run                   # headless run, all specs (CI default)
+npx cypress run --spec "cypress/e2e/auth/*.cy.ts"
+npx cypress run --component       # run component specs
+npx cypress run --browser chrome --headed
+npx cypress run --record --key <k>  # upload to Cypress Cloud (enables Test Replay, v13+)
+```
+
+Specs live in `cypress/e2e/**/*.cy.ts` (E2E) and beside components or `cypress/component/`
+(component). Config is a single `cypress.config.ts` at the repo root.
+
+## The Async Command Queue (read this first)
+
+`cy.get(...)` returns a **Chainer**, not the element and not a Promise. Commands are
+*scheduled*, then run in order after the test function returns. This is the source of
+nearly every Cypress mistake an agent makes.
+
+```ts
+// WRONG — cy.get does not return a value; `el` is a Chainer, this is meaningless
+const el = cy.get('[data-test=total]');
+if (el.text() === '$0') { /* never works */ }
+
+// WRONG — async/await does nothing useful; cy commands aren't awaitable promises
+const text = await cy.get('[data-test=total]');   // do NOT do this
+
+// RIGHT — yield the value into a callback; assertions inside .should() retry
+cy.get('[data-test=total]').should('have.text', '$0');
+
+// RIGHT — need the raw value? use .then() (but it does NOT retry — see below)
+cy.get('[data-test=total]').invoke('text').then((text) => {
+  // text is a string here; runs after the queue reaches this point
+});
+```
+
+Rules that follow from this:
+- **No `const`/`let` to "store" a command result.** Use `.as()` aliases + `cy.get('@alias')`.
+- **No `async/await` on `cy.*`.** The queue handles ordering. Mixing in real promises?
+  wrap them with `cy.then(() => promise)` or `cy.wrap(promise)`.
+- **No `if/else` on element state read synchronously.** Conditional testing is an
+  anti-pattern in Cypress (the DOM may not have settled); make the app deterministic, or
+  drive the branch off a server/`cy.intercept` state you control. Deep dive:
+  [references/network-and-auth.md](references/network-and-auth.md).
+
+## Retry-ability (why you almost never need waits)
+
+Cypress retries **queries** and **assertions** until they pass or the command times out
+(default 4s). It does **not** retry **actions** (`.click()`, `.type()`, `.select()`) —
+those fire once, though the queries *leading up to* them retry until the element is
+actionable (visible, not disabled, not animating).
+
+| Construct | Retries? | Use for |
+|-----------|----------|---------|
+| `cy.get` / `.find` / `.contains` / `.its` / `.invoke` (queries) | Yes — whole chain re-queries | Locating/reading DOM that may not be ready |
+| `.should(...)` / `expect` inside it | Yes — the callback re-runs | Assertions; conditional waits on settled state |
+| `.click` / `.type` / `.select` (actions) | No — fire once | Interactions (leading queries still retry) |
+| `.then(cb)` | **No** — runs once, no retry protection | Extracting a value; NOT for assertions |
+
+```ts
+// .should(callback) retries the whole callback — safe for racy DOM
+cy.get('[data-test=rows] li').should(($li) => {
+  expect($li).to.have.length(3);
+  expect($li.first()).to.contain('Alice');
+});
+
+// .then() does NOT retry — capturing $el here then asserting later races the render
+```
+
+If you reach for `cy.wait(3000)`, you're missing an assertion or an aliased intercept.
+The only legitimate `cy.wait` takes an **alias** (`cy.wait('@getUsers')`), never a number.
+
+## Selector Strategy
+
+**Prefer a dedicated test attribute over CSS classes, IDs, or tag names** — the latter are
+brittle and change with styling/refactors. Cypress recommends `data-cy` **or** `data-test`
+(the Cypress Real World App standardises on **`data-test`**); pick one and enforce it.
+
+```ts
+// GOOD — decoupled from styling and structure
+cy.get('[data-test=submit]').click();
+
+// AVOID — couples the test to CSS/markup that changes for non-test reasons
+cy.get('.btn-primary').click();
+cy.get('#submit').click();
+```
+
+Wrap the convention in a custom command so specs stay terse:
+
+```ts
+// cypress/support/commands.ts
+Cypress.Commands.add('getBySel', (sel, ...args) =>
+  cy.get(`[data-test=${sel}]`, ...args));
+Cypress.Commands.add('getBySelLike', (sel, ...args) =>
+  cy.get(`[data-test*=${sel}]`, ...args));  // substring match
+// usage: cy.getBySel('submit').click();
+```
+
+Reserve `cy.contains('Log In')` for when the **visible text itself** is what you're
+asserting; otherwise it couples tests to copy.
+
+## Network Stubbing — `cy.intercept`
+
+`cy.intercept` is the single API for spying on and stubbing network traffic. **Set it up
+before the action that triggers the request**, alias it, then wait on the alias.
+
+```ts
+// Stub with a fixture, alias, wait
+cy.intercept('GET', '/api/users', { fixture: 'users.json' }).as('getUsers');
+cy.visit('/users');
+cy.wait('@getUsers');                       // resolves when the request fires
+
+// Inline body / status
+cy.intercept('POST', '/api/login', { statusCode: 401, body: { error: 'nope' } }).as('login');
+
+// routeMatcher object (method + glob/regex url) + dynamic reply
+cy.intercept({ method: 'GET', url: '/api/orders/*' }, (req) => {
+  req.reply((res) => { res.body.hasMore = false; });   // tweak the real response
+}).as('orders');
+
+// Assert against the captured request/response
+cy.wait('@login').its('response.statusCode').should('eq', 401);
+
+// Wait on several at once
+cy.wait(['@getUsers', '@orders']);
+```
+
+**Stub what you don't own, exercise what you do.** Stubbing third-party/slow endpoints
+makes tests fast and deterministic; hitting your real backend (seeded via `cy.request`)
+verifies the client↔server contract. Decide per endpoint. GraphQL, request modification,
+and seed-via-`cy.request` patterns: [references/network-and-auth.md](references/network-and-auth.md).
+
+## Authentication — `cy.session`
+
+Log in **once**, cache the session, restore it across tests (and optionally specs). This is
+the biggest suite-speed win after stubbing.
+
+```ts
+// cypress/support/commands.ts
+Cypress.Commands.add('login', (username: string, password: string) => {
+  cy.session(
+    [username, password],                   // cache key — array/object is stringified
+    () => {                                  // setup: runs only on cache miss
+      cy.visit('/login');
+      cy.get('[data-test=name]').type(username);
+      cy.get('[data-test=password]').type(password);
+      cy.get('form').contains('Log In').click();
+      cy.url().should('contain', '/dashboard');   // assert logged-in before caching!
+    },
+    {
+      validate() {                           // runs after setup AND after each restore
+        cy.getCookie('auth_token').should('exist');  // invalid -> setup re-runs
+      },
+      cacheAcrossSpecs: true,                // default false; true = reuse in every spec
+    },
+  );
+});
+```
+
+Critical behaviour: **cookies, `localStorage`, and `sessionStorage` across all domains are
+cleared before `setup` runs, regardless of `testIsolation`.** Faster still: skip the UI and
+log in via `cy.request` inside `setup`, persisting the token. Patterns (API login, token
+priming, `cy.origin` for cross-origin SSO): [references/network-and-auth.md](references/network-and-auth.md).
+
+## Component vs E2E Testing
+
+Same runner, two testing types. **E2E** drives a deployed app through `cy.visit`.
+**Component** mounts a single component in a real browser via `cy.mount` — no server, no
+navigation, props/events under direct control.
+
+| | E2E | Component |
+|---|---|---|
+| Entry | `cy.visit('/path')` | `cy.mount(<Comp/>)` |
+| Needs running app server | Yes | No (bundler dev server only) |
+| Spec location | `cypress/e2e/**/*.cy.ts` | beside the component / `cypress/component/` |
+| Support file | `cypress/support/e2e.ts` | `cypress/support/component.ts` (registers `cy.mount`) |
+| Best for | User flows, integration, auth | Props/events/slots, edge states, visual |
+
+```ts
+// cypress/support/component.ts  (React example)
+import { mount } from 'cypress/react';
+Cypress.Commands.add('mount', mount);
+
+// Button.cy.tsx
+cy.mount(<Button label="Save" onClick={cy.stub().as('onClick')} />);
+cy.get('[data-test=button]').click();
+cy.get('@onClick').should('have.been.calledOnce');
+```
+
+Frameworks: React 18–19, Vue 3, Angular 18–21, Svelte 5. Bundlers: Vite 5–8 (React/Vue/
+Svelte) or webpack 5 (all + Next.js). Configured under `component.devServer.{framework,bundler}`.
+Mounting per framework, store/router mocking, slots: [references/component-testing.md](references/component-testing.md).
+
+## Test Isolation, Fixtures, Custom Commands
+
+- **`testIsolation: true`** (default, E2E) clears cookies/storage and resets to `about:blank`
+  before each test. Each test must pass run **in isolation** (`it.only` to verify) — never
+  rely on a previous test's state. Reset *server-side* state in `beforeEach`, not `afterEach`
+  (an `after` hook may not run if you refresh mid-test).
+- **Multiple assertions per test are fine** — don't split into one-assertion tests; state
+  reset between tests costs more than extra assertions.
+- **Fixtures** are static JSON in `cypress/fixtures/`, loaded by `cy.fixture('users.json')`
+  or referenced directly in `cy.intercept(..., { fixture: 'users.json' })`.
+- **Custom commands** (`Cypress.Commands.add`) live in `cypress/support/commands.ts`; add
+  the `cypress/react` (etc.) types and a `declare global` block for TS autocomplete.
+
+## CI
+
+```yaml
+# GitHub Actions — the official cypress-io/github-action handles install + cache + run
+- uses: actions/checkout@v5
+- uses: cypress-io/github-action@v6
+  with:
+    build: npm run build
+    start: npm start                 # boots app, waits on baseUrl before running
+    wait-on: 'http://localhost:3000'
+    browser: chrome
+    record: true                     # upload to Cypress Cloud (Test Replay)
+  env:
+    CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+```
+
+| Decision | Guidance |
+|----------|----------|
+| Start the app | Start it **before** Cypress (`start` + `wait-on`), kill after — never `cy.exec` a server mid-test |
+| Parallelism | `cypress run --record --parallel` splits specs across machines — **requires Cypress Cloud** (paid). Free alternative: shard specs manually across matrix jobs with `--spec` |
+| Retries | Config `retries: { runMode: 2, openMode: 0 }` — surface flakes as a queue, don't paper over them |
+| Debugging CI failures | **Test Replay** (v13+, Chromium-only) over video: captures DOM, network, console, errors for time-travel debugging in Cloud |
+
+Full workflows (matrix sharding, containers, artifact upload): [references/ci-and-flake.md](references/ci-and-flake.md).
+
+## Flake Diagnosis
+
+Most Cypress flake traces to one of: an action chained where a query/assertion belonged, a
+missing aliased `cy.wait`, conditional logic on un-settled DOM, or leaked state between tests.
+
+| Symptom | Likely cause | Fix |
+|---------|-------------|-----|
+| "element detached from DOM" | re-render between query and action | split the chain; let the action's leading query retry |
+| passes alone, fails in suite | inter-test state coupling | reset server state in `beforeEach`; `it.only` to confirm |
+| `cy.wait(number)` "fixes" it | racing the network | replace with `cy.intercept(...).as()` + `cy.wait('@alias')` |
+| value read with `.then()` is stale | `.then` doesn't retry | move the assertion into `.should(cb)` |
+
+Diagnosis tooling (Test Replay, `cypress run --headed`, time-travel in the App, screenshots/
+video), retry config, and a systematic playbook: [references/ci-and-flake.md](references/ci-and-flake.md).
+
+## Cypress vs Playwright (one-table decision)
+
+| Factor | Cypress | Playwright |
+|--------|---------|-----------|
+| Execution model | In-browser, async command queue (no `await`) | Out-of-process, real `async/await` |
+| Browsers | Chrome-family, Firefox, Electron; WebKit experimental | Chromium, Firefox, **WebKit (real Safari)** |
+| Parallelism | Cypress Cloud (paid) or manual sharding | Free, built-in, shardable |
+| Multi-tab / multi-origin | Constrained (`cy.origin` for cross-origin) | Native |
+| Component testing | **Mature, first-class** | Experimental |
+| Interactive DX | The original benchmark (Cypress App, time-travel) | UI mode (excellent) |
+| API testing | `cy.request` / `cy.intercept` | Built-in `request` context |
+
+Reach for **Cypress** when component-testing maturity, an existing Cypress investment, or its
+in-browser DX dominate. Default to **Playwright** for new E2E needing WebKit, free parallelism,
+or heavy multi-tab/multi-origin work. Sibling skill: `playwright-ops`.
+
+## Config Skeleton
+
+Full commented production template: [assets/cypress.config.template.ts](assets/cypress.config.template.ts)
+
+```ts
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+  e2e: {
+    baseUrl: 'http://localhost:3000',        // cy.visit('/path') resolves against this
+    specPattern: 'cypress/e2e/**/*.cy.{ts,tsx}',
+    retries: { runMode: 2, openMode: 0 },    // retry in CI only
+    setupNodeEvents(on, config) { return config; },
+  },
+  component: {
+    devServer: { framework: 'react', bundler: 'vite' },
+  },
+  // testIsolation defaults true; viewportWidth/Height, defaultCommandTimeout tunable here
+});
+```
+
+## References
+
+| File | Contents |
+|------|----------|
+| [references/network-and-auth.md](references/network-and-auth.md) | `cy.intercept` matching/modifying/GraphQL, `cy.session` deep dive, API login, `cy.origin`, seed-via-request |
+| [references/component-testing.md](references/component-testing.md) | Per-framework `cy.mount`, store/router/context mocking, slots/events, Vite vs webpack config |
+| [references/ci-and-flake.md](references/ci-and-flake.md) | Full GH Actions workflows, sharding, Test Replay, retry config, systematic flake playbook |
+| [assets/cypress.config.template.ts](assets/cypress.config.template.ts) | Commented production config template (E2E + component) |

+ 80 - 0
skills/cypress-ops/assets/cypress.config.template.ts

@@ -0,0 +1,80 @@
+/**
+ * Production Cypress config template (E2E + Component).
+ *
+ * Copy to cypress.config.ts at the repo root and adjust the marked sections.
+ * Conventions baked in:
+ *   - baseUrl set so specs use cy.visit('/relative') and relative cy.request
+ *   - retries only in runMode (CI) — feel flakes immediately in openMode (local)
+ *   - testIsolation left at its default (true) — each test starts from a clean slate
+ *   - Test Replay assumed for CI debugging, so video is off (saves CI time)
+ *   - one shared test-selector convention: [data-test=...]
+ */
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+  // Project-wide defaults (apply to both e2e and component unless overridden) ----------
+  // Standard 1280x720; bump for wide-layout apps.
+  viewportWidth: 1280,
+  viewportHeight: 720,
+
+  // Default 4000ms retry budget for queries/assertions. Raise only for genuinely slow
+  // apps — a high global timeout masks real perf problems and slows failure feedback.
+  defaultCommandTimeout: 4000,
+
+  // Test Replay (v13+, Cloud, Chromium-only) is the better CI debugging artefact than
+  // video and captures DOM/network/console. Turn video off when recording to Cloud.
+  video: false,
+  screenshotOnRunFailure: true,
+
+  // Retries are flake telemetry, not a fix: a retried-then-passed test shows as "flaky".
+  // 0 locally so you feel flakes the instant they appear; up to 2 in CI to keep PRs green
+  // while you triage the flaky queue.
+  retries: {
+    runMode: 2,   // cypress run (CI)
+    openMode: 0,  // cypress open (local)
+  },
+
+  // Fill from CI secrets via CYPRESS_RECORD_KEY env var; never hard-code it here.
+  // projectId: 'abc123',   // set when recording to Cypress Cloud
+
+  e2e: {
+    // cy.visit('/login') and relative cy.request resolve against this. Override per
+    // environment with the CYPRESS_BASE_URL env var.
+    baseUrl: 'http://localhost:3000',
+
+    specPattern: 'cypress/e2e/**/*.cy.{ts,tsx,js,jsx}',
+    supportFile: 'cypress/support/e2e.ts',
+
+    // testIsolation: true is the default — cookies/storage cleared and page reset to
+    // about:blank before each test. Leave it on; reset SERVER state in beforeEach.
+    // testIsolation: true,
+
+    setupNodeEvents(on, config) {
+      // Register Node-side plugins/tasks here, e.g. DB reset tasks, code coverage,
+      // or env-specific config. Return config if you mutate it.
+      //
+      // on('task', { resetDb() { /* ... */ return null; } });
+      return config;
+    },
+  },
+
+  component: {
+    // framework: which UI library; bundler: 'vite' or 'webpack'. Cypress infers the rest
+    // of the dev-server wiring. See references/component-testing.md for the support matrix.
+    devServer: {
+      framework: 'react',   // 'react' | 'vue' | 'angular' | 'svelte' | 'next' | 'nuxt'
+      bundler: 'vite',      // 'vite' | 'webpack'
+    },
+
+    // Co-locate component specs with the components, or point at cypress/component/.
+    specPattern: 'src/**/*.cy.{ts,tsx,js,jsx}',
+    supportFile: 'cypress/support/component.ts',  // must register cy.mount (see refs)
+  },
+});
+
+// Notes:
+// - Add cypress/screenshots/, cypress/videos/, and cypress/downloads/ to .gitignore.
+// - For a multi-server app, start each server (app + api) before `cypress run` and use
+//   wait-on; never start servers inside a test via cy.exec/cy.task.
+// - The default selector convention here is [data-test=...] — wrap it in a getBySel
+//   custom command (see SKILL.md Selector Strategy) and enforce data-test on the frontend.

+ 160 - 0
skills/cypress-ops/references/ci-and-flake.md

@@ -0,0 +1,160 @@
+# CI & Flake Hunting
+
+## GitHub Actions — the official action
+
+`cypress-io/github-action` wraps install, dependency caching, app boot, and the run. It is
+the path of least resistance.
+
+```yaml
+name: e2e
+on: [push, pull_request]
+jobs:
+  cypress:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/checkout@v5
+      - uses: cypress-io/github-action@v6
+        with:
+          build: npm run build
+          start: npm start                 # boots the app
+          wait-on: 'http://localhost:3000' # polls until the app answers — no `sleep`
+          wait-on-timeout: 120
+          browser: chrome
+        env:
+          CYPRESS_BASE_URL: http://localhost:3000
+```
+
+Key point: **start the app outside the test run** (`start` + `wait-on`), never `cy.exec` a
+server inside a test. Port conflicts and lost stdout follow from in-test servers.
+
+## Recording to Cypress Cloud (Test Replay)
+
+```yaml
+      - uses: cypress-io/github-action@v6
+        with:
+          start: npm start
+          wait-on: 'http://localhost:3000'
+          record: true
+        env:
+          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+```
+
+**Test Replay** (Cypress v13+) replaces video as the CI debugging artefact. It captures DOM
+mutations, network requests, JS errors, console logs, CSS, SVG, iframes, and shadow DOM —
+then lets you time-travel through the failed run in Cloud. Caveats: **Chromium-family
+browsers only** (Chrome, Edge, Electron — not Firefox/WebKit), and it does **not** capture
+video/audio elements, websockets, `localStorage`/cookies, or `cy.request` traffic. With Test
+Replay on, disable video (`video: false`) to save CI time.
+
+## Parallelism
+
+```yaml
+  cypress:
+    strategy:
+      fail-fast: false
+      matrix:
+        containers: [1, 2, 3, 4]           # 4 machines
+    steps:
+      - uses: cypress-io/github-action@v6
+        with:
+          record: true
+          parallel: true                   # Cloud balances specs across the 4
+          group: 'e2e'
+        env:
+          CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
+```
+
+`--parallel` **requires Cypress Cloud** (paid) — Cloud does the spec balancing. Without
+Cloud, shard manually by globbing distinct spec sets per matrix job:
+
+```yaml
+        with:
+          spec: cypress/e2e/group-${{ matrix.shard }}/**/*.cy.ts
+```
+
+This is cruder (no load balancing, you partition by hand) but free.
+
+## Container image
+
+```yaml
+    container:
+      image: cypress/browsers:node-22.11.0-chrome-131-ff-133
+```
+
+`cypress/browsers` and `cypress/included` images pin browser + OS — the right choice when
+screenshot/visual stability matters or to avoid installing system deps each run.
+
+## Retry configuration
+
+```ts
+// cypress.config.ts
+export default defineConfig({
+  retries: {
+    runMode: 2,      // cypress run (CI): retry a failing test up to 2x
+    openMode: 0,     // cypress open (local): never retry — feel flakes immediately
+  },
+});
+```
+
+Retries are **flake telemetry, not a cure**. A test that only passes on retry is a bug in
+the queue — Cypress flags it as flaky. Treat the flaky list as work, not noise.
+
+---
+
+## Flake playbook
+
+Most Cypress flake reduces to four root causes. Diagnose in this order.
+
+### 1. Action chained where a query/assertion belonged
+
+```ts
+// FLAKY — re-render between .find and .click detaches the element
+cy.get('[data-test=row]').find('[data-test=edit]').click().should('be.disabled');
+
+// STABLE — split so the action's leading query retries; assert separately
+cy.get('[data-test=row]').find('[data-test=edit]').click();
+cy.get('[data-test=edit]').should('be.disabled');
+```
+
+"Element is detached from the DOM" almost always means this.
+
+### 2. Racing the network with a numeric wait
+
+```ts
+cy.wait(2000);                                  // FLAKY — guesses at timing
+// →
+cy.intercept('GET', '/api/data').as('getData'); // STABLE — wait on the actual request
+cy.get('[data-test=load]').click();
+cy.wait('@getData');
+```
+
+### 3. Stale value captured with `.then()`
+
+```ts
+// FLAKY — .then doesn't retry; $count snapshot may be pre-update
+cy.get('[data-test=count]').then(($count) => {
+  expect($count.text()).to.eq('5');
+});
+// →
+cy.get('[data-test=count]').should('have.text', '5');   // STABLE — .should retries
+```
+
+### 4. Inter-test state leakage
+
+A test that passes alone but fails in the suite is coupled to another test's state.
+- Verify with `it.only` — does it pass in isolation? If yes, it's coupling.
+- Reset **server-side** state in `beforeEach` (not `afterEach` — an `after` hook may be
+  skipped if the run is interrupted or refreshed mid-test).
+- `testIsolation: true` (default) already clears browser state per test; don't disable it
+  to "fix" a leak — that hides the real coupling.
+
+### Diagnosis tooling
+
+| Tool | How | Use |
+|------|-----|-----|
+| Test Replay | `cypress run --record` → Cloud | Post-mortem a CI failure: DOM/network/console time-travel |
+| Cypress App | `cypress open` | Local time-travel: hover each command to see DOM snapshot |
+| Headed CI repro | `cypress run --headed --no-exit` | Watch the failing run locally |
+| Repeat to surface | `cypress run --spec <flaky> --env repeat=20` (loop in script) | Force intermittent flake to reproduce |
+| Screenshots | automatic on failure in `cypress/screenshots/` | Quick "what did the page look like" |

+ 142 - 0
skills/cypress-ops/references/component-testing.md

@@ -0,0 +1,142 @@
+# Component Testing
+
+Cypress Component Testing mounts a single component in a **real browser** (not jsdom) via a
+bundler dev server — no app server, no navigation. You get the same time-travel debugging,
+real CSS, and DevTools as E2E, scoped to one component.
+
+## Supported matrix
+
+| Framework | Versions | Bundlers |
+|-----------|----------|----------|
+| React | 18–19 | Vite 5–8, webpack 5 |
+| Vue | 3 | Vite 5–8, webpack 5 |
+| Angular | 18–21 | webpack 5 |
+| Svelte | 5 | Vite 5–8, webpack 5 |
+| Next.js | 14–16 | webpack 5 |
+
+Configure under `component.devServer` — Cypress infers most of the dev-server wiring:
+
+```ts
+// cypress.config.ts
+import { defineConfig } from 'cypress';
+
+export default defineConfig({
+  component: {
+    devServer: {
+      framework: 'react',     // 'react' | 'vue' | 'angular' | 'svelte' | 'next' | 'nuxt' | ...
+      bundler: 'vite',        // 'vite' | 'webpack'
+    },
+    specPattern: 'src/**/*.cy.{ts,tsx,js,jsx}',
+  },
+});
+```
+
+## Registering cy.mount
+
+`cy.mount` is **not** built in — register it once in the component support file so every
+spec gets it (and so types resolve):
+
+```ts
+// cypress/support/component.ts  (React)
+import { mount } from 'cypress/react';
+import './commands';
+
+Cypress.Commands.add('mount', mount);
+
+declare global {
+  namespace Cypress {
+    interface Chainable {
+      mount: typeof mount;
+    }
+  }
+}
+```
+
+Swap the import per framework: `cypress/react`, `cypress/vue`, `cypress/angular`,
+`cypress/svelte`.
+
+## Mounting per framework
+
+```tsx
+// React — JSX, props inline
+cy.mount(<Stepper initial={5} onChange={cy.stub().as('onChange')} />);
+
+// Vue 3 — props/slots via options object
+cy.mount(Stepper, {
+  props: { count: 100 },
+  slots: { default: 'Label text' },
+});
+
+// Angular — component class + config object
+cy.mount(StepperComponent, {
+  componentProperties: { count: 100 },
+});
+
+// Svelte 5
+cy.mount(Stepper, { props: { count: 100 } });
+```
+
+## Asserting props, events, slots
+
+Spy on callbacks with `cy.stub().as(...)`, drive the component through the DOM, assert the
+spy fired:
+
+```tsx
+it('emits on increment', () => {
+  cy.mount(<Stepper initial={0} onChange={cy.stub().as('onChange')} />);
+  cy.get('[data-test=increment]').click();
+  cy.get('[data-test=count]').should('have.text', '1');
+  cy.get('@onChange').should('have.been.calledWith', 1);
+});
+```
+
+## Mocking stores / router / context
+
+Components that consume a store, router, or context need a provider wrapper at mount.
+Compose it in a local helper so specs stay clean:
+
+```tsx
+// React — wrap in providers
+function mountWithProviders(ui: React.ReactNode, { route = '/' } = {}) {
+  window.history.pushState({}, '', route);
+  return cy.mount(
+    <MemoryRouter initialEntries={[route]}>
+      <QueryClientProvider client={new QueryClient()}>{ui}</QueryClientProvider>
+    </MemoryRouter>,
+  );
+}
+```
+
+```ts
+// Vue — global plugins/stubs
+cy.mount(UserCard, {
+  global: {
+    plugins: [createTestingPinia()],
+    stubs: { RouterLink: true },
+  },
+});
+```
+
+## Network in component tests
+
+`cy.intercept` works in component tests exactly as in E2E — stub the component's data
+fetches:
+
+```tsx
+cy.intercept('GET', '/api/user/1', { fixture: 'user.json' }).as('getUser');
+cy.mount(<UserProfile id={1} />);
+cy.wait('@getUser');
+cy.get('[data-test=name]').should('have.text', 'Alice');
+```
+
+## When component vs E2E
+
+| Test it as a **component** when… | Test it **E2E** when… |
+|----------------------------------|------------------------|
+| Verifying props/events/slots in isolation | Verifying a multi-page user flow |
+| Exercising many edge states (loading/error/empty) cheaply | Auth, routing, real backend integration |
+| Visual states of one component | Cross-component / cross-page behaviour |
+| No server or navigation required | The app must actually be running |
+
+A healthy suite uses **component tests for breadth** (many states, fast) and **E2E for the
+critical user journeys** — not E2E for everything.

+ 188 - 0
skills/cypress-ops/references/network-and-auth.md

@@ -0,0 +1,188 @@
+# Network Stubbing & Authentication
+
+Deep dive on `cy.intercept` and `cy.session`. The SKILL.md body covers the 80% path;
+this file is the rest.
+
+## cy.intercept — matching
+
+```ts
+// String shorthand: method + url glob
+cy.intercept('GET', '/api/users');
+cy.intercept('POST', '/api/users', { statusCode: 201 });
+
+// routeMatcher object — match on more than method+url
+cy.intercept({
+  method: 'GET',
+  url: '/api/orders/*',
+  query: { status: 'open' },        // match only when ?status=open
+  headers: { 'x-tenant': 'acme' },
+});
+
+// Regex urls
+cy.intercept(/\/api\/orders\/\d+$/);
+```
+
+Glob (`*` one segment, `**` many) is the default for string URLs; pass a `RegExp` for
+precise control. A later `cy.intercept` for the same route **overrides** an earlier one
+within the same test — define the most specific matcher first if both could match.
+
+## cy.intercept — stubbing variants
+
+```ts
+// Fixture file (cypress/fixtures/users.json)
+cy.intercept('GET', '/api/users', { fixture: 'users.json' });
+
+// Inline body + status + headers
+cy.intercept('GET', '/api/users', {
+  statusCode: 200,
+  body: [{ id: 1, name: 'Alice' }],
+  headers: { 'cache-control': 'no-store' },
+});
+
+// Force network failure / latency / offline
+cy.intercept('GET', '/api/slow', { forceNetworkError: true });
+cy.intercept('GET', '/api/slow', (req) => { req.reply({ delay: 2000, body: {} }); });
+```
+
+## cy.intercept — spying & modifying (the function form)
+
+The function form gives the request (`req`) for inspection/mutation; `req.reply` /
+`req.continue` control the response.
+
+```ts
+// Spy only (no stub) — let it hit the server, just observe
+cy.intercept('POST', '/api/cart').as('addToCart');
+cy.get('[data-test=add]').click();
+cy.wait('@addToCart').then(({ request, response }) => {
+  expect(request.body).to.deep.equal({ sku: 'ABC', qty: 1 });
+  expect(response?.statusCode).to.eq(200);
+});
+
+// Mutate the outgoing request
+cy.intercept('GET', '/api/me', (req) => {
+  req.headers['authorization'] = 'Bearer test-token';
+  req.continue();                 // pass through to the real server
+});
+
+// Mutate the real response before it reaches the app
+cy.intercept('GET', '/api/feed', (req) => {
+  req.reply((res) => {
+    res.body.items = res.body.items.slice(0, 1);   // truncate for a deterministic test
+  });
+});
+
+// Conditionally stub vs passthrough
+cy.intercept('GET', '/api/flags', (req) => {
+  if (req.query.exp === 'B') req.reply({ body: { variant: 'B' } });
+  else req.continue();
+});
+```
+
+## GraphQL (single endpoint, many operations)
+
+GraphQL POSTs everything to one URL, so match on the **operation name** in the body:
+
+```ts
+const hasOperation = (req, name) =>
+  req.body?.operationName === name;
+
+cy.intercept('POST', '/graphql', (req) => {
+  if (hasOperation(req, 'GetUser')) {
+    req.reply({ fixture: 'gql/getUser.json' });
+  }
+  if (hasOperation(req, 'ListOrders')) {
+    req.alias = 'gqlListOrders';          // dynamic alias per operation
+    req.reply({ fixture: 'gql/listOrders.json' });
+  }
+});
+cy.wait('@gqlListOrders');
+```
+
+## Waiting on multiple / counted requests
+
+```ts
+cy.wait(['@getUsers', '@getOrders']);     // both must fire
+
+// Nth occurrence of a repeated request
+cy.wait('@getUsers');                     // 1st
+cy.wait('@getUsers');                     // 2nd
+```
+
+---
+
+## cy.session — full mechanics
+
+```ts
+cy.session(id, setup);
+cy.session(id, setup, options);
+```
+
+| Param / option | Behaviour |
+|----------------|-----------|
+| `id` | String / Array / Object cache key. Arrays & objects are deterministically stringified. Same id → same cached session |
+| `setup` | Runs **only on cache miss** (or when `validate` fails). Establishes the session (login) |
+| `validate()` | Runs after setup **and** after every restore. Throw / failing assertion → session invalid. After-restore failure re-runs `setup`; after-setup failure fails the test |
+| `cacheAcrossSpecs` | `false` (default) = session lives for the spec. `true` = global, restorable in any spec in the run |
+
+**Always-cleared invariant:** cookies, `localStorage`, and `sessionStorage` in **all
+domains** are cleared before `setup` runs, *regardless of `testIsolation`*. So `setup`
+starts from a clean slate every cache miss.
+
+**Assert a logged-in signal inside `setup` before it returns** — otherwise Cypress caches
+a half-authenticated state and every restore is broken.
+
+## Faster auth: skip the UI
+
+UI login is slow and re-tests the login form on every session. Prefer a programmatic login
+in `setup`:
+
+```ts
+Cypress.Commands.add('loginByApi', (username, password) => {
+  cy.session([username, password], () => {
+    cy.request('POST', '/api/login', { username, password }).then(({ body }) => {
+      window.localStorage.setItem('auth_token', body.token);   // or set a cookie
+    });
+  }, {
+    validate() { cy.window().its('localStorage.auth_token').should('exist'); },
+    cacheAcrossSpecs: true,
+  });
+});
+```
+
+Keep **one** UI-driven login test that exercises the real form; everything else uses the
+API path.
+
+## Cross-origin: cy.origin
+
+Cypress confines a test to one superdomain. To interact with another origin (SSO provider,
+OAuth consent screen on a domain you control), wrap those steps in `cy.origin`:
+
+```ts
+cy.origin('https://auth.example.com', () => {
+  cy.get('[data-test=email]').type('user@example.com');
+  cy.get('[data-test=password]').type('secret');
+  cy.get('[data-test=submit]').click();
+});
+```
+
+Caveats: variables must be passed in via the `args` option (the callback runs in a separate
+context — no closure access); `data-test` selectors and commands work, but custom commands
+need re-registering inside or via `Cypress.require`. Avoid testing third-party social-login
+UIs you don't control (captchas, A/B tests, throttling, bans) — stub them or use `cy.request`
+against the provider's API instead.
+
+## Seed-via-request, assert-via-UI
+
+The fastest way to set up state: create it through the API, verify through the UI.
+
+```ts
+beforeEach(() => {
+  cy.request('POST', '/api/test/reset');                       // reset server state
+  cy.request('POST', '/api/projects', { name: 'Apollo' });     // seed
+});
+
+it('shows the seeded project', () => {
+  cy.visit('/projects');
+  cy.contains('[data-test=project]', 'Apollo').should('be.visible');
+});
+```

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


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

@@ -13,9 +13,9 @@ Brief description of the project and what it does.
 
 | Task | Agent | Example Prompt |
 |------|-------|----------------|
-| E2E testing | `cypress-expert` | "Stabilize these flaky tests" |
 | Web scraping | `firecrawl-expert` | "Extract structured data from this site" |
-| Deployment | `bash-expert` | "Deploy to staging" |
+| Project cleanup | `project-organizer` | "Restructure this directory layout" |
+| CMS modeling | `payloadcms-expert` | "Design these collections and access control" |
 
 ## Primary Agents
 

+ 3 - 3
skills/explain/SKILL.md

@@ -44,7 +44,7 @@ $ARGUMENTS
     |     +- .py -> general-purpose, preload python-* skill by topic
     |     +- .vue -> general-purpose, preload vue-ops
     |     +- .sql/migrations -> general-purpose, preload postgres-ops
-    |     +- agents/skills/commands -> claude-architect
+    |     +- agents/skills/commands -> general-purpose, preload claude-code-ops
     |     +- Default -> general-purpose
     |     +- All explainers preload: debug-ops (systematic analysis)
     |
@@ -128,13 +128,13 @@ Dispatch is skills-first: the generic `general-purpose` subagent preloads the re
 | `.py` | general-purpose | relevant `skills/python-*/SKILL.md` by topic | - |
 | `.vue` | general-purpose | `skills/vue-ops/SKILL.md` | - |
 | `.sql`, `migrations/*` | general-purpose | `skills/postgres-ops/SKILL.md` | - |
-| `agents/*.md`, `skills/*`, `commands/*` | claude-architect | - | Claude extensions |
+| `agents/*.md`, `skills/*`, `commands/*` | general-purpose | `skills/claude-code-ops/SKILL.md` | Claude extensions |
 | `*.test.*`, `*.spec.*` | general-purpose | (framework skill by file type) | - |
 | Other | general-purpose | - | Fallback |
 
 **Invoke via Task tool:**
 ```
-Task tool with subagent_type: "general-purpose" (or claude-architect for Claude extensions)
+Task tool with subagent_type: "general-purpose" (preload claude-code-ops for Claude extensions)
 model: "sonnet"
 Prompt includes:
   - Skill preloading (domain knowledge):

+ 1 - 1
skills/playwright-ops/SKILL.md

@@ -302,7 +302,7 @@ safer default, with Playwright covering E2E.
 | In-browser interactive DX | UI mode (excellent) | The original benchmark; some teams still prefer it |
 
 Reach for Cypress when component testing maturity or an existing Cypress investment dominates;
-otherwise Playwright is the default for new E2E suites. (Repo also has a `cypress-expert` agent.)
+otherwise Playwright is the default for new E2E suites. (Repo also has a sibling `cypress-ops` skill.)
 
 ## Debugging & Codegen
 

+ 12 - 9
skills/review/SKILL.md

@@ -45,7 +45,10 @@ review [target] [--focus] [--depth]
     │     ├─ Rust → general-purpose, preload rust-ops
     │     ├─ Vue → general-purpose, preload vue-ops
     │     ├─ SQL/migrations → general-purpose, preload postgres-ops
-    │     ├─ Claude extensions → claude-architect
+    │     ├─ Cypress/E2E → general-purpose, preload cypress-ops
+    │     ├─ Cloudflare/Workers → general-purpose, preload cloudflare-ops
+    │     ├─ Shell/bash → general-purpose, preload bash-ops
+    │     ├─ Claude extensions → general-purpose, preload claude-code-ops
     │     ├─ Multi-domain → parallel general-purpose dispatch
     │     └─ All reviewers preload: security-ops + testing-ops context
@@ -146,7 +149,7 @@ cat .github/workflows/*.yml 2>/dev/null | grep -E "eslint|prettier|pylint|ruff"
 
 ### Step 4: Route to Reviewers
 
-Dispatch is skills-first: domain knowledge lives in `-ops` skills, and the generic `general-purpose` subagent preloads the relevant SKILL.md before reviewing. Surviving specialist agents (cypress-expert, claude-architect, wrangler-expert, bash-expert) are still dispatched directly.
+Dispatch is skills-first: domain knowledge lives in `-ops` skills, and the generic `general-purpose` subagent preloads the relevant SKILL.md before reviewing.
 
 | File Pattern | Dispatch | Preload |
 |--------------|----------|---------|
@@ -157,13 +160,13 @@ Dispatch is skills-first: domain knowledge lives in `-ops` skills, and the gener
 | `*.go` | general-purpose | `skills/go-ops/SKILL.md` |
 | `*.rs` | general-purpose | `skills/rust-ops/SKILL.md` |
 | `*.sql`, `migrations/*` | general-purpose | `skills/postgres-ops/SKILL.md` |
-| `agents/*.md`, `skills/*`, `commands/*` | claude-architect | - |
-| `*.test.*`, `*.spec.*` | cypress-expert | (framework skill by file type) |
-| `*.cy.ts`, `cypress/*` | cypress-expert | `skills/typescript-ops/SKILL.md` |
-| `*.spec.ts` (Playwright) | general-purpose | `skills/typescript-ops/SKILL.md` |
-| `playwright/*`, `e2e/*` | general-purpose | `skills/typescript-ops/SKILL.md` |
-| `wrangler.toml`, `workers/*` | wrangler-expert (cloudflare-expert secondary) | - |
-| `*.sh`, `*.bash` | bash-expert | - |
+| `agents/*.md`, `skills/*`, `commands/*` | general-purpose | `skills/claude-code-ops/SKILL.md` |
+| `*.test.*`, `*.spec.*` | general-purpose | (framework skill by file type) |
+| `*.cy.ts`, `cypress/*` | general-purpose | `skills/cypress-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `*.spec.ts` (Playwright) | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `playwright/*`, `e2e/*` | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `wrangler.toml`, `workers/*` | general-purpose | `skills/cloudflare-ops/SKILL.md` |
+| `*.sh`, `*.bash` | general-purpose | `skills/bash-ops/SKILL.md` |
 
 **Invoke via Task tool:**
 ```

+ 2 - 2
skills/spawn/SKILL.md

@@ -15,7 +15,7 @@ Generate world-class, comprehensive expert agent prompts for Claude Code. Each a
 
 **Target quality:** 500-1000 lines per agent with real code examples, complete configs, and detailed patterns.
 
-**Benchmark agents:** `firecrawl-expert.md` (1026 lines), `claude-architect.md` (347 lines)
+**Benchmark agents:** `firecrawl-expert.md` (1026 lines), `project-organizer.md` (610 lines)
 
 ## Usage Modes
 
@@ -139,7 +139,7 @@ Every generated agent MUST follow this comprehensive 10-part structure:
 
 Plus: Quality Checklist, Anti-Patterns (5-10 with bad/good code), Canonical Resources (10-15 URLs)
 
-See `firecrawl-expert.md` and `claude-architect.md` in agents/ for reference implementations.
+See `firecrawl-expert.md` and `project-organizer.md` in agents/ for reference implementations.
 
 **Requirements:**
 - YAML frontmatter at top with required fields (name, description)

+ 13 - 7
skills/testgen/SKILL.md

@@ -45,8 +45,9 @@ testgen <target> [--type] [--focus] [--depth]
     │     ├─ .go → general-purpose, preload go-ops
     │     ├─ .rs → general-purpose, preload rust-ops
     │     ├─ .php → general-purpose, preload laravel-ops
-    │     ├─ E2E/Cypress → cypress-expert
+    │     ├─ E2E/Cypress → general-purpose, preload cypress-ops
     │     ├─ Playwright → general-purpose, preload typescript-ops
+    │     ├─ Shell/bash → general-purpose, preload bash-ops
     │     ├─ --visual → Chrome DevTools MCP
     │     └─ Multi-file → parallel general-purpose dispatch
@@ -140,7 +141,7 @@ src/auth.rs → src/auth.rs (mod tests { ... })            # inline tests
 
 ### Step 4: Route to Test Generator
 
-Dispatch is skills-first: the generic `general-purpose` subagent preloads the relevant `-ops` skill before generating tests. Surviving specialist agents (cypress-expert, bash-expert) are still dispatched directly.
+Dispatch is skills-first: the generic `general-purpose` subagent preloads the relevant `-ops` skill before generating tests.
 
 | File Pattern | Dispatch | Preload |
 |--------------|----------|---------|
@@ -151,10 +152,10 @@ Dispatch is skills-first: the generic `general-purpose` subagent preloads the re
 | `*.go` | general-purpose | `skills/go-ops/SKILL.md` |
 | `*.rs` | general-purpose | `skills/rust-ops/SKILL.md` |
 | `*.php` | general-purpose | `skills/laravel-ops/SKILL.md` |
-| `*.cy.ts`, `cypress/*` | cypress-expert | - |
-| `*.spec.ts` (Playwright) | general-purpose | `skills/typescript-ops/SKILL.md` |
-| `playwright/*`, `e2e/*` | general-purpose | `skills/typescript-ops/SKILL.md` |
-| `*.sh`, `*.bash` | bash-expert | - |
+| `*.cy.ts`, `cypress/*` | general-purpose | `skills/cypress-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `*.spec.ts` (Playwright) | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `playwright/*`, `e2e/*` | general-purpose | `skills/playwright-ops/SKILL.md` + `skills/typescript-ops/SKILL.md` |
+| `*.sh`, `*.bash` | general-purpose | `skills/bash-ops/SKILL.md` |
 | (--visual flag) | Chrome DevTools MCP | `skills/typescript-ops/SKILL.md` |
 
 **Invoke via Task tool:**
@@ -265,11 +266,16 @@ Next steps:
 - Database transactions
 - Factory usage
 
-### E2E → cypress-expert
+### E2E/Cypress (preload cypress-ops)
 - Page object patterns
 - Custom commands
 - Network stubbing
 
+### Shell/bash (preload bash-ops)
+- bats / shunit2 patterns
+- Stubbing external commands
+- Exit-code and stderr assertions
+
 ### Playwright (preload typescript-ops)
 - Page object model patterns
 - Locator strategies

+ 12 - 8
skills/tool-discovery/SKILL.md

@@ -5,7 +5,7 @@ license: MIT
 allowed-tools: "Read Glob"
 metadata:
   author: claude-mods
-  related-skills: claude-code-templates, claude-code-debug
+  related-skills: claude-code-ops
 ---
 
 # Tool Discovery
@@ -53,6 +53,10 @@ Is this a reference/lookup task?
 | **astro-ops** | astro, islands, content collections, partial hydration |
 | **laravel-ops** | laravel, eloquent, artisan, sanctum, pest |
 | **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 |
+| **bash-ops** | bash, shell scripting, traps, CI scripts, defensive scripting |
+| **claude-code-ops** | claude code extensions, skills, agents, hooks, MCP, plugins |
 | **auth-ops** | jwt, oauth2, session, rbac, passkey, mfa, login |
 | **monitoring-ops** | prometheus, grafana, opentelemetry, SLO, alerting |
 | **debug-ops** | debug, crash, memory leak, race condition, bisect |
@@ -66,16 +70,16 @@ Is this a reference/lookup task?
 
 | Agent | Triggers |
 |-------|----------|
-| **cloudflare-expert** | Workers, KV, D1, R2 |
-| **wrangler-expert** | wrangler.toml, Workers deployment |
-| **cypress-expert** | Cypress E2E, component testing |
 | **firecrawl-expert** | web scraping, crawling, anti-bot |
-| **claude-architect** | Claude Code extensions, MCP, plugins |
-| **bash-expert** | shell scripting, CI/CD scripts |
+| **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 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, 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.
 
 ## How to Launch
 
@@ -86,7 +90,7 @@ Skill tool → skill: "file-search"
 
 **Agents:**
 ```
-Task tool → subagent_type: "cloudflare-expert"
+Task tool → subagent_type: "firecrawl-expert"
          → prompt: "Your task"
 ```
 

+ 12 - 116
skills/tool-discovery/references/agents-catalog.md

@@ -4,85 +4,17 @@ Complete reference for all available agents in the Task tool.
 
 **Note:** Language and framework domains (Python, JavaScript, TypeScript, Go, Rust, React, Vue, Laravel, Astro, SQL, PostgreSQL) are covered by `-ops` skills, not agents — see `skills-catalog.md`. When subagent work is needed in those domains, dispatch `general-purpose` with an instruction to first read the relevant skill's SKILL.md and references (skill preloading).
 
-## Language Experts
+**Skills-first domains:** Cloudflare/Workers, Cypress/E2E, shell scripting, AWS ECS/Fargate containers, and Claude Code extension work are now covered by `-ops` skills rather than dedicated agents:
 
-### bash-expert
+| Former agent | Now use |
+|--------------|---------|
+| `cloudflare-expert`, `wrangler-expert` | `cloudflare-ops` skill |
+| `cypress-expert` | `cypress-ops` skill |
+| `bash-expert` | `bash-ops` skill |
+| `aws-fargate-ecs-expert` | `container-orchestration` skill |
+| `claude-architect` | `claude-code-ops` skill |
 
-**Triggers:** bash, shell, script, zsh, cli
-
-**Capabilities:**
-- Defensive bash scripting
-- Error handling and traps
-- CI/CD pipeline scripts
-- System utilities
-- Cross-platform considerations
-
-**Best For:**
-- Production automation scripts
-- CI/CD pipelines
-- System administration
-- Build scripts
-
----
-
-## Infrastructure Experts
-
-### cloudflare-expert
-
-**Triggers:** cloudflare, workers, pages, kv, d1, r2
-
-**Capabilities:**
-- Workers development
-- KV/D1/R2 storage
-- Edge computing patterns
-- Security configuration
-- DNS and CDN setup
-
-**Best For:**
-- Cloudflare Workers apps
-- Edge optimization
-- Storage architecture
-- Security hardening
-
----
-
-### wrangler-expert
-
-**Triggers:** wrangler, deploy, cloudflare cli
-
-**Capabilities:**
-- Wrangler CLI configuration
-- Multi-environment deployment
-- Binding configuration
-- Troubleshooting deployments
-- CI/CD integration
-
-**Best For:**
-- Deployment issues
-- Wrangler configuration
-- Environment setup
-- CI/CD pipelines
-
----
-
-### aws-fargate-ecs-expert
-
-**Triggers:** ecs, fargate, aws containers, task definition
-
-**Capabilities:**
-- ECS/Fargate deployment
-- Task definitions
-- Service Auto Scaling
-- Networking (awsvpc)
-- Logging (FireLens)
-
-**Best For:**
-- Container deployment on AWS
-- ECS architecture
-- Scaling strategy
-- Cost optimization
-
----
+For these, invoke the skill directly, or dispatch `general-purpose` with an instruction to read the skill's SKILL.md first.
 
 ## Specialized Experts
 
@@ -143,25 +75,6 @@ Complete reference for all available agents in the Task tool.
 
 ---
 
-### cypress-expert
-
-**Triggers:** cypress, e2e, component testing, test runner
-
-**Capabilities:**
-- E2E test architecture
-- Component testing
-- Custom commands
-- Network stubbing
-- CI integration
-
-**Best For:**
-- E2E test suite setup
-- Test architecture
-- Flaky test debugging
-- CI optimization
-
----
-
 ### asus-router-expert
 
 **Triggers:** asus router, asuswrt, merlin, network hardening
@@ -179,23 +92,6 @@ Complete reference for all available agents in the Task tool.
 
 ---
 
-### claude-architect
-
-**Triggers:** claude code extensions, skills, agents, hooks, MCP, plugins
-
-**Capabilities:**
-- Skill/agent/command/hook design
-- Plugin and marketplace configuration
-- MCP server integration
-- Extension debugging
-
-**Best For:**
-- Building Claude Code extensions
-- Reviewing skills and agents
-- Plugin architecture decisions
-
----
-
 ### git-agent
 
 **Triggers:** commit, push, PR, branch, rebase (dispatched by git-ops)
@@ -312,10 +208,10 @@ Complete reference for all available agents in the Task tool.
 | "Find where X is defined" | Explore | general-purpose |
 | "Plan feature implementation" | Plan | general-purpose |
 | "Scrape this website" | firecrawl-expert | - |
-| "Deploy to Cloudflare" | wrangler-expert | cloudflare-expert |
+| "Deploy to Cloudflare" | cloudflare-ops skill | general-purpose + skill preload |
 | "Fix React performance" | react-ops skill | general-purpose + skill preload |
-| "Write E2E tests" | cypress-expert | - |
+| "Write E2E tests" | cypress-ops skill | general-purpose + skill preload |
 | "Restructure project" | project-organizer | - |
 | "Go concurrency design" | go-ops skill | general-purpose + skill preload |
 | "Rust lifetime issues" | rust-ops skill | general-purpose + skill preload |
-| "Build a Claude Code skill" | claude-architect | - |
+| "Build a Claude Code skill" | claude-code-ops skill | general-purpose + skill preload |

Some files were not shown because too many files changed in this diff