"""
Features Router
===============

API endpoints for feature/test case management.
"""

import logging
import re
from contextlib import contextmanager
from pathlib import Path

from fastapi import APIRouter, HTTPException

from ..schemas import (
    FeatureCreate,
    FeatureListResponse,
    FeatureResponse,
)

# Lazy imports to avoid circular dependencies
_create_database = None
_Feature = None

logger = logging.getLogger(__name__)

def _get_project_path(project_name: str) -> Path:
    """Get project path from registry."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import get_project_path
    return get_project_path(project_name)

def _get_db_classes():
    """Lazy import of database classes."""
    global _create_database, _Feature
    if _create_database is None:
        import sys
        from pathlib import Path
        root = Path(__file__).parent.parent.parent
        if str(root) not in sys.path:
            sys.path.insert(0, str(root))
        from api.database import Feature, create_database
        _create_database = create_database
        _Feature = Feature
    return _create_database, _Feature

router = APIRouter(prefix="/api/projects/{project_name}/features", tags=["features"])

def validate_project_name(name: str) -> str:
    """Validate and sanitize project name to prevent path traversal."""
    if not re.match(r'^[a-zA-Z0-9_-]{1,50}$', name):
        raise HTTPException(
            status_code=400,
            detail="Invalid project name"
        )
    return name

@contextmanager
def get_db_session(project_dir: Path):
    """
    Context manager for database sessions.
    Ensures session is always closed, even on exceptions.
    """
    create_database, _ = _get_db_classes()
    _, SessionLocal = create_database(project_dir)
    session = SessionLocal()
    try:
        yield session
    finally:
        session.close()

def feature_to_response(f) -> FeatureResponse:
    """Convert a Feature model to a FeatureResponse."""
    return FeatureResponse(
        id=f.id,
        priority=f.priority,
        category=f.category,
        name=f.name,
        description=f.description,
        steps=f.steps if isinstance(f.steps, list) else [],
        passes=f.passes,
        in_progress=f.in_progress,
    )

@router.get("", response_model=FeatureListResponse)
async def list_features(project_name: str):
    """
    List all features for a project organized by status.

    Returns features in three lists:
    - pending: passes=False, not currently being worked on
    - in_progress: features currently being worked on (tracked via agent output)
    - done: passes=True
    """
    project_name = validate_project_name(project_name)
    project_dir = _get_project_path(project_name)

    if not project_dir:
        raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found in registry")

    if not project_dir.exists():
        raise HTTPException(status_code=404, detail="Project directory not found")

    db_file = project_dir / "features.db"
    if not db_file.exists():
        return FeatureListResponse(pending=[], in_progress=[], done=[])

    _, Feature = _get_db_classes()

    try:
        with get_db_session(project_dir) as session:
            all_features = session.query(Feature).order_by(Feature.priority).all()

            pending = []
            in_progress = []
            done = []

            for f in all_features:
                feature_response = feature_to_response(f)
                if f.passes:
                    done.append(feature_response)
                elif f.in_progress:
                    in_progress.append(feature_response)
                else:
                    pending.append(feature_response)

            return FeatureListResponse(
                pending=pending,
                in_progress=in_progress,
                done=done,
            )
    except HTTPException:
        raise
    except Exception:
        logger.exception("Database error in list_features")
        raise HTTPException(status_code=500, detail="Database error occurred")

@router.post("", response_model=FeatureResponse)
async def create_feature(project_name: str, feature: FeatureCreate):
    """Create a new feature/test case manually."""
    project_name = validate_project_name(project_name)
    project_dir = _get_project_path(project_name)

    if not project_dir:
        raise HTTPException(status_code=404, detail=f"Project '{project_name}' not found in registry")

    if not project_dir.exists():
        raise HTTPException(status_code=404, detail="Project directory not found")

    _, Feature = _get_db_classes()

    try:
        with get_db_session(project_dir) as session:
            # Get next priority if not specified
            if feature.priority is None:
                max_priority = session.query(Feature).order_by(Feature.priority.desc()).first()
                priority = (max_priority.priority + 1) if max_priority else 1
            else:
                priority = feature.priority

            # Create new feature
            db_feature = Feature(
                priority=priority,
                category=feature.category,
                name=feature.name,
                description=feature.description,
                steps=feature.steps,
                passes=False,
            )

            session.add(db_feature)
            session.commit()
            session.refresh(db_feature)

            return feature_to_response(db_feature)
    except HTTPException:
        raise
    except Exception:
        logger.exception("Failed to create feature")
        raise HTTPException(status_code=500, detail="Failed to create feature")

@router.get("/{feature_id}", response_model=FeatureResponse)
async def get_feature(project_name: str, feature_id: int):
    """Get details of a specific feature."""
    project_name = validate_project_name(project_name)
    project_dir = _get_project_path(project_name)


Now let me check more UI component patterns and the overall structure summary:


Bash

head -100 "/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/lib/api.ts" 2>/dev/null || echo "File not found"


cat "/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/hooks/useProjects.ts" | head -150


cat "/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/process_manager.py" | tail -150


/**
 * API Client for the Autonomous Coding UI
 */

import type {
  ProjectSummary,
  ProjectDetail,
  ProjectPrompts,
  FeatureListResponse,
  Feature,
  FeatureCreate,
  AgentStatusResponse,
  AgentActionResponse,
  SetupStatus,
  DirectoryListResponse,
  PathValidationResponse,
  AssistantConversation,
  AssistantConversationDetail,
  ReferenceFolder,
  ReferenceFolderCreate,
  ReferenceFolderUpdate,
} from './types'

const API_BASE = '/api'

async function fetchJSON<T>(url: string, options?: RequestInit): Promise<T> {
  const response = await fetch(`${API_BASE}${url}`, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      ...options?.headers,
    },
  })

  if (!response.ok) {
    const error = await response.json().catch(() => ({ detail: 'Unknown error' }))
    throw new Error(error.detail || `HTTP ${response.status}`)
  }

  return response.json()
}

// ============================================================================
// Projects API
// ============================================================================

export async function listProjects(): Promise<ProjectSummary[]> {
  return fetchJSON('/projects')
}

export async function createProject(
  name: string,
  path: string,
  specMethod: 'claude' | 'manual' = 'manual'
): Promise<ProjectSummary> {
  return fetchJSON('/projects', {
    method: 'POST',
    body: JSON.stringify({ name, path, spec_method: specMethod }),
  })
}

export async function getProject(name: string): Promise<ProjectDetail> {
  return fetchJSON(`/projects/${encodeURIComponent(name)}`)
}

export interface DeleteProjectResponse {
  success: boolean
  message: string
}

export async function deleteProject(
  name: string,
  deleteFiles: boolean = false
): Promise<DeleteProjectResponse> {
  return fetchJSON(`/projects/${encodeURIComponent(name)}?delete_files=${deleteFiles}`, {
    method: 'DELETE',
  })
}

// Import an existing project (analyzes codebase)
export interface CodebaseAnalysis {
  total_files: number
  file_types: Record<string, number>
  detected_stack: Record<string, string>
  has_claude_md: boolean
  claude_md_content: string | null
  has_readme: boolean
  readme_content: string | null
  directories: string[]
  entry_points: string[]
  project_name: string | null
}

export interface ProjectImportResponse {
  project: ProjectSummary
  analysis: CodebaseAnalysis
  is_import: boolean
}

export async function importProject(


/**
 * React Query hooks for project data
 */

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import * as api from '../lib/api'
import type { FeatureCreate, ReferenceFolderCreate, ReferenceFolderUpdate } from '../lib/types'

// ============================================================================
// Projects
// ============================================================================

export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: api.listProjects,
  })
}

export function useProject(name: string | null) {
  return useQuery({
    queryKey: ['project', name],
    queryFn: () => api.getProject(name!),
    enabled: !!name,
  })
}

export function useCreateProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, path, specMethod }: { name: string; path: string; specMethod?: 'claude' | 'manual' }) =>
      api.createProject(name, path, specMethod),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

export function useDeleteProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, deleteFiles = false }: { name: string; deleteFiles?: boolean }) =>
      api.deleteProject(name, deleteFiles),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

export function useImportProject() {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: ({ name, path }: { name: string; path: string }) =>
      api.importProject(name, path),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['projects'] })
    },
  })
}

// ============================================================================
// Features
// ============================================================================

export function useFeatures(projectName: string | null) {
  return useQuery({
    queryKey: ['features', projectName],
    queryFn: () => api.listFeatures(projectName!),
    enabled: !!projectName,
    refetchInterval: 5000, // Refetch every 5 seconds for real-time updates
  })
}

export function useCreateFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (feature: FeatureCreate) => api.createFeature(projectName, feature),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

export function useDeleteFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (featureId: number) => api.deleteFeature(projectName, featureId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

export function useSkipFeature(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (featureId: number) => api.skipFeature(projectName, featureId),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['features', projectName] })
    },
  })
}

// ============================================================================
// Agent
// ============================================================================

export function useAgentStatus(projectName: string | null) {
  return useQuery({
    queryKey: ['agent-status', projectName],
    queryFn: () => api.getAgentStatus(projectName!),
    enabled: !!projectName,
    refetchInterval: 3000, // Poll every 3 seconds
  })
}

export function useStartAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (yoloMode: boolean = false) => api.startAgent(projectName, yoloMode),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

export function useStopAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: () => api.stopAgent(projectName),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}

export function usePauseAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: () => api.pauseAgent(projectName),
    onSuccess: () => {


        """
        Check if the agent process is still alive.

        Updates status to 'crashed' if process has died unexpectedly.

        Returns:
            True if healthy, False otherwise
        """
        if not self.process:
            return self.status == "stopped"

        poll = self.process.poll()
        if poll is not None:
            # Process has terminated
            if self.status in ("running", "paused"):
                self.status = "crashed"
                self._remove_lock()
            return False

        return True

    def get_status_dict(self) -> dict:
        """Get current status as a dictionary."""
        return {
            "status": self.status,
            "pid": self.pid,
            "started_at": self.started_at.isoformat() if self.started_at else None,
            "yolo_mode": self.yolo_mode,
        }

# Global registry of process managers per project with thread safety
_managers: dict[str, AgentProcessManager] = {}
_managers_lock = threading.Lock()

def get_manager(project_name: str, project_dir: Path, root_dir: Path) -> AgentProcessManager:
    """Get or create a process manager for a project (thread-safe).

    Args:
        project_name: Name of the project
        project_dir: Absolute path to the project directory
        root_dir: Root directory of the autonomous-coding-ui project
    """
    with _managers_lock:
        if project_name not in _managers:
            _managers[project_name] = AgentProcessManager(project_name, project_dir, root_dir)
        return _managers[project_name]

async def cleanup_all_managers() -> None:
    """Stop all running agents. Called on server shutdown."""
    with _managers_lock:
        managers = list(_managers.values())

    for manager in managers:
        try:
            if manager.status != "stopped":
                await manager.stop()
        except Exception as e:
            logger.warning(f"Error stopping manager for {manager.project_name}: {e}")

    with _managers_lock:
        _managers.clear()

def find_orphan_processes() -> list[dict]:
    """
    Find orphaned Autocoder-related processes.

    Returns:
        List of dicts with pid, name, cmdline, create_time
    """
    orphans = []
    patterns = [
        "autonomous_agent_demo.py",
        "feature_mcp",
        "@playwright/mcp",
        "mcp_server",
    ]

    for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'create_time', 'ppid']):
        try:
            info = proc.info
            cmdline = " ".join(info.get('cmdline') or [])

            # Check if it matches our patterns
            if any(p in cmdline for p in patterns):
                # Check if parent is init/systemd (orphaned) or if parent doesn't exist
                ppid = info.get('ppid', 0)
                is_orphan = ppid in (0, 1) or not psutil.pid_exists(ppid)

                # Also check if it's not tracked by any manager
                with _managers_lock:
                    tracked_pids = {m.pid for m in _managers.values() if m.pid}

                if is_orphan or info['pid'] not in tracked_pids:
                    orphans.append({
                        'pid': info['pid'],
                        'name': info['name'],
                        'cmdline': cmdline[:200],  # Truncate for display
                        'create_time': info['create_time'],
                        'is_orphan': is_orphan,
                    })
        except (psutil.NoSuchProcess, psutil.AccessDenied):
            continue

    return orphans

def kill_orphan_processes() -> dict:
    """
    Find and kill orphaned Autocoder-related processes.

    Returns:
        Dict with counts of found, killed, failed
    """
    orphans = find_orphan_processes()
    results = {'found': len(orphans), 'killed': 0, 'failed': 0, 'details': []}

    for orphan in orphans:
        try:
            proc = psutil.Process(orphan['pid'])
            # Kill the process and its children
            children = proc.children(recursive=True)
            for child in children:
                try:
                    child.kill()
                except psutil.NoSuchProcess:
                    pass
            proc.kill()
            results['killed'] += 1
            results['details'].append({
                'pid': orphan['pid'],
                'status': 'killed',
                'cmdline': orphan['cmdline'][:100]
            })
            logger.info(f"Killed orphan process {orphan['pid']}: {orphan['cmdline'][:50]}")
        except psutil.NoSuchProcess:
            results['killed'] += 1  # Already dead, count as success
        except Exception as e:
            results['failed'] += 1
            results['details'].append({
                'pid': orphan['pid'],
                'status': 'failed',
                'error': str(e)
            })
            logger.warning(f"Failed to kill orphan {orphan['pid']}: {e}")

    return results


Perfect! Now let me gather more architectural details on a few more key files:


"""
Feature Dependency Graph
========================

Track feature dependencies and determine which features can be
executed in parallel without conflicts.
"""

import logging
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from sqlalchemy import Column, Integer, String, ForeignKey, Table, create_engine, text
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

logger = logging.getLogger(__name__)

Base = declarative_base()

# Association table for feature dependencies (many-to-many)
feature_dependencies = Table(
    'feature_dependencies',
    Base.metadata,
    Column('feature_id', Integer, ForeignKey('feature_graph.id'), primary_key=True),
    Column('depends_on_id', Integer, primary_key=True),  # References features.id in main db
)

class FeatureGraphEntry(Base):
    """Track metadata about features for parallel execution."""

    __tablename__ = "feature_graph"

    id = Column(Integer, primary_key=True)  # Same as feature.id
    files_touched = Column(String(4000), default="")  # Comma-separated file paths
    estimated_complexity = Column(Integer, default=1)  # 1-5 scale

class FileLockEntry(Base):
    """Track which files are currently locked by which feature."""

    __tablename__ = "file_locks"

    id = Column(Integer, primary_key=True, autoincrement=True)
    file_path = Column(String(500), unique=True, nullable=False, index=True)
    feature_id = Column(Integer, nullable=False)
    locked_at = Column(String(50))  # ISO timestamp

@dataclass
class ParallelFeature:
    """A feature that can be worked on in parallel."""
    id: int
    priority: int
    category: str
    name: str
    description: str
    steps: list[str]
    estimated_complexity: int = 1

@dataclass
class FeatureConflict:
    """Describes why two features cannot run in parallel."""
    feature_id: int
    conflicting_feature_id: int
    reason: str
    conflicting_files: list[str] = field(default_factory=list)

class FeatureGraph:
    """
    Track feature dependencies and file conflicts for parallel execution.

    This class maintains a separate SQLite database alongside the main
    features.db to track:
    - Which files each feature has touched/will touch
    - Dependencies between features
    - Current file locks

    This enables the orchestrator to determine which features can be
    safely worked on in parallel.
    """

    def __init__(self, project_dir: Path):
        """
        Initialize the feature graph.

        Args:
            project_dir: Path to the project directory
        """
        self.project_dir = project_dir
        self.db_path = project_dir / "feature_graph.db"
        self.db_url = f"sqlite:///{self.db_path.as_posix()}"

        # Create database
        self.engine = create_engine(


"""
Smart Scheduler
===============

Intelligent scheduling system that dynamically determines optimal
parallelism based on feature analysis, historical data, and system state.
"""

import asyncio
import json
import logging
import re
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional, Callable, Any
from collections import defaultdict

logger = logging.getLogger(__name__)

@dataclass
class FeatureAnalysis:
    """Analysis of a single feature."""
    feature_id: int
    name: str
    category: str
    complexity_score: int  # 1-10
    predicted_files: list[str]
    predicted_duration_minutes: int
    risk_factors: list[str]
    can_parallelize_with: list[int]  # Feature IDs
    should_run_alone: bool = False

@dataclass
class SchedulerDecision:
    """Scheduling decision with full reasoning."""
    parallelism: int
    feature_ids: list[int]
    reasoning: str
    confidence: float  # 0-1
    risk_level: str
    estimated_duration: int  # minutes
    warnings: list[str] = field(default_factory=list)

@dataclass
class HistoricalRecord:
    """Record of past feature execution."""
    feature_id: int
    feature_name: str
    category: str
    success: bool
    duration_seconds: float
    files_touched: list[str]
    timestamp: datetime
    parallelism_level: int

class SmartScheduler:
    """
    Intelligent scheduler that learns and adapts.

    Decision factors:
    1. Feature complexity (steps, description length, category)
    2. Predicted file conflicts (category-based, name-based heuristics)
    3. Historical performance (past durations, failure rates)
    4. Current system state (in-progress count, recent failures)
    5. Feature clustering (similarity detection)
    """

    # Category conflict matrix - categories that shouldn't run together
    CONFLICT_CATEGORIES = {
        ("database", "migration"),
        ("database", "schema"),
        ("migration", "schema"),
        ("config", "setup"),
        ("auth", "security"),
    }

    # Categories that are generally safe to parallelize
    SAFE_PARALLEL_CATEGORIES = {
        "ui", "frontend", "component", "style", "test", "docs",
    }

    # Keywords indicating features should run alone
    SOLO_KEYWORDS = [
        "refactor", "restructure", "migrate", "breaking", "schema",
        "database", "init", "setup", "config", "security", "auth",
    ]

    def __init__(
        self,
        project_dir: Path,
        min_parallel: int = 1,
        max_parallel: int = 5,
    ):
        """
        Initialize the smart scheduler.


"""
Terminal Chat Session
=====================

Manages interactive Claude terminal sessions for resource management,
infrastructure discovery, and nuanced configuration tasks.

The terminal provides full system access (no sandbox) and allows users to:
- Add and configure servers, services, and credentials
- Test connectivity to resources
- Save resources as project-specific or global
- Execute system commands for infrastructure tasks
"""

import json
import logging
import shutil
import threading
from datetime import datetime
from pathlib import Path
from typing import AsyncGenerator, Optional

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient

from .credential_resolver import get_resolver
from .resource_manager import (
    get_manager,
    Resource,
    ResourceType,
    ResourceScope,
    AuthType,
    ResourceAuth,
)

logger = logging.getLogger(__name__)

# Root directory of the project
ROOT_DIR = Path(__file__).parent.parent.parent

# Terminal system prompt
TERMINAL_SYSTEM_PROMPT = """You are a resource management assistant for Autocoder 2.0.

## Your Role

You help users manage infrastructure resources (servers, services, databases) that coding agents can use. Your job is to:

1. **Add Resources** - When users mention a server or service, help add it to their resources
2. **Test Connectivity** - Verify resources are accessible before saving
3. **Probe Capabilities** - Discover what tools/runtimes are installed on servers
4. **Manage Scope** - Help users decide if resources should be project-specific or global

## Available Tools

You have full system access:
- **Bash**: SSH, curl, git, docker, and any system command
- **Read/Write/Edit**: File operations
- **Glob/Grep**: Search files

## When Adding a Resource

Follow this workflow:
1. Get connection details from user (host, user, credentials)
2. Test connectivity (SSH for servers, curl for services)
3. Probe for relevant capabilities if it's a server
4. Ask user: "Should this be a global resource (all projects) or project-specific?"
5. Save to the appropriate resources.json

## Resource Storage

Resources are stored in JSON files:
- **Global**: `~/.autocoder/global_resources.json`
- **Project**: `{project_dir}/resources.json`

Use the resource management functions to save resources programmatically.

## Credential Handling

Credentials are stored separately in `credentials.md`. When saving a resource:
- Don't store passwords directly in resources.json
- Instead, use a `credential_ref` that points to credentials.md
- Example: `{"auth": {"type": "ssh-password", "credential_ref": "jupiter"}}`

## Example Interactions

User: "Add my build server at 172.16.3.30"
You: Test SSH, probe for rust/node/docker, ask about scope, save resource

User: "What servers do I have configured?"
You: List resources from global + project resources

User: "Test if the Gitea API is working"
You: curl the API endpoint, report results

## Project Context

{PROJECT_CONTEXT}

## Current Working Directory

{CWD}


Excellent! Now let me compile my findings into a comprehensive report. Let me create the final summary:


cat "/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/README.md" | head -80


# AutoCoder

[![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-FFDD00?style=flat&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/leonvanzyl)

A long-running autonomous coding agent powered by the Claude Agent SDK. This tool can build complete applications over multiple sessions using a two-agent pattern (initializer + coding agent). Includes a React-based UI for monitoring progress in real-time.

## Video Tutorial

[![Watch the tutorial](https://img.youtube.com/vi/lGWFlpffWk4/hqdefault.jpg)](https://youtu.be/lGWFlpffWk4)

> **[Watch the setup and usage guide →](https://youtu.be/lGWFlpffWk4)**

---

## Prerequisites

### Claude Code CLI (Required)

This project requires the Claude Code CLI to be installed. Install it using one of these methods:

**macOS / Linux:**
```bash
curl -fsSL https://claude.ai/install.sh | bash
```

**Windows (PowerShell):**
```powershell
irm https://claude.ai/install.ps1 | iex
```

### Authentication

You need one of the following:

- **Claude Pro/Max Subscription** - Use `claude login` to authenticate (recommended)
- **Anthropic API Key** - Pay-per-use from https://console.anthropic.com/

---

## Quick Start

### Option 1: Web UI (Recommended)

**Windows:**
```cmd
start_ui.bat
```

**macOS / Linux:**
```bash
./start_ui.sh
```

This launches the React-based web UI at `http://localhost:5173` with:
- Project selection and creation
- Kanban board view of features
- Real-time agent output streaming
- Start/pause/stop controls

### Option 2: CLI Mode

**Windows:**
```cmd
start.bat
```

**macOS / Linux:**
```bash
./start.sh
```

The start script will:
1. Check if Claude CLI is installed
2. Check if you're authenticated (prompt to run `claude login` if not)
3. Create a Python virtual environment
4. Install dependencies
5. Launch the main menu

### Creating or Continuing a Project


Perfect! Now I have comprehensive understanding. Let me create a detailed summary document:

## Autocoder 2.0 Architecture Exploration - Complete Summary

I've completed a thorough exploration of the Autocoder 2.0 codebase. Here's a comprehensive breakdown of the system architecture and patterns:

---

## 1. PROJECT STRUCTURE & KEY FILES

**Root Level:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/agent.py` - Main agent session loop
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/progress.py` - Progress tracking and database queries
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/config.py` - Centralized configuration management
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/client.py` - Claude SDK client configuration
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/registry.py` - Project registry management

**API Layer:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/api/database.py` - SQLAlchemy ORM models and database functions

**MCP Server:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/mcp_server/feature_mcp.py` - FastMCP server for feature management

**Server (FastAPI):**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/main.py` - FastAPI application entry point
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/websocket.py` - WebSocket handlers with connection manager
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/schemas.py` - Pydantic request/response models

**Services:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/process_manager.py` - Agent subprocess lifecycle management
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/resource_manager.py` - Infrastructure resource management
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/feature_graph.py` - Feature dependency graph tracking
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/smart_scheduler.py` - Intelligent parallelization scheduling
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/terminal_session.py` - Interactive terminal for resource management
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/credential_resolver.py` - Infrastructure credential parsing

**Routers:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/projects.py` - Project CRUD endpoints
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/features.py` - Feature management endpoints
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/agent.py` - Agent control endpoints

**UI:**
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/hooks/` - React Query hooks for data fetching
- `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/components/` - 24+ UI components

---

## 2. DATABASE SCHEMA (api/database.py)

**Feature Model:**
```python
class Feature(Base):
    __tablename__ = "features"

    id: int (primary key)
    priority: int (default=999, indexed)      # Lower = higher priority
    category: str(100)                        # Feature category
    name: str(255)                            # Feature name
    description: str                          # Detailed description
    steps: JSON array                         # Test/implementation steps
    passes: bool (default=False, indexed)    # Completion status
    in_progress: bool (default=False, indexed) # Currently being worked on
```

**Database Initialization:**
- Path: `{project_dir}/features.db` (SQLite)
- Automatic schema creation via `create_database()` function
- Migration support for adding `in_progress` column to legacy databases
- Cross-platform path handling using POSIX-style URLs

**Session Management:**
```python
def get_database_path(project_dir: Path) -> Path
def get_database_url(project_dir: Path) -> str  # Returns: sqlite:///{posix_path}
def create_database(project_dir: Path) -> tuple  # Returns: (engine, SessionLocal)
def set_session_maker(session_maker: sessionmaker) -> None  # FastAPI dependency
def get_db() -> Session  # FastAPI dependency injection
```

---

## 3. SERVICES ARCHITECTURE (server/services/)

### Pattern: Singleton Manager Pattern with Thread Safety

All services follow a consistent pattern:

```python
# Global manager with thread safety
_manager_instance = None
_manager_lock = threading.Lock()

def get_manager(...) -> ServiceManager:
    """Get or create singleton instance (thread-safe)"""
    with _manager_lock:
        if _manager_instance is None:
            _manager_instance = ServiceManager(...)
        return _manager_instance
```

### A. Process Manager (process_manager.py)

**Responsibilities:**
- Subprocess lifecycle (start/stop/pause/resume)
- Cross-platform support (Windows via psutil)
- Process output streaming to WebSocket clients
- Lock file management to prevent multiple instances
- Sensitive data redaction in logs

**Key Classes:**
```python
class AgentProcessManager:
    def __init__(project_name: str, project_dir: Path, root_dir: Path)

    # Properties
    @property status: Literal["stopped", "running", "paused", "crashed"]
    @property pid: int | None
    @property started_at: datetime | None

    # Methods
    async def start(yolo_mode: bool = False) -> tuple[bool, str]
    async def stop() -> tuple[bool, str]
    async def pause() -> tuple[bool, str]
    async def resume() -> tuple[bool, str]
    async def healthcheck() -> bool

    # Callbacks (multiple listeners supported)
    def add_output_callback(callback: Callable[[str], Awaitable[None]])
    def add_status_callback(callback: Callable[[str], Awaitable[None]])

    # Internal
    def _check_lock() -> bool
    def _create_lock() -> None
    def _remove_lock() -> None
    async def _stream_output() -> None
    async def _broadcast_output(line: str) -> None
```

**Output Sanitization:**
- Redacts API keys, tokens, passwords, AWS credentials
- Regex patterns for Anthropic, GitHub, AWS keys

**Process Isolation:**
- Lock file: `{project_dir}/.agent.lock` (contains PID)
- Prevents multiple agent instances per project
- Orphan detection via `psutil.pid_exists()`

### B. Resource Manager (resource_manager.py)

**Responsibilities:**
- Track servers, services, databases, repositories
- Infrastructure-aware context injection
- Credential resolution with SSH/API auth support
- Resource verification and capability probing

**Key Models:**
```python
@dataclass
class Resource:
    name: str
    type: ResourceType  # SERVER, SERVICE, DATABASE, REPOSITORY
    scope: ResourceScope  # GLOBAL, PROJECT
    host: str | None
    port: int | None
    url: str | None
    user: str | None
    auth: ResourceAuth
    capabilities: list[str]  # Probed at verification time
    verified_at: str | None
    last_error: str | None

@dataclass
class ResourceAuth:
    type: AuthType  # SSH_KEY, SSH_PASSWORD, API_TOKEN, API_KEY, BASIC_AUTH
    credential_ref: str | None  # Reference to credentials.md
```

**Manager Methods:**
```python
class ResourceManager:
    def add_resource(resource: Resource, scope: ResourceScope) -> Resource
    def update_resource(name: str, updates: dict) -> Resource
    def get_resource(name: str) -> Optional[Resource]
    def list_resources(scope: ResourceScope | None = None) -> list[Resource]
    def verify_resource(name: str) -> VerificationResult
    def probe_capabilities(resource: Resource) -> list[str]
    def get_context_for_agent() -> str  # Returns formatted context
```

### C. Feature Graph (feature_graph.py)

**Responsibilities:**
- Track feature dependencies and file conflicts
- Enable parallel execution analysis
- Separate database: `{project_dir}/feature_graph.db`

**Key Classes:**
```python
@dataclass
class ParallelFeature:
    id: int
    priority: int
    category: str
    name: str
    description: str
    steps: list[str]
    estimated_complexity: int  # 1-5 scale

@dataclass
class FeatureConflict:
    feature_id: int
    conflicting_feature_id: int
    reason: str
    conflicting_files: list[str]

class FeatureGraph:
    def __init__(project_dir: Path)

    # Analysis
    def detect_conflicts(feature_ids: list[int]) -> list[FeatureConflict]
    def find_parallelizable_groups(features: list[Feature]) -> list[list[int]]
    def get_estimated_complexity(feature_id: int) -> int

    # File tracking
    def track_files_touched(feature_id: int, files: list[str])
    def get_files_for_feature(feature_id: int) -> list[str]

    # Locking
    def acquire_file_locks(files: list[str], feature_id: int) -> bool
    def release_file_locks(feature_id: int) -> None
```

### D. Smart Scheduler (smart_scheduler.py)

**Responsibilities:**
- AI-driven parallelization decisions
- Complexity analysis and risk assessment
- Historical performance tracking
- Feature clustering

**Decision Factors:**
```python
@dataclass
class SchedulerDecision:
    parallelism: int          # 1-5
    feature_ids: list[int]    # Features to run together
    reasoning: str            # Explanation
    confidence: float         # 0-1
    risk_level: str          # "low", "medium", "high"
    estimated_duration: int   # minutes
    warnings: list[str]       # Risk warnings

@dataclass
class FeatureAnalysis:
    feature_id: int
    name: str
    complexity_score: int     # 1-10
    predicted_files: list[str]
    predicted_duration_minutes: int
    risk_factors: list[str]
    can_parallelize_with: list[int]
    should_run_alone: bool
```

**Conflict Detection:**
```python
CONFLICT_CATEGORIES = {
    ("database", "migration"),
    ("database", "schema"),
    ("migration", "schema"),
    ("config", "setup"),
    ("auth", "security"),
}

SAFE_PARALLEL_CATEGORIES = {
    "ui", "frontend", "component", "style", "test", "docs",
}

SOLO_KEYWORDS = [
    "refactor", "restructure", "migrate", "breaking", "schema",
    "database", "init", "setup", "config", "security", "auth",
]
```

### E. Terminal Session (terminal_session.py)

**Responsibilities:**
- Interactive Claude terminal for resource management
- Add/test/configure servers and credentials
- Full system access (sandbox disabled)

**Key Features:**
- Built on Claude Agent SDK
- No sandbox for full system access
- Infrastructure discovery workflows
- Saves resources to JSON (global or project-specific)

---

## 4. MCP SERVER IMPLEMENTATION (mcp_server/feature_mcp.py)

**Server Type:** FastMCP (Anthropic's modern MCP framework)

**Initialization Pattern:**
```python
@asynccontextmanager
async def server_lifespan(server: FastMCP):
    """Initialize database on startup, cleanup on shutdown."""
    global _session_maker, _engine

    # Startup
    PROJECT_DIR.mkdir(parents=True, exist_ok=True)
    _engine, _session_maker = create_database(PROJECT_DIR)
    migrate_json_to_sqlite(PROJECT_DIR, _session_maker)

    yield  # Server runs here

    # Shutdown
    if _engine:
        _engine.dispose()

mcp = FastMCP("features", lifespan=server_lifespan)
```

**Registered Tools:**
```
1. feature_get_stats()
   - Returns: {passing, in_progress, total, percentage}

2. feature_get_next()
   - Returns: Highest-priority pending feature
   - Filters: passes=false, not in_progress

3. feature_get_for_regression(limit: int = 3)
   - Returns: Random passing features for regression testing

4. feature_mark_passing(feature_id: int)
   - Sets: passes=true, clears in_progress

5. feature_skip(feature_id: int)
   - Moves feature to end of queue (reprioritizes)

6. feature_mark_in_progress(feature_id: int)
   - Sets: in_progress=true

7. feature_clear_in_progress(feature_id: int)
   - Clears: in_progress=false

8. feature_create_bulk(features: list[FeatureCreateItem])
   - Creates multiple features atomically
```

**Session Management:**
```python
def get_session() -> Session:
    """Get a new database session (caller must close)."""
    if _session_maker is None:
        raise RuntimeError("Database not initialized")
    return _session_maker()
```

---

## 5. AGENT LOOP STRUCTURE (agent.py)

**Main Session Function:**
```python
async def run_agent_session(
    client: ClaudeSDKClient,
    message: str,
    project_dir: Path,
) -> tuple[str, str]
```

**Session Flow:**
1. Send prompt to Claude via `client.query(message)`
2. Stream response with `client.receive_response()`
3. Handle message types:
   - `AssistantMessage` → text blocks + tool use
   - `UserMessage` → tool results
4. Display output with sanitization
5. Return (status, response_text)

**Response Handling:**
```python
async for msg in client.receive_response():
    msg_type = type(msg).__name__

    if msg_type == "AssistantMessage":
        for block in msg.content:
            if block_type == "TextBlock":
                response_text += block.text
                print(block.text, end="", flush=True)
            elif block_type == "ToolUseBlock":
                print(f"\n[Tool: {block.name}]", flush=True)
                if hasattr(block, "input"):
                    print(f"   Input: {str(block.input)[:200]}", flush=True)

    elif msg_type == "UserMessage":
        for block in msg.content:
            if block_type == "ToolResultBlock":
                is_error = getattr(block, "is_error", False)
                if "blocked" in str(content).lower():
                    print(f"   [BLOCKED] {content}", flush=True)
                elif is_error:
                    print(f"   [Error] {error_str[:500]}", flush=True)
                else:
                    print("   [Done]", flush=True)
```

**Windows Console Encoding:**
```python
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(
        sys.stdout.buffer,
        encoding='utf-8',
        errors='replace'
    )
```

---

## 6. UI COMPONENTS & HOOKS

### Hook Pattern: React Query + WebSocket

**WebSocket Hook (useProjectWebSocket):**
```typescript
interface WebSocketState {
  progress: {
    passing: number
    in_progress: number
    total: number
    percentage: number
  }
  agentStatus: AgentStatus
  logs: Array<{ line: string; timestamp: string }>
  isConnected: boolean
}

export function useProjectWebSocket(projectName: string | null) {
  const [state, setState] = useState<WebSocketState>(initialState)
  const wsRef = useRef<WebSocket | null>(null)
  const reconnectAttempts = useRef(0)

  // Features:
  // - Automatic reconnection with exponential backoff
  // - Max 100 log lines cached
  // - Ping every 30 seconds for keep-alive
  // - Message type discrimination
}
```

**Message Types:**
```typescript
type WSMessage =
  | { type: 'progress'; passing: number; in_progress: number; total: number; percentage: number }
  | { type: 'agent_status'; status: AgentStatus }
  | { type: 'log'; line: string; timestamp: string }
  | { type: 'feature_update'; feature_id: number; passes: boolean }
  | { type: 'pong' }
```

**React Query Hooks (useProjects):**
```typescript
export function useProjects() {
  return useQuery({
    queryKey: ['projects'],
    queryFn: api.listProjects,
  })
}

export function useFeatures(projectName: string | null) {
  return useQuery({
    queryKey: ['features', projectName],
    queryFn: () => api.listFeatures(projectName!),
    enabled: !!projectName,
    refetchInterval: 5000,  // Poll every 5 seconds
  })
}

export function useAgentStatus(projectName: string | null) {
  return useQuery({
    queryKey: ['agent-status', projectName],
    queryFn: () => api.getAgentStatus(projectName!),
    enabled: !!projectName,
    refetchInterval: 3000,  // Poll every 3 seconds
  })
}
```

**Mutation Hooks Pattern:**
```typescript
export function useStartAgent(projectName: string) {
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: (yoloMode: boolean = false) => api.startAgent(projectName, yoloMode),
    onSuccess: () => {
      queryClient.invalidateQueries({ queryKey: ['agent-status', projectName] })
    },
  })
}
```

### Key UI Components:

1. **ProgressDashboard** - Displays percentage, passing/total counts, connection status
2. **KanbanBoard** - Drag-drop feature management (pending/in-progress/done)
3. **FeatureCard** - Individual feature with priority, category, description
4. **AgentControl** - Start/stop/pause/resume buttons with status display
5. **TerminalChat** - Interactive terminal for resource management

---

## 7. PROGRESS TRACKING & SESSION MANAGEMENT

### Progress Module (progress.py)

**Direct Database Access (not API):**
```python
def has_features(project_dir: Path) -> bool:
    """Check if features exist (used before API is ready)."""
    # Checks: features.db has features OR feature_list.json exists

def count_passing_tests(project_dir: Path) -> tuple[int, int, int]:
    """Returns: (passing, in_progress, total)"""
    # Uses sqlite3 directly, handles missing in_progress column

def get_all_passing_features(project_dir: Path) -> list[dict]:
    """Get passing features for webhook notifications."""
    # Returns: [{"id": 1, "category": "...", "name": "..."}, ...]
```

**Progress Webhooks:**
```python
def send_progress_webhook(passing: int, total: int, project_dir: Path) -> None:
    """Sends n8n webhook when progress increases."""
    # Tracks: .progress_cache file
    # Detects: New passing features
    # Payload: {completed_count, newly_passing_tests: [...]}
```

**Cache File:** `.progress_cache` in project directory
```json
{
  "count": 5,
  "passing_ids": [1, 3, 5, 7, 9],
  "timestamp": "2024-01-11T..."
}
```

### Configuration Management (config.py)

**Configuration Structure:**
```python
@dataclass
class GitAgentConfig:
    auto_commit: bool = True
    auto_push: bool = False
    commit_style: str = "conventional"  # conventional|simple|detailed
    push_on: list[str] = ["feature_complete", "session_end"]
    pull_before: list[str] = ["code_review", "feature_start"]

@dataclass
class ParallelConfig:
    enabled: bool = True
    max_agents: int = 3
    lock_timeout_seconds: int = 3600

@dataclass
class SecurityConfig:
    sandbox_enabled: bool = False  # Full system access
    allowed_hosts: list[str] = []  # Empty = all allowed

@dataclass
class PathsConfig:
    credentials_path: str = "~/claude-projects/shared-data/credentials.md"
    global_resources_path: str = "~/.autocoder/global_resources.json"
    session_logs_path: str = "~/claude-projects/session-logs"

@dataclass
class UIConfig:
    theme: str = "neobrutalism"
    show_agent_thoughts: bool = True
    sound_enabled: bool = True
    celebration_enabled: bool = True

@dataclass
class AutocoderConfig:
    version: str = "2.0.0"
    default_model: str = "claude-sonnet-4-20250514"
    git_agent: GitAgentConfig
    parallel: ParallelConfig
    security: SecurityConfig
    paths: PathsConfig
    ui: UIConfig
```

**Storage Location:** `~/.autocoder/config.json`

**Access Pattern:**
```python
def get_config() -> AutocoderConfig:
    """Get global config (singleton)."""

def load_config(path: Optional[Path] = None) -> AutocoderConfig:
    """Load from file or return defaults."""

def save_config(config: Optional[AutocoderConfig] = None) -> None:
    """Save to ~/.autocoder/config.json"""

def update_config(**kwargs) -> AutocoderConfig:
    """Update specific config values."""
```

---

## 8. API ROUTES ORGANIZATION

### FastAPI Application (server/main.py)

**Lifespan Management:**
```python
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: (none - routers handle lazy init)
    yield
    # Shutdown:
    await cleanup_all_managers()
    await cleanup_assistant_sessions()
```

**Middleware Security:**
```python
# CORS: Only localhost
allow_origins = [
    "http://localhost:5173",      # Vite dev
    "http://127.0.0.1:5173",
    "http://localhost:8888",      # Production
    "http://127.0.0.1:8888",
]

# HTTP middleware: Localhost only
@app.middleware("http")
async def require_localhost(request: Request, call_next):
    client_host = request.client.host
    if client_host not in ("127.0.0.1", "::1", "localhost", None):
        raise HTTPException(status_code=403, detail="Localhost access only")
    return await call_next(request)
```

**Router Registration:**
```python
app.include_router(projects_router)      # 15 routes
app.include_router(features_router)      # 12 routes
app.include_router(agent_router)         # 10 routes
app.include_router(spec_creation_router) # 3 routes
app.include_router(filesystem_router)    # 5 routes
app.include_router(assistant_chat_router)# Various
app.include_router(terminal_router)      # Various
app.include_router(git_router)           # Various
app.include_router(scheduler_router)     # Various
app.include_router(system_router)        # Various
```

**WebSocket Endpoint:**
```python
@app.websocket("/ws/projects/{project_name}")
async def websocket_endpoint(websocket: WebSocket, project_name: str):
    await project_websocket(websocket, project_name)
```

**Health & Setup Endpoints:**
```python
@app.get("/api/health") → {"status": "healthy"}
@app.get("/api/setup/status") → SetupStatus
```

**Static File Serving (SPA):**
```python
# Mount /assets for static files
app.mount("/assets", StaticFiles(directory=UI_DIST_DIR / "assets"))

@app.get("/")
async def serve_index():
    return FileResponse(UI_DIST_DIR / "index.html")

@app.get("/{path:path}")
async def serve_spa(path: str):
    # Try file, fall back to index.html for SPA routing
```

---

## 9. WEBSOCKET IMPLEMENTATION

### Connection Manager (server/websocket.py)

**Architecture:**
```python
class ConnectionManager:
    def __init__(self):
        self.active_connections: dict[str, Set[WebSocket]] = {}  # project_name -> connections
        self._lock = asyncio.Lock()

    async def connect(websocket: WebSocket, project_name: str)
    async def disconnect(websocket: WebSocket, project_name: str)
    async def broadcast_to_project(project_name: str, message: dict)
    def get_connection_count(project_name: str) -> int
```

**Main Handler:**
```python
async def project_websocket(websocket: WebSocket, project_name: str):
    # Validate project name (path traversal prevention)
    if not validate_project_name(project_name):
        await websocket.close(code=4000, reason="Invalid project name")
        return

    # Get project manager
    agent_manager = get_manager(project_name, project_dir, ROOT_DIR)

    # Register callbacks
    agent_manager.add_output_callback(on_output)
    agent_manager.add_status_callback(on_status_change)

    # Start polling task
    poll_task = asyncio.create_task(poll_progress(...))

    # Send initial state
    await websocket.send_json({
        "type": "agent_status",
        "status": agent_manager.status,
    })

    # Handle incoming messages (ping, commands)
    while True:
        try:
            data = await websocket.receive_text()
            message = json.loads(data)
            if message.get("type") == "ping":
                await websocket.send_json({"type": "pong"})
        except WebSocketDisconnect:
            break
```

**Progress Polling:**
```python
async def poll_progress(websocket: WebSocket, project_name: str, project_dir: Path):
    """Poll database every 2 seconds for progress changes."""
    last_passing = -1
    last_in_progress = -1
    last_total = -1

    while True:
        passing, in_progress, total = count_passing_tests(project_dir)

        # Only send if changed
        if passing != last_passing or in_progress != last_in_progress or total != last_total:
            percentage = (passing / total * 100) if total > 0 else 0
            await websocket.send_json({
                "type": "progress",
                "passing": passing,
                "in_progress": in_progress,
                "total": total,
                "percentage": round(percentage, 1),
            })

        await asyncio.sleep(2)
```

---

## 10. PROCESS MANAGEMENT & CLEANUP PATTERNS

### Lifecycle Management

**Startup (implicit in routers):**
- Database created on first access
- Process managers created on first project access
- Registry loaded on first project access

**Shutdown:**
```python
async def cleanup_all_managers() -> None:
    """Stop all running agents (called on server shutdown)."""
    with _managers_lock:
        managers = list(_managers.values())

    for manager in managers:
        if manager.status != "stopped":
            await manager.stop()

    with _managers_lock:
        _managers.clear()

async def cleanup_assistant_sessions() -> None:
    """Stop all assistant chat sessions."""
    # Similar pattern for chat sessions
```

**Orphan Process Cleanup:**
```python
def find_orphan_processes() -> list[dict]:
    """Find Autocoder processes with missing parent."""
    patterns = [
        "autonomous_agent_demo.py",
        "feature_mcp",
        "@playwright/mcp",
        "mcp_server",
    ]

    for proc in psutil.process_iter(['pid', 'cmdline', 'ppid']):
        ppid = info.get('ppid', 0)
        is_orphan = ppid in (0, 1) or not psutil.pid_exists(ppid)

        if is_orphan and any(p in cmdline for p in patterns):
            orphans.append(...)

def kill_orphan_processes() -> dict:
    """Kill orphaned Autocoder processes."""
    # Returns: {found, killed, failed, details}
```

---

## 11. CONFIGURATION MANAGEMENT PATTERNS

### Lazy Initialization Pattern

Used throughout to avoid circular dependencies:

```python
# In routers
_imports_initialized = False
_check_spec_exists = None

def _init_imports():
    global _imports_initialized, _check_spec_exists
    if _imports_initialized:
        return

    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from progress import count_passing_tests
    from prompts import get_project_prompts_dir

    _check_spec_exists = check_spec_exists
    _imports_initialized = True
```

### Registry Functions Pattern

```python
def _get_registry_functions():
    """Get registry functions with lazy import."""
    import sys
    root = Path(__file__).parent.parent.parent
    if str(root) not in sys.path:
        sys.path.insert(0, str(root))

    from registry import (
        get_project_path,
        list_registered_projects,
        register_project,
    )
    return register_project, list_registered_projects, get_project_path

# In endpoint
register_project, _, get_project_path = _get_registry_functions()
```

---

## 12. KEY ARCHITECTURAL PRINCIPLES

### 1. **Cross-Platform Path Handling**
- Use POSIX-style paths (forward slashes) internally
- Convert using `.as_posix()` for SQLite URLs
- `Path` objects for OS-independent operations

### 2. **Lazy Imports**
- Avoid circular dependencies between routers and services
- Initialize on first access
- Use lazy import functions in routers

### 3. **Singleton Managers with Thread Safety**
- Global registry with `threading.Lock()`
- Reuse manager instances across requests
- Proper cleanup on shutdown

### 4. **Multiple Callback Support**
- Process manager supports multiple WebSocket clients
- Thread-safe callback registration/deregistration
- Async callback execution in event loop

### 5. **Security**
- Localhost-only HTTP middleware
- Bash output sanitization (redact API keys, tokens)
- Project name validation (regex, path traversal prevention)
- Sandbox disabled for full system access

### 6. **Real-Time Updates**
- WebSocket for progress streaming
- Polling for fallback/complex updates
- Redis-like message broadcasting
- Keep-alive ping/pong mechanism

### 7. **Database Access Patterns**
- Direct SQLite access in progress module (no API dependency)
- FastAPI dependency injection for sessions
- Automatic session cleanup via context managers
- Migration support for schema changes

### 8. **Error Handling**
- Try/catch in callbacks to prevent cascade failures
- Graceful degradation (offline mode)
- Detailed error messages in responses
- Logging for debugging

---

## RECOMMENDATIONS FOR ENHANCEMENTS

To maintain architectural consistency when adding features:

1. **New Services**: Follow the singleton+lock pattern in `__init__.py`
2. **New Routers**: Use lazy imports to avoid circular dependencies
3. **New Database Models**: Extend `api/database.py` with new classes
4. **WebSocket Messages**: Add to message type discrimination in handler
5. **UI Hooks**: Use React Query with appropriate refetch intervals
6. **Process Management**: Extend `AgentProcessManager` with new methods
7. **Configuration**: Add sections to `config.py` dataclasses
8. **Cleanup**: Ensure shutdown handlers in `lifespan()` or router cleanup

---

**Key File Locations Summary:**

| Component | Path |
|-----------|------|
| Database Models | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/api/database.py` |
| Process Manager | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/services/process_manager.py` |
| WebSocket Handler | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/websocket.py` |
| FastAPI App | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/main.py` |
| Schemas/Models | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/schemas.py` |
| Feature Router | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/features.py` |
| Agent Router | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/agent.py` |
| Projects Router | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/server/routers/projects.py` |
| React Hooks | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/hooks/` |
| UI Components | `/c/Users/MikeSwanson/claude-projects/Autocode-remix/Autocoder-2.0/ui/src/components/` |