feat: Add GitHub Actions CI for PR protection

- Add CI workflow with Python (ruff lint, security tests) and UI (ESLint, TypeScript, build) jobs
- Add ruff, mypy, pytest to requirements.txt
- Add pyproject.toml with ruff configuration
- Fix import sorting across Python files (ruff --fix)
- Fix test_security.py expectations to match actual security policy
- Remove invalid 'eof' command from ALLOWED_COMMANDS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-07 10:35:19 +02:00
parent 17b7354db8
commit 122f03dc21
28 changed files with 144 additions and 85 deletions

View File

@@ -5,12 +5,12 @@ API Routers
FastAPI routers for different API endpoints.
"""
from .projects import router as projects_router
from .features import router as features_router
from .agent import router as agent_router
from .spec_creation import router as spec_creation_router
from .filesystem import router as filesystem_router
from .assistant_chat import router as assistant_chat_router
from .features import router as features_router
from .filesystem import router as filesystem_router
from .projects import router as projects_router
from .spec_creation import router as spec_creation_router
__all__ = [
"projects_router",

View File

@@ -11,7 +11,7 @@ from pathlib import Path
from fastapi import APIRouter, HTTPException
from ..schemas import AgentStatus, AgentActionResponse, AgentStartRequest
from ..schemas import AgentActionResponse, AgentStartRequest, AgentStatus
from ..services.process_manager import get_manager

View File

@@ -11,21 +11,21 @@ import re
from pathlib import Path
from typing import Optional
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, HTTPException
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
from pydantic import BaseModel
from ..services.assistant_chat_session import (
AssistantChatSession,
get_session,
create_session,
remove_session,
get_session,
list_sessions,
remove_session,
)
from ..services.assistant_database import (
get_conversations,
get_conversation,
delete_conversation,
create_conversation,
delete_conversation,
get_conversation,
get_conversations,
)
logger = logging.getLogger(__name__)

View File

@@ -5,17 +5,17 @@ Features Router
API endpoints for feature/test case management.
"""
import re
import logging
from pathlib import Path
import re
from contextlib import contextmanager
from pathlib import Path
from fastapi import APIRouter, HTTPException
from ..schemas import (
FeatureCreate,
FeatureResponse,
FeatureListResponse,
FeatureResponse,
)
# Lazy imports to avoid circular dependencies
@@ -45,7 +45,7 @@ def _get_db_classes():
root = Path(__file__).parent.parent.parent
if str(root) not in sys.path:
sys.path.insert(0, str(root))
from api.database import create_database, Feature
from api.database import Feature, create_database
_create_database = create_database
_Feature = Feature
return _create_database, _Feature
@@ -110,7 +110,7 @@ async def list_features(project_name: str):
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=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
db_file = project_dir / "features.db"
if not db_file.exists():
@@ -142,7 +142,7 @@ async def list_features(project_name: str):
)
except HTTPException:
raise
except Exception as e:
except Exception:
logger.exception("Database error in list_features")
raise HTTPException(status_code=500, detail="Database error occurred")
@@ -157,7 +157,7 @@ async def create_feature(project_name: str, feature: FeatureCreate):
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=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
_, Feature = _get_db_classes()
@@ -187,7 +187,7 @@ async def create_feature(project_name: str, feature: FeatureCreate):
return feature_to_response(db_feature)
except HTTPException:
raise
except Exception as e:
except Exception:
logger.exception("Failed to create feature")
raise HTTPException(status_code=500, detail="Failed to create feature")
@@ -202,7 +202,7 @@ async def get_feature(project_name: str, feature_id: int):
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=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
db_file = project_dir / "features.db"
if not db_file.exists():
@@ -220,7 +220,7 @@ async def get_feature(project_name: str, feature_id: int):
return feature_to_response(feature)
except HTTPException:
raise
except Exception as e:
except Exception:
logger.exception("Database error in get_feature")
raise HTTPException(status_code=500, detail="Database error occurred")
@@ -235,7 +235,7 @@ async def delete_feature(project_name: str, feature_id: int):
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=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
_, Feature = _get_db_classes()
@@ -252,7 +252,7 @@ async def delete_feature(project_name: str, feature_id: int):
return {"success": True, "message": f"Feature {feature_id} deleted"}
except HTTPException:
raise
except Exception as e:
except Exception:
logger.exception("Failed to delete feature")
raise HTTPException(status_code=500, detail="Failed to delete feature")
@@ -272,7 +272,7 @@ async def skip_feature(project_name: str, feature_id: int):
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=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
_, Feature = _get_db_classes()
@@ -292,6 +292,6 @@ async def skip_feature(project_name: str, feature_id: int):
return {"success": True, "message": f"Feature {feature_id} moved to end of queue"}
except HTTPException:
raise
except Exception as e:
except Exception:
logger.exception("Failed to skip feature")
raise HTTPException(status_code=500, detail="Failed to skip feature")

View File

@@ -18,14 +18,13 @@ from fastapi import APIRouter, HTTPException, Query
logger = logging.getLogger(__name__)
from ..schemas import (
CreateDirectoryRequest,
DirectoryEntry,
DirectoryListResponse,
DriveInfo,
PathValidationResponse,
CreateDirectoryRequest,
)
router = APIRouter(prefix="/api/filesystem", tags=["filesystem"])

View File

@@ -14,11 +14,11 @@ from fastapi import APIRouter, HTTPException
from ..schemas import (
ProjectCreate,
ProjectSummary,
ProjectDetail,
ProjectPrompts,
ProjectPromptsUpdate,
ProjectStats,
ProjectSummary,
)
# Lazy imports to avoid circular dependencies
@@ -43,8 +43,8 @@ def _init_imports():
if str(root) not in sys.path:
sys.path.insert(0, str(root))
from prompts import scaffold_project_prompts, get_project_prompts_dir
from progress import count_passing_tests
from prompts import get_project_prompts_dir, scaffold_project_prompts
from start import check_spec_exists
_check_spec_exists = check_spec_exists
@@ -62,10 +62,10 @@ def _get_registry_functions():
sys.path.insert(0, str(root))
from registry import (
register_project,
unregister_project,
get_project_path,
list_registered_projects,
register_project,
unregister_project,
validate_project_path,
)
return register_project, unregister_project, get_project_path, list_registered_projects, validate_project_path
@@ -272,7 +272,7 @@ async def get_project_prompts(name: str):
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
if not project_dir.exists():
raise HTTPException(status_code=404, detail=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
prompts_dir = _get_project_prompts_dir(project_dir)
@@ -305,7 +305,7 @@ async def update_project_prompts(name: str, prompts: ProjectPromptsUpdate):
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
if not project_dir.exists():
raise HTTPException(status_code=404, detail=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
prompts_dir = _get_project_prompts_dir(project_dir)
prompts_dir.mkdir(parents=True, exist_ok=True)
@@ -335,6 +335,6 @@ async def get_project_stats_endpoint(name: str):
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
if not project_dir.exists():
raise HTTPException(status_code=404, detail=f"Project directory not found")
raise HTTPException(status_code=404, detail="Project directory not found")
return get_project_stats(project_dir)

View File

@@ -5,23 +5,22 @@ Spec Creation Router
WebSocket and REST endpoints for interactive spec creation with Claude.
"""
import asyncio
import json
import logging
import re
from pathlib import Path
from typing import Any, Optional
from typing import Optional
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, HTTPException
from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect
from pydantic import BaseModel, ValidationError
from ..schemas import ImageAttachment
from ..services.spec_chat_session import (
SpecChatSession,
get_session,
create_session,
remove_session,
get_session,
list_sessions,
remove_session,
)
logger = logging.getLogger(__name__)