Browse Source

refactor: Convert heavyweight commands to skills

- Migrate 5 commands to skills for slash-hint discovery:
  - explain → skills/explain/
  - spawn → skills/spawn/
  - atomise → skills/atomise/
  - setperms → skills/setperms/
  - review → skills/review/ (with framework-checks.md)
  - testgen → skills/testgen/ (with frameworks.md, visual-testing.md)

- Add rules/skill-agent-updates.md for mandatory docs check
- Add docs/COMMAND-SKILL-PATTERN.md documenting skills-first architecture
- Update plugin.json to v1.5.1 (3 commands, 38 skills, 5 rules)
- Update README, AGENTS.md, marketplace.json with new counts

Remaining commands: sync, save, canvas (session management + experimental)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
0xDarkMatter 2 months ago
parent
commit
c688059b24

+ 11 - 10
.claude-plugin/plugin.json

@@ -1,7 +1,7 @@
 {
   "name": "claude-mods",
-  "version": "1.4.0",
-  "description": "Custom commands, skills, and agents for Claude Code - session continuity, terminal canvas, 22 expert agents, 30 skills, 4 rules, modern CLI tools",
+  "version": "1.5.1",
+  "description": "Custom commands, skills, and agents for Claude Code - session continuity, terminal canvas, 22 expert agents, 38 skills, 3 commands, 5 rules, modern CLI tools",
   "author": "0xDarkMatter",
   "repository": "https://github.com/0xDarkMatter/claude-mods",
   "license": "MIT",
@@ -20,14 +20,6 @@
     "commands": [
       "commands/sync.md",
       "commands/save.md",
-      "commands/review.md",
-      "commands/testgen.md",
-      "commands/explain.md",
-      "commands/spawn.md",
-      "commands/conclave.md",
-      "commands/atomise.md",
-      "commands/setperms.md",
-      "commands/pulse.md",
       "commands/canvas.md"
     ],
     "agents": [
@@ -55,6 +47,7 @@
       "agents/wrangler-expert.md"
     ],
     "skills": [
+      "skills/atomise",
       "skills/claude-code-debug",
       "skills/claude-code-headless",
       "skills/claude-code-hooks",
@@ -63,9 +56,12 @@
       "skills/container-orchestration",
       "skills/data-processing",
       "skills/doc-scanner",
+      "skills/explain",
       "skills/file-search",
       "skills/find-replace",
       "skills/git-workflow",
+      "skills/introspect",
+      "skills/markitdown",
       "skills/mcp-patterns",
       "skills/project-planner",
       "skills/python-async-patterns",
@@ -77,19 +73,24 @@
       "skills/python-pytest-patterns",
       "skills/python-typing-patterns",
       "skills/rest-patterns",
+      "skills/review",
       "skills/security-patterns",
+      "skills/setperms",
+      "skills/spawn",
       "skills/sql-patterns",
       "skills/sqlite-ops",
       "skills/structural-search",
       "skills/tailwind-patterns",
       "skills/task-runner",
       "skills/testing-patterns",
+      "skills/testgen",
       "skills/tool-discovery"
     ],
     "rules": [
       "rules/cli-tools.md",
       "rules/commit-style.md",
       "rules/naming-conventions.md",
+      "rules/skill-agent-updates.md",
       "rules/thinking.md"
     ],
     "output-styles": [

+ 29 - 4
AGENTS.md

@@ -3,9 +3,9 @@
 ## Project Overview
 
 This is **claude-mods** - a collection of custom extensions for Claude Code:
-- **23 expert agents** for specialized domains (React, Python, Go, Rust, AWS, etc.)
-- **11 slash commands** for workflows (/sync, /save, /review, /atomise, etc.)
-- **30 skills** for CLI tool integration, patterns, and workflows
+- **22 expert agents** for specialized domains (React, Python, Go, Rust, AWS, etc.)
+- **3 commands** for session management (/sync, /save) and experimental features (/canvas)
+- **38 skills** for CLI tools, patterns, workflows, and development tasks
 - **Custom output styles** for response personality (e.g., Vesper)
 
 ## Installation
@@ -32,7 +32,7 @@ cd claude-mods && ./scripts/install.sh  # or .\scripts\install.ps1 on Windows
 | `skills/` | Skill definitions with SKILL.md |
 | `output-styles/` | Response personalities (vesper.md) |
 | `hooks/` | Hook examples (pre/post execution) |
-| `rules/` | Claude Code rules (4 files: cli-tools, thinking, commit-style, naming-conventions) |
+| `rules/` | Claude Code rules (5 files: cli-tools, thinking, commit-style, naming-conventions, skill-agent-updates) |
 | `tools/` | Modern CLI toolkit documentation |
 | `tests/` | Validation scripts + justfile |
 | `scripts/` | Install scripts |
@@ -62,6 +62,31 @@ On "INIT:" message at session start:
 
 **Extended Thinking:** "think" < "think hard" < "think harder" < "ultrathink"
 
+**Tasks API:** Use `TaskCreate`, `TaskList`, `TaskUpdate`, `TaskGet` for task management. Tasks are session-scoped (don't persist). Use `/save` to capture and `/sync` to restore.
+
+**Session Cache:** v3.0 schema stores full task objects (subject, description, activeForm, status, blockedBy). Legacy v2.0 files auto-migrate on `/sync`.
+
+## Performance
+
+**MCP Tool Search:** When using multiple MCP servers, enable tool search to save context:
+
+```json
+// .claude/settings.local.json
+{
+  "env": {
+    "ENABLE_TOOL_SEARCH": "true"
+  }
+}
+```
+
+| Value | Behavior |
+|-------|----------|
+| `"auto"` | Enable when MCP tools > 10% context (default) |
+| `"true"` | Always enabled (recommended with many MCP servers) |
+| `"false"` | Disabled, all tools loaded upfront |
+
+Requires Sonnet 4+ or Opus 4+.
+
 ## Testing
 
 ```bash

+ 99 - 45
README.md

@@ -5,19 +5,19 @@
 
 > *What if Claude Code remembered what it was doing yesterday?*
 
-Claude Code is brilliant - until your session ends and it forgets everything. Your TodoWrite tasks vanish. Your carefully-built context evaporates. You're back to explaining the codebase from scratch.
+Claude Code is brilliant - until your session ends and it forgets everything. Your tasks vanish. Your carefully-built context evaporates. You're back to explaining the codebase from scratch.
 
-**claude-mods fixes that.** It's a plugin that adds session persistence, expert-level domain knowledge, and the modern CLI tools that Claude should've been using all along. Save your work with `/save`, pick up where you left off with `/sync`, and let 23 specialized agents handle everything from React hooks to PostgreSQL optimization. No more "I don't have access to that" - just a smarter, more capable coding assistant that actually remembers.
+**claude-mods fixes that.** It's a plugin that adds session persistence, expert-level domain knowledge, and the modern CLI tools that Claude should've been using all along. Save your work with `/save`, pick up where you left off with `/sync`, and let 22 specialized agents handle everything from React hooks to PostgreSQL optimization. No more "I don't have access to that" - just a smarter, more capable coding assistant that actually remembers.
 
-**23 agents. 10 commands. 30 skills. One install.**
+**22 agents. 38 skills. 3 commands. One install.**
 
 ## Why claude-mods?
 
 Claude Code is powerful out of the box, but it has gaps. This toolkit fills them:
 
-- **Session continuity** — TodoWrite 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.
+- **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** — 23 specialized agents covering React, TypeScript, Python, Go, Rust, AWS, PostgreSQL, and more. Each agent is deeply researched with real-world patterns, not generic advice.
+- **Expert-level knowledge on demand** — 22 specialized agents covering React, TypeScript, Python, Go, Rust, AWS, PostgreSQL, and more. Each agent is deeply researched with real-world patterns, not generic advice.
 
 - **Modern CLI tools** — Stop using `grep`, `find`, and `cat`. Our rules automatically prefer `ripgrep`, `fd`, `eza`, and `bat` — 10-100x faster and token-efficient.
 
@@ -40,15 +40,15 @@ 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 (23)
-├── commands/           # Slash commands (11)
-├── skills/             # Custom skills (30)
+├── agents/             # Expert subagents (22)
+├── commands/           # Slash commands (3)
+├── skills/             # Custom skills (38)
 ├── output-styles/      # Response personalities
 ├── hooks/              # Hook examples & docs
 ├── rules/              # Claude Code rules
-├── tools/              # Modern CLI toolkit docs
+├── tools/              # Modern CLI toolkit installers
+├── scripts/            # Plugin install scripts
 ├── tests/              # Test suites + justfile
-├── scripts/            # Install scripts
 ├── docs/               # Project docs (PLAN.md, DASH.md)
 └── templates/          # Extension templates
 ```
@@ -73,27 +73,32 @@ This installs globally (available in all projects). Toggle on/off with `/plugin`
 ```bash
 git clone https://github.com/0xDarkMatter/claude-mods.git
 cd claude-mods
-./tools/install-unix.sh
+./scripts/install.sh
 ```
 
 **Windows (PowerShell):**
 ```powershell
 git clone https://github.com/0xDarkMatter/claude-mods.git
 cd claude-mods
-.\tools\install-windows.ps1
+.\scripts\install.ps1
 ```
 
-### Manual Install
+The install scripts:
+- Copy commands, skills, agents, rules to `~/.claude/`
+- Clean up deprecated items (e.g., old `/conclave` command)
+- Handle command→skill migrations (won't create duplicates)
+
+### CLI Tools (Optional)
+
+Install modern CLI tools (fd, rg, bat, etc.) for better performance:
 
 ```bash
-git clone https://github.com/0xDarkMatter/claude-mods.git
-```
+# Windows (Admin PowerShell)
+.\tools\install-windows.ps1
 
-Then symlink or copy to your Claude directories:
-- Commands → `~/.claude/commands/`
-- Skills → `~/.claude/skills/`
-- Agents → `~/.claude/agents/`
-- Rules → `~/.claude/rules/`
+# Linux/macOS
+./tools/install-unix.sh
+```
 
 ## What's Included
 
@@ -101,25 +106,9 @@ Then symlink or copy to your Claude directories:
 
 | Command | Description |
 |---------|-------------|
-| [sync](commands/sync.md) | Session bootstrap - read project context, restore saved state, show status. Quick orientation with optional deep dive. |
-| [save](commands/save.md) | Save session state - persist TodoWrite tasks, plan content, and git context. |
-| [review](commands/review.md) | Code review staged changes or specific files. Analyzes bugs, security, performance, style. |
-| [testgen](commands/testgen.md) | Generate tests with expert routing, framework detection, focus/depth modes. |
-| [explain](commands/explain.md) | Deep explanation of complex code, files, or concepts. Architecture, data flow, design decisions. |
-| [spawn](commands/spawn.md) | Generate expert agents with PhD-level patterns and code examples. |
-| [conclave](commands/conclave.md) | **[DEPRECATED]** Use [Conclave CLI](https://github.com/0xDarkMatter/conclave) instead. |
-| [atomise](commands/atomise.md) | Atom of Thoughts reasoning - decompose problems into atomic units with confidence tracking and backtracking. |
-| [pulse](commands/pulse.md) | **[MOVED]** See [0xDarkMatter/pulse](https://github.com/0xDarkMatter/pulse). |
-| [setperms](commands/setperms.md) | Set tool permissions and CLI preferences. |
-| [archive](commands/archive.md) | Archive completed plans and session state. |
-
-### Experimental Commands
-
-> ⚠️ These features are under active development. APIs may change.
-
-| Command | Description |
-|---------|-------------|
-| [canvas](commands/canvas.md) | Terminal canvas for content drafting with live markdown preview. Requires Warp terminal. |
+| [sync](commands/sync.md) | Session bootstrap - read project context, restore saved state, show status. |
+| [save](commands/save.md) | Save session state - persist tasks, plan content, and git context. |
+| [canvas](commands/canvas.md) | Terminal canvas for content drafting with live markdown preview. Requires Warp terminal. (Experimental) |
 
 ### Skills
 
@@ -152,6 +141,17 @@ Then symlink or copy to your Claude directories:
 | [python-env](skills/python-env/) | Fast Python environment management with uv |
 | [task-runner](skills/task-runner/) | Run project commands with just |
 
+#### Development Skills
+| Skill | Description |
+|-------|-------------|
+| [explain](skills/explain/) | Deep explanation of complex code, files, or concepts. Routes to expert agents. |
+| [spawn](skills/spawn/) | Generate PhD-level expert agent prompts for Claude Code. |
+| [atomise](skills/atomise/) | Atom of Thoughts reasoning - decompose problems into atomic units. |
+| [setperms](skills/setperms/) | Set tool permissions and CLI preferences in .claude/ directory. |
+| [introspect](skills/introspect/) | Analyze previous session logs without consuming current context. |
+| [review](skills/review/) | Code review with semantic diffs, expert routing, and auto-TaskCreate. |
+| [testgen](skills/testgen/) | Generate tests with expert routing and framework detection. |
+
 ### Agents
 
 | Agent | Description |
@@ -169,7 +169,6 @@ Then symlink or copy to your Claude directories:
 | [javascript-expert](agents/javascript-expert.md) | Modern JavaScript, async patterns, optimization |
 | [laravel-expert](agents/laravel-expert.md) | Laravel framework, Eloquent, testing |
 | [payloadcms-expert](agents/payloadcms-expert.md) | Payload CMS architecture and configuration |
-| [playwright-roulette-expert](agents/playwright-roulette-expert.md) | Playwright automation for casino testing |
 | [postgres-expert](agents/postgres-expert.md) | PostgreSQL management and optimization |
 | [project-organizer](agents/project-organizer.md) | Reorganize directory structures, cleanup |
 | [python-expert](agents/python-expert.md) | Advanced Python, testing, optimization |
@@ -188,6 +187,7 @@ Then symlink or copy to your Claude directories:
 | [thinking.md](rules/thinking.md) | Extended thinking triggers (think → ultrathink) |
 | [commit-style.md](rules/commit-style.md) | Conventional commits format and examples |
 | [naming-conventions.md](rules/naming-conventions.md) | Component naming patterns for agents, skills, commands |
+| [skill-agent-updates.md](rules/skill-agent-updates.md) | Mandatory docs check before creating/updating skills or agents |
 
 ### Tools & Hooks
 
@@ -268,9 +268,9 @@ just list-agents  # List all agents
 
 The `/save` and `/sync` commands fill a gap in Claude Code's native session management.
 
-**The problem:** Claude Code's `--resume` flag restores conversation history, but **TodoWrite task state does not persist between sessions—by design**. Claude Code treats each session as isolated; the philosophy is that persistent state belongs in files you control.
+**The problem:** Claude Code's `--resume` flag restores conversation history, but **task state does not persist between sessions—by design**. Claude Code treats each session as isolated; the philosophy is that persistent state belongs in files you control.
 
-TodoWrite tasks are stored at `~/.claude/todos/[session-id].json` and deleted when the session ends. This is intentional.
+Tasks (created via TaskCreate, managed via TaskList/TaskUpdate) are session-scoped and deleted when the session ends. This is intentional.
 
 **The solution:** `/save` and `/sync` implement the pattern from Anthropic's [Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents):
 
@@ -282,7 +282,7 @@ TodoWrite tasks are stored at `~/.claude/todos/[session-id].json` and deleted wh
 |---------------------|-----------|----------|
 | Conversation history | Yes | Internal (use `--resume`) |
 | CLAUDE.md context | Yes | `./CLAUDE.md` |
-| TodoWrite tasks | **No** | Deleted on session end |
+| Tasks | **No** | Deleted on session end |
 | Plan Mode state | **No** | In-memory only |
 
 ### Session Workflow
@@ -294,7 +294,7 @@ Session 1:
   /save "Stopped at auth module"     # Writes .claude/session-cache.json
 
 Session 2:
-  /sync                              # Restore TodoWrite, show status
+  /sync                              # Restore tasks, show status
   → "In progress: Auth module refactor"
   → "Notes: Stopped at auth module"
   /sync --status                     # Quick status check anytime
@@ -305,7 +305,7 @@ Session 2:
 | Feature | `--resume` | `/save` + `/sync` |
 |---------|------------|-------------------|
 | Conversation history | Yes | No |
-| TodoWrite tasks | **No** | Yes |
+| Tasks | **No** | Yes |
 | Git context | No | Yes |
 | Human-readable summary | No | Yes |
 | Git-trackable | No | Yes |
@@ -314,6 +314,30 @@ Session 2:
 
 **Use both together:** `claude --resume` for conversation context, `/sync` for task state.
 
+### Session Cache Schema (v3.0)
+
+The `.claude/session-cache.json` file stores full task objects:
+
+```json
+{
+  "version": "3.0",
+  "tasks": [
+    {
+      "subject": "Task title",
+      "description": "Detailed description",
+      "activeForm": "Working on task",
+      "status": "completed|in_progress|pending",
+      "blockedBy": [0, 1]
+    }
+  ],
+  "plan": { "goal": "...", "current_step": "...", "progress_percent": 40 },
+  "git": { "branch": "main", "last_commit": "abc123" },
+  "notes": "Session notes"
+}
+```
+
+**Migration:** `/sync` auto-detects v2.0 files (legacy `todos` format) and migrates them. Run `/save` after migration to upgrade the file.
+
 ## Updating
 
 ```bash
@@ -322,6 +346,36 @@ git pull
 
 Then re-run the install script to update your global Claude configuration.
 
+## Performance Tips
+
+### MCP Tool Search
+
+When using multiple MCP servers (Chrome DevTools, Vibe Kanban, etc.), their tool definitions consume context. Enable Tool Search to load tools on-demand:
+
+```json
+// .claude/settings.local.json
+{
+  "env": {
+    "ENABLE_TOOL_SEARCH": "true"
+  }
+}
+```
+
+| Value | Behavior |
+|-------|----------|
+| `"auto"` | Enable when MCP tools > 10% of context (default) |
+| `"auto:5"` | Custom threshold (5%) |
+| `"true"` | Always enabled (recommended) |
+| `"false"` | Disabled |
+
+**Requirements:** Sonnet 4+ or Opus 4+ (Haiku not supported)
+
+### Skills Over Commands
+
+Most functionality lives in skills rather than commands. Skills get slash-hint discovery via trigger keywords and load on-demand, reducing context overhead. Only session management (`/sync`, `/save`) and experimental features (`/canvas`) remain as commands.
+
+See `docs/COMMAND-SKILL-PATTERN.md` for details.
+
 ## Resources
 
 - [Claude Code Best Practices](https://www.anthropic.com/engineering/claude-code-best-practices) — Official Anthropic guide

+ 0 - 779
commands/testgen.md

@@ -1,779 +0,0 @@
----
-description: "Generate tests with expert routing, framework detection, and auto-TodoWrite. Creates unit, integration, or E2E tests following project conventions."
----
-
-# TestGen - AI Test Generation
-
-Generate comprehensive tests for your code with automatic framework detection, expert agent routing, and project convention matching. Routes to specialized experts (python-expert, react-expert, cypress-expert) for domain-specific test patterns.
-
-## Arguments
-
-$ARGUMENTS
-
-- `<file>`: Generate tests for specific file
-- `<file>:<function>`: Generate tests for specific function/method
-- `<directory>`: Generate tests for all files in directory
-- `--type <unit|integration|e2e|component>`: Specify test type
-- `--framework <jest|vitest|pytest|...>`: Override detected framework
-- `--focus <happy|edge|error|all>`: Focus on specific test cases
-- `--depth <quick|normal|thorough>`: Generation depth
-- `--stubs`: Generate empty test stubs only
-
-## Architecture
-
-```
-/testgen <target> [--type] [--focus] [--depth]
-    │
-    ├─→ Step 1: Analyze Target
-    │     ├─ File exists? → Read and parse
-    │     ├─ Function specified? → Extract signature
-    │     ├─ Directory? → List source files
-    │     └─ Find existing tests (avoid duplicates)
-    │
-    ├─→ Step 2: Detect Framework (parallel)
-    │     ├─ package.json → jest/vitest/mocha/cypress/playwright
-    │     ├─ pyproject.toml → pytest/unittest
-    │     ├─ composer.json → phpunit/pest
-    │     ├─ Check existing test patterns
-    │     └─ Fallback: infer from file extension
-    │
-    ├─→ Step 3: Load Project Standards
-    │     ├─ AGENTS.md, CLAUDE.md conventions
-    │     ├─ Existing test file structure
-    │     ├─ Import styles and assertion library
-    │     └─ Naming conventions (*.test.ts vs *.spec.ts)
-    │
-    ├─→ Step 4: Route to Expert Agent
-    │     ├─ .ts → typescript-expert
-    │     ├─ .tsx/.jsx → react-expert
-    │     ├─ .vue → vue-expert
-    │     ├─ .py → python-expert
-    │     ├─ .php → laravel-expert
-    │     ├─ E2E tests → cypress-expert
-    │     └─ Multi-file → parallel expert dispatch
-    │
-    ├─→ Step 5: Generate Tests
-    │     ├─ Create test file in correct location
-    │     ├─ Follow detected conventions
-    │     ├─ Include: happy path, edge cases, error handling
-    │     └─ Add proper mocking for dependencies
-    │
-    └─→ Step 6: Integration
-          ├─ Auto-create TodoWrite for generated tests
-          ├─ Suggest: run tests to verify
-          └─ Link to /save for tracking
-```
-
-## Execution Steps
-
-### Step 1: Analyze Target
-
-```bash
-# Check if target exists
-test -f "$TARGET" && echo "FILE" || test -d "$TARGET" && echo "DIRECTORY"
-
-# For function-specific: parse the file
-# /testgen src/auth.ts:validateToken → extract validateToken signature
-```
-
-**Extract function signature:**
-```bash
-# Use ast-grep if available
-command -v ast-grep >/dev/null 2>&1 && ast-grep -p "function $FUNCTION_NAME" "$FILE"
-
-# Fallback to ripgrep
-rg "(?:function|const|def|public|private)\s+$FUNCTION_NAME" "$FILE" -A 10
-```
-
-**Check for existing tests:**
-```bash
-# Find related test files
-fd -e test.ts -e spec.ts -e test.js -e spec.js | rg "$BASENAME"
-
-# Python
-fd "test_*.py" | rg "$BASENAME"
-```
-
-### Step 2: Detect Framework
-
-**JavaScript/TypeScript:**
-```bash
-# Check package.json devDependencies
-cat package.json 2>/dev/null | jq -r '.devDependencies | keys[]' | grep -E 'jest|vitest|mocha|cypress|playwright|@testing-library'
-```
-
-**Python:**
-```bash
-# Check pyproject.toml or requirements
-grep -E "pytest|unittest|nose" pyproject.toml setup.py requirements*.txt 2>/dev/null
-```
-
-**PHP:**
-```bash
-# Check composer.json
-cat composer.json 2>/dev/null | jq -r '.["require-dev"] | keys[]' | grep -E 'phpunit|pest|codeception'
-```
-
-**Detect test patterns:**
-```bash
-# Find existing test files to match conventions
-fd -e test.ts -e spec.ts -e test.tsx -e spec.tsx | head -3
-fd "test_*.py" tests/ | head -3
-```
-
-### Step 3: Load Project Standards
-
-**Check for conventions:**
-```bash
-# Claude Code conventions
-cat AGENTS.md 2>/dev/null | head -50
-cat CLAUDE.md 2>/dev/null | head -50
-
-# Test config files
-cat jest.config.* vitest.config.* pytest.ini pyproject.toml 2>/dev/null | head -30
-```
-
-**Determine test location convention:**
-```
-# JavaScript conventions (detect which is used)
-src/utils/helper.ts → src/utils/__tests__/helper.test.ts  # __tests__ folder
-                    → src/utils/helper.test.ts            # co-located
-                    → tests/utils/helper.test.ts          # separate tests/
-
-# Python conventions
-app/utils/helper.py → tests/test_helper.py               # tests/ folder
-                    → tests/utils/test_helper.py         # mirror structure
-                    → app/utils/test_helper.py           # co-located
-
-# PHP conventions
-app/Services/UserService.php → tests/Unit/Services/UserServiceTest.php
-                             → tests/Feature/UserServiceTest.php
-```
-
-### Step 4: Route to Expert Agent
-
-| File Pattern | Primary Expert | Secondary |
-|--------------|----------------|-----------|
-| `*.ts` | typescript-expert | - |
-| `*.tsx`, `*.jsx` | react-expert | typescript-expert |
-| `*.vue` | vue-expert | typescript-expert |
-| `*.py` | python-expert | - |
-| `*.php` | laravel-expert | - |
-| `*.cy.ts`, `cypress/*` | cypress-expert | - |
-| `*.spec.ts` (Playwright) | - | typescript-expert |
-| `*.sh`, `*.bash` | bash-expert | - |
-
-**Invoke via Task tool:**
-```
-Task tool with subagent_type: "[detected]-expert"
-Prompt includes:
-  - Source file content
-  - Function signatures to test
-  - Detected framework and conventions
-  - Requested test type and focus
-  - Project conventions from AGENTS.md
-```
-
-### Step 5: Generate Tests
-
-The expert produces tests following this structure:
-
-**Include test categories based on --focus:**
-
-| Focus | What to Generate |
-|-------|------------------|
-| `happy` | Normal input, expected output |
-| `edge` | Boundary values, empty inputs, nulls |
-| `error` | Invalid inputs, exceptions, error handling |
-| `all` | All of the above (default) |
-
-**Depth levels:**
-
-| Depth | Coverage |
-|-------|----------|
-| `quick` | Happy path only, 1-2 tests per function |
-| `normal` | Happy + common edge cases (default) |
-| `thorough` | Comprehensive: all paths, mocking, async |
-
-### Step 6: Integration
-
-**Auto-create TodoWrite:**
-```
-TodoWrite:
-  - content: "Run generated tests for src/auth.ts"
-    status: "pending"
-    activeForm: "Running generated tests for auth.ts"
-```
-
-**Suggest next steps:**
-```
-Tests generated: src/auth.test.ts
-
-Next steps:
-1. Run tests: npm test src/auth.test.ts
-2. Review and refine edge cases
-3. Use /save to track test coverage goals
-```
-
----
-
-## Expert Routing Details
-
-### TypeScript/JavaScript → typescript-expert
-
-Generates tests with:
-- Proper type imports
-- Generic type handling
-- Async/await patterns
-- Mock typing
-
-### React/JSX → react-expert
-
-Generates tests with:
-- React Testing Library patterns
-- Component rendering tests
-- Hook testing (renderHook)
-- Event simulation
-- Accessibility queries (getByRole)
-
-### Vue → vue-expert
-
-Generates tests with:
-- Vue Test Utils patterns
-- Composition API testing
-- Pinia store mocking
-- Component mounting
-
-### Python → python-expert
-
-Generates tests with:
-- pytest fixtures
-- Parametrized tests
-- Mock/patch patterns
-- Async test handling
-- Type hint verification
-
-### PHP/Laravel → laravel-expert
-
-Generates tests with:
-- PHPUnit/Pest patterns
-- Database transactions
-- Factory usage
-- HTTP testing
-- Mocking facades
-
-### E2E → cypress-expert
-
-Generates tests with:
-- Page object patterns
-- Custom commands
-- Network stubbing
-- Visual testing
-- CI configuration
-
----
-
-## Framework-Specific Output
-
-### Jest/Vitest (TypeScript)
-
-```typescript
-import { describe, it, expect, vi, beforeEach } from 'vitest';
-import { validateToken, TokenError } from '../auth';
-
-describe('validateToken', () => {
-  beforeEach(() => {
-    vi.clearAllMocks();
-  });
-
-  describe('happy path', () => {
-    it('should return true for valid JWT token', () => {
-      const token = 'eyJhbGciOiJIUzI1NiIs...';
-      expect(validateToken(token)).toBe(true);
-    });
-
-    it('should decode payload correctly', () => {
-      const token = createTestToken({ userId: 123 });
-      const result = validateToken(token);
-      expect(result.payload.userId).toBe(123);
-    });
-  });
-
-  describe('edge cases', () => {
-    it('should handle empty string', () => {
-      expect(validateToken('')).toBe(false);
-    });
-
-    it('should handle malformed token', () => {
-      expect(validateToken('not.a.token')).toBe(false);
-    });
-
-    it('should handle expired token', () => {
-      const expiredToken = createTestToken({ exp: Date.now() - 1000 });
-      expect(validateToken(expiredToken)).toBe(false);
-    });
-  });
-
-  describe('error handling', () => {
-    it('should throw TokenError for null input', () => {
-      expect(() => validateToken(null)).toThrow(TokenError);
-    });
-
-    it('should throw with descriptive message', () => {
-      expect(() => validateToken(null)).toThrow('Token cannot be null');
-    });
-  });
-});
-```
-
-### React Testing Library
-
-```typescript
-import { render, screen, fireEvent, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import { LoginForm } from '../LoginForm';
-
-describe('LoginForm', () => {
-  const mockOnSubmit = vi.fn();
-
-  beforeEach(() => {
-    mockOnSubmit.mockClear();
-  });
-
-  it('renders email and password fields', () => {
-    render(<LoginForm onSubmit={mockOnSubmit} />);
-
-    expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument();
-    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
-  });
-
-  it('submits form with credentials', async () => {
-    const user = userEvent.setup();
-    render(<LoginForm onSubmit={mockOnSubmit} />);
-
-    await user.type(screen.getByRole('textbox', { name: /email/i }), 'test@example.com');
-    await user.type(screen.getByLabelText(/password/i), 'password123');
-    await user.click(screen.getByRole('button', { name: /submit/i }));
-
-    expect(mockOnSubmit).toHaveBeenCalledWith({
-      email: 'test@example.com',
-      password: 'password123',
-    });
-  });
-
-  it('shows validation error for invalid email', async () => {
-    const user = userEvent.setup();
-    render(<LoginForm onSubmit={mockOnSubmit} />);
-
-    await user.type(screen.getByRole('textbox', { name: /email/i }), 'invalid');
-    await user.click(screen.getByRole('button', { name: /submit/i }));
-
-    expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
-    expect(mockOnSubmit).not.toHaveBeenCalled();
-  });
-
-  it('disables submit button while loading', () => {
-    render(<LoginForm onSubmit={mockOnSubmit} isLoading />);
-
-    expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();
-  });
-});
-```
-
-### pytest (Python)
-
-```python
-import pytest
-from unittest.mock import Mock, patch, AsyncMock
-from app.auth import validate_token, TokenError
-
-class TestValidateToken:
-    """Tests for validate_token function."""
-
-    def test_valid_token_returns_true(self):
-        """Should return True for valid JWT token."""
-        token = "eyJhbGciOiJIUzI1NiIs..."
-        assert validate_token(token) is True
-
-    def test_decodes_payload_correctly(self, valid_token):
-        """Should decode payload with correct user ID."""
-        result = validate_token(valid_token)
-        assert result.payload["userId"] == 123
-
-    @pytest.mark.parametrize("invalid_input", [
-        "",
-        "not.a.token",
-        "a.b",
-        None,
-    ])
-    def test_rejects_invalid_tokens(self, invalid_input):
-        """Should return False for invalid token formats."""
-        assert validate_token(invalid_input) is False
-
-    def test_rejects_expired_token(self, expired_token):
-        """Should return False for expired tokens."""
-        assert validate_token(expired_token) is False
-
-    def test_raises_token_error_for_null(self):
-        """Should raise TokenError with descriptive message."""
-        with pytest.raises(TokenError, match="Token cannot be null"):
-            validate_token(None)
-
-    @pytest.fixture
-    def valid_token(self):
-        """Create a valid test token."""
-        return create_test_token({"userId": 123})
-
-    @pytest.fixture
-    def expired_token(self):
-        """Create an expired test token."""
-        return create_test_token({"exp": time.time() - 1000})
-
-
-class TestValidateTokenAsync:
-    """Tests for async token validation."""
-
-    @pytest.mark.asyncio
-    async def test_async_validation(self):
-        """Should validate token asynchronously."""
-        token = create_test_token({"userId": 456})
-        result = await validate_token_async(token)
-        assert result.valid is True
-
-    @pytest.mark.asyncio
-    async def test_handles_network_timeout(self):
-        """Should handle network timeout gracefully."""
-        with patch("app.auth.fetch_public_key", new_callable=AsyncMock) as mock:
-            mock.side_effect = TimeoutError()
-
-            with pytest.raises(TokenError, match="Validation timeout"):
-                await validate_token_async("token")
-```
-
-### PHPUnit (PHP)
-
-```php
-<?php
-
-namespace Tests\Unit\Services;
-
-use PHPUnit\Framework\TestCase;
-use App\Services\AuthService;
-use App\Exceptions\TokenException;
-use Mockery;
-
-class AuthServiceTest extends TestCase
-{
-    private AuthService $service;
-
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->service = new AuthService();
-    }
-
-    protected function tearDown(): void
-    {
-        Mockery::close();
-        parent::tearDown();
-    }
-
-    /** @test */
-    public function it_validates_correct_token(): void
-    {
-        $token = $this->createValidToken(['user_id' => 123]);
-
-        $result = $this->service->validateToken($token);
-
-        $this->assertTrue($result);
-    }
-
-    /** @test */
-    public function it_rejects_expired_token(): void
-    {
-        $token = $this->createExpiredToken();
-
-        $result = $this->service->validateToken($token);
-
-        $this->assertFalse($result);
-    }
-
-    /** @test */
-    public function it_throws_for_null_token(): void
-    {
-        $this->expectException(TokenException::class);
-        $this->expectExceptionMessage('Token cannot be null');
-
-        $this->service->validateToken(null);
-    }
-
-    /**
-     * @test
-     * @dataProvider invalidTokenProvider
-     */
-    public function it_rejects_invalid_tokens(string $invalidToken): void
-    {
-        $result = $this->service->validateToken($invalidToken);
-
-        $this->assertFalse($result);
-    }
-
-    public static function invalidTokenProvider(): array
-    {
-        return [
-            'empty string' => [''],
-            'malformed' => ['not.a.token'],
-            'missing parts' => ['a.b'],
-        ];
-    }
-}
-```
-
-### Pest (PHP)
-
-```php
-<?php
-
-use App\Services\AuthService;
-use App\Exceptions\TokenException;
-
-describe('AuthService', function () {
-    beforeEach(function () {
-        $this->service = new AuthService();
-    });
-
-    describe('validateToken', function () {
-        it('validates correct token', function () {
-            $token = createValidToken(['user_id' => 123]);
-
-            expect($this->service->validateToken($token))->toBeTrue();
-        });
-
-        it('rejects expired token', function () {
-            $token = createExpiredToken();
-
-            expect($this->service->validateToken($token))->toBeFalse();
-        });
-
-        it('throws for null token', function () {
-            $this->service->validateToken(null);
-        })->throws(TokenException::class, 'Token cannot be null');
-
-        it('rejects invalid tokens', function (string $invalidToken) {
-            expect($this->service->validateToken($invalidToken))->toBeFalse();
-        })->with([
-            'empty string' => '',
-            'malformed' => 'not.a.token',
-            'missing parts' => 'a.b',
-        ]);
-    });
-});
-```
-
-### Cypress (E2E)
-
-```typescript
-describe('Login Flow', () => {
-  beforeEach(() => {
-    cy.visit('/login');
-  });
-
-  it('should login with valid credentials', () => {
-    cy.get('[data-cy=email]').type('user@example.com');
-    cy.get('[data-cy=password]').type('password123');
-    cy.get('[data-cy=submit]').click();
-
-    cy.url().should('include', '/dashboard');
-    cy.get('[data-cy=welcome]').should('contain', 'Welcome');
-  });
-
-  it('should show error with invalid credentials', () => {
-    cy.intercept('POST', '/api/login', {
-      statusCode: 401,
-      body: { error: 'Invalid credentials' },
-    }).as('loginRequest');
-
-    cy.get('[data-cy=email]').type('user@example.com');
-    cy.get('[data-cy=password]').type('wrong');
-    cy.get('[data-cy=submit]').click();
-
-    cy.wait('@loginRequest');
-    cy.get('[data-cy=error]').should('be.visible');
-    cy.url().should('include', '/login');
-  });
-
-  it('should persist session after reload', () => {
-    cy.login('user@example.com', 'password123');
-    cy.visit('/dashboard');
-    cy.reload();
-
-    cy.get('[data-cy=welcome]').should('be.visible');
-  });
-});
-```
-
-### Cypress (Component)
-
-```typescript
-import LoginForm from './LoginForm.vue';
-
-describe('LoginForm Component', () => {
-  it('renders login form', () => {
-    cy.mount(LoginForm);
-
-    cy.get('[data-cy=email]').should('exist');
-    cy.get('[data-cy=password]').should('exist');
-    cy.get('[data-cy=submit]').should('contain', 'Login');
-  });
-
-  it('emits submit event with credentials', () => {
-    const onSubmitSpy = cy.spy().as('submitSpy');
-    cy.mount(LoginForm, { props: { onSubmit: onSubmitSpy } });
-
-    cy.get('[data-cy=email]').type('user@example.com');
-    cy.get('[data-cy=password]').type('password123');
-    cy.get('[data-cy=submit]').click();
-
-    cy.get('@submitSpy').should('have.been.calledWith', {
-      email: 'user@example.com',
-      password: 'password123',
-    });
-  });
-
-  it('validates email format', () => {
-    cy.mount(LoginForm);
-
-    cy.get('[data-cy=email]').type('invalid-email');
-    cy.get('[data-cy=submit]').click();
-
-    cy.get('[data-cy=email-error]').should('contain', 'Invalid email');
-  });
-});
-```
-
----
-
-## Usage Examples
-
-```bash
-# Generate tests for a file
-/testgen src/utils/auth.ts
-
-# Generate tests for specific function
-/testgen src/utils/auth.ts:validateToken
-
-# Generate tests for directory
-/testgen src/services/
-
-# Specify test type
-/testgen src/api/users.ts --type integration
-
-# Override framework detection
-/testgen src/helpers.js --framework jest
-
-# Focus on edge cases only
-/testgen src/parser.ts --focus edge
-
-# Quick generation (happy path only)
-/testgen src/utils.ts --depth quick
-
-# Thorough generation (all cases + mocking)
-/testgen src/complex-service.ts --depth thorough
-
-# Generate test stubs only (no implementation)
-/testgen src/new-feature.ts --stubs
-
-# Generate E2E tests
-/testgen src/pages/Login.tsx --type e2e
-
-# Generate component tests
-/testgen src/components/Button.vue --type component
-```
-
----
-
-## Focus Modes
-
-| Mode | What's Generated | Use When |
-|------|------------------|----------|
-| `--focus happy` | Normal inputs, expected outputs | Quick smoke tests |
-| `--focus edge` | Boundaries, empty, null, limits | Hardening existing code |
-| `--focus error` | Invalid inputs, exceptions | Error handling coverage |
-| `--focus all` | Everything (default) | New code, full coverage |
-
----
-
-## Depth Modes
-
-| Mode | Coverage | Output Size |
-|------|----------|-------------|
-| `--depth quick` | Happy path, 1-2 tests/function | Minimal |
-| `--depth normal` | Happy + common edges (default) | Moderate |
-| `--depth thorough` | All paths, mocking, async, types | Comprehensive |
-
----
-
-## Smart Features
-
-### Dependency Detection
-Automatically identifies and mocks:
-- External API calls (fetch, axios, httpx)
-- Database operations (queries, transactions)
-- File system operations
-- Environment variables
-- Third-party services
-
-### Test Location Intelligence
-Detects project convention:
-```bash
-# Scans existing tests to match pattern
-fd -e test.ts -e spec.ts | head -5
-
-# Matches: __tests__/, co-located, or tests/
-```
-
-### Import Style Matching
-Matches existing test imports:
-```typescript
-// Detects: vitest vs jest vs mocha
-// Detects: @testing-library vs enzyme
-// Detects: expect() style vs assert
-```
-
----
-
-## CLI Tool Integration
-
-| Tool | Purpose | Fallback |
-|------|---------|----------|
-| `jq` | Parse package.json | Read tool |
-| `rg` | Find existing tests | Grep tool |
-| `ast-grep` | Parse function signatures | ripgrep patterns |
-| `fd` | Find test files | Glob tool |
-
-**Graceful degradation:**
-```bash
-command -v jq >/dev/null 2>&1 && cat package.json | jq '.devDependencies' || cat package.json
-```
-
----
-
-## Integration
-
-| Command | Relationship |
-|---------|--------------|
-| `/review` | Review generated tests before committing |
-| `/explain` | Understand complex code before testing |
-| `/save` | Track test coverage goals |
-| `/testgen` | This command |
-
----
-
-## Notes
-
-- Generated tests are starting points - refine as needed
-- Review mocks for accuracy and completeness
-- Expert routing improves framework-specific patterns
-- Use `--stubs` when you prefer to write test logic yourself
-- Always run generated tests to verify they pass
-- Consider `/review` on generated tests before committing

+ 136 - 0
docs/COMMAND-SKILL-PATTERN.md

@@ -0,0 +1,136 @@
+# Command-Skill Pattern
+
+## Overview
+
+Claude Code has two extension concepts:
+
+| Concept | Structure | Invocation | Metadata |
+|---------|-----------|------------|----------|
+| Command | Single `.md` file | `/command` | Minimal (description) |
+| Skill | Directory with `SKILL.md` | `/skillname` or keywords | Rich (allowed-tools, depends-on, triggers) |
+
+Skills get slash-hint discovery via trigger keywords in their description and load on-demand, making them more efficient for complex functionality.
+
+## Architecture: Skills First
+
+Most functionality lives in **skills**, not commands. Only session management and experimental features remain as commands.
+
+```
+commands/           # Minimal (3 files)
+  sync.md           # Session bootstrap
+  save.md           # Session persistence
+  canvas.md         # Experimental TUI
+
+skills/             # Everything else (38 directories)
+  explain/
+    SKILL.md        # Core logic + expert routing
+  testgen/
+    SKILL.md        # Core logic
+    frameworks.md   # Language-specific examples
+  review/
+    SKILL.md        # Core logic
+  spawn/
+    SKILL.md        # Agent generation
+  atomise/
+    SKILL.md        # AoT reasoning
+  setperms/
+    SKILL.md        # Tool permissions
+  introspect/
+    SKILL.md        # Session log analysis
+  ...
+```
+
+## Skill Structure
+
+### SKILL.md
+
+```yaml
+---
+name: explain
+description: "Deep explanation of complex code. Triggers on: explain, deep dive, how does X work, architecture."
+allowed-tools: "Read Glob Grep Bash Task"
+compatibility: "Uses ast-grep, tokei if available."
+depends-on: []
+related-skills: ["structural-search", "code-stats"]
+---
+
+# Skill Name
+
+[Core logic, execution steps, patterns]
+```
+
+### Optional Reference Files
+
+```
+skills/testgen/
+  SKILL.md              # Core logic
+  frameworks.md         # Go, Rust, Python, TS examples (loaded on demand)
+  visual-testing.md     # Chrome DevTools integration (loaded on demand)
+```
+
+## Benefits
+
+1. **Context efficiency** - Skills load on-demand via trigger keywords
+2. **Rich metadata** - `allowed-tools`, `depends-on`, `related-skills`
+3. **Slash discovery** - Trigger keywords enable `/skillname` hints
+4. **Scalability** - Add reference files without bloating core
+5. **Maintainability** - Focused files, clear structure
+
+## When to Use Commands
+
+| Scenario | Use |
+|----------|-----|
+| Session management | Command (sync, save) |
+| Experimental/WIP | Command (canvas) |
+| Everything else | Skill |
+
+## When to Use Skills
+
+| Scenario | Use |
+|----------|-----|
+| Needs trigger-based discovery | Skill |
+| Needs explicit tool permissions | Skill |
+| Needs reference files | Skill |
+| Needs dependency tracking | Skill |
+| Complex multi-step workflow | Skill |
+
+## Skill Invocation
+
+Skills can be invoked multiple ways:
+
+1. **Direct slash**: `/explain`, `/testgen`, `/review`
+2. **Trigger keywords**: "explain this code", "generate tests", "review changes"
+3. **Skill tool**: Explicit `Skill tool` invocation with args
+
+## Current Skills (Converted from Commands)
+
+| Skill | Purpose | Trigger Keywords |
+|-------|---------|------------------|
+| `explain` | Deep code explanation | explain, deep dive, how does X work |
+| `spawn` | Agent generation | spawn agent, create agent, new expert |
+| `atomise` | AoT reasoning | atomise, complex reasoning, decompose |
+| `setperms` | Tool permissions | setperms, init tools, setup project |
+| `introspect` | Session log analysis | introspect, session history, what did we do |
+| `review` | Code review | code review, review changes, check code |
+| `testgen` | Test generation | generate tests, write tests, add coverage |
+
+## Creating New Skills
+
+1. Create `skills/{name}/` directory
+2. Create `SKILL.md` with proper frontmatter:
+   - `name`: kebab-case, matches directory
+   - `description`: Include trigger keywords
+   - `allowed-tools`: Space-separated list
+3. Add optional reference files as needed
+4. Add to `plugin.json` under `components.skills`
+
+## Migration from Commands
+
+If you have a command that should be a skill:
+
+1. Create `skills/{name}/SKILL.md`
+2. Move content, add frontmatter with triggers
+3. Delete `commands/{name}.md`
+4. Update `plugin.json`:
+   - Remove from `components.commands`
+   - Add to `components.skills`

+ 44 - 23
docs/PLAN.md

@@ -3,7 +3,7 @@
 **Goal**: A centralized repository of custom Claude Code commands, agents, and skills that enhance Claude Code's native capabilities with persistent session state, specialized expert agents, and streamlined workflows.
 
 **Created**: 2025-11-27
-**Last Updated**: 2025-12-22
+**Last Updated**: 2026-01-24
 **Status**: Active Development
 
 ---
@@ -12,10 +12,10 @@
 
 | Component | Count | Notes |
 |-----------|-------|-------|
-| Agents | 23 | Domain experts (Python, Go, Rust, React, etc.) |
-| Skills | 30 | Pattern libraries (Python deep, others emerging) |
-| Commands | 11 | Workflow automation |
-| Rules | 4 | CLI tools, thinking, commit style, naming |
+| Agents | 22 | Domain experts (Python, Go, Rust, React, etc.) |
+| Skills | 38 | Pattern libraries, CLI tools, workflows, dev tasks |
+| Commands | 3 | Session management (sync, save) + experimental (canvas) |
+| Rules | 5 | CLI tools, thinking, commit style, naming, skill-agent-updates |
 | Output Styles | 1 | Vesper personality |
 | Hooks | 0 | Config examples only |
 
@@ -29,26 +29,25 @@
 - [x] Agent genesis system (`/spawn`)
 - [x] Installation scripts (Unix + Windows)
 
-### Expert Agents (23)
+### Expert Agents (22)
 - [x] Languages: Python, TypeScript, JavaScript, Go, Rust, SQL, Bash
 - [x] Frontend: React, Vue, Astro
 - [x] Backend: Laravel, PayloadCMS, CraftCMS
 - [x] Infrastructure: AWS Fargate, Cloudflare, Wrangler
-- [x] Testing: Cypress, Playwright
+- [x] Testing: Cypress
 - [x] Databases: PostgreSQL, SQL patterns
 - [x] Specialized: Claude-architect, Project-organizer
 
-### Skills (30)
+### Skills (38)
 - [x] Python patterns (8): async, cli, database, env, fastapi, observability, pytest, typing
 - [x] Claude Code internals: debug, headless, hooks, templates
 - [x] Workflows: git, data-processing, structural-search, task-runner
 - [x] Patterns: REST, SQL, security, testing, tailwind
+- [x] Development: explain, spawn, atomise, setperms, introspect, review, testgen
 
-### Commands (11)
-- [x] Session: `/save`, `/sync`, `/atomise`
-- [x] Development: `/review`, `/testgen`, `/explain`
-- [x] Multi-model: `/conclave`, `/spawn`
-- [x] Utilities: `/pulse`, `/setperms`, `/archive`
+### Commands (3)
+- [x] Session: `/save`, `/sync`
+- [x] Experimental: `/canvas`
 
 ### Documentation
 - [x] ARCHITECTURE.md - Extension system guide with authority levels
@@ -101,14 +100,14 @@
 | `testing-architect` | Strategy decisions |
 | `api-design-expert` | OpenAPI, versioning |
 
-#### Command Gaps
+#### Skill Gaps
 
-| Command | Purpose |
-|---------|---------|
-| `/debug` | Systematic debugging workflow |
-| `/migrate` | Framework/version upgrades |
-| `/refactor` | Safe refactoring |
-| `/secure` | Security audit checklist |
+| Skill | Purpose |
+|-------|---------|
+| `debug` | Systematic debugging workflow |
+| `migrate` | Framework/version upgrades |
+| `refactor` | Safe refactoring |
+| `secure` | Security audit checklist |
 
 #### Skill Parity
 
@@ -138,7 +137,7 @@ Languages needing Python-level depth:
             +-----------+-----------+
        High | Agent     | Analytics |
             | Gaps      |           |
-            | Commands  | Lang      |
+            | Skills    | Lang      |
             |           | Parity    |
             +-----------+-----------+
 ```
@@ -147,11 +146,33 @@ Languages needing Python-level depth:
 
 ## Immediate Next Steps
 
+### Command-to-Skill Consolidation (Complete)
+
+Most commands have been converted to skills for better discovery and on-demand loading. See `docs/COMMAND-SKILL-PATTERN.md`.
+
+**Completed conversions:**
+- [x] `/testgen` → `skills/testgen/`
+- [x] `/review` → `skills/review/`
+- [x] `/explain` → `skills/explain/`
+- [x] `/spawn` → `skills/spawn/`
+- [x] `/atomise` → `skills/atomise/`
+- [x] `/setperms` → `skills/setperms/`
+- [x] `/introspect` → `skills/introspect/`
+
+**Remaining as commands:**
+- `/sync` - Session bootstrap (paired with /save)
+- `/save` - Session persistence (paired with /sync)
+- `/canvas` - Experimental (Warp-specific)
+
+---
+
+### Planned Work
+
 - [x] Create `rules/commit-style.md`
 - [x] Create `rules/naming-conventions.md`
 - [ ] Create Spartan output style
 - [ ] Add docker-expert agent
-- [ ] Implement `/debug` command
+- [ ] Add `/debug` skill (systematic debugging workflow)
 
 ---
 
@@ -169,4 +190,4 @@ Languages needing Python-level depth:
 
 ---
 
-*Plan managed by `/save` command. Last updated: 2025-12-22*
+*Plan managed by `/save` command. Last updated: 2026-01-24*

+ 1 - 1
marketplace.json

@@ -4,7 +4,7 @@
   "plugins": [
     {
       "name": "claude-mods",
-      "description": "23 expert agents, 11 commands, 30 skills, 4 rules, and output styles for Claude Code",
+      "description": "22 expert agents, 38 skills, 3 commands, 5 rules, and output styles for Claude Code",
       "source": {
         "type": "git",
         "url": "https://github.com/0xDarkMatter/claude-mods.git"

+ 66 - 0
rules/skill-agent-updates.md

@@ -0,0 +1,66 @@
+# Skill and Agent Updates
+
+## Mandatory Documentation Check
+
+**BEFORE creating or updating any skill or agent**, always check the official Claude Code documentation:
+
+| Resource | URL | Check For |
+|----------|-----|-----------|
+| Skills | https://code.claude.com/docs/en/skills | New frontmatter fields, context options |
+| Sub-agents | https://code.claude.com/docs/en/sub-agents | Permission modes, built-in agents |
+
+These docs change frequently. Features we should watch for:
+
+## Current Skill Frontmatter Fields (January 2026)
+
+```yaml
+---
+name: skill-name                    # Required: kebab-case
+description: "Triggers on: ..."     # Required: include trigger keywords
+allowed-tools: "Read Write Bash"    # Restrict available tools
+disable-model-invocation: false     # true = manual /skill only
+user-invocable: true                # false = hide from slash completion
+context: main                       # main | fork (subagent isolation)
+agent: custom-agent                 # Custom system prompt agent
+hooks:
+  preToolUse:
+    - command: "echo pre"
+  postToolUse:
+    - command: "echo post"
+---
+```
+
+## Current Subagent Options
+
+| Field | Values | Purpose |
+|-------|--------|---------|
+| `permissionMode` | default, acceptEdits, bypassPermissions | Control autonomy |
+| `skills` | [skill-names] | Preload skills in subagent |
+| `model` | sonnet, opus, haiku | Override model |
+
+## Decision Framework: Main Context vs Fork
+
+| Question | If Yes → | If No → |
+|----------|----------|---------|
+| Does it need current session state (tasks, conversation)? | Main context | Consider fork |
+| Is output verbose (>500 lines)? | Consider fork | Main context |
+| Does it need user interaction during execution? | Main context | Consider fork |
+| Is it a one-shot research/analysis task? | Fork | Main context |
+
+## Skills Using Subagent Isolation
+
+Skills that delegate to Task agents or use `context: fork`:
+
+| Skill | Method | Why |
+|-------|--------|-----|
+| `introspect` | Task agent (background) | Session log analysis is verbose |
+
+## Session Commands Analysis
+
+| Command | Context | Rationale |
+|---------|---------|-----------|
+| `/sync` | Main | Must restore session state (tasks, context) |
+| `/save` | Main | Must access current tasks via TaskList |
+| `/canvas` | Main | Interactive TUI requires real-time feedback |
+
+These MUST run in main context - subagent isolation would break their core functionality.

+ 30 - 25
commands/atomise.md

@@ -1,5 +1,10 @@
 ---
-description: "Atom of Thoughts (AoT) reasoning - decompose complex problems into atomic units with confidence tracking and backtracking. For genuinely complex reasoning, not everyday questions."
+name: atomise
+description: "Atom of Thoughts (AoT) reasoning - decompose complex problems into atomic units with confidence tracking and backtracking. For genuinely complex reasoning, not everyday questions. Triggers on: atomise, complex reasoning, decompose problem, structured thinking, verify hypothesis."
+allowed-tools: "Read"
+compatibility: "Pure reasoning framework, no external dependencies."
+depends-on: []
+related-skills: []
 ---
 
 # Atomise - Atom of Thoughts Reasoning
@@ -18,11 +23,11 @@ Decompose complex problems into minimal, verifiable "atoms" of thought. Unlike c
 ## The Core Loop
 
 ```
-1. DECOMPOSE  Break into atomic subquestions (1-2 sentences each)
-2. SOLVE      Answer leaf nodes first, propagate up
-3. VERIFY     Test each hypothesis (counterexample, consistency, domain check)
-4. CONTRACT   Summarize verified state in 2 sentences (drop history)
-5. EVALUATE   Confident enough? Done. Too uncertain? Backtrack and try another path.
+1. DECOMPOSE -> Break into atomic subquestions (1-2 sentences each)
+2. SOLVE     -> Answer leaf nodes first, propagate up
+3. VERIFY    -> Test each hypothesis (counterexample, consistency, domain check)
+4. CONTRACT  -> Summarize verified state in 2 sentences (drop history)
+5. EVALUATE  -> Confident enough? Done. Too uncertain? Backtrack and try another path.
 ```
 
 Repeat until confident or all paths exhausted.
@@ -61,24 +66,24 @@ These numbers are *heuristic*, not calibrated probabilities. They're useful for
 | **< 0.5** | Backtrack - this path isn't working |
 
 **Verification adjusts confidence:**
-- Confirmed  maintain or slight boost
-- Partial  reduce ~15%
-- Refuted  major reduction, likely backtrack
+- Confirmed -> maintain or slight boost
+- Partial -> reduce ~15%
+- Refuted -> major reduction, likely backtrack
 
 ---
 
 ## Modes
 
 **Depth:**
-- `--light`  Fast: max 3 levels, 0.70 confidence threshold
-- *(default)*  Standard: max 5 levels, 0.85 threshold
-- `--deep`  Exhaustive: max 7 levels, 0.90 threshold
+- `--light` - Fast: max 3 levels, 0.70 confidence threshold
+- *(default)* - Standard: max 5 levels, 0.85 threshold
+- `--deep` - Exhaustive: max 7 levels, 0.90 threshold
 
 **Domain** (adjusts verification style):
-- `--math`  Arithmetic checks, proof validation, boundary tests
-- `--code`  Type checking, invariant verification, test generation
-- `--security`  Threat modeling, attack surface, adversarial thinking
-- `--design`  Tradeoff analysis, constraint satisfaction, feasibility
+- `--math` - Arithmetic checks, proof validation, boundary tests
+- `--code` - Type checking, invariant verification, test generation
+- `--security` - Threat modeling, attack surface, adversarial thinking
+- `--design` - Tradeoff analysis, constraint satisfaction, feasibility
 
 ---
 
@@ -88,7 +93,7 @@ These numbers are *heuristic*, not calibrated probabilities. They're useful for
 ANSWER: {result}
 CONFIDENCE: {0.0-1.0} - {why}
 
-KEY CHAIN: P1 → R1 → H1 → V1 → C1
+KEY CHAIN: P1 -> R1 -> H1 -> V1 -> C1
 
 ATOMS:
 | id | type | content | conf | verified |
@@ -114,14 +119,14 @@ Add `--verbose` for full trace, `--quiet` for just the answer.
 
 ### Phase 1+: Iterate
 
-1. **Atomicity gate:** Can you answer from verified atoms? Yes → solve. No → decompose.
+1. **Atomicity gate:** Can you answer from verified atoms? Yes -> solve. No -> decompose.
 2. **Decompose:** Build dependency tree of atomic subquestions
 3. **Solve + Verify:** Leaves first, propagate up. Every hypothesis needs verification.
-4. **Contract:** Summarize in 2 sentences. Drop everything else.
+4. **Contract:** Summarize in <=2 sentences. Drop everything else.
 5. **Evaluate:**
-   - Confident?  Terminate
-   - Uncertain but viable?  Continue
-   - Low confidence?  Backtrack, try alternative
+   - Confident? -> Terminate
+   - Uncertain but viable? -> Continue
+   - Low confidence? -> Backtrack, try alternative
 
 ### Backtracking
 
@@ -153,9 +158,9 @@ When a path yields confidence < 0.5 after verification:
 ## Anti-Patterns
 
 ```
-BAD:  /atomise "What's 2+2?"            Just answer it
-BAD:  /atomise "Rewrite this function"  That's implementation, not reasoning
-BAD:  Forcing conclusion despite low confidence  Let it backtrack
+BAD:  /atomise "What's 2+2?"           -> Just answer it
+BAD:  /atomise "Rewrite this function" -> That's implementation, not reasoning
+BAD:  Forcing conclusion despite low confidence -> Let it backtrack
 GOOD: /atomise for genuine uncertainty requiring structured decomposition
 ```
 

+ 47 - 43
commands/explain.md

@@ -1,5 +1,10 @@
 ---
-description: "Deep explanation of complex code, files, or concepts. Routes to expert agents, uses structural search, generates mermaid diagrams."
+name: explain
+description: "Deep explanation of complex code, files, or concepts. Routes to expert agents, uses structural search, generates mermaid diagrams. Triggers on: explain, deep dive, how does X work, architecture, data flow."
+allowed-tools: "Read Glob Grep Bash Task"
+compatibility: "Uses ast-grep, tokei, rg, fd if available. Falls back to standard tools."
+depends-on: []
+related-skills: ["structural-search", "code-stats"]
 ---
 
 # Explain - Deep Code Explanation
@@ -18,36 +23,36 @@ $ARGUMENTS
 
 ```
 /explain <target> [--depth] [--focus]
-    
-    ├─→ Step 1: Detect & Classify Target
-    │     ├─ File exists? → Read it
-    │     ├─ Function/class? → ast-grep to find definition
-    │     ├─ Directory? → tokei for overview
-    │     └─ Concept? → rg search codebase
-    
-    ├─→ Step 2: Gather Context (parallel)
-    │     ├─ structural-search skill → find usages
-    │     ├─ code-stats skill → assess scope
-    │     ├─ Find related: tests, types, docs
-    │     └─ Load: AGENTS.md, CLAUDE.md conventions
-    
-    ├─→ Step 3: Route to Expert Agent
-    │     ├─ .ts/.tsx → typescript-expert or react-expert
-    │     ├─ .py → python-expert
-    │     ├─ .vue → vue-expert
-    │     ├─ .sql/migrations → postgres-expert
-    │     ├─ agents/skills/commands → claude-architect
-    │     └─ Default → general-purpose
-    
-    ├─→ Step 4: Generate Explanation
-    │     ├─ Structured markdown with sections
-    │     ├─ Mermaid diagrams (flowchart/sequence/class)
-    │     ├─ Related code paths as file:line refs
-    │     └─ Design decisions and rationale
-    
-    └─→ Step 5: Integrate
-          ├─ Offer to save to ARCHITECTURE.md (if significant)
-          └─ Link to /save if working on related task
+    |
+    +-> Step 1: Detect & Classify Target
+    |     +- File exists? -> Read it
+    |     +- Function/class? -> ast-grep to find definition
+    |     +- Directory? -> tokei for overview
+    |     +- Concept? -> rg search codebase
+    |
+    +-> Step 2: Gather Context (parallel)
+    |     +- structural-search skill -> find usages
+    |     +- code-stats skill -> assess scope
+    |     +- Find related: tests, types, docs
+    |     +- Load: AGENTS.md, CLAUDE.md conventions
+    |
+    +-> Step 3: Route to Expert Agent
+    |     +- .ts/.tsx -> typescript-expert or react-expert
+    |     +- .py -> python-expert
+    |     +- .vue -> vue-expert
+    |     +- .sql/migrations -> postgres-expert
+    |     +- agents/skills/commands -> claude-architect
+    |     +- Default -> general-purpose
+    |
+    +-> Step 4: Generate Explanation
+    |     +- Structured markdown with sections
+    |     +- Mermaid diagrams (flowchart/sequence/class)
+    |     +- Related code paths as file:line refs
+    |     +- Design decisions and rationale
+    |
+    +-> Step 5: Integrate
+          +- Offer to save to ARCHITECTURE.md (if significant)
+          +- Link to /save if working on related task
 ```
 
 ## Execution Steps
@@ -68,7 +73,7 @@ test -d "$TARGET" && echo "DIRECTORY" && exit
 
 **For directories:** Get overview with tokei (if available):
 ```bash
-command -v tokei >/dev/null 2>&1 && tokei "$TARGET" --compact || echo "tokei unavailable"
+command -v tokei >/dev/null 2>&1 && tokei "$TARGET" --compact || echo "tokei unavailable"
 ```
 
 **For symbols (function/class):** Find definition with ast-grep:
@@ -148,16 +153,16 @@ The expert agent produces a structured explanation:
 [Mermaid diagram - choose appropriate type]
 
 ### Flowchart (for control flow)
-```mermaid
+` ` `mermaid
 flowchart TD
     A[Input] --> B{Validate}
     B -->|Valid| C[Process]
     B -->|Invalid| D[Error]
     C --> E[Output]
-```
+` ` `
 
 ### Sequence (for interactions)
-```mermaid
+` ` `mermaid
 sequenceDiagram
     participant Client
     participant Server
@@ -166,17 +171,17 @@ sequenceDiagram
     Server->>Database: Query
     Database-->>Server: Result
     Server-->>Client: Response
-```
+` ` `
 
 ### Class (for structures)
-```mermaid
+` ` `mermaid
 classDiagram
     class Component {
         +props: Props
         +state: State
         +render(): JSX
     }
-```
+` ` `
 
 ## How It Works
 
@@ -270,7 +275,7 @@ Commands use modern CLI tools with graceful fallbacks:
 
 **Check availability:**
 ```bash
-command -v tokei >/dev/null 2>&1 || echo "tokei not installed - skipping stats"
+command -v tokei >/dev/null 2>&1 || echo "tokei not installed - skipping stats"
 ```
 
 ## Usage Examples
@@ -300,12 +305,11 @@ command -v tokei >/dev/null 2>&1 || echo "ℹ tokei not installed - skipping sta
 
 ## Integration
 
-| Command | Relationship |
-|---------|--------------|
+| Skill/Command | Relationship |
+|---------------|--------------|
 | `/review` | Review after understanding |
-| `/test` | Generate tests for explained code |
+| `/testgen` | Generate tests for explained code |
 | `/save` | Save progress if working on related task |
-| Native `/plan` | Enter Claude Code's planning mode for implementation |
 
 ## Persistence
 

+ 108 - 178
commands/review.md

@@ -1,27 +1,17 @@
 ---
-description: "Code review with semantic diffs, expert routing, and auto-TodoWrite. Analyzes staged changes or specific files for bugs, security, performance, and style."
+name: review
+description: "Code review with semantic diffs, expert routing, and auto-TaskCreate. Triggers on: code review, review changes, check code, review PR, security audit."
+allowed-tools: "Read Write Edit Bash Glob Grep Task TaskCreate TaskUpdate"
 ---
 
-# Review - AI Code Review
+# Review Skill - AI Code Review
 
-Perform a comprehensive code review on staged changes, specific files, or pull requests. Routes to expert agents based on file types, respects project conventions, and automatically creates TodoWrite tasks for critical issues.
-
-## Arguments
-
-$ARGUMENTS
-
-- No args: Review staged changes (`git diff --cached`)
-- `<file>`: Review specific file
-- `<directory>`: Review all files in directory
-- `--all`: Review all uncommitted changes
-- `--pr <number>`: Review a GitHub PR
-- `--focus <security|perf|types|tests|style>`: Focus on specific area
-- `--depth <quick|normal|thorough>`: Review depth
+Perform comprehensive code reviews on staged changes, specific files, or pull requests. Routes to expert agents based on file types and automatically creates tasks for critical issues.
 
 ## Architecture
 
 ```
-/review [target] [--focus] [--depth]
+review [target] [--focus] [--depth]
     ├─→ Step 1: Determine Scope
     │     ├─ No args → git diff --cached (staged)
@@ -45,6 +35,8 @@ $ARGUMENTS
     │     ├─ TypeScript → typescript-expert
     │     ├─ React/JSX → react-expert
     │     ├─ Python → python-expert
+    │     ├─ Go → go-expert
+    │     ├─ Rust → rust-expert
     │     ├─ Vue → vue-expert
     │     ├─ SQL/migrations → postgres-expert
     │     ├─ Claude extensions → claude-architect
@@ -57,9 +49,9 @@ $ARGUMENTS
     │     └─ Overall verdict: Ready to commit? Y/N
     └─→ Step 6: Integration
-          ├─ Auto-create TodoWrite for CRITICAL issues
+          ├─ Auto-create tasks (TaskCreate) for CRITICAL issues
           ├─ Link to /save for tracking
-          └─ Suggest follow-up: /test, /explain
+          └─ Suggest follow-up: /testgen, /explain
 ```
 
 ## Execution Steps
@@ -88,6 +80,11 @@ gh pr diff $PR_NUMBER --patch
 git diff HEAD -- "$FILE"
 ```
 
+**For baseline comparison (--base):**
+```bash
+git diff $BASE_BRANCH...HEAD
+```
+
 ### Step 2: Analyze Changes
 
 Run semantic diff analysis (parallel where possible):
@@ -104,7 +101,6 @@ command -v delta >/dev/null 2>&1 && git diff --cached | delta || git diff --cach
 
 **Categorize changes:**
 ```bash
-# Get changed files
 git diff --cached --name-only | while read file; do
     case "$file" in
         *.test.* | *.spec.*) echo "TEST: $file" ;;
@@ -122,7 +118,6 @@ git diff --cached --stat
 
 ### Step 3: Load Project Standards
 
-**Check for project conventions:**
 ```bash
 # Claude Code conventions
 cat AGENTS.md 2>/dev/null | head -50
@@ -135,7 +130,6 @@ cat pyproject.toml 2>/dev/null | head -30
 
 # Test framework detection
 cat package.json 2>/dev/null | jq '.devDependencies | keys | map(select(test("jest|vitest|mocha|cypress|playwright")))' 2>/dev/null
-cat pyproject.toml 2>/dev/null | grep -E "pytest|unittest" 2>/dev/null
 ```
 
 **Check CI for existing linting:**
@@ -145,22 +139,23 @@ cat .github/workflows/*.yml 2>/dev/null | grep -E "eslint|prettier|pylint|ruff"
 
 ### Step 4: Route to Expert Reviewers
 
-Determine experts based on changed files:
-
 | File Pattern | Primary Expert | Secondary Expert |
 |--------------|----------------|------------------|
 | `*.ts` | typescript-expert | - |
 | `*.tsx` | react-expert | typescript-expert |
 | `*.vue` | vue-expert | typescript-expert |
 | `*.py` | python-expert | sql-expert (if ORM) |
+| `*.go` | go-expert | - |
+| `*.rs` | rust-expert | - |
 | `*.sql`, `migrations/*` | postgres-expert | - |
 | `agents/*.md`, `skills/*`, `commands/*` | claude-architect | - |
 | `*.test.*`, `*.spec.*` | cypress-expert | (framework expert) |
+| `*.cy.ts`, `cypress/*` | cypress-expert | typescript-expert |
+| `*.spec.ts` (Playwright) | typescript-expert | - |
+| `playwright/*`, `e2e/*` | typescript-expert | - |
 | `wrangler.toml`, `workers/*` | wrangler-expert | cloudflare-expert |
 | `*.sh`, `*.bash` | bash-expert | - |
 
-**Multi-domain changes:** If files span multiple domains, dispatch experts in parallel via Task tool.
-
 **Invoke via Task tool:**
 ```
 Task tool with subagent_type: "[detected]-expert"
@@ -218,7 +213,7 @@ The expert produces a structured review:
 
 **Issue:** Missing dependency in useEffect
 
-**Suggestion:** Add `userId` to dependency array or use useCallback
+**Suggestion:** Add `userId` to dependency array
 
 ```diff
 - useEffect(() => { fetchUser(userId) }, []);
@@ -229,22 +224,13 @@ The expert produces a structured review:
 
 ## Suggestions
 
-### `src/utils/helpers.ts:15`
-
-**Suggestion:** Consider using optional chaining
-
-```diff
-- const name = user && user.profile && user.profile.name;
-+ const name = user?.profile?.name;
-```
+[Style improvements, optional enhancements]
 
 ---
 
 ## Praise
 
-### `src/services/api.ts:78`
-
-**Good pattern:** Proper error boundary with typed error handling. This is exactly the pattern we want to follow.
+[Good patterns worth noting]
 
 ---
 
@@ -253,43 +239,45 @@ The expert produces a structured review:
 | File | Changes | Issues |
 |------|---------|--------|
 | `src/auth/login.ts` | +42/-8 | 1 critical |
-| `src/components/Form.tsx` | +89/-23 | 1 warning |
-| `src/utils/helpers.ts` | +15/-3 | 1 suggestion |
-
-## Follow-up
-
-- Run `/test src/auth/` to verify security fix
-- Run `/explain src/auth/login.ts` for deeper understanding
-- Use `/save` to track these issues
 ```
 
 ### Step 6: Integration
 
-**Auto-create TodoWrite tasks for CRITICAL issues:**
+**Auto-create tasks for CRITICAL issues:**
+```
+TaskCreate:
+  subject: "Fix: SQL injection in login.ts:42"
+  description: "SQL injection vulnerability found in user input handling."
+  activeForm: "Fixing SQL injection in login.ts:42"
+```
 
-For each CRITICAL issue found, automatically add to TodoWrite:
+**Link with dependencies for related issues:**
 ```
-TodoWrite:
-  - content: "Fix: SQL injection in login.ts:42"
-    status: "pending"
-    activeForm: "Fixing SQL injection in login.ts:42"
+TaskCreate: #1 "Fix SQL injection in login.ts"
+TaskCreate: #2 "Fix SQL injection in register.ts"
+TaskUpdate: taskId: "2", addBlockedBy: ["1"]
 ```
 
-**Link to session management:**
+**After fixing issues:**
 ```
-Issues have been added to your task list.
-Run /save to persist before ending session.
+TaskUpdate:
+  taskId: "1"
+  status: "completed"
 ```
 
+---
+
 ## Severity System
 
-| Level | Icon | Meaning | Action | Auto-Todo? |
+| Level | Icon | Meaning | Action | Auto-Task? |
 |-------|------|---------|--------|------------|
 | CRITICAL | :red_circle: | Security bug, data loss risk, crashes | Must fix before merge | Yes |
 | WARNING | :yellow_circle: | Logic issues, performance problems | Should address | No |
 | SUGGESTION | :blue_circle: | Style, minor improvements | Optional | No |
 | PRAISE | :star: | Good patterns worth noting | Recognition | No |
 
+---
+
 ## Focus Modes
 
 | Mode | What It Checks |
@@ -301,28 +289,7 @@ Run /save to persist before ending session.
 | `--style` | Naming, organization, dead code, comments |
 | (default) | All of the above |
 
-### Security Focus Example
-```bash
-/review --security
-```
-Checks for:
-- Hardcoded secrets, API keys
-- SQL/NoSQL injection
-- XSS vulnerabilities
-- Insecure dependencies
-- Auth/authz issues
-- CORS misconfigurations
-
-### Performance Focus Example
-```bash
-/review --perf
-```
-Checks for:
-- N+1 database queries
-- Unnecessary re-renders (React)
-- Memory leaks
-- Blocking operations in async code
-- Unoptimized algorithms
+---
 
 ## Depth Modes
 
@@ -332,6 +299,64 @@ Checks for:
 | `--normal` | Standard review, all severity levels (default) |
 | `--thorough` | Deep analysis, traces data flow, checks edge cases |
 
+---
+
+## Advanced Flags
+
+### `--base <branch>` - Baseline Comparison
+
+Compare changes against a specific branch instead of HEAD:
+
+```bash
+/review --base main
+/review src/ --base develop --thorough
+```
+
+### `--json` - CI/CD Integration
+
+Output review results as JSON:
+
+```json
+{
+  "summary": {
+    "files_reviewed": 3,
+    "lines_changed": { "added": 42, "removed": 8 },
+    "issues": { "critical": 1, "warning": 2, "suggestion": 1 }
+  },
+  "verdict": {
+    "ready_to_commit": false,
+    "reason": "1 critical issue requires attention"
+  },
+  "issues": [...]
+}
+```
+
+**CI/CD usage:**
+```yaml
+- name: Code Review
+  run: |
+    claude "/review --json" > review.json
+    if jq -e '.issues[] | select(.severity == "critical")' review.json; then
+      exit 1
+    fi
+```
+
+### `--fix` - Auto-Apply Fixes
+
+Automatically apply suggested fixes:
+
+1. Performs standard review
+2. For each fixable issue, prompts for confirmation
+3. Uses Edit tool to apply approved fixes
+4. Creates TaskUpdate for resolved issues
+
+**Non-interactive mode:**
+```bash
+/review --fix --auto-approve
+```
+
+---
+
 ## CLI Tool Integration
 
 | Tool | Purpose | Fallback |
@@ -347,114 +372,19 @@ Checks for:
 command -v delta >/dev/null 2>&1 && git diff --cached | delta || git diff --cached
 ```
 
-## Usage Examples
-
-```bash
-# Review staged changes (default)
-/review
-
-# Review all uncommitted changes
-/review --all
-
-# Review specific file
-/review src/auth/login.ts
-
-# Review a directory
-/review src/components/
-
-# Review a GitHub PR
-/review --pr 123
-
-# Security-focused review
-/review --security
-
-# Performance-focused review
-/review --perf
+---
 
-# Quick scan before committing
-/review --quick
+## Reference Files
 
-# Thorough review for important changes
-/review --thorough
+For framework-specific checks, see:
+- `framework-checks.md` - React, TypeScript, Python, Go, Rust, Vue, SQL patterns
 
-# Combined: thorough security review of PR
-/review --pr 456 --security --thorough
-```
-
-## Framework-Specific Checks
-
-### React/Next.js
-- Hook rules violations
-- Missing useEffect dependencies
-- Key prop issues in lists
-- Server/client component boundaries
-- Hydration mismatches
-
-### TypeScript
-- `any` type abuse
-- Missing type annotations on exports
-- Incorrect generic constraints
-- Type assertion overuse (`as`)
-- Null/undefined handling
-
-### Python
-- Mutable default arguments
-- Bare `except:` clauses
-- Resource leaks (files, connections)
-- SQL string formatting
-- Type hint inconsistencies
-
-### Vue
-- Reactivity gotchas
-- Missing v-key in v-for
-- Props mutation
-- Composition API anti-patterns
-
-### SQL/Database
-- SQL injection risks
-- N+1 query patterns
-- Missing indexes
-- Transaction handling
-- Migration safety
+---
 
 ## Integration
 
 | Command | Relationship |
 |---------|--------------|
 | `/explain` | Deep dive into flagged code |
-| `/test` | Generate tests for issues found |
+| `/testgen` | Generate tests for issues found |
 | `/save` | Persist review findings to session state |
-| Native `/plan` | Enter Claude Code's planning mode |
-
-## Workflow Examples
-
-### Pre-Commit Review
-```bash
-git add .
-/review
-# Fix issues...
-git commit -m "feat: add user auth"
-```
-
-### PR Review Workflow
-```bash
-/review --pr 123 --thorough
-# Creates TodoWrite tasks for critical issues
-# Fix issues...
-/save "Addressed review findings"
-```
-
-### Security Audit
-```bash
-/review src/ --security --thorough
-# Comprehensive security scan of entire directory
-```
-
-## Notes
-
-- Reviews are suggestions, not absolute rules
-- Context matters - some "issues" may be intentional
-- CRITICAL issues are auto-added to TodoWrite
-- Use `/save` to persist review tasks across sessions
-- Expert agents provide framework-specific insights
-- Respects project conventions from AGENTS.md

+ 873 - 0
skills/review/framework-checks.md

@@ -0,0 +1,873 @@
+# Framework-Specific Review Checks
+
+Reference document for expert reviewers. Contains common issues, anti-patterns, and best practices by framework.
+
+---
+
+## React / Next.js
+
+### Hook Rules
+
+```typescript
+// BAD: Conditional hook call
+function Component({ show }) {
+  if (show) {
+    const [value, setValue] = useState(0); // Hooks must be at top level
+  }
+}
+
+// GOOD: Always call hooks unconditionally
+function Component({ show }) {
+  const [value, setValue] = useState(0);
+  if (!show) return null;
+}
+```
+
+### useEffect Dependencies
+
+```typescript
+// BAD: Missing dependency
+useEffect(() => {
+  fetchUser(userId);
+}, []); // userId missing from deps
+
+// GOOD: Include all dependencies
+useEffect(() => {
+  fetchUser(userId);
+}, [userId]);
+
+// GOOD: Use useCallback for stable references
+const fetchUserData = useCallback(() => {
+  fetchUser(userId);
+}, [userId]);
+
+useEffect(() => {
+  fetchUserData();
+}, [fetchUserData]);
+```
+
+### Key Props in Lists
+
+```tsx
+// BAD: Index as key (causes issues with reordering)
+{items.map((item, index) => (
+  <Item key={index} {...item} />
+))}
+
+// GOOD: Stable unique identifier
+{items.map((item) => (
+  <Item key={item.id} {...item} />
+))}
+```
+
+### Server/Client Boundaries (Next.js App Router)
+
+```tsx
+// BAD: Using hooks in Server Component
+// app/page.tsx (Server Component by default)
+export default function Page() {
+  const [count, setCount] = useState(0); // Error!
+}
+
+// GOOD: Mark as Client Component
+'use client';
+export default function Page() {
+  const [count, setCount] = useState(0);
+}
+
+// BETTER: Keep Server Component, extract interactive part
+// app/page.tsx
+import Counter from './Counter';
+export default function Page() {
+  return <Counter />;
+}
+
+// app/Counter.tsx
+'use client';
+export default function Counter() {
+  const [count, setCount] = useState(0);
+  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
+}
+```
+
+### Prop Drilling vs Context
+
+```tsx
+// BAD: Excessive prop drilling
+<Parent user={user}>
+  <Child user={user}>
+    <GrandChild user={user}>
+      <DeepChild user={user} />  // 4 levels deep
+    </GrandChild>
+  </Child>
+</Parent>
+
+// GOOD: Use Context for widely-shared state
+const UserContext = createContext<User | null>(null);
+
+function Parent({ user }) {
+  return (
+    <UserContext.Provider value={user}>
+      <Child />
+    </UserContext.Provider>
+  );
+}
+
+function DeepChild() {
+  const user = useContext(UserContext);
+}
+```
+
+### Memo Optimization
+
+```tsx
+// BAD: Premature optimization
+const MemoizedComponent = memo(({ onClick }) => {
+  return <button onClick={onClick}>Click</button>;
+}); // onClick is new every render anyway
+
+// GOOD: Memoize the callback too
+const Parent = () => {
+  const handleClick = useCallback(() => {
+    console.log('clicked');
+  }, []);
+
+  return <MemoizedComponent onClick={handleClick} />;
+};
+```
+
+---
+
+## TypeScript
+
+### Avoid `any`
+
+```typescript
+// BAD: any defeats type safety
+function process(data: any) {
+  return data.foo.bar; // No type checking
+}
+
+// GOOD: Use unknown + type guards
+function process(data: unknown) {
+  if (isValidData(data)) {
+    return data.foo.bar; // Type-safe
+  }
+  throw new Error('Invalid data');
+}
+
+function isValidData(data: unknown): data is { foo: { bar: string } } {
+  return typeof data === 'object' && data !== null && 'foo' in data;
+}
+```
+
+### Non-null Assertions
+
+```typescript
+// BAD: Non-null assertion hiding potential bugs
+const user = users.find(u => u.id === id)!;
+console.log(user.name); // Runtime error if not found
+
+// GOOD: Handle the undefined case
+const user = users.find(u => u.id === id);
+if (!user) {
+  throw new Error(`User ${id} not found`);
+}
+console.log(user.name);
+```
+
+### Generic Constraints
+
+```typescript
+// BAD: Overly permissive generic
+function getProperty<T, K>(obj: T, key: K) {
+  return obj[key]; // Error: Type 'K' cannot be used to index type 'T'
+}
+
+// GOOD: Constrain K to keys of T
+function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
+  return obj[key];
+}
+```
+
+### Type vs Interface
+
+```typescript
+// Prefer interface for object shapes (extendable)
+interface User {
+  id: string;
+  name: string;
+}
+
+interface Admin extends User {
+  permissions: string[];
+}
+
+// Use type for unions, intersections, primitives
+type Status = 'pending' | 'active' | 'archived';
+type UserWithMeta = User & { metadata: Record<string, unknown> };
+```
+
+### Discriminated Unions
+
+```typescript
+// BAD: Optional fields for variants
+interface Result {
+  success: boolean;
+  data?: string;
+  error?: Error;
+}
+
+// GOOD: Discriminated union
+type Result =
+  | { success: true; data: string }
+  | { success: false; error: Error };
+
+function handle(result: Result) {
+  if (result.success) {
+    console.log(result.data); // TypeScript knows data exists
+  } else {
+    console.error(result.error); // TypeScript knows error exists
+  }
+}
+```
+
+---
+
+## Python
+
+### Mutable Default Arguments
+
+```python
+# BAD: Mutable default persists across calls
+def append_to(element, target=[]):
+    target.append(element)
+    return target
+
+append_to(1)  # [1]
+append_to(2)  # [1, 2] - Unexpected!
+
+# GOOD: Use None and create inside function
+def append_to(element, target=None):
+    if target is None:
+        target = []
+    target.append(element)
+    return target
+```
+
+### Bare Except Clauses
+
+```python
+# BAD: Catches everything including KeyboardInterrupt
+try:
+    risky_operation()
+except:
+    pass
+
+# BAD: Too broad
+try:
+    risky_operation()
+except Exception:
+    pass
+
+# GOOD: Catch specific exceptions
+try:
+    risky_operation()
+except (ValueError, TypeError) as e:
+    logger.error(f"Operation failed: {e}")
+    raise
+```
+
+### Resource Management
+
+```python
+# BAD: Manual resource management
+f = open('file.txt')
+content = f.read()
+f.close()  # May not run if exception occurs
+
+# GOOD: Context manager
+with open('file.txt') as f:
+    content = f.read()
+
+# GOOD: For database connections
+with engine.connect() as conn:
+    result = conn.execute(query)
+```
+
+### String Formatting for SQL
+
+```python
+# BAD: SQL injection vulnerability
+query = f"SELECT * FROM users WHERE id = {user_id}"
+
+# GOOD: Parameterized query
+query = "SELECT * FROM users WHERE id = %s"
+cursor.execute(query, (user_id,))
+
+# GOOD: SQLAlchemy
+query = select(User).where(User.id == user_id)
+```
+
+### Type Hints
+
+```python
+# BAD: No type information
+def process(data):
+    return data.items()
+
+# GOOD: Full type annotations
+from typing import Dict, List, Tuple
+
+def process(data: Dict[str, int]) -> List[Tuple[str, int]]:
+    return list(data.items())
+```
+
+### Async Patterns
+
+```python
+# BAD: Blocking call in async function
+async def fetch_data():
+    response = requests.get(url)  # Blocks event loop!
+    return response.json()
+
+# GOOD: Use async HTTP client
+async def fetch_data():
+    async with aiohttp.ClientSession() as session:
+        async with session.get(url) as response:
+            return await response.json()
+
+# BAD: Sequential when could be concurrent
+async def fetch_all(urls):
+    results = []
+    for url in urls:
+        results.append(await fetch(url))  # Sequential!
+    return results
+
+# GOOD: Concurrent execution
+async def fetch_all(urls):
+    return await asyncio.gather(*[fetch(url) for url in urls])
+```
+
+---
+
+## Go
+
+### Error Handling
+
+```go
+// BAD: Ignoring errors
+result, _ := someFunction()
+
+// BAD: Just returning error without context
+if err != nil {
+    return err
+}
+
+// GOOD: Wrap errors with context
+if err != nil {
+    return fmt.Errorf("failed to process user %s: %w", userID, err)
+}
+```
+
+### Goroutine Leaks
+
+```go
+// BAD: Goroutine blocked forever on channel
+func process() {
+    ch := make(chan int)
+    go func() {
+        result := expensiveComputation()
+        ch <- result  // Blocks if no receiver
+    }()
+    // Function returns without receiving from ch
+}
+
+// GOOD: Use buffered channel or context
+func process(ctx context.Context) error {
+    ch := make(chan int, 1)  // Buffered
+    go func() {
+        ch <- expensiveComputation()
+    }()
+
+    select {
+    case result := <-ch:
+        return processResult(result)
+    case <-ctx.Done():
+        return ctx.Err()
+    }
+}
+```
+
+### Race Conditions
+
+```go
+// BAD: Concurrent map access
+var cache = make(map[string]int)
+
+func get(key string) int {
+    return cache[key]  // Race!
+}
+
+func set(key string, value int) {
+    cache[key] = value  // Race!
+}
+
+// GOOD: Use sync.Map or mutex
+var cache sync.Map
+
+func get(key string) (int, bool) {
+    val, ok := cache.Load(key)
+    if !ok {
+        return 0, false
+    }
+    return val.(int), true
+}
+
+func set(key string, value int) {
+    cache.Store(key, value)
+}
+```
+
+### Context Propagation
+
+```go
+// BAD: Creating new context, breaking cancellation chain
+func handler(ctx context.Context) {
+    newCtx := context.Background()  // Loses parent's deadline/cancellation
+    doWork(newCtx)
+}
+
+// GOOD: Propagate context
+func handler(ctx context.Context) {
+    doWork(ctx)
+}
+
+// GOOD: Add timeout while preserving parent
+func handler(ctx context.Context) {
+    childCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
+    defer cancel()
+    doWork(childCtx)
+}
+```
+
+### Defer in Loops
+
+```go
+// BAD: Defers accumulate, resources not released until function returns
+func processFiles(files []string) error {
+    for _, file := range files {
+        f, err := os.Open(file)
+        if err != nil {
+            return err
+        }
+        defer f.Close()  // All closes happen at function end!
+    }
+    return nil
+}
+
+// GOOD: Use closure to scope defer
+func processFiles(files []string) error {
+    for _, file := range files {
+        if err := processFile(file); err != nil {
+            return err
+        }
+    }
+    return nil
+}
+
+func processFile(path string) error {
+    f, err := os.Open(path)
+    if err != nil {
+        return err
+    }
+    defer f.Close()  // Closes when this function returns
+    return process(f)
+}
+```
+
+### Interface Satisfaction
+
+```go
+// GOOD: Compile-time interface check
+var _ io.Reader = (*MyReader)(nil)
+
+type MyReader struct{}
+
+func (r *MyReader) Read(p []byte) (n int, err error) {
+    // Implementation
+}
+```
+
+---
+
+## Rust
+
+### Ownership and Borrowing
+
+```rust
+// BAD: Trying to use moved value
+let s1 = String::from("hello");
+let s2 = s1;
+println!("{}", s1);  // Error: value borrowed after move
+
+// GOOD: Clone if you need both
+let s1 = String::from("hello");
+let s2 = s1.clone();
+println!("{} {}", s1, s2);
+
+// BETTER: Borrow instead of move
+let s1 = String::from("hello");
+let s2 = &s1;
+println!("{} {}", s1, s2);
+```
+
+### Unwrap Abuse
+
+```rust
+// BAD: Panics on None/Err
+let value = some_option.unwrap();
+let result = some_result.unwrap();
+
+// GOOD: Handle the error case
+let value = some_option.ok_or_else(|| Error::new("value missing"))?;
+
+// GOOD: Provide default
+let value = some_option.unwrap_or_default();
+let value = some_option.unwrap_or_else(|| compute_default());
+
+// GOOD: Pattern matching
+match some_option {
+    Some(v) => process(v),
+    None => handle_missing(),
+}
+```
+
+### Lifetime Annotations
+
+```rust
+// BAD: Missing lifetime causes confusion
+fn longest(x: &str, y: &str) -> &str {
+    if x.len() > y.len() { x } else { y }
+}
+
+// GOOD: Explicit lifetime
+fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
+    if x.len() > y.len() { x } else { y }
+}
+```
+
+### Unsafe Blocks
+
+```rust
+// BAD: Unnecessary unsafe
+unsafe {
+    let v = vec![1, 2, 3];  // Safe operation in unsafe block
+}
+
+// BAD: Unsafe without documentation
+unsafe {
+    ptr::copy_nonoverlapping(src, dst, len);
+}
+
+// GOOD: Documented safety invariants
+// SAFETY: src and dst are valid for len bytes, non-overlapping,
+// and properly aligned for T
+unsafe {
+    ptr::copy_nonoverlapping(src, dst, len);
+}
+```
+
+### Error Handling
+
+```rust
+// BAD: String errors lose type information
+fn process() -> Result<(), String> {
+    Err("something went wrong".into())
+}
+
+// GOOD: Custom error types
+#[derive(Debug, thiserror::Error)]
+enum ProcessError {
+    #[error("failed to read config: {0}")]
+    ConfigRead(#[from] std::io::Error),
+    #[error("invalid format: {0}")]
+    InvalidFormat(String),
+}
+
+fn process() -> Result<(), ProcessError> {
+    let config = std::fs::read_to_string("config.toml")?;
+    // ...
+}
+```
+
+### Clone vs Copy
+
+```rust
+// Know when to derive Copy
+#[derive(Clone, Copy)]  // Small, stack-only types
+struct Point {
+    x: i32,
+    y: i32,
+}
+
+// Don't derive Copy for heap-allocated types
+#[derive(Clone)]  // Clone only, has String
+struct User {
+    name: String,
+    age: u32,
+}
+```
+
+---
+
+## Vue.js
+
+### Reactivity Gotchas
+
+```vue
+<script setup>
+// BAD: Destructuring loses reactivity
+const { name, email } = props;  // Not reactive!
+
+// GOOD: Use toRefs
+const { name, email } = toRefs(props);
+
+// BAD: Replacing reactive object
+let state = reactive({ count: 0 });
+state = reactive({ count: 1 });  // Loses reactivity!
+
+// GOOD: Mutate properties instead
+const state = reactive({ count: 0 });
+state.count = 1;
+</script>
+```
+
+### v-for Key Requirement
+
+```vue
+<!-- BAD: Missing key -->
+<li v-for="item in items">{{ item.name }}</li>
+
+<!-- BAD: Index as key with mutable list -->
+<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
+
+<!-- GOOD: Unique identifier -->
+<li v-for="item in items" :key="item.id">{{ item.name }}</li>
+```
+
+### Props Mutation
+
+```vue
+<script setup>
+// BAD: Mutating props directly
+const props = defineProps(['modelValue']);
+props.modelValue = 'new value';  // Error!
+
+// GOOD: Emit update event
+const emit = defineEmits(['update:modelValue']);
+emit('update:modelValue', 'new value');
+</script>
+```
+
+### Computed vs Methods
+
+```vue
+<script setup>
+// BAD: Method for derived data
+const getFullName = () => `${firstName.value} ${lastName.value}`;
+
+// GOOD: Computed for derived data (cached)
+const fullName = computed(() => `${firstName.value} ${lastName.value}`);
+
+// Methods are for actions/side effects
+const submit = () => {
+  api.save(fullName.value);
+};
+</script>
+```
+
+### Watch Cleanup
+
+```vue
+<script setup>
+// GOOD: Clean up side effects
+watch(searchQuery, async (newQuery, oldQuery, onCleanup) => {
+  const controller = new AbortController();
+  onCleanup(() => controller.abort());
+
+  const results = await fetch(`/search?q=${newQuery}`, {
+    signal: controller.signal
+  });
+});
+</script>
+```
+
+---
+
+## SQL / Database
+
+### SQL Injection
+
+```sql
+-- BAD: String concatenation
+query = "SELECT * FROM users WHERE id = " + userId
+
+-- GOOD: Parameterized queries
+query = "SELECT * FROM users WHERE id = $1"
+```
+
+### N+1 Query Problem
+
+```python
+# BAD: N+1 queries
+users = User.query.all()
+for user in users:
+    print(user.posts)  # Separate query for each user!
+
+# GOOD: Eager loading
+users = User.query.options(joinedload(User.posts)).all()
+for user in users:
+    print(user.posts)  # Already loaded
+```
+
+### Missing Indexes
+
+```sql
+-- Check for missing indexes on frequently queried columns
+-- BAD: Full table scan
+SELECT * FROM orders WHERE customer_id = 123;
+
+-- GOOD: Add index
+CREATE INDEX idx_orders_customer_id ON orders(customer_id);
+
+-- For compound queries
+CREATE INDEX idx_orders_customer_date ON orders(customer_id, created_at);
+```
+
+### Transaction Boundaries
+
+```python
+# BAD: No transaction for related operations
+user = create_user(data)
+profile = create_profile(user.id)  # What if this fails?
+
+# GOOD: Wrap in transaction
+with db.transaction():
+    user = create_user(data)
+    profile = create_profile(user.id)
+    # Both succeed or both rollback
+```
+
+### SELECT *
+
+```sql
+-- BAD: Fetching all columns
+SELECT * FROM users WHERE id = 1;
+
+-- GOOD: Fetch only needed columns
+SELECT id, name, email FROM users WHERE id = 1;
+```
+
+### LIKE with Leading Wildcard
+
+```sql
+-- BAD: Cannot use index
+SELECT * FROM products WHERE name LIKE '%widget%';
+
+-- BETTER: Trailing wildcard can use index
+SELECT * FROM products WHERE name LIKE 'widget%';
+
+-- BEST: Full-text search for complex patterns
+SELECT * FROM products WHERE to_tsvector('english', name) @@ to_tsquery('widget');
+```
+
+---
+
+## Security Checks (Cross-Framework)
+
+### Secrets in Code
+
+```
+# CRITICAL: Never commit secrets
+BAD:  api_key = "sk-1234567890abcdef"
+BAD:  password = "hunter2"
+BAD:  AWS_SECRET_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
+
+GOOD: Use environment variables
+GOOD: Use secret management (AWS Secrets Manager, HashiCorp Vault)
+GOOD: Use .env files (gitignored)
+```
+
+### Input Validation
+
+```
+# Always validate at system boundaries
+- User input from forms
+- URL parameters
+- API request bodies
+- File uploads (type, size, content)
+- Headers and cookies
+```
+
+### Authentication/Authorization
+
+```
+# Common issues:
+- Missing authentication on endpoints
+- Authorization bypass (checking user ID client-side)
+- Insecure session handling
+- Missing rate limiting
+- Weak password requirements
+```
+
+### Sensitive Data Exposure
+
+```
+# Check for:
+- Passwords in logs
+- PII in error messages
+- Sensitive data in URLs
+- Missing encryption at rest
+- Missing HTTPS
+```
+
+---
+
+## Performance Checks (Cross-Framework)
+
+### Unnecessary Re-renders
+
+```
+# React: Check for
+- Missing memo/useMemo/useCallback
+- Creating objects/arrays in render
+- Context value changing every render
+
+# Vue: Check for
+- Computed not used for derived state
+- Missing v-once for static content
+- Large reactive objects when ref would suffice
+```
+
+### Memory Leaks
+
+```
+# Check for:
+- Event listeners not removed
+- Timers not cleared
+- Subscriptions not unsubscribed
+- DOM references held after removal
+- Closures capturing large objects
+```
+
+### Bundle Size
+
+```
+# Check for:
+- Importing entire libraries (import _ from 'lodash')
+- Missing tree-shaking
+- Large dependencies for small features
+- Duplicate dependencies
+- Missing code splitting
+```

+ 7 - 3
commands/setperms.md

@@ -1,6 +1,10 @@
 ---
 name: setperms
-description: "Set tool permissions for Claude Code. Configures allowed commands, rules, and preferences in .claude/ directory."
+description: "Set tool permissions for Claude Code. Configures allowed commands, rules, and preferences in .claude/ directory. Triggers on: setperms, init tools, configure permissions, setup project, set permissions, init claude."
+allowed-tools: "Read Write Bash"
+compatibility: "Creates project-local .claude/ configuration."
+depends-on: []
+related-skills: []
 ---
 
 # /setperms
@@ -40,7 +44,7 @@ Tools from [dev-shell-tools](https://github.com/0xDarkMatter/dev-shell-tools):
 ## Execution Flow
 
 ```
-/init-tools
+/setperms
     |
     +-- Check for existing .claude/ files
     |     +-- If exists: Ask to overwrite or skip
@@ -228,7 +232,7 @@ Prefer `just` over Makefiles.
 
 ## AI CLI Tools
 
-For multi-model analysis (see /conclave command):
+For multi-model analysis:
 
 | Tool | Model | Best For |
 |------|-------|----------|

+ 28 - 27
commands/spawn.md

@@ -1,10 +1,15 @@
 ---
-description: Generate PhD-level expert agent prompts for Claude Code. Creates comprehensive 500-1000 line agents with detailed patterns, code examples, and best practices.
+name: spawn
+description: "Generate PhD-level expert agent prompts for Claude Code. Creates comprehensive 500-1000 line agents with detailed patterns, code examples, and best practices. Triggers on: spawn agent, create agent, generate expert, new agent, agent genesis."
+allowed-tools: "Read Write Bash WebSearch WebFetch AskUserQuestion"
+compatibility: "Requires internet access for WebSearch/WebFetch to research official docs."
+depends-on: []
+related-skills: ["claude-code-templates"]
 ---
 
 # Spawn - Expert Agent Generator
 
-Generate world-class, comprehensive expert agent prompts for Claude Code. Each agent should be a definitive reference for its domain—the kind of guide a PhD-level practitioner would create.
+Generate world-class, comprehensive expert agent prompts for Claude Code. Each agent should be a definitive reference for its domain - the kind of guide a PhD-level practitioner would create.
 
 **Target quality:** 500-1000 lines per agent with real code examples, complete configs, and detailed patterns.
 
@@ -171,7 +176,7 @@ When creating files programmatically:
 
 ### Example 1: Single Agent
 ```
-User: /agent-genesis
+User: /spawn
 Agent: [Shows multi-tab AskUserQuestion with 5 tabs]
   Tab 1 (Mode): Single Agent / Batch Generation / Architecture Analysis
   Tab 2 (Scope): Project Agent / Global Agent
@@ -189,7 +194,7 @@ Agent: [Generates Redis expert prompt and saves to ~/.claude/agents/redis-expert
 
 ### Example 2: Batch Generation
 ```
-User: /agent-genesis
+User: /spawn
 Agent: [Shows multi-tab AskUserQuestion with 3 tabs]
   Tab 1 (Mode): Single Agent / Batch Generation / Architecture Analysis
   Tab 2 (Scope): Project Agent / Global Agent
@@ -208,7 +213,7 @@ Agent: [Creates 3 .md files in .claude/agents/ (project directory)]
 
 ### Example 3: Architecture Analysis
 ```
-User: /agent-genesis
+User: /spawn
 Agent: [Shows multi-tab AskUserQuestion with 3 tabs]
   Tab 1 (Mode): Single Agent / Batch Generation / Architecture Analysis
   Tab 2 (Scope): Project Agent / Global Agent
@@ -277,24 +282,24 @@ Agent: [Generates 4 selected agents in ~/.claude/agents/]
 ## Quality Checklist
 
 Before outputting each agent prompt, verify:
-- YAML frontmatter present with required fields (name, description)
-- Name uses lowercase-with-hyphens format
-- Description is clear and specific (length is flexible)
-- Tools field specified if restricting access (best practice: limit to necessary tools)
-- 10+ authoritative URLs included in system prompt
-- 10+ production-ready code examples included
-- Complete dev and prod configuration files
-- Testing patterns with actual test code
-- Error handling patterns and exception hierarchy
-- 5+ anti-patterns with bad/good code comparison
-- Concise and scannable system prompt
-- Clear use cases defined
-- Integration points identified
-- Common patterns referenced
-- Anti-patterns listed
-- Proper markdown formatting throughout
-- Filename matches name field: `[name].md`
-- Follows Claude Code subagent best practices (see documentation links above)
+- YAML frontmatter present with required fields (name, description)
+- Name uses lowercase-with-hyphens format
+- Description is clear and specific (length is flexible)
+- Tools field specified if restricting access (best practice: limit to necessary tools)
+- 10+ authoritative URLs included in system prompt
+- 10+ production-ready code examples included
+- Complete dev and prod configuration files
+- Testing patterns with actual test code
+- Error handling patterns and exception hierarchy
+- 5+ anti-patterns with bad/good code comparison
+- Concise and scannable system prompt
+- Clear use cases defined
+- Integration points identified
+- Common patterns referenced
+- Anti-patterns listed
+- Proper markdown formatting throughout
+- Filename matches name field: `[name].md`
+- Follows Claude Code subagent best practices (see documentation links above)
 
 ## Post-Generation
 
@@ -309,7 +314,3 @@ After creating agents, remind user:
 - Use `/agents` command to view and manage all available agents
 - Refer to https://docs.claude.com/en/docs/claude-code/sub-agents for detailed subagent documentation
 - Check https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices for authoring guidelines
-
----
-
-**Execute this command to generate expert agent prompts on demand!**

+ 290 - 0
skills/testgen/SKILL.md

@@ -0,0 +1,290 @@
+---
+name: testgen
+description: "Generate tests with expert routing, framework detection, and auto-TaskCreate. Triggers on: generate tests, write tests, testgen, create test file, add test coverage."
+allowed-tools: "Read Write Edit Bash Glob Grep Task TaskCreate"
+---
+
+# TestGen Skill - AI Test Generation
+
+Generate comprehensive tests with automatic framework detection, expert agent routing, and project convention matching.
+
+## Architecture
+
+```
+testgen <target> [--type] [--focus] [--depth]
+    │
+    ├─→ Step 1: Analyze Target
+    │     ├─ File exists? → Read and parse
+    │     ├─ Function specified? → Extract signature
+    │     ├─ Directory? → List source files
+    │     └─ Find existing tests (avoid duplicates)
+    │
+    ├─→ Step 2: Detect Framework (parallel)
+    │     ├─ package.json → jest/vitest/mocha/cypress/playwright
+    │     ├─ pyproject.toml → pytest/unittest
+    │     ├─ go.mod → go test
+    │     ├─ Cargo.toml → cargo test
+    │     ├─ composer.json → phpunit/pest
+    │     └─ Check existing test patterns
+    │
+    ├─→ Step 3: Load Project Standards
+    │     ├─ AGENTS.md, CLAUDE.md conventions
+    │     ├─ Existing test file structure
+    │     └─ Naming conventions (*.test.ts vs *.spec.ts)
+    │
+    ├─→ Step 4: Route to Expert Agent
+    │     ├─ .ts → typescript-expert
+    │     ├─ .tsx/.jsx → react-expert
+    │     ├─ .vue → vue-expert
+    │     ├─ .py → python-expert
+    │     ├─ .go → go-expert
+    │     ├─ .rs → rust-expert
+    │     ├─ .php → laravel-expert
+    │     ├─ E2E/Cypress → cypress-expert
+    │     ├─ Playwright → typescript-expert
+    │     ├─ --visual → Chrome DevTools MCP
+    │     └─ Multi-file → parallel expert dispatch
+    │
+    ├─→ Step 5: Generate Tests
+    │     ├─ Create test file in correct location
+    │     ├─ Follow detected conventions
+    │     └─ Include: happy path, edge cases, error handling
+    │
+    └─→ Step 6: Integration
+          ├─ Auto-create task (TaskCreate) for verification
+          └─ Suggest: run tests, /review, /save
+```
+
+## Execution Steps
+
+### Step 1: Analyze Target
+
+```bash
+# Check if target exists
+test -f "$TARGET" && echo "FILE" || test -d "$TARGET" && echo "DIRECTORY"
+
+# For function-specific: extract signature
+command -v ast-grep >/dev/null 2>&1 && ast-grep -p "function $FUNCTION_NAME" "$FILE"
+
+# Fallback to ripgrep
+rg "(?:function|const|def|public|private)\s+$FUNCTION_NAME" "$FILE" -A 10
+```
+
+**Check for existing tests:**
+```bash
+fd -e test.ts -e spec.ts -e test.js -e spec.js | rg "$BASENAME"
+fd "test_*.py" | rg "$BASENAME"
+```
+
+### Step 2: Detect Framework
+
+**JavaScript/TypeScript:**
+```bash
+cat package.json 2>/dev/null | jq -r '.devDependencies | keys[]' | grep -E 'jest|vitest|mocha|cypress|playwright|@testing-library'
+```
+
+**Python:**
+```bash
+grep -E "pytest|unittest|nose" pyproject.toml setup.py requirements*.txt 2>/dev/null
+```
+
+**Go:**
+```bash
+test -f go.mod && echo "go test available"
+```
+
+**Rust:**
+```bash
+test -f Cargo.toml && echo "cargo test available"
+```
+
+**PHP:**
+```bash
+cat composer.json 2>/dev/null | jq -r '.["require-dev"] | keys[]' | grep -E 'phpunit|pest|codeception'
+```
+
+### Step 3: Load Project Standards
+
+```bash
+# Claude Code conventions
+cat AGENTS.md 2>/dev/null | head -50
+cat CLAUDE.md 2>/dev/null | head -50
+
+# Test config files
+cat jest.config.* vitest.config.* pytest.ini pyproject.toml 2>/dev/null | head -30
+```
+
+**Test location conventions:**
+```
+# JavaScript
+src/utils/helper.ts → src/utils/__tests__/helper.test.ts  # __tests__ folder
+                    → src/utils/helper.test.ts            # co-located
+                    → tests/utils/helper.test.ts          # separate tests/
+
+# Python
+app/utils/helper.py → tests/test_helper.py               # tests/ folder
+                    → tests/utils/test_helper.py         # mirror structure
+
+# Go
+pkg/auth/token.go → pkg/auth/token_test.go               # co-located (required)
+
+# Rust
+src/auth.rs → src/auth.rs (mod tests { ... })            # inline tests
+            → tests/auth_test.rs                          # integration tests
+```
+
+### Step 4: Route to Expert Agent
+
+| File Pattern | Primary Expert | Secondary |
+|--------------|----------------|-----------|
+| `*.ts` | typescript-expert | - |
+| `*.tsx`, `*.jsx` | react-expert | typescript-expert |
+| `*.vue` | vue-expert | typescript-expert |
+| `*.py` | python-expert | - |
+| `*.go` | go-expert | - |
+| `*.rs` | rust-expert | - |
+| `*.php` | laravel-expert | - |
+| `*.cy.ts`, `cypress/*` | cypress-expert | - |
+| `*.spec.ts` (Playwright) | typescript-expert | - |
+| `playwright/*`, `e2e/*` | typescript-expert | - |
+| `*.sh`, `*.bash` | bash-expert | - |
+| (--visual flag) | Chrome DevTools MCP | typescript-expert |
+
+**Invoke via Task tool:**
+```
+Task tool with subagent_type: "[detected]-expert"
+Prompt includes:
+  - Source file content
+  - Function signatures to test
+  - Detected framework and conventions
+  - Requested test type and focus
+```
+
+### Step 5: Generate Tests
+
+**Test categories based on --focus:**
+
+| Focus | What to Generate |
+|-------|------------------|
+| `happy` | Normal input, expected output |
+| `edge` | Boundary values, empty inputs, nulls |
+| `error` | Invalid inputs, exceptions, error handling |
+| `all` | All of the above (default) |
+
+**Depth levels:**
+
+| Depth | Coverage |
+|-------|----------|
+| `quick` | Happy path only, 1-2 tests per function |
+| `normal` | Happy + common edge cases (default) |
+| `thorough` | Comprehensive: all paths, mocking, async |
+
+### Step 6: Integration
+
+**Auto-create task:**
+```
+TaskCreate:
+  subject: "Run generated tests for src/auth.ts"
+  description: "Verify generated tests pass and review edge cases"
+  activeForm: "Running generated tests for auth.ts"
+```
+
+**Suggest next steps:**
+```
+Tests generated: src/auth.test.ts
+
+Next steps:
+1. Run tests: npm test src/auth.test.ts
+2. Review and refine edge cases
+3. Use /save to persist tasks across sessions
+```
+
+---
+
+## Expert Routing Details
+
+### TypeScript/JavaScript → typescript-expert
+- Proper type imports
+- Generic type handling
+- Async/await patterns
+- Mock typing
+
+### React/JSX → react-expert
+- React Testing Library patterns
+- Component rendering tests
+- Hook testing (renderHook)
+- Accessibility queries (getByRole)
+
+### Vue → vue-expert
+- Vue Test Utils patterns
+- Composition API testing
+- Pinia store mocking
+
+### Python → python-expert
+- pytest fixtures
+- Parametrized tests
+- Mock/patch patterns
+- Async test handling
+
+### Go → go-expert
+- Table-driven tests (`[]struct` pattern)
+- `testing.T` and subtests (`t.Run`)
+- Testify assertions (when detected)
+- Benchmark functions (`testing.B`)
+- Parallel tests (`t.Parallel()`)
+
+### Rust → rust-expert
+- `#[test]` attribute functions
+- `#[cfg(test)]` module organization
+- `#[should_panic]` for error testing
+- proptest/quickcheck for property testing
+
+### PHP/Laravel → laravel-expert
+- PHPUnit/Pest patterns
+- Database transactions
+- Factory usage
+
+### E2E → cypress-expert
+- Page object patterns
+- Custom commands
+- Network stubbing
+
+### Playwright → typescript-expert
+- Page object model patterns
+- Locator strategies
+- Visual regression testing
+
+---
+
+## CLI Tool Integration
+
+| Tool | Purpose | Fallback |
+|------|---------|----------|
+| `jq` | Parse package.json | Read tool |
+| `rg` | Find existing tests | Grep tool |
+| `ast-grep` | Parse function signatures | ripgrep patterns |
+| `fd` | Find test files | Glob tool |
+| Chrome DevTools MCP | Visual testing (--visual) | Playwright/Cypress |
+
+**Graceful degradation:**
+```bash
+command -v jq >/dev/null 2>&1 && cat package.json | jq '.devDependencies' || cat package.json
+```
+
+---
+
+## Reference Files
+
+For framework-specific code examples, see:
+- `frameworks.md` - Complete test examples for all supported languages
+- `visual-testing.md` - Chrome DevTools integration for --visual flag
+
+---
+
+## Integration
+
+| Command | Relationship |
+|---------|--------------|
+| `/review` | Review generated tests before committing |
+| `/explain` | Understand complex code before testing |
+| `/save` | Track test coverage goals |

+ 722 - 0
skills/testgen/frameworks.md

@@ -0,0 +1,722 @@
+# TestGen Framework Examples
+
+Code examples for each supported testing framework. These are loaded on-demand when the testgen skill detects a specific framework.
+
+---
+
+## Jest/Vitest (TypeScript)
+
+```typescript
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { validateToken, TokenError } from '../auth';
+
+describe('validateToken', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  describe('happy path', () => {
+    it('should return true for valid JWT token', () => {
+      const token = 'eyJhbGciOiJIUzI1NiIs...';
+      expect(validateToken(token)).toBe(true);
+    });
+
+    it('should decode payload correctly', () => {
+      const token = createTestToken({ userId: 123 });
+      const result = validateToken(token);
+      expect(result.payload.userId).toBe(123);
+    });
+  });
+
+  describe('edge cases', () => {
+    it('should handle empty string', () => {
+      expect(validateToken('')).toBe(false);
+    });
+
+    it('should handle malformed token', () => {
+      expect(validateToken('not.a.token')).toBe(false);
+    });
+
+    it('should handle expired token', () => {
+      const expiredToken = createTestToken({ exp: Date.now() - 1000 });
+      expect(validateToken(expiredToken)).toBe(false);
+    });
+  });
+
+  describe('error handling', () => {
+    it('should throw TokenError for null input', () => {
+      expect(() => validateToken(null)).toThrow(TokenError);
+    });
+
+    it('should throw with descriptive message', () => {
+      expect(() => validateToken(null)).toThrow('Token cannot be null');
+    });
+  });
+});
+```
+
+---
+
+## React Testing Library
+
+```typescript
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { LoginForm } from '../LoginForm';
+
+describe('LoginForm', () => {
+  const mockOnSubmit = vi.fn();
+
+  beforeEach(() => {
+    mockOnSubmit.mockClear();
+  });
+
+  it('renders email and password fields', () => {
+    render(<LoginForm onSubmit={mockOnSubmit} />);
+
+    expect(screen.getByRole('textbox', { name: /email/i })).toBeInTheDocument();
+    expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
+  });
+
+  it('submits form with credentials', async () => {
+    const user = userEvent.setup();
+    render(<LoginForm onSubmit={mockOnSubmit} />);
+
+    await user.type(screen.getByRole('textbox', { name: /email/i }), 'test@example.com');
+    await user.type(screen.getByLabelText(/password/i), 'password123');
+    await user.click(screen.getByRole('button', { name: /submit/i }));
+
+    expect(mockOnSubmit).toHaveBeenCalledWith({
+      email: 'test@example.com',
+      password: 'password123',
+    });
+  });
+
+  it('shows validation error for invalid email', async () => {
+    const user = userEvent.setup();
+    render(<LoginForm onSubmit={mockOnSubmit} />);
+
+    await user.type(screen.getByRole('textbox', { name: /email/i }), 'invalid');
+    await user.click(screen.getByRole('button', { name: /submit/i }));
+
+    expect(await screen.findByText(/invalid email/i)).toBeInTheDocument();
+    expect(mockOnSubmit).not.toHaveBeenCalled();
+  });
+
+  it('disables submit button while loading', () => {
+    render(<LoginForm onSubmit={mockOnSubmit} isLoading />);
+
+    expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();
+  });
+});
+```
+
+---
+
+## pytest (Python)
+
+```python
+import pytest
+from unittest.mock import Mock, patch, AsyncMock
+from app.auth import validate_token, TokenError
+
+class TestValidateToken:
+    """Tests for validate_token function."""
+
+    def test_valid_token_returns_true(self):
+        """Should return True for valid JWT token."""
+        token = "eyJhbGciOiJIUzI1NiIs..."
+        assert validate_token(token) is True
+
+    def test_decodes_payload_correctly(self, valid_token):
+        """Should decode payload with correct user ID."""
+        result = validate_token(valid_token)
+        assert result.payload["userId"] == 123
+
+    @pytest.mark.parametrize("invalid_input", [
+        "",
+        "not.a.token",
+        "a.b",
+        None,
+    ])
+    def test_rejects_invalid_tokens(self, invalid_input):
+        """Should return False for invalid token formats."""
+        assert validate_token(invalid_input) is False
+
+    def test_rejects_expired_token(self, expired_token):
+        """Should return False for expired tokens."""
+        assert validate_token(expired_token) is False
+
+    def test_raises_token_error_for_null(self):
+        """Should raise TokenError with descriptive message."""
+        with pytest.raises(TokenError, match="Token cannot be null"):
+            validate_token(None)
+
+    @pytest.fixture
+    def valid_token(self):
+        """Create a valid test token."""
+        return create_test_token({"userId": 123})
+
+    @pytest.fixture
+    def expired_token(self):
+        """Create an expired test token."""
+        return create_test_token({"exp": time.time() - 1000})
+
+
+class TestValidateTokenAsync:
+    """Tests for async token validation."""
+
+    @pytest.mark.asyncio
+    async def test_async_validation(self):
+        """Should validate token asynchronously."""
+        token = create_test_token({"userId": 456})
+        result = await validate_token_async(token)
+        assert result.valid is True
+
+    @pytest.mark.asyncio
+    async def test_handles_network_timeout(self):
+        """Should handle network timeout gracefully."""
+        with patch("app.auth.fetch_public_key", new_callable=AsyncMock) as mock:
+            mock.side_effect = TimeoutError()
+
+            with pytest.raises(TokenError, match="Validation timeout"):
+                await validate_token_async("token")
+```
+
+---
+
+## Go (Table-Driven Tests)
+
+```go
+package auth
+
+import (
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestValidateToken(t *testing.T) {
+	t.Parallel()
+
+	tests := []struct {
+		name    string
+		token   string
+		want    bool
+		wantErr error
+	}{
+		{
+			name:  "valid token",
+			token: "eyJhbGciOiJIUzI1NiIs...",
+			want:  true,
+		},
+		{
+			name:  "empty string",
+			token: "",
+			want:  false,
+		},
+		{
+			name:  "malformed token",
+			token: "not.a.token",
+			want:  false,
+		},
+		{
+			name:    "nil token",
+			token:   "",
+			wantErr: ErrTokenNil,
+		},
+	}
+
+	for _, tt := range tests {
+		tt := tt // capture range variable
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+
+			got, err := ValidateToken(tt.token)
+
+			if tt.wantErr != nil {
+				require.ErrorIs(t, err, tt.wantErr)
+				return
+			}
+
+			require.NoError(t, err)
+			assert.Equal(t, tt.want, got)
+		})
+	}
+}
+
+func TestValidateToken_Expired(t *testing.T) {
+	token := createTestToken(t, TokenClaims{
+		UserID: 123,
+		Exp:    time.Now().Add(-1 * time.Hour),
+	})
+
+	got, err := ValidateToken(token)
+
+	require.NoError(t, err)
+	assert.False(t, got, "expired token should be invalid")
+}
+
+func TestValidateToken_Integration(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping integration test in short mode")
+	}
+
+	client := NewAuthClient(testConfig)
+	token, _ := client.GenerateToken(TestUser)
+
+	got, err := ValidateToken(token)
+
+	require.NoError(t, err)
+	assert.True(t, got)
+}
+
+func BenchmarkValidateToken(b *testing.B) {
+	token := createValidToken()
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		ValidateToken(token)
+	}
+}
+
+func createTestToken(t *testing.T, claims TokenClaims) string {
+	t.Helper()
+	token, err := generateToken(claims, testSecret)
+	require.NoError(t, err)
+	return token
+}
+```
+
+---
+
+## Rust (#[test] Patterns)
+
+```rust
+use crate::auth::{validate_token, TokenError, TokenClaims};
+use std::time::{Duration, SystemTime};
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn valid_token_returns_ok() {
+        let token = create_test_token(TokenClaims {
+            user_id: 123,
+            exp: future_time(),
+        });
+
+        let result = validate_token(&token);
+
+        assert!(result.is_ok());
+        assert!(result.unwrap());
+    }
+
+    #[test]
+    fn empty_token_returns_false() {
+        let result = validate_token("");
+
+        assert!(result.is_ok());
+        assert!(!result.unwrap());
+    }
+
+    #[test]
+    fn malformed_token_returns_false() {
+        let result = validate_token("not.a.token");
+
+        assert!(result.is_ok());
+        assert!(!result.unwrap());
+    }
+
+    #[test]
+    fn expired_token_returns_false() {
+        let token = create_test_token(TokenClaims {
+            user_id: 123,
+            exp: past_time(),
+        });
+
+        let result = validate_token(&token);
+
+        assert!(result.is_ok());
+        assert!(!result.unwrap(), "expired token should be invalid");
+    }
+
+    #[test]
+    #[should_panic(expected = "Token cannot be null")]
+    fn null_token_panics() {
+        validate_token_unchecked(None);
+    }
+
+    #[test]
+    fn returns_token_error_for_invalid_signature() {
+        let token = "eyJhbGciOiJIUzI1NiIs.tampered.signature";
+
+        let result = validate_token(token);
+
+        assert!(matches!(result, Err(TokenError::InvalidSignature)));
+    }
+
+    #[test]
+    fn rejects_various_invalid_tokens() {
+        let invalid_tokens = [
+            ("empty", ""),
+            ("single_part", "abc"),
+            ("two_parts", "a.b"),
+            ("whitespace", "   "),
+            ("special_chars", "!@#$%"),
+        ];
+
+        for (name, token) in invalid_tokens {
+            let result = validate_token(token);
+            assert!(
+                result.is_ok() && !result.unwrap(),
+                "case '{}' should return false",
+                name
+            );
+        }
+    }
+
+    #[tokio::test]
+    async fn async_validation_works() {
+        let token = create_test_token(valid_claims());
+
+        let result = validate_token_async(&token).await;
+
+        assert!(result.is_ok());
+    }
+
+    fn create_test_token(claims: TokenClaims) -> String {
+        crate::auth::generate_token(&claims, TEST_SECRET).unwrap()
+    }
+
+    fn valid_claims() -> TokenClaims {
+        TokenClaims {
+            user_id: 123,
+            exp: future_time(),
+        }
+    }
+
+    fn future_time() -> SystemTime {
+        SystemTime::now() + Duration::from_secs(3600)
+    }
+
+    fn past_time() -> SystemTime {
+        SystemTime::now() - Duration::from_secs(3600)
+    }
+
+    const TEST_SECRET: &[u8] = b"test_secret_key";
+}
+
+#[cfg(test)]
+mod property_tests {
+    use super::*;
+    use proptest::prelude::*;
+
+    proptest! {
+        #[test]
+        fn doesnt_crash_on_arbitrary_input(s in "\\PC*") {
+            let _ = validate_token(&s);
+        }
+
+        #[test]
+        fn valid_tokens_always_validate(user_id in 1u64..1000000) {
+            let token = create_test_token(TokenClaims {
+                user_id,
+                exp: future_time(),
+            });
+
+            let result = validate_token(&token);
+            prop_assert!(result.is_ok());
+            prop_assert!(result.unwrap());
+        }
+    }
+}
+```
+
+---
+
+## PHPUnit (PHP)
+
+```php
+<?php
+
+namespace Tests\Unit\Services;
+
+use PHPUnit\Framework\TestCase;
+use App\Services\AuthService;
+use App\Exceptions\TokenException;
+use Mockery;
+
+class AuthServiceTest extends TestCase
+{
+    private AuthService $service;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->service = new AuthService();
+    }
+
+    protected function tearDown(): void
+    {
+        Mockery::close();
+        parent::tearDown();
+    }
+
+    /** @test */
+    public function it_validates_correct_token(): void
+    {
+        $token = $this->createValidToken(['user_id' => 123]);
+
+        $result = $this->service->validateToken($token);
+
+        $this->assertTrue($result);
+    }
+
+    /** @test */
+    public function it_rejects_expired_token(): void
+    {
+        $token = $this->createExpiredToken();
+
+        $result = $this->service->validateToken($token);
+
+        $this->assertFalse($result);
+    }
+
+    /** @test */
+    public function it_throws_for_null_token(): void
+    {
+        $this->expectException(TokenException::class);
+        $this->expectExceptionMessage('Token cannot be null');
+
+        $this->service->validateToken(null);
+    }
+
+    /**
+     * @test
+     * @dataProvider invalidTokenProvider
+     */
+    public function it_rejects_invalid_tokens(string $invalidToken): void
+    {
+        $result = $this->service->validateToken($invalidToken);
+
+        $this->assertFalse($result);
+    }
+
+    public static function invalidTokenProvider(): array
+    {
+        return [
+            'empty string' => [''],
+            'malformed' => ['not.a.token'],
+            'missing parts' => ['a.b'],
+        ];
+    }
+}
+```
+
+---
+
+## Pest (PHP)
+
+```php
+<?php
+
+use App\Services\AuthService;
+use App\Exceptions\TokenException;
+
+describe('AuthService', function () {
+    beforeEach(function () {
+        $this->service = new AuthService();
+    });
+
+    describe('validateToken', function () {
+        it('validates correct token', function () {
+            $token = createValidToken(['user_id' => 123]);
+
+            expect($this->service->validateToken($token))->toBeTrue();
+        });
+
+        it('rejects expired token', function () {
+            $token = createExpiredToken();
+
+            expect($this->service->validateToken($token))->toBeFalse();
+        });
+
+        it('throws for null token', function () {
+            $this->service->validateToken(null);
+        })->throws(TokenException::class, 'Token cannot be null');
+
+        it('rejects invalid tokens', function (string $invalidToken) {
+            expect($this->service->validateToken($invalidToken))->toBeFalse();
+        })->with([
+            'empty string' => '',
+            'malformed' => 'not.a.token',
+            'missing parts' => 'a.b',
+        ]);
+    });
+});
+```
+
+---
+
+## Cypress (E2E)
+
+```typescript
+describe('Login Flow', () => {
+  beforeEach(() => {
+    cy.visit('/login');
+  });
+
+  it('should login with valid credentials', () => {
+    cy.get('[data-cy=email]').type('user@example.com');
+    cy.get('[data-cy=password]').type('password123');
+    cy.get('[data-cy=submit]').click();
+
+    cy.url().should('include', '/dashboard');
+    cy.get('[data-cy=welcome]').should('contain', 'Welcome');
+  });
+
+  it('should show error with invalid credentials', () => {
+    cy.intercept('POST', '/api/login', {
+      statusCode: 401,
+      body: { error: 'Invalid credentials' },
+    }).as('loginRequest');
+
+    cy.get('[data-cy=email]').type('user@example.com');
+    cy.get('[data-cy=password]').type('wrong');
+    cy.get('[data-cy=submit]').click();
+
+    cy.wait('@loginRequest');
+    cy.get('[data-cy=error]').should('be.visible');
+    cy.url().should('include', '/login');
+  });
+
+  it('should persist session after reload', () => {
+    cy.login('user@example.com', 'password123');
+    cy.visit('/dashboard');
+    cy.reload();
+
+    cy.get('[data-cy=welcome]').should('be.visible');
+  });
+});
+```
+
+---
+
+## Cypress (Component)
+
+```typescript
+import LoginForm from './LoginForm.vue';
+
+describe('LoginForm Component', () => {
+  it('renders login form', () => {
+    cy.mount(LoginForm);
+
+    cy.get('[data-cy=email]').should('exist');
+    cy.get('[data-cy=password]').should('exist');
+    cy.get('[data-cy=submit]').should('contain', 'Login');
+  });
+
+  it('emits submit event with credentials', () => {
+    const onSubmitSpy = cy.spy().as('submitSpy');
+    cy.mount(LoginForm, { props: { onSubmit: onSubmitSpy } });
+
+    cy.get('[data-cy=email]').type('user@example.com');
+    cy.get('[data-cy=password]').type('password123');
+    cy.get('[data-cy=submit]').click();
+
+    cy.get('@submitSpy').should('have.been.calledWith', {
+      email: 'user@example.com',
+      password: 'password123',
+    });
+  });
+
+  it('validates email format', () => {
+    cy.mount(LoginForm);
+
+    cy.get('[data-cy=email]').type('invalid-email');
+    cy.get('[data-cy=submit]').click();
+
+    cy.get('[data-cy=email-error]').should('contain', 'Invalid email');
+  });
+});
+```
+
+---
+
+## Playwright (E2E)
+
+```typescript
+import { test, expect } from '@playwright/test';
+
+test.describe('Login Flow', () => {
+  test.beforeEach(async ({ page }) => {
+    await page.goto('/login');
+  });
+
+  test('should login with valid credentials', async ({ page }) => {
+    await page.getByRole('textbox', { name: /email/i }).fill('user@example.com');
+    await page.getByRole('textbox', { name: /password/i }).fill('password123');
+    await page.getByRole('button', { name: /submit/i }).click();
+
+    await expect(page).toHaveURL(/dashboard/);
+    await expect(page.getByText(/welcome/i)).toBeVisible();
+  });
+
+  test('should show error with invalid credentials', async ({ page }) => {
+    await page.route('**/api/login', async (route) => {
+      await route.fulfill({
+        status: 401,
+        body: JSON.stringify({ error: 'Invalid credentials' }),
+      });
+    });
+
+    await page.getByRole('textbox', { name: /email/i }).fill('user@example.com');
+    await page.getByRole('textbox', { name: /password/i }).fill('wrong');
+    await page.getByRole('button', { name: /submit/i }).click();
+
+    await expect(page.getByTestId('error-message')).toBeVisible();
+    await expect(page).toHaveURL(/login/);
+  });
+
+  test('should validate email format', async ({ page }) => {
+    await page.getByRole('textbox', { name: /email/i }).fill('invalid-email');
+    await page.getByRole('button', { name: /submit/i }).click();
+
+    await expect(page.getByText(/invalid email/i)).toBeVisible();
+  });
+
+  test('should persist session after reload', async ({ page, context }) => {
+    await page.getByRole('textbox', { name: /email/i }).fill('user@example.com');
+    await page.getByRole('textbox', { name: /password/i }).fill('password123');
+    await page.getByRole('button', { name: /submit/i }).click();
+    await expect(page).toHaveURL(/dashboard/);
+
+    await page.reload();
+    await expect(page.getByText(/welcome/i)).toBeVisible();
+  });
+
+  test('should be accessible', async ({ page }) => {
+    const accessibilityScanResults = await new AxeBuilder({ page }).analyze();
+    expect(accessibilityScanResults.violations).toEqual([]);
+  });
+});
+
+test.describe('Login Form - Visual', () => {
+  test('matches snapshot', async ({ page }) => {
+    await page.goto('/login');
+    await expect(page).toHaveScreenshot('login-form.png');
+  });
+
+  test('error state matches snapshot', async ({ page }) => {
+    await page.goto('/login');
+    await page.getByRole('textbox', { name: /email/i }).fill('invalid');
+    await page.getByRole('button', { name: /submit/i }).click();
+
+    await expect(page).toHaveScreenshot('login-form-error.png');
+  });
+});
+```

+ 186 - 0
skills/testgen/visual-testing.md

@@ -0,0 +1,186 @@
+# Visual Testing with Chrome DevTools
+
+Documentation for the `--visual` flag which uses Chrome DevTools MCP for interactive visual testing.
+
+---
+
+## Overview
+
+The `--visual` flag enables browser-based testing using Chrome DevTools MCP tools. This provides real browser verification, accessibility snapshots, and visual regression testing.
+
+```bash
+# Visual test with Chrome DevTools
+/testgen src/pages/Login.tsx --visual
+
+# Combined with E2E type
+/testgen src/components/Form.tsx --type e2e --visual
+```
+
+---
+
+## How It Works
+
+1. **Detects Chrome DevTools MCP availability** via `mcp__chrome_devtools__*` tools
+
+2. **For each component/page:**
+   - Launches browser with `mcp__chrome_devtools__navigate_page`
+   - Takes accessibility snapshot with `mcp__chrome_devtools__take_snapshot`
+   - Captures screenshot with `mcp__chrome_devtools__take_screenshot`
+   - Tests interactions with `mcp__chrome_devtools__click`, `mcp__chrome_devtools__fill`
+
+3. **Generates test documentation with:**
+   - Visual regression baselines
+   - Accessibility tree verification
+   - Interactive element mapping
+
+---
+
+## Chrome DevTools MCP Tools
+
+| Tool | Purpose |
+|------|---------|
+| `navigate_page` | Load component in browser |
+| `take_snapshot` | Get accessibility tree for element refs |
+| `take_screenshot` | Capture visual state |
+| `click` | Test interactive elements |
+| `fill` | Test form inputs |
+| `list_console_messages` | Capture JS errors |
+| `list_network_requests` | Verify API calls |
+| `performance_start_trace` | Performance profiling |
+
+---
+
+## When to Use --visual
+
+| Scenario | Recommended |
+|----------|-------------|
+| Component styling verification | Yes |
+| Form interaction testing | Yes |
+| Accessibility compliance | Yes |
+| Unit testing pure functions | No |
+| API integration tests | No |
+
+---
+
+## Example Output
+
+```markdown
+## Visual Test Results: LoginForm
+
+### Accessibility Snapshot
+- Form: role="form", aria-label="Login"
+- Email input: role="textbox", name="email"
+- Password input: role="textbox", name="password" (masked)
+- Submit button: role="button", name="Log in"
+
+### Screenshot
+Captured: login-form-baseline.png
+
+### Interactive Tests
+- [PASS] Email field accepts input
+- [PASS] Password field masks characters
+- [PASS] Submit button triggers form validation
+- [PASS] Error state displays for invalid email
+
+### Console Errors
+None detected
+
+### Suggested Playwright Tests
+[Generated code based on visual analysis]
+```
+
+---
+
+## Claude-in-Chrome Integration
+
+When the `mcp__claude-in-chrome__*` tools are available, `--visual` can also use:
+
+| Tool | Purpose |
+|------|---------|
+| `read_page` | Get comprehensive accessibility tree |
+| `find` | Locate elements by natural language |
+| `computer` | Simulate real user interactions |
+| `get_page_text` | Extract text content |
+
+---
+
+## Detection Logic
+
+```bash
+# Check for Chrome DevTools MCP
+if available(mcp__chrome_devtools__take_snapshot); then
+    echo "Chrome DevTools available - enabling visual testing"
+fi
+
+# Check for Claude-in-Chrome
+if available(mcp__claude-in-chrome__read_page); then
+    echo "Claude-in-Chrome available - enabling enhanced visual testing"
+fi
+```
+
+---
+
+## Fallback Chain
+
+If Chrome DevTools is not available, `--visual` gracefully degrades:
+
+```
+Chrome DevTools MCP → Claude-in-Chrome → Playwright → Cypress → Manual testing
+```
+
+1. **Chrome DevTools**: Real-time browser control via MCP
+2. **Claude-in-Chrome**: Enhanced accessibility and natural language queries
+3. **Playwright**: Generate Playwright test code for manual execution
+4. **Cypress**: Generate Cypress test code for manual execution
+5. **Manual**: Output instructions for manual visual testing
+
+---
+
+## Advanced Flags
+
+### --coverage + --visual
+
+Combine coverage analysis with visual testing:
+
+```bash
+/testgen src/components/ --coverage --visual
+```
+
+This identifies untested visual states and generates tests for them.
+
+### --from-review + --visual
+
+Generate visual regression tests for UI issues found by `/review`:
+
+```bash
+/testgen --from-review --visual
+```
+
+---
+
+## Output Artifacts
+
+When `--visual` is used, the following artifacts may be created:
+
+| Artifact | Location | Purpose |
+|----------|----------|---------|
+| Screenshots | `tests/__screenshots__/` | Visual regression baselines |
+| Accessibility snapshots | `tests/__a11y__/` | A11y tree for comparison |
+| Generated tests | `tests/visual/` | Playwright/Cypress test files |
+
+---
+
+## Integration with CI/CD
+
+Visual tests can be run in CI with Chrome:
+
+```yaml
+# GitHub Actions example
+- name: Visual Tests
+  run: |
+    npx playwright test --project=chromium
+  env:
+    CI: true
+```
+
+For Chrome DevTools MCP in CI, ensure the browser is launched in headless mode with remote debugging enabled.