This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
"""Workspace directory management for project isolation."""
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import shutil
|
||||
|
||||
|
||||
@dataclass
|
||||
class Workspace:
|
||||
"""Represents a project workspace with defined directory structure.
|
||||
|
||||
Each project has its own isolated workspace directory following the layout:
|
||||
~/gsd-workspaces/{project}/
|
||||
├── .gsd/
|
||||
│ ├── state.json # Session state
|
||||
│ ├── state.json.bak # State backup
|
||||
│ └── session-dump/ # Session dump
|
||||
├── code/ # Project code (optional)
|
||||
└── io-logs/
|
||||
└── {session-id}.log # I/O logs
|
||||
"""
|
||||
|
||||
project_name: str
|
||||
root: Optional[Path] = None
|
||||
|
||||
def __post_init__(self):
|
||||
"""Compute root path if not provided."""
|
||||
if self.root is None:
|
||||
self.root = Path.home() / "gsd-workspaces" / self.project_name
|
||||
elif isinstance(self.root, str):
|
||||
self.root = Path(self.root)
|
||||
|
||||
@property
|
||||
def gsd_dir(self) -> Path:
|
||||
"""Path to .gsd directory containing state files."""
|
||||
return self.root / ".gsd"
|
||||
|
||||
@property
|
||||
def state_path(self) -> Path:
|
||||
"""Path to state.json file."""
|
||||
return self.gsd_dir / "state.json"
|
||||
|
||||
@property
|
||||
def state_backup_path(self) -> Path:
|
||||
"""Path to state.json.bak file."""
|
||||
return self.gsd_dir / "state.json.bak"
|
||||
|
||||
@property
|
||||
def session_dump_dir(self) -> Path:
|
||||
"""Path to session-dump directory."""
|
||||
return self.gsd_dir / "session-dump"
|
||||
|
||||
@property
|
||||
def code_dir(self) -> Path:
|
||||
"""Path to code directory."""
|
||||
return self.root / "code"
|
||||
|
||||
@property
|
||||
def io_logs_dir(self) -> Path:
|
||||
"""Path to io-logs directory."""
|
||||
return self.root / "io-logs"
|
||||
|
||||
def io_log_path(self, session_id: str) -> Path:
|
||||
"""Get path to I/O log file for a specific session.
|
||||
|
||||
Args:
|
||||
session_id: The session identifier.
|
||||
|
||||
Returns:
|
||||
Path to the session's I/O log file.
|
||||
"""
|
||||
return self.io_logs_dir / f"{session_id}.log"
|
||||
|
||||
|
||||
class WorkspaceManager:
|
||||
"""Manages workspace creation, destruction, and lifecycle.
|
||||
|
||||
The WorkspaceManager handles:
|
||||
- Creating workspace directory structure
|
||||
- Destroying workspaces (cleanup)
|
||||
- Checking workspace existence
|
||||
- Providing workspace objects
|
||||
"""
|
||||
|
||||
WORKSPACES_ROOT = Path.home() / "gsd-workspaces"
|
||||
|
||||
def __init__(self, project_name: str, root: Optional[Path] = None):
|
||||
"""Initialize workspace manager.
|
||||
|
||||
Args:
|
||||
project_name: Name of the project (used for workspace directory).
|
||||
root: Optional custom root path for workspaces (useful for testing).
|
||||
"""
|
||||
self.project_name = project_name
|
||||
self.root = root or self.WORKSPACES_ROOT
|
||||
if isinstance(self.root, str):
|
||||
self.root = Path(self.root)
|
||||
self.workspace = Workspace(project_name, self.root / project_name)
|
||||
|
||||
def create(self) -> Workspace:
|
||||
"""Create workspace directory structure.
|
||||
|
||||
Creates all required directories:
|
||||
- .gsd/ (with session-dump/ subdirectory)
|
||||
- code/
|
||||
- io-logs/
|
||||
|
||||
This operation is idempotent - calling multiple times will not fail.
|
||||
|
||||
Returns:
|
||||
The Workspace object with paths configured.
|
||||
"""
|
||||
# Create all directories
|
||||
self.workspace.gsd_dir.mkdir(parents=True, exist_ok=True)
|
||||
self.workspace.session_dump_dir.mkdir(exist_ok=True)
|
||||
self.workspace.code_dir.mkdir(exist_ok=True)
|
||||
self.workspace.io_logs_dir.mkdir(exist_ok=True)
|
||||
return self.workspace
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Remove entire workspace directory.
|
||||
|
||||
Completely removes the workspace and all its contents.
|
||||
Safe to call even if workspace doesn't exist.
|
||||
"""
|
||||
if self.workspace.root.exists():
|
||||
shutil.rmtree(self.workspace.root)
|
||||
|
||||
def exists(self) -> bool:
|
||||
"""Check if workspace exists.
|
||||
|
||||
Returns:
|
||||
True if workspace root directory exists, False otherwise.
|
||||
"""
|
||||
return self.workspace.root.exists()
|
||||
|
||||
def get_workspace(self) -> Workspace:
|
||||
"""Get workspace object (does not create).
|
||||
|
||||
Returns:
|
||||
The Workspace object with paths configured.
|
||||
"""
|
||||
return self.workspace
|
||||
Reference in New Issue
Block a user