The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\mcp_server\feature_mcp.py has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   436→    finally:
   437→        session.close()
   438→
   439→
   440→@mcp.tool()
   441→def project_processes_status() -> str:
   442→    """Get status of all processes spawned by the project.
   443→
   444→    Shows dev servers, databases, build processes, and other processes
   445→    that were started during development. Use this to check what's running
   446→    before cleaning up or debugging issues.
   447→
   448→    Returns:
   449→        JSON with process counts and details by category
   450→    """
   451→    if not PROCESS_TRACKER_AVAILABLE:
   452→        return json.dumps({"error": "Process tracker not available"})
   453→
   454→    try:
   455→        tracker = get_tracker(PROJECT_DIR)
   456→        status = tracker.get_status()
   457→        return json.dumps(status, indent=2)
   458→    except Exception as e:
   459→        return json.dumps({"error": str(e)})
   460→
   461→
   462→@mcp.tool()
   463→def project_processes_cleanup(
   464→    category: Annotated[
   465→        str,
   466→        Field(
   467→            description="Category to cleanup: 'all', 'dev_server', 'database', 'build', or 'other'",
   468→            pattern="^(all|dev_server|database|build|other)$"
   469→        )
   470→    ] = "dev_server"
   471→) -> str:
   472→    """Manually cleanup project processes by category.
   473→
   474→    Use this to kill processes that are no longer needed. Common use cases:
   475→    - Kill dev servers before restarting them
   476→    - Clean up build processes after building
   477→    - Force cleanup everything when debugging
   478→
   479→    CAUTION: 'all' will kill databases too - use 'dev_server' for safe cleanup.
   480→
   481→    Args:
   482→        category: Which processes to kill
   483→            - 'dev_server': npm/yarn dev, vite, flask run, etc.
   484→            - 'database': postgres, mysql, mongodb, redis
   485→            - 'build': npm build, webpack, vite build
   486→            - 'other': miscellaneous processes
   487→            - 'all': everything (including databases)
   488→
   489→    Returns:
   490→        JSON with cleanup results (killed/failed counts)
   491→    """
   492→    if not PROCESS_TRACKER_AVAILABLE:
   493→        return json.dumps({"error": "Process tracker not available"})
   494→
   495→    try:
   496→        tracker = get_tracker(PROJECT_DIR)
   497→
   498→        if category == "all":
   499→            results = tracker.cleanup_all()
   500→        else:
   501→            results = tracker.cleanup_category(category)
   502→
   503→        return json.dumps(results, indent=2)
   504→    except Exception as e:
   505→        return json.dumps({"error": str(e)})
   506→
   507→
   508→@mcp.tool()
   509→def project_process_register(
   510→    pid: Annotated[int, Field(description="Process ID to register", ge=1)],
   511→    command: Annotated[str, Field(description="The command that spawned this process")]
   512→) -> str:
   513→    """Register a process for tracking.
   514→
   515→    Use this when you start a process that should be tracked for cleanup.
   516→    The process will be automatically categorized (dev_server, database, build, other)
   517→    based on the command pattern.
   518→
   519→    Args:
   520→        pid: Process ID (from command output or $! in bash)
   521→        command: The full command that started the process
   522→
   523→    Returns:
   524→        JSON with registration confirmation and detected category
   525→    """
   526→    if not PROCESS_TRACKER_AVAILABLE:
   527→        return json.dumps({"error": "Process tracker not available"})
   528→
   529→    try:
   530→        tracker = get_tracker(PROJECT_DIR)
   531→        tracker.register_process(pid, command)
   532→
   533→        # Get the category that was assigned
   534→        proc_info = tracker.processes.get(pid)
   535→        category = proc_info.category if proc_info else "unknown"
   536→
   537→        return json.dumps({
   538→            "pid": pid,
   539→            "command": command[:100],
   540→            "category": category,
   541→            "message": "Process registered for tracking"
   542→        }, indent=2)
   543→    except Exception as e:
   544→        return json.dumps({"error": str(e)})
   545→
   546→
   547→if __name__ == "__main__":
   548→    mcp.run()
   549→

if __name__ == "__main__":
    mcp.run()

@mcp.tool()
def project_processes_status() -> str:
    """Get status of all processes spawned by the project.

    Shows dev servers, databases, build processes, and other processes
    that were started during development. Use this to check what's running
    before cleaning up or debugging issues.

    Returns:
        JSON with process counts and details by category
    """
    if not PROCESS_TRACKER_AVAILABLE:
        return json.dumps({"error": "Process tracker not available"})

    try:
        tracker = get_tracker(PROJECT_DIR)
        status = tracker.get_status()
        return json.dumps(status, indent=2)
    except Exception as e:
        return json.dumps({"error": str(e)})

@mcp.tool()
def project_processes_cleanup(
    category: Annotated[
        str,
        Field(
            description="Category to cleanup: 'all', 'dev_server', 'database', 'build', or 'other'",
            pattern="^(all|dev_server|database|build|other)$"
        )
    ] = "dev_server"
) -> str:
    """Manually cleanup project processes by category.

    Use this to kill processes that are no longer needed. Common use cases:
    - Kill dev servers before restarting them
    - Clean up build processes after building
    - Force cleanup everything when debugging

    CAUTION: 'all' will kill databases too - use 'dev_server' for safe cleanup.

    Args:
        category: Which processes to kill
            - 'dev_server': npm/yarn dev, vite, flask run, etc.
            - 'database': postgres, mysql, mongodb, redis
            - 'build': npm build, webpack, vite build
            - 'other': miscellaneous processes
            - 'all': everything (including databases)

    Returns:
        JSON with cleanup results (killed/failed counts)
    """
    if not PROCESS_TRACKER_AVAILABLE:
        return json.dumps({"error": "Process tracker not available"})

    try:
        tracker = get_tracker(PROJECT_DIR)

        if category == "all":
            results = tracker.cleanup_all()
        else:
            results = tracker.cleanup_category(category)

        return json.dumps(results, indent=2)
    except Exception as e:
        return json.dumps({"error": str(e)})

@mcp.tool()
def project_process_register(
    pid: Annotated[int, Field(description="Process ID to register", ge=1)],
    command: Annotated[str, Field(description="The command that spawned this process")]
) -> str:
    """Register a process for tracking.

    Use this when you start a process that should be tracked for cleanup.
    The process will be automatically categorized (dev_server, database, build, other)
    based on the command pattern.

    Args:
        pid: Process ID (from command output or $! in bash)
        command: The full command that started the process

    Returns:
        JSON with registration confirmation and detected category
    """
    if not PROCESS_TRACKER_AVAILABLE:
        return json.dumps({"error": "Process tracker not available"})

    try:
        tracker = get_tracker(PROJECT_DIR)
        tracker.register_process(pid, command)

        # Get the category that was assigned
        proc_info = tracker.processes.get(pid)
        category = proc_info.category if proc_info else "unknown"

        return json.dumps({
            "pid": pid,
            "command": command[:100],
            "category": category,
            "message": "Process registered for tracking"
        }, indent=2)
    except Exception as e:
        return json.dumps({"error": str(e)})

if __name__ == "__main__":
    mcp.run()

#!/usr/bin/env python3
"""
MCP Server for Feature Management
==================================

Provides tools to manage features in the autonomous coding system,
replacing the previous FastAPI-based REST API.

Tools:
- feature_get_stats: Get progress statistics
- feature_get_next: Get next feature to implement
- feature_get_for_regression: Get random passing features for testing
- feature_mark_passing: Mark a feature as passing
- feature_skip: Skip a feature (move to end of queue)
- feature_mark_in_progress: Mark a feature as in-progress
- feature_clear_in_progress: Clear in-progress status
- feature_create_bulk: Create multiple features at once
"""

import json
import os
import sys
from contextlib import asynccontextmanager
from pathlib import Path
from typing import Annotated

from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
from sqlalchemy.sql.expression import func

# Add parent directory to path so we can import from api module
sys.path.insert(0, str(Path(__file__).parent.parent))

from api.database import Feature, create_database
from api.migration import migrate_json_to_sqlite

# Import process tracker (only if server module is available)
try:
    from server.services.project_process_tracker import get_tracker
    PROCESS_TRACKER_AVAILABLE = True
except ImportError:
    PROCESS_TRACKER_AVAILABLE = False

# Configuration from environment
PROJECT_DIR = Path(os.environ.get("PROJECT_DIR", ".")).resolve()

# Pydantic models for input validation
class MarkPassingInput(BaseModel):
    """Input for marking a feature as passing."""
    feature_id: int = Field(..., description="The ID of the feature to mark as passing", ge=1)

class SkipFeatureInput(BaseModel):
    """Input for skipping a feature."""
    feature_id: int = Field(..., description="The ID of the feature to skip", ge=1)

class MarkInProgressInput(BaseModel):
    """Input for marking a feature as in-progress."""
    feature_id: int = Field(..., description="The ID of the feature to mark as in-progress", ge=1)

class ClearInProgressInput(BaseModel):
    """Input for clearing in-progress status."""
    feature_id: int = Field(..., description="The ID of the feature to clear in-progress status", ge=1)

class RegressionInput(BaseModel):
    """Input for getting regression features."""
    limit: int = Field(default=3, ge=1, le=10, description="Maximum number of passing features to return")

class FeatureCreateItem(BaseModel):
    """Schema for creating a single feature."""
    category: str = Field(..., min_length=1, max_length=100, description="Feature category")
    name: str = Field(..., min_length=1, max_length=255, description="Feature name")
    description: str = Field(..., min_length=1, description="Detailed description")
    steps: list[str] = Field(..., min_length=1, description="Implementation/test steps")

class BulkCreateInput(BaseModel):
    """Input for bulk creating features."""
    features: list[FeatureCreateItem] = Field(..., min_length=1, description="List of features to create")

# Global database session maker (initialized on startup)
_session_maker = None
_engine = None

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

    # Create project directory if it doesn't exist
    PROJECT_DIR.mkdir(parents=True, exist_ok=True)

    # Initialize database
    _engine, _session_maker = create_database(PROJECT_DIR)

    # Run migration if needed (converts legacy JSON to SQLite)
    migrate_json_to_sqlite(PROJECT_DIR, _session_maker)

    yield

    # Cleanup
    if _engine:
        _engine.dispose()

# Initialize the MCP server
mcp = FastMCP("features", lifespan=server_lifespan)

def get_session():
    """Get a new database session."""
    if _session_maker is None:
        raise RuntimeError("Database not initialized")
    return _session_maker()

@mcp.tool()
def feature_get_stats() -> str:
    """Get statistics about feature completion progress.

    Returns the number of passing features, in-progress features, total features,
    and completion percentage. Use this to track overall progress of the implementation.

    Returns:
        JSON with: passing (int), in_progress (int), total (int), percentage (float)
    """
    session = get_session()
    try:
        total = session.query(Feature).count()
        passing = session.query(Feature).filter(Feature.passes == True).count()
        in_progress = session.query(Feature).filter(Feature.in_progress == True).count()
        percentage = round((passing / total) * 100, 1) if total > 0 else 0.0

        return json.dumps({
            "passing": passing,
            "in_progress": in_progress,
            "total": total,
            "percentage": percentage
        }, indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_get_next() -> str:
    """Get the highest-priority pending feature to work on.

    Returns the feature with the lowest priority number that has passes=false.
    Use this at the start of each coding session to determine what to implement next.

    Returns:
        JSON with feature details (id, priority, category, name, description, steps, passes, in_progress)
        or error message if all features are passing.
    """
    session = get_session()
    try:
        feature = (
            session.query(Feature)
            .filter(Feature.passes == False)
            .order_by(Feature.priority.asc(), Feature.id.asc())
            .first()
        )

        if feature is None:
            return json.dumps({"error": "All features are passing! No more work to do."})

        return json.dumps(feature.to_dict(), indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_get_for_regression(
    limit: Annotated[int, Field(default=3, ge=1, le=10, description="Maximum number of passing features to return")] = 3
) -> str:
    """Get random passing features for regression testing.

    Returns a random selection of features that are currently passing.
    Use this to verify that previously implemented features still work
    after making changes.

    Args:
        limit: Maximum number of features to return (1-10, default 3)

    Returns:
        JSON with: features (list of feature objects), count (int)
    """
    session = get_session()
    try:
        features = (
            session.query(Feature)
            .filter(Feature.passes == True)
            .order_by(func.random())
            .limit(limit)
            .all()
        )

        return json.dumps({
            "features": [f.to_dict() for f in features],
            "count": len(features)
        }, indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_mark_passing(
    feature_id: Annotated[int, Field(description="The ID of the feature to mark as passing", ge=1)]
) -> str:
    """Mark a feature as passing after successful implementation.

    Updates the feature's passes field to true and clears the in_progress flag.
    Use this after you have implemented the feature and verified it works correctly.

    IMPORTANT: This automatically cleans up dev server and build processes before
    marking the feature as passing. Database processes are kept running.

    Args:
        feature_id: The ID of the feature to mark as passing

    Returns:
        JSON with the updated feature details, or error if not found.
    """
    # Clean up dev servers and build processes before marking passing
    cleanup_results = {}
    if PROCESS_TRACKER_AVAILABLE:
        try:
            tracker = get_tracker(PROJECT_DIR)
            cleanup_results = tracker.cleanup_between_features()
        except Exception as e:
            # Don't fail the feature marking if cleanup fails
            cleanup_results = {"error": f"Process cleanup failed: {e}"}

    session = get_session()
    try:
        feature = session.query(Feature).filter(Feature.id == feature_id).first()

        if feature is None:
            return json.dumps({"error": f"Feature with ID {feature_id} not found"})

        feature.passes = True
        feature.in_progress = False
        session.commit()
        session.refresh(feature)

        result = feature.to_dict()
        if cleanup_results:
            result["cleanup"] = cleanup_results

        return json.dumps(result, indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_skip(
    feature_id: Annotated[int, Field(description="The ID of the feature to skip", ge=1)]
) -> str:
    """Skip a feature by moving it to the end of the priority queue.

    Use this when a feature cannot be implemented yet due to:
    - Dependencies on other features that aren't implemented yet
    - External blockers (missing assets, unclear requirements)
    - Technical prerequisites that need to be addressed first

    The feature's priority is set to max_priority + 1, so it will be
    worked on after all other pending features. Also clears the in_progress
    flag so the feature returns to "pending" status.

    Args:
        feature_id: The ID of the feature to skip

    Returns:
        JSON with skip details: id, name, old_priority, new_priority, message
    """
    session = get_session()
    try:
        feature = session.query(Feature).filter(Feature.id == feature_id).first()

        if feature is None:
            return json.dumps({"error": f"Feature with ID {feature_id} not found"})

        if feature.passes:
            return json.dumps({"error": "Cannot skip a feature that is already passing"})

        old_priority = feature.priority

        # Get max priority and set this feature to max + 1
        max_priority_result = session.query(Feature.priority).order_by(Feature.priority.desc()).first()
        new_priority = (max_priority_result[0] + 1) if max_priority_result else 1

        feature.priority = new_priority
        feature.in_progress = False
        session.commit()
        session.refresh(feature)

        return json.dumps({
            "id": feature.id,
            "name": feature.name,
            "old_priority": old_priority,
            "new_priority": new_priority,
            "message": f"Feature '{feature.name}' moved to end of queue"
        }, indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_mark_in_progress(
    feature_id: Annotated[int, Field(description="The ID of the feature to mark as in-progress", ge=1)]
) -> str:
    """Mark a feature as in-progress. Call immediately after feature_get_next().

    This prevents other agent sessions from working on the same feature.
    Use this as soon as you retrieve a feature to work on.

    Args:
        feature_id: The ID of the feature to mark as in-progress

    Returns:
        JSON with the updated feature details, or error if not found or already in-progress.
    """
    session = get_session()
    try:
        feature = session.query(Feature).filter(Feature.id == feature_id).first()

        if feature is None:
            return json.dumps({"error": f"Feature with ID {feature_id} not found"})

        if feature.passes:
            return json.dumps({"error": f"Feature with ID {feature_id} is already passing"})

        if feature.in_progress:
            return json.dumps({"error": f"Feature with ID {feature_id} is already in-progress"})

        feature.in_progress = True
        session.commit()
        session.refresh(feature)

        return json.dumps(feature.to_dict(), indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_clear_in_progress(
    feature_id: Annotated[int, Field(description="The ID of the feature to clear in-progress status", ge=1)]
) -> str:
    """Clear in-progress status from a feature.

    Use this when abandoning a feature or manually unsticking a stuck feature.
    The feature will return to the pending queue.

    Args:
        feature_id: The ID of the feature to clear in-progress status

    Returns:
        JSON with the updated feature details, or error if not found.
    """
    session = get_session()
    try:
        feature = session.query(Feature).filter(Feature.id == feature_id).first()

        if feature is None:
            return json.dumps({"error": f"Feature with ID {feature_id} not found"})

        feature.in_progress = False
        session.commit()
        session.refresh(feature)

        return json.dumps(feature.to_dict(), indent=2)
    finally:
        session.close()

@mcp.tool()
def feature_create_bulk(
    features: Annotated[list[dict], Field(description="List of features to create, each with category, name, description, and steps")]
) -> str:
    """Create multiple features in a single operation.

    Features are assigned sequential priorities based on their order.
    All features start with passes=false.

    This is typically used by the initializer agent to set up the initial
    feature list from the app specification.

    Args:
        features: List of features to create, each with:
            - category (str): Feature category
            - name (str): Feature name
            - description (str): Detailed description
            - steps (list[str]): Implementation/test steps

    Returns:
        JSON with: created (int) - number of features created
    """
    session = get_session()
    try:
        # Get the starting priority
        max_priority_result = session.query(Feature.priority).order_by(Feature.priority.desc()).first()
        start_priority = (max_priority_result[0] + 1) if max_priority_result else 1

        created_count = 0
        for i, feature_data in enumerate(features):
            # Validate required fields
            if not all(key in feature_data for key in ["category", "name", "description", "steps"]):
                return json.dumps({
                    "error": f"Feature at index {i} missing required fields (category, name, description, steps)"
                })

            db_feature = Feature(
                priority=start_priority + i,
                category=feature_data["category"],
                name=feature_data["name"],
                description=feature_data["description"],
                steps=feature_data["steps"],
                passes=False,
            )
            session.add(db_feature)
            created_count += 1

        session.commit()

        return json.dumps({"created": created_count}, indent=2)
    except Exception as e:
        session.rollback()
        return json.dumps({"error": str(e)})
    finally:
        session.close()

if __name__ == "__main__":
    mcp.run()

+    """Get status of all processes spawned by the project.

+    Shows dev servers, databases, build processes, and other processes

+    that were started during development. Use this to check what's running

+    before cleaning up or debugging issues.

+        JSON with process counts and details by category

+        return json.dumps({"error": "Process tracker not available"})

+        tracker = get_tracker(PROJECT_DIR)

+        return json.dumps(status, indent=2)

+        return json.dumps({"error": str(e)})

+            description="Category to cleanup: 'all', 'dev_server', 'database', 'build', or 'other'",

+            pattern="^(all|dev_server|database|build|other)$"

+    """Manually cleanup project processes by category.

+    Use this to kill processes that are no longer needed. Common use cases:

+    - Kill dev servers before restarting them

+    - Clean up build processes after building

+    - Force cleanup everything when debugging

+    CAUTION: 'all' will kill databases too - use 'dev_server' for safe cleanup.

+        category: Which processes to kill

+            - 'dev_server': npm/yarn dev, vite, flask run, etc.

+            - 'database': postgres, mysql, mongodb, redis

+            - 'build': npm build, webpack, vite build

+            - 'other': miscellaneous processes

+            - 'all': everything (including databases)

+        JSON with cleanup results (killed/failed counts)

+        tracker = get_tracker(PROJECT_DIR)

+            results = tracker.cleanup_all()

+            results = tracker.cleanup_category(category)

+        return json.dumps(results, indent=2)

+        return json.dumps({"error": str(e)})

+    pid: Annotated[int, Field(description="Process ID to register", ge=1)],

+    command: Annotated[str, Field(description="The command that spawned this process")]

+    """Register a process for tracking.

+    Use this when you start a process that should be tracked for cleanup.

+    The process will be automatically categorized (dev_server, database, build, other)

+        pid: Process ID (from command output or $! in bash)

+        command: The full command that started the process

+        JSON with registration confirmation and detected category

+        tracker.register_process(pid, command)

+        # Get the category that was assigned

+        proc_info = tracker.processes.get(pid)

+        category = proc_info.category if proc_info else "unknown"

+            "message": "Process registered for tracking"


Great! Now I need to update the client.py to add these new MCP tools to the allowed tools list, and then update the coding prompt to instruct agents about process management.


TodoWrite

Create ProjectProcessTracker class to monitor and manage project processes

Integrate tracker with MCP Feature server for cleanup hooks

Integrating tracker with MCP Feature server

Update coding prompt to include process cleanup instructions

Add session-end cleanup to agent.py

Test the process tracking and cleanup system


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

Create ProjectProcessTracker class to monitor and manage project processes

Integrate tracker with MCP Feature server for cleanup hooks

Integrating tracker with MCP Feature server

Update coding prompt to include process cleanup instructions

Add session-end cleanup to agent.py

Test the process tracking and cleanup system

Add session-end cleanup to agent.py


Let me update client.py to add the new MCP tools to the allowed list.


Edit

# 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",
]

# 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",
    # Project process management
    "mcp__features__project_processes_status",
    "mcp__features__project_processes_cleanup",
    "mcp__features__project_process_register",
]


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\client.py has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    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→    # Project process management
    29→    "mcp__features__project_processes_status",
    30→    "mcp__features__project_processes_cleanup",
    31→    "mcp__features__project_process_register",
    32→]
    33→
    34→# Playwright MCP tools for browser automation
    35→PLAYWRIGHT_TOOLS = [
    36→    # Core navigation & screenshots

# 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",
    # Project process management
    "mcp__features__project_processes_status",
    "mcp__features__project_processes_cleanup",
    "mcp__features__project_process_register",
]

"""
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 disabled for full system access (SSH, infrastructure, etc.)
        "sandbox": {"enabled": False},
        "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 DISABLED (full system access enabled)")
    print(f"   - Working directory: {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
        )
    )

+    "mcp__features__project_processes_status",

+    "mcp__features__project_processes_cleanup",

+    "mcp__features__project_process_register",


Good! Now I need to update the coding prompt template to instruct agents about process management. Let me read it first to see where to add this.


Now let me update the coding prompt to include process management instructions:


     1→## YOUR ROLE - CODING AGENT
     2→
     3→You are continuing work on a long-running autonomous development task.
     4→This is a FRESH context window - you have no memory of previous sessions.
     5→
     6→### STEP 1: GET YOUR BEARINGS (MANDATORY)
     7→
     8→Start by orienting yourself:
     9→
    10→```bash
    11→# 1. See your working directory
    12→pwd
    13→
    14→# 2. List files to understand project structure
    15→ls -la
    16→
    17→# 3. Read the project specification to understand what you're building
    18→cat app_spec.txt
    19→
    20→# 4. Read progress notes from previous sessions
    21→cat claude-progress.txt
    22→
    23→# 5. Check recent git history
    24→git log --oneline -20
    25→```
    26→
    27→Then use MCP tools to check feature status:
    28→
    29→```
    30→# 6. Get progress statistics (passing/total counts)
    31→Use the feature_get_stats tool
    32→
    33→# 7. Get the next feature to work on
    34→Use the feature_get_next tool
    35→```
    36→
    37→Understanding the `app_spec.txt` is critical - it contains the full requirements
    38→for the application you're building.
    39→
    40→### STEP 2: START SERVERS (IF NOT RUNNING)
    41→
    42→If `init.sh` exists, run it:
    43→
    44→```bash
    45→chmod +x init.sh
    46→./init.sh
    47→```
    48→
    49→Otherwise, start servers manually and document the process.
    50→
    51→### STEP 3: VERIFICATION TEST (CRITICAL!)
    52→
    53→**MANDATORY BEFORE NEW WORK:**
    54→
    55→The previous session may have introduced bugs. Before implementing anything
    56→new, you MUST run verification tests.
    57→
    58→Run 1-2 of the features marked as passing that are most core to the app's functionality to verify they still work.
    59→
    60→To get passing features for regression testing:
    61→
    62→```
    63→Use the feature_get_for_regression tool (returns up to 3 random passing features)
    64→```
    65→
    66→For example, if this were a chat app, you should perform a test that logs into the app, sends a message, and gets a response.
    67→
    68→**If you find ANY issues (functional or visual):**
    69→
    70→- Mark that feature as "passes": false immediately
    71→- Add issues to a list
    72→- Fix all issues BEFORE moving to new features
    73→- This includes UI bugs like:
    74→  - White-on-white text or poor contrast
    75→  - Random characters displayed
    76→  - Incorrect timestamps
    77→  - Layout issues or overflow
    78→  - Buttons too close together
    79→  - Missing hover states
    80→  - Console errors
    81→
    82→### STEP 4: CHOOSE ONE FEATURE TO IMPLEMENT
    83→
    84→#### TEST-DRIVEN DEVELOPMENT MINDSET (CRITICAL)
    85→
    86→Features are **test cases** that drive development. This is test-driven development:
    87→
    88→- **If you can't test a feature because functionality doesn't exist → BUILD IT**
    89→- You are responsible for implementing ALL required functionality
    90→- Never assume another process will build it later
    91→- "Missing functionality" is NOT a blocker - it's your job to create it
    92→
    93→**Example:** Feature says "User can filter flashcards by difficulty level"
    94→- WRONG: "Flashcard page doesn't exist yet" → skip feature
    95→- RIGHT: "Flashcard page doesn't exist yet" → build flashcard page → implement filter → test feature
    96→
    97→Get the next feature to implement:
    98→
    99→```
   100→# Get the highest-priority pending feature

<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>

## YOUR ROLE - CODING AGENT

You are continuing work on a long-running autonomous development task.
This is a FRESH context window - you have no memory of previous sessions.

### STEP 1: GET YOUR BEARINGS (MANDATORY)

Start by orienting yourself:

```bash
# 1. See your working directory
pwd

# 2. List files to understand project structure
ls -la

# 3. Read the project specification to understand what you're building
cat app_spec.txt

# 4. Read progress notes from previous sessions
cat claude-progress.txt

# 5. Check recent git history
git log --oneline -20
```

Then use MCP tools to check feature status:

```
# 6. Get progress statistics (passing/total counts)
Use the feature_get_stats tool

# 7. Get the next feature to work on
Use the feature_get_next tool
```

Understanding the `app_spec.txt` is critical - it contains the full requirements
for the application you're building.

### STEP 2: START SERVERS (IF NOT RUNNING)

If `init.sh` exists, run it:

```bash
chmod +x init.sh
./init.sh
```

Otherwise, start servers manually and document the process.

### STEP 3: VERIFICATION TEST (CRITICAL!)

**MANDATORY BEFORE NEW WORK:**

The previous session may have introduced bugs. Before implementing anything
new, you MUST run verification tests.

Run 1-2 of the features marked as passing that are most core to the app's functionality to verify they still work.

To get passing features for regression testing:

```
Use the feature_get_for_regression tool (returns up to 3 random passing features)
```

For example, if this were a chat app, you should perform a test that logs into the app, sends a message, and gets a response.

**If you find ANY issues (functional or visual):**

- Mark that feature as "passes": false immediately
- Add issues to a list
- Fix all issues BEFORE moving to new features
- This includes UI bugs like:
  - White-on-white text or poor contrast
  - Random characters displayed
  - Incorrect timestamps
  - Layout issues or overflow
  - Buttons too close together
  - Missing hover states
  - Console errors

### STEP 4: CHOOSE ONE FEATURE TO IMPLEMENT

#### TEST-DRIVEN DEVELOPMENT MINDSET (CRITICAL)

Features are **test cases** that drive development. This is test-driven development:

- **If you can't test a feature because functionality doesn't exist → BUILD IT**
- You are responsible for implementing ALL required functionality
- Never assume another process will build it later
- "Missing functionality" is NOT a blocker - it's your job to create it

**Example:** Feature says "User can filter flashcards by difficulty level"
- WRONG: "Flashcard page doesn't exist yet" → skip feature
- RIGHT: "Flashcard page doesn't exist yet" → build flashcard page → implement filter → test feature

Get the next feature to implement:

```
# Get the highest-priority pending feature


Let me insert the process management section after STEP 2. I'll add it as a new step about process tracking.


### STEP 2: START SERVERS (IF NOT RUNNING)

If `init.sh` exists, run it:

```bash
chmod +x init.sh
./init.sh
```

Otherwise, start servers manually and document the process.

### STEP 3: VERIFICATION TEST (CRITICAL!)

### STEP 2: START SERVERS (IF NOT RUNNING)

If `init.sh` exists, run it:

```bash
chmod +x init.sh
./init.sh
```

Otherwise, start servers manually and document the process.

### STEP 2.5: PROCESS MANAGEMENT (AUTOMATED CLEANUP)

**Process tracking is now AUTOMATIC** - dev servers and build processes are killed automatically when you mark a feature as passing.

**You usually don't need to manually manage processes**, but these tools are available if needed:

Available tools:
- `project_processes_status` - Check what processes are running
- `project_processes_cleanup` - Manually kill processes by category
- `project_process_register` - Register a background process for tracking (rarely needed)

**Automatic cleanup happens:**
- When you call `feature_mark_passing` - dev servers and builds are killed
- Database processes are kept running (don't kill between features)

**Manual cleanup when needed:**
```
# Check what's running
Use project_processes_status

# Kill dev servers before restarting
Use project_processes_cleanup with category="dev_server"
```

**When to manually register processes:**
If you start a background process with `&`, optionally register it:
```bash
npm run dev &
DEV_PID=$!
```
Then use: `project_process_register` with pid=$DEV_PID and command="npm run dev"

But this is **optional** - the automatic cleanup on feature_mark_passing handles most cases.

### STEP 3: VERIFICATION TEST (CRITICAL!)


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\.claude\templates\coding_prompt.template.md has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
    36→
    37→Understanding the `app_spec.txt` is critical - it contains the full requirements
    38→for the application you're building.
    39→
    40→### STEP 2: START SERVERS (IF NOT RUNNING)
    41→
    42→If `init.sh` exists, run it:
    43→
    44→```bash
    45→chmod +x init.sh
    46→./init.sh
    47→```
    48→
    49→Otherwise, start servers manually and document the process.
    50→
    51→### STEP 2.5: PROCESS MANAGEMENT (AUTOMATED CLEANUP)
    52→
    53→**Process tracking is now AUTOMATIC** - dev servers and build processes are killed automatically when you mark a feature as passing.
    54→
    55→**You usually don't need to manually manage processes**, but these tools are available if needed:
    56→
    57→Available tools:
    58→- `project_processes_status` - Check what processes are running
    59→- `project_processes_cleanup` - Manually kill processes by category
    60→- `project_process_register` - Register a background process for tracking (rarely needed)
    61→
    62→**Automatic cleanup happens:**
    63→- When you call `feature_mark_passing` - dev servers and builds are killed
    64→- Database processes are kept running (don't kill between features)
    65→
    66→**Manual cleanup when needed:**
    67→```
    68→# Check what's running
    69→Use project_processes_status
    70→
    71→# Kill dev servers before restarting
    72→Use project_processes_cleanup with category="dev_server"
    73→```
    74→
    75→**When to manually register processes:**
    76→If you start a background process with `&`, optionally register it:
    77→```bash
    78→npm run dev &
    79→DEV_PID=$!
    80→```
    81→Then use: `project_process_register` with pid=$DEV_PID and command="npm run dev"
    82→
    83→But this is **optional** - the automatic cleanup on feature_mark_passing handles most cases.
    84→
    85→### STEP 3: VERIFICATION TEST (CRITICAL!)
    86→
    87→**MANDATORY BEFORE NEW WORK:**
    88→
    89→The previous session may have introduced bugs. Before implementing anything

### STEP 2: START SERVERS (IF NOT RUNNING)

If `init.sh` exists, run it:

```bash
chmod +x init.sh
./init.sh
```

Otherwise, start servers manually and document the process.

### STEP 2.5: PROCESS MANAGEMENT (AUTOMATED CLEANUP)

**Process tracking is now AUTOMATIC** - dev servers and build processes are killed automatically when you mark a feature as passing.

**You usually don't need to manually manage processes**, but these tools are available if needed:

Available tools:
- `project_processes_status` - Check what processes are running
- `project_processes_cleanup` - Manually kill processes by category
- `project_process_register` - Register a background process for tracking (rarely needed)

**Automatic cleanup happens:**
- When you call `feature_mark_passing` - dev servers and builds are killed
- Database processes are kept running (don't kill between features)

**Manual cleanup when needed:**
```
# Check what's running
Use project_processes_status

# Kill dev servers before restarting
Use project_processes_cleanup with category="dev_server"
```

**When to manually register processes:**
If you start a background process with `&`, optionally register it:
```bash
npm run dev &
DEV_PID=$!
```
Then use: `project_process_register` with pid=$DEV_PID and command="npm run dev"

But this is **optional** - the automatic cleanup on feature_mark_passing handles most cases.

### STEP 3: VERIFICATION TEST (CRITICAL!)

## YOUR ROLE - CODING AGENT

You are continuing work on a long-running autonomous development task.
This is a FRESH context window - you have no memory of previous sessions.

### STEP 1: GET YOUR BEARINGS (MANDATORY)

Start by orienting yourself:

```bash
# 1. See your working directory
pwd

# 2. List files to understand project structure
ls -la

# 3. Read the project specification to understand what you're building
cat app_spec.txt

# 4. Read progress notes from previous sessions
cat claude-progress.txt

# 5. Check recent git history
git log --oneline -20
```

Then use MCP tools to check feature status:

```
# 6. Get progress statistics (passing/total counts)
Use the feature_get_stats tool

# 7. Get the next feature to work on
Use the feature_get_next tool
```

Understanding the `app_spec.txt` is critical - it contains the full requirements
for the application you're building.

### STEP 2: START SERVERS (IF NOT RUNNING)

If `init.sh` exists, run it:

```bash
chmod +x init.sh
./init.sh
```

Otherwise, start servers manually and document the process.

### STEP 3: VERIFICATION TEST (CRITICAL!)

**MANDATORY BEFORE NEW WORK:**

The previous session may have introduced bugs. Before implementing anything
new, you MUST run verification tests.

Run 1-2 of the features marked as passing that are most core to the app's functionality to verify they still work.

To get passing features for regression testing:

```
Use the feature_get_for_regression tool (returns up to 3 random passing features)
```

For example, if this were a chat app, you should perform a test that logs into the app, sends a message, and gets a response.

**If you find ANY issues (functional or visual):**

- Mark that feature as "passes": false immediately
- Add issues to a list
- Fix all issues BEFORE moving to new features
- This includes UI bugs like:
  - White-on-white text or poor contrast
  - Random characters displayed
  - Incorrect timestamps
  - Layout issues or overflow
  - Buttons too close together
  - Missing hover states
  - Console errors

### STEP 4: CHOOSE ONE FEATURE TO IMPLEMENT

#### TEST-DRIVEN DEVELOPMENT MINDSET (CRITICAL)

Features are **test cases** that drive development. This is test-driven development:

- **If you can't test a feature because functionality doesn't exist → BUILD IT**
- You are responsible for implementing ALL required functionality
- Never assume another process will build it later
- "Missing functionality" is NOT a blocker - it's your job to create it

**Example:** Feature says "User can filter flashcards by difficulty level"
- WRONG: "Flashcard page doesn't exist yet" → skip feature
- RIGHT: "Flashcard page doesn't exist yet" → build flashcard page → implement filter → test feature

Get the next feature to implement:

```
# Get the highest-priority pending feature
Use the feature_get_next tool
```

Once you've retrieved the feature, **immediately mark it as in-progress**:

```
# Mark feature as in-progress to prevent other sessions from working on it
Use the feature_mark_in_progress tool with feature_id=42
```

Focus on completing one feature perfectly and completing its testing steps in this session before moving on to other features.
It's ok if you only complete one feature in this session, as there will be more sessions later that continue to make progress.

#### When to Skip a Feature (EXTREMELY RARE)

**Skipping should almost NEVER happen.** Only skip for truly external blockers you cannot control:

- **External API not configured**: Third-party service credentials missing (e.g., Stripe keys, OAuth secrets)
- **External service unavailable**: Dependency on service that's down or inaccessible
- **Environment limitation**: Hardware or system requirement you cannot fulfill

**NEVER skip because:**

| Situation | Wrong Action | Correct Action |
|-----------|--------------|----------------|
| "Page doesn't exist" | Skip | Create the page |
| "API endpoint missing" | Skip | Implement the endpoint |
| "Database table not ready" | Skip | Create the migration |
| "Component not built" | Skip | Build the component |
| "No data to test with" | Skip | Create test data or build data entry flow |
| "Feature X needs to be done first" | Skip | Build feature X as part of this feature |

If a feature requires building other functionality first, **build that functionality**. You are the coding agent - your job is to make the feature work, not to defer it.

If you must skip (truly external blocker only):

```
Use the feature_skip tool with feature_id={id}
```

Document the SPECIFIC external blocker in `claude-progress.txt`. "Functionality not built" is NEVER a valid reason.

### STEP 5: IMPLEMENT THE FEATURE

Implement the chosen feature thoroughly:

1. Write the code (frontend and/or backend as needed)
2. Test manually using browser automation (see Step 6)
3. Fix any issues discovered
4. Verify the feature works end-to-end

### STEP 6: VERIFY WITH BROWSER AUTOMATION

**CRITICAL:** You MUST verify features through the actual UI.

Use browser automation tools:

- Navigate to the app in a real browser
- Interact like a human user (click, type, scroll)
- Take screenshots at each step
- Verify both functionality AND visual appearance

**DO:**

- Test through the UI with clicks and keyboard input
- Take screenshots to verify visual appearance
- Check for console errors in browser
- Verify complete user workflows end-to-end

**DON'T:**

- Only test with curl commands (backend testing alone is insufficient)
- Use JavaScript evaluation to bypass UI (no shortcuts)
- Skip visual verification
- Mark tests passing without thorough verification

### STEP 6.5: MANDATORY VERIFICATION CHECKLIST (BEFORE MARKING ANY TEST PASSING)

**You MUST complete ALL of these checks before marking any feature as "passes": true**

#### Security Verification (for protected features)

- [ ] Feature respects user role permissions
- [ ] Unauthenticated access is blocked (redirects to login)
- [ ] API endpoint checks authorization (returns 401/403 appropriately)
- [ ] Cannot access other users' data by manipulating URLs

#### Real Data Verification (CRITICAL - NO MOCK DATA)

- [ ] Created unique test data via UI (e.g., "TEST_12345_VERIFY_ME")
- [ ] Verified the EXACT data I created appears in UI
- [ ] Refreshed page - data persists (proves database storage)
- [ ] Deleted the test data - verified it's gone everywhere
- [ ] NO unexplained data appeared (would indicate mock data)
- [ ] Dashboard/counts reflect real numbers after my changes

#### Navigation Verification

- [ ] All buttons on this page link to existing routes
- [ ] No 404 errors when clicking any interactive element
- [ ] Back button returns to correct previous page
- [ ] Related links (edit, view, delete) have correct IDs in URLs

#### Integration Verification

- [ ] Console shows ZERO JavaScript errors
- [ ] Network tab shows successful API calls (no 500s)
- [ ] Data returned from API matches what UI displays
- [ ] Loading states appeared during API calls
- [ ] Error states handle failures gracefully

### STEP 6.6: MOCK DATA DETECTION SWEEP

**Run this sweep AFTER EVERY FEATURE before marking it as passing:**

#### 1. Code Pattern Search

Search the codebase for forbidden patterns:

```bash
# Search for mock data patterns
grep -r "mockData\|fakeData\|sampleData\|dummyData\|testData" --include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx"
grep -r "// TODO\|// FIXME\|// STUB\|// MOCK" --include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx"
grep -r "hardcoded\|placeholder" --include="*.js" --include="*.ts" --include="*.jsx" --include="*.tsx"
```

**If ANY matches found related to your feature - FIX THEM before proceeding.**

#### 2. Runtime Verification

For ANY data displayed in UI:

1. Create NEW data with UNIQUE content (e.g., "TEST_12345_DELETE_ME")
2. Verify that EXACT content appears in the UI
3. Delete the record
4. Verify it's GONE from the UI
5. **If you see data that wasn't created during testing - IT'S MOCK DATA. Fix it.**

#### 3. Database Verification

Check that:

- Database tables contain only data you created during tests
- Counts/statistics match actual database record counts
- No seed data is masquerading as user data

#### 4. API Response Verification

For API endpoints used by this feature:

- Call the endpoint directly
- Verify response contains actual database data
- Empty database = empty response (not pre-populated mock data)

### STEP 7: UPDATE FEATURE STATUS (CAREFULLY!)

**YOU CAN ONLY MODIFY ONE FIELD: "passes"**

After thorough verification, mark the feature as passing:

```
# Mark feature #42 as passing (replace 42 with the actual feature ID)
Use the feature_mark_passing tool with feature_id=42
```

**NEVER:**

- Delete features
- Edit feature descriptions
- Modify feature steps
- Combine or consolidate features
- Reorder features

**ONLY MARK A FEATURE AS PASSING AFTER VERIFICATION WITH SCREENSHOTS.**

### STEP 8: COMMIT YOUR PROGRESS

Make a descriptive git commit:

```bash
git add .
git commit -m "Implement [feature name] - verified end-to-end

- Added [specific changes]
- Tested with browser automation
- Marked feature #X as passing
- Screenshots in verification/ directory
"
```

### STEP 9: UPDATE PROGRESS NOTES

Update `claude-progress.txt` with:

- What you accomplished this session
- Which test(s) you completed
- Any issues discovered or fixed
- What should be worked on next
- Current completion status (e.g., "45/200 tests passing")

### STEP 10: END SESSION CLEANLY

Before context fills up:

1. Commit all working code
2. Update claude-progress.txt
3. Mark features as passing if tests verified
4. Ensure no uncommitted changes
5. Leave app in working state (no broken features)

---

## TESTING REQUIREMENTS

**ALL testing must use browser automation tools.**

Available tools:

**Navigation & Screenshots:**

- browser_navigate - Navigate to a URL
- browser_navigate_back - Go back to previous page
- browser_take_screenshot - Capture screenshot (use for visual verification)
- browser_snapshot - Get accessibility tree snapshot (structured page data)

**Element Interaction:**

- browser_click - Click elements (has built-in auto-wait)
- browser_type - Type text into editable elements
- browser_fill_form - Fill multiple form fields at once
- browser_select_option - Select dropdown options
- browser_hover - Hover over elements
- browser_drag - Drag and drop between elements
- browser_press_key - Press keyboard keys

**Debugging & Monitoring:**

- browser_console_messages - Get browser console output (check for errors)
- browser_network_requests - Monitor API calls and responses
- browser_evaluate - Execute JavaScript (USE SPARINGLY - debugging only, NOT for bypassing UI)

**Browser Management:**

- browser_close - Close the browser
- browser_resize - Resize browser window (use to test mobile: 375x667, tablet: 768x1024, desktop: 1280x720)
- browser_tabs - Manage browser tabs
- browser_wait_for - Wait for text/element/time
- browser_handle_dialog - Handle alert/confirm dialogs
- browser_file_upload - Upload files

**Key Benefits:**

- All interaction tools have **built-in auto-wait** - no manual timeouts needed
- Use `browser_console_messages` to detect JavaScript errors
- Use `browser_network_requests` to verify API calls succeed

Test like a human user with mouse and keyboard. Don't take shortcuts by using JavaScript evaluation.

---

## FEATURE TOOL USAGE RULES (CRITICAL - DO NOT VIOLATE)

The feature tools exist to reduce token usage. **DO NOT make exploratory queries.**

### ALLOWED Feature Tools (ONLY these):

```
# 1. Get progress stats (passing/in_progress/total counts)
feature_get_stats

# 2. Get the NEXT feature to work on (one feature only)
feature_get_next

# 3. Mark a feature as in-progress (call immediately after feature_get_next)
feature_mark_in_progress with feature_id={id}

# 4. Get up to 3 random passing features for regression testing
feature_get_for_regression

# 5. Mark a feature as passing (after verification)
feature_mark_passing with feature_id={id}

# 6. Skip a feature (moves to end of queue) - ONLY when blocked by dependency
feature_skip with feature_id={id}

# 7. Clear in-progress status (when abandoning a feature)
feature_clear_in_progress with feature_id={id}
```

### RULES:

- Do NOT try to fetch lists of all features
- Do NOT query features by category
- Do NOT list all pending features

**You do NOT need to see all features.** The feature_get_next tool tells you exactly what to work on. Trust it.

---

## EMAIL INTEGRATION (DEVELOPMENT MODE)

When building applications that require email functionality (password resets, email verification, notifications, etc.), you typically won't have access to a real email service or the ability to read email inboxes.

**Solution:** Configure the application to log emails to the terminal instead of sending them.

- Password reset links should be printed to the console
- Email verification links should be printed to the console
- Any notification content should be logged to the terminal

**During testing:**

1. Trigger the email action (e.g., click "Forgot Password")
2. Check the terminal/server logs for the generated link
3. Use that link directly to verify the functionality works

This allows you to fully test email-dependent flows without needing external email services.

---

## IMPORTANT REMINDERS

**Your Goal:** Production-quality application with all tests passing

**This Session's Goal:** Complete at least one feature perfectly

**Priority:** Fix broken tests before implementing new features

**Quality Bar:**

- Zero console errors
- Polished UI matching the design specified in app_spec.txt
- All features work end-to-end through the UI
- Fast, responsive, professional
- **NO MOCK DATA - all data from real database**
- **Security enforced - unauthorized access blocked**
- **All navigation works - no 404s or broken links**

**You have unlimited time.** Take as long as needed to get it right. The most important thing is that you
leave the code base in a clean state before terminating the session (Step 10).

---

Begin by running Step 1 (Get Your Bearings).

+### STEP 2.5: PROCESS MANAGEMENT (AUTOMATED CLEANUP)

+**Process tracking is now AUTOMATIC** - dev servers and build processes are killed automatically when you mark a feature as passing.

+**You usually don't need to manually manage processes**, but these tools are available if needed:

+- `project_processes_status` - Check what processes are running

+- `project_processes_cleanup` - Manually kill processes by category

+- `project_process_register` - Register a background process for tracking (rarely needed)

+- When you call `feature_mark_passing` - dev servers and builds are killed

+- Database processes are kept running (don't kill between features)

+Use project_processes_cleanup with category="dev_server"

+**When to manually register processes:**

+If you start a background process with `&`, optionally register it:

+Then use: `project_process_register` with pid=$DEV_PID and command="npm run dev"

+But this is **optional** - the automatic cleanup on feature_mark_passing handles most cases.


Good! Now I need to add session-end cleanup to agent.py. Let me find a good spot to add this cleanup when sessions end.


TodoWrite

Create ProjectProcessTracker class to monitor and manage project processes

Integrate tracker with MCP Feature server for cleanup hooks

Integrating tracker with MCP Feature server

Update coding prompt to include process cleanup instructions

Add session-end cleanup to agent.py

Test the process tracking and cleanup system


Add session-end cleanup to agent.py

Add session-end cleanup to agent.py


Now let me add session-end cleanup to agent.py. I'll add it at the end of the autonomous loop when it's completing.


Edit

    # Final summary
    print("\n" + "=" * 70)
    print("  SESSION COMPLETE")
    print("=" * 70)
    print(f"\nProject directory: {project_dir}")
    print_progress_summary(project_dir)

    # Print instructions for running the generated application
    print("\n" + "-" * 70)
    print("  TO RUN THE GENERATED APPLICATION:")
    print("-" * 70)
    print(f"\n  cd {project_dir.resolve()}")
    print("  ./init.sh           # Run the setup script")
    print("  # Or manually:")
    print("  npm install && npm run dev")
    print("\n  Then open http://localhost:3000 (or check init.sh for the URL)")
    print("-" * 70)

    print("\nDone!")

    # Clean up project processes before ending
    print("\n" + "=" * 70)
    print("  CLEANING UP PROCESSES")
    print("=" * 70)
    try:
        from server.services.project_process_tracker import get_tracker
        tracker = get_tracker(project_dir)
        cleanup_results = tracker.cleanup_all()
        total_killed = cleanup_results.get("total_killed", 0)
        if total_killed > 0:
            print(f"Killed {total_killed} project process(es)")
            for category, results in cleanup_results.get("by_category", {}).items():
                killed = results.get("killed", 0)
                if killed > 0:
                    print(f"  - {category}: {killed} process(es)")
        else:
            print("No project processes to clean up")
    except ImportError:
        print("Process tracker not available (running in standalone mode)")
    except Exception as e:
        print(f"Warning: Process cleanup failed: {e}")

    # Final summary
    print("\n" + "=" * 70)
    print("  SESSION COMPLETE")
    print("=" * 70)
    print(f"\nProject directory: {project_dir}")
    print_progress_summary(project_dir)

    # Print instructions for running the generated application
    print("\n" + "-" * 70)
    print("  TO RUN THE GENERATED APPLICATION:")
    print("-" * 70)
    print(f"\n  cd {project_dir.resolve()}")
    print("  ./init.sh           # Run the setup script")
    print("  # Or manually:")
    print("  npm install && npm run dev")
    print("\n  Then open http://localhost:3000 (or check init.sh for the URL)")
    print("-" * 70)

    print("\nDone!")


The file C:\Users\MikeSwanson\claude-projects\Autocode-remix\Autocoder-2.0\agent.py has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
   207→        if max_iterations is None or iteration < max_iterations:
   208→            print("\nPreparing next session...\n")
   209→            await asyncio.sleep(1)
   210→
   211→    # Clean up project processes before ending
   212→    print("\n" + "=" * 70)
   213→    print("  CLEANING UP PROCESSES")
   214→    print("=" * 70)
   215→    try:
   216→        from server.services.project_process_tracker import get_tracker
   217→        tracker = get_tracker(project_dir)
   218→        cleanup_results = tracker.cleanup_all()
   219→        total_killed = cleanup_results.get("total_killed", 0)
   220→        if total_killed > 0:
   221→            print(f"Killed {total_killed} project process(es)")
   222→            for category, results in cleanup_results.get("by_category", {}).items():
   223→                killed = results.get("killed", 0)
   224→                if killed > 0:
   225→                    print(f"  - {category}: {killed} process(es)")
   226→        else:
   227→            print("No project processes to clean up")
   228→    except ImportError:
   229→        print("Process tracker not available (running in standalone mode)")
   230→    except Exception as e:
   231→        print(f"Warning: Process cleanup failed: {e}")
   232→
   233→    # Final summary
   234→    print("\n" + "=" * 70)
   235→    print("  SESSION COMPLETE")
   236→    print("=" * 70)
   237→    print(f"\nProject directory: {project_dir}")
   238→    print_progress_summary(project_dir)
   239→
   240→    # Print instructions for running the generated application
   241→    print("\n" + "-" * 70)
   242→    print("  TO RUN THE GENERATED APPLICATION:")
   243→    print("-" * 70)
   244→    print(f"\n  cd {project_dir.resolve()}")
   245→    print("  ./init.sh           # Run the setup script")
   246→    print("  # Or manually:")
   247→    print("  npm install && npm run dev")
   248→    print("\n  Then open http://localhost:3000 (or check init.sh for the URL)")
   249→    print("-" * 70)
   250→
   251→    print("\nDone!")
   252→

    # Clean up project processes before ending
    print("\n" + "=" * 70)
    print("  CLEANING UP PROCESSES")
    print("=" * 70)
    try:
        from server.services.project_process_tracker import get_tracker
        tracker = get_tracker(project_dir)
        cleanup_results = tracker.cleanup_all()
        total_killed = cleanup_results.get("total_killed", 0)
        if total_killed > 0:
            print(f"Killed {total_killed} project process(es)")
            for category, results in cleanup_results.get("by_category", {}).items():
                killed = results.get("killed", 0)
                if killed > 0:
                    print(f"  - {category}: {killed} process(es)")
        else:
            print("No project processes to clean up")
    except ImportError:
        print("Process tracker not available (running in standalone mode)")
    except Exception as e:
        print(f"Warning: Process cleanup failed: {e}")

    # Final summary
    print("\n" + "=" * 70)
    print("  SESSION COMPLETE")
    print("=" * 70)
    print(f"\nProject directory: {project_dir}")
    print_progress_summary(project_dir)

    # Print instructions for running the generated application
    print("\n" + "-" * 70)
    print("  TO RUN THE GENERATED APPLICATION:")
    print("-" * 70)
    print(f"\n  cd {project_dir.resolve()}")
    print("  ./init.sh           # Run the setup script")
    print("  # Or manually:")
    print("  npm install && npm run dev")
    print("\n  Then open http://localhost:3000 (or check init.sh for the URL)")
    print("-" * 70)

    print("\nDone!")

"""
Agent Session Logic
===================

Core agent interaction functions for running autonomous coding sessions.
"""

import asyncio
import io
import sys
from pathlib import Path
from typing import Optional

from claude_agent_sdk import ClaudeSDKClient

# Fix Windows console encoding for Unicode characters (emoji, etc.)
# Without this, print() crashes when Claude outputs emoji like ✅
if sys.platform == "win32":
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
    sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')

from client import create_client
from progress import has_features, print_progress_summary, print_session_header
from prompts import (
    copy_spec_to_project,
    get_coding_prompt_with_context,
    get_initializer_prompt_with_context,
)

# Configuration
AUTO_CONTINUE_DELAY_SECONDS = 3

async def run_agent_session(
    client: ClaudeSDKClient,
    message: str,
    project_dir: Path,
) -> tuple[str, str]:
    """
    Run a single agent session using Claude Agent SDK.

    Args:
        client: Claude SDK client
        message: The prompt to send
        project_dir: Project directory path

    Returns:
        (status, response_text) where status is:
        - "continue" if agent should continue working
        - "error" if an error occurred
    """
    print("Sending prompt to Claude Agent SDK...\n")

    try:
        # Send the query
        await client.query(message)

        # Collect response text and show tool use
        response_text = ""
        async for msg in client.receive_response():
            msg_type = type(msg).__name__

            # Handle AssistantMessage (text and tool use)
            if msg_type == "AssistantMessage" and hasattr(msg, "content"):
                for block in msg.content:
                    block_type = type(block).__name__

                    if block_type == "TextBlock" and hasattr(block, "text"):
                        response_text += block.text
                        print(block.text, end="", flush=True)
                    elif block_type == "ToolUseBlock" and hasattr(block, "name"):
                        print(f"\n[Tool: {block.name}]", flush=True)
                        if hasattr(block, "input"):
                            input_str = str(block.input)
                            if len(input_str) > 200:
                                print(f"   Input: {input_str[:200]}...", flush=True)
                            else:
                                print(f"   Input: {input_str}", flush=True)

            # Handle UserMessage (tool results)
            elif msg_type == "UserMessage" and hasattr(msg, "content"):
                for block in msg.content:
                    block_type = type(block).__name__

                    if block_type == "ToolResultBlock":
                        result_content = getattr(block, "content", "")
                        is_error = getattr(block, "is_error", False)

                        # Check if command was blocked by security hook
                        if "blocked" in str(result_content).lower():
                            print(f"   [BLOCKED] {result_content}", flush=True)
                        elif is_error:
                            # Show errors (truncated)
                            error_str = str(result_content)[:500]
                            print(f"   [Error] {error_str}", flush=True)
                        else:
                            # Tool succeeded - just show brief confirmation
                            print("   [Done]", flush=True)

        print("\n" + "-" * 70 + "\n")
        return "continue", response_text

    except Exception as e:
        print(f"Error during agent session: {e}")
        return "error", str(e)

async def run_autonomous_agent(
    project_dir: Path,
    model: str,
    max_iterations: Optional[int] = None,
    yolo_mode: bool = False,
) -> None:
    """
    Run the autonomous agent loop.

    Args:
        project_dir: Directory for the project
        model: Claude model to use
        max_iterations: Maximum number of iterations (None for unlimited)
        yolo_mode: If True, skip browser testing and use YOLO prompt
    """
    print("\n" + "=" * 70)
    print("  AUTONOMOUS CODING AGENT DEMO")
    print("=" * 70)
    print(f"\nProject directory: {project_dir}")
    print(f"Model: {model}")
    if yolo_mode:
        print("Mode: YOLO (testing disabled)")
    else:
        print("Mode: Standard (full testing)")
    if max_iterations:
        print(f"Max iterations: {max_iterations}")
    else:
        print("Max iterations: Unlimited (will run until completion)")
    print()

    # Create project directory
    project_dir.mkdir(parents=True, exist_ok=True)

    # Check if this is a fresh start or continuation
    # Uses has_features() which checks if the database actually has features,
    # not just if the file exists (empty db should still trigger initializer)
    is_first_run = not has_features(project_dir)

    if is_first_run:
        print("Fresh start - will use initializer agent")
        print()
        print("=" * 70)
        print("  NOTE: First session takes 10-20+ minutes!")
        print("  The agent is generating 200 detailed test cases.")
        print("  This may appear to hang - it's working. Watch for [Tool: ...] output.")
        print("=" * 70)
        print()
        # Copy the app spec into the project directory for the agent to read
        copy_spec_to_project(project_dir)
    else:
        print("Continuing existing project")
        print_progress_summary(project_dir)

    # Main loop
    iteration = 0

    while True:
        iteration += 1

        # Check max iterations
        if max_iterations and iteration > max_iterations:
            print(f"\nReached max iterations ({max_iterations})")
            print("To continue, run the script again without --max-iterations")
            break

        # Print session header
        print_session_header(iteration, is_first_run)

        # Create client (fresh context)
        client = create_client(project_dir, model, yolo_mode=yolo_mode)

        # Choose prompt based on session type
        # Pass project_dir to enable project-specific prompts
        if is_first_run:
            prompt = get_initializer_prompt_with_context(project_dir.name, project_dir)
            is_first_run = False  # Only use initializer once
        else:
            # Use YOLO prompt if in YOLO mode
            if yolo_mode:
                prompt = get_coding_prompt_with_context(project_dir.name, project_dir, yolo_mode=True)
            else:
                prompt = get_coding_prompt_with_context(project_dir.name, project_dir)

        # Run session with async context manager
        async with client:
            status, response = await run_agent_session(client, prompt, project_dir)

        # Handle status
        if status == "continue":
            print(f"\nAgent will auto-continue in {AUTO_CONTINUE_DELAY_SECONDS}s...")
            print_progress_summary(project_dir)
            await asyncio.sleep(AUTO_CONTINUE_DELAY_SECONDS)

        elif status == "error":
            print("\nSession encountered an error")
            print("Will retry with a fresh session...")
            await asyncio.sleep(AUTO_CONTINUE_DELAY_SECONDS)

        # Small delay between sessions
        if max_iterations is None or iteration < max_iterations:
            print("\nPreparing next session...\n")
            await asyncio.sleep(1)

    # Final summary
    print("\n" + "=" * 70)
    print("  SESSION COMPLETE")
    print("=" * 70)
    print(f"\nProject directory: {project_dir}")
    print_progress_summary(project_dir)

    # Print instructions for running the generated application
    print("\n" + "-" * 70)
    print("  TO RUN THE GENERATED APPLICATION:")
    print("-" * 70)
    print(f"\n  cd {project_dir.resolve()}")
    print("  ./init.sh           # Run the setup script")
    print("  # Or manually:")
    print("  npm install && npm run dev")
    print("\n  Then open http://localhost:3000 (or check init.sh for the URL)")
    print("-" * 70)

    print("\nDone!")

+    # Clean up project processes before ending

+        from server.services.project_process_tracker import get_tracker

+        tracker = get_tracker(project_dir)

+        cleanup_results = tracker.cleanup_all()

+        total_killed = cleanup_results.get("total_killed", 0)

+            print(f"Killed {total_killed} project process(es)")

+            for category, results in cleanup_results.get("by_category", {}).items():

+                killed = results.get("killed", 0)

+                    print(f"  - {category}: {killed} process(es)")

+            print("No project processes to clean up")

+        print("Process tracker not available (running in standalone mode)")

+        print(f"Warning: Process cleanup failed: {e}")


Great! Now I should also add cleanup to the process manager's stop method so that when the UI stops the agent, it also cleans up project processes. Let me update process_manager.py.


    async def stop(self) -> tuple[bool, str]:
        """
        Stop the agent and all child processes (MCP servers, etc).

        Returns:
            Tuple of (success, message)
        """
        if not self.process or self.status == "stopped":
            return False, "Agent is not running"

        try:
            # Cancel output streaming
            if self._output_task:
                self._output_task.cancel()
                try:
                    await self._output_task
                except asyncio.CancelledError:
                    pass

            # Kill entire process tree (agent + MCP servers + any children)
            pid = self.process.pid
            loop = asyncio.get_running_loop()
            killed = await loop.run_in_executor(
                None, self._kill_process_tree, pid, 5.0
            )

            logger.info(f"Killed {killed} processes in tree for {self.project_name}")

            self._remove_lock()
            self.status = "stopped"
            self.process = None
            self.started_at = None
            self.yolo_mode = False  # Reset YOLO mode

            return True, f"Agent stopped ({killed} processes terminated)"
        except Exception as e:
            logger.exception("Failed to stop agent")
            return False, f"Failed to stop agent: {e}"

    async def stop(self) -> tuple[bool, str]:
        """
        Stop the agent and all child processes (MCP servers, etc).

        Returns:
            Tuple of (success, message)
        """
        if not self.process or self.status == "stopped":
            return False, "Agent is not running"

        try:
            # Cancel output streaming
            if self._output_task:
                self._output_task.cancel()
                try:
                    await self._output_task
                except asyncio.CancelledError:
                    pass

            # Kill entire process tree (agent + MCP servers + any children)
            pid = self.process.pid
            loop = asyncio.get_running_loop()
            killed = await loop.run_in_executor(
                None, self._kill_process_tree, pid, 5.0
            )

            logger.info(f"Killed {killed} processes in tree for {self.project_name}")

            # Also clean up project processes (dev servers, etc.)
            project_processes_killed = 0
            try:
                from server.services.project_process_tracker import get_tracker
                tracker = get_tracker(self.project_dir)
                cleanup_results = tracker.cleanup_all()
                project_processes_killed = cleanup_results.get("total_killed", 0)
                if project_processes_killed > 0:
                    logger.info(f"Killed {project_processes_killed} project processes")
            except ImportError:
                pass  # Tracker not available in standalone mode
            except Exception as e:
                logger.warning(f"Project process cleanup failed: {e}")

            self._remove_lock()
            self.status = "stopped"
            self.process = None
            self.started_at = None
            self.yolo_mode = False  # Reset YOLO mode

            total_killed = killed + project_processes_killed
            return True, f"Agent stopped ({total_killed} total processes terminated)"
        except Exception as e:
            logger.exception("Failed to stop agent")
            return False, f"Failed to stop agent: {e}"