multi-project-workspace-spec.md•27.4 kB
# Multi-Project Workspace Support
## User Story
As a developer managing multiple concurrent projects with distinct goals and contexts, I need the ability to organize my work into separate project workspaces so that I can keep commission extraction vendor tracking completely separate from game development mechanics, prevent unrelated work items from cluttering my task lists, and maintain clear boundaries between different client deliverables or personal projects. Currently, everything lives in one global namespace, which means my EPSON invoice extractor status gets mixed up with my D&D character creation system, my commission comparison analytics work items appear alongside my vendor integration tasks, and there's no way to switch context when I need to focus on a specific project. I need to be able to create distinct projects, switch between them seamlessly, and ensure that when I'm working on "Commission Extraction," I only see vendors, tasks, code repositories, and deployments related to that work—not anything from my "TTRPG Game System" or "Commission Comparison Dashboard" projects. This separation isn't just about organization; it's about cognitive load management and preventing costly mistakes like deploying the wrong code or updating the wrong vendor configuration because I forgot which project context I was in.
---
## Current State Analysis
### What Exists Today
- Singleton project configuration (always id=1)
- Global namespace for all entities (work items, vendors, repositories, deployments)
- No concept of project boundaries
- Active context type tracking (feature/maintenance/research) but no project scope
### Problems This Creates
1. **Context Contamination:** EPSON vendors mixed with game mechanics modules
2. **Cognitive Overload:** All tasks from all projects appear in every list
3. **Error Prone:** Easy to modify wrong project's configuration
4. **Scalability Issues:** Can't manage portfolio of projects
5. **No Isolation:** Code search returns results across all projects indiscriminately
---
## Proposed Solution: Project Workspace Pattern
### Core Concept
Each project is a complete workspace with its own:
- Work items hierarchy (projects, sessions, tasks, research)
- Vendor tracking and status
- Code repositories and semantic search indices
- Deployment history
- Configuration settings
### Key Principles
1. **Strong Isolation:** Projects cannot interfere with each other
2. **Active Project Context:** One project is "active" at any time
3. **Easy Switching:** Single command to change project context
4. **Optional Override:** Can access any project explicitly when needed
5. **Portfolio View:** Can query across all projects for high-level reporting
---
## Database Schema Changes
### New Projects Table
```sql
CREATE TABLE projects (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(200) UNIQUE NOT NULL,
slug VARCHAR(200) UNIQUE NOT NULL, -- URL-friendly identifier
description TEXT,
project_type VARCHAR(50) NOT NULL, -- 'commission_extraction', 'game_dev', 'analytics', 'research'
-- Configuration
active_context_type VARCHAR(50) DEFAULT 'feature', -- Moved from project_configuration
default_token_budget INTEGER DEFAULT 200000,
-- Project metadata
metadata JSONB DEFAULT '{}',
-- Git integration
git_remote_url VARCHAR(500),
git_default_branch VARCHAR(200) DEFAULT 'main',
-- Lifecycle
status VARCHAR(50) DEFAULT 'active', -- 'active', 'archived', 'on_hold'
archived_at TIMESTAMPTZ,
-- Audit
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
created_by VARCHAR(100) DEFAULT 'system',
updated_by VARCHAR(100) DEFAULT 'system',
-- Constraints
CONSTRAINT valid_context_type CHECK (active_context_type IN ('feature', 'maintenance', 'research')),
CONSTRAINT valid_status CHECK (status IN ('active', 'archived', 'on_hold')),
CONSTRAINT valid_token_budget CHECK (default_token_budget BETWEEN 1000 AND 1000000)
);
CREATE INDEX idx_projects_status ON projects(status) WHERE status = 'active';
CREATE INDEX idx_projects_type ON projects(project_type);
CREATE INDEX idx_projects_slug ON projects(slug);
```
### Foreign Key Additions to Existing Tables
```sql
-- Work Items
ALTER TABLE work_items
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE INDEX idx_work_items_project ON work_items(project_id, status, updated_at DESC);
-- Vendors
ALTER TABLE vendors
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE UNIQUE INDEX idx_vendors_name_project ON vendors(name, project_id);
-- Note: Vendor names only need to be unique within a project
-- Repositories
ALTER TABLE repositories
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE INDEX idx_repositories_project ON repositories(project_id);
-- Code Chunks (for semantic search isolation)
ALTER TABLE code_chunks
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE INDEX idx_code_chunks_project ON code_chunks(project_id);
-- Deployments
ALTER TABLE deployment_events
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE INDEX idx_deployments_project ON deployment_events(project_id, deployed_at DESC);
-- Tasks (legacy table - if still in use)
ALTER TABLE tasks
ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE CASCADE;
CREATE INDEX idx_tasks_project ON tasks(project_id, status);
```
### Updated Project Configuration Table
```sql
ALTER TABLE project_configuration
ADD COLUMN active_project_id UUID REFERENCES projects(id);
-- Remove now-project-scoped fields
ALTER TABLE project_configuration
DROP COLUMN active_context_type,
DROP COLUMN default_token_budget,
DROP COLUMN current_session_id;
-- Rename for clarity
ALTER TABLE project_configuration
RENAME TO global_configuration;
```
---
## New MCP Tools
### Project Management
#### `create_project`
**Purpose:** Initialize a new project workspace
**Parameters:**
```python
{
"name": str, # Human-readable (e.g., "Commission Extraction")
"slug": Optional[str], # URL-friendly (auto-generated from name if not provided)
"description": Optional[str], # Project overview
"project_type": str, # Classification: commission_extraction, game_dev, etc.
"metadata": Optional[Dict], # Project-specific configuration
"git_remote_url": Optional[str],
"git_default_branch": Optional[str],
"default_token_budget": Optional[int],
"created_by": str = "claude-code"
}
```
**Returns:**
```python
{
"id": "uuid",
"name": "Commission Extraction",
"slug": "commission-extraction",
"project_type": "commission_extraction",
"status": "active",
"created_at": "2025-10-11T15:00:00Z",
"default_token_budget": 200000,
"work_items_count": 0,
"vendors_count": 0
}
```
**Validation:**
- Name must be unique across all projects
- Slug must be URL-safe and unique
- Project type must be from valid enum
- Token budget must be within allowed range
---
#### `list_projects`
**Purpose:** Query available project workspaces
**Parameters:**
```python
{
"status": Optional[str], # 'active', 'archived', 'on_hold'
"project_type": Optional[str], # Filter by classification
"recently_active": bool = False, # Sort by last updated_at
"include_stats": bool = True, # Include work item/vendor counts
"limit": int = 50,
"offset": int = 0
}
```
**Returns:**
```python
{
"projects": [
{
"id": "uuid",
"name": "Commission Extraction",
"slug": "commission-extraction",
"project_type": "commission_extraction",
"status": "active",
"description": "...",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-10-11T15:00:00Z",
# Optional stats
"stats": {
"work_items_count": 47,
"active_sessions": 2,
"vendors_count": 8,
"repositories_count": 3,
"recent_deployments": 5
}
}
],
"total_count": 12,
"active_count": 8,
"archived_count": 4
}
```
---
#### `get_project`
**Purpose:** Retrieve detailed information about a specific project
**Parameters:**
```python
{
"project_id": Optional[UUID], # If None, returns active project
"include_stats": bool = True
}
```
**Returns:**
```python
{
"id": "uuid",
"name": "Commission Extraction",
"slug": "commission-extraction",
"description": "...",
"project_type": "commission_extraction",
"active_context_type": "feature",
"default_token_budget": 200000,
"git_remote_url": "git@github.com:user/repo.git",
"git_default_branch": "main",
"metadata": {...},
"status": "active",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-10-11T15:00:00Z",
"created_by": "claude-code",
"stats": {
"work_items": {
"total": 47,
"by_type": {
"project": 1,
"session": 5,
"task": 38,
"research": 3
},
"by_status": {
"active": 12,
"completed": 32,
"blocked": 3
}
},
"vendors": {
"total": 8,
"operational": 6,
"broken": 2
},
"repositories": {
"total": 3,
"indexed": 2,
"chunks": 1247
},
"deployments": {
"total": 15,
"last_deployed_at": "2025-10-10T12:00:00Z"
}
}
}
```
---
#### `update_project`
**Purpose:** Modify project configuration
**Parameters:**
```python
{
"project_id": UUID,
"name": Optional[str],
"description": Optional[str],
"active_context_type": Optional[str],
"default_token_budget": Optional[int],
"metadata": Optional[Dict], # Merged with existing
"status": Optional[str], # Can archive/activate
"updated_by": str = "claude-code"
}
```
**Returns:** Updated project object (same format as `get_project`)
---
#### `delete_project`
**Purpose:** Permanently remove a project and optionally its data
**Parameters:**
```python
{
"project_id": UUID,
"cascade": bool = False, # If True, deletes all work items, vendors, etc.
"confirm_name": str # Must match project name for safety
}
```
**Returns:**
```python
{
"deleted": true,
"project_id": "uuid",
"project_name": "Old Project",
"cascade_results": {
"work_items_deleted": 47,
"vendors_deleted": 8,
"repositories_deleted": 3,
"deployments_deleted": 15
}
}
```
**Safety Features:**
- Requires exact name match
- Cannot delete active project (must switch first)
- Cascade defaults to False (preserves data)
- Audit log entry created
---
#### `switch_active_project`
**Purpose:** Change current working project context
**Parameters:**
```python
{
"project_id": Optional[UUID], # Can provide ID or slug
"slug": Optional[str]
}
```
**Returns:**
```python
{
"previous_project": {
"id": "uuid-1",
"name": "Commission Extraction"
},
"active_project": {
"id": "uuid-2",
"name": "TTRPG Game System A",
"slug": "ttrpg-game-system-a",
"project_type": "game_dev",
"default_token_budget": 150000
},
"switched_at": "2025-10-11T15:30:00Z"
}
```
**Behavior:**
- All subsequent operations use new active project by default
- Previous context is preserved in global config for quick switching back
- Switching is instantaneous (no data movement)
---
#### `get_active_project`
**Purpose:** Query which project is currently active
**Parameters:** None
**Returns:** Same format as `get_project` for active project
---
### Modified Existing Tools
All existing tools get an optional `project_id` parameter that defaults to the active project:
```python
# Work Items
create_work_item(
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
item_type: str,
title: str,
metadata: Dict,
...
)
list_work_items(
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
status: Optional[str] = None,
...
)
query_work_item(
id: UUID,
project_id: Optional[UUID] = None # 🆕 Optional for cross-project queries
)
# Vendors
query_vendor_status(
name: str,
project_id: Optional[UUID] = None # 🆕 Defaults to active project
)
update_vendor_status(
name: str,
version: int,
status: Optional[str] = None,
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
...
)
# Code Search
index_repository(
repo_path: str,
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
force_reindex: bool = False
)
search_code(
query: str,
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
repository_id: Optional[UUID] = None,
...
)
# Deployments
record_deployment(
deployed_at: datetime,
metadata: Dict,
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
vendor_ids: Optional[List[UUID]] = None,
work_item_ids: Optional[List[UUID]] = None,
...
)
# Tasks (legacy)
create_task(
title: str,
project_id: Optional[UUID] = None, # 🆕 Defaults to active project
description: Optional[str] = None,
...
)
```
**Key Behavior:**
- If `project_id` is None → uses active project
- If `project_id` is specified → overrides for that call
- Active project doesn't change from explicit project_id usage
- Cross-project queries require explicit project_id
---
## Migration Strategy
### Phase 1: Database Schema (Week 1)
**Steps:**
1. Create `projects` table with validation constraints
2. Create default project for existing data
3. Add `project_id` columns to all tables (nullable initially)
4. Populate `project_id` with default project for all existing rows
5. Make `project_id` NOT NULL
6. Update `project_configuration` → `global_configuration`
7. Set default project as active
**Migration Script:**
```sql
-- Create projects table
CREATE TABLE projects (...);
-- Create default project
INSERT INTO projects (id, name, slug, description, project_type, status)
VALUES (
'00000000-0000-0000-0000-000000000001',
'Default Project',
'default-project',
'Legacy data migrated from singleton configuration',
'commission_extraction',
'active'
);
-- Add project_id columns (nullable)
ALTER TABLE work_items ADD COLUMN project_id UUID REFERENCES projects(id);
ALTER TABLE vendors ADD COLUMN project_id UUID REFERENCES projects(id);
-- ... etc for all tables
-- Populate with default project
UPDATE work_items SET project_id = '00000000-0000-0000-0000-000000000001';
UPDATE vendors SET project_id = '00000000-0000-0000-0000-000000000001';
-- ... etc
-- Make NOT NULL
ALTER TABLE work_items ALTER COLUMN project_id SET NOT NULL;
ALTER TABLE vendors ALTER COLUMN project_id SET NOT NULL;
-- ... etc
-- Create indexes
CREATE INDEX idx_work_items_project ON work_items(project_id, status, updated_at DESC);
-- ... etc
-- Update global configuration
ALTER TABLE project_configuration
ADD COLUMN active_project_id UUID REFERENCES projects(id);
UPDATE project_configuration
SET active_project_id = '00000000-0000-0000-0000-000000000001'
WHERE id = 1;
```
---
### Phase 2: MCP Tool Implementation (Week 2)
**Priority Order:**
1. `create_project` (enables creating new workspaces)
2. `list_projects` (enables discovery)
3. `get_project` (enables inspection)
4. `switch_active_project` (enables context switching)
5. `get_active_project` (enables confirmation)
6. `update_project` (enables configuration changes)
7. `delete_project` (enables cleanup)
**Rollout Strategy:**
- Add new tools first
- Update existing tools to respect active project
- Add optional `project_id` parameters
- Maintain backward compatibility (default to active project)
---
### Phase 3: Validation & Testing (Week 3)
**Test Scenarios:**
1. Create multiple projects with different types
2. Switch between projects and verify isolation
3. Create work items in different projects
4. Verify code search respects project boundaries
5. Test vendor name uniqueness within project (not global)
6. Verify cross-project queries work when explicit
7. Test project deletion with cascade
8. Verify archived projects don't appear in default lists
---
## Use Case Examples
### Example 1: Commission Extraction Project
```python
# Create project
project = create_project(
name="Commission Extraction",
slug="commission-extraction",
project_type="commission_extraction",
description="Multi-vendor commission file processing system",
default_token_budget=200000
)
# Switch to it
switch_active_project(slug="commission-extraction")
# All operations now scoped to this project
create_work_item(
item_type="project",
title="Q4 2025 Vendor Integration",
metadata={...}
)
# Vendors are project-scoped
update_vendor_status(
name="EPSON",
status="operational",
version=1
)
# Code search only searches this project's repositories
search_code("invoice extraction patterns")
```
---
### Example 2: TTRPG Game Development
```python
# Create game project
game_project = create_project(
name="TTRPG System - Starbound Adventures",
slug="ttrpg-starbound",
project_type="game_dev",
description="Science fiction roleplaying game system",
default_token_budget=150000
)
# Switch context
switch_active_project(slug="ttrpg-starbound")
# Game-specific work items
create_work_item(
item_type="session",
title="Character Creation System Design",
metadata={
"focus_area": "Core mechanics",
"token_budget": 80000,
"prompts_count": 0,
"yaml_frontmatter": {
"schema_version": "1.0",
"game_system": "starbound"
}
}
)
# Game mechanics as "vendors" (modules)
update_vendor_status(
name="core_mechanics_module",
status="operational",
version=1,
metadata={
"dice_system": "d20",
"skill_count": 12
}
)
# Search game-related code only
search_code("dice rolling mechanics")
```
---
### Example 3: Quick Project Switching
```python
# Morning: Work on commissions
switch_active_project(slug="commission-extraction")
list_work_items(status="active") # Shows commission tasks only
# Afternoon: Switch to game dev
switch_active_project(slug="ttrpg-starbound")
list_work_items(status="active") # Shows game tasks only
# Evening: New analytics project
analytics = create_project(
name="Commission Comparison Dashboard",
slug="commission-comparison",
project_type="analytics"
)
switch_active_project(project_id=analytics["id"])
```
---
### Example 4: Cross-Project Reporting
```python
# Get all projects with blocked work items
all_projects = list_projects(status="active")
for project in all_projects["projects"]:
blocked_items = list_work_items(
project_id=project["id"],
status="blocked"
)
if blocked_items["total_count"] > 0:
print(f"⚠️ {project['name']}: {blocked_items['total_count']} blocked items")
```
---
### Example 5: Project Archival
```python
# Old project completed
old_project = get_project(slug="legacy-system-migration")
# Archive it (keeps data)
update_project(
project_id=old_project["id"],
status="archived"
)
# Or fully delete (careful!)
delete_project(
project_id=old_project["id"],
cascade=True, # Deletes all work items, vendors, etc.
confirm_name="Legacy System Migration"
)
```
---
## Project Type Taxonomy
Suggested project types (extensible):
- **`commission_extraction`** - Vendor file processing systems
- **`commission_comparison`** - Analytics and comparison tools
- **`game_dev`** - TTRPG or game system development
- **`research`** - Exploratory or research projects
- **`client_work`** - Client-specific deliverables
- **`personal`** - Personal projects or experiments
- **`infrastructure`** - DevOps or platform work
- **`documentation`** - Documentation or writing projects
Projects can use custom types as needed.
---
## Configuration Inheritance
Projects can inherit from templates or parent projects:
```python
# Create project from template
create_project(
name="New Game System",
project_type="game_dev",
template="ttrpg-base-template", # 🆕 Optional
metadata={
"inherit_vendors": True,
"inherit_work_item_structure": False
}
)
```
This could copy:
- Vendor configurations
- Work item templates
- Default settings
- Git configuration patterns
---
## Security & Access Control (Future Enhancement)
When team collaboration is added:
```python
# Project-level permissions
{
"project_id": "uuid",
"permissions": {
"user_a": ["read", "write", "admin"],
"user_b": ["read"],
"team_qa": ["read", "comment"]
}
}
```
Current implementation: Single-user, all projects accessible
---
## Performance Considerations
### Index Strategy
```sql
-- Critical indexes for project filtering
CREATE INDEX idx_work_items_project_status ON work_items(project_id, status, updated_at DESC);
CREATE INDEX idx_vendors_project ON vendors(project_id, status);
CREATE INDEX idx_deployments_project_date ON deployment_events(project_id, deployed_at DESC);
-- Partial indexes for active projects only
CREATE INDEX idx_projects_active ON projects(updated_at DESC)
WHERE status = 'active';
```
### Query Optimization
- Always filter by `project_id` first (most selective)
- Use `project_id IN (...)` for cross-project queries
- Partition large tables by project_id if needed (future)
### Caching Strategy
- Cache active project details (updated on switch)
- Cache project stats (updated on work item changes)
- Invalidate on project updates
---
## Error Handling
### Common Error Scenarios
**Project Not Found:**
```json
{
"error": "ProjectNotFound",
"message": "Project with slug 'invalid-project' does not exist",
"available_projects": ["commission-extraction", "ttrpg-starbound"]
}
```
**Cross-Project Reference Error:**
```json
{
"error": "CrossProjectReference",
"message": "Work item belongs to project 'Commission Extraction', cannot link to deployment in 'Game Dev' project",
"work_item_project": "commission-extraction",
"deployment_project": "game-dev"
}
```
**Project Name Collision:**
```json
{
"error": "ProjectExists",
"message": "Project with name 'Commission Extraction' already exists",
"existing_project_id": "uuid",
"suggestion": "Use slug 'commission-extraction-2' or choose different name"
}
```
---
## Backward Compatibility
### For Existing Integrations
**Before Multi-Project:**
```python
create_work_item(item_type="task", title="Fix bug")
```
**After Multi-Project (still works):**
```python
# Uses active project implicitly
create_work_item(item_type="task", title="Fix bug")
# Or explicit
create_work_item(
item_type="task",
title="Fix bug",
project_id="uuid" # Optional override
)
```
**No Breaking Changes:**
- All existing tools work without modification
- Default behavior uses active project
- Explicit project_id is optional
- Legacy single-project mode still supported (default project)
---
## Implementation Checklist
### Database
- [ ] Create `projects` table with constraints
- [ ] Add `project_id` FK to all entity tables
- [ ] Create appropriate indexes
- [ ] Update `project_configuration` → `global_configuration`
- [ ] Write migration script
- [ ] Test migration on copy of production data
### MCP Server
- [ ] Implement `create_project` tool
- [ ] Implement `list_projects` tool
- [ ] Implement `get_project` tool
- [ ] Implement `update_project` tool
- [ ] Implement `delete_project` tool
- [ ] Implement `switch_active_project` tool
- [ ] Implement `get_active_project` tool
- [ ] Update all existing tools to accept `project_id` parameter
- [ ] Update all queries to filter by active project
- [ ] Add project validation to all operations
- [ ] Update error messages with project context
### Testing
- [ ] Unit tests for each new tool
- [ ] Integration tests for project switching
- [ ] Test project isolation (ensure no cross-contamination)
- [ ] Test migration script
- [ ] Performance testing with multiple projects
- [ ] Test cascade delete
- [ ] Test project archival
### Documentation
- [ ] Update MCP tool documentation
- [ ] Add project management guide
- [ ] Document migration process
- [ ] Add examples for common workflows
- [ ] Update API reference
---
## Success Metrics
**Functional:**
- ✅ Can create unlimited projects
- ✅ Projects are fully isolated
- ✅ Switching takes <50ms
- ✅ No cross-project data leakage
- ✅ All existing tools respect project boundaries
**Performance:**
- ✅ Project creation <200ms
- ✅ Project switching <50ms
- ✅ List projects <100ms (up to 1000 projects)
- ✅ No performance degradation on existing operations
**Usability:**
- ✅ Clear visual indication of active project
- ✅ Easy to switch between common projects
- ✅ Difficult to accidentally delete projects
- ✅ Clear error messages for cross-project issues
---
## Future Enhancements
### Phase 4: Project Templates
- Pre-configured project structures
- Vendor template libraries
- Work item hierarchies
- Standard workflows
### Phase 5: Project Analytics
- Cross-project time tracking
- Portfolio-level reporting
- Resource allocation analysis
- Project health dashboards
### Phase 6: Team Collaboration
- Multi-user access
- Project sharing
- Role-based permissions
- Activity feeds
### Phase 7: Project Dependencies
- Link related projects
- Cross-project work item references
- Dependency tracking
- Impact analysis
---
## Open Questions
1. **Default Project Behavior:** Should new installs create a default project automatically?
- Recommendation: Yes, create "Default Project" on first run
2. **Maximum Projects:** Should there be a limit?
- Recommendation: No hard limit, but warn after 50 projects
3. **Project Deletion:** Should archived projects be auto-deleted after X days?
- Recommendation: No auto-deletion, manual only
4. **Cross-Project Work Items:** Should we support work items spanning projects?
- Recommendation: No, keep strict isolation. Use project dependencies instead
5. **Vendor Name Conflicts:** If two projects have "EPSON" vendor, should they share status?
- Recommendation: No, completely independent. "EPSON" in Project A is different from "EPSON" in Project B
---
## Conclusion
The Project Workspace Pattern transforms the MCP server from a single-project tool into a portfolio management system. It maintains backward compatibility while enabling unlimited project organization with strong isolation guarantees. The implementation is straightforward (3-4 weeks), the benefits are immediate (context switching, organization), and the foundation is solid for future team collaboration features.
**Recommendation: HIGH PRIORITY** - This is foundational for real-world multi-project usage.