mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-01-30 06:12:06 +00:00
feat: add project reset functionality with quick and full reset options
Add the ability to reset a project to its initial state with two options:
- Quick Reset: Clears features.db, assistant.db, and settings files while
preserving app spec and prompts
- Full Reset: Deletes everything including prompts directory, triggering
the setup wizard for project reconfiguration
Backend changes:
- Add POST /{name}/reset endpoint to projects router with full_reset query param
- Validate agent lock file to prevent reset while agent is running (409 Conflict)
- Dispose database engines before deleting files to release Windows file locks
- Add engine caching to api/database.py for better connection management
- Add dispose_engine() functions to both database modules
- Delete WAL mode journal files (*.db-wal, *.db-shm) during reset
Frontend changes:
- Add ResetProjectModal component with toggle between Quick/Full reset modes
- Add ProjectSetupRequired component shown when has_spec is false
- Add resetProject API function and useResetProject React Query hook
- Integrate reset button in header (disabled when agent running)
- Add 'R' keyboard shortcut to open reset modal
- Show ProjectSetupRequired when project needs setup after full reset
This implements the feature from PR #4 directly on master to avoid merge
conflicts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -373,6 +373,87 @@ async def get_project_stats_endpoint(name: str):
|
||||
return get_project_stats(project_dir)
|
||||
|
||||
|
||||
@router.post("/{name}/reset")
|
||||
async def reset_project(name: str, full_reset: bool = False):
|
||||
"""
|
||||
Reset a project to its initial state.
|
||||
|
||||
Args:
|
||||
name: Project name to reset
|
||||
full_reset: If True, also delete prompts/ directory (triggers setup wizard)
|
||||
|
||||
Returns:
|
||||
Dictionary with list of deleted files and reset type
|
||||
"""
|
||||
_init_imports()
|
||||
(_, _, get_project_path, _, _, _, _) = _get_registry_functions()
|
||||
|
||||
name = validate_project_name(name)
|
||||
project_dir = get_project_path(name)
|
||||
|
||||
if not project_dir:
|
||||
raise HTTPException(status_code=404, detail=f"Project '{name}' not found")
|
||||
|
||||
if not project_dir.exists():
|
||||
raise HTTPException(status_code=404, detail="Project directory not found")
|
||||
|
||||
# Check if agent is running
|
||||
lock_file = project_dir / ".agent.lock"
|
||||
if lock_file.exists():
|
||||
raise HTTPException(
|
||||
status_code=409,
|
||||
detail="Cannot reset project while agent is running. Stop the agent first."
|
||||
)
|
||||
|
||||
# Dispose of database engines to release file locks (required on Windows)
|
||||
# Import here to avoid circular imports
|
||||
from api.database import dispose_engine as dispose_features_engine
|
||||
from server.services.assistant_database import dispose_engine as dispose_assistant_engine
|
||||
|
||||
dispose_features_engine(project_dir)
|
||||
dispose_assistant_engine(project_dir)
|
||||
|
||||
deleted_files: list[str] = []
|
||||
|
||||
# Files to delete in quick reset
|
||||
quick_reset_files = [
|
||||
"features.db",
|
||||
"features.db-wal", # WAL mode journal file
|
||||
"features.db-shm", # WAL mode shared memory file
|
||||
"assistant.db",
|
||||
"assistant.db-wal",
|
||||
"assistant.db-shm",
|
||||
".claude_settings.json",
|
||||
".claude_assistant_settings.json",
|
||||
]
|
||||
|
||||
for filename in quick_reset_files:
|
||||
file_path = project_dir / filename
|
||||
if file_path.exists():
|
||||
try:
|
||||
file_path.unlink()
|
||||
deleted_files.append(filename)
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to delete {filename}: {e}")
|
||||
|
||||
# Full reset: also delete prompts directory
|
||||
if full_reset:
|
||||
prompts_dir = project_dir / "prompts"
|
||||
if prompts_dir.exists():
|
||||
try:
|
||||
shutil.rmtree(prompts_dir)
|
||||
deleted_files.append("prompts/")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"Failed to delete prompts/: {e}")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"reset_type": "full" if full_reset else "quick",
|
||||
"deleted_files": deleted_files,
|
||||
"message": f"Project '{name}' has been reset" + (" (full reset)" if full_reset else " (quick reset)")
|
||||
}
|
||||
|
||||
|
||||
@router.patch("/{name}/settings", response_model=ProjectDetail)
|
||||
async def update_project_settings(name: str, settings: ProjectSettingsUpdate):
|
||||
"""Update project-level settings (concurrency, etc.)."""
|
||||
|
||||
@@ -79,6 +79,26 @@ def get_engine(project_dir: Path):
|
||||
return _engine_cache[cache_key]
|
||||
|
||||
|
||||
def dispose_engine(project_dir: Path) -> bool:
|
||||
"""Dispose of and remove the cached engine for a project.
|
||||
|
||||
This closes all database connections, releasing file locks on Windows.
|
||||
Should be called before deleting the database file.
|
||||
|
||||
Returns:
|
||||
True if an engine was disposed, False if no engine was cached.
|
||||
"""
|
||||
cache_key = project_dir.as_posix()
|
||||
|
||||
if cache_key in _engine_cache:
|
||||
engine = _engine_cache.pop(cache_key)
|
||||
engine.dispose()
|
||||
logger.debug(f"Disposed database engine for {cache_key}")
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_session(project_dir: Path):
|
||||
"""Get a new database session for a project."""
|
||||
engine = get_engine(project_dir)
|
||||
|
||||
Reference in New Issue
Block a user