Write

# Autocoder 2.0 Implementation Plan

## Overview

Transform the existing Autocoder into a more powerful system with:
- Removed sandbox constraints for real system access
- Claude Terminal for interactive resource management
- Git Agent for automated version control
- Parallel coding agents
- Context injection from credentials.md and infrastructure

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         Autocoder 2.0                            │
├─────────────────────────────────────────────────────────────────┤
│  UI: React + TypeScript (existing)                              │
│  ├── Project Dashboard                                          │
│  ├── Feature Kanban                                             │
│  ├── Claude Terminal (NEW)                                      │
│  └── Git Status Panel (NEW)                                     │
├─────────────────────────────────────────────────────────────────┤
│  Backend: FastAPI (existing)                                    │
│  ├── /api/terminal/ws/{project}     (NEW)                       │
│  ├── /api/git/{project}/*           (NEW)                       │
│  ├── /api/resources/*               (NEW)                       │
│  └── existing routers...                                        │
├─────────────────────────────────────────────────────────────────┤
│  Agents                                                         │
│  ├── Orchestrator (enhanced)                                    │
│  ├── Initializer Agent (existing)                               │
│  ├── Coding Agent(s) (parallel-enabled)                         │
│  └── Git Agent (NEW)                                            │
├─────────────────────────────────────────────────────────────────┤
│  Services                                                       │
│  ├── CredentialResolver (NEW) - Parse credentials.md            │
│  ├── ResourceManager (NEW) - Global + project resources         │
│  ├── ConnectionVerifier (NEW) - Test SSH/API connections        │
│  └── GitOperations (NEW) - Git primitives                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Phase 1: Foundation - Remove Sandbox & Add Context Injection

### 1.1 Remove Sandbox Constraints

**File:** `autocoder-master/client.py`

```python
# Line 128: Change from
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},

# To
"sandbox": {"enabled": False},
```

**File:** `autocoder-master/security.py`

- Expand `ALLOWED_COMMANDS` to include: `ssh`, `scp`, `rsync`, `git`, `docker`, `curl`, `wget`
- Or remove command allowlist entirely for trusted environments

### 1.2 Credential Resolver Service

**New File:** `server/services/credential_resolver.py`

```python
class CredentialResolver:
    """Parse credentials.md and resolve credential references."""

    def __init__(self, credentials_path: str = "~/claude-projects/shared-data/credentials.md"):
        self.credentials_path = Path(credentials_path).expanduser()
        self._cache: dict[str, dict] = {}
        self._load()

    def _load(self):
        """Parse markdown into structured credentials."""
        # Regex: ### ServiceName for sections
        # Regex: - **Key:** value for fields

    def get(self, service_name: str) -> dict | None:
        """Get credentials for a service."""

    def resolve_ref(self, credential_ref: str) -> str | None:
        """Resolve a credential reference like 'gitea_api_token'."""

    def get_ssh_config(self, host_alias: str) -> dict | None:
        """Get SSH connection details for a host."""
```

### 1.3 Resource Manager

**New File:** `server/services/resource_manager.py`

```python
class ResourceManager:
    """Manage global and project-specific resources."""

    def __init__(self):
        self.global_path = Path("~/.autocoder/global_resources.json").expanduser()
        self.credential_resolver = CredentialResolver()

    def get_merged_resources(self, project_name: str) -> dict:
        """Merge global + project resources."""

    def add_resource(self, resource: Resource, scope: str = "project"):
        """Add a resource (server, service, etc.)."""

    def verify_all(self, project_name: str) -> list[VerificationResult]:
        """Verify all resources are accessible."""
```

**Resource Schema:**
```json
{
  "servers": {
    "build-server": {
      "host": "172.16.3.30",
      "user": "root",
      "auth": {"type": "ssh-key"},
      "capabilities": [],
      "scope": "global"
    }
  },
  "services": {
    "gitea": {
      "url": "https://git.azcomputerguru.com",
      "credential_ref": "gitea_api_token",
      "scope": "global"
    }
  }
}
```

### 1.4 Context Injection into Agent Prompts

**Modify:** `autocoder-master/prompts.py`

Add function to inject context:
```python
def inject_infrastructure_context(prompt: str, project_dir: Path) -> str:
    """Inject credentials and infrastructure into prompt."""
    resolver = CredentialResolver()
    manager = ResourceManager()

    resources = manager.get_merged_resources(project_dir.name)

    context = """
## Available Infrastructure (verified at session start)

### Servers
{servers_table}

### Services
{services_table}

### Credentials
Use `resolve_credential("name")` to get secrets at runtime.
Available: {credential_names}
"""
    return prompt.replace("{INFRASTRUCTURE_CONTEXT}", context)
```

---

## Phase 2: Claude Terminal

### 2.1 Backend - Terminal Router

**New File:** `server/routers/terminal.py`

```python
@router.websocket("/api/terminal/ws/{project_name}")
async def terminal_websocket(websocket: WebSocket, project_name: str):
    """Interactive Claude terminal for resource management."""
    session = await get_or_create_terminal_session(project_name)
    await websocket.accept()

    # Send initial context
    await websocket.send_json({
        "type": "context",
        "resources": session.get_resources(),
        "project": project_name
    })

    while True:
        data = await websocket.receive_json()
        if data["type"] == "message":
            async for chunk in session.send_message(data["content"]):
                await websocket.send_json(chunk)
```

### 2.2 Backend - Terminal Session Service

**New File:** `server/services/terminal_session.py`

```python
class TerminalSession:
    """Manages interactive Claude session for resource management."""

    def __init__(self, project_name: str):
        self.project_name = project_name
        self.resource_manager = ResourceManager()
        self.credential_resolver = CredentialResolver()
        self.client = self._create_client()

    def _create_client(self) -> ClaudeSDKClient:
        """Create Claude client with full system access."""
        return ClaudeSDKClient(
            model="claude-sonnet-4-20250514",
            allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
            system_prompt=self._get_system_prompt(),
            # No sandbox
            security_settings={"sandbox": {"enabled": False}}
        )

    def _get_system_prompt(self) -> str:
        return """You are a resource management assistant for Autocoder.

Your job is to help the user:
1. Add and configure servers, services, and credentials
2. Test connectivity to resources
3. Save resources as project-specific or global

When adding a resource:
- Test connectivity first (SSH, API call, etc.)
- Probe for capabilities relevant to the project
- Ask if it should be project or global scope
- Save to the appropriate resources.json

Available commands via Bash: ssh, curl, git, docker, etc.
"""

    async def send_message(self, content: str) -> AsyncGenerator[dict, None]:
        """Send message and stream response."""
```

### 2.3 Frontend - Terminal Hook

**New File:** `ui/src/hooks/useTerminal.ts`

```typescript
export function useTerminal(projectName: string) {
  const [messages, setMessages] = useState<TerminalMessage[]>([])
  const [resources, setResources] = useState<Resources | null>(null)
  const wsRef = useRef<WebSocket | null>(null)

  const connect = useCallback(() => {
    const ws = new WebSocket(`ws://${host}/api/terminal/ws/${projectName}`)
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      // Handle: context, text, resource_added, verification_result
    }
    wsRef.current = ws
  }, [projectName])

  const sendMessage = (content: string) => {
    wsRef.current?.send(JSON.stringify({ type: "message", content }))
  }

  return { messages, resources, connect, sendMessage, disconnect }
}
```

### 2.4 Frontend - Terminal Component

**New File:** `ui/src/components/TerminalChat.tsx`

- Chat interface similar to SpecCreationChat
- Resource sidebar showing current servers/services
- Connection status indicators
- Scope toggle (project/global) for new resources

---

## Phase 3: Git Agent

### 3.1 Git Operations Service

**New File:** `server/services/git_operations.py`

```python
class GitOperations:
    """Low-level git operations."""

    def __init__(self, project_dir: Path):
        self.project_dir = project_dir

    async def status(self) -> GitStatus:
        """Get detailed git status."""

    async def commit(self, message: str, files: list[str] = None) -> GitResult:
        """Stage and commit changes."""

    async def push(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Push to remote."""

    async def pull(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Pull from remote."""

    async def get_diff(self, staged: bool = False) -> str:
        """Get diff of changes."""
```

### 3.2 Git Agent

**New File:** `server/agents/git_agent.py`

```python
class GitAgent:
    """Dedicated agent for version control operations."""

    def __init__(self, project_dir: Path, orchestrator: Orchestrator):
        self.project_dir = project_dir
        self.orchestrator = orchestrator
        self.git = GitOperations(project_dir)
        self.change_log: list[ChangeEntry] = []

    async def track_change(self, file: str, agent: str, feature_id: int, summary: str):
        """Record a change made by a coding agent."""
        self.change_log.append(ChangeEntry(...))

    async def on_feature_complete(self, feature_id: int):
        """Called when a coding agent completes a feature."""
        changes = [c for c in self.change_log if c.feature_id == feature_id]

        # Group related changes
        commit_message = self._generate_commit_message(changes)

        # Commit and push
        await self.git.commit(commit_message)
        await self.git.push()

        # Clear tracked changes for this feature
        self.change_log = [c for c in self.change_log if c.feature_id != feature_id]

    async def provide_review_context(self, requesting_agent: str, files: list[str]) -> str:
        """Pull latest and provide context for code review."""
        await self.git.pull()

        # Get recent commits affecting these files
        # Return formatted context for the requesting agent

    def _generate_commit_message(self, changes: list[ChangeEntry]) -> str:
        """Generate conventional commit message from changes."""
```

### 3.3 Git Agent Integration with Orchestrator

**Modify:** `server/agents/orchestrator.py` (or agent.py)

```python
class Orchestrator:
    def __init__(self, project_dir: Path):
        self.git_agent = GitAgent(project_dir, self)
        self.coding_agents: list[CodingAgent] = []

    async def notify(self, event: dict):
        """Handle inter-agent notifications."""
        if event["type"] == "file_changed":
            await self.git_agent.track_change(...)
        elif event["type"] == "feature_complete":
            await self.git_agent.on_feature_complete(event["feature_id"])
        elif event["type"] == "review_request":
            context = await self.git_agent.provide_review_context(...)
            # Send context to requesting agent
```

### 3.4 Git Status UI

**New File:** `server/routers/git.py`

```python
@router.get("/api/projects/{project_name}/git/status")
async def get_status(project_name: str) -> GitStatus:

@router.post("/api/projects/{project_name}/git/commit")
async def commit(project_name: str, request: CommitRequest) -> GitResult:

@router.post("/api/projects/{project_name}/git/push")
async def push(project_name: str) -> GitResult:

@router.websocket("/api/projects/{project_name}/git/ws")
async def git_websocket(websocket: WebSocket, project_name: str):
    """Real-time git status updates."""
```

**New File:** `ui/src/components/GitPanel.tsx`

- Branch indicator
- Changed files list (staged/unstaged)
- Commit history (recent)
- Push/pull buttons
- Auto-refresh on file changes

---

## Phase 4: Parallel Coding Agents

### 4.1 Feature Dependency Graph

**New File:** `server/services/feature_graph.py`

```python
class FeatureGraph:
    """Track feature dependencies for parallel execution."""

    def __init__(self, db_path: Path):
        self.db_path = db_path

    def get_parallelizable_features(self, count: int = 3) -> list[Feature]:
        """Get features that can be worked on in parallel."""
        # Features with no dependencies on in-progress features
        # Features that don't modify the same files

    def add_dependency(self, feature_id: int, depends_on: int):
        """Record that feature depends on another."""

    def record_files_touched(self, feature_id: int, files: list[str]):
        """Track which files a feature modifies."""
```

### 4.2 Parallel Agent Runner

**Modify:** `agent.py`

```python
async def run_parallel_agents(
    project_dir: Path,
    max_parallel: int = 3
) -> None:
    """Run multiple coding agents in parallel."""

    orchestrator = Orchestrator(project_dir)
    feature_graph = FeatureGraph(project_dir / "features.db")

    while True:
        # Get parallelizable features
        features = feature_graph.get_parallelizable_features(max_parallel)

        if not features:
            break

        # Create agent tasks
        tasks = [
            run_coding_agent(project_dir, feature, orchestrator)
            for feature in features
        ]

        # Run in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Handle results, trigger git commits via orchestrator
        for feature, result in zip(features, results):
            if isinstance(result, Exception):
                # Handle failure
                pass
            else:
                await orchestrator.notify({
                    "type": "feature_complete",
                    "feature_id": feature.id
                })
```

### 4.3 File Locking

**New File:** `server/services/file_lock.py`

```python
class FileLockManager:
    """Prevent concurrent modifications to same files."""

    def __init__(self):
        self._locks: dict[str, int] = {}  # file_path -> feature_id
        self._lock = asyncio.Lock()

    async def acquire(self, file_path: str, feature_id: int) -> bool:
        """Try to acquire lock on a file."""

    async def release(self, file_path: str, feature_id: int):
        """Release lock on a file."""

    async def get_locked_files(self) -> dict[str, int]:
        """Get currently locked files."""
```

---

## Phase 5: Integration & UI Updates

### 5.1 Update Main App.tsx

Add new navigation items:
- Terminal tab/button
- Git panel in header or sidebar

### 5.2 Update Project Dashboard

- Show resource count
- Show git status badge
- Show parallel agent count

### 5.3 Configuration

**New File:** `~/.autocoder/config.json`

```json
{
  "sandbox_enabled": false,
  "max_parallel_agents": 3,
  "git_agent": {
    "auto_commit": true,
    "auto_push": true,
    "commit_style": "conventional"
  },
  "credentials_path": "~/claude-projects/shared-data/credentials.md",
  "global_resources_path": "~/.autocoder/global_resources.json"
}
```

---

## File Summary

### New Files to Create

| File | Purpose |
|------|---------|
| `server/services/credential_resolver.py` | Parse credentials.md |
| `server/services/resource_manager.py` | Global + project resources |
| `server/services/git_operations.py` | Git primitives |
| `server/services/feature_graph.py` | Dependency tracking |
| `server/services/file_lock.py` | Concurrent access control |
| `server/agents/git_agent.py` | Version control agent |
| `server/routers/terminal.py` | Terminal WebSocket endpoint |
| `server/routers/git.py` | Git REST endpoints |
| `server/services/terminal_session.py` | Terminal Claude session |
| `ui/src/hooks/useTerminal.ts` | Terminal WebSocket hook |
| `ui/src/hooks/useGit.ts` | Git status hook |
| `ui/src/components/TerminalChat.tsx` | Terminal UI |
| `ui/src/components/GitPanel.tsx` | Git status UI |

### Files to Modify

| File | Changes |
|------|---------|
| `client.py` | Disable sandbox |
| `security.py` | Expand allowed commands |
| `agent.py` | Add parallel execution |
| `prompts.py` | Add context injection |
| `server/main.py` | Register new routers |
| `ui/src/App.tsx` | Add Terminal and Git UI |

---

## Implementation Order

1. **Phase 1** (Foundation): Credential resolver, resource manager, disable sandbox
2. **Phase 2** (Terminal): Backend + frontend for Claude Terminal
3. **Phase 3** (Git Agent): Git operations, agent, UI panel
4. **Phase 4** (Parallel): Feature graph, file locking, parallel runner
5. **Phase 5** (Integration): Config, UI polish, testing

---

## Verification

### Testing Phase 1
- [ ] Credentials.md parses correctly
- [ ] Resources save/load from JSON
- [ ] SSH connections work without sandbox
- [ ] Context appears in agent prompts

### Testing Phase 2
- [ ] Terminal WebSocket connects
- [ ] Claude responds to messages
- [ ] Resources can be added via terminal
- [ ] Connections verified and capabilities probed

### Testing Phase 3
- [ ] Git status displays correctly
- [ ] Commits created on feature completion
- [ ] Push works to Gitea
- [ ] Code review context provided

### Testing Phase 4
- [ ] Multiple agents run simultaneously
- [ ] File locks prevent conflicts
- [ ] Features complete in parallel
- [ ] Git commits coordinate correctly

### Testing Phase 5
- [ ] Full workflow: Terminal adds resource → Agents use it → Git commits
- [ ] UI shows all status correctly
- [ ] Config options work


File created successfully at: C:\Users\MikeSwanson\.claude\plans\floofy-pondering-sphinx.md

# Autocoder 2.0 Implementation Plan

## Overview

Transform the existing Autocoder into a more powerful system with:
- Removed sandbox constraints for real system access
- Claude Terminal for interactive resource management
- Git Agent for automated version control
- Parallel coding agents
- Context injection from credentials.md and infrastructure

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         Autocoder 2.0                            │
├─────────────────────────────────────────────────────────────────┤
│  UI: React + TypeScript (existing)                              │
│  ├── Project Dashboard                                          │
│  ├── Feature Kanban                                             │
│  ├── Claude Terminal (NEW)                                      │
│  └── Git Status Panel (NEW)                                     │
├─────────────────────────────────────────────────────────────────┤
│  Backend: FastAPI (existing)                                    │
│  ├── /api/terminal/ws/{project}     (NEW)                       │
│  ├── /api/git/{project}/*           (NEW)                       │
│  ├── /api/resources/*               (NEW)                       │
│  └── existing routers...                                        │
├─────────────────────────────────────────────────────────────────┤
│  Agents                                                         │
│  ├── Orchestrator (enhanced)                                    │
│  ├── Initializer Agent (existing)                               │
│  ├── Coding Agent(s) (parallel-enabled)                         │
│  └── Git Agent (NEW)                                            │
├─────────────────────────────────────────────────────────────────┤
│  Services                                                       │
│  ├── CredentialResolver (NEW) - Parse credentials.md            │
│  ├── ResourceManager (NEW) - Global + project resources         │
│  ├── ConnectionVerifier (NEW) - Test SSH/API connections        │
│  └── GitOperations (NEW) - Git primitives                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Phase 1: Foundation - Remove Sandbox & Add Context Injection

### 1.1 Remove Sandbox Constraints

**File:** `autocoder-master/client.py`

```python
# Line 128: Change from
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},

# To
"sandbox": {"enabled": False},
```

**File:** `autocoder-master/security.py`

- Expand `ALLOWED_COMMANDS` to include: `ssh`, `scp`, `rsync`, `git`, `docker`, `curl`, `wget`
- Or remove command allowlist entirely for trusted environments

### 1.2 Credential Resolver Service

**New File:** `server/services/credential_resolver.py`

```python
class CredentialResolver:
    """Parse credentials.md and resolve credential references."""

    def __init__(self, credentials_path: str = "~/claude-projects/shared-data/credentials.md"):
        self.credentials_path = Path(credentials_path).expanduser()
        self._cache: dict[str, dict] = {}
        self._load()

    def _load(self):
        """Parse markdown into structured credentials."""
        # Regex: ### ServiceName for sections
        # Regex: - **Key:** value for fields

    def get(self, service_name: str) -> dict | None:
        """Get credentials for a service."""

    def resolve_ref(self, credential_ref: str) -> str | None:
        """Resolve a credential reference like 'gitea_api_token'."""

    def get_ssh_config(self, host_alias: str) -> dict | None:
        """Get SSH connection details for a host."""
```

### 1.3 Resource Manager

**New File:** `server/services/resource_manager.py`

```python
class ResourceManager:
    """Manage global and project-specific resources."""

    def __init__(self):
        self.global_path = Path("~/.autocoder/global_resources.json").expanduser()
        self.credential_resolver = CredentialResolver()

    def get_merged_resources(self, project_name: str) -> dict:
        """Merge global + project resources."""

    def add_resource(self, resource: Resource, scope: str = "project"):
        """Add a resource (server, service, etc.)."""

    def verify_all(self, project_name: str) -> list[VerificationResult]:
        """Verify all resources are accessible."""
```

**Resource Schema:**
```json
{
  "servers": {
    "build-server": {
      "host": "172.16.3.30",
      "user": "root",
      "auth": {"type": "ssh-key"},
      "capabilities": [],
      "scope": "global"
    }
  },
  "services": {
    "gitea": {
      "url": "https://git.azcomputerguru.com",
      "credential_ref": "gitea_api_token",
      "scope": "global"
    }
  }
}
```

### 1.4 Context Injection into Agent Prompts

**Modify:** `autocoder-master/prompts.py`

Add function to inject context:
```python
def inject_infrastructure_context(prompt: str, project_dir: Path) -> str:
    """Inject credentials and infrastructure into prompt."""
    resolver = CredentialResolver()
    manager = ResourceManager()

    resources = manager.get_merged_resources(project_dir.name)

    context = """
## Available Infrastructure (verified at session start)

### Servers
{servers_table}

### Services
{services_table}

### Credentials
Use `resolve_credential("name")` to get secrets at runtime.
Available: {credential_names}
"""
    return prompt.replace("{INFRASTRUCTURE_CONTEXT}", context)
```

---

## Phase 2: Claude Terminal

### 2.1 Backend - Terminal Router

**New File:** `server/routers/terminal.py`

```python
@router.websocket("/api/terminal/ws/{project_name}")
async def terminal_websocket(websocket: WebSocket, project_name: str):
    """Interactive Claude terminal for resource management."""
    session = await get_or_create_terminal_session(project_name)
    await websocket.accept()

    # Send initial context
    await websocket.send_json({
        "type": "context",
        "resources": session.get_resources(),
        "project": project_name
    })

    while True:
        data = await websocket.receive_json()
        if data["type"] == "message":
            async for chunk in session.send_message(data["content"]):
                await websocket.send_json(chunk)
```

### 2.2 Backend - Terminal Session Service

**New File:** `server/services/terminal_session.py`

```python
class TerminalSession:
    """Manages interactive Claude session for resource management."""

    def __init__(self, project_name: str):
        self.project_name = project_name
        self.resource_manager = ResourceManager()
        self.credential_resolver = CredentialResolver()
        self.client = self._create_client()

    def _create_client(self) -> ClaudeSDKClient:
        """Create Claude client with full system access."""
        return ClaudeSDKClient(
            model="claude-sonnet-4-20250514",
            allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
            system_prompt=self._get_system_prompt(),
            # No sandbox
            security_settings={"sandbox": {"enabled": False}}
        )

    def _get_system_prompt(self) -> str:
        return """You are a resource management assistant for Autocoder.

Your job is to help the user:
1. Add and configure servers, services, and credentials
2. Test connectivity to resources
3. Save resources as project-specific or global

When adding a resource:
- Test connectivity first (SSH, API call, etc.)
- Probe for capabilities relevant to the project
- Ask if it should be project or global scope
- Save to the appropriate resources.json

Available commands via Bash: ssh, curl, git, docker, etc.
"""

    async def send_message(self, content: str) -> AsyncGenerator[dict, None]:
        """Send message and stream response."""
```

### 2.3 Frontend - Terminal Hook

**New File:** `ui/src/hooks/useTerminal.ts`

```typescript
export function useTerminal(projectName: string) {
  const [messages, setMessages] = useState<TerminalMessage[]>([])
  const [resources, setResources] = useState<Resources | null>(null)
  const wsRef = useRef<WebSocket | null>(null)

  const connect = useCallback(() => {
    const ws = new WebSocket(`ws://${host}/api/terminal/ws/${projectName}`)
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      // Handle: context, text, resource_added, verification_result
    }
    wsRef.current = ws
  }, [projectName])

  const sendMessage = (content: string) => {
    wsRef.current?.send(JSON.stringify({ type: "message", content }))
  }

  return { messages, resources, connect, sendMessage, disconnect }
}
```

### 2.4 Frontend - Terminal Component

**New File:** `ui/src/components/TerminalChat.tsx`

- Chat interface similar to SpecCreationChat
- Resource sidebar showing current servers/services
- Connection status indicators
- Scope toggle (project/global) for new resources

---

## Phase 3: Git Agent

### 3.1 Git Operations Service

**New File:** `server/services/git_operations.py`

```python
class GitOperations:
    """Low-level git operations."""

    def __init__(self, project_dir: Path):
        self.project_dir = project_dir

    async def status(self) -> GitStatus:
        """Get detailed git status."""

    async def commit(self, message: str, files: list[str] = None) -> GitResult:
        """Stage and commit changes."""

    async def push(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Push to remote."""

    async def pull(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Pull from remote."""

    async def get_diff(self, staged: bool = False) -> str:
        """Get diff of changes."""
```

### 3.2 Git Agent

**New File:** `server/agents/git_agent.py`

```python
class GitAgent:
    """Dedicated agent for version control operations."""

    def __init__(self, project_dir: Path, orchestrator: Orchestrator):
        self.project_dir = project_dir
        self.orchestrator = orchestrator
        self.git = GitOperations(project_dir)
        self.change_log: list[ChangeEntry] = []

    async def track_change(self, file: str, agent: str, feature_id: int, summary: str):
        """Record a change made by a coding agent."""
        self.change_log.append(ChangeEntry(...))

    async def on_feature_complete(self, feature_id: int):
        """Called when a coding agent completes a feature."""
        changes = [c for c in self.change_log if c.feature_id == feature_id]

        # Group related changes
        commit_message = self._generate_commit_message(changes)

        # Commit and push
        await self.git.commit(commit_message)
        await self.git.push()

        # Clear tracked changes for this feature
        self.change_log = [c for c in self.change_log if c.feature_id != feature_id]

    async def provide_review_context(self, requesting_agent: str, files: list[str]) -> str:
        """Pull latest and provide context for code review."""
        await self.git.pull()

        # Get recent commits affecting these files
        # Return formatted context for the requesting agent

    def _generate_commit_message(self, changes: list[ChangeEntry]) -> str:
        """Generate conventional commit message from changes."""
```

### 3.3 Git Agent Integration with Orchestrator

**Modify:** `server/agents/orchestrator.py` (or agent.py)

```python
class Orchestrator:
    def __init__(self, project_dir: Path):
        self.git_agent = GitAgent(project_dir, self)
        self.coding_agents: list[CodingAgent] = []

    async def notify(self, event: dict):
        """Handle inter-agent notifications."""
        if event["type"] == "file_changed":
            await self.git_agent.track_change(...)
        elif event["type"] == "feature_complete":
            await self.git_agent.on_feature_complete(event["feature_id"])
        elif event["type"] == "review_request":
            context = await self.git_agent.provide_review_context(...)
            # Send context to requesting agent
```

### 3.4 Git Status UI

**New File:** `server/routers/git.py`

```python
@router.get("/api/projects/{project_name}/git/status")
async def get_status(project_name: str) -> GitStatus:

@router.post("/api/projects/{project_name}/git/commit")
async def commit(project_name: str, request: CommitRequest) -> GitResult:

@router.post("/api/projects/{project_name}/git/push")
async def push(project_name: str) -> GitResult:

@router.websocket("/api/projects/{project_name}/git/ws")
async def git_websocket(websocket: WebSocket, project_name: str):
    """Real-time git status updates."""
```

**New File:** `ui/src/components/GitPanel.tsx`

- Branch indicator
- Changed files list (staged/unstaged)
- Commit history (recent)
- Push/pull buttons
- Auto-refresh on file changes

---

## Phase 4: Parallel Coding Agents

### 4.1 Feature Dependency Graph

**New File:** `server/services/feature_graph.py`

```python
class FeatureGraph:
    """Track feature dependencies for parallel execution."""

    def __init__(self, db_path: Path):
        self.db_path = db_path

    def get_parallelizable_features(self, count: int = 3) -> list[Feature]:
        """Get features that can be worked on in parallel."""
        # Features with no dependencies on in-progress features
        # Features that don't modify the same files

    def add_dependency(self, feature_id: int, depends_on: int):
        """Record that feature depends on another."""

    def record_files_touched(self, feature_id: int, files: list[str]):
        """Track which files a feature modifies."""
```

### 4.2 Parallel Agent Runner

**Modify:** `agent.py`

```python
async def run_parallel_agents(
    project_dir: Path,
    max_parallel: int = 3
) -> None:
    """Run multiple coding agents in parallel."""

    orchestrator = Orchestrator(project_dir)
    feature_graph = FeatureGraph(project_dir / "features.db")

    while True:
        # Get parallelizable features
        features = feature_graph.get_parallelizable_features(max_parallel)

        if not features:
            break

        # Create agent tasks
        tasks = [
            run_coding_agent(project_dir, feature, orchestrator)
            for feature in features
        ]

        # Run in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Handle results, trigger git commits via orchestrator
        for feature, result in zip(features, results):
            if isinstance(result, Exception):
                # Handle failure
                pass
            else:
                await orchestrator.notify({
                    "type": "feature_complete",
                    "feature_id": feature.id
                })
```

### 4.3 File Locking

**New File:** `server/services/file_lock.py`

```python
class FileLockManager:
    """Prevent concurrent modifications to same files."""

    def __init__(self):
        self._locks: dict[str, int] = {}  # file_path -> feature_id
        self._lock = asyncio.Lock()

    async def acquire(self, file_path: str, feature_id: int) -> bool:
        """Try to acquire lock on a file."""

    async def release(self, file_path: str, feature_id: int):
        """Release lock on a file."""

    async def get_locked_files(self) -> dict[str, int]:
        """Get currently locked files."""
```

---

## Phase 5: Integration & UI Updates

### 5.1 Update Main App.tsx

Add new navigation items:
- Terminal tab/button
- Git panel in header or sidebar

### 5.2 Update Project Dashboard

- Show resource count
- Show git status badge
- Show parallel agent count

### 5.3 Configuration

**New File:** `~/.autocoder/config.json`

```json
{
  "sandbox_enabled": false,
  "max_parallel_agents": 3,
  "git_agent": {
    "auto_commit": true,
    "auto_push": true,
    "commit_style": "conventional"
  },
  "credentials_path": "~/claude-projects/shared-data/credentials.md",
  "global_resources_path": "~/.autocoder/global_resources.json"
}
```

---

## File Summary

### New Files to Create

| File | Purpose |
|------|---------|
| `server/services/credential_resolver.py` | Parse credentials.md |
| `server/services/resource_manager.py` | Global + project resources |
| `server/services/git_operations.py` | Git primitives |
| `server/services/feature_graph.py` | Dependency tracking |
| `server/services/file_lock.py` | Concurrent access control |
| `server/agents/git_agent.py` | Version control agent |
| `server/routers/terminal.py` | Terminal WebSocket endpoint |
| `server/routers/git.py` | Git REST endpoints |
| `server/services/terminal_session.py` | Terminal Claude session |
| `ui/src/hooks/useTerminal.ts` | Terminal WebSocket hook |
| `ui/src/hooks/useGit.ts` | Git status hook |
| `ui/src/components/TerminalChat.tsx` | Terminal UI |
| `ui/src/components/GitPanel.tsx` | Git status UI |

### Files to Modify

| File | Changes |
|------|---------|
| `client.py` | Disable sandbox |
| `security.py` | Expand allowed commands |
| `agent.py` | Add parallel execution |
| `prompts.py` | Add context injection |
| `server/main.py` | Register new routers |
| `ui/src/App.tsx` | Add Terminal and Git UI |

---

## Implementation Order

1. **Phase 1** (Foundation): Credential resolver, resource manager, disable sandbox
2. **Phase 2** (Terminal): Backend + frontend for Claude Terminal
3. **Phase 3** (Git Agent): Git operations, agent, UI panel
4. **Phase 4** (Parallel): Feature graph, file locking, parallel runner
5. **Phase 5** (Integration): Config, UI polish, testing

---

## Verification

### Testing Phase 1
- [ ] Credentials.md parses correctly
- [ ] Resources save/load from JSON
- [ ] SSH connections work without sandbox
- [ ] Context appears in agent prompts

### Testing Phase 2
- [ ] Terminal WebSocket connects
- [ ] Claude responds to messages
- [ ] Resources can be added via terminal
- [ ] Connections verified and capabilities probed

### Testing Phase 3
- [ ] Git status displays correctly
- [ ] Commits created on feature completion
- [ ] Push works to Gitea
- [ ] Code review context provided

### Testing Phase 4
- [ ] Multiple agents run simultaneously
- [ ] File locks prevent conflicts
- [ ] Features complete in parallel
- [ ] Git commits coordinate correctly

### Testing Phase 5
- [ ] Full workflow: Terminal adds resource → Agents use it → Git commits
- [ ] UI shows all status correctly
- [ ] Config options work


I've written a comprehensive implementation plan. Let me review it and make sure it aligns with the user's requirements:

1. ✅ Remove sandbox constraints - covered in Phase 1
2. ✅ Claude Terminal for resource management - covered in Phase 2
3. ✅ Resources reference credentials.md - covered in credential resolver
4. ✅ Verify connections when starting work - covered in resource manager
5. ✅ Adaptive capability probing - mentioned in terminal session
6. ✅ Merge project + global resources - covered in resource manager
7. ✅ Git Agent for commits/pushes - covered in Phase 3
8. ✅ Git Agent handles pulls for code review - covered
9. ✅ Parallel coding agents - covered in Phase 4

The plan is comprehensive. I should ask if the user wants me to clarify anything or if they're ready for me to proceed.

Actually, I should just exit plan mode now since I have a complete plan and the user's requirements were clear from our earlier discussion. I don't have additional questions to ask.


I've written a comprehensive implementation plan covering all the features we discussed:

**Key aspects covered:**

1. **Phase 1 - Foundation**
   - Disable sandbox in `client.py`
   - Credential resolver that parses `credentials.md`
   - Resource manager with global + project scope (merging as you specified)
   - Context injection into agent prompts

2. **Phase 2 - Claude Terminal**
   - WebSocket-based interactive chat
   - Full system access (SSH, curl, etc.)
   - Add resources, test connectivity, probe capabilities
   - Save as project or global scope

3. **Phase 3 - Git Agent**
   - Tracks changes from coding agents
   - Auto-commits on feature completion
   - Auto-pushes to Gitea
   - Provides code review context (pulls latest, provides diff)

4. **Phase 4 - Parallel Coding Agents**
   - Feature dependency graph to prevent conflicts
   - File locking for concurrent access
   - `asyncio.gather()` for parallel execution

5. **Phase 5 - Integration**
   - UI updates for Terminal and Git panel
   - Configuration file for settings

The plan creates 13 new files and modifies 6 existing ones, organized into a logical implementation sequence.


ExitPlanMode

# Autocoder 2.0 Implementation Plan

## Overview

Transform the existing Autocoder into a more powerful system with:
- Removed sandbox constraints for real system access
- Claude Terminal for interactive resource management
- Git Agent for automated version control
- Parallel coding agents
- Context injection from credentials.md and infrastructure

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         Autocoder 2.0                            │
├─────────────────────────────────────────────────────────────────┤
│  UI: React + TypeScript (existing)                              │
│  ├── Project Dashboard                                          │
│  ├── Feature Kanban                                             │
│  ├── Claude Terminal (NEW)                                      │
│  └── Git Status Panel (NEW)                                     │
├─────────────────────────────────────────────────────────────────┤
│  Backend: FastAPI (existing)                                    │
│  ├── /api/terminal/ws/{project}     (NEW)                       │
│  ├── /api/git/{project}/*           (NEW)                       │
│  ├── /api/resources/*               (NEW)                       │
│  └── existing routers...                                        │
├─────────────────────────────────────────────────────────────────┤
│  Agents                                                         │
│  ├── Orchestrator (enhanced)                                    │
│  ├── Initializer Agent (existing)                               │
│  ├── Coding Agent(s) (parallel-enabled)                         │
│  └── Git Agent (NEW)                                            │
├─────────────────────────────────────────────────────────────────┤
│  Services                                                       │
│  ├── CredentialResolver (NEW) - Parse credentials.md            │
│  ├── ResourceManager (NEW) - Global + project resources         │
│  ├── ConnectionVerifier (NEW) - Test SSH/API connections        │
│  └── GitOperations (NEW) - Git primitives                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Phase 1: Foundation - Remove Sandbox & Add Context Injection

### 1.1 Remove Sandbox Constraints

**File:** `autocoder-master/client.py`

```python
# Line 128: Change from
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},

# To
"sandbox": {"enabled": False},
```

**File:** `autocoder-master/security.py`

- Expand `ALLOWED_COMMANDS` to include: `ssh`, `scp`, `rsync`, `git`, `docker`, `curl`, `wget`
- Or remove command allowlist entirely for trusted environments

### 1.2 Credential Resolver Service

**New File:** `server/services/credential_resolver.py`

```python
class CredentialResolver:
    """Parse credentials.md and resolve credential references."""

    def __init__(self, credentials_path: str = "~/claude-projects/shared-data/credentials.md"):
        self.credentials_path = Path(credentials_path).expanduser()
        self._cache: dict[str, dict] = {}
        self._load()

    def _load(self):
        """Parse markdown into structured credentials."""
        # Regex: ### ServiceName for sections
        # Regex: - **Key:** value for fields

    def get(self, service_name: str) -> dict | None:
        """Get credentials for a service."""

    def resolve_ref(self, credential_ref: str) -> str | None:
        """Resolve a credential reference like 'gitea_api_token'."""

    def get_ssh_config(self, host_alias: str) -> dict | None:
        """Get SSH connection details for a host."""
```

### 1.3 Resource Manager

**New File:** `server/services/resource_manager.py`

```python
class ResourceManager:
    """Manage global and project-specific resources."""

    def __init__(self):
        self.global_path = Path("~/.autocoder/global_resources.json").expanduser()
        self.credential_resolver = CredentialResolver()

    def get_merged_resources(self, project_name: str) -> dict:
        """Merge global + project resources."""

    def add_resource(self, resource: Resource, scope: str = "project"):
        """Add a resource (server, service, etc.)."""

    def verify_all(self, project_name: str) -> list[VerificationResult]:
        """Verify all resources are accessible."""
```

**Resource Schema:**
```json
{
  "servers": {
    "build-server": {
      "host": "172.16.3.30",
      "user": "root",
      "auth": {"type": "ssh-key"},
      "capabilities": [],
      "scope": "global"
    }
  },
  "services": {
    "gitea": {
      "url": "https://git.azcomputerguru.com",
      "credential_ref": "gitea_api_token",
      "scope": "global"
    }
  }
}
```

### 1.4 Context Injection into Agent Prompts

**Modify:** `autocoder-master/prompts.py`

Add function to inject context:
```python
def inject_infrastructure_context(prompt: str, project_dir: Path) -> str:
    """Inject credentials and infrastructure into prompt."""
    resolver = CredentialResolver()
    manager = ResourceManager()

    resources = manager.get_merged_resources(project_dir.name)

    context = """
## Available Infrastructure (verified at session start)

### Servers
{servers_table}

### Services
{services_table}

### Credentials
Use `resolve_credential("name")` to get secrets at runtime.
Available: {credential_names}
"""
    return prompt.replace("{INFRASTRUCTURE_CONTEXT}", context)
```

---

## Phase 2: Claude Terminal

### 2.1 Backend - Terminal Router

**New File:** `server/routers/terminal.py`

```python
@router.websocket("/api/terminal/ws/{project_name}")
async def terminal_websocket(websocket: WebSocket, project_name: str):
    """Interactive Claude terminal for resource management."""
    session = await get_or_create_terminal_session(project_name)
    await websocket.accept()

    # Send initial context
    await websocket.send_json({
        "type": "context",
        "resources": session.get_resources(),
        "project": project_name
    })

    while True:
        data = await websocket.receive_json()
        if data["type"] == "message":
            async for chunk in session.send_message(data["content"]):
                await websocket.send_json(chunk)
```

### 2.2 Backend - Terminal Session Service

**New File:** `server/services/terminal_session.py`

```python
class TerminalSession:
    """Manages interactive Claude session for resource management."""

    def __init__(self, project_name: str):
        self.project_name = project_name
        self.resource_manager = ResourceManager()
        self.credential_resolver = CredentialResolver()
        self.client = self._create_client()

    def _create_client(self) -> ClaudeSDKClient:
        """Create Claude client with full system access."""
        return ClaudeSDKClient(
            model="claude-sonnet-4-20250514",
            allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
            system_prompt=self._get_system_prompt(),
            # No sandbox
            security_settings={"sandbox": {"enabled": False}}
        )

    def _get_system_prompt(self) -> str:
        return """You are a resource management assistant for Autocoder.

Your job is to help the user:
1. Add and configure servers, services, and credentials
2. Test connectivity to resources
3. Save resources as project-specific or global

When adding a resource:
- Test connectivity first (SSH, API call, etc.)
- Probe for capabilities relevant to the project
- Ask if it should be project or global scope
- Save to the appropriate resources.json

Available commands via Bash: ssh, curl, git, docker, etc.
"""

    async def send_message(self, content: str) -> AsyncGenerator[dict, None]:
        """Send message and stream response."""
```

### 2.3 Frontend - Terminal Hook

**New File:** `ui/src/hooks/useTerminal.ts`

```typescript
export function useTerminal(projectName: string) {
  const [messages, setMessages] = useState<TerminalMessage[]>([])
  const [resources, setResources] = useState<Resources | null>(null)
  const wsRef = useRef<WebSocket | null>(null)

  const connect = useCallback(() => {
    const ws = new WebSocket(`ws://${host}/api/terminal/ws/${projectName}`)
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      // Handle: context, text, resource_added, verification_result
    }
    wsRef.current = ws
  }, [projectName])

  const sendMessage = (content: string) => {
    wsRef.current?.send(JSON.stringify({ type: "message", content }))
  }

  return { messages, resources, connect, sendMessage, disconnect }
}
```

### 2.4 Frontend - Terminal Component

**New File:** `ui/src/components/TerminalChat.tsx`

- Chat interface similar to SpecCreationChat
- Resource sidebar showing current servers/services
- Connection status indicators
- Scope toggle (project/global) for new resources

---

## Phase 3: Git Agent

### 3.1 Git Operations Service

**New File:** `server/services/git_operations.py`

```python
class GitOperations:
    """Low-level git operations."""

    def __init__(self, project_dir: Path):
        self.project_dir = project_dir

    async def status(self) -> GitStatus:
        """Get detailed git status."""

    async def commit(self, message: str, files: list[str] = None) -> GitResult:
        """Stage and commit changes."""

    async def push(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Push to remote."""

    async def pull(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Pull from remote."""

    async def get_diff(self, staged: bool = False) -> str:
        """Get diff of changes."""
```

### 3.2 Git Agent

**New File:** `server/agents/git_agent.py`

```python
class GitAgent:
    """Dedicated agent for version control operations."""

    def __init__(self, project_dir: Path, orchestrator: Orchestrator):
        self.project_dir = project_dir
        self.orchestrator = orchestrator
        self.git = GitOperations(project_dir)
        self.change_log: list[ChangeEntry] = []

    async def track_change(self, file: str, agent: str, feature_id: int, summary: str):
        """Record a change made by a coding agent."""
        self.change_log.append(ChangeEntry(...))

    async def on_feature_complete(self, feature_id: int):
        """Called when a coding agent completes a feature."""
        changes = [c for c in self.change_log if c.feature_id == feature_id]

        # Group related changes
        commit_message = self._generate_commit_message(changes)

        # Commit and push
        await self.git.commit(commit_message)
        await self.git.push()

        # Clear tracked changes for this feature
        self.change_log = [c for c in self.change_log if c.feature_id != feature_id]

    async def provide_review_context(self, requesting_agent: str, files: list[str]) -> str:
        """Pull latest and provide context for code review."""
        await self.git.pull()

        # Get recent commits affecting these files
        # Return formatted context for the requesting agent

    def _generate_commit_message(self, changes: list[ChangeEntry]) -> str:
        """Generate conventional commit message from changes."""
```

### 3.3 Git Agent Integration with Orchestrator

**Modify:** `server/agents/orchestrator.py` (or agent.py)

```python
class Orchestrator:
    def __init__(self, project_dir: Path):
        self.git_agent = GitAgent(project_dir, self)
        self.coding_agents: list[CodingAgent] = []

    async def notify(self, event: dict):
        """Handle inter-agent notifications."""
        if event["type"] == "file_changed":
            await self.git_agent.track_change(...)
        elif event["type"] == "feature_complete":
            await self.git_agent.on_feature_complete(event["feature_id"])
        elif event["type"] == "review_request":
            context = await self.git_agent.provide_review_context(...)
            # Send context to requesting agent
```

### 3.4 Git Status UI

**New File:** `server/routers/git.py`

```python
@router.get("/api/projects/{project_name}/git/status")
async def get_status(project_name: str) -> GitStatus:

@router.post("/api/projects/{project_name}/git/commit")
async def commit(project_name: str, request: CommitRequest) -> GitResult:

@router.post("/api/projects/{project_name}/git/push")
async def push(project_name: str) -> GitResult:

@router.websocket("/api/projects/{project_name}/git/ws")
async def git_websocket(websocket: WebSocket, project_name: str):
    """Real-time git status updates."""
```

**New File:** `ui/src/components/GitPanel.tsx`

- Branch indicator
- Changed files list (staged/unstaged)
- Commit history (recent)
- Push/pull buttons
- Auto-refresh on file changes

---

## Phase 4: Parallel Coding Agents

### 4.1 Feature Dependency Graph

**New File:** `server/services/feature_graph.py`

```python
class FeatureGraph:
    """Track feature dependencies for parallel execution."""

    def __init__(self, db_path: Path):
        self.db_path = db_path

    def get_parallelizable_features(self, count: int = 3) -> list[Feature]:
        """Get features that can be worked on in parallel."""
        # Features with no dependencies on in-progress features
        # Features that don't modify the same files

    def add_dependency(self, feature_id: int, depends_on: int):
        """Record that feature depends on another."""

    def record_files_touched(self, feature_id: int, files: list[str]):
        """Track which files a feature modifies."""
```

### 4.2 Parallel Agent Runner

**Modify:** `agent.py`

```python
async def run_parallel_agents(
    project_dir: Path,
    max_parallel: int = 3
) -> None:
    """Run multiple coding agents in parallel."""

    orchestrator = Orchestrator(project_dir)
    feature_graph = FeatureGraph(project_dir / "features.db")

    while True:
        # Get parallelizable features
        features = feature_graph.get_parallelizable_features(max_parallel)

        if not features:
            break

        # Create agent tasks
        tasks = [
            run_coding_agent(project_dir, feature, orchestrator)
            for feature in features
        ]

        # Run in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Handle results, trigger git commits via orchestrator
        for feature, result in zip(features, results):
            if isinstance(result, Exception):
                # Handle failure
                pass
            else:
                await orchestrator.notify({
                    "type": "feature_complete",
                    "feature_id": feature.id
                })
```

### 4.3 File Locking

**New File:** `server/services/file_lock.py`

```python
class FileLockManager:
    """Prevent concurrent modifications to same files."""

    def __init__(self):
        self._locks: dict[str, int] = {}  # file_path -> feature_id
        self._lock = asyncio.Lock()

    async def acquire(self, file_path: str, feature_id: int) -> bool:
        """Try to acquire lock on a file."""

    async def release(self, file_path: str, feature_id: int):
        """Release lock on a file."""

    async def get_locked_files(self) -> dict[str, int]:
        """Get currently locked files."""
```

---

## Phase 5: Integration & UI Updates

### 5.1 Update Main App.tsx

Add new navigation items:
- Terminal tab/button
- Git panel in header or sidebar

### 5.2 Update Project Dashboard

- Show resource count
- Show git status badge
- Show parallel agent count

### 5.3 Configuration

**New File:** `~/.autocoder/config.json`

```json
{
  "sandbox_enabled": false,
  "max_parallel_agents": 3,
  "git_agent": {
    "auto_commit": true,
    "auto_push": true,
    "commit_style": "conventional"
  },
  "credentials_path": "~/claude-projects/shared-data/credentials.md",
  "global_resources_path": "~/.autocoder/global_resources.json"
}
```

---

## File Summary

### New Files to Create

| File | Purpose |
|------|---------|
| `server/services/credential_resolver.py` | Parse credentials.md |
| `server/services/resource_manager.py` | Global + project resources |
| `server/services/git_operations.py` | Git primitives |
| `server/services/feature_graph.py` | Dependency tracking |
| `server/services/file_lock.py` | Concurrent access control |
| `server/agents/git_agent.py` | Version control agent |
| `server/routers/terminal.py` | Terminal WebSocket endpoint |
| `server/routers/git.py` | Git REST endpoints |
| `server/services/terminal_session.py` | Terminal Claude session |
| `ui/src/hooks/useTerminal.ts` | Terminal WebSocket hook |
| `ui/src/hooks/useGit.ts` | Git status hook |
| `ui/src/components/TerminalChat.tsx` | Terminal UI |
| `ui/src/components/GitPanel.tsx` | Git status UI |

### Files to Modify

| File | Changes |
|------|---------|
| `client.py` | Disable sandbox |
| `security.py` | Expand allowed commands |
| `agent.py` | Add parallel execution |
| `prompts.py` | Add context injection |
| `server/main.py` | Register new routers |
| `ui/src/App.tsx` | Add Terminal and Git UI |

---

## Implementation Order

1. **Phase 1** (Foundation): Credential resolver, resource manager, disable sandbox
2. **Phase 2** (Terminal): Backend + frontend for Claude Terminal
3. **Phase 3** (Git Agent): Git operations, agent, UI panel
4. **Phase 4** (Parallel): Feature graph, file locking, parallel runner
5. **Phase 5** (Integration): Config, UI polish, testing

---

## Verification

### Testing Phase 1
- [ ] Credentials.md parses correctly
- [ ] Resources save/load from JSON
- [ ] SSH connections work without sandbox
- [ ] Context appears in agent prompts

### Testing Phase 2
- [ ] Terminal WebSocket connects
- [ ] Claude responds to messages
- [ ] Resources can be added via terminal
- [ ] Connections verified and capabilities probed

### Testing Phase 3
- [ ] Git status displays correctly
- [ ] Commits created on feature completion
- [ ] Push works to Gitea
- [ ] Code review context provided

### Testing Phase 4
- [ ] Multiple agents run simultaneously
- [ ] File locks prevent conflicts
- [ ] Features complete in parallel
- [ ] Git commits coordinate correctly

### Testing Phase 5
- [ ] Full workflow: Terminal adds resource → Agents use it → Git commits
- [ ] UI shows all status correctly
- [ ] Config options work


User has approved your plan. You can now start coding. Start with updating your todo list if applicable

Your plan has been saved to: C:\Users\MikeSwanson\.claude\plans\floofy-pondering-sphinx.md
You can refer back to it if needed during implementation.

## Approved Plan:
# Autocoder 2.0 Implementation Plan

## Overview

Transform the existing Autocoder into a more powerful system with:
- Removed sandbox constraints for real system access
- Claude Terminal for interactive resource management
- Git Agent for automated version control
- Parallel coding agents
- Context injection from credentials.md and infrastructure

## Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                         Autocoder 2.0                            │
├─────────────────────────────────────────────────────────────────┤
│  UI: React + TypeScript (existing)                              │
│  ├── Project Dashboard                                          │
│  ├── Feature Kanban                                             │
│  ├── Claude Terminal (NEW)                                      │
│  └── Git Status Panel (NEW)                                     │
├─────────────────────────────────────────────────────────────────┤
│  Backend: FastAPI (existing)                                    │
│  ├── /api/terminal/ws/{project}     (NEW)                       │
│  ├── /api/git/{project}/*           (NEW)                       │
│  ├── /api/resources/*               (NEW)                       │
│  └── existing routers...                                        │
├─────────────────────────────────────────────────────────────────┤
│  Agents                                                         │
│  ├── Orchestrator (enhanced)                                    │
│  ├── Initializer Agent (existing)                               │
│  ├── Coding Agent(s) (parallel-enabled)                         │
│  └── Git Agent (NEW)                                            │
├─────────────────────────────────────────────────────────────────┤
│  Services                                                       │
│  ├── CredentialResolver (NEW) - Parse credentials.md            │
│  ├── ResourceManager (NEW) - Global + project resources         │
│  ├── ConnectionVerifier (NEW) - Test SSH/API connections        │
│  └── GitOperations (NEW) - Git primitives                       │
└─────────────────────────────────────────────────────────────────┘
```

---

## Phase 1: Foundation - Remove Sandbox & Add Context Injection

### 1.1 Remove Sandbox Constraints

**File:** `autocoder-master/client.py`

```python
# Line 128: Change from
"sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},

# To
"sandbox": {"enabled": False},
```

**File:** `autocoder-master/security.py`

- Expand `ALLOWED_COMMANDS` to include: `ssh`, `scp`, `rsync`, `git`, `docker`, `curl`, `wget`
- Or remove command allowlist entirely for trusted environments

### 1.2 Credential Resolver Service

**New File:** `server/services/credential_resolver.py`

```python
class CredentialResolver:
    """Parse credentials.md and resolve credential references."""

    def __init__(self, credentials_path: str = "~/claude-projects/shared-data/credentials.md"):
        self.credentials_path = Path(credentials_path).expanduser()
        self._cache: dict[str, dict] = {}
        self._load()

    def _load(self):
        """Parse markdown into structured credentials."""
        # Regex: ### ServiceName for sections
        # Regex: - **Key:** value for fields

    def get(self, service_name: str) -> dict | None:
        """Get credentials for a service."""

    def resolve_ref(self, credential_ref: str) -> str | None:
        """Resolve a credential reference like 'gitea_api_token'."""

    def get_ssh_config(self, host_alias: str) -> dict | None:
        """Get SSH connection details for a host."""
```

### 1.3 Resource Manager

**New File:** `server/services/resource_manager.py`

```python
class ResourceManager:
    """Manage global and project-specific resources."""

    def __init__(self):
        self.global_path = Path("~/.autocoder/global_resources.json").expanduser()
        self.credential_resolver = CredentialResolver()

    def get_merged_resources(self, project_name: str) -> dict:
        """Merge global + project resources."""

    def add_resource(self, resource: Resource, scope: str = "project"):
        """Add a resource (server, service, etc.)."""

    def verify_all(self, project_name: str) -> list[VerificationResult]:
        """Verify all resources are accessible."""
```

**Resource Schema:**
```json
{
  "servers": {
    "build-server": {
      "host": "172.16.3.30",
      "user": "root",
      "auth": {"type": "ssh-key"},
      "capabilities": [],
      "scope": "global"
    }
  },
  "services": {
    "gitea": {
      "url": "https://git.azcomputerguru.com",
      "credential_ref": "gitea_api_token",
      "scope": "global"
    }
  }
}
```

### 1.4 Context Injection into Agent Prompts

**Modify:** `autocoder-master/prompts.py`

Add function to inject context:
```python
def inject_infrastructure_context(prompt: str, project_dir: Path) -> str:
    """Inject credentials and infrastructure into prompt."""
    resolver = CredentialResolver()
    manager = ResourceManager()

    resources = manager.get_merged_resources(project_dir.name)

    context = """
## Available Infrastructure (verified at session start)

### Servers
{servers_table}

### Services
{services_table}

### Credentials
Use `resolve_credential("name")` to get secrets at runtime.
Available: {credential_names}
"""
    return prompt.replace("{INFRASTRUCTURE_CONTEXT}", context)
```

---

## Phase 2: Claude Terminal

### 2.1 Backend - Terminal Router

**New File:** `server/routers/terminal.py`

```python
@router.websocket("/api/terminal/ws/{project_name}")
async def terminal_websocket(websocket: WebSocket, project_name: str):
    """Interactive Claude terminal for resource management."""
    session = await get_or_create_terminal_session(project_name)
    await websocket.accept()

    # Send initial context
    await websocket.send_json({
        "type": "context",
        "resources": session.get_resources(),
        "project": project_name
    })

    while True:
        data = await websocket.receive_json()
        if data["type"] == "message":
            async for chunk in session.send_message(data["content"]):
                await websocket.send_json(chunk)
```

### 2.2 Backend - Terminal Session Service

**New File:** `server/services/terminal_session.py`

```python
class TerminalSession:
    """Manages interactive Claude session for resource management."""

    def __init__(self, project_name: str):
        self.project_name = project_name
        self.resource_manager = ResourceManager()
        self.credential_resolver = CredentialResolver()
        self.client = self._create_client()

    def _create_client(self) -> ClaudeSDKClient:
        """Create Claude client with full system access."""
        return ClaudeSDKClient(
            model="claude-sonnet-4-20250514",
            allowed_tools=["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
            system_prompt=self._get_system_prompt(),
            # No sandbox
            security_settings={"sandbox": {"enabled": False}}
        )

    def _get_system_prompt(self) -> str:
        return """You are a resource management assistant for Autocoder.

Your job is to help the user:
1. Add and configure servers, services, and credentials
2. Test connectivity to resources
3. Save resources as project-specific or global

When adding a resource:
- Test connectivity first (SSH, API call, etc.)
- Probe for capabilities relevant to the project
- Ask if it should be project or global scope
- Save to the appropriate resources.json

Available commands via Bash: ssh, curl, git, docker, etc.
"""

    async def send_message(self, content: str) -> AsyncGenerator[dict, None]:
        """Send message and stream response."""
```

### 2.3 Frontend - Terminal Hook

**New File:** `ui/src/hooks/useTerminal.ts`

```typescript
export function useTerminal(projectName: string) {
  const [messages, setMessages] = useState<TerminalMessage[]>([])
  const [resources, setResources] = useState<Resources | null>(null)
  const wsRef = useRef<WebSocket | null>(null)

  const connect = useCallback(() => {
    const ws = new WebSocket(`ws://${host}/api/terminal/ws/${projectName}`)
    ws.onmessage = (event) => {
      const data = JSON.parse(event.data)
      // Handle: context, text, resource_added, verification_result
    }
    wsRef.current = ws
  }, [projectName])

  const sendMessage = (content: string) => {
    wsRef.current?.send(JSON.stringify({ type: "message", content }))
  }

  return { messages, resources, connect, sendMessage, disconnect }
}
```

### 2.4 Frontend - Terminal Component

**New File:** `ui/src/components/TerminalChat.tsx`

- Chat interface similar to SpecCreationChat
- Resource sidebar showing current servers/services
- Connection status indicators
- Scope toggle (project/global) for new resources

---

## Phase 3: Git Agent

### 3.1 Git Operations Service

**New File:** `server/services/git_operations.py`

```python
class GitOperations:
    """Low-level git operations."""

    def __init__(self, project_dir: Path):
        self.project_dir = project_dir

    async def status(self) -> GitStatus:
        """Get detailed git status."""

    async def commit(self, message: str, files: list[str] = None) -> GitResult:
        """Stage and commit changes."""

    async def push(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Push to remote."""

    async def pull(self, remote: str = "origin", branch: str = None) -> GitResult:
        """Pull from remote."""

    async def get_diff(self, staged: bool = False) -> str:
        """Get diff of changes."""
```

### 3.2 Git Agent

**New File:** `server/agents/git_agent.py`

```python
class GitAgent:
    """Dedicated agent for version control operations."""

    def __init__(self, project_dir: Path, orchestrator: Orchestrator):
        self.project_dir = project_dir
        self.orchestrator = orchestrator
        self.git = GitOperations(project_dir)
        self.change_log: list[ChangeEntry] = []

    async def track_change(self, file: str, agent: str, feature_id: int, summary: str):
        """Record a change made by a coding agent."""
        self.change_log.append(ChangeEntry(...))

    async def on_feature_complete(self, feature_id: int):
        """Called when a coding agent completes a feature."""
        changes = [c for c in self.change_log if c.feature_id == feature_id]

        # Group related changes
        commit_message = self._generate_commit_message(changes)

        # Commit and push
        await self.git.commit(commit_message)
        await self.git.push()

        # Clear tracked changes for this feature
        self.change_log = [c for c in self.change_log if c.feature_id != feature_id]

    async def provide_review_context(self, requesting_agent: str, files: list[str]) -> str:
        """Pull latest and provide context for code review."""
        await self.git.pull()

        # Get recent commits affecting these files
        # Return formatted context for the requesting agent

    def _generate_commit_message(self, changes: list[ChangeEntry]) -> str:
        """Generate conventional commit message from changes."""
```

### 3.3 Git Agent Integration with Orchestrator

**Modify:** `server/agents/orchestrator.py` (or agent.py)

```python
class Orchestrator:
    def __init__(self, project_dir: Path):
        self.git_agent = GitAgent(project_dir, self)
        self.coding_agents: list[CodingAgent] = []

    async def notify(self, event: dict):
        """Handle inter-agent notifications."""
        if event["type"] == "file_changed":
            await self.git_agent.track_change(...)
        elif event["type"] == "feature_complete":
            await self.git_agent.on_feature_complete(event["feature_id"])
        elif event["type"] == "review_request":
            context = await self.git_agent.provide_review_context(...)
            # Send context to requesting agent
```

### 3.4 Git Status UI

**New File:** `server/routers/git.py`

```python
@router.get("/api/projects/{project_name}/git/status")
async def get_status(project_name: str) -> GitStatus:

@router.post("/api/projects/{project_name}/git/commit")
async def commit(project_name: str, request: CommitRequest) -> GitResult:

@router.post("/api/projects/{project_name}/git/push")
async def push(project_name: str) -> GitResult:

@router.websocket("/api/projects/{project_name}/git/ws")
async def git_websocket(websocket: WebSocket, project_name: str):
    """Real-time git status updates."""
```

**New File:** `ui/src/components/GitPanel.tsx`

- Branch indicator
- Changed files list (staged/unstaged)
- Commit history (recent)
- Push/pull buttons
- Auto-refresh on file changes

---

## Phase 4: Parallel Coding Agents

### 4.1 Feature Dependency Graph

**New File:** `server/services/feature_graph.py`

```python
class FeatureGraph:
    """Track feature dependencies for parallel execution."""

    def __init__(self, db_path: Path):
        self.db_path = db_path

    def get_parallelizable_features(self, count: int = 3) -> list[Feature]:
        """Get features that can be worked on in parallel."""
        # Features with no dependencies on in-progress features
        # Features that don't modify the same files

    def add_dependency(self, feature_id: int, depends_on: int):
        """Record that feature depends on another."""

    def record_files_touched(self, feature_id: int, files: list[str]):
        """Track which files a feature modifies."""
```

### 4.2 Parallel Agent Runner

**Modify:** `agent.py`

```python
async def run_parallel_agents(
    project_dir: Path,
    max_parallel: int = 3
) -> None:
    """Run multiple coding agents in parallel."""

    orchestrator = Orchestrator(project_dir)
    feature_graph = FeatureGraph(project_dir / "features.db")

    while True:
        # Get parallelizable features
        features = feature_graph.get_parallelizable_features(max_parallel)

        if not features:
            break

        # Create agent tasks
        tasks = [
            run_coding_agent(project_dir, feature, orchestrator)
            for feature in features
        ]

        # Run in parallel
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Handle results, trigger git commits via orchestrator
        for feature, result in zip(features, results):
            if isinstance(result, Exception):
                # Handle failure
                pass
            else:
                await orchestrator.notify({
                    "type": "feature_complete",
                    "feature_id": feature.id
                })
```

### 4.3 File Locking

**New File:** `server/services/file_lock.py`

```python
class FileLockManager:
    """Prevent concurrent modifications to same files."""

    def __init__(self):
        self._locks: dict[str, int] = {}  # file_path -> feature_id
        self._lock = asyncio.Lock()

    async def acquire(self, file_path: str, feature_id: int) -> bool:
        """Try to acquire lock on a file."""

    async def release(self, file_path: str, feature_id: int):
        """Release lock on a file."""

    async def get_locked_files(self) -> dict[str, int]:
        """Get currently locked files."""
```

---

## Phase 5: Integration & UI Updates

### 5.1 Update Main App.tsx

Add new navigation items:
- Terminal tab/button
- Git panel in header or sidebar

### 5.2 Update Project Dashboard

- Show resource count
- Show git status badge
- Show parallel agent count

### 5.3 Configuration

**New File:** `~/.autocoder/config.json`

```json
{
  "sandbox_enabled": false,
  "max_parallel_agents": 3,
  "git_agent": {
    "auto_commit": true,
    "auto_push": true,
    "commit_style": "conventional"
  },
  "credentials_path": "~/claude-projects/shared-data/credentials.md",
  "global_resources_path": "~/.autocoder/global_resources.json"
}
```

---

## File Summary

### New Files to Create

| File | Purpose |
|------|---------|
| `server/services/credential_resolver.py` | Parse credentials.md |
| `server/services/resource_manager.py` | Global + project resources |
| `server/services/git_operations.py` | Git primitives |
| `server/services/feature_graph.py` | Dependency tracking |
| `server/services/file_lock.py` | Concurrent access control |
| `server/agents/git_agent.py` | Version control agent |
| `server/routers/terminal.py` | Terminal WebSocket endpoint |
| `server/routers/git.py` | Git REST endpoints |
| `server/services/terminal_session.py` | Terminal Claude session |
| `ui/src/hooks/useTerminal.ts` | Terminal WebSocket hook |
| `ui/src/hooks/useGit.ts` | Git status hook |
| `ui/src/components/TerminalChat.tsx` | Terminal UI |
| `ui/src/components/GitPanel.tsx` | Git status UI |

### Files to Modify

| File | Changes |
|------|---------|
| `client.py` | Disable sandbox |
| `security.py` | Expand allowed commands |
| `agent.py` | Add parallel execution |
| `prompts.py` | Add context injection |
| `server/main.py` | Register new routers |
| `ui/src/App.tsx` | Add Terminal and Git UI |

---

## Implementation Order

1. **Phase 1** (Foundation): Credential resolver, resource manager, disable sandbox
2. **Phase 2** (Terminal): Backend + frontend for Claude Terminal
3. **Phase 3** (Git Agent): Git operations, agent, UI panel
4. **Phase 4** (Parallel): Feature graph, file locking, parallel runner
5. **Phase 5** (Integration): Config, UI polish, testing

---

## Verification

### Testing Phase 1
- [ ] Credentials.md parses correctly
- [ ] Resources save/load from JSON
- [ ] SSH connections work without sandbox
- [ ] Context appears in agent prompts

### Testing Phase 2
- [ ] Terminal WebSocket connects
- [ ] Claude responds to messages
- [ ] Resources can be added via terminal
- [ ] Connections verified and capabilities probed

### Testing Phase 3
- [ ] Git status displays correctly
- [ ] Commits created on feature completion
- [ ] Push works to Gitea
- [ ] Code review context provided

### Testing Phase 4
- [ ] Multiple agents run simultaneously
- [ ] File locks prevent conflicts
- [ ] Features complete in parallel
- [ ] Git commits coordinate correctly

### Testing Phase 5
- [ ] Full workflow: Terminal adds resource → Agents use it → Git commits
- [ ] UI shows all status correctly
- [ ] Config options work


The plan has been approved. Now I need to start implementing. Let me create a todo list to track progress through the phases.

I'll start with Phase 1 - the foundation work:
1. Disable sandbox in client.py
2. Expand allowed commands in security.py
3. Create credential_resolver.py
4. Create resource_manager.py
5. Update prompts.py for context injection

Let me set up the todo list and begin implementation.


Plan approved. Let me set up the todo list and begin implementation with Phase 1.


TodoWrite

Phase 1.1: Disable sandbox in client.py

Phase 1.2: Expand allowed commands in security.py

Expanding allowed commands in security.py

Phase 1.3: Create credential_resolver.py service

Phase 1.4: Create resource_manager.py service

Phase 1.5: Add context injection to prompts.py

Phase 2: Claude Terminal (backend + frontend)

Phase 3: Git Agent (operations + agent + UI)

Phase 4: Parallel coding agents

Phase 5: Integration and UI updates


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Phase 1.1: Disable sandbox in client.py

Phase 1.2: Expand allowed commands in security.py

Expanding allowed commands in security.py

Phase 1.3: Create credential_resolver.py service

Phase 1.4: Create resource_manager.py service

Phase 1.5: Add context injection to prompts.py

Phase 2: Claude Terminal (backend + frontend)

Phase 3: Git Agent (operations + agent + UI)

Phase 4: Parallel coding agents

Phase 5: Integration and UI updates


     1→"""
     2→Claude SDK Client Configuration
     3→===============================
     4→
     5→Functions for creating and configuring the Claude Agent SDK client.
     6→"""
     7→
     8→import json
     9→import os
    10→import shutil
    11→import sys
    12→from pathlib import Path
    13→
    14→from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
    15→from claude_agent_sdk.types import HookMatcher
    16→
    17→from security import bash_security_hook
    18→
    19→# Feature MCP tools for feature/test management
    20→FEATURE_MCP_TOOLS = [
    21→    "mcp__features__feature_get_stats",
    22→    "mcp__features__feature_get_next",
    23→    "mcp__features__feature_get_for_regression",
    24→    "mcp__features__feature_mark_in_progress",
    25→    "mcp__features__feature_mark_passing",
    26→    "mcp__features__feature_skip",
    27→    "mcp__features__feature_create_bulk",
    28→]
    29→
    30→# Playwright MCP tools for browser automation
    31→PLAYWRIGHT_TOOLS = [
    32→    # Core navigation & screenshots
    33→    "mcp__playwright__browser_navigate",
    34→    "mcp__playwright__browser_navigate_back",
    35→    "mcp__playwright__browser_take_screenshot",
    36→    "mcp__playwright__browser_snapshot",
    37→
    38→    # Element interaction
    39→    "mcp__playwright__browser_click",
    40→    "mcp__playwright__browser_type",
    41→    "mcp__playwright__browser_fill_form",
    42→    "mcp__playwright__browser_select_option",
    43→    "mcp__playwright__browser_hover",
    44→    "mcp__playwright__browser_drag",
    45→    "mcp__playwright__browser_press_key",
    46→
    47→    # JavaScript & debugging
    48→    "mcp__playwright__browser_evaluate",
    49→    # "mcp__playwright__browser_run_code",  # REMOVED - causes Playwright MCP server crash
    50→    "mcp__playwright__browser_console_messages",
    51→    "mcp__playwright__browser_network_requests",
    52→
    53→    # Browser management
    54→    "mcp__playwright__browser_close",
    55→    "mcp__playwright__browser_resize",
    56→    "mcp__playwright__browser_tabs",
    57→    "mcp__playwright__browser_wait_for",
    58→    "mcp__playwright__browser_handle_dialog",
    59→    "mcp__playwright__browser_file_upload",
    60→    "mcp__playwright__browser_install",
    61→]
    62→
    63→# Built-in tools
    64→BUILTIN_TOOLS = [
    65→    "Read",
    66→    "Write",
    67→    "Edit",
    68→    "Glob",
    69→    "Grep",
    70→    "Bash",
    71→    "WebFetch",
    72→    "WebSearch",
    73→]
    74→
    75→
    76→def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
    77→    """
    78→    Create a Claude Agent SDK client with multi-layered security.
    79→
    80→    Args:
    81→        project_dir: Directory for the project
    82→        model: Claude model to use
    83→        yolo_mode: If True, skip Playwright MCP server for rapid prototyping
    84→
    85→    Returns:
    86→        Configured ClaudeSDKClient (from claude_agent_sdk)
    87→
    88→    Security layers (defense in depth):
    89→    1. Sandbox - OS-level bash command isolation prevents filesystem escape
    90→    2. Permissions - File operations restricted to project_dir only
    91→    3. Security hooks - Bash commands validated against an allowlist
    92→       (see security.py for ALLOWED_COMMANDS)
    93→
    94→    Note: Authentication is handled by start.bat/start.sh before this runs.
    95→    The Claude SDK auto-detects credentials from ~/.claude/.credentials.json
    96→    """
    97→    # Build allowed tools list based on mode
    98→    # In YOLO mode, exclude Playwright tools for faster prototyping
    99→    allowed_tools = [*BUILTIN_TOOLS, *FEATURE_MCP_TOOLS]
   100→    if not yolo_mode:
   101→        allowed_tools.extend(PLAYWRIGHT_TOOLS)
   102→
   103→    # Build permissions list
   104→    permissions_list = [
   105→        # Allow all file operations within the project directory
   106→        "Read(./**)",
   107→        "Write(./**)",
   108→        "Edit(./**)",
   109→        "Glob(./**)",
   110→        "Grep(./**)",
   111→        # Bash permission granted here, but actual commands are validated
   112→        # by the bash_security_hook (see security.py for allowed commands)
   113→        "Bash(*)",
   114→        # Allow web tools for documentation lookup
   115→        "WebFetch",
   116→        "WebSearch",
   117→        # Allow Feature MCP tools for feature management
   118→        *FEATURE_MCP_TOOLS,
   119→    ]
   120→    if not yolo_mode:
   121→        # Allow Playwright MCP tools for browser automation (standard mode only)
   122→        permissions_list.extend(PLAYWRIGHT_TOOLS)
   123→
   124→    # Create comprehensive security settings
   125→    # Note: Using relative paths ("./**") restricts access to project directory
   126→    # since cwd is set to project_dir
   127→    security_settings = {
   128→        "sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
   129→        "permissions": {
   130→            "defaultMode": "acceptEdits",  # Auto-approve edits within allowed directories
   131→            "allow": permissions_list,
   132→        },
   133→    }
   134→
   135→    # Ensure project directory exists before creating settings file
   136→    project_dir.mkdir(parents=True, exist_ok=True)
   137→
   138→    # Write settings to a file in the project directory
   139→    settings_file = project_dir / ".claude_settings.json"
   140→    with open(settings_file, "w") as f:
   141→        json.dump(security_settings, f, indent=2)
   142→
   143→    print(f"Created security settings at {settings_file}")
   144→    print("   - Sandbox enabled (OS-level bash isolation)")
   145→    print(f"   - Filesystem restricted to: {project_dir.resolve()}")
   146→    print("   - Bash commands restricted to allowlist (see security.py)")
   147→    if yolo_mode:
   148→        print("   - MCP servers: features (database) - YOLO MODE (no Playwright)")
   149→    else:
   150→        print("   - MCP servers: playwright (browser), features (database)")
   151→    print("   - Project settings enabled (skills, commands, CLAUDE.md)")
   152→    print()
   153→
   154→    # Use system Claude CLI instead of bundled one (avoids Bun runtime crash on Windows)
   155→    system_cli = shutil.which("claude")
   156→    if system_cli:
   157→        print(f"   - Using system CLI: {system_cli}")
   158→    else:
   159→        print("   - Warning: System Claude CLI not found, using bundled CLI")
   160→
   161→    # Build MCP servers config - features is always included, playwright only in standard mode
   162→    mcp_servers = {
   163→        "features": {
   164→            "command": sys.executable,  # Use the same Python that's running this script
   165→            "args": ["-m", "mcp_server.feature_mcp"],
   166→            "env": {
   167→                # Inherit parent environment (PATH, ANTHROPIC_API_KEY, etc.)
   168→                **os.environ,
   169→                # Add custom variables
   170→                "PROJECT_DIR": str(project_dir.resolve()),
   171→                "PYTHONPATH": str(Path(__file__).parent.resolve()),
   172→            },
   173→        },
   174→    }
   175→    if not yolo_mode:
   176→        # Include Playwright MCP server for browser automation (standard mode only)
   177→        mcp_servers["playwright"] = {
   178→            "command": "npx",
   179→            "args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"],
   180→        }
   181→
   182→    return ClaudeSDKClient(
   183→        options=ClaudeAgentOptions(
   184→            model=model,
   185→            cli_path=system_cli,  # Use system CLI to avoid bundled Bun crash (exit code 3)
   186→            system_prompt="You are an expert full-stack developer building a production-quality web application.",
   187→            setting_sources=["project"],  # Enable skills, commands, and CLAUDE.md from project dir
   188→            max_buffer_size=10 * 1024 * 1024,  # 10MB for large Playwright screenshots
   189→            allowed_tools=allowed_tools,
   190→            mcp_servers=mcp_servers,
   191→            hooks={
   192→                "PreToolUse": [
   193→                    HookMatcher(matcher="Bash", hooks=[bash_security_hook]),
   194→                ],
   195→            },
   196→            max_turns=1000,
   197→            cwd=str(project_dir.resolve()),
   198→            settings=str(settings_file.resolve()),  # Use absolute path
   199→        )
   200→    )
   201→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

"""
Claude SDK Client Configuration
===============================

Functions for creating and configuring the Claude Agent SDK client.
"""

import json
import os
import shutil
import sys
from pathlib import Path

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient
from claude_agent_sdk.types import HookMatcher

from security import bash_security_hook

# Feature MCP tools for feature/test management
FEATURE_MCP_TOOLS = [
    "mcp__features__feature_get_stats",
    "mcp__features__feature_get_next",
    "mcp__features__feature_get_for_regression",
    "mcp__features__feature_mark_in_progress",
    "mcp__features__feature_mark_passing",
    "mcp__features__feature_skip",
    "mcp__features__feature_create_bulk",
]

# Playwright MCP tools for browser automation
PLAYWRIGHT_TOOLS = [
    # Core navigation & screenshots
    "mcp__playwright__browser_navigate",
    "mcp__playwright__browser_navigate_back",
    "mcp__playwright__browser_take_screenshot",
    "mcp__playwright__browser_snapshot",

    # Element interaction
    "mcp__playwright__browser_click",
    "mcp__playwright__browser_type",
    "mcp__playwright__browser_fill_form",
    "mcp__playwright__browser_select_option",
    "mcp__playwright__browser_hover",
    "mcp__playwright__browser_drag",
    "mcp__playwright__browser_press_key",

    # JavaScript & debugging
    "mcp__playwright__browser_evaluate",
    # "mcp__playwright__browser_run_code",  # REMOVED - causes Playwright MCP server crash
    "mcp__playwright__browser_console_messages",
    "mcp__playwright__browser_network_requests",

    # Browser management
    "mcp__playwright__browser_close",
    "mcp__playwright__browser_resize",
    "mcp__playwright__browser_tabs",
    "mcp__playwright__browser_wait_for",
    "mcp__playwright__browser_handle_dialog",
    "mcp__playwright__browser_file_upload",
    "mcp__playwright__browser_install",
]

# Built-in tools
BUILTIN_TOOLS = [
    "Read",
    "Write",
    "Edit",
    "Glob",
    "Grep",
    "Bash",
    "WebFetch",
    "WebSearch",
]

def create_client(project_dir: Path, model: str, yolo_mode: bool = False):
    """
    Create a Claude Agent SDK client with multi-layered security.

    Args:
        project_dir: Directory for the project
        model: Claude model to use
        yolo_mode: If True, skip Playwright MCP server for rapid prototyping

    Returns:
        Configured ClaudeSDKClient (from claude_agent_sdk)

    Security layers (defense in depth):
    1. Sandbox - OS-level bash command isolation prevents filesystem escape
    2. Permissions - File operations restricted to project_dir only
    3. Security hooks - Bash commands validated against an allowlist
       (see security.py for ALLOWED_COMMANDS)

    Note: Authentication is handled by start.bat/start.sh before this runs.
    The Claude SDK auto-detects credentials from ~/.claude/.credentials.json
    """
    # Build allowed tools list based on mode
    # In YOLO mode, exclude Playwright tools for faster prototyping
    allowed_tools = [*BUILTIN_TOOLS, *FEATURE_MCP_TOOLS]
    if not yolo_mode:
        allowed_tools.extend(PLAYWRIGHT_TOOLS)

    # Build permissions list
    permissions_list = [
        # Allow all file operations within the project directory
        "Read(./**)",
        "Write(./**)",
        "Edit(./**)",
        "Glob(./**)",
        "Grep(./**)",
        # Bash permission granted here, but actual commands are validated
        # by the bash_security_hook (see security.py for allowed commands)
        "Bash(*)",
        # Allow web tools for documentation lookup
        "WebFetch",
        "WebSearch",
        # Allow Feature MCP tools for feature management
        *FEATURE_MCP_TOOLS,
    ]
    if not yolo_mode:
        # Allow Playwright MCP tools for browser automation (standard mode only)
        permissions_list.extend(PLAYWRIGHT_TOOLS)

    # Create comprehensive security settings
    # Note: Using relative paths ("./**") restricts access to project directory
    # since cwd is set to project_dir
    security_settings = {
        "sandbox": {"enabled": True, "autoAllowBashIfSandboxed": True},
        "permissions": {
            "defaultMode": "acceptEdits",  # Auto-approve edits within allowed directories
            "allow": permissions_list,
        },
    }

    # Ensure project directory exists before creating settings file
    project_dir.mkdir(parents=True, exist_ok=True)

    # Write settings to a file in the project directory
    settings_file = project_dir / ".claude_settings.json"
    with open(settings_file, "w") as f:
        json.dump(security_settings, f, indent=2)

    print(f"Created security settings at {settings_file}")
    print("   - Sandbox enabled (OS-level bash isolation)")
    print(f"   - Filesystem restricted to: {project_dir.resolve()}")
    print("   - Bash commands restricted to allowlist (see security.py)")
    if yolo_mode:
        print("   - MCP servers: features (database) - YOLO MODE (no Playwright)")
    else:
        print("   - MCP servers: playwright (browser), features (database)")
    print("   - Project settings enabled (skills, commands, CLAUDE.md)")
    print()

    # Use system Claude CLI instead of bundled one (avoids Bun runtime crash on Windows)
    system_cli = shutil.which("claude")
    if system_cli:
        print(f"   - Using system CLI: {system_cli}")
    else:
        print("   - Warning: System Claude CLI not found, using bundled CLI")

    # Build MCP servers config - features is always included, playwright only in standard mode
    mcp_servers = {
        "features": {
            "command": sys.executable,  # Use the same Python that's running this script
            "args": ["-m", "mcp_server.feature_mcp"],
            "env": {
                # Inherit parent environment (PATH, ANTHROPIC_API_KEY, etc.)
                **os.environ,
                # Add custom variables
                "PROJECT_DIR": str(project_dir.resolve()),
                "PYTHONPATH": str(Path(__file__).parent.resolve()),
            },
        },
    }
    if not yolo_mode:
        # Include Playwright MCP server for browser automation (standard mode only)
        mcp_servers["playwright"] = {
            "command": "npx",
            "args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"],
        }

    return ClaudeSDKClient(
        options=ClaudeAgentOptions(
            model=model,
            cli_path=system_cli,  # Use system CLI to avoid bundled Bun crash (exit code 3)
            system_prompt="You are an expert full-stack developer building a production-quality web application.",
            setting_sources=["project"],  # Enable skills, commands, and CLAUDE.md from project dir
            max_buffer_size=10 * 1024 * 1024,  # 10MB for large Playwright screenshots
            allowed_tools=allowed_tools,
            mcp_servers=mcp_servers,
            hooks={
                "PreToolUse": [
                    HookMatcher(matcher="Bash", hooks=[bash_security_hook]),
                ],
            },
            max_turns=1000,
            cwd=str(project_dir.resolve()),
            settings=str(settings_file.resolve()),  # Use absolute path
        )
    )