diff --git a/.gitignore b/.gitignore
index 5b09186..36781ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -207,3 +207,5 @@ tasks/
# Claude Code integration generated files
integrations/claude-code/converter/generated/
+.cursorrules
+bun.lock
diff --git a/.opencode/context/core/context-system/guides/navigation-design-basics.md b/.opencode/context/core/context-system/guides/navigation-design-basics.md
new file mode 100644
index 0000000..c5c134c
--- /dev/null
+++ b/.opencode/context/core/context-system/guides/navigation-design-basics.md
@@ -0,0 +1,131 @@
+# Guide: Designing Navigation Files
+
+**Purpose**: How to create token-efficient, scannable navigation files
+
+---
+
+## Prerequisites
+
+- Understand MVI principle (`context-system/standards/mvi.md`)
+- Know your category's organizational pattern
+- Have content files already created
+
+**Estimated time**: 15-20 min per navigation file
+
+---
+
+## Core Principles
+
+### 1. Token Efficiency
+**Goal**: 200-300 tokens per navigation file
+
+**How**:
+- Use ASCII trees (not verbose descriptions)
+- Use tables (not paragraphs)
+- Be concise (not comprehensive)
+
+### 2. Scannable Structure
+**Goal**: AI can find what it needs in <5 seconds
+
+**Format**:
+1. **Structure** (ASCII tree) - See what exists
+2. **Quick Routes** (table) - Jump to common tasks
+3. **By Concern/Type** (sections) - Browse by category
+
+### 3. Self-Contained
+**Include**: ✅ Paths | ✅ Brief descriptions (3-5 words) | ✅ When to use
+**Exclude**: ❌ File contents | ❌ Detailed explanations | ❌ Duplicates
+
+---
+
+## Steps
+
+### 1. Determine Navigation Type
+
+| Type | Path | Purpose |
+|------|------|---------|
+| Category-level | `{category}/navigation.md` | Overview of category |
+| Subcategory-level | `{category}/{sub}/navigation.md` | Files in subcategory |
+| Specialized | `{category}/{domain}-navigation.md` | Cross-cutting (e.g., ui-navigation.md) |
+
+### 2. Create Structure Section
+
+```markdown
+## Structure
+
+```
+openagents-repo/
+├── navigation.md
+├── quick-start.md
+├── concepts/
+│ └── subagent-testing-modes.md
+├── guides/
+│ ├── adding-agent.md
+│ └── testing-agent.md
+└── lookup/
+ └── commands.md
+```
+```
+
+**Token count**: ~50-100 tokens
+
+### 3. Create Quick Routes Table
+
+```markdown
+## Quick Routes
+
+| Task | Path |
+|------|------|
+| **Add agent** | `guides/adding-agent.md` |
+| **Test agent** | `guides/testing-agent.md` |
+| **Find files** | `lookup/file-locations.md` |
+```
+
+**Guidelines**: Use **bold** for tasks | Relative paths | 5-10 common tasks
+
+### 4. Create By Concern/Type Sections
+
+```markdown
+## By Type
+
+**Concepts** → Core ideas and principles
+**Guides** → Step-by-step workflows
+**Lookup** → Quick reference tables
+**Errors** → Troubleshooting
+```
+
+### 5. Add Related Context (Optional)
+
+```markdown
+## Related Context
+
+- **Core Standards** → `../core/standards/navigation.md`
+```
+
+### 6. Validate Token Count
+
+**Target**: 200-300 tokens
+
+```bash
+wc -w navigation.md # Multiply by 1.3 for token estimate
+```
+
+---
+
+## Verification Checklist
+
+- [ ] Token count 200-300?
+- [ ] ASCII tree included?
+- [ ] Quick routes table?
+- [ ] By concern/type section?
+- [ ] Relative paths?
+- [ ] Descriptions 3-5 words?
+- [ ] No duplicate information?
+
+---
+
+## Related
+
+- `navigation-templates.md` - Ready-to-use templates
+- `../standards/mvi.md` - MVI principle
+- `../examples/navigation-examples.md` - More examples
diff --git a/.opencode/context/core/context-system/guides/navigation-design.md b/.opencode/context/core/context-system/guides/navigation-design.md
deleted file mode 100644
index 7b4fd72..0000000
--- a/.opencode/context/core/context-system/guides/navigation-design.md
+++ /dev/null
@@ -1,431 +0,0 @@
-# Guide: Designing Navigation Files
-
-**Purpose**: How to create token-efficient, scannable navigation files
-
-**Last Updated**: 2026-01-08
-
----
-
-## Prerequisites
-
-- Understand MVI principle (context-system/standards/mvi.md)
-- Know your category's organizational pattern (function-based or concern-based)
-- Have content files already created
-
-**Estimated time**: 15-20 min per navigation file
-
----
-
-## Core Principles
-
-### 1. Token Efficiency
-**Goal**: 200-300 tokens per navigation file
-
-**Why**:
-- Faster AI loading
-- Lower costs
-- Quicker decision-making
-
-**How**:
-- Use ASCII trees (not verbose descriptions)
-- Use tables (not paragraphs)
-- Be concise (not comprehensive)
-
----
-
-### 2. Scannable Structure
-**Goal**: AI can find what it needs in <5 seconds
-
-**Format**:
-1. **Structure** (ASCII tree) - See what exists
-2. **Quick Routes** (table) - Jump to common tasks
-3. **By Concern/Type** (sections) - Browse by category
-
----
-
-### 3. Self-Contained
-**Goal**: Navigation file answers "where do I go?"
-
-**Include**:
-- ✅ Paths to files
-- ✅ Brief descriptions (3-5 words)
-- ✅ When to use each file
-
-**Exclude**:
-- ❌ File contents (that's what the files are for)
-- ❌ Detailed explanations
-- ❌ Duplicate information
-
----
-
-## Steps
-
-### 1. Determine Navigation Type
-
-**Category-level navigation** (`{category}/navigation.md`):
-- Overview of entire category
-- Points to subcategories
-- Includes specialized navigation files
-
-**Subcategory-level navigation** (`{category}/{subcategory}/navigation.md`):
-- Overview of subcategory
-- Lists files in this subcategory
-- Points to related categories
-
-**Specialized navigation** (`{category}/{domain}-navigation.md`):
-- Cross-cutting concern (e.g., `ui-navigation.md`)
-- Spans multiple subcategories
-- Task-focused routes
-
----
-
-### 2. Create Structure Section
-
-**Format**: ASCII tree showing directory structure
-
-**Example** (Function-based):
-```markdown
-## Structure
-
-```
-openagents-repo/
-├── navigation.md
-├── quick-start.md
-│
-├── concepts/
-│ ├── navigation.md
-│ └── subagent-testing-modes.md
-│
-├── guides/
-│ ├── navigation.md
-│ ├── adding-agent.md
-│ └── testing-agent.md
-│
-└── lookup/
- ├── navigation.md
- └── commands.md
-```
-```
-
-**Example** (Concern-based):
-```markdown
-## Structure
-
-```
-development/
-├── navigation.md
-├── ui-navigation.md # Specialized
-│
-├── principles/
-│ ├── clean-code.md
-│ └── api-design.md
-│
-├── frontend/
-│ ├── react/
-│ │ ├── hooks-patterns.md
-│ │ └── tanstack/
-│ │ └── query-patterns.md
-│ └── vue/
-│
-└── backend/
- ├── api-patterns/
- │ ├── rest-design.md
- │ └── graphql-design.md
- └── nodejs/
-```
-```
-
-**Token count**: ~50-100 tokens
-
----
-
-### 3. Create Quick Routes Table
-
-**Format**: Task → Path mapping
-
-**Example**:
-```markdown
-## Quick Routes
-
-| Task | Path |
-|------|------|
-| **Add agent** | `guides/adding-agent.md` |
-| **Test agent** | `guides/testing-agent.md` |
-| **Debug issue** | `guides/debugging-issues.md` |
-| **Find files** | `lookup/file-locations.md` |
-```
-
-**Guidelines**:
-- Use **bold** for task names
-- Use relative paths
-- 5-10 most common tasks
-- Order by frequency of use
-
-**Token count**: ~50-100 tokens
-
----
-
-### 4. Create By Concern/Type Sections
-
-**Format**: Group files by concern or type
-
-**Example** (Function-based):
-```markdown
-## By Type
-
-**Concepts** → Core ideas and principles
-**Guides** → Step-by-step workflows
-**Lookup** → Quick reference tables
-**Errors** → Troubleshooting
-```
-
-**Example** (Concern-based):
-```markdown
-## By Concern
-
-**Principles** → Universal development practices
-**Frontend** → React, Vue, state management
-**Backend** → APIs, Node.js, Python, auth
-**Data** → SQL, NoSQL, ORMs
-```
-
-**Token count**: ~50-100 tokens
-
----
-
-### 5. Add Related Context (Optional)
-
-**Format**: Links to related categories
-
-**Example**:
-```markdown
-## Related Context
-
-- **Core Standards** → `../core/standards/navigation.md`
-- **UI Patterns** → `../ui/navigation.md`
-```
-
-**Token count**: ~20-50 tokens
-
----
-
-### 6. Validate Token Count
-
-**Target**: 200-300 tokens
-
-**Check**:
-```bash
-# Count tokens (rough estimate: words * 1.3)
-wc -w navigation.md
-# Multiply by 1.3 for token estimate
-```
-
-**If over 300 tokens**:
-- Remove verbose descriptions
-- Shorten table entries
-- Remove less-used routes
-
----
-
-## Templates
-
-### Category Navigation Template
-
-```markdown
-# {Category} Navigation
-
-**Purpose**: [1 sentence]
-
----
-
-## Structure
-
-```
-{category}/
-├── navigation.md
-├── {subcategory}/
-│ ├── navigation.md
-│ └── {files}.md
-```
-
----
-
-## Quick Routes
-
-| Task | Path |
-|------|------|
-| **{Task 1}** | `{path}` |
-| **{Task 2}** | `{path}` |
-| **{Task 3}** | `{path}` |
-
----
-
-## By {Concern/Type}
-
-**{Section 1}** → {description}
-**{Section 2}** → {description}
-**{Section 3}** → {description}
-
----
-
-## Related Context
-
-- **{Category}** → `../{category}/navigation.md`
-```
-
-**Token count**: ~200-250 tokens
-
----
-
-### Specialized Navigation Template
-
-```markdown
-# {Domain} Navigation
-
-**Scope**: [What this covers]
-
----
-
-## Structure
-
-```
-{Relevant directories across multiple categories}
-```
-
----
-
-## Quick Routes
-
-| Task | Path |
-|------|------|
-| **{Task 1}** | `{path}` |
-| **{Task 2}** | `{path}` |
-
----
-
-## By {Framework/Approach}
-
-**{Tech 1}** → `{path}`
-**{Tech 2}** → `{path}`
-
----
-
-## Common Workflows
-
-**{Workflow 1}**:
-1. `{file1}` ({purpose})
-2. `{file2}` ({purpose})
-```
-
-**Token count**: ~250-300 tokens
-
----
-
-## Examples
-
-### Good Navigation (Token-Efficient)
-
-```markdown
-# Development Navigation
-
-**Purpose**: Software development across all stacks
-
----
-
-## Structure
-
-```
-development/
-├── navigation.md
-├── ui-navigation.md
-├── principles/
-├── frontend/
-├── backend/
-└── data/
-```
-
----
-
-## Quick Routes
-
-| Task | Path |
-|------|------|
-| **UI/Frontend** | `ui-navigation.md` |
-| **Backend/API** | `backend-navigation.md` |
-| **Clean code** | `principles/clean-code.md` |
-
----
-
-## By Concern
-
-**Principles** → Universal practices
-**Frontend** → React, Vue, state
-**Backend** → APIs, Node, auth
-**Data** → SQL, NoSQL, ORMs
-```
-
-**Token count**: ~180 tokens ✅
-
----
-
-### Bad Navigation (Too Verbose)
-
-```markdown
-# Development Navigation
-
-**Purpose**: This navigation file helps you find software development patterns, standards, and best practices across all technology stacks including frontend, backend, databases, and infrastructure.
-
----
-
-## Introduction
-
-The development category contains comprehensive guides and patterns for building modern applications. Whether you're working on frontend user interfaces, backend APIs, database integrations, or infrastructure setup, you'll find relevant context here.
-
----
-
-## Directory Structure
-
-The development category is organized into several subcategories:
-
-- **principles/** - This directory contains universal development principles that apply regardless of the technology stack you're using. Files include clean code practices, API design principles, and more.
-
-- **frontend/** - This directory contains frontend development patterns including React, Vue, and state management solutions.
-
-[... continues for 500+ tokens]
-```
-
-**Token count**: 500+ tokens ❌
-
----
-
-## Verification
-
-After creating navigation file:
-
-- [ ] Token count 200-300?
-- [ ] ASCII tree included?
-- [ ] Quick routes table included?
-- [ ] By concern/type section included?
-- [ ] Paths are relative?
-- [ ] Descriptions are 3-5 words?
-- [ ] No duplicate information?
-- [ ] Links to related context?
-
----
-
-## Troubleshooting
-
-| Issue | Solution |
-|-------|----------|
-| Too many tokens | Remove verbose descriptions, shorten table entries |
-| Hard to scan | Use tables instead of paragraphs |
-| Missing files | Add to structure section and quick routes |
-| Unclear paths | Use relative paths, add brief descriptions |
-
----
-
-## Related
-
-- `../standards/mvi.md` - Minimal Viable Information principle
-- `organizing-context.md` - How to choose organizational pattern
-- `../examples/navigation-examples.md` - More examples
diff --git a/.opencode/context/core/context-system/guides/navigation-templates.md b/.opencode/context/core/context-system/guides/navigation-templates.md
new file mode 100644
index 0000000..1b802e5
--- /dev/null
+++ b/.opencode/context/core/context-system/guides/navigation-templates.md
@@ -0,0 +1,183 @@
+# Navigation File Templates
+
+**Purpose**: Ready-to-use templates for navigation files
+
+---
+
+## Category Navigation Template
+
+```markdown
+# {Category} Navigation
+
+**Purpose**: [1 sentence]
+
+---
+
+## Structure
+
+```
+{category}/
+├── navigation.md
+├── {subcategory}/
+│ ├── navigation.md
+│ └── {files}.md
+```
+
+---
+
+## Quick Routes
+
+| Task | Path |
+|------|------|
+| **{Task 1}** | `{path}` |
+| **{Task 2}** | `{path}` |
+| **{Task 3}** | `{path}` |
+
+---
+
+## By {Concern/Type}
+
+**{Section 1}** → {description}
+**{Section 2}** → {description}
+**{Section 3}** → {description}
+
+---
+
+## Related Context
+
+- **{Category}** → `../{category}/navigation.md`
+```
+
+**Token count**: ~200-250 tokens
+
+---
+
+## Specialized Navigation Template
+
+```markdown
+# {Domain} Navigation
+
+**Scope**: [What this covers]
+
+---
+
+## Structure
+
+```
+{Relevant directories across multiple categories}
+```
+
+---
+
+## Quick Routes
+
+| Task | Path |
+|------|------|
+| **{Task 1}** | `{path}` |
+| **{Task 2}** | `{path}` |
+
+---
+
+## By {Framework/Approach}
+
+**{Tech 1}** → `{path}`
+**{Tech 2}** → `{path}`
+
+---
+
+## Common Workflows
+
+**{Workflow 1}**:
+1. `{file1}` ({purpose})
+2. `{file2}` ({purpose})
+```
+
+**Token count**: ~250-300 tokens
+
+---
+
+## Good Example (Token-Efficient)
+
+```markdown
+# Development Navigation
+
+**Purpose**: Software development across all stacks
+
+---
+
+## Structure
+
+```
+development/
+├── navigation.md
+├── ui-navigation.md
+├── principles/
+├── frontend/
+├── backend/
+└── data/
+```
+
+---
+
+## Quick Routes
+
+| Task | Path |
+|------|------|
+| **UI/Frontend** | `ui-navigation.md` |
+| **Backend/API** | `backend-navigation.md` |
+| **Clean code** | `principles/clean-code.md` |
+
+---
+
+## By Concern
+
+**Principles** → Universal practices
+**Frontend** → React, Vue, state
+**Backend** → APIs, Node, auth
+**Data** → SQL, NoSQL, ORMs
+```
+
+**Token count**: ~180 tokens ✅
+
+---
+
+## Bad Example (Too Verbose)
+
+```markdown
+# Development Navigation
+
+**Purpose**: This navigation file helps you find software development
+patterns, standards, and best practices across all technology stacks
+including frontend, backend, databases, and infrastructure.
+
+---
+
+## Introduction
+
+The development category contains comprehensive guides and patterns
+for building modern applications. Whether you're working on frontend
+user interfaces, backend APIs, database integrations...
+
+[... continues for 500+ tokens]
+```
+
+**Token count**: 500+ tokens ❌
+
+---
+
+## Troubleshooting
+
+| Issue | Solution |
+|-------|----------|
+| Too many tokens | Remove verbose descriptions, shorten entries |
+| Hard to scan | Use tables instead of paragraphs |
+| Missing files | Add to structure and quick routes |
+| Unclear paths | Use relative paths, add brief descriptions |
+
+---
+
+## Related
+
+- `navigation-design-basics.md` - Core principles and steps
+- `../standards/mvi.md` - MVI principle
+- `../examples/navigation-examples.md` - More examples
diff --git a/.opencode/context/core/guides/resuming-sessions.md b/.opencode/context/core/guides/resuming-sessions.md
new file mode 100644
index 0000000..493c712
--- /dev/null
+++ b/.opencode/context/core/guides/resuming-sessions.md
@@ -0,0 +1,320 @@
+# Guide: Resuming Multi-Session Tasks
+
+**Purpose**: How to resume work from previous sessions using session context and task files
+
+**Last Updated**: 2026-02-04
+
+---
+
+## When to Use This Guide
+
+- Continuing work after a break or interruption
+- Picking up a task from another agent or developer
+- Understanding what was done in a previous session
+- Avoiding duplicate work or lost progress
+
+---
+
+## Session File Structure
+
+```
+.tmp/
+├── sessions/ # Session context
+│ └── {YYYY-MM-DD}-{task-slug}/
+│ ├── context.md # Full task context
+│ └── PROGRESS.md # Progress report
+│
+└── tasks/ # Task breakdowns
+ └── {task-slug}/
+ ├── task.json # Main task definition
+ └── subtask_NN.json # Individual subtasks
+```
+
+---
+
+## Quick Resume Steps
+
+### 1. Find Your Session (30 seconds)
+
+```bash
+# List recent sessions
+ls -lt .tmp/sessions/ | head -5
+
+# Most recent session
+SESSION=$(ls -t .tmp/sessions/ | head -1)
+echo "Latest session: $SESSION"
+```
+
+---
+
+### 2. Read Progress Report (2 minutes)
+
+```bash
+# View progress
+cat .tmp/sessions/$SESSION/PROGRESS.md
+```
+
+**Look for**:
+- ✅ **Completed Work** - What's already done
+- 🔥 **Next Steps** - What to do next
+- 📊 **Overall Progress** - How much is complete
+- ⚠️ **Blockers** - Any issues to resolve
+
+---
+
+### 3. Check Task Context (3 minutes)
+
+```bash
+# View full context
+cat .tmp/sessions/$SESSION/context.md
+```
+
+**Key sections**:
+- **Current Request** - What was asked for
+- **Context Files** - Standards to follow (CRITICAL)
+- **Reference Files** - Existing code to read
+- **Components** - What needs to be built
+- **Exit Criteria** - When you're done
+
+---
+
+### 4. Review Subtasks (2 minutes)
+
+```bash
+# Find task directory
+TASK_DIR=.tmp/tasks/$(basename $SESSION | cut -d'-' -f4-)
+
+# List subtasks
+ls $TASK_DIR/subtask_*.json
+
+# View next subtask
+cat $TASK_DIR/subtask_04.json # Example
+```
+
+**Subtask format**:
+```json
+{
+ "id": "04",
+ "name": "Migrate AgentLoader",
+ "description": "Create src/core/AgentLoader.ts with load/parse functions",
+ "estimated_hours": 1.5,
+ "status": "pending",
+ "dependencies": ["02"],
+ "context_files": ["...standards to load..."],
+ "acceptance_criteria": ["...checklist..."]
+}
+```
+
+---
+
+## Resume Workflow
+
+### Option 1: Continue from Last Checkpoint
+
+```bash
+# 1. Check what's done
+grep "✅" .tmp/sessions/$SESSION/PROGRESS.md
+
+# 2. Find next task
+grep "🔥 NEXT" .tmp/sessions/$SESSION/PROGRESS.md
+
+# 3. Tell the agent
+"Continue with subtask 04 from session $SESSION"
+```
+
+---
+
+### Option 2: Jump to Specific Phase
+
+```bash
+# 1. View phase structure
+cat .tmp/tasks/$TASK_DIR/task.json | jq '.phases'
+
+# 2. Find phase tasks
+cat .tmp/tasks/$TASK_DIR/task.json | jq '.phases[1]' # Phase 2
+
+# 3. Tell the agent
+"Start Phase 2 - Adapters from session $SESSION"
+```
+
+---
+
+### Option 3: Review and Decide
+
+```bash
+# 1. Quick status
+cat .tmp/sessions/$SESSION/PROGRESS.md | grep -A5 "Overall Progress"
+
+# 2. View completed work
+cat .tmp/sessions/$SESSION/PROGRESS.md | grep -A20 "Completed Work"
+
+# 3. Discuss with agent
+"Show me the current status of session $SESSION and suggest next steps"
+```
+
+---
+
+## Key Files to Read
+
+### context.md (ALWAYS)
+
+**Why**: Contains standards you MUST follow
+
+```markdown
+## Context Files (Standards to Follow)
+- .opencode/context/core/standards/code-quality.md - CRITICAL
+- .opencode/context/core/standards/test-coverage.md - CRITICAL
+```
+
+**Before writing any code**, load these standards!
+
+---
+
+### PROGRESS.md (RECOMMENDED)
+
+**Why**: Shows exactly what's done and what's next
+
+**Key sections**:
+- **Overall Progress** - % complete, phases
+- **Completed Work** - Files created, tasks done
+- **Next Steps** - What to do immediately
+- **Blockers** - Issues to resolve
+
+---
+
+### subtask_NN.json (WHEN NEEDED)
+
+**Why**: Detailed task definition with acceptance criteria
+
+**Use when**:
+- Starting a new subtask
+- Need to know exact requirements
+- Want to see dependencies
+
+---
+
+## Common Scenarios
+
+### Scenario 1: "I don't remember where I was"
+
+```bash
+# Quick catch-up
+cat .tmp/sessions/$SESSION/PROGRESS.md | head -50
+# Read: Overall Progress + Completed Work + Next Steps
+```
+
+**Time**: 2 minutes
+
+---
+
+### Scenario 2: "What was I supposed to build?"
+
+```bash
+# Read original request
+cat .tmp/sessions/$SESSION/context.md | grep -A20 "Current Request"
+
+# Read components
+cat .tmp/sessions/$SESSION/context.md | grep -A30 "Components"
+```
+
+**Time**: 3 minutes
+
+---
+
+### Scenario 3: "What standards should I follow?"
+
+```bash
+# Read context files list
+cat .tmp/sessions/$SESSION/context.md | grep -A15 "Context Files"
+
+# Load the standards
+cat .opencode/context/core/standards/code-quality.md
+```
+
+**Time**: 5 minutes
+
+---
+
+### Scenario 4: "How do I know when I'm done?"
+
+```bash
+# Read exit criteria
+cat .tmp/sessions/$SESSION/context.md | grep -A20 "Exit Criteria"
+```
+
+**Time**: 1 minute
+
+---
+
+## Agent Instructions
+
+When resuming a session, tell the agent:
+
+```
+"Resume session: {SESSION_ID}"
+```
+
+Or more specific:
+
+```
+"Continue with subtask 04 from session 2026-02-04-compatibility-layer-141"
+```
+
+The agent should:
+1. ✅ Read context.md for standards
+2. ✅ Read PROGRESS.md for current state
+3. ✅ Load next subtask JSON
+4. ✅ Load required context files (from subtask.context_files)
+5. ✅ Propose next steps
+6. ✅ Request approval before executing
+
+---
+
+## Session Lifecycle
+
+### Active Session
+- Created when task starts
+- Updated after each subtask completes
+- Contains live progress tracking
+
+### Completed Session
+- All subtasks marked complete
+- Exit criteria met
+- Ready for archival
+
+### Archived Session
+- Moved to `.tmp/archive/sessions/{date}/`
+- Knowledge harvested to permanent context
+- Task JSONs can be deleted
+
+---
+
+## Cleanup Checklist
+
+After completing a session:
+
+- [ ] All exit criteria met?
+- [ ] Valuable knowledge harvested to context?
+- [ ] Session files archived?
+- [ ] Task JSONs cleaned up?
+- [ ] Temporary files deleted?
+
+---
+
+## Best Practices
+
+1. **Always read context.md first** - Contains critical standards
+2. **Check PROGRESS.md before asking** - Likely has the answer
+3. **Use subtask JSONs for detailed requirements** - Not just memory
+4. **Update PROGRESS.md after each task** - Keep it current
+5. **Archive when done** - Don't clutter workspace
+
+---
+
+## Reference
+
+- **Session Template**: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/`
+- **Task Template**: `.tmp/tasks/{task-slug}/`
+- **Related**:
+ - guides/compatibility-layer-workflow.md (example session)
+ - standards/code-quality.md (what to follow)
diff --git a/.opencode/context/core/navigation.md b/.opencode/context/core/navigation.md
index 6633ccd..0f69090 100644
--- a/.opencode/context/core/navigation.md
+++ b/.opencode/context/core/navigation.md
@@ -28,6 +28,9 @@ core/
│ ├── session-management.md
│ └── design-iteration.md
│
+├── guides/
+│ └── resuming-sessions.md # NEW: Multi-session task resumption
+│
├── task-management/ # JSON-driven task tracking
│ ├── navigation.md
│ ├── standards/
@@ -61,6 +64,7 @@ core/
| **Review code** | `workflows/code-review.md` |
| **Delegate task** | `workflows/task-delegation.md` |
| **Break down feature** | `workflows/feature-breakdown.md` |
+| **Resume session** | `guides/resuming-sessions.md` |
| **Manage tasks** | `task-management/navigation.md` |
| **Task CLI commands** | `task-management/lookup/task-commands.md` |
| **Context system** | `context-system.md` |
diff --git a/.opencode/context/core/workflows/design-iteration-best-practices.md b/.opencode/context/core/workflows/design-iteration-best-practices.md
new file mode 100644
index 0000000..e129f83
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-best-practices.md
@@ -0,0 +1,179 @@
+
+# Design Iteration Best Practices
+
+## Iteration Process
+
+### When to Create Iterations
+
+**Create new iteration** (`{name}_1_1.html`) when:
+- User requests changes to existing design
+- Refining based on feedback
+- A/B testing variations
+- Progressive enhancement
+
+**Create new design** (`{name}_2.html`) when:
+- Complete redesign requested
+- Different approach/style
+- Alternative layout structure
+
+### Iteration Workflow
+
+```
+User: "Can you make the buttons larger and change the color?"
+
+1. Read current file: dashboard_1.html
+2. Make requested changes
+3. Save as: dashboard_1_1.html
+4. Present changes to user
+
+User: "Perfect! Now can we add a sidebar?"
+
+1. Read current file: dashboard_1_1.html
+2. Add sidebar component
+3. Save as: dashboard_1_2.html
+4. Present changes to user
+```
+
+---
+
+## File Management
+
+### Folder Structure
+
+```
+design_iterations/
+├── theme_1.css
+├── theme_2.css
+├── landing_1.html
+├── landing_1_1.html
+├── landing_1_2.html
+├── dashboard_1.html
+├── dashboard_1_1.html
+└── README.md (optional: design notes)
+```
+
+### Version Control
+
+**Track iterations**:
+- Initial: `design_1.html`
+- Iteration 1: `design_1_1.html`
+- Iteration 2: `design_1_2.html`
+- Iteration 3: `design_1_3.html`
+
+**New major version**:
+- Complete redesign: `design_2.html`
+- Then iterate: `design_2_1.html`, `design_2_2.html`
+
+---
+
+## Communication Patterns
+
+### Stage Transitions
+
+**After Layout**:
+```
+"Here's the proposed layout structure. The design uses a [description].
+Would you like to proceed with this layout, or should we make adjustments?"
+```
+
+**After Theme**:
+```
+"I've created a [style] theme with [key features]. The theme file is saved as theme_N.css.
+Does this match your vision, or would you like to adjust colors/typography?"
+```
+
+**After Animation**:
+```
+"Here's the animation plan using [timing/style]. All animations are optimized for performance.
+Are these animations appropriate, or should we adjust the timing/effects?"
+```
+
+**After Implementation**:
+```
+"I've created the complete design as {filename}.html. The design includes [key features].
+Please review and let me know if you'd like any changes or iterations."
+```
+
+### Iteration Requests
+
+**User requests change**:
+```
+"I'll update the design with [changes] and save it as {filename}_N.html.
+This preserves the previous version for reference."
+```
+
+---
+
+## Quality Checklist
+
+Before presenting each stage:
+
+**Layout Stage**:
+- [ ] ASCII wireframe is clear and detailed
+- [ ] Components are well-organized
+- [ ] Responsive behavior is planned
+- [ ] User approval requested
+
+**Theme Stage**:
+- [ ] Theme file created and saved
+- [ ] Colors use OKLCH format
+- [ ] Fonts loaded from Google Fonts
+- [ ] Contrast ratios meet WCAG AA
+- [ ] User approval requested
+
+**Animation Stage**:
+- [ ] Animations documented in micro-syntax
+- [ ] Timing is appropriate (< 400ms)
+- [ ] Performance optimized (transform/opacity)
+- [ ] Accessibility considered
+- [ ] User approval requested
+
+**Implementation Stage**:
+- [ ] Single HTML file created
+- [ ] Theme CSS referenced
+- [ ] Tailwind loaded via script tag
+- [ ] Icons initialized
+- [ ] Responsive design tested
+- [ ] Accessibility attributes added
+- [ ] Images use valid placeholder URLs
+- [ ] Semantic HTML used
+- [ ] User review requested
+
+---
+
+## Troubleshooting
+
+### Common Issues
+
+**Issue**: User wants to skip stages
+**Solution**: Explain benefits of structured approach, but accommodate if insisted
+
+**Issue**: Theme doesn't match user vision
+**Solution**: Iterate on theme file, create theme_2.css with adjustments
+
+**Issue**: Animations feel too slow/fast
+**Solution**: Adjust timing in micro-syntax, regenerate with new values
+
+**Issue**: Design doesn't work on mobile
+**Solution**: Review responsive breakpoints, add mobile-specific styles
+
+**Issue**: Colors have poor contrast
+**Solution**: Use WCAG contrast checker, adjust OKLCH lightness values
+
+---
+
+## References
+
+- [Design Systems Context](../../ui/web/design-systems.md)
+- [UI Styling Standards](../../ui/web/ui-styling-standards.md)
+- [Animation Patterns](../../ui/web/animation-patterns.md)
+- [ASCII Art Generator](https://www.asciiart.eu/)
+- [WCAG Contrast Checker](https://webaim.org/resources/contrastchecker/)
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Stage 4: Implementation](./design-iteration-stage-implementation.md)
+- [Plan Iterations](./design-iteration-plan-iterations.md)
diff --git a/.opencode/context/core/workflows/design-iteration-overview.md b/.opencode/context/core/workflows/design-iteration-overview.md
new file mode 100644
index 0000000..d4d020f
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-overview.md
@@ -0,0 +1,91 @@
+
+# Design Iteration Workflow - Overview
+
+## Overview
+
+A structured 4-stage workflow for creating and iterating on UI designs. This process ensures thoughtful design decisions with user approval at each stage.
+
+## Quick Reference
+
+**Stages**: Layout → Theme → Animation → Implementation
+**Approval**: Required between each stage
+**Output**: Single HTML file per design iteration
+**Location**: `design_iterations/` folder
+
+---
+
+## When to Use This Workflow
+
+### Delegate to OpenFrontendSpecialist When:
+
+**✅ STRONGLY RECOMMENDED** to delegate for:
+- **New UI/UX design work** - Landing pages, dashboards, app interfaces
+- **Design system creation** - Component libraries, theme systems, style guides
+- **Complex layouts** - Multi-column grids, responsive designs, intricate structures
+- **Visual polish** - Animations, transitions, micro-interactions
+- **Brand-focused work** - Marketing pages, product showcases, hero sections
+- **Accessibility-critical UI** - Forms, navigation, interactive components
+
+**Why delegate?**
+- OpenFrontendSpecialist follows the 4-stage design workflow (Layout → Theme → Animation → Implementation)
+- Ensures thoughtful design decisions with approval gates
+- Produces polished, accessible, production-ready UI
+- Handles responsive design, OKLCH colors, semantic HTML
+- Creates single-file HTML prototypes for quick iteration
+
+### Execute Directly When:
+
+**⚠️ Simple cases only**:
+- Minor text/content updates to existing UI
+- Small CSS tweaks (colors, spacing, fonts)
+- Adding simple utility classes
+- Updating existing component props
+- Bug fixes in existing UI code
+
+### Delegation Pattern
+
+```javascript
+// For UI design work
+task(
+ subagent_type="OpenFrontendSpecialist",
+ description="Design {feature} UI",
+ prompt="Design a {feature} following the 4-stage workflow:
+
+ Requirements:
+ - {requirement 1}
+ - {requirement 2}
+
+ Context: {what this UI is for}
+
+ Follow the design iteration workflow:
+ 1. Layout (ASCII wireframe)
+ 2. Theme (design system, colors)
+ 3. Animation (micro-interactions)
+ 4. Implementation (single HTML file)
+
+ Request approval between each stage."
+)
+```
+
+### Example Scenarios
+
+| Scenario | Action | Why |
+|----------|--------|-----|
+| "Create a landing page for our SaaS product" | ✅ Delegate to OpenFrontendSpecialist | Complex UI design, needs 4-stage workflow |
+| "Design a user dashboard with charts" | ✅ Delegate to OpenFrontendSpecialist | Complex layout, visual design, interactions |
+| "Build a component library with our brand" | ✅ Delegate to OpenFrontendSpecialist | Design system work, requires theme expertise |
+| "Fix button color from blue to green" | ⚠️ Execute directly | Simple CSS change |
+| "Update hero text content" | ⚠️ Execute directly | Content update only |
+
+---
+
+## Related Files
+
+- [Design Plan File](./design-iteration-plan-file.md) - MANDATORY plan file template
+- [Stage 1: Layout](./design-iteration-stage-layout.md)
+- [Stage 2: Theme](./design-iteration-stage-theme.md)
+- [Stage 3: Animation](./design-iteration-stage-animation.md)
+- [Stage 4: Implementation](./design-iteration-stage-implementation.md)
+- [Visual Content Generation](./design-iteration-visual-content.md)
+- [Best Practices](./design-iteration-best-practices.md)
+- [Plan Iterations](./design-iteration-plan-iterations.md)
diff --git a/.opencode/context/core/workflows/design-iteration-plan-file.md b/.opencode/context/core/workflows/design-iteration-plan-file.md
new file mode 100644
index 0000000..2457e7b
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-plan-file.md
@@ -0,0 +1,182 @@
+
+# Design Plan File (MANDATORY)
+
+**CRITICAL**: Before starting any design work, create a persistent design plan file.
+
+**Location**: `.tmp/design-plans/{project-name}-{feature-name}.md`
+
+**Purpose**:
+- Preserve design decisions across stages
+- Allow user to review and edit the plan
+- Maintain context for subagent calls
+- Track design evolution and iterations
+
+**When to Create**:
+- BEFORE Stage 1 (Layout Design)
+- After understanding user requirements
+- Before any design work begins
+
+## Template
+
+```markdown
+---
+project: {project-name}
+feature: {feature-name}
+created: {ISO timestamp}
+updated: {ISO timestamp}
+status: in_progress
+current_stage: layout
+---
+
+# Design Plan: {Feature Name}
+
+## User Requirements
+{What the user asked for - verbatim or close paraphrase}
+
+## Design Goals
+- {goal 1}
+- {goal 2}
+- {goal 3}
+
+## Target Audience
+{Who will use this UI}
+
+## Technical Constraints
+- Framework: {Next.js, React, etc.}
+- Responsive: {Yes/No}
+- Accessibility: {WCAG level}
+- Browser support: {Modern, IE11+, etc.}
+
+---
+
+## Stage 1: Layout Design
+
+### Status
+- [ ] Layout planned
+- [ ] ASCII wireframe created
+- [ ] User approved
+
+### Layout Structure
+{ASCII wireframe will be added here}
+
+### Component Breakdown
+{Component list will be added here}
+
+### User Feedback
+{User comments and requested changes}
+
+---
+
+## Stage 2: Theme Design
+
+### Status
+- [ ] Design system selected
+- [ ] Color palette chosen
+- [ ] Typography defined
+- [ ] User approved
+
+### Theme Details
+{Theme specifications will be added here}
+
+### User Feedback
+{User comments and requested changes}
+
+---
+
+## Stage 3: Animation Design
+
+### Status
+- [ ] Micro-interactions defined
+- [ ] Animation timing set
+- [ ] User approved
+
+### Animation Details
+{Animation specifications will be added here}
+
+### User Feedback
+{User comments and requested changes}
+
+---
+
+## Stage 4: Implementation
+
+### Status
+- [ ] HTML structure complete
+- [ ] CSS applied
+- [ ] Animations implemented
+- [ ] User approved
+
+### Output Files
+- HTML: {file path}
+- CSS: {file path}
+- Assets: {file paths}
+
+### User Feedback
+{Final comments and requested changes}
+
+---
+
+## Design Evolution
+
+### Iteration 1
+- Date: {timestamp}
+- Changes: {what changed}
+- Reason: {why it changed}
+
+### Iteration 2
+- Date: {timestamp}
+- Changes: {what changed}
+- Reason: {why it changed}
+```
+
+## Workflow Integration
+
+1. **Create plan file** → Write to `.tmp/design-plans/{name}.md`
+2. **Each stage** → Update plan file with decisions and user feedback
+3. **User approval** → Edit plan file with approved decisions
+4. **User requests changes** → Edit plan file with feedback, iterate
+5. **Subagent calls** → Pass plan file path for context preservation
+6. **Completion** → Plan file contains full design history
+
+## Benefits
+
+- ✅ Context preserved across subagent calls
+- ✅ User can review and edit plan directly
+- ✅ Design decisions documented
+- ✅ Easy to iterate and refine
+- ✅ Full design history tracked
+
+---
+
+## Stage 0: Create Design Plan (MANDATORY FIRST STEP)
+
+**Purpose**: Create persistent plan file before any design work
+
+**Process**:
+1. Understand user requirements
+2. Identify design goals and constraints
+3. Create plan file at `.tmp/design-plans/{project-name}-{feature-name}.md`
+4. Populate with user requirements and goals
+5. Present plan file location to user
+6. Proceed to Stage 1
+
+**Deliverable**: Design plan file created and initialized
+
+**Example**:
+```
+✅ Design plan created: .tmp/design-plans/saas-landing-page.md
+
+You can review and edit this file at any time. All design decisions will be tracked here.
+
+Ready to proceed to Stage 1 (Layout Design)?
+```
+
+**Approval Gate**: "Plan file created. Ready to start layout design?"
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Stage 1: Layout](./design-iteration-stage-layout.md)
+- [Plan Iterations](./design-iteration-plan-iterations.md)
diff --git a/.opencode/context/core/workflows/design-iteration-plan-iterations.md b/.opencode/context/core/workflows/design-iteration-plan-iterations.md
new file mode 100644
index 0000000..84aef76
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-plan-iterations.md
@@ -0,0 +1,153 @@
+
+# Handling Plan File Edits and Iterations
+
+## User Edits Plan File Directly
+
+**Scenario**: User opens `.tmp/design-plans/{name}.md` and makes changes
+
+**Process**:
+1. User edits plan file (changes requirements, adds constraints, modifies goals)
+2. User notifies agent: "I've updated the plan file"
+3. Agent reads updated plan file
+4. Agent identifies what changed
+5. Agent proposes how to incorporate changes
+6. Agent updates affected stages
+
+**Example**:
+```
+User: "I've updated the plan file - changed the color scheme to dark mode"
+
+Agent:
+✅ Read updated plan: .tmp/design-plans/saas-landing-page.md
+
+Changes detected:
+- Color scheme: Light → Dark mode
+- Primary color: Blue → Purple
+
+This affects:
+- Stage 2 (Theme) - needs regeneration
+- Stage 4 (Implementation) - needs CSS update
+
+Would you like me to:
+1. Regenerate theme with dark mode
+2. Update implementation with new theme
+```
+
+---
+
+## Iteration Within a Stage
+
+**Scenario**: User requests changes during a stage
+
+**Process**:
+1. Agent presents stage output (e.g., layout wireframe)
+2. User requests changes: "Make the hero section taller"
+3. Agent updates plan file with feedback
+4. Agent makes changes
+5. Agent updates plan file with new iteration
+6. Agent presents updated output
+
+**Example**:
+```
+Stage 1 - Layout Design
+
+Agent: [presents wireframe]
+
+User: "Make the hero section taller and move CTA above the fold"
+
+Agent:
+✅ Updated plan file with feedback
+✅ Revised layout wireframe
+✅ Updated plan file with Iteration 2
+
+[presents updated wireframe]
+```
+
+---
+
+## Tracking Iterations in Plan File
+
+**Format**:
+```markdown
+## Design Evolution
+
+### Iteration 1 - Initial Layout
+- Date: 2026-01-30T10:00:00Z
+- Stage: Layout
+- Changes: Initial wireframe created
+- User feedback: "Hero section too short, CTA below fold"
+
+### Iteration 2 - Revised Layout
+- Date: 2026-01-30T10:15:00Z
+- Stage: Layout
+- Changes: Increased hero height from 400px to 600px, moved CTA above fold
+- User feedback: "Perfect! Approved."
+- Status: ✅ Approved
+
+### Iteration 3 - Theme Adjustment
+- Date: 2026-01-30T10:30:00Z
+- Stage: Theme
+- Changes: Changed from light to dark mode, primary color blue → purple
+- User feedback: "Love the dark mode!"
+- Status: ✅ Approved
+```
+
+---
+
+## Subagent Context Preservation
+
+**Problem**: Subagents lose context between calls
+
+**Solution**: Always pass plan file path
+
+**Pattern**:
+```javascript
+// When delegating to subagent
+task(
+ subagent_type="OpenFrontendSpecialist",
+ description="Implement Stage 4",
+ prompt="Load design plan from .tmp/design-plans/saas-landing-page.md
+
+ Read the plan file for:
+ - All approved decisions from Stages 1-3
+ - User requirements and constraints
+ - Design evolution and iterations
+
+ Implement Stage 4 (Implementation) following all approved decisions.
+
+ Update the plan file with:
+ - Output file paths
+ - Implementation status
+ - Any issues encountered"
+)
+```
+
+---
+
+## Plan File as Single Source of Truth
+
+### Benefits
+
+- ✅ All design decisions in one place
+- ✅ User can review and edit anytime
+- ✅ Subagents have full context
+- ✅ Design history preserved
+- ✅ Easy to iterate and refine
+- ✅ No context loss between stages
+
+### Best Practices
+
+- Always read plan file at start of each stage
+- Update plan file after every user interaction
+- Track all iterations with timestamps
+- Document user feedback verbatim
+- Mark approved decisions clearly
+- Pass plan file path to all subagents
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Design Plan File](./design-iteration-plan-file.md)
+- [Best Practices](./design-iteration-best-practices.md)
diff --git a/.opencode/context/core/workflows/design-iteration-stage-animation.md b/.opencode/context/core/workflows/design-iteration-stage-animation.md
new file mode 100644
index 0000000..d354a6d
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-stage-animation.md
@@ -0,0 +1,80 @@
+
+# Stage 3: Animation Design
+
+**Purpose**: Define micro-interactions and transitions
+
+## Process
+
+1. Read design plan file from `.tmp/design-plans/{name}.md`
+2. Review approved theme from Stage 2
+3. Identify key interactions (hover, click, scroll)
+4. Define animation timing and easing
+5. Plan loading states and transitions
+6. Document animations using micro-syntax
+7. **Update plan file** with animation specifications
+8. Present animation plan to user for approval
+9. **Update plan file** with user feedback and approval status
+
+## Deliverable
+
+- Animation specification in micro-syntax format
+- Updated plan file with Stage 3 complete
+
+## Example Output
+
+```
+## Animation Design: Smooth & Professional
+
+### Button Interactions
+hover: 200ms ease-out [Y0→-2, shadow↗]
+press: 100ms ease-in [S1→0.95]
+ripple: 400ms ease-out [S0→2, α1→0]
+
+### Card Interactions
+cardHover: 300ms ease-out [Y0→-4, shadow↗]
+cardClick: 200ms ease-out [S1→1.02]
+
+### Page Transitions
+pageEnter: 300ms ease-out [α0→1, Y+20→0]
+pageExit: 200ms ease-in [α1→0]
+
+### Loading States
+spinner: 1000ms ∞ linear [R360°]
+skeleton: 2000ms ∞ [bg: muted↔accent]
+
+### Micro-Interactions
+inputFocus: 200ms ease-out [S1→1.01, ring]
+linkHover: 250ms ease-out [underline 0→100%]
+
+**Philosophy**: Subtle, purposeful animations that enhance UX without distraction
+**Performance**: All animations use transform/opacity for 60fps
+**Accessibility**: Respects prefers-reduced-motion
+```
+
+## Best Practices
+
+✅ **Do**:
+- Use micro-syntax for documentation
+- Keep animations under 400ms
+- Use transform/opacity for performance
+- Respect prefers-reduced-motion
+- Make animations purposeful
+
+❌ **Don't**:
+- Animate width/height (use scale)
+- Create distracting animations
+- Ignore performance implications
+- Skip accessibility considerations
+
+## Approval Gate
+
+"Are these animations appropriate for your design, or should we adjust?"
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Stage 2: Theme](./design-iteration-stage-theme.md)
+- [Stage 4: Implementation](./design-iteration-stage-implementation.md)
+- [Animation Patterns](../../ui/web/animation-patterns.md)
diff --git a/.opencode/context/core/workflows/design-iteration-stage-implementation.md b/.opencode/context/core/workflows/design-iteration-stage-implementation.md
new file mode 100644
index 0000000..bd90697
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-stage-implementation.md
@@ -0,0 +1,157 @@
+
+# Stage 4: Implementation
+
+**Purpose**: Generate complete HTML file with all components
+
+## Process
+
+1. Read design plan file from `.tmp/design-plans/{name}.md`
+2. Review all approved decisions from Stages 1-3
+3. Build individual UI components
+4. Integrate theme CSS
+5. Add animations and interactions
+6. Combine into single HTML file
+7. Test responsive behavior
+8. Save to design_iterations folder
+9. **Update plan file** with output file paths
+10. Present to user for review
+11. **Update plan file** with user feedback and final approval status
+
+## Deliverable
+
+- Complete HTML file with embedded or linked CSS
+- Updated plan file with Stage 4 complete and all output files documented
+
+## File Organization
+
+```
+design_iterations/
+├── theme_1.css # Theme file from Stage 2
+├── dashboard_1.html # Initial design
+├── dashboard_1_1.html # First iteration
+├── dashboard_1_2.html # Second iteration
+├── chat_ui_1.html # Different design
+└── chat_ui_1_1.html # Iteration of chat UI
+```
+
+## Naming Conventions
+
+| Type | Format | Example |
+|------|--------|---------|
+| Initial design | `{name}_1.html` | `table_1.html` |
+| First iteration | `{name}_1_1.html` | `table_1_1.html` |
+| Second iteration | `{name}_1_2.html` | `table_1_2.html` |
+| New design | `{name}_2.html` | `table_2.html` |
+
+## Implementation Checklist
+
+```html
+
+
+
+
+
+ Design Name
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+## Best Practices
+
+✅ **Do**:
+- Use single HTML file per design
+- Load Tailwind via script tag
+- Reference theme CSS file
+- Use !important for framework overrides
+- Test responsive behavior
+- Provide alt text for images
+- Use semantic HTML
+
+❌ **Don't**:
+- Split into multiple files
+- Load Tailwind as stylesheet
+- Inline all styles
+- Skip accessibility attributes
+- Use made-up image URLs
+- Use div soup (non-semantic HTML)
+
+## Approval Gate
+
+"Please review the design. Would you like any changes or iterations?"
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Stage 3: Animation](./design-iteration-stage-animation.md)
+- [Best Practices](./design-iteration-best-practices.md)
+- [Iteration Process](./design-iteration-plan-iterations.md)
diff --git a/.opencode/context/core/workflows/design-iteration-stage-layout.md b/.opencode/context/core/workflows/design-iteration-stage-layout.md
new file mode 100644
index 0000000..5e14b1c
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-stage-layout.md
@@ -0,0 +1,115 @@
+
+# Stage 1: Layout Design
+
+**Purpose**: Define the structure and component hierarchy before visual design
+
+## Process
+
+1. Read design plan file from `.tmp/design-plans/{name}.md`
+2. Analyze user requirements from plan
+3. Identify core UI components
+4. Plan layout structure and responsive behavior
+5. Create ASCII wireframe
+6. **Update plan file** with layout structure and component breakdown
+7. Present to user for approval
+8. **Update plan file** with user feedback and approval status
+
+## Deliverable
+
+- ASCII wireframe with component breakdown
+- Updated plan file with Stage 1 complete
+
+## Example Output
+
+```
+## Core UI Components
+
+**Header Area**
+- Logo/brand (Top left)
+- Navigation menu (Top center)
+- User actions (Top right)
+
+**Main Content Area**
+- Hero section (Full width)
+- Feature cards (3-column grid on desktop, stack on mobile)
+- Call-to-action (Centered)
+
+**Footer**
+- Links (4-column grid)
+- Social icons (Centered)
+- Copyright (Bottom)
+
+## Layout Structure
+
+Desktop (1024px+):
+┌─────────────────────────────────────────────────┐
+│ [Logo] Navigation [User Menu] │
+├─────────────────────────────────────────────────┤
+│ │
+│ HERO SECTION │
+│ (Full width, centered text) │
+│ │
+├─────────────────────────────────────────────────┤
+│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
+│ │ Card 1 │ │ Card 2 │ │ Card 3 │ │
+│ │ │ │ │ │ │ │
+│ └─────────┘ └─────────┘ └─────────┘ │
+├─────────────────────────────────────────────────┤
+│ [Call to Action] │
+├─────────────────────────────────────────────────┤
+│ Links Links Links Social │
+│ Copyright │
+└─────────────────────────────────────────────────┘
+
+Mobile (< 768px):
+┌─────────────────┐
+│ ☰ Logo [👤] │
+├─────────────────┤
+│ │
+│ HERO SECTION │
+│ │
+├─────────────────┤
+│ ┌───────────┐ │
+│ │ Card 1 │ │
+│ └───────────┘ │
+│ ┌───────────┐ │
+│ │ Card 2 │ │
+│ └───────────┘ │
+│ ┌───────────┐ │
+│ │ Card 3 │ │
+│ └───────────┘ │
+├─────────────────┤
+│ [CTA] │
+├─────────────────┤
+│ Links │
+│ Social │
+│ Copyright │
+└─────────────────┘
+```
+
+## Best Practices
+
+✅ **Do**:
+- Use ASCII wireframes for clarity
+- Break down into component hierarchy
+- Plan responsive behavior upfront
+- Consider mobile-first approach
+- Get approval before proceeding
+
+❌ **Don't**:
+- Skip wireframing and jump to code
+- Ignore responsive considerations
+- Proceed without user approval
+- Over-complicate initial layout
+
+## Approval Gate
+
+"Would you like to proceed with this layout or need modifications?"
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Design Plan File](./design-iteration-plan-file.md)
+- [Stage 2: Theme](./design-iteration-stage-theme.md)
diff --git a/.opencode/context/core/workflows/design-iteration-stage-theme.md b/.opencode/context/core/workflows/design-iteration-stage-theme.md
new file mode 100644
index 0000000..c953a76
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-stage-theme.md
@@ -0,0 +1,84 @@
+
+# Stage 2: Theme Design
+
+**Purpose**: Define colors, typography, spacing, and visual style
+
+## Process
+
+1. Read design plan file from `.tmp/design-plans/{name}.md`
+2. Review approved layout from Stage 1
+3. Choose design system (neo-brutalism, modern dark, custom)
+4. Select color palette (avoid Bootstrap blue unless requested)
+5. Choose typography (Google Fonts)
+6. Define spacing and shadows
+7. Generate theme CSS file
+8. **Update plan file** with theme specifications
+9. Present theme to user for approval
+10. **Update plan file** with user feedback and approval status
+
+## Deliverable
+
+- CSS theme file saved to `design_iterations/theme_N.css`
+- Updated plan file with Stage 2 complete
+
+## Theme Selection Criteria
+
+| Style | Use When | Avoid When |
+|-------|----------|------------|
+| Neo-Brutalism | Creative/artistic projects, retro aesthetic | Enterprise apps, accessibility-critical |
+| Modern Dark | SaaS, developer tools, professional dashboards | Playful consumer apps |
+| Custom | Specific brand requirements | Time-constrained projects |
+
+## Example Output
+
+```
+## Theme Design: Modern Professional
+
+**Style Reference**: Vercel/Linear aesthetic
+**Color Palette**: Monochromatic with accent
+**Typography**: Inter (UI) + JetBrains Mono (code)
+**Spacing**: 4px base unit
+**Shadows**: Subtle, soft elevation
+
+**Theme File**: design_iterations/theme_1.css
+
+Key Design Decisions:
+- Primary: Neutral gray for professional feel
+- Accent: Subtle blue for interactive elements
+- Radius: 0.625rem for modern, friendly feel
+- Shadows: Soft, minimal elevation
+- Fonts: System-like for familiarity
+```
+
+## File Naming
+
+`theme_1.css`, `theme_2.css`, etc.
+
+## Best Practices
+
+✅ **Do**:
+- Reference design system context files
+- Use CSS custom properties
+- Save theme to separate file
+- Consider accessibility (contrast ratios)
+- Avoid Bootstrap blue unless requested
+
+❌ **Don't**:
+- Hardcode colors in HTML
+- Use generic/overused color schemes
+- Skip contrast testing
+- Mix color formats (stick to OKLCH)
+
+## Approval Gate
+
+"Does this theme match your vision, or would you like adjustments?"
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Stage 1: Layout](./design-iteration-stage-layout.md)
+- [Stage 3: Animation](./design-iteration-stage-animation.md)
+- [Design Systems Context](../../ui/web/design-systems.md)
+- [UI Styling Standards](../../ui/web/ui-styling-standards.md)
diff --git a/.opencode/context/core/workflows/design-iteration-visual-content.md b/.opencode/context/core/workflows/design-iteration-visual-content.md
new file mode 100644
index 0000000..bf52df6
--- /dev/null
+++ b/.opencode/context/core/workflows/design-iteration-visual-content.md
@@ -0,0 +1,110 @@
+
+# Visual Content Generation
+
+## When to Use Image Specialist
+
+Delegate to **Image Specialist** subagent when users request:
+
+- **Diagrams & Visualizations**: Architecture diagrams, flowcharts, system visualizations
+- **UI Mockups & Wireframes**: Visual mockups, design concepts, interface previews
+- **Graphics & Assets**: Social media graphics, promotional images, icons, illustrations
+- **Image Editing**: Photo enhancement, image modifications, visual adjustments
+
+## Invocation Pattern
+
+```javascript
+task(
+ subagent_type="Image Specialist",
+ description="Generate/edit visual content",
+ prompt="Context to load:
+ - .opencode/context/core/visual-development.md
+
+ Task: [Specific visual requirement]
+
+ Requirements:
+ - [Visual style/aesthetic]
+ - [Dimensions/format]
+ - [Key elements to include]
+ - [Color scheme/branding]
+
+ Output: [Expected deliverable]"
+)
+```
+
+## Example Use Cases
+
+### Architecture Diagram
+
+```javascript
+task(
+ subagent_type="Image Specialist",
+ description="Generate microservices architecture diagram",
+ prompt="Create a diagram showing:
+ - 5 microservices (API Gateway, Auth, Orders, Payments, Notifications)
+ - Database connections
+ - Message queue (RabbitMQ)
+ - External services (Stripe, SendGrid)
+
+ Style: Clean, professional, modern
+ Format: PNG, 1920x1080"
+)
+```
+
+### UI Mockup
+
+```javascript
+task(
+ subagent_type="Image Specialist",
+ description="Generate dashboard mockup",
+ prompt="Create a mockup for an analytics dashboard:
+ - Header with navigation
+ - 4 metric cards (Users, Revenue, Conversion, Retention)
+ - Line chart showing trends
+ - Data table below
+
+ Style: Modern, dark theme, professional
+ Format: PNG, 1440x900"
+)
+```
+
+### Social Media Graphic
+
+```javascript
+task(
+ subagent_type="Image Specialist",
+ description="Generate product launch graphic",
+ prompt="Create a social media graphic announcing new feature:
+ - Bold headline: 'Introducing Real-Time Collaboration'
+ - Subtext: 'Work together, ship faster'
+ - Brand colors: #6366f1 (primary), #1e293b (dark)
+ - Include abstract collaboration visual
+
+ Format: PNG, 1200x630 (Twitter/LinkedIn)"
+)
+```
+
+## Tools Required
+
+- **tool:gemini** - Gemini Nano Banana AI for image generation/editing
+- Automatically available in Developer profile
+
+## When NOT to Delegate
+
+**Use design-iteration workflow instead** when:
+- Creating interactive HTML/CSS designs
+- Building complete UI implementations
+- Iterating on existing HTML files
+- Need responsive, production-ready code
+
+**Use image-specialist** when:
+- Need static visual assets
+- Creating diagrams or illustrations
+- Generating mockups for presentation
+- Quick visual concepts without code
+
+---
+
+## Related Files
+
+- [Overview](./design-iteration-overview.md)
+- [Visual Development](../visual-development.md)
diff --git a/.opencode/context/core/workflows/design-iteration.md b/.opencode/context/core/workflows/design-iteration.md
deleted file mode 100644
index bfc0724..0000000
--- a/.opencode/context/core/workflows/design-iteration.md
+++ /dev/null
@@ -1,1058 +0,0 @@
-
-# Design Iteration Workflow
-
-## Overview
-
-A structured 4-stage workflow for creating and iterating on UI designs. This process ensures thoughtful design decisions with user approval at each stage.
-
-## Quick Reference
-
-**Stages**: Layout → Theme → Animation → Implementation
-**Approval**: Required between each stage
-**Output**: Single HTML file per design iteration
-**Location**: `design_iterations/` folder
-
----
-
-## When to Use This Workflow
-
-### Delegate to OpenFrontendSpecialist When:
-
-**✅ STRONGLY RECOMMENDED** to delegate for:
-- **New UI/UX design work** - Landing pages, dashboards, app interfaces
-- **Design system creation** - Component libraries, theme systems, style guides
-- **Complex layouts** - Multi-column grids, responsive designs, intricate structures
-- **Visual polish** - Animations, transitions, micro-interactions
-- **Brand-focused work** - Marketing pages, product showcases, hero sections
-- **Accessibility-critical UI** - Forms, navigation, interactive components
-
-**Why delegate?**
-- OpenFrontendSpecialist follows the 4-stage design workflow (Layout → Theme → Animation → Implementation)
-- Ensures thoughtful design decisions with approval gates
-- Produces polished, accessible, production-ready UI
-- Handles responsive design, OKLCH colors, semantic HTML
-- Creates single-file HTML prototypes for quick iteration
-
-### Execute Directly When:
-
-**⚠️ Simple cases only**:
-- Minor text/content updates to existing UI
-- Small CSS tweaks (colors, spacing, fonts)
-- Adding simple utility classes
-- Updating existing component props
-- Bug fixes in existing UI code
-
-### Delegation Pattern
-
-```javascript
-// For UI design work
-task(
- subagent_type="OpenFrontendSpecialist",
- description="Design {feature} UI",
- prompt="Design a {feature} following the 4-stage workflow:
-
- Requirements:
- - {requirement 1}
- - {requirement 2}
-
- Context: {what this UI is for}
-
- Follow the design iteration workflow:
- 1. Layout (ASCII wireframe)
- 2. Theme (design system, colors)
- 3. Animation (micro-interactions)
- 4. Implementation (single HTML file)
-
- Request approval between each stage."
-)
-```
-
-### Example Scenarios
-
-| Scenario | Action | Why |
-|----------|--------|-----|
-| "Create a landing page for our SaaS product" | ✅ Delegate to OpenFrontendSpecialist | Complex UI design, needs 4-stage workflow |
-| "Design a user dashboard with charts" | ✅ Delegate to OpenFrontendSpecialist | Complex layout, visual design, interactions |
-| "Build a component library with our brand" | ✅ Delegate to OpenFrontendSpecialist | Design system work, requires theme expertise |
-| "Fix button color from blue to green" | ⚠️ Execute directly | Simple CSS change |
-| "Update hero text content" | ⚠️ Execute directly | Content update only |
-
----
-
-## Design Plan File (MANDATORY)
-
-**CRITICAL**: Before starting any design work, create a persistent design plan file.
-
-**Location**: `.tmp/design-plans/{project-name}-{feature-name}.md`
-
-**Purpose**:
-- Preserve design decisions across stages
-- Allow user to review and edit the plan
-- Maintain context for subagent calls
-- Track design evolution and iterations
-
-**When to Create**:
-- BEFORE Stage 1 (Layout Design)
-- After understanding user requirements
-- Before any design work begins
-
-**Template**:
-```markdown
----
-project: {project-name}
-feature: {feature-name}
-created: {ISO timestamp}
-updated: {ISO timestamp}
-status: in_progress
-current_stage: layout
----
-
-# Design Plan: {Feature Name}
-
-## User Requirements
-{What the user asked for - verbatim or close paraphrase}
-
-## Design Goals
-- {goal 1}
-- {goal 2}
-- {goal 3}
-
-## Target Audience
-{Who will use this UI}
-
-## Technical Constraints
-- Framework: {Next.js, React, etc.}
-- Responsive: {Yes/No}
-- Accessibility: {WCAG level}
-- Browser support: {Modern, IE11+, etc.}
-
----
-
-## Stage 1: Layout Design
-
-### Status
-- [ ] Layout planned
-- [ ] ASCII wireframe created
-- [ ] User approved
-
-### Layout Structure
-{ASCII wireframe will be added here}
-
-### Component Breakdown
-{Component list will be added here}
-
-### User Feedback
-{User comments and requested changes}
-
----
-
-## Stage 2: Theme Design
-
-### Status
-- [ ] Design system selected
-- [ ] Color palette chosen
-- [ ] Typography defined
-- [ ] User approved
-
-### Theme Details
-{Theme specifications will be added here}
-
-### User Feedback
-{User comments and requested changes}
-
----
-
-## Stage 3: Animation Design
-
-### Status
-- [ ] Micro-interactions defined
-- [ ] Animation timing set
-- [ ] User approved
-
-### Animation Details
-{Animation specifications will be added here}
-
-### User Feedback
-{User comments and requested changes}
-
----
-
-## Stage 4: Implementation
-
-### Status
-- [ ] HTML structure complete
-- [ ] CSS applied
-- [ ] Animations implemented
-- [ ] User approved
-
-### Output Files
-- HTML: {file path}
-- CSS: {file path}
-- Assets: {file paths}
-
-### User Feedback
-{Final comments and requested changes}
-
----
-
-## Design Evolution
-
-### Iteration 1
-- Date: {timestamp}
-- Changes: {what changed}
-- Reason: {why it changed}
-
-### Iteration 2
-- Date: {timestamp}
-- Changes: {what changed}
-- Reason: {why it changed}
-```
-
-**Workflow Integration**:
-1. **Create plan file** → Write to `.tmp/design-plans/{name}.md`
-2. **Each stage** → Update plan file with decisions and user feedback
-3. **User approval** → Edit plan file with approved decisions
-4. **User requests changes** → Edit plan file with feedback, iterate
-5. **Subagent calls** → Pass plan file path for context preservation
-6. **Completion** → Plan file contains full design history
-
-**Benefits**:
-- ✅ Context preserved across subagent calls
-- ✅ User can review and edit plan directly
-- ✅ Design decisions documented
-- ✅ Easy to iterate and refine
-- ✅ Full design history tracked
-
----
-
-## Workflow Stages
-
-### Stage 0: Create Design Plan (MANDATORY FIRST STEP)
-
-**Purpose**: Create persistent plan file before any design work
-
-**Process**:
-1. Understand user requirements
-2. Identify design goals and constraints
-3. Create plan file at `.tmp/design-plans/{project-name}-{feature-name}.md`
-4. Populate with user requirements and goals
-5. Present plan file location to user
-6. Proceed to Stage 1
-
-**Deliverable**: Design plan file created and initialized
-
-**Example**:
-```
-✅ Design plan created: .tmp/design-plans/saas-landing-page.md
-
-You can review and edit this file at any time. All design decisions will be tracked here.
-
-Ready to proceed to Stage 1 (Layout Design)?
-```
-
-**Approval Gate**: "Plan file created. Ready to start layout design?"
-
----
-
-### Stage 1: Layout Design
-
-**Purpose**: Define the structure and component hierarchy before visual design
-
-**Process**:
-1. Read design plan file from `.tmp/design-plans/{name}.md`
-2. Analyze user requirements from plan
-3. Identify core UI components
-4. Plan layout structure and responsive behavior
-5. Create ASCII wireframe
-6. **Update plan file** with layout structure and component breakdown
-7. Present to user for approval
-8. **Update plan file** with user feedback and approval status
-
-**Deliverable**:
-- ASCII wireframe with component breakdown
-- Updated plan file with Stage 1 complete
-
-**Example Output**:
-
-```
-## Core UI Components
-
-**Header Area**
-- Logo/brand (Top left)
-- Navigation menu (Top center)
-- User actions (Top right)
-
-**Main Content Area**
-- Hero section (Full width)
-- Feature cards (3-column grid on desktop, stack on mobile)
-- Call-to-action (Centered)
-
-**Footer**
-- Links (4-column grid)
-- Social icons (Centered)
-- Copyright (Bottom)
-
-## Layout Structure
-
-Desktop (1024px+):
-┌─────────────────────────────────────────────────┐
-│ [Logo] Navigation [User Menu] │
-├─────────────────────────────────────────────────┤
-│ │
-│ HERO SECTION │
-│ (Full width, centered text) │
-│ │
-├─────────────────────────────────────────────────┤
-│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
-│ │ Card 1 │ │ Card 2 │ │ Card 3 │ │
-│ │ │ │ │ │ │ │
-│ └─────────┘ └─────────┘ └─────────┘ │
-├─────────────────────────────────────────────────┤
-│ [Call to Action] │
-├─────────────────────────────────────────────────┤
-│ Links Links Links Social │
-│ Copyright │
-└─────────────────────────────────────────────────┘
-
-Mobile (< 768px):
-┌─────────────────┐
-│ ☰ Logo [👤] │
-├─────────────────┤
-│ │
-│ HERO SECTION │
-│ │
-├─────────────────┤
-│ ┌───────────┐ │
-│ │ Card 1 │ │
-│ └───────────┘ │
-│ ┌───────────┐ │
-│ │ Card 2 │ │
-│ └───────────┘ │
-│ ┌───────────┐ │
-│ │ Card 3 │ │
-│ └───────────┘ │
-├─────────────────┤
-│ [CTA] │
-├─────────────────┤
-│ Links │
-│ Social │
-│ Copyright │
-└─────────────────┘
-```
-
-**Approval Gate**: "Would you like to proceed with this layout or need modifications?"
-
----
-
-### Stage 2: Theme Design
-
-**Purpose**: Define colors, typography, spacing, and visual style
-
-**Process**:
-1. Read design plan file from `.tmp/design-plans/{name}.md`
-2. Review approved layout from Stage 1
-3. Choose design system (neo-brutalism, modern dark, custom)
-4. Select color palette (avoid Bootstrap blue unless requested)
-5. Choose typography (Google Fonts)
-6. Define spacing and shadows
-7. Generate theme CSS file
-8. **Update plan file** with theme specifications
-9. Present theme to user for approval
-10. **Update plan file** with user feedback and approval status
-
-**Deliverable**:
-- CSS theme file saved to `design_iterations/theme_N.css`
-- Updated plan file with Stage 2 complete
-
-**Theme Selection Criteria**:
-
-| Style | Use When | Avoid When |
-|-------|----------|------------|
-| Neo-Brutalism | Creative/artistic projects, retro aesthetic | Enterprise apps, accessibility-critical |
-| Modern Dark | SaaS, developer tools, professional dashboards | Playful consumer apps |
-| Custom | Specific brand requirements | Time-constrained projects |
-
-**Example Output**:
-
-```
-## Theme Design: Modern Professional
-
-**Style Reference**: Vercel/Linear aesthetic
-**Color Palette**: Monochromatic with accent
-**Typography**: Inter (UI) + JetBrains Mono (code)
-**Spacing**: 4px base unit
-**Shadows**: Subtle, soft elevation
-
-**Theme File**: design_iterations/theme_1.css
-
-Key Design Decisions:
-- Primary: Neutral gray for professional feel
-- Accent: Subtle blue for interactive elements
-- Radius: 0.625rem for modern, friendly feel
-- Shadows: Soft, minimal elevation
-- Fonts: System-like for familiarity
-```
-
-**File Naming**: `theme_1.css`, `theme_2.css`, etc.
-
-**Approval Gate**: "Does this theme match your vision, or would you like adjustments?"
-
----
-
-### Stage 3: Animation Design
-
-**Purpose**: Define micro-interactions and transitions
-
-**Process**:
-1. Read design plan file from `.tmp/design-plans/{name}.md`
-2. Review approved theme from Stage 2
-3. Identify key interactions (hover, click, scroll)
-4. Define animation timing and easing
-5. Plan loading states and transitions
-6. Document animations using micro-syntax
-7. **Update plan file** with animation specifications
-8. Present animation plan to user for approval
-9. **Update plan file** with user feedback and approval status
-
-**Deliverable**:
-- Animation specification in micro-syntax format
-- Updated plan file with Stage 3 complete
-
-**Example Output**:
-
-```
-## Animation Design: Smooth & Professional
-
-### Button Interactions
-hover: 200ms ease-out [Y0→-2, shadow↗]
-press: 100ms ease-in [S1→0.95]
-ripple: 400ms ease-out [S0→2, α1→0]
-
-### Card Interactions
-cardHover: 300ms ease-out [Y0→-4, shadow↗]
-cardClick: 200ms ease-out [S1→1.02]
-
-### Page Transitions
-pageEnter: 300ms ease-out [α0→1, Y+20→0]
-pageExit: 200ms ease-in [α1→0]
-
-### Loading States
-spinner: 1000ms ∞ linear [R360°]
-skeleton: 2000ms ∞ [bg: muted↔accent]
-
-### Micro-Interactions
-inputFocus: 200ms ease-out [S1→1.01, ring]
-linkHover: 250ms ease-out [underline 0→100%]
-
-**Philosophy**: Subtle, purposeful animations that enhance UX without distraction
-**Performance**: All animations use transform/opacity for 60fps
-**Accessibility**: Respects prefers-reduced-motion
-```
-
-**Approval Gate**: "Are these animations appropriate for your design, or should we adjust?"
-
----
-
-### Stage 4: Implementation
-
-**Purpose**: Generate complete HTML file with all components
-
-**Process**:
-1. Read design plan file from `.tmp/design-plans/{name}.md`
-2. Review all approved decisions from Stages 1-3
-3. Build individual UI components
-4. Integrate theme CSS
-5. Add animations and interactions
-6. Combine into single HTML file
-7. Test responsive behavior
-8. Save to design_iterations folder
-9. **Update plan file** with output file paths
-10. Present to user for review
-11. **Update plan file** with user feedback and final approval status
-
-**Deliverable**:
-- Complete HTML file with embedded or linked CSS
-- Updated plan file with Stage 4 complete and all output files documented
-
-**File Organization**:
-
-```
-design_iterations/
-├── theme_1.css # Theme file from Stage 2
-├── dashboard_1.html # Initial design
-├── dashboard_1_1.html # First iteration
-├── dashboard_1_2.html # Second iteration
-├── chat_ui_1.html # Different design
-└── chat_ui_1_1.html # Iteration of chat UI
-```
-
-**Naming Conventions**:
-
-| Type | Format | Example |
-|------|--------|---------|
-| Initial design | `{name}_1.html` | `table_1.html` |
-| First iteration | `{name}_1_1.html` | `table_1_1.html` |
-| Second iteration | `{name}_1_2.html` | `table_1_2.html` |
-| New design | `{name}_2.html` | `table_2.html` |
-
-**Implementation Checklist**:
-
-```html
-
-
-
-
-
- Design Name
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**Approval Gate**: "Please review the design. Would you like any changes or iterations?"
-
----
-
-## Visual Content Generation
-
-### When to Use Image Specialist
-
-Delegate to **Image Specialist** subagent when users request:
-
-- **Diagrams & Visualizations**: Architecture diagrams, flowcharts, system visualizations
-- **UI Mockups & Wireframes**: Visual mockups, design concepts, interface previews
-- **Graphics & Assets**: Social media graphics, promotional images, icons, illustrations
-- **Image Editing**: Photo enhancement, image modifications, visual adjustments
-
-### Invocation Pattern
-
-```javascript
-task(
- subagent_type="Image Specialist",
- description="Generate/edit visual content",
- prompt="Context to load:
- - .opencode/context/core/visual-development.md
-
- Task: [Specific visual requirement]
-
- Requirements:
- - [Visual style/aesthetic]
- - [Dimensions/format]
- - [Key elements to include]
- - [Color scheme/branding]
-
- Output: [Expected deliverable]"
-)
-```
-
-### Example Use Cases
-
-**Architecture Diagram**:
-```javascript
-task(
- subagent_type="Image Specialist",
- description="Generate microservices architecture diagram",
- prompt="Create a diagram showing:
- - 5 microservices (API Gateway, Auth, Orders, Payments, Notifications)
- - Database connections
- - Message queue (RabbitMQ)
- - External services (Stripe, SendGrid)
-
- Style: Clean, professional, modern
- Format: PNG, 1920x1080"
-)
-```
-
-**UI Mockup**:
-```javascript
-task(
- subagent_type="Image Specialist",
- description="Generate dashboard mockup",
- prompt="Create a mockup for an analytics dashboard:
- - Header with navigation
- - 4 metric cards (Users, Revenue, Conversion, Retention)
- - Line chart showing trends
- - Data table below
-
- Style: Modern, dark theme, professional
- Format: PNG, 1440x900"
-)
-```
-
-**Social Media Graphic**:
-```javascript
-task(
- subagent_type="Image Specialist",
- description="Generate product launch graphic",
- prompt="Create a social media graphic announcing new feature:
- - Bold headline: 'Introducing Real-Time Collaboration'
- - Subtext: 'Work together, ship faster'
- - Brand colors: #6366f1 (primary), #1e293b (dark)
- - Include abstract collaboration visual
-
- Format: PNG, 1200x630 (Twitter/LinkedIn)"
-)
-```
-
-### Tools Required
-
-- **tool:gemini** - Gemini Nano Banana AI for image generation/editing
-- Automatically available in Developer profile
-
-### When NOT to Delegate
-
-**Use design-iteration workflow instead** when:
-- Creating interactive HTML/CSS designs
-- Building complete UI implementations
-- Iterating on existing HTML files
-- Need responsive, production-ready code
-
-**Use image-specialist** when:
-- Need static visual assets
-- Creating diagrams or illustrations
-- Generating mockups for presentation
-- Quick visual concepts without code
-
----
-
-## Iteration Process
-
-### When to Create Iterations
-
-**Create new iteration** (`{name}_1_1.html`) when:
-- User requests changes to existing design
-- Refining based on feedback
-- A/B testing variations
-- Progressive enhancement
-
-**Create new design** (`{name}_2.html`) when:
-- Complete redesign requested
-- Different approach/style
-- Alternative layout structure
-
-### Iteration Workflow
-
-```
-User: "Can you make the buttons larger and change the color?"
-
-1. Read current file: dashboard_1.html
-2. Make requested changes
-3. Save as: dashboard_1_1.html
-4. Present changes to user
-
-User: "Perfect! Now can we add a sidebar?"
-
-1. Read current file: dashboard_1_1.html
-2. Add sidebar component
-3. Save as: dashboard_1_2.html
-4. Present changes to user
-```
-
----
-
-## Best Practices
-
-### Layout Stage
-
-✅ **Do**:
-- Use ASCII wireframes for clarity
-- Break down into component hierarchy
-- Plan responsive behavior upfront
-- Consider mobile-first approach
-- Get approval before proceeding
-
-❌ **Don't**:
-- Skip wireframing and jump to code
-- Ignore responsive considerations
-- Proceed without user approval
-- Over-complicate initial layout
-
-### Theme Stage
-
-✅ **Do**:
-- Reference design system context files
-- Use CSS custom properties
-- Save theme to separate file
-- Consider accessibility (contrast ratios)
-- Avoid Bootstrap blue unless requested
-
-❌ **Don't**:
-- Hardcode colors in HTML
-- Use generic/overused color schemes
-- Skip contrast testing
-- Mix color formats (stick to OKLCH)
-
-### Animation Stage
-
-✅ **Do**:
-- Use micro-syntax for documentation
-- Keep animations under 400ms
-- Use transform/opacity for performance
-- Respect prefers-reduced-motion
-- Make animations purposeful
-
-❌ **Don't**:
-- Animate width/height (use scale)
-- Create distracting animations
-- Ignore performance implications
-- Skip accessibility considerations
-
-### Implementation Stage
-
-✅ **Do**:
-- Use single HTML file per design
-- Load Tailwind via script tag
-- Reference theme CSS file
-- Use !important for framework overrides
-- Test responsive behavior
-- Provide alt text for images
-- Use semantic HTML
-
-❌ **Don't**:
-- Split into multiple files
-- Load Tailwind as stylesheet
-- Inline all styles
-- Skip accessibility attributes
-- Use made-up image URLs
-- Use div soup (non-semantic HTML)
-
----
-
-## File Management
-
-### Folder Structure
-
-```
-design_iterations/
-├── theme_1.css
-├── theme_2.css
-├── landing_1.html
-├── landing_1_1.html
-├── landing_1_2.html
-├── dashboard_1.html
-├── dashboard_1_1.html
-└── README.md (optional: design notes)
-```
-
-### Version Control
-
-**Track iterations**:
-- Initial: `design_1.html`
-- Iteration 1: `design_1_1.html`
-- Iteration 2: `design_1_2.html`
-- Iteration 3: `design_1_3.html`
-
-**New major version**:
-- Complete redesign: `design_2.html`
-- Then iterate: `design_2_1.html`, `design_2_2.html`
-
----
-
-## Communication Patterns
-
-### Stage Transitions
-
-**After Layout**:
-```
-"Here's the proposed layout structure. The design uses a [description].
-Would you like to proceed with this layout, or should we make adjustments?"
-```
-
-**After Theme**:
-```
-"I've created a [style] theme with [key features]. The theme file is saved as theme_N.css.
-Does this match your vision, or would you like to adjust colors/typography?"
-```
-
-**After Animation**:
-```
-"Here's the animation plan using [timing/style]. All animations are optimized for performance.
-Are these animations appropriate, or should we adjust the timing/effects?"
-```
-
-**After Implementation**:
-```
-"I've created the complete design as {filename}.html. The design includes [key features].
-Please review and let me know if you'd like any changes or iterations."
-```
-
-### Iteration Requests
-
-**User requests change**:
-```
-"I'll update the design with [changes] and save it as {filename}_N.html.
-This preserves the previous version for reference."
-```
-
----
-
-## Handling Plan File Edits and Iterations
-
-### User Edits Plan File Directly
-
-**Scenario**: User opens `.tmp/design-plans/{name}.md` and makes changes
-
-**Process**:
-1. User edits plan file (changes requirements, adds constraints, modifies goals)
-2. User notifies agent: "I've updated the plan file"
-3. Agent reads updated plan file
-4. Agent identifies what changed
-5. Agent proposes how to incorporate changes
-6. Agent updates affected stages
-
-**Example**:
-```
-User: "I've updated the plan file - changed the color scheme to dark mode"
-
-Agent:
-✅ Read updated plan: .tmp/design-plans/saas-landing-page.md
-
-Changes detected:
-- Color scheme: Light → Dark mode
-- Primary color: Blue → Purple
-
-This affects:
-- Stage 2 (Theme) - needs regeneration
-- Stage 4 (Implementation) - needs CSS update
-
-Would you like me to:
-1. Regenerate theme with dark mode
-2. Update implementation with new theme
-```
-
-### Iteration Within a Stage
-
-**Scenario**: User requests changes during a stage
-
-**Process**:
-1. Agent presents stage output (e.g., layout wireframe)
-2. User requests changes: "Make the hero section taller"
-3. Agent updates plan file with feedback
-4. Agent makes changes
-5. Agent updates plan file with new iteration
-6. Agent presents updated output
-
-**Example**:
-```
-Stage 1 - Layout Design
-
-Agent: [presents wireframe]
-
-User: "Make the hero section taller and move CTA above the fold"
-
-Agent:
-✅ Updated plan file with feedback
-✅ Revised layout wireframe
-✅ Updated plan file with Iteration 2
-
-[presents updated wireframe]
-```
-
-### Tracking Iterations in Plan File
-
-**Format**:
-```markdown
-## Design Evolution
-
-### Iteration 1 - Initial Layout
-- Date: 2026-01-30T10:00:00Z
-- Stage: Layout
-- Changes: Initial wireframe created
-- User feedback: "Hero section too short, CTA below fold"
-
-### Iteration 2 - Revised Layout
-- Date: 2026-01-30T10:15:00Z
-- Stage: Layout
-- Changes: Increased hero height from 400px to 600px, moved CTA above fold
-- User feedback: "Perfect! Approved."
-- Status: ✅ Approved
-
-### Iteration 3 - Theme Adjustment
-- Date: 2026-01-30T10:30:00Z
-- Stage: Theme
-- Changes: Changed from light to dark mode, primary color blue → purple
-- User feedback: "Love the dark mode!"
-- Status: ✅ Approved
-```
-
-### Subagent Context Preservation
-
-**Problem**: Subagents lose context between calls
-
-**Solution**: Always pass plan file path
-
-**Pattern**:
-```javascript
-// When delegating to subagent
-task(
- subagent_type="OpenFrontendSpecialist",
- description="Implement Stage 4",
- prompt="Load design plan from .tmp/design-plans/saas-landing-page.md
-
- Read the plan file for:
- - All approved decisions from Stages 1-3
- - User requirements and constraints
- - Design evolution and iterations
-
- Implement Stage 4 (Implementation) following all approved decisions.
-
- Update the plan file with:
- - Output file paths
- - Implementation status
- - Any issues encountered"
-)
-```
-
-### Plan File as Single Source of Truth
-
-**Benefits**:
-- ✅ All design decisions in one place
-- ✅ User can review and edit anytime
-- ✅ Subagents have full context
-- ✅ Design history preserved
-- ✅ Easy to iterate and refine
-- ✅ No context loss between stages
-
-**Best Practices**:
-- Always read plan file at start of each stage
-- Update plan file after every user interaction
-- Track all iterations with timestamps
-- Document user feedback verbatim
-- Mark approved decisions clearly
-- Pass plan file path to all subagents
-
----
-
-## Quality Checklist
-
-Before presenting each stage:
-
-**Layout Stage**:
-- [ ] ASCII wireframe is clear and detailed
-- [ ] Components are well-organized
-- [ ] Responsive behavior is planned
-- [ ] User approval requested
-
-**Theme Stage**:
-- [ ] Theme file created and saved
-- [ ] Colors use OKLCH format
-- [ ] Fonts loaded from Google Fonts
-- [ ] Contrast ratios meet WCAG AA
-- [ ] User approval requested
-
-**Animation Stage**:
-- [ ] Animations documented in micro-syntax
-- [ ] Timing is appropriate (< 400ms)
-- [ ] Performance optimized (transform/opacity)
-- [ ] Accessibility considered
-- [ ] User approval requested
-
-**Implementation Stage**:
-- [ ] Single HTML file created
-- [ ] Theme CSS referenced
-- [ ] Tailwind loaded via script tag
-- [ ] Icons initialized
-- [ ] Responsive design tested
-- [ ] Accessibility attributes added
-- [ ] Images use valid placeholder URLs
-- [ ] Semantic HTML used
-- [ ] User review requested
-
----
-
-## Troubleshooting
-
-### Common Issues
-
-**Issue**: User wants to skip stages
-**Solution**: Explain benefits of structured approach, but accommodate if insisted
-
-**Issue**: Theme doesn't match user vision
-**Solution**: Iterate on theme file, create theme_2.css with adjustments
-
-**Issue**: Animations feel too slow/fast
-**Solution**: Adjust timing in micro-syntax, regenerate with new values
-
-**Issue**: Design doesn't work on mobile
-**Solution**: Review responsive breakpoints, add mobile-specific styles
-
-**Issue**: Colors have poor contrast
-**Solution**: Use WCAG contrast checker, adjust OKLCH lightness values
-
----
-
-## References
-
-- [Design Systems Context](../ui/web/design-systems.md)
-- [UI Styling Standards](../ui/web/ui-styling-standards.md)
-- [Animation Patterns](../ui/web/animation-patterns.md)
-- [Design Assets](../ui/web/design-assets.md)
-- [ASCII Art Generator](https://www.asciiart.eu/)
-- [WCAG Contrast Checker](https://webaim.org/resources/contrastchecker/)
diff --git a/.opencode/context/core/workflows/external-libraries-faq.md b/.opencode/context/core/workflows/external-libraries-faq.md
new file mode 100644
index 0000000..b4f5c43
--- /dev/null
+++ b/.opencode/context/core/workflows/external-libraries-faq.md
@@ -0,0 +1,165 @@
+
+# External Libraries: FAQ
+
+**Purpose**: Troubleshooting and common questions about ExternalScout
+
+---
+
+## When exactly should I use ExternalScout?
+
+**ALWAYS when working with external packages.**
+
+**Triggers:**
+- User mentions library
+- `import`/`require` statements
+- package.json deps
+- Build errors
+- First-time setup
+- Version upgrades
+
+**Rule**: If it's not in `.opencode/context/`, use ExternalScout.
+
+---
+
+## What if I already know the library?
+
+**DON'T rely on training data - it's outdated.**
+
+Example: You think "I know Next.js, I'll use pages/"
+Reality: Next.js 15 uses app/
+Result: Broken code ❌
+
+**Always fetch current docs, even if you "know" the library.**
+
+---
+
+## How do I know if something is external?
+
+**External:** npm/pip/gem/cargo packages | Third-party frameworks | ORMs | Auth libraries | UI libraries
+
+**NOT external:** Your project's code | Project utilities | Internal modules
+
+**Check:** Is it in `package.json` dependencies? → External → Use ExternalScout
+
+---
+
+## Can I use both ContextScout and ExternalScout?
+
+**YES! Use both for most features.**
+
+```javascript
+// 1. ContextScout: Project standards
+task(subagent_type="ContextScout", ...)
+
+// 2. ExternalScout: Library docs
+task(subagent_type="ExternalScout", ...)
+
+// 3. Combine: Implement using both
+```
+
+---
+
+## What if ExternalScout doesn't have the library?
+
+ExternalScout has two sources:
+1. **Context7 API** (primary): 50+ popular libraries
+2. **Official docs** (fallback): Any library with public docs
+
+If library not in Context7: Auto-fallback to official docs via webfetch.
+
+---
+
+## How do I write a good ExternalScout prompt?
+
+**Template:**
+```javascript
+task(
+ subagent_type="ExternalScout",
+ description="Fetch [Library] docs for [specific topic]",
+ prompt="Fetch current documentation for [Library]: [specific question]
+
+ Focus on:
+ - [What you need - be specific]
+ - [Related features/APIs]
+
+ Context: [What you're building]"
+)
+```
+
+**Good:** ✅ Specific | ✅ Focused (3-5 things) | ✅ Contextual
+**Bad:** ❌ Vague | ❌ Too broad | ❌ No context
+
+---
+
+## What if I get an error after using ExternalScout?
+
+**Process:**
+1. Read error message carefully
+2. ExternalScout again with specific error:
+```javascript
+task(
+ subagent_type="ExternalScout",
+ description="Fetch docs for error resolution",
+ prompt="Fetch [Library] docs: [error message]
+ Error: [paste actual error]
+ Focus on: Common causes | Solutions"
+)
+```
+3. Check install scripts (maybe setup incomplete)
+4. Verify versions (package.json vs docs)
+
+---
+
+## Do I need approval to use ExternalScout?
+
+**NO - ExternalScout is read-only, no approval required.**
+
+**Approval required:** ❌ Write code | ❌ Run commands | ❌ Install packages
+**No approval:** ✅ ContextScout | ✅ ExternalScout | ✅ Read files
+
+---
+
+## ContextScout vs ExternalScout?
+
+| Aspect | ContextScout | ExternalScout |
+|--------|--------------|---------------|
+| **Searches** | Internal project files | External documentation |
+| **Location** | `.opencode/context/` | Internet (Context7, docs) |
+| **Returns** | Project standards | Library APIs |
+| **Use for** | "How we do things here" | "How this library works" |
+| **Speed** | Fast (local) | Slower (network) |
+
+**Use both together for best results.**
+
+---
+
+## Quick Checklist
+
+Before implementing with external libraries:
+
+- [ ] Used ContextScout for project standards?
+- [ ] Checked for install scripts first?
+- [ ] Used ExternalScout for EACH external library?
+- [ ] Asked for installation steps?
+- [ ] Asked for current API patterns?
+- [ ] Read returned docs before coding?
+
+**All checked? → You're doing it right! ✅**
+
+---
+
+## Supported Libraries
+
+**See**: `.opencode/skill/context7/library-registry.md`
+
+**Categories:** Database/ORM | Auth | Frontend | Infrastructure | UI | State | Validation | Testing
+
+Not listed? ExternalScout can still fetch from official docs.
+
+---
+
+## Related
+
+- `external-libraries-workflow.md` - Core workflow
+- `external-libraries-scenarios.md` - Common scenarios
+- `.opencode/agent/subagents/core/externalscout.md` - ExternalScout agent
diff --git a/.opencode/context/core/workflows/external-libraries-scenarios.md b/.opencode/context/core/workflows/external-libraries-scenarios.md
new file mode 100644
index 0000000..6f906e9
--- /dev/null
+++ b/.opencode/context/core/workflows/external-libraries-scenarios.md
@@ -0,0 +1,130 @@
+
+# External Libraries: Common Scenarios
+
+**Purpose**: Real-world examples of using ExternalScout
+
+---
+
+## Scenario 1: New Build with External Packages
+
+**Example**: Next.js app with Drizzle + Better Auth
+
+**Process:**
+1. Check install scripts: `ls scripts/install/`
+2. Identify packages: Next.js, Drizzle ORM, Better Auth
+3. ExternalScout for each package
+4. Check requirements: PostgreSQL? Env vars?
+5. Verify version compatibility
+6. Implement following current docs
+7. Test integration points
+
+**ExternalScout calls:**
+```javascript
+// Drizzle ORM
+task(
+ subagent_type="ExternalScout",
+ description="Fetch Drizzle PostgreSQL setup",
+ prompt="Fetch Drizzle ORM docs: PostgreSQL setup w/ modular schemas
+ Focus on: Installation | DB connection | Schema patterns | Migrations
+ Context: Next.js commerce site w/ PostgreSQL"
+)
+
+// Next.js App Router
+task(
+ subagent_type="ExternalScout",
+ description="Fetch Next.js App Router docs",
+ prompt="Fetch Next.js docs: App Router w/ Server Actions
+ Focus on: Installation | Directory structure | Server Actions
+ Context: Commerce site w/ order processing"
+)
+```
+
+---
+
+## Scenario 2: Package Error During Build
+
+**Example**: `Error: Cannot find module 'drizzle-orm/pg-core'`
+
+**Process:**
+1. Identify package: Drizzle ORM
+2. ExternalScout: "Fetch Drizzle docs: PostgreSQL imports"
+3. Check current import patterns
+4. Verify package.json has correct deps
+5. Propose fix from current docs
+6. Request approval → Apply fix
+
+---
+
+## Scenario 3: First-Time Package Setup
+
+**Example**: Setting up TanStack Query in Next.js
+
+**Process:**
+1. Check install scripts
+2. ExternalScout: "Fetch TanStack Query docs: Next.js App Router setup"
+3. Get: Install steps | Peer deps | Config | Patterns
+4. If install script exists: Review → Run
+5. If no script: Follow docs for manual setup
+6. Implement → Test
+
+---
+
+## Scenario 4: Version Upgrade
+
+**Example**: Next.js 14 → 15
+
+**Process:**
+1. ExternalScout: "Fetch Next.js 15 docs: Breaking changes and migration"
+2. Review breaking changes
+3. Identify affected code
+4. Plan migration steps
+5. Request approval → Implement → Test
+
+---
+
+## Real-World Example: Auth Implementation
+
+**Task**: "Add authentication with Better Auth to Next.js commerce"
+
+```javascript
+// 1. ContextScout: Project standards
+task(
+ subagent_type="ContextScout",
+ description="Find auth standards",
+ prompt="Find context files: Auth patterns | Security standards"
+)
+// Returns: security-patterns.md, code-quality.md
+
+// 2. ExternalScout: Better Auth docs (MANDATORY)
+task(
+ subagent_type="ExternalScout",
+ description="Fetch Better Auth + Next.js docs",
+ prompt="Fetch Better Auth docs: Next.js App Router integration
+ Focus on: Installation | App Router setup | Drizzle adapter | Session mgmt
+ Context: Adding auth to Next.js commerce w/ Drizzle ORM"
+)
+// Returns: Installation | Integration patterns | Working examples
+
+// 3. Combine and implement
+// - Better Auth patterns (from ExternalScout)
+// - Security standards (from ContextScout)
+// = Secure, well-structured auth ✅
+```
+
+---
+
+## Error Handling Patterns
+
+| Error Type | Process |
+|------------|---------|
+| **Package Installation** | ExternalScout: installation docs → Verify package name/version → Check peer deps |
+| **Import/Module** | ExternalScout: import patterns → Check current API exports |
+| **API/Configuration** | ExternalScout: API docs → Check current signatures |
+| **Build Errors** | Identify package → ExternalScout: relevant docs → Check known issues |
+
+---
+
+## Related
+
+- `external-libraries-workflow.md` - Core workflow
+- `external-libraries-faq.md` - Troubleshooting FAQ
diff --git a/.opencode/context/core/workflows/external-libraries-workflow.md b/.opencode/context/core/workflows/external-libraries-workflow.md
new file mode 100644
index 0000000..3550402
--- /dev/null
+++ b/.opencode/context/core/workflows/external-libraries-workflow.md
@@ -0,0 +1,138 @@
+
+# Workflow: External Libraries
+
+**Purpose**: Fetch current documentation for external packages before implementation
+
+---
+
+## Quick Start
+
+**Golden Rule**: NEVER rely on training data for external libraries → ALWAYS fetch current docs
+
+**Process**: Detect package → Check install scripts → Use ExternalScout → Implement
+
+**When to use ExternalScout** (MANDATORY):
+- New builds with external packages
+- First-time package setup
+- Package/dependency errors
+- Version upgrades
+- ANY external library work
+
+---
+
+## Core Principle
+
+
+ Training data is OUTDATED for external libraries.
+ ALWAYS fetch current docs using ExternalScout before implementation.
+
+
+**Why:**
+- APIs change (new methods, deprecated features)
+- Configuration patterns evolve
+- Breaking changes happen frequently
+
+**Example:**
+```
+Training data (2023): Next.js 13 uses pages/ directory
+Current (2025): Next.js 15 uses app/ directory
+
+Training data = broken code ❌
+ExternalScout = working code ✅
+```
+
+---
+
+## Workflow Stages
+
+### 1. Detect External Package
+
+**Triggers**: User mentions library | package.json deps | import statements | build errors
+
+### 2. Check Install Scripts
+
+```bash
+ls scripts/install/ scripts/setup/ bin/install* setup.sh
+grep -r "postinstall\|preinstall" package.json
+```
+
+Read scripts if found: What does it do? Environment variables? Prerequisites?
+
+### 3. Fetch Current Documentation (MANDATORY)
+
+```javascript
+task(
+ subagent_type="ExternalScout",
+ description="Fetch [Library] docs for [topic]",
+ prompt="Fetch current documentation for [Library]: [specific question]
+
+ Focus on:
+ - Installation and setup steps
+ - [Specific feature/API needed]
+ - Required environment variables
+
+ Context: [What you're building]"
+)
+```
+
+### 4. Verify Compatibility
+
+Check: Version compatibility | Peer dependencies | Breaking changes
+
+### 5. Implement with Current Patterns
+
+- Use exact API signatures from docs
+- Follow current best practices
+- Use recommended config patterns
+
+### 6. Test Integration
+
+Verify: Package installs | Imports work | API calls match docs
+
+---
+
+## Decision Flow: ContextScout + ExternalScout
+
+```
+User Request: "Build Next.js commerce w/ Drizzle"
+ ↓
+STEP 1: ContextScout → Search internal context
+ ↓
+ Internal context found?
+ ↓
+ YES → Use internal NO → Is it external library?
+ ↓
+ YES → STEP 2: ExternalScout (MANDATORY)
+ ↓
+ STEP 3: Combine internal + external → Implement
+```
+
+| Scenario | ContextScout | ExternalScout |
+|----------|--------------|---------------|
+| Project coding standards | ✅ | ❌ |
+| External library setup | ❌ | ✅ MANDATORY |
+| Feature with external lib | ✅ standards | ✅ lib docs |
+
+---
+
+## Best Practices
+
+**Do ✅:**
+- Check install scripts first
+- Always fetch current docs
+- Verify versions match
+- Test integrations
+
+**Don't ❌:**
+- Assume APIs based on training data
+- Skip version checks
+- Ignore peer dependencies
+- Run install scripts blindly
+
+---
+
+## Related
+
+- `external-libraries-scenarios.md` - Common scenarios and examples
+- `external-libraries-faq.md` - Troubleshooting FAQ
+- `.opencode/agent/subagents/core/externalscout.md` - ExternalScout agent
diff --git a/.opencode/context/core/workflows/external-libraries.md b/.opencode/context/core/workflows/external-libraries.md
deleted file mode 100644
index bc09971..0000000
--- a/.opencode/context/core/workflows/external-libraries.md
+++ /dev/null
@@ -1,531 +0,0 @@
-
-# Workflow: External Libraries
-
-**Purpose**: Fetch current documentation for external packages before implementation
-
-**Last Updated**: 2026-01-28
-
----
-
-## Quick Start
-
-**Golden Rule**: NEVER rely on training data for external libraries → ALWAYS fetch current docs
-
-**Process**: Detect package → Check install scripts → Use ExternalScout → Implement
-
-**When to use ExternalScout** (MANDATORY):
-- New builds w/ external packages
-- First-time package setup
-- Package/dependency errors
-- Version upgrades
-- ANY external library work
-
----
-
-## Core Principle
-
-
- Training data is OUTDATED for external libraries.
- ALWAYS fetch current docs using ExternalScout before implementation.
-
-
-**Why**:
-- APIs change (new methods, deprecated features)
-- Configuration patterns evolve
-- Breaking changes happen frequently
-- Version-specific features differ
-
-**Example**:
-```
-Training data (2023): Next.js 13 uses pages/ directory
-Current (2025): Next.js 15 uses app/ directory (App Router)
-
-Training data = broken code ❌
-ExternalScout = working code ✅
-```
-
----
-
-## Workflow Stages
-
-### 1. Detect External Package
-
-**Triggers**: User mentions library | package.json deps | import statements | build errors | first-time setup
-
-**Action**: Identify which external packages involved
-
----
-
-### 2. Check Install Scripts (First-Time Builds)
-
-**Check for**:
-```bash
-# Look for install scripts
-ls scripts/install/ scripts/setup/ bin/install* setup.sh install.sh
-
-# Check package-specific requirements
-grep -r "postinstall\|preinstall" package.json
-```
-
-**Read scripts if found**:
-- What does it do?
-- Environment variables needed?
-- Prerequisites (database, services)?
-
-**Why**: Scripts may set up databases, generate files, configure services in specific order
-
----
-
-### 3. Fetch Current Documentation (MANDATORY)
-
-**Basic Pattern**:
-```javascript
-task(
- subagent_type="ExternalScout",
- description="Fetch [Library] docs for [topic]",
- prompt="Fetch current documentation for [Library]: [specific question]
-
- Focus on:
- - Installation and setup steps
- - [Specific feature/API needed]
- - [Integration requirements]
- - Required environment variables
- - Database/service setup
-
- Context: [What you're building]"
-)
-```
-
-**Real Examples**:
-
-**Drizzle ORM Setup**:
-```javascript
-task(
- subagent_type="ExternalScout",
- description="Fetch Drizzle PostgreSQL setup",
- prompt="Fetch Drizzle ORM docs: PostgreSQL setup w/ modular schemas
-
- Focus on: Installation | DB connection | Schema patterns | Migrations | TypeScript config
- Context: Next.js commerce site w/ PostgreSQL"
-)
-```
-
-**Next.js App Router**:
-```javascript
-task(
- subagent_type="ExternalScout",
- description="Fetch Next.js App Router docs",
- prompt="Fetch Next.js docs: App Router w/ Server Actions
-
- Focus on: Installation | Directory structure | Server Actions | Data fetching | Route handlers
- Context: Commerce site w/ order processing"
-)
-```
-
-**Better Auth Integration**:
-```javascript
-task(
- subagent_type="ExternalScout",
- description="Fetch Better Auth + Next.js integration",
- prompt="Fetch Better Auth docs: Next.js App Router integration w/ Drizzle
-
- Focus on: Installation | App Router setup | Drizzle adapter | Session mgmt | Route protection
- Context: Adding auth to Next.js commerce w/ Drizzle ORM"
-)
-```
-
-**ExternalScout Returns**:
-- Current, version-specific docs
-- Installation steps + required packages
-- Official API references
-- Working code examples
-- Current best practices
-- Integration patterns
-- Setup prerequisites
-- Environment variables
-- Common pitfalls + solutions
-
----
-
-### 4. Verify Compatibility
-
-**Check**: Version compatibility | Peer dependencies | Breaking changes | Platform requirements
-
-**If version mismatch**: Note in plan → Request approval for upgrade → Fetch docs for specific version
-
----
-
-### 5. Implement with Current Patterns
-
-**Apply docs**:
-- Use exact API signatures from docs
-- Follow current best practices
-- Use recommended config patterns
-- Implement error handling as documented
-
-**Don't**:
-- ❌ Assume API based on training data
-- ❌ Use deprecated patterns
-- ❌ Skip version-specific requirements
-- ❌ Ignore breaking changes
-
----
-
-### 6. Test Integration
-
-**Verify**: Package installs | Imports work | API calls match docs | Error handling | Integration w/ other packages
-
----
-
-## Decision Flow: ContextScout + ExternalScout
-
-```
-User Request: "Build Next.js commerce w/ Drizzle"
- ↓
-┌──────────────────────────────────────────────────┐
-│ STEP 1: ContextScout │
-│ → Search internal context (.opencode/context/) │
-│ → Find project standards, patterns, workflows │
-└──────────────────────────────────────────────────┘
- ↓
- Internal context found?
- ↓
- ┌───────────┴───────────┐
- YES NO
- │ │
- ↓ ↓
- Use internal Is it external library?
- context ↓
- ┌────────┴────────┐
- YES NO
- │ │
- ↓ ↓
- ┌─────────────────┐ Report: No context
- │ STEP 2: │ available
- │ ExternalScout │
- │ (MANDATORY) │
- └─────────────────┘
- ↓
- Fetch: Next.js docs
- Drizzle docs
- Integration patterns
- ↓
- ┌─────────────────────────┐
- │ STEP 3: Combine │
- │ Internal: Standards │
- │ External: Library docs │
- │ → Implement w/ both │
- └─────────────────────────┘
-```
-
-**When to Use Which**:
-
-| Scenario | ContextScout | ExternalScout | Both |
-|----------|--------------|---------------|------|
-| Project coding standards | ✅ | ❌ | ❌ |
-| External library setup | ❌ | ✅ MANDATORY | ❌ |
-| Project-specific patterns | ✅ | ❌ | ❌ |
-| External API usage | ❌ | ✅ MANDATORY | ❌ |
-| Feature w/ external lib | ✅ standards | ✅ lib docs | ✅ |
-| Package installation | ❌ | ✅ MANDATORY | ❌ |
-| Security patterns | ✅ | ❌ | ❌ |
-| External lib integration | ✅ project | ✅ lib docs | ✅ |
-
-**Key Principle**: ContextScout + ExternalScout = Complete Context
-- **ContextScout**: "How we do things in THIS project"
-- **ExternalScout**: "How to use THIS library (current version)"
-- **Combined**: "How to use THIS library following OUR standards"
-
----
-
-## Common Scenarios
-
-### Scenario 1: New Build w/ External Packages
-
-**Example**: Next.js app w/ Drizzle + Better Auth
-
-**Process**:
-1. Check install scripts: `ls scripts/install/ scripts/setup/`
-2. Identify packages: Next.js, Drizzle ORM, Better Auth
-3. ExternalScout for each: Installation | Setup | Integration
-4. Check requirements: PostgreSQL? Env vars? Services?
-5. Verify version compatibility
-6. Run install scripts (if exist) OR create setup from docs
-7. Implement following current docs
-8. Test integration points
-
----
-
-### Scenario 2: Package Error During Build
-
-**Example**: `Error: Cannot find module 'drizzle-orm/pg-core'`
-
-**Process**:
-1. Identify package: Drizzle ORM
-2. ExternalScout: "Fetch Drizzle docs: PostgreSQL setup and imports"
-3. Check current import patterns
-4. Verify package.json has correct deps
-5. Propose fix from current docs
-6. Request approval → Apply fix
-
----
-
-### Scenario 3: First-Time Package Setup
-
-**Example**: Setting up TanStack Query in Next.js
-
-**Process**:
-1. Check install scripts: `ls scripts/install/` | `grep -r "tanstack\|react-query" scripts/`
-2. ExternalScout: "Fetch TanStack Query docs: Installation, Next.js App Router setup w/ Server Components"
-3. Get: Install steps | Peer deps | Config files | Current patterns | Best practices
-4. If install script exists: Review → Run
-5. If no script: Follow docs for manual setup
-6. Implement → Test
-
----
-
-### Scenario 4: Version Upgrade
-
-**Example**: Next.js 14 → 15
-
-**Process**:
-1. ExternalScout: "Fetch Next.js 15 docs: Breaking changes and migration guide"
-2. Review breaking changes
-3. Identify affected code
-4. Plan migration steps
-5. Request approval → Implement → Test
-
----
-
-## Real-World Example: Auth Implementation
-
-**Task**: "Add authentication w/ Better Auth to Next.js commerce site"
-
-```javascript
-// 1. ContextScout: Project standards
-task(
- subagent_type="ContextScout",
- description="Find auth and security standards",
- prompt="Find context files: Auth patterns | Security standards | Code quality | Project structure"
-)
-// Returns: security-patterns.md, code-quality.md
-
-// 2. ExternalScout: Better Auth docs (MANDATORY)
-task(
- subagent_type="ExternalScout",
- description="Fetch Better Auth + Next.js docs",
- prompt="Fetch Better Auth docs: Next.js App Router integration
-
- Focus on: Installation | App Router setup | Session mgmt | Route protection | Drizzle adapter
- Context: Adding auth to Next.js commerce w/ Drizzle ORM"
-)
-// Returns: Current installation | Integration patterns | Drizzle config | Working examples
-
-// 3. Combine and implement
-// - Better Auth patterns (from ExternalScout)
-// - Security standards (from ContextScout)
-// - Code quality standards (from ContextScout)
-// = Secure, well-structured auth implementation ✅
-```
-
----
-
-## Error Handling
-
-| Error Type | Process |
-|------------|---------|
-| **Package Installation** | ExternalScout: installation docs → Verify package name/version → Check peer deps → Propose fix w/ current steps |
-| **Import/Module** | ExternalScout: import patterns → Check current API exports → Verify import paths → Propose fix w/ current imports |
-| **API/Configuration** | ExternalScout: API docs → Check current signatures → Verify config format → Propose fix w/ current patterns |
-| **Build Errors** | Identify package → ExternalScout: relevant docs → Check known issues/breaking changes → Propose fix from current docs |
-
----
-
-## Best Practices
-
-**Do** ✅:
-- Check install scripts first on new builds
-- Always fetch current docs before implementing
-- Use ExternalScout proactively on new builds
-- Verify versions match (package.json vs docs)
-- Check breaking changes when upgrading
-- Read install scripts before running
-- Check prerequisites (databases, services, env vars)
-- Test integrations between packages
-- Cache useful docs in `.opencode/context/development/frameworks/` if frequently used
-
-**Don't** ❌:
-- Assume APIs based on training data
-- Skip version checks (breaking changes are common)
-- Ignore peer dependencies
-- Mix patterns from different versions
-- Run install scripts blindly
-- Skip environment setup
-
----
-
-## Troubleshooting FAQ
-
-### "When exactly should I use ExternalScout?"
-
-**ALWAYS use ExternalScout when working with external packages.**
-
-**Triggers**: User mentions library | `import`/`require` statements | package.json deps | build errors | first-time setup | version upgrades
-
-**Rule**: If it's not in `.opencode/context/`, use ExternalScout.
-
----
-
-### "What if I already know the library?"
-
-**DON'T rely on training data - it's outdated.**
-
-Example: You think "I know Next.js, I'll use pages/" → Reality: Next.js 15 uses app/ → Result: Broken code ❌
-
-**Always fetch current docs, even if you "know" the library.**
-
----
-
-### "How do I know if something is external?"
-
-**External libraries**: npm/pip/gem/cargo packages | Third-party frameworks | ORMs/databases | Auth libraries | UI libraries
-
-**NOT external**: Your project's code | Project utilities | Internal modules
-
-**Check**: Is it in `package.json` dependencies? → External → Use ExternalScout
-
----
-
-### "Can I use both ContextScout and ExternalScout?"
-
-**YES! Use both for most features.**
-
-```javascript
-// 1. ContextScout: Project standards
-task(subagent_type="ContextScout", ...)
-// Returns: code-quality.md, security-patterns.md
-
-// 2. ExternalScout: Library docs
-task(subagent_type="ExternalScout", ...)
-// Returns: Current Next.js docs, Drizzle docs
-
-// 3. Combine: Implement using both
-```
-
----
-
-### "What if ExternalScout doesn't have the library?"
-
-**ExternalScout has two sources**:
-1. **Context7 API** (primary): 50+ popular libraries
-2. **Official docs** (fallback): Any library w/ public docs
-
-**If library not in Context7**: Auto-fallback to official docs via webfetch
-
-**You don't need to worry - ExternalScout handles it.**
-
----
-
-### "How do I write a good ExternalScout prompt?"
-
-**Template**:
-```javascript
-task(
- subagent_type="ExternalScout",
- description="Fetch [Library] docs for [specific topic]",
- prompt="Fetch current documentation for [Library]: [specific question]
-
- Focus on:
- - [What you need - be specific]
- - [Related features/APIs]
- - [Integration requirements]
-
- Context: [What you're building]"
-)
-```
-
-**Good prompts**: ✅ Specific | ✅ Focused (3-5 things) | ✅ Contextual | ✅ Current
-
-**Bad prompts**: ❌ Vague | ❌ Too broad | ❌ No context
-
----
-
-### "What if I get an error after using ExternalScout?"
-
-**Normal - errors happen. Process**:
-1. Read error message carefully
-2. ExternalScout again w/ specific error:
- ```javascript
- task(
- subagent_type="ExternalScout",
- description="Fetch docs for error resolution",
- prompt="Fetch [Library] docs: [error message]
-
- Error: [paste actual error]
- Focus on: Common causes | Solutions | Correct API usage"
- )
- ```
-3. Check install scripts (maybe setup incomplete)
-4. Verify versions (package.json vs docs)
-
----
-
-### "Do I need approval to use ExternalScout?"
-
-**NO - ExternalScout is read-only, no approval required.**
-
-**Approval required**: ❌ Write code | ❌ Run commands | ❌ Install packages
-
-**Approval NOT required**: ✅ ContextScout | ✅ ExternalScout | ✅ Read files | ✅ Search code
-
----
-
-### "ContextScout vs ExternalScout?"
-
-| Aspect | ContextScout | ExternalScout |
-|--------|--------------|---------------|
-| **Searches** | Internal project files | External documentation |
-| **Location** | `.opencode/context/` | Internet (Context7, official docs) |
-| **Returns** | Project standards, patterns | Library APIs, installation |
-| **Use for** | "How we do things here" | "How this library works" |
-| **Tools** | glob, read, grep | webfetch, Context7 API |
-| **Speed** | Fast (local) | Slower (network) |
-| **Currency** | Static (project docs) | Live (current docs) |
-
-**Use both together for best results.**
-
----
-
-### "Quick Checklist: Am I doing it right?"
-
-Before implementing w/ external libraries:
-
-- [ ] Used ContextScout for project standards?
-- [ ] Checked for install scripts first?
-- [ ] Used ExternalScout for EACH external library?
-- [ ] Asked for installation steps in ExternalScout prompt?
-- [ ] Asked for current API patterns and examples?
-- [ ] Specified what I'm building (context)?
-- [ ] Read returned docs before coding?
-- [ ] Following both project standards AND library docs?
-
-**All checked? → You're doing it right! ✅**
-
----
-
-## Supported Libraries
-
-**See**: `.opencode/skill/context7/library-registry.md`
-
-**Categories**: Database/ORM (Drizzle, Prisma) | Auth (Better Auth, NextAuth, Clerk) | Frontend (Next.js, React, TanStack) | Infrastructure (Cloudflare, AWS, Vercel) | UI (Shadcn/ui, Radix, Tailwind) | State (Zustand, Jotai) | Validation (Zod, React Hook Form) | Testing (Vitest, Playwright)
-
-**Not listed?** ExternalScout can still fetch from official docs via webfetch
-
----
-
-## References
-
-- **ExternalScout Agent**: `.opencode/agent/subagents/core/externalscout.md`
-- **Library Registry**: `.opencode/skill/context7/library-registry.md`
-- **ContextScout Agent**: `.opencode/agent/subagents/core/contextscout.md`
-- **Code Standards**: `.opencode/context/core/standards/code-quality.md`
diff --git a/.opencode/context/core/workflows/navigation.md b/.opencode/context/core/workflows/navigation.md
index e517b25..e2deb91 100644
--- a/.opencode/context/core/workflows/navigation.md
+++ b/.opencode/context/core/workflows/navigation.md
@@ -9,10 +9,23 @@
| File | Topic | Priority | Load When |
|------|-------|----------|-----------|
| `code-review.md` | Code review process | ⭐⭐⭐⭐ | Reviewing code |
-| `task-delegation.md` | Delegating to subagents | ⭐⭐⭐⭐ | Using task tool |
+| `task-delegation-basics.md` | Core delegation workflow | ⭐⭐⭐⭐ | Using task tool |
+| `task-delegation-specialists.md` | When to delegate to whom | ⭐⭐⭐⭐ | Choosing specialist |
+| `task-delegation-caching.md` | Context caching | ⭐⭐⭐ | Repeated tasks |
+| `external-libraries-workflow.md` | External library process | ⭐⭐⭐⭐ | External packages |
+| `external-libraries-scenarios.md` | Common scenarios | ⭐⭐⭐ | Examples needed |
+| `external-libraries-faq.md` | Troubleshooting | ⭐⭐⭐ | Errors/questions |
| `feature-breakdown.md` | Breaking down features | ⭐⭐⭐⭐ | 4+ files, complex tasks |
| `session-management.md` | Managing sessions | ⭐⭐⭐ | Session cleanup |
-| `design-iteration.md` | Design iteration process | ⭐⭐⭐ | Design work |
+| `design-iteration-overview.md` | Design workflow overview | ⭐⭐⭐⭐ | Starting design work |
+| `design-iteration-plan-file.md` | Design plan template | ⭐⭐⭐⭐ | Creating design plan |
+| `design-iteration-stage-layout.md` | Stage 1: Layout | ⭐⭐⭐ | Layout design |
+| `design-iteration-stage-theme.md` | Stage 2: Theme | ⭐⭐⭐ | Theme design |
+| `design-iteration-stage-animation.md` | Stage 3: Animation | ⭐⭐⭐ | Animation design |
+| `design-iteration-stage-implementation.md` | Stage 4: Implementation | ⭐⭐⭐ | Implementation |
+| `design-iteration-visual-content.md` | Visual content generation | ⭐⭐ | Image generation |
+| `design-iteration-best-practices.md` | Best practices & troubleshooting | ⭐⭐⭐ | Quality check |
+| `design-iteration-plan-iterations.md` | Plan file iterations | ⭐⭐⭐ | Managing iterations |
---
@@ -23,7 +36,12 @@
2. Depends on: `../standards/code-quality.md`, `../standards/security-patterns.md`
**For task delegation**:
-1. Load `task-delegation.md` (high)
+1. Load `task-delegation-basics.md` (high)
+2. Load `task-delegation-specialists.md` (when choosing agent)
+
+**For external libraries**:
+1. Load `external-libraries-workflow.md` (high)
+2. Reference `external-libraries-scenarios.md` for examples
**For complex features**:
1. Load `feature-breakdown.md` (high)
diff --git a/.opencode/context/core/workflows/task-delegation-basics.md b/.opencode/context/core/workflows/task-delegation-basics.md
new file mode 100644
index 0000000..7903d1d
--- /dev/null
+++ b/.opencode/context/core/workflows/task-delegation-basics.md
@@ -0,0 +1,138 @@
+
+# Delegation Context Template
+
+## Quick Reference
+
+**Process**: Discover → Propose → Approve → Init Session → Persist Context → Delegate → Cleanup
+
+**Location**: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/context.md`
+
+**Key Principle**: ContextScout discovers paths. The orchestrator persists them into context.md AFTER approval. Downstream agents read from context.md — no re-discovery.
+
+---
+
+## When to Create a Session
+
+Only create a session when:
+- User has **approved** the proposed approach (never before)
+- Task requires delegation to TaskManager or working agents
+- Task is complex enough to need shared context (4+ files, >60min)
+
+For simple tasks (1-3 files, direct execution): skip session creation entirely.
+
+---
+
+## The Flow
+
+```
+Stage 1: DISCOVER → ContextScout finds paths (read-only, nothing written)
+Stage 2: PROPOSE → Show user lightweight summary (nothing written)
+Stage 3: APPROVE → User says yes. NOW we can write.
+Stage 4: INIT → Create session dir + context.md (persist discovered paths here)
+Stage 5: DELEGATE → Pass session path to TaskManager / working agents
+Stage 6: CLEANUP → Ask user, then delete session dir
+```
+
+---
+
+## Template Structure
+
+**Location**: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/context.md`
+
+```markdown
+# Task Context: {Task Name}
+
+Session ID: {YYYY-MM-DD}-{task-slug}
+Created: {ISO timestamp}
+Status: in_progress
+
+## Current Request
+{What user asked for — verbatim or close paraphrase}
+
+## Context Files (Standards to Follow)
+Paths ContextScout discovered. Downstream agents load these for coding standards.
+- .opencode/context/core/standards/code-quality.md
+- {other paths}
+
+## Reference Files (Source Material)
+Project files relevant to the task — NOT standards.
+- {e.g. package.json}
+- {e.g. src/existing-module.ts}
+
+## External Context Fetched
+Live docs fetched via ExternalScout. Read-only cache.
+- `.tmp/external-context/{package}/{topic}.md` — {description}
+
+## Components
+- {Component 1} — {what it does}
+- {Component 2} — {what it does}
+
+## Constraints
+{Technical constraints, preferences, version requirements}
+
+## Exit Criteria
+- [ ] {specific completion condition}
+
+## Progress
+- [ ] Session initialized
+- [ ] Tasks created (if using TaskManager)
+- [ ] Implementation complete
+```
+
+---
+
+## Delegation Process
+
+**Step 1-3: Discover, Propose, Approve** (before any writes)
+- Call ContextScout, capture paths
+- Call ExternalScout if external libraries involved
+- Show user lightweight summary, wait for approval
+
+**Step 4: Init Session** (first writes, after approval)
+- Create `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/`
+- Write `context.md` with discovered paths
+
+**Step 5: Delegate**
+```javascript
+task(
+ subagent_type="TaskManager",
+ description="{brief}",
+ prompt="Load context from .tmp/sessions/{session-id}/context.md
+ {specific instructions}"
+)
+```
+
+**Step 6: Cleanup**
+- Ask user: "Task complete. Clean up session files?"
+- If approved: Delete session directory
+
+---
+
+## Semantic Rules for Task JSONs
+
+| Field | Contains | Example |
+|-------|----------|---------|
+| `context_files` | **Standards only** | `.opencode/context/core/standards/code-quality.md` |
+| `reference_files` | **Source material only** | `src/auth/service.ts` |
+| `external_context` | **External docs only** (read-only) | `.tmp/external-context/drizzle/schemas.md` |
+
+**Never mix them.** Standards vs source material vs external docs.
+
+---
+
+## What Downstream Agents Expect
+
+| Agent | Reads | Does |
+|-------|-------|------|
+| **TaskManager** | `context.md` (full) | Extracts files, creates subtask JSONs |
+| **CoderAgent** | subtask JSON | Loads standards, references source, implements |
+| **TestEngineer** | session path | Writes tests against same standards |
+| **CodeReviewer** | session path | Reviews against applied standards |
+
+---
+
+## Related
+
+- `task-delegation-specialists.md` - When to delegate to which specialist
+- `task-delegation-caching.md` - Context caching for repeated patterns
+- `../context-system/standards/mvi.md` - MVI principle
diff --git a/.opencode/context/core/workflows/task-delegation-caching.md b/.opencode/context/core/workflows/task-delegation-caching.md
new file mode 100644
index 0000000..a377520
--- /dev/null
+++ b/.opencode/context/core/workflows/task-delegation-caching.md
@@ -0,0 +1,143 @@
+
+# Context Caching for Delegation
+
+**Purpose**: Cache discovered context to avoid re-discovery overhead in repeated tasks
+
+---
+
+## When to Cache
+
+Cache context when:
+- Same task type appears multiple times in session
+- Same context files needed repeatedly
+- Multiple subtasks use identical standards
+- Parallel tasks need same context
+
+---
+
+## Cache Structure
+
+```
+.tmp/sessions/{session-id}/
+├── context.md (main session context)
+├── .cache/
+│ ├── test-coverage.md (cached from .opencode/context/)
+│ ├── code-quality.md
+│ └── code-review.md
+└── .manifest.json (tracks cache status)
+```
+
+---
+
+## Cache Manifest
+
+```json
+{
+ "session_id": "2026-01-28-parallel-tests",
+ "created_at": "2026-01-28T14:30:22Z",
+ "cache": {
+ "test-coverage.md": {
+ "source": ".opencode/context/core/standards/test-coverage.md",
+ "cached_at": "2026-01-28T14:30:25Z",
+ "used_by": ["subtask_01", "subtask_02"],
+ "status": "valid"
+ }
+ }
+}
+```
+
+---
+
+## Invalidation Rules
+
+**Cache is INVALID when:**
+- Source file modified (check timestamp)
+- Session older than 24 hours
+- Context file version changed
+- User explicitly requests refresh
+
+**Cache is VALID when:**
+- Source timestamp matches
+- Session less than 24 hours old
+- No version changes
+- Multiple tasks in same session
+
+---
+
+## Implementation Pattern
+
+```javascript
+// Before delegating to subagent
+IF cache exists AND cache is valid:
+ USE cached context file
+ SKIP re-reading from .opencode/context/
+ELSE:
+ READ from .opencode/context/
+ CACHE the file
+```
+
+---
+
+## Example: Parallel Tasks
+
+```javascript
+session_id = "2026-01-28-parallel-tests"
+
+// Task 1: Write component A (parallel)
+task(
+ subagent_type="CoderAgent",
+ description="Write component A",
+ prompt="Load context from .tmp/sessions/{session_id}/context.md
+ Use cached context if available at .cache/"
+)
+
+// Task 2: Write component B (parallel)
+task(
+ subagent_type="CoderAgent",
+ description="Write component B",
+ prompt="Load context from .tmp/sessions/{session_id}/context.md
+ Use cached context if available at .cache/"
+)
+
+// Result: Task 1 caches context, Task 2 uses cache (faster)
+```
+
+---
+
+## Cache Effectiveness
+
+Track metrics:
+```json
+{
+ "cache_stats": {
+ "total_reads": 15,
+ "cache_hits": 9,
+ "cache_misses": 6,
+ "hit_rate": "60%"
+ }
+}
+```
+
+---
+
+## Best Practices
+
+✅ **Do:**
+- Cache context for repeated task types
+- Validate cache before using
+- Invalidate when source changes
+- Monitor hit rate
+- Clean up cache with session
+
+❌ **Don't:**
+- Cache external context (always fetch fresh)
+- Cache for single-task sessions (overhead not worth it)
+- Ignore invalidation rules
+- Mix cached and fresh context in same task
+
+---
+
+## Related
+
+- `task-delegation-basics.md` - Core delegation workflow
+- `task-delegation-specialists.md` - When to delegate
diff --git a/.opencode/context/core/workflows/task-delegation-specialists.md b/.opencode/context/core/workflows/task-delegation-specialists.md
new file mode 100644
index 0000000..0645803
--- /dev/null
+++ b/.opencode/context/core/workflows/task-delegation-specialists.md
@@ -0,0 +1,130 @@
+
+# When to Delegate to Specialists
+
+**Purpose**: Guidance on when to delegate to specific specialist agents
+
+---
+
+## OpenFrontendSpecialist - UI/UX Design
+
+**✅ DELEGATE when:**
+- Creating new UI/UX designs (landing pages, dashboards)
+- Building design systems (components, themes, style guides)
+- Complex layouts requiring responsive design
+- Visual polish (animations, transitions, micro-interactions)
+- Brand-focused pages (marketing, product showcases)
+- Accessibility-critical UI
+
+**Delegation pattern:**
+```javascript
+task(
+ subagent_type="OpenFrontendSpecialist",
+ description="Design {feature} UI",
+ prompt="Load context from .tmp/sessions/{session-id}/context.md
+
+ Design {feature} following 4-stage workflow:
+ 1. Stage 0: Create design plan file (MANDATORY FIRST)
+ 2. Stage 1: Layout (ASCII wireframe)
+ 3. Stage 2: Theme (design system, colors)
+ 4. Stage 3: Animation (micro-interactions)
+ 5. Stage 4: Implementation (single HTML file)
+
+ Request approval between stages."
+)
+```
+
+**Why?** Follows structured 4-stage workflow with approval gates, produces polished UI.
+
+---
+
+## TestEngineer - Test Authoring
+
+**✅ DELEGATE when:**
+- Writing comprehensive test suites
+- TDD workflows (tests before implementation)
+- Complex test scenarios (edge cases, error handling)
+- Integration tests across multiple components
+
+**Delegation pattern:**
+```javascript
+task(
+ subagent_type="TestEngineer",
+ description="Write tests for {feature}",
+ prompt="Load context from .tmp/sessions/{session-id}/context.md
+
+ Write comprehensive tests for {feature}
+ Files to test: {file list}
+ Follow test coverage standards from context."
+)
+```
+
+---
+
+## CodeReviewer - Quality Assurance
+
+**✅ DELEGATE when:**
+- Reviewing complex implementations
+- Security-critical code review
+- Pre-merge quality checks
+- Architecture validation
+
+**Delegation pattern:**
+```javascript
+task(
+ subagent_type="CodeReviewer",
+ description="Review {feature}",
+ prompt="Load context from .tmp/sessions/{session-id}/context.md
+
+ Review {feature} against standards
+ Files: {file list}
+ Focus: security, performance, maintainability"
+)
+```
+
+---
+
+## CoderAgent - Focused Implementation
+
+**✅ DELEGATE when:**
+- Implementing atomic subtasks from TaskManager
+- Isolated feature work (single component/module)
+- Following specific implementation specs
+
+**Delegation pattern:**
+```javascript
+task(
+ subagent_type="CoderAgent",
+ description="Implement {subtask}",
+ prompt="Load context from .tmp/sessions/{session-id}/context.md
+
+ Implement subtask: {description}
+ Follow implementation spec exactly.
+ Mark subtask complete when done."
+)
+```
+
+---
+
+## Decision Matrix
+
+| Scenario | Agent | Why |
+|----------|-------|-----|
+| New landing page | OpenFrontendSpecialist | 4-stage design workflow |
+| Test suite for auth | TestEngineer | Comprehensive coverage |
+| Security review | CodeReviewer | Security focus |
+| Single API endpoint | CoderAgent | Focused implementation |
+| Complex multi-file feature | TaskManager → CoderAgent | Breakdown then implement |
+
+---
+
+## Key Principle
+
+**TestEngineer and CodeReviewer should ALWAYS receive session context path.** This ensures they review against the same standards used during implementation.
+
+---
+
+## Related
+
+- `task-delegation-basics.md` - Core delegation workflow
+- `task-delegation-caching.md` - Context caching
+- `design-iteration.md` - OpenFrontendSpecialist workflow
diff --git a/.opencode/context/core/workflows/task-delegation.md b/.opencode/context/core/workflows/task-delegation.md
deleted file mode 100644
index 4c3bac9..0000000
--- a/.opencode/context/core/workflows/task-delegation.md
+++ /dev/null
@@ -1,460 +0,0 @@
-
-# Delegation Context Template
-
-## Quick Reference
-
-**Process**: Discover → Propose → Approve → Init Session → Persist Context → Delegate → Cleanup
-
-**Location**: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/context.md`
-
-**Key Principle**: ContextScout discovers paths. The orchestrator persists them into context.md AFTER approval. Downstream agents read from context.md — no re-discovery.
-
----
-
-## When to Create a Session
-
-Only create a session when:
-- User has **approved** the proposed approach (never before)
-- Task requires delegation to TaskManager or working agents
-- Task is complex enough to need shared context (4+ files, >60min)
-
-For simple tasks (1-3 files, direct execution): skip session creation entirely.
-
----
-
-## The Flow
-
-```
-Stage 1: DISCOVER → ContextScout finds paths (read-only, nothing written)
-Stage 2: PROPOSE → Show user lightweight summary (nothing written)
-Stage 3: APPROVE → User says yes. NOW we can write.
-Stage 4: INIT → Create session dir + context.md (persist discovered paths here)
-Stage 5: DELEGATE → Pass session path to TaskManager / working agents
-Stage 6: CLEANUP → Ask user, then delete session dir
-```
-
----
-
-## Template Structure
-
-**Location**: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/context.md`
-
-```markdown
-# Task Context: {Task Name}
-
-Session ID: {YYYY-MM-DD}-{task-slug}
-Created: {ISO timestamp}
-Status: in_progress
-
-## Current Request
-{What user asked for — verbatim or close paraphrase}
-
-## Context Files (Standards to Follow)
-These are the paths ContextScout discovered. Downstream agents load these for coding standards, patterns, and conventions.
-- .opencode/context/core/standards/code-quality.md
-- .opencode/context/core/standards/test-coverage.md
-- {other paths discovered by ContextScout}
-
-## Reference Files (Source Material to Look At)
-These are project files relevant to the task — NOT standards. Downstream agents reference these to understand existing code, config, or structure.
-- {e.g. package.json}
-- {e.g. src/existing-module.ts}
-
-## External Context Fetched
-These are live documentation files fetched from external libraries via ExternalScout. Subagents should reference these instead of re-fetching.
-- `.tmp/external-context/{package-name}/{topic}.md` — {description}
-- `.tmp/external-context/{package-name}/{topic}.md` — {description}
-
-**Important**: These files are read-only and cached for reference. Do not modify them.
-
-## Components
-{The functional units identified during proposal}
-- {Component 1} — {what it does}
-- {Component 2} — {what it does}
-
-## Constraints
-{Technical constraints, preferences, compatibility notes, version requirements}
-
-## Exit Criteria
-- [ ] {specific, measurable completion condition}
-- [ ] {specific, measurable completion condition}
-
-## Progress
-- [ ] Session initialized
-- [ ] Tasks created (if using TaskManager)
-- [ ] Implementation complete
-- [ ] Tests passing
-- [ ] Handoff complete
-```
-
----
-
-## Delegation Process
-
-**Step 1: Discover** (before approval)
-- Call ContextScout. Capture the returned file paths.
-- Call ExternalScout if external libraries involved.
-- Do NOT write anything to disk.
-
-**Step 2: Propose** (before approval)
-- Show user a lightweight summary of approach + discovered context.
-- Do NOT create session or plan docs.
-
-**Step 3: Approve** (gate)
-- Wait for explicit user approval.
-- If rejected or redirected → back to Step 1.
-
-**Step 4: Init Session** (first writes, only after approval)
-- Create `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/`
-- Write `context.md` using the template above.
-- **CRITICAL**: Populate `## Context Files` with the paths ContextScout discovered in Step 1. This is the handoff point — if you skip this, downstream agents lose the context.
-- Populate `## Reference Files` with any project source files relevant to the task.
-
-**Step 5: Delegate with context path**
-```
-task(
- subagent_type="TaskManager", // or CoderAgent, TestEngineer, etc.
- description="{brief description}",
- prompt="Load context from .tmp/sessions/{session-id}/context.md
-
- Read the context file for full requirements, standards, and constraints.
- {specific instructions for this subagent}"
-)
-```
-
-**Step 6: Cleanup after completion**
-- Ask user: "Task complete. Clean up session files at `.tmp/sessions/{session-id}/`?"
-- If approved: Delete session directory.
-
----
-
-## Semantic Rules for Task JSONs
-
-When TaskManager creates subtask JSONs, it MUST follow these rules:
-
-| Field | Contains | Example |
-|-------|----------|---------|
-| `context_files` | **Standards only** — paths to coding conventions, patterns, security rules | `.opencode/context/core/standards/code-quality.md` |
-| `reference_files` | **Source material only** — project files to look at for existing patterns | `src/auth/existing-service.ts`, `package.json` |
-| `external_context` | **External docs only** — cached documentation from external libraries (read-only) | `.tmp/external-context/drizzle-orm/modular-schemas.md` |
-
-**Never mix them.** A downstream agent reading `context_files` expects "rules to follow." A downstream agent reading `reference_files` expects "files to understand." A downstream agent reading `external_context` expects "cached external docs to reference" (read-only). Mixing them causes confusion about what to follow vs. what to reference vs. what to read.
-
----
-
-## What Downstream Agents Expect
-
-| Agent | What it reads | What it does with it |
-|-------|---------------|---------------------|
-| **TaskManager** | `context.md` (full session) | Extracts context_files + reference_files + external_context, puts them into subtask JSONs |
-| **CoderAgent** | subtask JSON (`context_files` + `reference_files` + `external_context`) | Loads standards, references source files, reads external docs, implements |
-| **TestEngineer** | session `context.md` path (passed in prompt) | Knows what standards were applied, reads external context, writes tests accordingly |
-| **CodeReviewer** | session `context.md` path (passed in prompt) | Knows what standards were applied, reviews against them, considers external context |
-| **OpenFrontendSpecialist** | session `context.md` path (passed in prompt) | Follows 4-stage design workflow (Layout → Theme → Animation → Implementation) |
-
-**Key**:
-- TestEngineer and CodeReviewer should ALWAYS receive the session context path when delegated. This way they review against the same standards that were used during implementation — not whatever they independently discover.
-- All agents should read `external_context` files to understand external library patterns and requirements — this avoids re-fetching and ensures consistency.
-- **OpenFrontendSpecialist should be used for UI/UX design work** - See `workflows/design-iteration.md` for when to delegate vs execute directly.
-
----
-
-## When to Delegate to Specialists
-
-### OpenFrontendSpecialist - UI/UX Design
-
-**✅ STRONGLY RECOMMENDED to delegate when:**
-- Creating new UI/UX designs (landing pages, dashboards, app interfaces)
-- Building design systems (component libraries, themes, style guides)
-- Complex layouts requiring responsive design
-- Visual polish work (animations, transitions, micro-interactions)
-- Brand-focused pages (marketing, product showcases)
-- Accessibility-critical UI (forms, navigation, interactive components)
-
-**Delegation pattern:**
-```javascript
-task(
- subagent_type="OpenFrontendSpecialist",
- description="Design {feature} UI",
- prompt="Load context from .tmp/sessions/{session-id}/context.md
-
- Design a {feature} following the 4-stage workflow:
-
- CRITICAL: Create design plan file at .tmp/design-plans/{project}-{feature}.md BEFORE starting
-
- Stages:
- 1. Stage 0: Create design plan file (MANDATORY FIRST)
- 2. Stage 1: Layout (ASCII wireframe) → Update plan file
- 3. Stage 2: Theme (design system, colors) → Update plan file
- 4. Stage 3: Animation (micro-interactions) → Update plan file
- 5. Stage 4: Implementation (single HTML file) → Update plan file
-
- Requirements:
- - {requirement 1}
- - {requirement 2}
-
- Plan file preserves context across stages and allows user to review/edit.
- Request approval between each stage.
- Update plan file after each stage and user feedback."
-)
-```
-
-**Why delegate?**
-- Follows structured 4-stage design workflow with approval gates
-- Produces polished, accessible, production-ready UI
-- Handles responsive design, OKLCH colors, semantic HTML
-- Creates single-file HTML prototypes for quick iteration
-
-**See**: `workflows/design-iteration.md` for full workflow details
-
-### TestEngineer - Test Authoring
-
-**Delegate when:**
-- Writing comprehensive test suites
-- TDD workflows (tests before implementation)
-- Complex test scenarios (edge cases, error handling)
-- Integration tests across multiple components
-
-**Delegation pattern:**
-```javascript
-task(
- subagent_type="TestEngineer",
- description="Write tests for {feature}",
- prompt="Load context from .tmp/sessions/{session-id}/context.md
-
- Write comprehensive tests for {feature}
-
- Files to test:
- - {file 1}
- - {file 2}
-
- Follow test coverage standards from context."
-)
-```
-
-### CodeReviewer - Quality Assurance
-
-**Delegate when:**
-- Reviewing complex implementations
-- Security-critical code review
-- Pre-merge quality checks
-- Architecture validation
-
-**Delegation pattern:**
-```javascript
-task(
- subagent_type="CodeReviewer",
- description="Review {feature} implementation",
- prompt="Load context from .tmp/sessions/{session-id}/context.md
-
- Review {feature} implementation against standards
-
- Files to review:
- - {file 1}
- - {file 2}
-
- Focus on: security, performance, maintainability"
-)
-```
-
-### CoderAgent - Focused Implementation
-
-**Delegate when:**
-- Implementing atomic subtasks from TaskManager
-- Isolated feature work (single component/module)
-- Following specific implementation specs
-
-**Delegation pattern:**
-```javascript
-task(
- subagent_type="CoderAgent",
- description="Implement {subtask}",
- prompt="Load context from .tmp/sessions/{session-id}/context.md
-
- Implement subtask: {subtask description}
-
- Follow the implementation spec exactly.
- Mark subtask as complete when done."
-)
-```
-
----
-
-## Context Caching for Repeated Patterns
-
-For repeated task types (e.g., "write tests", "code review", "documentation"), cache discovered context to avoid re-discovery overhead.
-
-### When to Cache
-
-Cache context when:
-- Same task type appears multiple times in a session
-- Same context files are needed repeatedly
-- Multiple subtasks use identical standards
-- Parallel tasks need the same context
-
-### Cache Structure
-
-```
-.tmp/sessions/{session-id}/
-├── context.md (main session context)
-├── .cache/
-│ ├── test-coverage.md (cached from .opencode/context/core/standards/test-coverage.md)
-│ ├── code-quality.md (cached from .opencode/context/core/standards/code-quality.md)
-│ └── code-review.md (cached from .opencode/context/core/workflows/code-review.md)
-└── .manifest.json (tracks cache status)
-```
-
-### Cache Manifest
-
-```json
-{
- "session_id": "2026-01-28-parallel-tests",
- "created_at": "2026-01-28T14:30:22Z",
- "cache": {
- "test-coverage.md": {
- "source": ".opencode/context/core/standards/test-coverage.md",
- "cached_at": "2026-01-28T14:30:25Z",
- "used_by": ["subtask_01", "subtask_02", "subtask_03"],
- "status": "valid"
- },
- "code-quality.md": {
- "source": ".opencode/context/core/standards/code-quality.md",
- "cached_at": "2026-01-28T14:30:26Z",
- "used_by": ["subtask_01", "subtask_02"],
- "status": "valid"
- }
- }
-}
-```
-
-### Cache Invalidation Rules
-
-Cache is INVALID when:
-- Source file has been modified (check timestamp)
-- Session is older than 24 hours
-- Context file version has changed
-- User explicitly requests cache refresh
-
-Cache is VALID when:
-- Source file timestamp matches cached timestamp
-- Session is less than 24 hours old
-- No version changes detected
-- Multiple tasks in same session use same context
-
-### Implementation Pattern
-
-**Step 1: Check Cache**
-```javascript
-// Before delegating to subagent
-IF cache exists AND cache is valid:
- USE cached context file
- SKIP re-reading from .opencode/context/
-ELSE:
- READ from .opencode/context/
- CACHE the file
-```
-
-**Step 2: Cache Hit Example**
-```
-Session: 2026-01-28-parallel-tests
-
-Task 1: Write component A
- → Load test-coverage.md (CACHE MISS)
- → Cache it at .tmp/sessions/.../cache/test-coverage.md
-
-Task 2: Write component B
- → Load test-coverage.md (CACHE HIT)
- → Use cached version (faster)
-
-Task 3: Write tests
- → Load test-coverage.md (CACHE HIT)
- → Use cached version (faster)
-```
-
-**Step 3: Cache Miss Example**
-```
-Session: 2026-01-28-parallel-tests
-
-Task 1: Write code
- → Load code-quality.md (CACHE MISS)
- → Cache it
-
-Task 2: Code review
- → Load code-review.md (CACHE MISS)
- → Cache it
-
-Task 3: Write more code
- → Load code-quality.md (CACHE HIT)
- → Use cached version
-```
-
-### Benefits
-
-- **Faster execution**: Avoid re-reading same files
-- **Reduced I/O**: Fewer file system operations
-- **Better performance**: Especially for parallel tasks
-- **Consistent context**: All tasks use same version
-
-### Example: Parallel Tasks with Caching
-
-```javascript
-// Session initialization
-session_id = "2026-01-28-parallel-tests"
-context_cache = {}
-
-// Task 1: Write component A (parallel)
-task(
- subagent_type="CoderAgent",
- description="Write component A",
- prompt="Load context from .tmp/sessions/{session_id}/context.md
- Use cached context if available at .tmp/sessions/{session_id}/.cache/"
-)
-
-// Task 2: Write component B (parallel)
-task(
- subagent_type="CoderAgent",
- description="Write component B",
- prompt="Load context from .tmp/sessions/{session_id}/context.md
- Use cached context if available at .tmp/sessions/{session_id}/.cache/"
-)
-
-// Task 3: Write tests (depends on 1+2)
-task(
- subagent_type="TestEngineer",
- description="Write tests",
- prompt="Load context from .tmp/sessions/{session_id}/context.md
- Use cached context if available at .tmp/sessions/{session_id}/.cache/"
-)
-
-// Result: Tasks 1 & 2 cache context, Task 3 uses cache (faster)
-```
-
-### Monitoring Cache
-
-Track cache effectiveness:
-```json
-{
- "cache_stats": {
- "total_reads": 15,
- "cache_hits": 9,
- "cache_misses": 6,
- "hit_rate": "60%",
- "time_saved": "2.3 seconds"
- }
-}
-```
-
-### Best Practices
-
-✅ **Do**:
-- Cache context for repeated task types
-- Validate cache before using
-- Invalidate cache when source changes
-- Monitor cache hit rate
-- Clean up cache with session
-
-❌ **Don't**:
-- Cache external context (always fetch fresh)
-- Cache for single-task sessions (overhead not worth it)
-- Ignore cache invalidation rules
-- Mix cached and fresh context in same task
diff --git a/.opencode/context/openagents-repo/concepts/agent-skills.md b/.opencode/context/openagents-repo/concepts/agent-skills.md
new file mode 100644
index 0000000..a54bfdd
--- /dev/null
+++ b/.opencode/context/openagents-repo/concepts/agent-skills.md
@@ -0,0 +1,59 @@
+# Agent Skills
+
+**Purpose**: Markdown files that teach Claude how to do specific tasks
+
+---
+
+## Core Concept
+
+A Skill is a `SKILL.md` file that teaches Claude specialized knowledge. Skills are **model-invoked**: Claude automatically applies them based on task context matching the description field.
+
+---
+
+## Key Points
+
+- Skills live in `~/.claude/skills/` (personal) or `.claude/skills/` (project)
+- Each skill needs a directory with `SKILL.md` inside
+- Name and description in YAML frontmatter are required
+- Claude loads only name/description at startup (lazy loading)
+- Full content loaded when skill is activated
+
+---
+
+## Skill File Format
+
+```yaml
+---
+name: explaining-code
+description: Explains code with diagrams and analogies. Use when explaining how code works.
+---
+
+When explaining code, always include:
+1. **Start with an analogy**: Compare to everyday life
+2. **Draw a diagram**: ASCII art for flow/structure
+3. **Walk through the code**: Step-by-step
+4. **Highlight a gotcha**: Common mistakes
+```
+
+---
+
+## Where Skills Live
+
+| Location | Path | Scope |
+|----------|------|-------|
+| Enterprise | Managed settings | All org users |
+| Personal | `~/.claude/skills/` | You, all projects |
+| Project | `.claude/skills/` | Anyone in repo |
+| Plugin | Plugin's `skills/` | Plugin users |
+
+Priority: Enterprise > Personal > Project > Plugin
+
+---
+
+## Related
+
+- `../guides/creating-skills.md` - How to create skills
+- `../lookup/skill-metadata.md` - Metadata fields
+- `../lookup/skills-comparison.md` - Skills vs other options
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/concepts/compatibility-layer.md b/.opencode/context/openagents-repo/concepts/compatibility-layer.md
new file mode 100644
index 0000000..45e86b2
--- /dev/null
+++ b/.opencode/context/openagents-repo/concepts/compatibility-layer.md
@@ -0,0 +1,128 @@
+# Concept: Compatibility Layer Architecture
+
+**Purpose**: Enable bidirectional translation between OpenAgents Control and other AI coding tools
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Core Idea
+
+Adapter pattern that translates agent configurations between OAC format and tool-specific formats (Cursor IDE, Claude Code, Windsurf). Enables seamless migration and multi-tool workflows through bidirectional mapping with graceful degradation.
+
+---
+
+## Key Components
+
+**Adapters**: Tool-specific translation classes inheriting from BaseAdapter
+- ClaudeAdapter - Claude Code format (.claude/config.json + skills)
+- CursorAdapter - Cursor IDE format (.cursorrules single file)
+- WindsurfAdapter - Windsurf format (.windsurf/agents/)
+
+**Mappers**: Pure functions for feature translation
+- ToolMapper - Tool name mapping (bash → terminal, task → delegate)
+- PermissionMapper - Permission translation with degradation
+- ModelMapper - Model ID mapping with fallbacks
+- ContextMapper - Context file path translation
+
+**Core Services**:
+- AgentLoader - Parse OAC markdown files with YAML frontmatter
+- AdapterRegistry - Register and retrieve adapters
+- TranslationEngine - Orchestrate bidirectional conversion
+- CapabilityMatrix - Track feature parity across tools
+
+---
+
+## Architecture Pattern
+
+```
+OAC Agent File (.md)
+ ↓ (parse)
+AgentLoader → OpenAgent object (validated with Zod)
+ ↓ (convert)
+BaseAdapter.fromOAC()
+ ↓ (map features)
+Mappers (tools, permissions, models, contexts)
+ ↓ (output)
+Tool-specific config files
+```
+
+**Reverse direction**: Tool config → toOAC() → OpenAgent → OAC .md file
+
+---
+
+## Design Principles
+
+- **Bidirectional**: Convert OAC → Tool AND Tool → OAC where possible
+- **Graceful Degradation**: Map unsupported features to closest equivalent with warnings
+- **Pure Mappers**: All mapping functions are pure (no side effects)
+- **Template Method**: BaseAdapter defines algorithm, subclasses fill details
+- **Validation First**: Zod schemas validate before/after conversion
+
+---
+
+## Feature Parity
+
+| Feature | OAC | Claude | Cursor | Windsurf |
+|---------|-----|--------|--------|----------|
+| Multiple agents | ✅ | ✅ | ❌ Single | ✅ |
+| Granular permissions | ✅ | ⚠️ Simplified | ❌ | ⚠️ Partial |
+| Temperature | ✅ | ❌ | ⚠️ Partial | ⚠️ Partial |
+| Skills | ✅ | ✅ | ❌ | ⚠️ Partial |
+| Hooks | ✅ | ✅ | ❌ | ❌ |
+| Context files | ✅ | ✅ Skills | ✅ .cursorrules | ✅ |
+
+---
+
+## Quick Example
+
+```typescript
+// Load OAC agent
+const agent = await AgentLoader.loadAgent('.opencode/agent/openagent.md')
+
+// Get Claude adapter
+const adapter = AdapterRegistry.get('claude')
+
+// Convert to Claude format
+const result = await adapter.fromOAC(agent)
+
+// Outputs: .claude/config.json, .claude/skills/[...]
+console.log(result.configs) // Array of files to write
+console.log(result.warnings) // Feature degradation warnings
+```
+
+---
+
+## Common Challenges
+
+**Single-File Tools** (Cursor): Merge multiple OAC agents into one .cursorrules file
+**Permission Mapping**: OAC granular permissions → simplified tool permissions
+**Model IDs**: Different tools use different model identifiers (requires mapping table)
+**Context Paths**: OAC uses .opencode/context/, tools use various paths
+
+---
+
+## Implementation Status
+
+**Issue #141**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+
+**Progress**: 28.13% (9/32 subtasks)
+- ✅ Phase 1 (Foundation): 100% complete - 1,475 lines
+- ⬅️ Phase 2 (Adapters): 50% complete - 1,858 lines (implementations done, tests pending)
+- 📝 Phase 3 (Mappers): 0% - Pending
+- 📝 Phase 4 (CLI): 0% - Pending
+- 📝 Phase 5 (Documentation): 0% - Pending
+
+**Location**: `packages/compatibility-layer/src/`
+
+---
+
+## Reference
+
+- **Related**:
+ - examples/baseadapter-implementation.md
+ - guides/compatibility-layer-development.md
+ - lookup/compatibility-layer-adapters.md
+ - lookup/compatibility-layer-progress.md
+ - lookup/compatibility-learnings.md
+ - lookup/tool-feature-parity.md
diff --git a/.opencode/context/openagents-repo/concepts/hooks-system.md b/.opencode/context/openagents-repo/concepts/hooks-system.md
new file mode 100644
index 0000000..195cced
--- /dev/null
+++ b/.opencode/context/openagents-repo/concepts/hooks-system.md
@@ -0,0 +1,64 @@
+# Hooks System
+
+**Purpose**: User-defined shell commands that execute at Claude Code lifecycle points
+
+---
+
+## Core Concept
+
+Hooks provide **deterministic control** over Claude Code's behavior. Unlike prompting (which suggests), hooks **guarantee** certain actions happen at specific points in the workflow.
+
+---
+
+## Key Points
+
+- Hooks are shell commands triggered by events
+- Run automatically during agent loop with your credentials
+- Can block operations (exit code 2), allow silently, or provide feedback
+- Configure in `settings.json` or plugin's `hooks/hooks.json`
+- Matchers filter which tools/events trigger the hook
+
+---
+
+## Common Use Cases
+
+| Use Case | Event | Example |
+|----------|-------|---------|
+| Auto-format | `PostToolUse` | Run prettier after Edit/Write |
+| Notifications | `Notification` | Desktop alert when input needed |
+| File protection | `PreToolUse` | Block edits to .env files |
+| Logging | `PreToolUse` | Track all bash commands |
+| Feedback | `PostToolUse` | Lint check after code changes |
+
+---
+
+## Quick Example
+
+```json
+{
+ "hooks": {
+ "PreToolUse": [{
+ "matcher": "Bash",
+ "hooks": [{
+ "type": "command",
+ "command": "jq -r '.tool_input.command' >> ~/.claude/bash-log.txt"
+ }]
+ }]
+ }
+}
+```
+
+---
+
+## Security Warning
+
+Hooks run with your credentials. Malicious hooks can exfiltrate data. Always review hook implementations before adding them.
+
+---
+
+## Related
+
+- `../lookup/hook-events.md` - All hook events
+- `../examples/hooks/` - Hook examples
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/hooks
diff --git a/.opencode/context/openagents-repo/concepts/subagents-system.md b/.opencode/context/openagents-repo/concepts/subagents-system.md
new file mode 100644
index 0000000..38078b9
--- /dev/null
+++ b/.opencode/context/openagents-repo/concepts/subagents-system.md
@@ -0,0 +1,58 @@
+# Subagents System
+
+**Purpose**: Specialized AI assistants with own context window for task-specific workflows
+
+---
+
+## Core Concept
+
+Subagents are isolated AI assistants that handle specific tasks. Each runs in its own context with custom system prompt, specific tool access, and independent permissions. When Claude encounters a matching task, it delegates to the subagent.
+
+---
+
+## Key Benefits
+
+- **Preserve context**: Keep exploration/implementation out of main conversation
+- **Enforce constraints**: Limit which tools subagent can use
+- **Reuse configs**: User-level subagents work across projects
+- **Specialize behavior**: Focused prompts for specific domains
+- **Control costs**: Route to faster/cheaper models (Haiku)
+
+---
+
+## How Delegation Works
+
+1. Claude analyzes task description and subagent descriptions
+2. Matches task to appropriate subagent
+3. Delegates work to subagent's isolated context
+4. Subagent works independently
+5. Returns results to main conversation
+
+---
+
+## Subagent Locations
+
+| Location | Scope | Priority |
+|----------|-------|----------|
+| `--agents` CLI flag | Current session | 1 (highest) |
+| `.claude/agents/` | Current project | 2 |
+| `~/.claude/agents/` | All your projects | 3 |
+| Plugin's `agents/` | Plugin users | 4 (lowest) |
+
+---
+
+## Key Constraints
+
+- Subagents cannot spawn other subagents
+- Background subagents auto-deny permissions not pre-approved
+- MCP tools not available in background subagents
+
+---
+
+## Related
+
+- `../lookup/builtin-subagents.md` - Built-in subagents
+- `../guides/creating-subagents.md` - Creation guide
+- `../lookup/subagent-frontmatter.md` - Configuration
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/errors/skills-errors.md b/.opencode/context/openagents-repo/errors/skills-errors.md
new file mode 100644
index 0000000..699c017
--- /dev/null
+++ b/.opencode/context/openagents-repo/errors/skills-errors.md
@@ -0,0 +1,102 @@
+# Skills Troubleshooting
+
+**Purpose**: Common skill issues and solutions
+
+---
+
+## Skill Not Triggering
+
+**Symptom**: Claude doesn't use your skill when expected
+
+**Cause**: Description doesn't match user requests
+
+**Solution**: Write specific descriptions with trigger terms:
+```yaml
+# Bad
+description: Helps with documents
+
+# Good
+description: Extract text from PDF files, fill forms, merge documents. Use when working with PDFs, forms, or document extraction.
+```
+
+**Tip**: Include keywords users would naturally say
+
+---
+
+## Skill Doesn't Load
+
+**Check file path**:
+| Type | Correct Path |
+|------|--------------|
+| Personal | `~/.claude/skills/my-skill/SKILL.md` |
+| Project | `.claude/skills/my-skill/SKILL.md` |
+| Plugin | `skills/my-skill/SKILL.md` |
+
+**Check YAML syntax**:
+- First line must be `---` (no blank lines before)
+- End frontmatter with `---`
+- Use spaces, not tabs
+
+**Debug**: Run `claude --debug` to see loading errors
+
+---
+
+## Skill Has Errors
+
+**Dependencies not installed**:
+- List required packages in description
+- User must install before skill works
+
+**Script permissions**:
+```bash
+chmod +x scripts/*.py
+```
+
+**Path format**: Use forward slashes (Unix style)
+
+---
+
+## Multiple Skills Conflict
+
+**Symptom**: Claude uses wrong skill
+
+**Cause**: Similar descriptions
+
+**Solution**: Make descriptions distinct:
+```yaml
+# Skill 1
+description: Analyze sales data in Excel files and CRM exports
+
+# Skill 2
+description: Analyze log files and system metrics
+```
+
+---
+
+## Plugin Skills Not Appearing
+
+**Solution**: Clear cache and reinstall
+```bash
+rm -rf ~/.claude/plugins/cache
+# Restart Claude Code
+/plugin install plugin-name@marketplace
+```
+
+**Verify structure**:
+```
+my-plugin/
+├── .claude-plugin/
+│ └── plugin.json
+└── skills/
+ └── my-skill/
+ └── SKILL.md
+```
+
+---
+
+## Related
+
+- `../concepts/agent-skills.md` - Skills overview
+- `../guides/creating-skills.md` - Creation guide
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/examples/baseadapter-implementation.md b/.opencode/context/openagents-repo/examples/baseadapter-implementation.md
new file mode 100644
index 0000000..69b9395
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/baseadapter-implementation.md
@@ -0,0 +1,158 @@
+# Example: BaseAdapter Implementation Pattern
+
+**Purpose**: Template method pattern for AI tool adapters
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Core Pattern
+
+**Template Method**: BaseAdapter defines algorithm structure, subclasses implement tool-specific details
+
+---
+
+## BaseAdapter Structure
+
+```typescript
+export abstract class BaseAdapter {
+ abstract name: string
+ abstract displayName: string
+
+ // Must implement
+ abstract toOAC(source: string): Promise
+ abstract fromOAC(agent: OpenAgent): Promise
+ abstract getConfigPath(): string
+ abstract getCapabilities(): ToolCapabilities
+ abstract validateConversion(agent: OpenAgent): string[]
+
+ // Shared utilities
+ supportsFeature(feature: keyof ToolCapabilities): boolean
+ warn(message: string): void
+ createSuccessResult(configs, warnings): ConversionResult
+ safeParseJSON(content, filename): unknown | null
+ unsupportedFeatureWarning(feature, value): string
+ degradedFeatureWarning(feature, from, to): string
+}
+```
+
+---
+
+## Implementation Example
+
+```typescript
+export class WindsurfAdapter extends BaseAdapter {
+ name = 'windsurf'
+ displayName = 'Windsurf'
+
+ async toOAC(source: string): Promise {
+ const config = this.safeParseJSON(source, 'config.json')
+ return {
+ frontmatter: {
+ name: config.name,
+ model: this.mapWindsurfModelToOAC(config.model),
+ tools: this.parseWindsurfTools(config.tools),
+ temperature: this.mapCreativityToTemperature(config.creativity)
+ },
+ systemPrompt: config.systemPrompt,
+ contexts: []
+ }
+ }
+
+ async fromOAC(agent: OpenAgent): Promise {
+ const warnings: string[] = []
+
+ // Warn on unsupported features
+ if (agent.frontmatter.hooks) {
+ warnings.push(this.unsupportedFeatureWarning('hooks', 'lost'))
+ }
+
+ const config = {
+ name: agent.frontmatter.name,
+ model: this.mapOACModelToWindsurf(agent.frontmatter.model),
+ creativity: this.mapTemperatureToCreativity(agent.frontmatter.temperature)
+ }
+
+ return this.createSuccessResult([
+ { fileName: '.windsurf/config.json', content: JSON.stringify(config) }
+ ], warnings)
+ }
+
+ getCapabilities(): ToolCapabilities {
+ return {
+ supportsMultipleAgents: true,
+ supportsHooks: false,
+ supportsTemperature: true // via creativity
+ }
+ }
+}
+```
+
+---
+
+## Key Methods
+
+### toOAC()
+Parse tool format → OpenAgent object
+- Parse source (JSON/YAML)
+- Map fields
+- Validate with Zod
+- Return OpenAgent
+
+### fromOAC()
+Convert OpenAgent → tool format
+- Validate input
+- Map fields
+- Detect unsupported features → warnings
+- Generate config files
+
+### getCapabilities()
+Declare supported features (used for validation)
+
+---
+
+## Utility Usage
+
+```typescript
+// Safe parsing
+const config = this.safeParseJSON(content, 'config.json')
+
+// Feature checks
+if (this.supportsFeature('supportsTemperature')) {
+ config.temperature = agent.frontmatter.temperature
+}
+
+// Warnings
+if (!this.supportsFeature('supportsHooks')) {
+ warnings.push(this.unsupportedFeatureWarning('hooks'))
+}
+
+// Results
+return this.createSuccessResult([{ fileName, content }], warnings)
+```
+
+---
+
+## Design Principles
+
+1. **Template Method** - Base defines structure, subs fill details
+2. **Pure toOAC/fromOAC** - Deterministic conversion
+3. **Capabilities First** - Declare support upfront
+4. **Graceful Degradation** - Warn, don't fail
+5. **Validate Early** - Check before converting
+
+---
+
+## Reference
+
+**Implementation**: `packages/compatibility-layer/src/adapters/BaseAdapter.ts`
+
+**Concrete Adapters**:
+- ClaudeAdapter.ts (600 lines)
+- CursorAdapter.ts (554 lines)
+- WindsurfAdapter.ts (514 lines)
+
+**Related**:
+- concepts/compatibility-layer.md
+- guides/compatibility-layer-development.md
+- lookup/compatibility-layer-adapters.md
diff --git a/.opencode/context/openagents-repo/examples/baseadapter-pattern.md b/.opencode/context/openagents-repo/examples/baseadapter-pattern.md
new file mode 100644
index 0000000..8ca34cd
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/baseadapter-pattern.md
@@ -0,0 +1,194 @@
+# Example: BaseAdapter Implementation Pattern
+
+**Purpose**: Template Method pattern for AI coding tool adapters
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Core Pattern
+
+Template Method: BaseAdapter defines algorithm structure, subclasses implement tool-specific details.
+
+---
+
+## BaseAdapter Structure
+
+```typescript
+export abstract class BaseAdapter {
+ abstract name: string
+ abstract displayName: string
+
+ // Must implement
+ abstract toOAC(source: string): Promise
+ abstract fromOAC(agent: OpenAgent): Promise
+ abstract getConfigPath(): string
+ abstract getCapabilities(): ToolCapabilities
+ abstract validateConversion(agent: OpenAgent): string[]
+
+ // Shared utilities
+ supportsFeature(feature: keyof ToolCapabilities): boolean
+ warn(message: string): void
+ createSuccessResult(configs, warnings): ConversionResult
+ safeParseJSON(content, filename): unknown | null
+}
+```
+
+---
+
+## ClaudeAdapter Example
+
+```typescript
+export class ClaudeAdapter extends BaseAdapter {
+ name = 'claude'
+ displayName = 'Claude Code'
+
+ async toOAC(source: string): Promise {
+ const config = this.safeParseJSON(source, 'config.json')
+ return {
+ frontmatter: {
+ name: config.name,
+ mode: config.mode || 'primary',
+ model: this.mapModel(config.model),
+ tools: config.tools,
+ skills: config.skills?.map(s => ({ name: s }))
+ },
+ systemPrompt: config.systemPrompt || ''
+ }
+ }
+
+ async fromOAC(agent: OpenAgent): Promise {
+ const warnings: string[] = []
+
+ // Warn on unsupported features
+ if (agent.frontmatter.temperature) {
+ warnings.push(this.unsupportedFeatureWarning('temperature'))
+ }
+
+ const config = {
+ name: agent.frontmatter.name,
+ model: agent.frontmatter.model,
+ systemPrompt: agent.systemPrompt,
+ tools: agent.frontmatter.tools
+ }
+
+ return this.createSuccessResult([
+ { fileName: '.claude/config.json', content: JSON.stringify(config) }
+ ], warnings)
+ }
+
+ getConfigPath() { return '.claude/' }
+
+ getCapabilities(): ToolCapabilities {
+ return {
+ supportsMultipleAgents: true,
+ supportsSkills: true,
+ supportsHooks: true,
+ supportsTemperature: false
+ }
+ }
+
+ validateConversion(agent: OpenAgent): string[] {
+ return agent.frontmatter.name ? [] : ['Agent name required']
+ }
+}
+```
+
+---
+
+## Key Methods
+
+### toOAC()
+Parse tool format → OpenAgent object
+
+**Steps**: Parse source → Map fields → Validate with Zod → Return
+
+---
+
+### fromOAC()
+Convert OpenAgent → tool format
+
+**Steps**: Validate → Map fields → Detect unsupported features → Generate warnings → Create files
+
+---
+
+### getCapabilities()
+Declare supported features
+
+```typescript
+{
+ supportsMultipleAgents: boolean
+ supportsSkills: boolean
+ supportsHooks: boolean
+ supportsGranularPermissions: boolean
+ supportsTemperature: boolean
+}
+```
+
+---
+
+## Utility Usage
+
+```typescript
+// Safe parsing
+const config = this.safeParseJSON(content, 'config.json')
+
+// Feature checks
+if (this.supportsFeature('supportsTemperature')) {
+ config.temperature = agent.frontmatter.temperature
+}
+
+// Warnings
+if (!this.supportsFeature('supportsHooks')) {
+ warnings.push(this.unsupportedFeatureWarning('hooks'))
+}
+
+// Results
+return this.createSuccessResult([{ fileName: 'config.json', content }], warnings)
+```
+
+---
+
+## Design Principles
+
+1. **Template Method** - Base defines structure, subs fill details
+2. **Pure toOAC/fromOAC** - Deterministic conversion
+3. **Capabilities First** - Declare support upfront
+4. **Graceful Degradation** - Warn, don't fail
+5. **Validate Early** - Check before converting
+
+---
+
+## Testing Pattern
+
+```typescript
+describe('ClaudeAdapter', () => {
+ it('converts OAC to Claude', async () => {
+ const agent: OpenAgent = { /* ... */ }
+ const result = await adapter.fromOAC(agent)
+
+ expect(result.success).toBe(true)
+ expect(result.configs[0].fileName).toBe('.claude/config.json')
+ })
+
+ it('warns on unsupported temperature', async () => {
+ const agent: OpenAgent = { frontmatter: { temperature: 0.7 } }
+ const result = await adapter.fromOAC(agent)
+
+ expect(result.warnings).toContainEqual(
+ expect.stringContaining('temperature')
+ )
+ })
+})
+```
+
+---
+
+## Reference
+
+- **Implementation**: `packages/compatibility-layer/src/adapters/BaseAdapter.ts`
+- **Adapters**: `ClaudeAdapter.ts`, `CursorAdapter.ts`, `WindsurfAdapter.ts`
+- **Related**:
+ - concepts/compatibility-layer.md
+ - examples/zod-schema-migration.md
+ - guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/examples/hooks/formatting-hook.md b/.opencode/context/openagents-repo/examples/hooks/formatting-hook.md
new file mode 100644
index 0000000..2f0cbb4
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/hooks/formatting-hook.md
@@ -0,0 +1,58 @@
+# Code Formatting Hook
+
+**Purpose**: Auto-format files after Claude edits them
+
+---
+
+## TypeScript/Prettier Hook
+
+```json
+{
+ "hooks": {
+ "PostToolUse": [{
+ "matcher": "Edit|Write",
+ "hooks": [{
+ "type": "command",
+ "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }"
+ }]
+ }]
+ }
+}
+```
+
+---
+
+## How It Works
+
+1. Triggers after Edit or Write tool completes
+2. Extracts file path from JSON input via jq
+3. Checks if file is `.ts` extension
+4. Runs prettier on matching files
+
+---
+
+## Variations
+
+**Go files**:
+```bash
+if echo "$file_path" | grep -q '\\.go$'; then gofmt -w "$file_path"; fi
+```
+
+**Python files**:
+```bash
+if echo "$file_path" | grep -q '\\.py$'; then black "$file_path"; fi
+```
+
+**Multiple extensions**:
+```bash
+if echo "$file_path" | grep -qE '\\.(ts|tsx|js|jsx)$'; then npx prettier --write "$file_path"; fi
+```
+
+---
+
+## Related
+
+- `../../lookup/hook-events.md` - Hook events reference
+- `./protection-hook.md` - File protection example
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/hooks
diff --git a/.opencode/context/openagents-repo/examples/hooks/markdown-formatter.md b/.opencode/context/openagents-repo/examples/hooks/markdown-formatter.md
new file mode 100644
index 0000000..3178e7e
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/hooks/markdown-formatter.md
@@ -0,0 +1,85 @@
+# Markdown Formatter Hook
+
+**Purpose**: Auto-fix markdown formatting issues after file edits
+
+---
+
+## Configuration
+
+```json
+{
+ "hooks": {
+ "PostToolUse": [{
+ "matcher": "Edit|Write",
+ "hooks": [{
+ "type": "command",
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"
+ }]
+ }]
+ }
+}
+```
+
+---
+
+## Python Script
+
+```python
+#!/usr/bin/env python3
+import json, sys, re, os
+
+def detect_language(code):
+ s = code.strip()
+ if re.search(r'^\s*[{\[]', s):
+ try:
+ json.loads(s)
+ return 'json'
+ except: pass
+ if re.search(r'^\s*def\s+\w+\s*\(', s, re.M):
+ return 'python'
+ if re.search(r'\b(function\s+\w+|const\s+\w+\s*=)', s):
+ return 'javascript'
+ if re.search(r'\b(SELECT|INSERT|UPDATE)\s+', s, re.I):
+ return 'sql'
+ return 'text'
+
+def format_markdown(content):
+ def add_lang(match):
+ indent, info, body, closing = match.groups()
+ if not info.strip():
+ return f"{indent}```{detect_language(body)}\n{body}{closing}\n"
+ return match.group(0)
+
+ pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'
+ content = re.sub(pattern, add_lang, content)
+ return re.sub(r'\n{3,}', '\n\n', content).rstrip() + '\n'
+
+# Main
+data = json.load(sys.stdin)
+path = data.get('tool_input', {}).get('file_path', '')
+if path.endswith(('.md', '.mdx')) and os.path.exists(path):
+ with open(path, 'r') as f:
+ content = f.read()
+ formatted = format_markdown(content)
+ if formatted != content:
+ with open(path, 'w') as f:
+ f.write(formatted)
+ print(f"✓ Fixed formatting in {path}")
+```
+
+---
+
+## What It Fixes
+
+- Unlabeled code blocks → detects language
+- Excessive blank lines → max 2 consecutive
+- Trailing whitespace
+
+---
+
+## Related
+
+- `../../lookup/hook-events.md` - Hook events
+- `./formatting-hook.md` - Code formatting
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/hooks
diff --git a/.opencode/context/openagents-repo/examples/hooks/protection-hook.md b/.opencode/context/openagents-repo/examples/hooks/protection-hook.md
new file mode 100644
index 0000000..d5a1d4c
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/hooks/protection-hook.md
@@ -0,0 +1,72 @@
+# File Protection Hook
+
+**Purpose**: Block edits to sensitive files
+
+---
+
+## Configuration
+
+```json
+{
+ "hooks": {
+ "PreToolUse": [{
+ "matcher": "Edit|Write",
+ "hooks": [{
+ "type": "command",
+ "command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\""
+ }]
+ }]
+ }
+}
+```
+
+---
+
+## How It Works
+
+1. Triggers BEFORE Edit or Write tool executes
+2. Reads file path from JSON input
+3. Checks against protected patterns
+4. Exit code 2 blocks the operation
+5. Claude receives feedback about blocked file
+
+---
+
+## Protected Patterns
+
+Default patterns to block:
+- `.env` - Environment secrets
+- `package-lock.json` - Dependency locks
+- `.git/` - Git internals
+- `credentials.json` - API keys
+- `*.pem`, `*.key` - Private keys
+
+---
+
+## Custom Protection Script
+
+```python
+#!/usr/bin/env python3
+import json, sys
+
+PROTECTED = ['.env', 'package-lock.json', '.git/',
+ 'credentials', '.pem', '.key', 'secrets']
+
+data = json.load(sys.stdin)
+path = data.get('tool_input', {}).get('file_path', '')
+
+if any(p in path for p in PROTECTED):
+ print(f"Blocked: {path} is protected", file=sys.stderr)
+ sys.exit(2)
+
+sys.exit(0)
+```
+
+---
+
+## Related
+
+- `../../lookup/hook-events.md` - Hook events reference
+- `./formatting-hook.md` - Auto-format example
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/hooks
diff --git a/.opencode/context/openagents-repo/examples/skills/multi-file-skill.md b/.opencode/context/openagents-repo/examples/skills/multi-file-skill.md
new file mode 100644
index 0000000..adbce42
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/skills/multi-file-skill.md
@@ -0,0 +1,79 @@
+# Multi-File Skill Structure
+
+**Purpose**: Example of a skill with supporting files and scripts
+
+---
+
+## Directory Structure
+
+```
+pdf-processing/
+├── SKILL.md # Overview and quick start
+├── FORMS.md # Form field mappings
+├── REFERENCE.md # API details
+└── scripts/
+ ├── fill_form.py # Utility to populate forms
+ └── validate.py # Check PDFs for required fields
+```
+
+---
+
+## SKILL.md
+
+```yaml
+---
+name: pdf-processing
+description: Extract text, fill forms, merge PDFs. Use when working with PDF files.
+allowed-tools: Read, Bash(python:*)
+---
+
+# PDF Processing
+
+## Quick start
+
+Extract text:
+```python
+import pdfplumber
+with pdfplumber.open("doc.pdf") as pdf:
+ text = pdf.pages[0].extract_text()
+```
+
+For form filling, see [FORMS.md](FORMS.md).
+For detailed API reference, see [REFERENCE.md](REFERENCE.md).
+
+## Requirements
+
+```bash
+pip install pypdf pdfplumber
+```
+```
+
+---
+
+## Key Patterns
+
+1. **Keep SKILL.md concise** (<500 lines)
+2. **Reference supporting files** with markdown links
+3. **One level deep** - link directly, avoid nested references
+4. **Scripts are executed, not loaded** - saves context tokens
+
+---
+
+## Script Usage
+
+In SKILL.md, tell Claude to run (not read):
+```markdown
+Run the validation script:
+python scripts/validate.py input.pdf
+```
+
+Output goes to Claude, script content stays out of context.
+
+---
+
+## Related
+
+- `../concepts/agent-skills.md` - Skills overview
+- `../../lookup/skill-metadata.md` - Metadata fields
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/examples/subagents/code-reviewer.md b/.opencode/context/openagents-repo/examples/subagents/code-reviewer.md
new file mode 100644
index 0000000..645d49a
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/subagents/code-reviewer.md
@@ -0,0 +1,70 @@
+# Code Reviewer Subagent
+
+**Purpose**: Read-only subagent example for code review
+
+---
+
+## Configuration
+
+```yaml
+---
+name: code-reviewer
+description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
+tools: Read, Grep, Glob, Bash
+model: inherit
+---
+
+You are a senior code reviewer ensuring high standards of code quality and security.
+
+When invoked:
+1. Run git diff to see recent changes
+2. Focus on modified files
+3. Begin review immediately
+
+Review checklist:
+- Code is clear and readable
+- Functions and variables are well-named
+- No duplicated code
+- Proper error handling
+- No exposed secrets or API keys
+- Input validation implemented
+- Good test coverage
+- Performance considerations addressed
+
+Provide feedback organized by priority:
+- Critical issues (must fix)
+- Warnings (should fix)
+- Suggestions (consider improving)
+
+Include specific examples of how to fix issues.
+```
+
+---
+
+## Key Design Decisions
+
+| Decision | Rationale |
+|----------|-----------|
+| No Edit/Write | Read-only ensures review doesn't modify code |
+| `model: inherit` | Uses same model as main conversation |
+| Bash included | Allows running git diff |
+| Proactive description | Claude uses it automatically after code changes |
+
+---
+
+## Usage
+
+```
+Use the code-reviewer subagent to review my recent changes
+```
+
+Or Claude delegates automatically when code is modified.
+
+---
+
+## Related
+
+- `./debugger.md` - Debugger subagent (with edit access)
+- `../../guides/creating-subagents.md` - Creation guide
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/examples/subagents/db-validator.md b/.opencode/context/openagents-repo/examples/subagents/db-validator.md
new file mode 100644
index 0000000..a6f5125
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/subagents/db-validator.md
@@ -0,0 +1,84 @@
+# DB Query Validator Subagent
+
+**Purpose**: Subagent with conditional tool validation via hooks
+
+---
+
+## Configuration
+
+```yaml
+---
+name: db-reader
+description: Execute read-only database queries. Use when analyzing data or generating reports.
+tools: Bash
+hooks:
+ PreToolUse:
+ - matcher: "Bash"
+ hooks:
+ - type: command
+ command: "./scripts/validate-readonly-query.sh"
+---
+
+You are a database analyst with read-only access. Execute SELECT queries to answer questions about the data.
+
+When asked to analyze data:
+1. Identify which tables contain the relevant data
+2. Write efficient SELECT queries with appropriate filters
+3. Present results clearly with context
+
+You cannot modify data. If asked to INSERT, UPDATE, DELETE, or modify schema, explain that you only have read access.
+```
+
+---
+
+## Validation Script
+
+```bash
+#!/bin/bash
+# ./scripts/validate-readonly-query.sh
+
+INPUT=$(cat)
+COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
+
+if [ -z "$COMMAND" ]; then
+ exit 0
+fi
+
+# Block write operations (case-insensitive)
+if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE|REPLACE|MERGE)\b' > /dev/null; then
+ echo "Blocked: Write operations not allowed. Use SELECT queries only." >&2
+ exit 2
+fi
+
+exit 0
+```
+
+Make executable: `chmod +x ./scripts/validate-readonly-query.sh`
+
+---
+
+## How It Works
+
+1. Subagent has Bash access
+2. PreToolUse hook intercepts every Bash command
+3. Script checks for SQL write keywords
+4. Exit 2 blocks operation, stderr fed to Claude
+5. Exit 0 allows operation
+
+---
+
+## Key Pattern
+
+Use hooks for **conditional validation** when:
+- Tool access is needed but constrained
+- Rules are dynamic/complex
+- You need finer control than `tools` field provides
+
+---
+
+## Related
+
+- `../../lookup/hook-events.md` - Hook reference
+- `../../concepts/hooks-system.md` - Hooks overview
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/examples/subagents/debugger.md b/.opencode/context/openagents-repo/examples/subagents/debugger.md
new file mode 100644
index 0000000..0b19da6
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/subagents/debugger.md
@@ -0,0 +1,77 @@
+# Debugger Subagent
+
+**Purpose**: Full-access subagent for debugging and fixing issues
+
+---
+
+## Configuration
+
+```yaml
+---
+name: debugger
+description: Debugging specialist for errors, test failures, and unexpected behavior. Use proactively when encountering any issues.
+tools: Read, Edit, Bash, Grep, Glob
+---
+
+You are an expert debugger specializing in root cause analysis.
+
+When invoked:
+1. Capture error message and stack trace
+2. Identify reproduction steps
+3. Isolate the failure location
+4. Implement minimal fix
+5. Verify solution works
+
+Debugging process:
+- Analyze error messages and logs
+- Check recent code changes
+- Form and test hypotheses
+- Add strategic debug logging
+- Inspect variable states
+
+For each issue, provide:
+- Root cause explanation
+- Evidence supporting the diagnosis
+- Specific code fix
+- Testing approach
+- Prevention recommendations
+
+Focus on fixing the underlying issue, not the symptoms.
+```
+
+---
+
+## Key Design Decisions
+
+| Decision | Rationale |
+|----------|-----------|
+| Edit included | Debugger needs to fix code |
+| Model not specified | Defaults to Sonnet |
+| Proactive description | Auto-triggers on errors |
+
+---
+
+## Comparison with Code Reviewer
+
+| Aspect | Code Reviewer | Debugger |
+|--------|---------------|----------|
+| Edit access | ❌ No | ✓ Yes |
+| Purpose | Review quality | Fix issues |
+| Output | Feedback | Fixes |
+
+---
+
+## Usage
+
+```
+Use the debugger subagent to fix this failing test
+```
+
+---
+
+## Related
+
+- `./code-reviewer.md` - Read-only reviewer
+- `./db-validator.md` - Constrained access example
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/examples/zod-schema-migration.md b/.opencode/context/openagents-repo/examples/zod-schema-migration.md
new file mode 100644
index 0000000..41bf8e7
--- /dev/null
+++ b/.opencode/context/openagents-repo/examples/zod-schema-migration.md
@@ -0,0 +1,188 @@
+# Example: Zod Schema Migration Pattern
+
+**Purpose**: Migrate TypeScript definitions to runtime-validated Zod schemas
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Why Zod?
+
+**Benefits**: Runtime validation + single source of truth + better error messages
+
+---
+
+## Basic Pattern
+
+```typescript
+// BEFORE: .d.ts interface
+interface AgentFrontmatter {
+ name: string
+ mode: 'primary' | 'subagent' | 'all'
+ temperature?: number
+}
+
+// AFTER: Zod schema
+import { z } from 'zod'
+
+export const AgentFrontmatterSchema = z.object({
+ name: z.string(),
+ mode: z.enum(['primary', 'subagent', 'all']),
+ temperature: z.number().min(0).max(2).optional()
+})
+
+export type AgentFrontmatter = z.infer
+```
+
+---
+
+## Complex Schema
+
+```typescript
+// Granular permissions
+export const GranularPermissionSchema = z.object({
+ allow: z.array(z.string()).optional(),
+ deny: z.array(z.string()).optional(),
+ ask: z.array(z.string()).optional()
+})
+
+export const PermissionRuleSchema = z.union([
+ z.literal('allow'),
+ z.literal('deny'),
+ z.boolean(),
+ GranularPermissionSchema
+])
+
+export const ToolAccessSchema = z.object({
+ read: PermissionRuleSchema.optional(),
+ write: PermissionRuleSchema.optional(),
+ bash: PermissionRuleSchema.optional()
+})
+
+export type ToolAccess = z.infer
+```
+
+---
+
+## Validation Usage
+
+```typescript
+// Strict parsing (throws on invalid)
+const frontmatter = AgentFrontmatterSchema.parse(data)
+
+// Safe parsing (returns result object)
+const result = AgentFrontmatterSchema.safeParse(data)
+
+if (result.success) {
+ console.log(result.data) // Typed correctly
+} else {
+ console.error(result.error.errors) // Detailed errors
+}
+```
+
+---
+
+## Common Patterns
+
+### Enums
+```typescript
+export const ModeSchema = z.enum(['primary', 'subagent']).default('primary')
+```
+
+### Arrays
+```typescript
+export const SkillSchema = z.union([
+ z.string(), // "skill-name"
+ z.object({ name: z.string() }) // { name, config }
+])
+export const SkillsSchema = z.array(SkillSchema).optional()
+```
+
+### Records
+```typescript
+export const MetadataSchema = z.record(z.unknown()) // { [key: string]: any }
+```
+
+### Optionals
+```typescript
+z.string().optional() // string | undefined
+z.number().default(0.7) // number with default
+```
+
+---
+
+## Full Example: OpenAgent
+
+```typescript
+export const OpenAgentSchema = z.object({
+ frontmatter: AgentFrontmatterSchema,
+ metadata: AgentMetadataSchema.optional(),
+ systemPrompt: z.string(),
+ contexts: z.array(ContextReferenceSchema).optional()
+})
+
+export type OpenAgent = z.infer
+```
+
+---
+
+## Migration Checklist
+
+- [ ] `interface` → `z.object()`
+- [ ] `type X = Y | Z` → `z.union([...])`
+- [ ] `?` optional → `.optional()`
+- [ ] Add constraints (`.min()`, `.max()`)
+- [ ] Export schema AND type
+- [ ] Test with valid/invalid data
+
+---
+
+## Testing
+
+```typescript
+describe('AgentFrontmatterSchema', () => {
+ it('validates correct data', () => {
+ expect(() => AgentFrontmatterSchema.parse({
+ name: 'TestAgent',
+ mode: 'primary'
+ })).not.toThrow()
+ })
+
+ it('rejects invalid mode', () => {
+ expect(() => AgentFrontmatterSchema.parse({
+ name: 'Test',
+ mode: 'invalid'
+ })).toThrow()
+ })
+
+ it('rejects out-of-range temperature', () => {
+ expect(() => AgentFrontmatterSchema.parse({
+ name: 'Test',
+ temperature: 5.0 // Max 2.0
+ })).toThrow()
+ })
+})
+```
+
+---
+
+## Key Schemas (Compatibility Layer)
+
+**20+ schemas migrated**:
+- `OpenAgentSchema` - Complete agent
+- `AgentFrontmatterSchema` - YAML frontmatter
+- `ToolAccessSchema` - Tool permissions
+- `PermissionRuleSchema` - Permission types
+- `SkillReferenceSchema` - Skill definitions
+- `HookDefinitionSchema` - Hook configs
+
+---
+
+## Reference
+
+- **Implementation**: `packages/compatibility-layer/src/types.ts` (315 lines)
+- **Original**: `packages/compatibility-layer/dist/src/types.d.ts` (679 lines)
+- **Zod Docs**: https://zod.dev
+- **Related**:
+ - examples/baseadapter-pattern.md
+ - concepts/compatibility-layer.md
diff --git a/.opencode/context/openagents-repo/guides/adding-agent-basics.md b/.opencode/context/openagents-repo/guides/adding-agent-basics.md
new file mode 100644
index 0000000..bfdfd6c
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/adding-agent-basics.md
@@ -0,0 +1,154 @@
+# Guide: Adding a New Agent (Basics)
+
+**Prerequisites**: Load `core-concepts/agents.md` first
+**Purpose**: Create and register a new agent in 4 steps
+
+---
+
+## Overview
+
+Adding a new agent involves:
+1. Creating the agent file
+2. Creating test structure
+3. Updating the registry
+4. Validating everything works
+
+**Time**: ~15-20 minutes
+
+---
+
+## Step 1: Create Agent File
+
+### Choose Category
+
+```bash
+# Available categories:
+# - core/ (system agents)
+# - development/ (dev specialists)
+# - content/ (content creators)
+# - data/ (data analysts)
+# - product/ (product managers)
+# - learning/ (educators)
+```
+
+### Create File with Frontmatter
+
+```bash
+touch .opencode/agent/{category}/{agent-name}.md
+```
+
+```markdown
+---
+description: "Brief description of what this agent does"
+category: "{category}"
+type: "agent"
+tags: ["tag1", "tag2"]
+dependencies: []
+---
+
+# Agent Name
+
+**Purpose**: What this agent does
+
+## Focus
+- Key responsibility 1
+- Key responsibility 2
+
+## Workflow
+1. Step 1
+2. Step 2
+
+## Constraints
+- Constraint 1
+- Constraint 2
+```
+
+---
+
+## Step 2: Create Test Structure
+
+```bash
+# Create directories
+mkdir -p evals/agents/{category}/{agent-name}/{config,tests}
+
+# Create config
+cat > evals/agents/{category}/{agent-name}/config/config.yaml << 'EOF'
+agent: {category}/{agent-name}
+model: anthropic/claude-sonnet-4-5
+timeout: 60000
+suites:
+ - smoke
+EOF
+
+# Create smoke test
+cat > evals/agents/{category}/{agent-name}/tests/smoke-test.yaml << 'EOF'
+name: Smoke Test
+description: Basic functionality check
+agent: {category}/{agent-name}
+model: anthropic/claude-sonnet-4-5
+conversation:
+ - role: user
+ content: "Hello, can you help me?"
+expectations:
+ - type: no_violations
+EOF
+```
+
+---
+
+## Step 3: Update Registry
+
+```bash
+# Dry run first
+./scripts/registry/auto-detect-components.sh --dry-run
+
+# Add to registry
+./scripts/registry/auto-detect-components.sh --auto-add
+
+# Verify
+cat registry.json | jq '.components.agents[] | select(.id == "{agent-name}")'
+```
+
+---
+
+## Step 4: Validate
+
+```bash
+# Validate registry
+./scripts/registry/validate-registry.sh
+
+# Run smoke test
+cd evals/framework
+npm run eval:sdk -- --agent={category}/{agent-name} --pattern="smoke-test.yaml"
+
+# Test installation
+REGISTRY_URL="file://$(pwd)/registry.json" ./install.sh --list
+```
+
+---
+
+## Checklist
+
+- [ ] Agent file created with proper frontmatter
+- [ ] Test structure created (config + smoke test)
+- [ ] Registry updated via auto-detect
+- [ ] Registry validation passes
+- [ ] Smoke test passes
+- [ ] Agent appears in `./install.sh --list`
+
+---
+
+## Next Steps
+
+- **Add more tests** → `adding-agent-testing.md`
+- **Test thoroughly** → `testing-agent.md`
+- **Debug issues** → `debugging.md`
+
+---
+
+## Related
+
+- `core-concepts/agents.md` - Agent concepts
+- `adding-agent-testing.md` - Additional test patterns
+- `testing-agent.md` - Testing guide
+- `creating-subagents.md` - Claude Code subagents (different system)
diff --git a/.opencode/context/openagents-repo/guides/adding-agent-testing.md b/.opencode/context/openagents-repo/guides/adding-agent-testing.md
new file mode 100644
index 0000000..ff65078
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/adding-agent-testing.md
@@ -0,0 +1,143 @@
+# Guide: Adding Agent Tests
+
+**Prerequisites**: Load `adding-agent-basics.md` first
+**Purpose**: Additional test patterns for agents
+
+---
+
+## Additional Test Types
+
+### Approval Gate Test
+
+```yaml
+# evals/agents/{category}/{agent-name}/tests/approval-gate.yaml
+name: Approval Gate Test
+description: Verify agent requests approval before execution
+agent: {category}/{agent-name}
+model: anthropic/claude-sonnet-4-5
+conversation:
+ - role: user
+ content: "Create a new file called test.js"
+expectations:
+ - type: specific_evaluator
+ evaluator: approval_gate
+ should_pass: true
+```
+
+### Context Loading Test
+
+```yaml
+# evals/agents/{category}/{agent-name}/tests/context-loading.yaml
+name: Context Loading Test
+description: Verify agent loads required context
+agent: {category}/{agent-name}
+model: anthropic/claude-sonnet-4-5
+conversation:
+ - role: user
+ content: "Write a new function"
+expectations:
+ - type: context_loaded
+ contexts: ["core/standards/code-quality.md"]
+```
+
+---
+
+## Complete Example: API Specialist
+
+```bash
+# 1. Create agent file
+cat > .opencode/agent/development/api-specialist.md << 'EOF'
+---
+description: "Expert in REST and GraphQL API design"
+category: "development"
+type: "agent"
+tags: ["api", "rest", "graphql"]
+dependencies: ["subagent:tester"]
+---
+
+# API Specialist
+
+**Purpose**: Design and implement robust APIs
+
+## Focus
+- REST API design
+- GraphQL schemas
+- API documentation
+- Authentication/authorization
+
+## Workflow
+1. Analyze requirements
+2. Design API structure
+3. Implement endpoints
+4. Add tests
+5. Document API
+
+## Constraints
+- Follow REST best practices
+- Use proper HTTP methods
+- Include error handling
+- Add comprehensive tests
+EOF
+
+# 2. Create test structure
+mkdir -p evals/agents/development/api-specialist/{config,tests}
+
+cat > evals/agents/development/api-specialist/config/config.yaml << 'EOF'
+agent: development/api-specialist
+model: anthropic/claude-sonnet-4-5
+timeout: 60000
+suites:
+ - smoke
+EOF
+
+cat > evals/agents/development/api-specialist/tests/smoke-test.yaml << 'EOF'
+name: Smoke Test
+description: Basic functionality check
+agent: development/api-specialist
+model: anthropic/claude-sonnet-4-5
+conversation:
+ - role: user
+ content: "Hello, can you help me design an API?"
+expectations:
+ - type: no_violations
+EOF
+
+# 3. Update registry
+./scripts/registry/auto-detect-components.sh --auto-add
+
+# 4. Validate
+./scripts/registry/validate-registry.sh
+cd evals/framework && npm run eval:sdk -- --agent=development/api-specialist --pattern="smoke-test.yaml"
+```
+
+---
+
+## Common Issues
+
+| Problem | Solution |
+|---------|----------|
+| Auto-detect doesn't find agent | Check frontmatter is valid YAML |
+| Registry validation fails | Verify file path is correct |
+| Test fails unexpectedly | Load `debugging.md` for troubleshooting |
+
+---
+
+## Claude Code Subagent (Optional)
+
+For Claude Code-only helpers, create a project subagent:
+
+- **Path**: `.claude/agents/{subagent-name}.md`
+- **Required**: `name`, `description` frontmatter
+- **Optional**: `tools`, `disallowedTools`, `permissionMode`, `skills`, `hooks`
+- **Reload**: restart Claude Code or run `/agents`
+
+See `creating-subagents.md` for Claude Code subagent details.
+
+---
+
+## Related
+
+- `adding-agent-basics.md` - Basic agent creation
+- `testing-agent.md` - Testing guide
+- `debugging.md` - Troubleshooting
+- `creating-subagents.md` - Claude Code subagents
diff --git a/.opencode/context/openagents-repo/guides/adding-agent.md b/.opencode/context/openagents-repo/guides/adding-agent.md
deleted file mode 100644
index 85c6879..0000000
--- a/.opencode/context/openagents-repo/guides/adding-agent.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# Guide: Adding a New Agent
-
-**Prerequisites**: Load `core-concepts/agents.md` first
-**Purpose**: Step-by-step workflow for adding a new agent
-
----
-
-## Overview
-
-Adding a new agent involves:
-1. Creating the agent file
-2. Creating test structure
-3. Updating the registry
-4. Validating everything works
-
-**Time**: ~15-20 minutes
-
----
-
-## Step 1: Create Agent File
-
-### Choose Category
-
-```bash
-# Available categories:
-# - core/ (system agents)
-# - development/ (dev specialists)
-# - content/ (content creators)
-# - data/ (data analysts)
-# - product/ (product managers)
-# - learning/ (educators)
-```
-
-### Create File
-
-```bash
-# Create agent file
-touch .opencode/agent/{category}/{agent-name}.md
-```
-
-### Add Frontmatter and Content
-
-```markdown
----
-description: "Brief description of what this agent does"
-category: "{category}"
-type: "agent"
-tags: ["tag1", "tag2"]
-dependencies: []
----
-
-# Agent Name
-
-**Purpose**: What this agent does
-
-## Focus
-
-- Key responsibility 1
-- Key responsibility 2
-- Key responsibility 3
-
-## Workflow
-
-1. Step 1
-2. Step 2
-3. Step 3
-
-## Constraints
-
-- Constraint 1
-- Constraint 2
-```
-
----
-
-## Optional: Claude Code Subagent
-
-If you want a Claude Code-only helper for this repo, create a project subagent:
-
-- Path: `.claude/agents/{subagent-name}.md`
-- Required frontmatter: `name`, `description`
-- Optional: `tools`, `disallowedTools`, `permissionMode`, `skills`, `hooks`
-- Reload: restart Claude Code or run `/agents`
-
-For full Claude Code subagent details, see `../to-be-consumed/claude-code-docs/create-subagents.md`.
-
----
-
-## Step 2: Create Test Structure
-
-### Create Directories
-
-```bash
-mkdir -p evals/agents/{category}/{agent-name}/{config,tests}
-```
-
-### Create Config File
-
-```bash
-cat > evals/agents/{category}/{agent-name}/config/config.yaml << 'EOF'
-agent: {category}/{agent-name}
-model: anthropic/claude-sonnet-4-5
-timeout: 60000
-suites:
- - smoke
-EOF
-```
-
-### Create Smoke Test
-
-```bash
-cat > evals/agents/{category}/{agent-name}/tests/smoke-test.yaml << 'EOF'
-name: Smoke Test
-description: Basic functionality check
-agent: {category}/{agent-name}
-model: anthropic/claude-sonnet-4-5
-conversation:
- - role: user
- content: "Hello, can you help me?"
-expectations:
- - type: no_violations
-EOF
-```
-
----
-
-## Step 3: Update Registry
-
-### Auto-Detect
-
-```bash
-# Dry run first (see what would be added)
-./scripts/registry/auto-detect-components.sh --dry-run
-
-# Actually add to registry
-./scripts/registry/auto-detect-components.sh --auto-add
-```
-
-### Verify Registry Entry
-
-```bash
-# Check registry
-cat registry.json | jq '.components.agents[] | select(.id == "{agent-name}")'
-```
-
----
-
-## Step 4: Validate
-
-### Validate Registry
-
-```bash
-./scripts/registry/validate-registry.sh
-```
-
-### Run Smoke Test
-
-```bash
-cd evals/framework
-npm run eval:sdk -- --agent={category}/{agent-name} --pattern="smoke-test.yaml"
-```
-
-### Test Installation
-
-```bash
-# Test with local registry
-REGISTRY_URL="file://$(pwd)/registry.json" ./install.sh --list
-```
-
----
-
-## Step 5: Add Additional Tests (Optional)
-
-### Approval Gate Test
-
-```bash
-cat > evals/agents/{category}/{agent-name}/tests/approval-gate.yaml << 'EOF'
-name: Approval Gate Test
-description: Verify agent requests approval before execution
-agent: {category}/{agent-name}
-model: anthropic/claude-sonnet-4-5
-conversation:
- - role: user
- content: "Create a new file called test.js"
-expectations:
- - type: specific_evaluator
- evaluator: approval_gate
- should_pass: true
-EOF
-```
-
-### Context Loading Test
-
-```bash
-cat > evals/agents/{category}/{agent-name}/tests/context-loading.yaml << 'EOF'
-name: Context Loading Test
-description: Verify agent loads required context
-agent: {category}/{agent-name}
-model: anthropic/claude-sonnet-4-5
-conversation:
- - role: user
- content: "Write a new function"
-expectations:
- - type: context_loaded
- contexts: ["core/standards/code-quality.md"]
-EOF
-```
-
----
-
-## Complete Example
-
-### Example: Adding `api-specialist`
-
-```bash
-# 1. Create agent file
-cat > .opencode/agent/development/api-specialist.md << 'EOF'
----
-description: "Expert in REST and GraphQL API design"
-category: "development"
-type: "agent"
-tags: ["api", "rest", "graphql"]
-dependencies: ["subagent:tester"]
----
-
-# API Specialist
-
-**Purpose**: Design and implement robust APIs
-
-## Focus
-- REST API design
-- GraphQL schemas
-- API documentation
-- Authentication/authorization
-
-## Workflow
-1. Analyze requirements
-2. Design API structure
-3. Implement endpoints
-4. Add tests
-5. Document API
-
-## Constraints
-- Follow REST best practices
-- Use proper HTTP methods
-- Include error handling
-- Add comprehensive tests
-EOF
-
-# 2. Create test structure
-mkdir -p evals/agents/development/api-specialist/{config,tests}
-
-cat > evals/agents/development/api-specialist/config/config.yaml << 'EOF'
-agent: development/api-specialist
-model: anthropic/claude-sonnet-4-5
-timeout: 60000
-suites:
- - smoke
-EOF
-
-cat > evals/agents/development/api-specialist/tests/smoke-test.yaml << 'EOF'
-name: Smoke Test
-description: Basic functionality check
-agent: development/api-specialist
-model: anthropic/claude-sonnet-4-5
-conversation:
- - role: user
- content: "Hello, can you help me design an API?"
-expectations:
- - type: no_violations
-EOF
-
-# 3. Update registry
-./scripts/registry/auto-detect-components.sh --auto-add
-
-# 4. Validate
-./scripts/registry/validate-registry.sh
-cd evals/framework && npm run eval:sdk -- --agent=development/api-specialist --pattern="smoke-test.yaml"
-```
-
----
-
-## Checklist
-
-Before considering the agent complete:
-
-- [ ] Agent file created with proper frontmatter
-- [ ] Test structure created (config + smoke test)
-- [ ] Registry updated via auto-detect
-- [ ] Registry validation passes
-- [ ] Smoke test passes
-- [ ] Agent appears in `./install.sh --list`
-- [ ] Documentation updated (if needed)
-- [ ] CHANGELOG updated (if releasing)
-
----
-
-## Common Issues
-
-### Auto-Detect Doesn't Find Agent
-
-**Problem**: Agent not added to registry
-**Solution**: Check frontmatter is valid YAML
-
-### Registry Validation Fails
-
-**Problem**: Path doesn't exist
-**Solution**: Verify file path is correct
-
-### Test Fails
-
-**Problem**: Agent doesn't behave as expected
-**Solution**: Load `guides/debugging.md` for troubleshooting
-
----
-
-## Next Steps
-
-After adding agent:
-1. **Test thoroughly** → Load `guides/testing-agent.md`
-2. **Add more tests** → Approval gate, context loading, etc.
-3. **Update docs** → Add to README or docs/
-4. **Create PR** → Submit for review
-
----
-
-## Related Files
-
-- **Agent concepts**: `core-concepts/agents.md`
-- **Testing guide**: `guides/testing-agent.md`
-- **Registry guide**: `guides/updating-registry.md`
-- **Debugging**: `guides/debugging.md`
-- **Claude Code subagents**: `../to-be-consumed/claude-code-docs/create-subagents.md`
-
----
-
-**Last Updated**: 2026-01-13
-**Version**: 0.5.1
diff --git a/.opencode/context/openagents-repo/guides/adding-skill-basics.md b/.opencode/context/openagents-repo/guides/adding-skill-basics.md
new file mode 100644
index 0000000..2e147f0
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/adding-skill-basics.md
@@ -0,0 +1,147 @@
+# Guide: Adding an OpenCode Skill (Basics)
+
+**Prerequisites**: Load `plugins/context/capabilities/events_skills.md` first
+**Purpose**: Create an OpenCode skill directory and SKILL.md file
+
+**Note**: This is for **OpenCode skills** (internal system). For **Claude Code Skills**, see `creating-skills.md`.
+
+---
+
+## Overview
+
+Adding an OpenCode skill involves:
+1. Creating skill directory structure
+2. Creating SKILL.md file
+3. Creating router script (optional)
+4. Creating CLI implementation (optional)
+5. Registering in registry (optional)
+6. Testing
+
+**Time**: ~10-15 minutes
+
+---
+
+## Step 1: Create Skill Directory
+
+### Choose Skill Name
+
+- **kebab-case**: `task-management`, `brand-guidelines`
+- **Descriptive**: Clear indication of what skill provides
+- **Short**: Max 3-4 words
+
+### Create Structure
+
+```bash
+mkdir -p .opencode/skill/{skill-name}/scripts
+```
+
+**Standard structure**:
+```
+.opencode/skill/{skill-name}/
+├── SKILL.md # Required: Main skill documentation
+├── router.sh # Optional: CLI router script
+└── scripts/
+ └── skill-cli.ts # Optional: CLI tool implementation
+```
+
+---
+
+## Step 2: Create SKILL.md
+
+### Frontmatter
+
+```markdown
+---
+name: {skill-name}
+description: Brief description of what the skill provides
+---
+
+# Skill Name
+
+**Purpose**: What this skill helps users do
+
+## What I do
+
+- Feature 1
+- Feature 2
+- Feature 3
+
+## How to use me
+
+### Basic Commands
+
+```bash
+npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts command1
+```
+
+### Command Reference
+
+| Command | Description |
+|---------|-------------|
+| `command1` | What command1 does |
+| `command2` | What command2 does |
+```
+
+### Claude Code Skills (Optional)
+
+For Claude Code Skills (`.claude/skills/`), add extra frontmatter:
+- `allowed-tools` - Tool restrictions
+- `context` + `agent` - Run in forked subagent
+- `hooks` - Lifecycle events
+- `user-invocable` - Hide from slash menu
+
+See `creating-skills.md` for Claude Code Skills details.
+
+---
+
+## Step 3: Create Router Script (Optional)
+
+For CLI-based skills:
+
+```bash
+#!/bin/bash
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+if [ $# -eq 0 ]; then
+ echo "Usage: bash router.sh [options]"
+ exit 1
+fi
+
+COMMAND="$1"
+shift
+
+case "$COMMAND" in
+ help|--help|-h)
+ echo "{Skill Name} - Description"
+ echo "Commands: command1, command2, help"
+ ;;
+ command1|command2)
+ npx ts-node "$SCRIPT_DIR/scripts/skill-cli.ts" "$COMMAND" "$@"
+ ;;
+ *)
+ echo "Unknown command: $COMMAND"
+ exit 1
+ ;;
+esac
+```
+
+```bash
+chmod +x .opencode/skill/{skill-name}/router.sh
+```
+
+---
+
+## Next Steps
+
+- **CLI Implementation** → `adding-skill-implementation.md`
+- **Complete Example** → `adding-skill-example.md`
+- **Claude Code Skills** → `creating-skills.md`
+
+---
+
+## Related
+
+- `creating-skills.md` - Claude Code Skills (different system)
+- `adding-skill-implementation.md` - CLI and registry
+- `adding-skill-example.md` - Task-management example
+- `plugins/context/capabilities/events_skills.md` - Skills Plugin
diff --git a/.opencode/context/openagents-repo/guides/adding-skill-example.md b/.opencode/context/openagents-repo/guides/adding-skill-example.md
new file mode 100644
index 0000000..7a7ad0d
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/adding-skill-example.md
@@ -0,0 +1,167 @@
+# Example: Task-Management Skill
+
+**Purpose**: Complete example of creating an OpenCode skill
+
+---
+
+## Directory Structure
+
+```bash
+mkdir -p .opencode/skill/task-management/scripts
+```
+
+```
+.opencode/skill/task-management/
+├── SKILL.md
+├── router.sh
+└── scripts/
+ └── task-cli.ts
+```
+
+---
+
+## SKILL.md
+
+```markdown
+---
+name: task-management
+description: Task management CLI for tracking feature subtasks
+---
+
+# Task Management Skill
+
+**Purpose**: Track and manage feature subtasks
+
+## What I do
+
+- Track task progress
+- Show next eligible tasks
+- Identify blocked tasks
+- Mark completion
+- Validate task integrity
+
+## Usage
+
+```bash
+# Show all task statuses
+npx ts-node .opencode/skill/task-management/scripts/task-cli.ts status
+
+# Show next eligible tasks
+npx ts-node .opencode/skill/task-management/scripts/task-cli.ts next
+
+# Mark complete
+npx ts-node .opencode/skill/task-management/scripts/task-cli.ts complete "summary"
+```
+```
+
+---
+
+## router.sh
+
+```bash
+#!/bin/bash
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
+case "$1" in
+ help|--help|-h)
+ echo "Task Management Skill"
+ echo "Usage: bash router.sh "
+ echo "Commands: status, next, blocked, complete, validate"
+ ;;
+ status|next|blocked|validate)
+ npx ts-node "$SCRIPT_DIR/scripts/task-cli.ts" "$@"
+ ;;
+ complete)
+ npx ts-node "$SCRIPT_DIR/scripts/task-cli.ts" "$@"
+ ;;
+ *)
+ echo "Unknown command: $1"
+ bash "$0" help
+ ;;
+esac
+```
+
+---
+
+## task-cli.ts (Excerpt)
+
+```typescript
+#!/usr/bin/env ts-node
+
+interface Task {
+ id: string
+ status: 'pending' | 'in_progress' | 'completed'
+ title: string
+}
+
+async function main() {
+ const command = process.argv[2] || 'help'
+
+ switch (command) {
+ case 'status':
+ await showStatus()
+ break
+ case 'next':
+ await showNext()
+ break
+ case 'complete':
+ const [, , , feature, seq, summary] = process.argv
+ await markComplete(feature, seq, summary)
+ break
+ default:
+ showHelp()
+ }
+}
+
+async function showStatus() {
+ // Implementation
+ console.log('Task status...')
+}
+
+async function showNext() {
+ // Implementation
+ console.log('Next tasks...')
+}
+
+async function markComplete(feature: string, seq: string, summary: string) {
+ // Implementation
+ console.log(`Completing ${feature} ${seq}: ${summary}`)
+}
+
+function showHelp() {
+ console.log(`
+Task Management CLI
+
+Commands:
+ status Show all task statuses
+ next Show next eligible tasks
+ blocked Show blocked tasks
+ complete Mark task complete
+ validate Validate task integrity
+`)
+}
+
+main().catch(console.error)
+```
+
+---
+
+## Integration with Agents
+
+Skills integrate with agents via:
+- Event hooks (`tool.execute.before`, `tool.execute.after`)
+- Skill content injection into conversation
+- Output enhancement
+
+Example agent prompt invoking skill:
+```
+Use the task-management skill to show current task status
+```
+
+---
+
+## Related
+
+- `adding-skill-basics.md` - Directory and SKILL.md setup
+- `adding-skill-implementation.md` - CLI and registry
+- `plugins/context/capabilities/events_skills.md` - Skills Plugin
diff --git a/.opencode/context/openagents-repo/guides/adding-skill-implementation.md b/.opencode/context/openagents-repo/guides/adding-skill-implementation.md
new file mode 100644
index 0000000..c2792b5
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/adding-skill-implementation.md
@@ -0,0 +1,167 @@
+# Guide: OpenCode Skill Implementation
+
+**Prerequisites**: Load `adding-skill-basics.md` first
+**Purpose**: CLI implementation, registry, and testing for OpenCode skills
+
+---
+
+## CLI Implementation
+
+### Basic Structure
+
+```typescript
+#!/usr/bin/env ts-node
+// CLI implementation for {skill-name} skill
+
+interface Args {
+ command: string
+ [key: string]: any
+}
+
+async function main() {
+ const args = parseArgs()
+
+ switch (args.command) {
+ case 'command1':
+ await handleCommand1(args)
+ break
+ case 'command2':
+ await handleCommand2(args)
+ break
+ case 'help':
+ default:
+ showHelp()
+ }
+}
+
+function parseArgs(): Args {
+ const args = process.argv.slice(2)
+ return {
+ command: args[0] || 'help',
+ ...parseOptions(args.slice(1))
+ }
+}
+
+async function handleCommand1(args: Args) {
+ console.log('Running command1...')
+}
+
+function showHelp() {
+ console.log(`
+{Skill Name}
+
+Usage: npx ts-node scripts/skill-cli.ts [options]
+
+Commands:
+ command1 Description
+ command2 Description
+ help Show this help
+`)
+}
+
+main().catch(console.error)
+```
+
+---
+
+## Register in Registry (Optional)
+
+### Add to Components
+
+```json
+{
+ "skills": [
+ {
+ "id": "{skill-name}",
+ "name": "Skill Name",
+ "type": "skill",
+ "path": ".opencode/skill/{skill-name}/SKILL.md",
+ "description": "Brief description",
+ "tags": ["tag1", "tag2"],
+ "dependencies": []
+ }
+ ]
+}
+```
+
+### Add to Profiles
+
+```json
+{
+ "profiles": {
+ "essential": {
+ "components": [
+ "skill:{skill-name}"
+ ]
+ }
+ }
+}
+```
+
+---
+
+## Testing
+
+### Test CLI Commands
+
+```bash
+# Test help
+bash .opencode/skill/{skill-name}/router.sh help
+
+# Test commands
+bash .opencode/skill/{skill-name}/router.sh command1 --option value
+
+# Test with npx
+npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts help
+```
+
+### Test OpenCode Integration
+
+1. Call skill via OpenCode
+2. Verify event hooks fire correctly
+3. Check conversation history for skill content
+4. Verify output enhancement works
+
+---
+
+## Best Practices
+
+### Keep Skills Focused
+- ✅ Task management skill → Tracks tasks
+- ❌ Task management + code generation + testing → Too broad
+
+### Clear Documentation
+- Provide usage examples
+- Document all commands
+- Include expected outputs
+
+### Error Handling
+- Handle missing arguments gracefully
+- Provide helpful error messages
+- Validate inputs before processing
+
+### Performance
+- Use efficient algorithms
+- Cache when appropriate
+- Avoid unnecessary file operations
+
+---
+
+## Checklist
+
+- [ ] `.opencode/skill/{skill-name}/SKILL.md` created
+- [ ] `.opencode/skill/{skill-name}/router.sh` created (if CLI-based)
+- [ ] Router script is executable (`chmod +x`)
+- [ ] Registry updated (if needed)
+- [ ] Profile updated (if needed)
+- [ ] All commands tested
+- [ ] Documentation complete
+
+---
+
+## Related
+
+- `adding-skill-basics.md` - Directory and SKILL.md setup
+- `adding-skill-example.md` - Complete example
+- `creating-skills.md` - Claude Code Skills
+- `plugins/context/capabilities/events_skills.md` - Skills Plugin
diff --git a/.opencode/context/openagents-repo/guides/adding-skill.md b/.opencode/context/openagents-repo/guides/adding-skill.md
deleted file mode 100644
index 023e88e..0000000
--- a/.opencode/context/openagents-repo/guides/adding-skill.md
+++ /dev/null
@@ -1,456 +0,0 @@
-# Guide: Adding a New Skill
-
-**Prerequisites**: Load `plugins/context/capabilities/events_skills.md` first
-**Purpose**: Step-by-step workflow for adding a new skill
-**Related**: See `plugins/context/architecture/overview.md` for plugin concepts
-
----
-
-## Overview
-
-Adding a new skill involves:
-1. Creating the skill directory structure
-2. Creating the SKILL.md file
-3. Creating the router script (for CLI-based skills)
-4. Creating skill content/scripts
-5. Registering the skill (if needed)
-6. Testing the skill
-
-**Time**: ~10-15 minutes
-
----
-
-## Step 1: Create Skill Directory
-
-### Choose Skill Name
-
-Skill names should be:
-- **kebab-case**: `task-management`, `brand-guidelines`, `api-reference`
-- **Descriptive**: Clear indication of what the skill provides
-- **Short**: Avoid overly long names (max 3-4 words)
-
-### Create Directory Structure
-
-```bash
-# Create skill directory
-mkdir -p .opencode/skill/{skill-name}
-
-# Create subdirectories as needed
-mkdir -p .opencode/skill/{skill-name}/scripts
-```
-
-### Standard Skill Structure
-
-```
-.opencode/skill/{skill-name}/
-├── SKILL.md # Required: Main skill documentation
-├── router.sh # Optional: CLI router script
-└── scripts/
- └── skill-cli.ts # Optional: CLI tool implementation
-```
-
----
-
-## Step 2: Create SKILL.md
-
-The SKILL.md file is required and provides skill documentation and integration details.
-
-### Optional: Claude Code Skills
-
-Claude Code Skills live in `.claude/skills/{skill-name}/SKILL.md` and support extra frontmatter:
-- `allowed-tools` for tool restrictions
-- `context` + `agent` to run in a forked subagent
-- `hooks` for lifecycle events
-- `user-invocable` to hide from slash menu
-
-Keep descriptions keyword-rich so auto-discovery triggers reliably. See `../to-be-consumed/claude-code-docs/agent-skills.md` for full details.
-
-### SKILL.md Frontmatter
-
-```markdown
----
-name: {skill-name}
-description: Brief description of what the skill provides
----
-
-# Skill Name
-
-**Purpose**: What this skill helps users do
-
-## What I do
-
-- Feature 1
-- Feature 2
-- Feature 3
-
-## How to use me
-
-### Basic Commands
-
-```bash
-# Example command 1
-npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts command1
-
-# Example command 2
-npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts command2
-```
-
-### Command Reference
-
-| Command | Description |
-|---------|-------------|
-| `command1` | What command1 does |
-| `command2` | What command2 does |
-
-## Examples
-
-### Example 1
-
-```
-$ npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts example
-
-Expected output or behavior
-```
-
-## Architecture
-
-```
-.opencode/
-└── skill/
- └── {skill-name}/
- ├── SKILL.md # This file
- ├── router.sh # CLI router
- └── scripts/
- └── skill-cli.ts # CLI implementation
-```
-
-## Integration
-
-### With Agents
-
-This skill integrates with agents by:
-- Description of how agents use the skill
-- Example agent prompt that invokes the skill
-
-### With OpenCode Events
-
-The skill uses OpenCode event hooks:
-- `tool.execute.before` - For [what it does]
-- `tool.execute.after` - For [what it does]
-
----
-
-## Step 3: Create Router Script (Optional)
-
-For CLI-based skills, create a router.sh script:
-
-```bash
-#!/bin/bash
-# Router script for {skill-name} skill
-
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-
-# Default to help if no arguments
-if [ $# -eq 0 ]; then
- echo "Usage: bash router.sh [options]"
- echo "Run 'bash router.sh help' for more information"
- exit 1
-fi
-
-COMMAND="$1"
-shift
-
-# Route to appropriate script
-case "$COMMAND" in
- help|--help|-h)
- echo "{Skill Name} - Skill Description"
- echo ""
- echo "Usage: bash router.sh [options]"
- echo ""
- echo "Commands:"
- echo " command1 Description of command1"
- echo " command2 Description of command2"
- echo " help Show this help message"
- ;;
- command1)
- npx ts-node "$SCRIPT_DIR/scripts/skill-cli.ts" command1 "$@"
- ;;
- command2)
- npx ts-node "$SCRIPT_DIR/scripts/skill-cli.ts" command2 "$@"
- ;;
- *)
- echo "Unknown command: $COMMAND"
- echo "Run 'bash router.sh help' for available commands"
- exit 1
- ;;
-esac
-```
-
-### Make Router Executable
-
-```bash
-chmod +x .opencode/skill/{skill-name}/router.sh
-```
-
----
-
-## Step 4: Create CLI Implementation
-
-### Basic CLI Structure
-
-```typescript
-#!/usr/bin/env ts-node
-// CLI implementation for {skill-name} skill
-
-interface Args {
- command: string
- [key: string]: any
-}
-
-async function main() {
- const args = parseArgs()
-
- switch (args.command) {
- case 'command1':
- await handleCommand1(args)
- break
- case 'command2':
- await handleCommand2(args)
- break
- case 'help':
- default:
- showHelp()
- }
-}
-
-function parseArgs(): Args {
- const args = process.argv.slice(2)
- return {
- command: args[0] || 'help',
- ...parseOptions(args.slice(1))
- }
-}
-
-async function handleCommand1(args: Args) {
- // Implementation
- console.log('Running command1...')
-}
-
-function showHelp() {
- console.log(`
-{ Skill Name }
-
-Usage: npx ts-node scripts/skill-cli.ts [options]
-
-Commands:
- command1 Description of command1
- command2 Description of command2
- help Show this help message
-
-Options:
- --option1 Description of option1
- --option2 Description of option2
-`)
-}
-
-main().catch(console.error)
-```
-
----
-
-## Step 5: Register in Registry (Optional)
-
-If the skill should be included in profiles, add it to `registry.json`:
-
-### Add to Components
-
-```json
-{
- "skills": [
- {
- "id": "{skill-name}",
- "name": "Skill Name",
- "type": "skill",
- "path": ".opencode/skill/{skill-name}/SKILL.md",
- "description": "Brief description",
- "tags": ["tag1", "tag2"],
- "dependencies": []
- }
- ]
-}
-```
-
-### Add to Profiles
-
-```json
-{
- "profiles": {
- "essential": {
- "components": [
- "skill:{skill-name}"
- ]
- }
- }
-}
-```
-
----
-
-## Step 6: Test the Skill
-
-### Test CLI Commands
-
-```bash
-# Test help
-bash .opencode/skill/{skill-name}/router.sh help
-
-# Test command1
-bash .opencode/skill/{skill-name}/router.sh command1 --option value
-
-# Test with npx
-npx ts-node .opencode/skill/{skill-name}/scripts/skill-cli.ts help
-```
-
-### Test OpenCode Integration
-
-If the skill uses OpenCode events:
-1. Call the skill via OpenCode
-2. Verify event hooks fire correctly
-3. Check conversation history for skill content injection
-4. Verify output enhancement works
-
----
-
-## Example: Creating Task-Management Skill
-
-### Directory Creation
-
-```bash
-mkdir -p .opencode/skill/task-management/scripts
-```
-
-### SKILL.md
-
-```markdown
----
-name: task-management
-description: Task management CLI for tracking feature subtasks
----
-
-# Task Management Skill
-
-**Purpose**: Track and manage feature subtasks
-
-## What I do
-
-- Track task progress
-- Show next eligible tasks
-- Identify blocked tasks
-- Mark completion
-- Validate task integrity
-
-## Usage
-
-```bash
-# Show all task statuses
-npx ts-node .opencode/skill/task-management/scripts/task-cli.ts status
-
-# Show next eligible tasks
-npx ts-node .opencode/skill/task-management/scripts/task-cli.ts next
-
-# Mark complete
-npx ts-node .opencode/skill/task-management/scripts/task-cli.ts complete "summary"
-```
-
-### router.sh
-
-```bash
-#!/bin/bash
-SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
-
-case "$1" in
- help|--help|-h)
- echo "Task Management Skill"
- echo "Usage: bash router.sh "
- echo "Commands: status, next, blocked, complete, validate"
- ;;
- status|next|blocked|validate)
- npx ts-node "$SCRIPT_DIR/scripts/task-cli.ts" "$@"
- ;;
- complete)
- npx ts-node "$SCRIPT_DIR/scripts/task-cli.ts" "$@"
- ;;
- *)
- echo "Unknown command: $1"
- bash router.sh help
- ;;
-esac
-```
-
----
-
-## Best Practices
-
-### 1. Keep Skills Focused
-
-Each skill should do one thing well:
-- ✅ Task management skill → Tracks tasks
-- ❌ Task management + code generation + testing → Too broad
-
-### 2. Clear Documentation
-
-- Provide usage examples
-- Document all commands
-- Include expected outputs
-- Explain integration points
-
-### 3. Error Handling
-
-- Handle missing arguments gracefully
-- Provide helpful error messages
-- Validate inputs before processing
-
-### 4. Performance
-
-- Use efficient algorithms
-- Cache when appropriate
-- Avoid unnecessary file operations
-
-### 5. Testing
-
-- Test all commands
-- Test error conditions
-- Verify integration with OpenCode
-
----
-
-## Quick Reference
-
-### File Checklist
-
-- [ ] `.opencode/skill/{skill-name}/SKILL.md` created
-- [ ] `.opencode/skill/{skill-name}/router.sh` created (if CLI-based)
-- [ ] `.opencode/skill/{skill-name}/scripts/skill-cli.ts` created (if CLI-based)
-- [ ] Router script is executable (`chmod +x`)
-- [ ] Registry updated (if needed)
-- [ ] Profile updated (if needed)
-- [ ] All commands tested
-- [ ] Documentation complete
-
-### Command Reference
-
-| Command | Purpose |
-|---------|---------|
-| `mkdir -p .opencode/skill/{name}` | Create skill directory |
-| `chmod +x router.sh` | Make router executable |
-| `npx ts-node scripts/cli.ts help` | Test CLI help |
-
----
-
-## Related Documentation
-
-- **Skills Plugin**: `plugins/context/capabilities/events_skills.md`
-- **Plugin Overview**: `plugins/context/architecture/overview.md`
-- **Event System**: `plugins/context/capabilities/events.md`
-- **Adding Agents**: `guides/adding-agent.md`
-- **Claude Code Skills**: `../to-be-consumed/claude-code-docs/agent-skills.md`
diff --git a/.opencode/context/openagents-repo/guides/compatibility-layer-development.md b/.opencode/context/openagents-repo/guides/compatibility-layer-development.md
new file mode 100644
index 0000000..beb044c
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/compatibility-layer-development.md
@@ -0,0 +1,165 @@
+# Guide: Compatibility Layer Development Workflow
+
+**Purpose**: Step-by-step workflow for developing compatibility layer features
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Core Workflow
+
+Development follows a structured 6-stage approach:
+
+```
+1. Discover (Context)
+ ↓
+2. Propose (Approval)
+ ↓
+3. Init Session (Setup)
+ ↓
+4. Plan (Breakdown)
+ ↓
+5. Execute (Implement)
+ ↓
+6. Validate & Handoff
+```
+
+---
+
+## Stage 1: Discover
+
+**Goal**: Understand requirements before implementation
+
+**Actions**:
+1. Use `ContextScout` to discover relevant context files
+2. For external packages: Check install scripts first, then use `ExternalScout`
+3. Read context standards (code-quality.md is MANDATORY)
+
+**No files created** - Read-only exploration
+
+---
+
+## Stage 2: Propose
+
+**Goal**: Get user approval BEFORE any file creation
+
+**Present lightweight summary**:
+- What we're building (1-2 sentences)
+- Components list
+- Approach (direct execution vs delegation)
+- Context discovered
+
+**Wait for user approval** before proceeding
+
+---
+
+## Stage 3: Init Session
+
+**Goal**: Create session and persist context (ONLY after approval)
+
+**Actions**:
+1. Create session directory: `.tmp/sessions/{YYYY-MM-DD}-{task-slug}/`
+2. Write `context.md` with:
+ - Task requirements
+ - Context files (standards to follow)
+ - Reference files (source material)
+ - Components
+ - Exit criteria
+
+**This becomes single source of truth for all agents**
+
+---
+
+## Stage 4: Plan
+
+**Decision**: Simple vs Complex?
+
+**Simple** (1-3 files, <30min):
+- Skip TaskManager
+- Execute directly in Stage 5
+
+**Complex** (4+ files, >60min):
+- Delegate to `TaskManager`
+- Creates `.tmp/tasks/{feature}/task.json` + subtasks
+- Present plan for confirmation
+
+---
+
+## Stage 5: Execute
+
+**Incremental approach** (one component at a time):
+
+1. **Plan Component** (if using component-planning):
+ - Create `component-{name}.md`
+ - Request approval for design
+
+2. **Execute**:
+ - Load context from session
+ - Implement → Validate → Mark complete
+ - If delegating: Pass subtask JSON to `CoderAgent`
+
+3. **Integrate**:
+ - Verify integration with previous components
+
+**Critical**: Never implement entire plan at once
+
+---
+
+## Stage 6: Validate & Handoff
+
+**Actions**:
+1. Run full system integration tests
+2. Delegate to `TestEngineer` or `CodeReviewer` (pass session context)
+3. Summarize what was built
+4. Ask user to clean up `.tmp` files
+
+---
+
+## Key Principles
+
+**Context First**: Load standards BEFORE coding
+**Approval Gates**: Never execute without approval
+**Incremental**: One step at a time, validate each
+**Stop on Failure**: Report → Propose fix → Request approval → Then fix
+
+---
+
+## Example: Adapter Implementation
+
+```
+1. Discover:
+ - ContextScout finds baseadapter-pattern.md
+ - Load code-quality.md standards
+
+2. Propose:
+ "Implement WindsurfAdapter (514 lines)
+ - Bidirectional conversion
+ - Extends BaseAdapter
+ - JSON config format"
+
+3. Init Session:
+ Create .tmp/sessions/2026-02-04-windsurf-adapter/
+ Write context.md with standards + references
+
+4. Plan:
+ Simple (1 file) → Skip TaskManager
+
+5. Execute:
+ - Create WindsurfAdapter.ts
+ - Implement toOAC(), fromOAC()
+ - Export in index.ts
+ - npm run build (validate)
+
+6. Validate:
+ - Compilation: ✅ 0 errors
+ - Ready for unit tests (next phase)
+```
+
+---
+
+## Reference
+
+**Related**:
+- standards/code-quality.md (MANDATORY before coding)
+- concepts/compatibility-layer.md
+- examples/baseadapter-implementation.md
diff --git a/.opencode/context/openagents-repo/guides/compatibility-layer-workflow.md b/.opencode/context/openagents-repo/guides/compatibility-layer-workflow.md
new file mode 100644
index 0000000..193f299
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/compatibility-layer-workflow.md
@@ -0,0 +1,212 @@
+# Guide: Compatibility Layer Development Workflow
+
+**Purpose**: Step-by-step process for extending the compatibility layer to support new AI coding tools
+
+**Last Updated**: 2026-02-05
+
+---
+
+## When to Use This Guide
+
+- Adding support for a new AI coding tool (e.g., Codeium, GitHub Copilot)
+- Extending existing adapter capabilities
+- Understanding the development phases for compatibility work
+
+---
+
+## Development Phases
+
+### Phase 1: Foundation ✅ COMPLETE
+
+**Objective**: Set up project infrastructure and core types
+
+1. **Project Setup** (1.5h) ✅
+ - Create `packages/compatibility-layer/package.json`
+ - Configure TypeScript with strict mode
+ - Set up Vitest with 80%+ coverage thresholds
+
+2. **Type System** (1.5h) ✅
+ - Create `src/types.ts` with Zod schemas
+ - Define `OpenAgentSchema`, `AgentFrontmatterSchema`
+ - Export TypeScript types with `z.infer<>`
+
+3. **Base Adapter** (1.5h) ✅
+ - Create `src/adapters/BaseAdapter.ts` abstract class
+ - Define abstract methods: `toOAC()`, `fromOAC()`, `getCapabilities()`
+
+4. **Agent Loader** (1.5h) ✅
+ - Create `src/core/AgentLoader.ts`
+ - Use gray-matter for YAML frontmatter parsing
+
+5. **Adapter Registry** (1h) ✅
+ - Create `src/core/AdapterRegistry.ts`
+ - Implement registry pattern with `Map`
+
+6. **Public API** (1h) ✅
+ - Create `src/index.ts`
+ - Export all public APIs
+
+**Result**: 1,475 lines TypeScript, 0 compilation errors
+
+---
+
+### Phase 2: Adapter Migration ✅ COMPLETE
+
+**Objective**: Migrate existing adapters to TypeScript
+
+7. **Claude Adapter** (3h) ✅ - 600 lines
+8. **Cursor Adapter** (3h) ✅ - 554 lines
+9. **Windsurf Adapter** (3h) ✅ - 514 lines
+10. **ClaudeAdapter Tests** ✅ - 80 tests, 96% coverage
+11. **CursorAdapter Tests** ✅ - 78 tests, 99% coverage
+12. **WindsurfAdapter Tests** ✅ - 78 tests, 99% coverage
+
+**Result**: 1,858 lines TypeScript, 236 tests passing
+
+---
+
+### Phase 3: Mappers & Translation ✅ COMPLETE
+
+**Objective**: Implement feature mapping logic
+
+13. **Tool Mapper** ✅ - 308 lines, 34 tests, 100% coverage
+14. **Permission Mapper** ✅ - 354 lines, 37 tests, 98% coverage
+15. **Model Mapper** ✅ - 413 lines, 37 tests, 99% coverage
+16. **Context Mapper** ✅ - 384 lines, 51 tests, 97% coverage
+17. **Capability Matrix** ✅ - 559 lines, 43 tests, 99% coverage
+18. **Translation Engine** ✅ - 453 lines, 47 tests, 99% coverage
+19. **Mapper Tests** ✅ - 249 tests total
+
+**Result**: 2,471 lines TypeScript, 249 tests passing
+
+---
+
+### Phase 4: CLI Tool ⬅️ NEXT
+
+**Objective**: Build command-line interface
+
+20. **CLI Scaffolding** (1.5h)
+ - Create `src/cli/index.ts` with Commander.js
+ - Define commands: convert, validate, migrate, info
+ - Set up chalk for colored output, ora for spinners
+
+21. **Convert Command** (2h)
+ - Implement `commands/convert.ts`
+ - Usage: `oac-compat convert --from oac --to claude agent.md`
+ - Support batch conversion
+
+22. **Validate Command** (1.5h)
+ - Implement `commands/validate.ts`
+ - Check compatibility before conversion
+ - Report warnings and incompatibilities
+
+23. **Migrate Command** (2h)
+ - Implement `commands/migrate.ts`
+ - Migrate entire projects (all agents + context)
+ - Generate migration report
+
+24. **Info Command** (1h)
+ - Implement `commands/info.ts`
+ - Show tool capabilities and feature matrices
+ - Display adapter list
+
+25. **CLI Integration Tests** (2h)
+ - Test each command end-to-end
+ - Test error handling
+ - Test output formatting
+
+---
+
+### Phase 5: Documentation
+
+**Objective**: Create migration guides and API docs
+
+26-30. **Migration Guides** (4h total)
+ - `docs/migration-guides/cursor-to-oac.md`
+ - `docs/migration-guides/claude-to-oac.md`
+ - `docs/migration-guides/oac-to-cursor.md`
+ - `docs/migration-guides/oac-to-claude.md`
+ - `docs/migration-guides/oac-to-windsurf.md`
+
+31. **Feature Matrices** (1h)
+ - Generate comparison tables
+ - Document degradation patterns
+
+32. **API Documentation** (1h)
+ - Document programmatic API usage
+ - Add examples for each adapter
+
+---
+
+## Adding a New Tool Adapter
+
+### Step-by-Step
+
+1. **Research Tool Format**
+ - Study tool's configuration file structure
+ - Identify supported features
+ - Note limitations vs OAC
+
+2. **Create Adapter Class**
+ ```typescript
+ export class NewToolAdapter extends BaseAdapter {
+ name = 'newtool'
+ displayName = 'New Tool'
+
+ async toOAC(source: string): Promise { /* ... */ }
+ async fromOAC(agent: OpenAgent): Promise { /* ... */ }
+ getConfigPath(): string { /* ... */ }
+ getCapabilities(): ToolCapabilities { /* ... */ }
+ validateConversion(agent: OpenAgent): string[] { /* ... */ }
+ }
+ ```
+
+3. **Use Existing Mappers**
+ - ToolMapper for tool name translation
+ - PermissionMapper for permission translation
+ - ModelMapper for model ID translation
+ - ContextMapper for context path translation
+
+4. **Register Adapter**
+ ```typescript
+ AdapterRegistry.register(new NewToolAdapter())
+ ```
+
+5. **Write Tests** (Target: 80%+ coverage)
+
+6. **Update Documentation**
+
+---
+
+## Success Criteria
+
+**Phase 1-3** ✅ ACHIEVED:
+- [x] All files compile without errors
+- [x] Zod schemas validate correctly
+- [x] All 3 adapters migrated to TypeScript
+- [x] Unit tests pass with 80%+ coverage
+- [x] All mappers are pure functions
+- [x] Graceful degradation works
+
+**Phase 4** (upcoming):
+- [ ] CLI commands work end-to-end
+- [ ] Error handling is comprehensive
+- [ ] Output is user-friendly
+
+**Phase 5** (upcoming):
+- [ ] Migration guides cover all tools
+- [ ] API docs are complete
+- [ ] Examples demonstrate usage
+
+---
+
+## Reference
+
+- **Issue**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+- **Branch**: `devalexanderdaza/issue141`
+- **Location**: `packages/compatibility-layer/`
+
+**Related**:
+- lookup/compatibility-layer-progress.md
+- lookup/compatibility-layer-adapters.md
+- lookup/compatibility-layer-structure.md
diff --git a/.opencode/context/openagents-repo/guides/creating-skills.md b/.opencode/context/openagents-repo/guides/creating-skills.md
new file mode 100644
index 0000000..30a29f3
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/creating-skills.md
@@ -0,0 +1,81 @@
+# Creating Skills
+
+**Purpose**: Step-by-step guide to create Agent Skills
+
+---
+
+## Quickstart
+
+1. **Create directory**:
+ ```bash
+ mkdir -p ~/.claude/skills/my-skill
+ ```
+
+2. **Create SKILL.md** with frontmatter:
+ ```yaml
+ ---
+ name: my-skill
+ description: What it does and when to use it
+ ---
+
+ Instructions for Claude...
+ ```
+
+3. **Verify**: Ask Claude "What Skills are available?"
+
+4. **Test**: Ask Claude something matching the description
+
+---
+
+## Writing Good Descriptions
+
+The description determines when Claude uses the skill. Include:
+- **What it does**: List specific capabilities
+- **When to use**: Include trigger terms users would say
+
+```yaml
+# Bad
+description: Helps with documents
+
+# Good
+description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
+```
+
+---
+
+## Progressive Disclosure
+
+Keep `SKILL.md` under 500 lines. Use supporting files for details:
+
+```
+my-skill/
+├── SKILL.md # Overview (required)
+├── reference.md # Detailed docs (loaded when needed)
+├── examples.md # Usage examples
+└── scripts/
+ └── helper.py # Utility script (executed, not loaded)
+```
+
+Reference in SKILL.md:
+```markdown
+For complete API details, see [reference.md](reference.md)
+```
+
+---
+
+## Key Points
+
+- Skills are loaded automatically (no restart needed)
+- Use `claude --debug` to see loading errors
+- Skills cannot spawn subagents
+- Use allowed-tools to restrict capabilities
+
+---
+
+## Related
+
+- `../concepts/agent-skills.md` - Skills overview
+- `../lookup/skill-metadata.md` - All metadata fields
+- `../examples/skills/` - Example skills
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/guides/creating-subagents.md b/.opencode/context/openagents-repo/guides/creating-subagents.md
new file mode 100644
index 0000000..aaba67c
--- /dev/null
+++ b/.opencode/context/openagents-repo/guides/creating-subagents.md
@@ -0,0 +1,73 @@
+# Creating Subagents
+
+**Purpose**: Step-by-step guide to create custom subagents
+
+---
+
+## Interactive Method (/agents)
+
+1. Run `/agents` in Claude Code
+2. Select **Create new agent**
+3. Choose scope: **User-level** or **Project-level**
+4. Select **Generate with Claude** and describe the subagent
+5. Select tools (or keep all)
+6. Choose model
+7. Pick a color
+8. Save - available immediately
+
+---
+
+## Manual Method (Markdown File)
+
+Create file at `~/.claude/agents/` (personal) or `.claude/agents/` (project):
+
+```markdown
+---
+name: code-reviewer
+description: Reviews code for quality and best practices
+tools: Read, Glob, Grep
+model: sonnet
+---
+
+You are a code reviewer. Analyze code and provide specific,
+actionable feedback on quality, security, and best practices.
+```
+
+Restart session or use `/agents` to load.
+
+---
+
+## CLI Method (JSON)
+
+```bash
+claude --agents '{
+ "code-reviewer": {
+ "description": "Expert code reviewer",
+ "prompt": "You are a senior code reviewer...",
+ "tools": ["Read", "Grep", "Glob", "Bash"],
+ "model": "sonnet"
+ }
+}'
+```
+
+Session-only, not saved to disk.
+
+---
+
+## Key Points
+
+- Filename becomes subagent name (without `.md`)
+- Description determines when Claude delegates
+- Body becomes system prompt
+- Subagents only get their system prompt, not full Claude Code prompt
+- Include "use proactively" in description for automatic delegation
+
+---
+
+## Related
+
+- `../concepts/subagents-system.md` - Subagents overview
+- `../lookup/subagent-frontmatter.md` - All configuration fields
+- `../examples/subagents/` - Example subagents
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/guides/navigation.md b/.opencode/context/openagents-repo/guides/navigation.md
index 78ec414..a0a8363 100644
--- a/.opencode/context/openagents-repo/guides/navigation.md
+++ b/.opencode/context/openagents-repo/guides/navigation.md
@@ -18,9 +18,12 @@ openagents-repo/guides/
| Task | Path |
|------|------|
-| **Adding agents** | `./adding-agent.md` |
+| **Adding agents (basics)** | `./adding-agent-basics.md` |
+| **Adding agents (tests)** | `./adding-agent-testing.md` |
+| **Adding OpenCode skills** | `./adding-skill-basics.md` |
+| **Creating Claude Code skills** | `./creating-skills.md` |
+| **Creating Claude Code subagents** | `./creating-subagents.md` |
| **Testing subagents** | `./testing-subagents.md` |
-| **View all guides** | `./` |
---
diff --git a/.opencode/context/openagents-repo/lookup/builtin-subagents.md b/.opencode/context/openagents-repo/lookup/builtin-subagents.md
new file mode 100644
index 0000000..e9b1a0f
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/builtin-subagents.md
@@ -0,0 +1,71 @@
+# Built-in Subagents
+
+**Purpose**: Quick reference for Claude Code's default subagents
+
+---
+
+## Explore
+
+**Model**: Haiku (fast, low-latency)
+**Tools**: Read-only (denied Write, Edit)
+**Purpose**: File discovery, code search, codebase exploration
+
+Thoroughness levels:
+- `quick` - Targeted lookups
+- `medium` - Balanced exploration
+- `very thorough` - Comprehensive analysis
+
+---
+
+## Plan
+
+**Model**: Inherits from main conversation
+**Tools**: Read-only (denied Write, Edit)
+**Purpose**: Codebase research for planning
+
+Used during plan mode to gather context before presenting a plan. Cannot spawn other subagents.
+
+---
+
+## general-purpose
+
+**Model**: Inherits from main conversation
+**Tools**: All tools
+**Purpose**: Complex research, multi-step operations, code modifications
+
+Used when task requires both exploration AND modification, complex reasoning, or multiple dependent steps.
+
+---
+
+## Other Built-in Agents
+
+| Agent | Model | Purpose |
+|-------|-------|---------|
+| Bash | Inherits | Terminal commands in separate context |
+| statusline-setup | Sonnet | Configure status line (`/statusline`) |
+| Claude Code Guide | Haiku | Questions about Claude Code features |
+
+---
+
+## Disabling Subagents
+
+In settings or CLI:
+```json
+{
+ "permissions": {
+ "deny": ["Task(Explore)", "Task(my-custom-agent)"]
+ }
+}
+```
+
+Or: `claude --disallowedTools "Task(Explore)"`
+
+---
+
+## Related
+
+- `../concepts/subagents-system.md` - Subagents overview
+- `../guides/creating-subagents.md` - Custom subagents
+- `../lookup/subagent-frontmatter.md` - Configuration
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/lookup/compatibility-layer-adapters.md b/.opencode/context/openagents-repo/lookup/compatibility-layer-adapters.md
new file mode 100644
index 0000000..23e0412
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/compatibility-layer-adapters.md
@@ -0,0 +1,156 @@
+# Lookup: Compatibility Layer Adapters
+
+**Purpose**: Quick reference for implemented adapter details
+
+**Last Updated**: 2026-02-05
+
+---
+
+## Adapter Summary
+
+| Adapter | Lines | Tests | Coverage | Status |
+|---------|-------|-------|----------|--------|
+| **ClaudeAdapter** | 600 | 80 | 96% | ✅ Complete |
+| **CursorAdapter** | 554 | 78 | 99% | ✅ Complete |
+| **WindsurfAdapter** | 514 | 78 | 99% | ✅ Complete |
+
+**Total**: 1,858 lines of TypeScript, 236 tests
+
+---
+
+## ClaudeAdapter
+
+**Format**: `.claude/config.json` (primary), `.claude/agents/*.md` (subagents)
+
+**Capabilities**:
+- ✅ Multiple agents
+- ✅ Skills system
+- ✅ Hooks (5 types: PreToolUse, PostToolUse, PermissionRequest, AgentStart, AgentEnd)
+- ⚠️ Binary permissions (granular → degraded)
+- ❌ Temperature not supported
+
+**Key Mappings**:
+- Model: `claude-sonnet-4` ↔ `claude-sonnet-4-20250514`
+- Permissions: Granular OAC → `permissionMode` (default/acceptEdits/dontAsk/bypassPermissions)
+- Contexts → Skills system
+
+**Tests**: 80 tests covering toOAC(), fromOAC(), roundtrip, error handling
+
+---
+
+## CursorAdapter
+
+**Format**: `.cursorrules` (single file in project root)
+
+**Capabilities**:
+- ❌ Single agent only (multi-agent → merged)
+- ❌ No Skills system (contexts → inlined)
+- ❌ No Hooks
+- ⚠️ Binary permissions only
+- ✅ Temperature support (limited)
+
+**Key Features**:
+- `mergeAgents()` method for multi-agent → single-file conversion
+- Optional YAML frontmatter
+- Context inlining with references
+
+**Key Mappings**:
+- Model: `claude-sonnet-4` → `claude-3-sonnet` (fallback to v3)
+- Multiple agents → Merged with section headers
+
+**Tests**: 78 tests covering toOAC(), fromOAC(), merging, error handling
+
+---
+
+## WindsurfAdapter
+
+**Format**: `.windsurf/config.json`, `.windsurf/agents/*.json`
+
+**Capabilities**:
+- ✅ Multiple agents (`.windsurf/agents/`)
+- ⚠️ Partial Skills (→ context references)
+- ❌ No Hooks
+- ⚠️ Binary permissions only
+- ✅ Temperature via creativity setting
+
+**Key Mappings**:
+- Model: `claude-sonnet-4` ↔ `claude-4-sonnet`
+- Temperature ↔ Creativity: `≤0.4 → low`, `≤0.8 → medium`, `>0.8 → high`
+- Priority: `critical/high → high`, `medium/low → low`
+- Skills → Context file references in `.windsurf/context/`
+
+**Tests**: 78 tests covering toOAC(), fromOAC(), creativity mapping, error handling
+
+---
+
+## Mappers (Phase 3 ✅)
+
+All mappers are pure functions used by adapters and TranslationEngine:
+
+| Mapper | Lines | Tests | Coverage | Purpose |
+|--------|-------|-------|----------|---------|
+| **ToolMapper** | 308 | 34 | 100% | Tool name mapping (bash↔terminal, etc.) |
+| **PermissionMapper** | 354 | 37 | 98% | Granular↔binary permissions |
+| **ModelMapper** | 413 | 37 | 99% | Model ID mapping with fallbacks |
+| **ContextMapper** | 384 | 51 | 97% | Context path mapping between platforms |
+
+---
+
+## Core Services (Phase 3 ✅)
+
+| Service | Lines | Tests | Coverage | Purpose |
+|---------|-------|-------|----------|---------|
+| **CapabilityMatrix** | 559 | 43 | 99% | Feature compatibility analysis |
+| **TranslationEngine** | 453 | 47 | 99% | Orchestrates all mappers |
+
+---
+
+## Common Patterns
+
+### Bidirectional Conversion
+All 3 adapters support:
+- `toOAC()`: Tool format → OpenAgent
+- `fromOAC()`: OpenAgent → Tool format
+
+### Graceful Degradation
+All adapters warn when features are lost:
+- Granular permissions → Binary on/off
+- Unsupported features → Warning messages
+- Feature loss → Clear user notifications
+
+### Type Safety
+- All extend `BaseAdapter`
+- Full TypeScript strict mode
+- Zod schema validation
+- 0 compilation errors
+
+---
+
+## Feature Parity Matrix
+
+| Feature | OAC | Claude | Cursor | Windsurf |
+|---------|-----|--------|--------|----------|
+| Multiple Agents | ✅ | ✅ | ❌ | ✅ |
+| Granular Permissions | ✅ | ❌ | ❌ | ❌ |
+| Ask Permissions | ✅ | ❌ | ❌ | ❌ |
+| Hooks | ✅ | ✅ | ❌ | ❌ |
+| Skills | ✅ | ✅ | ❌ | ⚠️ |
+| External Context | ✅ | ✅ | ❌ | ✅ |
+| Context Priority | ✅ | ❌ | ❌ | ❌ |
+| Temperature | ✅ | ❌ | ⚠️ | ✅ |
+| Model Selection | ✅ | ✅ | ✅ | ✅ |
+| Task Delegation | ✅ | ✅ | ❌ | ⚠️ |
+
+Legend: ✅ Full support, ⚠️ Partial support, ❌ Not supported
+
+---
+
+## Reference
+
+**Implementation**: `packages/compatibility-layer/src/adapters/`
+**Issue**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+
+**Related**:
+- lookup/compatibility-layer-progress.md
+- lookup/compatibility-layer-structure.md
+- guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/lookup/compatibility-layer-progress.md b/.opencode/context/openagents-repo/lookup/compatibility-layer-progress.md
new file mode 100644
index 0000000..7b80bbf
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/compatibility-layer-progress.md
@@ -0,0 +1,161 @@
+# Lookup: Compatibility Layer Progress
+
+**Purpose**: Quick reference for Issue #141 development progress
+
+**Last Updated**: 2026-02-05
+
+---
+
+## Current Status
+
+**Overall Progress**: 59.4% (19/32 subtasks completed)
+
+```
+Phase 1 (Foundation): ████████████████████ 100% (6/6) ✅
+Phase 2 (Adapters): ████████████████████ 100% (6/6) ✅
+Phase 3 (Mappers): ████████████████████ 100% (7/7) ✅
+Phase 4 (CLI): ░░░░░░░░░░░░░░░░░░░░ 0% (0/6) ⬅️ NEXT
+Phase 5 (Documentation): ░░░░░░░░░░░░░░░░░░░░ 0% (0/7)
+```
+
+---
+
+## Phase Breakdown
+
+### Phase 1: Foundation (100% ✅)
+**Time**: 7.5h / 8h estimated
+
+- ✅ Subtask 01: Project Setup
+- ✅ Subtask 02: Migrate types.ts (315 lines)
+- ✅ Subtask 03: Create BaseAdapter (190 lines)
+- ✅ Subtask 04: Migrate AgentLoader (386 lines)
+- ✅ Subtask 05: Migrate AdapterRegistry (416 lines)
+- ✅ Subtask 06: Create index.ts (168 lines)
+
+**Total**: 1,475 lines TypeScript
+
+---
+
+### Phase 2: Adapters (100% ✅)
+**Time**: ~12h
+
+- ✅ Subtask 07: ClaudeAdapter (600 lines)
+- ✅ Subtask 08: CursorAdapter (554 lines)
+- ✅ Subtask 09: WindsurfAdapter (514 lines)
+- ✅ Subtask 10: ClaudeAdapter tests (80 tests)
+- ✅ Subtask 11: CursorAdapter tests (78 tests)
+- ✅ Subtask 12: WindsurfAdapter tests (78 tests)
+
+**Total**: 1,858 lines TypeScript (adapters) + 236 tests
+
+---
+
+### Phase 3: Mappers & Translation (100% ✅)
+**Time**: ~10h
+
+- ✅ Subtask 13: ToolMapper (308 lines, 34 tests)
+- ✅ Subtask 14: PermissionMapper (354 lines, 37 tests)
+- ✅ Subtask 15: ModelMapper (413 lines, 37 tests)
+- ✅ Subtask 16: ContextMapper (384 lines, 51 tests)
+- ✅ Subtask 17: CapabilityMatrix (559 lines, 43 tests)
+- ✅ Subtask 18: TranslationEngine (453 lines, 47 tests)
+- ✅ Subtask 19: Mapper & Core Tests (249 tests total)
+
+**Total**: 2,471 lines TypeScript + 249 tests
+
+---
+
+### Phase 4: CLI (0% ⬅️ NEXT)
+**Estimated**: 8h
+
+- 📝 Subtask 20: CLI Scaffolding (Commander.js setup)
+- 📝 Subtask 21: Convert Command
+- 📝 Subtask 22: Validate Command
+- 📝 Subtask 23: Migrate Command
+- 📝 Subtask 24: Info Command
+- 📝 Subtask 25: CLI Integration Tests
+
+---
+
+### Phase 5: Documentation (0%)
+**Estimated**: 6h
+
+- 📝 Subtask 26-30: Migration Guides (5 guides)
+- 📝 Subtask 31: Feature Matrices
+- 📝 Subtask 32: API Documentation
+
+---
+
+## Code Stats
+
+| Category | Lines | Files |
+|----------|-------|-------|
+| Source Code | 5,799 | 14 |
+| Test Code | 6,322 | 9 |
+| **Total** | **12,121** | **23** |
+
+### By Phase
+
+| Phase | Source Lines | Tests |
+|-------|-------------|-------|
+| Phase 1 (Foundation) | 1,475 | - |
+| Phase 2 (Adapters) | 1,858 | 236 |
+| Phase 3 (Mappers) | 2,471 | 249 |
+| **Total** | **5,804** | **485** |
+
+---
+
+## Test Coverage
+
+**485 tests passing** ✅
+
+| Module | Statements | Branches | Functions |
+|--------|-----------|----------|-----------|
+| Adapters | 97-99% | 82-93% | 95-100% |
+| Mappers | 97-100% | 90-100% | 100% |
+| Core (Matrix/Engine) | 98-99% | 93-96% | 100% |
+
+---
+
+## Commits (Issue #141)
+
+| Commit | Phase | Description |
+|--------|-------|-------------|
+| `0b98cbd` | 1 | Foundation implementation |
+| `340d144` | 1 | AgentLoader |
+| `81c2f65` | 1 | AdapterRegistry |
+| `175eac8` | 1 | index.ts entry point |
+| `d38dc27` | 2 | ClaudeAdapter |
+| `7695696` | 2 | CursorAdapter |
+| `71fa384` | 2 | WindsurfAdapter |
+| `a9176c9` | 2 | ClaudeAdapter tests |
+| `713fd09` | 2 | CursorAdapter tests |
+| `0b93a47` | 2 | WindsurfAdapter tests |
+| `99eba67` | 3 | Mappers, Matrix, Engine + tests |
+
+---
+
+## Next Steps
+
+1. **Phase 4**: CLI Tool Implementation
+ - Create `src/cli/index.ts` with Commander.js
+ - Implement convert, validate, migrate, info commands
+ - Add CLI integration tests
+
+2. **Phase 5**: Documentation
+ - Migration guides for all tool combinations
+ - Feature matrices
+ - API documentation
+
+---
+
+## Reference
+
+**Issue**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+**Branch**: `devalexanderdaza/issue141`
+**Location**: `packages/compatibility-layer/`
+
+**Related**:
+- lookup/compatibility-layer-adapters.md
+- lookup/compatibility-layer-structure.md
+- guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/lookup/compatibility-layer-structure.md b/.opencode/context/openagents-repo/lookup/compatibility-layer-structure.md
new file mode 100644
index 0000000..079a0ee
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/compatibility-layer-structure.md
@@ -0,0 +1,168 @@
+# Lookup: Compatibility Layer File Structure
+
+**Purpose**: Quick reference for where files go in the compatibility-layer package
+
+**Last Updated**: 2026-02-05
+
+---
+
+## Package Location
+
+```
+packages/compatibility-layer/
+```
+
+---
+
+## Directory Structure (Current State)
+
+```
+compatibility-layer/
+├── package.json # Dependencies, scripts
+├── tsconfig.json # TypeScript config (strict, ES2022)
+├── vitest.config.ts # Test config (80% coverage threshold)
+├── README.md # Package documentation
+│
+├── src/ # Source code (5,799 lines)
+│ ├── types.ts # Zod schemas + type exports (315 lines) ✅
+│ ├── index.ts # Public API exports (335 lines) ✅
+│ │
+│ ├── adapters/ # Tool adapters ✅ COMPLETE
+│ │ ├── BaseAdapter.ts # Abstract base class (190 lines)
+│ │ ├── ClaudeAdapter.ts # Claude Code adapter (600 lines)
+│ │ ├── CursorAdapter.ts # Cursor IDE adapter (554 lines)
+│ │ └── WindsurfAdapter.ts # Windsurf adapter (514 lines)
+│ │
+│ ├── core/ # Core services ✅ COMPLETE
+│ │ ├── AgentLoader.ts # Load/parse OAC agents (386 lines)
+│ │ ├── AdapterRegistry.ts # Adapter management (416 lines)
+│ │ ├── CapabilityMatrix.ts # Feature parity tracking (559 lines)
+│ │ └── TranslationEngine.ts # Conversion orchestration (453 lines)
+│ │
+│ ├── mappers/ # Feature mappers ✅ COMPLETE
+│ │ ├── ToolMapper.ts # Tool name mapping (308 lines)
+│ │ ├── PermissionMapper.ts # Permission translation (354 lines)
+│ │ ├── ModelMapper.ts # Model ID mapping (413 lines)
+│ │ └── ContextMapper.ts # Context path mapping (384 lines)
+│ │
+│ └── cli/ # Command-line interface 📝 TODO
+│ ├── index.ts # CLI entry point
+│ └── commands/
+│ ├── convert.ts # Convert command
+│ ├── validate.ts # Validate command
+│ ├── migrate.ts # Migrate command
+│ └── info.ts # Info command
+│
+├── tests/ # Test files (6,322 lines, 485 tests)
+│ └── unit/
+│ ├── adapters/ # Adapter tests ✅ COMPLETE
+│ │ ├── ClaudeAdapter.test.ts (80 tests)
+│ │ ├── CursorAdapter.test.ts (78 tests)
+│ │ └── WindsurfAdapter.test.ts (78 tests)
+│ ├── mappers/ # Mapper tests ✅ COMPLETE
+│ │ ├── ToolMapper.test.ts (34 tests)
+│ │ ├── PermissionMapper.test.ts (37 tests)
+│ │ ├── ModelMapper.test.ts (37 tests)
+│ │ └── ContextMapper.test.ts (51 tests)
+│ └── core/ # Core tests ✅ COMPLETE
+│ ├── CapabilityMatrix.test.ts (43 tests)
+│ └── TranslationEngine.test.ts (47 tests)
+│
+├── docs/ # Documentation 📝 TODO
+│ ├── migration-guides/ # Migration instructions
+│ ├── feature-matrices/ # Feature comparison tables
+│ └── api/ # API documentation
+│
+└── dist/ # Compiled output (auto-generated)
+```
+
+---
+
+## Implementation Status
+
+### ✅ Complete (Phases 1-3)
+
+| File | Lines | Tests | Coverage |
+|------|-------|-------|----------|
+| types.ts | 315 | - | - |
+| index.ts | 335 | - | - |
+| BaseAdapter.ts | 190 | - | 92% |
+| ClaudeAdapter.ts | 600 | 80 | 96% |
+| CursorAdapter.ts | 554 | 78 | 99% |
+| WindsurfAdapter.ts | 514 | 78 | 99% |
+| AgentLoader.ts | 386 | - | 0%* |
+| AdapterRegistry.ts | 416 | - | 0%* |
+| CapabilityMatrix.ts | 559 | 43 | 99% |
+| TranslationEngine.ts | 453 | 47 | 99% |
+| ToolMapper.ts | 308 | 34 | 100% |
+| PermissionMapper.ts | 354 | 37 | 98% |
+| ModelMapper.ts | 413 | 37 | 99% |
+| ContextMapper.ts | 384 | 51 | 97% |
+
+*AgentLoader and AdapterRegistry are tested indirectly via adapters
+
+### 📝 Pending (Phase 4-5)
+
+| File | Purpose | Phase |
+|------|---------|-------|
+| cli/index.ts | CLI entry point | 4 |
+| cli/commands/convert.ts | Convert command | 4 |
+| cli/commands/validate.ts | Validate command | 4 |
+| cli/commands/migrate.ts | Migrate command | 4 |
+| cli/commands/info.ts | Info command | 4 |
+| docs/migration-guides/*.md | Migration guides | 5 |
+| docs/api/*.md | API documentation | 5 |
+
+---
+
+## Dependencies
+
+### Production
+
+| Package | Purpose | Version |
+|---------|---------|---------|
+| zod | Schema validation | ^3.22.0 |
+| js-yaml | YAML parsing | ^4.1.0 |
+| gray-matter | Frontmatter extraction | ^4.0.3 |
+
+### Development
+
+| Package | Purpose | Version |
+|---------|---------|---------|
+| typescript | TypeScript compiler | ^5.4.0 |
+| vitest | Test framework | ^1.6.1 |
+| @vitest/coverage-v8 | Coverage reporting | ^1.6.1 |
+
+### CLI (Phase 4 - to be added)
+
+| Package | Purpose | Version |
+|---------|---------|---------|
+| commander | CLI framework | ^11.1.0 |
+| chalk | Terminal colors | ^5.3.0 |
+| ora | Loading spinners | ^7.0.1 |
+
+---
+
+## Scripts
+
+```json
+{
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:coverage": "vitest run --coverage"
+}
+```
+
+---
+
+## Reference
+
+- **Issue**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+- **Branch**: `devalexanderdaza/issue141`
+
+**Related**:
+- lookup/compatibility-layer-progress.md
+- lookup/compatibility-layer-adapters.md
+- guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/lookup/compatibility-layer-summary.md b/.opencode/context/openagents-repo/lookup/compatibility-layer-summary.md
new file mode 100644
index 0000000..f13723f
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/compatibility-layer-summary.md
@@ -0,0 +1,97 @@
+# Compatibility Layer - Executive Summary
+
+**Issue**: #141 | **Branch**: `devalexanderdaza/issue141` | **Updated**: 2026-02-05
+
+---
+
+## Quick Status
+
+```
+Progress: ████████████░░░░░░░░ 59.4% (19/32 subtasks)
+
+✅ Phase 1: Foundation - 6/6 complete
+✅ Phase 2: Adapters - 6/6 complete
+✅ Phase 3: Mappers - 7/7 complete
+⬅️ Phase 4: CLI - 0/6 pending (NEXT)
+📝 Phase 5: Documentation - 0/7 pending
+```
+
+---
+
+## Codebase Stats
+
+| Metric | Value |
+|--------|-------|
+| Source Lines | 5,799 (14 files) |
+| Test Lines | 6,322 (9 files) |
+| Tests | 485 passing |
+| Coverage | 97-100% on Phase 3 |
+
+---
+
+## What's Implemented
+
+### Adapters (3)
+- `ClaudeAdapter` - .claude/ format ↔ OAC
+- `CursorAdapter` - .cursorrules ↔ OAC
+- `WindsurfAdapter` - .windsurf/ ↔ OAC
+
+### Mappers (4)
+- `ToolMapper` - bash↔terminal, task↔delegate
+- `PermissionMapper` - granular↔binary
+- `ModelMapper` - model IDs with fallbacks
+- `ContextMapper` - context paths between platforms
+
+### Core (2)
+- `CapabilityMatrix` - feature compatibility analysis
+- `TranslationEngine` - orchestrates all mappers
+
+---
+
+## What's Next
+
+**Phase 4: CLI Tool** (8h estimated)
+1. CLI scaffolding (Commander.js)
+2. `convert` command
+3. `validate` command
+4. `migrate` command
+5. `info` command
+6. Integration tests
+
+---
+
+## Key Commands
+
+```bash
+# Work directory
+cd packages/compatibility-layer
+
+# Build & Test
+npm run build # Compile TypeScript
+npx vitest run # Run all tests
+npx vitest run --coverage # With coverage
+
+# Git
+git log --oneline -5 # Recent commits
+```
+
+---
+
+## Feature Parity
+
+| Feature | OAC | Claude | Cursor | Windsurf |
+|---------|:---:|:------:|:------:|:--------:|
+| Multi-Agent | ✅ | ✅ | ❌ | ✅ |
+| Hooks | ✅ | ✅ | ❌ | ❌ |
+| Granular Perms | ✅ | ❌ | ❌ | ❌ |
+| Skills | ✅ | ✅ | ❌ | ⚠️ |
+| Temperature | ✅ | ❌ | ⚠️ | ✅ |
+
+---
+
+## Reference Files
+
+- Progress: `lookup/compatibility-layer-progress.md`
+- Structure: `lookup/compatibility-layer-structure.md`
+- Adapters: `lookup/compatibility-layer-adapters.md`
+- Workflow: `guides/compatibility-layer-workflow.md`
diff --git a/.opencode/context/openagents-repo/lookup/compatibility-learnings.md b/.opencode/context/openagents-repo/lookup/compatibility-learnings.md
new file mode 100644
index 0000000..285d69f
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/compatibility-learnings.md
@@ -0,0 +1,161 @@
+# Lookup: Compatibility Layer Key Learnings
+
+**Purpose**: Important insights from Phase 1-3 development
+
+**Last Updated**: 2026-02-05
+
+---
+
+## TypeScript & Architecture
+
+**TypeScript strict mode works perfectly**
+- Zero compilation errors across all 14 source files
+- Type safety catches conversion errors early
+- Zod + TypeScript prevent runtime issues
+
+**Adapter pattern scales beautifully**
+- 3 adapters, 0 duplication, consistent API
+- Template method pattern enables reuse
+- BaseAdapter provides robust foundation
+
+**Project structure is clean**
+- Modular organization enables parallel development
+- Function-based folders improve discoverability
+- Barrel exports provide clean public API
+
+---
+
+## Code Quality
+
+**Zod schemas are comprehensive**
+- All 20+ schemas validated and working
+- Runtime validation prevents bad data
+- Type inference from schemas reduces duplication
+
+**Pure functions enable testing**
+- All mappers are pure functions (ToolMapper, PermissionMapper, etc.)
+- Easy to test in isolation
+- Deterministic conversion
+- 97-100% coverage achieved on mappers
+
+**Registry pattern is powerful**
+- Map-based storage + aliases = great DX
+- O(1) adapter lookup
+- Type-safe registration
+
+---
+
+## Development Process
+
+**Context loading matters**
+- Reading standards BEFORE coding prevents rework
+- ContextScout saves discovery time
+- Persistent session context enables handoffs
+
+**Approval gates prevent mistakes**
+- User confirmation before destructive ops
+- Incremental execution catches issues early
+- Stop on failure prevents cascading errors
+
+**Phase completion tracking**
+- Phase 1: ✅ 100% (Foundation)
+- Phase 2: ✅ 100% (Adapters + Tests)
+- Phase 3: ✅ 100% (Mappers + Tests)
+- Overall: 59.4% complete (19/32 subtasks)
+
+---
+
+## Feature Implementation
+
+**Bidirectional conversion is achievable**
+- All 3 adapters support roundtrip (OAC ↔ Tool ↔ OAC)
+- Lossy conversions handled with clear warnings
+- Feature parity matrix guides expectations
+
+**Graceful degradation works**
+- Clear warnings guide users on feature loss
+- Binary permissions instead of failing
+- Temperature ↔ Creativity mapping (approximate but functional)
+
+**Translation Engine orchestration**
+- Coordinates all mappers for complete translation
+- Collects warnings from all components
+- Provides preview/compatibility analysis
+
+---
+
+## Technical Wins
+
+**Type-safe mappers prevent bugs**
+- Model ID mapping with fallbacks
+- Permission degradation with warnings
+- Priority normalization handles edge cases
+
+**Error handling is robust**
+- Custom error classes per module
+- Descriptive error messages
+- Validation at boundaries
+
+**Build system is solid**
+- TypeScript compilation fast (~1s)
+- All 485 tests pass
+- Coverage exceeds 80% on tested modules
+
+---
+
+## Test Coverage Summary
+
+| Category | Tests | Coverage |
+|----------|-------|----------|
+| Adapters | 236 | 97-99% |
+| Mappers | 159 | 97-100% |
+| Core | 90 | 98-99% |
+| **Total** | **485** | **>80%** |
+
+---
+
+## What Worked Well
+
+✅ Loading context before implementation
+✅ Approval gates for safety
+✅ Incremental execution (one step at a time)
+✅ Template method pattern for adapters
+✅ Pure functions for all mappers
+✅ Zod validation throughout
+✅ Comprehensive test coverage
+✅ Conventional commits tracking
+
+---
+
+## What to Improve
+
+⚠️ Could add integration tests earlier
+⚠️ Feature parity matrix could be auto-generated
+⚠️ CLI tool would help during development
+⚠️ AgentLoader/AdapterRegistry need direct unit tests
+
+---
+
+## Remaining Work
+
+**Phase 4 (CLI)**: 6 subtasks
+- CLI scaffolding with Commander.js
+- convert, validate, migrate, info commands
+- Integration tests
+
+**Phase 5 (Documentation)**: 7 subtasks
+- 5 migration guides
+- Feature matrices
+- API documentation
+
+---
+
+## Reference
+
+**Issue**: https://github.com/darrenhinde/OpenAgentsControl/issues/141
+**Branch**: `devalexanderdaza/issue141`
+
+**Related**:
+- lookup/compatibility-layer-progress.md
+- lookup/compatibility-layer-structure.md
+- guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/lookup/hook-events.md b/.opencode/context/openagents-repo/lookup/hook-events.md
new file mode 100644
index 0000000..31f0318
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/hook-events.md
@@ -0,0 +1,64 @@
+# Hook Events Reference
+
+**Purpose**: Quick reference for all Claude Code hook events
+
+---
+
+## Available Events
+
+| Event | When It Fires | Can Block? |
+|-------|---------------|------------|
+| `PreToolUse` | Before tool calls | Yes (exit 2) |
+| `PostToolUse` | After tool calls complete | No |
+| `PermissionRequest` | Permission dialog shown | Yes (allow/deny) |
+| `UserPromptSubmit` | User submits prompt | No |
+| `Notification` | Claude sends notification | No |
+| `Stop` | Claude finishes responding | No |
+| `SubagentStop` | Subagent task completes | No |
+| `PreCompact` | Before compact operation | No |
+| `SessionStart` | New/resumed session | No |
+| `SessionEnd` | Session ends | No |
+
+---
+
+## Matcher Syntax
+
+- `"Bash"` - Match specific tool
+- `"Edit|Write"` - Match multiple tools (OR)
+- `"*"` - Match all tools
+- `""` - Match all (for Notification)
+
+---
+
+## Exit Codes
+
+| Code | Behavior |
+|------|----------|
+| 0 | Success, continue execution |
+| 1 | Error logged, continue execution |
+| 2 | **Block operation**, feed stderr to Claude |
+
+---
+
+## Input/Output
+
+Hooks receive JSON via stdin:
+```json
+{
+ "tool_name": "Bash",
+ "tool_input": { "command": "ls -la" },
+ "session_id": "...",
+ "transcript_path": "..."
+}
+```
+
+Output to stderr is fed back to Claude when blocking (exit 2).
+
+---
+
+## Related
+
+- `../concepts/hooks-system.md` - Hooks overview
+- `../examples/hooks/` - Hook examples
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/hooks
diff --git a/.opencode/context/openagents-repo/lookup/skill-metadata.md b/.opencode/context/openagents-repo/lookup/skill-metadata.md
new file mode 100644
index 0000000..30e8294
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/skill-metadata.md
@@ -0,0 +1,74 @@
+# Skill Metadata Fields
+
+**Purpose**: Quick reference for SKILL.md frontmatter fields
+
+---
+
+## Required Fields
+
+| Field | Description |
+|-------|-------------|
+| `name` | Unique identifier (lowercase, hyphens, max 64 chars) |
+| `description` | What skill does and when to use (max 1024 chars) |
+
+---
+
+## Optional Fields
+
+| Field | Default | Description |
+|-------|---------|-------------|
+| `allowed-tools` | All | Tools Claude can use without permission |
+| `model` | Conversation | Model to use (e.g., `claude-sonnet-4-20250514`) |
+| `context` | - | Set to `fork` for isolated sub-agent context |
+| `agent` | `general-purpose` | Agent type when `context: fork` |
+| `hooks` | - | Lifecycle hooks (`PreToolUse`, `PostToolUse`, `Stop`) |
+| `user-invocable` | `true` | Show in slash command menu |
+| `disable-model-invocation` | `false` | Block programmatic invocation |
+
+---
+
+## allowed-tools Syntax
+
+```yaml
+# Comma-separated
+allowed-tools: Read, Grep, Glob
+
+# YAML list
+allowed-tools:
+ - Read
+ - Grep
+ - Glob
+```
+
+---
+
+## hooks Syntax
+
+```yaml
+hooks:
+ PreToolUse:
+ - matcher: "Bash"
+ hooks:
+ - type: command
+ command: "./scripts/validate.sh"
+ once: true # Run only once per session
+```
+
+---
+
+## Visibility Control
+
+| Setting | Slash Menu | Skill Tool | Auto-discovery |
+|---------|------------|------------|----------------|
+| Default | ✓ | ✓ | ✓ |
+| `user-invocable: false` | ✗ | ✓ | ✓ |
+| `disable-model-invocation: true` | ✓ | ✗ | ✓ |
+
+---
+
+## Related
+
+- `../concepts/agent-skills.md` - Skills overview
+- `../guides/creating-skills.md` - Creation guide
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/lookup/skills-comparison.md b/.opencode/context/openagents-repo/lookup/skills-comparison.md
new file mode 100644
index 0000000..2e9e3a7
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/skills-comparison.md
@@ -0,0 +1,64 @@
+# Skills vs Other Options
+
+**Purpose**: When to use Skills versus other Claude Code customizations
+
+---
+
+## Comparison Table
+
+| Option | When It Runs | Best For |
+|--------|--------------|----------|
+| **Skills** | Claude chooses when relevant | Specialized knowledge (review standards, patterns) |
+| **Slash commands** | You type `/command` | Reusable prompts (`/deploy staging`) |
+| **CLAUDE.md** | Every conversation | Project-wide instructions |
+| **Subagents** | Claude delegates or you invoke | Isolated context, different tools |
+| **Hooks** | Specific tool events | Auto-format, logging, protection |
+| **MCP servers** | Claude calls as needed | External tools and data sources |
+
+---
+
+## Key Distinctions
+
+### Skills vs Slash Commands
+- **Skills**: Claude decides when to use based on task
+- **Commands**: You explicitly type `/command`
+
+### Skills vs Subagents
+- **Skills**: Add knowledge to current conversation
+- **Subagents**: Separate context with own tools/permissions
+
+### Skills vs MCP
+- **Skills**: Tell Claude *how* to use tools
+- **MCP**: *Provide* the tools themselves
+
+### Skills vs CLAUDE.md
+- **Skills**: Loaded only when relevant
+- **CLAUDE.md**: Always loaded into every conversation
+
+---
+
+## Decision Guide
+
+**Use Skills when**:
+- Teaching domain-specific knowledge
+- Defining review/coding standards
+- Want Claude to decide when to apply
+
+**Use Subagents when**:
+- Need tool isolation
+- Task produces verbose output
+- Want separate context window
+
+**Use CLAUDE.md when**:
+- Rules apply to ALL interactions
+- Project-wide coding standards
+
+---
+
+## Related
+
+- `../concepts/agent-skills.md` - Skills overview
+- `../concepts/subagents-system.md` - Subagents overview
+- `../../standards/subagent-structure.md` - Subagent patterns
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/skills
diff --git a/.opencode/context/openagents-repo/lookup/subagent-frontmatter.md b/.opencode/context/openagents-repo/lookup/subagent-frontmatter.md
new file mode 100644
index 0000000..7896657
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/subagent-frontmatter.md
@@ -0,0 +1,83 @@
+# Subagent Frontmatter
+
+**Purpose**: Quick reference for subagent configuration fields
+
+---
+
+## Required Fields
+
+| Field | Description |
+|-------|-------------|
+| `name` | Unique identifier (lowercase, hyphens) |
+| `description` | When Claude should delegate to this subagent |
+
+---
+
+## Optional Fields
+
+| Field | Default | Description |
+|-------|---------|-------------|
+| `tools` | All | Tools subagent can use |
+| `disallowedTools` | - | Tools to deny (removed from list) |
+| `model` | `sonnet` | Model: `sonnet`, `opus`, `haiku`, `inherit` |
+| `permissionMode` | `default` | Permission handling mode |
+| `skills` | - | Skills to load into context |
+| `hooks` | - | Lifecycle hooks for this subagent |
+
+---
+
+## Permission Modes
+
+| Mode | Behavior |
+|------|----------|
+| `default` | Standard permission checking |
+| `acceptEdits` | Auto-accept file edits |
+| `dontAsk` | Auto-deny permission prompts |
+| `bypassPermissions` | Skip all permission checks |
+| `plan` | Read-only exploration |
+
+⚠️ `bypassPermissions` is dangerous - skips all checks
+
+---
+
+## Hooks Syntax
+
+```yaml
+hooks:
+ PreToolUse:
+ - matcher: "Bash"
+ hooks:
+ - type: command
+ command: "./scripts/validate.sh"
+ PostToolUse:
+ - matcher: "Edit|Write"
+ hooks:
+ - type: command
+ command: "./scripts/lint.sh"
+```
+
+---
+
+## Example
+
+```yaml
+---
+name: debugger
+description: Debugging specialist for errors and test failures
+tools: Read, Edit, Bash, Grep, Glob
+model: inherit
+permissionMode: default
+---
+
+You are an expert debugger...
+```
+
+---
+
+## Related
+
+- `../concepts/subagents-system.md` - Subagents overview
+- `../guides/creating-subagents.md` - Creation guide
+- `../lookup/builtin-subagents.md` - Built-in subagents
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/sub-agents
diff --git a/.opencode/context/openagents-repo/lookup/tool-feature-parity.md b/.opencode/context/openagents-repo/lookup/tool-feature-parity.md
new file mode 100644
index 0000000..f20cd6a
--- /dev/null
+++ b/.opencode/context/openagents-repo/lookup/tool-feature-parity.md
@@ -0,0 +1,133 @@
+# Lookup: Tool Feature Parity Matrix
+
+**Purpose**: Quick reference for feature support across AI coding tools
+
+**Last Updated**: 2026-02-04
+
+---
+
+## Feature Comparison
+
+### Core Features
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf | Notes |
+|---------|-----|-------------|------------|----------|-------|
+| **Multiple agents** | ✅ Yes | ✅ Yes | ❌ Single only | ✅ Yes | Cursor requires merged config |
+| **Agent modes** | ✅ primary/subagent | ✅ Yes | ❌ No | ⚠️ Partial | OAC has explicit mode field |
+| **Temperature control** | ✅ 0.0-2.0 | ❌ No | ⚠️ Limited | ⚠️ Limited | Maps to creativity settings |
+| **Model selection** | ✅ Full control | ✅ Yes | ✅ Yes | ✅ Yes | Different ID formats |
+| **Context files** | ✅ .opencode/context/ | ✅ Skills system | ✅ .cursorrules | ✅ Yes | Path mapping required |
+
+### Tool Permissions
+
+| Tool Access | OAC | Claude Code | Cursor IDE | Windsurf |
+|-------------|-----|-------------|------------|----------|
+| **read** | ✅ Granular | ⚠️ Binary on/off | ⚠️ Binary | ⚠️ Binary |
+| **write** | ✅ Granular | ⚠️ Binary | ⚠️ Binary | ⚠️ Binary |
+| **edit** | ✅ Granular | ⚠️ Binary | ⚠️ Binary | ⚠️ Binary |
+| **bash** | ✅ Granular | ✅ Yes | ✅ Yes | ✅ Yes |
+| **task** | ✅ Delegation | ✅ Yes | ❌ No | ⚠️ Limited |
+| **grep/glob** | ✅ Separate | ✅ Yes | ✅ Yes | ✅ Yes |
+
+**Legend**: ✅ Full support | ⚠️ Partial/degraded | ❌ Not supported
+
+### Advanced Features
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf |
+|---------|-----|-------------|------------|----------|
+| **Skills system** | ✅ Yes | ✅ Yes | ❌ No | ⚠️ Partial |
+| **Hooks** | ✅ 5 types | ✅ Yes | ❌ No | ❌ No |
+| **Dependencies** | ✅ Explicit | ✅ Yes | ❌ No | ⚠️ Partial |
+| **Priority levels** | ✅ 4 levels | ⚠️ 2 levels | ❌ No | ⚠️ 2 levels |
+| **Agent categories** | ✅ 8 types | ⚠️ Limited | ❌ No | ⚠️ Limited |
+
+---
+
+## Hook Event Support
+
+| Event | OAC | Claude Code | Cursor | Windsurf |
+|-------|-----|-------------|--------|----------|
+| PreToolUse | ✅ | ✅ | ❌ | ❌ |
+| PostToolUse | ✅ | ✅ | ❌ | ❌ |
+| PermissionRequest | ✅ | ✅ | ❌ | ❌ |
+| AgentStart | ✅ | ✅ | ❌ | ❌ |
+| AgentEnd | ✅ | ✅ | ❌ | ❌ |
+
+---
+
+## Permission Granularity Mapping
+
+### OAC → Other Tools (Degradation)
+
+```
+OAC Granular Permission:
+{
+ allow: ["src/**/*.ts"],
+ deny: ["src/secrets/**"],
+ ask: ["package.json"]
+}
+
+↓ Maps to ↓
+
+Claude: tools.write = true (with warnings in skills)
+Cursor: Full write access (no granularity)
+Windsurf: Basic allow/deny (ask → deny)
+```
+
+---
+
+## Model ID Mapping
+
+| OAC Model ID | Claude Code | Cursor IDE | Windsurf |
+|--------------|-------------|------------|----------|
+| claude-sonnet-4 | claude-sonnet-4-20250514 | claude-sonnet-4 | claude-4-sonnet |
+| gpt-4 | N/A | gpt-4 | gpt-4 |
+| gpt-4-turbo | N/A | gpt-4-turbo | gpt-4-turbo |
+
+---
+
+## Configuration File Paths
+
+| Tool | Primary Config | Context/Skills | Agents |
+|------|----------------|----------------|--------|
+| **OAC** | .opencode/agent/ | .opencode/context/ | .opencode/agent/*.md |
+| **Claude** | .claude/config.json | .claude/skills/ | .claude/agents/ |
+| **Cursor** | .cursorrules | (inline) | Single file only |
+| **Windsurf** | .windsurf/config.json | .windsurf/context/ | .windsurf/agents/ |
+
+---
+
+## Conversion Compatibility
+
+### OAC → Claude Code
+- ✅ Full feature preservation
+- ⚠️ Temperature ignored (not supported)
+- ✅ Hooks preserved
+- ✅ Skills map 1:1
+
+### OAC → Cursor IDE
+- ⚠️ Multiple agents → merged single file
+- ❌ Hooks lost
+- ❌ Skills → inline context
+- ⚠️ Granular permissions → binary
+
+### OAC → Windsurf
+- ✅ Multiple agents preserved
+- ⚠️ Temperature → creativity setting
+- ❌ Hooks lost
+- ⚠️ Skills → partial mapping
+
+### Reverse Conversions
+- Claude → OAC: ✅ High fidelity
+- Cursor → OAC: ⚠️ Limited (single agent)
+- Windsurf → OAC: ⚠️ Partial feature loss
+
+---
+
+## Reference
+
+- **Implementation**: `packages/compatibility-layer/src/mappers/`
+- **Related**:
+ - concepts/compatibility-layer.md
+ - examples/baseadapter-pattern.md
+ - guides/compatibility-layer-workflow.md
diff --git a/.opencode/context/openagents-repo/navigation.md b/.opencode/context/openagents-repo/navigation.md
index 09831fa..8e2e02c 100644
--- a/.opencode/context/openagents-repo/navigation.md
+++ b/.opencode/context/openagents-repo/navigation.md
@@ -2,7 +2,7 @@
**Purpose**: Context files specific to the OpenAgents Control repository
-**Last Updated**: 2026-01-31
+**Last Updated**: 2026-02-04
---
@@ -11,11 +11,11 @@
| Function | Files | Purpose |
|----------|-------|---------|
| **Standards** | 2 files | Agent creation standards |
-| **Concepts** | 2 files | Core ideas and principles |
-| **Examples** | 1 file | Working code samples |
-| **Guides** | 10 files | Step-by-step workflows |
-| **Lookup** | 4 files | Quick reference tables |
-| **Errors** | 1 file | Common issues + solutions |
+| **Concepts** | 6 files | Core ideas and principles |
+| **Examples** | 9 files | Working code samples |
+| **Guides** | 14 files | Step-by-step workflows |
+| **Lookup** | 11 files | Quick reference tables |
+| **Errors** | 2 files | Common issues + solutions |
---
@@ -34,9 +34,13 @@
| File | Topic | Priority |
|------|-------|----------|
+| `concepts/compatibility-layer.md` | Adapter pattern for AI coding tools | ⭐⭐⭐⭐⭐ |
| `concepts/subagent-testing-modes.md` | Standalone vs delegation testing | ⭐⭐⭐⭐⭐ |
+| `concepts/hooks-system.md` | User-defined lifecycle commands | ⭐⭐⭐⭐ |
+| `concepts/agent-skills.md` | Skills that teach Claude tasks | ⭐⭐⭐⭐ |
+| `concepts/subagents-system.md` | Specialized AI assistants | ⭐⭐⭐⭐ |
-**When to read**: Before testing any subagent
+**When to read**: Before testing any subagent or working with tool adapters
---
@@ -44,9 +48,11 @@
| File | Topic | Priority |
|------|-------|----------|
+| `examples/baseadapter-pattern.md` | Template Method pattern for tool adapters | ⭐⭐⭐⭐⭐ |
+| `examples/zod-schema-migration.md` | Migrating TypeScript to Zod schemas | ⭐⭐⭐⭐ |
| `examples/subagent-prompt-structure.md` | Optimized subagent prompt template | ⭐⭐⭐⭐ |
-**When to read**: When creating or optimizing subagent prompts
+**When to read**: When creating adapters, schemas, or optimizing subagent prompts
---
@@ -54,8 +60,13 @@
| File | Topic | Priority |
|------|-------|----------|
+| `guides/compatibility-layer-workflow.md` | Developing compatibility layer for AI tools | ⭐⭐⭐⭐⭐ |
| `guides/testing-subagents.md` | How to test subagents standalone | ⭐⭐⭐⭐⭐ |
-| `guides/adding-agent.md` | How to add new agents | ⭐⭐⭐⭐ |
+| `guides/adding-agent-basics.md` | How to add new agents (basics) | ⭐⭐⭐⭐ |
+| `guides/adding-agent-testing.md` | How to add agent tests | ⭐⭐⭐⭐ |
+| `guides/adding-skill-basics.md` | How to add OpenCode skills | ⭐⭐⭐⭐ |
+| `guides/creating-skills.md` | How to create Claude Code skills | ⭐⭐⭐⭐ |
+| `guides/creating-subagents.md` | How to create Claude Code subagents | ⭐⭐⭐⭐ |
| `guides/testing-agent.md` | How to test agents | ⭐⭐⭐⭐ |
| `guides/external-libraries-workflow.md` | How to handle external library dependencies | ⭐⭐⭐⭐ |
| `guides/github-issues-workflow.md` | How to work with GitHub issues and project board | ⭐⭐⭐⭐ |
@@ -73,11 +84,18 @@
| File | Topic | Priority |
|------|-------|----------|
+| `lookup/tool-feature-parity.md` | AI coding tool feature comparison | ⭐⭐⭐⭐⭐ |
+| `lookup/compatibility-layer-structure.md` | Compatibility package file structure | ⭐⭐⭐⭐⭐ |
| `lookup/subagent-test-commands.md` | Subagent testing commands | ⭐⭐⭐⭐⭐ |
+| `lookup/hook-events.md` | All hook events reference | ⭐⭐⭐⭐ |
+| `lookup/skill-metadata.md` | SKILL.md frontmatter fields | ⭐⭐⭐⭐ |
+| `lookup/skills-comparison.md` | Skills vs other options | ⭐⭐⭐⭐ |
+| `lookup/builtin-subagents.md` | Default subagents (Explore, Plan) | ⭐⭐⭐⭐ |
+| `lookup/subagent-frontmatter.md` | Subagent configuration fields | ⭐⭐⭐⭐ |
| `lookup/file-locations.md` | Where files are located | ⭐⭐⭐⭐ |
| `lookup/commands.md` | Available slash commands | ⭐⭐⭐ |
-**When to read**: Quick command lookups
+**When to read**: Quick command lookups and feature comparisons
---
@@ -86,6 +104,7 @@
| File | Topic | Priority |
|------|-------|----------|
| `errors/tool-permission-errors.md` | Tool permission issues | ⭐⭐⭐⭐⭐ |
+| `errors/skills-errors.md` | Skills not triggering/loading | ⭐⭐⭐⭐ |
**When to read**: When tests fail with permission errors
@@ -116,7 +135,7 @@
1. Load `standards/agent-frontmatter.md` (valid YAML frontmatter)
2. Load `standards/subagent-structure.md` (file structure)
3. Load `core-concepts/agents.md` (understand system)
-4. Load `guides/adding-agent.md` (step-by-step)
+4. Load `guides/adding-agent-basics.md` (step-by-step)
5. **If using external libraries**: Load `guides/external-libraries-workflow.md` (fetch docs)
6. Load `examples/subagent-prompt-structure.md` (if subagent)
7. Load `guides/testing-agent.md` (validate)
@@ -153,7 +172,7 @@ All files follow MVI principle (<200 lines):
- `../core/context-system/` - Context management system
- `quick-start.md` - 2-minute repo orientation
- `../content-creation/navigation.md` - Content creation principles
-- `../to-be-consumed/claude-code-docs/plugins.md` - Claude Code extension docs
+- `plugins/context/context-overview.md` - Plugin system context
---
diff --git a/.opencode/context/openagents-repo/plugins/context/concepts/plugin-architecture.md b/.opencode/context/openagents-repo/plugins/context/concepts/plugin-architecture.md
new file mode 100644
index 0000000..9461bdd
--- /dev/null
+++ b/.opencode/context/openagents-repo/plugins/context/concepts/plugin-architecture.md
@@ -0,0 +1,47 @@
+# Plugin Architecture
+
+**Purpose**: Extend Claude Code with slash commands, agents, Skills, hooks, and MCP servers
+
+---
+
+## Core Concept
+
+Plugins are directories containing a manifest (`.claude-plugin/plugin.json`) plus optional components (commands, agents, skills, hooks). They enable sharing functionality across projects and teams with namespaced commands.
+
+**Key Difference**:
+- **Standalone** (`.claude/`): Personal workflows, short names like `/hello`
+- **Plugins**: Shareable, versioned, namespaced like `/plugin-name:hello`
+
+---
+
+## Key Points
+
+- Plugin manifest at `.claude-plugin/plugin.json` defines identity (name, description, version)
+- Name field becomes namespace prefix for all slash commands
+- Components live at plugin root, NOT inside `.claude-plugin/`
+- Load with `--plugin-dir ./my-plugin` for testing
+- Multiple plugins can be loaded simultaneously
+
+---
+
+## Quick Example
+
+```json
+// .claude-plugin/plugin.json
+{
+ "name": "my-plugin",
+ "description": "A greeting plugin",
+ "version": "1.0.0",
+ "author": { "name": "Your Name" }
+}
+```
+
+---
+
+## Related
+
+- `../guides/creating-plugins.md` - How to create plugins
+- `../lookup/plugin-structure.md` - Directory structure
+- `../../guides/adding-skill.md` - Adding skills to plugins
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/plugins
diff --git a/.opencode/context/openagents-repo/plugins/context/context-overview.md b/.opencode/context/openagents-repo/plugins/context/context-overview.md
index 2c8c26a..fe44734 100644
--- a/.opencode/context/openagents-repo/plugins/context/context-overview.md
+++ b/.opencode/context/openagents-repo/plugins/context/context-overview.md
@@ -20,6 +20,13 @@ Deep dives into specific plugin features.
Guidelines and troubleshooting.
- [Best Practices](./reference/best-practices.md): Message injection workarounds, security, and performance.
+### 🧩 Claude Code Plugins (External)
+Claude Code plugin system documentation (harvested from external docs).
+- [Concepts: Plugin Architecture](./concepts/plugin-architecture.md): Core concepts and structure
+- [Guides: Creating Plugins](./guides/creating-plugins.md): Step-by-step creation
+- [Guides: Migrating to Plugins](./guides/migrating-to-plugins.md): Convert standalone to plugin
+- [Lookup: Plugin Structure](./lookup/plugin-structure.md): Directory reference
+
## 🚀 How to use this library
If you are asking an AI to build a new feature:
1. **For a new tool**: Provide `architecture/overview.md` and `capabilities/tools.md`.
diff --git a/.opencode/context/openagents-repo/plugins/context/guides/creating-plugins.md b/.opencode/context/openagents-repo/plugins/context/guides/creating-plugins.md
new file mode 100644
index 0000000..484f513
--- /dev/null
+++ b/.opencode/context/openagents-repo/plugins/context/guides/creating-plugins.md
@@ -0,0 +1,61 @@
+# Creating Plugins
+
+**Purpose**: Step-by-step guide to create Claude Code plugins
+
+---
+
+## Quickstart Workflow
+
+1. **Create directory**: `mkdir my-plugin`
+2. **Create manifest**: `mkdir my-plugin/.claude-plugin`
+3. **Add plugin.json** with name, description, version
+4. **Add commands**: `mkdir my-plugin/commands` → add `.md` files
+5. **Test**: `claude --plugin-dir ./my-plugin`
+6. **Use**: `/my-plugin:command-name`
+
+---
+
+## Key Points
+
+- Filename becomes command name (e.g., `hello.md` → `/plugin:hello`)
+- Use `$ARGUMENTS` placeholder for user input
+- Use `$1`, `$2` for individual arguments
+- Restart Claude Code to pick up changes
+- Load multiple: `claude --plugin-dir ./one --plugin-dir ./two`
+
+---
+
+## Command File Format
+
+```markdown
+---
+description: Greet the user with a personalized message
+---
+
+# Hello Command
+
+Greet the user named "$ARGUMENTS" warmly.
+```
+
+---
+
+## Adding Components
+
+| Component | Location | Purpose |
+|-----------|----------|---------|
+| Commands | `commands/*.md` | Slash commands |
+| Agents | `agents/*.md` | Custom subagents |
+| Skills | `skills/*/SKILL.md` | Agent capabilities |
+| Hooks | `hooks/hooks.json` | Event handlers |
+| MCP | `.mcp.json` | External tools |
+| LSP | `.lsp.json` | Code intelligence |
+
+---
+
+## Related
+
+- `../concepts/plugin-architecture.md` - Architecture overview
+- `../lookup/plugin-structure.md` - Full directory structure
+- `./migrating-to-plugins.md` - Convert standalone to plugin
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/plugins
diff --git a/.opencode/context/openagents-repo/plugins/context/guides/migrating-to-plugins.md b/.opencode/context/openagents-repo/plugins/context/guides/migrating-to-plugins.md
new file mode 100644
index 0000000..2a8fe95
--- /dev/null
+++ b/.opencode/context/openagents-repo/plugins/context/guides/migrating-to-plugins.md
@@ -0,0 +1,65 @@
+# Migrating Standalone to Plugin
+
+**Purpose**: Convert `.claude/` configurations into shareable plugins
+
+---
+
+## Migration Steps
+
+1. **Create plugin structure**:
+ ```bash
+ mkdir -p my-plugin/.claude-plugin
+ ```
+
+2. **Create manifest** (`my-plugin/.claude-plugin/plugin.json`):
+ ```json
+ {
+ "name": "my-plugin",
+ "description": "Migrated from standalone",
+ "version": "1.0.0"
+ }
+ ```
+
+3. **Copy components**:
+ ```bash
+ cp -r .claude/commands my-plugin/
+ cp -r .claude/agents my-plugin/
+ cp -r .claude/skills my-plugin/
+ ```
+
+4. **Migrate hooks** (from `settings.json` to `hooks/hooks.json`):
+ ```bash
+ mkdir my-plugin/hooks
+ # Copy hooks object from settings.json
+ ```
+
+5. **Test**: `claude --plugin-dir ./my-plugin`
+
+---
+
+## What Changes
+
+| Standalone | Plugin |
+|------------|--------|
+| One project only | Shareable via marketplaces |
+| Files in `.claude/` | Files in `plugin-name/` |
+| Hooks in `settings.json` | Hooks in `hooks/hooks.json` |
+| Manual copy to share | Install with `/plugin install` |
+
+---
+
+## Key Points
+
+- Plugin commands are namespaced (`/plugin:cmd` vs `/cmd`)
+- Hook format is identical between settings.json and hooks.json
+- Remove original `.claude/` files after verifying migration
+- Plugin version takes precedence when loaded
+
+---
+
+## Related
+
+- `../lookup/plugin-structure.md` - Directory structure
+- `../concepts/plugin-architecture.md` - Architecture
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/plugins
diff --git a/.opencode/context/openagents-repo/plugins/context/lookup/plugin-structure.md b/.opencode/context/openagents-repo/plugins/context/lookup/plugin-structure.md
new file mode 100644
index 0000000..e436650
--- /dev/null
+++ b/.opencode/context/openagents-repo/plugins/context/lookup/plugin-structure.md
@@ -0,0 +1,62 @@
+# Plugin Directory Structure
+
+**Purpose**: Quick reference for plugin file organization
+
+---
+
+## Standard Layout
+
+```
+my-plugin/
+├── .claude-plugin/ # REQUIRED - manifest only
+│ └── plugin.json # Plugin identity
+│
+├── commands/ # Slash commands as .md files
+│ └── hello.md
+│
+├── agents/ # Custom subagent definitions
+│ └── reviewer.md
+│
+├── skills/ # Agent Skills
+│ └── code-review/
+│ └── SKILL.md
+│
+├── hooks/ # Event handlers
+│ └── hooks.json
+│
+├── .mcp.json # MCP server configs
+└── .lsp.json # LSP server configs
+```
+
+---
+
+## Critical Rules
+
+- Only `plugin.json` goes inside `.claude-plugin/`
+- All other directories at plugin root level
+- Never put commands/, agents/, skills/ inside .claude-plugin/
+
+---
+
+## Manifest Schema
+
+```json
+{
+ "name": "plugin-name", // Required: namespace prefix
+ "description": "What it does", // Required: shown in manager
+ "version": "1.0.0", // Required: semver
+ "author": { "name": "You" }, // Optional
+ "homepage": "https://...", // Optional
+ "repository": "https://...", // Optional
+ "license": "MIT" // Optional
+}
+```
+
+---
+
+## Related
+
+- `../concepts/plugin-architecture.md` - Architecture
+- `../guides/creating-plugins.md` - Creation guide
+
+**Reference**: https://docs.anthropic.com/en/docs/claude-code/plugins-reference
diff --git a/.opencode/context/to-be-consumed/claude-code-docs/agent-skills.md b/.opencode/context/to-be-consumed/claude-code-docs/agent-skills.md
deleted file mode 100644
index 1149ec1..0000000
--- a/.opencode/context/to-be-consumed/claude-code-docs/agent-skills.md
+++ /dev/null
@@ -1,565 +0,0 @@
-# Agent Skills
-
-> Create, manage, and share Skills to extend Claude's capabilities in Claude Code.
-
-This guide shows you how to create, use, and manage Agent Skills in Claude Code. For background on how Skills work across Claude products, see [What are Skills?](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview).
-
-A Skill is a markdown file that teaches Claude how to do something specific: reviewing PRs using your team's standards, generating commit messages in your preferred format, or querying your company's database schema. When you ask Claude something that matches a Skill's purpose, Claude automatically applies it.
-
-## Create your first Skill
-
-This example creates a personal Skill that teaches Claude to explain code using visual diagrams and analogies. Unlike Claude's default explanations, this Skill ensures every explanation includes an ASCII diagram and a real-world analogy.
-
-
-
- Before creating a Skill, see what Skills Claude already has access to:
-
- ```
- What Skills are available?
- ```
-
- Claude will list any Skills currently loaded. You may see none, or you may see Skills from plugins or your organization.
-
-
-
- Create a directory for the Skill in your personal Skills folder. Personal Skills are available across all your projects. (You can also create [project Skills](#where-skills-live) in `.claude/skills/` to share with your team.)
-
- ```bash theme={null}
- mkdir -p ~/.claude/skills/explaining-code
- ```
-
-
-
- Every Skill needs a `SKILL.md` file. The file starts with YAML metadata between `---` markers and must include a `name` and `description`, followed by Markdown instructions that Claude follows when the Skill is active.
-
- The `description` is especially important, because Claude uses it to decide when to apply the Skill.
-
- Create `~/.claude/skills/explaining-code/SKILL.md`:
-
- ```yaml theme={null}
- ---
- name: explaining-code
- description: Explains code with visual diagrams and analogies. Use when explaining how code works, teaching about a codebase, or when the user asks "how does this work?"
- ---
-
- When explaining code, always include:
-
- 1. **Start with an analogy**: Compare the code to something from everyday life
- 2. **Draw a diagram**: Use ASCII art to show the flow, structure, or relationships
- 3. **Walk through the code**: Explain step-by-step what happens
- 4. **Highlight a gotcha**: What's a common mistake or misconception?
-
- Keep explanations conversational. For complex concepts, use multiple analogies.
- ```
-
-
-
- Skills are automatically loaded when created or modified. Verify the Skill appears in the list:
-
- ```
- What Skills are available?
- ```
-
- You should see `explaining-code` in the list with its description.
-
-
-
- Open any file in your project and ask Claude a question that matches the Skill's description:
-
- ```
- How does this code work?
- ```
-
- Claude should ask to use the `explaining-code` Skill, then include an analogy and ASCII diagram in its explanation. If the Skill doesn't trigger, try rephrasing to include more keywords from the description, like "explain how this works."
-
-
-
-The rest of this guide covers how Skills work, configuration options, and troubleshooting.
-
-## How Skills work
-
-Skills are **model-invoked**: Claude decides which Skills to use based on your request. You don't need to explicitly call a Skill. Claude automatically applies relevant Skills when your request matches their description.
-
-When you send a request, Claude follows these steps to find and use relevant Skills:
-
-
-
- At startup, Claude loads only the name and description of each available Skill. This keeps startup fast while giving Claude enough context to know when each Skill might be relevant.
-
-
-
- When your request matches a Skill's description, Claude asks to use the Skill. You'll see a confirmation prompt before the full `SKILL.md` is loaded into context. Since Claude reads these descriptions to find relevant Skills, [write descriptions](#skill-not-triggering) that include keywords users would naturally say.
-
-
-
- Claude follows the Skill's instructions, loading referenced files or running bundled scripts as needed.
-
-
-
-### Where Skills live
-
-Where you store a Skill determines who can use it:
-
-| Location | Path | Applies to |
-| :--------- | :----------------------------------------------- | :-------------------------------- |
-| Enterprise | See [managed settings](/en/iam#managed-settings) | All users in your organization |
-| Personal | `~/.claude/skills/` | You, across all projects |
-| Project | `.claude/skills/` | Anyone working in this repository |
-| Plugin | Bundled with [plugins](/en/plugins) | Anyone with the plugin installed |
-
-If two Skills have the same name, the higher row wins: managed overrides personal, personal overrides project, and project overrides plugin.
-
-### When to use Skills versus other options
-
-Claude Code offers several ways to customize behavior. The key difference: **Skills are triggered automatically by Claude** based on your request, while slash commands require you to type `/command` explicitly.
-
-| Use this | When you want to... | When it runs |
-| :--------------------------------------- | :------------------------------------------------------------------------- | :----------------------------------------- |
-| **Skills** | Give Claude specialized knowledge (e.g., "review PRs using our standards") | Claude chooses when relevant |
-| **[Slash commands](/en/slash-commands)** | Create reusable prompts (e.g., `/deploy staging`) | You type `/command` to run it |
-| **[CLAUDE.md](/en/memory)** | Set project-wide instructions (e.g., "use TypeScript strict mode") | Loaded into every conversation |
-| **[Subagents](/en/sub-agents)** | Delegate tasks to a separate context with its own tools | Claude delegates, or you invoke explicitly |
-| **[Hooks](/en/hooks)** | Run scripts on events (e.g., lint on file save) | Fires on specific tool events |
-| **[MCP servers](/en/mcp)** | Connect Claude to external tools and data sources | Claude calls MCP tools as needed |
-
-**Skills vs. subagents**: Skills add knowledge to the current conversation. Subagents run in a separate context with their own tools. Use Skills for guidance and standards; use subagents when you need isolation or different tool access.
-
-**Skills vs. MCP**: Skills tell Claude *how* to use tools; MCP *provides* the tools. For example, an MCP server connects Claude to your database, while a Skill teaches Claude your data model and query patterns.
-
-
- For a deep dive into the architecture and real-world applications of Agent Skills, read [Equipping agents for the real world with Agent Skills](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills).
-
-
-## Configure Skills
-
-This section covers Skill file structure, supporting files, tool restrictions, and distribution options.
-
-### Write SKILL.md
-
-The `SKILL.md` file is the only required file in a Skill. It has two parts: YAML metadata (the section between `---` markers) at the top, and Markdown instructions that tell Claude how to use the Skill:
-
-```yaml theme={null}
----
-name: your-skill-name
-description: Brief description of what this Skill does and when to use it
----
-
-# Your Skill Name
-
-## Instructions
-Provide clear, step-by-step guidance for Claude.
-
-## Examples
-Show concrete examples of using this Skill.
-```
-
-#### Available metadata fields
-
-You can use the following fields in the YAML frontmatter:
-
-| Field | Required | Description |
-| :--------------- | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
-| `name` | Yes | Skill name. Must use lowercase letters, numbers, and hyphens only (max 64 characters). Should match the directory name. |
-| `description` | Yes | What the Skill does and when to use it (max 1024 characters). Claude uses this to decide when to apply the Skill. |
-| `allowed-tools` | No | Tools Claude can use without asking permission when this Skill is active. Supports comma-separated values or YAML-style lists. See [Restrict tool access](#restrict-tool-access-with-allowed-tools). |
-| `model` | No | [Model](https://docs.claude.com/en/docs/about-claude/models/overview) to use when this Skill is active (e.g., `claude-sonnet-4-20250514`). Defaults to the conversation's model. |
-| `context` | No | Set to `fork` to run the Skill in a forked sub-agent context with its own conversation history. |
-| `agent` | No | Specify which [agent type](/en/sub-agents#built-in-subagents) to use when `context: fork` is set (e.g., `Explore`, `Plan`, `general-purpose`, or a custom agent name from `.claude/agents/`). Defaults to `general-purpose` if not specified. Only applicable when combined with `context: fork`. |
-| `hooks` | No | Define hooks scoped to this Skill's lifecycle. Supports `PreToolUse`, `PostToolUse`, and `Stop` events. |
-| `user-invocable` | No | Controls whether the Skill appears in the slash command menu. Does not affect the [`Skill` tool](/en/slash-commands#skill-tool) or automatic discovery. Defaults to `true`. See [Control Skill visibility](#control-skill-visibility). |
-
-See the [best practices guide](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices) for complete authoring guidance including validation rules.
-
-### Update or delete a Skill
-
-To update a Skill, edit its `SKILL.md` file directly. To remove a Skill, delete its directory. Changes take effect immediately.
-
-### Add supporting files with progressive disclosure
-
-Skills share Claude's context window with conversation history, other Skills, and your request. To keep context focused, use **progressive disclosure**: put essential information in `SKILL.md` and detailed reference material in separate files that Claude reads only when needed.
-
-This approach lets you bundle comprehensive documentation, examples, and scripts without consuming context upfront. Claude loads additional files only when the task requires them.
-
-Keep `SKILL.md` under 500 lines for optimal performance. If your content exceeds this, split detailed reference material into separate files.
-
-#### Example: multi-file Skill structure
-
-Claude discovers supporting files through links in your `SKILL.md`. The following example shows a Skill with detailed documentation in separate files and utility scripts that Claude can execute without reading:
-
-```
-my-skill/
-├── SKILL.md (required - overview and navigation)
-├── reference.md (detailed API docs - loaded when needed)
-├── examples.md (usage examples - loaded when needed)
-└── scripts/
- └── helper.py (utility script - executed, not loaded)
-```
-
-The `SKILL.md` file references these supporting files so Claude knows they exist:
-
-````markdown theme={null}
-## Overview
-
-[Essential instructions here]
-
-## Additional resources
-
-- For complete API details, see [reference.md](reference.md)
-- For usage examples, see [examples.md](examples.md)
-
-## Utility scripts
-
-To validate input files, run the helper script. It checks for required fields and returns any validation errors:
-```bash
-python scripts/helper.py input.txt
-```
-````
-
-Keep references one level deep. Link directly from `SKILL.md` to reference files. Deeply nested references (file A links to file B which links to file C) may result in Claude partially reading files.
-
-**Bundle utility scripts for zero-context execution.** Scripts in your Skill directory can be executed without loading their contents into context. Claude runs the script and only the output consumes tokens. This is useful for:
-
-* Complex validation logic that would be verbose to describe in prose
-* Data processing that's more reliable as tested code than generated code
-* Operations that benefit from consistency across uses
-
-In `SKILL.md`, tell Claude to run the script rather than read it:
-
-```markdown theme={null}
-Run the validation script to check the form:
-python scripts/validate_form.py input.pdf
-```
-
-For complete guidance on structuring Skills, see the [best practices guide](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/best-practices#progressive-disclosure-patterns).
-
-### Restrict tool access with allowed-tools
-
-Use the `allowed-tools` frontmatter field to limit which tools Claude can use when a Skill is active. You can specify tools as a comma-separated string or a YAML list:
-
-```yaml theme={null}
----
-name: reading-files-safely
-description: Read files without making changes. Use when you need read-only file access.
-allowed-tools: Read, Grep, Glob
----
-```
-
-Or use YAML-style lists for better readability:
-
-```yaml theme={null}
----
-name: reading-files-safely
-description: Read files without making changes. Use when you need read-only file access.
-allowed-tools:
- - Read
- - Grep
- - Glob
----
-```
-
-When this Skill is active, Claude can only use the specified tools (Read, Grep, Glob) without needing to ask for permission. This is useful for:
-
-* Read-only Skills that shouldn't modify files
-* Skills with limited scope: for example, only data analysis, no file writing
-* Security-sensitive workflows where you want to restrict capabilities
-
-If `allowed-tools` is omitted, the Skill doesn't restrict tools. Claude uses its standard permission model and may ask you to approve tool usage.
-
-
- `allowed-tools` is only supported for Skills in Claude Code.
-
-
-### Run Skills in a forked context
-
-Use `context: fork` to run a Skill in an isolated sub-agent context with its own conversation history. This is useful for Skills that perform complex multi-step operations without cluttering the main conversation:
-
-```yaml theme={null}
----
-name: code-analysis
-description: Analyze code quality and generate detailed reports
-context: fork
----
-```
-
-### Define hooks for Skills
-
-Skills can define hooks that run during the Skill's lifecycle. Use the `hooks` field to specify `PreToolUse`, `PostToolUse`, or `Stop` handlers:
-
-```yaml theme={null}
----
-name: secure-operations
-description: Perform operations with additional security checks
-hooks:
- PreToolUse:
- - matcher: "Bash"
- hooks:
- - type: command
- command: "./scripts/security-check.sh $TOOL_INPUT"
- once: true
----
-```
-
-The `once: true` option runs the hook only once per session. After the first successful execution, the hook is removed.
-
-Hooks defined in a Skill are scoped to that Skill's execution and are automatically cleaned up when the Skill finishes.
-
-See [Hooks](/en/hooks) for the complete hook configuration format.
-
-### Control Skill visibility
-
-Skills can be invoked in three ways:
-
-1. **Manual invocation**: You type `/skill-name` in the prompt
-2. **Programmatic invocation**: Claude calls it via the [`Skill` tool](/en/slash-commands#skill-tool)
-3. **Automatic discovery**: Claude reads the Skill's description and loads it when relevant to the conversation
-
-The `user-invocable` field controls only manual invocation. When set to `false`, the Skill is hidden from the slash command menu but Claude can still invoke it programmatically or discover it automatically.
-
-To block programmatic invocation via the `Skill` tool, use `disable-model-invocation: true` instead.
-
-#### When to use each setting
-
-| Setting | Slash menu | `Skill` tool | Auto-discovery | Use case |
-| :------------------------------- | :--------- | :----------- | :------------- | :-------------------------------------------------------------- |
-| `user-invocable: true` (default) | Visible | Allowed | Yes | Skills you want users to invoke directly |
-| `user-invocable: false` | Hidden | Allowed | Yes | Skills that Claude can use but users shouldn't invoke manually |
-| `disable-model-invocation: true` | Visible | Blocked | Yes | Skills you want users to invoke but not Claude programmatically |
-
-#### Example: model-only Skill
-
-Set `user-invocable: false` to hide a Skill from the slash menu while still allowing Claude to invoke it programmatically:
-
-```yaml theme={null}
----
-name: internal-review-standards
-description: Apply internal code review standards when reviewing pull requests
-user-invocable: false
----
-```
-
-With this setting, users won't see the Skill in the `/` menu, but Claude can still invoke it via the `Skill` tool or discover it automatically based on context.
-
-### Skills and subagents
-
-There are two ways Skills and subagents can work together:
-
-#### Give a subagent access to Skills
-
-[Subagents](/en/sub-agents) do not automatically inherit Skills from the main conversation. To give a custom subagent access to specific Skills, list them in the subagent's `skills` field:
-
-```yaml theme={null}
-# .claude/agents/code-reviewer.md
----
-name: code-reviewer
-description: Review code for quality and best practices
-skills: pr-review, security-check
----
-```
-
-The full content of each listed Skill is injected into the subagent's context at startup, not just made available for invocation. If the `skills` field is omitted, no Skills are loaded for that subagent.
-
-
- Built-in agents (Explore, Plan, general-purpose) do not have access to your Skills. Only custom subagents you define in `.claude/agents/` with an explicit `skills` field can use Skills.
-
-
-#### Run a Skill in a subagent context
-
-Use `context: fork` and `agent` to run a Skill in a forked subagent with its own separate context. See [Run Skills in a forked context](#run-skills-in-a-forked-context) for details.
-
-### Distribute Skills
-
-You can share Skills in several ways:
-
-* **Project Skills**: Commit `.claude/skills/` to version control. Anyone who clones the repository gets the Skills.
-* **Plugins**: To share Skills across multiple repositories, create a `skills/` directory in your [plugin](/en/plugins) with Skill folders containing `SKILL.md` files. Distribute through a [plugin marketplace](/en/plugin-marketplaces).
-* **Managed**: Administrators can deploy Skills organization-wide through [managed settings](/en/iam#managed-settings). See [Where Skills live](#where-skills-live) for managed Skill paths.
-
-## Examples
-
-These examples show common Skill patterns, from minimal single-file Skills to multi-file Skills with supporting documentation and scripts.
-
-### Simple Skill (single file)
-
-A minimal Skill needs only a `SKILL.md` file with frontmatter and instructions. This example helps Claude generate commit messages by examining staged changes:
-
-```
-commit-helper/
-└── SKILL.md
-```
-
-```yaml theme={null}
----
-name: generating-commit-messages
-description: Generates clear commit messages from git diffs. Use when writing commit messages or reviewing staged changes.
----
-
-# Generating Commit Messages
-
-## Instructions
-
-1. Run `git diff --staged` to see changes
-2. I'll suggest a commit message with:
- - Summary under 50 characters
- - Detailed description
- - Affected components
-
-## Best practices
-
-- Use present tense
-- Explain what and why, not how
-```
-
-### Use multiple files
-
-For complex Skills, use progressive disclosure to keep the main `SKILL.md` focused while providing detailed documentation in supporting files. This PDF processing Skill includes reference docs, utility scripts, and uses `allowed-tools` to restrict Claude to specific tools:
-
-```
-pdf-processing/
-├── SKILL.md # Overview and quick start
-├── FORMS.md # Form field mappings and filling instructions
-├── REFERENCE.md # API details for pypdf and pdfplumber
-└── scripts/
- ├── fill_form.py # Utility to populate form fields
- └── validate.py # Checks PDFs for required fields
-```
-
-**`SKILL.md`**:
-
-````yaml theme={null}
----
-name: pdf-processing
-description: Extract text, fill forms, merge PDFs. Use when working with PDF files, forms, or document extraction. Requires pypdf and pdfplumber packages.
-allowed-tools: Read, Bash(python:*)
----
-
-# PDF Processing
-
-## Quick start
-
-Extract text:
-```python
-import pdfplumber
-with pdfplumber.open("doc.pdf") as pdf:
- text = pdf.pages[0].extract_text()
-```
-
-For form filling, see [FORMS.md](FORMS.md).
-For detailed API reference, see [REFERENCE.md](REFERENCE.md).
-
-## Requirements
-
-Packages must be installed in your environment:
-```bash
-pip install pypdf pdfplumber
-```
-````
-
-
- If your Skill requires external packages, list them in the description. Packages must be installed in your environment before Claude can use them.
-
-
-## Troubleshooting
-
-### View and test Skills
-
-To see which Skills Claude has access to, ask Claude a question like "What Skills are available?" Claude loads all available Skill names and descriptions into the context window when a conversation starts, so it can list the Skills it currently has access to.
-
-To test a specific Skill, ask Claude to do a task that matches the Skill's description. For example, if your Skill has the description "Reviews pull requests for code quality", ask Claude to "Review the changes in my current branch." Claude automatically uses the Skill when the request matches its description.
-
-### Skill not triggering
-
-The description field is how Claude decides whether to use your Skill. Vague descriptions like "Helps with documents" don't give Claude enough information to match your Skill to relevant requests.
-
-A good description answers two questions:
-
-1. **What does this Skill do?** List the specific capabilities.
-2. **When should Claude use it?** Include trigger terms users would mention.
-
-```yaml theme={null}
-description: Extract text and tables from PDF files, fill forms, merge documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
-```
-
-This description works because it names specific actions (extract, fill, merge) and includes keywords users would say (PDF, forms, document extraction).
-
-### Skill doesn't load
-
-**Check the file path.** Skills must be in the correct directory with the exact filename `SKILL.md` (case-sensitive):
-
-| Type | Path |
-| :--------- | :---------------------------------------------------------------------- |
-| Personal | `~/.claude/skills/my-skill/SKILL.md` |
-| Project | `.claude/skills/my-skill/SKILL.md` |
-| Enterprise | See [Where Skills live](#where-skills-live) for platform-specific paths |
-| Plugin | `skills/my-skill/SKILL.md` inside the plugin directory |
-
-**Check the YAML syntax.** Invalid YAML in the frontmatter prevents the Skill from loading. The frontmatter must start with `---` on line 1 (no blank lines before it), end with `---` before the Markdown content, and use spaces for indentation (not tabs).
-
-**Run debug mode.** Use `claude --debug` to see Skill loading errors.
-
-### Skill has errors
-
-**Check dependencies are installed.** If your Skill uses external packages, they must be installed in your environment before Claude can use them.
-
-**Check script permissions.** Scripts need execute permissions: `chmod +x scripts/*.py`
-
-**Check file paths.** Use forward slashes (Unix style) in all paths. Use `scripts/helper.py`, not `scripts\helper.py`.
-
-### Multiple Skills conflict
-
-If Claude uses the wrong Skill or seems confused between similar Skills, the descriptions are probably too similar. Make each description distinct by using specific trigger terms.
-
-For example, instead of two Skills with "data analysis" in both descriptions, differentiate them: one for "sales data in Excel files and CRM exports" and another for "log files and system metrics". The more specific your trigger terms, the easier it is for Claude to match the right Skill to your request.
-
-### Plugin Skills not appearing
-
-**Symptom**: You installed a plugin from a marketplace, but its Skills don't appear when you ask Claude "What Skills are available?"
-
-**Solution**: Clear the plugin cache and reinstall:
-
-```bash theme={null}
-rm -rf ~/.claude/plugins/cache
-```
-
-Then restart Claude Code and reinstall the plugin:
-
-```shell theme={null}
-/plugin install plugin-name@marketplace-name
-```
-
-This forces Claude Code to re-download and re-register the plugin's Skills.
-
-**If Skills still don't appear**, verify the plugin's directory structure is correct. Skills must be in a `skills/` directory at the plugin root:
-
-```
-my-plugin/
-├── .claude-plugin/
-│ └── plugin.json
-└── skills/
- └── my-skill/
- └── SKILL.md
-```
-
-## Next steps
-
-
-
- Write Skills that Claude can use effectively
-
-
-
- Learn how Skills work across Claude products
-
-
-
- Use Skills programmatically with TypeScript and Python
-
-
-
- Create your first Skill
-
-
-
-
----
-
-> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt
\ No newline at end of file
diff --git a/.opencode/context/to-be-consumed/claude-code-docs/create-subagents.md b/.opencode/context/to-be-consumed/claude-code-docs/create-subagents.md
deleted file mode 100644
index e460e9b..0000000
--- a/.opencode/context/to-be-consumed/claude-code-docs/create-subagents.md
+++ /dev/null
@@ -1,716 +0,0 @@
-# Create custom subagents
-
-> Create and use specialized AI subagents in Claude Code for task-specific workflows and improved context management.
-
-Subagents are specialized AI assistants that handle specific types of tasks. Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions. When Claude encounters a task that matches a subagent's description, it delegates to that subagent, which works independently and returns results.
-
-Subagents help you:
-
-* **Preserve context** by keeping exploration and implementation out of your main conversation
-* **Enforce constraints** by limiting which tools a subagent can use
-* **Reuse configurations** across projects with user-level subagents
-* **Specialize behavior** with focused system prompts for specific domains
-* **Control costs** by routing tasks to faster, cheaper models like Haiku
-
-Claude uses each subagent's description to decide when to delegate tasks. When you create a subagent, write a clear description so Claude knows when to use it.
-
-Claude Code includes several built-in subagents like **Explore**, **Plan**, and **general-purpose**. You can also create custom subagents to handle specific tasks. This page covers the [built-in subagents](#built-in-subagents), [how to create your own](#quickstart-create-your-first-subagent), [full configuration options](#configure-subagents), [patterns for working with subagents](#work-with-subagents), and [example subagents](#example-subagents).
-
-## Built-in subagents
-
-Claude Code includes built-in subagents that Claude automatically uses when appropriate. Each inherits the parent conversation's permissions with additional tool restrictions.
-
-
-
- A fast, read-only agent optimized for searching and analyzing codebases.
-
- * **Model**: Haiku (fast, low-latency)
- * **Tools**: Read-only tools (denied access to Write and Edit tools)
- * **Purpose**: File discovery, code search, codebase exploration
-
- Claude delegates to Explore when it needs to search or understand a codebase without making changes. This keeps exploration results out of your main conversation context.
-
- When invoking Explore, Claude specifies a thoroughness level: **quick** for targeted lookups, **medium** for balanced exploration, or **very thorough** for comprehensive analysis.
-
-
-
- A research agent used during [plan mode](/en/common-workflows#use-plan-mode-for-safe-code-analysis) to gather context before presenting a plan.
-
- * **Model**: Inherits from main conversation
- * **Tools**: Read-only tools (denied access to Write and Edit tools)
- * **Purpose**: Codebase research for planning
-
- When you're in plan mode and Claude needs to understand your codebase, it delegates research to the Plan subagent. This prevents infinite nesting (subagents cannot spawn other subagents) while still gathering necessary context.
-
-
-
- A capable agent for complex, multi-step tasks that require both exploration and action.
-
- * **Model**: Inherits from main conversation
- * **Tools**: All tools
- * **Purpose**: Complex research, multi-step operations, code modifications
-
- Claude delegates to general-purpose when the task requires both exploration and modification, complex reasoning to interpret results, or multiple dependent steps.
-
-
-
- Claude Code includes additional helper agents for specific tasks. These are typically invoked automatically, so you don't need to use them directly.
-
- | Agent | Model | When Claude uses it |
- | :---------------- | :------- | :------------------------------------------------------- |
- | Bash | Inherits | Running terminal commands in a separate context |
- | statusline-setup | Sonnet | When you run `/statusline` to configure your status line |
- | Claude Code Guide | Haiku | When you ask questions about Claude Code features |
-
-
-
-Beyond these built-in subagents, you can create your own with custom prompts, tool restrictions, permission modes, hooks, and skills. The following sections show how to get started and customize subagents.
-
-## Quickstart: create your first subagent
-
-Subagents are defined in Markdown files with YAML frontmatter. You can [create them manually](#write-subagent-files) or use the `/agents` slash command.
-
-This walkthrough guides you through creating a user-level subagent with the `/agent` command. The subagent reviews code and suggests improvements for the codebase.
-
-
-
- In Claude Code, run:
-
- ```
- /agents
- ```
-
-
-
- Select **Create new agent**, then choose **User-level**. This saves the subagent to `~/.claude/agents/` so it's available in all your projects.
-
-
-
- Select **Generate with Claude**. When prompted, describe the subagent:
-
- ```
- A code improvement agent that scans files and suggests improvements
- for readability, performance, and best practices. It should explain
- each issue, show the current code, and provide an improved version.
- ```
-
- Claude generates the system prompt and configuration. Press `e` to open it in your editor if you want to customize it.
-
-
-
- For a read-only reviewer, deselect everything except **Read-only tools**. If you keep all tools selected, the subagent inherits all tools available to the main conversation.
-
-
-
- Choose which model the subagent uses. For this example agent, select **Sonnet**, which balances capability and speed for analyzing code patterns.
-
-
-
- Pick a background color for the subagent. This helps you identify which subagent is running in the UI.
-
-
-
- Save the subagent. It's available immediately (no restart needed). Try it:
-
- ```
- Use the code-improver agent to suggest improvements in this project
- ```
-
- Claude delegates to your new subagent, which scans the codebase and returns improvement suggestions.
-
-
-
-You now have a subagent you can use in any project on your machine to analyze codebases and suggest improvements.
-
-You can also create subagents manually as Markdown files, define them via CLI flags, or distribute them through plugins. The following sections cover all configuration options.
-
-## Configure subagents
-
-### Use the /agents command
-
-The `/agents` command provides an interactive interface for managing subagents. Run `/agents` to:
-
-* View all available subagents (built-in, user, project, and plugin)
-* Create new subagents with guided setup or Claude generation
-* Edit existing subagent configuration and tool access
-* Delete custom subagents
-* See which subagents are active when duplicates exist
-
-This is the recommended way to create and manage subagents. For manual creation or automation, you can also add subagent files directly.
-
-### Choose the subagent scope
-
-Subagents are Markdown files with YAML frontmatter. Store them in different locations depending on scope. When multiple subagents share the same name, the higher-priority location wins.
-
-| Location | Scope | Priority | How to create |
-| :--------------------------- | :---------------------- | :---------- | :------------------------------------ |
-| `--agents` CLI flag | Current session | 1 (highest) | Pass JSON when launching Claude Code |
-| `.claude/agents/` | Current project | 2 | Interactive or manual |
-| `~/.claude/agents/` | All your projects | 3 | Interactive or manual |
-| Plugin's `agents/` directory | Where plugin is enabled | 4 (lowest) | Installed with [plugins](/en/plugins) |
-
-**Project subagents** (`.claude/agents/`) are ideal for subagents specific to a codebase. Check them into version control so your team can use and improve them collaboratively.
-
-**User subagents** (`~/.claude/agents/`) are personal subagents available in all your projects.
-
-**CLI-defined subagents** are passed as JSON when launching Claude Code. They exist only for that session and aren't saved to disk, making them useful for quick testing or automation scripts:
-
-```bash theme={null}
-claude --agents '{
- "code-reviewer": {
- "description": "Expert code reviewer. Use proactively after code changes.",
- "prompt": "You are a senior code reviewer. Focus on code quality, security, and best practices.",
- "tools": ["Read", "Grep", "Glob", "Bash"],
- "model": "sonnet"
- }
-}'
-```
-
-The `--agents` flag accepts JSON with the same fields as [frontmatter](#supported-frontmatter-fields). Use `prompt` for the system prompt (equivalent to the markdown body in file-based subagents). See the [CLI reference](/en/cli-reference#agents-flag-format) for the full JSON format.
-
-**Plugin subagents** come from [plugins](/en/plugins) you've installed. They appear in `/agents` alongside your custom subagents. See the [plugin components reference](/en/plugins-reference#agents) for details on creating plugin subagents.
-
-### Write subagent files
-
-Subagent files use YAML frontmatter for configuration, followed by the system prompt in Markdown:
-
-
- Subagents are loaded at session start. If you create a subagent by manually adding a file, restart your session or use `/agents` to load it immediately.
-
-
-```markdown theme={null}
----
-name: code-reviewer
-description: Reviews code for quality and best practices
-tools: Read, Glob, Grep
-model: sonnet
----
-
-You are a code reviewer. When invoked, analyze the code and provide
-specific, actionable feedback on quality, security, and best practices.
-```
-
-The frontmatter defines the subagent's metadata and configuration. The body becomes the system prompt that guides the subagent's behavior. Subagents receive only this system prompt (plus basic environment details like working directory), not the full Claude Code system prompt.
-
-#### Supported frontmatter fields
-
-The following fields can be used in the YAML frontmatter. Only `name` and `description` are required.
-
-| Field | Required | Description |
-| :---------------- | :------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `name` | Yes | Unique identifier using lowercase letters and hyphens |
-| `description` | Yes | When Claude should delegate to this subagent |
-| `tools` | No | [Tools](#available-tools) the subagent can use. Inherits all tools if omitted |
-| `disallowedTools` | No | Tools to deny, removed from inherited or specified list |
-| `model` | No | [Model](#choose-a-model) to use: `sonnet`, `opus`, `haiku`, or `inherit`. Defaults to `sonnet` |
-| `permissionMode` | No | [Permission mode](#permission-modes): `default`, `acceptEdits`, `dontAsk`, `bypassPermissions`, or `plan` |
-| `skills` | No | [Skills](/en/skills) to load into the subagent's context at startup. The full skill content is injected, not just made available for invocation. Subagents don't inherit skills from the parent conversation |
-| `hooks` | No | [Lifecycle hooks](#define-hooks-for-subagents) scoped to this subagent |
-
-### Choose a model
-
-The `model` field controls which [AI model](/en/model-config) the subagent uses:
-
-* **Model alias**: Use one of the available aliases: `sonnet`, `opus`, or `haiku`
-* **inherit**: Use the same model as the main conversation (useful for consistency)
-* **Omitted**: If not specified, uses the default model configured for subagents (`sonnet`)
-
-### Control subagent capabilities
-
-You can control what subagents can do through tool access, permission modes, and conditional rules.
-
-#### Available tools
-
-Subagents can use any of Claude Code's [internal tools](/en/settings#tools-available-to-claude). By default, subagents inherit all tools from the main conversation, including MCP tools.
-
-To restrict tools, use the `tools` field (allowlist) or `disallowedTools` field (denylist):
-
-```yaml theme={null}
----
-name: safe-researcher
-description: Research agent with restricted capabilities
-tools: Read, Grep, Glob, Bash
-disallowedTools: Write, Edit
----
-```
-
-#### Permission modes
-
-The `permissionMode` field controls how the subagent handles permission prompts. Subagents inherit the permission context from the main conversation but can override the mode.
-
-| Mode | Behavior |
-| :------------------ | :----------------------------------------------------------------- |
-| `default` | Standard permission checking with prompts |
-| `acceptEdits` | Auto-accept file edits |
-| `dontAsk` | Auto-deny permission prompts (explicitly allowed tools still work) |
-| `bypassPermissions` | Skip all permission checks |
-| `plan` | Plan mode (read-only exploration) |
-
-
- Use `bypassPermissions` with caution. It skips all permission checks, allowing the subagent to execute any operation without approval.
-
-
-If the parent uses `bypassPermissions`, this takes precedence and cannot be overridden.
-
-#### Conditional rules with hooks
-
-For more dynamic control over tool usage, use `PreToolUse` hooks to validate operations before they execute. This is useful when you need to allow some operations of a tool while blocking others.
-
-This example creates a subagent that only allows read-only database queries. The `PreToolUse` hook runs the script specified in `command` before each Bash command executes:
-
-```yaml theme={null}
----
-name: db-reader
-description: Execute read-only database queries
-tools: Bash
-hooks:
- PreToolUse:
- - matcher: "Bash"
- hooks:
- - type: command
- command: "./scripts/validate-readonly-query.sh"
----
-```
-
-Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the Bash command, and [exits with code 2](/en/hooks#exit-code-2-behavior) to block write operations:
-
-```bash theme={null}
-#!/bin/bash
-# ./scripts/validate-readonly-query.sh
-
-INPUT=$(cat)
-COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
-
-# Block SQL write operations (case-insensitive)
-if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE)\b' > /dev/null; then
- echo "Blocked: Only SELECT queries are allowed" >&2
- exit 2
-fi
-
-exit 0
-```
-
-See [Hook input](/en/hooks#pretooluse-input) for the complete input schema and [exit codes](/en/hooks#exit-codes) for how exit codes affect behavior.
-
-#### Disable specific subagents
-
-You can prevent Claude from using specific subagents by adding them to the `deny` array in your [settings](/en/settings#permission-settings). Use the format `Task(subagent-name)` where `subagent-name` matches the subagent's name field.
-
-```json theme={null}
-{
- "permissions": {
- "deny": ["Task(Explore)", "Task(my-custom-agent)"]
- }
-}
-```
-
-This works for both built-in and custom subagents. You can also use the `--disallowedTools` CLI flag:
-
-```bash theme={null}
-claude --disallowedTools "Task(Explore)"
-```
-
-See [IAM documentation](/en/iam#tool-specific-permission-rules) for more details on permission rules.
-
-### Define hooks for subagents
-
-Subagents can define [hooks](/en/hooks) that run during the subagent's lifecycle. There are two ways to configure hooks:
-
-1. **In the subagent's frontmatter**: Define hooks that run only while that subagent is active
-2. **In `settings.json`**: Define hooks that run in the main session when subagents start or stop
-
-#### Hooks in subagent frontmatter
-
-Define hooks directly in the subagent's markdown file. These hooks only run while that specific subagent is active and are cleaned up when it finishes.
-
-| Event | Matcher input | When it fires |
-| :------------ | :------------ | :------------------------------ |
-| `PreToolUse` | Tool name | Before the subagent uses a tool |
-| `PostToolUse` | Tool name | After the subagent uses a tool |
-| `Stop` | (none) | When the subagent finishes |
-
-This example validates Bash commands with the `PreToolUse` hook and runs a linter after file edits with `PostToolUse`:
-
-```yaml theme={null}
----
-name: code-reviewer
-description: Review code changes with automatic linting
-hooks:
- PreToolUse:
- - matcher: "Bash"
- hooks:
- - type: command
- command: "./scripts/validate-command.sh $TOOL_INPUT"
- PostToolUse:
- - matcher: "Edit|Write"
- hooks:
- - type: command
- command: "./scripts/run-linter.sh"
----
-```
-
-`Stop` hooks in frontmatter are automatically converted to `SubagentStop` events.
-
-#### Project-level hooks for subagent events
-
-Configure hooks in `settings.json` that respond to subagent lifecycle events in the main session. Use the `matcher` field to target specific agent types by name.
-
-| Event | Matcher input | When it fires |
-| :-------------- | :-------------- | :------------------------------- |
-| `SubagentStart` | Agent type name | When a subagent begins execution |
-| `SubagentStop` | Agent type name | When a subagent completes |
-
-This example runs setup and cleanup scripts only when the `db-agent` subagent starts and stops:
-
-```json theme={null}
-{
- "hooks": {
- "SubagentStart": [
- {
- "matcher": "db-agent",
- "hooks": [
- { "type": "command", "command": "./scripts/setup-db-connection.sh" }
- ]
- }
- ],
- "SubagentStop": [
- {
- "matcher": "db-agent",
- "hooks": [
- { "type": "command", "command": "./scripts/cleanup-db-connection.sh" }
- ]
- }
- ]
- }
-}
-```
-
-See [Hooks](/en/hooks) for the complete hook configuration format.
-
-## Work with subagents
-
-### Understand automatic delegation
-
-Claude automatically delegates tasks based on the task description in your request, the `description` field in subagent configurations, and current context. To encourage proactive delegation, include phrases like "use proactively" in your subagent's description field.
-
-You can also request a specific subagent explicitly:
-
-```
-Use the test-runner subagent to fix failing tests
-Have the code-reviewer subagent look at my recent changes
-```
-
-### Run subagents in foreground or background
-
-Subagents can run in the foreground (blocking) or background (concurrent):
-
-* **Foreground subagents** block the main conversation until complete. Permission prompts and clarifying questions (like [`AskUserQuestion`](/en/settings#tools-available-to-claude)) are passed through to you.
-* **Background subagents** run concurrently while you continue working. They inherit the parent's permissions and auto-deny anything not pre-approved. If a background subagent needs a permission it doesn't have or needs to ask clarifying questions, that tool call fails but the subagent continues. MCP tools are not available in background subagents.
-
-If a background subagent fails due to missing permissions, you can [resume it](#resume-subagents) in the foreground to retry with interactive prompts.
-
-Claude decides whether to run subagents in the foreground or background based on the task. You can also:
-
-* Ask Claude to "run this in the background"
-* Press **Ctrl+B** to background a running task
-
-To disable all background task functionality, set the `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS` environment variable to `1`. See [Environment variables](/en/settings#environment-variables).
-
-### Common patterns
-
-#### Isolate high-volume operations
-
-One of the most effective uses for subagents is isolating operations that produce large amounts of output. Running tests, fetching documentation, or processing log files can consume significant context. By delegating these to a subagent, the verbose output stays in the subagent's context while only the relevant summary returns to your main conversation.
-
-```
-Use a subagent to run the test suite and report only the failing tests with their error messages
-```
-
-#### Run parallel research
-
-For independent investigations, spawn multiple subagents to work simultaneously:
-
-```
-Research the authentication, database, and API modules in parallel using separate subagents
-```
-
-Each subagent explores its area independently, then Claude synthesizes the findings. This works best when the research paths don't depend on each other.
-
-
- When subagents complete, their results return to your main conversation. Running many subagents that each return detailed results can consume significant context.
-
-
-#### Chain subagents
-
-For multi-step workflows, ask Claude to use subagents in sequence. Each subagent completes its task and returns results to Claude, which then passes relevant context to the next subagent.
-
-```
-Use the code-reviewer subagent to find performance issues, then use the optimizer subagent to fix them
-```
-
-### Choose between subagents and main conversation
-
-Use the **main conversation** when:
-
-* The task needs frequent back-and-forth or iterative refinement
-* Multiple phases share significant context (planning → implementation → testing)
-* You're making a quick, targeted change
-* Latency matters. Subagents start fresh and may need time to gather context
-
-Use **subagents** when:
-
-* The task produces verbose output you don't need in your main context
-* You want to enforce specific tool restrictions or permissions
-* The work is self-contained and can return a summary
-
-Consider [Skills](/en/skills) instead when you want reusable prompts or workflows that run in the main conversation context rather than isolated subagent context.
-
-
- Subagents cannot spawn other subagents. If your workflow requires nested delegation, use [Skills](/en/skills) or [chain subagents](#chain-subagents) from the main conversation.
-
-
-### Manage subagent context
-
-#### Resume subagents
-
-Each subagent invocation creates a new instance with fresh context. To continue an existing subagent's work instead of starting over, ask Claude to resume it.
-
-Resumed subagents retain their full conversation history, including all previous tool calls, results, and reasoning. The subagent picks up exactly where it stopped rather than starting fresh.
-
-When a subagent completes, Claude receives its agent ID. To resume a subagent, ask Claude to continue the previous work:
-
-```
-Use the code-reviewer subagent to review the authentication module
-[Agent completes]
-
-Continue that code review and now analyze the authorization logic
-[Claude resumes the subagent with full context from previous conversation]
-```
-
-You can also ask Claude for the agent ID if you want to reference it explicitly, or find IDs in the transcript files at `~/.claude/projects/{project}/{sessionId}/subagents/`. Each transcript is stored as `agent-{agentId}.jsonl`.
-
-For programmatic usage, see [Subagents in the Agent SDK](/en/agent-sdk/subagents).
-
-Subagent transcripts persist independently of the main conversation:
-
-* **Main conversation compaction**: When the main conversation compacts, subagent transcripts are unaffected. They're stored in separate files.
-* **Session persistence**: Subagent transcripts persist within their session. You can [resume a subagent](#resume-subagents) after restarting Claude Code by resuming the same session.
-* **Automatic cleanup**: Transcripts are cleaned up based on the `cleanupPeriodDays` setting (default: 30 days).
-
-#### Auto-compaction
-
-Subagents support automatic compaction using the same logic as the main conversation. When a subagent's context approaches its limit, Claude Code summarizes older messages to free up space while preserving important context.
-
-Compaction events are logged in subagent transcript files:
-
-```json theme={null}
-{
- "type": "system",
- "subtype": "compact_boundary",
- "compactMetadata": {
- "trigger": "auto",
- "preTokens": 167189
- }
-}
-```
-
-The `preTokens` value shows how many tokens were used before compaction occurred.
-
-## Example subagents
-
-These examples demonstrate effective patterns for building subagents. Use them as starting points, or generate a customized version with Claude.
-
-
- **Best practices:**
-
- * **Design focused subagents:** each subagent should excel at one specific task
- * **Write detailed descriptions:** Claude uses the description to decide when to delegate
- * **Limit tool access:** grant only necessary permissions for security and focus
- * **Check into version control:** share project subagents with your team
-
-
-### Code reviewer
-
-A read-only subagent that reviews code without modifying it. This example shows how to design a focused subagent with limited tool access (no Edit or Write) and a detailed prompt that specifies exactly what to look for and how to format output.
-
-```markdown theme={null}
----
-name: code-reviewer
-description: Expert code review specialist. Proactively reviews code for quality, security, and maintainability. Use immediately after writing or modifying code.
-tools: Read, Grep, Glob, Bash
-model: inherit
----
-
-You are a senior code reviewer ensuring high standards of code quality and security.
-
-When invoked:
-1. Run git diff to see recent changes
-2. Focus on modified files
-3. Begin review immediately
-
-Review checklist:
-- Code is clear and readable
-- Functions and variables are well-named
-- No duplicated code
-- Proper error handling
-- No exposed secrets or API keys
-- Input validation implemented
-- Good test coverage
-- Performance considerations addressed
-
-Provide feedback organized by priority:
-- Critical issues (must fix)
-- Warnings (should fix)
-- Suggestions (consider improving)
-
-Include specific examples of how to fix issues.
-```
-
-### Debugger
-
-A subagent that can both analyze and fix issues. Unlike the code reviewer, this one includes Edit because fixing bugs requires modifying code. The prompt provides a clear workflow from diagnosis to verification.
-
-```markdown theme={null}
----
-name: debugger
-description: Debugging specialist for errors, test failures, and unexpected behavior. Use proactively when encountering any issues.
-tools: Read, Edit, Bash, Grep, Glob
----
-
-You are an expert debugger specializing in root cause analysis.
-
-When invoked:
-1. Capture error message and stack trace
-2. Identify reproduction steps
-3. Isolate the failure location
-4. Implement minimal fix
-5. Verify solution works
-
-Debugging process:
-- Analyze error messages and logs
-- Check recent code changes
-- Form and test hypotheses
-- Add strategic debug logging
-- Inspect variable states
-
-For each issue, provide:
-- Root cause explanation
-- Evidence supporting the diagnosis
-- Specific code fix
-- Testing approach
-- Prevention recommendations
-
-Focus on fixing the underlying issue, not the symptoms.
-```
-
-### Data scientist
-
-A domain-specific subagent for data analysis work. This example shows how to create subagents for specialized workflows outside of typical coding tasks. It explicitly sets `model: sonnet` for more capable analysis.
-
-```markdown theme={null}
----
-name: data-scientist
-description: Data analysis expert for SQL queries, BigQuery operations, and data insights. Use proactively for data analysis tasks and queries.
-tools: Bash, Read, Write
-model: sonnet
----
-
-You are a data scientist specializing in SQL and BigQuery analysis.
-
-When invoked:
-1. Understand the data analysis requirement
-2. Write efficient SQL queries
-3. Use BigQuery command line tools (bq) when appropriate
-4. Analyze and summarize results
-5. Present findings clearly
-
-Key practices:
-- Write optimized SQL queries with proper filters
-- Use appropriate aggregations and joins
-- Include comments explaining complex logic
-- Format results for readability
-- Provide data-driven recommendations
-
-For each analysis:
-- Explain the query approach
-- Document any assumptions
-- Highlight key findings
-- Suggest next steps based on data
-
-Always ensure queries are efficient and cost-effective.
-```
-
-### Database query validator
-
-A subagent that allows Bash access but validates commands to permit only read-only SQL queries. This example shows how to use `PreToolUse` hooks for conditional validation when you need finer control than the `tools` field provides.
-
-```markdown theme={null}
----
-name: db-reader
-description: Execute read-only database queries. Use when analyzing data or generating reports.
-tools: Bash
-hooks:
- PreToolUse:
- - matcher: "Bash"
- hooks:
- - type: command
- command: "./scripts/validate-readonly-query.sh"
----
-
-You are a database analyst with read-only access. Execute SELECT queries to answer questions about the data.
-
-When asked to analyze data:
-1. Identify which tables contain the relevant data
-2. Write efficient SELECT queries with appropriate filters
-3. Present results clearly with context
-
-You cannot modify data. If asked to INSERT, UPDATE, DELETE, or modify schema, explain that you only have read access.
-```
-
-Claude Code [passes hook input as JSON](/en/hooks#pretooluse-input) via stdin to hook commands. The validation script reads this JSON, extracts the command being executed, and checks it against a list of SQL write operations. If a write operation is detected, the script [exits with code 2](/en/hooks#exit-code-2-behavior) to block execution and returns an error message to Claude via stderr.
-
-Create the validation script anywhere in your project. The path must match the `command` field in your hook configuration:
-
-```bash theme={null}
-#!/bin/bash
-# Blocks SQL write operations, allows SELECT queries
-
-# Read JSON input from stdin
-INPUT=$(cat)
-
-# Extract the command field from tool_input using jq
-COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
-
-if [ -z "$COMMAND" ]; then
- exit 0
-fi
-
-# Block write operations (case-insensitive)
-if echo "$COMMAND" | grep -iE '\b(INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|TRUNCATE|REPLACE|MERGE)\b' > /dev/null; then
- echo "Blocked: Write operations not allowed. Use SELECT queries only." >&2
- exit 2
-fi
-
-exit 0
-```
-
-Make the script executable:
-
-```bash theme={null}
-chmod +x ./scripts/validate-readonly-query.sh
-```
-
-The hook receives JSON via stdin with the Bash command in `tool_input.command`. Exit code 2 blocks the operation and feeds the error message back to Claude. See [Hooks](/en/hooks#exit-codes) for details on exit codes and [Hook input](/en/hooks#pretooluse-input) for the complete input schema.
-
-## Next steps
-
-Now that you understand subagents, explore these related features:
-
-* [Distribute subagents with plugins](/en/plugins) to share subagents across teams or projects
-* [Run Claude Code programmatically](/en/headless) with the Agent SDK for CI/CD and automation
-* [Use MCP servers](/en/mcp) to give subagents access to external tools and data
-
-
----
-
-> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt
\ No newline at end of file
diff --git a/.opencode/context/to-be-consumed/claude-code-docs/hooks.md b/.opencode/context/to-be-consumed/claude-code-docs/hooks.md
deleted file mode 100644
index ac75bf1..0000000
--- a/.opencode/context/to-be-consumed/claude-code-docs/hooks.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# Get started with Claude Code hooks
-
-> Learn how to customize and extend Claude Code's behavior by registering shell commands
-
-Claude Code hooks are user-defined shell commands that execute at various points
-in Claude Code's lifecycle. Hooks provide deterministic control over Claude
-Code's behavior, ensuring certain actions always happen rather than relying on
-the LLM to choose to run them.
-
-
- For reference documentation on hooks, see [Hooks reference](/en/hooks).
-
-
-Example use cases for hooks include:
-
-* **Notifications**: Customize how you get notified when Claude Code is awaiting
- your input or permission to run something.
-* **Automatic formatting**: Run `prettier` on .ts files, `gofmt` on .go files,
- etc. after every file edit.
-* **Logging**: Track and count all executed commands for compliance or
- debugging.
-* **Feedback**: Provide automated feedback when Claude Code produces code that
- does not follow your codebase conventions.
-* **Custom permissions**: Block modifications to production files or sensitive
- directories.
-
-By encoding these rules as hooks rather than prompting instructions, you turn
-suggestions into app-level code that executes every time it is expected to run.
-
-
- You must consider the security implication of hooks as you add them, because hooks run automatically during the agent loop with your current environment's credentials.
- For example, malicious hooks code can exfiltrate your data. Always review your hooks implementation before registering them.
-
- For full security best practices, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.
-
-
-## Hook Events Overview
-
-Claude Code provides several hook events that run at different points in the
-workflow:
-
-* **PreToolUse**: Runs before tool calls (can block them)
-* **PermissionRequest**: Runs when a permission dialog is shown (can allow or deny)
-* **PostToolUse**: Runs after tool calls complete
-* **UserPromptSubmit**: Runs when the user submits a prompt, before Claude processes it
-* **Notification**: Runs when Claude Code sends notifications
-* **Stop**: Runs when Claude Code finishes responding
-* **SubagentStop**: Runs when subagent tasks complete
-* **PreCompact**: Runs before Claude Code is about to run a compact operation
-* **SessionStart**: Runs when Claude Code starts a new session or resumes an existing session
-* **SessionEnd**: Runs when Claude Code session ends
-
-Each event receives different data and can control Claude's behavior in
-different ways.
-
-## Quickstart
-
-In this quickstart, you'll add a hook that logs the shell commands that Claude
-Code runs.
-
-### Prerequisites
-
-Install `jq` for JSON processing in the command line.
-
-### Step 1: Open hooks configuration
-
-Run the `/hooks` [slash command](/en/slash-commands) and select
-the `PreToolUse` hook event.
-
-`PreToolUse` hooks run before tool calls and can block them while providing
-Claude feedback on what to do differently.
-
-### Step 2: Add a matcher
-
-Select `+ Add new matcher…` to run your hook only on Bash tool calls.
-
-Type `Bash` for the matcher.
-
-You can use `*` to match all tools.
-
-### Step 3: Add the hook
-
-Select `+ Add new hook…` and enter this command:
-
-```bash theme={null}
-jq -r '"\(.tool_input.command) - \(.tool_input.description // "No description")"' >> ~/.claude/bash-command-log.txt
-```
-
-### Step 4: Save your configuration
-
-For storage location, select `User settings` since you're logging to your home
-directory. This hook will then apply to all projects, not just your current
-project.
-
-Then press `Esc` until you return to the REPL. Your hook is now registered.
-
-### Step 5: Verify your hook
-
-Run `/hooks` again or check `~/.claude/settings.json` to see your configuration:
-
-```json theme={null}
-{
- "hooks": {
- "PreToolUse": [
- {
- "matcher": "Bash",
- "hooks": [
- {
- "type": "command",
- "command": "jq -r '\"\\(.tool_input.command) - \\(.tool_input.description // \"No description\")\"' >> ~/.claude/bash-command-log.txt"
- }
- ]
- }
- ]
- }
-}
-```
-
-### Step 6: Test your hook
-
-Ask Claude to run a simple command like `ls` and check your log file:
-
-```bash theme={null}
-cat ~/.claude/bash-command-log.txt
-```
-
-You should see entries like:
-
-```
-ls - Lists files and directories
-```
-
-## More Examples
-
-
- For a complete example implementation, see the [bash command validator example](https://github.com/anthropics/claude-code/blob/main/examples/hooks/bash_command_validator_example.py) in our public codebase.
-
-
-### Code Formatting Hook
-
-Automatically format TypeScript files after editing:
-
-```json theme={null}
-{
- "hooks": {
- "PostToolUse": [
- {
- "matcher": "Edit|Write",
- "hooks": [
- {
- "type": "command",
- "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -q '\\.ts$'; then npx prettier --write \"$file_path\"; fi; }"
- }
- ]
- }
- ]
- }
-}
-```
-
-### Markdown Formatting Hook
-
-Automatically fix missing language tags and formatting issues in markdown files:
-
-```json theme={null}
-{
- "hooks": {
- "PostToolUse": [
- {
- "matcher": "Edit|Write",
- "hooks": [
- {
- "type": "command",
- "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/markdown_formatter.py"
- }
- ]
- }
- ]
- }
-}
-```
-
-Create `.claude/hooks/markdown_formatter.py` with this content:
-
-````python theme={null}
-#!/usr/bin/env python3
-"""
-Markdown formatter for Claude Code output.
-Fixes missing language tags and spacing issues while preserving code content.
-"""
-import json
-import sys
-import re
-import os
-
-def detect_language(code):
- """Best-effort language detection from code content."""
- s = code.strip()
-
- # JSON detection
- if re.search(r'^\s*[{\[]', s):
- try:
- json.loads(s)
- return 'json'
- except:
- pass
-
- # Python detection
- if re.search(r'^\s*def\s+\w+\s*\(', s, re.M) or \
- re.search(r'^\s*(import|from)\s+\w+', s, re.M):
- return 'python'
-
- # JavaScript detection
- if re.search(r'\b(function\s+\w+\s*\(|const\s+\w+\s*=)', s) or \
- re.search(r'=>|console\.(log|error)', s):
- return 'javascript'
-
- # Bash detection
- if re.search(r'^#!.*\b(bash|sh)\b', s, re.M) or \
- re.search(r'\b(if|then|fi|for|in|do|done)\b', s):
- return 'bash'
-
- # SQL detection
- if re.search(r'\b(SELECT|INSERT|UPDATE|DELETE|CREATE)\s+', s, re.I):
- return 'sql'
-
- return 'text'
-
-def format_markdown(content):
- """Format markdown content with language detection."""
- # Fix unlabeled code fences
- def add_lang_to_fence(match):
- indent, info, body, closing = match.groups()
- if not info.strip():
- lang = detect_language(body)
- return f"{indent}```{lang}\n{body}{closing}\n"
- return match.group(0)
-
- fence_pattern = r'(?ms)^([ \t]{0,3})```([^\n]*)\n(.*?)(\n\1```)\s*$'
- content = re.sub(fence_pattern, add_lang_to_fence, content)
-
- # Fix excessive blank lines (only outside code fences)
- content = re.sub(r'\n{3,}', '\n\n', content)
-
- return content.rstrip() + '\n'
-
-# Main execution
-try:
- input_data = json.load(sys.stdin)
- file_path = input_data.get('tool_input', {}).get('file_path', '')
-
- if not file_path.endswith(('.md', '.mdx')):
- sys.exit(0) # Not a markdown file
-
- if os.path.exists(file_path):
- with open(file_path, 'r', encoding='utf-8') as f:
- content = f.read()
-
- formatted = format_markdown(content)
-
- if formatted != content:
- with open(file_path, 'w', encoding='utf-8') as f:
- f.write(formatted)
- print(f"✓ Fixed markdown formatting in {file_path}")
-
-except Exception as e:
- print(f"Error formatting markdown: {e}", file=sys.stderr)
- sys.exit(1)
-````
-
-Make the script executable:
-
-```bash theme={null}
-chmod +x .claude/hooks/markdown_formatter.py
-```
-
-This hook automatically:
-
-* Detects programming languages in unlabeled code blocks
-* Adds appropriate language tags for syntax highlighting
-* Fixes excessive blank lines while preserving code content
-* Only processes markdown files (`.md`, `.mdx`)
-
-### Custom Notification Hook
-
-Get desktop notifications when Claude needs input:
-
-```json theme={null}
-{
- "hooks": {
- "Notification": [
- {
- "matcher": "",
- "hooks": [
- {
- "type": "command",
- "command": "notify-send 'Claude Code' 'Awaiting your input'"
- }
- ]
- }
- ]
- }
-}
-```
-
-### File Protection Hook
-
-Block edits to sensitive files:
-
-```json theme={null}
-{
- "hooks": {
- "PreToolUse": [
- {
- "matcher": "Edit|Write",
- "hooks": [
- {
- "type": "command",
- "command": "python3 -c \"import json, sys; data=json.load(sys.stdin); path=data.get('tool_input',{}).get('file_path',''); sys.exit(2 if any(p in path for p in ['.env', 'package-lock.json', '.git/']) else 0)\""
- }
- ]
- }
- ]
- }
-}
-```
-
-## Learn more
-
-* For reference documentation on hooks, see [Hooks reference](/en/hooks).
-* For comprehensive security best practices and safety guidelines, see [Security Considerations](/en/hooks#security-considerations) in the hooks reference documentation.
-* For troubleshooting steps and debugging techniques, see [Debugging](/en/hooks#debugging) in the hooks reference
- documentation.
-
-
----
-
-> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt
\ No newline at end of file
diff --git a/.opencode/context/to-be-consumed/claude-code-docs/navigation.md b/.opencode/context/to-be-consumed/claude-code-docs/navigation.md
deleted file mode 100644
index 9e1628e..0000000
--- a/.opencode/context/to-be-consumed/claude-code-docs/navigation.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Claude Code Documentation
-
-**Purpose**: External documentation for Claude Code to be integrated into main context
-
----
-
-## Structure
-
-```
-to-be-consumed/claude-code-docs/
-├── navigation.md (this file)
-└── [external documentation files]
-```
-
----
-
-## Quick Routes
-
-| Task | Path |
-|------|------|
-| **View docs** | `./` |
-| **To-be-consumed** | `../navigation.md` |
-
----
-
-## By Type
-
-**External Docs** → Documentation from external sources
-**To Be Integrated** → Content awaiting integration into main context
-
----
-
-## Related Context
-
-- **To-be-consumed Navigation** → `../navigation.md`
-- **Development** → `../../development/navigation.md`
diff --git a/.opencode/context/to-be-consumed/claude-code-docs/plugins.md b/.opencode/context/to-be-consumed/claude-code-docs/plugins.md
deleted file mode 100644
index 918dfaa..0000000
--- a/.opencode/context/to-be-consumed/claude-code-docs/plugins.md
+++ /dev/null
@@ -1,413 +0,0 @@
-# Create plugins
-
-> Create custom plugins to extend Claude Code with slash commands, agents, hooks, Skills, and MCP servers.
-
-Plugins let you extend Claude Code with custom functionality that can be shared across projects and teams. This guide covers creating your own plugins with slash commands, agents, Skills, hooks, and MCP servers.
-
-Looking to install existing plugins? See [Discover and install plugins](/en/discover-plugins). For complete technical specifications, see [Plugins reference](/en/plugins-reference).
-
-## When to use plugins vs standalone configuration
-
-Claude Code supports two ways to add custom slash commands, agents, and hooks:
-
-| Approach | Slash command names | Best for |
-| :---------------------------------------------------------- | :------------------- | :---------------------------------------------------------------------------------------------- |
-| **Standalone** (`.claude/` directory) | `/hello` | Personal workflows, project-specific customizations, quick experiments |
-| **Plugins** (directories with `.claude-plugin/plugin.json`) | `/plugin-name:hello` | Sharing with teammates, distributing to community, versioned releases, reusable across projects |
-
-**Use standalone configuration when**:
-
-* You're customizing Claude Code for a single project
-* The configuration is personal and doesn't need to be shared
-* You're experimenting with slash commands or hooks before packaging them
-* You want short slash command names like `/hello` or `/review`
-
-**Use plugins when**:
-
-* You want to share functionality with your team or community
-* You need the same slash commands/agents across multiple projects
-* You want version control and easy updates for your extensions
-* You're distributing through a marketplace
-* You're okay with namespaced slash commands like `/my-plugin:hello` (namespacing prevents conflicts between plugins)
-
-
- Start with standalone configuration in `.claude/` for quick iteration, then [convert to a plugin](#convert-existing-configurations-to-plugins) when you're ready to share.
-
-
-## Quickstart
-
-This quickstart walks you through creating a plugin with a custom slash command. You'll create a manifest (the configuration file that defines your plugin), add a slash command, and test it locally using the `--plugin-dir` flag.
-
-### Prerequisites
-
-* Claude Code [installed and authenticated](/en/quickstart#step-1-install-claude-code)
-* Claude Code version 1.0.33 or later (run `claude --version` to check)
-
-
- If you don't see the `/plugin` command, update Claude Code to the latest version. See [Troubleshooting](/en/troubleshooting) for upgrade instructions.
-
-
-### Create your first plugin
-
-
-
- Every plugin lives in its own directory containing a manifest and your custom commands, agents, or hooks. Create one now:
-
- ```bash theme={null}
- mkdir my-first-plugin
- ```
-
-
-
- The manifest file at `.claude-plugin/plugin.json` defines your plugin's identity: its name, description, and version. Claude Code uses this metadata to display your plugin in the plugin manager.
-
- Create the `.claude-plugin` directory inside your plugin folder:
-
- ```bash theme={null}
- mkdir my-first-plugin/.claude-plugin
- ```
-
- Then create `my-first-plugin/.claude-plugin/plugin.json` with this content:
-
- ```json my-first-plugin/.claude-plugin/plugin.json theme={null}
- {
- "name": "my-first-plugin",
- "description": "A greeting plugin to learn the basics",
- "version": "1.0.0",
- "author": {
- "name": "Your Name"
- }
- }
- ```
-
- | Field | Purpose |
- | :------------ | :--------------------------------------------------------------------------------------------------------------------- |
- | `name` | Unique identifier and slash command namespace. Slash commands are prefixed with this (e.g., `/my-first-plugin:hello`). |
- | `description` | Shown in the plugin manager when browsing or installing plugins. |
- | `version` | Track releases using [semantic versioning](/en/plugins-reference#version-management). |
- | `author` | Optional. Helpful for attribution. |
-
- For additional fields like `homepage`, `repository`, and `license`, see the [full manifest schema](/en/plugins-reference#plugin-manifest-schema).
-
-
-
- Slash commands are Markdown files in the `commands/` directory. The filename becomes the slash command name, prefixed with the plugin's namespace (`hello.md` in a plugin named `my-first-plugin` creates `/my-first-plugin:hello`). The Markdown content tells Claude how to respond when someone runs the slash command.
-
- Create a `commands` directory in your plugin folder:
-
- ```bash theme={null}
- mkdir my-first-plugin/commands
- ```
-
- Then create `my-first-plugin/commands/hello.md` with this content:
-
- ```markdown my-first-plugin/commands/hello.md theme={null}
- ---
- description: Greet the user with a friendly message
- ---
-
- # Hello Command
-
- Greet the user warmly and ask how you can help them today.
- ```
-
-
-
- Run Claude Code with the `--plugin-dir` flag to load your plugin:
-
- ```bash theme={null}
- claude --plugin-dir ./my-first-plugin
- ```
-
- Once Claude Code starts, try your new command:
-
- ```shell theme={null}
- /my-first-plugin:hello
- ```
-
- You'll see Claude respond with a greeting. Run `/help` to see your command listed under the plugin namespace.
-
-
- **Why namespacing?** Plugin slash commands are always namespaced (like `/greet:hello`) to prevent conflicts when multiple plugins have commands with the same name.
-
- To change the namespace prefix, update the `name` field in `plugin.json`.
-
-
-
-
- Make your slash command dynamic by accepting user input. The `$ARGUMENTS` placeholder captures any text the user provides after the slash command.
-
- Update your `hello.md` file:
-
- ```markdown my-first-plugin/commands/hello.md theme={null}
- ---
- description: Greet the user with a personalized message
- ---
-
- # Hello Command
-
- Greet the user named "$ARGUMENTS" warmly and ask how you can help them today. Make the greeting personal and encouraging.
- ```
-
- Restart Claude Code to pick up the changes, then try the command with your name:
-
- ```shell theme={null}
- /my-first-plugin:hello Alex
- ```
-
- Claude will greet you by name. For more argument options like `$1`, `$2` for individual parameters, see [Slash commands](/en/slash-commands).
-
-
-
-You've successfully created and tested a plugin with these key components:
-
-* **Plugin manifest** (`.claude-plugin/plugin.json`): describes your plugin's metadata
-* **Commands directory** (`commands/`): contains your custom slash commands
-* **Command arguments** (`$ARGUMENTS`): captures user input for dynamic behavior
-
-
- The `--plugin-dir` flag is useful for development and testing. When you're ready to share your plugin with others, see [Create and distribute a plugin marketplace](/en/plugin-marketplaces).
-
-
-## Plugin structure overview
-
-You've created a plugin with a slash command, but plugins can include much more: custom agents, Skills, hooks, MCP servers, and LSP servers.
-
-
- **Common mistake**: Don't put `commands/`, `agents/`, `skills/`, or `hooks/` inside the `.claude-plugin/` directory. Only `plugin.json` goes inside `.claude-plugin/`. All other directories must be at the plugin root level.
-
-
-| Directory | Location | Purpose |
-| :---------------- | :---------- | :---------------------------------------------- |
-| `.claude-plugin/` | Plugin root | Contains only `plugin.json` manifest (required) |
-| `commands/` | Plugin root | Slash commands as Markdown files |
-| `agents/` | Plugin root | Custom agent definitions |
-| `skills/` | Plugin root | Agent Skills with `SKILL.md` files |
-| `hooks/` | Plugin root | Event handlers in `hooks.json` |
-| `.mcp.json` | Plugin root | MCP server configurations |
-| `.lsp.json` | Plugin root | LSP server configurations for code intelligence |
-
-
- **Next steps**: Ready to add more features? Jump to [Develop more complex plugins](#develop-more-complex-plugins) to add agents, hooks, MCP servers, and LSP servers. For complete technical specifications of all plugin components, see [Plugins reference](/en/plugins-reference).
-
-
-## Develop more complex plugins
-
-Once you're comfortable with basic plugins, you can create more sophisticated extensions.
-
-### Add Skills to your plugin
-
-Plugins can include [Agent Skills](/en/skills) to extend Claude's capabilities. Skills are model-invoked: Claude automatically uses them based on the task context.
-
-Add a `skills/` directory at your plugin root with Skill folders containing `SKILL.md` files:
-
-```
-my-plugin/
-├── .claude-plugin/
-│ └── plugin.json
-└── skills/
- └── code-review/
- └── SKILL.md
-```
-
-Each `SKILL.md` needs frontmatter with `name` and `description` fields, followed by instructions:
-
-```yaml theme={null}
----
-name: code-review
-description: Reviews code for best practices and potential issues. Use when reviewing code, checking PRs, or analyzing code quality.
----
-
-When reviewing code, check for:
-1. Code organization and structure
-2. Error handling
-3. Security concerns
-4. Test coverage
-```
-
-After installing the plugin, restart Claude Code to load the Skills. For complete Skill authoring guidance including progressive disclosure and tool restrictions, see [Agent Skills](/en/skills).
-
-### Add LSP servers to your plugin
-
-
- For common languages like TypeScript, Python, and Rust, install the pre-built LSP plugins from the official marketplace. Create custom LSP plugins only when you need support for languages not already covered.
-
-
-LSP (Language Server Protocol) plugins give Claude real-time code intelligence. If you need to support a language that doesn't have an official LSP plugin, you can create your own by adding an `.lsp.json` file to your plugin:
-
-```json .lsp.json theme={null}
-{
- "go": {
- "command": "gopls",
- "args": ["serve"],
- "extensionToLanguage": {
- ".go": "go"
- }
- }
-}
-```
-
-Users installing your plugin must have the language server binary installed on their machine.
-
-For complete LSP configuration options, see [LSP servers](/en/plugins-reference#lsp-servers).
-
-### Organize complex plugins
-
-For plugins with many components, organize your directory structure by functionality. For complete directory layouts and organization patterns, see [Plugin directory structure](/en/plugins-reference#plugin-directory-structure).
-
-### Test your plugins locally
-
-Use the `--plugin-dir` flag to test plugins during development. This loads your plugin directly without requiring installation.
-
-```bash theme={null}
-claude --plugin-dir ./my-plugin
-```
-
-As you make changes to your plugin, restart Claude Code to pick up the updates. Test your plugin components:
-
-* Try your commands with `/command-name`
-* Check that agents appear in `/agents`
-* Verify hooks work as expected
-
-
- You can load multiple plugins at once by specifying the flag multiple times:
-
- ```bash theme={null}
- claude --plugin-dir ./plugin-one --plugin-dir ./plugin-two
- ```
-
-
-### Debug plugin issues
-
-If your plugin isn't working as expected:
-
-1. **Check the structure**: Ensure your directories are at the plugin root, not inside `.claude-plugin/`
-2. **Test components individually**: Check each command, agent, and hook separately
-3. **Use validation and debugging tools**: See [Debugging and development tools](/en/plugins-reference#debugging-and-development-tools) for CLI commands and troubleshooting techniques
-
-### Share your plugins
-
-When your plugin is ready to share:
-
-1. **Add documentation**: Include a `README.md` with installation and usage instructions
-2. **Version your plugin**: Use [semantic versioning](/en/plugins-reference#version-management) in your `plugin.json`
-3. **Create or use a marketplace**: Distribute through [plugin marketplaces](/en/plugin-marketplaces) for installation
-4. **Test with others**: Have team members test the plugin before wider distribution
-
-Once your plugin is in a marketplace, others can install it using the instructions in [Discover and install plugins](/en/discover-plugins).
-
-
- For complete technical specifications, debugging techniques, and distribution strategies, see [Plugins reference](/en/plugins-reference).
-
-
-## Convert existing configurations to plugins
-
-If you already have custom commands, Skills, or hooks in your `.claude/` directory, you can convert them into a plugin for easier sharing and distribution.
-
-### Migration steps
-
-
-
- Create a new plugin directory:
-
- ```bash theme={null}
- mkdir -p my-plugin/.claude-plugin
- ```
-
- Create the manifest file at `my-plugin/.claude-plugin/plugin.json`:
-
- ```json my-plugin/.claude-plugin/plugin.json theme={null}
- {
- "name": "my-plugin",
- "description": "Migrated from standalone configuration",
- "version": "1.0.0"
- }
- ```
-
-
-
- Copy your existing configurations to the plugin directory:
-
- ```bash theme={null}
- # Copy commands
- cp -r .claude/commands my-plugin/
-
- # Copy agents (if any)
- cp -r .claude/agents my-plugin/
-
- # Copy skills (if any)
- cp -r .claude/skills my-plugin/
- ```
-
-
-
- If you have hooks in your settings, create a hooks directory:
-
- ```bash theme={null}
- mkdir my-plugin/hooks
- ```
-
- Create `my-plugin/hooks/hooks.json` with your hooks configuration. Copy the `hooks` object from your `.claude/settings.json` or `settings.local.json`—the format is the same:
-
- ```json my-plugin/hooks/hooks.json theme={null}
- {
- "hooks": {
- "PostToolUse": [
- {
- "matcher": "Write|Edit",
- "hooks": [{ "type": "command", "command": "npm run lint:fix $FILE" }]
- }
- ]
- }
- }
- ```
-
-
-
- Load your plugin to verify everything works:
-
- ```bash theme={null}
- claude --plugin-dir ./my-plugin
- ```
-
- Test each component: run your commands, check agents appear in `/agents`, and verify hooks trigger correctly.
-
-
-
-### What changes when migrating
-
-| Standalone (`.claude/`) | Plugin |
-| :---------------------------- | :------------------------------- |
-| Only available in one project | Can be shared via marketplaces |
-| Files in `.claude/commands/` | Files in `plugin-name/commands/` |
-| Hooks in `settings.json` | Hooks in `hooks/hooks.json` |
-| Must manually copy to share | Install with `/plugin install` |
-
-
- After migrating, you can remove the original files from `.claude/` to avoid duplicates. The plugin version will take precedence when loaded.
-
-
-## Next steps
-
-Now that you understand Claude Code's plugin system, here are suggested paths for different goals:
-
-### For plugin users
-
-* [Discover and install plugins](/en/discover-plugins): browse marketplaces and install plugins
-* [Configure team marketplaces](/en/discover-plugins#configure-team-marketplaces): set up repository-level plugins for your team
-
-### For plugin developers
-
-* [Create and distribute a marketplace](/en/plugin-marketplaces): package and share your plugins
-* [Plugins reference](/en/plugins-reference): complete technical specifications
-* Dive deeper into specific plugin components:
- * [Slash commands](/en/slash-commands): command development details
- * [Subagents](/en/sub-agents): agent configuration and capabilities
- * [Agent Skills](/en/skills): extend Claude's capabilities
- * [Hooks](/en/hooks): event handling and automation
- * [MCP](/en/mcp): external tool integration
-
-
----
-
-> To find navigation and other pages in this documentation, fetch the llms.txt file at: https://code.claude.com/docs/llms.txt
\ No newline at end of file
diff --git a/.opencode/context/ui/web/animation-advanced.md b/.opencode/context/ui/web/animation-advanced.md
new file mode 100644
index 0000000..7439244
--- /dev/null
+++ b/.opencode/context/ui/web/animation-advanced.md
@@ -0,0 +1,200 @@
+
+# Advanced Animation Patterns
+
+Recipes, best practices, micro-interactions, and accessibility considerations.
+
+---
+
+## Page Transitions
+
+### Route Changes
+
+```css
+/* Page fade out */
+.page-exit {
+ animation: fadeOut 200ms ease-in;
+}
+@keyframes fadeOut {
+ from { opacity: 1; }
+ to { opacity: 0; }
+}
+
+/* Page fade in */
+.page-enter {
+ animation: fadeIn 300ms ease-out;
+}
+@keyframes fadeIn {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+```
+
+**Micro-syntax**:
+```
+pageExit: 200ms ease-in [α1→0]
+pageEnter: 300ms ease-out [α0→1]
+```
+
+---
+
+## Micro-Interactions
+
+### Hover Effects
+
+```css
+/* Link underline slide */
+.link {
+ position: relative;
+}
+.link::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ width: 0;
+ height: 2px;
+ background: currentColor;
+ transition: width 250ms ease-out;
+}
+.link:hover::after {
+ width: 100%;
+}
+```
+
+**Micro-syntax**:
+```
+linkHover: 250ms ease-out [width0→100%]
+```
+
+### Toggle Switches
+
+```css
+/* Toggle slide */
+.toggle-switch {
+ transition: background-color 200ms ease-out;
+}
+.toggle-switch .thumb {
+ transition: transform 200ms ease-out;
+}
+.toggle-switch.on .thumb {
+ transform: translateX(20px);
+}
+```
+
+**Micro-syntax**:
+```
+toggle: 200ms ease-out [X0→20, bg→accent]
+```
+
+---
+
+## Animation Recipes
+
+### Chat UI Complete Animation System
+
+```
+## Core Message Flow
+userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
+aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
+typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
+status: 300ms ease-out [α0.6→1, S1→1.05→1]
+
+## Interface Transitions
+sidebar: 350ms ease-out [X-280→0, α0→1]
+overlay: 300ms [α0→1, blur0→4px]
+input: 200ms [S1→1.01, shadow+ring] focus
+input: 150ms [S1.01→1, shadow-ring] blur
+
+## Button Interactions
+sendBtn: 150ms [S1→0.95→1, R±2°] press
+sendBtn: 200ms [S1→1.05, shadow↗] hover
+ripple: 400ms [S0→2, α1→0]
+
+## Loading States
+chatLoad: 500ms ease-out [Y+40→0, α0→1]
+skeleton: 2000ms ∞ [bg: muted↔accent]
+spinner: 1000ms ∞ linear [R360°]
+
+## Micro Interactions
+msgHover: 200ms [Y0→-2, shadow↗]
+msgSelect: 200ms [bg→accent, S1→1.02]
+error: 400ms [X±5] shake
+success: 600ms bounce [S0→1.2→1, R360°]
+
+## Scroll & Navigation
+autoScroll: 400ms smooth
+scrollHint: 800ms ∞×3 [Y±5]
+```
+
+---
+
+## Best Practices
+
+### Do's ✅
+
+- Keep animations under 400ms for most interactions
+- Use `transform` and `opacity` for 60fps performance
+- Provide purpose for every animation
+- Use ease-out for entrances, ease-in for exits
+- Test on low-end devices
+- Respect `prefers-reduced-motion`
+- Stagger animations for lists (50-100ms delay)
+- Use consistent timing across similar interactions
+
+### Don'ts ❌
+
+- Don't animate width/height (use scale instead)
+- Don't use animations longer than 800ms
+- Don't animate too many elements at once
+- Don't use animations without purpose
+- Don't ignore accessibility preferences
+- Don't use jarring/distracting animations
+- Don't animate on every interaction
+- Don't use complex easing for simple interactions
+
+---
+
+## Accessibility
+
+### Reduced Motion
+
+```css
+/* Respect user preferences */
+@media (prefers-reduced-motion: reduce) {
+ *,
+ *::before,
+ *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+}
+```
+
+### Focus Indicators
+
+```css
+/* Always animate focus states */
+:focus-visible {
+ outline: 2px solid var(--ring);
+ outline-offset: 2px;
+ transition: outline-offset 150ms ease-out;
+}
+```
+
+---
+
+## References
+
+- [Web Animation API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
+- [CSS Easing Functions](https://easings.net/)
+- [Animation Performance](https://web.dev/animations-guide/)
+- [Reduced Motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
+
+---
+
+## Related Files
+
+- [Animation Basics](./animation-basics.md) - Fundamentals
+- [UI Animations](./animation-ui.md) - Common UI patterns
+- [Loading Animations](./animation-loading.md) - Loading states
diff --git a/.opencode/context/ui/web/animation-basics.md b/.opencode/context/ui/web/animation-basics.md
new file mode 100644
index 0000000..f1472cc
--- /dev/null
+++ b/.opencode/context/ui/web/animation-basics.md
@@ -0,0 +1,94 @@
+
+# Animation Basics
+
+## Overview
+
+Standards and patterns for UI animations, micro-interactions, and transitions. Animations should feel natural, purposeful, and enhance user experience without causing distraction.
+
+## Quick Reference
+
+**Timing**: 150-400ms for most interactions
+**Easing**: ease-out for entrances, ease-in for exits
+**Purpose**: Every animation should have a clear purpose
+**Performance**: Use transform and opacity for 60fps
+
+---
+
+## Animation Micro-Syntax
+
+### Notation Guide
+
+**Format**: `element: duration easing [properties] modifiers`
+
+**Symbols**:
+- `→` = transition from → to
+- `±` = oscillate/shake
+- `↗` = increase
+- `↘` = decrease
+- `∞` = infinite loop
+- `×N` = repeat N times
+- `+Nms` = delay N milliseconds
+
+**Properties**:
+- `Y` = translateY
+- `X` = translateX
+- `S` = scale
+- `R` = rotate
+- `α` = opacity
+- `bg` = background
+
+**Example**: `button: 200ms ease-out [S1→1.05, α0.8→1]`
+- Button scales from 1 to 1.05 and fades from 0.8 to 1 over 200ms with ease-out
+
+---
+
+## Core Animation Principles
+
+### Timing Standards
+
+```
+Ultra-fast: 100-150ms (micro-feedback, hover states)
+Fast: 150-250ms (button clicks, toggles)
+Standard: 250-350ms (modals, dropdowns, navigation)
+Moderate: 350-500ms (page transitions, complex animations)
+Slow: 500-800ms (dramatic reveals, storytelling)
+```
+
+### Easing Functions
+
+```css
+/* Entrances - start slow, end fast */
+ease-out: cubic-bezier(0, 0, 0.2, 1);
+
+/* Exits - start fast, end slow */
+ease-in: cubic-bezier(0.4, 0, 1, 1);
+
+/* Both - smooth throughout */
+ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
+
+/* Bounce - playful, attention-grabbing */
+bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
+
+/* Elastic - spring-like */
+elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6);
+```
+
+### Performance Guidelines
+
+**60fps Animations** (GPU-accelerated):
+- ✅ `transform` (translate, scale, rotate)
+- ✅ `opacity`
+- ✅ `filter` (with caution)
+
+**Avoid** (causes reflow/repaint):
+- ❌ `width`, `height`
+- ❌ `top`, `left`, `right`, `bottom`
+- ❌ `margin`, `padding`
+
+---
+
+## Related Files
+
+- [UI Animations](./animation-ui.md) - Common UI patterns
+- [Loading Animations](./animation-loading.md) - Loading states
+- [Advanced Animations](./animation-advanced.md) - Recipes & best practices
diff --git a/.opencode/context/ui/web/animation-chat.md b/.opencode/context/ui/web/animation-chat.md
new file mode 100644
index 0000000..8f21e9e
--- /dev/null
+++ b/.opencode/context/ui/web/animation-chat.md
@@ -0,0 +1,113 @@
+
+# Chat UI Animation Patterns
+
+Animation patterns for message entrances, typing indicators, and chat interactions.
+
+---
+
+## Message Entrance
+
+```css
+/* User message - slide from right */
+.message-user {
+ animation: slideInRight 400ms ease-out;
+}
+@keyframes slideInRight {
+ from {
+ transform: translateX(10px) translateY(20px);
+ opacity: 0;
+ scale: 0.9;
+ }
+ to {
+ transform: translateX(0) translateY(0);
+ opacity: 1;
+ scale: 1;
+ }
+}
+
+/* AI message - slide from left with bounce */
+.message-ai {
+ animation: slideInLeft 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
+ animation-delay: 200ms;
+}
+@keyframes slideInLeft {
+ from {
+ transform: translateY(15px);
+ opacity: 0;
+ scale: 0.95;
+ }
+ to {
+ transform: translateY(0);
+ opacity: 1;
+ scale: 1;
+ }
+}
+```
+
+**Micro-syntax**:
+```
+userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
+aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
+```
+
+---
+
+## Typing Indicator
+
+```css
+.typing-indicator span {
+ animation: typingDot 1400ms infinite;
+}
+.typing-indicator span:nth-child(2) { animation-delay: 200ms; }
+.typing-indicator span:nth-child(3) { animation-delay: 400ms; }
+
+@keyframes typingDot {
+ 0%, 60%, 100% {
+ transform: translateY(0);
+ opacity: 0.4;
+ }
+ 30% {
+ transform: translateY(-8px);
+ opacity: 1;
+ }
+}
+```
+
+**Micro-syntax**:
+```
+typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
+```
+
+---
+
+## Chat Message Micro-Interactions
+
+```css
+/* Message hover */
+.message:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+ transition: all 200ms ease-out;
+}
+
+/* Message selection */
+.message.selected {
+ background-color: var(--accent);
+ transform: scale(1.02);
+ transition: all 200ms ease-out;
+}
+```
+
+**Micro-syntax**:
+```
+msgHover: 200ms [Y0→-2, shadow↗]
+msgSelect: 200ms [bg→accent, S1→1.02]
+```
+
+---
+
+## Related Files
+
+- [Animation Basics](./animation-basics.md) - Fundamentals
+- [Component Animations](./animation-components.md) - UI components
+- [Loading Animations](./animation-loading.md) - Loading states
diff --git a/.opencode/context/ui/web/animation-components.md b/.opencode/context/ui/web/animation-components.md
new file mode 100644
index 0000000..9c27f38
--- /dev/null
+++ b/.opencode/context/ui/web/animation-components.md
@@ -0,0 +1,137 @@
+
+# Component Animation Patterns
+
+Animation patterns for buttons, cards, modals, dropdowns, and sidebars.
+
+---
+
+## Button Interactions
+
+```css
+.button {
+ transition: transform 200ms ease-out, box-shadow 200ms ease-out;
+}
+.button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+.button:active {
+ transform: scale(0.95);
+ transition: transform 100ms ease-in;
+}
+
+@keyframes ripple {
+ from { transform: scale(0); opacity: 1; }
+ to { transform: scale(2); opacity: 0; }
+}
+.button::after { animation: ripple 400ms ease-out; }
+```
+
+**Micro-syntax**:
+```
+buttonHover: 200ms ease-out [Y0→-2, shadow↗]
+buttonPress: 100ms ease-in [S1→0.95]
+ripple: 400ms ease-out [S0→2, α1→0]
+```
+
+---
+
+## Card Interactions
+
+```css
+.card {
+ transition: transform 300ms ease-out, box-shadow 300ms ease-out;
+}
+.card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
+}
+.card.selected {
+ transform: scale(1.02);
+ background-color: var(--accent);
+ transition: all 200ms ease-out;
+}
+```
+
+**Micro-syntax**:
+```
+cardHover: 300ms ease-out [Y0→-4, shadow↗]
+cardSelect: 200ms ease-out [S1→1.02, bg→accent]
+```
+
+---
+
+## Modal/Dialog Animations
+
+```css
+.modal-backdrop { animation: fadeIn 300ms ease-out; }
+@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
+
+.modal { animation: slideUp 350ms ease-out; }
+@keyframes slideUp {
+ from { transform: translateY(40px); opacity: 0; }
+ to { transform: translateY(0); opacity: 1; }
+}
+
+.modal.closing { animation: slideDown 250ms ease-in; }
+@keyframes slideDown {
+ from { transform: translateY(0); opacity: 1; }
+ to { transform: translateY(40px); opacity: 0; }
+}
+```
+
+**Micro-syntax**:
+```
+backdrop: 300ms ease-out [α0→1]
+modalEnter: 350ms ease-out [Y+40→0, α0→1]
+modalExit: 250ms ease-in [Y0→+40, α1→0]
+```
+
+---
+
+## Dropdown/Menu Animations
+
+```css
+.dropdown {
+ animation: dropdownOpen 200ms ease-out;
+ transform-origin: top;
+}
+@keyframes dropdownOpen {
+ from { transform: scaleY(0.95); opacity: 0; }
+ to { transform: scaleY(1); opacity: 1; }
+}
+```
+
+**Micro-syntax**: `dropdown: 200ms ease-out [scaleY0.95→1, α0→1]`
+
+---
+
+## Sidebar/Drawer Animations
+
+```css
+.sidebar { animation: slideInLeft 350ms ease-out; }
+@keyframes slideInLeft {
+ from { transform: translateX(-280px); opacity: 0; }
+ to { transform: translateX(0); opacity: 1; }
+}
+
+.overlay { animation: overlayFade 300ms ease-out; }
+@keyframes overlayFade {
+ from { opacity: 0; backdrop-filter: blur(0); }
+ to { opacity: 1; backdrop-filter: blur(4px); }
+}
+```
+
+**Micro-syntax**:
+```
+sidebar: 350ms ease-out [X-280→0, α0→1]
+overlay: 300ms ease-out [α0→1, blur0→4px]
+```
+
+---
+
+## Related Files
+
+- [Animation Basics](./animation-basics.md) - Fundamentals
+- [Chat Animations](./animation-chat.md) - Message patterns
+- [Loading Animations](./animation-loading.md) - Loading states
diff --git a/.opencode/context/ui/web/animation-forms.md b/.opencode/context/ui/web/animation-forms.md
new file mode 100644
index 0000000..e9283b8
--- /dev/null
+++ b/.opencode/context/ui/web/animation-forms.md
@@ -0,0 +1,121 @@
+
+# Form Animation Patterns
+
+Animation patterns for form inputs, validation states, and scroll animations.
+
+---
+
+## Focus States
+
+```css
+/* Input focus - ring and scale */
+.input {
+ transition: all 200ms ease-out;
+}
+.input:focus {
+ transform: scale(1.01);
+ box-shadow: 0 0 0 3px var(--ring);
+}
+
+/* Input blur - return to normal */
+.input:not(:focus) {
+ transition: all 150ms ease-in;
+}
+```
+
+**Micro-syntax**:
+```
+inputFocus: 200ms ease-out [S1→1.01, shadow+ring]
+inputBlur: 150ms ease-in [S1.01→1, shadow-ring]
+```
+
+---
+
+## Validation States
+
+```css
+/* Error shake */
+.input-error {
+ animation: shake 400ms ease-in-out;
+}
+@keyframes shake {
+ 0%, 100% { transform: translateX(0); }
+ 25% { transform: translateX(-5px); }
+ 75% { transform: translateX(5px); }
+}
+
+/* Success checkmark */
+.input-success::after {
+ animation: checkmark 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
+}
+@keyframes checkmark {
+ from {
+ transform: scale(0) rotate(0deg);
+ opacity: 0;
+ }
+ to {
+ transform: scale(1.2) rotate(360deg);
+ opacity: 1;
+ }
+}
+```
+
+**Micro-syntax**:
+```
+error: 400ms ease-in-out [X±5] shake
+success: 600ms bounce [S0→1.2, R0→360°, α0→1]
+```
+
+---
+
+## Scroll Animations
+
+### Scroll-Triggered Fade In
+
+```css
+.fade-in-on-scroll {
+ opacity: 0;
+ transform: translateY(40px);
+ transition: opacity 500ms ease-out, transform 500ms ease-out;
+}
+.fade-in-on-scroll.visible {
+ opacity: 1;
+ transform: translateY(0);
+}
+```
+
+**Micro-syntax**:
+```
+scrollFadeIn: 500ms ease-out [Y+40→0, α0→1]
+```
+
+### Auto-Scroll
+
+```css
+html {
+ scroll-behavior: smooth;
+}
+
+.scroll-hint {
+ animation: scrollHint 800ms infinite;
+ animation-iteration-count: 3;
+}
+@keyframes scrollHint {
+ 0%, 100% { transform: translateY(0); }
+ 50% { transform: translateY(5px); }
+}
+```
+
+**Micro-syntax**:
+```
+autoScroll: 400ms smooth
+scrollHint: 800ms ∞×3 [Y±5]
+```
+
+---
+
+## Related Files
+
+- [Animation Basics](./animation-basics.md) - Fundamentals
+- [UI Animations](./animation-ui.md) - Common UI patterns
+- [Loading Animations](./animation-loading.md) - Loading states
diff --git a/.opencode/context/ui/web/animation-loading.md b/.opencode/context/ui/web/animation-loading.md
new file mode 100644
index 0000000..3eee033
--- /dev/null
+++ b/.opencode/context/ui/web/animation-loading.md
@@ -0,0 +1,118 @@
+
+# Loading State Animations
+
+Animation patterns for skeleton screens, spinners, progress bars, and status indicators.
+
+---
+
+## Skeleton Screens
+
+```css
+/* Skeleton shimmer */
+.skeleton {
+ animation: shimmer 2000ms infinite;
+ background: linear-gradient(
+ 90deg,
+ var(--muted) 0%,
+ var(--accent) 50%,
+ var(--muted) 100%
+ );
+ background-size: 200% 100%;
+}
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+```
+
+**Micro-syntax**:
+```
+skeleton: 2000ms ∞ [bg: muted↔accent]
+```
+
+---
+
+## Spinners
+
+```css
+/* Circular spinner */
+.spinner {
+ animation: spin 1000ms linear infinite;
+}
+@keyframes spin {
+ from { transform: rotate(0deg); }
+ to { transform: rotate(360deg); }
+}
+
+/* Pulsing dots */
+.loading-dots span {
+ animation: dotPulse 1500ms infinite;
+}
+.loading-dots span:nth-child(2) { animation-delay: 200ms; }
+.loading-dots span:nth-child(3) { animation-delay: 400ms; }
+@keyframes dotPulse {
+ 0%, 80%, 100% { opacity: 0.3; scale: 0.8; }
+ 40% { opacity: 1; scale: 1; }
+}
+```
+
+**Micro-syntax**:
+```
+spinner: 1000ms ∞ linear [R360°]
+dotPulse: 1500ms ∞ [α0.3→1→0.3, S0.8→1→0.8] stagger+200ms
+```
+
+---
+
+## Progress Bars
+
+```css
+/* Indeterminate progress */
+.progress-bar {
+ animation: progress 2000ms ease-in-out infinite;
+}
+@keyframes progress {
+ 0% { transform: translateX(-100%); }
+ 50% { transform: translateX(0); }
+ 100% { transform: translateX(100%); }
+}
+```
+
+**Micro-syntax**:
+```
+progress: 2000ms ∞ ease-in-out [X-100%→0→100%]
+```
+
+---
+
+## Status Indicators
+
+```css
+/* Online status pulse */
+.status-online {
+ animation: pulse 2000ms infinite;
+}
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ scale: 1;
+ }
+ 50% {
+ opacity: 0.6;
+ scale: 1.05;
+ }
+}
+```
+
+**Micro-syntax**:
+```
+status: 2000ms ∞ [α1→0.6→1, S1→1.05→1]
+```
+
+---
+
+## Related Files
+
+- [Animation Basics](./animation-basics.md) - Fundamentals
+- [Form Animations](./animation-forms.md) - Form patterns
+- [Advanced Animations](./animation-advanced.md) - Recipes & best practices
diff --git a/.opencode/context/ui/web/animation-patterns.md b/.opencode/context/ui/web/animation-patterns.md
deleted file mode 100644
index 0e4fd14..0000000
--- a/.opencode/context/ui/web/animation-patterns.md
+++ /dev/null
@@ -1,753 +0,0 @@
-
-# Animation Patterns
-
-## Overview
-
-Standards and patterns for UI animations, micro-interactions, and transitions. Animations should feel natural, purposeful, and enhance user experience without causing distraction.
-
-## Quick Reference
-
-**Timing**: 150-400ms for most interactions
-**Easing**: ease-out for entrances, ease-in for exits
-**Purpose**: Every animation should have a clear purpose
-**Performance**: Use transform and opacity for 60fps
-
----
-
-## Animation Micro-Syntax
-
-### Notation Guide
-
-**Format**: `element: duration easing [properties] modifiers`
-
-**Symbols**:
-- `→` = transition from → to
-- `±` = oscillate/shake
-- `↗` = increase
-- `↘` = decrease
-- `∞` = infinite loop
-- `×N` = repeat N times
-- `+Nms` = delay N milliseconds
-
-**Properties**:
-- `Y` = translateY
-- `X` = translateX
-- `S` = scale
-- `R` = rotate
-- `α` = opacity
-- `bg` = background
-
-**Example**: `button: 200ms ease-out [S1→1.05, α0.8→1]`
-- Button scales from 1 to 1.05 and fades from 0.8 to 1 over 200ms with ease-out
-
----
-
-## Core Animation Principles
-
-### Timing Standards
-
-```
-Ultra-fast: 100-150ms (micro-feedback, hover states)
-Fast: 150-250ms (button clicks, toggles)
-Standard: 250-350ms (modals, dropdowns, navigation)
-Moderate: 350-500ms (page transitions, complex animations)
-Slow: 500-800ms (dramatic reveals, storytelling)
-```
-
-### Easing Functions
-
-```css
-/* Entrances - start slow, end fast */
-ease-out: cubic-bezier(0, 0, 0.2, 1);
-
-/* Exits - start fast, end slow */
-ease-in: cubic-bezier(0.4, 0, 1, 1);
-
-/* Both - smooth throughout */
-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
-
-/* Bounce - playful, attention-grabbing */
-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55);
-
-/* Elastic - spring-like */
-elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6);
-```
-
-### Performance Guidelines
-
-**60fps Animations** (GPU-accelerated):
-- ✅ `transform` (translate, scale, rotate)
-- ✅ `opacity`
-- ✅ `filter` (with caution)
-
-**Avoid** (causes reflow/repaint):
-- ❌ `width`, `height`
-- ❌ `top`, `left`, `right`, `bottom`
-- ❌ `margin`, `padding`
-
----
-
-## Common UI Animation Patterns
-
-### Button Interactions
-
-```css
-/* Hover - subtle lift */
-.button {
- transition: transform 200ms ease-out, box-shadow 200ms ease-out;
-}
-.button:hover {
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
-}
-
-/* Press - scale down */
-.button:active {
- transform: scale(0.95);
- transition: transform 100ms ease-in;
-}
-
-/* Ripple effect */
-@keyframes ripple {
- from {
- transform: scale(0);
- opacity: 1;
- }
- to {
- transform: scale(2);
- opacity: 0;
- }
-}
-.button::after {
- animation: ripple 400ms ease-out;
-}
-```
-
-**Micro-syntax**:
-```
-buttonHover: 200ms ease-out [Y0→-2, shadow↗]
-buttonPress: 100ms ease-in [S1→0.95]
-ripple: 400ms ease-out [S0→2, α1→0]
-```
-
-### Card Interactions
-
-```css
-/* Hover - lift and shadow */
-.card {
- transition: transform 300ms ease-out, box-shadow 300ms ease-out;
-}
-.card:hover {
- transform: translateY(-4px);
- box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
-}
-
-/* Select - scale and highlight */
-.card.selected {
- transform: scale(1.02);
- background-color: var(--accent);
- transition: all 200ms ease-out;
-}
-```
-
-**Micro-syntax**:
-```
-cardHover: 300ms ease-out [Y0→-4, shadow↗]
-cardSelect: 200ms ease-out [S1→1.02, bg→accent]
-```
-
-### Modal/Dialog Animations
-
-```css
-/* Backdrop fade in */
-.modal-backdrop {
- animation: fadeIn 300ms ease-out;
-}
-@keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-
-/* Modal slide up and fade */
-.modal {
- animation: slideUp 350ms ease-out;
-}
-@keyframes slideUp {
- from {
- transform: translateY(40px);
- opacity: 0;
- }
- to {
- transform: translateY(0);
- opacity: 1;
- }
-}
-
-/* Modal exit */
-.modal.closing {
- animation: slideDown 250ms ease-in;
-}
-@keyframes slideDown {
- from {
- transform: translateY(0);
- opacity: 1;
- }
- to {
- transform: translateY(40px);
- opacity: 0;
- }
-}
-```
-
-**Micro-syntax**:
-```
-backdrop: 300ms ease-out [α0→1]
-modalEnter: 350ms ease-out [Y+40→0, α0→1]
-modalExit: 250ms ease-in [Y0→+40, α1→0]
-```
-
-### Dropdown/Menu Animations
-
-```css
-/* Dropdown slide and fade */
-.dropdown {
- animation: dropdownOpen 200ms ease-out;
- transform-origin: top;
-}
-@keyframes dropdownOpen {
- from {
- transform: scaleY(0.95);
- opacity: 0;
- }
- to {
- transform: scaleY(1);
- opacity: 1;
- }
-}
-```
-
-**Micro-syntax**:
-```
-dropdown: 200ms ease-out [scaleY0.95→1, α0→1]
-```
-
-### Sidebar/Drawer Animations
-
-```css
-/* Sidebar slide in */
-.sidebar {
- animation: slideInLeft 350ms ease-out;
-}
-@keyframes slideInLeft {
- from {
- transform: translateX(-280px);
- opacity: 0;
- }
- to {
- transform: translateX(0);
- opacity: 1;
- }
-}
-
-/* Overlay fade */
-.overlay {
- animation: overlayFade 300ms ease-out;
-}
-@keyframes overlayFade {
- from {
- opacity: 0;
- backdrop-filter: blur(0);
- }
- to {
- opacity: 1;
- backdrop-filter: blur(4px);
- }
-}
-```
-
-**Micro-syntax**:
-```
-sidebar: 350ms ease-out [X-280→0, α0→1]
-overlay: 300ms ease-out [α0→1, blur0→4px]
-```
-
----
-
-## Message/Chat UI Animations
-
-### Message Entrance
-
-```css
-/* User message - slide from right */
-.message-user {
- animation: slideInRight 400ms ease-out;
-}
-@keyframes slideInRight {
- from {
- transform: translateX(10px) translateY(20px);
- opacity: 0;
- scale: 0.9;
- }
- to {
- transform: translateX(0) translateY(0);
- opacity: 1;
- scale: 1;
- }
-}
-
-/* AI message - slide from left with bounce */
-.message-ai {
- animation: slideInLeft 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
- animation-delay: 200ms;
-}
-@keyframes slideInLeft {
- from {
- transform: translateY(15px);
- opacity: 0;
- scale: 0.95;
- }
- to {
- transform: translateY(0);
- opacity: 1;
- scale: 1;
- }
-}
-```
-
-**Micro-syntax**:
-```
-userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
-aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
-```
-
-### Typing Indicator
-
-```css
-/* Typing dots animation */
-.typing-indicator span {
- animation: typingDot 1400ms infinite;
-}
-.typing-indicator span:nth-child(2) {
- animation-delay: 200ms;
-}
-.typing-indicator span:nth-child(3) {
- animation-delay: 400ms;
-}
-@keyframes typingDot {
- 0%, 60%, 100% {
- transform: translateY(0);
- opacity: 0.4;
- }
- 30% {
- transform: translateY(-8px);
- opacity: 1;
- }
-}
-```
-
-**Micro-syntax**:
-```
-typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
-```
-
-### Status Indicators
-
-```css
-/* Online status pulse */
-.status-online {
- animation: pulse 2000ms infinite;
-}
-@keyframes pulse {
- 0%, 100% {
- opacity: 1;
- scale: 1;
- }
- 50% {
- opacity: 0.6;
- scale: 1.05;
- }
-}
-```
-
-**Micro-syntax**:
-```
-status: 2000ms ∞ [α1→0.6→1, S1→1.05→1]
-```
-
----
-
-## Form Input Animations
-
-### Focus States
-
-```css
-/* Input focus - ring and scale */
-.input {
- transition: all 200ms ease-out;
-}
-.input:focus {
- transform: scale(1.01);
- box-shadow: 0 0 0 3px var(--ring);
-}
-
-/* Input blur - return to normal */
-.input:not(:focus) {
- transition: all 150ms ease-in;
-}
-```
-
-**Micro-syntax**:
-```
-inputFocus: 200ms ease-out [S1→1.01, shadow+ring]
-inputBlur: 150ms ease-in [S1.01→1, shadow-ring]
-```
-
-### Validation States
-
-```css
-/* Error shake */
-.input-error {
- animation: shake 400ms ease-in-out;
-}
-@keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-5px); }
- 75% { transform: translateX(5px); }
-}
-
-/* Success checkmark */
-.input-success::after {
- animation: checkmark 600ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
-}
-@keyframes checkmark {
- from {
- transform: scale(0) rotate(0deg);
- opacity: 0;
- }
- to {
- transform: scale(1.2) rotate(360deg);
- opacity: 1;
- }
-}
-```
-
-**Micro-syntax**:
-```
-error: 400ms ease-in-out [X±5] shake
-success: 600ms bounce [S0→1.2, R0→360°, α0→1]
-```
-
----
-
-## Loading States
-
-### Skeleton Screens
-
-```css
-/* Skeleton shimmer */
-.skeleton {
- animation: shimmer 2000ms infinite;
- background: linear-gradient(
- 90deg,
- var(--muted) 0%,
- var(--accent) 50%,
- var(--muted) 100%
- );
- background-size: 200% 100%;
-}
-@keyframes shimmer {
- 0% { background-position: 200% 0; }
- 100% { background-position: -200% 0; }
-}
-```
-
-**Micro-syntax**:
-```
-skeleton: 2000ms ∞ [bg: muted↔accent]
-```
-
-### Spinners
-
-```css
-/* Circular spinner */
-.spinner {
- animation: spin 1000ms linear infinite;
-}
-@keyframes spin {
- from { transform: rotate(0deg); }
- to { transform: rotate(360deg); }
-}
-
-/* Pulsing dots */
-.loading-dots span {
- animation: dotPulse 1500ms infinite;
-}
-.loading-dots span:nth-child(2) { animation-delay: 200ms; }
-.loading-dots span:nth-child(3) { animation-delay: 400ms; }
-@keyframes dotPulse {
- 0%, 80%, 100% { opacity: 0.3; scale: 0.8; }
- 40% { opacity: 1; scale: 1; }
-}
-```
-
-**Micro-syntax**:
-```
-spinner: 1000ms ∞ linear [R360°]
-dotPulse: 1500ms ∞ [α0.3→1→0.3, S0.8→1→0.8] stagger+200ms
-```
-
-### Progress Bars
-
-```css
-/* Indeterminate progress */
-.progress-bar {
- animation: progress 2000ms ease-in-out infinite;
-}
-@keyframes progress {
- 0% { transform: translateX(-100%); }
- 50% { transform: translateX(0); }
- 100% { transform: translateX(100%); }
-}
-```
-
-**Micro-syntax**:
-```
-progress: 2000ms ∞ ease-in-out [X-100%→0→100%]
-```
-
----
-
-## Scroll Animations
-
-### Scroll-Triggered Fade In
-
-```css
-/* Fade in on scroll */
-.fade-in-on-scroll {
- opacity: 0;
- transform: translateY(40px);
- transition: opacity 500ms ease-out, transform 500ms ease-out;
-}
-.fade-in-on-scroll.visible {
- opacity: 1;
- transform: translateY(0);
-}
-```
-
-**Micro-syntax**:
-```
-scrollFadeIn: 500ms ease-out [Y+40→0, α0→1]
-```
-
-### Auto-Scroll
-
-```css
-/* Smooth scroll behavior */
-html {
- scroll-behavior: smooth;
-}
-
-/* Scroll hint animation */
-.scroll-hint {
- animation: scrollHint 800ms infinite;
- animation-iteration-count: 3;
-}
-@keyframes scrollHint {
- 0%, 100% { transform: translateY(0); }
- 50% { transform: translateY(5px); }
-}
-```
-
-**Micro-syntax**:
-```
-autoScroll: 400ms smooth
-scrollHint: 800ms ∞×3 [Y±5]
-```
-
----
-
-## Page Transitions
-
-### Route Changes
-
-```css
-/* Page fade out */
-.page-exit {
- animation: fadeOut 200ms ease-in;
-}
-@keyframes fadeOut {
- from { opacity: 1; }
- to { opacity: 0; }
-}
-
-/* Page fade in */
-.page-enter {
- animation: fadeIn 300ms ease-out;
-}
-@keyframes fadeIn {
- from { opacity: 0; }
- to { opacity: 1; }
-}
-```
-
-**Micro-syntax**:
-```
-pageExit: 200ms ease-in [α1→0]
-pageEnter: 300ms ease-out [α0→1]
-```
-
----
-
-## Micro-Interactions
-
-### Hover Effects
-
-```css
-/* Link underline slide */
-.link {
- position: relative;
-}
-.link::after {
- content: '';
- position: absolute;
- bottom: 0;
- left: 0;
- width: 0;
- height: 2px;
- background: currentColor;
- transition: width 250ms ease-out;
-}
-.link:hover::after {
- width: 100%;
-}
-```
-
-**Micro-syntax**:
-```
-linkHover: 250ms ease-out [width0→100%]
-```
-
-### Toggle Switches
-
-```css
-/* Toggle slide */
-.toggle-switch {
- transition: background-color 200ms ease-out;
-}
-.toggle-switch .thumb {
- transition: transform 200ms ease-out;
-}
-.toggle-switch.on .thumb {
- transform: translateX(20px);
-}
-```
-
-**Micro-syntax**:
-```
-toggle: 200ms ease-out [X0→20, bg→accent]
-```
-
----
-
-## Animation Recipes
-
-### Chat UI Complete Animation System
-
-```
-## Core Message Flow
-userMsg: 400ms ease-out [Y+20→0, X+10→0, S0.9→1]
-aiMsg: 600ms bounce [Y+15→0, S0.95→1] +200ms
-typing: 1400ms ∞ [Y±8, α0.4→1] stagger+200ms
-status: 300ms ease-out [α0.6→1, S1→1.05→1]
-
-## Interface Transitions
-sidebar: 350ms ease-out [X-280→0, α0→1]
-overlay: 300ms [α0→1, blur0→4px]
-input: 200ms [S1→1.01, shadow+ring] focus
-input: 150ms [S1.01→1, shadow-ring] blur
-
-## Button Interactions
-sendBtn: 150ms [S1→0.95→1, R±2°] press
-sendBtn: 200ms [S1→1.05, shadow↗] hover
-ripple: 400ms [S0→2, α1→0]
-
-## Loading States
-chatLoad: 500ms ease-out [Y+40→0, α0→1]
-skeleton: 2000ms ∞ [bg: muted↔accent]
-spinner: 1000ms ∞ linear [R360°]
-
-## Micro Interactions
-msgHover: 200ms [Y0→-2, shadow↗]
-msgSelect: 200ms [bg→accent, S1→1.02]
-error: 400ms [X±5] shake
-success: 600ms bounce [S0→1.2→1, R360°]
-
-## Scroll & Navigation
-autoScroll: 400ms smooth
-scrollHint: 800ms ∞×3 [Y±5]
-```
-
----
-
-## Best Practices
-
-### Do's ✅
-
-- Keep animations under 400ms for most interactions
-- Use `transform` and `opacity` for 60fps performance
-- Provide purpose for every animation
-- Use ease-out for entrances, ease-in for exits
-- Test on low-end devices
-- Respect `prefers-reduced-motion`
-- Stagger animations for lists (50-100ms delay)
-- Use consistent timing across similar interactions
-
-### Don'ts ❌
-
-- Don't animate width/height (use scale instead)
-- Don't use animations longer than 800ms
-- Don't animate too many elements at once
-- Don't use animations without purpose
-- Don't ignore accessibility preferences
-- Don't use jarring/distracting animations
-- Don't animate on every interaction
-- Don't use complex easing for simple interactions
-
----
-
-## Accessibility
-
-### Reduced Motion
-
-```css
-/* Respect user preferences */
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
-}
-```
-
-### Focus Indicators
-
-```css
-/* Always animate focus states */
-:focus-visible {
- outline: 2px solid var(--ring);
- outline-offset: 2px;
- transition: outline-offset 150ms ease-out;
-}
-```
-
----
-
-## References
-
-- [Web Animation API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)
-- [CSS Easing Functions](https://easings.net/)
-- [Animation Performance](https://web.dev/animations-guide/)
-- [Reduced Motion](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
diff --git a/.opencode/context/ui/web/cdn-resources.md b/.opencode/context/ui/web/cdn-resources.md
new file mode 100644
index 0000000..33caaae
--- /dev/null
+++ b/.opencode/context/ui/web/cdn-resources.md
@@ -0,0 +1,149 @@
+
+# CDN Resources
+
+**Purpose**: Common CDN libraries for frontend development
+
+---
+
+## CSS Frameworks
+
+### Tailwind CSS
+```html
+
+```
+
+### Flowbite
+```html
+
+
+```
+
+### Bootstrap
+```html
+
+
+```
+
+---
+
+## JavaScript Libraries
+
+### Alpine.js
+```html
+
+```
+
+### HTMX
+```html
+
+```
+
+### Chart.js
+```html
+
+```
+
+---
+
+## Animation Libraries
+
+### Animate.css
+```html
+
+```
+
+### AOS (Animate On Scroll)
+```html
+
+
+```
+
+---
+
+## Video Placeholders
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Best Practices
+
+### Version Pinning
+```html
+
+
+
+
+
+```
+
+### Integrity Hashes
+```html
+
+```
+
+---
+
+## File Naming Conventions
+
+**Design Files:**
+- Initial: `{name}_1.html` (e.g., `table_1.html`)
+- Iterations: `{name}_1_1.html`, `{name}_1_2.html`
+- Themes: `theme_1.css`, `theme_2.css`
+
+**Asset Files:**
+- Images: `hero-image.jpg`, `product-1.png`
+- Icons: `logo.svg`, `icon-menu.svg`
+- Fonts: `inter-var.woff2`
+
+---
+
+## Project Structure
+
+```
+design_iterations/
+├── theme_1.css
+├── ui_1.html
+├── ui_1_1.html (iteration)
+├── dashboard_1.html
+└── assets/
+ ├── images/
+ ├── icons/
+ └── fonts/
+```
+
+---
+
+## Accessibility Checklist
+
+**Images:**
+- [ ] All images have alt text
+- [ ] Decorative images have `alt=""` and `role="presentation"`
+- [ ] Complex images have figcaption
+
+**Icons:**
+- [ ] Icon-only buttons have `aria-label`
+- [ ] Decorative icons have `aria-hidden="true"`
+- [ ] Icons with text are hidden from screen readers
+
+---
+
+## Related
+
+- `images-guide.md` - Image guidelines
+- `icons-guide.md` - Icon systems
+- `fonts-guide.md` - Font loading
diff --git a/.opencode/context/ui/web/design-assets.md b/.opencode/context/ui/web/design-assets.md
deleted file mode 100644
index 6f5b074..0000000
--- a/.opencode/context/ui/web/design-assets.md
+++ /dev/null
@@ -1,567 +0,0 @@
-
-# Design Assets
-
-## Overview
-
-Guidelines for images, icons, fonts, and other design assets in frontend development. Focus on using reliable CDN sources and placeholder services.
-
-## Quick Reference
-
-**Images**: Unsplash, placehold.co (never make up URLs)
-**Icons**: Lucide (default), Heroicons, Font Awesome
-**Fonts**: Google Fonts
-**CDN**: Use established CDN services only
-
----
-
-## Image Guidelines
-
-### Placeholder Images
-
-**Rule**: NEVER make up image URLs. Always use known placeholder services.
-
-#### Unsplash (Recommended)
-
-**Random Images**:
-```html
-
-
-
-
-
-
-
-```
-
-**Categories Available**:
-- nature, landscape, mountains, ocean, forest
-- technology, computer, code, workspace
-- people, portrait, business, team
-- food, coffee, restaurant
-- architecture, building, interior
-- travel, city, street
-- abstract, pattern, texture
-
-**Specific Images**:
-```html
-
-
-```
-
-#### Placehold.co
-
-**Simple Placeholders**:
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-#### Picsum Photos
-
-**Random Photos**:
-```html
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Image Best Practices
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
----
-
-## Icon Systems
-
-### Lucide Icons (Recommended Default)
-
-**Loading**:
-```html
-
-
-
-
-
-```
-
-**Usage**:
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-**Common Icons**:
-```
-Navigation: home, menu, x, chevron-down, chevron-up, arrow-left, arrow-right
-User: user, user-plus, users, user-check, user-x
-Actions: edit, trash, save, download, upload, share, copy
-Communication: mail, message-circle, phone, send
-Media: image, video, music, file, folder
-UI: search, settings, bell, heart, star, bookmark
-Status: check, x, alert-circle, info, help-circle
-```
-
-### Heroicons
-
-**Loading**:
-```html
-
-
-```
-
-**Usage**:
-```html
-
-
-
-
-
-
-
-
-
-```
-
-### Font Awesome
-
-**Loading**:
-```html
-
-
-```
-
-**Usage**:
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Icon Best Practices
-
-```html
-
-
-
-
-
-
-
-
- Visit website
-
-
-
-
-
-
-
-
-
-
- Featured
-
-```
-
----
-
-## Font Loading
-
-### Google Fonts (Recommended)
-
-**Loading**:
-```html
-
-
-
-
-
-
-
-
-
-```
-
-**Usage**:
-```css
-body {
- font-family: 'Inter', sans-serif;
-}
-
-code, pre {
- font-family: 'JetBrains Mono', monospace;
-}
-```
-
-**Popular Font Combinations**:
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Font Loading Strategies
-
-```html
-
-
-
-
-
-
-
-
-```
-
----
-
-## CDN Resources
-
-### CSS Frameworks
-
-```html
-
-
-
-
-
-
-
-
-
-
-```
-
-### JavaScript Libraries
-
-```html
-
-
-
-
-
-
-
-
-```
-
-### Utility Libraries
-
-```html
-
-
-
-
-
-
-```
-
----
-
-## SVG Assets
-
-### Inline SVG
-
-```html
-
-
-
-
-
-
-
-
-
-```
-
-### SVG Backgrounds
-
-```html
-
-
-```
-
----
-
-## Video Assets
-
-### Placeholder Videos
-
-```html
-
-
-
- Your browser does not support the video tag.
-
-
-
-
-
-
-```
-
----
-
-## Asset Organization
-
-### File Structure
-
-```
-design_iterations/
-├── theme_1.css
-├── ui_1.html
-├── ui_1_1.html (iteration)
-├── ui_1_2.html (iteration)
-├── dashboard_1.html
-└── assets/
- ├── images/
- ├── icons/
- └── fonts/
-```
-
-### Naming Conventions
-
-**Design Files**:
-- Initial: `{design_name}_1.html` (e.g., `table_1.html`)
-- Iterations: `{design_name}_1_1.html`, `{design_name}_1_2.html`
-- Theme files: `theme_1.css`, `theme_2.css`
-
-**Asset Files**:
-- Images: `hero-image.jpg`, `product-1.png`
-- Icons: `logo.svg`, `icon-menu.svg`
-- Fonts: `inter-var.woff2`, `jetbrains-mono.woff2`
-
----
-
-## Performance Optimization
-
-### Image Optimization
-
-```html
-
-
-
-
-
-
-
-
-
-
-
-
-```
-
-### Font Optimization
-
-```html
-
-
-
-
-
-```
-
-### CDN Best Practices
-
-```html
-
-
-
-
-
-```
-
----
-
-## Best Practices
-
-### Do's ✅
-
-- Use established placeholder services (Unsplash, placehold.co)
-- Always provide alt text for images
-- Use Lucide as default icon library
-- Load fonts from Google Fonts
-- Use lazy loading for images
-- Provide responsive image srcsets
-- Use semantic SVG with accessible labels
-- Specify CDN versions for stability
-- Optimize images before deployment
-- Use modern image formats (WebP, AVIF)
-
-### Don'ts ❌
-
-- Don't make up image URLs
-- Don't use images without alt text
-- Don't load unnecessary icon libraries
-- Don't use too many font families (2-3 max)
-- Don't skip lazy loading
-- Don't use unoptimized images
-- Don't forget ARIA labels for icon buttons
-- Don't use latest CDN versions in production
-- Don't load fonts synchronously
-- Don't use decorative images in content
-
----
-
-## Accessibility
-
-### Image Accessibility
-
-```html
-
-
-
-
-
-
-
-
-
-
- The diagram shows three layers: frontend, API, and database.
-
-
-```
-
-### Icon Accessibility
-
-```html
-
-
-
- Delete
-
-
-
-
-
-
-
-
-
-
- Search
-
-```
-
----
-
-## References
-
-- [Unsplash Source](https://source.unsplash.com/)
-- [Placehold.co](https://placehold.co/)
-- [Lucide Icons](https://lucide.dev/)
-- [Google Fonts](https://fonts.google.com/)
-- [Web.dev Image Optimization](https://web.dev/fast/#optimize-your-images)
diff --git a/.opencode/context/ui/web/design/guides/premium-dark-ui-advanced.md b/.opencode/context/ui/web/design/guides/premium-dark-ui-advanced.md
new file mode 100644
index 0000000..8fa83f6
--- /dev/null
+++ b/.opencode/context/ui/web/design/guides/premium-dark-ui-advanced.md
@@ -0,0 +1,145 @@
+# Premium Dark UI - Advanced
+
+Animations, accessibility, and checklists for premium dark UI.
+
+---
+
+## Animations
+
+### Hover Effects
+
+```tsx
+// Card hover
+
+ Card content
+
+
+// Button hover
+
+ Button
+
+
+// Image hover
+
+```
+
+### Scroll Reveal
+
+```tsx
+// npm install react-intersection-observer
+import { useInView } from 'react-intersection-observer'
+
+function Component() {
+ const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1 })
+
+ return (
+
+ Content
+
+ )
+}
+```
+
+---
+
+## Accessibility
+
+### Color Contrast
+
+- White on `#0a0f0d`: 21:1 (AAA) ✅
+- `slate-300` on `#0a0f0d`: 12.6:1 (AAA) ✅
+- `#80cca5` on `#0a0f0d`: 7.8:1 (AAA) ✅
+
+### Focus States
+
+```tsx
+
+ Button
+
+```
+
+### Semantic HTML
+
+```tsx
+
+
+
+```
+
+---
+
+## Quick Wins (Instant Premium Feel)
+
+1. Replace all backgrounds with `#0a0f0d`
+2. Use only white and slate-300 for text
+3. Make all CTAs `#80cca5` green
+4. Add backdrop-blur-xl to all cards
+5. Use rounded-2xl for cards, rounded-full for buttons
+6. Add one radial glow to hero section
+7. Increase padding (py-24 for sections, p-8 for cards)
+8. Add hover effects to all interactive elements
+
+---
+
+## Common Mistakes
+
+❌ **DON'T**:
+- Use light backgrounds (`bg-white`, `bg-slate-100`)
+- Use `dark:` variants (this is dark-only)
+- Mix multiple accent colors
+- Use high-opacity glass (`bg-white/50`)
+- Skip backdrop blur on glass elements
+- Use small padding (looks cramped)
+- Forget hover states
+- Ignore mobile layout
+
+✅ **DO**:
+- Stick to the color palette
+- Use consistent spacing (multiples of 4)
+- Add subtle animations
+- Test on mobile first
+- Keep glass opacity low
+- Use radial glows sparingly
+- Maintain high contrast for text
+
+---
+
+## Final Checklist
+
+Before launching, verify:
+
+- [ ] All backgrounds are `#0a0f0d` or `black`
+- [ ] All headings are `text-white`
+- [ ] All body text is `text-slate-300`
+- [ ] All CTAs are `#80cca5` green
+- [ ] All cards have `backdrop-blur-xl`
+- [ ] All cards use `bg-white/[0.02]`
+- [ ] All buttons have hover states
+- [ ] All forms have focus states
+- [ ] Spacing uses multiples of 4
+- [ ] Mobile layout tested
+- [ ] Radial glows are subtle (not overwhelming)
+- [ ] No light mode variants (`dark:`)
+- [ ] Contrast ratios meet WCAG AA minimum
+
+---
+
+**Time to premium**: 30-60 minutes
+**Maintenance**: Easy (4 components, 1 color palette)
+**Scalability**: High (reusable components)
+
+---
+
+## Related Files
+
+- [Colors & Typography](./premium-dark-ui-colors.md)
+- [Components](./premium-dark-ui-components.md)
+- [Layouts](./premium-dark-ui-layouts.md)
+- [Full System Guide](./premium-dark-ui-system.md)
diff --git a/.opencode/context/ui/web/design/guides/premium-dark-ui-colors.md b/.opencode/context/ui/web/design/guides/premium-dark-ui-colors.md
new file mode 100644
index 0000000..f31e5f2
--- /dev/null
+++ b/.opencode/context/ui/web/design/guides/premium-dark-ui-colors.md
@@ -0,0 +1,164 @@
+# Premium Dark UI - Colors & Typography
+
+Quick reference for colors, spacing, and typography in premium dark UI.
+
+---
+
+## Color Palette
+
+```css
+/* Backgrounds */
+--bg-deep-dark: #0a0f0d; /* Main background */
+--bg-black: #000000; /* Alternate sections */
+
+/* Text */
+--text-heading: #ffffff; /* Headings */
+--text-body: #cbd5e1; /* Body (slate-300) */
+--text-muted: #94a3b8; /* Muted (slate-400) */
+
+/* Accent */
+--accent: #80cca5; /* Green - CTAs, links, highlights */
+--accent-hover: #6bb890; /* Hover state */
+
+/* Glass */
+--glass-bg: rgba(255, 255, 255, 0.02);
+--glass-border: rgba(255, 255, 255, 0.1);
+```
+
+**Rule**: Use ONLY these colors. No exceptions.
+
+---
+
+## Spacing System
+
+```css
+/* Use these values ONLY */
+py-4 /* 16px - Small spacing */
+py-8 /* 32px - Medium spacing */
+py-12 /* 48px - Large spacing */
+py-24 /* 96px - Section spacing */
+
+px-4 /* 16px - Mobile padding */
+px-8 /* 32px - Card padding */
+px-12 /* 48px - Large card padding */
+
+gap-4 /* 16px - Small gaps */
+gap-8 /* 32px - Medium gaps */
+gap-12 /* 48px - Large gaps */
+
+mb-4 /* 16px - Small margin bottom */
+mb-6 /* 24px - Medium margin bottom */
+mb-8 /* 32px - Large margin bottom */
+mb-16 /* 64px - Section margin bottom */
+```
+
+**Rule**: Stick to multiples of 4 (4, 8, 12, 16, 24, 32, 48, 64, 96).
+
+---
+
+## Typography Scale
+
+```tsx
+// Page Title (H1)
+
+ Page Title
+
+
+// Section Title (H2)
+
+ Section Title Accent
+
+
+// Subsection (H3)
+
+ Subsection
+
+
+// Body Text
+
+ Body text goes here
+
+
+// Muted Text
+
+ Secondary information
+
+
+// Accent Text
+
+ Call to action text
+
+```
+
+---
+
+## Buttons & Links
+
+```tsx
+// Primary CTA Button
+
+ Primary Action
+
+
+// Secondary Button
+
+ Secondary Action
+
+
+// Link
+
+ Link Text
+
+```
+
+---
+
+## Form Inputs
+
+```tsx
+// Text Input
+
+
+// Label
+
+ Field Label
+
+```
+
+---
+
+## Related Files
+
+- [Components](./premium-dark-ui-components.md)
+- [Layouts](./premium-dark-ui-layouts.md)
+- [Advanced](./premium-dark-ui-advanced.md)
diff --git a/.opencode/context/ui/web/design/guides/premium-dark-ui-components.md b/.opencode/context/ui/web/design/guides/premium-dark-ui-components.md
new file mode 100644
index 0000000..02ed6a3
--- /dev/null
+++ b/.opencode/context/ui/web/design/guides/premium-dark-ui-components.md
@@ -0,0 +1,142 @@
+# Premium Dark UI - Core Components
+
+The 4 building blocks for premium dark UI.
+
+---
+
+## A. Section Wrapper
+
+```tsx
+// PremiumSection.tsx
+export function PremiumSection({ children, withGlow = true }) {
+ return (
+
+ {withGlow && (
+
+ )}
+
+ {children}
+
+
+ )
+}
+```
+
+---
+
+## B. Glass Card
+
+```tsx
+// PremiumCard.tsx
+export function PremiumCard({ children, className = "" }) {
+ return (
+
+ {children}
+
+ )
+}
+```
+
+---
+
+## C. Heading
+
+```tsx
+// PremiumHeading.tsx
+export function PremiumHeading({ children, accent, as: Tag = 'h2' }) {
+ return (
+
+ {children}
+ {accent && {accent} }
+
+ )
+}
+```
+
+---
+
+## D. Radial Glow
+
+```tsx
+// RadialGlow.tsx
+export function RadialGlow({ className = "" }) {
+ return (
+
+ )
+}
+```
+
+---
+
+## Glassmorphism Rules
+
+✅ **DO**:
+- Use `bg-white/[0.02]` for card backgrounds
+- Add `backdrop-blur-xl` for blur effect
+- Use `border-white/10` for subtle borders
+- Keep opacity LOW (0.02 to 0.04)
+- Layer multiple glass surfaces
+
+❌ **DON'T**:
+- Use high opacity (breaks glass effect)
+- Skip backdrop blur (looks flat)
+- Use on light backgrounds (invisible)
+- Overuse (reserve for cards/panels)
+
+---
+
+## Radial Glow Usage
+
+### When to Use:
+- ✅ Hero sections (large, centered)
+- ✅ Important CTAs (behind forms)
+- ✅ Section breaks (corner glows)
+- ❌ Every section (overwhelming)
+- ❌ Small components (too subtle)
+
+### Sizes:
+- **Small**: 400px (subtle accent)
+- **Medium**: 600px (default)
+- **Large**: 800px (hero sections)
+
+### Positioning:
+```tsx
+// Centered
+
+
+// Top-left
+
+
+// Bottom-right
+
+```
+
+---
+
+## Related Files
+
+- [Colors & Typography](./premium-dark-ui-colors.md)
+- [Layouts](./premium-dark-ui-layouts.md)
+- [Advanced](./premium-dark-ui-advanced.md)
diff --git a/.opencode/context/ui/web/design/guides/premium-dark-ui-layouts.md b/.opencode/context/ui/web/design/guides/premium-dark-ui-layouts.md
new file mode 100644
index 0000000..dd1ce36
--- /dev/null
+++ b/.opencode/context/ui/web/design/guides/premium-dark-ui-layouts.md
@@ -0,0 +1,173 @@
+# Premium Dark UI - Layouts
+
+Common layout patterns for premium dark UI.
+
+---
+
+## Hero Section
+
+```tsx
+
+
+
+ Build Production
+
+
+ Your subtitle goes here
+
+
+ Get Started
+
+
+
+```
+
+---
+
+## Feature Grid
+
+```tsx
+
+
+
+ {features.map((feature) => (
+
+
+
+ {feature.title}
+
+
+ {feature.description}
+
+
+ ))}
+
+
+```
+
+---
+
+## CTA Section
+
+```tsx
+
+
+
+
+ Get
+
+
+ Join thousands of users
+
+
+
+
+
+
+
+```
+
+---
+
+## Responsive Design
+
+```tsx
+// Mobile: Stack, Desktop: Grid
+
+ {/* Items */}
+
+
+// Mobile: Full width, Desktop: Constrained
+
+ {/* Content */}
+
+
+// Mobile: Small text, Desktop: Large text
+
+ Responsive Heading
+
+
+// Mobile: Center, Desktop: Left
+
+ {/* Content */}
+
+```
+
+**Breakpoints**:
+- `sm`: 640px (tablets)
+- `md`: 768px (tablets/small laptops)
+- `lg`: 1024px (laptops)
+- `xl`: 1280px (desktops)
+
+---
+
+## Starter Template
+
+```tsx
+export default function Page() {
+ return (
+
+ {/* Hero */}
+
+
+
+ Your
+
+
Subtitle here
+
+ Get Started
+
+
+
+
+ {/* Features */}
+
+
+
+
+ Feature 1
+ Description
+
+ {/* More cards */}
+
+
+
+ {/* CTA */}
+
+
+
+
+ )
+}
+```
+
+---
+
+## Related Files
+
+- [Colors & Typography](./premium-dark-ui-colors.md)
+- [Components](./premium-dark-ui-components.md)
+- [Advanced](./premium-dark-ui-advanced.md)
diff --git a/.opencode/context/ui/web/design/guides/premium-dark-ui-quick-start.md b/.opencode/context/ui/web/design/guides/premium-dark-ui-quick-start.md
deleted file mode 100644
index 1d67a0c..0000000
--- a/.opencode/context/ui/web/design/guides/premium-dark-ui-quick-start.md
+++ /dev/null
@@ -1,638 +0,0 @@
-# Premium Dark UI - Quick Start Guide
-
-**Goal**: Create a sophisticated, modern dark website with glassmorphism and depth.
-
-**Time to implement**: 30-60 minutes
-
----
-
-## 1. COLOR PALETTE (Copy This)
-
-```css
-/* Backgrounds */
---bg-deep-dark: #0a0f0d; /* Main background */
---bg-black: #000000; /* Alternate sections */
-
-/* Text */
---text-heading: #ffffff; /* Headings */
---text-body: #cbd5e1; /* Body (slate-300) */
---text-muted: #94a3b8; /* Muted (slate-400) */
-
-/* Accent */
---accent: #80cca5; /* Green - CTAs, links, highlights */
---accent-hover: #6bb890; /* Hover state */
-
-/* Glass */
---glass-bg: rgba(255, 255, 255, 0.02);
---glass-border: rgba(255, 255, 255, 0.1);
-```
-
-**Rule**: Use ONLY these colors. No exceptions.
-
----
-
-## 2. CORE COMPONENTS (4 Building Blocks)
-
-### A. Section Wrapper
-
-```tsx
-// PremiumSection.tsx
-export function PremiumSection({ children, withGlow = true }) {
- return (
-
- {withGlow && (
-
- )}
-
- {children}
-
-
- )
-}
-```
-
-### B. Glass Card
-
-```tsx
-// PremiumCard.tsx
-export function PremiumCard({ children, className = "" }) {
- return (
-
- {children}
-
- )
-}
-```
-
-### C. Heading
-
-```tsx
-// PremiumHeading.tsx
-export function PremiumHeading({ children, accent, as: Tag = 'h2' }) {
- return (
-
- {children}
- {accent && {accent} }
-
- )
-}
-```
-
-### D. Radial Glow
-
-```tsx
-// RadialGlow.tsx
-export function RadialGlow({ className = "" }) {
- return (
-
- )
-}
-```
-
----
-
-## 3. SPACING SYSTEM
-
-```css
-/* Use these values ONLY */
-py-4 /* 16px - Small spacing */
-py-8 /* 32px - Medium spacing */
-py-12 /* 48px - Large spacing */
-py-24 /* 96px - Section spacing */
-
-px-4 /* 16px - Mobile padding */
-px-8 /* 32px - Card padding */
-px-12 /* 48px - Large card padding */
-
-gap-4 /* 16px - Small gaps */
-gap-8 /* 32px - Medium gaps */
-gap-12 /* 48px - Large gaps */
-
-mb-4 /* 16px - Small margin bottom */
-mb-6 /* 24px - Medium margin bottom */
-mb-8 /* 32px - Large margin bottom */
-mb-16 /* 64px - Section margin bottom */
-```
-
-**Rule**: Stick to multiples of 4 (4, 8, 12, 16, 24, 32, 48, 64, 96).
-
----
-
-## 4. TYPOGRAPHY SCALE
-
-```tsx
-// Page Title (H1)
-
- Page Title
-
-
-// Section Title (H2)
-
- Section Title Accent
-
-
-// Subsection (H3)
-
- Subsection
-
-
-// Body Text
-
- Body text goes here
-
-
-// Muted Text
-
- Secondary information
-
-
-// Accent Text
-
- Call to action text
-
-```
-
----
-
-## 5. BUTTONS & LINKS
-
-```tsx
-// Primary CTA Button
-
- Primary Action
-
-
-// Secondary Button
-
- Secondary Action
-
-
-// Link
-
- Link Text
-
-```
-
----
-
-## 6. FORM INPUTS
-
-```tsx
-// Text Input
-
-
-// Textarea
-
-
-// Label
-
- Field Label
-
-```
-
----
-
-## 7. COMMON LAYOUTS
-
-### Hero Section
-
-```tsx
-
-
-
- Build Production
-
-
- Your subtitle goes here
-
-
- Get Started
-
-
-
-```
-
-### Feature Grid
-
-```tsx
-
-
-
- {features.map((feature) => (
-
-
-
- {feature.title}
-
-
- {feature.description}
-
-
- ))}
-
-
-```
-
-### CTA Section
-
-```tsx
-
-
-
-
- Get
-
-
- Join thousands of users
-
-
-
-
-
-
-
-```
-
----
-
-## 8. GLASSMORPHISM RULES
-
-✅ **DO**:
-- Use `bg-white/[0.02]` for card backgrounds
-- Add `backdrop-blur-xl` for blur effect
-- Use `border-white/10` for subtle borders
-- Keep opacity LOW (0.02 to 0.04)
-- Layer multiple glass surfaces
-
-❌ **DON'T**:
-- Use high opacity (breaks glass effect)
-- Skip backdrop blur (looks flat)
-- Use on light backgrounds (invisible)
-- Overuse (reserve for cards/panels)
-
-**Example**:
-```tsx
-
- Glass content
-
-```
-
----
-
-## 9. RADIAL GLOW USAGE
-
-### When to Use:
-- ✅ Hero sections (large, centered)
-- ✅ Important CTAs (behind forms)
-- ✅ Section breaks (corner glows)
-- ❌ Every section (overwhelming)
-- ❌ Small components (too subtle)
-
-### Sizes:
-- **Small**: 400px (subtle accent)
-- **Medium**: 600px (default)
-- **Large**: 800px (hero sections)
-
-### Positioning:
-```tsx
-// Centered
-
-
-// Top-left
-
-
-// Bottom-right
-
-```
-
----
-
-## 10. RESPONSIVE DESIGN
-
-```tsx
-// Mobile: Stack, Desktop: Grid
-
- {/* Items */}
-
-
-// Mobile: Full width, Desktop: Constrained
-
- {/* Content */}
-
-
-// Mobile: Small text, Desktop: Large text
-
- Responsive Heading
-
-
-// Mobile: Center, Desktop: Left
-
- {/* Content */}
-
-```
-
-**Breakpoints**:
-- `sm`: 640px (tablets)
-- `md`: 768px (tablets/small laptops)
-- `lg`: 1024px (laptops)
-- `xl`: 1280px (desktops)
-
----
-
-## 11. ANIMATIONS
-
-### Hover Effects
-
-```tsx
-// Card hover
-
- Card content
-
-
-// Button hover
-
- Button
-
-
-// Image hover
-
-```
-
-### Scroll Reveal (Optional)
-
-```tsx
-// Install: npm install react-intersection-observer
-import { useInView } from 'react-intersection-observer'
-
-function Component() {
- const { ref, inView } = useInView({ triggerOnce: true, threshold: 0.1 })
-
- return (
-
- Content
-
- )
-}
-```
-
----
-
-## 12. ACCESSIBILITY CHECKLIST
-
-✅ **Color Contrast**:
-- White on `#0a0f0d`: 21:1 (AAA) ✅
-- `slate-300` on `#0a0f0d`: 12.6:1 (AAA) ✅
-- `#80cca5` on `#0a0f0d`: 7.8:1 (AAA) ✅
-
-✅ **Focus States**:
-```tsx
-
- Button
-
-```
-
-✅ **Semantic HTML**:
-```tsx
-
-
-
-```
-
-✅ **Alt Text**:
-```tsx
-
-```
-
----
-
-## 13. QUICK WINS (Instant Premium Feel)
-
-1. **Replace all backgrounds** with `#0a0f0d`
-2. **Use only white and slate-300** for text
-3. **Make all CTAs** `#80cca5` green
-4. **Add backdrop-blur-xl** to all cards
-5. **Use rounded-2xl** for cards, `rounded-full` for buttons
-6. **Add one radial glow** to hero section
-7. **Increase padding** (py-24 for sections, p-8 for cards)
-8. **Add hover effects** to all interactive elements
-
----
-
-## 14. COMMON MISTAKES TO AVOID
-
-❌ **Don't**:
-- Use light backgrounds (`bg-white`, `bg-slate-100`)
-- Use `dark:` variants (this is dark-only)
-- Mix multiple accent colors
-- Use high-opacity glass (`bg-white/50`)
-- Skip backdrop blur on glass elements
-- Use small padding (looks cramped)
-- Forget hover states
-- Ignore mobile layout
-
-✅ **Do**:
-- Stick to the color palette
-- Use consistent spacing (multiples of 4)
-- Add subtle animations
-- Test on mobile first
-- Keep glass opacity low
-- Use radial glows sparingly
-- Maintain high contrast for text
-
----
-
-## 15. COPY-PASTE STARTER TEMPLATE
-
-```tsx
-import React from 'react'
-
-// 1. Copy the 4 core components (Section A-D above)
-// 2. Use this page structure:
-export default function Page() {
- return (
-
- {/* Hero */}
-
-
-
- Your
-
-
- Subtitle goes here
-
-
- Get Started
-
-
-
-
- {/* Features */}
-
-
-
-
- Feature 1
- Description
-
-
- Feature 2
- Description
-
-
- Feature 3
- Description
-
-
-
-
- {/* CTA */}
-
-
-
-
- )
-}
-```
-
----
-
-## 16. FINAL CHECKLIST
-
-Before launching, verify:
-
-- [ ] All backgrounds are `#0a0f0d` or `black`
-- [ ] All headings are `text-white`
-- [ ] All body text is `text-slate-300`
-- [ ] All CTAs are `#80cca5` green
-- [ ] All cards have `backdrop-blur-xl`
-- [ ] All cards use `bg-white/[0.02]`
-- [ ] All buttons have hover states
-- [ ] All forms have focus states
-- [ ] Spacing uses multiples of 4
-- [ ] Mobile layout tested
-- [ ] Radial glows are subtle (not overwhelming)
-- [ ] No light mode variants (`dark:`)
-- [ ] Contrast ratios meet WCAG AA minimum
-
----
-
-## DONE! 🎉
-
-You now have everything needed to create a premium dark UI.
-
-**Time to premium**: 30-60 minutes
-**Maintenance**: Easy (4 components, 1 color palette)
-**Scalability**: High (reusable components)
-
-**Questions?** Reference the full guide: `.opencode/context/ui/web/design/guides/premium-dark-ui-system.md`
diff --git a/.opencode/context/ui/web/fonts-guide.md b/.opencode/context/ui/web/fonts-guide.md
new file mode 100644
index 0000000..7e5fa3d
--- /dev/null
+++ b/.opencode/context/ui/web/fonts-guide.md
@@ -0,0 +1,118 @@
+
+# Font Loading
+
+**Purpose**: Guidelines for loading and using web fonts
+
+---
+
+## Quick Reference
+
+**Recommended**: Google Fonts with preconnect
+
+```html
+
+
+
+```
+
+---
+
+## Google Fonts (Recommended)
+
+### Basic Loading
+```html
+
+
+
+```
+
+### Multiple Fonts
+```html
+
+```
+
+### CSS Usage
+```css
+body {
+ font-family: 'Inter', sans-serif;
+}
+
+code, pre {
+ font-family: 'JetBrains Mono', monospace;
+}
+```
+
+---
+
+## Popular Font Combinations
+
+### Modern UI
+```html
+
+```
+
+### Professional
+```html
+
+```
+
+### Editorial
+```html
+
+```
+
+### Friendly
+```html
+
+```
+
+---
+
+## Performance Optimization
+
+### Subset Fonts
+```html
+
+
+```
+
+### Preload Critical Fonts
+```html
+
+```
+
+### Self-Hosted Fonts
+```html
+
+```
+
+---
+
+## Best Practices
+
+**Do ✅:**
+- Use `font-display: swap` (included in Google Fonts URL)
+- Preconnect to font servers
+- Limit to 2-3 font families
+- Subset when possible
+- Preload critical fonts
+
+**Don't ❌:**
+- Load too many font weights
+- Use fonts synchronously (blocking)
+- Load fonts you don't need
+
+---
+
+## Related
+
+- `images-guide.md` - Image guidelines
+- `icons-guide.md` - Icon systems
+- `cdn-resources.md` - CDN libraries
diff --git a/.opencode/context/ui/web/icons-guide.md b/.opencode/context/ui/web/icons-guide.md
new file mode 100644
index 0000000..c6fa040
--- /dev/null
+++ b/.opencode/context/ui/web/icons-guide.md
@@ -0,0 +1,144 @@
+
+# Icon Systems
+
+**Purpose**: Guidelines for using icon libraries
+
+---
+
+## Quick Reference
+
+| Library | Loading | Recommended For |
+|---------|---------|-----------------|
+| Lucide | CDN script | Default choice |
+| Heroicons | Inline SVG | Tailwind projects |
+| Font Awesome | CDN CSS | Brand icons |
+
+---
+
+## Lucide Icons (Recommended Default)
+
+### Loading
+```html
+
+
+
+```
+
+### Usage
+```html
+
+
+
+
+
+
+```
+
+### Common Icons
+```
+Navigation: home, menu, x, chevron-down, arrow-left, arrow-right
+User: user, user-plus, users, user-check
+Actions: edit, trash, save, download, upload, share, copy
+Communication: mail, message-circle, phone, send
+Media: image, video, music, file, folder
+UI: search, settings, bell, heart, star, bookmark
+Status: check, x, alert-circle, info, help-circle
+```
+
+---
+
+## Heroicons
+
+### Usage (Inline SVG)
+```html
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Font Awesome
+
+### Loading
+```html
+
+```
+
+### Usage
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Accessibility Best Practices
+
+```html
+
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+
+ Featured
+
+
+
+
+
+ Search
+
+```
+
+---
+
+## Custom SVG
+
+```html
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Related
+
+- `images-guide.md` - Image guidelines
+- `fonts-guide.md` - Font loading
+- `cdn-resources.md` - CDN libraries
diff --git a/.opencode/context/ui/web/images-guide.md b/.opencode/context/ui/web/images-guide.md
new file mode 100644
index 0000000..925600d
--- /dev/null
+++ b/.opencode/context/ui/web/images-guide.md
@@ -0,0 +1,127 @@
+
+# Image Guidelines
+
+**Purpose**: Guidelines for placeholder and responsive images
+
+---
+
+## Quick Reference
+
+**Rule**: NEVER make up image URLs. Always use known placeholder services.
+
+| Service | Best For | URL Pattern |
+|---------|----------|-------------|
+| Unsplash | Real photos | `source.unsplash.com/random/WxH/?category` |
+| Placehold.co | Simple placeholders | `placehold.co/WxH` |
+| Picsum | Random photos | `picsum.photos/W/H` |
+
+---
+
+## Unsplash (Recommended)
+
+### Random Images
+```html
+
+
+
+```
+
+### Categories
+- nature, landscape, mountains, ocean, forest
+- technology, computer, code, workspace
+- people, portrait, business, team
+- food, coffee, restaurant
+- architecture, building, interior
+- travel, city, street
+- abstract, pattern, texture
+
+### Specific Images
+```html
+
+```
+
+---
+
+## Placehold.co
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Picsum Photos
+
+```html
+
+
+
+
+```
+
+---
+
+## Responsive Images
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Optimization
+
+```html
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Related
+
+- `icons-guide.md` - Icon systems
+- `fonts-guide.md` - Font loading
+- `cdn-resources.md` - CDN libraries
diff --git a/.opencode/context/ui/web/navigation.md b/.opencode/context/ui/web/navigation.md
index 0ca83c0..2bc73a6 100644
--- a/.opencode/context/ui/web/navigation.md
+++ b/.opencode/context/ui/web/navigation.md
@@ -12,11 +12,19 @@
| File | Description | Priority |
|------|-------------|----------|
-| [animation-patterns.md](animation-patterns.md) | CSS animations, transitions, micro-interactions | high |
+| [animation-basics.md](animation-basics.md) | Animation fundamentals, timing, easing | high |
+| [animation-components.md](animation-components.md) | Button, card, modal, dropdown animations | high |
+| [animation-chat.md](animation-chat.md) | Chat UI and message animations | medium |
+| [animation-loading.md](animation-loading.md) | Skeleton, spinner, progress animations | medium |
+| [animation-forms.md](animation-forms.md) | Form input and validation animations | medium |
+| [animation-advanced.md](animation-advanced.md) | Recipes, best practices, accessibility | medium |
| [ui-styling-standards.md](ui-styling-standards.md) | CSS frameworks, Tailwind patterns, styling best practices | high |
| [react-patterns.md](react-patterns.md) | Modern React patterns, hooks, component design | high |
| [design-systems.md](design-systems.md) | Design system principles and component libraries | medium |
-| [design-assets.md](design-assets.md) | Icons, fonts, images, and asset management | medium |
+| [images-guide.md](images-guide.md) | Placeholder and responsive images | medium |
+| [icons-guide.md](icons-guide.md) | Icon systems (Lucide, Heroicons, FA) | medium |
+| [fonts-guide.md](fonts-guide.md) | Font loading and optimization | medium |
+| [cdn-resources.md](cdn-resources.md) | CDN libraries and resources | medium |
### Subcategories
@@ -34,8 +42,10 @@
3. Reference `animation-patterns.md` (if animations needed)
### For animation work:
-1. Load `animation-patterns.md` (CSS animations, transitions)
-2. Reference `design/` subcategory for advanced patterns
+1. Load `animation-basics.md` (fundamentals, timing, easing)
+2. Load `animation-components.md` (UI component animations)
+3. Reference `animation-chat.md` for chat UI patterns
+4. Reference `animation-advanced.md` for recipes and accessibility
### For scroll animations:
1. Navigate to `design/` subcategory
@@ -59,10 +69,10 @@ This subcategory covers:
## File Summaries
-### animation-patterns.md
-CSS animations, micro-interactions, and UI transitions. Covers timing standards, easing functions, button/card interactions, modals, loading states, and accessibility.
+### animation-basics.md, animation-components.md, animation-chat.md, animation-loading.md, animation-forms.md, animation-advanced.md
+CSS animations, micro-interactions, and UI transitions split into focused modules.
-**Key topics**: Animation micro-syntax, 60fps performance, reduced motion, chat UI animations
+**Key topics**: Animation micro-syntax, 60fps performance, reduced motion, chat UI animations, component patterns
### ui-styling-standards.md
CSS framework usage, Tailwind CSS patterns, responsive design, and styling best practices.
@@ -79,10 +89,10 @@ Design system principles, component libraries, and maintaining consistency acros
**Key topics**: Design tokens, component APIs, documentation, versioning
-### design-assets.md
-Managing icons, fonts, images, and other design assets in web applications.
+### images-guide.md, icons-guide.md, fonts-guide.md, cdn-resources.md
+Managing design assets in web applications - split into focused guides.
-**Key topics**: Icon libraries (Lucide, Font Awesome), web fonts, image optimization
+**Key topics**: Placeholder images, icon libraries (Lucide, Heroicons), web fonts, CDN resources
---
@@ -101,6 +111,6 @@ Managing icons, fonts, images, and other design assets in web applications.
---
## Statistics
-- Core files: 5
+- Core files: 8
- Subcategories: 1 (design/)
-- **Total context files**: 5 + design subcategory
+- **Total context files**: 8 + design subcategory
diff --git a/packages/compatibility-layer/.eslintrc.json b/packages/compatibility-layer/.eslintrc.json
new file mode 100644
index 0000000..5335060
--- /dev/null
+++ b/packages/compatibility-layer/.eslintrc.json
@@ -0,0 +1,25 @@
+{
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": 2022,
+ "sourceType": "module",
+ "project": "./tsconfig.json"
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:@typescript-eslint/recommended",
+ "plugin:@typescript-eslint/recommended-requiring-type-checking"
+ ],
+ "plugins": ["@typescript-eslint"],
+ "rules": {
+ "@typescript-eslint/explicit-function-return-type": "warn",
+ "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
+ "@typescript-eslint/no-explicit-any": "warn",
+ "no-console": ["warn", { "allow": ["warn", "error", "log"] }]
+ },
+ "env": {
+ "node": true,
+ "es2022": true
+ },
+ "ignorePatterns": ["**/__tests__/**", "**/*.test.ts", "**/*.spec.ts"]
+}
diff --git a/packages/compatibility-layer/.gitignore b/packages/compatibility-layer/.gitignore
new file mode 100644
index 0000000..cc13cd1
--- /dev/null
+++ b/packages/compatibility-layer/.gitignore
@@ -0,0 +1,35 @@
+# Dependencies
+node_modules/
+
+# Build output
+dist/
+*.tsbuildinfo
+
+# Coverage
+coverage/
+.nyc_output/
+
+# IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# Logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Environment
+.env
+.env.local
+.env.*.local
+
+# Testing
+.vitest/
diff --git a/packages/compatibility-layer/README.md b/packages/compatibility-layer/README.md
new file mode 100644
index 0000000..a54173a
--- /dev/null
+++ b/packages/compatibility-layer/README.md
@@ -0,0 +1,368 @@
+# @openagents-control/compatibility-layer
+
+> Compatibility layer for converting OpenAgents Control agents to/from other AI coding tools
+
+[](https://opensource.org/licenses/MIT)
+[](https://www.typescriptlang.org/)
+[](https://nodejs.org/)
+
+## Overview
+
+This package provides bidirectional conversion between OpenAgents Control (OAC) agent format and various AI coding tools:
+
+- **Cursor IDE** - VSCode-based AI editor with `.cursorrules`
+ [](./docs/migration-guides/oac-to-cursor.md)
+- **Claude Code** - Anthropic's official CLI with `config.json` and `agents/*.md`
+ [](./docs/migration-guides/oac-to-claude.md)
+- **Windsurf** - AI-powered development environment with JSON config
+
+## Features
+
+- ✅ **Bidirectional conversion** - Convert OAC ↔ Tool formats
+- ✅ **Feature parity tracking** - Know what's supported in each tool
+- ✅ **Graceful degradation** - Handle unsupported features with warnings
+- ✅ **CLI tool** - Easy conversion from command line
+- ✅ **Batch migration** - Migrate entire projects at once
+- ✅ **Type-safe** - Full TypeScript with Zod validation
+- ✅ **Extensible** - Plugin architecture for new tools
+
+## Installation
+
+```bash
+# npm
+npm install @openagents-control/compatibility-layer
+
+# pnpm
+pnpm add @openagents-control/compatibility-layer
+```
+
+## Quick Start
+
+### CLI Usage
+
+```bash
+# Convert a single file
+oac-compat convert agent.md -f cursor -o .cursorrules
+
+# Convert with auto-detection of source format
+oac-compat convert .cursorrules -f oac -o agent.md
+
+# Validate compatibility before conversion
+oac-compat validate agent.md -t claude --strict
+
+# Migrate entire project
+oac-compat migrate ./agents -f claude --out-dir ./output
+
+# Preview migration without writing files
+oac-compat migrate ./agents -f windsurf --dry-run
+
+# Show platform capabilities
+oac-compat info cursor
+
+# Compare two platforms
+oac-compat info cursor --compare claude
+```
+
+### Programmatic Usage
+
+```typescript
+import {
+ loadAgent,
+ getAdapter,
+ translate,
+ analyzeCompatibility,
+} from "@openagents-control/compatibility-layer";
+
+// Load an OAC agent
+const agent = await loadAgent("./agent.md");
+
+// Check compatibility with target platform
+const compatibility = analyzeCompatibility(agent, "cursor");
+console.log("Compatible:", compatibility.compatible);
+console.log("Warnings:", compatibility.warnings);
+console.log("Blockers:", compatibility.blockers);
+
+// Convert to another format
+const adapter = getAdapter("claude");
+const result = await adapter.fromOAC(agent);
+
+if (result.success) {
+ for (const config of result.configs) {
+ console.log(`File: ${config.fileName}`);
+ console.log(config.content);
+ }
+}
+
+// Or use the translate function for quick conversion
+const translated = translate(agent, "cursor");
+```
+
+## CLI Commands
+
+### `convert` - Convert a single file
+
+```bash
+oac-compat convert -f [options]
+
+Options:
+ -f, --format Target format (oac, cursor, claude, windsurf)
+ -o, --output Output file path (stdout if omitted)
+ --from Source format (auto-detect if omitted)
+ --force Overwrite existing output file
+```
+
+### `validate` - Check compatibility
+
+```bash
+oac-compat validate -t [options]
+
+Options:
+ -t, --target Target format to validate against
+ --strict Enable strict validation mode
+```
+
+### `migrate` - Batch migration
+
+```bash
+oac-compat migrate -f [options]
+
+Options:
+ -f, --format Target format
+ -o, --out-dir Output directory
+ --dry-run Preview without writing files
+ --force Overwrite existing files
+```
+
+### `info` - Platform information
+
+```bash
+oac-compat info [platform] [options]
+
+Options:
+ -d, --detailed Show detailed capability information
+ -c, --compare Compare with another platform
+```
+
+### Global Options
+
+```bash
+-v, --verbose Enable verbose output
+-q, --quiet Suppress all output except errors
+--output-format Output format (text, json)
+```
+
+## Feature Parity Matrix
+
+| Feature | OAC | Cursor | Claude | Windsurf |
+| -------------------- | -------- | ---------- | ------ | -------- |
+| **Config Format** | YAML+MD | Plain text | JSON | JSON |
+| **Multiple Agents** | ✅ | ❌ | ✅ | ✅ |
+| **Tool Permissions** | Granular | Binary | Binary | Binary |
+| **Temperature** | ✅ | ❌ | ❌ | ✅ |
+| **Skills/Contexts** | ✅ | ❌ | ✅ | ⚠️ |
+| **Hooks** | ✅ | ❌ | ✅ | ❌ |
+| **Model Selection** | ✅ | ✅ | ✅ | ✅ |
+
+See [Feature Matrices](docs/feature-matrices.md) for detailed comparison.
+
+## Migration Guides
+
+- [Cursor → OAC](docs/migration-guides/cursor-to-oac.md) - Import from Cursor
+- [Claude → OAC](docs/migration-guides/claude-to-oac.md) - Import from Claude Code
+- [OAC → Cursor](docs/migration-guides/oac-to-cursor.md) - Export to Cursor
+- [OAC → Claude](docs/migration-guides/oac-to-claude.md) - Export to Claude Code
+- [OAC → Windsurf](docs/migration-guides/oac-to-windsurf.md) - Export to Windsurf
+
+## API Reference
+
+### Agent Loading
+
+```typescript
+import {
+ loadAgent,
+ loadAgents,
+ AgentLoader,
+} from "@openagents-control/compatibility-layer";
+
+// Load single agent
+const agent = await loadAgent("./agent.md");
+
+// Load all agents from directory
+const agents = await loadAgents("./agents/");
+
+// Using the loader class
+const loader = new AgentLoader();
+const agent = await loader.loadFromFile("./agent.md");
+```
+
+### Adapter Registry
+
+```typescript
+import {
+ registry,
+ getAdapter,
+ listAdapters,
+} from "@openagents-control/compatibility-layer";
+
+// Get adapter by name
+const adapter = getAdapter("cursor");
+
+// List all adapters
+const names = listAdapters(); // ['cursor', 'claude', 'windsurf']
+
+// Get all capabilities
+const capabilities = getAllCapabilities();
+```
+
+### Translation Engine
+
+```typescript
+import {
+ translate,
+ previewTranslation,
+ TranslationEngine,
+} from "@openagents-control/compatibility-layer";
+
+// Quick translate
+const result = translate(agent, "cursor");
+
+// Preview without converting
+const preview = previewTranslation(agent, "claude");
+console.log("Features lost:", preview.featuresLost);
+console.log("Features gained:", preview.featuresGained);
+
+// Using the engine for more control
+const engine = new TranslationEngine();
+const result = engine.translate(agent, "windsurf", {
+ preserveComments: true,
+ strictMode: false,
+});
+```
+
+### Mappers
+
+```typescript
+import {
+ mapToolFromOAC,
+ mapModelFromOAC,
+ mapPermissionsFromOAC,
+ mapContextPathFromOAC,
+} from "@openagents-control/compatibility-layer";
+
+// Map tool names
+mapToolFromOAC("bash", "cursor"); // { name: 'terminal', exact: true }
+
+// Map model identifiers
+mapModelFromOAC("claude-sonnet-4", "cursor"); // { id: 'claude-3-sonnet', exact: true }
+
+// Map permissions
+mapPermissionsFromOAC({ bash: { "*": "allow" } }, "claude");
+// { permissions: { bash: true }, warnings: [...] }
+```
+
+## Architecture
+
+```
+src/
+├── types.ts # Zod schemas and TypeScript types
+├── index.ts # Public API exports
+├── core/
+│ ├── AgentLoader.ts # OAC agent file parser
+│ ├── AdapterRegistry.ts # Adapter management
+│ ├── CapabilityMatrix.ts # Feature compatibility
+│ └── TranslationEngine.ts # Orchestrates conversion
+├── adapters/
+│ ├── BaseAdapter.ts # Abstract base class
+│ ├── CursorAdapter.ts # Cursor IDE adapter
+│ ├── ClaudeAdapter.ts # Claude Code adapter
+│ └── WindsurfAdapter.ts # Windsurf adapter
+├── mappers/
+│ ├── ToolMapper.ts # Tool name translation
+│ ├── ModelMapper.ts # Model ID translation
+│ ├── PermissionMapper.ts # Permission translation
+│ └── ContextMapper.ts # Context path translation
+└── cli/
+ ├── index.ts # CLI entry point
+ ├── types.ts # CLI types
+ ├── utils.ts # CLI utilities
+ └── commands/
+ ├── convert.ts # convert command
+ ├── validate.ts # validate command
+ ├── migrate.ts # migrate command
+ └── info.ts # info command
+```
+
+## Development
+
+```bash
+# Install dependencies
+npm install
+
+# Build
+npm run build
+
+# Run tests
+npm test
+
+# Test with coverage
+npm run test:coverage
+
+# Watch mode for development
+npm run build:watch
+
+# Lint
+npm run lint
+npm run lint:fix
+```
+
+## Creating Custom Adapters
+
+```typescript
+import {
+ BaseAdapter,
+ OpenAgent,
+ ConversionResult,
+ ToolCapabilities,
+} from "@openagents-control/compatibility-layer";
+
+class MyToolAdapter extends BaseAdapter {
+ readonly name = "my-tool";
+ readonly displayName = "My Tool";
+ readonly version = "1.0.0";
+
+ getCapabilities(): ToolCapabilities {
+ return {
+ supportsMultiAgent: false,
+ supportsSkills: false,
+ supportsHooks: false,
+ supportsGranularPermissions: false,
+ configFormat: "json",
+ };
+ }
+
+ async toOAC(source: string): Promise {
+ // Parse source and return OpenAgent
+ }
+
+ async fromOAC(agent: OpenAgent): Promise {
+ // Convert OpenAgent to your format
+ return {
+ success: true,
+ configs: [{ fileName: "config.json", content: "...", encoding: "utf-8" }],
+ warnings: [],
+ };
+ }
+}
+
+// Register the adapter
+import { registry } from "@openagents-control/compatibility-layer";
+registry.register(new MyToolAdapter());
+```
+
+## Contributing
+
+See [CONTRIBUTING.md](../../CONTRIBUTING.md) for guidelines.
+
+## License
+
+MIT © OpenAgents Control Contributors
diff --git a/packages/compatibility-layer/docs/feature-matrices.md b/packages/compatibility-layer/docs/feature-matrices.md
new file mode 100644
index 0000000..5774637
--- /dev/null
+++ b/packages/compatibility-layer/docs/feature-matrices.md
@@ -0,0 +1,193 @@
+# Feature Compatibility Matrices
+
+Comprehensive comparison of feature support across OpenAgents Control (OAC), Claude Code, Cursor IDE, and Windsurf platforms. Use these matrices to understand conversion impact before migrating agent configurations.
+
+---
+
+## Quick Reference - Platform Support Summary
+
+| Platform | Config Format | Structure | Multiple Agents | Skills | Hooks | Granular Perms |
+|----------|---------------|-----------|-----------------|--------|-------|----------------|
+| **OAC** | Markdown+YAML | Directory | ✅ | ✅ | ✅ | ✅ |
+| **Claude Code** | JSON/Markdown | Directory | ✅ | ✅ | ✅ | ❌ |
+| **Cursor IDE** | Plain text | Single file | ❌ | ❌ | ❌ | ❌ |
+| **Windsurf** | JSON | Directory | ✅ | ⚠️ | ❌ | ❌ |
+
+**Legend:** ✅ Full support | ⚠️ Partial support | ❌ Not supported
+
+---
+
+## Detailed Matrices
+
+### Tool/Permission Capabilities
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf |
+|---------|-----|-------------|------------|----------|
+| **bash/shell execution** | ✅ | ✅ | ✅ | ✅ |
+| **file read** | ✅ | ✅ | ✅ | ✅ |
+| **file write** | ✅ | ✅ | ✅ | ✅ |
+| **file edit/patch** | ✅ | ✅ | ✅ | ✅ |
+| **glob search** | ✅ | ✅ | ✅ | ✅ |
+| **grep content search** | ✅ | ✅ | ✅ | ✅ |
+| **task delegation** | ✅ | ✅ | ❌ | ⚠️ |
+| **granular permissions** | ✅ | ❌ | ❌ | ❌ |
+| **ask permissions** | ✅ | ❌ | ❌ | ❌ |
+| **path pattern rules** | ✅ | ❌ | ❌ | ⚠️ |
+
+**Notes:**
+- Cursor has no task delegation - all work happens in a single agent
+- All platforms reduce OAC granular permissions (allow/deny/ask per path) to binary on/off
+- Windsurf's delegation is limited compared to Claude's full subagent system
+
+---
+
+### Configuration Features
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf |
+|---------|-----|-------------|------------|----------|
+| **custom model selection** | ✅ | ✅ | ✅ | ✅ |
+| **temperature control** | ✅ | ❌ | ⚠️ | ⚠️ |
+| **maxSteps limit** | ✅ | ❌ | ❌ | ❌ |
+| **agent disable/hidden** | ✅ | ⚠️ | ❌ | ⚠️ |
+| **custom prompts** | ✅ | ✅ | ✅ | ✅ |
+| **version metadata** | ✅ | ⚠️ | ❌ | ⚠️ |
+| **author metadata** | ✅ | ⚠️ | ❌ | ⚠️ |
+| **tags** | ✅ | ❌ | ❌ | ⚠️ |
+
+**Notes:**
+- Claude does not support temperature configuration
+- Cursor has limited temperature range support
+- Windsurf maps temperature to "creativity" setting (low/medium/high)
+- maxSteps is OAC-exclusive
+
+---
+
+### Context/Memory Features
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf |
+|---------|-----|-------------|------------|----------|
+| **external context files** | ✅ | ✅ | ❌ | ✅ |
+| **context priority levels** | ✅ | ❌ | ❌ | ❌ |
+| **context subdirectories** | ✅ | ✅ | ❌ | ✅ |
+| **skills/modules system** | ✅ | ✅ | ❌ | ⚠️ |
+| **context descriptions** | ✅ | ⚠️ | ❌ | ⚠️ |
+| **dependency declarations** | ✅ | ✅ | ❌ | ⚠️ |
+
+**Notes:**
+- Cursor requires all context to be inline in `.cursorrules`
+- OAC supports 4 priority levels: critical, high, medium, low
+- Windsurf only supports high/low priority (medium/critical get degraded)
+- Skills in Windsurf become basic context file references
+
+---
+
+### Agent/Mode Features
+
+| Feature | OAC | Claude Code | Cursor IDE | Windsurf |
+|---------|-----|-------------|------------|----------|
+| **multiple agents** | ✅ | ✅ | ❌ | ✅ |
+| **primary/subagent modes** | ✅ | ✅ | ❌ | ⚠️ |
+| **agent categories** | ✅ | ⚠️ | ❌ | ⚠️ |
+| **event hooks** | ✅ | ✅ | ❌ | ❌ |
+| **hook matchers** | ✅ | ✅ | ❌ | ❌ |
+| **priority levels** | ✅ (4) | ⚠️ (2) | ❌ | ⚠️ (2) |
+
+**Notes:**
+- Cursor merges all agents into a single `.cursorrules` file
+- Hook events supported by Claude: PreToolUse, PostToolUse, PermissionRequest, AgentStart, AgentEnd
+- Windsurf has limited mode distinction
+
+---
+
+## Conversion Impact
+
+### Lossless Conversions
+
+These conversions preserve all features without degradation:
+
+| From | To | Notes |
+|------|-----|-------|
+| Cursor | OAC | Full upgrade - gains all OAC features |
+| Cursor | Claude | Gains hooks, skills, multiple agents |
+| Cursor | Windsurf | Gains multiple agents, contexts |
+| Claude | OAC | Full upgrade - gains granular permissions, temperature |
+| Windsurf | OAC | Full upgrade - gains hooks, granular permissions |
+
+---
+
+### Lossy Conversions
+
+These conversions result in feature loss or degradation:
+
+| From | To | What's Lost/Degraded |
+|------|-----|---------------------|
+| OAC | Claude | Temperature, maxSteps, granular permissions → binary |
+| OAC | Cursor | **Significant loss**: hooks, skills, multiple agents, contexts (inline only), granular permissions |
+| OAC | Windsurf | Hooks, granular permissions, context priority (4→2 levels), temperature → creativity |
+| Claude | Cursor | **Significant loss**: hooks, skills, multiple agents, contexts |
+| Claude | Windsurf | Hooks, temperature |
+| Windsurf | Cursor | Multiple agents merged, skills lost, contexts inline only |
+| Windsurf | Claude | Temperature mapping may be imprecise |
+
+---
+
+## Platform-Specific Notes
+
+### OpenAgents Control (OAC)
+- **Config location**: `.opencode/agent/` directory
+- **Format**: Markdown files with YAML frontmatter
+- **Strengths**: Full feature support, granular permissions, 4-level priorities
+- **Best for**: Complex multi-agent workflows requiring fine-grained control
+
+### Claude Code
+- **Config location**: `.claude/` directory
+- **Primary config**: `.claude/config.json` or `.claude/agents/*.md`
+- **Strengths**: Full hooks support, skills system, multiple agents
+- **Limitations**: No temperature control, binary permissions only
+- **Permission modes**: default, acceptEdits, dontAsk, bypassPermissions
+
+### Cursor IDE
+- **Config location**: `.cursorrules` (single file in project root)
+- **Format**: Plain text with optional YAML frontmatter
+- **Strengths**: Simple setup, universal tool support
+- **Limitations**: Single agent only, no external contexts, no hooks/skills
+- **Best for**: Simple, single-purpose project rules
+
+### Windsurf
+- **Config location**: `.windsurf/` directory
+- **Primary config**: `.windsurf/config.json`
+- **Strengths**: Multiple agents, context files, creativity settings
+- **Limitations**: No hooks, binary permissions, 2-level priority only
+- **Temperature mapping**: `≤0.4` → low, `0.4-0.8` → medium, `>0.8` → high
+
+---
+
+## Version Compatibility
+
+| Package Version | OAC Schema | Claude Support | Cursor Support | Windsurf Support |
+|-----------------|------------|----------------|----------------|------------------|
+| 1.0.x | v1.0 | ✅ Full | ✅ Full | ✅ Full |
+
+### Model Mappings
+
+| OAC Model ID | Claude Code | Cursor IDE | Windsurf |
+|--------------|-------------|------------|----------|
+| `claude-sonnet-4` | `claude-sonnet-4-20250514` | `claude-sonnet-4` | `claude-4-sonnet` |
+| `claude-opus-4` | `claude-opus-4-20250514` | `claude-opus-4` | `claude-4-opus` |
+| `claude-3.5-sonnet` | `claude-3-5-sonnet-20241022` | `claude-3.5-sonnet` | `claude-3-5-sonnet` |
+| `gpt-4` | — | `gpt-4` | `gpt-4` |
+| `gpt-4-turbo` | — | `gpt-4-turbo` | `gpt-4-turbo` |
+| `gpt-4o` | — | `gpt-4o` | `gpt-4o` |
+
+### Tool Name Mappings
+
+| OAC Tool | Claude Code | Cursor IDE | Windsurf |
+|----------|-------------|------------|----------|
+| `bash` | `Bash` | `terminal` | `shell` |
+| `read` | `Read` | `file_read` | `read_file` |
+| `write` | `Write` | `file_write` | `write_file` |
+| `edit` | `Edit` | `file_edit` | `edit_file` |
+| `glob` | `Glob` | `file_search` | `find_files` |
+| `grep` | `Grep` | `content_search` | `search_content` |
+| `task` | `Task` | ❌ (unsupported) | `delegate` |
+| `patch` | `Edit` | `file_edit` | `edit_file` |
diff --git a/packages/compatibility-layer/docs/migration-guides/claude-code-validation.png b/packages/compatibility-layer/docs/migration-guides/claude-code-validation.png
new file mode 100644
index 0000000..29f8b46
Binary files /dev/null and b/packages/compatibility-layer/docs/migration-guides/claude-code-validation.png differ
diff --git a/packages/compatibility-layer/docs/migration-guides/claude-to-oac.md b/packages/compatibility-layer/docs/migration-guides/claude-to-oac.md
new file mode 100644
index 0000000..c0dd1b7
--- /dev/null
+++ b/packages/compatibility-layer/docs/migration-guides/claude-to-oac.md
@@ -0,0 +1,606 @@
+# Migrating from Claude Code to OAC
+
+> Convert your `.claude/` configuration to OpenAgents Control format and gain temperature control, maxSteps limits, and granular per-path permissions.
+
+## Overview
+
+Claude Code uses `.claude/config.json` for primary agents and `.claude/agents/*.md` for subagents. OAC provides all Claude Code capabilities plus temperature control, step limits, and granular permissions that Claude's binary permission modes can't express.
+
+## Prerequisites
+
+**Required:**
+- Node.js >= 18
+- npm or pnpm
+
+**Install the compatibility layer:**
+
+```bash
+npm install @openagents-control/compatibility-layer
+```
+
+## Quick Migration
+
+**One command conversion:**
+
+```bash
+oac-compat convert .claude/config.json -f oac -o agent.md
+```
+
+For subagents:
+
+```bash
+oac-compat convert .claude/agents/code-reviewer.md -f oac -o .opencode/agent/code-reviewer.md
+```
+
+**Verify the result:**
+
+```bash
+oac-compat validate agent.md
+```
+
+## Step-by-Step Manual Migration
+
+### 1. Claude Code Structure
+
+Claude Code uses a directory-based structure:
+
+```
+.claude/
+├── config.json # Primary agent configuration
+├── agents/ # Subagent definitions
+│ ├── code-reviewer.md
+│ └── doc-writer.md
+└── skills/ # Context injection
+ └── project-context/
+ └── SKILL.md
+```
+
+**Primary agent (config.json):**
+
+```json
+{
+ "name": "main-agent",
+ "description": "Primary development assistant",
+ "systemPrompt": "You are a senior developer.",
+ "model": "claude-sonnet-4-20250514",
+ "tools": ["Read", "Write", "Bash"],
+ "permissionMode": "default",
+ "skills": ["project-context"],
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Write",
+ "hooks": [{ "type": "command", "command": "npm run lint" }]
+ }
+ ]
+ }
+}
+```
+
+**Subagent (agents/code-reviewer.md):**
+
+```yaml
+---
+name: "code-reviewer"
+description: "Reviews code for quality"
+tools: "Read, Grep, Glob"
+model: "sonnet"
+permissionMode: "default"
+---
+
+You are a code reviewer. Analyze code for:
+- Best practices
+- Performance issues
+- Security vulnerabilities
+```
+
+### 2. OAC Agent Structure
+
+OAC agents use markdown with YAML frontmatter:
+
+```yaml
+---
+name: main-agent
+description: Primary development assistant
+mode: primary
+model: claude-sonnet-4
+temperature: 0.7
+maxSteps: 50
+tools:
+ read: true
+ write: true
+ bash: true
+permission:
+ write:
+ "/src/**": allow
+ "/config/**": ask
+ "/**": deny
+skills:
+ - context-manager
+hooks:
+ - event: PreToolUse
+ matchers:
+ - "write"
+ commands:
+ - type: command
+ command: "npm run lint"
+---
+
+# Main Agent
+
+You are a senior developer.
+
+## Guidelines
+
+- Write clean, maintainable code
+- Follow project conventions
+```
+
+### 3. Key Differences
+
+| Aspect | Claude Code | OAC |
+|--------|-------------|-----|
+| **Config location** | `.claude/` directory | `.opencode/agent/` directory |
+| **Primary config** | `config.json` (JSON) | `agent.md` (YAML frontmatter) |
+| **Subagent format** | Markdown with YAML | Markdown with YAML |
+| **Model IDs** | `claude-sonnet-4-20250514` | `claude-sonnet-4` |
+| **Tools format** | Array: `["Read", "Write"]` | Object: `{ read: true, write: true }` |
+| **Permissions** | Binary modes only | Granular per-path rules |
+| **Temperature** | Not supported | Full control (0.0-2.0) |
+| **Max steps** | Not supported | Configurable limit |
+| **Skills path** | `.claude/skills/` | `.opencode/skill/` |
+
+### 4. Feature Mapping
+
+| Claude Code Feature | OAC Equivalent | Notes |
+|---------------------|----------------|-------|
+| `name` | `frontmatter.name` | Direct mapping |
+| `description` | `frontmatter.description` | Direct mapping |
+| `systemPrompt` | Body content (markdown) | Becomes structured markdown |
+| `model: "claude-sonnet-4-20250514"` | `model: claude-sonnet-4` | Simplified ID |
+| `model: "sonnet"` | `model: claude-sonnet-4` | Alias resolved |
+| `model: "opus"` | `model: claude-opus-4` | Alias resolved |
+| `model: "haiku"` | `model: claude-haiku-4` | Alias resolved |
+| `tools: ["Read", "Write"]` | `tools: { read: true, write: true }` | Object format |
+| `permissionMode: "default"` | `permission: { ... }` | Granular rules available |
+| `permissionMode: "bypassPermissions"` | `permission: { "*": "allow" }` | Full access |
+| `permissionMode: "dontAsk"` | `permission: { "*": "deny" }` | Auto-deny |
+| `skills: ["name"]` | `skills: [name]` | Direct mapping |
+| `hooks.PreToolUse` | `hooks: [{ event: PreToolUse }]` | Array format |
+| (not available) | `frontmatter.temperature` | New capability |
+| (not available) | `frontmatter.maxSteps` | New capability |
+| (not available) | Granular path permissions | New capability |
+
+### 5. Common Patterns
+
+#### Pattern 1: Primary Agent Config
+
+**Before (Claude Code - config.json):**
+
+```json
+{
+ "name": "typescript-dev",
+ "description": "TypeScript development assistant",
+ "systemPrompt": "You are a senior TypeScript developer. Follow strict mode. Write tests.",
+ "model": "claude-sonnet-4-20250514",
+ "tools": ["Read", "Write", "Edit", "Bash"],
+ "permissionMode": "default"
+}
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: typescript-dev
+description: TypeScript development assistant
+mode: primary
+model: claude-sonnet-4
+temperature: 0.7
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: true
+permission:
+ write:
+ "/src/**": allow
+ "/tests/**": allow
+ "/**": ask
+---
+
+# TypeScript Developer
+
+You are a senior TypeScript developer.
+
+## Guidelines
+
+- Follow strict mode
+- Write tests for all code
+- Use type-safe patterns
+```
+
+---
+
+#### Pattern 2: Subagent Migration
+
+**Before (Claude Code - agents/code-reviewer.md):**
+
+```yaml
+---
+name: "code-reviewer"
+description: "Reviews code for quality and best practices"
+tools: "Read, Grep, Glob"
+model: "sonnet"
+permissionMode: "default"
+---
+
+You are a code reviewer. Focus on:
+- Code quality
+- Performance issues
+- Security vulnerabilities
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: code-reviewer
+description: Reviews code for quality and best practices
+mode: subagent
+model: claude-sonnet-4
+tools:
+ read: true
+ grep: true
+ glob: true
+ write: false
+ edit: false
+ bash: false
+---
+
+# Code Reviewer
+
+You are a code reviewer.
+
+## Focus Areas
+
+- Code quality and maintainability
+- Performance issues and bottlenecks
+- Security vulnerabilities and risks
+
+## Output Format
+
+Provide findings as:
+1. **Issue**: Description
+2. **Location**: File and line
+3. **Severity**: High/Medium/Low
+4. **Recommendation**: Fix suggestion
+```
+
+---
+
+#### Pattern 3: Hooks Migration
+
+**Before (Claude Code - config.json hooks):**
+
+```json
+{
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "Write",
+ "hooks": [{ "type": "command", "command": "npm run lint:check" }]
+ },
+ {
+ "matcher": "Bash",
+ "hooks": [{ "type": "command", "command": "echo 'Executing bash...'" }]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "*",
+ "hooks": [{ "type": "command", "command": "npm run test:affected" }]
+ }
+ ]
+ }
+}
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: validated-dev
+description: Developer with validation hooks
+mode: primary
+hooks:
+ - event: PreToolUse
+ matchers:
+ - "write"
+ commands:
+ - type: command
+ command: "npm run lint:check"
+ - event: PreToolUse
+ matchers:
+ - "bash"
+ commands:
+ - type: command
+ command: "echo 'Executing bash...'"
+ - event: PostToolUse
+ matchers:
+ - "*"
+ commands:
+ - type: command
+ command: "npm run test:affected"
+---
+```
+
+---
+
+#### Pattern 4: Skills to Contexts Migration
+
+**Before (Claude Code - .claude/skills/api-docs/SKILL.md):**
+
+```yaml
+---
+name: api-docs
+description: API documentation context
+---
+
+# API Documentation
+
+This skill provides API documentation context.
+
+Load from: `docs/api/`
+```
+
+**After (OAC contexts):**
+
+```yaml
+---
+name: api-aware-agent
+description: Agent with API documentation context
+mode: primary
+---
+
+# API-Aware Agent
+
+
+```
+
+With context file `.opencode/context/api-docs.md`:
+
+```yaml
+---
+path: docs/api/
+priority: high
+description: API documentation for reference
+---
+
+# API Documentation Context
+
+Reference these docs when working with API endpoints.
+```
+
+---
+
+#### Pattern 5: Granular Permissions (OAC Exclusive)
+
+Claude Code only supports binary permission modes. OAC enables per-path rules:
+
+**Claude Code (limited):**
+
+```json
+{
+ "permissionMode": "default"
+}
+```
+
+**OAC (granular):**
+
+```yaml
+---
+name: safe-developer
+description: Developer with path-based permissions
+mode: primary
+permission:
+ write:
+ "/src/**": allow
+ "/tests/**": allow
+ "/docs/**": allow
+ "/config/**": ask
+ "/package.json": ask
+ "/.env*": deny
+ "/**": deny
+ bash:
+ "npm run *": allow
+ "git *": allow
+ "rm *": deny
+ "*": ask
+---
+```
+
+### 6. Limitations
+
+Features that don't fully translate between formats:
+
+| Feature | Claude Code | OAC | Migration Notes |
+|---------|-------------|-----|-----------------|
+| Temperature | Not supported | Full support | Set desired value after migration |
+| Max steps | Not supported | Configurable | Add limit after migration |
+| Granular permissions | Binary modes | Per-path rules | Expand after migration |
+| Permission mode `acceptEdits` | Supported | No direct equivalent | Use `permission: { edit: "allow" }` |
+| Skill directory structure | `.claude/skills/` | `.opencode/skill/` | Move and rename |
+| Agent directory | `.claude/agents/` | `.opencode/agent/` | Move and rename |
+
+### 7. Validation
+
+**Validate your migrated agent:**
+
+```bash
+# Check OAC format validity
+oac-compat validate agent.md
+
+# Compare capabilities
+oac-compat info --tool claude
+oac-compat info --tool oac
+
+# Test round-trip conversion
+oac-compat convert agent.md -f claude -o .claude/config.test.json
+```
+
+**Programmatic validation:**
+
+```typescript
+import { loadAgent, validateAgent } from '@openagents-control/compatibility-layer';
+
+const agent = await loadAgent('./agent.md');
+const errors = validateAgent(agent);
+
+if (errors.length === 0) {
+ console.log('Agent is valid!');
+} else {
+ console.error('Validation errors:', errors);
+}
+```
+
+**Verify feature preservation:**
+
+```typescript
+import { ClaudeAdapter } from '@openagents-control/compatibility-layer';
+
+const adapter = new ClaudeAdapter();
+const capabilities = adapter.getCapabilities();
+
+console.log('Claude supports:');
+console.log('- Multiple agents:', capabilities.supportsMultipleAgents);
+console.log('- Skills:', capabilities.supportsSkills);
+console.log('- Hooks:', capabilities.supportsHooks);
+console.log('- Temperature:', capabilities.supportsTemperature); // false
+console.log('- Max steps:', capabilities.supportsMaxSteps); // false
+console.log('- Granular perms:', capabilities.supportsGranularPermissions); // false
+```
+
+## Troubleshooting
+
+### Model ID not recognized
+
+**Symptom:** Model defaults to `sonnet` or conversion warning appears.
+
+**Cause:** Claude Code uses full version IDs like `claude-sonnet-4-20250514`.
+
+**Fix:** OAC uses simplified model IDs:
+
+| Claude Code | OAC |
+|-------------|-----|
+| `claude-sonnet-4-20250514` | `claude-sonnet-4` |
+| `claude-opus-4` | `claude-opus-4` |
+| `claude-haiku-4` | `claude-haiku-4` |
+| `sonnet` | `claude-sonnet-4` |
+| `opus` | `claude-opus-4` |
+| `haiku` | `claude-haiku-4` |
+
+---
+
+### Tools array not converting
+
+**Symptom:** Tools show as all disabled or wrong format.
+
+**Cause:** Claude uses capitalized array format; OAC uses lowercase object format.
+
+**Fix:** Convert manually:
+
+```json
+// Claude Code
+"tools": ["Read", "Write", "Bash"]
+```
+
+```yaml
+# OAC
+tools:
+ read: true
+ write: true
+ bash: true
+ edit: false
+ grep: false
+ glob: false
+```
+
+---
+
+### Permission mode warning
+
+**Symptom:** Warning about permission degradation.
+
+**Cause:** Claude's `permissionMode` is binary; can't express granular rules.
+
+**Fix:** After migration, expand to granular permissions:
+
+```yaml
+# Instead of permissionMode: "default"
+permission:
+ write:
+ "/src/**": allow
+ "/config/**": ask
+ "/**": deny
+ bash:
+ "npm *": allow
+ "git *": allow
+ "*": ask
+```
+
+---
+
+### Hooks not triggering
+
+**Symptom:** Hooks defined but not executing.
+
+**Cause:** Hook event names or matchers may differ.
+
+**Fix:** Verify hook format:
+
+```yaml
+hooks:
+ - event: PreToolUse # Exact event name
+ matchers:
+ - "write" # Lowercase tool name
+ - "edit"
+ commands:
+ - type: command
+ command: "npm run lint"
+```
+
+Valid events: `PreToolUse`, `PostToolUse`, `PermissionRequest`, `AgentStart`, `AgentEnd`
+
+---
+
+### Skills not found
+
+**Symptom:** Skills referenced but not loading.
+
+**Cause:** Different directory structure between Claude Code and OAC.
+
+**Fix:** Move skills to OAC location:
+
+```bash
+# Claude Code location
+.claude/skills/my-skill/SKILL.md
+
+# OAC location
+.opencode/skill/my-skill/SKILL.md
+```
+
+Update skill references in agent:
+
+```yaml
+skills:
+ - my-skill # Name matches directory
+```
+
+## Next Steps
+
+- **[OAC Agent Configuration](../configuration/agent-format.md)** - Deep dive into OAC format
+- **[Granular Permissions](../features/permissions.md)** - Per-path permission rules
+- **[Skills System](../features/skills.md)** - Dynamic skill loading
+- **[Hooks Reference](../features/hooks.md)** - Event-driven automation
+- **[OAC → Claude Migration](./oac-to-claude.md)** - Reverse migration guide
diff --git a/packages/compatibility-layer/docs/migration-guides/contextscout-cursor.png b/packages/compatibility-layer/docs/migration-guides/contextscout-cursor.png
new file mode 100644
index 0000000..8456237
Binary files /dev/null and b/packages/compatibility-layer/docs/migration-guides/contextscout-cursor.png differ
diff --git a/packages/compatibility-layer/docs/migration-guides/cursor-to-oac.md b/packages/compatibility-layer/docs/migration-guides/cursor-to-oac.md
new file mode 100644
index 0000000..22c195a
--- /dev/null
+++ b/packages/compatibility-layer/docs/migration-guides/cursor-to-oac.md
@@ -0,0 +1,480 @@
+# Migrating from Cursor to OAC
+
+> Convert your `.cursorrules` to OpenAgents Control format and unlock Skills, Hooks, multi-agent support, and granular permissions.
+
+## Overview
+
+**Why migrate?**
+
+| Feature | Cursor | OAC |
+|---------|--------|-----|
+| Multiple agents | Single file only | Unlimited agents |
+| Skills system | Not supported | Full support |
+| Hooks | Not supported | Event-driven hooks |
+| Permissions | Binary (on/off) | Granular (allow/deny/ask per path) |
+| Contexts | Inline only | External context files |
+
+OAC provides a superset of Cursor's capabilities while maintaining backward compatibility.
+
+## Prerequisites
+
+**Required:**
+- Node.js >= 18
+- npm or pnpm
+
+**Install the compatibility layer:**
+
+```bash
+npm install @openagents-control/compatibility-layer
+```
+
+## Quick Migration
+
+**One command conversion:**
+
+```bash
+oac-compat convert .cursorrules -f oac -o agent.md
+```
+
+This reads your `.cursorrules` file and generates an OAC-compatible `agent.md`.
+
+**Verify the result:**
+
+```bash
+oac-compat validate agent.md
+```
+
+## Step-by-Step Manual Migration
+
+### 1. Cursor Rules Structure
+
+Cursor uses a single `.cursorrules` file in the project root. Two formats are supported:
+
+**Plain text format:**
+
+```
+You are a senior TypeScript developer.
+Always use strict mode.
+Prefer functional programming patterns.
+Write comprehensive tests for all code.
+```
+
+**Frontmatter format (optional):**
+
+```yaml
+---
+name: typescript-dev
+description: TypeScript development assistant
+model: gpt-4
+temperature: 0.7
+---
+
+You are a senior TypeScript developer.
+Always use strict mode.
+Prefer functional programming patterns.
+Write comprehensive tests for all code.
+```
+
+### 2. OAC Agent Structure
+
+OAC agents use markdown with YAML frontmatter:
+
+```yaml
+---
+name: typescript-dev
+description: TypeScript development assistant
+mode: primary
+model: gpt-4
+temperature: 0.7
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: true
+ grep: true
+ glob: true
+---
+
+# TypeScript Developer
+
+You are a senior TypeScript developer.
+
+## Guidelines
+
+- Always use strict mode
+- Prefer functional programming patterns
+- Write comprehensive tests for all code
+
+## Skills
+
+
+
+## Contexts
+
+
+```
+
+### 3. Key Differences
+
+| Aspect | Cursor | OAC |
+|--------|--------|-----|
+| **File format** | `.cursorrules` (plain/YAML) | `.md` with YAML frontmatter |
+| **Location** | Project root | `.opencode/agent/` directory |
+| **Multiple agents** | Not supported | Full support |
+| **Tool config** | Implicit or comma-separated | Explicit object with booleans |
+| **Permissions** | Binary only | Granular rules per path |
+| **Context handling** | Inline in prompt | External files with priority |
+| **Skills** | Not supported | Dynamic skill loading |
+| **Hooks** | Not supported | Event-driven lifecycle hooks |
+
+### 4. Feature Mapping
+
+| Cursor Feature | OAC Equivalent | Notes |
+|----------------|----------------|-------|
+| `name` | `frontmatter.name` | Direct mapping |
+| `description` | `frontmatter.description` | Direct mapping |
+| `model: gpt-4` | `frontmatter.model: gpt-4` | Direct mapping |
+| `model: claude-3-opus` | `frontmatter.model: claude-opus-3` | Model ID normalized |
+| `model: claude-3-sonnet` | `frontmatter.model: claude-sonnet-3` | Model ID normalized |
+| `model: claude-3-haiku` | `frontmatter.model: claude-haiku-3` | Model ID normalized |
+| `temperature` | `frontmatter.temperature` | Direct mapping |
+| `tools: "read, write"` | `tools: { read: true, write: true }` | Object format |
+| Plain text body | `systemPrompt` | Becomes the agent prompt |
+| (not available) | `frontmatter.mode` | Set to `primary` or `subagent` |
+| (not available) | `frontmatter.skills` | Add skill references |
+| (not available) | `frontmatter.hooks` | Add lifecycle hooks |
+| (not available) | `frontmatter.permission` | Add granular permissions |
+| (not available) | `contexts` | Add external context files |
+
+### 5. Common Patterns
+
+#### Pattern 1: Basic Rules
+
+**Before (Cursor):**
+
+```
+You are a helpful coding assistant.
+Follow best practices for clean code.
+Always explain your changes.
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: code-assistant
+description: Helpful coding assistant
+mode: primary
+---
+
+# Code Assistant
+
+You are a helpful coding assistant.
+
+## Guidelines
+
+- Follow best practices for clean code
+- Always explain your changes
+```
+
+---
+
+#### Pattern 2: Tool Configuration
+
+**Before (Cursor):**
+
+```yaml
+---
+name: restricted-agent
+tools: read, grep, glob
+---
+
+You can only read and search files.
+Do not modify any files.
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: restricted-agent
+description: Read-only agent for code analysis
+mode: primary
+tools:
+ read: true
+ grep: true
+ glob: true
+ write: false
+ edit: false
+ bash: false
+---
+
+# Restricted Agent
+
+You can only read and search files.
+
+## Restrictions
+
+- Read files: ALLOWED
+- Search files: ALLOWED
+- Modify files: DENIED
+- Execute commands: DENIED
+```
+
+---
+
+#### Pattern 3: Model Configuration
+
+**Before (Cursor):**
+
+```yaml
+---
+name: creative-writer
+model: claude-3-opus
+temperature: 0.9
+---
+
+Write creative and engaging documentation.
+```
+
+**After (OAC):**
+
+```yaml
+---
+name: creative-writer
+description: Creative documentation writer
+mode: primary
+model: claude-opus-3
+temperature: 0.9
+tools:
+ read: true
+ write: true
+ edit: true
+---
+
+# Creative Writer
+
+Write creative and engaging documentation.
+
+## Style Guidelines
+
+- Use vivid language
+- Include examples
+- Make content accessible
+```
+
+> **Note:** Claude model IDs are normalized (`claude-3-opus` → `claude-opus-3`).
+
+---
+
+#### Pattern 4: Adding OAC-Exclusive Features
+
+After migrating, enhance your agent with OAC-exclusive features:
+
+**Add Skills:**
+
+```yaml
+---
+name: full-stack-dev
+description: Full-stack development assistant
+mode: primary
+skills:
+ - context-manager
+ - task-management
+---
+```
+
+**Add Hooks:**
+
+```yaml
+---
+name: validated-writer
+description: Writer with validation hooks
+mode: primary
+hooks:
+ - event: PreToolUse
+ matchers:
+ - "write"
+ - "edit"
+ commands:
+ - type: command
+ command: "npm run lint:check"
+---
+```
+
+**Add Granular Permissions:**
+
+```yaml
+---
+name: safe-modifier
+description: Agent with path-based permissions
+mode: primary
+permission:
+ write:
+ "/src/**": allow
+ "/tests/**": allow
+ "/config/**": ask
+ "/**": deny
+---
+```
+
+**Add Contexts:**
+
+```yaml
+---
+name: context-aware
+description: Agent with external context files
+mode: primary
+---
+
+# Context-Aware Agent
+
+
+```
+
+With context files in `.opencode/context/`:
+
+```
+.opencode/context/
+├── core/
+│ ├── architecture.md
+│ └── conventions.md
+└── domain/
+ └── business-rules.md
+```
+
+### 6. Limitations
+
+Features that don't exist in Cursor (gained during migration):
+
+| Feature | Cursor | OAC | Workaround |
+|---------|--------|-----|------------|
+| Multiple agents | Single file | Unlimited | Create separate agent files |
+| Skills system | None | Full support | Inline skill content in prompt |
+| Hooks | None | Event-driven | Manual validation steps |
+| Granular permissions | Binary | Per-path rules | Document restrictions in prompt |
+| External contexts | None | Priority-based | Inline context in prompt |
+| Subagent mode | None | Delegation support | Use primary mode only |
+| Max steps | None | Configurable | Not available in Cursor |
+
+### 7. Validation
+
+**Validate your migrated agent:**
+
+```bash
+# Check OAC format validity
+oac-compat validate agent.md
+
+# Compare capabilities
+oac-compat info --tool cursor
+oac-compat info --tool oac
+
+# Test round-trip conversion
+oac-compat convert agent.md -f cursor -o .cursorrules.test
+```
+
+**Programmatic validation:**
+
+```typescript
+import { loadAgent, validateAgent } from '@openagents-control/compatibility-layer';
+
+const agent = await loadAgent('./agent.md');
+const errors = validateAgent(agent);
+
+if (errors.length === 0) {
+ console.log('Agent is valid!');
+} else {
+ console.error('Validation errors:', errors);
+}
+```
+
+## Troubleshooting
+
+### Frontmatter not parsing
+
+**Symptom:** Agent name shows as `cursor-agent` instead of your custom name.
+
+**Cause:** Frontmatter must have exact format with `---` delimiters.
+
+**Fix:**
+
+```yaml
+---
+name: my-agent
+---
+
+Content here...
+```
+
+Not:
+
+```yaml
+---
+name: my-agent
+Content here...
+```
+
+---
+
+### Model not recognized
+
+**Symptom:** Model defaults to `gpt-4`.
+
+**Cause:** Cursor model IDs differ from OAC.
+
+**Fix:** Use OAC model IDs:
+
+| Cursor | OAC |
+|--------|-----|
+| `claude-3-opus` | `claude-opus-3` |
+| `claude-3-sonnet` | `claude-sonnet-3` |
+| `claude-3-haiku` | `claude-haiku-3` |
+| `gpt-4` | `gpt-4` |
+| `gpt-4-turbo` | `gpt-4-turbo` |
+| `gpt-4o` | `gpt-4o` |
+
+---
+
+### Tools not working
+
+**Symptom:** Agent can't use expected tools.
+
+**Cause:** Tools must be explicitly enabled in OAC.
+
+**Fix:** Add tools to frontmatter:
+
+```yaml
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: true
+ grep: true
+ glob: true
+```
+
+---
+
+### Content appears duplicated
+
+**Symptom:** System prompt contains frontmatter text.
+
+**Cause:** Frontmatter delimiters missing or malformed.
+
+**Fix:** Ensure proper YAML frontmatter format:
+
+```yaml
+---
+name: agent-name
+---
+
+Content starts here (not in frontmatter).
+```
+
+## Next Steps
+
+- **[OAC Agent Configuration](../configuration/agent-format.md)** - Deep dive into OAC format
+- **[Skills System](../features/skills.md)** - Learn to use dynamic skills
+- **[Hooks Reference](../features/hooks.md)** - Event-driven automation
+- **[Multi-Agent Setup](../features/multi-agent.md)** - Configure agent delegation
+- **[OAC → Cursor Migration](./oac-to-cursor.md)** - Reverse migration guide
diff --git a/packages/compatibility-layer/docs/migration-guides/oac-to-claude.md b/packages/compatibility-layer/docs/migration-guides/oac-to-claude.md
new file mode 100644
index 0000000..b7871db
--- /dev/null
+++ b/packages/compatibility-layer/docs/migration-guides/oac-to-claude.md
@@ -0,0 +1,546 @@
+# Migrating from OAC to Claude Code
+
+> Convert OpenAgents Control agents to Claude Code's `.claude/` directory format
+
+## Overview
+
+Export to Claude Code format when you want to use your agents with Claude Code IDE. Claude Code supports subagents (`.claude/agents/*.md`), Skills (`.claude/skills/`), and hooks, making it a feature-rich target with good OAC compatibility.
+
+## Prerequisites
+
+| Requirement | Version | Notes |
+|-------------|---------|-------|
+| Node.js | >= 18 | Required for CLI |
+| @openagents-control/compatibility-layer | Latest | Install via npm |
+| Claude Code | Any | Target platform |
+
+```bash
+npm install -g @openagents-control/compatibility-layer
+```
+
+## Quick Migration
+
+```bash
+# Single agent conversion
+oac-compat convert agent.md -f claude -o .claude/
+
+# Convert with verbose output
+oac-compat convert agent.md -f claude -o .claude/ --verbose
+
+# Validate before converting
+oac-compat validate --target claude agent.md
+```
+
+## Step-by-Step Manual Migration
+
+### 1. OAC Agent Structure
+
+Typical OAC agent with full features:
+
+```yaml
+---
+name: DocWriter
+description: Documentation authoring agent
+mode: subagent
+temperature: 0.2
+model: claude-sonnet-4
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: false
+permission:
+ bash:
+ "*": "deny"
+ edit:
+ "**/*.md": "allow"
+ "**/*.env*": "deny"
+skills:
+ - context-manager
+ - task-management
+hooks:
+ - event: PreToolUse
+ matchers: ["edit"]
+ commands:
+ - type: command
+ command: "echo 'Editing file...'"
+maxSteps: 50
+---
+
+# DocWriter
+
+> Mission: Create and update documentation
+
+## Rules
+
+1. Always follow project documentation standards
+2. Use concise, example-driven writing
+3. Propose changes before implementing
+```
+
+### 2. Claude Code Structure
+
+Equivalent `.claude/agents/DocWriter.md` format:
+
+```yaml
+---
+name: "DocWriter"
+description: "Documentation authoring agent"
+tools: "Read, Write, Edit"
+model: "claude-sonnet-4-20250514"
+permissionMode: "default"
+skills: ["context-manager", "task-management"]
+hooks: {"PreToolUse": [{"matcher": "edit", "hooks": [{"type": "command", "command": "echo 'Editing file...'"}]}]}
+---
+
+# DocWriter
+
+> Mission: Create and update documentation
+
+## Rules
+
+1. Always follow project documentation standards
+2. Use concise, example-driven writing
+3. Propose changes before implementing
+```
+
+### 3. Key Differences
+
+| Aspect | OAC | Claude Code |
+|--------|-----|-------------|
+| File format | Markdown + YAML frontmatter | Markdown + YAML frontmatter |
+| File location | `.opencode/agent/*.md` | `.claude/agents/*.md` (subagents) or `.claude/config.json` (primary) |
+| Multiple agents | Supported | Supported (as subagents) |
+| Permissions | Granular (allow/deny/ask per path) | Binary (permissionMode) |
+| Skills | Full system | Supported (`.claude/skills/`) |
+| Hooks | Full system | Supported (PreToolUse, PostToolUse, etc.) |
+| Contexts | Referenced files | Converted to Skills |
+| Temperature | Supported | Not supported |
+| maxSteps | Supported | Not supported |
+
+### 4. Feature Mapping
+
+| OAC Feature | Claude Code Equivalent | Notes |
+|-------------|------------------------|-------|
+| `frontmatter.name` | `name` in frontmatter | Direct mapping |
+| `frontmatter.description` | `description` in frontmatter | Direct mapping |
+| `frontmatter.model` | `model` in frontmatter | Model name converted |
+| `frontmatter.temperature` | N/A | Not supported - warning emitted |
+| `frontmatter.mode` | File location | `primary` → `config.json`, `subagent` → `agents/*.md` |
+| `frontmatter.tools` | `tools` in frontmatter | Converted to comma-separated string |
+| `frontmatter.permission` | `permissionMode` | Degraded to binary mode |
+| `frontmatter.skills` | `skills` array | Direct mapping |
+| `frontmatter.hooks` | `hooks` object | Converted to Claude format |
+| `frontmatter.maxSteps` | N/A | Not supported - warning emitted |
+| `contexts` | `.claude/skills/` | Converted to skill files |
+| `systemPrompt` | Body content | Direct mapping |
+
+### 5. Model Name Mapping
+
+| OAC Model | Claude Code Model |
+|-----------|-------------------|
+| `claude-sonnet-4` | `claude-sonnet-4-20250514` |
+| `claude-opus-4` | `claude-opus-4` |
+| `claude-haiku-4` | `claude-haiku-4` |
+| Other models | `sonnet` (default alias) |
+
+## Common Patterns
+
+### Pattern 1: Basic Subagent
+
+**Before (OAC):**
+
+```yaml
+---
+name: CodeReviewer
+description: Reviews code for quality
+mode: subagent
+tools:
+ read: true
+ grep: true
+ glob: true
+---
+
+# Code Reviewer
+
+Review code for:
+- Code style consistency
+- Potential bugs
+- Performance issues
+```
+
+**After (Claude Code `.claude/agents/CodeReviewer.md`):**
+
+```yaml
+---
+name: "CodeReviewer"
+description: "Reviews code for quality"
+tools: "Read, Grep, Glob"
+---
+
+# Code Reviewer
+
+Review code for:
+- Code style consistency
+- Potential bugs
+- Performance issues
+```
+
+### Pattern 2: Primary Agent with Hooks
+
+**Before (OAC):**
+
+```yaml
+---
+name: MainAgent
+description: Primary development agent
+mode: primary
+model: claude-opus-4
+hooks:
+ - event: PreToolUse
+ matchers: ["bash"]
+ commands:
+ - type: command
+ command: "echo 'Running bash command...'"
+ - event: PostToolUse
+ matchers: ["write"]
+ commands:
+ - type: command
+ command: "npm run lint"
+---
+
+# Main Agent
+
+Primary agent for development tasks.
+```
+
+**After (Claude Code `.claude/config.json`):**
+
+```json
+{
+ "name": "MainAgent",
+ "description": "Primary development agent",
+ "systemPrompt": "# Main Agent\n\nPrimary agent for development tasks.",
+ "model": "claude-opus-4",
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "bash",
+ "hooks": [
+ { "type": "command", "command": "echo 'Running bash command...'" }
+ ]
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "write",
+ "hooks": [
+ { "type": "command", "command": "npm run lint" }
+ ]
+ }
+ ]
+ }
+}
+```
+
+### Pattern 3: Agent with Contexts (→ Skills)
+
+**Before (OAC):**
+
+```yaml
+---
+name: StyleGuide
+description: Enforces code style
+mode: subagent
+---
+
+# Style Guide Agent
+
+Follow the project style guide.
+```
+
+With `contexts`:
+```json
+[
+ { "path": ".opencode/context/style-guide.md", "priority": "critical", "description": "Project style guidelines" }
+]
+```
+
+**After (Claude Code):**
+
+`.claude/agents/StyleGuide.md`:
+```yaml
+---
+name: "StyleGuide"
+description: "Enforces code style"
+skills: ["style-guide"]
+---
+
+# Style Guide Agent
+
+Follow the project style guide.
+```
+
+`.claude/skills/style-guide/SKILL.md`:
+```yaml
+---
+name: style-guide
+description: Project style guidelines
+---
+
+# style-guide
+
+This skill provides context from: `.opencode/context/style-guide.md`
+
+Priority: critical
+
+Load the full context file for detailed information:
+```bash
+cat .opencode/context/style-guide.md
+```
+```
+
+### Pattern 4: Agent with Granular Permissions
+
+**Before (OAC):**
+
+```yaml
+---
+name: SecureWriter
+description: Writes files with restrictions
+mode: subagent
+permission:
+ edit:
+ "src/**/*.ts": "allow"
+ "**/*.env*": "deny"
+ bash:
+ "*": "deny"
+---
+
+# Secure Writer
+
+Write TypeScript files only. Never access secrets.
+```
+
+**After (Claude Code `.claude/agents/SecureWriter.md`):**
+
+```yaml
+---
+name: "SecureWriter"
+description: "Writes files with restrictions"
+permissionMode: "default"
+---
+
+# Secure Writer
+
+Write TypeScript files only. Never access secrets.
+
+## Permission Notes
+
+> **Warning**: Granular OAC permissions were degraded to `permissionMode: default`.
+> Original restrictions:
+> - edit: Only `src/**/*.ts` allowed
+> - edit: `**/*.env*` denied
+> - bash: Completely denied
+>
+> Enforce these manually.
+```
+
+## Limitations
+
+### Features Lost in Conversion
+
+| Feature | Impact | Workaround |
+|---------|--------|------------|
+| **temperature** | Not supported | Use creativity settings in Claude Code |
+| **maxSteps** | Not supported | Claude Code manages internally |
+| **Granular permissions** | Degraded to `permissionMode` | Document restrictions in prompt |
+
+### Permission Mode Mapping
+
+| OAC Permission Pattern | Claude `permissionMode` |
+|------------------------|------------------------|
+| All `"allow"` / `true` | `bypassPermissions` |
+| All `"deny"` / `false` | `dontAsk` |
+| Contains `"ask"` | `default` |
+| Mixed/complex rules | `default` (with warning) |
+
+### Conversion Warnings
+
+The converter emits warnings for:
+
+```
+⚠️ Agent name is required for Claude Code
+⚠️ Agent description is required for Claude Code
+⚠️ temperature not supported in Claude Code (value: 0.2)
+⚠️ maxSteps not supported in Claude Code (value: 50)
+⚠️ granular permissions degraded: allow/deny/ask per operation → binary permissionMode (default/acceptEdits/dontAsk/bypassPermissions)
+⚠️ Complex permission rules degraded to 'default' permissionMode. Claude Code does not support granular allow/deny/ask per operation.
+```
+
+### What's Preserved
+
+- Agent name and description
+- Model selection (with name mapping)
+- System prompt content
+- Tool access list
+- Skills references
+- Hooks (full conversion)
+- Contexts (converted to Skills)
+
+## Validation
+
+### Verify Conversion Success
+
+```bash
+# 1. Run conversion with verbose output
+oac-compat convert agent.md -f claude -o .claude/ --verbose
+
+# 2. Check for warnings
+# Review any warnings about degraded features
+
+# 3. Validate output files exist
+ls -la .claude/
+ls -la .claude/agents/
+ls -la .claude/skills/
+
+# 4. Test in Claude Code
+# Open project in Claude Code and verify agent behavior
+```
+
+### Manual Verification Checklist
+
+- [ ] `.claude/config.json` created (for primary agents)
+- [ ] `.claude/agents/*.md` created (for subagents)
+- [ ] `.claude/skills/` created (if contexts present)
+- [ ] Agent name and description present in frontmatter
+- [ ] Model setting correct (with Claude naming)
+- [ ] System prompt content complete
+- [ ] Tools list includes expected tools
+- [ ] Skills references match generated skill files
+- [ ] Hooks converted correctly (if applicable)
+- [ ] Warnings reviewed and addressed
+
+### Programmatic Validation
+
+```typescript
+import { ClaudeAdapter, loadAgent } from '@openagents-control/compatibility-layer';
+
+async function validateConversion() {
+ const agent = await loadAgent('./agent.md');
+ const adapter = new ClaudeAdapter();
+
+ // Check capabilities
+ const caps = adapter.getCapabilities();
+ console.log('Claude Code capabilities:', caps);
+ // Output: { supportsSkills: true, supportsHooks: true, supportsTemperature: false, ... }
+
+ // Validate conversion
+ const warnings = adapter.validateConversion(agent);
+ if (warnings.length > 0) {
+ console.log('Conversion warnings:', warnings);
+ }
+
+ // Convert
+ const result = await adapter.fromOAC(agent);
+
+ if (result.success) {
+ console.log('Conversion successful!');
+ console.log('Output files:', result.configs.map(c => c.fileName));
+ // Output: ['.claude/agents/MyAgent.md', '.claude/skills/context-skill/SKILL.md']
+ console.log('Warnings:', result.warnings);
+ } else {
+ console.error('Conversion failed:', result.errors);
+ }
+}
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### "Agent name is required" warning
+
+**Cause**: OAC agent doesn't have a `name` in frontmatter.
+
+**Solution**: Add name to your OAC agent:
+```yaml
+---
+name: MyAgent
+description: My agent description
+---
+```
+
+#### Temperature setting ignored
+
+**Cause**: Claude Code doesn't support temperature control.
+
+**Solution**: No direct equivalent. Claude Code manages creativity internally. Remove temperature from your expectations or use Claude Code's built-in settings.
+
+#### maxSteps not available
+
+**Cause**: Claude Code doesn't expose step limits.
+
+**Solution**: No workaround - Claude Code manages execution internally.
+
+#### Permissions not enforced granularly
+
+**Cause**: Claude Code only supports `permissionMode` (binary modes).
+
+**Solution**: Document permission restrictions in your system prompt:
+```
+# IMPORTANT RESTRICTIONS
+- Only edit files in src/ directory
+- Never access .env files
+- Do not run bash commands without approval
+```
+
+Or accept the degraded `permissionMode`:
+- `default` - Prompts for permission
+- `acceptEdits` - Auto-accepts edits
+- `dontAsk` - Auto-denies
+- `bypassPermissions` - Full access
+
+#### Skills not loading in Claude Code
+
+**Cause**: Skills must be in `.claude/skills/{name}/SKILL.md` format.
+
+**Solution**: Verify the skill directory structure:
+```
+.claude/
+ skills/
+ my-skill/
+ SKILL.md # Must be named SKILL.md
+```
+
+#### Hooks not triggering
+
+**Cause**: Hook format mismatch or unsupported event.
+
+**Solution**: Verify hooks use supported events:
+- `PreToolUse` - Before tool execution
+- `PostToolUse` - After tool execution
+- `PermissionRequest` - On permission prompt
+- `AgentStart` - Agent session start
+- `AgentEnd` - Agent session end
+
+Check hook format in converted file:
+```json
+{
+ "hooks": {
+ "PreToolUse": [
+ {
+ "matcher": "bash",
+ "hooks": [{ "type": "command", "command": "echo 'test'" }]
+ }
+ ]
+ }
+}
+```
+
+## Next Steps
+
+- [Claude → OAC Migration](./claude-to-oac.md) - Import Claude Code configurations
+- [OAC → Cursor Migration](./oac-to-cursor.md) - Convert to Cursor format
+- [Feature Capabilities Matrix](../feature-matrices/capabilities-overview.md) - Full feature comparison
+- [Claude Code Documentation](https://code.claude.com/docs) - Official Claude Code docs
diff --git a/packages/compatibility-layer/docs/migration-guides/oac-to-cursor.md b/packages/compatibility-layer/docs/migration-guides/oac-to-cursor.md
new file mode 100644
index 0000000..836892f
--- /dev/null
+++ b/packages/compatibility-layer/docs/migration-guides/oac-to-cursor.md
@@ -0,0 +1,538 @@
+# Migrating from OAC to Cursor
+
+> Convert OpenAgents Control agents to Cursor IDE's `.cursorrules` format
+
+## Overview
+
+Export to Cursor format when you:
+- Use Cursor IDE as your primary development environment
+- Want to share agent configurations with Cursor-only teams
+- Need a simpler, single-file agent configuration
+
+**Important**: Cursor uses a single `.cursorrules` file. Multiple OAC agents will be merged into one file with potential feature loss.
+
+## Prerequisites
+
+| Requirement | Version | Notes |
+|-------------|---------|-------|
+| Node.js | >= 18 | Required for CLI |
+| @openagents-control/compatibility-layer | Latest | Install via npm |
+| Cursor IDE | Any | Target platform |
+
+```bash
+npm install -g @openagents-control/compatibility-layer
+```
+
+## Quick Migration
+
+```bash
+# Single agent conversion
+oac-compat convert agent.md -f cursor -o .cursorrules
+
+# Convert with verbose output
+oac-compat convert agent.md -f cursor -o .cursorrules --verbose
+
+# Validate before converting
+oac-compat validate --target cursor agent.md
+```
+
+## Step-by-Step Manual Migration
+
+### 1. OAC Agent Structure
+
+Typical OAC agent with full features:
+
+```yaml
+---
+name: DocWriter
+description: Documentation authoring agent
+mode: subagent
+temperature: 0.2
+model: claude-opus-4
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: false
+permission:
+ bash:
+ "*": "deny"
+ edit:
+ "**/*.md": "allow"
+ "**/*.env*": "deny"
+skills:
+ - context-manager
+ - task-management
+hooks:
+ - event: PreToolUse
+ matchers: ["edit"]
+ commands:
+ - type: command
+ command: "echo 'Editing file...'"
+maxSteps: 50
+---
+
+# DocWriter
+
+> Mission: Create and update documentation
+
+## Rules
+
+1. Always follow project documentation standards
+2. Use concise, example-driven writing
+3. Propose changes before implementing
+
+## Context
+
+Load documentation standards before writing.
+```
+
+### 2. Cursor Rules Structure
+
+Equivalent `.cursorrules` format:
+
+```yaml
+---
+name: DocWriter
+description: Documentation authoring agent
+model: claude-3-opus
+temperature: 0.2
+---
+
+# DocWriter
+
+> Mission: Create and update documentation
+
+## Rules
+
+1. Always follow project documentation standards
+2. Use concise, example-driven writing
+3. Propose changes before implementing
+
+## Context
+
+Load documentation standards before writing.
+
+---
+
+# Tool Access
+
+Enabled tools:
+- read
+- write
+- edit
+```
+
+### 3. Key Differences
+
+| Aspect | OAC | Cursor |
+|--------|-----|--------|
+| File format | Markdown with YAML frontmatter | Plain text with optional frontmatter |
+| File location | `.opencode/agent/*.md` | `.cursorrules` (project root) |
+| Multiple agents | Supported | Single file only |
+| Permissions | Granular (allow/deny/ask per path) | Binary (on/off) |
+| Skills | Full system | Not supported |
+| Hooks | Full system | Not supported |
+| Contexts | Referenced files | Must inline or reference manually |
+| Model names | OAC format | Cursor format |
+
+### 4. Feature Mapping
+
+| OAC Feature | Cursor Equivalent | Notes |
+|-------------|-------------------|-------|
+| `frontmatter.name` | `name` in frontmatter | Direct mapping |
+| `frontmatter.description` | `description` in frontmatter | Direct mapping |
+| `frontmatter.model` | `model` in frontmatter | Model name converted |
+| `frontmatter.temperature` | `temperature` in frontmatter | Direct mapping |
+| `frontmatter.mode` | N/A | Cursor doesn't distinguish modes |
+| `frontmatter.tools` | Listed in body | Converted to tool list |
+| `frontmatter.permission` | N/A | Lost - degraded to binary |
+| `frontmatter.skills` | N/A | Lost - consider inlining |
+| `frontmatter.hooks` | N/A | Lost entirely |
+| `frontmatter.maxSteps` | N/A | Not supported |
+| `contexts` | Inlined references | Manual loading required |
+| `systemPrompt` | Body content | Direct mapping |
+
+### 5. Model Name Mapping
+
+| OAC Model | Cursor Model |
+|-----------|--------------|
+| `gpt-4` | `gpt-4` |
+| `gpt-4-turbo` | `gpt-4-turbo` |
+| `gpt-4o` | `gpt-4o` |
+| `gpt-3.5-turbo` | `gpt-3.5-turbo` |
+| `claude-opus-3` | `claude-3-opus` |
+| `claude-sonnet-3` | `claude-3-sonnet` |
+| `claude-haiku-3` | `claude-3-haiku` |
+| `claude-opus-4` | `claude-3-opus` (fallback) |
+| `claude-sonnet-4` | `claude-3-sonnet` (fallback) |
+| `claude-haiku-4` | `claude-3-haiku` (fallback) |
+
+## Common Patterns
+
+### Pattern 1: Basic Agent
+
+**Before (OAC):**
+
+```yaml
+---
+name: CodeReviewer
+description: Reviews code for quality
+mode: subagent
+tools:
+ read: true
+ grep: true
+ glob: true
+---
+
+# Code Reviewer
+
+Review code for:
+- Code style consistency
+- Potential bugs
+- Performance issues
+```
+
+**After (Cursor):**
+
+```yaml
+---
+name: CodeReviewer
+description: Reviews code for quality
+---
+
+# Code Reviewer
+
+Review code for:
+- Code style consistency
+- Potential bugs
+- Performance issues
+
+---
+
+# Tool Access
+
+Enabled tools:
+- read
+- grep
+- glob
+```
+
+### Pattern 2: Agent with Contexts
+
+**Before (OAC):**
+
+```yaml
+---
+name: StyleGuide
+description: Enforces code style
+mode: subagent
+---
+
+# Style Guide Agent
+
+Follow the project style guide.
+
+
+```
+
+With `contexts`:
+```json
+[
+ { "path": ".opencode/context/style-guide.md", "priority": "critical" }
+]
+```
+
+**After (Cursor):**
+
+```yaml
+---
+name: StyleGuide
+description: Enforces code style
+---
+
+# Style Guide Agent
+
+Follow the project style guide.
+
+---
+
+# Context Files
+
+The following contexts from OpenAgents Control have been inlined:
+
+## .opencode/context/style-guide.md
+**Priority**: critical
+
+> **Note**: Original context file: `.opencode/context/style-guide.md`. Load this file for full details.
+```
+
+### Pattern 3: Agent with Granular Permissions
+
+**Before (OAC):**
+
+```yaml
+---
+name: SecureWriter
+description: Writes files with restrictions
+mode: subagent
+permission:
+ edit:
+ "src/**/*.ts": "allow"
+ "**/*.env*": "deny"
+ "**/*.secret": "deny"
+ bash:
+ "*": "deny"
+---
+
+# Secure Writer
+
+Write TypeScript files only. Never access secrets.
+```
+
+**After (Cursor):**
+
+```yaml
+---
+name: SecureWriter
+description: Writes files with restrictions
+---
+
+# Secure Writer
+
+Write TypeScript files only. Never access secrets.
+
+---
+
+# Tool Access
+
+Enabled tools:
+- edit
+
+# IMPORTANT: Permission Restrictions
+
+This agent had granular permissions in OAC that cannot be enforced in Cursor:
+- edit: Only `src/**/*.ts` files allowed
+- edit: `**/*.env*` and `**/*.secret` files denied
+- bash: Completely denied
+
+Please enforce these restrictions manually.
+```
+
+## Multi-Agent Handling
+
+Cursor only supports a single `.cursorrules` file. When converting multiple OAC agents:
+
+```bash
+# Multiple agents are merged automatically
+oac-compat convert agent1.md agent2.md agent3.md -f cursor -o .cursorrules
+```
+
+**Merge behavior:**
+
+1. **Names** - Combined: `agent1-agent2-agent3`
+2. **System prompts** - Concatenated with separators
+3. **Tools** - Union of all enabled tools
+4. **Temperature** - Highest value used
+5. **Model** - First available model used
+6. **Contexts** - All contexts merged
+
+**Example merged output:**
+
+```yaml
+---
+name: agent1-agent2-agent3
+description: Merged agent: Agent 1, Agent 2, Agent 3
+model: gpt-4
+temperature: 0.7
+---
+
+# Agent 1: Agent 1
+First agent description
+
+[First agent system prompt]
+
+---
+
+# Agent 2: Agent 2
+Second agent description
+
+[Second agent system prompt]
+
+---
+
+# Agent 3: Agent 3
+Third agent description
+
+[Third agent system prompt]
+
+---
+
+# Tool Access
+
+Enabled tools:
+- read
+- write
+- edit
+- bash
+```
+
+## Limitations
+
+### Features Lost in Conversion
+
+| Feature | Impact | Workaround |
+|---------|--------|------------|
+| **Skills** | Completely lost | Inline skill content into main prompt |
+| **Hooks** | Completely lost | No equivalent - manual process required |
+| **maxSteps** | Not supported | None - Cursor manages this internally |
+| **Granular permissions** | Degraded to binary | Document restrictions in prompt |
+| **Multiple agents** | Must merge | Use merged file or choose primary agent |
+| **Agent modes** | Ignored | Cursor doesn't distinguish primary/subagent |
+
+### Conversion Warnings
+
+The converter will emit warnings for:
+
+```
+⚠️ Agent name missing - using 'cursor-agent' as default
+⚠️ Cursor IDE does not distinguish between primary and subagent modes
+⚠️ skills not supported in Cursor IDE (2 skills)
+💡 Consider inlining skill content into the main prompt for Cursor
+⚠️ hooks not supported in Cursor IDE (1 hooks)
+⚠️ maxSteps not supported in Cursor IDE
+⚠️ granular permissions degraded: allow/deny/ask per path → binary on/off per tool
+💡 2 context file(s) referenced - consider loading them manually in Cursor
+```
+
+### What's Preserved
+
+- Agent name and description
+- Model selection (with name mapping)
+- Temperature setting
+- System prompt content
+- Tool access (binary on/off)
+- Context references (as inlined notes)
+
+## Validation
+
+### Verify Conversion Success
+
+```bash
+# 1. Run conversion with verbose output
+oac-compat convert agent.md -f cursor -o .cursorrules --verbose
+
+# 2. Check for warnings
+# Review any warnings about lost features
+
+# 3. Validate output file exists
+ls -la .cursorrules
+
+# 4. Test in Cursor IDE
+# Open project in Cursor and verify agent behavior
+```
+
+### Manual Verification Checklist
+
+- [ ] `.cursorrules` file created in project root
+- [ ] Agent name and description present in frontmatter
+- [ ] Model and temperature settings correct
+- [ ] System prompt content complete
+- [ ] Tool access section lists expected tools
+- [ ] Context references noted (if applicable)
+- [ ] Warnings reviewed and addressed
+
+### Programmatic Validation
+
+```typescript
+import { CursorAdapter, loadAgent } from '@openagents-control/compatibility-layer';
+
+async function validateConversion() {
+ const agent = await loadAgent('./agent.md');
+ const adapter = new CursorAdapter();
+
+ // Check capabilities
+ const caps = adapter.getCapabilities();
+ console.log('Cursor capabilities:', caps);
+
+ // Validate conversion
+ const warnings = adapter.validateConversion(agent);
+ if (warnings.length > 0) {
+ console.log('Conversion warnings:', warnings);
+ }
+
+ // Convert
+ const result = await adapter.fromOAC(agent);
+
+ if (result.success) {
+ console.log('Conversion successful!');
+ console.log('Output files:', result.configs.map(c => c.fileName));
+ console.log('Warnings:', result.warnings);
+ } else {
+ console.error('Conversion failed:', result.errors);
+ }
+}
+```
+
+## Troubleshooting
+
+### Common Issues
+
+#### "Agent name missing" warning
+
+**Cause**: OAC agent doesn't have a `name` in frontmatter.
+
+**Solution**: Add name to your OAC agent:
+```yaml
+---
+name: MyAgent
+---
+```
+
+#### Skills content not appearing
+
+**Cause**: Cursor doesn't support the Skills system.
+
+**Solution**: Manually inline skill content into your system prompt before conversion, or add it to the `.cursorrules` file after conversion.
+
+#### Hooks not working
+
+**Cause**: Cursor has no hooks system.
+
+**Solution**: No direct equivalent. Consider:
+- Using Cursor's built-in features
+- Documenting manual steps in the prompt
+- Accepting this feature loss
+
+#### Model not recognized
+
+**Cause**: Model name mapping failed.
+
+**Solution**: The converter defaults to `gpt-4`. Update the `model` field in `.cursorrules` manually if needed.
+
+#### Permissions not enforced
+
+**Cause**: Cursor only supports binary tool access.
+
+**Solution**: Document permission restrictions in your system prompt:
+```
+# IMPORTANT RESTRICTIONS
+- Only edit files in src/ directory
+- Never access .env files
+- Do not run bash commands
+```
+
+#### Multiple agents merged unexpectedly
+
+**Cause**: Cursor only supports single agent.
+
+**Solution**: Either accept the merged configuration or:
+1. Convert only your primary agent
+2. Create separate projects for different agents
+
+## Next Steps
+
+- [Cursor → OAC Migration](./cursor-to-oac.md) - Import Cursor configurations
+- [OAC → Claude Migration](./oac-to-claude.md) - Convert to Claude Code format
+- [Feature Capabilities Matrix](../feature-matrices/capabilities-overview.md) - Full feature comparison
+- [Cursor IDE Documentation](https://cursor.sh/docs) - Official Cursor docs
diff --git a/packages/compatibility-layer/docs/migration-guides/oac-to-windsurf.md b/packages/compatibility-layer/docs/migration-guides/oac-to-windsurf.md
new file mode 100644
index 0000000..b739ad5
--- /dev/null
+++ b/packages/compatibility-layer/docs/migration-guides/oac-to-windsurf.md
@@ -0,0 +1,464 @@
+# OAC to Windsurf Migration Guide
+
+Migrate your OpenAgents Control (OAC) agent configurations to Windsurf format. This guide covers the CLI migration command, manual conversion steps, and feature mapping differences between the two formats.
+
+---
+
+## Prerequisites
+
+- **Node.js** v18+
+- **@openagents/compatibility-layer** CLI installed:
+ ```bash
+ npm install -g @openagents/compatibility-layer
+ ```
+
+---
+
+## Quick Migration
+
+One command converts your OAC agent to Windsurf:
+
+```bash
+oac convert --from oac --to windsurf --input .opencode/agent/my-agent.md --output .windsurf/
+```
+
+**Output:**
+- Primary agents → `.windsurf/config.json`
+- Subagents → `.windsurf/agents/{name}.json`
+
+---
+
+## Step-by-Step Manual Migration
+
+### Step 1: Extract Frontmatter
+
+**Before (OAC `.md` file):**
+```yaml
+---
+name: code-reviewer
+description: Reviews code for quality and security
+mode: subagent
+model: claude-sonnet-4
+temperature: 0.3
+tools:
+ read: true
+ write: false
+ bash: false
+---
+```
+
+**After (Windsurf `.json`):**
+```json
+{
+ "name": "code-reviewer",
+ "description": "Reviews code for quality and security",
+ "type": "subagent",
+ "model": "claude-4-sonnet",
+ "creativity": "low",
+ "tools": {
+ "read": true,
+ "write": false,
+ "bash": false
+ }
+}
+```
+
+### Step 2: Convert System Prompt
+
+**Before (OAC):**
+```markdown
+---
+name: code-reviewer
+---
+
+You are a code reviewer. Analyze code for:
+- Security vulnerabilities
+- Performance issues
+- Best practices
+```
+
+**After (Windsurf):**
+```json
+{
+ "name": "code-reviewer",
+ "systemPrompt": "You are a code reviewer. Analyze code for:\n- Security vulnerabilities\n- Performance issues\n- Best practices"
+}
+```
+
+### Step 3: Map Contexts
+
+**Before (OAC):**
+```yaml
+contexts:
+ - path: .opencode/context/code-standards.md
+ priority: critical
+ description: Company coding standards
+ - path: .opencode/context/security-rules.md
+ priority: high
+```
+
+**After (Windsurf):**
+```json
+{
+ "contexts": [
+ {
+ "path": ".windsurf/context/code-standards.md",
+ "priority": "high",
+ "description": "Company coding standards"
+ },
+ {
+ "path": ".windsurf/context/security-rules.md",
+ "priority": "high"
+ }
+ ]
+}
+```
+
+> **Note:** Copy context files from `.opencode/context/` to `.windsurf/context/`
+
+---
+
+## Key Differences
+
+| Aspect | OAC | Windsurf |
+|--------|-----|----------|
+| **Format** | Markdown + YAML frontmatter | JSON |
+| **Location** | `.opencode/agent/*.md` | `.windsurf/config.json` or `.windsurf/agents/*.json` |
+| **Temperature** | Numeric (0.0-2.0) | String: `low`, `medium`, `high` |
+| **Permissions** | Granular (allow/deny/ask per path) | Binary (on/off) |
+| **Hooks** | Full support | Not supported |
+| **Priority Levels** | 4 levels (critical/high/medium/low) | 2 levels (high/low) |
+| **Skills** | Full Skills system | Basic context references |
+
+---
+
+## Feature Mapping
+
+| OAC Feature | Windsurf Equivalent | Notes |
+|-------------|---------------------|-------|
+| `name` | `name` | Direct mapping |
+| `description` | `description` | Direct mapping |
+| `mode: primary` | `type: "primary"` | Renamed field |
+| `mode: subagent` | `type: "subagent"` | Renamed field |
+| `model: claude-sonnet-4` | `model: "claude-4-sonnet"` | Model ID format differs |
+| `model: claude-opus-4` | `model: "claude-4-opus"` | Model ID format differs |
+| `model: gpt-4o` | `model: "gpt-4o"` | Direct mapping |
+| `temperature: 0.0-0.4` | `creativity: "low"` | Mapped to string |
+| `temperature: 0.4-0.8` | `creativity: "medium"` | Mapped to string |
+| `temperature: 0.8-2.0` | `creativity: "high"` | Mapped to string |
+| `tools: { read: true }` | `tools: { read: true }` | Direct mapping |
+| `permission: { read: "allow" }` | `permissions: { read: true }` | Simplified to boolean |
+| `permission: { write: "ask" }` | `permissions: { write: false }` | "ask" → false (cautious) |
+| `skills: ["skill-name"]` | `contexts: [{ path: ".windsurf/context/skill-name.md" }]` | Converted to context refs |
+| `hooks` | ❌ Not supported | Lost in conversion |
+| `maxSteps` | ❌ Not supported | Lost in conversion |
+| `priority: critical` | `priority: "high"` | Downgraded |
+| `priority: medium` | `priority: "low"` | Downgraded |
+
+---
+
+## Common Patterns
+
+### Pattern 1: Agent with Restricted Tools
+
+**Before (OAC):**
+```yaml
+---
+name: doc-writer
+description: Writes documentation
+mode: subagent
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: false
+ task: false
+---
+
+You write technical documentation following project standards.
+```
+
+**After (Windsurf):**
+```json
+{
+ "name": "doc-writer",
+ "description": "Writes documentation",
+ "type": "subagent",
+ "tools": {
+ "read": true,
+ "write": true,
+ "edit": true,
+ "bash": false,
+ "task": false
+ },
+ "systemPrompt": "You write technical documentation following project standards."
+}
+```
+
+### Pattern 2: Agent with Granular Permissions
+
+**Before (OAC):**
+```yaml
+---
+name: safe-coder
+description: Writes code with restricted access
+mode: subagent
+permission:
+ read: allow
+ write:
+ allow: ["src/**/*.ts"]
+ deny: ["src/secrets/**"]
+ bash: ask
+---
+```
+
+**After (Windsurf):**
+```json
+{
+ "name": "safe-coder",
+ "description": "Writes code with restricted access",
+ "type": "subagent",
+ "permissions": {
+ "read": true,
+ "write": true,
+ "bash": false
+ }
+}
+```
+
+> **Warning:** Granular path rules (`allow: ["src/**/*.ts"]`) are lost. Windsurf only supports binary on/off.
+
+### Pattern 3: Agent with Skills
+
+**Before (OAC):**
+```yaml
+---
+name: context-expert
+description: Manages project context
+mode: subagent
+skills:
+ - context-manager
+ - task-management
+---
+```
+
+**After (Windsurf):**
+```json
+{
+ "name": "context-expert",
+ "description": "Manages project context",
+ "type": "subagent",
+ "contexts": [
+ {
+ "path": ".windsurf/context/context-manager.md",
+ "priority": "medium",
+ "description": "Skill: context-manager"
+ },
+ {
+ "path": ".windsurf/context/task-management.md",
+ "priority": "medium",
+ "description": "Skill: task-management"
+ }
+ ]
+}
+```
+
+> **Note:** You must manually create the skill context files in `.windsurf/context/`
+
+### Pattern 4: Primary Agent with Full Config
+
+**Before (OAC):**
+```yaml
+---
+name: main-agent
+description: Primary development agent
+mode: primary
+model: claude-opus-4
+temperature: 0.7
+tools:
+ read: true
+ write: true
+ edit: true
+ bash: true
+ task: true
+ grep: true
+ glob: true
+---
+
+You are the primary development agent for this project.
+```
+
+**After (Windsurf `.windsurf/config.json`):**
+```json
+{
+ "name": "main-agent",
+ "description": "Primary development agent",
+ "type": "primary",
+ "model": "claude-4-opus",
+ "creativity": "medium",
+ "tools": {
+ "read": true,
+ "write": true,
+ "edit": true,
+ "bash": true,
+ "task": true,
+ "grep": true,
+ "glob": true
+ },
+ "systemPrompt": "You are the primary development agent for this project."
+}
+```
+
+---
+
+## Limitations
+
+| OAC Feature | Windsurf Support | Impact |
+|-------------|------------------|--------|
+| **Hooks** | ❌ Not supported | Behavioral rules (PreToolUse, PostToolUse, etc.) are completely lost |
+| **maxSteps** | ❌ Not supported | Execution step limits cannot be enforced |
+| **Granular Permissions** | ⚠️ Degraded | Path-based allow/deny rules become binary on/off |
+| **"ask" Permission** | ⚠️ Degraded | Converted to `false` (deny) for safety |
+| **4-Level Priority** | ⚠️ Degraded | `critical` → `high`, `medium` → `low` |
+| **Skills System** | ⚠️ Partial | Converted to basic context file references |
+| **Markdown Format** | ⚠️ Changed | Agent definition moves from `.md` to `.json` |
+
+---
+
+## Validation Steps
+
+After migration, verify your configuration:
+
+### 1. Validate JSON Syntax
+
+```bash
+# Check JSON is valid
+cat .windsurf/config.json | jq .
+```
+
+### 2. Verify Required Fields
+
+```bash
+# Check required fields exist
+jq 'has("name") and has("description") and has("systemPrompt")' .windsurf/config.json
+```
+
+### 3. Test Agent Loading
+
+```bash
+# If Windsurf CLI available
+windsurf validate .windsurf/config.json
+```
+
+### 4. Check Context Files Exist
+
+```bash
+# Verify all referenced contexts exist
+jq -r '.contexts[]?.path // empty' .windsurf/config.json | while read path; do
+ [ -f "$path" ] || echo "Missing: $path"
+done
+```
+
+### 5. Review Conversion Warnings
+
+The CLI outputs warnings for feature degradation:
+
+```
+⚠️ Permission "ask" for bash degraded to false (deny). Windsurf only supports binary on/off.
+⚠️ Granular permissions degraded from allow/deny/ask per path to binary on/off per tool
+❌ Windsurf does not support hooks - behavioral rules will be lost
+💡 2 context file(s) referenced - ensure they exist in .windsurf/context/
+```
+
+---
+
+## Troubleshooting
+
+### Issue: "Invalid Windsurf config format"
+
+**Cause:** Input file is not valid JSON or has unexpected structure.
+
+**Solution:**
+```bash
+# Validate your source OAC file first
+oac validate .opencode/agent/my-agent.md
+
+# Check for YAML syntax errors in frontmatter
+```
+
+### Issue: Hooks Not Working
+
+**Cause:** Windsurf does not support hooks. They are silently dropped.
+
+**Solution:**
+- Document hook behavior manually in context files
+- Use Windsurf's native features if available
+- Accept limitation or stay on OAC
+
+### Issue: Permissions Too Restrictive
+
+**Cause:** `ask` permissions convert to `false` (deny).
+
+**Solution:**
+```json
+// Manually change if you want to allow
+"permissions": {
+ "bash": true // Changed from false
+}
+```
+
+### Issue: Context Files Not Found
+
+**Cause:** Context paths reference `.opencode/context/` but Windsurf expects `.windsurf/context/`.
+
+**Solution:**
+```bash
+# Copy context files to Windsurf location
+mkdir -p .windsurf/context
+cp .opencode/context/*.md .windsurf/context/
+```
+
+### Issue: Model Not Recognized
+
+**Cause:** Model ID format differs between OAC and Windsurf.
+
+**Solution:**
+```json
+// OAC model IDs map to Windsurf:
+// claude-sonnet-4 → claude-4-sonnet
+// claude-opus-4 → claude-4-opus
+// claude-haiku-4 → claude-4-haiku
+```
+
+### Issue: Skills Not Loading
+
+**Cause:** Skills are converted to context references but the context files don't exist.
+
+**Solution:**
+1. Export skill content from OAC
+2. Create `.windsurf/context/{skill-name}.md` files
+3. Include skill instructions in each context file
+
+---
+
+## Next Steps
+
+1. **Run the migration:**
+ ```bash
+ oac convert --from oac --to windsurf --input .opencode/agent/ --output .windsurf/
+ ```
+
+2. **Review warnings** for feature degradation
+
+3. **Copy context files:**
+ ```bash
+ cp -r .opencode/context/* .windsurf/context/
+ ```
+
+4. **Create skill context files** for any referenced skills
+
+5. **Test the converted agent** in Windsurf
+
+6. **Document lost features** (hooks, granular permissions) in your project README
+
+7. **Consider keeping OAC as source of truth** if you need features Windsurf doesn't support
diff --git a/packages/compatibility-layer/package-lock.json b/packages/compatibility-layer/package-lock.json
new file mode 100644
index 0000000..369079f
--- /dev/null
+++ b/packages/compatibility-layer/package-lock.json
@@ -0,0 +1,3808 @@
+{
+ "name": "@openagents-control/compatibility-layer",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@openagents-control/compatibility-layer",
+ "version": "0.1.0",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "commander": "^12.1.0",
+ "gray-matter": "^4.0.3",
+ "js-yaml": "^4.1.0",
+ "ora": "^8.0.1",
+ "zod": "^3.23.8"
+ },
+ "bin": {
+ "oac-compat": "dist/cli/index.js"
+ },
+ "devDependencies": {
+ "@types/js-yaml": "^4.0.9",
+ "@types/node": "^20.12.12",
+ "@typescript-eslint/eslint-plugin": "^7.10.0",
+ "@typescript-eslint/parser": "^7.10.0",
+ "@vitest/coverage-v8": "^1.6.0",
+ "eslint": "^8.57.0",
+ "typescript": "^5.4.5",
+ "vitest": "^1.6.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
+ "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
+ "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
+ "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
+ "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
+ "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
+ "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
+ "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
+ "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
+ "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
+ "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
+ "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
+ "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
+ "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
+ "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
+ "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
+ "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
+ "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.57.1",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.57.1",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
+ "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
+ "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
+ "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
+ "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
+ "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.57.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
+ "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.10",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/js-yaml": {
+ "version": "4.0.9",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.31",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
+ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitest/coverage-v8": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@bcoe/v8-coverage": "^0.2.3",
+ "debug": "^4.3.4",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-lib-source-maps": "^5.0.4",
+ "istanbul-reports": "^3.1.6",
+ "magic-string": "^0.30.5",
+ "magicast": "^0.3.3",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "test-exclude": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "vitest": "1.6.1"
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "chai": "^4.3.10"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "1.6.1",
+ "p-limit": "^5.0.0",
+ "pathe": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^2.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "diff-sequences": "^29.6.3",
+ "estree-walker": "^3.0.3",
+ "loupe": "^2.3.7",
+ "pretty-format": "^29.7.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.15.0",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "1.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chai": {
+ "version": "4.5.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^1.1.0",
+ "check-error": "^1.0.3",
+ "deep-eql": "^4.1.3",
+ "get-func-name": "^2.0.2",
+ "loupe": "^2.3.6",
+ "pathval": "^1.1.1",
+ "type-detect": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.6.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
+ "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "1.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.2"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
+ "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/cli-spinners": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
+ "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "12.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
+ "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.1.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "4.1.4",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-detect": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/execa": {
+ "version": "8.0.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^8.0.1",
+ "human-signals": "^5.0.0",
+ "is-stream": "^3.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^5.1.0",
+ "onetime": "^6.0.0",
+ "signal-exit": "^4.1.0",
+ "strip-final-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.17"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/extend-shallow": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
+ "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
+ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
+ "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-func-name": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "8.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/gray-matter": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-yaml": "^3.13.1",
+ "kind-of": "^6.0.2",
+ "section-matter": "^1.0.0",
+ "strip-bom-string": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/gray-matter/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/gray-matter/node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/human-signals": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.17.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-extendable": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz",
+ "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-interactive": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
+ "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+ "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "9.0.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/local-pkg": {
+ "version": "0.5.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mlly": "^1.7.3",
+ "pkg-types": "^1.2.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/log-symbols": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz",
+ "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "is-unicode-supported": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/is-unicode-supported": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
+ "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "2.3.7",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-func-name": "^2.0.1"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mimic-function": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
+ "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mlly": {
+ "version": "1.8.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "pathe": "^2.0.3",
+ "pkg-types": "^1.3.1",
+ "ufo": "^1.6.1"
+ }
+ },
+ "node_modules/mlly/node_modules/pathe": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/npm-run-path": {
+ "version": "5.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/ora": {
+ "version": "8.2.0",
+ "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz",
+ "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^5.3.0",
+ "cli-cursor": "^5.0.0",
+ "cli-spinners": "^2.9.2",
+ "is-interactive": "^2.0.0",
+ "is-unicode-supported": "^2.0.0",
+ "log-symbols": "^6.0.0",
+ "stdin-discarder": "^0.2.2",
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ora/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ora/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "5.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate/node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "1.3.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.1.8",
+ "mlly": "^1.7.4",
+ "pathe": "^2.0.1"
+ }
+ },
+ "node_modules/pkg-types/node_modules/pathe": {
+ "version": "2.0.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
+ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^7.0.0",
+ "signal-exit": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/restore-cursor/node_modules/onetime": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
+ "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-function": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.57.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.57.1",
+ "@rollup/rollup-android-arm64": "4.57.1",
+ "@rollup/rollup-darwin-arm64": "4.57.1",
+ "@rollup/rollup-darwin-x64": "4.57.1",
+ "@rollup/rollup-freebsd-arm64": "4.57.1",
+ "@rollup/rollup-freebsd-x64": "4.57.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.57.1",
+ "@rollup/rollup-linux-arm64-musl": "4.57.1",
+ "@rollup/rollup-linux-loong64-gnu": "4.57.1",
+ "@rollup/rollup-linux-loong64-musl": "4.57.1",
+ "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
+ "@rollup/rollup-linux-ppc64-musl": "4.57.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
+ "@rollup/rollup-linux-riscv64-musl": "4.57.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-gnu": "4.57.1",
+ "@rollup/rollup-linux-x64-musl": "4.57.1",
+ "@rollup/rollup-openbsd-x64": "4.57.1",
+ "@rollup/rollup-openharmony-arm64": "4.57.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.57.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.57.1",
+ "@rollup/rollup-win32-x64-gnu": "4.57.1",
+ "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "license": "MIT",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.7.3",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stdin-discarder": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
+ "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom-string": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz",
+ "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "3.0.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-literal": {
+ "version": "2.1.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^9.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinypool": {
+ "version": "0.8.4",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "2.2.1",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.1.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/ufo": {
+ "version": "1.6.3",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.4",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "1.6.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "1.6.1",
+ "@vitest/runner": "1.6.1",
+ "@vitest/snapshot": "1.6.1",
+ "@vitest/spy": "1.6.1",
+ "@vitest/utils": "1.6.1",
+ "acorn-walk": "^8.3.2",
+ "chai": "^4.3.10",
+ "debug": "^4.3.4",
+ "execa": "^8.0.1",
+ "local-pkg": "^0.5.0",
+ "magic-string": "^0.30.5",
+ "pathe": "^1.1.1",
+ "picocolors": "^1.0.0",
+ "std-env": "^3.5.0",
+ "strip-literal": "^2.0.0",
+ "tinybench": "^2.5.1",
+ "tinypool": "^0.8.3",
+ "vite": "^5.0.0",
+ "vite-node": "1.6.1",
+ "why-is-node-running": "^2.2.2"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "1.6.1",
+ "@vitest/ui": "1.6.1",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "1.2.2",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/packages/compatibility-layer/package.json b/packages/compatibility-layer/package.json
new file mode 100644
index 0000000..de30561
--- /dev/null
+++ b/packages/compatibility-layer/package.json
@@ -0,0 +1,74 @@
+{
+ "name": "@openagents-control/compatibility-layer",
+ "version": "0.1.0",
+ "type": "module",
+ "description": "Compatibility layer for converting OpenAgents Control agents to/from other AI coding tools (Cursor, Claude Code, Windsurf)",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "bin": {
+ "oac-compat": "dist/cli/index.js"
+ },
+ "files": [
+ "dist",
+ "README.md",
+ "LICENSE"
+ ],
+ "scripts": {
+ "build": "tsc",
+ "build:watch": "tsc --watch",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "test:coverage": "vitest run --coverage",
+ "lint": "eslint src --ext .ts",
+ "lint:fix": "eslint src --ext .ts --fix",
+ "clean": "rm -rf dist",
+ "prepublishOnly": "npm run clean && npm run build"
+ },
+ "keywords": [
+ "openagents",
+ "compatibility",
+ "cursor",
+ "claude",
+ "windsurf",
+ "ai-tools",
+ "agent-conversion"
+ ],
+ "author": {
+ "name": "Darren Hinde",
+ "url": "https://github.com/darrenhinde/OpenAgentsControl"
+ },
+ "contributors": [
+ {
+ "name": "Alexander Daza",
+ "email": "dev.alexander.daza@gmail.com",
+ "url": "https://github.com/devalexanderdaza"
+ }
+ ],
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/darrenhinde/OpenAgentsControl.git",
+ "directory": "packages/compatibility-layer"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "dependencies": {
+ "zod": "^3.23.8",
+ "js-yaml": "^4.1.0",
+ "gray-matter": "^4.0.3",
+ "commander": "^12.1.0",
+ "chalk": "^5.3.0",
+ "ora": "^8.0.1"
+ },
+ "devDependencies": {
+ "@types/node": "^20.12.12",
+ "@types/js-yaml": "^4.0.9",
+ "typescript": "^5.4.5",
+ "vitest": "^1.6.0",
+ "@vitest/coverage-v8": "^1.6.0",
+ "eslint": "^8.57.0",
+ "@typescript-eslint/eslint-plugin": "^7.10.0",
+ "@typescript-eslint/parser": "^7.10.0"
+ }
+}
diff --git a/packages/compatibility-layer/src/adapters/BaseAdapter.ts b/packages/compatibility-layer/src/adapters/BaseAdapter.ts
new file mode 100644
index 0000000..92e0e27
--- /dev/null
+++ b/packages/compatibility-layer/src/adapters/BaseAdapter.ts
@@ -0,0 +1,210 @@
+import type {
+ OpenAgent,
+ ConversionResult,
+ ToolCapabilities,
+} from "../types.js";
+
+/**
+ * Abstract base class for all tool adapters.
+ *
+ * Tool adapters convert OpenAgents Control agents to/from tool-specific formats.
+ * Each adapter implements bidirectional conversion and reports its capabilities.
+ *
+ * @example
+ * ```ts
+ * class CursorAdapter extends BaseAdapter {
+ * readonly name = 'cursor';
+ * readonly displayName = 'Cursor IDE';
+ *
+ * async toOAC(source: string): Promise\u003cOpenAgent\u003e {
+ * // Parse Cursor .cursorrules format
+ * // Convert to OpenAgent
+ * }
+ *
+ * async fromOAC(agent: OpenAgent): Promise\u003cConversionResult\u003e {
+ * // Convert OpenAgent to Cursor format
+ * // Return converted files
+ * }
+ * }
+ * ```
+ */
+export abstract class BaseAdapter {
+ /**
+ * Unique identifier for this adapter (e.g., "cursor", "claude", "windsurf")
+ */
+ abstract readonly name: string;
+
+ /**
+ * Human-readable display name (e.g., "Cursor IDE", "Claude Code")
+ */
+ abstract readonly displayName: string;
+
+ /**
+ * Protected constructor - only subclasses can instantiate
+ */
+ protected constructor() {}
+
+ // ============================================================================
+ // ABSTRACT METHODS - Must be implemented by subclasses
+ // ============================================================================
+
+ /**
+ * Convert from tool-specific format TO OpenAgents Control format.
+ *
+ * @param source - The tool-specific config file content
+ * @returns Parsed OpenAgent object
+ * @throws {Error} If parsing or conversion fails
+ */
+ abstract toOAC(source: string): Promise;
+
+ /**
+ * Convert FROM OpenAgents Control format to tool-specific format.
+ *
+ * @param agent - The OpenAgent to convert
+ * @returns Conversion result with generated files, warnings, and errors
+ */
+ abstract fromOAC(agent: OpenAgent): Promise;
+
+ /**
+ * Get the configuration path where files should be written.
+ *
+ * @param agent - Optional agent for context-specific paths
+ * @returns Relative path from project root (e.g., ".cursorrules", ".claude/")
+ */
+ abstract getConfigPath(agent?: OpenAgent): string;
+
+ /**
+ * Get capabilities of this tool.
+ *
+ * @returns Tool capabilities object describing supported features
+ */
+ abstract getCapabilities(): ToolCapabilities;
+
+ /**
+ * Validate if an agent can be converted with full fidelity.
+ *
+ * @param agent - The agent to validate
+ * @returns Array of warnings about features that will be lost or degraded
+ */
+ abstract validateConversion(agent: OpenAgent): string[];
+
+ // ============================================================================
+ // CONCRETE METHODS - Common functionality for all adapters
+ // ============================================================================
+
+ /**
+ * Check if a specific feature is supported by this adapter.
+ *
+ * @param feature - The feature name to check
+ * @returns True if supported, false otherwise
+ */
+ supportsFeature(feature: keyof ToolCapabilities): boolean {
+ const capabilities = this.getCapabilities();
+ return capabilities[feature] === true;
+ }
+
+ /**
+ * Log a warning message (can be overridden for custom logging).
+ *
+ * @param message - Warning message
+ */
+ protected warn(message: string): void {
+ console.warn(`[${this.name}] ${message}`);
+ }
+
+ /**
+ * Log an error message (can be overridden for custom logging).
+ *
+ * @param message - Error message
+ */
+ protected error(message: string): void {
+ console.error(`[${this.name}] ${message}`);
+ }
+
+ /**
+ * Create a successful conversion result.
+ *
+ * @param configs - Generated configuration files
+ * @param warnings - Optional warnings
+ * @returns ConversionResult object
+ */
+ protected createSuccessResult(
+ configs: ConversionResult["configs"],
+ warnings: string[] = []
+ ): ConversionResult {
+ return {
+ success: true,
+ configs,
+ warnings,
+ capabilities: this.getCapabilities(),
+ };
+ }
+
+ /**
+ * Create a failed conversion result.
+ *
+ * @param errors - Error messages
+ * @param warnings - Optional warnings
+ * @returns ConversionResult object
+ */
+ protected createErrorResult(
+ errors: string[],
+ warnings: string[] = []
+ ): ConversionResult {
+ return {
+ success: false,
+ configs: [],
+ warnings,
+ errors,
+ capabilities: this.getCapabilities(),
+ };
+ }
+
+ /**
+ * Safely parse JSON with error handling.
+ *
+ * @param content - JSON string to parse
+ * @param filename - Optional filename for error messages
+ * @returns Parsed object or null on error
+ */
+ protected safeParseJSON(content: string, filename?: string): unknown {
+ try {
+ return JSON.parse(content);
+ } catch (error) {
+ const context = filename ? ` in ${filename}` : "";
+ this.error(`Failed to parse JSON${context}: ${String(error)}`);
+ return null;
+ }
+ }
+
+ /**
+ * Generate a standard warning message for unsupported features.
+ *
+ * @param feature - Feature name
+ * @param value - Feature value (if applicable)
+ * @returns Formatted warning message
+ */
+ protected unsupportedFeatureWarning(
+ feature: string,
+ value?: string | number | boolean
+ ): string {
+ const valueStr = value !== undefined ? ` (${String(value)})` : "";
+ return `⚠️ Feature '${feature}'${valueStr} is not supported by ${this.displayName}`;
+ }
+
+ /**
+ * Generate a standard warning message for degraded features.
+ *
+ * @param feature - Feature name
+ * @param from - Original format
+ * @param to - Target format
+ * @returns Formatted warning message
+ */
+ protected degradedFeatureWarning(
+ feature: string,
+ from: string,
+ to: string
+ ): string {
+ return `⚠️ Feature '${feature}' will be degraded: ${from} → ${to}`;
+ }
+}
diff --git a/packages/compatibility-layer/src/adapters/ClaudeAdapter.ts b/packages/compatibility-layer/src/adapters/ClaudeAdapter.ts
new file mode 100644
index 0000000..5cc8677
--- /dev/null
+++ b/packages/compatibility-layer/src/adapters/ClaudeAdapter.ts
@@ -0,0 +1,603 @@
+import { BaseAdapter } from "./BaseAdapter.js";
+import type {
+ OpenAgent,
+ ConversionResult,
+ ToolCapabilities,
+ ToolConfig,
+ AgentFrontmatter,
+ SkillReference,
+ HookDefinition,
+ HookEvent,
+} from "../types.js";
+
+/**
+ * Claude Code adapter for converting between OpenAgents Control and Claude Code formats.
+ *
+ * Claude Code uses:
+ * - `.claude/config.json` for main agent configuration
+ * - `.claude/agents/*.md` for subagents (YAML frontmatter + markdown)
+ * - `.claude/skills/` for Skills (context/knowledge injection)
+ *
+ * @see https://code.claude.com/docs/en/sub-agents
+ * @see https://code.claude.com/docs/en/skills
+ */
+export class ClaudeAdapter extends BaseAdapter {
+ readonly name = "claude";
+ readonly displayName = "Claude Code";
+
+ constructor() {
+ super();
+ }
+
+ // ============================================================================
+ // CONVERSION METHODS
+ // ============================================================================
+
+ /**
+ * Convert Claude Code format TO OpenAgents Control format.
+ *
+ * Expects either:
+ * - A JSON string (config.json content)
+ * - A markdown string with YAML frontmatter (agent.md content)
+ *
+ * @param source - Claude config.json or agent.md content
+ * @returns OpenAgent object
+ */
+ toOAC(source: string): Promise {
+ // Try parsing as JSON first (config.json)
+ if (source.trim().startsWith("{")) {
+ return Promise.resolve(this.parseClaudeConfig(source));
+ }
+
+ // Otherwise, parse as markdown with frontmatter (agent.md)
+ return Promise.resolve(this.parseClaudeAgent(source));
+ }
+
+ /**
+ * Convert FROM OpenAgents Control format to Claude Code format.
+ *
+ * Generates:
+ * - `.claude/config.json` for primary agents
+ * - `.claude/agents/{name}.md` for subagents
+ * - Skills conversion (contexts → skills)
+ *
+ * @param agent - OpenAgent to convert
+ * @returns ConversionResult with generated files and warnings
+ */
+ fromOAC(agent: OpenAgent): Promise {
+ const warnings: string[] = [];
+ const configs: ToolConfig[] = [];
+
+ // Validate conversion
+ const validationWarnings = this.validateConversion(agent);
+ warnings.push(...validationWarnings);
+
+ // Check for unsupported features
+ if (agent.frontmatter.temperature !== undefined) {
+ warnings.push(
+ this.unsupportedFeatureWarning("temperature", agent.frontmatter.temperature)
+ );
+ }
+
+ if (agent.frontmatter.maxSteps !== undefined) {
+ warnings.push(
+ this.unsupportedFeatureWarning("maxSteps", agent.frontmatter.maxSteps)
+ );
+ }
+
+ // Determine if this is a subagent or primary agent
+ const isSubagent = agent.frontmatter.mode === "subagent";
+
+ if (isSubagent) {
+ // Generate subagent markdown file
+ const agentMd = this.generateClaudeAgentMarkdown(agent, warnings);
+ configs.push({
+ fileName: `.claude/agents/${agent.frontmatter.name}.md`,
+ content: agentMd,
+ encoding: "utf-8",
+ });
+ } else {
+ // Generate primary agent config.json
+ const configJson = this.generateClaudeConfig(agent, warnings);
+ configs.push({
+ fileName: ".claude/config.json",
+ content: JSON.stringify(configJson, null, 2),
+ encoding: "utf-8",
+ });
+ }
+
+ // Generate skills from contexts if present
+ if (agent.contexts && agent.contexts.length > 0) {
+ const skillConfigs = this.generateSkillsFromContexts(agent.contexts);
+ configs.push(...skillConfigs);
+ }
+
+ return Promise.resolve(this.createSuccessResult(configs, warnings));
+ }
+
+ /**
+ * Get the configuration path for Claude Code.
+ */
+ getConfigPath(): string {
+ return ".claude/";
+ }
+
+ /**
+ * Get Claude Code capabilities.
+ */
+ getCapabilities(): ToolCapabilities {
+ return {
+ name: this.name,
+ displayName: this.displayName,
+ supportsMultipleAgents: true,
+ supportsSkills: true,
+ supportsHooks: true,
+ supportsGranularPermissions: false, // Only binary/simplified permissions
+ supportsContexts: true,
+ supportsCustomModels: true,
+ supportsTemperature: false, // ⚠️ Not supported
+ supportsMaxSteps: false,
+ configFormat: "markdown",
+ outputStructure: "directory",
+ notes: [
+ "Permissions are binary (on/off) - granular OAC permissions degrade to permissionMode",
+ "Temperature control not supported - use creativity settings instead",
+ "Hooks support: PreToolUse, PostToolUse, PermissionRequest, AgentStart, AgentEnd",
+ "Skills system provides context injection similar to OAC contexts",
+ ],
+ };
+ }
+
+ /**
+ * Validate if an agent can be converted with full fidelity.
+ */
+ validateConversion(agent: OpenAgent): string[] {
+ const warnings: string[] = [];
+
+ if (!agent.frontmatter.name) {
+ warnings.push("⚠️ Agent name is required for Claude Code");
+ }
+
+ if (!agent.frontmatter.description) {
+ warnings.push("⚠️ Agent description is required for Claude Code");
+ }
+
+ // Check for granular permissions that will be degraded
+ if (agent.frontmatter.permission) {
+ const hasGranularPerms = Object.values(agent.frontmatter.permission).some(
+ (perm) => typeof perm === "object" && !Array.isArray(perm)
+ );
+
+ if (hasGranularPerms) {
+ warnings.push(
+ this.degradedFeatureWarning(
+ "granular permissions",
+ "allow/deny/ask per operation",
+ "binary permissionMode (default/acceptEdits/dontAsk/bypassPermissions)"
+ )
+ );
+ }
+ }
+
+ return warnings;
+ }
+
+ // ============================================================================
+ // PARSING HELPERS (toOAC)
+ // ============================================================================
+
+ /**
+ * Parse Claude config.json to OpenAgent.
+ */
+ private parseClaudeConfig(source: string): OpenAgent {
+ const config = this.safeParseJSON(source, "config.json");
+ if (!config || typeof config !== "object") {
+ throw new Error("Invalid Claude config.json format");
+ }
+
+ const claudeConfig = config as Record;
+
+ const frontmatter: AgentFrontmatter = {
+ name: String(claudeConfig.name || "unnamed"),
+ description: String(claudeConfig.description || ""),
+ mode: "primary",
+ model: this.mapClaudeModelToOAC(claudeConfig.model as string),
+ tools: this.parseClaudeTools(claudeConfig.tools),
+ skills: this.parseClaudeSkills(claudeConfig.skills),
+ hooks: this.parseClaudeHooks(claudeConfig.hooks),
+ };
+
+ return {
+ frontmatter,
+ metadata: {
+ name: frontmatter.name,
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: String(claudeConfig.systemPrompt || ""),
+ contexts: [],
+ };
+ }
+
+ /**
+ * Parse Claude agent.md (subagent) to OpenAgent.
+ */
+ private parseClaudeAgent(source: string): OpenAgent {
+ const { frontmatter, body } = this.parseFrontmatter(source);
+
+ const agentFrontmatter: AgentFrontmatter = {
+ name: String(frontmatter.name || "unnamed"),
+ description: String(frontmatter.description || ""),
+ mode: "subagent",
+ model: this.mapClaudeModelToOAC(frontmatter.model as string | undefined),
+ tools: this.parseClaudeTools(frontmatter.tools),
+ skills: this.parseClaudeSkills(frontmatter.skills),
+ hooks: this.parseClaudeHooks(frontmatter.hooks),
+ };
+
+ return {
+ frontmatter: agentFrontmatter,
+ metadata: {
+ name: agentFrontmatter.name,
+ category: "specialist",
+ type: "subagent",
+ },
+ systemPrompt: body.trim(),
+ contexts: [],
+ };
+ }
+
+ /**
+ * Parse YAML frontmatter from markdown.
+ */
+ private parseFrontmatter(content: string): {
+ frontmatter: Record;
+ body: string;
+ } {
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
+ if (!match) {
+ return { frontmatter: {}, body: content };
+ }
+
+ const yamlContent = match[1] || "";
+ const body = match[2] || "";
+
+ // Simple YAML parser (supports basic key: value format)
+ const frontmatter: Record = {};
+ yamlContent.split("\n").forEach((line) => {
+ const colonIndex = line.indexOf(":");
+ if (colonIndex > -1) {
+ const key = line.slice(0, colonIndex).trim();
+ let value: unknown = line.slice(colonIndex + 1).trim();
+
+ // Parse arrays: [item1, item2]
+ if (typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
+ value = value
+ .slice(1, -1)
+ .split(",")
+ .map((v) => v.trim().replace(/"/g, ""));
+ }
+
+ frontmatter[key] = value;
+ }
+ });
+
+ return { frontmatter, body };
+ }
+
+ // ============================================================================
+ // GENERATION HELPERS (fromOAC)
+ // ============================================================================
+
+ /**
+ * Generate Claude config.json from OpenAgent.
+ */
+ private generateClaudeConfig(
+ agent: OpenAgent,
+ warnings: string[]
+ ): Record {
+ const config: Record = {
+ name: agent.frontmatter.name,
+ description: agent.frontmatter.description,
+ systemPrompt: agent.systemPrompt,
+ };
+
+ // Model mapping
+ if (agent.frontmatter.model) {
+ config.model = this.mapOACModelToClaude(agent.frontmatter.model);
+ }
+
+ // Tools mapping
+ if (agent.frontmatter.tools) {
+ config.tools = this.mapOACToolsToClaude(agent.frontmatter.tools);
+ }
+
+ // Skills mapping
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ config.skills = agent.frontmatter.skills.map((skill) =>
+ typeof skill === "string" ? skill : skill.name
+ );
+ }
+
+ // Hooks mapping
+ if (agent.frontmatter.hooks && agent.frontmatter.hooks.length > 0) {
+ config.hooks = this.mapOACHooksToClaude(agent.frontmatter.hooks);
+ }
+
+ // Permission mode mapping
+ if (agent.frontmatter.permission) {
+ config.permissionMode = this.mapOACPermissionsToClaude(
+ agent.frontmatter.permission,
+ warnings
+ );
+ }
+
+ return config;
+ }
+
+ /**
+ * Generate Claude agent.md (subagent) from OpenAgent.
+ */
+ private generateClaudeAgentMarkdown(
+ agent: OpenAgent,
+ warnings: string[]
+ ): string {
+ const frontmatter: Record = {
+ name: agent.frontmatter.name,
+ description: agent.frontmatter.description,
+ };
+
+ // Tools
+ if (agent.frontmatter.tools) {
+ const tools = this.mapOACToolsToClaude(agent.frontmatter.tools);
+ frontmatter.tools = tools.join(", ");
+ }
+
+ // Model
+ if (agent.frontmatter.model) {
+ frontmatter.model = this.mapOACModelToClaude(agent.frontmatter.model);
+ }
+
+ // Permission mode
+ if (agent.frontmatter.permission) {
+ frontmatter.permissionMode = this.mapOACPermissionsToClaude(
+ agent.frontmatter.permission,
+ warnings
+ );
+ }
+
+ // Skills
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ frontmatter.skills = agent.frontmatter.skills.map((skill) =>
+ typeof skill === "string" ? skill : skill.name
+ );
+ }
+
+ // Hooks
+ if (agent.frontmatter.hooks && agent.frontmatter.hooks.length > 0) {
+ frontmatter.hooks = this.mapOACHooksToClaude(agent.frontmatter.hooks);
+ }
+
+ // Generate YAML frontmatter
+ const yamlLines = Object.entries(frontmatter).map(([key, value]) => {
+ if (Array.isArray(value)) {
+ return `${key}: [${value.map((v) => `"${v}"`).join(", ")}]`;
+ }
+ return `${key}: "${String(value)}"`;
+ });
+
+ return `---\n${yamlLines.join("\n")}\n---\n\n${agent.systemPrompt}`;
+ }
+
+ /**
+ * Generate Skills from OAC contexts.
+ */
+ private generateSkillsFromContexts(
+ contexts: Array<{ path: string; priority?: string; description?: string }>
+ ): ToolConfig[] {
+ return contexts.map((ctx) => {
+ const skillName = ctx.path
+ .split("/")
+ .pop()
+ ?.replace(/\.md$/, "")
+ .toLowerCase()
+ .replace(/\s+/g, "-") || "context-skill";
+
+ const skillContent = `---
+name: ${skillName}
+description: ${ctx.description || `Context from ${ctx.path}`}
+---
+
+# ${skillName}
+
+This skill provides context from: \`${ctx.path}\`
+
+Priority: ${ctx.priority || "medium"}
+
+Load the full context file for detailed information:
+\`\`\`bash
+cat ${ctx.path}
+\`\`\`
+`;
+
+ return {
+ fileName: `.claude/skills/${skillName}/SKILL.md`,
+ content: skillContent,
+ encoding: "utf-8" as const,
+ };
+ });
+ }
+
+ // ============================================================================
+ // MAPPING HELPERS
+ // ============================================================================
+
+ /**
+ * Map Claude model ID to OAC model ID.
+ */
+ private mapClaudeModelToOAC(model?: string): string | undefined {
+ if (!model) return undefined;
+
+ const modelMap: Record = {
+ "claude-sonnet-4-20250514": "claude-sonnet-4",
+ "claude-opus-4": "claude-opus-4",
+ "claude-haiku-4": "claude-haiku-4",
+ sonnet: "claude-sonnet-4",
+ opus: "claude-opus-4",
+ haiku: "claude-haiku-4",
+ };
+
+ return modelMap[model] || model;
+ }
+
+ /**
+ * Map OAC model ID to Claude model ID.
+ */
+ private mapOACModelToClaude(model: string): string {
+ const modelMap: Record = {
+ "claude-sonnet-4": "claude-sonnet-4-20250514",
+ "claude-opus-4": "claude-opus-4",
+ "claude-haiku-4": "claude-haiku-4",
+ };
+
+ return modelMap[model] || "sonnet"; // Default to sonnet alias
+ }
+
+ /**
+ * Parse Claude tools to OAC ToolAccess.
+ */
+ private parseClaudeTools(tools: unknown): Record | undefined {
+ if (!tools) return undefined;
+
+ const toolAccess: Record = {};
+
+ if (typeof tools === "string") {
+ // Parse comma-separated string: "Read, Write, Bash"
+ tools.split(",").forEach((tool) => {
+ const toolName = tool.trim().toLowerCase();
+ toolAccess[toolName] = true;
+ });
+ } else if (Array.isArray(tools)) {
+ // Parse array: ["Read", "Write", "Bash"]
+ tools.forEach((tool) => {
+ const toolName = String(tool).toLowerCase();
+ toolAccess[toolName] = true;
+ });
+ }
+
+ return Object.keys(toolAccess).length > 0 ? toolAccess : undefined;
+ }
+
+ /**
+ * Map OAC ToolAccess to Claude tools array.
+ */
+ private mapOACToolsToClaude(tools: Record): string[] {
+ return Object.entries(tools)
+ .filter(([, enabled]) => enabled)
+ .map(([tool]) => {
+ // Capitalize first letter for Claude format
+ return tool.charAt(0).toUpperCase() + tool.slice(1);
+ });
+ }
+
+ /**
+ * Parse Claude skills to OAC SkillReference array.
+ */
+ private parseClaudeSkills(skills: unknown): SkillReference[] | undefined {
+ if (!skills) return undefined;
+
+ if (typeof skills === "string") {
+ return skills.split(",").map((s) => s.trim());
+ }
+
+ if (Array.isArray(skills)) {
+ return skills.map((s) => String(s));
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Parse Claude hooks to OAC HookDefinition array.
+ */
+ private parseClaudeHooks(hooks: unknown): HookDefinition[] | undefined {
+ if (!hooks || typeof hooks !== "object") return undefined;
+
+ const hookDefinitions: HookDefinition[] = [];
+ const hooksObj = hooks as Record;
+
+ // Claude hooks format: { PreToolUse: [...], PostToolUse: [...] }
+ for (const [event, hookList] of Object.entries(hooksObj)) {
+ if (!Array.isArray(hookList)) continue;
+
+ hookList.forEach((hook) => {
+ if (typeof hook === "object" && hook !== null) {
+ const hookObj = hook as Record;
+ hookDefinitions.push({
+ event: event as HookEvent,
+ matchers: hookObj.matcher
+ ? [String(hookObj.matcher)]
+ : undefined,
+ commands: hookObj.hooks
+ ? (hookObj.hooks as Array<{ type: "command"; command: string }>)
+ : [],
+ });
+ }
+ });
+ }
+
+ return hookDefinitions.length > 0 ? hookDefinitions : undefined;
+ }
+
+ /**
+ * Map OAC hooks to Claude hooks format.
+ */
+ private mapOACHooksToClaude(
+ hooks: HookDefinition[]
+ ): Record {
+ const claudeHooks: Record = {};
+
+ hooks.forEach((hook) => {
+ const event = hook.event;
+ if (!claudeHooks[event]) {
+ claudeHooks[event] = [];
+ }
+
+ claudeHooks[event].push({
+ matcher: hook.matchers?.[0] || "*",
+ hooks: hook.commands,
+ });
+ });
+
+ return claudeHooks;
+ }
+
+ /**
+ * Map OAC granular permissions to Claude permissionMode.
+ */
+ private mapOACPermissionsToClaude(
+ permissions: Record,
+ warnings: string[]
+ ): string {
+ // Analyze permission patterns to determine best permissionMode
+ const values = Object.values(permissions);
+ const hasAllAllow = values.every((v) => v === "allow" || v === true);
+ const hasAllDeny = values.every((v) => v === "deny" || v === false);
+ const hasAsk = values.some((v) => v === "ask");
+
+ if (hasAllAllow) {
+ return "bypassPermissions"; // Full access
+ } else if (hasAllDeny) {
+ return "dontAsk"; // Auto-deny
+ } else if (hasAsk) {
+ return "default"; // Prompt for permission
+ } else {
+ // Mixed or granular permissions - default to standard mode
+ warnings.push(
+ "⚠️ Complex permission rules degraded to 'default' permissionMode. " +
+ "Claude Code does not support granular allow/deny/ask per operation."
+ );
+ return "default";
+ }
+ }
+}
diff --git a/packages/compatibility-layer/src/adapters/CursorAdapter.ts b/packages/compatibility-layer/src/adapters/CursorAdapter.ts
new file mode 100644
index 0000000..aeb54a4
--- /dev/null
+++ b/packages/compatibility-layer/src/adapters/CursorAdapter.ts
@@ -0,0 +1,557 @@
+import { BaseAdapter } from "./BaseAdapter.js";
+import type {
+ OpenAgent,
+ ConversionResult,
+ ToolCapabilities,
+ AgentFrontmatter,
+} from "../types.js";
+
+/**
+ * Cursor IDE adapter for converting between OpenAgents Control and Cursor IDE formats.
+ *
+ * Cursor IDE uses:
+ * - `.cursorrules` - Single file in project root (plain text with optional frontmatter)
+ * - No support for multiple agents (must merge into one)
+ * - No support for hooks or skills
+ * - Binary permissions (on/off only)
+ *
+ * **Key Limitations**:
+ * - Single agent only - multiple OAC agents get merged
+ * - No Skills system - contexts must be inlined
+ * - No Hooks - lost during conversion
+ * - Binary permissions - granular rules degraded
+ *
+ * @see https://cursor.sh/
+ */
+export class CursorAdapter extends BaseAdapter {
+ readonly name = "cursor";
+ readonly displayName = "Cursor IDE";
+
+ constructor() {
+ super();
+ }
+
+ // ============================================================================
+ // CONVERSION METHODS
+ // ============================================================================
+
+ /**
+ * Convert Cursor IDE format TO OpenAgents Control format.
+ *
+ * Parses .cursorrules file which can be:
+ * - Plain text (becomes systemPrompt)
+ * - Markdown with frontmatter (YAML config + instructions)
+ *
+ * @param source - .cursorrules file content
+ * @returns OpenAgent object
+ */
+ toOAC(source: string): Promise {
+ // Try parsing frontmatter
+ const { frontmatter, body } = this.parseFrontmatter(source);
+
+ // Build agent frontmatter
+ const agentFrontmatter: AgentFrontmatter = {
+ name: String(frontmatter.name || "cursor-agent"),
+ description: String(
+ frontmatter.description || "Agent imported from Cursor IDE"
+ ),
+ mode: "primary",
+ model: this.mapCursorModelToOAC(frontmatter.model as string | undefined),
+ tools: this.parseCursorTools(frontmatter.tools),
+ };
+
+ // Add temperature if present
+ if (frontmatter.temperature !== undefined) {
+ agentFrontmatter.temperature = Number(frontmatter.temperature);
+ }
+
+ return Promise.resolve({
+ frontmatter: agentFrontmatter,
+ metadata: {
+ name: agentFrontmatter.name,
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: body.trim() || source.trim(),
+ contexts: [],
+ });
+ }
+
+ /**
+ * Convert FROM OpenAgents Control format to Cursor IDE format.
+ *
+ * Generates single `.cursorrules` file.
+ *
+ * **Important**: If multiple agents are provided, they will be merged
+ * into a single .cursorrules file with warnings.
+ *
+ * @param agent - OpenAgent to convert (or array of agents to merge)
+ * @returns ConversionResult with .cursorrules file and warnings
+ */
+ fromOAC(agent: OpenAgent): Promise {
+ const warnings: string[] = [];
+
+ // Validate conversion
+ const validationWarnings = this.validateConversion(agent);
+ warnings.push(...validationWarnings);
+
+ // Check for features that will be lost
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ warnings.push(
+ this.unsupportedFeatureWarning(
+ "skills",
+ `${agent.frontmatter.skills.length} skills`
+ )
+ );
+ warnings.push(
+ "💡 Consider inlining skill content into the main prompt for Cursor"
+ );
+ }
+
+ if (agent.frontmatter.hooks && agent.frontmatter.hooks.length > 0) {
+ warnings.push(
+ this.unsupportedFeatureWarning(
+ "hooks",
+ `${agent.frontmatter.hooks.length} hooks`
+ )
+ );
+ }
+
+ if (agent.frontmatter.maxSteps !== undefined) {
+ warnings.push(
+ this.unsupportedFeatureWarning("maxSteps", agent.frontmatter.maxSteps)
+ );
+ }
+
+ // Check for granular permissions
+ if (agent.frontmatter.permission) {
+ const hasGranular = Object.values(agent.frontmatter.permission).some(
+ (perm) => typeof perm === "object" && !Array.isArray(perm)
+ );
+
+ if (hasGranular) {
+ warnings.push(
+ this.degradedFeatureWarning(
+ "granular permissions",
+ "allow/deny/ask per path",
+ "binary on/off per tool"
+ )
+ );
+ }
+ }
+
+ // Generate .cursorrules content
+ const cursorRules = this.generateCursorRules(agent, warnings);
+
+ return Promise.resolve(this.createSuccessResult(
+ [
+ {
+ fileName: ".cursorrules",
+ content: cursorRules,
+ encoding: "utf-8",
+ },
+ ],
+ warnings
+ ));
+ }
+
+ /**
+ * Get the configuration path for Cursor IDE.
+ */
+ getConfigPath(): string {
+ return ".cursorrules";
+ }
+
+ /**
+ * Get Cursor IDE capabilities.
+ */
+ getCapabilities(): ToolCapabilities {
+ return {
+ name: this.name,
+ displayName: this.displayName,
+ supportsMultipleAgents: false, // ❌ Single file only
+ supportsSkills: false, // ❌ No skills system
+ supportsHooks: false, // ❌ No hooks
+ supportsGranularPermissions: false, // ⚠️ Binary only
+ supportsContexts: true, // ✅ Can inline context
+ supportsCustomModels: true, // ✅ Model selection
+ supportsTemperature: true, // ✅ Limited support
+ supportsMaxSteps: false, // ❌ Not supported
+ configFormat: "plain",
+ outputStructure: "single-file",
+ notes: [
+ "Single .cursorrules file in project root",
+ "Multiple agents must be merged into one",
+ "No Skills system - inline context content instead",
+ "No Hooks support - behavioral rules lost",
+ "Binary permissions only - granular rules degraded",
+ "Temperature support limited compared to OAC",
+ ],
+ };
+ }
+
+ /**
+ * Validate if an agent can be converted with full fidelity.
+ */
+ validateConversion(agent: OpenAgent): string[] {
+ const warnings: string[] = [];
+
+ // Cursor always works with single file, so minimal validation
+ if (!agent.frontmatter.name) {
+ warnings.push("⚠️ Agent name missing - using 'cursor-agent' as default");
+ }
+
+ // Warn about subagent mode (Cursor doesn't distinguish)
+ if (agent.frontmatter.mode === "subagent") {
+ warnings.push(
+ "⚠️ Cursor IDE does not distinguish between primary and subagent modes"
+ );
+ }
+
+ return warnings;
+ }
+
+ // ============================================================================
+ // PARSING HELPERS (toOAC)
+ // ============================================================================
+
+ /**
+ * Parse frontmatter from .cursorrules file.
+ * Cursor supports optional YAML frontmatter like:
+ * ---
+ * name: my-agent
+ * model: gpt-4
+ * ---
+ * Rules content here...
+ */
+ private parseFrontmatter(content: string): {
+ frontmatter: Record;
+ body: string;
+ } {
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
+ if (!match) {
+ // No frontmatter, entire content is the body
+ return { frontmatter: {}, body: content };
+ }
+
+ const yamlContent = match[1] || "";
+ const body = match[2] || "";
+
+ // Simple YAML parser
+ const frontmatter: Record = {};
+ yamlContent.split("\n").forEach((line) => {
+ const colonIndex = line.indexOf(":");
+ if (colonIndex > -1) {
+ const key = line.slice(0, colonIndex).trim();
+ let value: unknown = line.slice(colonIndex + 1).trim();
+
+ // Remove quotes
+ if (typeof value === "string") {
+ value = value.replace(/^["']|["']$/g, "");
+ }
+
+ // Parse numbers
+ if (typeof value === "string" && !isNaN(Number(value))) {
+ value = Number(value);
+ }
+
+ // Parse booleans
+ if (value === "true") value = true;
+ if (value === "false") value = false;
+
+ frontmatter[key] = value;
+ }
+ });
+
+ return { frontmatter, body };
+ }
+
+ /**
+ * Parse Cursor tools specification.
+ * Cursor typically doesn't have explicit tool config in .cursorrules,
+ * but we support it if present.
+ */
+ private parseCursorTools(
+ tools: unknown
+ ): Record | undefined {
+ if (!tools) return undefined;
+
+ const toolAccess: Record = {};
+
+ if (typeof tools === "string") {
+ // Parse comma-separated: "read, write, bash"
+ tools.split(",").forEach((tool) => {
+ const toolName = tool.trim().toLowerCase();
+ if (toolName) {
+ toolAccess[toolName] = true;
+ }
+ });
+ } else if (Array.isArray(tools)) {
+ // Parse array: ["read", "write", "bash"]
+ tools.forEach((tool) => {
+ const toolName = String(tool).toLowerCase();
+ if (toolName) {
+ toolAccess[toolName] = true;
+ }
+ });
+ }
+
+ return Object.keys(toolAccess).length > 0 ? toolAccess : undefined;
+ }
+
+ // ============================================================================
+ // GENERATION HELPERS (fromOAC)
+ // ============================================================================
+
+ /**
+ * Generate .cursorrules content from OpenAgent.
+ *
+ * Format:
+ * ---
+ * name: agent-name
+ * model: gpt-4
+ * temperature: 0.7
+ * ---
+ *
+ * System prompt content here...
+ *
+ * [Inlined context if present]
+ */
+ private generateCursorRules(
+ agent: OpenAgent,
+ warnings: string[]
+ ): string {
+ const parts: string[] = [];
+
+ // Generate frontmatter if we have metadata
+ const hasFrontmatter =
+ agent.frontmatter.name ||
+ agent.frontmatter.model ||
+ agent.frontmatter.temperature !== undefined;
+
+ if (hasFrontmatter) {
+ const frontmatterLines: string[] = [];
+
+ if (agent.frontmatter.name) {
+ frontmatterLines.push(`name: ${agent.frontmatter.name}`);
+ }
+
+ if (agent.frontmatter.description) {
+ frontmatterLines.push(`description: ${agent.frontmatter.description}`);
+ }
+
+ if (agent.frontmatter.model) {
+ const cursorModel = this.mapOACModelToCursor(agent.frontmatter.model);
+ frontmatterLines.push(`model: ${cursorModel}`);
+ }
+
+ if (agent.frontmatter.temperature !== undefined) {
+ frontmatterLines.push(`temperature: ${agent.frontmatter.temperature}`);
+ }
+
+ if (frontmatterLines.length > 0) {
+ parts.push("---");
+ parts.push(frontmatterLines.join("\n"));
+ parts.push("---");
+ parts.push(""); // Empty line after frontmatter
+ }
+ }
+
+ // Add system prompt
+ if (agent.systemPrompt) {
+ parts.push(agent.systemPrompt);
+ }
+
+ // Inline contexts if present
+ if (agent.contexts && agent.contexts.length > 0) {
+ parts.push("");
+ parts.push("---");
+ parts.push("");
+ parts.push("# Context Files");
+ parts.push("");
+ parts.push(
+ "The following contexts from OpenAgents Control have been inlined:"
+ );
+ parts.push("");
+
+ agent.contexts.forEach((ctx) => {
+ parts.push(`## ${ctx.path}`);
+ if (ctx.description) {
+ parts.push(`*${ctx.description}*`);
+ }
+ if (ctx.priority) {
+ parts.push(`**Priority**: ${ctx.priority}`);
+ }
+ parts.push("");
+ parts.push(
+ `> **Note**: Original context file: \`${ctx.path}\`. Load this file for full details.`
+ );
+ parts.push("");
+ });
+
+ warnings.push(
+ `💡 ${agent.contexts.length} context file(s) referenced - consider loading them manually in Cursor`
+ );
+ }
+
+ // Add tool access information as comments
+ if (agent.frontmatter.tools) {
+ const enabledTools = Object.entries(agent.frontmatter.tools)
+ .filter(([, enabled]) => enabled)
+ .map(([tool]) => tool);
+
+ if (enabledTools.length > 0) {
+ parts.push("");
+ parts.push("---");
+ parts.push("");
+ parts.push("# Tool Access");
+ parts.push("");
+ parts.push("Enabled tools:");
+ enabledTools.forEach((tool) => {
+ parts.push(`- ${tool}`);
+ });
+ }
+ }
+
+ return parts.join("\n");
+ }
+
+ // ============================================================================
+ // MAPPING HELPERS
+ // ============================================================================
+
+ /**
+ * Map Cursor model ID to OAC model ID.
+ */
+ private mapCursorModelToOAC(model?: string): string | undefined {
+ if (!model) return undefined;
+
+ const modelMap: Record = {
+ "gpt-4": "gpt-4",
+ "gpt-4-turbo": "gpt-4-turbo",
+ "gpt-4o": "gpt-4o",
+ "gpt-3.5-turbo": "gpt-3.5-turbo",
+ "claude-3-opus": "claude-opus-3",
+ "claude-3-sonnet": "claude-sonnet-3",
+ "claude-3-haiku": "claude-haiku-3",
+ };
+
+ return modelMap[model] || model;
+ }
+
+ /**
+ * Map OAC model ID to Cursor model ID.
+ */
+ private mapOACModelToCursor(model: string): string {
+ const modelMap: Record = {
+ "gpt-4": "gpt-4",
+ "gpt-4-turbo": "gpt-4-turbo",
+ "gpt-4o": "gpt-4o",
+ "gpt-3.5-turbo": "gpt-3.5-turbo",
+ "claude-opus-3": "claude-3-opus",
+ "claude-sonnet-3": "claude-3-sonnet",
+ "claude-haiku-3": "claude-3-haiku",
+ "claude-opus-4": "claude-3-opus", // Fallback to v3
+ "claude-sonnet-4": "claude-3-sonnet", // Fallback to v3
+ "claude-haiku-4": "claude-3-haiku", // Fallback to v3
+ };
+
+ return modelMap[model] || "gpt-4"; // Default to gpt-4
+ }
+
+ /**
+ * Merge multiple OpenAgents into a single Cursor-compatible agent.
+ *
+ * This is needed because Cursor only supports a single .cursorrules file.
+ *
+ * @param agents - Array of OpenAgent objects to merge
+ * @returns Single merged OpenAgent
+ */
+ mergeAgents(agents: OpenAgent[]): OpenAgent {
+ if (agents.length === 0) {
+ throw new Error("Cannot merge empty agent array");
+ }
+
+ if (agents.length === 1) {
+ const agent = agents[0];
+ if (!agent) {
+ throw new Error("Agent at index 0 is undefined");
+ }
+ return {
+ frontmatter: { ...agent.frontmatter },
+ metadata: { ...agent.metadata },
+ systemPrompt: agent.systemPrompt,
+ contexts: agent.contexts ? [...agent.contexts] : [],
+ sections: agent.sections ? { ...agent.sections } : undefined,
+ };
+ }
+
+ // Use first agent as base
+ const merged: OpenAgent = {
+ frontmatter: {
+ name: agents.map((a) => a.frontmatter.name).join("-"),
+ description: `Merged agent: ${agents.map((a) => a.frontmatter.name).join(", ")}`,
+ mode: "primary",
+ },
+ metadata: {
+ name: "merged-agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "",
+ contexts: [],
+ };
+
+ // Merge system prompts
+ const prompts: string[] = [];
+ agents.forEach((agent, index) => {
+ prompts.push(`# Agent ${index + 1}: ${agent.frontmatter.name}`);
+ prompts.push(agent.frontmatter.description || "");
+ prompts.push("");
+ prompts.push(agent.systemPrompt);
+ prompts.push("");
+ prompts.push("---");
+ prompts.push("");
+ });
+
+ merged.systemPrompt = prompts.join("\n");
+
+ // Merge tools (union of all enabled tools)
+ const allTools: Record = {};
+ agents.forEach((agent) => {
+ if (agent.frontmatter.tools) {
+ Object.entries(agent.frontmatter.tools).forEach(([tool, enabled]) => {
+ if (enabled) {
+ allTools[tool] = true;
+ }
+ });
+ }
+ });
+
+ if (Object.keys(allTools).length > 0) {
+ merged.frontmatter.tools = allTools;
+ }
+
+ // Merge contexts
+ const allContexts = agents.flatMap((agent) => agent.contexts || []);
+ if (allContexts.length > 0) {
+ merged.contexts = allContexts;
+ }
+
+ // Use highest temperature if any
+ const temps = agents
+ .map((a) => a.frontmatter.temperature)
+ .filter((t): t is number => t !== undefined);
+ if (temps.length > 0) {
+ merged.frontmatter.temperature = Math.max(...temps);
+ }
+
+ // Use first available model
+ const model = agents.find((a) => a.frontmatter.model)?.frontmatter.model;
+ if (model) {
+ merged.frontmatter.model = model;
+ }
+
+ return merged;
+ }
+}
diff --git a/packages/compatibility-layer/src/adapters/WindsurfAdapter.ts b/packages/compatibility-layer/src/adapters/WindsurfAdapter.ts
new file mode 100644
index 0000000..7d4e105
--- /dev/null
+++ b/packages/compatibility-layer/src/adapters/WindsurfAdapter.ts
@@ -0,0 +1,523 @@
+import { BaseAdapter } from "./BaseAdapter.js";
+import type {
+ OpenAgent,
+ ConversionResult,
+ ToolCapabilities,
+ ToolConfig,
+ AgentFrontmatter,
+} from "../types.js";
+
+/**
+ * Windsurf adapter for converting between OpenAgents Control and Windsurf formats.
+ *
+ * Windsurf uses:
+ * - `.windsurf/config.json` for main configuration
+ * - `.windsurf/agents/*.json` for individual agents
+ * - `.windsurf/context/` for context files
+ *
+ * **Key Features**:
+ * - ✅ Multiple agents support
+ * - ⚠️ Partial permissions (binary on/off, not granular)
+ * - ⚠️ Limited temperature support (maps to creativity)
+ * - ⚠️ Partial skills support
+ * - ❌ No hooks support
+ *
+ * @see https://windsurf.ai/docs
+ */
+export class WindsurfAdapter extends BaseAdapter {
+ readonly name = "windsurf";
+ readonly displayName = "Windsurf";
+
+ constructor() {
+ super();
+ }
+
+ /**
+ * Get the config path for Windsurf.
+ */
+ getConfigPath(): string {
+ return ".windsurf/";
+ }
+
+ // ============================================================================
+ // CONVERSION METHODS
+ // ============================================================================
+
+ /**
+ * Convert Windsurf format TO OpenAgents Control format.
+ *
+ * Expects JSON content from either:
+ * - `.windsurf/config.json` (main config)
+ * - `.windsurf/agents/{name}.json` (agent config)
+ *
+ * @param source - Windsurf JSON config content
+ * @returns OpenAgent object
+ */
+ toOAC(source: string): Promise {
+ const config = this.safeParseJSON(source, "windsurf-config.json");
+ if (!config || typeof config !== "object") {
+ return Promise.reject(new Error("Invalid Windsurf config format"));
+ }
+
+ const windsurfConfig = config as Record;
+
+ // Build agent frontmatter
+ const frontmatter: AgentFrontmatter = {
+ name: String(windsurfConfig.name || "windsurf-agent"),
+ description: String(
+ windsurfConfig.description || "Agent imported from Windsurf"
+ ),
+ mode: windsurfConfig.type === "subagent" ? "subagent" : "primary",
+ model: this.mapWindsurfModelToOAC(windsurfConfig.model as string),
+ tools: this.parseWindsurfTools(windsurfConfig.tools),
+ };
+
+ // Map creativity to temperature (approximate)
+ if (windsurfConfig.creativity !== undefined) {
+ frontmatter.temperature = this.mapCreativityToTemperature(
+ windsurfConfig.creativity as string | number
+ );
+ }
+
+ // Validate category is one of the valid AgentCategory values
+ const validCategories = ["core", "development", "content", "data", "product", "learning", "meta", "specialist"] as const;
+ const categoryStr = String(windsurfConfig.category || "core");
+ const category = validCategories.includes(categoryStr as never) ? (categoryStr as typeof validCategories[number]) : "core";
+
+ return Promise.resolve({
+ frontmatter,
+ metadata: {
+ name: frontmatter.name,
+ category,
+ type: frontmatter.mode === "subagent" ? "subagent" : "agent",
+ },
+ systemPrompt: String(windsurfConfig.systemPrompt || windsurfConfig.prompt || ""),
+ contexts: this.parseWindsurfContexts(windsurfConfig.contexts),
+ });
+ }
+
+ /**
+ * Convert FROM OpenAgents Control format to Windsurf format.
+ *
+ * Generates:
+ * - `.windsurf/agents/{name}.json` for each agent
+ * - Context file references
+ *
+ * @param agent - OpenAgent to convert
+ * @returns ConversionResult with generated files and warnings
+ */
+ fromOAC(agent: OpenAgent): Promise {
+ const warnings: string[] = [];
+ const configs: ToolConfig[] = [];
+
+ // Validate conversion
+ const validationWarnings = this.validateConversion(agent);
+ warnings.push(...validationWarnings);
+
+ // Check for unsupported features
+ if (agent.frontmatter.hooks && agent.frontmatter.hooks.length > 0) {
+ warnings.push(
+ this.unsupportedFeatureWarning(
+ "hooks",
+ `${agent.frontmatter.hooks.length} hooks`
+ )
+ );
+ warnings.push("❌ Windsurf does not support hooks - behavioral rules will be lost");
+ }
+
+ if (agent.frontmatter.maxSteps !== undefined) {
+ warnings.push(
+ this.unsupportedFeatureWarning("maxSteps", agent.frontmatter.maxSteps)
+ );
+ }
+
+ // Check for granular permissions
+ if (agent.frontmatter.permission) {
+ const hasGranular = Object.values(agent.frontmatter.permission).some(
+ (perm) => typeof perm === "object" && !Array.isArray(perm)
+ );
+
+ if (hasGranular) {
+ warnings.push(
+ this.degradedFeatureWarning(
+ "granular permissions",
+ "allow/deny/ask per path",
+ "binary on/off per tool"
+ )
+ );
+ }
+ }
+
+ // Check for skills
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ warnings.push(
+ this.degradedFeatureWarning(
+ "skills",
+ "full Skills system",
+ "basic context references"
+ )
+ );
+ }
+
+ // Generate Windsurf agent config
+ const windsurfConfig = this.generateWindsurfConfig(agent, warnings);
+
+ // Determine output path based on agent mode
+ const fileName =
+ agent.frontmatter.mode === "subagent"
+ ? `.windsurf/agents/${agent.frontmatter.name}.json`
+ : ".windsurf/config.json";
+
+ configs.push({
+ fileName,
+ content: JSON.stringify(windsurfConfig, null, 2),
+ encoding: "utf-8",
+ });
+
+ // Generate context file references if present
+ if (agent.contexts && agent.contexts.length > 0) {
+ warnings.push(
+ `💡 ${agent.contexts.length} context file(s) referenced - ensure they exist in .windsurf/context/`
+ );
+ }
+
+ return Promise.resolve(this.createSuccessResult(configs, warnings));
+ }
+
+ /**
+ * Get Windsurf capabilities.
+ */
+ getCapabilities(): ToolCapabilities {
+ return {
+ name: this.name,
+ displayName: this.displayName,
+ supportsMultipleAgents: true, // ✅ Supports .windsurf/agents/
+ supportsSkills: true, // ⚠️ Partial - basic context support
+ supportsHooks: false, // ❌ Not supported
+ supportsGranularPermissions: false, // ⚠️ Binary only
+ supportsContexts: true, // ✅ .windsurf/context/
+ supportsCustomModels: true, // ✅ Model selection
+ supportsTemperature: true, // ⚠️ Via creativity setting
+ supportsMaxSteps: false, // ❌ Not supported
+ configFormat: "json",
+ outputStructure: "directory",
+ notes: [
+ "Multiple agents supported via .windsurf/agents/",
+ "Permissions are binary (on/off) - granular rules degraded",
+ "Temperature maps to/from creativity setting (low/medium/high)",
+ "No hooks support - behavioral rules will be lost",
+ "Skills map to basic context file references",
+ "Priority levels: only high/low (medium/critical → degraded)",
+ ],
+ };
+ }
+
+ /**
+ * Validate if an agent can be converted with full fidelity.
+ */
+ validateConversion(agent: OpenAgent): string[] {
+ const warnings: string[] = [];
+
+ if (!agent.frontmatter.name) {
+ warnings.push("⚠️ Agent name is required for Windsurf");
+ }
+
+ if (!agent.frontmatter.description) {
+ warnings.push("⚠️ Agent description recommended for Windsurf");
+ }
+
+ return warnings;
+ }
+
+ // ============================================================================
+ // PARSING HELPERS (toOAC)
+ // ============================================================================
+
+ /**
+ * Parse Windsurf tools to OAC ToolAccess.
+ */
+ private parseWindsurfTools(
+ tools: unknown
+ ): Record | undefined {
+ if (!tools) return undefined;
+
+ const toolAccess: Record = {};
+
+ if (typeof tools === "object" && !Array.isArray(tools)) {
+ // Parse object: { read: true, write: false }
+ const toolsObj = tools as Record;
+ for (const [tool, enabled] of Object.entries(toolsObj)) {
+ toolAccess[tool.toLowerCase()] = Boolean(enabled);
+ }
+ } else if (Array.isArray(tools)) {
+ // Parse array: ["read", "write", "bash"]
+ tools.forEach((tool) => {
+ const toolName = String(tool).toLowerCase();
+ if (toolName) {
+ toolAccess[toolName] = true;
+ }
+ });
+ }
+
+ return Object.keys(toolAccess).length > 0 ? toolAccess : undefined;
+ }
+
+ /**
+ * Parse Windsurf contexts to OAC format.
+ */
+ private parseWindsurfContexts(
+ contexts: unknown
+ ): Array<{ path: string; priority?: "critical" | "high" | "medium" | "low"; description?: string }> {
+ if (!contexts || !Array.isArray(contexts)) return [];
+
+ return contexts.map((ctx) => {
+ if (typeof ctx === "string") {
+ return { path: ctx };
+ }
+
+ if (typeof ctx === "object" && ctx !== null) {
+ const ctxObj = ctx as Record;
+ const priority = ctxObj.priority ? String(ctxObj.priority).toLowerCase() : undefined;
+
+ return {
+ path: String(ctxObj.path || ""),
+ priority: this.normalizeOACPriority(priority),
+ description: ctxObj.description ? String(ctxObj.description) : undefined,
+ };
+ }
+
+ return { path: "" };
+ }).filter((ctx) => ctx.path);
+ }
+
+ /**
+ * Normalize priority to OAC valid values.
+ */
+ private normalizeOACPriority(
+ priority?: string
+ ): "critical" | "high" | "medium" | "low" | undefined {
+ if (!priority) return undefined;
+
+ const normalized = priority.toLowerCase();
+ if (
+ normalized === "critical" ||
+ normalized === "high" ||
+ normalized === "medium" ||
+ normalized === "low"
+ ) {
+ return normalized;
+ }
+
+ // Default to medium if invalid
+ return "medium";
+ }
+
+ /**
+ * Map Windsurf creativity setting to OAC temperature.
+ */
+ private mapCreativityToTemperature(creativity: string | number): number {
+ if (typeof creativity === "number") {
+ return creativity; // Already numeric
+ }
+
+ const creativityMap: Record = {
+ low: 0.3,
+ medium: 0.7,
+ high: 1.0,
+ balanced: 0.5,
+ };
+
+ return creativityMap[creativity.toLowerCase()] || 0.7;
+ }
+
+ // ============================================================================
+ // GENERATION HELPERS (fromOAC)
+ // ============================================================================
+
+ /**
+ * Generate Windsurf config from OpenAgent.
+ */
+ private generateWindsurfConfig(
+ agent: OpenAgent,
+ warnings: string[]
+ ): Record {
+ const config: Record = {
+ name: agent.frontmatter.name,
+ description: agent.frontmatter.description,
+ type: agent.frontmatter.mode === "subagent" ? "subagent" : "primary",
+ systemPrompt: agent.systemPrompt,
+ };
+
+ // Model mapping
+ if (agent.frontmatter.model) {
+ config.model = this.mapOACModelToWindsurf(agent.frontmatter.model);
+ }
+
+ // Tools mapping
+ if (agent.frontmatter.tools) {
+ config.tools = this.mapOACToolsToWindsurf(agent.frontmatter.tools);
+ }
+
+ // Temperature → creativity
+ if (agent.frontmatter.temperature !== undefined) {
+ config.creativity = this.mapTemperatureToCreativity(
+ agent.frontmatter.temperature
+ );
+ }
+
+ // Category
+ if (agent.metadata?.category) {
+ config.category = agent.metadata.category;
+ }
+
+ // Contexts
+ if (agent.contexts && agent.contexts.length > 0) {
+ config.contexts = agent.contexts.map((ctx) => ({
+ path: ctx.path,
+ priority: this.mapOACPriorityToWindsurf(ctx.priority || "medium"),
+ description: ctx.description,
+ }));
+ }
+
+ // Skills → context references
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ const skillContexts = agent.frontmatter.skills.map((skill) => {
+ const skillName = typeof skill === "string" ? skill : skill.name;
+ return {
+ path: `.windsurf/context/${skillName}.md`,
+ priority: "medium",
+ description: `Skill: ${skillName}`,
+ };
+ });
+
+ if (!config.contexts) {
+ config.contexts = skillContexts;
+ } else {
+ (config.contexts as Array).push(...skillContexts);
+ }
+ }
+
+ // Permissions (simplified)
+ if (agent.frontmatter.permission) {
+ config.permissions = this.mapOACPermissionsToWindsurf(
+ agent.frontmatter.permission,
+ warnings
+ );
+ }
+
+ return config;
+ }
+
+ // ============================================================================
+ // MAPPING HELPERS
+ // ============================================================================
+
+ /**
+ * Map Windsurf model ID to OAC model ID.
+ */
+ private mapWindsurfModelToOAC(model?: string): string | undefined {
+ if (!model) return undefined;
+
+ const modelMap: Record = {
+ "claude-4-sonnet": "claude-sonnet-4",
+ "claude-4-opus": "claude-opus-4",
+ "claude-4-haiku": "claude-haiku-4",
+ "gpt-4": "gpt-4",
+ "gpt-4-turbo": "gpt-4-turbo",
+ "gpt-4o": "gpt-4o",
+ };
+
+ return modelMap[model] || model;
+ }
+
+ /**
+ * Map OAC model ID to Windsurf model ID.
+ */
+ private mapOACModelToWindsurf(model: string): string {
+ const modelMap: Record = {
+ "claude-sonnet-4": "claude-4-sonnet",
+ "claude-opus-4": "claude-4-opus",
+ "claude-haiku-4": "claude-4-haiku",
+ "gpt-4": "gpt-4",
+ "gpt-4-turbo": "gpt-4-turbo",
+ "gpt-4o": "gpt-4o",
+ };
+
+ return modelMap[model] || "claude-4-sonnet"; // Default
+ }
+
+ /**
+ * Map OAC ToolAccess to Windsurf tools object.
+ */
+ private mapOACToolsToWindsurf(
+ tools: Record
+ ): Record {
+ // Windsurf uses binary on/off for tools
+ const windsurfTools: Record = {};
+
+ for (const [tool, enabled] of Object.entries(tools)) {
+ windsurfTools[tool] = Boolean(enabled);
+ }
+
+ return windsurfTools;
+ }
+
+ /**
+ * Map OAC temperature to Windsurf creativity.
+ */
+ private mapTemperatureToCreativity(temperature: number): string {
+ if (temperature <= 0.4) return "low";
+ if (temperature <= 0.8) return "medium";
+ return "high";
+ }
+
+ /**
+ * Map OAC priority to Windsurf priority.
+ */
+ private mapOACPriorityToWindsurf(priority: string): string {
+ const priorityMap: Record = {
+ critical: "high",
+ high: "high",
+ medium: "low",
+ low: "low",
+ };
+
+ return priorityMap[priority.toLowerCase()] || "low";
+ }
+
+ /**
+ * Map OAC granular permissions to Windsurf binary permissions.
+ */
+ private mapOACPermissionsToWindsurf(
+ permissions: Record,
+ warnings: string[]
+ ): Record {
+ const windsurfPerms: Record = {};
+
+ // Analyze each permission and convert to binary
+ for (const [tool, perm] of Object.entries(permissions)) {
+ if (typeof perm === "boolean") {
+ windsurfPerms[tool] = perm;
+ } else if (perm === "allow") {
+ windsurfPerms[tool] = true;
+ } else if (perm === "deny") {
+ windsurfPerms[tool] = false;
+ } else if (perm === "ask") {
+ // "ask" → default to false (cautious approach)
+ windsurfPerms[tool] = false;
+ warnings.push(
+ `⚠️ Permission "ask" for ${tool} degraded to false (deny). Windsurf only supports binary on/off.`
+ );
+ } else if (typeof perm === "object" && perm !== null) {
+ // Granular permissions - default to true if any allow exists
+ const permObj = perm as Record;
+ const hasAllow = permObj.allow !== undefined;
+ windsurfPerms[tool] = hasAllow;
+ } else {
+ windsurfPerms[tool] = false;
+ }
+ }
+
+ return windsurfPerms;
+ }
+}
diff --git a/packages/compatibility-layer/src/cli/__tests__/convert.test.ts b/packages/compatibility-layer/src/cli/__tests__/convert.test.ts
new file mode 100644
index 0000000..41d107a
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/convert.test.ts
@@ -0,0 +1,514 @@
+import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
+import { readFile, writeFile, mkdir, rm, access } from "fs/promises";
+import { join, dirname } from "path";
+import { fileURLToPath } from "url";
+import { executeConvert } from "../commands/convert.js";
+import type { GlobalOptions } from "../types.js";
+
+/**
+ * Integration tests for the convert CLI command
+ *
+ * Test strategy:
+ * 1. Successful conversions - OAC to/from Cursor, Claude, Windsurf
+ * 2. Error handling - Non-existent files, invalid formats
+ * 3. Output options - stdout, file output, --force flag
+ * 4. Format detection - Auto-detect input formats
+ * 5. Roundtrip - Data integrity checks
+ */
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const FIXTURES_DIR = join(__dirname, "fixtures");
+const TEMP_DIR = join(__dirname, "temp-convert");
+
+// Default global options for tests
+const defaultGlobalOptions: GlobalOptions = {
+ verbose: false,
+ quiet: true,
+ outputFormat: "text",
+};
+
+describe("convert command", () => {
+ // Setup temp directory before each test
+ beforeEach(async () => {
+ await mkdir(TEMP_DIR, { recursive: true });
+ });
+
+ // Cleanup temp directory after each test
+ afterEach(async () => {
+ try {
+ await rm(TEMP_DIR, { recursive: true, force: true });
+ } catch {
+ // Ignore cleanup errors
+ }
+ });
+
+ // ============================================================================
+ // SUCCESSFUL CONVERSIONS
+ // ============================================================================
+
+ describe("successful conversions", () => {
+ it("converts OAC to Cursor format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.configs).toBeDefined();
+ expect(result.data?.configs.length).toBeGreaterThan(0);
+ expect(result.data?.configs[0].fileName).toBe(".cursorrules");
+ });
+
+ it("converts OAC to Claude format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "claude" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.configs).toBeDefined();
+ expect(result.data?.configs[0].fileName).toContain(".claude/");
+ });
+
+ it("converts Cursor to OAC format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-cursorrules");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac", from: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.configs).toBeDefined();
+ expect(result.data?.configs[0].content).toContain("---");
+ });
+
+ it("converts Claude to OAC format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-claude-config.json");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac", from: "claude" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.configs).toBeDefined();
+ expect(result.data?.configs[0].content).toContain("---");
+ expect(result.data?.configs[0].content).toContain("sample-claude-agent");
+ });
+
+ it("auto-detects OAC input format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act - no --from specified
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.configs[0].fileName).toBe(".cursorrules");
+ });
+
+ it("auto-detects Cursor input format from .cursorrules filename", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-cursorrules");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("preserves agent name through conversion", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.configs[0].content).toContain("sample-oac-agent");
+ });
+
+ it("preserves system prompt content through conversion", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.configs[0].content).toContain("helpful assistant");
+ });
+ });
+
+ // ============================================================================
+ // ERROR HANDLING
+ // ============================================================================
+
+ describe("error handling", () => {
+ it("returns error for non-existent file", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "does-not-exist.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toBeDefined();
+ });
+
+ it("returns error for same source/target format", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac", from: "oac" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("same");
+ });
+
+ it("handles conversion errors gracefully", async () => {
+ // Arrange - create a file that will cause parsing issues
+ const brokenFile = join(TEMP_DIR, "broken.md");
+ await writeFile(brokenFile, "---\ninvalid: yaml: content:\n---\nContent");
+
+ // Act
+ const result = await executeConvert(
+ brokenFile,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert - should either succeed with degraded content or fail gracefully
+ // The adapter should handle malformed YAML
+ expect(typeof result.success).toBe("boolean");
+ });
+ });
+
+ // ============================================================================
+ // OUTPUT OPTIONS
+ // ============================================================================
+
+ describe("output options", () => {
+ it("writes to specified output path", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const outputPath = join(TEMP_DIR, "output.cursorrules");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor", output: outputPath },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ // Verify file was written
+ const content = await readFile(outputPath, "utf-8");
+ expect(content).toContain("sample-oac-agent");
+ });
+
+ it("outputs to stdout when no output specified", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Console.log should have been called with the content
+ expect(consoleSpy).toHaveBeenCalled();
+
+ // Cleanup
+ consoleSpy.mockRestore();
+ });
+
+ it("respects --force flag to overwrite existing file", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const outputPath = join(TEMP_DIR, "existing.cursorrules");
+
+ // Create existing file
+ await writeFile(outputPath, "existing content");
+
+ // Act - with force flag
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor", output: outputPath, force: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ const content = await readFile(outputPath, "utf-8");
+ expect(content).not.toBe("existing content");
+ expect(content).toContain("sample-oac-agent");
+ });
+
+ it("returns error when output file exists and --force not set", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const outputPath = join(TEMP_DIR, "existing2.cursorrules");
+
+ // Create existing file
+ await writeFile(outputPath, "existing content");
+
+ // Act - without force flag
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor", output: outputPath, force: false },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("exists");
+ });
+
+ it("creates output directory if it does not exist", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const outputPath = join(TEMP_DIR, "nested", "deep", "output.cursorrules");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor", output: outputPath },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ const content = await readFile(outputPath, "utf-8");
+ expect(content).toContain("sample-oac-agent");
+ });
+ });
+
+ // ============================================================================
+ // FORMAT DETECTION
+ // ============================================================================
+
+ describe("format detection", () => {
+ it("detects OAC format from frontmatter", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("detects Claude format from JSON structure", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-claude-config.json");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac" },
+ { ...defaultGlobalOptions, verbose: true }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("uses specified --from format over auto-detection", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-cursorrules");
+
+ // Act - explicitly specify cursor format
+ const result = await executeConvert(
+ inputPath,
+ { format: "oac", from: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // ROUNDTRIP CONVERSION
+ // ============================================================================
+
+ describe("roundtrip conversion", () => {
+ it("roundtrip OAC -> Claude -> OAC preserves core properties", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const claudeOutputPath = join(TEMP_DIR, "claude-config.json");
+ const oacOutputPath = join(TEMP_DIR, "roundtrip-agent.md");
+
+ // Act - Step 1: Convert OAC to Claude
+ const toClaude = await executeConvert(
+ inputPath,
+ { format: "claude", output: claudeOutputPath },
+ defaultGlobalOptions
+ );
+ expect(toClaude.success).toBe(true);
+
+ // Act - Step 2: Convert Claude back to OAC
+ const toOAC = await executeConvert(
+ claudeOutputPath,
+ { format: "oac", from: "claude", output: oacOutputPath },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(toOAC.success).toBe(true);
+
+ const originalContent = await readFile(inputPath, "utf-8");
+ const roundtripContent = await readFile(oacOutputPath, "utf-8");
+
+ // Core properties should be preserved
+ expect(roundtripContent).toContain("sample-oac-agent");
+ expect(roundtripContent).toContain("helpful assistant");
+ });
+
+ it("roundtrip OAC -> Cursor -> OAC preserves core properties", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const cursorOutputPath = join(TEMP_DIR, ".cursorrules");
+ const oacOutputPath = join(TEMP_DIR, "roundtrip-cursor.md");
+
+ // Act - Step 1: Convert OAC to Cursor
+ const toCursor = await executeConvert(
+ inputPath,
+ { format: "cursor", output: cursorOutputPath },
+ defaultGlobalOptions
+ );
+ expect(toCursor.success).toBe(true);
+
+ // Act - Step 2: Convert Cursor back to OAC
+ const toOAC = await executeConvert(
+ cursorOutputPath,
+ { format: "oac", from: "cursor", output: oacOutputPath },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(toOAC.success).toBe(true);
+
+ const roundtripContent = await readFile(oacOutputPath, "utf-8");
+ expect(roundtripContent).toContain("sample-oac-agent");
+ });
+ });
+
+ // ============================================================================
+ // WARNINGS HANDLING
+ // ============================================================================
+
+ describe("warnings handling", () => {
+ it("includes warnings in result when features are lost", async () => {
+ // Arrange - OAC with advanced features that Cursor doesn't support
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Warnings may or may not be present depending on feature parity
+ expect(Array.isArray(result.warnings) || result.warnings === undefined).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // VERBOSE OUTPUT
+ // ============================================================================
+
+ describe("verbose output", () => {
+ it("respects verbose flag", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeConvert(
+ inputPath,
+ { format: "cursor" },
+ { ...defaultGlobalOptions, verbose: true }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+ });
+});
diff --git a/packages/compatibility-layer/src/cli/__tests__/fixtures/invalid-format.txt b/packages/compatibility-layer/src/cli/__tests__/fixtures/invalid-format.txt
new file mode 100644
index 0000000..a88cb83
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/fixtures/invalid-format.txt
@@ -0,0 +1,6 @@
+This is not valid YAML frontmatter
+---broken
+name: test
+---
+
+Some content
\ No newline at end of file
diff --git a/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-claude-config.json b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-claude-config.json
new file mode 100644
index 0000000..2978af8
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-claude-config.json
@@ -0,0 +1,9 @@
+{
+ "name": "sample-claude-agent",
+ "description": "A sample Claude Code agent for testing",
+ "model": "claude-sonnet-4-20250514",
+ "tools": ["Read", "Write", "Bash", "Edit"],
+ "systemPrompt": "You are a Claude Code assistant.\n\n## Guidelines\n\n- Help users write better code\n- Provide clear explanations\n- Follow best practices\n\n## Capabilities\n\n- Code generation\n- Code review\n- Documentation",
+ "skills": ["code-review", "documentation"],
+ "permissionMode": "default"
+}
diff --git a/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-cursorrules b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-cursorrules
new file mode 100644
index 0000000..5e904ff
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-cursorrules
@@ -0,0 +1,20 @@
+---
+name: sample-cursor-agent
+model: gpt-4
+temperature: 0.5
+tools: read, write, bash
+---
+
+You are a Cursor IDE assistant.
+
+# Guidelines
+
+- Always write clean, readable code
+- Follow project conventions
+- Add helpful comments
+
+# Code Style
+
+- Use TypeScript for type safety
+- Prefer functional programming patterns
+- Write comprehensive tests
diff --git a/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-oac-agent.md b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-oac-agent.md
new file mode 100644
index 0000000..734af0c
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-oac-agent.md
@@ -0,0 +1,28 @@
+---
+name: "sample-oac-agent"
+description: "A sample OAC agent for testing"
+mode: primary
+model: claude-sonnet-4
+temperature: 0.7
+tools:
+ read: true
+ write: true
+ bash: true
+ edit: true
+---
+
+# Sample OAC Agent
+
+You are a helpful assistant that follows best practices.
+
+## Guidelines
+
+1. Always be helpful and professional
+2. Provide clear explanations
+3. Follow coding standards
+
+## Capabilities
+
+- Code review and analysis
+- Documentation generation
+- Bug fixing assistance
diff --git a/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-subagent.md b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-subagent.md
new file mode 100644
index 0000000..d067023
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/fixtures/sample-subagent.md
@@ -0,0 +1,25 @@
+---
+name: "sample-subagent"
+description: "A sample subagent for testing"
+mode: subagent
+model: claude-sonnet-4
+tools:
+ read: true
+ edit: true
+---
+
+# Sample Subagent
+
+You are a specialized code reviewer.
+
+## Your Role
+
+- Review code for quality
+- Suggest improvements
+- Identify bugs
+
+## Guidelines
+
+- Be thorough but constructive
+- Focus on high-impact issues
+- Provide actionable feedback
diff --git a/packages/compatibility-layer/src/cli/__tests__/info.test.ts b/packages/compatibility-layer/src/cli/__tests__/info.test.ts
new file mode 100644
index 0000000..f0e196d
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/info.test.ts
@@ -0,0 +1,534 @@
+import { describe, it, expect } from "vitest";
+import { executeInfo } from "../commands/info.js";
+import type { GlobalOptions } from "../types.js";
+
+/**
+ * Integration tests for the info CLI command
+ *
+ * Test strategy:
+ * 1. Feature matrix - Display all platforms info
+ * 2. Single platform info - Detailed info for one platform
+ * 3. Platform comparison - Compare two platforms
+ * 4. Error handling - Invalid platform names
+ * 5. Output format - JSON and text output
+ */
+
+// Default global options for tests
+const defaultGlobalOptions: GlobalOptions = {
+ verbose: false,
+ quiet: true,
+ outputFormat: "text",
+};
+
+describe("info command", () => {
+ // ============================================================================
+ // FEATURE MATRIX (NO PLATFORM SPECIFIED)
+ // ============================================================================
+
+ describe("feature matrix (no platform specified)", () => {
+ it("returns info for all platforms when no platform specified", async () => {
+ // Act
+ const result = await executeInfo(
+ undefined,
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platforms).toBeDefined();
+ expect(Array.isArray(result.data?.platforms)).toBe(true);
+ });
+
+ it("includes all four platforms in matrix", async () => {
+ // Act
+ const result = await executeInfo(
+ undefined,
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ const platformNames = result.data?.platforms?.map(p => p.platform) || [];
+ expect(platformNames).toContain("oac");
+ expect(platformNames).toContain("cursor");
+ expect(platformNames).toContain("claude");
+ expect(platformNames).toContain("windsurf");
+ });
+
+ it("each platform has required fields", async () => {
+ // Act
+ const result = await executeInfo(
+ undefined,
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ for (const platform of result.data?.platforms || []) {
+ expect(platform.platform).toBeDefined();
+ expect(platform.displayName).toBeDefined();
+ expect(platform.capabilities).toBeDefined();
+ expect(platform.features).toBeDefined();
+ }
+ });
+ });
+
+ // ============================================================================
+ // SINGLE PLATFORM INFO
+ // ============================================================================
+
+ describe("single platform info", () => {
+ it("returns detailed info for OAC platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform).toBeDefined();
+ expect(result.data?.platform?.platform).toBe("oac");
+ expect(result.data?.platform?.displayName).toBe("OpenAgents Control");
+ });
+
+ it("returns detailed info for Cursor platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "cursor",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform?.platform).toBe("cursor");
+ expect(result.data?.platform?.displayName).toBe("Cursor IDE");
+ });
+
+ it("returns detailed info for Claude platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "claude",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform?.platform).toBe("claude");
+ expect(result.data?.platform?.displayName).toBe("Claude Code");
+ });
+
+ it("returns detailed info for Windsurf platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "windsurf",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform?.platform).toBe("windsurf");
+ expect(result.data?.platform?.displayName).toBe("Windsurf");
+ });
+
+ it("includes capabilities in platform info", async () => {
+ // Act
+ const result = await executeInfo(
+ "cursor",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ const caps = result.data?.platform?.capabilities;
+ expect(caps).toBeDefined();
+ expect(typeof caps?.supportsMultipleAgents).toBe("boolean");
+ expect(typeof caps?.supportsSkills).toBe("boolean");
+ expect(typeof caps?.supportsHooks).toBe("boolean");
+ expect(typeof caps?.supportsGranularPermissions).toBe("boolean");
+ expect(typeof caps?.supportsContexts).toBe("boolean");
+ expect(typeof caps?.supportsCustomModels).toBe("boolean");
+ expect(typeof caps?.supportsTemperature).toBe("boolean");
+ expect(typeof caps?.supportsMaxSteps).toBe("boolean");
+ expect(caps?.configFormat).toBeDefined();
+ expect(caps?.outputStructure).toBeDefined();
+ });
+
+ it("includes features list in platform info", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.platform?.features)).toBe(true);
+
+ for (const feature of result.data?.platform?.features || []) {
+ expect(feature.name).toBeDefined();
+ expect(feature.category).toBeDefined();
+ expect(feature.description).toBeDefined();
+ expect(["full", "partial", "none"]).toContain(feature.support);
+ }
+ });
+ });
+
+ // ============================================================================
+ // PLATFORM COMPARISON
+ // ============================================================================
+
+ describe("platform comparison", () => {
+ it("compares OAC with Cursor", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.comparison).toBeDefined();
+ expect(result.data?.comparison?.platformA).toBe("oac");
+ expect(result.data?.comparison?.platformB).toBe("cursor");
+ });
+
+ it("compares Claude with Windsurf", async () => {
+ // Act
+ const result = await executeInfo(
+ "claude",
+ { compare: "windsurf" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.comparison).toBeDefined();
+ expect(result.data?.comparison?.platformA).toBe("claude");
+ expect(result.data?.comparison?.platformB).toBe("windsurf");
+ });
+
+ it("comparison includes identical features list", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.comparison?.identical)).toBe(true);
+ });
+
+ it("comparison includes betterInA features list", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.comparison?.betterInA)).toBe(true);
+ });
+
+ it("comparison includes betterInB features list", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.comparison?.betterInB)).toBe(true);
+ });
+
+ it("also returns platform info when comparing", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "claude" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform).toBeDefined();
+ expect(result.data?.comparison).toBeDefined();
+ });
+ });
+
+ // ============================================================================
+ // ERROR HANDLING
+ // ============================================================================
+
+ describe("error handling", () => {
+ it("returns error for invalid platform name", async () => {
+ // Act
+ const result = await executeInfo(
+ "invalid-platform",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("Unknown platform");
+ });
+
+ it("returns error for invalid comparison platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "invalid-platform" as any },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("Unknown platform");
+ });
+
+ it("handles case-sensitive platform names", async () => {
+ // Act
+ const result = await executeInfo(
+ "OAC", // uppercase should fail
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ });
+ });
+
+ // ============================================================================
+ // JSON OUTPUT FORMAT
+ // ============================================================================
+
+ describe("JSON output format", () => {
+ it("works with JSON output format for matrix", async () => {
+ // Act
+ const result = await executeInfo(
+ undefined,
+ {},
+ { ...defaultGlobalOptions, outputFormat: "json" }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platforms).toBeDefined();
+ });
+
+ it("works with JSON output format for single platform", async () => {
+ // Act
+ const result = await executeInfo(
+ "cursor",
+ {},
+ { ...defaultGlobalOptions, outputFormat: "json" }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform).toBeDefined();
+ });
+
+ it("works with JSON output format for comparison", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { compare: "claude" },
+ { ...defaultGlobalOptions, outputFormat: "json" }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.comparison).toBeDefined();
+ });
+ });
+
+ // ============================================================================
+ // DETAILED OPTION
+ // ============================================================================
+
+ describe("detailed option", () => {
+ it("respects detailed flag", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ { detailed: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // CAPABILITY VALUES
+ // ============================================================================
+
+ describe("capability values", () => {
+ it("OAC supports all features", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ const caps = result.data?.platform?.capabilities;
+ expect(caps?.supportsMultipleAgents).toBe(true);
+ expect(caps?.supportsSkills).toBe(true);
+ expect(caps?.supportsHooks).toBe(true);
+ expect(caps?.supportsGranularPermissions).toBe(true);
+ expect(caps?.supportsContexts).toBe(true);
+ });
+
+ it("Cursor has limited feature support", async () => {
+ // Act
+ const result = await executeInfo(
+ "cursor",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ const caps = result.data?.platform?.capabilities;
+ expect(caps?.supportsMultipleAgents).toBe(false);
+ expect(caps?.supportsSkills).toBe(false);
+ expect(caps?.supportsHooks).toBe(false);
+ });
+
+ it("Claude supports skills and hooks", async () => {
+ // Act
+ const result = await executeInfo(
+ "claude",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ const caps = result.data?.platform?.capabilities;
+ expect(caps?.supportsMultipleAgents).toBe(true);
+ expect(caps?.supportsSkills).toBe(true);
+ expect(caps?.supportsHooks).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // FEATURE CATEGORIES
+ // ============================================================================
+
+ describe("feature categories", () => {
+ it("features are organized by category", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ const categories = new Set(
+ result.data?.platform?.features?.map(f => f.category) || []
+ );
+
+ // Should have multiple categories
+ expect(categories.size).toBeGreaterThan(1);
+ });
+
+ it("includes expected categories", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ const categories = new Set(
+ result.data?.platform?.features?.map(f => f.category) || []
+ );
+
+ // Should include common categories
+ const expectedCategories = ["agents", "permissions", "tools", "context", "model", "advanced"];
+ for (const cat of expectedCategories) {
+ expect(categories.has(cat)).toBe(true);
+ }
+ });
+ });
+
+ // ============================================================================
+ // CONFIG FORMAT AND OUTPUT STRUCTURE
+ // ============================================================================
+
+ describe("config format and output structure", () => {
+ it("OAC uses markdown format and directory structure", async () => {
+ // Act
+ const result = await executeInfo(
+ "oac",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform?.capabilities?.configFormat).toBe("markdown");
+ expect(result.data?.platform?.capabilities?.outputStructure).toBe("directory");
+ });
+
+ it("Cursor uses plain format and single-file structure", async () => {
+ // Act
+ const result = await executeInfo(
+ "cursor",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.platform?.capabilities?.configFormat).toBe("plain");
+ expect(result.data?.platform?.capabilities?.outputStructure).toBe("single-file");
+ });
+
+ it("Claude uses JSON format and directory structure", async () => {
+ // Act
+ const result = await executeInfo(
+ "claude",
+ {},
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Note: CapabilityMatrix defines Claude as 'json' format (not markdown)
+ // because Claude Code settings.json uses JSON configuration
+ expect(result.data?.platform?.capabilities?.configFormat).toBe("json");
+ expect(result.data?.platform?.capabilities?.outputStructure).toBe("directory");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/src/cli/__tests__/migrate.test.ts b/packages/compatibility-layer/src/cli/__tests__/migrate.test.ts
new file mode 100644
index 0000000..07fb369
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/migrate.test.ts
@@ -0,0 +1,602 @@
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import { mkdir, rm, writeFile, readFile, readdir } from "fs/promises";
+import { join, dirname } from "path";
+import { fileURLToPath } from "url";
+import { executeMigrate } from "../commands/migrate.js";
+import type { GlobalOptions } from "../types.js";
+
+/**
+ * Integration tests for the migrate CLI command
+ *
+ * Test strategy:
+ * 1. Successful migrations - Directory migrations to different formats
+ * 2. Error handling - Non-existent directories, invalid formats
+ * 3. Dry run mode - Preview migrations without writing
+ * 4. Output directory options - Custom output dirs, auto-generated names
+ * 5. Force overwrite - Handling existing files
+ * 6. Migration report - Verify report structure and counts
+ */
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const FIXTURES_DIR = join(__dirname, "fixtures");
+const TEMP_DIR = join(__dirname, "temp-migrate");
+
+// Default global options for tests
+const defaultGlobalOptions: GlobalOptions = {
+ verbose: false,
+ quiet: true,
+ outputFormat: "text",
+};
+
+describe("migrate command", () => {
+ // Setup temp directory before each test
+ beforeEach(async () => {
+ await mkdir(TEMP_DIR, { recursive: true });
+ });
+
+ // Cleanup temp directory after each test
+ afterEach(async () => {
+ try {
+ await rm(TEMP_DIR, { recursive: true, force: true });
+ } catch {
+ // Ignore cleanup errors
+ }
+ });
+
+ // Helper to create test source directory with agent files
+ const createTestSourceDir = async (dirName: string): Promise => {
+ const sourceDir = join(TEMP_DIR, dirName);
+ await mkdir(sourceDir, { recursive: true });
+
+ // Copy OAC agent fixture
+ const oacContent = await readFile(join(FIXTURES_DIR, "sample-oac-agent.md"), "utf-8");
+ await writeFile(join(sourceDir, "agent1.md"), oacContent);
+
+ // Create another agent
+ await writeFile(join(sourceDir, "agent2.md"), `---
+name: "agent-two"
+description: "Second test agent"
+mode: primary
+---
+
+You are a second agent for testing.
+`);
+
+ return sourceDir;
+ };
+
+ // ============================================================================
+ // SUCCESSFUL MIGRATIONS
+ // ============================================================================
+
+ describe("successful migrations", () => {
+ it("migrates directory to Cursor format", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-cursor");
+ const outDir = join(TEMP_DIR, "output-cursor");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", outDir },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.total).toBeGreaterThan(0);
+ expect(result.data?.successful).toBeGreaterThan(0);
+ });
+
+ it("migrates directory to Claude format", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-claude");
+ const outDir = join(TEMP_DIR, "output-claude");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "claude", outDir },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.total).toBeGreaterThan(0);
+ });
+
+ it("migrates directory to OAC format", async () => {
+ // Arrange - create a directory with cursor-style files
+ const sourceDir = join(TEMP_DIR, "source-oac");
+ await mkdir(sourceDir, { recursive: true });
+
+ const cursorContent = await readFile(join(FIXTURES_DIR, "sample-cursorrules"), "utf-8");
+ await writeFile(join(sourceDir, ".cursorrules"), cursorContent);
+
+ const outDir = join(TEMP_DIR, "output-oac");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "oac", outDir },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("creates output files in target directory", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-files");
+ const outDir = join(TEMP_DIR, "output-files");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", outDir },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ // Verify output directory was created
+ const outputFiles = await readdir(outDir, { recursive: true });
+ expect(outputFiles.length).toBeGreaterThan(0);
+ });
+
+ it("auto-generates output directory name when not specified", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-auto");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+
+ // Check that the auto-generated directory exists
+ const expectedOutDir = join(sourceDir, "migrated-cursor");
+ const files = await readdir(expectedOutDir).catch(() => []);
+ expect(files.length).toBeGreaterThanOrEqual(0);
+ });
+ });
+
+ // ============================================================================
+ // ERROR HANDLING
+ // ============================================================================
+
+ describe("error handling", () => {
+ it("returns error for non-existent source directory", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "does-not-exist");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("not found");
+ });
+
+ it("returns error when source path is a file, not directory", async () => {
+ // Arrange
+ const filePath = join(TEMP_DIR, "not-a-directory.txt");
+ await writeFile(filePath, "This is a file");
+
+ // Act
+ const result = await executeMigrate(
+ filePath,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("not a directory");
+ });
+
+ it("returns success with warning when no agent files found", async () => {
+ // Arrange
+ const emptyDir = join(TEMP_DIR, "empty-dir");
+ await mkdir(emptyDir, { recursive: true });
+
+ // Act
+ const result = await executeMigrate(
+ emptyDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.total).toBe(0);
+ expect(result.warnings).toBeDefined();
+ });
+ });
+
+ // ============================================================================
+ // DRY RUN MODE
+ // ============================================================================
+
+ describe("dry run mode", () => {
+ it("does not write files in dry run mode", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-dry");
+ const outDir = join(TEMP_DIR, "output-dry");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", outDir, dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.successful).toBeGreaterThan(0);
+
+ // Verify output directory does NOT exist
+ try {
+ await readdir(outDir);
+ // If we get here, the directory exists which is wrong
+ expect(false).toBe(true); // Fail the test
+ } catch (err: any) {
+ expect(err.code).toBe("ENOENT");
+ }
+ });
+
+ it("reports what would be migrated in dry run", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-dry-report");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.files).toBeDefined();
+ expect(result.data?.files.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ============================================================================
+ // FORCE OVERWRITE
+ // ============================================================================
+
+ describe("force overwrite", () => {
+ it("skips existing files without --force flag", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-no-force");
+ const outDir = join(TEMP_DIR, "output-no-force");
+
+ // Create output directory with existing file
+ await mkdir(outDir, { recursive: true });
+ await writeFile(join(outDir, "agent1.md"), "existing content");
+
+ // Act - first migration
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", outDir, force: false },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Check that skipped count is reasonable
+ expect(result.data?.skipped).toBeGreaterThanOrEqual(0);
+ });
+
+ it("overwrites existing files with --force flag", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-force");
+ const outDir = join(TEMP_DIR, "output-force");
+
+ // Create output directory with existing file
+ await mkdir(outDir, { recursive: true });
+ const existingFile = join(outDir, "agent1.cursorrules");
+ await writeFile(existingFile, "old content");
+
+ // Act - migrate with force
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", outDir, force: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // MIGRATION REPORT
+ // ============================================================================
+
+ describe("migration report", () => {
+ it("returns total count of discovered files", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-total");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(typeof result.data?.total).toBe("number");
+ expect(result.data?.total).toBeGreaterThan(0);
+ });
+
+ it("returns successful count in report", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-success");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(typeof result.data?.successful).toBe("number");
+ });
+
+ it("returns failed count in report", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-failed");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(typeof result.data?.failed).toBe("number");
+ });
+
+ it("returns skipped count in report", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-skipped");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(typeof result.data?.skipped).toBe("number");
+ });
+
+ it("returns list of migrated files in report", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-files");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.files)).toBe(true);
+
+ // Each file entry should have required properties
+ for (const file of result.data?.files || []) {
+ expect(file.source).toBeDefined();
+ expect(file.status).toBeDefined();
+ expect(["success", "failed", "skipped"]).toContain(file.status);
+ }
+ });
+
+ it("returns warnings array in report", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-report-warnings");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.warnings)).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // FILE DISCOVERY
+ // ============================================================================
+
+ describe("file discovery", () => {
+ it("discovers .md files in directory", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-discover-md");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.total).toBeGreaterThan(0);
+ });
+
+ it("discovers .json files in directory", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "source-discover-json");
+ await mkdir(sourceDir, { recursive: true });
+
+ const claudeContent = await readFile(join(FIXTURES_DIR, "sample-claude-config.json"), "utf-8");
+ await writeFile(join(sourceDir, "claude-config.json"), claudeContent);
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "oac", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("discovers .cursorrules files", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "source-discover-cursor");
+ await mkdir(sourceDir, { recursive: true });
+
+ const cursorContent = await readFile(join(FIXTURES_DIR, "sample-cursorrules"), "utf-8");
+ await writeFile(join(sourceDir, ".cursorrules"), cursorContent);
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "oac", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+
+ it("skips hidden directories by default (except special ones)", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "source-skip-hidden");
+ await mkdir(sourceDir, { recursive: true });
+ await mkdir(join(sourceDir, ".hidden"), { recursive: true });
+
+ await writeFile(join(sourceDir, "visible.md"), `---
+name: visible
+description: A visible test agent
+mode: primary
+---
+Content`);
+ await writeFile(join(sourceDir, ".hidden", "hidden.md"), `---
+name: hidden
+description: A hidden test agent
+mode: primary
+---
+Hidden content`);
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Should only find the visible file
+ expect(result.data?.total).toBe(1);
+ });
+
+ it("includes .opencode directory in discovery", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "source-opencode");
+ await mkdir(join(sourceDir, ".opencode", "agents"), { recursive: true });
+
+ await writeFile(join(sourceDir, ".opencode", "agents", "agent.md"), `---
+name: opencode-agent
+description: An opencode test agent
+mode: primary
+---
+Content`);
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.total).toBeGreaterThan(0);
+ });
+ });
+
+ // ============================================================================
+ // SAME FORMAT SKIPPING
+ // ============================================================================
+
+ describe("same format skipping", () => {
+ it("skips files already in target format", async () => {
+ // Arrange
+ const sourceDir = join(TEMP_DIR, "source-same-format");
+ await mkdir(sourceDir, { recursive: true });
+
+ // Create a .cursorrules file
+ const cursorContent = await readFile(join(FIXTURES_DIR, "sample-cursorrules"), "utf-8");
+ await writeFile(join(sourceDir, ".cursorrules"), cursorContent);
+
+ // Act - try to migrate to cursor (same format)
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor", dryRun: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // The file should be skipped since it's already in cursor format
+ expect(result.data?.skipped).toBeGreaterThanOrEqual(0);
+ });
+ });
+
+ // ============================================================================
+ // VERBOSE OUTPUT
+ // ============================================================================
+
+ describe("verbose output", () => {
+ it("respects verbose flag", async () => {
+ // Arrange
+ const sourceDir = await createTestSourceDir("source-verbose");
+
+ // Act
+ const result = await executeMigrate(
+ sourceDir,
+ { format: "cursor" },
+ { ...defaultGlobalOptions, verbose: true }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ });
+ });
+});
diff --git a/packages/compatibility-layer/src/cli/__tests__/validate.test.ts b/packages/compatibility-layer/src/cli/__tests__/validate.test.ts
new file mode 100644
index 0000000..d013c9f
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/__tests__/validate.test.ts
@@ -0,0 +1,451 @@
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
+import { mkdir, rm, writeFile } from "fs/promises";
+import { join, dirname } from "path";
+import { fileURLToPath } from "url";
+import { executeValidate, getValidationExitCode } from "../commands/validate.js";
+import type { GlobalOptions } from "../types.js";
+
+/**
+ * Integration tests for the validate CLI command
+ *
+ * Test strategy:
+ * 1. Successful validations - Valid agents against target platforms
+ * 2. Error handling - Missing files, invalid targets, missing required options
+ * 3. Compatibility scoring - Score calculation and status determination
+ * 4. Feature analysis - Preserved, degraded, and lost features
+ * 5. Exit codes - Proper exit codes for different validation results
+ */
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+const FIXTURES_DIR = join(__dirname, "fixtures");
+const TEMP_DIR = join(__dirname, "temp-validate");
+
+// Default global options for tests
+const defaultGlobalOptions: GlobalOptions = {
+ verbose: false,
+ quiet: true,
+ outputFormat: "text",
+};
+
+describe("validate command", () => {
+ // Setup temp directory before each test
+ beforeEach(async () => {
+ await mkdir(TEMP_DIR, { recursive: true });
+ });
+
+ // Cleanup temp directory after each test
+ afterEach(async () => {
+ try {
+ await rm(TEMP_DIR, { recursive: true, force: true });
+ } catch {
+ // Ignore cleanup errors
+ }
+ });
+
+ // ============================================================================
+ // SUCCESSFUL VALIDATIONS
+ // ============================================================================
+
+ describe("successful validations", () => {
+ it("validates OAC agent against Cursor target", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.target).toBe("cursor");
+ expect(result.data?.score).toBeGreaterThanOrEqual(0);
+ expect(result.data?.score).toBeLessThanOrEqual(100);
+ });
+
+ it("validates OAC agent against Claude target", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "claude" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.target).toBe("claude");
+ expect(typeof result.data?.score).toBe("number");
+ });
+
+ it("validates OAC agent against Windsurf target", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "windsurf" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ expect(result.data?.target).toBe("windsurf");
+ });
+
+ it("returns status field in validation result", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.status).toBeDefined();
+ expect(["compatible", "partial", "incompatible"]).toContain(result.data?.status);
+ });
+
+ it("returns canConvert field in validation result", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(typeof result.data?.canConvert).toBe("boolean");
+ });
+
+ it("returns feature breakdown in validation result", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.preserved)).toBe(true);
+ expect(Array.isArray(result.data?.degraded)).toBe(true);
+ expect(Array.isArray(result.data?.lost)).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // ERROR HANDLING
+ // ============================================================================
+
+ describe("error handling", () => {
+ it("returns error when target is not specified", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: undefined },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("target");
+ });
+
+ it("returns error when target is OAC (invalid target)", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "oac" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toContain("OAC");
+ });
+
+ it("returns error for non-existent file", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "does-not-exist.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(false);
+ expect(result.error).toBeDefined();
+ });
+
+ it("handles invalid agent file gracefully", async () => {
+ // Arrange
+ const brokenFile = join(TEMP_DIR, "broken-agent.md");
+ await writeFile(brokenFile, "---\ninvalid yaml: ::::\n---\nContent");
+
+ // Act
+ const result = await executeValidate(
+ brokenFile,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert - should either succeed with degraded result or fail gracefully
+ expect(typeof result.success).toBe("boolean");
+ });
+ });
+
+ // ============================================================================
+ // STRICT MODE
+ // ============================================================================
+
+ describe("strict mode", () => {
+ it("applies strict validation when --strict flag is set", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor", strict: true },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // In strict mode, any lost features should mark as incompatible
+ if (result.data?.lost && result.data.lost.length > 0) {
+ expect(result.data.status).toBe("incompatible");
+ }
+ });
+
+ it("non-strict mode allows partial compatibility", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor", strict: false },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ // Should allow partial even with some lost features
+ expect(["compatible", "partial", "incompatible"]).toContain(result.data?.status);
+ });
+ });
+
+ // ============================================================================
+ // EXIT CODES
+ // ============================================================================
+
+ describe("exit codes", () => {
+ it("returns exit code 0 for compatible status", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Act
+ const exitCode = getValidationExitCode(result);
+
+ // Assert
+ if (result.data?.status === "compatible") {
+ expect(exitCode).toBe(0);
+ }
+ });
+
+ it("returns exit code 1 for partial compatibility", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Act
+ const exitCode = getValidationExitCode(result);
+
+ // Assert
+ if (result.data?.status === "partial") {
+ expect(exitCode).toBe(1);
+ }
+ });
+
+ it("returns exit code 2 for incompatible status", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor", strict: true },
+ defaultGlobalOptions
+ );
+
+ // Act
+ const exitCode = getValidationExitCode(result);
+
+ // Assert
+ if (result.data?.status === "incompatible") {
+ expect(exitCode).toBe(2);
+ }
+ });
+
+ it("returns exit code 2 for failed validations", async () => {
+ // Arrange - non-existent file
+ const inputPath = join(FIXTURES_DIR, "does-not-exist.md");
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Act
+ const exitCode = getValidationExitCode(result);
+
+ // Assert
+ expect(exitCode).toBe(2);
+ });
+ });
+
+ // ============================================================================
+ // SUGGESTIONS
+ // ============================================================================
+
+ describe("suggestions", () => {
+ it("returns suggestions for lost or degraded features", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.suggestions)).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // BLOCKERS
+ // ============================================================================
+
+ describe("blockers", () => {
+ it("returns blockers array in validation result", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(Array.isArray(result.data?.blockers)).toBe(true);
+ });
+
+ it("canConvert is false when blockers exist", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ if (result.data?.blockers && result.data.blockers.length > 0) {
+ expect(result.data.canConvert).toBe(false);
+ }
+ });
+ });
+
+ // ============================================================================
+ // JSON OUTPUT
+ // ============================================================================
+
+ describe("JSON output format", () => {
+ it("works with JSON output format option", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ { ...defaultGlobalOptions, outputFormat: "json" }
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data).toBeDefined();
+ });
+ });
+
+ // ============================================================================
+ // INPUT PATH VALIDATION
+ // ============================================================================
+
+ describe("input path validation", () => {
+ it("includes input path in result data", async () => {
+ // Arrange
+ const inputPath = join(FIXTURES_DIR, "sample-oac-agent.md");
+
+ // Act
+ const result = await executeValidate(
+ inputPath,
+ { target: "cursor" },
+ defaultGlobalOptions
+ );
+
+ // Assert
+ expect(result.success).toBe(true);
+ expect(result.data?.input).toBe(inputPath);
+ });
+ });
+});
diff --git a/packages/compatibility-layer/src/cli/commands/convert.ts b/packages/compatibility-layer/src/cli/commands/convert.ts
new file mode 100644
index 0000000..fea651e
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/commands/convert.ts
@@ -0,0 +1,436 @@
+/**
+ * Convert Command
+ *
+ * Converts agent files between different AI coding tool formats.
+ * Supports OAC, Cursor, Claude, and Windsurf formats.
+ *
+ * @module cli/commands/convert
+ */
+
+import { readFile, writeFile, mkdir } from "fs/promises";
+import { dirname, basename, extname, resolve } from "path";
+import type { Command } from "commander";
+import type { OpenAgent, ConversionResult } from "../../types.js";
+import {
+ type GlobalOptions,
+ type Platform,
+ successResult,
+ errorResult,
+ type CommandResult,
+} from "../types.js";
+import {
+ createLoggerConfig,
+ logInfo,
+ logSuccess,
+ logWarning,
+ logVerbose,
+ createSpinner,
+ spinnerSuccess,
+ spinnerFail,
+ printOutput,
+ handleError,
+ type LoggerConfig,
+} from "../utils.js";
+
+// Dynamic imports to avoid ESM resolution issues at startup
+const getAgentLoader = async () => {
+ const module = await import("../../core/AgentLoader.js");
+ return module;
+};
+
+const getAdapterRegistry = async () => {
+ const module = await import("../../core/AdapterRegistry.js");
+ // Ensure built-in adapters are registered
+ await module.registry.registerBuiltInAdapters();
+ return module;
+};
+
+// ============================================================================
+// TYPES
+// ============================================================================
+
+/**
+ * Options specific to the convert command
+ */
+interface ConvertCommandOptions {
+ output?: string;
+ format: Platform;
+ from?: Platform;
+ force?: boolean;
+}
+
+/**
+ * Detected format information
+ */
+interface FormatDetection {
+ format: Platform;
+ confidence: "high" | "medium" | "low";
+ reason: string;
+}
+
+// ============================================================================
+// FORMAT DETECTION
+// ============================================================================
+
+interface ParsedConfig {
+ cascadeConfig?: unknown;
+ creativity?: unknown;
+ permissions?: unknown;
+ skills?: unknown;
+}
+
+/**
+ * Detect the format of an input file based on path and content
+ */
+const detectInputFormat = (
+ filePath: string,
+ content: string
+): FormatDetection => {
+ const fileName = basename(filePath).toLowerCase();
+ const ext = extname(filePath).toLowerCase();
+
+ // Check file name patterns
+ if (fileName === ".cursorrules" || fileName === "cursorrules") {
+ return { format: "cursor", confidence: "high", reason: "File name .cursorrules" };
+ }
+
+ if (fileName.includes("windsurf") || filePath.includes(".windsurf/")) {
+ return { format: "windsurf", confidence: "high", reason: "Windsurf path pattern" };
+ }
+
+ if (filePath.includes(".claude/") || fileName.includes("claude")) {
+ return { format: "claude", confidence: "high", reason: "Claude path pattern" };
+ }
+
+ if (filePath.includes(".opencode/") || filePath.includes("openagent")) {
+ return { format: "oac", confidence: "high", reason: "OAC path pattern" };
+ }
+
+ // Check content patterns
+ if (ext === ".md") {
+ // Check for OAC frontmatter
+ if (content.startsWith("---")) {
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
+ if (frontmatterMatch?.[1]) {
+ const frontmatter = frontmatterMatch[1];
+ if (frontmatter.includes("mode:") || frontmatter.includes("tools:")) {
+ return { format: "oac", confidence: "high", reason: "OAC frontmatter detected" };
+ }
+ }
+ }
+ // Default markdown to OAC
+ return { format: "oac", confidence: "medium", reason: "Markdown file (assuming OAC)" };
+ }
+
+ if (ext === ".json") {
+ try {
+ const parsed = JSON.parse(content) as ParsedConfig;
+ if (parsed.cascadeConfig || parsed.creativity !== undefined) {
+ return { format: "windsurf", confidence: "high", reason: "Windsurf JSON structure" };
+ }
+ if (parsed.permissions || parsed.skills) {
+ return { format: "claude", confidence: "high", reason: "Claude JSON structure" };
+ }
+ } catch {
+ // Not valid JSON
+ }
+ return { format: "oac", confidence: "low", reason: "JSON file (format unclear)" };
+ }
+
+ // Plain text - likely cursor
+ if (ext === ".txt" || ext === "") {
+ return { format: "cursor", confidence: "medium", reason: "Plain text (assuming Cursor)" };
+ }
+
+ return { format: "oac", confidence: "low", reason: "Unknown format (defaulting to OAC)" };
+};
+
+// ============================================================================
+// CONVERSION LOGIC
+// ============================================================================
+
+/**
+ * Load an agent from any supported format
+ */
+const loadAgentFromFormat = async (
+ filePath: string,
+ content: string,
+ format: Platform,
+ config: LoggerConfig
+): Promise => {
+ logVerbose(config, `Loading agent from ${format} format`);
+
+ if (format === "oac") {
+ // Use AgentLoader for OAC format
+ const { loadAgent } = await getAgentLoader();
+ return await loadAgent(filePath);
+ }
+
+ // Use appropriate adapter for other formats
+ const { getAdapter } = await getAdapterRegistry();
+ const adapter = getAdapter(format);
+ if (!adapter) {
+ throw new Error(`No adapter found for format: ${format}`);
+ }
+ return await adapter.toOAC(content);
+};
+
+/**
+ * Convert an agent to the target format
+ */
+const convertToFormat = async (
+ agent: OpenAgent,
+ targetFormat: Platform,
+ config: LoggerConfig
+): Promise => {
+ logVerbose(config, `Converting to ${targetFormat} format`);
+
+ if (targetFormat === "oac") {
+ // Converting TO OAC - just serialize the agent
+ const content = serializeOACAgent(agent);
+ return {
+ success: true,
+ configs: [
+ {
+ fileName: `${agent.frontmatter.name || "agent"}.md`,
+ content,
+ encoding: "utf-8",
+ },
+ ],
+ warnings: [],
+ };
+ }
+
+ // Use adapter for other formats
+ const { getAdapter } = await getAdapterRegistry();
+ const adapter = getAdapter(targetFormat);
+ if (!adapter) {
+ throw new Error(`No adapter found for format: ${targetFormat}`);
+ }
+ return await adapter.fromOAC(agent);
+};
+
+/**
+ * Serialize an OpenAgent to OAC markdown format
+ */
+const serializeOACAgent = (agent: OpenAgent): string => {
+ const lines: string[] = ["---"];
+
+ // Serialize frontmatter
+ const fm = agent.frontmatter;
+ lines.push(`name: "${fm.name}"`);
+ if (fm.description) lines.push(`description: "${fm.description}"`);
+ lines.push(`mode: ${fm.mode}`);
+ if (fm.model) lines.push(`model: ${fm.model}`);
+ if (fm.temperature !== undefined) lines.push(`temperature: ${fm.temperature}`);
+
+ // Tools
+ if (fm.tools) {
+ lines.push("tools:");
+ for (const [tool, access] of Object.entries(fm.tools)) {
+ const accessValue = access as boolean | string;
+ if (typeof accessValue === "boolean") {
+ lines.push(` ${tool}: ${String(accessValue)}`);
+ } else {
+ lines.push(` ${tool}: "${String(accessValue)}"`);
+ }
+ }
+ }
+
+ lines.push("---");
+ lines.push("");
+ lines.push(agent.systemPrompt);
+
+ return lines.join("\n");
+};
+
+/**
+ * Determine output path for converted file
+ */
+const getOutputPath = (
+ _inputPath: string,
+ outputOption: string | undefined,
+ _result: ConversionResult
+): string | null => {
+ if (outputOption) {
+ return resolve(outputOption);
+ }
+
+ // No output specified - will write to stdout
+ return null;
+};
+
+/**
+ * Write conversion result to file or stdout
+ */
+const writeOutput = async (
+ result: ConversionResult,
+ outputPath: string | null,
+ config: LoggerConfig,
+ force: boolean
+): Promise => {
+ if (result.configs.length === 0) {
+ throw new Error("Conversion produced no output files");
+ }
+
+ // Get primary output content
+ const primaryConfig = result.configs[0];
+ if (!primaryConfig) {
+ throw new Error("No primary config in conversion result");
+ }
+
+ if (outputPath) {
+ // Write to file
+ const dir = dirname(outputPath);
+ await mkdir(dir, { recursive: true });
+
+ // Check if file exists (unless --force)
+ if (!force) {
+ try {
+ await readFile(outputPath);
+ throw new Error(
+ `Output file already exists: ${outputPath}. Use --force to overwrite.`
+ );
+ } catch (err) {
+ if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
+ throw err;
+ }
+ }
+ }
+
+ await writeFile(outputPath, primaryConfig.content, "utf-8");
+ logSuccess(config, `Wrote ${outputPath}`);
+
+ // Write additional files if present
+ for (let i = 1; i < result.configs.length; i++) {
+ const additionalConfig = result.configs[i];
+ if (additionalConfig) {
+ const additionalPath = resolve(dirname(outputPath), additionalConfig.fileName);
+ await writeFile(additionalPath, additionalConfig.content, "utf-8");
+ logSuccess(config, `Wrote ${additionalPath}`);
+ }
+ }
+ } else {
+ // Write to stdout
+ console.log(primaryConfig.content);
+ }
+};
+
+// ============================================================================
+// COMMAND HANDLER
+// ============================================================================
+
+/**
+ * Execute the convert command
+ */
+export const executeConvert = async (
+ input: string,
+ options: ConvertCommandOptions,
+ globalOptions: GlobalOptions
+): Promise> => {
+ const config = createLoggerConfig(globalOptions);
+
+ try {
+ // Read input file
+ logVerbose(config, `Reading input file: ${input}`);
+ const content = await readFile(input, "utf-8");
+
+ // Detect or use specified source format
+ let sourceFormat: Platform;
+ if (options.from) {
+ sourceFormat = options.from;
+ logVerbose(config, `Using specified source format: ${sourceFormat}`);
+ } else {
+ const detection = detectInputFormat(input, content);
+ sourceFormat = detection.format;
+ logVerbose(
+ config,
+ `Detected source format: ${sourceFormat} (${detection.confidence} confidence: ${detection.reason})`
+ );
+
+ if (detection.confidence === "low") {
+ logWarning(
+ config,
+ `Low confidence format detection. Use --from to specify source format.`
+ );
+ }
+ }
+
+ // Check for same format conversion
+ if (sourceFormat === options.format) {
+ return errorResult(
+ `Source and target formats are the same: ${sourceFormat}. No conversion needed.`
+ );
+ }
+
+ // Create spinner for conversion
+ const spinner = createSpinner(
+ config,
+ `Converting from ${sourceFormat} to ${options.format}...`
+ );
+
+ try {
+ // Load agent from source format
+ const agent = await loadAgentFromFormat(input, content, sourceFormat, config);
+ logVerbose(config, `Loaded agent: ${agent.frontmatter.name}`);
+
+ // Convert to target format
+ const result = await convertToFormat(agent, options.format, config);
+
+ if (!result.success) {
+ spinnerFail(spinner, "Conversion failed");
+ return errorResult(
+ result.errors?.join(", ") || "Unknown conversion error",
+ result.warnings
+ );
+ }
+
+ spinnerSuccess(spinner, `Converted to ${options.format} format`);
+
+ // Show warnings
+ if (result.warnings.length > 0) {
+ logInfo(config, "");
+ for (const warning of result.warnings) {
+ logWarning(config, warning);
+ }
+ }
+
+ // Write output
+ const outputPath = getOutputPath(input, options.output, result);
+ await writeOutput(result, outputPath, config, options.force ?? false);
+
+ return successResult(result, result.warnings);
+ } catch (err) {
+ spinnerFail(spinner, "Conversion failed");
+ throw err;
+ }
+ } catch (err) {
+ return handleError(config, err);
+ }
+};
+
+/**
+ * Create the convert command action handler
+ */
+export const createConvertAction = (): ((
+ input: string,
+ options: ConvertCommandOptions,
+ command: Command
+) => Promise) => {
+ return async (
+ input: string,
+ options: ConvertCommandOptions,
+ command: Command
+ ): Promise => {
+ const globalOptions = command.optsWithGlobals() as unknown as GlobalOptions;
+ const result = await executeConvert(input, options, globalOptions);
+
+ if (globalOptions.outputFormat === "json") {
+ printOutput(createLoggerConfig(globalOptions), result);
+ }
+
+ if (!result.success) {
+ process.exit(1);
+ }
+ };
+};
diff --git a/packages/compatibility-layer/src/cli/commands/info.ts b/packages/compatibility-layer/src/cli/commands/info.ts
new file mode 100644
index 0000000..928476f
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/commands/info.ts
@@ -0,0 +1,523 @@
+/**
+ * Info Command
+ *
+ * Displays tool capabilities and feature parity matrices for AI coding tools.
+ * Helps users understand what features are supported by each platform.
+ *
+ * @module cli/commands/info
+ */
+
+import type { Command } from "commander";
+import {
+ type GlobalOptions,
+ type Platform,
+ successResult,
+ errorResult,
+ type CommandResult,
+ PLATFORMS,
+ PLATFORM_NAMES,
+ isValidPlatform,
+} from "../types.js";
+import {
+ createLoggerConfig,
+ logInfo,
+ logVerbose,
+ printOutput,
+ handleError,
+ type LoggerConfig,
+} from "../utils.js";
+import chalk from "chalk";
+import type { FeatureDefinition } from "../../core/CapabilityMatrix.js";
+
+// Dynamic import for CapabilityMatrix
+const loadCapabilityMatrixModule = async () => {
+ const module = await import("../../core/CapabilityMatrix.js");
+ return module;
+};
+
+// ============================================================================
+// TYPES
+// ============================================================================
+
+/**
+ * Options specific to the info command
+ */
+interface InfoCommandOptions {
+ detailed?: boolean;
+ compare?: Platform;
+}
+
+/**
+ * Platform info data structure
+ */
+interface PlatformInfo {
+ platform: Platform;
+ displayName: string;
+ capabilities: {
+ supportsMultipleAgents: boolean;
+ supportsSkills: boolean;
+ supportsHooks: boolean;
+ supportsGranularPermissions: boolean;
+ supportsContexts: boolean;
+ supportsCustomModels: boolean;
+ supportsTemperature: boolean;
+ supportsMaxSteps: boolean;
+ configFormat: string;
+ outputStructure: string;
+ };
+ features: FeatureInfo[];
+}
+
+/**
+ * Feature info for a platform
+ */
+interface FeatureInfo {
+ name: string;
+ category: string;
+ description: string;
+ support: "full" | "partial" | "none";
+ note?: string;
+}
+
+/**
+ * Comparison result between two platforms
+ */
+interface PlatformComparison {
+ platformA: Platform;
+ platformB: Platform;
+ identical: string[];
+ betterInA: string[];
+ betterInB: string[];
+}
+
+/**
+ * Info result data
+ */
+interface InfoResultData {
+ platforms?: PlatformInfo[];
+ platform?: PlatformInfo;
+ comparison?: PlatformComparison;
+}
+
+// ============================================================================
+// DOCUMENTATION LINKS
+// ============================================================================
+
+const DOCUMENTATION_LINKS: Record = {
+ oac: "https://github.com/OpenAgentsInc/openagents/blob/main/COMPATIBILITY.md",
+ cursor: "https://docs.cursor.com/context/rules-for-ai",
+ claude: "https://docs.anthropic.com/en/docs/build-with-claude/computer-use",
+ windsurf: "https://docs.codeium.com/windsurf/overview",
+};
+
+// ============================================================================
+// INFO GENERATION
+// ============================================================================
+
+/**
+ * Get info for a single platform
+ */
+const getPlatformInfo = async (platform: Platform): Promise => {
+ const capabilityModule = await loadCapabilityMatrixModule();
+ const { getCapabilityMatrix, getToolCapabilities } = capabilityModule;
+
+ const matrix = getCapabilityMatrix();
+
+ // Get capabilities (only for non-oac platforms)
+ let capabilities: PlatformInfo["capabilities"];
+ if (platform === "oac") {
+ capabilities = {
+ supportsMultipleAgents: true,
+ supportsSkills: true,
+ supportsHooks: true,
+ supportsGranularPermissions: true,
+ supportsContexts: true,
+ supportsCustomModels: true,
+ supportsTemperature: true,
+ supportsMaxSteps: true,
+ configFormat: "markdown",
+ outputStructure: "directory",
+ };
+ } else {
+ const toolCaps = getToolCapabilities(platform);
+ capabilities = {
+ supportsMultipleAgents: toolCaps.supportsMultipleAgents,
+ supportsSkills: toolCaps.supportsSkills,
+ supportsHooks: toolCaps.supportsHooks,
+ supportsGranularPermissions: toolCaps.supportsGranularPermissions,
+ supportsContexts: toolCaps.supportsContexts,
+ supportsCustomModels: toolCaps.supportsCustomModels,
+ supportsTemperature: toolCaps.supportsTemperature,
+ supportsMaxSteps: toolCaps.supportsMaxSteps,
+ configFormat: toolCaps.configFormat,
+ outputStructure: toolCaps.outputStructure,
+ };
+ }
+
+ // Get feature details
+ const features: FeatureInfo[] = matrix.map((feature: FeatureDefinition) => ({
+ name: feature.name,
+ category: feature.category,
+ description: feature.description,
+ support: feature.support[platform],
+ note: feature.notes?.[platform],
+ }));
+
+ return {
+ platform,
+ displayName: PLATFORM_NAMES[platform],
+ capabilities,
+ features,
+ };
+};
+
+/**
+ * Compare two platforms
+ */
+const comparePlatformsInfo = async (
+ platformA: Platform,
+ platformB: Platform
+): Promise => {
+ const capabilityModule = await loadCapabilityMatrixModule();
+ const comparison = capabilityModule.comparePlatforms(platformA, platformB);
+
+ return {
+ platformA,
+ platformB,
+ identical: comparison.identical,
+ betterInA: comparison.betterInA,
+ betterInB: comparison.betterInB,
+ };
+};
+
+// ============================================================================
+// TEXT FORMATTING
+// ============================================================================
+
+/**
+ * Format support level for display
+ */
+const formatSupport = (support: "full" | "partial" | "none"): string => {
+ switch (support) {
+ case "full":
+ return chalk.green("✓ Full");
+ case "partial":
+ return chalk.yellow("◐ Partial");
+ case "none":
+ return chalk.red("✗ None");
+ }
+};
+
+/**
+ * Format capability value for display
+ */
+const formatCapability = (value: boolean): string => {
+ return value ? chalk.green("✓") : chalk.red("✗");
+};
+
+/**
+ * Print feature matrix for all platforms
+ */
+const printFeatureMatrix = async (config: LoggerConfig): Promise => {
+ const capabilityModule = await loadCapabilityMatrixModule();
+ const matrix = capabilityModule.getCapabilityMatrix();
+
+ logInfo(config, "");
+ logInfo(config, chalk.bold.cyan("═══ Feature Compatibility Matrix ═══"));
+ logInfo(config, "");
+
+ // Header
+ const platforms: Platform[] = ["oac", "cursor", "claude", "windsurf"];
+ const header =
+ chalk.bold("Feature".padEnd(25)) +
+ platforms.map((p) => chalk.bold(p.padStart(10))).join("");
+ logInfo(config, header);
+ logInfo(config, "─".repeat(65));
+
+ // Group by category
+ const categories = ["agents", "permissions", "tools", "context", "model", "advanced"];
+
+ for (const category of categories) {
+ logInfo(config, chalk.dim.bold(`\n${category.toUpperCase()}`));
+
+ const categoryFeatures = matrix.filter((f) => f.category === category);
+ for (const feature of categoryFeatures) {
+ const name = feature.name.padEnd(25);
+ const supports = platforms
+ .map((p) => {
+ const level = feature.support[p];
+ const icon =
+ level === "full"
+ ? chalk.green("✓")
+ : level === "partial"
+ ? chalk.yellow("◐")
+ : chalk.red("✗");
+ return icon.padStart(10);
+ })
+ .join("");
+ logInfo(config, `${name}${supports}`);
+ }
+ }
+
+ logInfo(config, "");
+ logInfo(config, chalk.dim("Legend: ✓ Full support | ◐ Partial support | ✗ Not supported"));
+ logInfo(config, "");
+
+ // Documentation links
+ logInfo(config, chalk.bold("Documentation:"));
+ for (const platform of platforms) {
+ logInfo(
+ config,
+ ` ${chalk.cyan(platform.padEnd(10))} ${DOCUMENTATION_LINKS[platform]}`
+ );
+ }
+};
+
+/**
+ * Print detailed info for a single platform
+ */
+const printPlatformDetail = (
+ config: LoggerConfig,
+ info: PlatformInfo
+): void => {
+ logInfo(config, "");
+ logInfo(config, chalk.bold.cyan(`═══ ${info.displayName} (${info.platform}) ═══`));
+ logInfo(config, "");
+
+ // Capabilities summary
+ logInfo(config, chalk.bold("Capabilities:"));
+ logInfo(
+ config,
+ ` Multiple Agents: ${formatCapability(info.capabilities.supportsMultipleAgents)}`
+ );
+ logInfo(
+ config,
+ ` Skills System: ${formatCapability(info.capabilities.supportsSkills)}`
+ );
+ logInfo(
+ config,
+ ` Event Hooks: ${formatCapability(info.capabilities.supportsHooks)}`
+ );
+ logInfo(
+ config,
+ ` Granular Permissions: ${formatCapability(info.capabilities.supportsGranularPermissions)}`
+ );
+ logInfo(
+ config,
+ ` External Contexts: ${formatCapability(info.capabilities.supportsContexts)}`
+ );
+ logInfo(
+ config,
+ ` Custom Models: ${formatCapability(info.capabilities.supportsCustomModels)}`
+ );
+ logInfo(
+ config,
+ ` Temperature Control: ${formatCapability(info.capabilities.supportsTemperature)}`
+ );
+ logInfo(
+ config,
+ ` Max Steps Limit: ${formatCapability(info.capabilities.supportsMaxSteps)}`
+ );
+ logInfo(config, "");
+ logInfo(config, ` Config Format: ${chalk.cyan(info.capabilities.configFormat)}`);
+ logInfo(config, ` Output Structure: ${chalk.cyan(info.capabilities.outputStructure)}`);
+ logInfo(config, "");
+
+ // Feature details by category
+ logInfo(config, chalk.bold("Feature Support:"));
+
+ const categories = ["agents", "permissions", "tools", "context", "model", "advanced"];
+ for (const category of categories) {
+ const categoryFeatures = info.features.filter((f) => f.category === category);
+ if (categoryFeatures.length === 0) continue;
+
+ logInfo(config, chalk.dim.bold(`\n ${category.toUpperCase()}`));
+ for (const feature of categoryFeatures) {
+ const support = formatSupport(feature.support);
+ logInfo(config, ` ${feature.name.padEnd(22)} ${support}`);
+ if (feature.note) {
+ logInfo(config, chalk.dim(` └─ ${feature.note}`));
+ }
+ }
+ }
+
+ logInfo(config, "");
+ logInfo(config, chalk.bold("Documentation:"));
+ logInfo(config, ` ${DOCUMENTATION_LINKS[info.platform]}`);
+};
+
+/**
+ * Print platform comparison
+ */
+const printComparison = (
+ config: LoggerConfig,
+ comparison: PlatformComparison,
+ infoA: PlatformInfo,
+ infoB: PlatformInfo
+): void => {
+ logInfo(config, "");
+ logInfo(
+ config,
+ chalk.bold.cyan(
+ `═══ Comparison: ${infoA.displayName} vs ${infoB.displayName} ═══`
+ )
+ );
+ logInfo(config, "");
+
+ // Summary stats
+ const total =
+ comparison.identical.length +
+ comparison.betterInA.length +
+ comparison.betterInB.length;
+ logInfo(
+ config,
+ `Features compared: ${chalk.bold(total.toString())}`
+ );
+ logInfo(
+ config,
+ ` Identical: ${chalk.green(comparison.identical.length.toString())} features`
+ );
+ logInfo(
+ config,
+ ` Better in ${comparison.platformA}: ${chalk.cyan(comparison.betterInA.length.toString())} features`
+ );
+ logInfo(
+ config,
+ ` Better in ${comparison.platformB}: ${chalk.cyan(comparison.betterInB.length.toString())} features`
+ );
+ logInfo(config, "");
+
+ // Better in A
+ if (comparison.betterInA.length > 0) {
+ logInfo(
+ config,
+ chalk.bold(`Better in ${infoA.displayName}:`)
+ );
+ for (const feature of comparison.betterInA) {
+ logInfo(config, ` ${chalk.cyan("→")} ${feature}`);
+ }
+ logInfo(config, "");
+ }
+
+ // Better in B
+ if (comparison.betterInB.length > 0) {
+ logInfo(
+ config,
+ chalk.bold(`Better in ${infoB.displayName}:`)
+ );
+ for (const feature of comparison.betterInB) {
+ logInfo(config, ` ${chalk.cyan("→")} ${feature}`);
+ }
+ logInfo(config, "");
+ }
+
+ // Identical features
+ if (comparison.identical.length > 0) {
+ logInfo(config, chalk.bold("Identical support:"));
+ logInfo(config, ` ${chalk.dim(comparison.identical.join(", "))}`);
+ }
+};
+
+// ============================================================================
+// COMMAND HANDLER
+// ============================================================================
+
+/**
+ * Execute the info command
+ */
+export const executeInfo = async (
+ platform: string | undefined,
+ options: InfoCommandOptions,
+ globalOptions: GlobalOptions
+): Promise> => {
+ const config = createLoggerConfig(globalOptions);
+
+ try {
+ // If no platform specified, show feature matrix
+ if (!platform) {
+ logVerbose(config, "Showing feature matrix for all platforms");
+
+ // For text output, print the matrix
+ if (globalOptions.outputFormat !== "json") {
+ await printFeatureMatrix(config);
+ }
+
+ // For JSON, return all platform info
+ const platforms: PlatformInfo[] = [];
+ for (const p of PLATFORMS) {
+ platforms.push(await getPlatformInfo(p));
+ }
+
+ return successResult({ platforms });
+ }
+
+ // Validate platform
+ if (!isValidPlatform(platform)) {
+ return errorResult(
+ `Unknown platform: ${platform}. Valid platforms: ${PLATFORMS.join(", ")}`
+ );
+ }
+
+ // Get platform info
+ const info = await getPlatformInfo(platform);
+ logVerbose(config, `Getting info for: ${info.displayName}`);
+
+ // Handle comparison
+ if (options.compare) {
+ if (!isValidPlatform(options.compare)) {
+ const comparePlatform = String(options.compare);
+ return errorResult(
+ `Unknown platform for comparison: ${comparePlatform}. Valid platforms: ${PLATFORMS.join(", ")}`
+ );
+ }
+
+ const compareInfo = await getPlatformInfo(options.compare);
+ const comparison = await comparePlatformsInfo(platform, options.compare);
+
+ if (globalOptions.outputFormat !== "json") {
+ printComparison(config, comparison, info, compareInfo);
+ }
+
+ return successResult({
+ platform: info,
+ comparison,
+ });
+ }
+
+ // Print detailed platform info
+ if (globalOptions.outputFormat !== "json") {
+ printPlatformDetail(config, info);
+ }
+
+ return successResult({ platform: info });
+ } catch (err) {
+ return handleError(config, err);
+ }
+};
+
+/**
+ * Create the info command action handler
+ */
+export const createInfoAction = (): ((
+ platform: string | undefined,
+ options: InfoCommandOptions,
+ command: Command
+ ) => Promise) => {
+ return async (
+ platform: string | undefined,
+ options: InfoCommandOptions,
+ command: Command
+ ): Promise => {
+ const globalOptions = command.optsWithGlobals() as unknown as GlobalOptions;
+ const result = await executeInfo(platform, options, globalOptions);
+
+ // Print JSON output if requested
+ if (globalOptions.outputFormat === "json") {
+ printOutput(createLoggerConfig(globalOptions), result);
+ }
+
+ if (!result.success) {
+ process.exit(1);
+ }
+ };
+};
diff --git a/packages/compatibility-layer/src/cli/commands/migrate.ts b/packages/compatibility-layer/src/cli/commands/migrate.ts
new file mode 100644
index 0000000..bbfa090
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/commands/migrate.ts
@@ -0,0 +1,733 @@
+/**
+ * Migrate Command
+ *
+ * Migrates entire projects between different AI coding tool formats.
+ * Discovers all agent files in a directory and converts them to the target format.
+ *
+ * @module cli/commands/migrate
+ */
+
+import { readFile, writeFile, mkdir, readdir, stat, access } from "fs/promises";
+import { dirname, basename, extname, resolve, join, relative } from "path";
+import type { Command } from "commander";
+import type { OpenAgent, ConversionResult } from "../../types.js";
+import {
+ type GlobalOptions,
+ type Platform,
+ successResult,
+ errorResult,
+ type CommandResult,
+} from "../types.js";
+import {
+ createLoggerConfig,
+ logInfo,
+ logSuccess,
+ logWarning,
+ logVerbose,
+ logError,
+ createSpinner,
+ spinnerSuccess,
+ spinnerFail,
+ spinnerWarn,
+ printOutput,
+ handleError,
+ type LoggerConfig,
+} from "../utils.js";
+
+// Dynamic imports to avoid ESM resolution issues at startup
+const getAgentLoader = async () => {
+ const module = await import("../../core/AgentLoader.js");
+ return module;
+};
+
+const getAdapterRegistry = async () => {
+ const module = await import("../../core/AdapterRegistry.js");
+ // Ensure built-in adapters are registered
+ await module.registry.registerBuiltInAdapters();
+ return module;
+};
+
+// ============================================================================
+// TYPES
+// ============================================================================
+
+/**
+ * Options specific to the migrate command
+ */
+interface MigrateCommandOptions {
+ format: Platform;
+ outDir?: string;
+ dryRun?: boolean;
+ force?: boolean;
+}
+
+/**
+ * Status of a single migrated file
+ */
+type MigratedFileStatus = "success" | "failed" | "skipped";
+
+/**
+ * Information about a single migrated file
+ */
+interface MigratedFile {
+ source: string;
+ destination: string;
+ status: MigratedFileStatus;
+ warnings?: string[];
+ error?: string;
+}
+
+/**
+ * Complete migration report
+ */
+interface MigrationReport {
+ total: number;
+ successful: number;
+ failed: number;
+ skipped: number;
+ warnings: string[];
+ files: MigratedFile[];
+}
+
+/**
+ * Detected format information
+ */
+interface FormatDetection {
+ format: Platform;
+ confidence: "high" | "medium" | "low";
+ reason: string;
+}
+
+/**
+ * Discovered agent file with its detected format
+ */
+interface DiscoveredFile {
+ path: string;
+ relativePath: string;
+ detectedFormat: FormatDetection;
+}
+
+// ============================================================================
+// FORMAT DETECTION (reused from convert.ts pattern)
+// ============================================================================
+
+interface ParsedConfig {
+ cascadeConfig?: unknown;
+ creativity?: unknown;
+ permissions?: unknown;
+ skills?: unknown;
+}
+
+/**
+ * Detect the format of an input file based on path and content
+ */
+const detectInputFormat = (
+ filePath: string,
+ content: string
+): FormatDetection => {
+ const fileName = basename(filePath).toLowerCase();
+ const ext = extname(filePath).toLowerCase();
+
+ // Check file name patterns
+ if (fileName === ".cursorrules" || fileName === "cursorrules") {
+ return { format: "cursor", confidence: "high", reason: "File name .cursorrules" };
+ }
+
+ if (fileName.includes("windsurf") || filePath.includes(".windsurf/")) {
+ return { format: "windsurf", confidence: "high", reason: "Windsurf path pattern" };
+ }
+
+ if (filePath.includes(".claude/") || fileName.includes("claude")) {
+ return { format: "claude", confidence: "high", reason: "Claude path pattern" };
+ }
+
+ if (filePath.includes(".opencode/") || filePath.includes("openagent")) {
+ return { format: "oac", confidence: "high", reason: "OAC path pattern" };
+ }
+
+ // Check content patterns
+ if (ext === ".md") {
+ // Check for OAC frontmatter
+ if (content.startsWith("---")) {
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
+ if (frontmatterMatch?.[1]) {
+ const frontmatter = frontmatterMatch[1];
+ if (frontmatter.includes("mode:") || frontmatter.includes("tools:")) {
+ return { format: "oac", confidence: "high", reason: "OAC frontmatter detected" };
+ }
+ }
+ }
+ // Default markdown to OAC
+ return { format: "oac", confidence: "medium", reason: "Markdown file (assuming OAC)" };
+ }
+
+ if (ext === ".json") {
+ try {
+ const parsed = JSON.parse(content) as ParsedConfig;
+ if (parsed.cascadeConfig || parsed.creativity !== undefined) {
+ return { format: "windsurf", confidence: "high", reason: "Windsurf JSON structure" };
+ }
+ if (parsed.permissions || parsed.skills) {
+ return { format: "claude", confidence: "high", reason: "Claude JSON structure" };
+ }
+ } catch {
+ // Not valid JSON
+ }
+ return { format: "oac", confidence: "low", reason: "JSON file (format unclear)" };
+ }
+
+ // Plain text - likely cursor
+ if (ext === ".txt" || ext === "") {
+ return { format: "cursor", confidence: "medium", reason: "Plain text (assuming Cursor)" };
+ }
+
+ return { format: "oac", confidence: "low", reason: "Unknown format (defaulting to OAC)" };
+};
+
+// ============================================================================
+// FILE DISCOVERY
+// ============================================================================
+
+/**
+ * Supported file extensions for agent files
+ */
+const AGENT_FILE_EXTENSIONS = new Set([".md", ".json", ".txt", ""]);
+
+/**
+ * Check if a file might be an agent configuration file
+ */
+const isAgentFile = (fileName: string): boolean => {
+ const ext = extname(fileName).toLowerCase();
+ const baseName = basename(fileName).toLowerCase();
+
+ // Always include specific agent file names
+ if (baseName === ".cursorrules" || baseName === "cursorrules") {
+ return true;
+ }
+
+ return AGENT_FILE_EXTENSIONS.has(ext);
+};
+
+/**
+ * Recursively discover all agent files in a directory
+ */
+const discoverAgentFiles = async (
+ sourceDir: string,
+ config: LoggerConfig
+): Promise => {
+ const discovered: DiscoveredFile[] = [];
+
+ const scanDirectory = async (currentPath: string): Promise => {
+ const entries = await readdir(currentPath, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = join(currentPath, entry.name);
+
+ // Skip hidden directories (except .opencode, .claude, .windsurf)
+ if (entry.isDirectory()) {
+ const dirName = entry.name.toLowerCase();
+ if (dirName.startsWith(".") &&
+ !["opencode", "claude", "windsurf"].some(d => dirName.includes(d))) {
+ continue;
+ }
+ await scanDirectory(fullPath);
+ } else if (entry.isFile() && isAgentFile(entry.name)) {
+ try {
+ const content = await readFile(fullPath, "utf-8");
+ const detectedFormat = detectInputFormat(fullPath, content);
+
+ discovered.push({
+ path: fullPath,
+ relativePath: relative(sourceDir, fullPath),
+ detectedFormat,
+ });
+
+ logVerbose(config, `Found: ${relative(sourceDir, fullPath)} (${detectedFormat.format})`);
+ } catch (err) {
+ logVerbose(config, `Skipping unreadable file: ${fullPath}`);
+ }
+ }
+ }
+ };
+
+ await scanDirectory(sourceDir);
+ return discovered;
+};
+
+// ============================================================================
+// CONVERSION LOGIC
+// ============================================================================
+
+/**
+ * Load an agent from any supported format
+ */
+const loadAgentFromFormat = async (
+ filePath: string,
+ content: string,
+ format: Platform,
+ config: LoggerConfig
+): Promise => {
+ logVerbose(config, `Loading agent from ${format} format`);
+
+ if (format === "oac") {
+ // Use AgentLoader for OAC format
+ const { loadAgent } = await getAgentLoader();
+ return await loadAgent(filePath);
+ }
+
+ // Use appropriate adapter for other formats
+ const { getAdapter } = await getAdapterRegistry();
+ const adapter = getAdapter(format);
+ if (!adapter) {
+ throw new Error(`No adapter found for format: ${format}`);
+ }
+ return await adapter.toOAC(content);
+};
+
+/**
+ * Convert an agent to the target format
+ */
+const convertToFormat = async (
+ agent: OpenAgent,
+ targetFormat: Platform,
+ config: LoggerConfig
+): Promise => {
+ logVerbose(config, `Converting to ${targetFormat} format`);
+
+ if (targetFormat === "oac") {
+ // Converting TO OAC - just serialize the agent
+ const content = serializeOACAgent(agent);
+ return {
+ success: true,
+ configs: [
+ {
+ fileName: `${agent.frontmatter.name || "agent"}.md`,
+ content,
+ encoding: "utf-8",
+ },
+ ],
+ warnings: [],
+ };
+ }
+
+ // Use adapter for other formats
+ const { getAdapter } = await getAdapterRegistry();
+ const adapter = getAdapter(targetFormat);
+ if (!adapter) {
+ throw new Error(`No adapter found for format: ${targetFormat}`);
+ }
+ return await adapter.fromOAC(agent);
+};
+
+/**
+ * Serialize an OpenAgent to OAC markdown format
+ */
+const serializeOACAgent = (agent: OpenAgent): string => {
+ const lines: string[] = ["---"];
+
+ // Serialize frontmatter
+ const fm = agent.frontmatter;
+ lines.push(`name: "${fm.name}"`);
+ if (fm.description) lines.push(`description: "${fm.description}"`);
+ lines.push(`mode: ${fm.mode}`);
+ if (fm.model) lines.push(`model: ${fm.model}`);
+ if (fm.temperature !== undefined) lines.push(`temperature: ${fm.temperature}`);
+
+ // Tools
+ if (fm.tools) {
+ lines.push("tools:");
+ for (const [tool, access] of Object.entries(fm.tools)) {
+ const accessValue = access as boolean | string;
+ if (typeof accessValue === "boolean") {
+ lines.push(` ${tool}: ${String(accessValue)}`);
+ } else {
+ lines.push(` ${tool}: "${String(accessValue)}"`);
+ }
+ }
+ }
+
+ lines.push("---");
+ lines.push("");
+ lines.push(agent.systemPrompt);
+
+ return lines.join("\n");
+};
+
+/**
+ * Determine output path for a migrated file
+ */
+const getOutputPath = (
+ relativePath: string,
+ outputDir: string,
+ result: ConversionResult
+): string => {
+ const baseName = basename(relativePath, extname(relativePath));
+ const primaryConfig = result.configs[0];
+ const outputFileName = primaryConfig?.fileName || `${baseName}.md`;
+
+ // Preserve directory structure
+ const relativeDir = dirname(relativePath);
+ const targetDir = relativeDir === "." ? outputDir : join(outputDir, relativeDir);
+
+ return join(targetDir, outputFileName);
+};
+
+/**
+ * Check if file exists
+ */
+const fileExists = async (filePath: string): Promise => {
+ try {
+ await access(filePath);
+ return true;
+ } catch {
+ return false;
+ }
+};
+
+// ============================================================================
+// SINGLE FILE MIGRATION
+// ============================================================================
+
+/**
+ * Migrate a single file to the target format
+ */
+const migrateFile = async (
+ file: DiscoveredFile,
+ targetFormat: Platform,
+ outputDir: string,
+ options: { dryRun: boolean; force: boolean },
+ config: LoggerConfig
+): Promise => {
+ const { path: sourcePath, relativePath, detectedFormat } = file;
+
+ // Skip if source and target format are the same
+ if (detectedFormat.format === targetFormat) {
+ return {
+ source: relativePath,
+ destination: "",
+ status: "skipped",
+ warnings: [`Already in ${targetFormat} format`],
+ };
+ }
+
+ try {
+ // Read source file
+ const content = await readFile(sourcePath, "utf-8");
+
+ // Load agent from source format
+ const agent = await loadAgentFromFormat(
+ sourcePath,
+ content,
+ detectedFormat.format,
+ config
+ );
+
+ // Convert to target format
+ const result = await convertToFormat(agent, targetFormat, config);
+
+ if (!result.success) {
+ return {
+ source: relativePath,
+ destination: "",
+ status: "failed",
+ error: result.errors?.join(", ") || "Unknown conversion error",
+ warnings: result.warnings,
+ };
+ }
+
+ // Determine output path
+ const outputPath = getOutputPath(relativePath, outputDir, result);
+ const relativeOutputPath = relative(outputDir, outputPath);
+
+ // Check if output file exists
+ if (!options.force && await fileExists(outputPath)) {
+ return {
+ source: relativePath,
+ destination: relativeOutputPath,
+ status: "skipped",
+ warnings: ["Output file already exists (use --force to overwrite)"],
+ };
+ }
+
+ // Write output (unless dry run)
+ if (!options.dryRun) {
+ const dir = dirname(outputPath);
+ await mkdir(dir, { recursive: true });
+
+ const primaryConfig = result.configs[0];
+ if (primaryConfig) {
+ await writeFile(outputPath, primaryConfig.content, "utf-8");
+ }
+
+ // Write additional files if present
+ for (let i = 1; i < result.configs.length; i++) {
+ const additionalConfig = result.configs[i];
+ if (additionalConfig) {
+ const additionalPath = join(dirname(outputPath), additionalConfig.fileName);
+ await writeFile(additionalPath, additionalConfig.content, "utf-8");
+ }
+ }
+ }
+
+ return {
+ source: relativePath,
+ destination: relativeOutputPath,
+ status: "success",
+ warnings: result.warnings.length > 0 ? result.warnings : undefined,
+ };
+ } catch (err) {
+ const message = err instanceof Error ? err.message : String(err);
+ return {
+ source: relativePath,
+ destination: "",
+ status: "failed",
+ error: message,
+ };
+ }
+};
+
+// ============================================================================
+// REPORT GENERATION
+// ============================================================================
+
+/**
+ * Create initial empty migration report
+ */
+const createEmptyReport = (): MigrationReport => ({
+ total: 0,
+ successful: 0,
+ failed: 0,
+ skipped: 0,
+ warnings: [],
+ files: [],
+});
+
+/**
+ * Aggregate migration results into a report
+ */
+const aggregateResults = (files: MigratedFile[]): MigrationReport => {
+ const report = createEmptyReport();
+ report.total = files.length;
+ report.files = files;
+
+ for (const file of files) {
+ switch (file.status) {
+ case "success":
+ report.successful++;
+ break;
+ case "failed":
+ report.failed++;
+ break;
+ case "skipped":
+ report.skipped++;
+ break;
+ }
+
+ // Collect warnings
+ if (file.warnings) {
+ for (const warning of file.warnings) {
+ report.warnings.push(`${file.source}: ${warning}`);
+ }
+ }
+
+ // Collect errors as warnings too for summary
+ if (file.error) {
+ report.warnings.push(`${file.source}: ${file.error}`);
+ }
+ }
+
+ return report;
+};
+
+/**
+ * Print migration report to console
+ */
+const printReport = (
+ report: MigrationReport,
+ config: LoggerConfig,
+ dryRun: boolean
+): void => {
+ const prefix = dryRun ? "[DRY RUN] " : "";
+
+ logInfo(config, "");
+ logInfo(config, `${prefix}Migration Summary:`);
+ logInfo(config, ` Total files: ${report.total}`);
+ logSuccess(config, `Successful: ${report.successful}`);
+
+ if (report.skipped > 0) {
+ logWarning(config, `Skipped: ${report.skipped}`);
+ }
+
+ if (report.failed > 0) {
+ logError(config, `Failed: ${report.failed}`);
+ }
+
+ // Show file details
+ if (config.verbose || report.failed > 0) {
+ logInfo(config, "");
+ logInfo(config, "File Details:");
+
+ for (const file of report.files) {
+ const statusIcon = getStatusIcon(file.status);
+ const destination = file.destination ? ` -> ${file.destination}` : "";
+ logInfo(config, ` ${statusIcon} ${file.source}${destination}`);
+
+ if (file.error) {
+ logError(config, ` Error: ${file.error}`);
+ }
+ }
+ }
+
+ // Show warnings summary
+ if (report.warnings.length > 0 && config.verbose) {
+ logInfo(config, "");
+ logWarning(config, `${report.warnings.length} warning(s) occurred during migration`);
+ }
+};
+
+/**
+ * Get status icon for display
+ */
+const getStatusIcon = (status: MigratedFileStatus): string => {
+ switch (status) {
+ case "success":
+ return "\u2713"; // checkmark
+ case "failed":
+ return "\u2717"; // X mark
+ case "skipped":
+ return "-";
+ }
+};
+
+// ============================================================================
+// COMMAND HANDLER
+// ============================================================================
+
+/**
+ * Execute the migrate command
+ */
+export const executeMigrate = async (
+ sourceDir: string,
+ options: MigrateCommandOptions,
+ globalOptions: GlobalOptions
+): Promise> => {
+ const config = createLoggerConfig(globalOptions);
+
+ try {
+ // Validate source directory exists
+ try {
+ const stats = await stat(sourceDir);
+ if (!stats.isDirectory()) {
+ return errorResult(`Source path is not a directory: ${sourceDir}`);
+ }
+ } catch {
+ return errorResult(`Source directory not found: ${sourceDir}`);
+ }
+
+ // Determine output directory
+ const outputDir = options.outDir
+ ? resolve(options.outDir)
+ : resolve(sourceDir, `migrated-${options.format}`);
+
+ logVerbose(config, `Source directory: ${sourceDir}`);
+ logVerbose(config, `Output directory: ${outputDir}`);
+ logVerbose(config, `Target format: ${options.format}`);
+ if (options.dryRun) {
+ logInfo(config, "Dry run mode - no files will be written");
+ }
+
+ // Discover agent files
+ const discoverySpinner = createSpinner(config, "Discovering agent files...");
+ const discoveredFiles = await discoverAgentFiles(sourceDir, config);
+
+ if (discoveredFiles.length === 0) {
+ spinnerWarn(discoverySpinner, "No agent files found");
+ const emptyReport = createEmptyReport();
+ emptyReport.warnings = ["No agent files found in source directory"];
+ return successResult(emptyReport, ["No agent files found in source directory"]);
+ }
+
+ spinnerSuccess(discoverySpinner, `Found ${discoveredFiles.length} agent file(s)`);
+
+ // Create output directory (unless dry run)
+ if (!options.dryRun) {
+ await mkdir(outputDir, { recursive: true });
+ }
+
+ // Migrate files
+ const migrationSpinner = createSpinner(
+ config,
+ `Migrating ${discoveredFiles.length} file(s) to ${options.format}...`
+ );
+
+ const migratedFiles: MigratedFile[] = [];
+
+ for (const file of discoveredFiles) {
+ const result = await migrateFile(
+ file,
+ options.format,
+ outputDir,
+ {
+ dryRun: options.dryRun ?? false,
+ force: options.force ?? false,
+ },
+ config
+ );
+ migratedFiles.push(result);
+ }
+
+ // Generate report
+ const report = aggregateResults(migratedFiles);
+
+ // Update spinner based on results
+ if (report.failed > 0) {
+ spinnerFail(migrationSpinner, `Migration completed with ${report.failed} error(s)`);
+ } else if (report.skipped === report.total) {
+ spinnerWarn(migrationSpinner, "All files skipped");
+ } else {
+ spinnerSuccess(
+ migrationSpinner,
+ options.dryRun
+ ? `Would migrate ${report.successful} file(s)`
+ : `Migrated ${report.successful} file(s)`
+ );
+ }
+
+ // Print report
+ printReport(report, config, options.dryRun ?? false);
+
+ // Return result
+ const hasErrors = report.failed > 0;
+ return hasErrors
+ ? errorResult(`Migration completed with ${report.failed} error(s)`, report.warnings)
+ : successResult(report, report.warnings.length > 0 ? report.warnings : undefined);
+ } catch (err) {
+ return handleError(config, err);
+ }
+};
+
+/**
+ * Create the migrate command action handler
+ */
+export const createMigrateAction = (): ((
+ source: string,
+ options: MigrateCommandOptions,
+ command: Command
+) => Promise) => {
+ return async (
+ source: string,
+ options: MigrateCommandOptions,
+ command: Command
+ ): Promise => {
+ const globalOptions = command.optsWithGlobals() as unknown as GlobalOptions;
+ const result = await executeMigrate(source, options, globalOptions);
+
+ if (globalOptions.outputFormat === "json") {
+ printOutput(createLoggerConfig(globalOptions), result);
+ }
+
+ if (!result.success) {
+ process.exit(1);
+ }
+ };
+};
diff --git a/packages/compatibility-layer/src/cli/commands/validate.ts b/packages/compatibility-layer/src/cli/commands/validate.ts
new file mode 100644
index 0000000..7ea0bad
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/commands/validate.ts
@@ -0,0 +1,427 @@
+/**
+ * Validate Command
+ *
+ * Validates agent compatibility with a target format before conversion.
+ * Reports compatibility score, feature parity, and potential issues.
+ *
+ * @module cli/commands/validate
+ */
+
+import { readFile } from "fs/promises";
+import type { Command } from "commander";
+import type { OpenAgent } from "../../types.js";
+import {
+ type GlobalOptions,
+ type Platform,
+ successResult,
+ errorResult,
+ type CommandResult,
+} from "../types.js";
+import {
+ createLoggerConfig,
+ logInfo,
+ logSuccess,
+ logError,
+ logVerbose,
+ createSpinner,
+ spinnerSuccess,
+ spinnerFail,
+ spinnerWarn,
+ printOutput,
+ handleError,
+ getPlatformDisplayName,
+ type LoggerConfig,
+} from "../utils.js";
+import chalk from "chalk";
+
+// Dynamic imports to avoid ESM resolution issues at startup
+const getAgentLoader = async () => {
+ const module = await import("../../core/AgentLoader.js");
+ return module;
+};
+
+const getCapabilityMatrix = async () => {
+ const module = await import("../../core/CapabilityMatrix.js");
+ return module;
+};
+
+// ============================================================================
+// TYPES
+// ============================================================================
+
+/**
+ * Options specific to the validate command
+ */
+interface ValidateCommandOptions {
+ target?: Platform;
+ strict?: boolean;
+}
+
+/**
+ * Validation result data
+ */
+interface ValidationResultData {
+ /** Input file path */
+ input: string;
+ /** Target platform */
+ target: Platform;
+ /** Compatibility score (0-100) */
+ score: number;
+ /** Overall compatibility status */
+ status: "compatible" | "partial" | "incompatible";
+ /** Whether conversion is possible */
+ canConvert: boolean;
+ /** Features that will be preserved */
+ preserved: string[];
+ /** Features that will be degraded */
+ degraded: string[];
+ /** Features that will be lost */
+ lost: string[];
+ /** Blocking issues */
+ blockers: string[];
+ /** Suggestions for unsupported features */
+ suggestions: string[];
+}
+
+/**
+ * Exit codes for validation
+ */
+const EXIT_CODES = {
+ COMPATIBLE: 0,
+ PARTIAL: 1,
+ INCOMPATIBLE: 2,
+} as const;
+
+// ============================================================================
+// SUGGESTION MAPPINGS
+// ============================================================================
+
+/**
+ * Suggestions for features that are lost or degraded
+ */
+const FEATURE_SUGGESTIONS: Record> = {
+ cursor: {
+ granularPermissions: "Use .cursorrules sections to document expected permissions",
+ hooks: "Implement hook-like behavior in custom cursor rules or scripts",
+ externalContext: "Inline all context content directly in .cursorrules",
+ contextPriority: "Order context sections by importance in .cursorrules",
+ multipleAgents: "Merge agent behaviors into a single comprehensive .cursorrules",
+ skillsSystem: "Document skill instructions inline in the rules file",
+ askPermissions: "Configure permissions at IDE settings level",
+ maxSteps: "Use cursor settings to manage execution limits",
+ },
+ claude: {
+ temperatureControl: "Temperature is managed by Claude Code settings",
+ maxSteps: "Execution limits managed by Claude Code runtime",
+ contextPriority: "Order context files alphabetically or by naming convention",
+ },
+ windsurf: {
+ granularPermissions: "Use cascadeConfig for workspace-level permissions",
+ hooks: "Implement workflows using Windsurf's flow system",
+ askPermissions: "Configure permissions in Windsurf settings",
+ },
+};
+
+/**
+ * Get suggestions for a feature on a platform
+ */
+const getSuggestion = (feature: string, platform: Platform): string | null => {
+ return FEATURE_SUGGESTIONS[platform]?.[feature] ?? null;
+};
+
+// ============================================================================
+// VALIDATION LOGIC
+// ============================================================================
+
+/**
+ * Load an agent from OAC format
+ */
+const loadAgent = async (
+ filePath: string,
+ config: LoggerConfig
+): Promise => {
+ logVerbose(config, `Loading agent from: ${filePath}`);
+ const { loadAgent } = await getAgentLoader();
+ return await loadAgent(filePath);
+};
+
+/**
+ * Analyze compatibility and generate validation result
+ */
+const analyzeAgent = async (
+ agent: OpenAgent,
+ target: Platform,
+ filePath: string,
+ strict: boolean
+): Promise => {
+ const { analyzeCompatibility } = await getCapabilityMatrix();
+
+ // Analyze compatibility (target must not be 'oac')
+ const targetPlatform = target as Exclude;
+ const analysis = analyzeCompatibility(agent, targetPlatform);
+
+ // Generate suggestions for lost/degraded features
+ const suggestions: string[] = [];
+ for (const feature of [...analysis.lost, ...analysis.degraded]) {
+ const suggestion = getSuggestion(feature, target);
+ if (suggestion) {
+ suggestions.push(`${feature}: ${suggestion}`);
+ }
+ }
+
+ // Determine status
+ let status: "compatible" | "partial" | "incompatible";
+ if (analysis.blockers.length > 0 || (strict && analysis.lost.length > 0)) {
+ status = "incompatible";
+ } else if (analysis.degraded.length > 0 || analysis.lost.length > 0) {
+ status = "partial";
+ } else {
+ status = "compatible";
+ }
+
+ return {
+ input: filePath,
+ target,
+ score: analysis.score,
+ status,
+ canConvert: analysis.blockers.length === 0,
+ preserved: analysis.preserved,
+ degraded: analysis.degraded,
+ lost: analysis.lost,
+ blockers: analysis.blockers,
+ suggestions,
+ };
+};
+
+/**
+ * Format feature list for display
+ */
+const formatFeatureList = (
+ features: string[],
+ icon: string,
+ color: (s: string) => string
+): string[] => {
+ if (features.length === 0) return [];
+ return features.map((f) => ` ${icon} ${color(f)}`);
+};
+
+/**
+ * Print validation result in text format
+ */
+const printTextResult = (
+ config: LoggerConfig,
+ result: ValidationResultData
+): void => {
+ const targetName = getPlatformDisplayName(result.target);
+
+ // Header
+ logInfo(config, "");
+ logInfo(config, chalk.bold(`Validation Report: ${result.input}`));
+ logInfo(config, chalk.dim(`Target: ${targetName}`));
+ logInfo(config, "");
+
+ // Score bar
+ const scoreColor =
+ result.score >= 80
+ ? chalk.green
+ : result.score >= 50
+ ? chalk.yellow
+ : chalk.red;
+ const scoreBar = "█".repeat(Math.floor(result.score / 5)) +
+ "░".repeat(20 - Math.floor(result.score / 5));
+ logInfo(config, `Compatibility Score: ${scoreColor(`${result.score}%`)} [${scoreBar}]`);
+ logInfo(config, "");
+
+ // Status
+ const statusIcon =
+ result.status === "compatible"
+ ? chalk.green("✓")
+ : result.status === "partial"
+ ? chalk.yellow("◐")
+ : chalk.red("✗");
+ const statusText =
+ result.status === "compatible"
+ ? chalk.green("Fully Compatible")
+ : result.status === "partial"
+ ? chalk.yellow("Partially Compatible")
+ : chalk.red("Incompatible");
+ logInfo(config, `Status: ${statusIcon} ${statusText}`);
+ logInfo(config, "");
+
+ // Blockers (if any)
+ if (result.blockers.length > 0) {
+ logInfo(config, chalk.red.bold("Blocking Issues:"));
+ for (const blocker of result.blockers) {
+ logInfo(config, ` ${chalk.red("✗")} ${blocker}`);
+ }
+ logInfo(config, "");
+ }
+
+ // Feature breakdown
+ if (result.preserved.length > 0) {
+ logInfo(config, chalk.green.bold("Preserved Features:"));
+ for (const line of formatFeatureList(result.preserved, "✓", chalk.green)) {
+ logInfo(config, line);
+ }
+ logInfo(config, "");
+ }
+
+ if (result.degraded.length > 0) {
+ logInfo(config, chalk.yellow.bold("Degraded Features:"));
+ for (const line of formatFeatureList(result.degraded, "◐", chalk.yellow)) {
+ logInfo(config, line);
+ }
+ logInfo(config, "");
+ }
+
+ if (result.lost.length > 0) {
+ logInfo(config, chalk.red.bold("Lost Features:"));
+ for (const line of formatFeatureList(result.lost, "✗", chalk.red)) {
+ logInfo(config, line);
+ }
+ logInfo(config, "");
+ }
+
+ // Suggestions
+ if (result.suggestions.length > 0) {
+ logInfo(config, chalk.cyan.bold("Suggestions:"));
+ for (const suggestion of result.suggestions) {
+ logInfo(config, ` ${chalk.cyan("→")} ${suggestion}`);
+ }
+ logInfo(config, "");
+ }
+
+ // Summary
+ if (result.canConvert) {
+ logSuccess(config, `Conversion to ${targetName} is possible`);
+ } else {
+ logError(config, `Conversion to ${targetName} is blocked`);
+ }
+};
+
+// ============================================================================
+// COMMAND HANDLER
+// ============================================================================
+
+/**
+ * Execute the validate command
+ */
+export const executeValidate = async (
+ input: string,
+ options: ValidateCommandOptions,
+ globalOptions: GlobalOptions
+): Promise> => {
+ const config = createLoggerConfig(globalOptions);
+
+ try {
+ // Validate target is specified
+ if (!options.target) {
+ return errorResult(
+ "Target format is required. Use --target to specify."
+ );
+ }
+
+ // Cannot validate against OAC (source format)
+ if (options.target === "oac") {
+ return errorResult(
+ "Cannot validate against OAC format. Specify a target platform: cursor, claude, or windsurf."
+ );
+ }
+
+ // Check file exists
+ logVerbose(config, `Validating file: ${input}`);
+ try {
+ await readFile(input, "utf-8");
+ } catch {
+ return errorResult(`File not found or not readable: ${input}`);
+ }
+
+ // Create spinner
+ const spinner = createSpinner(
+ config,
+ `Validating compatibility with ${getPlatformDisplayName(options.target)}...`
+ );
+
+ try {
+ // Load agent
+ const agent = await loadAgent(input, config);
+ logVerbose(config, `Loaded agent: ${agent.frontmatter.name}`);
+
+ // Analyze compatibility
+ const result = await analyzeAgent(
+ agent,
+ options.target,
+ input,
+ options.strict ?? false
+ );
+
+ // Update spinner based on result
+ if (result.status === "compatible") {
+ spinnerSuccess(spinner, "Validation complete - Fully compatible");
+ } else if (result.status === "partial") {
+ spinnerWarn(spinner, "Validation complete - Partially compatible");
+ } else {
+ spinnerFail(spinner, "Validation complete - Incompatible");
+ }
+
+ // Print detailed result (text format)
+ if (globalOptions.outputFormat !== "json") {
+ printTextResult(config, result);
+ }
+
+ return successResult(result);
+ } catch (err) {
+ spinnerFail(spinner, "Validation failed");
+ throw err;
+ }
+ } catch (err) {
+ return handleError(config, err);
+ }
+};
+
+/**
+ * Get exit code based on validation result
+ */
+export const getValidationExitCode = (
+ result: CommandResult
+): number => {
+ if (!result.success || !result.data) {
+ return EXIT_CODES.INCOMPATIBLE;
+ }
+
+ switch (result.data.status) {
+ case "compatible":
+ return EXIT_CODES.COMPATIBLE;
+ case "partial":
+ return EXIT_CODES.PARTIAL;
+ case "incompatible":
+ return EXIT_CODES.INCOMPATIBLE;
+ }
+};
+
+/**
+ * Create the validate command action handler
+ */
+export const createValidateAction = (): ((
+ input: string,
+ options: ValidateCommandOptions,
+ command: Command
+) => Promise) => {
+ return async (
+ input: string,
+ options: ValidateCommandOptions,
+ command: Command
+ ): Promise => {
+ const globalOptions = command.optsWithGlobals() as unknown as GlobalOptions;
+ const result = await executeValidate(input, options, globalOptions);
+
+ // Print JSON output if requested
+ if (globalOptions.outputFormat === "json") {
+ printOutput(createLoggerConfig(globalOptions), result);
+ }
+
+ // Exit with appropriate code
+ const exitCode = getValidationExitCode(result);
+ process.exit(exitCode);
+ };
+};
diff --git a/packages/compatibility-layer/src/cli/index.ts b/packages/compatibility-layer/src/cli/index.ts
new file mode 100644
index 0000000..a6ab54d
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/index.ts
@@ -0,0 +1,209 @@
+#!/usr/bin/env node
+/**
+ * oac-compat CLI
+ *
+ * Command-line interface for converting agent definitions between
+ * AI coding tool formats (OAC, Cursor, Claude, Windsurf).
+ *
+ * @module cli
+ *
+ * @example
+ * ```bash
+ * # Convert an agent file
+ * oac-compat convert agent.md --format cursor
+ *
+ * # Validate compatibility
+ * oac-compat validate agent.md --target claude
+ *
+ * # Migrate a project
+ * oac-compat migrate ./agents --format windsurf --out-dir ./output
+ *
+ * # Show tool capabilities
+ * oac-compat info cursor --compare claude
+ * ```
+ */
+
+import { Command, Option } from "commander";
+import { readFileSync } from "fs";
+import { fileURLToPath } from "url";
+import { dirname, join } from "path";
+import {
+ OUTPUT_FORMATS,
+ PLATFORMS,
+ type OutputFormat,
+} from "./types.js";
+import {
+ logError,
+} from "./utils.js";
+import { createConvertAction } from "./commands/convert.js";
+import { createValidateAction } from "./commands/validate.js";
+import { createInfoAction } from "./commands/info.js";
+import { createMigrateAction } from "./commands/migrate.js";
+
+// Read version from package.json
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+const packageJsonPath = join(__dirname, "..", "..", "package.json");
+const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8")) as { version: string };
+const VERSION = packageJson.version;
+
+// ============================================================================
+// PROGRAM SETUP
+// ============================================================================
+
+/**
+ * Create and configure the main CLI program
+ */
+const createProgram = (): Command => {
+ const program = new Command();
+
+ program
+ .name("oac-compat")
+ .description(
+ "Convert agent definitions between AI coding tool formats\n\n" +
+ "Supported formats: OAC (OpenAgents Control), Cursor, Claude Code, Windsurf"
+ )
+ .version(VERSION, "-V, --version", "display version number")
+ .helpOption("-h, --help", "display help for command")
+ .configureHelp({ showGlobalOptions: true });
+
+ // Global options
+ program
+ .option("-v, --verbose", "enable verbose output")
+ .option("-q, --quiet", "suppress all output except errors")
+ .addOption(
+ new Option("--output-format ", "output format")
+ .choices(OUTPUT_FORMATS as unknown as string[])
+ .default("text" as OutputFormat)
+ );
+
+ // Validate mutually exclusive options
+ program.hook("preAction", (thisCommand) => {
+ const opts = thisCommand.opts();
+ if (opts.verbose && opts.quiet) {
+ console.error("Error: --verbose and --quiet are mutually exclusive");
+ process.exit(1);
+ }
+ });
+
+ return program;
+};
+
+// ============================================================================
+// COMMANDS
+// ============================================================================
+
+/**
+ * Register the 'convert' command
+ */
+const registerConvertCommand = (program: Command): void => {
+ program
+ .command("convert")
+ .description("Convert an agent file to another format")
+ .argument(" ", "input agent file path")
+ .option("-o, --output ", "output file path")
+ .addOption(
+ new Option("-f, --format ", "target format")
+ .choices(PLATFORMS as unknown as string[])
+ .makeOptionMandatory()
+ )
+ .addOption(
+ new Option("--from ", "source format (auto-detect if omitted)")
+ .choices(PLATFORMS as unknown as string[])
+ )
+ .option("--force", "overwrite existing output file")
+ .action(createConvertAction());
+};
+
+/**
+ * Register the 'validate' command
+ */
+const registerValidateCommand = (program: Command): void => {
+ program
+ .command("validate")
+ .description("Validate agent compatibility with a target format")
+ .argument(" ", "input agent file path")
+ .addOption(
+ new Option("-t, --target ", "target format to validate against")
+ .choices(PLATFORMS as unknown as string[])
+ .makeOptionMandatory()
+ )
+ .option("--strict", "enable strict validation mode")
+ .action(createValidateAction());
+};
+
+/**
+ * Register the 'migrate' command
+ */
+const registerMigrateCommand = (program: Command): void => {
+ program
+ .command("migrate")
+ .description("Migrate an entire project to another format")
+ .argument("", "source directory path")
+ .addOption(
+ new Option("-f, --format ", "target format")
+ .choices(PLATFORMS as unknown as string[])
+ .makeOptionMandatory()
+ )
+ .option("-o, --out-dir ", "output directory path")
+ .option("--dry-run", "simulate migration without making changes")
+ .option("--force", "overwrite existing files")
+ .action(createMigrateAction());
+};
+
+/**
+ * Register the 'info' command
+ */
+const registerInfoCommand = (program: Command): void => {
+ program
+ .command("info")
+ .description("Display tool capabilities and feature parity")
+ .argument("[platform]", "platform to show info for")
+ .option("-d, --detailed", "show detailed capability information")
+ .addOption(
+ new Option("-c, --compare ", "compare with another platform")
+ .choices(PLATFORMS as unknown as string[])
+ )
+ .action(createInfoAction());
+};
+
+// ============================================================================
+// MAIN
+// ============================================================================
+
+/**
+ * Main CLI entry point
+ */
+const main = async (): Promise => {
+ const program = createProgram();
+
+ // Register all commands
+ registerConvertCommand(program);
+ registerValidateCommand(program);
+ registerMigrateCommand(program);
+ registerInfoCommand(program);
+
+ // Add examples to help
+ program.addHelpText(
+ "after",
+ `
+Examples:
+ $ oac-compat convert agent.md -f cursor -o cursor-rules.md
+ $ oac-compat validate agent.md -t claude --strict
+ $ oac-compat migrate ./agents -f windsurf --dry-run
+ $ oac-compat info cursor --compare claude
+ $ oac-compat --help
+`
+ );
+
+ try {
+ await program.parseAsync(process.argv);
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ logError({ verbose: false, quiet: false, outputFormat: "text" }, message);
+ process.exit(1);
+ }
+};
+
+// Run CLI
+void main();
diff --git a/packages/compatibility-layer/src/cli/types.ts b/packages/compatibility-layer/src/cli/types.ts
new file mode 100644
index 0000000..4ff3334
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/types.ts
@@ -0,0 +1,182 @@
+/**
+ * CLI Type Definitions
+ *
+ * Type definitions for the oac-compat CLI tool.
+ * Follows functional patterns with explicit types.
+ *
+ * @module cli/types
+ */
+
+// ============================================================================
+// OUTPUT FORMATS
+// ============================================================================
+
+/**
+ * Supported output formats for CLI commands
+ */
+export type OutputFormat = "text" | "json";
+
+/**
+ * Valid output format values (for validation)
+ */
+export const OUTPUT_FORMATS: readonly OutputFormat[] = ["text", "json"] as const;
+
+/**
+ * Check if a string is a valid output format
+ */
+export const isValidOutputFormat = (value: string): value is OutputFormat =>
+ OUTPUT_FORMATS.includes(value as OutputFormat);
+
+// ============================================================================
+// GLOBAL OPTIONS
+// ============================================================================
+
+/**
+ * Global CLI options available to all commands
+ */
+export interface GlobalOptions {
+ /** Enable verbose output */
+ readonly verbose?: boolean;
+ /** Suppress all output except errors */
+ readonly quiet?: boolean;
+ /** Output format (text or json) */
+ readonly outputFormat: OutputFormat;
+}
+
+/**
+ * Default global options
+ */
+export const DEFAULT_GLOBAL_OPTIONS: GlobalOptions = {
+ verbose: false,
+ quiet: false,
+ outputFormat: "text",
+} as const;
+
+// ============================================================================
+// COMMAND OPTIONS
+// ============================================================================
+
+/**
+ * Options for the 'convert' command
+ */
+export interface ConvertOptions extends GlobalOptions {
+ /** Output file path */
+ readonly output?: string;
+ /** Target format (cursor, claude, windsurf, oac) */
+ readonly format?: string;
+ /** Source format (auto-detect if not specified) */
+ readonly from?: string;
+ /** Overwrite existing files */
+ readonly force?: boolean;
+}
+
+/**
+ * Options for the 'validate' command
+ */
+export interface ValidateOptions extends GlobalOptions {
+ /** Target platform to validate against */
+ readonly target?: string;
+ /** Enable strict validation */
+ readonly strict?: boolean;
+}
+
+/**
+ * Options for the 'migrate' command
+ */
+export interface MigrateOptions extends GlobalOptions {
+ /** Target format for migration */
+ readonly format?: string;
+ /** Output directory */
+ readonly outDir?: string;
+ /** Simulate migration without changes */
+ readonly dryRun?: boolean;
+ /** Overwrite existing files */
+ readonly force?: boolean;
+}
+
+/**
+ * Options for the 'info' command
+ */
+export interface InfoOptions extends GlobalOptions {
+ /** Show detailed information */
+ readonly detailed?: boolean;
+ /** Compare with another platform */
+ readonly compare?: string;
+}
+
+// ============================================================================
+// RESULT TYPES
+// ============================================================================
+
+/**
+ * CLI command result for consistent output handling
+ */
+export interface CommandResult {
+ /** Whether the command succeeded */
+ readonly success: boolean;
+ /** Result data (if success) */
+ readonly data?: T;
+ /** Error message (if failure) */
+ readonly error?: string;
+ /** Warning messages */
+ readonly warnings?: readonly string[];
+}
+
+/**
+ * Create a success result
+ */
+export const successResult = (
+ data: T,
+ warnings?: readonly string[]
+): CommandResult => ({
+ success: true,
+ data,
+ warnings,
+});
+
+/**
+ * Create an error result
+ */
+export const errorResult = (
+ error: string,
+ warnings?: readonly string[]
+): CommandResult => ({
+ success: false,
+ error,
+ warnings,
+});
+
+// ============================================================================
+// SUPPORTED PLATFORMS
+// ============================================================================
+
+/**
+ * Supported AI coding tool platforms
+ */
+export type Platform = "oac" | "cursor" | "claude" | "windsurf";
+
+/**
+ * All supported platforms
+ */
+export const PLATFORMS: readonly Platform[] = [
+ "oac",
+ "cursor",
+ "claude",
+ "windsurf",
+] as const;
+
+/**
+ * Platform display names
+ */
+export const PLATFORM_NAMES: Record = {
+ oac: "OpenAgents Control",
+ cursor: "Cursor IDE",
+ claude: "Claude Code",
+ windsurf: "Windsurf",
+} as const;
+
+/**
+ * Check if a string is a valid platform
+ */
+export const isValidPlatform = (value: string): value is Platform =>
+ PLATFORMS.includes(value as Platform);
diff --git a/packages/compatibility-layer/src/cli/utils.ts b/packages/compatibility-layer/src/cli/utils.ts
new file mode 100644
index 0000000..5c380f5
--- /dev/null
+++ b/packages/compatibility-layer/src/cli/utils.ts
@@ -0,0 +1,274 @@
+/**
+ * CLI Utility Functions
+ *
+ * Pure utility functions for consistent CLI output and formatting.
+ * Follows functional patterns with no side effects in core functions.
+ *
+ * @module cli/utils
+ */
+
+import chalk from "chalk";
+import ora, { type Ora } from "ora";
+import type { GlobalOptions, OutputFormat, CommandResult, Platform } from "./types.js";
+import { PLATFORM_NAMES } from "./types.js";
+
+// ============================================================================
+// LOGGING
+// ============================================================================
+
+/**
+ * Logger configuration based on global options
+ */
+export interface LoggerConfig {
+ readonly verbose: boolean;
+ readonly quiet: boolean;
+ readonly outputFormat: OutputFormat;
+}
+
+/**
+ * Create logger config from global options
+ */
+export const createLoggerConfig = (options: GlobalOptions): LoggerConfig => ({
+ verbose: options.verbose ?? false,
+ quiet: options.quiet ?? false,
+ outputFormat: options.outputFormat ?? "text",
+});
+
+/**
+ * Log an info message (respects quiet mode)
+ */
+export const logInfo = (config: LoggerConfig, message: string): void => {
+ if (config.quiet) return;
+ if (config.outputFormat === "json") return;
+ console.log(message);
+};
+
+/**
+ * Log a success message (respects quiet mode)
+ */
+export const logSuccess = (config: LoggerConfig, message: string): void => {
+ if (config.quiet) return;
+ if (config.outputFormat === "json") return;
+ console.log(chalk.green("✓"), message);
+};
+
+/**
+ * Log a warning message (respects quiet mode)
+ */
+export const logWarning = (config: LoggerConfig, message: string): void => {
+ if (config.quiet) return;
+ if (config.outputFormat === "json") return;
+ console.log(chalk.yellow("⚠"), message);
+};
+
+/**
+ * Log an error message (always shown)
+ */
+export const logError = (config: LoggerConfig, message: string): void => {
+ if (config.outputFormat === "json") return;
+ console.error(chalk.red("✗"), message);
+};
+
+/**
+ * Log a verbose message (only in verbose mode)
+ */
+export const logVerbose = (config: LoggerConfig, message: string): void => {
+ if (!config.verbose || config.quiet) return;
+ if (config.outputFormat === "json") return;
+ console.log(chalk.dim("›"), chalk.dim(message));
+};
+
+// ============================================================================
+// SPINNER
+// ============================================================================
+
+/**
+ * Create a spinner for async operations
+ */
+export const createSpinner = (
+ config: LoggerConfig,
+ text: string
+): Ora | null => {
+ if (config.quiet || config.outputFormat === "json") {
+ return null;
+ }
+ return ora({ text, color: "cyan" }).start();
+};
+
+/**
+ * Stop spinner with success
+ */
+export const spinnerSuccess = (spinner: Ora | null, text: string): void => {
+ if (spinner) {
+ spinner.succeed(text);
+ }
+};
+
+/**
+ * Stop spinner with failure
+ */
+export const spinnerFail = (spinner: Ora | null, text: string): void => {
+ if (spinner) {
+ spinner.fail(text);
+ }
+};
+
+/**
+ * Stop spinner with warning
+ */
+export const spinnerWarn = (spinner: Ora | null, text: string): void => {
+ if (spinner) {
+ spinner.warn(text);
+ }
+};
+
+// ============================================================================
+// OUTPUT FORMATTING
+// ============================================================================
+
+/**
+ * Format output based on output format setting
+ */
+export const formatOutput = (
+ config: LoggerConfig,
+ result: CommandResult
+): string => {
+ if (config.outputFormat === "json") {
+ return JSON.stringify(result, null, 2);
+ }
+ return formatTextOutput(result);
+};
+
+/**
+ * Format result as human-readable text
+ */
+const formatTextOutput = (result: CommandResult): string => {
+ const lines: string[] = [];
+
+ if (result.success) {
+ if (result.data !== undefined) {
+ lines.push(formatData(result.data));
+ }
+ } else {
+ lines.push(chalk.red(`Error: ${result.error}`));
+ }
+
+ if (result.warnings?.length) {
+ lines.push("");
+ lines.push(chalk.yellow("Warnings:"));
+ result.warnings.forEach((w) => lines.push(chalk.yellow(` • ${w}`)));
+ }
+
+ return lines.join("\n");
+};
+
+/**
+ * Format data for text output
+ */
+const formatData = (data: unknown): string => {
+ if (typeof data === "string") return data;
+ if (typeof data === "object" && data !== null) {
+ return JSON.stringify(data, null, 2);
+ }
+ return String(data);
+};
+
+/**
+ * Print formatted output to console
+ */
+export const printOutput = (
+ config: LoggerConfig,
+ result: CommandResult
+): void => {
+ const output = formatOutput(config, result);
+ if (output) {
+ console.log(output);
+ }
+};
+
+// ============================================================================
+// PLATFORM HELPERS
+// ============================================================================
+
+/**
+ * Get display name for a platform
+ */
+export const getPlatformDisplayName = (platform: Platform): string =>
+ PLATFORM_NAMES[platform] ?? platform;
+
+/**
+ * Format platform list for display
+ */
+export const formatPlatformList = (platforms: readonly Platform[]): string =>
+ platforms.map((p) => `${p} (${getPlatformDisplayName(p)})`).join(", ");
+
+// ============================================================================
+// VALIDATION HELPERS
+// ============================================================================
+
+/**
+ * Validate that a file path exists and is readable
+ */
+export const validateFilePath = async (
+ filePath: string
+): Promise> => {
+ const fs = await import("fs/promises");
+
+ try {
+ await fs.access(filePath);
+ return { success: true, data: filePath };
+ } catch {
+ return {
+ success: false,
+ error: `File not found or not readable: ${filePath}`,
+ };
+ }
+};
+
+/**
+ * Validate output directory exists or can be created
+ */
+export const validateOutputDir = async (
+ dirPath: string
+): Promise> => {
+ const fs = await import("fs/promises");
+ const path = await import("path");
+
+ try {
+ const resolved = path.resolve(dirPath);
+ await fs.mkdir(resolved, { recursive: true });
+ return { success: true, data: resolved };
+ } catch (error) {
+ return {
+ success: false,
+ error: `Cannot create output directory: ${dirPath}`,
+ };
+ }
+};
+
+// ============================================================================
+// ERROR HANDLING
+// ============================================================================
+
+/**
+ * Handle CLI errors consistently
+ */
+export const handleError = (
+ config: LoggerConfig,
+ error: unknown
+): CommandResult => {
+ const message = error instanceof Error ? error.message : String(error);
+ logError(config, message);
+
+ return {
+ success: false,
+ error: message,
+ };
+};
+
+/**
+ * Exit with appropriate code based on result
+ */
+export const exitWithResult = (result: CommandResult): never => {
+ process.exit(result.success ? 0 : 1);
+};
diff --git a/packages/compatibility-layer/src/core/AdapterRegistry.ts b/packages/compatibility-layer/src/core/AdapterRegistry.ts
new file mode 100644
index 0000000..aee6d56
--- /dev/null
+++ b/packages/compatibility-layer/src/core/AdapterRegistry.ts
@@ -0,0 +1,411 @@
+import type { BaseAdapter } from "../adapters/BaseAdapter.js";
+import type { ToolCapabilities } from "../types.js";
+
+// ============================================================================
+// ADAPTER REGISTRY TYPES
+// ============================================================================
+
+/**
+ * Information about a registered adapter including its capabilities
+ */
+export interface AdapterInfo {
+ name: string;
+ adapter: BaseAdapter;
+ capabilities: ToolCapabilities;
+}
+
+/**
+ * Registry error for missing or duplicate adapters
+ */
+export class AdapterRegistryError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "AdapterRegistryError";
+ }
+}
+
+// ============================================================================
+// ADAPTER REGISTRY CLASS
+// ============================================================================
+
+/**
+ * Registry for managing tool adapters.
+ *
+ * The registry maintains a collection of adapters and provides
+ * type-safe registration, lookup, and querying capabilities.
+ *
+ * Features:
+ * - Type-safe adapter storage with Map
+ * - Alias support for adapter names
+ * - Capability-based adapter discovery
+ * - Singleton pattern for global registry
+ *
+ * @example
+ * ```ts
+ * const registry = new AdapterRegistry();
+ *
+ * // Register adapter with aliases
+ * registry.register(new CursorAdapter(), ['cursor-ide', 'cursor-editor']);
+ *
+ * // Get adapter by name or alias
+ * const adapter = registry.get('cursor'); // or 'cursor-ide'
+ *
+ * // List all adapters
+ * const names = registry.list(); // ['claude', 'cursor', 'windsurf']
+ * ```
+ */
+export class AdapterRegistry {
+ private adapters: Map = new Map();
+ private aliases: Map = new Map();
+
+ constructor() {
+ // Registry starts empty - adapters registered manually or via registerBuiltInAdapters()
+ }
+
+ /**
+ * Register a tool adapter with optional aliases.
+ *
+ * @param adapter - The adapter instance to register
+ * @param aliases - Optional array of alias names
+ * @throws {AdapterRegistryError} If adapter with same name already exists
+ *
+ * @example
+ * ```ts
+ * registry.register(new CursorAdapter(), ['cursor-ide', 'cursor-editor']);
+ * ```
+ */
+ register(adapter: BaseAdapter, aliases?: string[]): void {
+ const normalizedName = adapter.name.toLowerCase();
+
+ // Check for duplicate registration
+ if (this.adapters.has(normalizedName)) {
+ throw new AdapterRegistryError(
+ `Adapter '${adapter.name}' is already registered. Use a different name or unregister the existing adapter first.`
+ );
+ }
+
+ // Register main adapter
+ this.adapters.set(normalizedName, adapter);
+
+ // Register aliases
+ if (aliases && aliases.length > 0) {
+ aliases.forEach((alias) => {
+ const normalizedAlias = alias.toLowerCase();
+
+ // Check if alias conflicts with existing adapter name
+ if (this.adapters.has(normalizedAlias)) {
+ throw new AdapterRegistryError(
+ `Alias '${alias}' conflicts with existing adapter name '${normalizedAlias}'`
+ );
+ }
+
+ // Check if alias is already registered
+ if (this.aliases.has(normalizedAlias)) {
+ throw new AdapterRegistryError(
+ `Alias '${alias}' is already registered for adapter '${this.aliases.get(normalizedAlias)}'`
+ );
+ }
+
+ this.aliases.set(normalizedAlias, normalizedName);
+ });
+ }
+ }
+
+ /**
+ * Get an adapter by name or alias.
+ *
+ * @param nameOrAlias - The adapter name or alias (case-insensitive)
+ * @returns The adapter instance, or undefined if not found
+ *
+ * @example
+ * ```ts
+ * const adapter = registry.get('cursor'); // or 'cursor-ide'
+ * if (adapter) {
+ * const result = await adapter.fromOAC(agent);
+ * }
+ * ```
+ */
+ get(nameOrAlias: string): BaseAdapter | undefined {
+ const normalized = nameOrAlias.toLowerCase();
+
+ // Try direct lookup
+ if (this.adapters.has(normalized)) {
+ return this.adapters.get(normalized);
+ }
+
+ // Try alias lookup
+ if (this.aliases.has(normalized)) {
+ const actualName = this.aliases.get(normalized);
+ if (actualName) {
+ return this.adapters.get(actualName);
+ }
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Check if an adapter exists by name or alias.
+ *
+ * @param nameOrAlias - The adapter name or alias (case-insensitive)
+ * @returns True if adapter exists, false otherwise
+ *
+ * @example
+ * ```ts
+ * if (registry.has('cursor')) {
+ * console.log('Cursor adapter is available');
+ * }
+ * ```
+ */
+ has(nameOrAlias: string): boolean {
+ return this.get(nameOrAlias) !== undefined;
+ }
+
+ /**
+ * List all registered adapter names (sorted alphabetically).
+ *
+ * @returns Array of adapter names
+ *
+ * @example
+ * ```ts
+ * const names = registry.list(); // ['claude', 'cursor', 'windsurf']
+ * ```
+ */
+ list(): string[] {
+ return Array.from(this.adapters.keys()).sort();
+ }
+
+ /**
+ * Get all adapters with their capabilities.
+ *
+ * @returns Array of adapter info objects (sorted by name)
+ *
+ * @example
+ * ```ts
+ * const all = registry.getAll();
+ * all.forEach(({ name, capabilities }) => {
+ * console.log(`${name}: supports ${capabilities.multipleAgents ? 'multiple' : 'single'} agents`);
+ * });
+ * ```
+ */
+ getAll(): AdapterInfo[] {
+ const result: AdapterInfo[] = [];
+
+ this.adapters.forEach((adapter, name) => {
+ result.push({
+ name,
+ adapter,
+ capabilities: adapter.getCapabilities(),
+ });
+ });
+
+ return result.sort((a, b) => a.name.localeCompare(b.name));
+ }
+
+ /**
+ * Get capabilities for a specific adapter.
+ *
+ * @param nameOrAlias - The adapter name or alias
+ * @returns Tool capabilities, or undefined if adapter not found
+ *
+ * @example
+ * ```ts
+ * const caps = registry.getCapabilities('cursor');
+ * if (caps?.multipleAgents === false) {
+ * console.warn('Cursor only supports single agent files');
+ * }
+ * ```
+ */
+ getCapabilities(nameOrAlias: string): ToolCapabilities | undefined {
+ const adapter = this.get(nameOrAlias);
+ return adapter?.getCapabilities();
+ }
+
+ /**
+ * Find all adapters that support a specific feature.
+ *
+ * @param feature - The feature key to search for
+ * @returns Array of adapters that support the feature
+ *
+ * @example
+ * ```ts
+ * const withMultiAgent = registry.findByFeature('multipleAgents');
+ * console.log(`${withMultiAgent.length} adapters support multiple agents`);
+ * ```
+ */
+ findByFeature(feature: keyof ToolCapabilities): BaseAdapter[] {
+ const result: BaseAdapter[] = [];
+
+ this.adapters.forEach((adapter) => {
+ const capabilities = adapter.getCapabilities();
+ if (capabilities[feature] === true) {
+ result.push(adapter);
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Unregister an adapter by name.
+ *
+ * @param name - The adapter name to remove
+ * @returns True if adapter was removed, false if not found
+ *
+ * @example
+ * ```ts
+ * registry.unregister('cursor');
+ * ```
+ */
+ unregister(name: string): boolean {
+ const normalized = name.toLowerCase();
+
+ // Remove adapter
+ const removed = this.adapters.delete(normalized);
+
+ // Remove all aliases pointing to this adapter
+ if (removed) {
+ const aliasesToRemove: string[] = [];
+ this.aliases.forEach((adapterName, alias) => {
+ if (adapterName === normalized) {
+ aliasesToRemove.push(alias);
+ }
+ });
+ aliasesToRemove.forEach((alias) => this.aliases.delete(alias));
+ }
+
+ return removed;
+ }
+
+ /**
+ * Clear all registered adapters and aliases.
+ *
+ * @example
+ * ```ts
+ * registry.clear();
+ * console.log(registry.list()); // []
+ * ```
+ */
+ clear(): void {
+ this.adapters.clear();
+ this.aliases.clear();
+ }
+
+ /**
+ * Get the number of registered adapters.
+ *
+ * @returns Count of registered adapters (excluding aliases)
+ */
+ get size(): number {
+ return this.adapters.size;
+ }
+
+ /**
+ * Register built-in adapters (lazy loading).
+ *
+ * This method is called on-demand to avoid circular dependencies.
+ * Adapters are imported dynamically when first needed.
+ *
+ * @example
+ * ```ts
+ * await registry.registerBuiltInAdapters();
+ * ```
+ */
+ async registerBuiltInAdapters(): Promise {
+ // Dynamic imports to avoid circular dependencies
+ try {
+ // Cursor IDE
+ const { CursorAdapter } = await import("../adapters/CursorAdapter.js");
+ this.register(new CursorAdapter(), ["cursor-ide", "cursor-editor"]);
+ } catch (error) {
+ // Adapter not yet implemented - skip silently
+ }
+
+ try {
+ // Claude Code
+ const { ClaudeAdapter } = await import("../adapters/ClaudeAdapter.js");
+ this.register(new ClaudeAdapter(), ["claude-code", "anthropic-claude"]);
+ } catch (error) {
+ // Adapter not yet implemented - skip silently
+ }
+
+ try {
+ // Windsurf (experimental)
+ const { WindsurfAdapter } = await import("../adapters/WindsurfAdapter.js");
+ this.register(new WindsurfAdapter(), ["windsurf-ide"]);
+ } catch (error) {
+ // Adapter not yet implemented - skip silently
+ }
+ }
+}
+
+// ============================================================================
+// SINGLETON INSTANCE
+// ============================================================================
+
+/**
+ * Global singleton registry instance.
+ *
+ * Use this for most cases unless you need isolated registries for testing.
+ *
+ * @example
+ * ```ts
+ * import { registry } from './core/AdapterRegistry.js';
+ *
+ * const adapter = registry.get('cursor');
+ * ```
+ */
+export const registry = new AdapterRegistry();
+
+// ============================================================================
+// CONVENIENCE FUNCTIONS
+// ============================================================================
+
+/**
+ * Get an adapter from the global registry.
+ *
+ * @param nameOrAlias - The adapter name or alias
+ * @returns The adapter instance, or undefined if not found
+ *
+ * @example
+ * ```ts
+ * const adapter = getAdapter('cursor');
+ * ```
+ */
+export function getAdapter(nameOrAlias: string): BaseAdapter | undefined {
+ return registry.get(nameOrAlias);
+}
+
+/**
+ * List all adapters in the global registry.
+ *
+ * @returns Array of adapter names
+ *
+ * @example
+ * ```ts
+ * const names = listAdapters(); // ['claude', 'cursor', 'windsurf']
+ * ```
+ */
+export function listAdapters(): string[] {
+ return registry.list();
+}
+
+/**
+ * Get all adapter capabilities from the global registry.
+ *
+ * @returns Array of objects with name and capabilities
+ *
+ * @example
+ * ```ts
+ * const capabilities = getAllCapabilities();
+ * capabilities.forEach(({ name, capabilities }) => {
+ * console.log(`${name}:`, capabilities);
+ * });
+ * ```
+ */
+export function getAllCapabilities(): Array<{ name: string; capabilities: ToolCapabilities }> {
+ return registry.getAll().map(({ name, capabilities }) => ({
+ name,
+ capabilities,
+ }));
+}
diff --git a/packages/compatibility-layer/src/core/AgentLoader.ts b/packages/compatibility-layer/src/core/AgentLoader.ts
new file mode 100644
index 0000000..4e5f63b
--- /dev/null
+++ b/packages/compatibility-layer/src/core/AgentLoader.ts
@@ -0,0 +1,386 @@
+import { readFileSync, readdirSync } from "fs";
+import { join, extname, basename } from "path";
+import * as yaml from "js-yaml";
+import { ZodError } from "zod";
+import { OpenAgentSchema, AgentFrontmatterSchema, OpenAgent, AgentFrontmatter } from "../types.js";
+
+// ============================================================================
+// ERROR TYPES
+// ============================================================================
+
+/**
+ * Base error for agent loading operations
+ */
+export class AgentLoadError extends Error {
+ public readonly filePath: string;
+ public override readonly cause?: unknown;
+
+ constructor(
+ message: string,
+ filePath: string,
+ cause?: unknown
+ ) {
+ super(message);
+ this.filePath = filePath;
+ this.cause = cause;
+ this.name = "AgentLoadError";
+ }
+}
+
+/**
+ * Error when frontmatter parsing fails
+ */
+export class FrontmatterParseError extends AgentLoadError {
+ constructor(filePath: string, cause: unknown) {
+ super(`Failed to parse frontmatter in ${filePath}`, filePath, cause);
+ this.name = "FrontmatterParseError";
+ }
+}
+
+/**
+ * Error when Zod validation fails
+ */
+export class ValidationError extends AgentLoadError {
+ constructor(
+ filePath: string,
+ public readonly validationErrors: ZodError
+ ) {
+ super(
+ `Validation failed for ${filePath}:\n${validationErrors.errors
+ .map((e) => ` - ${e.path.join(".")}: ${e.message}`)
+ .join("\n")}`,
+ filePath,
+ validationErrors
+ );
+ this.name = "ValidationError";
+ }
+}
+
+// ============================================================================
+// FRONTMATTER PARSING
+// ============================================================================
+
+interface ParsedContent {
+ frontmatter: unknown;
+ body: string;
+}
+
+/**
+ * Parse YAML frontmatter from markdown content
+ * @param content - Full markdown file content
+ * @param filePath - Path to file (for error reporting)
+ * @returns Parsed frontmatter and body content
+ */
+function parseFrontmatter(content: string, filePath: string): ParsedContent {
+ // Match YAML frontmatter between --- delimiters
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
+ const match = content.match(frontmatterRegex);
+
+ if (!match) {
+ return { frontmatter: null, body: content };
+ }
+
+ const [, yamlContent = "", body = ""] = match;
+
+ try {
+ const frontmatter = yaml.load(yamlContent);
+ return { frontmatter, body: body.trim() };
+ } catch (error) {
+ throw new FrontmatterParseError(filePath, error);
+ }
+}
+
+// ============================================================================
+// MARKDOWN SECTION EXTRACTION
+// ============================================================================
+
+interface AgentSections {
+ skills: string[];
+ examples: string[];
+ commands: string[];
+ workflow?: string;
+}
+
+/**
+ * Extract structured sections from markdown body
+ * @param markdown - Agent markdown content (after frontmatter)
+ * @returns Extracted sections
+ */
+function extractSections(markdown: string): AgentSections {
+ const sections: AgentSections = {
+ skills: [],
+ examples: [],
+ commands: [],
+ };
+
+ // Extract ## Skills or ## Available Skills section
+ const skillsMatch = markdown.match(/##\s+(?:Available\s+)?Skills\s*\n([\s\S]*?)(?=\n##|\n---|$)/i);
+ if (skillsMatch && skillsMatch[1]) {
+ const skillsText = skillsMatch[1];
+ // Extract list items or quoted names
+ const skillItems = skillsText.match(/[-*]\s+`?([^`\n]+)`?/g);
+ if (skillItems) {
+ sections.skills = skillItems.map((item) =>
+ item.replace(/[-*]\s+`?([^`\n]+)`?/, "$1").trim()
+ );
+ }
+ }
+
+ // Extract ## Examples section
+ const examplesMatch = markdown.match(/##\s+Examples?\s*\n([\s\S]*?)(?=\n##|\n---|$)/i);
+ if (examplesMatch && examplesMatch[1]) {
+ const examplesText = examplesMatch[1];
+ // Extract code blocks
+ const codeBlocks = examplesText.match(/```[\s\S]*?```/g);
+ if (codeBlocks) {
+ sections.examples = codeBlocks;
+ }
+ }
+
+ // Extract ## Commands or ## Available Commands section
+ const commandsMatch = markdown.match(/##\s+(?:Available\s+)?Commands?\s*\n([\s\S]*?)(?=\n##|\n---|$)/i);
+ if (commandsMatch && commandsMatch[1]) {
+ const commandsText = commandsMatch[1];
+ const commandItems = commandsText.match(/[-*]\s+`?([^`\n]+)`?/g);
+ if (commandItems) {
+ sections.commands = commandItems.map((item) =>
+ item.replace(/[-*]\s+`?([^`\n]+)`?/, "$1").trim()
+ );
+ }
+ }
+
+ // Extract ## Workflow section
+ const workflowMatch = markdown.match(/##\s+Workflow\s*\n([\s\S]*?)(?=\n##|\n---|$)/i);
+ if (workflowMatch && workflowMatch[1]) {
+ sections.workflow = workflowMatch[1].trim();
+ }
+
+ return sections;
+}
+
+// ============================================================================
+// METADATA LOADER (from agent-metadata.json)
+// ============================================================================
+
+interface AgentMetadataFile {
+ agents?: Record>;
+}
+
+let cachedMetadata: Record> = {};
+let metadataLoaded = false;
+
+/**
+ * Load agent metadata from .opencode/config/agent-metadata.json
+ * @param projectRoot - Optional project root path
+ * @returns Metadata object keyed by agent ID
+ */
+function loadMetadataFile(projectRoot?: string): Record> {
+ if (metadataLoaded) {
+ return cachedMetadata;
+ }
+
+ const metadataPath = projectRoot
+ ? join(projectRoot, ".opencode/config/agent-metadata.json")
+ : ".opencode/config/agent-metadata.json";
+
+ try {
+ const content = readFileSync(metadataPath, "utf-8");
+ const parsed = JSON.parse(content) as AgentMetadataFile;
+ cachedMetadata = parsed.agents || {};
+ } catch (error) {
+ // Metadata file is optional
+ cachedMetadata = {};
+ }
+
+ metadataLoaded = true;
+ return cachedMetadata;
+}
+
+/**
+ * Infer agent ID from file path
+ * @param filePath - Path to agent file
+ * @returns Agent ID (lowercase, kebab-case)
+ */
+function inferAgentId(filePath: string): string {
+ // Extract agent ID from filename (e.g., "opencoder.md" -> "opencoder")
+ const filename = basename(filePath, extname(filePath));
+ return filename.toLowerCase().replace(/[^a-z0-9-]/g, "-");
+}
+
+// ============================================================================
+// AGENT LOADER CLASS
+// ============================================================================
+
+/**
+ * Loads and parses OpenAgent files from the filesystem
+ */
+export class AgentLoader {
+ private projectRoot?: string;
+
+ constructor(projectRoot?: string) {
+ this.projectRoot = projectRoot;
+ }
+
+ /**
+ * Load and parse a single agent file
+ * @param filePath - Path to agent markdown file
+ * @returns Parsed and validated OpenAgent
+ */
+ loadFromFile(filePath: string): Promise {
+ let content: string;
+
+ try {
+ content = readFileSync(filePath, "utf-8");
+ } catch (error) {
+ throw new AgentLoadError(`Failed to read file: ${filePath}`, filePath, error);
+ }
+
+ // Parse frontmatter
+ const { frontmatter, body } = parseFrontmatter(content, filePath);
+
+ if (!frontmatter) {
+ throw new AgentLoadError(
+ `No frontmatter found in ${filePath}. Agent files must have YAML frontmatter.`,
+ filePath
+ );
+ }
+
+ // Validate frontmatter against schema
+ let validatedFrontmatter: AgentFrontmatter;
+ try {
+ validatedFrontmatter = AgentFrontmatterSchema.parse(frontmatter);
+ } catch (error) {
+ if (error instanceof ZodError) {
+ throw new ValidationError(filePath, error);
+ }
+ throw error;
+ }
+
+ // Load extended metadata from agent-metadata.json
+ const agentId = inferAgentId(filePath);
+ const metadataFile = loadMetadataFile(this.projectRoot);
+ const extendedMetadata = metadataFile[agentId] || {};
+
+ // Merge metadata with defaults
+ const metadata: OpenAgent["metadata"] = {
+ id: agentId,
+ name: validatedFrontmatter.name,
+ version: "1.0.0",
+ author: "opencode",
+ tags: [],
+ dependencies: [],
+ ...extendedMetadata,
+ };
+
+ // Extract sections from markdown body
+ const sections = extractSections(body);
+
+ // Construct OpenAgent object
+ const agent: OpenAgent = {
+ frontmatter: validatedFrontmatter,
+ metadata,
+ systemPrompt: body,
+ contexts: [], // Can be populated by context discovery later
+ sections,
+ };
+
+ // Final validation
+ try {
+ return Promise.resolve(OpenAgentSchema.parse(agent));
+ } catch (error) {
+ if (error instanceof ZodError) {
+ return Promise.reject(new ValidationError(filePath, error));
+ }
+ return Promise.reject(error);
+ }
+ }
+
+ /**
+ * Load multiple agents from a directory (recursive)
+ * @param dirPath - Path to directory containing agent files
+ * @returns Array of parsed OpenAgents
+ */
+ async loadFromDirectory(dirPath: string): Promise {
+ const agents: OpenAgent[] = [];
+ const errors: AgentLoadError[] = [];
+
+ const scanDirectory = async (currentPath: string): Promise => {
+ const entries = readdirSync(currentPath, { withFileTypes: true });
+
+ for (const entry of entries) {
+ const fullPath = join(currentPath, entry.name);
+
+ if (entry.isDirectory()) {
+ await scanDirectory(fullPath);
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
+ try {
+ const agent = await new AgentLoader(this.projectRoot).loadFromFile(fullPath);
+ agents.push(agent);
+ } catch (error) {
+ if (error instanceof AgentLoadError) {
+ errors.push(error);
+ } else {
+ errors.push(
+ new AgentLoadError(`Unexpected error loading ${fullPath}`, fullPath, error)
+ );
+ }
+ }
+ }
+ }
+ };
+
+ await scanDirectory(dirPath);
+
+ if (errors.length > 0 && agents.length === 0) {
+ throw new AgentLoadError(
+ `Failed to load any agents from ${dirPath}. Errors:\n${errors
+ .map((e) => e.message)
+ .join("\n")}`,
+ dirPath
+ );
+ }
+
+ return agents;
+ }
+
+ /**
+ * Validate an agent file without fully loading it
+ * @param filePath - Path to agent file
+ * @returns Validation result
+ */
+ async validate(filePath: string): Promise<{ valid: true } | { valid: false; errors: ZodError }> {
+ try {
+ await this.loadFromFile(filePath);
+ return { valid: true };
+ } catch (error) {
+ if (error instanceof ValidationError) {
+ return { valid: false, errors: error.validationErrors };
+ }
+ throw error;
+ }
+ }
+}
+
+// ============================================================================
+// CONVENIENCE FUNCTIONS
+// ============================================================================
+
+/**
+ * Load a single agent file (convenience function)
+ * @param filePath - Path to agent markdown file
+ * @returns Parsed OpenAgent
+ */
+export async function loadAgent(filePath: string): Promise {
+ const loader = new AgentLoader();
+ return loader.loadFromFile(filePath);
+}
+
+/**
+ * Load all agents from a directory (convenience function)
+ * @param dirPath - Path to directory containing agents
+ * @returns Array of parsed OpenAgents
+ */
+export async function loadAgents(dirPath: string): Promise {
+ const loader = new AgentLoader();
+ return loader.loadFromDirectory(dirPath);
+}
diff --git a/packages/compatibility-layer/src/core/CapabilityMatrix.ts b/packages/compatibility-layer/src/core/CapabilityMatrix.ts
new file mode 100644
index 0000000..ec41302
--- /dev/null
+++ b/packages/compatibility-layer/src/core/CapabilityMatrix.ts
@@ -0,0 +1,559 @@
+/**
+ * CapabilityMatrix - Feature compatibility matrix across platforms
+ *
+ * Provides a centralized registry of what features each platform supports,
+ * enabling pre-conversion validation and compatibility reporting.
+ *
+ * @example
+ * ```ts
+ * const compatibility = analyzeCompatibility(agent, 'cursor');
+ * // => { compatible: false, warnings: [...], blockers: [...] }
+ * ```
+ */
+
+import type { OpenAgent, ToolCapabilities } from "../types.js";
+
+// ============================================================================
+// Types
+// ============================================================================
+
+export type Platform = "oac" | "claude" | "cursor" | "windsurf";
+
+/**
+ * Feature categories for the capability matrix
+ */
+export type FeatureCategory =
+ | "agents"
+ | "permissions"
+ | "tools"
+ | "context"
+ | "model"
+ | "advanced";
+
+/**
+ * Support level for a feature
+ */
+export type SupportLevel = "full" | "partial" | "none";
+
+/**
+ * Feature definition in the capability matrix
+ */
+export interface FeatureDefinition {
+ name: string;
+ category: FeatureCategory;
+ description: string;
+ support: Record;
+ notes?: Partial>;
+}
+
+/**
+ * Compatibility analysis result
+ */
+export interface CompatibilityResult {
+ /** Overall compatibility assessment */
+ compatible: boolean;
+ /** Score from 0-100 representing compatibility percentage */
+ score: number;
+ /** Warnings about features that will be degraded */
+ warnings: string[];
+ /** Blocking issues that prevent conversion */
+ blockers: string[];
+ /** Features that will be fully preserved */
+ preserved: string[];
+ /** Features that will be partially preserved */
+ degraded: string[];
+ /** Features that will be lost */
+ lost: string[];
+}
+
+// ============================================================================
+// Capability Matrix
+// ============================================================================
+
+/**
+ * Complete feature capability matrix
+ */
+const CAPABILITY_MATRIX: FeatureDefinition[] = [
+ // Agent Features
+ {
+ name: "multipleAgents",
+ category: "agents",
+ description: "Support for multiple agent definitions",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "full" },
+ notes: { cursor: "Single .cursorrules file only - agents will be merged" },
+ },
+ {
+ name: "agentModes",
+ category: "agents",
+ description: "Primary/subagent mode distinction",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "partial" },
+ notes: { windsurf: "Limited mode support" },
+ },
+ {
+ name: "agentCategories",
+ category: "agents",
+ description: "Agent categorization (core, development, etc.)",
+ support: { oac: "full", claude: "partial", cursor: "none", windsurf: "partial" },
+ },
+
+ // Permission Features
+ {
+ name: "granularPermissions",
+ category: "permissions",
+ description: "Fine-grained allow/deny/ask patterns",
+ support: { oac: "full", claude: "none", cursor: "none", windsurf: "none" },
+ notes: {
+ claude: "Binary on/off only",
+ cursor: "Binary on/off only",
+ windsurf: "Binary on/off only",
+ },
+ },
+ {
+ name: "askPermissions",
+ category: "permissions",
+ description: "Interactive permission requests",
+ support: { oac: "full", claude: "none", cursor: "none", windsurf: "none" },
+ },
+ {
+ name: "pathPatterns",
+ category: "permissions",
+ description: "Glob patterns for file permissions",
+ support: { oac: "full", claude: "none", cursor: "none", windsurf: "partial" },
+ },
+
+ // Tool Features
+ {
+ name: "taskDelegation",
+ category: "tools",
+ description: "Agent-to-agent task delegation",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "partial" },
+ notes: { cursor: "No delegation support" },
+ },
+ {
+ name: "bashExecution",
+ category: "tools",
+ description: "Shell command execution",
+ support: { oac: "full", claude: "full", cursor: "full", windsurf: "full" },
+ },
+ {
+ name: "fileOperations",
+ category: "tools",
+ description: "Read/write/edit file operations",
+ support: { oac: "full", claude: "full", cursor: "full", windsurf: "full" },
+ },
+ {
+ name: "searchOperations",
+ category: "tools",
+ description: "Grep/glob search operations",
+ support: { oac: "full", claude: "full", cursor: "full", windsurf: "full" },
+ },
+
+ // Context Features
+ {
+ name: "externalContext",
+ category: "context",
+ description: "External context file references",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "full" },
+ notes: { cursor: "Context must be inline in .cursorrules" },
+ },
+ {
+ name: "contextPriority",
+ category: "context",
+ description: "Priority levels for context loading",
+ support: { oac: "full", claude: "none", cursor: "none", windsurf: "none" },
+ },
+ {
+ name: "contextSubdirs",
+ category: "context",
+ description: "Nested context directory structure",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "full" },
+ },
+ {
+ name: "skillsSystem",
+ category: "context",
+ description: "Loadable skill modules",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "partial" },
+ },
+
+ // Model Features
+ {
+ name: "modelSelection",
+ category: "model",
+ description: "Custom model selection",
+ support: { oac: "full", claude: "full", cursor: "full", windsurf: "full" },
+ },
+ {
+ name: "temperatureControl",
+ category: "model",
+ description: "Temperature parameter control",
+ support: { oac: "full", claude: "none", cursor: "partial", windsurf: "partial" },
+ notes: {
+ claude: "Temperature not configurable",
+ cursor: "Limited range",
+ windsurf: "Maps to creativity setting",
+ },
+ },
+ {
+ name: "maxSteps",
+ category: "model",
+ description: "Maximum execution steps limit",
+ support: { oac: "full", claude: "none", cursor: "none", windsurf: "none" },
+ },
+
+ // Advanced Features
+ {
+ name: "hooks",
+ category: "advanced",
+ description: "Event hooks (PreToolUse, PostToolUse, etc.)",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "none" },
+ notes: { cursor: "No hook support", windsurf: "No hook support" },
+ },
+ {
+ name: "dependencies",
+ category: "advanced",
+ description: "Agent dependency declarations",
+ support: { oac: "full", claude: "full", cursor: "none", windsurf: "partial" },
+ },
+ {
+ name: "priorityLevels",
+ category: "advanced",
+ description: "Task priority levels",
+ support: { oac: "full", claude: "partial", cursor: "none", windsurf: "partial" },
+ notes: { oac: "4 levels", claude: "2 levels", windsurf: "2 levels" },
+ },
+];
+
+// ============================================================================
+// Core Functions
+// ============================================================================
+
+/**
+ * Get the full capability matrix.
+ *
+ * @returns Array of all feature definitions
+ */
+export function getCapabilityMatrix(): FeatureDefinition[] {
+ return [...CAPABILITY_MATRIX];
+}
+
+/**
+ * Get features by category.
+ *
+ * @param category - Feature category to filter by
+ * @returns Array of features in that category
+ */
+export function getFeaturesByCategory(
+ category: FeatureCategory
+): FeatureDefinition[] {
+ return CAPABILITY_MATRIX.filter((f) => f.category === category);
+}
+
+/**
+ * Get the support level for a specific feature on a platform.
+ *
+ * @param featureName - Name of the feature
+ * @param platform - Target platform
+ * @returns Support level or undefined if feature not found
+ */
+export function getFeatureSupport(
+ featureName: string,
+ platform: Platform
+): SupportLevel | undefined {
+ const feature = CAPABILITY_MATRIX.find((f) => f.name === featureName);
+ return feature?.support[platform];
+}
+
+/**
+ * Check if a feature is fully supported on a platform.
+ *
+ * @param featureName - Name of the feature
+ * @param platform - Target platform
+ * @returns True if fully supported
+ */
+export function isFeatureSupported(
+ featureName: string,
+ platform: Platform
+): boolean {
+ return getFeatureSupport(featureName, platform) === "full";
+}
+
+// ============================================================================
+// Compatibility Analysis
+// ============================================================================
+
+/**
+ * Analyze compatibility of an OpenAgent with a target platform.
+ *
+ * @param agent - The OpenAgent to analyze
+ * @param targetPlatform - Target platform for conversion
+ * @returns Detailed compatibility analysis
+ */
+export function analyzeCompatibility(
+ agent: OpenAgent,
+ targetPlatform: Exclude
+): CompatibilityResult {
+ const warnings: string[] = [];
+ const blockers: string[] = [];
+ const preserved: string[] = [];
+ const degraded: string[] = [];
+ const lost: string[] = [];
+
+ // Check agent mode
+ if (agent.frontmatter.mode === "subagent") {
+ const modeSupport = getFeatureSupport("agentModes", targetPlatform);
+ if (modeSupport === "none") {
+ warnings.push(`Agent mode 'subagent' not supported by ${targetPlatform}`);
+ degraded.push("agentModes");
+ } else if (modeSupport === "partial") {
+ warnings.push(`Agent mode may have limited support on ${targetPlatform}`);
+ degraded.push("agentModes");
+ } else {
+ preserved.push("agentModes");
+ }
+ }
+
+ // Check temperature
+ if (agent.frontmatter.temperature !== undefined) {
+ const tempSupport = getFeatureSupport("temperatureControl", targetPlatform);
+ if (tempSupport === "none") {
+ warnings.push(
+ `Temperature setting (${agent.frontmatter.temperature}) will be ignored by ${targetPlatform}`
+ );
+ lost.push("temperatureControl");
+ } else if (tempSupport === "partial") {
+ warnings.push(`Temperature will be approximated on ${targetPlatform}`);
+ degraded.push("temperatureControl");
+ } else {
+ preserved.push("temperatureControl");
+ }
+ }
+
+ // Check hooks
+ if (agent.frontmatter.hooks && agent.frontmatter.hooks.length > 0) {
+ const hookSupport = getFeatureSupport("hooks", targetPlatform);
+ if (hookSupport === "none") {
+ blockers.push(
+ `Hooks are not supported by ${targetPlatform} - ${agent.frontmatter.hooks.length} hook(s) will be lost`
+ );
+ lost.push("hooks");
+ } else {
+ preserved.push("hooks");
+ }
+ }
+
+ // Check skills
+ if (agent.frontmatter.skills && agent.frontmatter.skills.length > 0) {
+ const skillSupport = getFeatureSupport("skillsSystem", targetPlatform);
+ if (skillSupport === "none") {
+ warnings.push(
+ `Skills system not supported by ${targetPlatform} - skills will be converted to inline context`
+ );
+ degraded.push("skillsSystem");
+ } else if (skillSupport === "partial") {
+ warnings.push(`Skills may have limited functionality on ${targetPlatform}`);
+ degraded.push("skillsSystem");
+ } else {
+ preserved.push("skillsSystem");
+ }
+ }
+
+ // Check granular permissions
+ if (agent.frontmatter.permission) {
+ const hasGranular = Object.values(agent.frontmatter.permission).some(
+ (rule) => typeof rule === "object" && rule !== null
+ );
+ if (hasGranular) {
+ const permSupport = getFeatureSupport("granularPermissions", targetPlatform);
+ if (permSupport === "none") {
+ warnings.push(
+ `Granular permissions will be simplified to binary allow/deny for ${targetPlatform}`
+ );
+ degraded.push("granularPermissions");
+ }
+ }
+ }
+
+ // Check contexts
+ if (agent.contexts && agent.contexts.length > 0) {
+ const contextSupport = getFeatureSupport("externalContext", targetPlatform);
+ if (contextSupport === "none") {
+ warnings.push(
+ `External context files not supported by ${targetPlatform} - content must be inline`
+ );
+ degraded.push("externalContext");
+ } else {
+ preserved.push("externalContext");
+ }
+
+ // Check priority
+ const hasPriority = agent.contexts.some((c) => c.priority);
+ if (hasPriority) {
+ const prioritySupport = getFeatureSupport("contextPriority", targetPlatform);
+ if (prioritySupport === "none") {
+ warnings.push(`Context priority metadata will be ignored by ${targetPlatform}`);
+ lost.push("contextPriority");
+ }
+ }
+ }
+
+ // Check maxSteps
+ if (agent.frontmatter.maxSteps !== undefined) {
+ const stepsSupport = getFeatureSupport("maxSteps", targetPlatform);
+ if (stepsSupport === "none") {
+ warnings.push(`maxSteps setting will be ignored by ${targetPlatform}`);
+ lost.push("maxSteps");
+ }
+ }
+
+ // Calculate compatibility score
+ const totalFeatures = preserved.length + degraded.length + lost.length;
+ const score =
+ totalFeatures > 0
+ ? Math.round(
+ ((preserved.length + degraded.length * 0.5) / totalFeatures) * 100
+ )
+ : 100;
+
+ return {
+ compatible: blockers.length === 0,
+ score,
+ warnings,
+ blockers,
+ preserved,
+ degraded,
+ lost,
+ };
+}
+
+// ============================================================================
+// ToolCapabilities Generation
+// ============================================================================
+
+/**
+ * Generate a ToolCapabilities object for a platform.
+ *
+ * @param platform - Target platform
+ * @returns ToolCapabilities object
+ */
+export function getToolCapabilities(
+ platform: Exclude
+): ToolCapabilities {
+ const displayNames: Record, string> = {
+ claude: "Claude Code",
+ cursor: "Cursor IDE",
+ windsurf: "Windsurf",
+ };
+
+ const configFormats: Record, ToolCapabilities["configFormat"]> = {
+ claude: "json",
+ cursor: "plain",
+ windsurf: "json",
+ };
+
+ const outputStructures: Record<
+ Exclude,
+ ToolCapabilities["outputStructure"]
+ > = {
+ claude: "directory",
+ cursor: "single-file",
+ windsurf: "directory",
+ };
+
+ return {
+ name: platform,
+ displayName: displayNames[platform],
+ supportsMultipleAgents: isFeatureSupported("multipleAgents", platform),
+ supportsSkills: getFeatureSupport("skillsSystem", platform) !== "none",
+ supportsHooks: isFeatureSupported("hooks", platform),
+ supportsGranularPermissions: isFeatureSupported("granularPermissions", platform),
+ supportsContexts: getFeatureSupport("externalContext", platform) !== "none",
+ supportsCustomModels: isFeatureSupported("modelSelection", platform),
+ supportsTemperature: getFeatureSupport("temperatureControl", platform) !== "none",
+ supportsMaxSteps: isFeatureSupported("maxSteps", platform),
+ configFormat: configFormats[platform],
+ outputStructure: outputStructures[platform],
+ };
+}
+
+// ============================================================================
+// Comparison Utilities
+// ============================================================================
+
+/**
+ * Compare two platforms' capabilities.
+ *
+ * @param platformA - First platform
+ * @param platformB - Second platform
+ * @returns Comparison showing which features differ
+ */
+export function comparePlatforms(
+ platformA: Platform,
+ platformB: Platform
+): {
+ identical: string[];
+ betterInA: string[];
+ betterInB: string[];
+ different: string[];
+} {
+ const identical: string[] = [];
+ const betterInA: string[] = [];
+ const betterInB: string[] = [];
+ const different: string[] = [];
+
+ const supportOrder: Record = {
+ full: 2,
+ partial: 1,
+ none: 0,
+ };
+
+ for (const feature of CAPABILITY_MATRIX) {
+ const supportA = feature.support[platformA];
+ const supportB = feature.support[platformB];
+
+ if (supportA === supportB) {
+ identical.push(feature.name);
+ } else {
+ different.push(feature.name);
+ if (supportOrder[supportA] > supportOrder[supportB]) {
+ betterInA.push(feature.name);
+ } else {
+ betterInB.push(feature.name);
+ }
+ }
+ }
+
+ return { identical, betterInA, betterInB, different };
+}
+
+/**
+ * Get a summary of what will happen during conversion.
+ *
+ * @param sourcePlatform - Source platform
+ * @param targetPlatform - Target platform
+ * @returns Human-readable summary
+ */
+export function getConversionSummary(
+ sourcePlatform: Platform,
+ targetPlatform: Platform
+): string[] {
+ const comparison = comparePlatforms(sourcePlatform, targetPlatform);
+ const summary: string[] = [];
+
+ if (comparison.betterInA.length > 0) {
+ summary.push(
+ `Features that may be degraded: ${comparison.betterInA.join(", ")}`
+ );
+ }
+
+ if (comparison.betterInB.length > 0) {
+ summary.push(
+ `Features that may be enhanced: ${comparison.betterInB.join(", ")}`
+ );
+ }
+
+ if (comparison.identical.length === CAPABILITY_MATRIX.length) {
+ summary.push("Full feature parity - no degradation expected");
+ }
+
+ return summary;
+}
diff --git a/packages/compatibility-layer/src/core/TranslationEngine.ts b/packages/compatibility-layer/src/core/TranslationEngine.ts
new file mode 100644
index 0000000..69a7418
--- /dev/null
+++ b/packages/compatibility-layer/src/core/TranslationEngine.ts
@@ -0,0 +1,453 @@
+/**
+ * TranslationEngine - Orchestrates all mappers for agent conversion
+ *
+ * The TranslationEngine coordinates ToolMapper, PermissionMapper, ModelMapper,
+ * ContextMapper, and CapabilityMatrix to provide complete agent translation
+ * between OAC and other platforms.
+ *
+ * @example
+ * ```ts
+ * const engine = new TranslationEngine();
+ * const result = engine.translate(agent, 'cursor');
+ * // => { agent: translatedAgent, warnings: [...], compatible: true }
+ * ```
+ */
+
+import type {
+ OpenAgent,
+ AgentFrontmatter,
+ ToolAccess,
+ GranularPermission,
+ ContextReference,
+ SkillReference,
+} from "../types.js";
+
+import {
+ mapToolAccessFromOAC,
+ mapToolAccessToOAC,
+} from "../mappers/ToolMapper.js";
+
+import {
+ mapPermissionsFromOAC,
+ mapPermissionsToOAC,
+ type DegradationStrategy,
+ type BinaryPermissions,
+} from "../mappers/PermissionMapper.js";
+
+import {
+ mapModelFromOAC,
+ mapModelToOAC,
+} from "../mappers/ModelMapper.js";
+
+import {
+ mapContextReferencesFromOAC,
+ mapContextPathToOAC,
+ mapSkillsToClaudeFormat,
+ mapSkillsFromClaudeFormat,
+} from "../mappers/ContextMapper.js";
+
+import {
+ analyzeCompatibility,
+ getToolCapabilities,
+ type Platform,
+ type CompatibilityResult,
+} from "../core/CapabilityMatrix.js";
+
+// ============================================================================
+// Types
+// ============================================================================
+
+/**
+ * Target platform for translation (excludes OAC since we translate TO/FROM OAC)
+ */
+export type TranslationTarget = Exclude;
+
+/**
+ * Configuration options for translation
+ */
+export interface TranslationOptions {
+ /** Strategy for handling 'ask' permissions (default: 'permissive') */
+ permissionStrategy?: DegradationStrategy;
+ /** Whether to include compatibility analysis (default: true) */
+ analyzeCompatibility?: boolean;
+ /** Whether to preserve unsupported features as comments (default: false) */
+ preserveAsComments?: boolean;
+ /** Custom model fallback if model not available on target */
+ modelFallback?: string;
+}
+
+/**
+ * Result of translating an agent
+ */
+export interface TranslationResult {
+ /** The translated agent frontmatter */
+ frontmatter: Partial;
+ /** Translated tools (platform format) */
+ tools?: Record;
+ /** Translated permissions (platform format) */
+ permissions?: BinaryPermissions;
+ /** Translated model ID */
+ model?: string;
+ /** Translated context paths */
+ contextPaths?: string[];
+ /** Translated skills (for Claude) */
+ skills?: string[];
+ /** All warnings generated during translation */
+ warnings: string[];
+ /** Compatibility analysis result */
+ compatibility?: CompatibilityResult;
+ /** Whether translation was successful */
+ success: boolean;
+}
+
+/**
+ * Result of translating from a platform back to OAC
+ */
+export interface ReverseTranslationResult {
+ /** Partial OAC agent that can be merged */
+ agent: Partial;
+ /** Warnings generated during translation */
+ warnings: string[];
+ /** Whether translation was successful */
+ success: boolean;
+}
+
+// ============================================================================
+// Default Options
+// ============================================================================
+
+const DEFAULT_OPTIONS: Required = {
+ permissionStrategy: "permissive",
+ analyzeCompatibility: true,
+ preserveAsComments: false,
+ modelFallback: "claude-sonnet-4",
+};
+
+// ============================================================================
+// TranslationEngine Class
+// ============================================================================
+
+/**
+ * Engine that orchestrates all mappers for complete agent translation.
+ */
+export class TranslationEngine {
+ private options: Required;
+
+ constructor(options: TranslationOptions = {}) {
+ this.options = { ...DEFAULT_OPTIONS, ...options };
+ }
+
+ // ==========================================================================
+ // OAC → Platform Translation
+ // ==========================================================================
+
+ /**
+ * Translate an OpenAgent to a target platform format.
+ *
+ * @param agent - The OpenAgent to translate
+ * @param target - Target platform
+ * @param options - Override default options
+ * @returns Translation result
+ */
+ translate(
+ agent: OpenAgent,
+ target: TranslationTarget,
+ options?: TranslationOptions
+ ): TranslationResult {
+ const opts = { ...this.options, ...options };
+ const warnings: string[] = [];
+
+ // Analyze compatibility first
+ let compatibility: CompatibilityResult | undefined;
+ if (opts.analyzeCompatibility) {
+ compatibility = analyzeCompatibility(agent, target);
+ warnings.push(...compatibility.warnings);
+ }
+
+ // Translate tools
+ let tools: Record | undefined;
+ if (agent.frontmatter.tools) {
+ const toolResult = mapToolAccessFromOAC(
+ agent.frontmatter.tools,
+ target
+ );
+ tools = toolResult.tools;
+ warnings.push(...toolResult.warnings);
+ }
+
+ // Translate permissions
+ let permissions: BinaryPermissions | undefined;
+ if (agent.frontmatter.permission) {
+ const permResult = mapPermissionsFromOAC(
+ agent.frontmatter.permission,
+ target,
+ opts.permissionStrategy
+ );
+ permissions = permResult.permissions as BinaryPermissions;
+ warnings.push(...permResult.warnings);
+ }
+
+ // Translate model
+ let model: string | undefined;
+ if (agent.frontmatter.model) {
+ const modelResult = mapModelFromOAC(
+ agent.frontmatter.model,
+ target
+ );
+ model = modelResult.id;
+ if (modelResult.warning) {
+ warnings.push(modelResult.warning);
+ }
+ }
+
+ // Translate contexts
+ let contextPaths: string[] | undefined;
+ if (agent.contexts && agent.contexts.length > 0) {
+ const contextResult = mapContextReferencesFromOAC(
+ agent.contexts,
+ target
+ );
+ contextPaths = contextResult.paths;
+ warnings.push(...contextResult.warnings);
+ }
+
+ // Translate skills (Claude-specific)
+ let skills: string[] | undefined;
+ if (target === "claude" && agent.frontmatter.skills) {
+ const skillResult = mapSkillsToClaudeFormat(agent.frontmatter.skills);
+ skills = skillResult.skills;
+ warnings.push(...skillResult.warnings);
+ }
+
+ // Build translated frontmatter
+ const frontmatter: Partial = {
+ name: agent.frontmatter.name,
+ description: agent.frontmatter.description,
+ mode: agent.frontmatter.mode,
+ };
+
+ // Include temperature if supported
+ if (agent.frontmatter.temperature !== undefined) {
+ const capabilities = getToolCapabilities(target);
+ if (capabilities.supportsTemperature) {
+ frontmatter.temperature = agent.frontmatter.temperature;
+ }
+ }
+
+ return {
+ frontmatter,
+ tools,
+ permissions,
+ model,
+ contextPaths,
+ skills,
+ warnings,
+ compatibility,
+ success: !compatibility || compatibility.compatible,
+ };
+ }
+
+ // ==========================================================================
+ // Platform → OAC Translation
+ // ==========================================================================
+
+ /**
+ * Translate from a platform format back to OAC format.
+ *
+ * @param source - Platform-specific agent data
+ * @param platform - Source platform
+ * @returns Partial OpenAgent that can be merged
+ */
+ translateToOAC(
+ source: {
+ name?: string;
+ description?: string;
+ tools?: Record;
+ permissions?: BinaryPermissions;
+ model?: string;
+ contextPaths?: string[];
+ skills?: string[];
+ systemPrompt?: string;
+ },
+ platform: TranslationTarget
+ ): ReverseTranslationResult {
+ const warnings: string[] = [];
+
+ // Translate tools
+ let oacTools: ToolAccess | undefined;
+ if (source.tools) {
+ const toolResult = mapToolAccessToOAC(source.tools, platform);
+ oacTools = toolResult.tools;
+ warnings.push(...toolResult.warnings);
+ }
+
+ // Translate permissions
+ let oacPermissions: GranularPermission | undefined;
+ if (source.permissions) {
+ const permResult = mapPermissionsToOAC(
+ source.permissions,
+ platform
+ );
+ oacPermissions = permResult.permissions as GranularPermission;
+ warnings.push(...permResult.warnings);
+ }
+
+ // Translate model
+ let oacModel: string | undefined;
+ if (source.model) {
+ const modelResult = mapModelToOAC(source.model, platform);
+ oacModel = modelResult.id;
+ if (modelResult.warning) {
+ warnings.push(modelResult.warning);
+ }
+ }
+
+ // Translate context paths
+ let oacContexts: ContextReference[] | undefined;
+ if (source.contextPaths && source.contextPaths.length > 0) {
+ oacContexts = source.contextPaths.map((path) => {
+ const result = mapContextPathToOAC(path, platform);
+ if (result.warning) {
+ warnings.push(result.warning);
+ }
+ return { path: result.path };
+ });
+ }
+
+ // Translate skills (from Claude format)
+ let oacSkills: SkillReference[] | undefined;
+ if (platform === "claude" && source.skills) {
+ oacSkills = mapSkillsFromClaudeFormat(source.skills);
+ }
+
+ // Build partial OpenAgent
+ const agent: Partial = {
+ frontmatter: {
+ name: source.name || "Unnamed Agent",
+ description: source.description || "Imported agent",
+ mode: "primary",
+ ...(oacTools && { tools: oacTools }),
+ ...(oacPermissions && { permission: oacPermissions }),
+ ...(oacModel && { model: oacModel }),
+ ...(oacSkills && { skills: oacSkills }),
+ },
+ ...(source.systemPrompt && { systemPrompt: source.systemPrompt }),
+ ...(oacContexts && { contexts: oacContexts }),
+ };
+
+ return {
+ agent,
+ warnings,
+ success: true,
+ };
+ }
+
+ // ==========================================================================
+ // Batch Translation
+ // ==========================================================================
+
+ /**
+ * Translate multiple agents to a target platform.
+ *
+ * @param agents - Array of OpenAgents to translate
+ * @param target - Target platform
+ * @returns Array of translation results
+ */
+ translateBatch(
+ agents: OpenAgent[],
+ target: TranslationTarget
+ ): TranslationResult[] {
+ return agents.map((agent) => this.translate(agent, target));
+ }
+
+ // ==========================================================================
+ // Utility Methods
+ // ==========================================================================
+
+ /**
+ * Get a preview of what will happen during translation without actually translating.
+ *
+ * @param agent - The agent to preview
+ * @param target - Target platform
+ * @returns Compatibility analysis
+ */
+ preview(agent: OpenAgent, target: TranslationTarget): CompatibilityResult {
+ return analyzeCompatibility(agent, target);
+ }
+
+ /**
+ * Check if an agent can be translated to a target with full fidelity.
+ *
+ * @param agent - The agent to check
+ * @param target - Target platform
+ * @returns True if no features will be lost
+ */
+ isFullyCompatible(agent: OpenAgent, target: TranslationTarget): boolean {
+ const result = analyzeCompatibility(agent, target);
+ return result.lost.length === 0 && result.degraded.length === 0;
+ }
+
+ /**
+ * Get the current translation options.
+ *
+ * @returns Current options
+ */
+ getOptions(): Required {
+ return { ...this.options };
+ }
+
+ /**
+ * Update translation options.
+ *
+ * @param options - Options to merge
+ */
+ setOptions(options: TranslationOptions): void {
+ this.options = { ...this.options, ...options };
+ }
+}
+
+// ============================================================================
+// Factory Functions
+// ============================================================================
+
+/**
+ * Create a TranslationEngine with default options.
+ *
+ * @returns New TranslationEngine instance
+ */
+export function createTranslationEngine(
+ options?: TranslationOptions
+): TranslationEngine {
+ return new TranslationEngine(options);
+}
+
+/**
+ * Quick translate function for one-off translations.
+ *
+ * @param agent - Agent to translate
+ * @param target - Target platform
+ * @param options - Translation options
+ * @returns Translation result
+ */
+export function translate(
+ agent: OpenAgent,
+ target: TranslationTarget,
+ options?: TranslationOptions
+): TranslationResult {
+ const engine = new TranslationEngine(options);
+ return engine.translate(agent, target);
+}
+
+/**
+ * Quick preview function for one-off compatibility checks.
+ *
+ * @param agent - Agent to preview
+ * @param target - Target platform
+ * @returns Compatibility result
+ */
+export function previewTranslation(
+ agent: OpenAgent,
+ target: TranslationTarget
+): CompatibilityResult {
+ return analyzeCompatibility(agent, target);
+}
diff --git a/packages/compatibility-layer/src/index.ts b/packages/compatibility-layer/src/index.ts
new file mode 100644
index 0000000..0c36be7
--- /dev/null
+++ b/packages/compatibility-layer/src/index.ts
@@ -0,0 +1,335 @@
+/**
+ * @openagents-control/compatibility-layer
+ *
+ * A TypeScript library for converting OpenAgents Control agent definitions
+ * to and from other AI coding tool formats (Cursor, Claude, Windsurf, etc.).
+ *
+ * @module @openagents-control/compatibility-layer
+ *
+ * @example
+ * ```typescript
+ * import { loadAgent, registry, CursorAdapter } from '@openagents-control/compatibility-layer';
+ *
+ * // Load an OAC agent
+ * const agent = await loadAgent('.opencode/agent/opencoder.md');
+ *
+ * // Convert to Cursor format
+ * const cursorAdapter = registry.get('cursor');
+ * const result = await cursorAdapter.fromOAC(agent);
+ * ```
+ */
+
+// ============================================================================
+// TYPES & SCHEMAS
+// ============================================================================
+
+/**
+ * All Zod schemas and TypeScript types for OpenAgents Control.
+ *
+ * Includes:
+ * - OpenAgent, AgentFrontmatter, AgentMetadata
+ * - ToolAccess, PermissionRule, GranularPermission
+ * - ContextReference, DependencyReference
+ * - ModelIdentifier, TemperatureSchema
+ * - SkillReference, HookDefinition
+ * - ToolCapabilities, ConversionResult
+ */
+export * from "./types.js";
+
+// ============================================================================
+// CORE - Agent Loading
+// ============================================================================
+
+/**
+ * AgentLoader class and convenience functions for loading OAC agent files.
+ *
+ * @example
+ * ```typescript
+ * import { loadAgent, loadAgents } from '@openagents-control/compatibility-layer';
+ *
+ * // Load single agent
+ * const agent = await loadAgent('.opencode/agent/opencoder.md');
+ *
+ * // Load all agents from directory
+ * const agents = await loadAgents('.opencode/agent/');
+ * ```
+ */
+export {
+ AgentLoader,
+ loadAgent,
+ loadAgents,
+} from "./core/AgentLoader.js";
+
+/**
+ * Error classes from AgentLoader for handling load failures.
+ *
+ * - AgentLoadError: Base error for agent loading
+ * - FrontmatterParseError: YAML parsing errors
+ * - ValidationError: Zod schema validation errors
+ */
+export {
+ AgentLoadError,
+ FrontmatterParseError,
+ ValidationError,
+} from "./core/AgentLoader.js";
+
+// ============================================================================
+// CORE - Adapter Registry
+// ============================================================================
+
+/**
+ * AdapterRegistry for managing tool adapters.
+ *
+ * @example
+ * ```typescript
+ * import { registry, getAdapter } from '@openagents-control/compatibility-layer';
+ *
+ * // Get adapter from registry
+ * const adapter = getAdapter('cursor');
+ *
+ * // List all adapters
+ * const names = registry.list();
+ * ```
+ */
+export {
+ AdapterRegistry,
+ registry,
+ getAdapter,
+ listAdapters,
+ getAllCapabilities,
+} from "./core/AdapterRegistry.js";
+
+/**
+ * Error class for adapter registry operations.
+ */
+export { AdapterRegistryError } from "./core/AdapterRegistry.js";
+
+/**
+ * Type for adapter information including capabilities.
+ */
+export type { AdapterInfo } from "./core/AdapterRegistry.js";
+
+// ============================================================================
+// ADAPTERS - Base Class
+// ============================================================================
+
+/**
+ * BaseAdapter abstract class for creating custom adapters.
+ *
+ * @example
+ * ```typescript
+ * import { BaseAdapter } from '@openagents-control/compatibility-layer';
+ *
+ * class MyAdapter extends BaseAdapter {
+ * readonly name = 'my-tool';
+ * readonly displayName = 'My Tool';
+ *
+ * async toOAC(source: string): Promise {
+ * // Implementation
+ * }
+ *
+ * async fromOAC(agent: OpenAgent): Promise {
+ * // Implementation
+ * }
+ * }
+ * ```
+ */
+export { BaseAdapter } from "./adapters/BaseAdapter.js";
+
+// ============================================================================
+// ADAPTERS - Built-in Implementations
+// ============================================================================
+
+/**
+ * Built-in adapters for popular AI coding tools.
+ *
+ * Note: These will be available in Phase 2.
+ * For now, they need to be registered manually.
+ *
+ * @example
+ * ```typescript
+ * // Available in Phase 2:
+ * // import { CursorAdapter, ClaudeAdapter, WindsurfAdapter } from '@openagents-control/compatibility-layer';
+ * ```
+ */
+
+// Phase 2 adapters (implemented)
+export { CursorAdapter } from "./adapters/CursorAdapter.js";
+export { ClaudeAdapter } from "./adapters/ClaudeAdapter.js";
+export { WindsurfAdapter } from "./adapters/WindsurfAdapter.js";
+
+// ============================================================================
+// MAPPERS - Feature Translation (Phase 3)
+// ============================================================================
+
+/**
+ * ToolMapper for translating tool names between platforms.
+ *
+ * @example
+ * ```typescript
+ * import { mapToolFromOAC, mapToolToOAC } from '@openagents-control/compatibility-layer';
+ *
+ * mapToolFromOAC('bash', 'cursor'); // => { name: 'terminal', exact: true }
+ * ```
+ */
+export {
+ mapToolToOAC,
+ mapToolFromOAC,
+ mapToolAccessFromOAC,
+ mapToolAccessToOAC,
+ getSupportedTools,
+ getUnsupportedTools,
+ isToolSupported,
+ type ToolPlatform,
+ type ToolMappingConfig,
+ type ToolMappingResult,
+} from "./mappers/ToolMapper.js";
+
+/**
+ * PermissionMapper for translating between granular and binary permissions.
+ *
+ * @example
+ * ```typescript
+ * import { mapPermissionsFromOAC } from '@openagents-control/compatibility-layer';
+ *
+ * mapPermissionsFromOAC({ bash: { "*": "allow" } }, 'claude');
+ * // => { permissions: { bash: true }, warnings: [...] }
+ * ```
+ */
+export {
+ resolvePermissionRule,
+ isGranularRule,
+ mapPermissionsFromOAC,
+ mapPermissionsToOAC,
+ createGranularRule,
+ extractPatterns,
+ mergePermissionRules,
+ hasGranularPermissions,
+ hasAskPermissions,
+ analyzePermissionDegradation,
+ type PermissionPlatform,
+ type BinaryPermissions,
+ type PermissionMappingResult,
+ type DegradationStrategy,
+} from "./mappers/PermissionMapper.js";
+
+/**
+ * ModelMapper for translating AI model identifiers.
+ *
+ * @example
+ * ```typescript
+ * import { mapModelFromOAC, getModelsForPlatform } from '@openagents-control/compatibility-layer';
+ *
+ * mapModelFromOAC('claude-sonnet-4', 'cursor');
+ * // => { id: 'claude-sonnet-4', exact: true }
+ * ```
+ */
+export {
+ mapModelFromOAC,
+ mapModelToOAC,
+ getModelFamily,
+ getModelInfo,
+ getAllModels,
+ getModelsForPlatform,
+ isModelAvailable,
+ getDefaultModel,
+ type ModelPlatform,
+ type ModelMappingResult,
+ type ModelFamily,
+ type ModelInfo,
+} from "./mappers/ModelMapper.js";
+
+/**
+ * ContextMapper for translating context file paths.
+ *
+ * @example
+ * ```typescript
+ * import { mapContextPathFromOAC } from '@openagents-control/compatibility-layer';
+ *
+ * mapContextPathFromOAC('.opencode/context/core/standards.md', 'claude');
+ * // => { path: '.claude/skills/core-standards.md', exact: true }
+ * ```
+ */
+export {
+ mapContextPathFromOAC,
+ mapContextPathToOAC,
+ mapContextReferenceFromOAC,
+ mapContextReferencesFromOAC,
+ mapSkillsToClaudeFormat,
+ mapSkillsFromClaudeFormat,
+ getContextBaseDir,
+ supportsExternalContext,
+ supportsContextSubdirs,
+ supportsContextPriority,
+ createContextReference,
+ normalizeContextPath,
+ getRelativeContextPath,
+ type ContextPlatform,
+ type ContextMappingResult,
+} from "./mappers/ContextMapper.js";
+
+// ============================================================================
+// CORE - Capability Matrix & Translation Engine (Phase 3)
+// ============================================================================
+
+/**
+ * CapabilityMatrix for feature compatibility analysis.
+ *
+ * @example
+ * ```typescript
+ * import { analyzeCompatibility, getToolCapabilities } from '@openagents-control/compatibility-layer';
+ *
+ * const result = analyzeCompatibility(agent, 'cursor');
+ * // => { compatible: false, warnings: [...], blockers: [...] }
+ * ```
+ */
+export {
+ getCapabilityMatrix,
+ getFeaturesByCategory,
+ getFeatureSupport,
+ isFeatureSupported,
+ analyzeCompatibility,
+ getToolCapabilities,
+ comparePlatforms,
+ getConversionSummary,
+ type Platform,
+ type FeatureCategory,
+ type SupportLevel,
+ type FeatureDefinition,
+ type CompatibilityResult,
+} from "./core/CapabilityMatrix.js";
+
+/**
+ * TranslationEngine for orchestrating complete agent translation.
+ *
+ * @example
+ * ```typescript
+ * import { TranslationEngine, translate } from '@openagents-control/compatibility-layer';
+ *
+ * // Using the engine
+ * const engine = new TranslationEngine();
+ * const result = engine.translate(agent, 'cursor');
+ *
+ * // Quick translate
+ * const result = translate(agent, 'cursor');
+ * ```
+ */
+export {
+ TranslationEngine,
+ createTranslationEngine,
+ translate,
+ previewTranslation,
+ type TranslationTarget,
+ type TranslationOptions,
+ type TranslationResult,
+ type ReverseTranslationResult,
+} from "./core/TranslationEngine.js";
+
+// ============================================================================
+// VERSION INFO
+// ============================================================================
+
+/**
+ * Package version (injected at build time)
+ */
+export const VERSION = "0.1.0";
diff --git a/packages/compatibility-layer/src/mappers/ContextMapper.ts b/packages/compatibility-layer/src/mappers/ContextMapper.ts
new file mode 100644
index 0000000..a4f1fb2
--- /dev/null
+++ b/packages/compatibility-layer/src/mappers/ContextMapper.ts
@@ -0,0 +1,384 @@
+/**
+ * ContextMapper - Maps context file paths between OAC and other platforms
+ *
+ * Different tools store context/skills in different locations with different
+ * naming conventions. This mapper handles path translation.
+ *
+ * @example
+ * ```ts
+ * mapContextPathFromOAC('.opencode/context/core/standards.md', 'claude')
+ * // => { path: '.claude/skills/core-standards.md', exact: true }
+ * ```
+ */
+
+import type { ContextReference, SkillReference } from "../types.js";
+
+// ============================================================================
+// Types
+// ============================================================================
+
+export type ContextPlatform = "oac" | "claude" | "cursor" | "windsurf";
+
+/**
+ * Result of a context path mapping operation
+ */
+export interface ContextMappingResult {
+ /** The mapped path */
+ path: string;
+ /** Whether the mapping was exact or approximated */
+ exact: boolean;
+ /** Warning message if mapping was degraded */
+ warning?: string;
+}
+
+/**
+ * Configuration for context locations per platform
+ */
+interface ContextConfig {
+ /** Base directory for context files */
+ baseDir: string;
+ /** File extension for context files */
+ extension: string;
+ /** Supports subdirectories */
+ supportsSubdirs: boolean;
+ /** Supports priority metadata */
+ supportsPriority: boolean;
+ /** Inline-only (no external files) */
+ inlineOnly: boolean;
+}
+
+// ============================================================================
+// Platform Configurations
+// ============================================================================
+
+const CONTEXT_CONFIGS: Record = {
+ oac: {
+ baseDir: ".opencode/context",
+ extension: ".md",
+ supportsSubdirs: true,
+ supportsPriority: true,
+ inlineOnly: false,
+ },
+ claude: {
+ baseDir: ".claude/skills",
+ extension: ".md",
+ supportsSubdirs: true,
+ supportsPriority: false,
+ inlineOnly: false,
+ },
+ cursor: {
+ baseDir: "",
+ extension: "",
+ supportsSubdirs: false,
+ supportsPriority: false,
+ inlineOnly: true, // Cursor uses inline context in .cursorrules
+ },
+ windsurf: {
+ baseDir: ".windsurf/context",
+ extension: ".md",
+ supportsSubdirs: true,
+ supportsPriority: false,
+ inlineOnly: false,
+ },
+};
+
+// ============================================================================
+// Core Mapping Functions (Pure)
+// ============================================================================
+
+/**
+ * Map a context file path from OAC format to a target platform format.
+ *
+ * @param oacPath - OAC context path (e.g., '.opencode/context/core/standards.md')
+ * @param platform - Target platform
+ * @returns Mapping result with platform path
+ */
+export function mapContextPathFromOAC(
+ oacPath: string,
+ platform: Exclude
+): ContextMappingResult {
+ const oacConfig = CONTEXT_CONFIGS.oac;
+ const targetConfig = CONTEXT_CONFIGS[platform];
+
+ // Handle inline-only platforms
+ if (targetConfig.inlineOnly) {
+ return {
+ path: "",
+ exact: false,
+ warning: `${platform} does not support external context files - content must be inline`,
+ };
+ }
+
+ // Remove OAC base dir prefix
+ let relativePath = oacPath;
+ if (oacPath.startsWith(oacConfig.baseDir)) {
+ relativePath = oacPath.slice(oacConfig.baseDir.length + 1); // +1 for the /
+ }
+
+ // Handle subdirectories
+ if (!targetConfig.supportsSubdirs && relativePath.includes("/")) {
+ // Flatten the path by replacing / with -
+ const flatPath = relativePath.replace(/\//g, "-");
+ const targetPath = `${targetConfig.baseDir}/${flatPath}`;
+
+ return {
+ path: targetPath,
+ exact: false,
+ warning: `${platform} does not support subdirectories - path flattened`,
+ };
+ }
+
+ // Build target path
+ const targetPath = targetConfig.baseDir
+ ? `${targetConfig.baseDir}/${relativePath}`
+ : relativePath;
+
+ return { path: targetPath, exact: true };
+}
+
+/**
+ * Map a context file path from a platform format to OAC format.
+ *
+ * @param platformPath - Platform context path
+ * @param platform - Source platform
+ * @returns Mapping result with OAC path
+ */
+export function mapContextPathToOAC(
+ platformPath: string,
+ platform: Exclude
+): ContextMappingResult {
+ const sourceConfig = CONTEXT_CONFIGS[platform];
+ const oacConfig = CONTEXT_CONFIGS.oac;
+
+ // Handle inline-only platforms
+ if (sourceConfig.inlineOnly) {
+ return {
+ path: "",
+ exact: false,
+ warning: `${platform} uses inline context - no path to convert`,
+ };
+ }
+
+ // Remove platform base dir prefix
+ let relativePath = platformPath;
+ if (sourceConfig.baseDir && platformPath.startsWith(sourceConfig.baseDir)) {
+ relativePath = platformPath.slice(sourceConfig.baseDir.length + 1);
+ }
+
+ // Build OAC path
+ const oacPath = `${oacConfig.baseDir}/${relativePath}`;
+
+ return { path: oacPath, exact: true };
+}
+
+// ============================================================================
+// Context Reference Mapping
+// ============================================================================
+
+/**
+ * Map an OAC ContextReference to a platform path.
+ *
+ * @param context - OAC context reference
+ * @param platform - Target platform
+ * @returns Mapping result
+ */
+export function mapContextReferenceFromOAC(
+ context: ContextReference,
+ platform: Exclude
+): ContextMappingResult & { priority?: string } {
+ const pathResult = mapContextPathFromOAC(context.path, platform);
+
+ // Add priority warning if target doesn't support it
+ const targetConfig = CONTEXT_CONFIGS[platform];
+ const warnings: string[] = [];
+
+ if (pathResult.warning) {
+ warnings.push(pathResult.warning);
+ }
+
+ if (context.priority && !targetConfig.supportsPriority) {
+ warnings.push(
+ `${platform} does not support priority metadata - '${context.priority}' will be ignored`
+ );
+ }
+
+ return {
+ path: pathResult.path,
+ exact: pathResult.exact && warnings.length === 0,
+ warning: warnings.length > 0 ? warnings.join("; ") : undefined,
+ priority: context.priority,
+ };
+}
+
+/**
+ * Map multiple OAC context references to a platform.
+ *
+ * @param contexts - Array of OAC context references
+ * @param platform - Target platform
+ * @returns Object with mapped paths and warnings
+ */
+export function mapContextReferencesFromOAC(
+ contexts: ContextReference[],
+ platform: Exclude
+): { paths: string[]; warnings: string[] } {
+ const paths: string[] = [];
+ const warnings: string[] = [];
+
+ for (const context of contexts) {
+ const result = mapContextReferenceFromOAC(context, platform);
+ if (result.path) {
+ paths.push(result.path);
+ }
+ if (result.warning) {
+ warnings.push(result.warning);
+ }
+ }
+
+ return { paths, warnings };
+}
+
+// ============================================================================
+// Skill Reference Mapping (OAC → Claude)
+// ============================================================================
+
+/**
+ * Map OAC skills to Claude skills format.
+ *
+ * @param skills - OAC skill references
+ * @returns Object with Claude skill paths and warnings
+ */
+export function mapSkillsToClaudeFormat(
+ skills: SkillReference[]
+): { skills: string[]; warnings: string[] } {
+ const claudeSkills: string[] = [];
+ const warnings: string[] = [];
+
+ for (const skill of skills) {
+ if (typeof skill === "string") {
+ // Simple string skill
+ claudeSkills.push(`.claude/skills/${skill}.md`);
+ } else {
+ // Object skill with config
+ claudeSkills.push(`.claude/skills/${skill.name}.md`);
+ if (skill.config && Object.keys(skill.config).length > 0) {
+ warnings.push(
+ `Skill config for '${skill.name}' will be ignored - Claude skills don't support inline config`
+ );
+ }
+ }
+ }
+
+ return { skills: claudeSkills, warnings };
+}
+
+/**
+ * Map Claude skills to OAC skill references.
+ *
+ * @param skillPaths - Claude skill paths
+ * @returns Array of OAC skill references
+ */
+export function mapSkillsFromClaudeFormat(
+ skillPaths: string[]
+): SkillReference[] {
+ return skillPaths.map((path): SkillReference => {
+ // Extract skill name from path
+ const match = path.match(/\/([^/]+)\.md$/);
+ // Use the matched name or fall back to the full path
+ return match?.[1] ?? path;
+ });
+}
+
+// ============================================================================
+// Utility Functions
+// ============================================================================
+
+/**
+ * Get the context base directory for a platform.
+ *
+ * @param platform - Target platform
+ * @returns Base directory path
+ */
+export function getContextBaseDir(platform: ContextPlatform): string {
+ return CONTEXT_CONFIGS[platform].baseDir;
+}
+
+/**
+ * Check if a platform supports external context files.
+ *
+ * @param platform - Target platform
+ * @returns True if external files are supported
+ */
+export function supportsExternalContext(platform: ContextPlatform): boolean {
+ return !CONTEXT_CONFIGS[platform].inlineOnly;
+}
+
+/**
+ * Check if a platform supports context subdirectories.
+ *
+ * @param platform - Target platform
+ * @returns True if subdirectories are supported
+ */
+export function supportsContextSubdirs(platform: ContextPlatform): boolean {
+ return CONTEXT_CONFIGS[platform].supportsSubdirs;
+}
+
+/**
+ * Check if a platform supports context priority metadata.
+ *
+ * @param platform - Target platform
+ * @returns True if priority is supported
+ */
+export function supportsContextPriority(platform: ContextPlatform): boolean {
+ return CONTEXT_CONFIGS[platform].supportsPriority;
+}
+
+/**
+ * Create an OAC context reference.
+ *
+ * @param path - Context file path
+ * @param priority - Optional priority level
+ * @param description - Optional description
+ * @returns ContextReference object
+ */
+export function createContextReference(
+ path: string,
+ priority?: "critical" | "high" | "medium" | "low",
+ description?: string
+): ContextReference {
+ return {
+ path,
+ ...(priority && { priority }),
+ ...(description && { description }),
+ };
+}
+
+/**
+ * Normalize a context path (remove redundant slashes, etc.).
+ *
+ * @param path - Context path to normalize
+ * @returns Normalized path
+ */
+export function normalizeContextPath(path: string): string {
+ return path
+ .replace(/\/+/g, "/") // Remove duplicate slashes
+ .replace(/^\//, "") // Remove leading slash
+ .replace(/\/$/, ""); // Remove trailing slash
+}
+
+/**
+ * Get the relative path within the context directory.
+ *
+ * @param fullPath - Full context file path
+ * @param platform - Platform the path belongs to
+ * @returns Relative path within context directory
+ */
+export function getRelativeContextPath(
+ fullPath: string,
+ platform: ContextPlatform
+): string {
+ const baseDir = CONTEXT_CONFIGS[platform].baseDir;
+ if (baseDir && fullPath.startsWith(baseDir)) {
+ return fullPath.slice(baseDir.length + 1);
+ }
+ return fullPath;
+}
diff --git a/packages/compatibility-layer/src/mappers/ModelMapper.ts b/packages/compatibility-layer/src/mappers/ModelMapper.ts
new file mode 100644
index 0000000..2edeb34
--- /dev/null
+++ b/packages/compatibility-layer/src/mappers/ModelMapper.ts
@@ -0,0 +1,413 @@
+/**
+ * ModelMapper - Maps AI model identifiers between OAC and other platforms
+ *
+ * Different tools use different model ID formats. This mapper handles
+ * bidirectional translation with fallbacks for unknown models.
+ *
+ * @example
+ * ```ts
+ * mapModelFromOAC('claude-sonnet-4', 'claude')
+ * // => { id: 'claude-sonnet-4-20250514', exact: true }
+ *
+ * mapModelToOAC('gpt-4-turbo', 'cursor')
+ * // => { id: 'gpt-4-turbo', exact: true }
+ * ```
+ */
+
+// ============================================================================
+// Types
+// ============================================================================
+
+export type ModelPlatform = "oac" | "claude" | "cursor" | "windsurf";
+
+/**
+ * Result of a model mapping operation
+ */
+export interface ModelMappingResult {
+ /** The mapped model ID */
+ id: string;
+ /** Whether the mapping was exact or a fallback */
+ exact: boolean;
+ /** Warning message if mapping was approximated */
+ warning?: string;
+}
+
+/**
+ * Model family for grouping related models
+ */
+export type ModelFamily = "claude" | "gpt" | "gemini" | "llama" | "mistral" | "other";
+
+/**
+ * Model information with metadata
+ */
+export interface ModelInfo {
+ /** Canonical OAC model ID */
+ oacId: string;
+ /** Model family */
+ family: ModelFamily;
+ /** Platform-specific IDs */
+ platformIds: Partial, string>>;
+ /** Human-readable display name */
+ displayName: string;
+}
+
+// ============================================================================
+// Model Registry
+// ============================================================================
+
+/**
+ * Registry of known models with their platform-specific IDs
+ */
+const MODEL_REGISTRY: ModelInfo[] = [
+ // Claude Models
+ {
+ oacId: "claude-sonnet-4",
+ family: "claude",
+ displayName: "Claude Sonnet 4",
+ platformIds: {
+ claude: "claude-sonnet-4-20250514",
+ cursor: "claude-sonnet-4",
+ windsurf: "claude-4-sonnet",
+ },
+ },
+ {
+ oacId: "claude-opus-4",
+ family: "claude",
+ displayName: "Claude Opus 4",
+ platformIds: {
+ claude: "claude-opus-4-20250514",
+ cursor: "claude-opus-4",
+ windsurf: "claude-4-opus",
+ },
+ },
+ {
+ oacId: "claude-3.5-sonnet",
+ family: "claude",
+ displayName: "Claude 3.5 Sonnet",
+ platformIds: {
+ claude: "claude-3-5-sonnet-20241022",
+ cursor: "claude-3.5-sonnet",
+ windsurf: "claude-3-5-sonnet",
+ },
+ },
+ {
+ oacId: "claude-3-opus",
+ family: "claude",
+ displayName: "Claude 3 Opus",
+ platformIds: {
+ claude: "claude-3-opus-20240229",
+ cursor: "claude-3-opus",
+ windsurf: "claude-3-opus",
+ },
+ },
+ {
+ oacId: "claude-3-haiku",
+ family: "claude",
+ displayName: "Claude 3 Haiku",
+ platformIds: {
+ claude: "claude-3-haiku-20240307",
+ cursor: "claude-3-haiku",
+ windsurf: "claude-3-haiku",
+ },
+ },
+
+ // GPT Models
+ {
+ oacId: "gpt-4",
+ family: "gpt",
+ displayName: "GPT-4",
+ platformIds: {
+ cursor: "gpt-4",
+ windsurf: "gpt-4",
+ },
+ },
+ {
+ oacId: "gpt-4-turbo",
+ family: "gpt",
+ displayName: "GPT-4 Turbo",
+ platformIds: {
+ cursor: "gpt-4-turbo",
+ windsurf: "gpt-4-turbo",
+ },
+ },
+ {
+ oacId: "gpt-4o",
+ family: "gpt",
+ displayName: "GPT-4o",
+ platformIds: {
+ cursor: "gpt-4o",
+ windsurf: "gpt-4o",
+ },
+ },
+ {
+ oacId: "gpt-4o-mini",
+ family: "gpt",
+ displayName: "GPT-4o Mini",
+ platformIds: {
+ cursor: "gpt-4o-mini",
+ windsurf: "gpt-4o-mini",
+ },
+ },
+
+ // Gemini Models
+ {
+ oacId: "gemini-pro",
+ family: "gemini",
+ displayName: "Gemini Pro",
+ platformIds: {
+ cursor: "gemini-pro",
+ windsurf: "gemini-pro",
+ },
+ },
+ {
+ oacId: "gemini-2.0-flash",
+ family: "gemini",
+ displayName: "Gemini 2.0 Flash",
+ platformIds: {
+ cursor: "gemini-2.0-flash",
+ windsurf: "gemini-2-flash",
+ },
+ },
+];
+
+// ============================================================================
+// Lookup Maps (Built from Registry)
+// ============================================================================
+
+/**
+ * Build lookup maps for fast bidirectional mapping
+ */
+function buildLookupMaps(): {
+ oacToplatform: Map, string>>;
+ platformToOac: Map, Map>;
+} {
+ const oacToplatform = new Map, string>>();
+ const platformToOac = new Map, Map>();
+
+ // Initialize platform maps
+ const platforms: Exclude[] = ["claude", "cursor", "windsurf"];
+ for (const platform of platforms) {
+ platformToOac.set(platform, new Map());
+ }
+
+ // Populate maps from registry
+ for (const model of MODEL_REGISTRY) {
+ const platformMap = new Map, string>();
+
+ for (const [platform, id] of Object.entries(model.platformIds)) {
+ const p = platform as Exclude;
+ if (id) {
+ platformMap.set(p, id);
+ platformToOac.get(p)!.set(id, model.oacId);
+ }
+ }
+
+ oacToplatform.set(model.oacId, platformMap);
+ }
+
+ return { oacToplatform, platformToOac };
+}
+
+const { oacToplatform, platformToOac } = buildLookupMaps();
+
+// ============================================================================
+// Core Mapping Functions (Pure)
+// ============================================================================
+
+/**
+ * Map a model ID from OAC format to a target platform format.
+ *
+ * @param modelId - OAC model ID
+ * @param platform - Target platform
+ * @returns Mapping result with platform model ID
+ */
+export function mapModelFromOAC(
+ modelId: string,
+ platform: Exclude
+): ModelMappingResult {
+ const platformMap = oacToplatform.get(modelId);
+
+ if (platformMap) {
+ const platformId = platformMap.get(platform);
+ if (platformId) {
+ return { id: platformId, exact: true };
+ }
+ }
+
+ // Check if the model ID looks like it's already in platform format
+ if (isLikelyPlatformFormat(modelId, platform)) {
+ return { id: modelId, exact: true };
+ }
+
+ // Fallback: use OAC ID as-is with warning
+ return {
+ id: modelId,
+ exact: false,
+ warning: `Unknown model '${modelId}' for ${platform}, using as-is`,
+ };
+}
+
+/**
+ * Map a model ID from a platform format to OAC format.
+ *
+ * @param modelId - Platform model ID
+ * @param platform - Source platform
+ * @returns Mapping result with OAC model ID
+ */
+export function mapModelToOAC(
+ modelId: string,
+ platform: Exclude
+): ModelMappingResult {
+ const oacMap = platformToOac.get(platform);
+
+ if (oacMap) {
+ const oacId = oacMap.get(modelId);
+ if (oacId) {
+ return { id: oacId, exact: true };
+ }
+ }
+
+ // Try to find by pattern matching
+ const guessedId = guessOACModelId(modelId);
+ if (guessedId !== modelId) {
+ return {
+ id: guessedId,
+ exact: false,
+ warning: `Model '${modelId}' from ${platform} mapped to '${guessedId}' by pattern`,
+ };
+ }
+
+ // Fallback: use platform ID as-is
+ return {
+ id: modelId,
+ exact: false,
+ warning: `Unknown model '${modelId}' from ${platform}, using as-is`,
+ };
+}
+
+// ============================================================================
+// Utility Functions
+// ============================================================================
+
+/**
+ * Check if a model ID looks like it's in a specific platform's format.
+ */
+function isLikelyPlatformFormat(
+ modelId: string,
+ platform: Exclude
+): boolean {
+ switch (platform) {
+ case "claude":
+ // Claude uses date suffixes like "20250514"
+ return /claude-.*-\d{8}$/.test(modelId);
+ case "cursor":
+ case "windsurf":
+ // These typically use simpler IDs
+ return !/-\d{8}$/.test(modelId);
+ default:
+ return false;
+ }
+}
+
+/**
+ * Attempt to guess the OAC model ID from a platform-specific ID.
+ */
+function guessOACModelId(platformId: string): string {
+ // Remove date suffixes (Claude format)
+ let guessed = platformId.replace(/-\d{8}$/, "");
+
+ // Normalize common variations
+ guessed = guessed
+ .replace(/^claude-(\d+)-(\d+)-/, "claude-$1.$2-") // claude-3-5 → claude-3.5
+ .replace(/^claude-(\d+)-/, "claude-$1-") // keep claude-3- as is
+ .replace(/-sonnet$/, "-sonnet") // ensure sonnet suffix
+ .replace(/-opus$/, "-opus") // ensure opus suffix
+ .replace(/-haiku$/, "-haiku"); // ensure haiku suffix
+
+ return guessed;
+}
+
+/**
+ * Get the model family for a given model ID.
+ *
+ * @param modelId - Model ID (OAC or platform format)
+ * @returns Model family or 'other' if unknown
+ */
+export function getModelFamily(modelId: string): ModelFamily {
+ const lowerModelId = modelId.toLowerCase();
+
+ if (lowerModelId.includes("claude")) return "claude";
+ if (lowerModelId.includes("gpt")) return "gpt";
+ if (lowerModelId.includes("gemini")) return "gemini";
+ if (lowerModelId.includes("llama")) return "llama";
+ if (lowerModelId.includes("mistral")) return "mistral";
+
+ return "other";
+}
+
+/**
+ * Get model info from the registry by OAC ID.
+ *
+ * @param oacId - OAC model ID
+ * @returns Model info or undefined if not found
+ */
+export function getModelInfo(oacId: string): ModelInfo | undefined {
+ return MODEL_REGISTRY.find((m) => m.oacId === oacId);
+}
+
+/**
+ * Get all registered models.
+ *
+ * @returns Array of all model info objects
+ */
+export function getAllModels(): ModelInfo[] {
+ return [...MODEL_REGISTRY];
+}
+
+/**
+ * Get models available on a specific platform.
+ *
+ * @param platform - Target platform
+ * @returns Array of model info objects available on the platform
+ */
+export function getModelsForPlatform(
+ platform: Exclude
+): ModelInfo[] {
+ return MODEL_REGISTRY.filter(
+ (m) => m.platformIds[platform] !== undefined
+ );
+}
+
+/**
+ * Check if a model is available on a specific platform.
+ *
+ * @param modelId - OAC model ID
+ * @param platform - Target platform
+ * @returns True if the model is available
+ */
+export function isModelAvailable(
+ modelId: string,
+ platform: Exclude
+): boolean {
+ const platformMap = oacToplatform.get(modelId);
+ return platformMap?.has(platform) ?? false;
+}
+
+/**
+ * Get a default/fallback model for a platform.
+ *
+ * @param platform - Target platform
+ * @returns Default model ID for the platform
+ */
+export function getDefaultModel(platform: Exclude): string {
+ switch (platform) {
+ case "claude":
+ return "claude-sonnet-4-20250514";
+ case "cursor":
+ return "claude-sonnet-4";
+ case "windsurf":
+ return "claude-4-sonnet";
+ default:
+ return "claude-sonnet-4";
+ }
+}
diff --git a/packages/compatibility-layer/src/mappers/PermissionMapper.ts b/packages/compatibility-layer/src/mappers/PermissionMapper.ts
new file mode 100644
index 0000000..086bb9d
--- /dev/null
+++ b/packages/compatibility-layer/src/mappers/PermissionMapper.ts
@@ -0,0 +1,354 @@
+/**
+ * PermissionMapper - Translates permissions between OAC granular and tool binary formats
+ *
+ * OAC supports granular permissions with allow/deny/ask patterns.
+ * Most other tools only support binary (on/off) permissions.
+ * This mapper handles the translation with graceful degradation.
+ *
+ * @example
+ * ```ts
+ * // OAC granular → Claude binary
+ * mapPermissionsFromOAC({ bash: { "rm *": "deny", "*": "allow" } }, 'claude')
+ * // => { bash: true, warnings: ["Granular bash permissions degraded to binary"] }
+ * ```
+ */
+
+import type { GranularPermission, PermissionRule } from "../types.js";
+
+// ============================================================================
+// Types
+// ============================================================================
+
+export type PermissionPlatform = "oac" | "claude" | "cursor" | "windsurf";
+
+/**
+ * Binary permission representation used by most tools
+ */
+export interface BinaryPermissions {
+ read?: boolean;
+ write?: boolean;
+ edit?: boolean;
+ bash?: boolean;
+ task?: boolean;
+ grep?: boolean;
+ glob?: boolean;
+}
+
+/**
+ * Result of permission mapping
+ */
+export interface PermissionMappingResult {
+ permissions: BinaryPermissions | GranularPermission;
+ warnings: string[];
+}
+
+/**
+ * Permission strategy when converting from granular to binary
+ */
+export type DegradationStrategy = "permissive" | "restrictive" | "ask-as-deny";
+
+// ============================================================================
+// Configuration
+// ============================================================================
+
+/**
+ * Platform-specific permission capabilities
+ */
+const PLATFORM_CAPABILITIES: Record<
+ Exclude,
+ { supportsGranular: boolean; supportsAsk: boolean }
+> = {
+ claude: { supportsGranular: false, supportsAsk: false },
+ cursor: { supportsGranular: false, supportsAsk: false },
+ windsurf: { supportsGranular: false, supportsAsk: false },
+};
+
+// ============================================================================
+// Core Mapping Functions (Pure)
+// ============================================================================
+
+/**
+ * Resolve a single permission rule to a boolean value.
+ *
+ * @param rule - Permission rule (allow/deny/ask/boolean/record)
+ * @param strategy - How to handle 'ask' permissions
+ * @returns Boolean permission value
+ */
+export function resolvePermissionRule(
+ rule: PermissionRule,
+ strategy: DegradationStrategy = "permissive"
+): boolean {
+ // Handle boolean directly
+ if (typeof rule === "boolean") {
+ return rule;
+ }
+
+ // Handle literal strings
+ if (rule === "allow") return true;
+ if (rule === "deny") return false;
+ if (rule === "ask") {
+ switch (strategy) {
+ case "permissive":
+ return true;
+ case "restrictive":
+ case "ask-as-deny":
+ return false;
+ }
+ }
+
+ // Handle record (complex granular rules)
+ if (typeof rule === "object" && rule !== null) {
+ // Check if there's any "allow" in the record
+ const values = Object.values(rule);
+ const hasAllow = values.some((v) => v === "allow");
+ const hasDeny = values.some((v) => v === "deny");
+
+ switch (strategy) {
+ case "permissive":
+ return hasAllow || !hasDeny;
+ case "restrictive":
+ return hasAllow && !hasDeny;
+ case "ask-as-deny":
+ return hasAllow && !hasDeny;
+ }
+ }
+
+ // Default to permissive
+ return true;
+}
+
+/**
+ * Check if a permission rule is granular (not simple boolean/literal).
+ */
+export function isGranularRule(rule: PermissionRule): boolean {
+ if (typeof rule === "boolean") return false;
+ if (rule === "allow" || rule === "deny" || rule === "ask") return false;
+ return typeof rule === "object" && rule !== null;
+}
+
+/**
+ * Map OAC granular permissions to binary permissions for a target platform.
+ *
+ * @param permissions - OAC granular permissions
+ * @param platform - Target platform
+ * @param strategy - How to handle 'ask' and granular rules
+ * @returns Binary permissions with warnings
+ */
+export function mapPermissionsFromOAC(
+ permissions: GranularPermission,
+ platform: Exclude,
+ strategy: DegradationStrategy = "permissive"
+): PermissionMappingResult {
+ const capabilities = PLATFORM_CAPABILITIES[platform];
+ const result: BinaryPermissions = {};
+ const warnings: string[] = [];
+
+ for (const [tool, rule] of Object.entries(permissions)) {
+ // Check for granular permission degradation
+ if (isGranularRule(rule) && !capabilities.supportsGranular) {
+ warnings.push(
+ `⚠️ Granular '${tool}' permissions degraded to binary for ${platform}`
+ );
+ }
+
+ // Check for 'ask' permission degradation
+ if (rule === "ask" && !capabilities.supportsAsk) {
+ const action = strategy === "permissive" ? "allowed" : "denied";
+ warnings.push(
+ `⚠️ Permission '${tool}: ask' converted to '${action}' for ${platform}`
+ );
+ }
+
+ // Resolve to boolean
+ const boolValue = resolvePermissionRule(rule, strategy);
+ (result as Record)[tool] = boolValue;
+ }
+
+ return { permissions: result, warnings };
+}
+
+/**
+ * Map binary permissions to OAC granular format.
+ * Since binary is a subset of granular, this is a straightforward conversion.
+ *
+ * @param permissions - Binary permissions from another tool
+ * @param platform - Source platform
+ * @returns Granular permissions (no warnings needed for upgrade)
+ */
+export function mapPermissionsToOAC(
+ permissions: BinaryPermissions,
+ _platform: Exclude
+): PermissionMappingResult {
+ const result: GranularPermission = {};
+
+ for (const [tool, enabled] of Object.entries(permissions)) {
+ if (enabled !== undefined) {
+ result[tool] = enabled ? "allow" : "deny";
+ }
+ }
+
+ return { permissions: result, warnings: [] };
+}
+
+// ============================================================================
+// Granular Permission Utilities
+// ============================================================================
+
+/**
+ * Create a granular permission rule from patterns.
+ *
+ * @param allow - Patterns to allow
+ * @param deny - Patterns to deny
+ * @param ask - Patterns that require confirmation
+ * @returns Granular permission rule
+ */
+export function createGranularRule(
+ allow: string[] = [],
+ deny: string[] = [],
+ ask: string[] = []
+): PermissionRule {
+ const rule: Record = {};
+
+ for (const pattern of allow) {
+ rule[pattern] = "allow";
+ }
+ for (const pattern of deny) {
+ rule[pattern] = "deny";
+ }
+ for (const pattern of ask) {
+ rule[pattern] = "ask";
+ }
+
+ return rule;
+}
+
+/**
+ * Extract patterns from a granular permission rule.
+ *
+ * @param rule - Granular permission rule
+ * @returns Object with allow, deny, and ask patterns
+ */
+export function extractPatterns(rule: PermissionRule): {
+ allow: string[];
+ deny: string[];
+ ask: string[];
+} {
+ const result = { allow: [] as string[], deny: [] as string[], ask: [] as string[] };
+
+ if (typeof rule === "boolean") {
+ result[rule ? "allow" : "deny"].push("*");
+ return result;
+ }
+
+ if (rule === "allow" || rule === "deny" || rule === "ask") {
+ result[rule].push("*");
+ return result;
+ }
+
+ if (typeof rule === "object" && rule !== null) {
+ for (const [pattern, permission] of Object.entries(rule)) {
+ if (permission === "allow" || permission === "deny" || permission === "ask") {
+ result[permission].push(pattern);
+ }
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Merge multiple permission rules into one.
+ * Later rules override earlier ones for the same patterns.
+ *
+ * @param rules - Permission rules to merge
+ * @returns Merged permission rule
+ */
+export function mergePermissionRules(...rules: PermissionRule[]): PermissionRule {
+ const merged: Record = {};
+
+ for (const rule of rules) {
+ const patterns = extractPatterns(rule);
+
+ for (const pattern of patterns.allow) {
+ merged[pattern] = "allow";
+ }
+ for (const pattern of patterns.deny) {
+ merged[pattern] = "deny";
+ }
+ for (const pattern of patterns.ask) {
+ merged[pattern] = "ask";
+ }
+ }
+
+ // Simplify if only one universal pattern
+ const keys = Object.keys(merged);
+ if (keys.length === 1 && keys[0] === "*") {
+ const value = merged["*"];
+ if (value !== undefined) {
+ return value;
+ }
+ }
+
+ return merged;
+}
+
+// ============================================================================
+// Validation Functions
+// ============================================================================
+
+/**
+ * Check if permissions have any granular rules that would be degraded.
+ *
+ * @param permissions - OAC permissions to check
+ * @returns True if any granular rules exist
+ */
+export function hasGranularPermissions(permissions: GranularPermission): boolean {
+ return Object.values(permissions).some(isGranularRule);
+}
+
+/**
+ * Check if permissions have any 'ask' rules.
+ *
+ * @param permissions - OAC permissions to check
+ * @returns True if any 'ask' rules exist
+ */
+export function hasAskPermissions(permissions: GranularPermission): boolean {
+ const checkRule = (rule: PermissionRule): boolean => {
+ if (rule === "ask") return true;
+ if (typeof rule === "object" && rule !== null) {
+ return Object.values(rule).some((v) => v === "ask");
+ }
+ return false;
+ };
+
+ return Object.values(permissions).some(checkRule);
+}
+
+/**
+ * Generate warnings for permission degradation without actually converting.
+ *
+ * @param permissions - OAC permissions to analyze
+ * @param platform - Target platform
+ * @returns Array of warning messages
+ */
+export function analyzePermissionDegradation(
+ permissions: GranularPermission,
+ platform: Exclude
+): string[] {
+ const warnings: string[] = [];
+ const capabilities = PLATFORM_CAPABILITIES[platform];
+
+ if (hasGranularPermissions(permissions) && !capabilities.supportsGranular) {
+ warnings.push(
+ `⚠️ ${platform} does not support granular permissions - rules will be simplified`
+ );
+ }
+
+ if (hasAskPermissions(permissions) && !capabilities.supportsAsk) {
+ warnings.push(
+ `⚠️ ${platform} does not support 'ask' permissions - will be converted to allow/deny`
+ );
+ }
+
+ return warnings;
+}
diff --git a/packages/compatibility-layer/src/mappers/ToolMapper.ts b/packages/compatibility-layer/src/mappers/ToolMapper.ts
new file mode 100644
index 0000000..b910996
--- /dev/null
+++ b/packages/compatibility-layer/src/mappers/ToolMapper.ts
@@ -0,0 +1,308 @@
+/**
+ * ToolMapper - Maps tool names between OAC and other formats
+ *
+ * Pure functions for bidirectional tool name mapping.
+ * Handles differences in how tools are named across platforms.
+ *
+ * @example
+ * ```ts
+ * mapToolToOAC('terminal', 'claude') // => 'bash'
+ * mapToolFromOAC('bash', 'claude') // => 'terminal'
+ * ```
+ */
+
+import type { ToolAccess } from "../types.js";
+
+// ============================================================================
+// Types
+// ============================================================================
+
+/**
+ * Supported tool platforms for mapping
+ */
+export type ToolPlatform = "oac" | "claude" | "cursor" | "windsurf";
+
+/**
+ * Tool mapping configuration for a specific platform
+ */
+export interface ToolMappingConfig {
+ /** Map from OAC tool name to platform tool name */
+ fromOAC: Record;
+ /** Map from platform tool name to OAC tool name */
+ toOAC: Record;
+ /** Tools that are not supported by this platform */
+ unsupported: string[];
+}
+
+/**
+ * Result of a tool mapping operation
+ */
+export interface ToolMappingResult {
+ /** The mapped tool name */
+ name: string;
+ /** Whether the mapping was exact or a fallback */
+ exact: boolean;
+ /** Warning message if mapping was degraded */
+ warning?: string;
+}
+
+// ============================================================================
+// Mapping Tables
+// ============================================================================
+
+/**
+ * Tool name mappings for each platform
+ */
+const TOOL_MAPPINGS: Record, ToolMappingConfig> = {
+ claude: {
+ fromOAC: {
+ bash: "Bash",
+ read: "Read",
+ write: "Write",
+ edit: "Edit",
+ glob: "Glob",
+ grep: "Grep",
+ task: "Task",
+ patch: "Edit", // Claude uses Edit for patching
+ },
+ toOAC: {
+ Bash: "bash",
+ Read: "read",
+ Write: "write",
+ Edit: "edit",
+ Glob: "glob",
+ Grep: "grep",
+ Task: "task",
+ // Claude-specific tools that map to OAC equivalents
+ WebSearch: "bash", // Approximation
+ WebFetch: "bash", // Approximation
+ },
+ unsupported: [], // Claude supports most tools
+ },
+
+ cursor: {
+ fromOAC: {
+ bash: "terminal",
+ read: "file_read",
+ write: "file_write",
+ edit: "file_edit",
+ glob: "file_search",
+ grep: "content_search",
+ task: "task", // Not really supported, maps to itself
+ patch: "file_edit",
+ },
+ toOAC: {
+ terminal: "bash",
+ file_read: "read",
+ file_write: "write",
+ file_edit: "edit",
+ file_search: "glob",
+ content_search: "grep",
+ },
+ unsupported: ["task"], // Cursor doesn't support delegation
+ },
+
+ windsurf: {
+ fromOAC: {
+ bash: "shell",
+ read: "read_file",
+ write: "write_file",
+ edit: "edit_file",
+ glob: "find_files",
+ grep: "search_content",
+ task: "delegate", // Limited support
+ patch: "edit_file",
+ },
+ toOAC: {
+ shell: "bash",
+ read_file: "read",
+ write_file: "write",
+ edit_file: "edit",
+ find_files: "glob",
+ search_content: "grep",
+ delegate: "task",
+ },
+ unsupported: [], // Windsurf has equivalents for most
+ },
+};
+
+// ============================================================================
+// Core Mapping Functions (Pure)
+// ============================================================================
+
+/**
+ * Map a single tool name from platform format to OAC format.
+ *
+ * @param toolName - Tool name in platform format
+ * @param platform - Source platform
+ * @returns Mapping result with OAC tool name
+ */
+export function mapToolToOAC(
+ toolName: string,
+ platform: Exclude
+): ToolMappingResult {
+ const mapping = TOOL_MAPPINGS[platform];
+ const oacName = mapping.toOAC[toolName];
+
+ if (oacName) {
+ return { name: oacName, exact: true };
+ }
+
+ // Fallback: use lowercase version as-is
+ const fallbackName = toolName.toLowerCase();
+ return {
+ name: fallbackName,
+ exact: false,
+ warning: `Unknown tool '${toolName}' from ${platform}, using '${fallbackName}'`,
+ };
+}
+
+/**
+ * Map a single tool name from OAC format to platform format.
+ *
+ * @param toolName - Tool name in OAC format
+ * @param platform - Target platform
+ * @returns Mapping result with platform tool name
+ */
+export function mapToolFromOAC(
+ toolName: string,
+ platform: Exclude
+): ToolMappingResult {
+ const mapping = TOOL_MAPPINGS[platform];
+
+ // Check if tool is unsupported
+ if (mapping.unsupported.includes(toolName)) {
+ return {
+ name: toolName,
+ exact: false,
+ warning: `Tool '${toolName}' is not supported by ${platform}`,
+ };
+ }
+
+ const platformName = mapping.fromOAC[toolName];
+
+ if (platformName) {
+ return { name: platformName, exact: true };
+ }
+
+ // Fallback: use the OAC name as-is
+ return {
+ name: toolName,
+ exact: false,
+ warning: `No mapping for tool '${toolName}' to ${platform}, using as-is`,
+ };
+}
+
+// ============================================================================
+// Batch Mapping Functions
+// ============================================================================
+
+/**
+ * Map all tools from a ToolAccess object to platform format.
+ *
+ * @param tools - OAC ToolAccess object
+ * @param platform - Target platform
+ * @returns Object with mapped tools and warnings
+ */
+export function mapToolAccessFromOAC(
+ tools: ToolAccess,
+ platform: Exclude
+): { tools: Record; warnings: string[] } {
+ const result: Record = {};
+ const warnings: string[] = [];
+
+ for (const [toolName, enabled] of Object.entries(tools)) {
+ if (enabled === undefined) continue;
+
+ const mapping = mapToolFromOAC(toolName, platform);
+ result[mapping.name] = enabled;
+
+ if (mapping.warning) {
+ warnings.push(mapping.warning);
+ }
+ }
+
+ return { tools: result, warnings };
+}
+
+/**
+ * Map platform tools to OAC ToolAccess format.
+ *
+ * @param tools - Platform tools object
+ * @param platform - Source platform
+ * @returns Object with OAC ToolAccess and warnings
+ */
+export function mapToolAccessToOAC(
+ tools: Record,
+ platform: Exclude
+): { tools: ToolAccess; warnings: string[] } {
+ const result: ToolAccess = {};
+ const warnings: string[] = [];
+
+ for (const [toolName, enabled] of Object.entries(tools)) {
+ const mapping = mapToolToOAC(toolName, platform);
+
+ // Map to the correct OAC tool property
+ const oacTool = mapping.name as keyof ToolAccess;
+ if (isValidOACTool(oacTool)) {
+ result[oacTool] = enabled;
+ }
+
+ if (mapping.warning) {
+ warnings.push(mapping.warning);
+ }
+ }
+
+ return { tools: result, warnings };
+}
+
+// ============================================================================
+// Utility Functions
+// ============================================================================
+
+/**
+ * Check if a tool name is a valid OAC tool.
+ */
+function isValidOACTool(name: string): name is keyof ToolAccess {
+ const validTools = ["read", "write", "edit", "bash", "task", "grep", "glob", "patch"];
+ return validTools.includes(name);
+}
+
+/**
+ * Get list of tools supported by a platform.
+ *
+ * @param platform - Target platform
+ * @returns Array of supported tool names
+ */
+export function getSupportedTools(
+ platform: Exclude
+): string[] {
+ const mapping = TOOL_MAPPINGS[platform];
+ return Object.keys(mapping.fromOAC);
+}
+
+/**
+ * Get list of tools NOT supported by a platform.
+ *
+ * @param platform - Target platform
+ * @returns Array of unsupported OAC tool names
+ */
+export function getUnsupportedTools(
+ platform: Exclude
+): string[] {
+ return TOOL_MAPPINGS[platform].unsupported;
+}
+
+/**
+ * Check if a specific tool is supported by a platform.
+ *
+ * @param toolName - OAC tool name
+ * @param platform - Target platform
+ * @returns True if supported
+ */
+export function isToolSupported(
+ toolName: string,
+ platform: Exclude
+): boolean {
+ return !TOOL_MAPPINGS[platform].unsupported.includes(toolName);
+}
diff --git a/packages/compatibility-layer/src/types.ts b/packages/compatibility-layer/src/types.ts
new file mode 100644
index 0000000..42cc718
--- /dev/null
+++ b/packages/compatibility-layer/src/types.ts
@@ -0,0 +1,315 @@
+import { z } from "zod";
+
+// ============================================================================
+// Tool Access Schema
+// ============================================================================
+
+/**
+ * Defines which tools an agent has access to.
+ * Each tool can be enabled/disabled via boolean flags.
+ */
+export const ToolAccessSchema = z.object({
+ read: z.boolean().optional(),
+ write: z.boolean().optional(),
+ edit: z.boolean().optional(),
+ bash: z.boolean().optional(),
+ task: z.boolean().optional(),
+ grep: z.boolean().optional(),
+ glob: z.boolean().optional(),
+ patch: z.boolean().optional(),
+});
+
+// ============================================================================
+// Permission Schemas
+// ============================================================================
+
+/**
+ * Permission rules can be:
+ * - A literal: "allow", "deny", "ask"
+ * - A boolean (true = allow, false = deny)
+ * - A record mapping specific operations to permission literals
+ */
+export const PermissionRuleSchema = z.union([
+ z.literal("allow"),
+ z.literal("deny"),
+ z.literal("ask"),
+ z.boolean(),
+ z.record(z.string(), z.union([
+ z.literal("allow"),
+ z.literal("deny"),
+ z.literal("ask"),
+ ])),
+]);
+
+/**
+ * Granular permissions allow fine-grained control over different operations.
+ * Maps operation names to permission rules.
+ */
+export const GranularPermissionSchema = z.record(z.string(), PermissionRuleSchema);
+
+// ============================================================================
+// Context Schemas
+// ============================================================================
+
+/**
+ * Context priority levels for loading order and importance.
+ */
+export const ContextPrioritySchema = z.enum(["critical", "high", "medium", "low"]);
+
+/**
+ * References a context file with optional priority and description.
+ */
+export const ContextReferenceSchema = z.object({
+ path: z.string(),
+ priority: ContextPrioritySchema.optional(),
+ description: z.string().optional(),
+});
+
+// ============================================================================
+// Dependency Schema
+// ============================================================================
+
+/**
+ * References external dependencies like subagents, contexts, commands, skills, or tools.
+ */
+export const DependencyReferenceSchema = z.object({
+ type: z.enum(["subagent", "context", "command", "skill", "tool"]),
+ id: z.string(),
+});
+
+// ============================================================================
+// Agent Configuration Schemas
+// ============================================================================
+
+/**
+ * Defines the operational mode of an agent.
+ */
+export const AgentModeSchema = z.enum(["primary", "subagent", "all"]);
+
+/**
+ * Agent categories for organizational purposes.
+ */
+export const AgentCategorySchema = z.enum([
+ "core",
+ "development",
+ "content",
+ "data",
+ "product",
+ "learning",
+ "meta",
+ "specialist",
+]);
+
+/**
+ * Defines whether this is a primary agent or subagent.
+ */
+export const AgentTypeSchema = z.enum(["agent", "subagent"]);
+
+// ============================================================================
+// Model Configuration Schemas
+// ============================================================================
+
+/**
+ * Model identifier - can be any string representing a model name or ID.
+ */
+export const ModelIdentifierSchema = z.union([z.string(), z.string()]);
+
+/**
+ * Temperature parameter for model inference (typically 0.0 to 2.0).
+ */
+export const TemperatureSchema = z.number();
+
+// ============================================================================
+// Skill Schema
+// ============================================================================
+
+/**
+ * Skill reference can be:
+ * - A simple string (skill name)
+ * - An object with name and optional configuration
+ */
+export const SkillReferenceSchema = z.union([
+ z.string(),
+ z.object({
+ name: z.string(),
+ config: z.record(z.string(), z.any()).optional(),
+ }),
+]);
+
+// ============================================================================
+// Hook Schemas
+// ============================================================================
+
+/**
+ * Events that can trigger hooks during agent execution.
+ */
+export const HookEventSchema = z.enum([
+ "PreToolUse",
+ "PostToolUse",
+ "PermissionRequest",
+ "AgentStart",
+ "AgentEnd",
+]);
+
+/**
+ * Defines a hook that executes commands in response to specific events.
+ */
+export const HookDefinitionSchema = z.object({
+ event: HookEventSchema,
+ matchers: z.array(z.string()).optional(),
+ commands: z.array(
+ z.object({
+ type: z.literal("command"),
+ command: z.string(),
+ })
+ ),
+});
+
+// ============================================================================
+// Agent Frontmatter Schema
+// ============================================================================
+
+/**
+ * Agent frontmatter contains the primary configuration defined in the agent's
+ * markdown file header (YAML frontmatter).
+ */
+export const AgentFrontmatterSchema = z.object({
+ name: z.string(),
+ description: z.string(),
+ mode: AgentModeSchema,
+ temperature: TemperatureSchema.optional(),
+ model: ModelIdentifierSchema.optional(),
+ maxSteps: z.number().optional(),
+ disable: z.boolean().optional(),
+ hidden: z.boolean().optional(),
+ prompt: z.string().optional(),
+ tools: ToolAccessSchema.optional(),
+ permission: GranularPermissionSchema.optional(),
+ skills: z.array(SkillReferenceSchema).optional(),
+ hooks: z.array(HookDefinitionSchema).optional(),
+});
+
+// ============================================================================
+// Agent Metadata Schema
+// ============================================================================
+
+/**
+ * Agent metadata contains identification and organizational information.
+ * Stored separately from frontmatter in agent-metadata.json.
+ */
+export const AgentMetadataSchema = z.object({
+ id: z.string(),
+ name: z.string(),
+ category: AgentCategorySchema,
+ type: AgentTypeSchema,
+ version: z.string(),
+ author: z.string(),
+ tags: z.array(z.string()).optional().default([]),
+ dependencies: z.array(DependencyReferenceSchema).optional().default([]),
+});
+
+// ============================================================================
+// OpenAgent Schema
+// ============================================================================
+
+/**
+ * Complete OpenAgent schema combining frontmatter, metadata, system prompt,
+ * contexts, and optional sections.
+ *
+ * This represents the full agent definition after parsing and merging all
+ * configuration sources.
+ */
+export const OpenAgentSchema = z.object({
+ frontmatter: AgentFrontmatterSchema,
+ metadata: z.object({
+ id: z.string().optional(),
+ name: z.string().optional(),
+ category: AgentCategorySchema.optional(),
+ type: AgentTypeSchema.optional(),
+ version: z.string().optional(),
+ author: z.string().optional(),
+ tags: z.array(z.string()).optional().default([]).optional(),
+ dependencies: z.array(DependencyReferenceSchema).optional().default([]).optional(),
+ }),
+ systemPrompt: z.string(),
+ contexts: z.array(ContextReferenceSchema).optional().default([]),
+ sections: z.object({
+ skills: z.array(z.string()).optional().default([]),
+ examples: z.array(z.string()).optional().default([]),
+ commands: z.array(z.string()).optional().default([]),
+ workflow: z.string().optional(),
+ }).optional(),
+});
+
+// ============================================================================
+// Tool Configuration Schema
+// ============================================================================
+
+/**
+ * Configuration file output for external tools.
+ * Contains the file name, content, and encoding format.
+ */
+export const ToolConfigSchema = z.object({
+ fileName: z.string(),
+ content: z.string(),
+ encoding: z.enum(["utf-8", "base64"]).optional().default("utf-8"),
+});
+
+// ============================================================================
+// Type Exports (z.infer)
+// ============================================================================
+
+export type ToolAccess = z.infer;
+export type PermissionRule = z.infer;
+export type GranularPermission = z.infer;
+export type ContextPriority = z.infer;
+export type ContextReference = z.infer;
+export type DependencyReference = z.infer;
+export type AgentMode = z.infer;
+export type AgentCategory = z.infer;
+export type AgentType = z.infer;
+export type ModelIdentifier = z.infer;
+export type Temperature = z.infer;
+export type SkillReference = z.infer;
+export type HookEvent = z.infer;
+export type HookDefinition = z.infer;
+export type AgentFrontmatter = z.infer;
+export type AgentMetadata = z.infer;
+export type OpenAgent = z.infer;
+export type ToolConfig = z.infer;
+
+// ============================================================================
+// Interfaces
+// ============================================================================
+
+/**
+ * Describes the capabilities of a specific tool/platform that OpenAgent
+ * configurations can be converted to.
+ */
+export interface ToolCapabilities {
+ name: string;
+ displayName: string;
+ supportsMultipleAgents: boolean;
+ supportsSkills: boolean;
+ supportsHooks: boolean;
+ supportsGranularPermissions: boolean;
+ supportsContexts: boolean;
+ supportsCustomModels: boolean;
+ supportsTemperature: boolean;
+ supportsMaxSteps: boolean;
+ configFormat: "markdown" | "yaml" | "json" | "plain";
+ outputStructure: "single-file" | "multi-file" | "directory";
+ notes?: string[];
+}
+
+/**
+ * Result of converting an OpenAgent configuration to another tool's format.
+ * Includes the generated config files, warnings, and optional errors.
+ */
+export interface ConversionResult {
+ success: boolean;
+ configs: ToolConfig[];
+ warnings: string[];
+ errors?: string[];
+ capabilities?: ToolCapabilities;
+}
diff --git a/packages/compatibility-layer/tests/unit/adapters/ClaudeAdapter.test.ts b/packages/compatibility-layer/tests/unit/adapters/ClaudeAdapter.test.ts
new file mode 100644
index 0000000..8f30813
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/adapters/ClaudeAdapter.test.ts
@@ -0,0 +1,1486 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { ClaudeAdapter } from "../../../src/adapters/ClaudeAdapter";
+import type {
+ OpenAgent,
+ AgentFrontmatter,
+ HookDefinition,
+ SkillReference,
+} from "../../../src/types";
+
+/**
+ * Unit tests for ClaudeAdapter with 80%+ coverage
+ *
+ * Test strategy:
+ * 1. toOAC() - Parse Claude formats to OpenAgent
+ * 2. fromOAC() - Convert OpenAgent to Claude formats
+ * 3. getCapabilities() - Feature matrix validation
+ * 4. validateConversion() - Validation and warnings
+ * 5. Helper methods - Model/tool/hook/skill mapping
+ * 6. Edge cases - Invalid input, missing fields, empty values
+ * 7. Roundtrip - Data integrity checks
+ */
+
+describe("ClaudeAdapter", () => {
+ let adapter: ClaudeAdapter;
+
+ beforeEach(() => {
+ adapter = new ClaudeAdapter();
+ });
+
+ // ============================================================================
+ // ADAPTER IDENTITY
+ // ============================================================================
+
+ describe("adapter identity", () => {
+ it("has correct name", () => {
+ expect(adapter.name).toBe("claude");
+ });
+
+ it("has correct displayName", () => {
+ expect(adapter.displayName).toBe("Claude Code");
+ });
+
+ it("returns correct config path", () => {
+ expect(adapter.getConfigPath()).toBe(".claude/");
+ });
+ });
+
+ // ============================================================================
+ // CAPABILITIES
+ // ============================================================================
+
+ describe("getCapabilities()", () => {
+ it("returns correct capabilities object", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.name).toBe("claude");
+ expect(capabilities.displayName).toBe("Claude Code");
+ expect(capabilities.supportsMultipleAgents).toBe(true);
+ expect(capabilities.supportsSkills).toBe(true);
+ expect(capabilities.supportsHooks).toBe(true);
+ expect(capabilities.supportsGranularPermissions).toBe(false);
+ expect(capabilities.supportsContexts).toBe(true);
+ expect(capabilities.supportsCustomModels).toBe(true);
+ expect(capabilities.supportsTemperature).toBe(false);
+ expect(capabilities.supportsMaxSteps).toBe(false);
+ expect(capabilities.configFormat).toBe("markdown");
+ expect(capabilities.outputStructure).toBe("directory");
+ });
+
+ it("includes appropriate notes", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.notes).toBeDefined();
+ expect(capabilities.notes?.length).toBeGreaterThan(0);
+ expect(capabilities.notes?.some((n) => n.includes("permissions"))).toBe(
+ true
+ );
+ });
+ });
+
+ // ============================================================================
+ // toOAC() - PARSING CLAUDE CONFIG.JSON
+ // ============================================================================
+
+ describe("toOAC() - parsing config.json", () => {
+ it("parses minimal config.json", async () => {
+ const source = JSON.stringify({
+ name: "TestAgent",
+ description: "Test description",
+ systemPrompt: "You are helpful",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("TestAgent");
+ expect(result.frontmatter.description).toBe("Test description");
+ expect(result.systemPrompt).toBe("You are helpful");
+ expect(result.frontmatter.mode).toBe("primary");
+ });
+
+ it("parses config with model", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ model: "claude-sonnet-4-20250514",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-sonnet-4");
+ });
+
+ it("parses config with tools array", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ tools: ["Read", "Write", "Bash"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: true,
+ });
+ });
+
+ it("parses config with tools string", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ tools: "Read, Write, Edit",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ edit: true,
+ });
+ });
+
+ it("parses config with skills", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ skills: ["skill1", "skill2"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.skills).toEqual(["skill1", "skill2"]);
+ });
+
+ it("parses config with hooks", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: "*.txt",
+ hooks: [{ type: "command", command: "validate" }],
+ },
+ ],
+ },
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.hooks).toBeDefined();
+ expect(result.frontmatter.hooks?.length).toBe(1);
+ expect(result.frontmatter.hooks?.[0].event).toBe("PreToolUse");
+ });
+
+ it("handles missing optional fields gracefully", async () => {
+ const source = JSON.stringify({
+ name: "MinimalAgent",
+ description: "Minimal",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("MinimalAgent");
+ expect(result.systemPrompt).toBe("");
+ expect(result.frontmatter.tools).toBeUndefined();
+ expect(result.frontmatter.skills).toBeUndefined();
+ });
+
+ it("parses invalid JSON as markdown (subagent fallback)", async () => {
+ const source = "not valid json";
+
+ const result = await adapter.toOAC(source);
+
+ // Falls back to markdown parsing
+ expect(result.frontmatter.mode).toBe("subagent");
+ expect(result.systemPrompt).toBe("not valid json");
+ });
+
+ it("parses non-object JSON string as markdown (subagent fallback)", async () => {
+ const source = JSON.stringify("just a string");
+
+ const result = await adapter.toOAC(source);
+
+ // Falls back to markdown parsing
+ expect(result.frontmatter.mode).toBe("subagent");
+ });
+ });
+
+ // ============================================================================
+ // toOAC() - PARSING CLAUDE AGENT.MD (SUBAGENTS)
+ // ============================================================================
+
+ describe("toOAC() - parsing agent.md with YAML frontmatter", () => {
+ it("parses agent.md with minimal frontmatter", async () => {
+ const source = `---
+name: SubAgent
+description: A subagent
+---
+
+This is the system prompt for the agent.`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("SubAgent");
+ expect(result.frontmatter.description).toBe("A subagent");
+ expect(result.frontmatter.mode).toBe("subagent");
+ expect(result.systemPrompt).toBe("This is the system prompt for the agent.");
+ });
+
+ it("parses agent.md with model in frontmatter", async () => {
+ const source = `---
+name: Agent
+description: Test
+model: claude-opus-4
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-opus-4");
+ });
+
+ it("parses agent.md with tools array in frontmatter", async () => {
+ const source = `---
+name: Agent
+description: Test
+tools: ["Read", "Write", "Bash"]
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: true,
+ });
+ });
+
+ it("parses agent.md with skills in frontmatter", async () => {
+ const source = `---
+name: Agent
+description: Test
+skills: ["skill1", "skill2"]
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.skills).toEqual(["skill1", "skill2"]);
+ });
+
+ it("handles agent.md without frontmatter as markdown content", async () => {
+ const source = "No frontmatter here, just markdown content";
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toBe("No frontmatter here, just markdown content");
+ expect(result.frontmatter.mode).toBe("subagent");
+ });
+
+ it("preserves multiline system prompt", async () => {
+ const source = `---
+name: Agent
+description: Test
+---
+
+You are a helpful assistant.
+You should be friendly and professional.
+Always provide detailed responses.`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain("You are a helpful assistant");
+ expect(result.systemPrompt).toContain("Always provide detailed responses");
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - CONVERTING TO CLAUDE CONFIG.JSON
+ // ============================================================================
+
+ describe("fromOAC() - converting to config.json", () => {
+ const createOpenAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "TestAgent",
+ description: "Test agent",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ tools: { read: true, write: true },
+ ...overrides?.frontmatter,
+ },
+ metadata: {
+ name: "TestAgent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "You are helpful",
+ contexts: [],
+ ...overrides,
+ });
+
+ it("converts primary agent to config.json", async () => {
+ const agent = createOpenAgent();
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".claude/config.json");
+
+ const config = JSON.parse(result.configs[0].content);
+ expect(config.name).toBe("TestAgent");
+ expect(config.description).toBe("Test agent");
+ expect(config.model).toBe("claude-sonnet-4-20250514");
+ });
+
+ it("maps tools correctly in config.json", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ tools: { read: true, write: true, bash: true },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.tools).toEqual(["Read", "Write", "Bash"]);
+ });
+
+ it("includes skills in config.json", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ skills: ["skill1", "skill2"],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.skills).toEqual(["skill1", "skill2"]);
+ });
+
+ it("includes hooks in config.json", async () => {
+ const hook: HookDefinition = {
+ event: "PreToolUse",
+ matchers: ["*.txt"],
+ commands: [{ type: "command", command: "validate" }],
+ };
+
+ const agent = createOpenAgent({
+ frontmatter: {
+ hooks: [hook],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.hooks).toBeDefined();
+ expect(config.hooks.PreToolUse).toBeDefined();
+ });
+
+ it("warns when temperature is set (unsupported)", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ temperature: 0.7,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("temperature"))).toBe(true);
+ });
+
+ it("warns when maxSteps is set (unsupported)", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ maxSteps: 10,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("maxSteps"))).toBe(true);
+ });
+
+ it("includes validationWarnings in result", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ name: "",
+ description: "",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.length).toBeGreaterThan(0);
+ });
+
+ it("handles mixed permission rules gracefully", async () => {
+ const agent = createOpenAgent({
+ frontmatter: {
+ permission: {
+ read: "allow",
+ write: "ask",
+ bash: "deny",
+ },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ // Should include some form of warning or degradation
+ expect(result.warnings.length).toBeGreaterThanOrEqual(0);
+ expect(result.success).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - CONVERTING TO CLAUDE AGENT.MD (SUBAGENTS)
+ // ============================================================================
+
+ describe("fromOAC() - converting to agent.md for subagents", () => {
+ const createSubagent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "CodeAnalyzer",
+ description: "Analyzes code",
+ mode: "subagent",
+ model: "claude-sonnet-4",
+ ...overrides?.frontmatter,
+ },
+ metadata: {
+ name: "CodeAnalyzer",
+ category: "specialist",
+ type: "subagent",
+ },
+ systemPrompt: "Analyze code quality",
+ contexts: [],
+ ...overrides,
+ });
+
+ it("converts subagent to agent.md file", async () => {
+ const agent = createSubagent();
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".claude/agents/CodeAnalyzer.md");
+ });
+
+ it("generates proper YAML frontmatter in agent.md", async () => {
+ const agent = createSubagent();
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toMatch(/^---/);
+ expect(content).toMatch(/name: "CodeAnalyzer"/);
+ expect(content).toMatch(/description: "Analyzes code"/);
+ expect(content).toContain("---\n\n");
+ });
+
+ it("includes system prompt in agent.md body", async () => {
+ const agent = createSubagent({
+ systemPrompt: "Analyze code quality\nCheck for best practices",
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("Analyze code quality");
+ expect(content).toContain("Check for best practices");
+ });
+
+ it("includes tools in agent.md frontmatter", async () => {
+ const agent = createSubagent({
+ frontmatter: {
+ tools: { read: true, bash: true },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("Read");
+ expect(content).toContain("Bash");
+ expect(content).toContain("tools");
+ });
+
+ it("includes model in agent.md frontmatter", async () => {
+ const agent = createSubagent({
+ frontmatter: {
+ model: "claude-opus-4",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model");
+ expect(content).toContain("claude-opus-4");
+ });
+
+ it("includes permission mode in agent.md frontmatter", async () => {
+ const agent = createSubagent({
+ frontmatter: {
+ permission: { read: "allow", write: "allow" },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("permissionMode");
+ expect(content).toContain("bypassPermissions");
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - SKILLS GENERATION FROM CONTEXTS
+ // ============================================================================
+
+ describe("fromOAC() - generating skills from contexts", () => {
+ it("generates skill files from contexts", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [
+ {
+ path: ".opencode/context/skills/python-best-practices.md",
+ description: "Python coding standards",
+ },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs.length).toBeGreaterThan(1);
+ const skillConfig = result.configs.find((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+ expect(skillConfig).toBeDefined();
+ expect(skillConfig?.fileName).toMatch(/\.claude\/skills\/.*\/SKILL\.md/);
+ });
+
+ it("generates correct skill name from context path", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [
+ {
+ path: "docs/React Hooks Guide.md",
+ description: "React hooks documentation",
+ },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const skillConfig = result.configs.find((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+
+ expect(skillConfig?.fileName).toMatch(/react-hooks-guide/);
+ });
+
+ it("includes context priority in skill content", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [
+ {
+ path: "context/important.md",
+ priority: "high",
+ description: "Important context",
+ },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const skillConfig = result.configs.find((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+
+ expect(skillConfig?.content).toContain("Priority: high");
+ });
+
+ it("generates multiple skills from multiple contexts", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [
+ { path: "context1.md", description: "First" },
+ { path: "context2.md", description: "Second" },
+ { path: "context3.md", description: "Third" },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const skillConfigs = result.configs.filter((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+
+ expect(skillConfigs).toHaveLength(3);
+ });
+
+ it("includes skill metadata when context lacks description", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [{ path: ".opencode/context/styles.md" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const skillConfig = result.configs.find((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+
+ expect(skillConfig?.content).toContain("Context from");
+ expect(skillConfig?.content).toContain("styles.md");
+ });
+
+ it("handles skill names with special characters", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: {
+ name: "Agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Prompt",
+ contexts: [
+ { path: "docs/Type Script Advanced Rules.md", description: "Test" },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const skillConfig = result.configs.find((c) =>
+ c.fileName.includes(".claude/skills/")
+ );
+
+ expect(skillConfig).toBeDefined();
+ // Should have lowercase and dashed name from path
+ expect(skillConfig?.fileName).toMatch(/type-script-advanced-rules/);
+ });
+ });
+
+ // ============================================================================
+ // VALIDATION
+ // ============================================================================
+
+ describe("validateConversion()", () => {
+ const createAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ ...overrides,
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ });
+
+ it("returns no warnings for valid agent", () => {
+ const agent = createAgent();
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings).toHaveLength(0);
+ });
+
+ it("warns when name is missing", () => {
+ const agent = createAgent({ name: "" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("name"))).toBe(true);
+ });
+
+ it("warns when description is missing", () => {
+ const agent = createAgent({ description: "" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("description"))).toBe(true);
+ });
+
+ it("warns about granular permission degradation", () => {
+ const agent = createAgent({
+ permission: {
+ read: { "file1.txt": "allow", "file2.txt": "deny" },
+ },
+ });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(
+ warnings.some((w) => w.includes("granular permissions"))
+ ).toBe(true);
+ });
+
+ it("does not warn about simple permission rules", () => {
+ const agent = createAgent({
+ permission: { read: "allow", write: "allow" },
+ });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(
+ warnings.some((w) => w.includes("granular permissions"))
+ ).toBe(false);
+ });
+ });
+
+ // ============================================================================
+ // MODEL MAPPING
+ // ============================================================================
+
+ describe("model mapping (Claude to OAC)", () => {
+ it("maps claude-sonnet-4-20250514 to claude-sonnet-4", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ model: "claude-sonnet-4-20250514",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-sonnet-4");
+ });
+
+ it("maps short model names", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ model: "opus",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-opus-4");
+ });
+
+ it("preserves unknown models", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ model: "claude-custom-model",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-custom-model");
+ });
+
+ it("handles missing model gracefully", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBeUndefined();
+ });
+ });
+
+ // ============================================================================
+ // MODEL MAPPING (OAC to Claude)
+ // ============================================================================
+
+ describe("model mapping (OAC to Claude)", () => {
+ it("maps claude-sonnet-4 to full version", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBe("claude-sonnet-4-20250514");
+ });
+
+ it("preserves other model IDs", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ model: "claude-opus-4",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBe("claude-opus-4");
+ });
+
+ it("does not set model when not provided", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBeUndefined();
+ });
+ });
+
+ // ============================================================================
+ // TOOL MAPPING
+ // ============================================================================
+
+ describe("tool mapping and parsing", () => {
+ it("parses comma-separated tools string", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ tools: "Read, Write, Edit, Bash",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ edit: true,
+ bash: true,
+ });
+ });
+
+ it("normalizes tool names to lowercase", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ tools: ["Read", "WRITE", "BaSh"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(Object.keys(result.frontmatter.tools || {})).toContain("read");
+ expect(Object.keys(result.frontmatter.tools || {})).toContain("write");
+ expect(Object.keys(result.frontmatter.tools || {})).toContain("bash");
+ });
+
+ it("capitalizes tools for Claude format", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ tools: { read: true, write: true, bash: true },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.tools).toEqual(
+ expect.arrayContaining(["Read", "Write", "Bash"])
+ );
+ });
+
+ it("omits disabled tools", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ tools: { read: true, write: false, bash: true },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.tools).not.toContain("Write");
+ expect(config.tools).toContain("Read");
+ });
+ });
+
+ // ============================================================================
+ // PERMISSION MAPPING
+ // ============================================================================
+
+ describe("permission mapping", () => {
+ it("maps all-allow permissions to bypassPermissions", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ permission: { read: "allow", write: "allow", bash: true },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissionMode).toBe("bypassPermissions");
+ });
+
+ it("maps all-deny permissions to dontAsk", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ permission: { read: "deny", write: "deny", bash: false },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissionMode).toBe("dontAsk");
+ });
+
+ it("maps ask permissions to default", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ permission: { read: "ask", write: "allow" },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissionMode).toBe("default");
+ });
+
+ it("warns on mixed granular permissions", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ permission: {
+ read: "allow",
+ "write.file1": "deny",
+ },
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("permission"))).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // SKILL MAPPING
+ // ============================================================================
+
+ describe("skill parsing and mapping", () => {
+ it("parses skills as comma-separated string", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ skills: "skill1, skill2, skill3",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.skills).toEqual(["skill1", "skill2", "skill3"]);
+ });
+
+ it("parses skills as array", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ skills: ["skill1", "skill2"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.skills).toEqual(["skill1", "skill2"]);
+ });
+
+ it("handles empty skills gracefully", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ skills: [],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.skills).toEqual([]);
+ });
+
+ it("includes skills in config.json", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ skills: ["skill1", "skill2"],
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.skills).toEqual(["skill1", "skill2"]);
+ });
+
+ it("handles skill objects with name property", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ skills: [{ name: "skill1" }],
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.skills).toContain("skill1");
+ });
+ });
+
+ // ============================================================================
+ // HOOK MAPPING
+ // ============================================================================
+
+ describe("hook parsing and mapping", () => {
+ it("parses hooks with matchers", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: "*.txt",
+ hooks: [{ type: "command", command: "validate" }],
+ },
+ ],
+ },
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.hooks).toBeDefined();
+ expect(result.frontmatter.hooks![0].matchers).toEqual(["*.txt"]);
+ });
+
+ it("maps hook commands correctly", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ hooks: {
+ PostToolUse: [
+ {
+ matcher: "*",
+ hooks: [
+ { type: "command", command: "log" },
+ { type: "command", command: "notify" },
+ ],
+ },
+ ],
+ },
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.hooks![0].commands.length).toBe(2);
+ });
+
+ it("converts hooks back to Claude format", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ hooks: [
+ {
+ event: "PreToolUse",
+ matchers: ["*.txt"],
+ commands: [{ type: "command", command: "validate" }],
+ },
+ ],
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.hooks.PreToolUse).toBeDefined();
+ expect(config.hooks.PreToolUse[0].matcher).toBe("*.txt");
+ });
+
+ it("defaults matcher to wildcard", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ hooks: [
+ {
+ event: "AgentStart",
+ commands: [{ type: "command", command: "init" }],
+ },
+ ],
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.hooks.AgentStart[0].matcher).toBe("*");
+ });
+ });
+
+ // ============================================================================
+ // ROUNDTRIP CONVERSION
+ // ============================================================================
+
+ describe("roundtrip conversion", () => {
+ it("roundtrip primary agent config", async () => {
+ // Start with OAC
+ const original: OpenAgent = {
+ frontmatter: {
+ name: "TestAgent",
+ description: "A test agent",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ tools: { read: true, write: true },
+ skills: ["skill1"],
+ },
+ metadata: { name: "TestAgent", category: "core", type: "agent" },
+ systemPrompt: "You are helpful",
+ contexts: [],
+ };
+
+ // Convert to Claude
+ const toClaudeResult = await adapter.fromOAC(original);
+ const claudeConfig = JSON.parse(toClaudeResult.configs[0].content);
+
+ // Convert back to OAC
+ const backToOAC = await adapter.toOAC(JSON.stringify(claudeConfig));
+
+ // Verify core properties are preserved
+ expect(backToOAC.frontmatter.name).toBe(original.frontmatter.name);
+ expect(backToOAC.frontmatter.description).toBe(
+ original.frontmatter.description
+ );
+ expect(backToOAC.systemPrompt).toBe(original.systemPrompt);
+ expect(backToOAC.frontmatter.tools).toEqual(original.frontmatter.tools);
+ });
+
+ it("roundtrip preserves model through conversion", async () => {
+ const original: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ model: "claude-opus-4",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const toClaudeResult = await adapter.fromOAC(original);
+ const claudeConfig = JSON.parse(toClaudeResult.configs[0].content);
+ const backToOAC = await adapter.toOAC(JSON.stringify(claudeConfig));
+
+ expect(backToOAC.frontmatter.model).toBe("claude-opus-4");
+ });
+ });
+
+ // ============================================================================
+ // EDGE CASES & ERROR HANDLING
+ // ============================================================================
+
+ describe("edge cases and error handling", () => {
+ it("omits empty tools from config", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ tools: {},
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ // Empty tools should not be included in config
+ expect(config.tools === undefined || config.tools?.length === 0).toBe(true);
+ });
+
+ it("handles null system prompt", async () => {
+ const source = JSON.stringify({
+ name: "Agent",
+ description: "Test",
+ systemPrompt: null,
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toBe("");
+ });
+
+ it("handles empty system prompt", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.systemPrompt).toBe("");
+ });
+
+ it("handles special characters in names", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent-With-Dashes_and_underscores",
+ description: "Test with special chars: @#$",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ const config = JSON.parse(result.configs[0].content);
+ expect(config.name).toContain("-");
+ });
+
+ it("handles very long system prompt", async () => {
+ const longPrompt = "A".repeat(5000);
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: longPrompt,
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.systemPrompt.length).toBe(5000);
+ });
+
+ it("handles multiline YAML values in frontmatter", async () => {
+ const source = `---
+name: Agent
+description: Test description
+---
+
+System prompt here`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("Agent");
+ expect(result.systemPrompt).toBe("System prompt here");
+ });
+ });
+
+ // ============================================================================
+ // CONVERSION RESULT STRUCTURE
+ // ============================================================================
+
+ describe("conversion result structure", () => {
+ it("returns success true on valid conversion", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("includes capabilities in result", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.capabilities).toBeDefined();
+ expect(result.capabilities?.name).toBe("claude");
+ });
+
+ it("includes encoding in tool config", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "Agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "Agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs[0].encoding).toBe("utf-8");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/adapters/CursorAdapter.test.ts b/packages/compatibility-layer/tests/unit/adapters/CursorAdapter.test.ts
new file mode 100644
index 0000000..60d91c8
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/adapters/CursorAdapter.test.ts
@@ -0,0 +1,1237 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { CursorAdapter } from "../../../src/adapters/CursorAdapter";
+import type { OpenAgent, AgentFrontmatter } from "../../../src/types";
+
+/**
+ * Unit tests for CursorAdapter with 80%+ coverage
+ *
+ * Test strategy:
+ * 1. toOAC() - Parse .cursorrules to OpenAgent
+ * 2. fromOAC() - Convert OpenAgent to .cursorrules
+ * 3. getCapabilities() - Feature matrix validation
+ * 4. validateConversion() - Validation and warnings
+ * 5. Helper methods - Model/tool mapping
+ * 6. Agent Merging - Multiple agents to single file (CURSOR-SPECIFIC)
+ * 7. Edge cases - Invalid input, missing fields, empty values
+ * 8. Roundtrip - Data integrity checks
+ */
+
+describe("CursorAdapter", () => {
+ let adapter: CursorAdapter;
+
+ beforeEach(() => {
+ adapter = new CursorAdapter();
+ });
+
+ // ============================================================================
+ // ADAPTER IDENTITY
+ // ============================================================================
+
+ describe("adapter identity", () => {
+ it("has correct name", () => {
+ expect(adapter.name).toBe("cursor");
+ });
+
+ it("has correct displayName", () => {
+ expect(adapter.displayName).toBe("Cursor IDE");
+ });
+
+ it("returns correct config path", () => {
+ expect(adapter.getConfigPath()).toBe(".cursorrules");
+ });
+ });
+
+ // ============================================================================
+ // CAPABILITIES
+ // ============================================================================
+
+ describe("getCapabilities()", () => {
+ it("returns correct capabilities object", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.name).toBe("cursor");
+ expect(capabilities.displayName).toBe("Cursor IDE");
+ expect(capabilities.supportsMultipleAgents).toBe(false);
+ expect(capabilities.supportsSkills).toBe(false);
+ expect(capabilities.supportsHooks).toBe(false);
+ expect(capabilities.supportsGranularPermissions).toBe(false);
+ expect(capabilities.supportsContexts).toBe(true);
+ expect(capabilities.supportsCustomModels).toBe(true);
+ expect(capabilities.supportsTemperature).toBe(true);
+ expect(capabilities.supportsMaxSteps).toBe(false);
+ expect(capabilities.configFormat).toBe("plain");
+ expect(capabilities.outputStructure).toBe("single-file");
+ });
+
+ it("includes appropriate notes", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.notes).toBeDefined();
+ expect(capabilities.notes && capabilities.notes.length > 0).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // toOAC() - PARSING .cursorrules (PLAIN TEXT)
+ // ============================================================================
+
+ describe("toOAC() - parsing plain .cursorrules", () => {
+ it("parses plain text .cursorrules", async () => {
+ const source = `You are a helpful assistant.
+Do not delete files.
+Always confirm before bash commands.`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("cursor-agent");
+ expect(result.systemPrompt).toBe(source);
+ expect(result.frontmatter.mode).toBe("primary");
+ });
+
+ it("handles very simple rules", async () => {
+ const source = "Be helpful.";
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toBe("Be helpful.");
+ expect(result.frontmatter.name).toBe("cursor-agent");
+ });
+
+ it("preserves multiline plain text", async () => {
+ const source = `Line 1
+Line 2
+Line 3
+Line 4`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain("Line 1");
+ expect(result.systemPrompt).toContain("Line 4");
+ });
+ });
+
+ // ============================================================================
+ // toOAC() - PARSING .cursorrules WITH YAML FRONTMATTER
+ // ============================================================================
+
+ describe("toOAC() - parsing with YAML frontmatter", () => {
+ it("parses .cursorrules with name in frontmatter", async () => {
+ const source = `---
+name: my-agent
+---
+
+Rules here`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("my-agent");
+ expect(result.systemPrompt).toBe("Rules here");
+ });
+
+ it("parses model in frontmatter", async () => {
+ const source = `---
+name: analyzer
+model: gpt-4
+---
+
+Analyze code`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("parses temperature in frontmatter", async () => {
+ const source = `---
+name: creative
+temperature: 0.8
+---
+
+Be creative`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.8);
+ });
+
+ it("parses tools as comma-separated string", async () => {
+ const source = `---
+name: agent
+tools: read, write, bash
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: true,
+ });
+ });
+
+ it("parses tools as array", async () => {
+ const source = `---
+name: agent
+tools: [read, write]
+---
+
+Prompt`;
+
+ // Note: Simple YAML parser doesn't handle arrays in the implementation
+ // This tests current behavior
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toBeDefined();
+ });
+
+ it("parses description in frontmatter", async () => {
+ const source = `---
+name: agent
+description: A test agent
+---
+
+Content`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain("Content");
+ });
+
+ it("handles numeric values in frontmatter", async () => {
+ const source = `---
+name: agent
+temperature: 0.5
+max_tokens: 2000
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.5);
+ expect(typeof result.frontmatter.temperature).toBe("number");
+ });
+
+ it("handles quoted values in frontmatter", async () => {
+ const source = `---
+name: "quoted-agent"
+model: "gpt-4"
+---
+
+Rules`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("quoted-agent");
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("handles boolean values in frontmatter", async () => {
+ const source = `---
+name: agent
+some_flag: true
+---
+
+Content`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain("Content");
+ });
+
+ it("preserves multiline content after frontmatter", async () => {
+ const source = `---
+name: agent
+---
+
+Line 1
+Line 2
+Line 3`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain("Line 1");
+ expect(result.systemPrompt).toContain("Line 3");
+ });
+
+ it("handles empty body with frontmatter", async () => {
+ const source = `---
+name: agent
+model: gpt-4
+---
+
+`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("agent");
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - GENERATING .cursorrules
+ // ============================================================================
+
+ describe("fromOAC() - generating .cursorrules", () => {
+ const createAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "test-agent",
+ description: "A test agent",
+ mode: "primary",
+ ...overrides?.frontmatter,
+ },
+ metadata: {
+ name: "test-agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "You are helpful",
+ contexts: [],
+ ...overrides,
+ });
+
+ it("converts agent to .cursorrules file", async () => {
+ const agent = createAgent();
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".cursorrules");
+ });
+
+ it("includes agent name in frontmatter", async () => {
+ const agent = createAgent();
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("name: test-agent");
+ });
+
+ it("includes system prompt in body", async () => {
+ const agent = createAgent({
+ systemPrompt: "Be helpful and kind",
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("Be helpful and kind");
+ });
+
+ it("includes model in frontmatter", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ model: "gpt-4",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model: gpt-4");
+ });
+
+ it("includes temperature in frontmatter", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ temperature: 0.7,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("temperature: 0.7");
+ });
+
+ it("includes description in frontmatter when agent has one", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ name: "analyzer",
+ description: "Code analyzer",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ // Description should be in frontmatter only if other frontmatter exists
+ expect(content).toBeDefined();
+ expect(result.success).toBe(true);
+ });
+
+ it("warns on unsupported skills", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ skills: ["skill1", "skill2"],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("skills"))).toBe(true);
+ });
+
+ it("warns on unsupported hooks", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ hooks: [
+ { event: "PreToolUse", commands: [{ type: "command", command: "log" }] },
+ ],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("hooks"))).toBe(true);
+ });
+
+ it("warns on unsupported maxSteps", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ maxSteps: 10,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("maxSteps"))).toBe(true);
+ });
+
+ it("warns on mixed permissions", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ permission: {
+ read: { "*.txt": "allow", "*.js": "deny" },
+ write: "ask",
+ },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ // Should warn about granular permissions if they're detected
+ expect(result.warnings.length >= 0).toBe(true);
+ expect(result.success).toBe(true);
+ });
+
+ it("handles agent with tools", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ tools: { read: true, write: true, bash: true },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("read");
+ expect(content).toContain("write");
+ expect(content).toContain("bash");
+ });
+
+ it("generates frontmatter only when needed", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ mode: "primary",
+ },
+ metadata: {
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "Rules here",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ // If no frontmatter data, should not include frontmatter section
+ expect(content).toBe("Rules here");
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - CONTEXT INLINING
+ // ============================================================================
+
+ describe("fromOAC() - context inlining", () => {
+ it("inlines single context", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [
+ {
+ path: ".opencode/context/python-style.md",
+ description: "Python style guide",
+ },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("# Context Files");
+ expect(content).toContain("python-style.md");
+ expect(content).toContain("Python style guide");
+ });
+
+ it("inlines multiple contexts", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [
+ { path: "context1.md", description: "First" },
+ { path: "context2.md", description: "Second" },
+ { path: "context3.md", description: "Third" },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("context1.md");
+ expect(content).toContain("context2.md");
+ expect(content).toContain("context3.md");
+ });
+
+ it("includes context priority information in inlined content", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [
+ {
+ path: "important.md",
+ priority: "critical",
+ description: "Critical context",
+ },
+ ],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ // Priority should be mentioned in the content
+ expect(content).toContain("critical");
+ expect(content).toContain("important.md");
+ });
+
+ it("generates warning for inlined contexts", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "context.md", description: "Test" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("context"))).toBe(true);
+ });
+
+ it("handles contexts without description", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "context.md" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("context.md");
+ });
+
+ it("includes tool access information", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ tools: { read: true, write: true, bash: false },
+ },
+ metadata: { name: "agent", category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("# Tool Access");
+ expect(content).toContain("read");
+ expect(content).toContain("write");
+ expect(content).not.toContain("- bash");
+ });
+ });
+
+ // ============================================================================
+ // VALIDATION
+ // ============================================================================
+
+ describe("validateConversion()", () => {
+ const createAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ ...overrides,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ });
+
+ it("returns no warnings for valid agent", () => {
+ const agent = createAgent();
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings).toHaveLength(0);
+ });
+
+ it("warns when name is missing", () => {
+ const agent = createAgent({ name: "" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("name"))).toBe(true);
+ });
+
+ it("warns about subagent mode", () => {
+ const agent = createAgent({ mode: "subagent" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("subagent"))).toBe(true);
+ });
+
+ it("accepts primary mode", () => {
+ const agent = createAgent({ mode: "primary" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.length).toBe(0);
+ });
+ });
+
+ // ============================================================================
+ // AGENT MERGING (CURSOR-SPECIFIC)
+ // ============================================================================
+
+ describe("mergeAgents() - merging multiple agents", () => {
+ const createAgent = (name: string, prompt: string): OpenAgent => ({
+ frontmatter: {
+ name,
+ description: `Agent ${name}`,
+ mode: "primary",
+ tools: { read: true },
+ },
+ metadata: { name, category: "core", type: "agent" },
+ systemPrompt: prompt,
+ contexts: [],
+ });
+
+ it("returns cloned agent for single agent", () => {
+ const agent = createAgent("single", "Single agent prompt");
+
+ const result = adapter.mergeAgents([agent]);
+
+ expect(result.frontmatter.name).toBe("single");
+ expect(result.systemPrompt).toBe("Single agent prompt");
+ });
+
+ it("merges two agents", () => {
+ const agents = [
+ createAgent("agent1", "First prompt"),
+ createAgent("agent2", "Second prompt"),
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.frontmatter.name).toContain("agent1");
+ expect(result.frontmatter.name).toContain("agent2");
+ expect(result.systemPrompt).toContain("First prompt");
+ expect(result.systemPrompt).toContain("Second prompt");
+ });
+
+ it("merges three agents", () => {
+ const agents = [
+ createAgent("a1", "P1"),
+ createAgent("a2", "P2"),
+ createAgent("a3", "P3"),
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.systemPrompt).toContain("P1");
+ expect(result.systemPrompt).toContain("P2");
+ expect(result.systemPrompt).toContain("P3");
+ });
+
+ it("uses union of tools from all agents", () => {
+ const agents = [
+ {
+ ...createAgent("a1", "P1"),
+ frontmatter: { ...createAgent("a1", "P1").frontmatter, tools: { read: true, write: false } },
+ },
+ {
+ ...createAgent("a2", "P2"),
+ frontmatter: { ...createAgent("a2", "P2").frontmatter, tools: { write: true, bash: true } },
+ },
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.frontmatter.tools?.read).toBe(true);
+ expect(result.frontmatter.tools?.write).toBe(true);
+ expect(result.frontmatter.tools?.bash).toBe(true);
+ });
+
+ it("uses maximum temperature", () => {
+ const agents = [
+ { ...createAgent("a1", "P1"), frontmatter: { ...createAgent("a1", "P1").frontmatter, temperature: 0.5 } },
+ { ...createAgent("a2", "P2"), frontmatter: { ...createAgent("a2", "P2").frontmatter, temperature: 0.8 } },
+ { ...createAgent("a3", "P3"), frontmatter: { ...createAgent("a3", "P3").frontmatter, temperature: 0.6 } },
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.frontmatter.temperature).toBe(0.8);
+ });
+
+ it("uses first available model", () => {
+ const agents = [
+ { ...createAgent("a1", "P1"), frontmatter: { ...createAgent("a1", "P1").frontmatter, model: undefined } },
+ { ...createAgent("a2", "P2"), frontmatter: { ...createAgent("a2", "P2").frontmatter, model: "gpt-4" } },
+ { ...createAgent("a3", "P3"), frontmatter: { ...createAgent("a3", "P3").frontmatter, model: "gpt-3.5" } },
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("flattens contexts from all agents", () => {
+ const agents = [
+ { ...createAgent("a1", "P1"), contexts: [{ path: "ctx1.md" }] },
+ { ...createAgent("a2", "P2"), contexts: [{ path: "ctx2.md" }, { path: "ctx3.md" }] },
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.contexts).toHaveLength(3);
+ expect(result.contexts?.map((c) => c.path)).toContain("ctx1.md");
+ });
+
+ it("throws error on empty array", () => {
+ expect(() => adapter.mergeAgents([])).toThrow("Cannot merge empty agent array");
+ });
+
+ it("throws error on undefined agent", () => {
+ expect(() => adapter.mergeAgents([undefined as any])).toThrow("Agent at index 0 is undefined");
+ });
+
+ it("includes divider between prompts", () => {
+ const agents = [
+ createAgent("a1", "First"),
+ createAgent("a2", "Second"),
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.systemPrompt).toContain("---");
+ });
+
+ it("includes agent descriptions in merged prompt", () => {
+ const agents = [
+ { ...createAgent("a1", "First"), frontmatter: { ...createAgent("a1", "First").frontmatter, description: "First agent description" } },
+ { ...createAgent("a2", "Second"), frontmatter: { ...createAgent("a2", "Second").frontmatter, description: "Second agent description" } },
+ ];
+
+ const result = adapter.mergeAgents(agents);
+
+ expect(result.systemPrompt).toContain("First agent description");
+ expect(result.systemPrompt).toContain("Second agent description");
+ });
+ });
+
+ // ============================================================================
+ // MODEL MAPPING
+ // ============================================================================
+
+ describe("model mapping (Cursor to OAC)", () => {
+ it("maps gpt-4 to gpt-4", async () => {
+ const source = `---
+name: agent
+model: gpt-4
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("maps gpt-4o to gpt-4o", async () => {
+ const source = `---
+name: agent
+model: gpt-4o
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("gpt-4o");
+ });
+
+ it("maps claude-3-opus to claude-opus-3", async () => {
+ const source = `---
+name: agent
+model: claude-3-opus
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-opus-3");
+ });
+
+ it("preserves unknown models", async () => {
+ const source = `---
+name: agent
+model: custom-model
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("custom-model");
+ });
+ });
+
+ describe("model mapping (OAC to Cursor)", () => {
+ it("maps gpt-4 to gpt-4", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ model: "gpt-4",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model: gpt-4");
+ });
+
+ it("maps gpt-4o to gpt-4o", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ model: "gpt-4o",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model: gpt-4o");
+ });
+
+ it("maps claude-sonnet-4 to claude-3-sonnet (v3 fallback)", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model: claude-3-sonnet");
+ });
+
+ it("defaults to gpt-4 for unknown models", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ model: "unknown-model",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("model: gpt-4");
+ });
+
+ it("omits model when not provided", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).not.toContain("model:");
+ });
+ });
+
+ // ============================================================================
+ // TOOL PARSING
+ // ============================================================================
+
+ describe("tool parsing", () => {
+ it("parses comma-separated tools", async () => {
+ const source = `---
+name: agent
+tools: read, write, bash
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: true,
+ });
+ });
+
+ it("normalizes tools to lowercase", async () => {
+ const source = `---
+name: agent
+tools: Read, WRITE, BaSh
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(Object.keys(result.frontmatter.tools || {})).toContain("read");
+ expect(Object.keys(result.frontmatter.tools || {})).toContain("write");
+ });
+
+ it("handles empty tools", async () => {
+ const source = `---
+name: agent
+tools:
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toBeUndefined();
+ });
+
+ it("omits disabled tools from output", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ tools: { read: true, write: false, bash: true },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("- read");
+ expect(content).toContain("- bash");
+ expect(content).not.toContain("- write");
+ });
+ });
+
+ // ============================================================================
+ // ROUNDTRIP & EDGE CASES
+ // ============================================================================
+
+ describe("roundtrip conversion", () => {
+ it("preserves agent properties through roundtrip", async () => {
+ const original: OpenAgent = {
+ frontmatter: {
+ name: "roundtrip-agent",
+ description: "Test roundtrip",
+ mode: "primary",
+ model: "gpt-4",
+ temperature: 0.6,
+ },
+ metadata: { name: "roundtrip-agent", category: "core", type: "agent" },
+ systemPrompt: "Be helpful",
+ contexts: [],
+ };
+
+ // Convert to Cursor
+ const cursorResult = await adapter.fromOAC(original);
+ const cursorRules = cursorResult.configs[0].content;
+
+ // Convert back to OAC
+ const backToOAC = await adapter.toOAC(cursorRules);
+
+ expect(backToOAC.frontmatter.name).toBe("roundtrip-agent");
+ expect(backToOAC.systemPrompt).toContain("Be helpful");
+ expect(backToOAC.frontmatter.temperature).toBe(0.6);
+ });
+ });
+
+ describe("edge cases", () => {
+ it("handles empty system prompt", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ expect(result.configs[0].content).toBeDefined();
+ });
+
+ it("handles very long system prompt", async () => {
+ const longPrompt = "A".repeat(5000);
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: longPrompt,
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content.length).toBeGreaterThan(5000);
+ });
+
+ it("handles special characters in names", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent-with-dashes_and_underscores",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const content = result.configs[0].content;
+
+ expect(content).toContain("agent-with-dashes_and_underscores");
+ });
+
+ it("handles null context array", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: null as any,
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("handles undefined tools", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ tools: undefined,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("handles decimal temperature values", async () => {
+ const source = `---
+name: agent
+temperature: 0.123
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.123);
+ });
+
+ it("handles zero temperature", async () => {
+ const source = `---
+name: agent
+temperature: 0
+---
+
+Prompt`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0);
+ });
+
+ it("preserves whitespace in prompts", async () => {
+ const source = `---
+name: agent
+---
+
+ Indented line 1
+ Indented line 2
+Regular line`;
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toContain(" Indented");
+ expect(result.systemPrompt).toContain(" Indented");
+ });
+ });
+
+ // ============================================================================
+ // CONVERSION RESULT STRUCTURE
+ // ============================================================================
+
+ describe("conversion result structure", () => {
+ it("returns success true on valid conversion", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("includes capabilities in result", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.capabilities).toBeDefined();
+ expect(result.capabilities?.name).toBe("cursor");
+ });
+
+ it("includes encoding in tool config", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs[0].encoding).toBe("utf-8");
+ });
+
+ it("always returns single .cursorrules file", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ mode: "primary",
+ tools: { read: true, write: true },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "ctx1.md" }, { path: "ctx2.md" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".cursorrules");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/adapters/WindsurfAdapter.test.ts b/packages/compatibility-layer/tests/unit/adapters/WindsurfAdapter.test.ts
new file mode 100644
index 0000000..abcf8d3
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/adapters/WindsurfAdapter.test.ts
@@ -0,0 +1,1269 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { WindsurfAdapter } from "../../../src/adapters/WindsurfAdapter";
+import type { OpenAgent, AgentFrontmatter } from "../../../src/types";
+
+/**
+ * Unit tests for WindsurfAdapter with 80%+ coverage
+ *
+ * Test strategy:
+ * 1. toOAC() - Parse Windsurf JSON to OpenAgent
+ * 2. fromOAC() - Convert OpenAgent to Windsurf JSON
+ * 3. getCapabilities() - Feature matrix validation
+ * 4. validateConversion() - Validation and warnings
+ * 5. Helper methods - Model/tool/creativity mapping
+ * 6. Temperature ↔ Creativity mapping (WINDSURF-SPECIFIC)
+ * 7. Priority mapping (critical→high, medium→low)
+ * 8. Context and skill handling
+ * 9. Edge cases and roundtrip
+ */
+
+describe("WindsurfAdapter", () => {
+ let adapter: WindsurfAdapter;
+
+ beforeEach(() => {
+ adapter = new WindsurfAdapter();
+ });
+
+ // ============================================================================
+ // ADAPTER IDENTITY
+ // ============================================================================
+
+ describe("adapter identity", () => {
+ it("has correct name", () => {
+ expect(adapter.name).toBe("windsurf");
+ });
+
+ it("has correct displayName", () => {
+ expect(adapter.displayName).toBe("Windsurf");
+ });
+
+ it("returns correct config path", () => {
+ expect(adapter.getConfigPath()).toBe(".windsurf/");
+ });
+ });
+
+ // ============================================================================
+ // CAPABILITIES
+ // ============================================================================
+
+ describe("getCapabilities()", () => {
+ it("returns correct capabilities object", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.name).toBe("windsurf");
+ expect(capabilities.displayName).toBe("Windsurf");
+ expect(capabilities.supportsMultipleAgents).toBe(true);
+ expect(capabilities.supportsSkills).toBe(true);
+ expect(capabilities.supportsHooks).toBe(false);
+ expect(capabilities.supportsGranularPermissions).toBe(false);
+ expect(capabilities.supportsContexts).toBe(true);
+ expect(capabilities.supportsCustomModels).toBe(true);
+ expect(capabilities.supportsTemperature).toBe(true);
+ expect(capabilities.supportsMaxSteps).toBe(false);
+ expect(capabilities.configFormat).toBe("json");
+ expect(capabilities.outputStructure).toBe("directory");
+ });
+
+ it("includes appropriate notes", () => {
+ const capabilities = adapter.getCapabilities();
+
+ expect(capabilities.notes).toBeDefined();
+ expect(capabilities.notes && capabilities.notes.length > 0).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // toOAC() - PARSING WINDSURF JSON
+ // ============================================================================
+
+ describe("toOAC() - parsing Windsurf JSON", () => {
+ it("parses minimal config", async () => {
+ const source = JSON.stringify({
+ name: "windsurf-agent",
+ description: "Test agent",
+ systemPrompt: "You are helpful",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.name).toBe("windsurf-agent");
+ expect(result.frontmatter.description).toBe("Test agent");
+ expect(result.systemPrompt).toBe("You are helpful");
+ expect(result.frontmatter.mode).toBe("primary");
+ });
+
+ it("parses agent with model", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ model: "gpt-4",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("parses agent with tools object", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ tools: { read: true, write: true, bash: false },
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: false,
+ });
+ });
+
+ it("parses agent with tools array", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ tools: ["read", "write", "bash"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: true,
+ bash: true,
+ });
+ });
+
+ it("parses agent with creativity setting", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: "high",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(1.0);
+ });
+
+ it("parses numeric creativity value", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: 0.6,
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.6);
+ });
+
+ it("parses agent type (primary vs subagent)", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ type: "subagent",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.mode).toBe("subagent");
+ });
+
+ it("parses contexts", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ contexts: [
+ { path: "context1.md", priority: "high", description: "Important" },
+ { path: "context2.md" },
+ ],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.contexts).toHaveLength(2);
+ expect(result.contexts[0].path).toBe("context1.md");
+ expect(result.contexts[0].priority).toBe("high");
+ });
+
+ it("parses contexts as string array", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ contexts: ["context1.md", "context2.md"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.contexts).toHaveLength(2);
+ expect(result.contexts[0].path).toBe("context1.md");
+ });
+
+ it("throws error on invalid JSON", async () => {
+ const source = "not valid json";
+
+ await expect(adapter.toOAC(source)).rejects.toThrow(
+ "Invalid Windsurf config format"
+ );
+ });
+
+ it("throws error on non-object JSON", async () => {
+ const source = JSON.stringify("just a string");
+
+ await expect(adapter.toOAC(source)).rejects.toThrow(
+ "Invalid Windsurf config format"
+ );
+ });
+
+ it("handles prompt field as fallback for systemPrompt", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ prompt: "This is the prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toBe("This is the prompt");
+ });
+
+ it("prefers systemPrompt over prompt field", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ systemPrompt: "System",
+ prompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.systemPrompt).toBe("System");
+ });
+
+ it("uses category from config", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ category: "specialist",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.metadata.category).toBe("specialist");
+ });
+
+ it("defaults to core category", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.metadata.category).toBe("core");
+ });
+ });
+
+ // ============================================================================
+ // fromOAC() - GENERATING WINDSURF JSON
+ // ============================================================================
+
+ describe("fromOAC() - generating Windsurf config", () => {
+ const createAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "test-agent",
+ description: "A test agent",
+ mode: "primary",
+ ...overrides?.frontmatter,
+ },
+ metadata: {
+ name: "test-agent",
+ category: "core",
+ type: "agent",
+ },
+ systemPrompt: "You are helpful",
+ contexts: [],
+ ...overrides,
+ });
+
+ it("converts agent to config.json file", async () => {
+ const agent = createAgent();
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".windsurf/config.json");
+ });
+
+ it("converts subagent to agents/{name}.json file", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ name: "test-agent",
+ mode: "subagent",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs[0].fileName).toBe(".windsurf/agents/test-agent.json");
+ });
+
+ it("includes agent name and description", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ name: "analyzer",
+ description: "Code analyzer",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.name).toBe("analyzer");
+ expect(config.description).toBe("Code analyzer");
+ });
+
+ it("includes system prompt", async () => {
+ const agent = createAgent({
+ systemPrompt: "You are a code reviewer",
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.systemPrompt).toBe("You are a code reviewer");
+ });
+
+ it("maps model correctly", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ model: "gpt-4",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBe("gpt-4");
+ });
+
+ it("includes type field (primary vs subagent)", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ mode: "subagent",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.type).toBe("subagent");
+ });
+
+ it("maps temperature to creativity", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ temperature: 0.9,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("high");
+ });
+
+ it("includes category from metadata", async () => {
+ const agent = createAgent({
+ metadata: {
+ name: "agent",
+ category: "specialist",
+ type: "agent",
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.category).toBe("specialist");
+ });
+
+ it("warns on unsupported hooks", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ hooks: [{ event: "PreToolUse", commands: [{ type: "command", command: "log" }] }],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("hooks"))).toBe(true);
+ });
+
+ it("warns on unsupported maxSteps", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ maxSteps: 10,
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("maxSteps"))).toBe(true);
+ });
+
+ it("warns on granular permissions", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ permission: {
+ read: { "*.txt": "allow" },
+ },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("granular"))).toBe(true);
+ });
+
+ it("warns on degraded skills", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ skills: ["skill1", "skill2"],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("skills"))).toBe(true);
+ });
+
+ it("maps skills to context references", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ skills: ["python-patterns", "typescript-tips"],
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.contexts).toBeDefined();
+ expect(config.contexts.some((c: any) => c.path.includes("python-patterns"))).toBe(true);
+ });
+
+ it("includes tool access", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ tools: { read: true, write: true, bash: false },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.tools).toEqual({ read: true, write: true, bash: false });
+ });
+
+ it("includes contexts with priority mapping", async () => {
+ const agent = createAgent({
+ contexts: [
+ { path: "critical-context.md", priority: "critical" },
+ { path: "medium-context.md", priority: "medium" },
+ ],
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.contexts).toHaveLength(2);
+ // critical → high, medium → low
+ expect(config.contexts[0].priority).toBe("high");
+ expect(config.contexts[1].priority).toBe("low");
+ });
+
+ it("generates context warning", async () => {
+ const agent = createAgent({
+ contexts: [{ path: "context.md" }],
+ });
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.warnings.some((w) => w.includes("context"))).toBe(true);
+ });
+
+ it("maps permissions to binary on/off", async () => {
+ const agent = createAgent({
+ frontmatter: {
+ permission: {
+ read: "allow",
+ write: "deny",
+ bash: "ask",
+ },
+ },
+ });
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissions.read).toBe(true);
+ expect(config.permissions.write).toBe(false);
+ expect(config.permissions.bash).toBe(false); // ask → false
+ });
+ });
+
+ // ============================================================================
+ // VALIDATION
+ // ============================================================================
+
+ describe("validateConversion()", () => {
+ const createAgent = (overrides?: Partial): OpenAgent => ({
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ ...overrides,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ });
+
+ it("returns no warnings for valid agent", () => {
+ const agent = createAgent();
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings).toHaveLength(0);
+ });
+
+ it("warns when name is missing", () => {
+ const agent = createAgent({ name: "" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("name"))).toBe(true);
+ });
+
+ it("warns when description is missing", () => {
+ const agent = createAgent({ description: "" });
+
+ const warnings = adapter.validateConversion(agent);
+
+ expect(warnings.some((w) => w.includes("description"))).toBe(true);
+ });
+ });
+
+ // ============================================================================
+ // TEMPERATURE ↔ CREATIVITY MAPPING (WINDSURF-SPECIFIC)
+ // ============================================================================
+
+ describe("temperature to creativity mapping", () => {
+ it("maps low temperature (0.3) to low creativity", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0.3,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("low");
+ });
+
+ it("maps medium temperature (0.6) to medium creativity", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0.6,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("medium");
+ });
+
+ it("maps high temperature (0.9) to high creativity", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0.9,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("high");
+ });
+
+ it("maps creativity low string to 0.3 temperature", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: "low",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.3);
+ });
+
+ it("maps creativity high string to 1.0 temperature", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: "high",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(1.0);
+ });
+
+ it("maps creativity balanced to 0.5 temperature", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: "balanced",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.5);
+ });
+
+ it("defaults to medium (0.7) for unknown creativity", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ creativity: "unknown-value",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.temperature).toBe(0.7);
+ });
+
+ it("handles boundary temperature 0.4", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0.4,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("low");
+ });
+
+ it("handles boundary temperature 0.8", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0.8,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("medium");
+ });
+ });
+
+ // ============================================================================
+ // PRIORITY MAPPING
+ // ============================================================================
+
+ describe("priority mapping (OAC → Windsurf)", () => {
+ it("maps critical to high", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "critical.md", priority: "critical" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.contexts[0].priority).toBe("high");
+ });
+
+ it("maps medium to low", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "medium.md", priority: "medium" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.contexts[0].priority).toBe("low");
+ });
+
+ it("normalizes invalid priority to medium", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ contexts: [{ path: "context.md", priority: "invalid" }],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.contexts[0].priority).toBe("medium");
+ });
+ });
+
+ // ============================================================================
+ // MODEL MAPPING
+ // ============================================================================
+
+ describe("model mapping", () => {
+ it("maps claude-4-sonnet to claude-sonnet-4", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ model: "claude-4-sonnet",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-sonnet-4");
+ });
+
+ it("maps claude-4-opus to claude-opus-4", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ model: "claude-4-opus",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("claude-opus-4");
+ });
+
+ it("maps gpt-4 to gpt-4", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ model: "gpt-4",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("gpt-4");
+ });
+
+ it("preserves unknown models", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ model: "custom-model",
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.model).toBe("custom-model");
+ });
+
+ it("maps claude-sonnet-4 back to claude-4-sonnet", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBe("claude-4-sonnet");
+ });
+
+ it("defaults to claude-4-sonnet for unknown models", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ model: "unknown-model",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.model).toBe("claude-4-sonnet");
+ });
+ });
+
+ // ============================================================================
+ // TOOL MAPPING
+ // ============================================================================
+
+ describe("tool handling", () => {
+ it("preserves tool boolean values", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ tools: { read: true, write: false, bash: true },
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toEqual({
+ read: true,
+ write: false,
+ bash: true,
+ });
+ });
+
+ it("normalizes tools array to object", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ tools: ["Read", "WRITE", "Bash"],
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools?.read).toBe(true);
+ expect(result.frontmatter.tools?.write).toBe(true);
+ expect(result.frontmatter.tools?.bash).toBe(true);
+ });
+
+ it("handles empty tools", async () => {
+ const source = JSON.stringify({
+ name: "agent",
+ description: "Test",
+ tools: {},
+ systemPrompt: "Prompt",
+ });
+
+ const result = await adapter.toOAC(source);
+
+ expect(result.frontmatter.tools).toBeUndefined();
+ });
+ });
+
+ // ============================================================================
+ // PERMISSION MAPPING
+ // ============================================================================
+
+ describe("permission mapping", () => {
+ it("maps allow to true", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ permission: { read: "allow" },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissions.read).toBe(true);
+ });
+
+ it("maps deny to false", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ permission: { write: "deny" },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissions.write).toBe(false);
+ });
+
+ it("maps ask to false with warning", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ permission: { bash: "ask" },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissions.bash).toBe(false);
+ expect(result.warnings.some((w) => w.includes("ask"))).toBe(true);
+ });
+
+ it("handles boolean permissions", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ permission: { read: true, write: false },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.permissions.read).toBe(true);
+ expect(config.permissions.write).toBe(false);
+ });
+ });
+
+ // ============================================================================
+ // ROUNDTRIP & EDGE CASES
+ // ============================================================================
+
+ describe("roundtrip conversion", () => {
+ it("preserves agent properties through roundtrip", async () => {
+ const original: OpenAgent = {
+ frontmatter: {
+ name: "roundtrip-agent",
+ description: "Test roundtrip",
+ mode: "primary",
+ model: "gpt-4o",
+ temperature: 0.7,
+ tools: { read: true, write: true },
+ },
+ metadata: { name: "roundtrip-agent", category: "core", type: "agent" },
+ systemPrompt: "Be helpful",
+ contexts: [{ path: "context.md", priority: "high" }],
+ };
+
+ // Convert to Windsurf
+ const windsurfResult = await adapter.fromOAC(original);
+ const windsurfConfig = JSON.parse(windsurfResult.configs[0].content);
+
+ // Convert back to OAC
+ const backToOAC = await adapter.toOAC(JSON.stringify(windsurfConfig));
+
+ expect(backToOAC.frontmatter.name).toBe("roundtrip-agent");
+ expect(backToOAC.systemPrompt).toBe("Be helpful");
+ expect(backToOAC.frontmatter.temperature).toBe(0.7);
+ });
+ });
+
+ describe("edge cases", () => {
+ it("handles empty system prompt", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("handles very long system prompt", async () => {
+ const longPrompt = "A".repeat(5000);
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: longPrompt,
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.systemPrompt.length).toBe(5000);
+ });
+
+ it("handles special characters in names", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent-with-dashes_and_underscores",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.name).toBe("agent-with-dashes_and_underscores");
+ });
+
+ it("handles zero temperature", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 0,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("low");
+ });
+
+ it("handles high temperature values", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ temperature: 1.5,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.creativity).toBe("high");
+ });
+
+ it("handles null contexts", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: null as any,
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("handles undefined tools", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ tools: undefined,
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ expect(config.tools).toBeUndefined();
+ });
+
+ it("combines contexts and skills", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ skills: ["python-tips", "typescript-guide"],
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "project-style.md", priority: "high" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+ const config = JSON.parse(result.configs[0].content);
+
+ // Should have 3 contexts: 1 original + 2 skills
+ expect(config.contexts.length).toBe(3);
+ });
+ });
+
+ // ============================================================================
+ // CONVERSION RESULT STRUCTURE
+ // ============================================================================
+
+ describe("conversion result structure", () => {
+ it("returns success true on valid conversion", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.success).toBe(true);
+ });
+
+ it("includes capabilities in result", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.capabilities).toBeDefined();
+ expect(result.capabilities?.name).toBe("windsurf");
+ });
+
+ it("includes encoding in tool config", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs[0].encoding).toBe("utf-8");
+ });
+
+ it("returns single config file per agent", async () => {
+ const agent: OpenAgent = {
+ frontmatter: {
+ name: "agent",
+ description: "Test",
+ mode: "primary",
+ tools: { read: true, write: true },
+ },
+ metadata: { category: "core", type: "agent" },
+ systemPrompt: "Prompt",
+ contexts: [{ path: "ctx1.md" }, { path: "ctx2.md" }],
+ };
+
+ const result = await adapter.fromOAC(agent);
+
+ expect(result.configs).toHaveLength(1);
+ expect(result.configs[0].fileName).toBe(".windsurf/config.json");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/core/CapabilityMatrix.test.ts b/packages/compatibility-layer/tests/unit/core/CapabilityMatrix.test.ts
new file mode 100644
index 0000000..cb85d89
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/core/CapabilityMatrix.test.ts
@@ -0,0 +1,484 @@
+/**
+ * Unit tests for CapabilityMatrix
+ *
+ * Tests feature capability matrix and compatibility analysis.
+ */
+
+import { describe, it, expect } from "vitest";
+import {
+ getCapabilityMatrix,
+ getFeaturesByCategory,
+ getFeatureSupport,
+ isFeatureSupported,
+ analyzeCompatibility,
+ getToolCapabilities,
+ comparePlatforms,
+ getConversionSummary,
+ type Platform,
+ type FeatureCategory,
+ type SupportLevel,
+} from "../../../src/core/CapabilityMatrix";
+import type { OpenAgent } from "../../../src/types";
+
+// =============================================================================
+// Test Fixtures
+// =============================================================================
+
+const createMinimalAgent = (overrides: Partial = {}): OpenAgent => ({
+ frontmatter: {
+ name: "Test Agent",
+ description: "A test agent",
+ mode: "primary",
+ ...overrides.frontmatter,
+ },
+ systemPrompt: "You are a test agent.",
+ contexts: [],
+ ...overrides,
+});
+
+describe("CapabilityMatrix", () => {
+ // ==========================================================================
+ // getCapabilityMatrix
+ // ==========================================================================
+ describe("getCapabilityMatrix()", () => {
+ it("returns array of feature definitions", () => {
+ const matrix = getCapabilityMatrix();
+ expect(Array.isArray(matrix)).toBe(true);
+ expect(matrix.length).toBeGreaterThan(0);
+ });
+
+ it("each feature has required properties", () => {
+ const matrix = getCapabilityMatrix();
+ for (const feature of matrix) {
+ expect(feature).toHaveProperty("name");
+ expect(feature).toHaveProperty("category");
+ expect(feature).toHaveProperty("description");
+ expect(feature).toHaveProperty("support");
+ expect(typeof feature.name).toBe("string");
+ expect(typeof feature.description).toBe("string");
+ }
+ });
+
+ it("each feature has support for all platforms", () => {
+ const matrix = getCapabilityMatrix();
+ const platforms: Platform[] = ["oac", "claude", "cursor", "windsurf"];
+
+ for (const feature of matrix) {
+ for (const platform of platforms) {
+ expect(feature.support).toHaveProperty(platform);
+ expect(["full", "partial", "none"]).toContain(feature.support[platform]);
+ }
+ }
+ });
+
+ it("returns a copy (immutable)", () => {
+ const matrix1 = getCapabilityMatrix();
+ const matrix2 = getCapabilityMatrix();
+ expect(matrix1).not.toBe(matrix2);
+ expect(matrix1).toEqual(matrix2);
+ });
+ });
+
+ // ==========================================================================
+ // getFeaturesByCategory
+ // ==========================================================================
+ describe("getFeaturesByCategory()", () => {
+ it("returns features for agents category", () => {
+ const features = getFeaturesByCategory("agents");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("agents");
+ }
+ });
+
+ it("returns features for permissions category", () => {
+ const features = getFeaturesByCategory("permissions");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("permissions");
+ }
+ });
+
+ it("returns features for tools category", () => {
+ const features = getFeaturesByCategory("tools");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("tools");
+ }
+ });
+
+ it("returns features for context category", () => {
+ const features = getFeaturesByCategory("context");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("context");
+ }
+ });
+
+ it("returns features for model category", () => {
+ const features = getFeaturesByCategory("model");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("model");
+ }
+ });
+
+ it("returns features for advanced category", () => {
+ const features = getFeaturesByCategory("advanced");
+ expect(features.length).toBeGreaterThan(0);
+ for (const feature of features) {
+ expect(feature.category).toBe("advanced");
+ }
+ });
+ });
+
+ // ==========================================================================
+ // getFeatureSupport
+ // ==========================================================================
+ describe("getFeatureSupport()", () => {
+ it("returns support level for known feature", () => {
+ const support = getFeatureSupport("multipleAgents", "cursor");
+ expect(support).toBe("none");
+ });
+
+ it("returns full for OAC on most features", () => {
+ const support = getFeatureSupport("granularPermissions", "oac");
+ expect(support).toBe("full");
+ });
+
+ it("returns undefined for unknown feature", () => {
+ const support = getFeatureSupport("unknownFeature", "claude");
+ expect(support).toBeUndefined();
+ });
+
+ it("returns correct support for bashExecution (universally supported)", () => {
+ expect(getFeatureSupport("bashExecution", "oac")).toBe("full");
+ expect(getFeatureSupport("bashExecution", "claude")).toBe("full");
+ expect(getFeatureSupport("bashExecution", "cursor")).toBe("full");
+ expect(getFeatureSupport("bashExecution", "windsurf")).toBe("full");
+ });
+ });
+
+ // ==========================================================================
+ // isFeatureSupported
+ // ==========================================================================
+ describe("isFeatureSupported()", () => {
+ it("returns true for fully supported features", () => {
+ expect(isFeatureSupported("bashExecution", "claude")).toBe(true);
+ });
+
+ it("returns false for partially supported features", () => {
+ expect(isFeatureSupported("temperatureControl", "cursor")).toBe(false);
+ });
+
+ it("returns false for unsupported features", () => {
+ expect(isFeatureSupported("multipleAgents", "cursor")).toBe(false);
+ });
+
+ it("returns false for unknown features", () => {
+ expect(isFeatureSupported("unknownFeature", "claude")).toBe(false);
+ });
+ });
+
+ // ==========================================================================
+ // analyzeCompatibility
+ // ==========================================================================
+ describe("analyzeCompatibility()", () => {
+ describe("minimal agent analysis", () => {
+ it("returns compatible result for minimal agent", () => {
+ const agent = createMinimalAgent();
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.compatible).toBe(true);
+ expect(result.blockers).toHaveLength(0);
+ });
+
+ it("calculates score between 0 and 100", () => {
+ const agent = createMinimalAgent();
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.score).toBeGreaterThanOrEqual(0);
+ expect(result.score).toBeLessThanOrEqual(100);
+ });
+ });
+
+ describe("subagent mode analysis", () => {
+ it("warns about subagent mode on unsupported platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Subagent",
+ description: "A subagent",
+ mode: "subagent",
+ },
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(result.warnings.some((w) => w.includes("mode"))).toBe(true);
+ expect(result.degraded).toContain("agentModes");
+ });
+
+ it("degrades subagent mode on partially supported platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Subagent",
+ description: "A subagent",
+ mode: "subagent",
+ },
+ });
+ const result = analyzeCompatibility(agent, "windsurf");
+ expect(result.degraded).toContain("agentModes");
+ });
+ });
+
+ describe("temperature analysis", () => {
+ it("warns when temperature will be ignored", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with temp",
+ mode: "primary",
+ temperature: 0.7,
+ },
+ });
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.warnings.some((w) => w.includes("Temperature"))).toBe(true);
+ expect(result.lost).toContain("temperatureControl");
+ });
+
+ it("degrades temperature on partial support platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with temp",
+ mode: "primary",
+ temperature: 0.7,
+ },
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(result.degraded).toContain("temperatureControl");
+ });
+ });
+
+ describe("hooks analysis", () => {
+ it("blocks when hooks are not supported", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with hooks",
+ mode: "primary",
+ hooks: [{ event: "PreToolUse", command: "echo hello" }],
+ },
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(result.compatible).toBe(false);
+ expect(result.blockers.some((b) => b.includes("Hooks"))).toBe(true);
+ expect(result.lost).toContain("hooks");
+ });
+
+ it("preserves hooks on supported platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with hooks",
+ mode: "primary",
+ hooks: [{ event: "PreToolUse", command: "echo hello" }],
+ },
+ });
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.preserved).toContain("hooks");
+ });
+ });
+
+ describe("skills analysis", () => {
+ it("degrades skills on unsupported platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with skills",
+ mode: "primary",
+ skills: ["coding", "testing"],
+ },
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(result.warnings.some((w) => w.includes("Skills"))).toBe(true);
+ expect(result.degraded).toContain("skillsSystem");
+ });
+
+ it("preserves skills on supported platforms", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with skills",
+ mode: "primary",
+ skills: ["coding"],
+ },
+ });
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.preserved).toContain("skillsSystem");
+ });
+ });
+
+ describe("granular permissions analysis", () => {
+ it("degrades granular permissions", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with permissions",
+ mode: "primary",
+ permission: {
+ bash: { allow: ["npm *"], deny: ["rm -rf *"] },
+ },
+ },
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(
+ result.warnings.some((w) => w.includes("Granular permissions"))
+ ).toBe(true);
+ expect(result.degraded).toContain("granularPermissions");
+ });
+ });
+
+ describe("context analysis", () => {
+ it("degrades external context for inline-only platforms", () => {
+ const agent = createMinimalAgent({
+ contexts: [{ path: ".opencode/context/core/standards.md" }],
+ });
+ const result = analyzeCompatibility(agent, "cursor");
+ expect(
+ result.warnings.some((w) => w.includes("External context"))
+ ).toBe(true);
+ expect(result.degraded).toContain("externalContext");
+ });
+
+ it("warns about context priority loss", () => {
+ const agent = createMinimalAgent({
+ contexts: [
+ { path: ".opencode/context/core/standards.md", priority: "critical" },
+ ],
+ });
+ const result = analyzeCompatibility(agent, "claude");
+ expect(
+ result.warnings.some((w) => w.includes("priority"))
+ ).toBe(true);
+ expect(result.lost).toContain("contextPriority");
+ });
+ });
+
+ describe("maxSteps analysis", () => {
+ it("warns when maxSteps will be ignored", () => {
+ const agent = createMinimalAgent({
+ frontmatter: {
+ name: "Agent",
+ description: "Agent with maxSteps",
+ mode: "primary",
+ maxSteps: 100,
+ },
+ });
+ const result = analyzeCompatibility(agent, "claude");
+ expect(result.warnings.some((w) => w.includes("maxSteps"))).toBe(true);
+ expect(result.lost).toContain("maxSteps");
+ });
+ });
+ });
+
+ // ==========================================================================
+ // getToolCapabilities
+ // ==========================================================================
+ describe("getToolCapabilities()", () => {
+ describe("Claude capabilities", () => {
+ it("returns correct capabilities for Claude", () => {
+ const caps = getToolCapabilities("claude");
+ expect(caps.name).toBe("claude");
+ expect(caps.displayName).toBe("Claude Code");
+ expect(caps.supportsMultipleAgents).toBe(true);
+ expect(caps.supportsSkills).toBe(true);
+ expect(caps.supportsHooks).toBe(true);
+ expect(caps.supportsGranularPermissions).toBe(false);
+ expect(caps.configFormat).toBe("json");
+ expect(caps.outputStructure).toBe("directory");
+ });
+ });
+
+ describe("Cursor capabilities", () => {
+ it("returns correct capabilities for Cursor", () => {
+ const caps = getToolCapabilities("cursor");
+ expect(caps.name).toBe("cursor");
+ expect(caps.displayName).toBe("Cursor IDE");
+ expect(caps.supportsMultipleAgents).toBe(false);
+ expect(caps.supportsSkills).toBe(false);
+ expect(caps.supportsHooks).toBe(false);
+ expect(caps.configFormat).toBe("plain");
+ expect(caps.outputStructure).toBe("single-file");
+ });
+ });
+
+ describe("Windsurf capabilities", () => {
+ it("returns correct capabilities for Windsurf", () => {
+ const caps = getToolCapabilities("windsurf");
+ expect(caps.name).toBe("windsurf");
+ expect(caps.displayName).toBe("Windsurf");
+ expect(caps.supportsMultipleAgents).toBe(true);
+ expect(caps.configFormat).toBe("json");
+ expect(caps.outputStructure).toBe("directory");
+ });
+ });
+ });
+
+ // ==========================================================================
+ // comparePlatforms
+ // ==========================================================================
+ describe("comparePlatforms()", () => {
+ it("compares OAC and Claude", () => {
+ const comparison = comparePlatforms("oac", "claude");
+ expect(comparison).toHaveProperty("identical");
+ expect(comparison).toHaveProperty("betterInA");
+ expect(comparison).toHaveProperty("betterInB");
+ expect(comparison).toHaveProperty("different");
+ expect(Array.isArray(comparison.identical)).toBe(true);
+ });
+
+ it("OAC has more features than Cursor", () => {
+ const comparison = comparePlatforms("oac", "cursor");
+ expect(comparison.betterInA.length).toBeGreaterThan(0);
+ });
+
+ it("identical platforms have no differences", () => {
+ const comparison = comparePlatforms("oac", "oac");
+ expect(comparison.betterInA).toHaveLength(0);
+ expect(comparison.betterInB).toHaveLength(0);
+ expect(comparison.different).toHaveLength(0);
+ });
+
+ it("comparison is symmetric", () => {
+ const ab = comparePlatforms("claude", "cursor");
+ const ba = comparePlatforms("cursor", "claude");
+ expect(ab.betterInA).toEqual(ba.betterInB);
+ expect(ab.betterInB).toEqual(ba.betterInA);
+ });
+ });
+
+ // ==========================================================================
+ // getConversionSummary
+ // ==========================================================================
+ describe("getConversionSummary()", () => {
+ it("returns array of summary strings", () => {
+ const summary = getConversionSummary("oac", "cursor");
+ expect(Array.isArray(summary)).toBe(true);
+ expect(summary.length).toBeGreaterThan(0);
+ });
+
+ it("mentions degraded features when converting to simpler platform", () => {
+ const summary = getConversionSummary("oac", "cursor");
+ expect(summary.some((s) => s.includes("degraded"))).toBe(true);
+ });
+
+ it("handles identical platforms", () => {
+ const summary = getConversionSummary("oac", "oac");
+ expect(summary.some((s) => s.includes("Full feature parity"))).toBe(true);
+ });
+
+ it("mentions enhanced features when converting to richer platform", () => {
+ const summary = getConversionSummary("cursor", "claude");
+ expect(
+ summary.some((s) => s.includes("enhanced") || s.includes("parity"))
+ ).toBe(true);
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/core/TranslationEngine.test.ts b/packages/compatibility-layer/tests/unit/core/TranslationEngine.test.ts
new file mode 100644
index 0000000..02f9082
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/core/TranslationEngine.test.ts
@@ -0,0 +1,522 @@
+/**
+ * Unit tests for TranslationEngine
+ *
+ * Tests the orchestration of all mappers for complete agent translation.
+ */
+
+import { describe, it, expect, beforeEach } from "vitest";
+import {
+ TranslationEngine,
+ createTranslationEngine,
+ translate,
+ previewTranslation,
+ type TranslationTarget,
+ type TranslationOptions,
+} from "../../../src/core/TranslationEngine";
+import type { OpenAgent, ToolAccess, GranularPermission } from "../../../src/types";
+
+// =============================================================================
+// Test Fixtures
+// =============================================================================
+
+const createTestAgent = (overrides: Partial = {}): OpenAgent => ({
+ frontmatter: {
+ name: "Test Agent",
+ description: "A comprehensive test agent",
+ mode: "primary",
+ model: "claude-sonnet-4",
+ temperature: 0.7,
+ tools: {
+ bash: true,
+ read: true,
+ write: true,
+ edit: true,
+ grep: true,
+ glob: true,
+ task: true,
+ },
+ permission: {
+ bash: { allow: ["npm *", "git *"], deny: ["rm -rf *"] },
+ read: { allow: ["**/*"] },
+ write: { allow: ["src/**/*"], deny: ["node_modules/**"] },
+ },
+ skills: ["coding", "testing"],
+ ...overrides.frontmatter,
+ },
+ systemPrompt: "You are a helpful coding assistant.",
+ contexts: [
+ { path: ".opencode/context/core/standards.md", priority: "critical" },
+ { path: ".opencode/context/guides/workflow.md", priority: "high" },
+ ],
+ ...overrides,
+});
+
+const createMinimalAgent = (overrides: Partial = {}): OpenAgent => ({
+ frontmatter: {
+ name: "Minimal Agent",
+ description: "A minimal test agent",
+ mode: "primary",
+ ...overrides.frontmatter,
+ },
+ systemPrompt: "You are a minimal agent.",
+ contexts: [],
+ ...overrides,
+});
+
+describe("TranslationEngine", () => {
+ let engine: TranslationEngine;
+
+ beforeEach(() => {
+ engine = new TranslationEngine();
+ });
+
+ // ==========================================================================
+ // Constructor and Options
+ // ==========================================================================
+ describe("constructor", () => {
+ it("creates engine with default options", () => {
+ const engine = new TranslationEngine();
+ const opts = engine.getOptions();
+ expect(opts.permissionStrategy).toBe("permissive");
+ expect(opts.analyzeCompatibility).toBe(true);
+ expect(opts.preserveAsComments).toBe(false);
+ expect(opts.modelFallback).toBe("claude-sonnet-4");
+ });
+
+ it("creates engine with custom options", () => {
+ const engine = new TranslationEngine({
+ permissionStrategy: "restrictive",
+ analyzeCompatibility: false,
+ });
+ const opts = engine.getOptions();
+ expect(opts.permissionStrategy).toBe("restrictive");
+ expect(opts.analyzeCompatibility).toBe(false);
+ });
+ });
+
+ describe("getOptions()", () => {
+ it("returns a copy of options", () => {
+ const opts1 = engine.getOptions();
+ const opts2 = engine.getOptions();
+ expect(opts1).not.toBe(opts2);
+ expect(opts1).toEqual(opts2);
+ });
+ });
+
+ describe("setOptions()", () => {
+ it("updates options", () => {
+ engine.setOptions({ permissionStrategy: "restrictive" });
+ const opts = engine.getOptions();
+ expect(opts.permissionStrategy).toBe("restrictive");
+ });
+
+ it("preserves unset options", () => {
+ engine.setOptions({ permissionStrategy: "restrictive" });
+ const opts = engine.getOptions();
+ expect(opts.analyzeCompatibility).toBe(true);
+ });
+ });
+
+ // ==========================================================================
+ // translate() - OAC to Platform
+ // ==========================================================================
+ describe("translate()", () => {
+ describe("basic translation", () => {
+ it("translates minimal agent successfully", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.frontmatter.name).toBe("Minimal Agent");
+ expect(result.frontmatter.description).toBe("A minimal test agent");
+ });
+
+ it("includes compatibility analysis by default", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.compatibility).toBeDefined();
+ expect(result.compatibility?.compatible).toBe(true);
+ });
+
+ it("skips compatibility analysis when disabled", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude", {
+ analyzeCompatibility: false,
+ });
+ expect(result.compatibility).toBeUndefined();
+ });
+ });
+
+ describe("tool translation", () => {
+ it("translates tools to target platform format", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.tools).toBeDefined();
+ expect(result.tools?.Bash).toBe(true);
+ expect(result.tools?.Read).toBe(true);
+ expect(result.tools?.Write).toBe(true);
+ });
+
+ it("translates tools to Cursor format", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "cursor");
+ expect(result.tools).toBeDefined();
+ expect(result.tools?.terminal).toBe(true);
+ expect(result.tools?.file_read).toBe(true);
+ });
+ });
+
+ describe("permission translation", () => {
+ it("translates granular permissions to binary format", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.permissions).toBeDefined();
+ expect(typeof result.permissions?.bash).toBe("boolean");
+ expect(typeof result.permissions?.read).toBe("boolean");
+ expect(typeof result.permissions?.write).toBe("boolean");
+ });
+
+ it("uses permissive strategy by default", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ // With permissive strategy, allow patterns enable the permission
+ expect(result.permissions?.bash).toBe(true);
+ });
+
+ it("respects restrictive strategy option", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude", {
+ permissionStrategy: "restrictive",
+ });
+ // Even with restrictive, basic permissions should be enabled if allow exists
+ expect(result.permissions).toBeDefined();
+ });
+ });
+
+ describe("model translation", () => {
+ it("translates model ID to target platform format", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.model).toBeDefined();
+ expect(result.model).toBe("claude-sonnet-4-20250514");
+ });
+
+ it("handles missing model", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.model).toBeUndefined();
+ });
+ });
+
+ describe("context translation", () => {
+ it("translates context paths to target platform", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.contextPaths).toBeDefined();
+ expect(result.contextPaths?.length).toBe(2);
+ expect(result.contextPaths?.[0]).toContain(".claude/skills");
+ });
+
+ it("returns empty paths for Cursor (inline-only)", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "cursor");
+ // Cursor doesn't support external context, paths should be filtered
+ expect(result.contextPaths).toBeDefined();
+ expect(result.contextPaths?.length).toBe(0);
+ });
+
+ it("collects context priority warnings", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.warnings.some((w) => w.includes("priority"))).toBe(true);
+ });
+ });
+
+ describe("skill translation", () => {
+ it("translates skills to Claude format", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.skills).toBeDefined();
+ expect(result.skills?.length).toBe(2);
+ expect(result.skills?.[0]).toBe(".claude/skills/coding.md");
+ });
+
+ it("does not include skills for non-Claude targets", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "cursor");
+ expect(result.skills).toBeUndefined();
+ });
+ });
+
+ describe("temperature handling", () => {
+ it("includes temperature when supported", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "cursor");
+ // Cursor has partial temperature support
+ expect(result.frontmatter.temperature).toBeDefined();
+ });
+
+ it("excludes temperature when not supported", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "claude");
+ // Claude doesn't support temperature
+ expect(result.frontmatter.temperature).toBeUndefined();
+ });
+ });
+
+ describe("warnings collection", () => {
+ it("collects all warnings from sub-mappers", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "cursor");
+ expect(result.warnings.length).toBeGreaterThan(0);
+ });
+
+ it("includes compatibility warnings", () => {
+ const agent = createTestAgent({
+ frontmatter: {
+ name: "Agent with hooks",
+ description: "Agent",
+ mode: "primary",
+ hooks: [{ event: "PreToolUse", command: "echo test" }],
+ },
+ });
+ const result = engine.translate(agent, "cursor");
+ expect(result.success).toBe(false);
+ expect(result.compatibility?.compatible).toBe(false);
+ expect(result.compatibility?.blockers.length).toBeGreaterThan(0);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // translateToOAC() - Platform to OAC
+ // ==========================================================================
+ describe("translateToOAC()", () => {
+ it("translates Claude format to OAC", () => {
+ const source = {
+ name: "Claude Agent",
+ description: "An agent from Claude",
+ tools: { Bash: true, Read: true, Write: true },
+ permissions: { bash: true, read: true, write: false },
+ model: "claude-sonnet-4-20250514",
+ skills: [".claude/skills/coding.md"],
+ systemPrompt: "You are helpful.",
+ };
+ const result = engine.translateToOAC(source, "claude");
+ expect(result.success).toBe(true);
+ expect(result.agent.frontmatter?.name).toBe("Claude Agent");
+ expect(result.agent.frontmatter?.tools).toBeDefined();
+ expect(result.agent.frontmatter?.model).toBe("claude-sonnet-4");
+ });
+
+ it("translates Cursor format to OAC", () => {
+ const source = {
+ name: "Cursor Agent",
+ tools: { terminal: true, file_read: true },
+ permissions: { bash: true, read: true },
+ };
+ const result = engine.translateToOAC(source, "cursor");
+ expect(result.success).toBe(true);
+ expect(result.agent.frontmatter?.name).toBe("Cursor Agent");
+ expect(result.agent.frontmatter?.tools?.bash).toBe(true);
+ expect(result.agent.frontmatter?.tools?.read).toBe(true);
+ });
+
+ it("handles missing fields with defaults", () => {
+ const source = {};
+ const result = engine.translateToOAC(source, "claude");
+ expect(result.success).toBe(true);
+ expect(result.agent.frontmatter?.name).toBe("Unnamed Agent");
+ expect(result.agent.frontmatter?.description).toBe("Imported agent");
+ expect(result.agent.frontmatter?.mode).toBe("primary");
+ });
+
+ it("translates context paths back to OAC format", () => {
+ const source = {
+ name: "Agent",
+ contextPaths: [".claude/skills/core/standards.md"],
+ };
+ const result = engine.translateToOAC(source, "claude");
+ expect(result.agent.contexts).toBeDefined();
+ expect(result.agent.contexts?.[0].path).toBe(
+ ".opencode/context/core/standards.md"
+ );
+ });
+
+ it("converts Claude skills to OAC skill references", () => {
+ const source = {
+ name: "Agent",
+ skills: [".claude/skills/coding.md", ".claude/skills/testing.md"],
+ };
+ const result = engine.translateToOAC(source, "claude");
+ expect(result.agent.frontmatter?.skills).toHaveLength(2);
+ expect(result.agent.frontmatter?.skills?.[0]).toBe("coding");
+ });
+
+ it("includes system prompt when provided", () => {
+ const source = {
+ name: "Agent",
+ systemPrompt: "Custom prompt",
+ };
+ const result = engine.translateToOAC(source, "claude");
+ expect(result.agent.systemPrompt).toBe("Custom prompt");
+ });
+ });
+
+ // ==========================================================================
+ // translateBatch()
+ // ==========================================================================
+ describe("translateBatch()", () => {
+ it("translates multiple agents", () => {
+ const agents = [
+ createMinimalAgent({ frontmatter: { name: "Agent 1", description: "First", mode: "primary" } }),
+ createMinimalAgent({ frontmatter: { name: "Agent 2", description: "Second", mode: "primary" } }),
+ ];
+ const results = engine.translateBatch(agents, "claude");
+ expect(results).toHaveLength(2);
+ expect(results[0].frontmatter.name).toBe("Agent 1");
+ expect(results[1].frontmatter.name).toBe("Agent 2");
+ });
+
+ it("handles empty array", () => {
+ const results = engine.translateBatch([], "claude");
+ expect(results).toHaveLength(0);
+ });
+
+ it("processes each agent independently", () => {
+ const agents = [
+ createTestAgent(),
+ createMinimalAgent(),
+ ];
+ const results = engine.translateBatch(agents, "claude");
+ expect(results[0].tools).toBeDefined();
+ expect(results[1].tools).toBeUndefined();
+ });
+ });
+
+ // ==========================================================================
+ // preview()
+ // ==========================================================================
+ describe("preview()", () => {
+ it("returns compatibility analysis without translation", () => {
+ const agent = createTestAgent();
+ const result = engine.preview(agent, "cursor");
+ expect(result).toHaveProperty("compatible");
+ expect(result).toHaveProperty("score");
+ expect(result).toHaveProperty("warnings");
+ expect(result).toHaveProperty("blockers");
+ });
+
+ it("identifies incompatible agents", () => {
+ const agent = createTestAgent({
+ frontmatter: {
+ name: "Agent with hooks",
+ description: "Agent",
+ mode: "primary",
+ hooks: [{ event: "PreToolUse", command: "echo test" }],
+ },
+ });
+ const result = engine.preview(agent, "cursor");
+ expect(result.compatible).toBe(false);
+ expect(result.blockers.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ==========================================================================
+ // isFullyCompatible()
+ // ==========================================================================
+ describe("isFullyCompatible()", () => {
+ it("returns true for minimal agent to Claude", () => {
+ const agent = createMinimalAgent();
+ expect(engine.isFullyCompatible(agent, "claude")).toBe(true);
+ });
+
+ it("returns false when features will be lost", () => {
+ const agent = createTestAgent(); // Has temperature, contexts with priority
+ expect(engine.isFullyCompatible(agent, "cursor")).toBe(false);
+ });
+ });
+
+ // ==========================================================================
+ // Factory Functions
+ // ==========================================================================
+ describe("createTranslationEngine()", () => {
+ it("creates engine with default options", () => {
+ const engine = createTranslationEngine();
+ expect(engine).toBeInstanceOf(TranslationEngine);
+ expect(engine.getOptions().permissionStrategy).toBe("permissive");
+ });
+
+ it("creates engine with custom options", () => {
+ const engine = createTranslationEngine({
+ permissionStrategy: "restrictive",
+ });
+ expect(engine.getOptions().permissionStrategy).toBe("restrictive");
+ });
+ });
+
+ describe("translate() function", () => {
+ it("performs one-off translation", () => {
+ const agent = createMinimalAgent();
+ const result = translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.frontmatter.name).toBe("Minimal Agent");
+ });
+
+ it("accepts options", () => {
+ const agent = createMinimalAgent();
+ const result = translate(agent, "claude", {
+ analyzeCompatibility: false,
+ });
+ expect(result.compatibility).toBeUndefined();
+ });
+ });
+
+ describe("previewTranslation()", () => {
+ it("performs one-off compatibility check", () => {
+ const agent = createTestAgent();
+ const result = previewTranslation(agent, "cursor");
+ expect(result).toHaveProperty("compatible");
+ expect(result).toHaveProperty("score");
+ });
+ });
+
+ // ==========================================================================
+ // Edge Cases
+ // ==========================================================================
+ describe("edge cases", () => {
+ it("handles agent with no tools", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.tools).toBeUndefined();
+ });
+
+ it("handles agent with no permissions", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.permissions).toBeUndefined();
+ });
+
+ it("handles agent with no contexts", () => {
+ const agent = createMinimalAgent({ contexts: [] });
+ const result = engine.translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.contextPaths).toBeUndefined();
+ });
+
+ it("handles agent with no skills", () => {
+ const agent = createMinimalAgent();
+ const result = engine.translate(agent, "claude");
+ expect(result.success).toBe(true);
+ expect(result.skills).toBeUndefined();
+ });
+
+ it("handles Windsurf target", () => {
+ const agent = createTestAgent();
+ const result = engine.translate(agent, "windsurf");
+ expect(result.success).toBe(true);
+ expect(result.frontmatter.name).toBe("Test Agent");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/mappers/ContextMapper.test.ts b/packages/compatibility-layer/tests/unit/mappers/ContextMapper.test.ts
new file mode 100644
index 0000000..a7b63eb
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/mappers/ContextMapper.test.ts
@@ -0,0 +1,444 @@
+/**
+ * Unit tests for ContextMapper
+ *
+ * Tests context file path mapping between OAC and other platforms.
+ */
+
+import { describe, it, expect } from "vitest";
+import {
+ mapContextPathFromOAC,
+ mapContextPathToOAC,
+ mapContextReferenceFromOAC,
+ mapContextReferencesFromOAC,
+ mapSkillsToClaudeFormat,
+ mapSkillsFromClaudeFormat,
+ getContextBaseDir,
+ supportsExternalContext,
+ supportsContextSubdirs,
+ supportsContextPriority,
+ createContextReference,
+ normalizeContextPath,
+ getRelativeContextPath,
+ type ContextPlatform,
+} from "../../../src/mappers/ContextMapper";
+import type { ContextReference, SkillReference } from "../../../src/types";
+
+describe("ContextMapper", () => {
+ // ==========================================================================
+ // mapContextPathFromOAC - OAC to Platform Path Mapping
+ // ==========================================================================
+ describe("mapContextPathFromOAC()", () => {
+ describe("Claude platform", () => {
+ it("maps OAC context path to Claude skills path", () => {
+ const result = mapContextPathFromOAC(
+ ".opencode/context/core/standards.md",
+ "claude"
+ );
+ expect(result.path).toBe(".claude/skills/core/standards.md");
+ expect(result.exact).toBe(true);
+ expect(result.warning).toBeUndefined();
+ });
+
+ it("preserves subdirectory structure for Claude", () => {
+ const result = mapContextPathFromOAC(
+ ".opencode/context/domain/auth/models.md",
+ "claude"
+ );
+ expect(result.path).toBe(".claude/skills/domain/auth/models.md");
+ expect(result.exact).toBe(true);
+ });
+
+ it("handles paths without base dir prefix", () => {
+ const result = mapContextPathFromOAC("core/standards.md", "claude");
+ expect(result.path).toBe(".claude/skills/core/standards.md");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("Cursor platform (inline-only)", () => {
+ it("returns empty path with warning for Cursor", () => {
+ const result = mapContextPathFromOAC(
+ ".opencode/context/core/standards.md",
+ "cursor"
+ );
+ expect(result.path).toBe("");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("does not support external context files");
+ });
+ });
+
+ describe("Windsurf platform", () => {
+ it("maps OAC context path to Windsurf context path", () => {
+ const result = mapContextPathFromOAC(
+ ".opencode/context/core/standards.md",
+ "windsurf"
+ );
+ expect(result.path).toBe(".windsurf/context/core/standards.md");
+ expect(result.exact).toBe(true);
+ });
+
+ it("preserves subdirectory structure for Windsurf", () => {
+ const result = mapContextPathFromOAC(
+ ".opencode/context/guides/development.md",
+ "windsurf"
+ );
+ expect(result.path).toBe(".windsurf/context/guides/development.md");
+ expect(result.exact).toBe(true);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // mapContextPathToOAC - Platform to OAC Path Mapping
+ // ==========================================================================
+ describe("mapContextPathToOAC()", () => {
+ describe("from Claude", () => {
+ it("maps Claude skills path to OAC context path", () => {
+ const result = mapContextPathToOAC(
+ ".claude/skills/core/standards.md",
+ "claude"
+ );
+ expect(result.path).toBe(".opencode/context/core/standards.md");
+ expect(result.exact).toBe(true);
+ });
+
+ it("handles paths without base dir prefix", () => {
+ const result = mapContextPathToOAC("core/standards.md", "claude");
+ expect(result.path).toBe(".opencode/context/core/standards.md");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("from Cursor (inline-only)", () => {
+ it("returns empty path with warning for Cursor", () => {
+ const result = mapContextPathToOAC("some-path.md", "cursor");
+ expect(result.path).toBe("");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("uses inline context");
+ });
+ });
+
+ describe("from Windsurf", () => {
+ it("maps Windsurf context path to OAC context path", () => {
+ const result = mapContextPathToOAC(
+ ".windsurf/context/core/standards.md",
+ "windsurf"
+ );
+ expect(result.path).toBe(".opencode/context/core/standards.md");
+ expect(result.exact).toBe(true);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // mapContextReferenceFromOAC - Context Reference Mapping
+ // ==========================================================================
+ describe("mapContextReferenceFromOAC()", () => {
+ it("maps context reference without priority", () => {
+ const context: ContextReference = {
+ path: ".opencode/context/core/standards.md",
+ };
+ const result = mapContextReferenceFromOAC(context, "claude");
+ expect(result.path).toBe(".claude/skills/core/standards.md");
+ expect(result.exact).toBe(true);
+ expect(result.priority).toBeUndefined();
+ });
+
+ it("includes priority in result but warns if unsupported", () => {
+ const context: ContextReference = {
+ path: ".opencode/context/core/standards.md",
+ priority: "critical",
+ };
+ const result = mapContextReferenceFromOAC(context, "claude");
+ expect(result.path).toBe(".claude/skills/core/standards.md");
+ expect(result.priority).toBe("critical");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("does not support priority metadata");
+ });
+
+ it("returns inline warning for Cursor", () => {
+ const context: ContextReference = {
+ path: ".opencode/context/core/standards.md",
+ priority: "high",
+ };
+ const result = mapContextReferenceFromOAC(context, "cursor");
+ expect(result.path).toBe("");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toBeDefined();
+ });
+ });
+
+ // ==========================================================================
+ // mapContextReferencesFromOAC - Batch Context Reference Mapping
+ // ==========================================================================
+ describe("mapContextReferencesFromOAC()", () => {
+ it("maps multiple context references", () => {
+ const contexts: ContextReference[] = [
+ { path: ".opencode/context/core/standards.md" },
+ { path: ".opencode/context/guides/workflow.md" },
+ ];
+ const result = mapContextReferencesFromOAC(contexts, "claude");
+ expect(result.paths).toHaveLength(2);
+ expect(result.paths[0]).toBe(".claude/skills/core/standards.md");
+ expect(result.paths[1]).toBe(".claude/skills/guides/workflow.md");
+ expect(result.warnings).toHaveLength(0);
+ });
+
+ it("collects warnings from all references", () => {
+ const contexts: ContextReference[] = [
+ { path: ".opencode/context/core/standards.md", priority: "critical" },
+ { path: ".opencode/context/guides/workflow.md", priority: "high" },
+ ];
+ const result = mapContextReferencesFromOAC(contexts, "claude");
+ expect(result.paths).toHaveLength(2);
+ expect(result.warnings.length).toBeGreaterThan(0);
+ });
+
+ it("filters out empty paths for Cursor", () => {
+ const contexts: ContextReference[] = [
+ { path: ".opencode/context/core/standards.md" },
+ { path: ".opencode/context/guides/workflow.md" },
+ ];
+ const result = mapContextReferencesFromOAC(contexts, "cursor");
+ expect(result.paths).toHaveLength(0);
+ expect(result.warnings.length).toBeGreaterThan(0);
+ });
+
+ it("handles empty array", () => {
+ const result = mapContextReferencesFromOAC([], "claude");
+ expect(result.paths).toHaveLength(0);
+ expect(result.warnings).toHaveLength(0);
+ });
+ });
+
+ // ==========================================================================
+ // mapSkillsToClaudeFormat - OAC Skills to Claude Format
+ // ==========================================================================
+ describe("mapSkillsToClaudeFormat()", () => {
+ it("maps string skill references", () => {
+ const skills: SkillReference[] = ["coding", "testing"];
+ const result = mapSkillsToClaudeFormat(skills);
+ expect(result.skills).toHaveLength(2);
+ expect(result.skills[0]).toBe(".claude/skills/coding.md");
+ expect(result.skills[1]).toBe(".claude/skills/testing.md");
+ expect(result.warnings).toHaveLength(0);
+ });
+
+ it("maps object skill references", () => {
+ const skills: SkillReference[] = [
+ { name: "coding" },
+ { name: "testing" },
+ ];
+ const result = mapSkillsToClaudeFormat(skills);
+ expect(result.skills).toHaveLength(2);
+ expect(result.skills[0]).toBe(".claude/skills/coding.md");
+ expect(result.skills[1]).toBe(".claude/skills/testing.md");
+ });
+
+ it("warns when skill config will be ignored", () => {
+ const skills: SkillReference[] = [
+ { name: "coding", config: { theme: "dark" } },
+ ];
+ const result = mapSkillsToClaudeFormat(skills);
+ expect(result.skills).toHaveLength(1);
+ expect(result.warnings).toHaveLength(1);
+ expect(result.warnings[0]).toContain("config for 'coding' will be ignored");
+ });
+
+ it("handles mixed string and object skills", () => {
+ const skills: SkillReference[] = [
+ "simple-skill",
+ { name: "configured-skill", config: { option: true } },
+ ];
+ const result = mapSkillsToClaudeFormat(skills);
+ expect(result.skills).toHaveLength(2);
+ expect(result.skills[0]).toBe(".claude/skills/simple-skill.md");
+ expect(result.skills[1]).toBe(".claude/skills/configured-skill.md");
+ expect(result.warnings).toHaveLength(1);
+ });
+
+ it("handles empty skill config without warning", () => {
+ const skills: SkillReference[] = [{ name: "coding", config: {} }];
+ const result = mapSkillsToClaudeFormat(skills);
+ expect(result.warnings).toHaveLength(0);
+ });
+ });
+
+ // ==========================================================================
+ // mapSkillsFromClaudeFormat - Claude Skills to OAC Format
+ // ==========================================================================
+ describe("mapSkillsFromClaudeFormat()", () => {
+ it("extracts skill names from Claude paths", () => {
+ const skillPaths = [".claude/skills/coding.md", ".claude/skills/testing.md"];
+ const result = mapSkillsFromClaudeFormat(skillPaths);
+ expect(result).toHaveLength(2);
+ expect(result[0]).toBe("coding");
+ expect(result[1]).toBe("testing");
+ });
+
+ it("handles nested skill paths", () => {
+ const skillPaths = [".claude/skills/advanced/coding.md"];
+ const result = mapSkillsFromClaudeFormat(skillPaths);
+ expect(result).toHaveLength(1);
+ expect(result[0]).toBe("coding");
+ });
+
+ it("handles paths without expected format", () => {
+ const skillPaths = ["some/weird/path"];
+ const result = mapSkillsFromClaudeFormat(skillPaths);
+ expect(result).toHaveLength(1);
+ // Falls back to full path when no match
+ expect(result[0]).toBe("some/weird/path");
+ });
+
+ it("handles empty array", () => {
+ const result = mapSkillsFromClaudeFormat([]);
+ expect(result).toHaveLength(0);
+ });
+ });
+
+ // ==========================================================================
+ // Utility Functions
+ // ==========================================================================
+ describe("getContextBaseDir()", () => {
+ it("returns correct base dir for OAC", () => {
+ expect(getContextBaseDir("oac")).toBe(".opencode/context");
+ });
+
+ it("returns correct base dir for Claude", () => {
+ expect(getContextBaseDir("claude")).toBe(".claude/skills");
+ });
+
+ it("returns empty string for Cursor (inline-only)", () => {
+ expect(getContextBaseDir("cursor")).toBe("");
+ });
+
+ it("returns correct base dir for Windsurf", () => {
+ expect(getContextBaseDir("windsurf")).toBe(".windsurf/context");
+ });
+ });
+
+ describe("supportsExternalContext()", () => {
+ it("returns true for OAC", () => {
+ expect(supportsExternalContext("oac")).toBe(true);
+ });
+
+ it("returns true for Claude", () => {
+ expect(supportsExternalContext("claude")).toBe(true);
+ });
+
+ it("returns false for Cursor", () => {
+ expect(supportsExternalContext("cursor")).toBe(false);
+ });
+
+ it("returns true for Windsurf", () => {
+ expect(supportsExternalContext("windsurf")).toBe(true);
+ });
+ });
+
+ describe("supportsContextSubdirs()", () => {
+ it("returns true for OAC", () => {
+ expect(supportsContextSubdirs("oac")).toBe(true);
+ });
+
+ it("returns true for Claude", () => {
+ expect(supportsContextSubdirs("claude")).toBe(true);
+ });
+
+ it("returns false for Cursor", () => {
+ expect(supportsContextSubdirs("cursor")).toBe(false);
+ });
+
+ it("returns true for Windsurf", () => {
+ expect(supportsContextSubdirs("windsurf")).toBe(true);
+ });
+ });
+
+ describe("supportsContextPriority()", () => {
+ it("returns true only for OAC", () => {
+ expect(supportsContextPriority("oac")).toBe(true);
+ expect(supportsContextPriority("claude")).toBe(false);
+ expect(supportsContextPriority("cursor")).toBe(false);
+ expect(supportsContextPriority("windsurf")).toBe(false);
+ });
+ });
+
+ describe("createContextReference()", () => {
+ it("creates reference with path only", () => {
+ const ref = createContextReference(".opencode/context/core/standards.md");
+ expect(ref.path).toBe(".opencode/context/core/standards.md");
+ expect(ref.priority).toBeUndefined();
+ expect(ref.description).toBeUndefined();
+ });
+
+ it("creates reference with priority", () => {
+ const ref = createContextReference(
+ ".opencode/context/core/standards.md",
+ "critical"
+ );
+ expect(ref.path).toBe(".opencode/context/core/standards.md");
+ expect(ref.priority).toBe("critical");
+ });
+
+ it("creates reference with all fields", () => {
+ const ref = createContextReference(
+ ".opencode/context/core/standards.md",
+ "high",
+ "Code quality standards"
+ );
+ expect(ref.path).toBe(".opencode/context/core/standards.md");
+ expect(ref.priority).toBe("high");
+ expect(ref.description).toBe("Code quality standards");
+ });
+ });
+
+ describe("normalizeContextPath()", () => {
+ it("removes duplicate slashes", () => {
+ expect(normalizeContextPath("path//to///file.md")).toBe("path/to/file.md");
+ });
+
+ it("removes leading slash", () => {
+ expect(normalizeContextPath("/path/to/file.md")).toBe("path/to/file.md");
+ });
+
+ it("removes trailing slash", () => {
+ expect(normalizeContextPath("path/to/directory/")).toBe("path/to/directory");
+ });
+
+ it("handles combined issues", () => {
+ expect(normalizeContextPath("//path//to///file/")).toBe("path/to/file");
+ });
+
+ it("returns unchanged normal path", () => {
+ expect(normalizeContextPath("path/to/file.md")).toBe("path/to/file.md");
+ });
+ });
+
+ describe("getRelativeContextPath()", () => {
+ it("extracts relative path for OAC", () => {
+ const result = getRelativeContextPath(
+ ".opencode/context/core/standards.md",
+ "oac"
+ );
+ expect(result).toBe("core/standards.md");
+ });
+
+ it("extracts relative path for Claude", () => {
+ const result = getRelativeContextPath(
+ ".claude/skills/coding/standards.md",
+ "claude"
+ );
+ expect(result).toBe("coding/standards.md");
+ });
+
+ it("returns full path if base dir not found", () => {
+ const result = getRelativeContextPath("some/other/path.md", "oac");
+ expect(result).toBe("some/other/path.md");
+ });
+
+ it("handles Cursor (empty base dir)", () => {
+ const result = getRelativeContextPath("any/path.md", "cursor");
+ expect(result).toBe("any/path.md");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/mappers/ModelMapper.test.ts b/packages/compatibility-layer/tests/unit/mappers/ModelMapper.test.ts
new file mode 100644
index 0000000..f0916d6
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/mappers/ModelMapper.test.ts
@@ -0,0 +1,282 @@
+/**
+ * Unit tests for ModelMapper
+ *
+ * Tests AI model identifier mapping between platforms.
+ */
+
+import { describe, it, expect } from "vitest";
+import {
+ mapModelFromOAC,
+ mapModelToOAC,
+ getModelFamily,
+ getModelInfo,
+ getAllModels,
+ getModelsForPlatform,
+ isModelAvailable,
+ getDefaultModel,
+} from "../../../src/mappers/ModelMapper";
+
+describe("ModelMapper", () => {
+ // ==========================================================================
+ // mapModelFromOAC
+ // ==========================================================================
+ describe("mapModelFromOAC()", () => {
+ describe("Claude models", () => {
+ it("maps claude-sonnet-4 to Claude format with date suffix", () => {
+ const result = mapModelFromOAC("claude-sonnet-4", "claude");
+ expect(result.id).toBe("claude-sonnet-4-20250514");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps claude-sonnet-4 to Cursor format", () => {
+ const result = mapModelFromOAC("claude-sonnet-4", "cursor");
+ expect(result.id).toBe("claude-sonnet-4");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps claude-sonnet-4 to Windsurf format", () => {
+ const result = mapModelFromOAC("claude-sonnet-4", "windsurf");
+ expect(result.id).toBe("claude-4-sonnet");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps claude-3.5-sonnet correctly", () => {
+ const result = mapModelFromOAC("claude-3.5-sonnet", "claude");
+ expect(result.id).toBe("claude-3-5-sonnet-20241022");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps claude-3-opus correctly", () => {
+ const result = mapModelFromOAC("claude-3-opus", "claude");
+ expect(result.id).toBe("claude-3-opus-20240229");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("GPT models", () => {
+ it("maps gpt-4 to Cursor", () => {
+ const result = mapModelFromOAC("gpt-4", "cursor");
+ expect(result.id).toBe("gpt-4");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps gpt-4-turbo to Windsurf", () => {
+ const result = mapModelFromOAC("gpt-4-turbo", "windsurf");
+ expect(result.id).toBe("gpt-4-turbo");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps gpt-4o to Cursor", () => {
+ const result = mapModelFromOAC("gpt-4o", "cursor");
+ expect(result.id).toBe("gpt-4o");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("unknown models", () => {
+ it("returns model as-is with warning", () => {
+ const result = mapModelFromOAC("unknown-model", "claude");
+ expect(result.id).toBe("unknown-model");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("Unknown model");
+ });
+
+ it("recognizes platform format and returns as-is", () => {
+ const result = mapModelFromOAC("claude-sonnet-4-20250514", "claude");
+ expect(result.id).toBe("claude-sonnet-4-20250514");
+ expect(result.exact).toBe(true);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // mapModelToOAC
+ // ==========================================================================
+ describe("mapModelToOAC()", () => {
+ describe("from Claude", () => {
+ it("maps Claude format to OAC", () => {
+ const result = mapModelToOAC("claude-sonnet-4-20250514", "claude");
+ expect(result.id).toBe("claude-sonnet-4");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps claude-3-5-sonnet with date to OAC", () => {
+ const result = mapModelToOAC("claude-3-5-sonnet-20241022", "claude");
+ expect(result.id).toBe("claude-3.5-sonnet");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("from Cursor", () => {
+ it("maps Cursor format to OAC", () => {
+ const result = mapModelToOAC("claude-sonnet-4", "cursor");
+ expect(result.id).toBe("claude-sonnet-4");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps gpt-4 from Cursor", () => {
+ const result = mapModelToOAC("gpt-4", "cursor");
+ expect(result.id).toBe("gpt-4");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("from Windsurf", () => {
+ it("maps Windsurf format to OAC", () => {
+ const result = mapModelToOAC("claude-4-sonnet", "windsurf");
+ expect(result.id).toBe("claude-sonnet-4");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("unknown models", () => {
+ it("attempts pattern matching", () => {
+ const result = mapModelToOAC("claude-sonnet-new-20260101", "claude");
+ // Should strip date and attempt normalization
+ expect(result.exact).toBe(false);
+ expect(result.warning).toBeDefined();
+ });
+
+ it("returns as-is for completely unknown models", () => {
+ const result = mapModelToOAC("some-new-model", "cursor");
+ expect(result.id).toBe("some-new-model");
+ expect(result.exact).toBe(false);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // getModelFamily
+ // ==========================================================================
+ describe("getModelFamily()", () => {
+ it("identifies Claude models", () => {
+ expect(getModelFamily("claude-sonnet-4")).toBe("claude");
+ expect(getModelFamily("claude-3-opus")).toBe("claude");
+ expect(getModelFamily("CLAUDE-HAIKU")).toBe("claude");
+ });
+
+ it("identifies GPT models", () => {
+ expect(getModelFamily("gpt-4")).toBe("gpt");
+ expect(getModelFamily("gpt-4-turbo")).toBe("gpt");
+ expect(getModelFamily("GPT-4o")).toBe("gpt");
+ });
+
+ it("identifies Gemini models", () => {
+ expect(getModelFamily("gemini-pro")).toBe("gemini");
+ expect(getModelFamily("gemini-2.0-flash")).toBe("gemini");
+ });
+
+ it("identifies Llama models", () => {
+ expect(getModelFamily("llama-3")).toBe("llama");
+ });
+
+ it("identifies Mistral models", () => {
+ expect(getModelFamily("mistral-large")).toBe("mistral");
+ });
+
+ it("returns 'other' for unknown models", () => {
+ expect(getModelFamily("some-unknown-model")).toBe("other");
+ });
+ });
+
+ // ==========================================================================
+ // getModelInfo
+ // ==========================================================================
+ describe("getModelInfo()", () => {
+ it("returns model info for known OAC model", () => {
+ const info = getModelInfo("claude-sonnet-4");
+ expect(info).toBeDefined();
+ expect(info?.displayName).toBe("Claude Sonnet 4");
+ expect(info?.family).toBe("claude");
+ });
+
+ it("returns undefined for unknown model", () => {
+ const info = getModelInfo("unknown-model");
+ expect(info).toBeUndefined();
+ });
+ });
+
+ // ==========================================================================
+ // getAllModels
+ // ==========================================================================
+ describe("getAllModels()", () => {
+ it("returns array of all registered models", () => {
+ const models = getAllModels();
+ expect(Array.isArray(models)).toBe(true);
+ expect(models.length).toBeGreaterThan(0);
+ });
+
+ it("includes Claude models", () => {
+ const models = getAllModels();
+ const claudeModels = models.filter((m) => m.family === "claude");
+ expect(claudeModels.length).toBeGreaterThan(0);
+ });
+
+ it("includes GPT models", () => {
+ const models = getAllModels();
+ const gptModels = models.filter((m) => m.family === "gpt");
+ expect(gptModels.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ==========================================================================
+ // getModelsForPlatform
+ // ==========================================================================
+ describe("getModelsForPlatform()", () => {
+ it("returns models available on Claude", () => {
+ const models = getModelsForPlatform("claude");
+ expect(models.length).toBeGreaterThan(0);
+ expect(models.every((m) => m.platformIds.claude !== undefined)).toBe(true);
+ });
+
+ it("returns models available on Cursor", () => {
+ const models = getModelsForPlatform("cursor");
+ expect(models.length).toBeGreaterThan(0);
+ });
+
+ it("returns models available on Windsurf", () => {
+ const models = getModelsForPlatform("windsurf");
+ expect(models.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ==========================================================================
+ // isModelAvailable
+ // ==========================================================================
+ describe("isModelAvailable()", () => {
+ it("returns true for available models", () => {
+ expect(isModelAvailable("claude-sonnet-4", "claude")).toBe(true);
+ expect(isModelAvailable("gpt-4", "cursor")).toBe(true);
+ });
+
+ it("returns false for unavailable models", () => {
+ expect(isModelAvailable("unknown-model", "claude")).toBe(false);
+ });
+
+ it("returns false for models not on specific platform", () => {
+ // GPT models are not available on Claude platform
+ expect(isModelAvailable("gpt-4", "claude")).toBe(false);
+ });
+ });
+
+ // ==========================================================================
+ // getDefaultModel
+ // ==========================================================================
+ describe("getDefaultModel()", () => {
+ it("returns Claude format default for Claude", () => {
+ const model = getDefaultModel("claude");
+ expect(model).toContain("claude");
+ expect(model).toMatch(/\d{8}$/); // Date suffix
+ });
+
+ it("returns Cursor format default for Cursor", () => {
+ const model = getDefaultModel("cursor");
+ expect(model).toContain("claude");
+ });
+
+ it("returns Windsurf format default for Windsurf", () => {
+ const model = getDefaultModel("windsurf");
+ expect(model).toContain("claude");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/mappers/PermissionMapper.test.ts b/packages/compatibility-layer/tests/unit/mappers/PermissionMapper.test.ts
new file mode 100644
index 0000000..e719528
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/mappers/PermissionMapper.test.ts
@@ -0,0 +1,320 @@
+/**
+ * Unit tests for PermissionMapper
+ *
+ * Tests permission translation between OAC granular and binary formats.
+ */
+
+import { describe, it, expect } from "vitest";
+import {
+ resolvePermissionRule,
+ isGranularRule,
+ mapPermissionsFromOAC,
+ mapPermissionsToOAC,
+ createGranularRule,
+ extractPatterns,
+ mergePermissionRules,
+ hasGranularPermissions,
+ hasAskPermissions,
+ analyzePermissionDegradation,
+} from "../../../src/mappers/PermissionMapper";
+
+describe("PermissionMapper", () => {
+ // ==========================================================================
+ // resolvePermissionRule
+ // ==========================================================================
+ describe("resolvePermissionRule()", () => {
+ describe("boolean rules", () => {
+ it("returns true for true", () => {
+ expect(resolvePermissionRule(true)).toBe(true);
+ });
+
+ it("returns false for false", () => {
+ expect(resolvePermissionRule(false)).toBe(false);
+ });
+ });
+
+ describe("literal rules", () => {
+ it("returns true for 'allow'", () => {
+ expect(resolvePermissionRule("allow")).toBe(true);
+ });
+
+ it("returns false for 'deny'", () => {
+ expect(resolvePermissionRule("deny")).toBe(false);
+ });
+
+ it("returns true for 'ask' with permissive strategy", () => {
+ expect(resolvePermissionRule("ask", "permissive")).toBe(true);
+ });
+
+ it("returns false for 'ask' with restrictive strategy", () => {
+ expect(resolvePermissionRule("ask", "restrictive")).toBe(false);
+ });
+
+ it("returns false for 'ask' with ask-as-deny strategy", () => {
+ expect(resolvePermissionRule("ask", "ask-as-deny")).toBe(false);
+ });
+ });
+
+ describe("granular rules (records)", () => {
+ it("returns true if any allow exists with permissive strategy", () => {
+ const rule = { "*.ts": "allow" as const, "*.js": "deny" as const };
+ expect(resolvePermissionRule(rule, "permissive")).toBe(true);
+ });
+
+ it("returns false if no allow and has deny with restrictive strategy", () => {
+ const rule = { "*.ts": "deny" as const };
+ expect(resolvePermissionRule(rule, "restrictive")).toBe(false);
+ });
+
+ it("returns true if allow and no deny with restrictive strategy", () => {
+ const rule = { "*.ts": "allow" as const };
+ expect(resolvePermissionRule(rule, "restrictive")).toBe(true);
+ });
+ });
+ });
+
+ // ==========================================================================
+ // isGranularRule
+ // ==========================================================================
+ describe("isGranularRule()", () => {
+ it("returns false for boolean", () => {
+ expect(isGranularRule(true)).toBe(false);
+ expect(isGranularRule(false)).toBe(false);
+ });
+
+ it("returns false for literal strings", () => {
+ expect(isGranularRule("allow")).toBe(false);
+ expect(isGranularRule("deny")).toBe(false);
+ expect(isGranularRule("ask")).toBe(false);
+ });
+
+ it("returns true for object rules", () => {
+ expect(isGranularRule({ "*.ts": "allow" })).toBe(true);
+ expect(isGranularRule({ "*": "deny" })).toBe(true);
+ });
+ });
+
+ // ==========================================================================
+ // mapPermissionsFromOAC
+ // ==========================================================================
+ describe("mapPermissionsFromOAC()", () => {
+ it("converts simple allow/deny to binary", () => {
+ const permissions = { bash: "allow" as const, write: "deny" as const };
+ const result = mapPermissionsFromOAC(permissions, "claude");
+
+ expect(result.permissions).toEqual({ bash: true, write: false });
+ expect(result.warnings).toHaveLength(0);
+ });
+
+ it("generates warnings for granular permissions", () => {
+ const permissions = {
+ bash: { "rm *": "deny" as const, "*": "allow" as const },
+ };
+ const result = mapPermissionsFromOAC(permissions, "claude");
+
+ expect(result.warnings.length).toBeGreaterThan(0);
+ expect(result.warnings[0]).toContain("Granular");
+ });
+
+ it("generates warnings for 'ask' permissions", () => {
+ const permissions = { bash: "ask" as const };
+ const result = mapPermissionsFromOAC(permissions, "claude");
+
+ expect(result.warnings.length).toBeGreaterThan(0);
+ expect(result.warnings[0]).toContain("ask");
+ });
+
+ it("respects degradation strategy for 'ask'", () => {
+ const permissions = { bash: "ask" as const };
+
+ const permissive = mapPermissionsFromOAC(permissions, "claude", "permissive");
+ expect(permissive.permissions).toEqual({ bash: true });
+
+ const restrictive = mapPermissionsFromOAC(permissions, "claude", "restrictive");
+ expect(restrictive.permissions).toEqual({ bash: false });
+ });
+ });
+
+ // ==========================================================================
+ // mapPermissionsToOAC
+ // ==========================================================================
+ describe("mapPermissionsToOAC()", () => {
+ it("converts binary to allow/deny", () => {
+ const permissions = { bash: true, write: false };
+ const result = mapPermissionsToOAC(permissions, "claude");
+
+ expect(result.permissions).toEqual({ bash: "allow", write: "deny" });
+ expect(result.warnings).toHaveLength(0);
+ });
+
+ it("skips undefined permissions", () => {
+ const permissions = { bash: true, write: undefined };
+ const result = mapPermissionsToOAC(permissions, "claude");
+
+ expect(result.permissions).toEqual({ bash: "allow" });
+ });
+ });
+
+ // ==========================================================================
+ // createGranularRule
+ // ==========================================================================
+ describe("createGranularRule()", () => {
+ it("creates rule from allow patterns", () => {
+ const rule = createGranularRule(["*.ts", "*.tsx"]);
+ expect(rule).toEqual({ "*.ts": "allow", "*.tsx": "allow" });
+ });
+
+ it("creates rule from allow and deny patterns", () => {
+ const rule = createGranularRule(["*"], ["node_modules/**"]);
+ expect(rule).toEqual({ "*": "allow", "node_modules/**": "deny" });
+ });
+
+ it("creates rule with ask patterns", () => {
+ const rule = createGranularRule([], [], ["package.json"]);
+ expect(rule).toEqual({ "package.json": "ask" });
+ });
+
+ it("creates combined rule", () => {
+ const rule = createGranularRule(["src/**"], ["dist/**"], ["*.config.js"]);
+ expect(rule).toEqual({
+ "src/**": "allow",
+ "dist/**": "deny",
+ "*.config.js": "ask",
+ });
+ });
+ });
+
+ // ==========================================================================
+ // extractPatterns
+ // ==========================================================================
+ describe("extractPatterns()", () => {
+ it("extracts patterns from boolean", () => {
+ expect(extractPatterns(true)).toEqual({
+ allow: ["*"],
+ deny: [],
+ ask: [],
+ });
+
+ expect(extractPatterns(false)).toEqual({
+ allow: [],
+ deny: ["*"],
+ ask: [],
+ });
+ });
+
+ it("extracts patterns from literals", () => {
+ expect(extractPatterns("allow")).toEqual({
+ allow: ["*"],
+ deny: [],
+ ask: [],
+ });
+
+ expect(extractPatterns("deny")).toEqual({
+ allow: [],
+ deny: ["*"],
+ ask: [],
+ });
+
+ expect(extractPatterns("ask")).toEqual({
+ allow: [],
+ deny: [],
+ ask: ["*"],
+ });
+ });
+
+ it("extracts patterns from granular rule", () => {
+ const rule = { "*.ts": "allow" as const, "*.js": "deny" as const, "*.config": "ask" as const };
+ const result = extractPatterns(rule);
+
+ expect(result.allow).toContain("*.ts");
+ expect(result.deny).toContain("*.js");
+ expect(result.ask).toContain("*.config");
+ });
+ });
+
+ // ==========================================================================
+ // mergePermissionRules
+ // ==========================================================================
+ describe("mergePermissionRules()", () => {
+ it("merges multiple rules", () => {
+ const rule1 = { "*.ts": "allow" as const };
+ const rule2 = { "*.js": "deny" as const };
+ const merged = mergePermissionRules(rule1, rule2);
+
+ expect(merged).toEqual({ "*.ts": "allow", "*.js": "deny" });
+ });
+
+ it("later rules override earlier ones", () => {
+ const rule1 = { "*": "allow" as const };
+ const rule2 = { "*": "deny" as const };
+ const merged = mergePermissionRules(rule1, rule2);
+
+ expect(merged).toBe("deny"); // Simplified to single value
+ });
+
+ it("merges boolean and literal rules", () => {
+ const merged = mergePermissionRules(true, "deny");
+ expect(merged).toBe("deny");
+ });
+ });
+
+ // ==========================================================================
+ // hasGranularPermissions
+ // ==========================================================================
+ describe("hasGranularPermissions()", () => {
+ it("returns false for simple permissions", () => {
+ const permissions = { bash: "allow" as const, write: true };
+ expect(hasGranularPermissions(permissions)).toBe(false);
+ });
+
+ it("returns true for granular permissions", () => {
+ const permissions = { bash: { "*": "allow" as const } };
+ expect(hasGranularPermissions(permissions)).toBe(true);
+ });
+ });
+
+ // ==========================================================================
+ // hasAskPermissions
+ // ==========================================================================
+ describe("hasAskPermissions()", () => {
+ it("returns false when no ask permissions", () => {
+ const permissions = { bash: "allow" as const, write: "deny" as const };
+ expect(hasAskPermissions(permissions)).toBe(false);
+ });
+
+ it("returns true for top-level ask", () => {
+ const permissions = { bash: "ask" as const };
+ expect(hasAskPermissions(permissions)).toBe(true);
+ });
+
+ it("returns true for nested ask", () => {
+ const permissions = { bash: { "rm *": "ask" as const } };
+ expect(hasAskPermissions(permissions)).toBe(true);
+ });
+ });
+
+ // ==========================================================================
+ // analyzePermissionDegradation
+ // ==========================================================================
+ describe("analyzePermissionDegradation()", () => {
+ it("returns empty warnings for simple permissions", () => {
+ const permissions = { bash: "allow" as const };
+ const warnings = analyzePermissionDegradation(permissions, "claude");
+ expect(warnings).toHaveLength(0);
+ });
+
+ it("warns about granular permission degradation", () => {
+ const permissions = { bash: { "*": "allow" as const } };
+ const warnings = analyzePermissionDegradation(permissions, "claude");
+ expect(warnings.length).toBeGreaterThan(0);
+ expect(warnings[0]).toContain("granular");
+ });
+
+ it("warns about ask permission conversion", () => {
+ const permissions = { bash: "ask" as const };
+ const warnings = analyzePermissionDegradation(permissions, "claude");
+ expect(warnings.length).toBeGreaterThan(0);
+ expect(warnings[0]).toContain("ask");
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tests/unit/mappers/ToolMapper.test.ts b/packages/compatibility-layer/tests/unit/mappers/ToolMapper.test.ts
new file mode 100644
index 0000000..daab6dd
--- /dev/null
+++ b/packages/compatibility-layer/tests/unit/mappers/ToolMapper.test.ts
@@ -0,0 +1,278 @@
+/**
+ * Unit tests for ToolMapper
+ *
+ * Tests tool name mapping between OAC and other platforms.
+ */
+
+import { describe, it, expect } from "vitest";
+import {
+ mapToolToOAC,
+ mapToolFromOAC,
+ mapToolAccessFromOAC,
+ mapToolAccessToOAC,
+ getSupportedTools,
+ getUnsupportedTools,
+ isToolSupported,
+} from "../../../src/mappers/ToolMapper";
+
+describe("ToolMapper", () => {
+ // ==========================================================================
+ // mapToolFromOAC - OAC to Platform
+ // ==========================================================================
+ describe("mapToolFromOAC()", () => {
+ describe("Claude platform", () => {
+ it("maps bash to Bash", () => {
+ const result = mapToolFromOAC("bash", "claude");
+ expect(result.name).toBe("Bash");
+ expect(result.exact).toBe(true);
+ expect(result.warning).toBeUndefined();
+ });
+
+ it("maps read to Read", () => {
+ const result = mapToolFromOAC("read", "claude");
+ expect(result.name).toBe("Read");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps write to Write", () => {
+ const result = mapToolFromOAC("write", "claude");
+ expect(result.name).toBe("Write");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps edit to Edit", () => {
+ const result = mapToolFromOAC("edit", "claude");
+ expect(result.name).toBe("Edit");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps task to Task", () => {
+ const result = mapToolFromOAC("task", "claude");
+ expect(result.name).toBe("Task");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps patch to Edit (Claude uses Edit for patching)", () => {
+ const result = mapToolFromOAC("patch", "claude");
+ expect(result.name).toBe("Edit");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("Cursor platform", () => {
+ it("maps bash to terminal", () => {
+ const result = mapToolFromOAC("bash", "cursor");
+ expect(result.name).toBe("terminal");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps read to file_read", () => {
+ const result = mapToolFromOAC("read", "cursor");
+ expect(result.name).toBe("file_read");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps grep to content_search", () => {
+ const result = mapToolFromOAC("grep", "cursor");
+ expect(result.name).toBe("content_search");
+ expect(result.exact).toBe(true);
+ });
+
+ it("warns about unsupported task tool", () => {
+ const result = mapToolFromOAC("task", "cursor");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("not supported");
+ });
+ });
+
+ describe("Windsurf platform", () => {
+ it("maps bash to shell", () => {
+ const result = mapToolFromOAC("bash", "windsurf");
+ expect(result.name).toBe("shell");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps task to delegate", () => {
+ const result = mapToolFromOAC("task", "windsurf");
+ expect(result.name).toBe("delegate");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps glob to find_files", () => {
+ const result = mapToolFromOAC("glob", "windsurf");
+ expect(result.name).toBe("find_files");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("unknown tools", () => {
+ it("returns tool name as-is with warning for unknown tools", () => {
+ const result = mapToolFromOAC("unknown_tool", "claude");
+ expect(result.name).toBe("unknown_tool");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("No mapping");
+ });
+ });
+ });
+
+ // ==========================================================================
+ // mapToolToOAC - Platform to OAC
+ // ==========================================================================
+ describe("mapToolToOAC()", () => {
+ describe("from Claude", () => {
+ it("maps Bash to bash", () => {
+ const result = mapToolToOAC("Bash", "claude");
+ expect(result.name).toBe("bash");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps Read to read", () => {
+ const result = mapToolToOAC("Read", "claude");
+ expect(result.name).toBe("read");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps WebSearch to bash (approximation)", () => {
+ const result = mapToolToOAC("WebSearch", "claude");
+ expect(result.name).toBe("bash");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("from Cursor", () => {
+ it("maps terminal to bash", () => {
+ const result = mapToolToOAC("terminal", "cursor");
+ expect(result.name).toBe("bash");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps file_read to read", () => {
+ const result = mapToolToOAC("file_read", "cursor");
+ expect(result.name).toBe("read");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps content_search to grep", () => {
+ const result = mapToolToOAC("content_search", "cursor");
+ expect(result.name).toBe("grep");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("from Windsurf", () => {
+ it("maps shell to bash", () => {
+ const result = mapToolToOAC("shell", "windsurf");
+ expect(result.name).toBe("bash");
+ expect(result.exact).toBe(true);
+ });
+
+ it("maps delegate to task", () => {
+ const result = mapToolToOAC("delegate", "windsurf");
+ expect(result.name).toBe("task");
+ expect(result.exact).toBe(true);
+ });
+ });
+
+ describe("unknown tools", () => {
+ it("returns lowercase version with warning", () => {
+ const result = mapToolToOAC("UnknownTool", "claude");
+ expect(result.name).toBe("unknowntool");
+ expect(result.exact).toBe(false);
+ expect(result.warning).toContain("Unknown tool");
+ });
+ });
+ });
+
+ // ==========================================================================
+ // mapToolAccessFromOAC - Batch OAC to Platform
+ // ==========================================================================
+ describe("mapToolAccessFromOAC()", () => {
+ it("maps multiple tools at once", () => {
+ const tools = { bash: true, read: true, write: false };
+ const result = mapToolAccessFromOAC(tools, "claude");
+
+ expect(result.tools["Bash"]).toBe(true);
+ expect(result.tools["Read"]).toBe(true);
+ expect(result.tools["Write"]).toBe(false);
+ expect(result.warnings).toHaveLength(0);
+ });
+
+ it("skips undefined tools", () => {
+ const tools = { bash: true, read: undefined };
+ const result = mapToolAccessFromOAC(tools, "claude");
+
+ expect(result.tools["Bash"]).toBe(true);
+ expect(result.tools["Read"]).toBeUndefined();
+ });
+
+ it("collects warnings for unsupported tools", () => {
+ const tools = { task: true };
+ const result = mapToolAccessFromOAC(tools, "cursor");
+
+ expect(result.warnings.length).toBeGreaterThan(0);
+ expect(result.warnings[0]).toContain("not supported");
+ });
+ });
+
+ // ==========================================================================
+ // mapToolAccessToOAC - Batch Platform to OAC
+ // ==========================================================================
+ describe("mapToolAccessToOAC()", () => {
+ it("maps multiple tools back to OAC format", () => {
+ const tools = { terminal: true, file_read: true, file_write: false };
+ const result = mapToolAccessToOAC(tools, "cursor");
+
+ expect(result.tools.bash).toBe(true);
+ expect(result.tools.read).toBe(true);
+ expect(result.tools.write).toBe(false);
+ });
+
+ it("handles unknown tools with warnings", () => {
+ const tools = { unknown_tool: true };
+ const result = mapToolAccessToOAC(tools, "cursor");
+
+ expect(result.warnings.length).toBeGreaterThan(0);
+ });
+ });
+
+ // ==========================================================================
+ // Utility Functions
+ // ==========================================================================
+ describe("getSupportedTools()", () => {
+ it("returns list of supported tools for Claude", () => {
+ const tools = getSupportedTools("claude");
+ expect(tools).toContain("bash");
+ expect(tools).toContain("read");
+ expect(tools).toContain("task");
+ });
+
+ it("returns list of supported tools for Cursor", () => {
+ const tools = getSupportedTools("cursor");
+ expect(tools).toContain("bash");
+ expect(tools).toContain("read");
+ });
+ });
+
+ describe("getUnsupportedTools()", () => {
+ it("returns empty array for Claude (supports most tools)", () => {
+ const tools = getUnsupportedTools("claude");
+ expect(tools).toHaveLength(0);
+ });
+
+ it("returns task for Cursor (no delegation support)", () => {
+ const tools = getUnsupportedTools("cursor");
+ expect(tools).toContain("task");
+ });
+ });
+
+ describe("isToolSupported()", () => {
+ it("returns true for supported tools", () => {
+ expect(isToolSupported("bash", "claude")).toBe(true);
+ expect(isToolSupported("read", "cursor")).toBe(true);
+ });
+
+ it("returns false for unsupported tools", () => {
+ expect(isToolSupported("task", "cursor")).toBe(false);
+ });
+ });
+});
diff --git a/packages/compatibility-layer/tsconfig.json b/packages/compatibility-layer/tsconfig.json
new file mode 100644
index 0000000..ce9dcca
--- /dev/null
+++ b/packages/compatibility-layer/tsconfig.json
@@ -0,0 +1,54 @@
+{
+ "compilerOptions": {
+ /* Language and Environment */
+ "target": "ES2022",
+ "lib": ["ES2022"],
+
+ /* Modules */
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+
+ /* Emit */
+ "declaration": true,
+ "declarationMap": true,
+ "sourceMap": true,
+ "outDir": "./dist",
+ "removeComments": true,
+ "importHelpers": false,
+
+ /* Interop Constraints */
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "forceConsistentCasingInFileNames": true,
+
+ /* Type Checking */
+ "strict": true,
+ "noImplicitAny": true,
+ "strictNullChecks": true,
+ "strictFunctionTypes": true,
+ "strictBindCallApply": true,
+ "strictPropertyInitialization": true,
+ "noImplicitThis": true,
+ "alwaysStrict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noImplicitReturns": true,
+ "noFallthroughCasesInSwitch": true,
+ "noUncheckedIndexedAccess": true,
+ "noImplicitOverride": true,
+ "noPropertyAccessFromIndexSignature": false,
+
+ /* Completeness */
+ "skipLibCheck": true
+ },
+ "include": [
+ "src/**/*"
+ ],
+ "exclude": [
+ "node_modules",
+ "dist",
+ "**/*.test.ts",
+ "**/*.spec.ts"
+ ]
+}
diff --git a/packages/compatibility-layer/vitest.config.ts b/packages/compatibility-layer/vitest.config.ts
new file mode 100644
index 0000000..ed6a4e6
--- /dev/null
+++ b/packages/compatibility-layer/vitest.config.ts
@@ -0,0 +1,33 @@
+import { defineConfig } from 'vitest/config';
+
+export default defineConfig({
+ test: {
+ globals: true,
+ environment: 'node',
+ include: [
+ 'tests/**/*.test.ts',
+ 'src/cli/__tests__/**/*.test.ts', // CLI integration tests
+ ],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json', 'html', 'lcov'],
+ include: ['src/**/*.ts'],
+ exclude: [
+ 'src/**/*.d.ts',
+ 'src/**/*.test.ts',
+ 'src/**/*.spec.ts',
+ ],
+ thresholds: {
+ lines: 80,
+ functions: 80,
+ branches: 75,
+ statements: 80,
+ },
+ },
+ },
+ resolve: {
+ alias: {
+ '@': '/src',
+ },
+ },
+});