PM Dashboard Development Patterns
This skill guides development on the PM Dashboard plugin codebase with established patterns, architecture, and testing requirements.
Architecture Overview
Database Layer (SQLite + WAL)
↓
Tools Layer (MCP tools via tools_lib/)
↓
MCP Server (FastMCP integration)
↓
API Layer (FastAPI + WebSocket)
↓
Frontend (Static HTML/CSS/JS)
For detailed architecture, see ARCHITECTURE.md.
Quick Start Commands
# Activate virtual environment source activate.sh # Install dependencies pip install -e ".[dev]" # Run tests python -m pytest test/project_manager/ -v # Run specific test file python -m pytest test/project_manager/test_database.py -v # Type checking mypy src/ # Code formatting black . # Lint code ruff check . # Start API server python -m task_manager.api # Start MCP server python -m task_manager.mcp_server
Project Structure
src/task_manager/ ├── database/ │ ├── connection.py # Database initialization & schema │ ├── tasks.py # Task CRUD operations │ ├── locks.py # Atomic task locking │ └── projects.py # Project/epic operations ├── tools_lib/ │ ├── base.py # BaseTool class │ ├── tasks.py # Task management tools │ ├── knowledge.py # Knowledge management tools │ └── assumptions.py # RA tag tools ├── routers/ │ ├── board.py # Board state endpoint │ ├── tasks.py # Task endpoints │ ├── projects.py # Project endpoints │ └── knowledge.py # Knowledge endpoints ├── api.py # FastAPI app + WebSocket manager ├── mcp_server.py # FastMCP server ├── models.py # Pydantic models └── static/ # Frontend assets
Adding New MCP Tools
See ADDING_MCP_TOOLS.md for complete step-by-step guide.
Quick Pattern
- •Create tool class in
tools_lib/:
from .base import BaseTool
class MyNewTool(BaseTool):
async def apply(self, param1: str, param2: Optional[int] = None) -> str:
"""Tool description."""
try:
# Implementation
return self._format_success_response(
"Operation successful",
result_data=data
)
except Exception as e:
return self._format_error_response("Operation failed", error_details=str(e))
- •Register in MCP server (
mcp_server.py):
@mcp.tool
async def my_new_tool(param1: str, param2: Optional[int] = None) -> str:
"""
Tool description for MCP clients.
Args:
param1: Description
param2: Description (optional)
Returns:
JSON string with results
"""
tool = MyNewTool(self.db, self.websocket_manager)
return await tool.apply(param1, param2)
- •Add tests in
test/project_manager/test_tools.py
Database Patterns
Status Vocabulary Mapping
UI Vocabulary (for frontend):
- •TODO
- •IN_PROGRESS
- •REVIEW
- •DONE
- •BACKLOG
Database Vocabulary (internal):
- •pending
- •in_progress
- •review
- •completed
- •backlog
Always map between these in API endpoints!
Atomic Operations
Prefer atomic patterns for concurrency safety:
# GOOD - Atomic single UPDATE
cursor.execute("""
UPDATE tasks SET lock_holder = ?, lock_expires_at = ?
WHERE id = ? AND (lock_holder IS NULL OR lock_expires_at < ?)
""", (agent_id, expires_at, task_id, current_time))
# BAD - Race condition risk
lock_status = get_lock_status(task_id)
if not lock_status["is_locked"]:
acquire_lock(task_id, agent_id) # Another agent could lock between these!
JSON Validation
All JSON fields have database-level validation:
CONSTRAINT json_ra_tags CHECK (
ra_tags IS NULL OR (
json_valid(ra_tags) AND
json_type(ra_tags) = 'array'
)
)
Ensure your code generates valid JSON!
WebSocket Broadcasting
from ..api import connection_manager
# Broadcast event to all connected clients
await connection_manager.optimized_broadcast({
"type": "task.status_changed",
"task_id": task_id,
"status": "IN_PROGRESS",
"agent_id": "claude"
})
Common event types:
- •
task.created - •
task.updated - •
task.status_changed - •
task.locked - •
task.unlocked - •
task_deleted - •
project_deleted - •
epic_deleted
Testing Requirements
See TESTING.md for comprehensive guide.
Before Every Commit
Run this test suite (all must pass):
# 1. Database tests python -m pytest test/project_manager/test_database.py -v # 2. API tests python -m pytest test/project_manager/test_api.py::TestBoardStateEndpoint -v # 3. MCP server tests python -m pytest test/project_manager/test_mcp_server.py -v # 4. Code formatting black --check . # 5. Type checking mypy src/
Test Script
Use the provided test runner:
bash scripts/run-full-tests.sh
Code Style
- •Black for formatting (line length: 100)
- •Type hints required for all functions
- •Docstrings for public APIs (Google style)
- •Pydantic models for validation
- •Async/await for I/O operations
Common Patterns
Error Handling in API Endpoints
try:
result = db.some_operation(param)
if result["success"]:
await connection_manager.broadcast({...})
return {"success": True, "data": result}
else:
raise HTTPException(status_code=400, detail=result["error"])
except HTTPException:
raise # Re-raise HTTP exceptions
except Exception as e:
logger.error(f"Operation failed: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
MCP Tool Response Format
# Success
return self._format_success_response(
"Task created successfully",
task_id=task_id,
task_name=name
)
# Error
return self._format_error_response(
"Task not found",
task_id=task_id
)
Parameter Validation (MCP Tools)
MCP tools receive string parameters only:
# Convert to appropriate types
task_id = int(task_id_str)
limit = int(limit_str) if limit_str else None
# Parse JSON strings
ra_tags = json.loads(ra_tags_str) if ra_tags_str else []
ra_metadata = json.loads(ra_metadata_str) if ra_metadata_str else {}
Security Considerations
- •SQL injection: Use parameterized queries (we do this everywhere)
- •Input validation: Pydantic models validate all API inputs
- •No authentication: This is a local-only tool (documented limitation)
- •JSON validation: Database constraints prevent invalid JSON
Performance Tips
- •Database: WAL mode enabled for concurrent reads/writes
- •Indexes: 25+ strategic indexes for common queries
- •WebSocket: Optimized parallel broadcasting with asyncio.gather
- •Caching: Connection manager caches connection stats
Common Pitfalls
❌ Wrong: Hardcoded IDs
db.get_task(42) # Fragile!
✅ Right: Dynamic IDs
task_id = result["task_id"] db.get_task(task_id)
❌ Wrong: Missing Status Mapping
return {"status": "pending"} # Frontend expects "TODO"!
✅ Right: Map to UI Vocabulary
status_map = {"pending": "TODO", "in_progress": "IN_PROGRESS"}
return {"status": status_map[db_status]}
❌ Wrong: Sync Database Call
def get_tasks(): # Blocking!
return db.get_all_tasks()
✅ Right: Async Pattern
async def get_tasks():
# Database operations are sync, but endpoint should be async
tasks = db.get_all_tasks()
return tasks
File Organization
- •Database code →
database/ - •MCP tools →
tools_lib/ - •API endpoints →
routers/ - •Tests →
test/project_manager/ - •Frontend →
static/
Keep related code together!
Versioning
Check pyproject.toml for current version. Update version when:
- •Adding new MCP tools (minor version bump)
- •Breaking API changes (major version bump)
- •Bug fixes (patch version bump)
Current: v0.2.2
Need Help?
- •Architecture questions → ARCHITECTURE.md
- •Adding tools → ADDING_MCP_TOOLS.md
- •Testing guide → TESTING.md
- •Run tests →
bash scripts/run-full-tests.sh